Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

contrib/database/sql: Submit DBStats as Datadog metrics #2543

Merged
merged 46 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6eafc80
Implementation 1: Run goroutine for polling metrics from within contr…
mtoffl01 Feb 7, 2024
8ed75ef
nits: Added public fn definition and cleaned up if/else statement
mtoffl01 Feb 7, 2024
11cd3a1
database/sql & tracer: implemented channel of comms between contrib a…
mtoffl01 Feb 8, 2024
f92d158
nits from github bot
mtoffl01 Feb 8, 2024
9bb05ba
foundation for sql_test tests
mtoffl01 Feb 8, 2024
df2f0b2
update other calls to reportContribMetrics now that the fn accepts ju…
mtoffl01 Feb 8, 2024
4f64c73
Implementing new type StatsCarrier to share statsd client between con…
mtoffl01 Feb 12, 2024
d1a72bf
fix panic on attempt to close a closed channel on statscarrier
mtoffl01 Feb 12, 2024
57b83b8
removed superfluous log test
mtoffl01 Feb 12, 2024
0296193
Update contrib/database/sql/sql.go
mtoffl01 Feb 13, 2024
38b969b
Fleshing out tests and making changes as a result of tests
mtoffl01 Feb 15, 2024
321e2a7
Changed Stat implementation to interface, with gauge count and timing…
mtoffl01 Feb 15, 2024
015bcfc
go fmt'd
mtoffl01 Feb 16, 2024
8d3a473
Merge branch 'main' into mtoff/sql_metrics
mtoffl01 Feb 16, 2024
1b1a612
Moved metrics collection out of sql.go file and into its own metrics …
mtoffl01 Feb 16, 2024
ad442c4
Remove TestContribStats from metrics_test.go
mtoffl01 Feb 16, 2024
041f64d
added lock to globalconfig's statscarrier to protect against data race
mtoffl01 Feb 20, 2024
55501ea
fix locks on globalconfig & change pollDBStats function order
mtoffl01 Feb 21, 2024
8ae57ad
Added new TestDBStats fn
mtoffl01 Feb 21, 2024
e361c8d
Fixed TestWithDBStats tests
mtoffl01 Feb 21, 2024
59f5192
Merge branch 'main' into mtoff/sql_metrics
mtoffl01 Feb 21, 2024
9aa52a1
Adding test for WithDBStats
mtoffl01 Feb 21, 2024
82c9107
Added documentation to WithDBStats about Open function v Register
mtoffl01 Feb 21, 2024
494dd35
Add definition to public types/functions and change tests slightly
mtoffl01 Feb 21, 2024
58cedac
Expanded godoc comments, examples and fixed bug in dbstats feature wh…
mtoffl01 Feb 22, 2024
fc7c049
Add last todos and tags
mtoffl01 Feb 22, 2024
7c800b7
gofmt
mtoffl01 Feb 22, 2024
098595d
Made db polling interval nonconfigurable, and fixed lock issue in sta…
mtoffl01 Feb 22, 2024
6541d9d
go fmt
mtoffl01 Feb 22, 2024
441d233
Merge branch 'main' into mtoff/sql_metrics
mtoffl01 Feb 23, 2024
92547d3
Fixed flaky tests
mtoffl01 Feb 23, 2024
0becfbd
merge with remote branch
mtoffl01 Feb 23, 2024
50daa34
Remove slices dependency
mtoffl01 Feb 23, 2024
41cac05
Update example_test.go
mtoffl01 Feb 26, 2024
9a85b14
Update metrics.go
mtoffl01 Feb 26, 2024
77cfd1e
Update globalconfig.go
mtoffl01 Feb 26, 2024
0e8dbc2
Update statsd.go
mtoffl01 Feb 26, 2024
2e356e9
change all stats to gauge
mtoffl01 Feb 26, 2024
c6ed0ad
Update example_test.go
mtoffl01 Feb 26, 2024
8ebc177
Merge branch 'mtoff/sql_metrics' of github.com:DataDog/dd-trace-go in…
mtoffl01 Feb 26, 2024
b7eaf30
Reduce pollDBStats interval for testing purposes
mtoffl01 Feb 26, 2024
3cdae9f
use t.Cleanup in globalconfig_test.go
mtoffl01 Feb 26, 2024
df8d794
rework TestOpenOptions in sql_test.go
katiehockman Feb 27, 2024
f016742
Merge branch 'main' into mtoff/sql_metrics
katiehockman Feb 27, 2024
9523300
reworked TestOpenOptions logic
mtoffl01 Feb 27, 2024
834354b
Merge branch 'main' into mtoff/sql_metrics
katiehockman Feb 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions contrib/database/sql/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"reflect"
"strings"
"time"

