Skip to content

Commit 46b59c6

Browse files
committed
adds stats to ocsql
1 parent cdb861a commit 46b59c6

File tree

3 files changed

+253
-3
lines changed

3 files changed

+253
-3
lines changed

dbstats_go1.11.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// +build go1.11
2+
3+
package ocsql
4+
5+
import (
6+
"context"
7+
"database/sql"
8+
"time"
9+
10+
"go.opencensus.io/stats"
11+
)
12+
13+
// RecordStats records database statistics for provided sql.DB at the provided
14+
// interval.
15+
func RecordStats(db *sql.DB, interval time.Duration) (fnStop func()) {
16+
var (
17+
ctx = context.Background()
18+
ticker = time.NewTicker(interval)
19+
done = make(chan struct{})
20+
)
21+
22+
go func() {
23+
for {
24+
select {
25+
case <-ticker.C:
26+
dbStats := db.Stats()
27+
stats.Record(ctx,
28+
MeasureOpenConnections.M(int64(dbStats.OpenConnections)),
29+
MeasureIdleConnections.M(int64(dbStats.Idle)),
30+
MeasureActiveConnections.M(int64(dbStats.InUse)),
31+
MeasureWaitCount.M(dbStats.WaitCount),
32+
MeasureWaitDuration.M(float64(dbStats.WaitDuration.Nanoseconds())/1e6),
33+
MeasureIdleClosed.M(dbStats.MaxIdleClosed),
34+
MeasureLifetimeClosed.M(dbStats.MaxLifetimeClosed),
35+
)
36+
case <-done:
37+
ticker.Stop()
38+
return
39+
}
40+
}
41+
}()
42+
43+
return func() {
44+
close(done)
45+
}
46+
}

driver.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ type ocConn struct {
123123
}
124124

