diff --git a/model/labelset.go b/model/labelset.go index 6eda08a73..b12ebcb63 100644 --- a/model/labelset.go +++ b/model/labelset.go @@ -18,6 +18,8 @@ import ( "fmt" "sort" "strings" + + "github.com/mailru/easyjson/jwriter" ) // A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet @@ -150,6 +152,29 @@ func (ls LabelSet) FastFingerprint() Fingerprint { return labelSetToFastFingerprint(ls) } +func (l LabelSet) MarshalEasyJSON(w *jwriter.Writer) { + w.RawByte('{') + first := true + for k, v := range l { + if !first { + w.RawByte(',') + } else { + first = false + } + w.RawString(`"` + string(k) + `"`) + w.RawByte(':') + w.RawString(`"` + string(v) + `"`) + } + w.RawByte('}') +} + +// MarshalJSON implements the json.Marshaler interface. +func (l LabelSet) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + l.MarshalEasyJSON(&w) + return w.Buffer.BuildBytes(), w.Error +} + // UnmarshalJSON implements the json.Unmarshaler interface. func (l *LabelSet) UnmarshalJSON(b []byte) error { var m map[LabelName]LabelValue diff --git a/model/metric.go b/model/metric.go index f7250909b..4318d0111 100644 --- a/model/metric.go +++ b/model/metric.go @@ -18,6 +18,8 @@ import ( "regexp" "sort" "strings" + + "github.com/mailru/easyjson/jwriter" ) var ( @@ -87,6 +89,29 @@ func (m Metric) FastFingerprint() Fingerprint { return LabelSet(m).FastFingerprint() } +func (m Metric) MarshalEasyJSON(w *jwriter.Writer) { + w.RawByte('{') + first := true + for k, v := range m { + if !first { + w.RawByte(',') + } else { + first = false + } + w.RawString(`"` + string(k) + `"`) + w.RawByte(':') + w.RawString(`"` + string(v) + `"`) + } + w.RawByte('}') +} + +// MarshalJSON implements the json.Marshaler interface. +func (m Metric) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + m.MarshalEasyJSON(&w) + return w.Buffer.BuildBytes(), w.Error +} + // IsValidMetricName returns true iff name matches the pattern of MetricNameRE. // This function, however, does not use MetricNameRE for the check but a much // faster hardcoded implementation. diff --git a/model/time.go b/model/time.go index 74ed5a9f7..03f649ca7 100644 --- a/model/time.go +++ b/model/time.go @@ -20,6 +20,8 @@ import ( "strconv" "strings" "time" + + "github.com/mailru/easyjson/jwriter" ) const ( @@ -39,6 +41,9 @@ const ( Latest = Time(math.MaxInt64) ) +// How many digits the second variable is (needed for formatting to string) +var secondDigitLen = len(strconv.FormatInt(second, 10)) - 1 + // Time is the number of milliseconds since the epoch // (1970-01-01 00:00 UTC) excluding leap seconds. type Time int64 @@ -112,12 +117,53 @@ var dotPrecision = int(math.Log10(float64(second))) // String returns a string representation of the Time. func (t Time) String() string { - return strconv.FormatFloat(float64(t)/float64(second), 'f', -1, 64) + timeStr := strconv.FormatInt(int64(t), 10) + lenDelta := secondDigitLen - len(timeStr) + + retString := "" + + // Put out anything before a decimal + if len(timeStr) > secondDigitLen { + retString = timeStr[:len(timeStr)-secondDigitLen] + } + + // put the decimal there + retString += "." + + // pad (if needed) + if lenDelta > 0 { + retString += strings.Repeat("0", lenDelta) + timeStr + } else { + retString += timeStr[len(timeStr)-secondDigitLen:] + } + return retString +} + +func (t Time) MarshalEasyJSON(w *jwriter.Writer) { + timeStr := strconv.FormatInt(int64(t), 10) + lenDelta := secondDigitLen - len(timeStr) + + // Put out anything before a decimal + if len(timeStr) > secondDigitLen { + w.RawString(timeStr[:len(timeStr)-secondDigitLen]) + } + + // put the decimal there + w.RawByte('.') + + // pad (if needed) + if lenDelta > 0 { + w.RawString(strings.Repeat("0", lenDelta) + timeStr) + } else { + w.RawString(timeStr[len(timeStr)-secondDigitLen:]) + } } // MarshalJSON implements the json.Marshaler interface. func (t Time) MarshalJSON() ([]byte, error) { - return []byte(t.String()), nil + w := jwriter.Writer{} + t.MarshalEasyJSON(&w) + return w.Buffer.BuildBytes(), w.Error } // UnmarshalJSON implements the json.Unmarshaler interface. diff --git a/model/time_test.go b/model/time_test.go index 3efdd65ff..e6be9889b 100644 --- a/model/time_test.go +++ b/model/time_test.go @@ -130,3 +130,77 @@ func TestParseDuration(t *testing.T) { } } } + +func TestTimeString(t *testing.T) { + var cases = []struct { + in Time + out string + }{ + { + in: Time(1), + out: ".001", + }, + { + in: Time(10), + out: ".010", + }, + { + in: Time(100), + out: ".100", + }, + { + in: Time(10000), + out: "10.000", + }, + { + in: Time(1000000), + out: "1000.000", + }, + } + + for _, c := range cases { + actual := c.in.String() + if actual != c.out { + t.Fatalf("Mismatched time output expected=%s actual=%s", c.out, actual) + } + } +} + +func TestTimeJson(t *testing.T) { + var cases = []struct { + in Time + out string + }{ + { + in: Time(1), + out: ".001", + }, + { + in: Time(10), + out: ".010", + }, + { + in: Time(100), + out: ".100", + }, + { + in: Time(10000), + out: "10.000", + }, + { + in: Time(1000000), + out: "1000.000", + }, + } + + for _, c := range cases { + b, err := c.in.MarshalJSON() + if err != nil { + t.Fatalf("Error marshaling time: %v", err) + } + actual := string(b) + if actual != c.out { + t.Fatalf("Mismatched time output expected=%s actual=%s", c.out, actual) + } + } +} diff --git a/model/value.go b/model/value.go index c9ed3ffd8..795604271 100644 --- a/model/value.go +++ b/model/value.go @@ -20,6 +20,8 @@ import ( "sort" "strconv" "strings" + + "github.com/mailru/easyjson/jwriter" ) var ( @@ -42,9 +44,18 @@ var ( // time. type SampleValue float64 +func (v SampleValue) MarshalEasyJSON(w *jwriter.Writer) { + w.RawByte('"') + w.Buffer.EnsureSpace(20) + w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(v), 'f', -1, 64) + w.RawByte('"') +} + // MarshalJSON implements json.Marshaler. func (v SampleValue) MarshalJSON() ([]byte, error) { - return json.Marshal(v.String()) + w := jwriter.Writer{} + v.MarshalEasyJSON(&w) + return w.Buffer.BuildBytes(), w.Error } // UnmarshalJSON implements json.Unmarshaler. @@ -80,17 +91,19 @@ type SamplePair struct { Value SampleValue } +func (s SamplePair) MarshalEasyJSON(w *jwriter.Writer) { + w.RawByte('[') + s.Timestamp.MarshalEasyJSON(w) + w.RawByte(',') + s.Value.MarshalEasyJSON(w) + w.RawByte(']') +} + // MarshalJSON implements json.Marshaler. func (s SamplePair) MarshalJSON() ([]byte, error) { - t, err := json.Marshal(s.Timestamp) - if err != nil { - return nil, err - } - v, err := json.Marshal(s.Value) - if err != nil { - return nil, err - } - return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil + w := jwriter.Writer{} + s.MarshalEasyJSON(&w) + return w.Buffer.BuildBytes(), w.Error } // UnmarshalJSON implements json.Unmarshaler. @@ -220,6 +233,7 @@ func (s Samples) Equal(o Samples) bool { } // SampleStream is a stream of Values belonging to an attached COWMetric. +//easyjson:json type SampleStream struct { Metric Metric `json:"metric"` Values []SamplePair `json:"values"` @@ -303,17 +317,21 @@ type Scalar struct { Timestamp Time `json:"timestamp"` } -func (s Scalar) String() string { - return fmt.Sprintf("scalar: %v @[%v]", s.Value, s.Timestamp) +func (s Scalar) MarshalEasyJSON(w *jwriter.Writer) { + w.RawByte('[') + s.Timestamp.MarshalEasyJSON(w) + w.RawByte(',') + s.Value.MarshalEasyJSON(w) + w.RawByte(']') } // MarshalJSON implements json.Marshaler. func (s Scalar) MarshalJSON() ([]byte, error) { - v := strconv.FormatFloat(float64(s.Value), 'f', -1, 64) - return json.Marshal([...]interface{}{s.Timestamp, string(v)}) + w := jwriter.Writer{} + s.MarshalEasyJSON(&w) + return w.Buffer.BuildBytes(), w.Error } -// UnmarshalJSON implements json.Unmarshaler. func (s *Scalar) UnmarshalJSON(b []byte) error { var f string v := [...]interface{}{&s.Timestamp, &f} @@ -330,6 +348,10 @@ func (s *Scalar) UnmarshalJSON(b []byte) error { return nil } +func (s Scalar) String() string { + return fmt.Sprintf("scalar: %v @[%v]", s.Value, s.Timestamp) +} + // String is a string value evaluated at the set timestamp. type String struct { Value string `json:"value"` @@ -353,6 +375,7 @@ func (s *String) UnmarshalJSON(b []byte) error { // Vector is basically only an alias for Samples, but the // contract is that in a Vector, all Samples have the same timestamp. +//easyjson:json type Vector []*Sample func (vec Vector) String() string { @@ -395,6 +418,7 @@ func (vec Vector) Equal(o Vector) bool { } // Matrix is a list of time series. +//easyjson:json type Matrix []*SampleStream func (m Matrix) Len() int { return len(m) } diff --git a/model/value_bench_test.go b/model/value_bench_test.go new file mode 100644 index 000000000..d3b68f782 --- /dev/null +++ b/model/value_bench_test.go @@ -0,0 +1,104 @@ +package model + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "reflect" + "strconv" + "testing" + "time" + + "github.com/mailru/easyjson" +) + +func generateData(timeseries, datapoints int) Matrix { + // Create the top-level matrix + m := make(Matrix, 0) + + for i := 0; i < timeseries; i++ { + lset := map[LabelName]LabelValue{ + MetricNameLabel: LabelValue("timeseries_" + strconv.Itoa(i)), + } + + now := Now() + + values := make([]SamplePair, datapoints) + + for x := datapoints; x > 0; x-- { + values[x-1] = SamplePair{ + Timestamp: now.Add(time.Second * -15 * time.Duration(x)), // Set the time back assuming a 15s interval + Value: SampleValue(float64(x)), + } + } + + ss := &SampleStream{ + Metric: Metric(lset), + Values: values, + } + + m = append(m, ss) + } + return m +} + +var benchBytes []byte + +func doMarshal(b *testing.B, v interface{}) { + b.Run("encoding/json", func(b *testing.B) { + for n := 0; n < b.N; n++ { + benchBytes, _ = json.Marshal(v) + } + }) + + b.Run("easyjson", func(b *testing.B) { + m, ok := v.(easyjson.Marshaler) + if !ok { + t := reflect.TypeOf(v) + fmt.Println(t, "not easyjson.Marshaler") + b.Skip() + } + + b.ResetTimer() + + for n := 0; n < b.N; n++ { + easyjson.MarshalToWriter(m, ioutil.Discard) + } + }) +} + +func BenchmarkMarshal(b *testing.B) { + NUM_TIMESERIES := 500 + NUM_DATAPOINTS := 100 + m := generateData(NUM_TIMESERIES, NUM_DATAPOINTS) + + // Single float64 + b.Run("SampleValue", func(b *testing.B) { + doMarshal(b, m[0].Values[0].Value) + }) + + // Single timestamp + b.Run("Timestamp", func(b *testing.B) { + doMarshal(b, m[0].Values[0].Timestamp) + }) + + // Single pair of value and timestamp + b.Run("SamplePair", func(b *testing.B) { + doMarshal(b, m[0].Values[0]) + }) + + // Labelset for the metrics + b.Run("labelset", func(b *testing.B) { + doMarshal(b, m[0].Metric) + }) + + // labelset + []SamplePair + b.Run("SampleStream", func(b *testing.B) { + doMarshal(b, m[0]) + }) + + // labelset + []SamplePair + b.Run("Matrix", func(b *testing.B) { + doMarshal(b, m) + }) +} diff --git a/model/value_easyjson.go b/model/value_easyjson.go new file mode 100644 index 000000000..73d286932 --- /dev/null +++ b/model/value_easyjson.go @@ -0,0 +1,305 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package model + +import ( + json "encoding/json" + + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjsonC5568b25DecodeGithubComPrometheusCommonModel(in *jlexer.Lexer, out *Vector) { + isTopLevel := in.IsStart() + if in.IsNull() { + in.Skip() + *out = nil + } else { + in.Delim('[') + if *out == nil { + if !in.IsDelim(']') { + *out = make(Vector, 0, 8) + } else { + *out = Vector{} + } + } else { + *out = (*out)[:0] + } + for !in.IsDelim(']') { + var v1 *Sample + if in.IsNull() { + in.Skip() + v1 = nil + } else { + if v1 == nil { + v1 = new(Sample) + } + if data := in.Raw(); in.Ok() { + in.AddError((*v1).UnmarshalJSON(data)) + } + } + *out = append(*out, v1) + in.WantComma() + } + in.Delim(']') + } + if isTopLevel { + in.Consumed() + } +} +func easyjsonC5568b25EncodeGithubComPrometheusCommonModel(out *jwriter.Writer, in Vector) { + if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v2, v3 := range in { + if v2 > 0 { + out.RawByte(',') + } + if v3 == nil { + out.RawString("null") + } else { + out.Raw((*v3).MarshalJSON()) + } + } + out.RawByte(']') + } +} + +// MarshalJSON supports json.Marshaler interface +func (v Vector) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC5568b25EncodeGithubComPrometheusCommonModel(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v Vector) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC5568b25EncodeGithubComPrometheusCommonModel(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *Vector) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC5568b25DecodeGithubComPrometheusCommonModel(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *Vector) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC5568b25DecodeGithubComPrometheusCommonModel(l, v) +} +func easyjsonC5568b25DecodeGithubComPrometheusCommonModel1(in *jlexer.Lexer, out *SampleStream) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "metric": + if in.IsNull() { + in.Skip() + } else { + in.Delim('{') + if !in.IsDelim('}') { + out.Metric = make(Metric) + } else { + out.Metric = nil + } + for !in.IsDelim('}') { + key := LabelName(in.String()) + in.WantColon() + var v4 LabelValue + v4 = LabelValue(in.String()) + (out.Metric)[key] = v4 + in.WantComma() + } + in.Delim('}') + } + case "values": + if in.IsNull() { + in.Skip() + out.Values = nil + } else { + in.Delim('[') + if out.Values == nil { + if !in.IsDelim(']') { + out.Values = make([]SamplePair, 0, 4) + } else { + out.Values = []SamplePair{} + } + } else { + out.Values = (out.Values)[:0] + } + for !in.IsDelim(']') { + var v5 SamplePair + if data := in.Raw(); in.Ok() { + in.AddError((v5).UnmarshalJSON(data)) + } + out.Values = append(out.Values, v5) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonC5568b25EncodeGithubComPrometheusCommonModel1(out *jwriter.Writer, in SampleStream) { + out.RawByte('{') + first := true + _ = first + if !first { + out.RawByte(',') + } + first = false + out.RawString("\"metric\":") + (in.Metric).MarshalEasyJSON(out) + if !first { + out.RawByte(',') + } + first = false + out.RawString("\"values\":") + if in.Values == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v6, v7 := range in.Values { + if v6 > 0 { + out.RawByte(',') + } + (v7).MarshalEasyJSON(out) + } + out.RawByte(']') + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v SampleStream) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC5568b25EncodeGithubComPrometheusCommonModel1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v SampleStream) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC5568b25EncodeGithubComPrometheusCommonModel1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *SampleStream) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC5568b25DecodeGithubComPrometheusCommonModel1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *SampleStream) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC5568b25DecodeGithubComPrometheusCommonModel1(l, v) +} +func easyjsonC5568b25DecodeGithubComPrometheusCommonModel2(in *jlexer.Lexer, out *Matrix) { + isTopLevel := in.IsStart() + if in.IsNull() { + in.Skip() + *out = nil + } else { + in.Delim('[') + if *out == nil { + if !in.IsDelim(']') { + *out = make(Matrix, 0, 8) + } else { + *out = Matrix{} + } + } else { + *out = (*out)[:0] + } + for !in.IsDelim(']') { + var v8 *SampleStream + if in.IsNull() { + in.Skip() + v8 = nil + } else { + if v8 == nil { + v8 = new(SampleStream) + } + (*v8).UnmarshalEasyJSON(in) + } + *out = append(*out, v8) + in.WantComma() + } + in.Delim(']') + } + if isTopLevel { + in.Consumed() + } +} +func easyjsonC5568b25EncodeGithubComPrometheusCommonModel2(out *jwriter.Writer, in Matrix) { + if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v9, v10 := range in { + if v9 > 0 { + out.RawByte(',') + } + if v10 == nil { + out.RawString("null") + } else { + (*v10).MarshalEasyJSON(out) + } + } + out.RawByte(']') + } +} + +// MarshalJSON supports json.Marshaler interface +func (v Matrix) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjsonC5568b25EncodeGithubComPrometheusCommonModel2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v Matrix) MarshalEasyJSON(w *jwriter.Writer) { + easyjsonC5568b25EncodeGithubComPrometheusCommonModel2(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *Matrix) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjsonC5568b25DecodeGithubComPrometheusCommonModel2(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *Matrix) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonC5568b25DecodeGithubComPrometheusCommonModel2(l, v) +}