"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
"gopkg.in/DataDog/dd-trace-go.v1/internal"
Expand All @@ -29,6 +30,7 @@ type config struct {
errCheck func(err error) bool
tags map[string]interface{}
dbmPropagationMode tracer.DBMPropagationMode
dbStats time.Duration
}

func (c *config) checkDBMPropagation(driverName string, driver driver.Driver, dsn string) {
Expand Down Expand Up @@ -262,3 +264,13 @@ func WithDBMPropagation(mode tracer.DBMPropagationMode) Option {
cfg.dbmPropagationMode = mode
}
}

// WithDBStats enables polling of DBStats metrics on the registered DB at the specified interval
// the interval must be non-zero to enable the feature
// ref: https://pkg.go.dev/database/sql#DBStats
// These metrics are submitted to Datadog and are not billed as custom metrics
katiehockman marked this conversation as resolved.
Show resolved Hide resolved
func WithDBStats(interval time.Duration) Option {
return func(cfg *config) {
cfg.dbStats = interval
}
}
25 changes: 25 additions & 0 deletions contrib/database/sql/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package sql

import (
"testing"
"time"

"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"

Expand Down Expand Up @@ -43,3 +44,27 @@ func TestAnalyticsSettings(t *testing.T) {
assert.Equal(t, 0.2, cfg.analyticsRate)
})
}

func TestWithDBStats(t *testing.T) {
t.Run("default off", func(t *testing.T) {
cfg := new(config)
defaults(cfg, "", nil)
assert.True(t, int64(cfg.dbStats) == 0)
assert.False(t, dbStatsEnabled(cfg))
})
t.Run("on", func(t *testing.T) {
cfg := new(config)
defaults(cfg, "", nil)
WithDBStats(1 * time.Second)
assert.True(t, int64(cfg.dbStats) == 1)
assert.True(t, dbStatsEnabled(cfg))
})
t.Run("interval 0", func(t *testing.T) {
// this test demonstrates that the logic for checking whether DBStats is enabled, is for the interval to be > 0
cfg := new(config)
defaults(cfg, "", nil)
WithDBStats(0 * time.Second)
assert.True(t, int64(cfg.dbStats) == 0)
assert.False(t, dbStatsEnabled(cfg))
})
}
36 changes: 33 additions & 3 deletions contrib/database/sql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import (
"sync"
"time"

"gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/internal"
sqlinternal "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/internal"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
"gopkg.in/DataDog/dd-trace-go.v1/internal"
"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry"
)
Expand Down Expand Up @@ -154,7 +156,7 @@ func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) {
cfg: t.cfg,
}
if dsn != "" {
tp.meta, _ = internal.ParseDSN(t.driverName, dsn)
tp.meta, _ = sqlinternal.ParseDSN(t.driverName, dsn)
}
start := time.Now()
ctx, end := startTraceTask(ctx, string(QueryTypeConnect))
Expand Down Expand Up @@ -209,7 +211,15 @@ func OpenDB(c driver.Connector, opts ...Option) *sql.DB {
driverName: driverName,
cfg: cfg,
}
return sql.OpenDB(tc)
db := sql.OpenDB(tc)
if dbStatsEnabled(cfg) {
go pollDBStats(cfg.dbStats, db, pushDBStats)
}
return db
}

