Files
nmon/internal/rollup/parse.go

77 lines
1.7 KiB
Go

package rollup
import (
"encoding/json"
"fmt"
"math"
"strconv"
)
// point is one stored sample; ts is Unix milliseconds.
type point struct {
ts int64
v float64
}
// series is one result row of a matrix (pts) or vector (one pt) response.
type series struct {
labels map[string]string
pts []point
}
type vmResponse struct {
Status string `json:"status"`
Error string `json:"error"`
Data struct {
Result []struct {
Metric map[string]string `json:"metric"`
Value []any `json:"value"`
Values [][]any `json:"values"`
} `json:"result"`
} `json:"data"`
}
// parseResult decodes an instant-query response of either shape. Non-finite
// values are dropped: a NaN is "no data", not a zero.
func parseResult(body []byte) ([]series, error) {
var r vmResponse
if err := json.Unmarshal(body, &r); err != nil {
return nil, fmt.Errorf("rollup: decode store response: %w", err)
}
if r.Status != "success" {
return nil, fmt.Errorf("rollup: store query failed: %s", r.Error)
}
out := make([]series, 0, len(r.Data.Result))
for _, row := range r.Data.Result {
s := series{labels: row.Metric}
if len(row.Value) == 2 {
if p, ok := decodePoint(row.Value); ok {
s.pts = append(s.pts, p)
}
}
for _, v := range row.Values {
if p, ok := decodePoint(v); ok {
s.pts = append(s.pts, p)
}
}
out = append(out, s)
}
return out, nil
}
func decodePoint(raw []any) (point, bool) {
if len(raw) != 2 {
return point{}, false
}
t, ok := raw[0].(float64)
str, ok2 := raw[1].(string)
if !ok || !ok2 {
return point{}, false
}
v, err := strconv.ParseFloat(str, 64)
if err != nil || math.IsNaN(v) || math.IsInf(v, 0) {
return point{}, false
}
return point{ts: int64(math.Round(t * 1000)), v: v}, true
}