| 178 | } |
| 179 | |
| 180 | func (m *withExemplarsMetric) Write(pb *dto.Metric) error { |
| 181 | if err := m.Metric.Write(pb); err != nil { |
| 182 | return err |
| 183 | } |
| 184 | |
| 185 | switch { |
| 186 | case pb.Counter != nil: |
| 187 | pb.Counter.Exemplar = m.exemplars[len(m.exemplars)-1] |
| 188 | case pb.Histogram != nil: |
| 189 | h := pb.Histogram |
| 190 | for _, e := range m.exemplars { |
| 191 | if (h.GetZeroThreshold() != 0 || h.GetZeroCount() != 0 || |
| 192 | len(h.PositiveSpan) != 0 || len(h.NegativeSpan) != 0) && |
| 193 | e.GetTimestamp() != nil { |
| 194 | h.Exemplars = append(h.Exemplars, e) |
| 195 | if len(h.Bucket) == 0 { |
| 196 | // Don't proceed to classic buckets if there are none. |
| 197 | continue |
| 198 | } |
| 199 | } |
| 200 | // h.Bucket are sorted by UpperBound. |
| 201 | i := sort.Search(len(h.Bucket), func(i int) bool { |
| 202 | return h.Bucket[i].GetUpperBound() >= e.GetValue() |
| 203 | }) |
| 204 | if i < len(h.Bucket) { |
| 205 | h.Bucket[i].Exemplar = e |
| 206 | } else { |
| 207 | // The +Inf bucket should be explicitly added if there is an exemplar for it, similar to non-const histogram logic in https://github.com/prometheus/client_golang/blob/main/prometheus/histogram.go#L357-L365. |
| 208 | b := &dto.Bucket{ |
| 209 | CumulativeCount: proto.Uint64(h.GetSampleCount()), |
| 210 | UpperBound: proto.Float64(math.Inf(1)), |
| 211 | Exemplar: e, |
| 212 | } |
| 213 | h.Bucket = append(h.Bucket, b) |
| 214 | } |
| 215 | } |
| 216 | default: |
| 217 | // TODO(bwplotka): Implement Gauge? |
| 218 | return errors.New("cannot inject exemplar into Gauge, Summary or Untyped") |
| 219 | } |
| 220 | |
| 221 | return nil |
| 222 | } |
| 223 | |
| 224 | // Exemplar is easier to use, user-facing representation of *dto.Exemplar. |
| 225 | type Exemplar struct { |