func dbStatsEnabled(cfg *config) bool {
return int64(cfg.dbStats) > 0
}

// Open returns connection to a DB using the traced version of the given driver. The driver may
Expand Down Expand Up @@ -250,3 +260,23 @@ func processOptions(cfg *config, driverName string, driver driver.Driver, dsn st
}
cfg.checkDBMPropagation(driverName, driver, dsn)
}

// pollDBStats calls (*DB).Stats on the db, at the specified interval. It pushes the DBStat off to the pushFn.
func pollDBStats(interval time.Duration, db *sql.DB, pushFn func(stat sql.DBStats)) {
for range time.NewTicker(interval).C {
if db == nil {
log.Debug("No traced DB connection found; cannot pull DB stats.")
return
}
log.Debug("Traced DB connection found: DB stats will be gathered and sent every %v.", interval)
pushFn(db.Stats())
}
}

// pushDBStats separates the DBStats type out into individual statsd payloads and submits to the globalconfig's statsd client
func pushDBStats(stats sql.DBStats) {
// Starting with just 1 metric & no tags, to complete a MVP.
openConns := stats.OpenConnections
s := internal.NewGauge("sql.db.open_connections", float64(openConns), nil, 1)
globalconfig.PushStat(s)
}
30 changes: 30 additions & 0 deletions contrib/database/sql/sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package sql

import (
"context"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
Expand Down Expand Up @@ -44,6 +45,9 @@ func TestMain(m *testing.M) {
os.Exit(testResult)
}

// func TestDBStats(){}
// Holding off on writing this until I get review on the implementation decision

func TestSqlServer(t *testing.T) {
driverName := "sqlserver"
Register(driverName, &mssql.Driver{})
Expand Down Expand Up @@ -270,6 +274,32 @@ func TestOpenOptions(t *testing.T) {
})
}

func TestPollDBStats(t *testing.T) {
driverName := "postgres"
dsn := "postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable"
db, err := Open(driverName, dsn)
assert.NoError(t, err)
interval := 3 * time.Millisecond
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
pollDBStats(interval, db, pollDBStatsCounter)
}()
time.Sleep(3 * interval)
assert.Len(t, dbStatsCollector, 3)
}

var dbStatsCollector []sql.DBStats

func pollDBStatsCounter(stats sql.DBStats) {
dbStatsCollector = append(dbStatsCollector, stats)
}

// func TestPushDBStats(t *testing.T){

// }

