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

engine: report go version on startup #159

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 @@ -13,6 +13,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 @@ -95,6 +98,7 @@ func TestServer(t *testing.T) {
}

func startTestServer(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
30 changes: 19 additions & 11 deletions engine_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package stats_test

import (
"io/ioutil"
"io"
"net/http"
"reflect"
"strings"
Expand Down 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 Expand Up @@ -549,7 +557,7 @@ type discardTransport struct{}
func (t *discardTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(strings.NewReader("")),
Body: io.NopCloser(strings.NewReader("")),
Request: req,
}, nil
}
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/v4"
"github.com/segmentio/stats/v4/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
Loading