2024-02-06 21:58:11 +01:00
|
|
|
package datadogsketches
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/easyproto"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2024-02-07 00:15:25 +01:00
|
|
|
// TODO: @AndrewChubatiuk, please provide a permalink for the original source code where these constants were extracted
|
2024-02-06 21:58:11 +01:00
|
|
|
epsillon = 1.0 / 128
|
|
|
|
gamma = 1 + 2*epsillon
|
|
|
|
gammaLn = math.Log(gamma)
|
|
|
|
defaultMin = 0.981e-9
|
|
|
|
bias = 1 - int(math.Floor(math.Log(defaultMin)/gammaLn))
|
|
|
|
quantiles = []float64{0.5, 0.75, 0.9, 0.95, 0.99}
|
|
|
|
)
|
|
|
|
|
2024-02-07 00:15:25 +01:00
|
|
|
var quantilesStr = func() []string {
|
|
|
|
a := make([]string, len(quantiles))
|
|
|
|
for i, q := range quantiles {
|
|
|
|
a[i] = strconv.FormatFloat(q, 'g', 3, 64)
|
|
|
|
}
|
|
|
|
return a
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Label is a single label for Metric
|
|
|
|
type Label struct {
|
2024-02-06 21:58:11 +01:00
|
|
|
Name string
|
|
|
|
Value string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Metric stores metrics extracted from sketches
|
|
|
|
type Metric struct {
|
|
|
|
Name string
|
2024-02-07 00:15:25 +01:00
|
|
|
Labels []Label
|
|
|
|
Points []Point
|
|
|
|
}
|
|
|
|
|
|
|
|
// Point stores a single point extracted from sketches
|
|
|
|
type Point struct {
|
|
|
|
Value float64
|
|
|
|
Timestamp int64
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// SketchPayload stores sketches extracted from /api/beta/sketches endpoint
|
|
|
|
//
|
|
|
|
// message SketchPayload {
|
|
|
|
// repeated Sketch sketches = 1
|
|
|
|
// }
|
2024-02-07 00:15:25 +01:00
|
|
|
//
|
|
|
|
// See https://github.com/DataDog/agent-payload/blob/38db68d9641c8a0bd2e1eac53b9d54793448850f/proto/metrics/agent_payload.proto#L90
|
2024-02-06 21:58:11 +01:00
|
|
|
type SketchPayload struct {
|
|
|
|
Sketches []*Sketch
|
|
|
|
}
|
|
|
|
|
2024-02-07 00:15:25 +01:00
|
|
|
// UnmarshalProtobuf decodes src to SketchPayload struct
|
2024-02-06 21:58:11 +01:00
|
|
|
func (sp *SketchPayload) UnmarshalProtobuf(src []byte) (err error) {
|
2024-02-07 00:15:25 +01:00
|
|
|
sp.Sketches = nil
|
2024-02-06 21:58:11 +01:00
|
|
|
var fc easyproto.FieldContext
|
|
|
|
for len(src) > 0 {
|
|
|
|
src, err = fc.NextField(src)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot read next field in SketchPayload message: %w", err)
|
|
|
|
}
|
|
|
|
switch fc.FieldNum {
|
|
|
|
case 1:
|
|
|
|
data, ok := fc.MessageData()
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read Sketch data")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
2024-02-07 00:15:25 +01:00
|
|
|
var s Sketch
|
2024-02-06 21:58:11 +01:00
|
|
|
if err := s.unmarshalProtobuf(data); err != nil {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot unmarshal Sketch: %w", err)
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
2024-02-07 00:15:25 +01:00
|
|
|
sp.Sketches = append(sp.Sketches, &s)
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sketch proto struct
|
|
|
|
//
|
|
|
|
// message Sketch {
|
|
|
|
// string metric = 1;
|
|
|
|
// string host = 2;
|
|
|
|
// repeated string tags = 4;
|
|
|
|
// repeated Dogsketch dogsketches = 7
|
|
|
|
// }
|
2024-02-07 00:15:25 +01:00
|
|
|
//
|
|
|
|
// See https://github.com/DataDog/agent-payload/blob/38db68d9641c8a0bd2e1eac53b9d54793448850f/proto/metrics/agent_payload.proto#L91
|
2024-02-06 21:58:11 +01:00
|
|
|
type Sketch struct {
|
|
|
|
Metric string
|
|
|
|
Host string
|
|
|
|
Tags []string
|
|
|
|
Dogsketches []*Dogsketch
|
|
|
|
}
|
|
|
|
|
2024-02-07 00:15:25 +01:00
|
|
|
// unmarshalProtobuf decodes src to Sketch struct
|
2024-02-06 21:58:11 +01:00
|
|
|
func (s *Sketch) unmarshalProtobuf(src []byte) (err error) {
|
|
|
|
s.Metric = ""
|
|
|
|
s.Host = ""
|
2024-02-07 00:15:25 +01:00
|
|
|
s.Tags = nil
|
|
|
|
s.Dogsketches = nil
|
2024-02-06 21:58:11 +01:00
|
|
|
|
|
|
|
var fc easyproto.FieldContext
|
|
|
|
for len(src) > 0 {
|
|
|
|
src, err = fc.NextField(src)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot read next field in Sketch message: %w", err)
|
|
|
|
}
|
|
|
|
switch fc.FieldNum {
|
|
|
|
case 1:
|
|
|
|
metric, ok := fc.String()
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read metric")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
s.Metric = metric
|
|
|
|
case 2:
|
|
|
|
host, ok := fc.String()
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read host")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
s.Host = host
|
|
|
|
case 4:
|
|
|
|
tag, ok := fc.String()
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read tag")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
s.Tags = append(s.Tags, tag)
|
|
|
|
case 7:
|
|
|
|
data, ok := fc.MessageData()
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read Dogsketch data")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
2024-02-07 00:15:25 +01:00
|
|
|
var d Dogsketch
|
2024-02-06 21:58:11 +01:00
|
|
|
if err := d.unmarshalProtobuf(data); err != nil {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot unmarshal Dogsketch: %w", err)
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
2024-02-07 00:15:25 +01:00
|
|
|
s.Dogsketches = append(s.Dogsketches, &d)
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-02-07 00:15:25 +01:00
|
|
|
// RowsCount returns the number of samples s generates.
|
2024-02-06 21:58:11 +01:00
|
|
|
func (s *Sketch) RowsCount() int {
|
2024-02-07 00:15:25 +01:00
|
|
|
// The sketch contains len(quantiles) plus *_sum and *_count metrics
|
|
|
|
// per each Dogsketch in s.Dogsketches.
|
|
|
|
return (len(quantiles) + 2) * len(s.Dogsketches)
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
|
2024-02-07 00:15:25 +01:00
|
|
|
// ToSummary generates Prometheus summary from the given s.
|
|
|
|
func (s *Sketch) ToSummary() []*Metric {
|
|
|
|
metrics := make([]*Metric, len(quantiles)+2)
|
|
|
|
dogsketches := s.Dogsketches
|
|
|
|
|
|
|
|
sumPoints := make([]Point, len(dogsketches))
|
|
|
|
countPoints := make([]Point, len(dogsketches))
|
|
|
|
metrics[len(metrics)-2] = &Metric{
|
|
|
|
Name: s.Metric + "_sum",
|
|
|
|
Points: sumPoints,
|
|
|
|
}
|
|
|
|
metrics[len(metrics)-1] = &Metric{
|
|
|
|
Name: s.Metric + "_count",
|
|
|
|
Points: countPoints,
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
|
2024-02-07 00:15:25 +01:00
|
|
|
for i, q := range quantiles {
|
|
|
|
points := make([]Point, len(dogsketches))
|
|
|
|
for j, d := range dogsketches {
|
|
|
|
timestamp := d.Ts * 1000
|
|
|
|
points[j] = Point{
|
|
|
|
Timestamp: timestamp,
|
|
|
|
Value: d.valueForQuantile(q),
|
|
|
|
}
|
|
|
|
sumPoints[j] = Point{
|
|
|
|
Timestamp: timestamp,
|
|
|
|
Value: d.Sum,
|
|
|
|
}
|
|
|
|
countPoints[j] = Point{
|
|
|
|
Timestamp: timestamp,
|
|
|
|
Value: float64(d.Cnt),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
metrics[i] = &Metric{
|
2024-02-06 21:58:11 +01:00
|
|
|
Name: s.Metric,
|
2024-02-07 00:15:25 +01:00
|
|
|
Labels: []Label{{
|
2024-02-06 21:58:11 +01:00
|
|
|
Name: "quantile",
|
2024-02-07 00:15:25 +01:00
|
|
|
Value: quantilesStr[i],
|
2024-02-06 21:58:11 +01:00
|
|
|
}},
|
2024-02-07 00:15:25 +01:00
|
|
|
Points: points,
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
}
|
2024-02-07 00:15:25 +01:00
|
|
|
|
|
|
|
return metrics
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Dogsketch proto struct
|
|
|
|
//
|
|
|
|
// message Dogsketch {
|
|
|
|
// int64 ts = 1;
|
|
|
|
// int64 cnt = 2;
|
|
|
|
// double min = 3;
|
|
|
|
// double max = 4;
|
|
|
|
// double sum = 6;
|
|
|
|
// repeated sint32 k = 7;
|
|
|
|
// repeated uint32 n = 8;
|
|
|
|
// }
|
2024-02-07 00:15:25 +01:00
|
|
|
//
|
|
|
|
// See https://github.com/DataDog/agent-payload/blob/38db68d9641c8a0bd2e1eac53b9d54793448850f/proto/metrics/agent_payload.proto#L104
|
2024-02-06 21:58:11 +01:00
|
|
|
type Dogsketch struct {
|
|
|
|
Ts int64
|
|
|
|
Cnt int64
|
|
|
|
Min float64
|
|
|
|
Max float64
|
|
|
|
Sum float64
|
|
|
|
K []int32
|
|
|
|
N []uint32
|
|
|
|
}
|
|
|
|
|
2024-02-07 00:15:25 +01:00
|
|
|
// unmarshalProtobuf decodes src to Dogsketch struct
|
2024-02-06 21:58:11 +01:00
|
|
|
func (d *Dogsketch) unmarshalProtobuf(src []byte) (err error) {
|
|
|
|
d.Ts = 0
|
|
|
|
d.Cnt = 0
|
|
|
|
d.Min = 0.0
|
|
|
|
d.Max = 0.0
|
|
|
|
d.Sum = 0.0
|
2024-02-07 00:15:25 +01:00
|
|
|
d.K = nil
|
|
|
|
d.N = nil
|
|
|
|
|
2024-02-06 21:58:11 +01:00
|
|
|
var fc easyproto.FieldContext
|
|
|
|
for len(src) > 0 {
|
|
|
|
src, err = fc.NextField(src)
|
|
|
|
if err != nil {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read next field in Dogsketch message: %w", err)
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
switch fc.FieldNum {
|
|
|
|
case 1:
|
|
|
|
ts, ok := fc.Int64()
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read timestamp")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
d.Ts = ts
|
|
|
|
case 2:
|
|
|
|
cnt, ok := fc.Int64()
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read count")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
d.Cnt = cnt
|
|
|
|
case 3:
|
|
|
|
min, ok := fc.Double()
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read min")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
d.Min = min
|
|
|
|
case 4:
|
|
|
|
max, ok := fc.Double()
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read max")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
d.Max = max
|
|
|
|
case 6:
|
|
|
|
sum, ok := fc.Double()
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read sum")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
d.Sum = sum
|
|
|
|
case 7:
|
|
|
|
var ok bool
|
|
|
|
d.K, ok = fc.UnpackSint32s(d.K)
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read k")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
case 8:
|
|
|
|
var ok bool
|
|
|
|
d.N, ok = fc.UnpackUint32s(d.N)
|
|
|
|
if !ok {
|
2024-02-07 00:15:25 +01:00
|
|
|
return fmt.Errorf("cannot read n")
|
2024-02-06 21:58:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-02-07 00:15:25 +01:00
|
|
|
func (d *Dogsketch) valueForQuantile(q float64) float64 {
|
2024-02-06 21:58:11 +01:00
|
|
|
switch {
|
|
|
|
case d.Cnt == 0:
|
|
|
|
return 0
|
2024-02-07 00:15:25 +01:00
|
|
|
case q <= 0:
|
2024-02-06 21:58:11 +01:00
|
|
|
return d.Min
|
2024-02-07 00:15:25 +01:00
|
|
|
case q >= 1:
|
2024-02-06 21:58:11 +01:00
|
|
|
return d.Max
|
|
|
|
}
|
|
|
|
|
2024-02-07 00:15:25 +01:00
|
|
|
ns := d.N
|
|
|
|
ks := d.K
|
|
|
|
if len(ns) != len(ks) {
|
|
|
|
// Avoid index out of range panic in the loop below.
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
rank := q * float64(d.Cnt-1)
|
|
|
|
cnt := float64(0)
|
|
|
|
for i, n := range ns {
|
|
|
|
cnt += float64(n)
|
2024-02-06 21:58:11 +01:00
|
|
|
if cnt <= rank {
|
|
|
|
continue
|
|
|
|
}
|
2024-02-07 00:15:25 +01:00
|
|
|
weight := (cnt - rank) / float64(n)
|
|
|
|
vLow := f64(ks[i])
|
2024-02-06 21:58:11 +01:00
|
|
|
vHigh := vLow * gamma
|
|
|
|
switch i {
|
2024-02-07 00:15:25 +01:00
|
|
|
// TODO: I'm unsure this code is correct. i cannot equal len(ns) in this loop.
|
|
|
|
// @AndrewChubatiuk, please add a permalink to the original source code, which was used
|
|
|
|
// for writing this code, in the comments to this function.
|
|
|
|
case len(ns):
|
2024-02-06 21:58:11 +01:00
|
|
|
vHigh = d.Max
|
|
|
|
case 0:
|
|
|
|
vLow = d.Min
|
|
|
|
}
|
|
|
|
return vLow*weight + vHigh*(1-weight)
|
|
|
|
}
|
|
|
|
return d.Max
|
|
|
|
}
|
|
|
|
|
|
|
|
func f64(k int32) float64 {
|
|
|
|
switch {
|
|
|
|
case k < 0:
|
|
|
|
return -f64(-k)
|
2024-02-07 00:15:25 +01:00
|
|
|
// TODO: I'm unsure this logic is correct, since k can be smaller than math.MinInt16 and bigger than math.MaxInt16
|
|
|
|
// @AndrewChubatiuk, please add a permalink to the original source code, which was used for writing this code.
|
2024-02-06 21:58:11 +01:00
|
|
|
case k == math.MaxInt16 || k == math.MinInt16:
|
|
|
|
return math.Inf(int(k))
|
|
|
|
case k == 0:
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
exp := float64(int(k) - bias)
|
|
|
|
return math.Pow(gamma, exp)
|
|
|
|
}
|