func TestMySQLUint64(t *testing.T) {
Register("mysql", &mysql.MySQLDriver{})
defer unregister("mysql")
Expand Down
21 changes: 21 additions & 0 deletions ddtrace/tracer/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,24 @@ func TestTracerMetrics(t *testing.T) {
assert.Equal(1, calls["datadog.tracer.stopped"])
assert.True(tg.closed)
}

func TestContribStats(t *testing.T) {
mtoffl01 marked this conversation as resolved.
Show resolved Hide resolved
// t.Run("gauge", func(t *testing.T) {
// assert := assert.New(t)
// var tg testStatsdClient
// trc := newUnstartedTracer(withStatsdClient(&tg))
// defer trc.statsd.Close()
// s := globalinternal.NewGauge("gauge", float64(1), nil, 1)
// tick := time.NewTicker(time.Millisecond)
// defer tick.Stop()
// trc.wg.Add(1)
// go func() {
// defer trc.wg.Done()
// for i := 0; i < 10; i++ {
// trc.statsCarrier.Add(s)
// }
// calls := tg.CallsByName()
// assert.Equal(10, calls["gauge"])
// }()
// })
}
12 changes: 12 additions & 0 deletions ddtrace/tracer/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ type config struct {

// headerAsTags holds the header as tags configuration.
headerAsTags dynamicConfig[[]string]

contribStats bool
}

// orchestrionConfig contains Orchestrion configuration.
Expand Down Expand Up @@ -340,6 +342,7 @@ func newConfig(opts ...StartOption) *config {
c.logToStdout = true
}
c.logStartup = internal.BoolEnv("DD_TRACE_STARTUP_LOGS", true)
c.contribStats = internal.BoolEnv("DD_TRACE_CONTRIB_STATS_ENABLED", true)
c.runtimeMetrics = internal.BoolEnv("DD_RUNTIME_METRICS_ENABLED", false)
c.debug = internal.BoolEnv("DD_TRACE_DEBUG", false)
c.enabled = internal.BoolEnv("DD_TRACE_ENABLED", true)
Expand Down Expand Up @@ -1258,6 +1261,15 @@ func setHeaderTags(headerAsTags []string) bool {
return true
}

// WithContribStats opens up a channel of communication between tracer and contrib libraries
// for submitting stats from contribs to Datadog via the tracer's statsd client
// It is enabled by default but can be disabled with `WithContribStats(false)`
func WithContribStats(enabled bool) StartOption {
return func(c *config) {
c.contribStats = enabled
}
}

katiehockman marked this conversation as resolved.
Show resolved Hide resolved
// UserMonitoringConfig is used to configure what is used to identify a user.
// This configuration can be set by combining one or several UserMonitoringOption with a call to SetUser().
type UserMonitoringConfig struct {
Expand Down
21 changes: 21 additions & 0 deletions ddtrace/tracer/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,27 @@ func TestWithHeaderTags(t *testing.T) {
assert.Equal(t, 0, globalconfig.HeaderTagsLen())
}

func TestContribStatsEnabled(t *testing.T) {
t.Run("Default", func(t *testing.T) {
c := newConfig()
assert.True(t, c.contribStats)
})
t.Run("Disable", func(t *testing.T) {
c := newConfig(WithContribStats(false))
assert.False(t, c.contribStats)
})
t.Run("Disable with ENV", func(t *testing.T) {
t.Setenv("DD_TRACE_CONTRIB_STATS_ENABLED", "false")
c := newConfig()
assert.False(t, c.contribStats)
})
t.Run("Env override", func(t *testing.T) {
t.Setenv("DD_TRACE_CONTRIB_STATS_ENABLED", "false")
c := newConfig(WithContribStats(true))
assert.True(t, c.contribStats)
})
}

func TestHostnameDisabled(t *testing.T) {
t.Run("DisabledWithUDS", func(t *testing.T) {
t.Setenv("DD_TRACE_AGENT_URL", "unix://somefakesocket")
Expand Down
20 changes: 18 additions & 2 deletions ddtrace/tracer/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec"
appsecConfig "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/config"
"gopkg.in/DataDog/dd-trace-go.v1/internal/datastreams"
"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
"gopkg.in/DataDog/dd-trace-go.v1/internal/hostname"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/remoteconfig"
Expand Down Expand Up @@ -105,6 +106,8 @@ type tracer struct {
// abandonedSpansDebugger specifies where and how potentially abandoned spans are stored
// when abandoned spans debugging is enabled.
abandonedSpansDebugger *abandonedSpansDebugger

statsCarrier *globalinternal.StatsCarrier
}

const (
Expand Down Expand Up @@ -259,6 +262,10 @@ func newUnstartedTracer(opts ...StartOption) *tracer {
return f.DataStreams
})
}
var statsCarrier *globalinternal.StatsCarrier
if c.contribStats {
statsCarrier = globalinternal.NewStatsCarrier(statsd)
}
t := &tracer{
config: c,
traceWriter: writer,
Expand All @@ -278,8 +285,9 @@ func newUnstartedTracer(opts ...StartOption) *tracer {
Cache: c.agent.HasFlag("sql_cache"),
},
}),
statsd: statsd,
dataStreams: dataStreamsProcessor,
statsd: statsd,
dataStreams: dataStreamsProcessor,
statsCarrier: statsCarrier,
}
return t
}
Expand Down Expand Up @@ -323,6 +331,10 @@ func newTracer(opts ...StartOption) *tracer {
t.reportHealthMetrics(statsInterval)
}()
t.stats.Start()
if sc := t.statsCarrier; sc != nil {
sc.Start()
globalconfig.SetStatsCarrier(sc)
}
return t
}

