Skip to content

Commit

Permalink
engine: report go version on startup
Browse files Browse the repository at this point in the history
The first time you report any metric, additionally report the running
Go version. Add two flags that can be used to disable this behavior.
  • Loading branch information
kevinburkesegment committed Oct 28, 2024
1 parent 8cab33e commit 53208e5
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 21 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,3 +385,16 @@ func main() {
// ...
}
```

### Addendum

By default, the stats library will report the running go version when you
invoke NewEngine() as three metrics:

- `go_version.major`
- `go_version.minor`
- `go_version.patch`

Set `STATS_DISABLE_GO_VERSION_REPORTING` to `true` in your environment, or set
`stats.GoVersionReportingEnabled` to `false` before collecting any metrics, to
disable this behavior.
4 changes: 4 additions & 0 deletions datadog/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import (
)

func TestServer(t *testing.T) {
initValue := stats.GoVersionReportingEnabled
stats.GoVersionReportingEnabled = false
defer func() { stats.GoVersionReportingEnabled = initValue }()
engine := stats.NewEngine("datadog.test", nil)

a := uint32(0)
Expand Down Expand Up @@ -97,6 +100,7 @@ func TestServer(t *testing.T) {
}

func startUDPTestServer(t *testing.T, handler Handler) (addr string, closer io.Closer) {
t.Helper()
conn, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Error(err)
Expand Down
30 changes: 26 additions & 4 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import (
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"sync"
"time"
)

// An Engine carries the context for producing metrics, it is configured by
// An Engine carries the context for producing metrics. It is configured by
// setting the exported fields or using the helper methods to create sub-engines
// that inherit the configuration of the base they were created from.
//
// The program must not modify the engine's handler, prefix, or tags after it
// started using it. If changes need to be made new engines must be created by
// starts using them. If changes need to be made new engines must be created by
// calls to WithPrefix or WithTags.
type Engine struct {
// The measure handler that the engine forwards measures to.
Expand All @@ -26,7 +28,7 @@ type Engine struct {
//
// The list of tags has to be sorted. This is automatically managed by the
// helper methods WithPrefix, WithTags and the NewEngine function. A program
// that manipulates this field directly has to respect this requirement.
// that manipulates this field directly must respect this requirement.
Tags []Tag

// Indicates whether to allow duplicated tags from the tags list before sending.
Expand All @@ -41,16 +43,19 @@ type Engine struct {
// The cached values include the engine prefix in the measure names, which
// is why the cache must be local to the engine.
cache measureCache

once sync.Once
}

// NewEngine creates and returns a new engine configured with prefix, handler,
// and tags.
func NewEngine(prefix string, handler Handler, tags ...Tag) *Engine {
return &Engine{
e := &Engine{
Handler: handler,
Prefix: prefix,
Tags: SortTags(copyTags(tags)),
}
return e
}

// Register adds handler to eng.
Expand Down Expand Up @@ -144,7 +149,24 @@ func (eng *Engine) ClockAt(name string, start time.Time, tags ...Tag) *Clock {
}
}

var GoVersionReportingEnabled = os.Getenv("STATS_DISABLE_GO_VERSION_REPORTING") != "true"

func (eng *Engine) measure(t time.Time, name string, value interface{}, ftype FieldType, tags ...Tag) {
if GoVersionReportingEnabled {
eng.once.Do(func() {
vsn := strings.TrimPrefix(runtime.Version(), "go")
parts := strings.Split(vsn, ".")
// this filters out weird compiled Go versions like tip.
// older Go version might be "go1.13"
if len(parts) == 2 || len(parts) == 3 {
eng.measureOne(t, "go_version", 1, Gauge, []Tag{{"go_version", vsn}}...)
}
})
}
eng.measureOne(t, name, value, ftype, tags...)
}

func (eng *Engine) measureOne(t time.Time, name string, value interface{}, ftype FieldType, tags ...Tag) {
name, field := splitMeasureField(name)
mp := measureArrayPool.Get().(*[1]Measure)

Expand Down
26 changes: 17 additions & 9 deletions engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,21 @@ func TestEngine(t *testing.T) {
},
}

for _, test := range tests {
testFunc := test.function
t.Run(test.scenario, func(t *testing.T) {
t.Parallel()
h := &statstest.Handler{}
testFunc(t, stats.NewEngine("test", h, stats.T("service", "test-service")))
})
}
initValue := stats.GoVersionReportingEnabled
stats.GoVersionReportingEnabled = false
defer func() { stats.GoVersionReportingEnabled = initValue }()
// Extra t.Run is necessary so above defer runs after parallel tests
// complete.
t.Run("subtests", func(t *testing.T) {
for _, test := range tests {
testFunc := test.function
t.Run(test.scenario, func(t *testing.T) {
t.Parallel()
h := &statstest.Handler{}
testFunc(t, stats.NewEngine("test", h, stats.T("service", "test-service")))
})
}
})
}

func testEngineWithPrefix(t *testing.T, eng *stats.Engine) {
Expand Down Expand Up @@ -369,7 +376,8 @@ func BenchmarkEngine(b *testing.B) {
},
}

for _, eng := range engines {
for i := range engines {
eng := &engines[i]
b.Run(eng.name, func(b *testing.B) {
tests := []struct {
scenario string
Expand Down
6 changes: 6 additions & 0 deletions netstats/conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ func TestBaseConn(t *testing.T) {
}

func TestConn(t *testing.T) {
initValue := stats.GoVersionReportingEnabled
stats.GoVersionReportingEnabled = false
defer func() { stats.GoVersionReportingEnabled = initValue }()
h := &statstest.Handler{}
e := stats.NewEngine("netstats.test", h)

Expand Down Expand Up @@ -84,6 +87,9 @@ func TestConn(t *testing.T) {
}

func TestConnError(t *testing.T) {
initValue := stats.GoVersionReportingEnabled
stats.GoVersionReportingEnabled = false
defer func() { stats.GoVersionReportingEnabled = initValue }()
h := &statstest.Handler{}
e := stats.NewEngine("netstats.test", h)

Expand Down
35 changes: 28 additions & 7 deletions netstats/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package netstats
import (
"net"
"reflect"
"runtime"
"strings"
"testing"

"github.com/segmentio/stats/v5"
"github.com/segmentio/stats/v5/statstest"
)

func TestListener(t *testing.T) {
initValue := stats.GoVersionReportingEnabled
stats.GoVersionReportingEnabled = false
defer func() { stats.GoVersionReportingEnabled = initValue }()
h := &statstest.Handler{}
e := stats.NewEngine("netstats.test", h)

Expand Down Expand Up @@ -58,15 +63,31 @@ func TestListenerError(t *testing.T) {

lstn.Close()

expected := []stats.Measure{
{
Name: "netstats.test.conn.error",
Fields: []stats.Field{stats.MakeField("count", 1, stats.Counter)},
Tags: []stats.Tag{stats.T("operation", "accept"), stats.T("protocol", "tcp")},
},
vsn := strings.TrimPrefix(runtime.Version(), "go")
parts := strings.Split(vsn, ".")
measures := h.Measures()
if len(parts) == 2 || len(parts) == 3 {
if len(measures) != 1+1 {
t.Fatalf("expecting to get %d metrics, got back %d: %v", 1+1, len(measures), measures)
}
}
var foundMetric stats.Measure
for i := range measures {
if measures[i].Name == "netstats.test.conn.error" {
foundMetric = measures[i]
break
}
}
if foundMetric.Name == "" {
t.Errorf("did not find netstats metric: %v", measures)
}

if !reflect.DeepEqual(expected, h.Measures()) {
expected := stats.Measure{
Name: "netstats.test.conn.error",
Fields: []stats.Field{stats.MakeField("count", 1, stats.Counter)},
Tags: []stats.Tag{stats.T("operation", "accept"), stats.T("protocol", "tcp")},
}
if !reflect.DeepEqual(expected, foundMetric) {
t.Error("bad measures:")
t.Logf("expected: %v", expected)
t.Logf("found: %v", h.Measures())
Expand Down
2 changes: 1 addition & 1 deletion procstats/go.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func NewGoMetrics() *GoMetrics {
return NewGoMetricsWith(stats.DefaultEngine)
}

// NewGoMetricsWith creates a new collector for the Go unrtime that producers
// NewGoMetricsWith creates a new collector for the Go runtime that producers
// metrics on eng.
func NewGoMetricsWith(eng *stats.Engine) *GoMetrics {
g := &GoMetrics{
Expand Down

0 comments on commit 53208e5

Please sign in to comment.