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

debugstats: new package #175

Merged
merged 2 commits into from
Dec 19, 2024
Merged
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
11 changes: 11 additions & 0 deletions .mailmap
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Daniel Fuentes <[email protected]> <[email protected]>
Amir Abu Shareb <[email protected]> Amir Abushareb
Achille Roussel <[email protected]> Achille <[email protected]>
Achille Roussel <[email protected]> Achille Roussel <[email protected]>
Achille Roussel <[email protected]> Achille <[email protected]>
John Boggs <[email protected]> <[email protected]>
Michael S. Fischer <[email protected]> <[email protected]>
Ray Jenkins <[email protected]> <[email protected]>
Alan Braithwaite <[email protected]> <[email protected]>
Hyonjee Joo <[email protected]> <[email protected]>
Julien Fabre <[email protected]> <[email protected]>
36 changes: 36 additions & 0 deletions AUTHORS.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Achille Roussel <[email protected]>
Alan Braithwaite <[email protected]>
Amir Abu Shareb <[email protected]>
Bill Havanki <[email protected]>
Clement Rey <[email protected]>
Colin <[email protected]>
Collin Van Dyck <[email protected]>
Daniel Fuentes <[email protected]>
David Betts <[email protected]>
David Scrobonia <[email protected]>
Dean Karn <[email protected]>
Dominic Barnes <[email protected]>
Erik Weathers <[email protected]>
Hyonjee Joo <[email protected]>
Jeremy Jackins <[email protected]>
John Boggs <[email protected]>
Joseph Herlant <[email protected]>
Julien Fabre <[email protected]>
Kevin Burke <[email protected]>
Kevin Gillette <[email protected]>
Matt Layher <[email protected]>
Michael S. Fischer <[email protected]>
Mike Nichols <[email protected]>
Prateek Srivastava <[email protected]>
Ray Jenkins <[email protected]>
Rick Branson <[email protected]>
Rob McQueen <[email protected]>
Rohit Garg <[email protected]>
Thomas Meson <[email protected]>
Tyson Mote <[email protected]>
Ulysse Carion <[email protected]>
Vic Vijayakumar <[email protected]>
Yerden Zhumabekov <[email protected]>
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
noctarius aka Christoph Engelbert <[email protected]>
sungjujin <[email protected]>
13 changes: 13 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# History

### v5.3.0 (December 19, 2024)

- Add `debugstats` package that can easily print metrics to stdout.

- Fix file handle leak in procstats in the DelayMetrics collector.

### v5.2.0

- `go_version.value` and `stats_version.value` will be emitted the first
time any metrics are sent from an Engine. Disable this behavior by setting
`GoVersionReportingEnabled = false` in code, or setting environment variable
`STATS_DISABLE_GO_VERSION_REPORTING=true`.

### v5.1.0

Add support for publishing stats to Unix datagram sockets (UDS).
Expand Down
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ lint:

release:
go run github.com/kevinburke/bump_version@latest --tag-prefix=v minor version/version.go

force: ;

AUTHORS.txt: force
go run github.com/kevinburke/write_mailmap@latest > AUTHORS.txt

authors: AUTHORS.txt
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Installation
go get github.com/segmentio/stats/v5
```

Migration to v5
Migration to v4/v5
---------------

Version 4 of the stats package introduced a new way of producing metrics based
Expand Down Expand Up @@ -47,7 +47,7 @@ To avoid greatly increasing the complexity of the codebase some old APIs were
removed in favor of this new approach, other were transformed to provide more
flexibility and leverage new features.

The stats package used to only support float values, metrics can now be of
The stats package used to only support float values. Metrics can now be of
various numeric types (see stats.MakeMeasure for a detailed description),
therefore functions like `stats.Add` now accept an `interface{}` value instead
of `float64`. `stats.ObserveDuration` was also removed since this new approach
Expand Down Expand Up @@ -176,6 +176,23 @@ func main() {
}
```

### Troubleshooting

Use the `debugstats` package to print all stats to the console.

```go
handler := debugstats.Client{Dst: os.Stdout}
engine := stats.NewEngine("engine-name", handler)
engine.Incr("server.start")
```

You can use the `Grep` property to filter the printed metrics for only ones you
care about:

```go
handler := debugstats.Client{Dst: os.Stdout, Grep: regexp.MustCompile("server.start")}
```

Monitoring
----------