Expand Down Expand Up @@ -651,6 +663,10 @@ func (t *tracer) Stop() {
if t.dataStreams != nil {
t.dataStreams.Stop()
}
if t.statsCarrier != nil {
t.statsCarrier.Stop()
globalconfig.ClearStatsCarrier()
}
appsec.Stop()
remoteconfig.Stop()
}
Expand Down
47 changes: 47 additions & 0 deletions ddtrace/tracer/tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry"
"gopkg.in/DataDog/dd-trace-go.v1/internal/version"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -686,6 +687,52 @@ func TestTracerRuntimeMetrics(t *testing.T) {
})
}

var debugPrefix = fmt.Sprintf("Datadog Tracer %v DEBUG: ", version.Tag)

func TestTracerContribStats(t *testing.T) {
stat := maininternal.NewGauge("test",float64(1),nil,1)
t.Run("default on", func(t *testing.T) {
tp := new(log.RecordLogger)
tracer := newTracer(WithDebugMode(true), WithLogger(tp))
defer tracer.Stop()
assert.NotNil(t, tracer.statsCarrier)
//check that the statscarrier has been set on the globalconfig
globalconfig.PushStat(stat)
assert.NotContains(t, tp.Logs(), fmt.Sprintf("%vNo stats carrier found; dropping stat %v", debugPrefix, stat.Name()))
})
t.Run("off", func(t *testing.T) {
tp := new(log.RecordLogger)
tracer := newTracer(WithContribStats(false), WithLogger(tp), WithDebugMode(true))
defer tracer.Stop()
assert.Nil(t, tracer.statsCarrier)
//check that there is no statscarrier on the globalconfig
globalconfig.PushStat(stat)
assert.Contains(t, tp.Logs(), fmt.Sprintf("%vNo stats carrier found; dropping stat %v", debugPrefix, stat.Name()))
})
t.Run("env", func(t *testing.T) {
os.Setenv("DD_TRACE_CONTRIB_STATS_ENABLED", "false")
defer os.Unsetenv("DD_TRACE_CONTRIB_STATS_ENABLED")
tp := new(log.RecordLogger)
tracer := newTracer(WithLogger(tp), WithDebugMode(true))
defer tracer.Stop()
assert.Nil(t, tracer.statsCarrier)
//check that there is no statscarrier on the globalconfig
globalconfig.PushStat(stat)
assert.Contains(t, tp.Logs(), fmt.Sprintf("%vNo stats carrier found; dropping stat %v", debugPrefix, stat.Name()))
})
t.Run("env override", func(t *testing.T) {
os.Setenv("DD_TRACE_CONTRIB_STATS_ENABLED", "false")
defer os.Unsetenv("DD_TRACE_CONTRIB_STATS_ENABLED")
tp := new(log.RecordLogger)
tracer := newTracer(WithLogger(tp), WithDebugMode(true), WithContribStats(true))
defer tracer.Stop()
assert.NotNil(t, tracer.statsCarrier)
//check that the statscarrier has been set on the globalconfig
globalconfig.PushStat(stat)
assert.NotContains(t, tp.Logs(), fmt.Sprintf("%vNo stats carrier found; dropping stat %v", debugPrefix, stat.Name()))
})
}

func TestTracerStartSpanOptions(t *testing.T) {
tracer := newTracer()
defer tracer.Stop()
Expand Down
Loading
Loading