2021-01-31 23:10:16 +00:00
|
|
|
package vm
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
2021-01-31 23:31:25 +00:00
|
|
|
// TimeSeries represents a time series.
|
2021-01-31 23:10:16 +00:00
|
|
|
type TimeSeries struct {
|
|
|
|
Name string
|
|
|
|
LabelPairs []LabelPair
|
|
|
|
Timestamps []int64
|
|
|
|
Values []float64
|
|
|
|
}
|
|
|
|
|
2021-01-31 23:31:25 +00:00
|
|
|
// LabelPair represents a label
|
2021-01-31 23:10:16 +00:00
|
|
|
type LabelPair struct {
|
|
|
|
Name string
|
|
|
|
Value string
|
|
|
|
}
|
|
|
|
|
2021-01-31 23:31:25 +00:00
|
|
|
// String returns user-readable ts.
|
2021-01-31 23:10:16 +00:00
|
|
|
func (ts TimeSeries) String() string {
|
|
|
|
s := ts.Name
|
|
|
|
if len(ts.LabelPairs) < 1 {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
var labels string
|
|
|
|
for i, lp := range ts.LabelPairs {
|
|
|
|
labels += fmt.Sprintf("%s=%q", lp.Name, lp.Value)
|
|
|
|
if i < len(ts.LabelPairs)-1 {
|
|
|
|
labels += ","
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%s{%s}", s, labels)
|
|
|
|
}
|
|
|
|
|
|
|
|
// cWriter used to avoid error checking
|
|
|
|
// while doing Write calls.
|
|
|
|
// cWriter caches the first error if any
|
|
|
|
// and discards all sequential write calls
|
|
|
|
type cWriter struct {
|
|
|
|
w io.Writer
|
|
|
|
n int
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cw *cWriter) printf(format string, args ...interface{}) {
|
|
|
|
if cw.err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
n, err := fmt.Fprintf(cw.w, format, args...)
|
|
|
|
cw.n += n
|
|
|
|
cw.err = err
|
|
|
|
}
|
|
|
|
|
2022-07-11 16:21:59 +00:00
|
|
|
// "{"metric":{"__name__":"cpu_usage_guest","arch":"x64","hostname":"host_19",},"timestamps":[1567296000000,1567296010000],"values":[1567296000000,66]}
|
2021-01-31 23:10:16 +00:00
|
|
|
func (ts *TimeSeries) write(w io.Writer) (int, error) {
|
2021-06-18 12:26:47 +00:00
|
|
|
timestamps := ts.Timestamps
|
|
|
|
values := ts.Values
|
2021-01-31 23:10:16 +00:00
|
|
|
cw := &cWriter{w: w}
|
2021-06-18 12:26:47 +00:00
|
|
|
for len(timestamps) > 0 {
|
|
|
|
// Split long lines with more than 10K samples into multiple JSON lines.
|
|
|
|
// This should limit memory usage at VictoriaMetrics during data ingestion,
|
|
|
|
// since it allocates memory for the whole JSON line and processes it in one go.
|
|
|
|
batchSize := 10000
|
|
|
|
if batchSize > len(timestamps) {
|
|
|
|
batchSize = len(timestamps)
|
|
|
|
}
|
|
|
|
timestampsBatch := timestamps[:batchSize]
|
|
|
|
valuesBatch := values[:batchSize]
|
|
|
|
timestamps = timestamps[batchSize:]
|
|
|
|
values = values[batchSize:]
|
|
|
|
|
|
|
|
cw.printf(`{"metric":{"__name__":%q`, ts.Name)
|
2021-01-31 23:10:16 +00:00
|
|
|
for _, lp := range ts.LabelPairs {
|
|
|
|
cw.printf(",%q:%q", lp.Name, lp.Value)
|
|
|
|
}
|
|
|
|
|
2021-06-18 12:26:47 +00:00
|
|
|
pointsCount := len(timestampsBatch)
|
|
|
|
cw.printf(`},"timestamps":[`)
|
|
|
|
for i := 0; i < pointsCount-1; i++ {
|
|
|
|
cw.printf(`%d,`, timestampsBatch[i])
|
|
|
|
}
|
|
|
|
cw.printf(`%d],"values":[`, timestampsBatch[pointsCount-1])
|
|
|
|
for i := 0; i < pointsCount-1; i++ {
|
|
|
|
cw.printf(`%v,`, valuesBatch[i])
|
|
|
|
}
|
|
|
|
cw.printf("%v]}\n", valuesBatch[pointsCount-1])
|
2021-01-31 23:10:16 +00:00
|
|
|
}
|
|
|
|
return cw.n, cw.err
|
|
|
|
}
|