Expand Down Expand Up @@ -300,7 +317,7 @@ func main() {
```

You can also modify the default HTTP client to automatically get metrics for all
packages using it, this is very convinient to get insights into dependencies.
packages using it, this is very convenient to get insights into dependencies.

```go
package main
Expand Down
119 changes: 119 additions & 0 deletions debugstats/debugstats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Package debugstats simplifies metric troubleshooting by sending metrics to
// any io.Writer.
//
// By default, metrics will be printed to os.Stdout. Use the Dst and Grep fields
// to customize the output as appropriate.
package debugstats

import (
"fmt"
"io"
"math"
"os"
"regexp"
"strconv"
"time"

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

// Client will print out received metrics. If Dst is nil, metrics will be
// printed to stdout, otherwise they will be printed to Dst.
//
// You can optionally provide a Grep regexp to limit printed metrics to ones
// matching the regular expression.
type Client struct {
Dst io.Writer
Grep *regexp.Regexp
}

func (c *Client) Write(p []byte) (int, error) {
if c.Dst == nil {
return os.Stdout.Write(p)
}
return c.Dst.Write(p)
}

func normalizeFloat(f float64) float64 {
switch {
case math.IsNaN(f):
return 0.0
case math.IsInf(f, +1):
return +math.MaxFloat64
case math.IsInf(f, -1):
return -math.MaxFloat64
default:
return f
}
}

func appendMeasure(b []byte, m stats.Measure) []byte {
for _, field := range m.Fields {
b = append(b, m.Name...)
if len(field.Name) != 0 {
b = append(b, '.')
b = append(b, field.Name...)
}
b = append(b, ':')

switch v := field.Value; v.Type() {
case stats.Bool:
if v.Bool() {
b = append(b, '1')
} else {
b = append(b, '0')
}
case stats.Int:
b = strconv.AppendInt(b, v.Int(), 10)
case stats.Uint:
b = strconv.AppendUint(b, v.Uint(), 10)
case stats.Float:
b = strconv.AppendFloat(b, normalizeFloat(v.Float()), 'g', -1, 64)
case stats.Duration:
b = strconv.AppendFloat(b, v.Duration().Seconds(), 'g', -1, 64)
default:
b = append(b, '0')
}

switch field.Type() {
case stats.Counter:
b = append(b, '|', 'c')
case stats.Gauge:
b = append(b, '|', 'g')
default:
b = append(b, '|', 'd')
}

if n := len(m.Tags); n != 0 {
b = append(b, '|', '#')

for i, t := range m.Tags {
if i != 0 {
b = append(b, ',')
}
b = append(b, t.Name...)
b = append(b, ':')
b = append(b, t.Value...)
}
}

b = append(b, '\n')
}

return b
}

func (c *Client) HandleMeasures(t time.Time, measures ...stats.Measure) {
for i := range measures {
m := &measures[i]

// Process and output the measure
out := make([]byte, 0)
out = appendMeasure(out, *m)
if c.Grep != nil && !c.Grep.Match(out) {
continue // Skip this measure
}

fmt.Fprintf(c, "%s %s", t.Format(time.RFC3339), out)
}
}
48 changes: 48 additions & 0 deletions debugstats/debugstats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package debugstats

import (
"bytes"
"regexp"
"strings"
"testing"

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

func TestStdout(t *testing.T) {
var buf bytes.Buffer
s := &Client{Dst: &buf}
stats.Register(s)
stats.Set("blah", 7)
stats.Observe("compression_ratio", 0.3, stats.T("file_size_bucket", "bucket_name"), stats.T("algorithm", "jwt256"))
bufstr := buf.String()
want := "debugstats.test.compression_ratio:0.3|d|#algorithm:jwt256,file_size_bucket:bucket_name\n"
if !strings.HasSuffix(bufstr, want) {
t.Errorf("debugstats: got %v want %v", bufstr, want)
}
}

func TestStdoutGrepMatch(t *testing.T) {
var buf bytes.Buffer
s := &Client{
Dst: &buf,
Grep: regexp.MustCompile(`compression_ratio`),
}
eng := stats.NewEngine("prefix", s)

// Send measures that match and don't match the Grep pattern
eng.Set("compression_ratio", 0.3)
eng.Set("other_metric", 42)
eng.Flush()

bufstr := buf.String()

// Check that only the matching measure is output
if !strings.Contains(bufstr, "compression_ratio:0.3") {
t.Errorf("debugstats: expected output to contain 'compression_ratio:0.3', but it did not. Output: %s", bufstr)
}

if strings.Contains(bufstr, "other_metric") {
t.Errorf("debugstats: expected output not to contain 'other_metric', but it did. Output: %s", bufstr)
}
}
Loading
Loading