cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / internal / telemetry / otlpmetrics.go
otlpmetrics.go 4.5K
1package telemetry23import (4 "maps"5 "slices"6 "time"7)89// The metrics OTLP sends: per-second frame windows as summaries and10// gauges, and cumulative counters and operation histograms.1112// durationBounds are the histogram buckets for operation durations, in13// milliseconds.14var durationBounds = []float64{0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 30000}1516type durationHist struct {17 counts []uint6418 count uint6419 sum, min, max float6420}2122func (h *durationHist) add(v float64) {23 if h.counts == nil {24 h.counts = make([]uint64, len(durationBounds)+1)25 h.min, h.max = v, v26 }27 i, _ := slices.BinarySearch(durationBounds, v)28 h.counts[i]++29 h.count++30 h.sum += v31 h.min, h.max = min(h.min, v), max(h.max, v)32}3334// frameWindow is one second of frames, from the frame summary.35type frameWindow struct {36 start, end time.Time37 render, key stats38 heap int6439 gauges map[string]int6440}4142type stats struct {43 count int44 sum float64 // ms45 p50, p95, pmax float64 // ms46}4748// metricsLocked builds the metrics: the queued frame windows as49// summaries and gauges, and the cumulative counters and histograms as50// they stand at now.51func (e *exporter) metricsLocked(windows []frameWindow, now time.Time) []metric {52 out := windowMetrics(windows)53 out = append(out, metric{Name: "o12.frames", Unit: "{frame}", Description: "Frames drawn", Sum: &sum{54 DataPoints: []numberPoint{intPoint(e.started, now, e.frames)},55 AggregationTemporality: temporalityCumulative, IsMonotonic: true,56 }})57 if len(e.ops) > 0 {58 out = append(out, metric{Name: "o12.operation.duration", Unit: "ms", Description: "Duration of operations such as recalc, import and sort", Histogram: e.opsHistogram(now)})59 }60 dropped := &sum{AggregationTemporality: temporalityCumulative, IsMonotonic: true}61 for i, n := range e.dropped {62 dropped.DataPoints = append(dropped.DataPoints, intPoint(e.started, now, n+e.failed[i], keyValue{Key: "signal", Value: str(signalNames[i])}))63 }64 out = append(out, metric{Name: "o12.telemetry.dropped", Unit: "{item}", Description: "Telemetry items not delivered: queue full or request failed", Sum: dropped})65 return out66}6768// windowMetrics are the frame windows as a summary of frame durations,69// one of key latencies, and gauges of the heap and whatever Set70// recorded; none when there are no windows.71func windowMetrics(windows []frameWindow) []metric {72 if len(windows) == 0 {73 return nil74 }75 render := &summary{}76 key := &summary{}77 heap := &gauge{}78 gauges := map[string]*gauge{}79 for _, w := range windows {80 render.DataPoints = append(render.DataPoints, w.render.point(w.start, w.end))81 if w.key.count > 0 {82 key.DataPoints = append(key.DataPoints, w.key.point(w.start, w.end))83 }84 heap.DataPoints = append(heap.DataPoints, intPoint(time.Time{}, w.end, w.heap))85 for k, v := range w.gauges {86 g := gauges[k]87 if g == nil {88 g = &gauge{}89 gauges[k] = g90 }91 g.DataPoints = append(g.DataPoints, intPoint(time.Time{}, w.end, v))92 }93 }94 out := []metric{95 {Name: "o12.frame.duration", Unit: "ms", Description: "Time to draw a frame (View), per second", Summary: render},96 {Name: "o12.heap", Unit: "By", Description: "Heap held by live and unswept objects", Gauge: heap},97 }98 if len(key.DataPoints) > 0 {99 out = append(out, metric{Name: "o12.key.latency", Unit: "ms", Description: "From a key press to the end of its frame, per second", Summary: key})100 }101 for _, k := range slices.Sorted(maps.Keys(gauges)) {102 out = append(out, metric{Name: "o12." + k, Gauge: gauges[k]})103 }104 return out105}106107// opsHistogram is the operation durations since the start, one point per108// operation.109func (e *exporter) opsHistogram(now time.Time) *histogram {110 h := &histogram{AggregationTemporality: temporalityCumulative}111 for _, name := range slices.Sorted(maps.Keys(e.ops)) {112 d := e.ops[name]113 counts := make([]u64, len(d.counts))114 for i, c := range d.counts {115 counts[i] = u64(c)116 }117 h.DataPoints = append(h.DataPoints, histogramPoint{118 Attributes: []keyValue{{Key: "event", Value: str(name)}},119 StartTimeUnixNano: nanos(e.started), TimeUnixNano: nanos(now),120 Count: u64(d.count), Sum: d.sum, BucketCounts: counts, ExplicitBounds: durationBounds,121 Min: d.min, Max: d.max,122 })123 }124 return h125}126127func (s stats) point(start, end time.Time) summaryPoint {128 return summaryPoint{129 StartTimeUnixNano: nanos(start), TimeUnixNano: nanos(end),130 Count: u64(s.count), Sum: s.sum,131 QuantileValues: []quantileValue{{0.5, s.p50}, {0.95, s.p95}, {1, s.pmax}},132 }133}