Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
valyala committed Nov 29, 2023
1 parent 6945973 commit 77e6234
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 141 deletions.
110 changes: 38 additions & 72 deletions go_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,25 @@ package metrics
import (
"fmt"
"io"
"log"
"math"
"runtime"
runtime_metrics "runtime/metrics"
runtimemetrics "runtime/metrics"

"github.com/valyala/histogram"
)

// See https://pkg.go.dev/runtime/metrics#hdr-Supported_metrics
var runtimeMetrics = [][2]string{
{"/sched/latencies:seconds", "go_sched_latencies_seconds"},
{"/sync/mutex/wait/total:seconds", "go_mutex_wait_seconds_total"},
{"/cpu/classes/gc/mark/assist:cpu-seconds", "go_gc_mark_assist_cpu_seconds_total"},
{"/cpu/classes/gc/total:cpu-seconds", "go_gc_cpu_seconds_total"},
{"/cpu/classes/scavenge/total:cpu-seconds", "go_scavenge_cpu_seconds_total"},
{"/gc/gomemlimit:bytes", "go_memlimit_bytes"},
}

func writeGoMetrics(w io.Writer) {
// https://pkg.go.dev/runtime/metrics#hdr-Supported_metrics
runtimeMetricSamples := [2]runtime_metrics.Sample{
{Name: "/sched/latencies:seconds"},
{Name: "/sync/mutex/wait/total:seconds"},
}
runtime_metrics.Read(runtimeMetricSamples[:])
writeRuntimeMetric(w, "go_sched_latency_seconds", runtimeMetricSamples[0])
writeRuntimeMetric(w, "go_mutex_wait_total_seconds", runtimeMetricSamples[1])
writeRuntimeMetrics(w)

var ms runtime.MemStats
runtime.ReadMemStats(&ms)
Expand Down Expand Up @@ -76,75 +78,39 @@ func writeGoMetrics(w io.Writer) {
runtime.Compiler, runtime.GOARCH, runtime.GOOS, runtime.GOROOT())
}