125125
func (c ocConn) Ping(ctx context.Context) (err error) {
126+
defer recordCallStats(ctx, "go.sql.ping")(err)
127+
126128
if c.options.Ping && (c.options.AllowRoot || trace.FromContext(ctx) != nil) {
127129
var span *trace.Span
128130
ctx, span = trace.StartSpan(ctx, "sql:ping", trace.WithSpanKind(trace.SpanKindClient))
@@ -149,6 +151,8 @@ func (c ocConn) Ping(ctx context.Context) (err error) {
149151
}
150152

151153
func (c ocConn) Exec(query string, args []driver.Value) (res driver.Result, err error) {
154+
defer recordCallStats(context.Background(), "go.sql.exec")(err)
155+
152156
if exec, ok := c.parent.(driver.Execer); ok {
153157
if !c.options.AllowRoot {
154158
return exec.Exec(query, args)
@@ -188,6 +192,8 @@ func (c ocConn) Exec(query string, args []driver.Value) (res driver.Result, err
188192
}
189193

190194
func (c ocConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (res driver.Result, err error) {
195+
defer recordCallStats(ctx, "go.sql.exec")(err)
196+
191197
if execCtx, ok := c.parent.(driver.ExecerContext); ok {
192198
parentSpan := trace.FromContext(ctx)
193199
if !c.options.AllowRoot && parentSpan == nil {
@@ -225,6 +231,8 @@ func (c ocConn) ExecContext(ctx context.Context, query string, args []driver.Nam
225231
}
226232

227233
func (c ocConn) Query(query string, args []driver.Value) (rows driver.Rows, err error) {
234+
defer recordCallStats(context.Background(), "go.sql.query")(err)
235+
228236
if queryer, ok := c.parent.(driver.Queryer); ok {
229237
if !c.options.AllowRoot {
230238
return queryer.Query(query, args)
@@ -265,6 +273,8 @@ func (c ocConn) Query(query string, args []driver.Value) (rows driver.Rows, err
265273
}
266274

267275
func (c ocConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) {
276+
defer recordCallStats(ctx, "go.sql.query")(err)
277+
268278
if queryerCtx, ok := c.parent.(driver.QueryerContext); ok {
269279
parentSpan := trace.FromContext(ctx)
270280
if !c.options.AllowRoot && parentSpan == nil {
@@ -303,6 +313,8 @@ func (c ocConn) QueryContext(ctx context.Context, query string, args []driver.Na
303313
}
304314

305315
func (c ocConn) Prepare(query string) (stmt driver.Stmt, err error) {
316+
defer recordCallStats(context.Background(), "go.sql.prepare")(err)
317+
306318
if c.options.AllowRoot {
307319
_, span := trace.StartSpan(context.Background(), "sql:prepare", trace.WithSpanKind(trace.SpanKindClient))
308320
attrs := make([]trace.Attribute, 0, len(c.options.DefaultAttributes)+1)
@@ -337,6 +349,8 @@ func (c *ocConn) Begin() (driver.Tx, error) {
337349
}
338350

339351
func (c *ocConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) {
352+
defer recordCallStats(ctx, "go.sql.prepare")(err)
353+
340354
var span *trace.Span
341355
attrs := append([]trace.Attribute(nil), c.options.DefaultAttributes...)
342356
if c.options.AllowRoot || trace.FromContext(ctx) != nil {
@@ -367,7 +381,9 @@ func (c *ocConn) PrepareContext(ctx context.Context, query string) (stmt driver.
367381
return
368382
}
369383

370-
func (c *ocConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
384+
func (c *ocConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx driver.Tx, err error) {
385+
defer recordCallStats(ctx, "go.sql.begin")(err)
386+
371387
if !c.options.AllowRoot && trace.FromContext(ctx) == nil {
372388
if connBeginTx, ok := c.parent.(driver.ConnBeginTx); ok {
373389
return connBeginTx.BeginTx(ctx, opts)
@@ -392,7 +408,7 @@ func (c *ocConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx,
392408
defer span.End()
393409

394410
if connBeginTx, ok := c.parent.(driver.ConnBeginTx); ok {
395-
tx, err := connBeginTx.BeginTx(ctx, opts)
411+
tx, err = connBeginTx.BeginTx(ctx, opts)
396412
setSpanStatus(span, err)
397413
if err != nil {
398414
return nil, err
@@ -407,7 +423,7 @@ func (c *ocConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx,
407423
"ocsql.deprecated", "driver does not support ConnBeginTx",
408424
),
409425
)
410-
tx, err := c.parent.Begin()
426+
tx, err = c.parent.Begin()
411427
setSpanStatus(span, err)
412428
if err != nil {
413429
return nil, err
@@ -462,6 +478,8 @@ type ocStmt struct {
462478
}
463479

464480
func (s ocStmt) Exec(args []driver.Value) (res driver.Result, err error) {
481+
defer recordCallStats(context.Background(), "go.sql.stmt.exec")(err)
482+
465483
if !s.options.AllowRoot {
466484
return s.parent.Exec(args)
467485
}
@@ -507,6 +525,8 @@ func (s ocStmt) NumInput() int {
507525
}
508526

509527
func (s ocStmt) Query(args []driver.Value) (rows driver.Rows, err error) {
528+
defer recordCallStats(context.Background(), "go.sql.stmt.query")(err)
529+
510530
if !s.options.AllowRoot {
511531
return s.parent.Query(args)
512532
}
@@ -543,6 +563,8 @@ func (s ocStmt) Query(args []driver.Value) (rows driver.Rows, err error) {
543563
}
544564

545565
func (s ocStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) {
566+
defer recordCallStats(ctx, "go.sql.stmt.exec")(err)
567+
546568
parentSpan := trace.FromContext(ctx)
547569
if !s.options.AllowRoot && parentSpan == nil {
548570
// we already tested driver to implement StmtExecContext
@@ -580,6 +602,8 @@ func (s ocStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res
580602
}
581603

582604
func (s ocStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) {
605+
defer recordCallStats(ctx, "go.sql.stmt.query")(err)
606+
583607
parentSpan := trace.FromContext(ctx)
584608
if !s.options.AllowRoot && parentSpan == nil {
585609
// we already tested driver to implement StmtQueryContext
@@ -775,6 +799,8 @@ type ocTx struct {
775799
}
776800

777801
func (t ocTx) Commit() (err error) {
802+
defer recordCallStats(context.Background(), "go.sql.commit")(err)
803+
778804
_, span := trace.StartSpan(t.ctx, "sql:commit", trace.WithSpanKind(trace.SpanKindClient))
779805
if len(t.options.DefaultAttributes) > 0 {
780806
span.AddAttributes(t.options.DefaultAttributes...)
@@ -789,6 +815,8 @@ func (t ocTx) Commit() (err error) {
789815
}
790816

791817
func (t ocTx) Rollback() (err error) {
818+
defer recordCallStats(context.Background(), "go.sql.rollback")(err)
819+
792820
_, span := trace.StartSpan(t.ctx, "sql:rollback", trace.WithSpanKind(trace.SpanKindClient))
793821
if len(t.options.DefaultAttributes) > 0 {
794822
span.AddAttributes(t.options.DefaultAttributes...)

observability.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package ocsql
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"go.opencensus.io/stats"
8+
"go.opencensus.io/stats/view"
9+
"go.opencensus.io/tag"
10+
)
11+
12+
// The following tags are applied to stats recorded by this package.
13+
var (
14+
// GoSqlMethod is the SQL method called.
15+
GoSqlMethod, _ = tag.NewKey("go_sql_method")
16+
// GoSqlError is the error received while calling a SQL method.
17+
GoSqlError, _ = tag.NewKey("go_sql_error")
18+
// GoSqlStatus identifies success vs. error from the SQL method response.
19+
GoSqlStatus, _ = tag.NewKey("go_sql_status")
20+
21+
valueOK = tag.Insert(GoSqlStatus, "OK")
22+
valueErr = tag.Insert(GoSqlStatus, "ERROR")
23+
)
24+
25+
// The following measures are supported for use in custom views.
26+
var (
27+
MeasureLatencyMs = stats.Float64("go.sql/latency", "The latency of calls in milliseconds", stats.UnitMilliseconds)
28+
MeasureOpenConnections = stats.Int64("go.sql/connections/open", "Count of open connections in the pool", stats.UnitDimensionless)
29+
MeasureIdleConnections = stats.Int64("go.sql/connections/idle", "Count of idle connections in the pool", stats.UnitDimensionless)
30+
MeasureActiveConnections = stats.Int64("go.sql/connections/active", "Count of active connections in the pool", stats.UnitDimensionless)
31+
MeasureWaitCount = stats.Int64("go.sql/connections/wait_count", "The total number of connections waited for", stats.UnitDimensionless)
32+
MeasureWaitDuration = stats.Float64("go.sql/connections/wait_duration", "The total time blocked waiting for a new connection", stats.UnitMilliseconds)
33+
MeasureIdleClosed = stats.Int64("go.sql/connections/idle_closed", "The total number of connections closed due to SetMaxIdleConns", stats.UnitDimensionless)
34+
MeasureLifetimeClosed = stats.Int64("go.sql/connections/lifetime_closed", "The total number of connections closed due to SetConnMaxLifetime", stats.UnitDimensionless)
35+
)
36+
37+
// Default distributions used by views in this package
38+
var (
39+
DefaultMillisecondsDistribution = view.Distribution(
40+
0.0,
41+
0.001,
42+
0.005,
43+
0.01,
44+
0.05,
45+
0.1,
46+
0.5,
47+
1.0,
48+
1.5,
49+
2.0,
50+
2.5,
51+
5.0,
52+
10.0,
53+
25.0,
54+
50.0,
55+
100.0,
56+
200.0,
57+
400.0,
58+
600.0,
59+
800.0,
60+
1000.0,
61+
1500.0,
62+
2000.0,
63+
2500.0,
64+
5000.0,
65+
10000.0,
66+
20000.0,
67+
40000.0,
68+
100000.0,
69+
200000.0,
70+
500000.0)
71+
)
72+
73+
// Package ocsql provides some convenience views.
74+
// You still need to register these views for data to actually be collected.
75+
// You can use the RegisterAllViews function for this.
76+
var (
77+
SqlClientLatencyView = &view.View{
78+
Name: "go.sql/client/latency",
79+
Description: "The distribution of latencies of various calls in milliseconds",
80+
Measure: MeasureLatencyMs,
81+
Aggregation: DefaultMillisecondsDistribution,
82+
TagKeys: []tag.Key{GoSqlMethod, GoSqlError, GoSqlStatus},
83+
}
84+
85+
SqlClientCallsView = &view.View{
86+
Name: "go.sql/client/calls",
87+
Description: "The number of various calls of methods",
88+
Measure: MeasureLatencyMs,
89+
Aggregation: view.Count(),
90+
TagKeys: []tag.Key{GoSqlMethod, GoSqlError, GoSqlStatus},
91+
}
92+
93+
SqlClientOpenConnectionsView = &view.View{
94+
Name: "go.sql/db/connections/open",
95+
Description: "The number of open connections",
96+
Measure: MeasureOpenConnections,
97+
Aggregation: view.LastValue(),
98+
}
99+
100+
SqlClientIdleConnectionsView = &view.View{
101+
Name: "go.sql/db/connections/idle",
102+
Description: "The number of idle connections",
103+
Measure: MeasureIdleConnections,
104+
Aggregation: view.LastValue(),
105+
}
106+
107+
SqlClientActiveConnectionsView = &view.View{
108+
Name: "go.sql/db/connections/active",
109+
Description: "The number of active connections",
110+
Measure: MeasureActiveConnections,
111+
Aggregation: view.LastValue(),
112+
}
113+
114+
SqlClientWaitCountView = &view.View{
115+
Name: "go.sql/db/connections/wait_count",
116+
Description: "The total number of connections waited for",
117+
Measure: MeasureWaitCount,
118+
Aggregation: view.LastValue(),
119+
}
120+
121+
SqlClientWaitDurationView = &view.View{
122+
Name: "go.sql/db/connections/wait_duration",
123+
Description: "The total time blocked waiting for a new connection",
124+
Measure: MeasureWaitDuration,
125+
Aggregation: view.LastValue(),
126+
}
127+
128+
SqlClientIdleClosedView = &view.View{
129+
Name: "go.sql/db/connections/idle_closed_count",
130+
Description: "The total number of connections closed due to SetMaxIdleConns",
131+
Measure: MeasureIdleClosed,
132+
Aggregation: view.LastValue(),
133+
}
134+
135+
SqlClientLifetimeClosedView = &view.View{
136+
Name: "go.sql/db/connections/lifetime_closed_count",
137+
Description: "The total number of connections closed due to SetConnMaxLifetime",
138+
Measure: MeasureLifetimeClosed,
139+
Aggregation: view.LastValue(),
140+
}
141+
142+
DefaultViews = []*view.View{
143+
SqlClientLatencyView, SqlClientCallsView, SqlClientOpenConnectionsView,
144+
SqlClientIdleConnectionsView, SqlClientActiveConnectionsView,
145+
SqlClientWaitCountView, SqlClientWaitDurationView,
146+
SqlClientIdleClosedView, SqlClientLifetimeClosedView,
147+
}
148+
)
149+
150+
// RegisterAllViews registers all ocsql views to enable collection of stats.
151+
func RegisterAllViews() {
152+
if err := view.Register(DefaultViews...); err != nil {
153+
panic(err)
154+
}
155+
}
156+
157+
func recordCallStats(ctx context.Context, method string) func(err error) {
158+
var tags []tag.Mutator
159+
startTime := time.Now()
160+
161+
return func(err error) {
162+
timeSpent := float64(time.Since(startTime).Nanoseconds()) / 1e6
163+
164+
if err != nil {
165+
tags = []tag.Mutator{
166+
tag.Insert(GoSqlMethod, method), valueErr, tag.Insert(GoSqlError, err.Error()),
167+
}
168+
} else {
169+
tags = []tag.Mutator{
170+
tag.Insert(GoSqlMethod, method), valueOK,
171+
}
172+
}
173+
174+
_ = stats.RecordWithTags(ctx, tags, MeasureLatencyMs.M(timeSpent))
175+
}
176+
}

0 commit comments

Comments
 (0)