Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exemplar support for const histogram #986

Merged
merged 7 commits into from Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 59 additions & 1 deletion prometheus/examples_test.go
Expand Up @@ -22,6 +22,8 @@ import (
"strings"
"time"

"google.golang.org/protobuf/types/known/timestamppb"

//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto"
"github.com/prometheus/common/expfmt"
Expand Down Expand Up @@ -549,11 +551,27 @@ func ExampleNewConstHistogram() {
prometheus.Labels{"owner": "example"},
)

var exemplars []*dto.Exemplar
n := "testName"
v := "testVal"
lp := dto.LabelPair{Name: &n, Value: &v}
var labelPairs []*dto.LabelPair
labelPairs = append(labelPairs, &lp)
vals := []float64{24.0, 42.0, 89.0, 157.0}
t, _ := time.Parse("unix", "Mon Jan _2 15:04:05 MST 2006")
ts := timestamppb.New(t)

for i := 0; i < 4; i++ {
e := dto.Exemplar{Label: labelPairs, Value: &vals[i], Timestamp: ts}
exemplars = append(exemplars, &e)
}

// Create a constant histogram from values we got from a 3rd party telemetry system.
h := prometheus.MustNewConstHistogram(
h := prometheus.MustNewConstHistogramWithExemplar(
desc,
4711, 403.34,
map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233},
exemplars,
"200", "get",
)

Expand Down Expand Up @@ -583,18 +601,58 @@ func ExampleNewConstHistogram() {
// bucket: <
// cumulative_count: 121
// upper_bound: 25
// exemplar: <
// label: <
// name: "testName"
// value: "testVal"
// >
// value: 24
// timestamp: <
// seconds: -62135596800
// >
// >
// >
// bucket: <
// cumulative_count: 2403
// upper_bound: 50
// exemplar: <
// label: <
// name: "testName"
// value: "testVal"
// >
// value: 42
// timestamp: <
// seconds: -62135596800
// >
// >
// >
// bucket: <
// cumulative_count: 3221
// upper_bound: 100
// exemplar: <
// label: <
// name: "testName"
// value: "testVal"
// >
// value: 89
// timestamp: <
// seconds: -62135596800
// >
// >
// >
// bucket: <
// cumulative_count: 4233
// upper_bound: 200
// exemplar: <
// label: <
// name: "testName"
// value: "testVal"
// >
// value: 157
// timestamp: <
// seconds: -62135596800
// >
// >
// >
// >
}
Expand Down
63 changes: 62 additions & 1 deletion prometheus/histogram.go
Expand Up @@ -573,6 +573,7 @@ type constHistogram struct {
sum float64
buckets map[float64]uint64
labelPairs []*dto.LabelPair
exemplars []*dto.Exemplar
}

func (h *constHistogram) Desc() *Desc {
Expand All @@ -581,21 +582,39 @@ func (h *constHistogram) Desc() *Desc {

func (h *constHistogram) Write(out *dto.Metric) error {
his := &dto.Histogram{}
// h.buckets, buckets and bounds are all the same length
buckets := make([]*dto.Bucket, 0, len(h.buckets))
bounds := make([]float64, 0, len(h.buckets))

his.SampleCount = proto.Uint64(h.count)
his.SampleSum = proto.Float64(h.sum)

for upperBound, count := range h.buckets {
buckets = append(buckets, &dto.Bucket{
CumulativeCount: proto.Uint64(count),
UpperBound: proto.Float64(upperBound),
})
bounds = append(bounds, upperBound)
}

// make sure that both bounds and buckets have the same ordering
if len(buckets) > 0 {
sort.Sort(buckSort(buckets))
sort.Float64s(bounds)
}

if len(h.exemplars) > 0 {
r := len(buckets)
l := len(h.exemplars)
for i := 0; i < r && i < l; i++ {
bound := sort.SearchFloat64s(bounds, *h.exemplars[i].Value)
// Only append the exemplar if it's within the bounds defined in the
// buckets.
if bound < r {
buckets[bound].Exemplar = h.exemplars[i]
}
}
}

his.Bucket = buckets

out.Histogram = his
Expand All @@ -617,6 +636,7 @@ func (h *constHistogram) Write(out *dto.Metric) error {
//
// NewConstHistogram returns an error if the length of labelValues is not
// consistent with the variable labels in Desc or if Desc is invalid.

func NewConstHistogram(
desc *Desc,
count uint64,
Expand Down Expand Up @@ -655,6 +675,47 @@ func MustNewConstHistogram(
return m
}

func NewConstHistogramWithExemplar(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use NewConstHistogramWithExemplar in NewConstHistogram for less code repetition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, did you mean to use NewConstHistogram inside NewConstHistogramWithExemplar? My goal was to keep the existing API unchanged but whatever you folks prefer is fine by me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, opposite. I think you misunderstood, let me show in PR to your PR 🙃

desc *Desc,
count uint64,
sum float64,
buckets map[float64]uint64,
exemplars []*dto.Exemplar,
labelValues ...string,

) (Metric, error) {
if desc.err != nil {
return nil, desc.err
}
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
return nil, err
}

h, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
if err != nil {
return nil, err
}

h.(*constHistogram).exemplars = exemplars

return h, nil
}

// MustNewConstHistogram is a version of NewConstHistogram that panics where
// NewConstHistogram would have returned an error.
func MustNewConstHistogramWithExemplar(
desc *Desc,
count uint64,
sum float64,
buckets map[float64]uint64,
exemplars []*dto.Exemplar,
labelValues ...string,
) Metric {
h := MustNewConstHistogram(desc, count, sum, buckets, labelValues...)
h.(*constHistogram).exemplars = exemplars
return h
}

type buckSort []*dto.Bucket

func (s buckSort) Len() int {
Expand Down
12 changes: 6 additions & 6 deletions prometheus/histogram_test.go
Expand Up @@ -424,24 +424,24 @@ func TestHistogramExemplar(t *testing.T) {
}
expectedExemplars := []*dto.Exemplar{
nil,
&dto.Exemplar{
{
Label: []*dto.LabelPair{
&dto.LabelPair{Name: proto.String("id"), Value: proto.String("2")},
{Name: proto.String("id"), Value: proto.String("2")},
},
Value: proto.Float64(1.6),
Timestamp: ts,
},
nil,
&dto.Exemplar{
{
Label: []*dto.LabelPair{
&dto.LabelPair{Name: proto.String("id"), Value: proto.String("3")},
{Name: proto.String("id"), Value: proto.String("3")},
},
Value: proto.Float64(4),
Timestamp: ts,
},
&dto.Exemplar{
{
Label: []*dto.LabelPair{
&dto.LabelPair{Name: proto.String("id"), Value: proto.String("4")},
{Name: proto.String("id"), Value: proto.String("4")},
},
Value: proto.Float64(4.5),
Timestamp: ts,
Expand Down