func writeRuntimeMetric(w io.Writer, name string, sample runtime_metrics.Sample) {
func writeRuntimeMetrics(w io.Writer) {
samples := make([]runtimemetrics.Sample, len(runtimeMetrics))
for i, rm := range runtimeMetrics {
samples[i].Name = rm[0]
}
runtimemetrics.Read(samples)
for i, rm := range runtimeMetrics {
writeRuntimeMetric(w, rm[1], &samples[i])
}
}

func writeRuntimeMetric(w io.Writer, name string, sample *runtimemetrics.Sample) {
switch sample.Value.Kind() {
case runtime_metrics.KindBad:
// not supported sample kind by current runtime version
return
case runtime_metrics.KindUint64:
case runtimemetrics.KindBad:
panic(fmt.Errorf("BUG: unexpected runtimemetrics.KindBad"))
case runtimemetrics.KindUint64:
fmt.Fprintf(w, "%s %d\n", name, sample.Value.Uint64())
case runtime_metrics.KindFloat64:
case runtimemetrics.KindFloat64:
fmt.Fprintf(w, "%s %g\n", name, sample.Value.Float64())
case runtime_metrics.KindFloat64Histogram:
case runtimemetrics.KindFloat64Histogram:
writeRuntimeHistogramMetric(w, name, sample.Value.Float64Histogram())
}
}

func writeRuntimeHistogramMetric(w io.Writer, name string, h *runtime_metrics.Float64Histogram) {
// it's unsafe to modify histogram
if len(h.Buckets) == 0 {
return
}
// sanity check
if len(h.Buckets) < len(h.Counts) {
log.Printf("ERROR: runtime_metrics.histogram: %q bad format for histogram, expected buckets to be less then counts, got: bucket %d: counts: %d", name, len(h.Buckets), len(h.Counts))
return
}
var sum uint64
// filter empty bins and convert histogram to cumulative
for _, weight := range h.Counts {
if weight == 0 {
continue
}
sum += weight
}
var lastNonInf float64
for i := len(h.Buckets) - 1; i > 0; i-- {
if !math.IsInf(h.Buckets[i], 0) {
lastNonInf = h.Buckets[i]
break
}
}
quantile := func(phi float64) float64 {
switch phi {
case 0:
return h.Buckets[0]
case 1:
return lastNonInf
}
reqValue := phi * float64(sum)
upperBoundIdx := 0
cumulativeWeight := uint64(0)
for idx, weight := range h.Counts {
cumulativeWeight += weight
if float64(cumulativeWeight) > reqValue {
upperBoundIdx = idx
break
}
}
// the first bucket is inclusive
if upperBoundIdx > 0 {
upperBoundIdx++
}
// last bucket may have an inf value, return last non inf in this case
if upperBoundIdx >= len(h.Buckets)-1 {
return lastNonInf
}
return h.Buckets[upperBoundIdx]
func writeRuntimeHistogramMetric(w io.Writer, name string, h *runtimemetrics.Float64Histogram) {
runningCount := uint64(0)
buckets := h.Buckets
for i, count := range h.Counts {
fmt.Fprintf(w, `%s_bucket{le="%g"} %d`+"\n", name, buckets[i], runningCount)
runningCount += count
}
phis := []float64{0, 0.25, 0.5, 0.75, 0.95, 1}
for _, phi := range phis {
q := quantile(phi)
fmt.Fprintf(w, `%s{quantile="%g"} %g`+"\n", name, phi, q)
fmt.Fprintf(w, `%s_bucket{le="%g"} %d`+"\n", name, buckets[len(buckets)-1], runningCount)
if !math.IsInf(buckets[len(buckets)-1], 1) {
fmt.Fprintf(w, `%s_bucket{le="+Inf"} %d`+"\n", name, runningCount)
}
}
115 changes: 46 additions & 69 deletions go_metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,87 +2,64 @@ package metrics

import (
"math"
runtime_metrics "runtime/metrics"
runtimemetrics "runtime/metrics"
"strings"
"testing"
)

func TestWriteRuntimeHistogramMetricOk(t *testing.T) {
f := func(expected string, metricName string, h runtime_metrics.Float64Histogram) {
f := func(h *runtimemetrics.Float64Histogram, resultExpected string) {
t.Helper()
var wOut strings.Builder
writeRuntimeHistogramMetric(&wOut, metricName, &h)
got := wOut.String()
if got != expected {
t.Fatalf("got out: \n%s\nwant: \n%s", got, expected)
writeRuntimeHistogramMetric(&wOut, "foo", h)
result := wOut.String()
if result != resultExpected {
t.Fatalf("unexpected result; got\n%s\nwant\n%s", result, resultExpected)
}

}

f(`runtime_latency_seconds{quantile="0"} 1
runtime_latency_seconds{quantile="0.25"} 3
runtime_latency_seconds{quantile="0.5"} 4
runtime_latency_seconds{quantile="0.75"} 4
runtime_latency_seconds{quantile="0.95"} 4
runtime_latency_seconds{quantile="1"} 4
`,
`runtime_latency_seconds`, runtime_metrics.Float64Histogram{
Counts: []uint64{1, 2, 3},
Buckets: []float64{1.0, 2.0, 3.0, 4.0},
})
f(`runtime_latency_seconds{quantile="0"} 1
runtime_latency_seconds{quantile="0.25"} 3
runtime_latency_seconds{quantile="0.5"} 3
runtime_latency_seconds{quantile="0.75"} 3
runtime_latency_seconds{quantile="0.95"} 4
runtime_latency_seconds{quantile="1"} 4
`,
`runtime_latency_seconds`, runtime_metrics.Float64Histogram{
Counts: []uint64{0, 25, 1, 3, 0},
Buckets: []float64{1.0, 2.0, 3.0, 4.0, math.Inf(1)},
})
f(`runtime_latency_seconds{quantile="0"} 1
runtime_latency_seconds{quantile="0.25"} 7
runtime_latency_seconds{quantile="0.5"} 9
runtime_latency_seconds{quantile="0.75"} 9
runtime_latency_seconds{quantile="0.95"} 10
runtime_latency_seconds{quantile="1"} 10
`,
`runtime_latency_seconds`, runtime_metrics.Float64Histogram{
Counts: []uint64{0, 25, 1, 3, 0, 44, 15, 132, 10, 11},
Buckets: []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, math.Inf(1)},
})
f(`runtime_latency_seconds{quantile="0"} -Inf
runtime_latency_seconds{quantile="0.25"} 4
runtime_latency_seconds{quantile="0.5"} 4
runtime_latency_seconds{quantile="0.75"} 4
runtime_latency_seconds{quantile="0.95"} 4
runtime_latency_seconds{quantile="1"} 4
`,
`runtime_latency_seconds`, runtime_metrics.Float64Histogram{
Counts: []uint64{1, 5},
Buckets: []float64{math.Inf(-1), 4.0, math.Inf(1)},
})
}
f(&runtimemetrics.Float64Histogram{
Counts: []uint64{1, 2, 3},
Buckets: []float64{1, 2, 3, 4},
}, `foo_bucket{le="1"} 0
foo_bucket{le="2"} 1
foo_bucket{le="3"} 3
foo_bucket{le="4"} 6
foo_bucket{le="+Inf"} 6
`)

func TestWriteRuntimeHistogramMetricFail(t *testing.T) {
f := func(h runtime_metrics.Float64Histogram) {
t.Helper()
var wOut strings.Builder
writeRuntimeHistogramMetric(&wOut, ``, &h)
got := wOut.String()
if got != "" {
t.Fatalf("expected empty output, got out: \n%s", got)
}
f(&runtimemetrics.Float64Histogram{
Counts: []uint64{0, 25, 1, 3},
Buckets: []float64{1, 2, 3, 4, math.Inf(1)},
}, `foo_bucket{le="1"} 0
foo_bucket{le="2"} 0
foo_bucket{le="3"} 25
foo_bucket{le="4"} 26
foo_bucket{le="+Inf"} 29
`)

}
f(&runtimemetrics.Float64Histogram{
Counts: []uint64{0, 25, 1, 3, 0, 44, 15, 132, 10, 11},
Buckets: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, math.Inf(1)},
}, `foo_bucket{le="1"} 0
foo_bucket{le="2"} 0
foo_bucket{le="3"} 25
foo_bucket{le="4"} 26
foo_bucket{le="5"} 29
foo_bucket{le="6"} 29
foo_bucket{le="7"} 73
foo_bucket{le="8"} 88
foo_bucket{le="9"} 220
foo_bucket{le="10"} 230
foo_bucket{le="+Inf"} 241
`)

f(runtime_metrics.Float64Histogram{
Counts: []uint64{},
Buckets: []float64{},
})
f(runtime_metrics.Float64Histogram{
Counts: []uint64{0, 25, 1, 3, 0, 12, 12},
Buckets: []float64{1.0, 2.0, 3.0, 4.0, math.Inf(1)},
})
f(&runtimemetrics.Float64Histogram{
Counts: []uint64{1, 5},
Buckets: []float64{math.Inf(-1), 4, math.Inf(1)},
}, `foo_bucket{le="-Inf"} 0
foo_bucket{le="4"} 1
foo_bucket{le="+Inf"} 6
`)
}

0 comments on commit 77e6234

Please sign in to comment.