Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

745 wiersze
24 KiB

  1. // Copyright 2014 The Prometheus Authors
  2. // Licensed under the Apache License, Version 2.0 (the "License");
  3. // you may not use this file except in compliance with the License.
  4. // You may obtain a copy of the License at
  5. //
  6. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. package prometheus
  14. import (
  15. "fmt"
  16. "math"
  17. "runtime"
  18. "sort"
  19. "sync"
  20. "sync/atomic"
  21. "time"
  22. "github.com/beorn7/perks/quantile"
  23. //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
  24. "github.com/golang/protobuf/proto"
  25. dto "github.com/prometheus/client_model/go"
  26. )
  27. // quantileLabel is used for the label that defines the quantile in a
  28. // summary.
  29. const quantileLabel = "quantile"
  30. // A Summary captures individual observations from an event or sample stream and
  31. // summarizes them in a manner similar to traditional summary statistics: 1. sum
  32. // of observations, 2. observation count, 3. rank estimations.
  33. //
  34. // A typical use-case is the observation of request latencies. By default, a
  35. // Summary provides the median, the 90th and the 99th percentile of the latency
  36. // as rank estimations. However, the default behavior will change in the
  37. // upcoming v1.0.0 of the library. There will be no rank estimations at all by
  38. // default. For a sane transition, it is recommended to set the desired rank
  39. // estimations explicitly.
  40. //
  41. // Note that the rank estimations cannot be aggregated in a meaningful way with
  42. // the Prometheus query language (i.e. you cannot average or add them). If you
  43. // need aggregatable quantiles (e.g. you want the 99th percentile latency of all
  44. // queries served across all instances of a service), consider the Histogram
  45. // metric type. See the Prometheus documentation for more details.
  46. //
  47. // To create Summary instances, use NewSummary.
  48. type Summary interface {
  49. Metric
  50. Collector
  51. // Observe adds a single observation to the summary. Observations are
  52. // usually positive or zero. Negative observations are accepted but
  53. // prevent current versions of Prometheus from properly detecting
  54. // counter resets in the sum of observations. See
  55. // https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations
  56. // for details.
  57. Observe(float64)
  58. }
  59. var errQuantileLabelNotAllowed = fmt.Errorf(
  60. "%q is not allowed as label name in summaries", quantileLabel,
  61. )
  62. // Default values for SummaryOpts.
  63. const (
  64. // DefMaxAge is the default duration for which observations stay
  65. // relevant.
  66. DefMaxAge time.Duration = 10 * time.Minute
  67. // DefAgeBuckets is the default number of buckets used to calculate the
  68. // age of observations.
  69. DefAgeBuckets = 5
  70. // DefBufCap is the standard buffer size for collecting Summary observations.
  71. DefBufCap = 500
  72. )
  73. // SummaryOpts bundles the options for creating a Summary metric. It is
  74. // mandatory to set Name to a non-empty string. While all other fields are
  75. // optional and can safely be left at their zero value, it is recommended to set
  76. // a help string and to explicitly set the Objectives field to the desired value
  77. // as the default value will change in the upcoming v1.0.0 of the library.
  78. type SummaryOpts struct {
  79. // Namespace, Subsystem, and Name are components of the fully-qualified
  80. // name of the Summary (created by joining these components with
  81. // "_"). Only Name is mandatory, the others merely help structuring the
  82. // name. Note that the fully-qualified name of the Summary must be a
  83. // valid Prometheus metric name.
  84. Namespace string
  85. Subsystem string
  86. Name string
  87. // Help provides information about this Summary.
  88. //
  89. // Metrics with the same fully-qualified name must have the same Help
  90. // string.
  91. Help string
  92. // ConstLabels are used to attach fixed labels to this metric. Metrics
  93. // with the same fully-qualified name must have the same label names in
  94. // their ConstLabels.
  95. //
  96. // Due to the way a Summary is represented in the Prometheus text format
  97. // and how it is handled by the Prometheus server internally, “quantile”
  98. // is an illegal label name. Construction of a Summary or SummaryVec
  99. // will panic if this label name is used in ConstLabels.
  100. //
  101. // ConstLabels are only used rarely. In particular, do not use them to
  102. // attach the same labels to all your metrics. Those use cases are
  103. // better covered by target labels set by the scraping Prometheus
  104. // server, or by one specific metric (e.g. a build_info or a
  105. // machine_role metric). See also
  106. // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels
  107. ConstLabels Labels
  108. // Objectives defines the quantile rank estimates with their respective
  109. // absolute error. If Objectives[q] = e, then the value reported for q
  110. // will be the φ-quantile value for some φ between q-e and q+e. The
  111. // default value is an empty map, resulting in a summary without
  112. // quantiles.
  113. Objectives map[float64]float64
  114. // MaxAge defines the duration for which an observation stays relevant
  115. // for the summary. Only applies to pre-calculated quantiles, does not
  116. // apply to _sum and _count. Must be positive. The default value is
  117. // DefMaxAge.
  118. MaxAge time.Duration
  119. // AgeBuckets is the number of buckets used to exclude observations that
  120. // are older than MaxAge from the summary. A higher number has a
  121. // resource penalty, so only increase it if the higher resolution is
  122. // really required. For very high observation rates, you might want to
  123. // reduce the number of age buckets. With only one age bucket, you will
  124. // effectively see a complete reset of the summary each time MaxAge has
  125. // passed. The default value is DefAgeBuckets.
  126. AgeBuckets uint32
  127. // BufCap defines the default sample stream buffer size. The default
  128. // value of DefBufCap should suffice for most uses. If there is a need
  129. // to increase the value, a multiple of 500 is recommended (because that
  130. // is the internal buffer size of the underlying package
  131. // "github.com/bmizerany/perks/quantile").
  132. BufCap uint32
  133. }
  134. // Problem with the sliding-window decay algorithm... The Merge method of
  135. // perk/quantile is actually not working as advertised - and it might be
  136. // unfixable, as the underlying algorithm is apparently not capable of merging
  137. // summaries in the first place. To avoid using Merge, we are currently adding
  138. // observations to _each_ age bucket, i.e. the effort to add a sample is
  139. // essentially multiplied by the number of age buckets. When rotating age
  140. // buckets, we empty the previous head stream. On scrape time, we simply take
  141. // the quantiles from the head stream (no merging required). Result: More effort
  142. // on observation time, less effort on scrape time, which is exactly the
  143. // opposite of what we try to accomplish, but at least the results are correct.
  144. //
  145. // The quite elegant previous contraption to merge the age buckets efficiently
  146. // on scrape time (see code up commit 6b9530d72ea715f0ba612c0120e6e09fbf1d49d0)
  147. // can't be used anymore.
  148. // NewSummary creates a new Summary based on the provided SummaryOpts.
  149. func NewSummary(opts SummaryOpts) Summary {
  150. return newSummary(
  151. NewDesc(
  152. BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
  153. opts.Help,
  154. nil,
  155. opts.ConstLabels,
  156. ),
  157. opts,
  158. )
  159. }
  160. func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
  161. if len(desc.variableLabels) != len(labelValues) {
  162. panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
  163. }
  164. for _, n := range desc.variableLabels {
  165. if n == quantileLabel {
  166. panic(errQuantileLabelNotAllowed)
  167. }
  168. }
  169. for _, lp := range desc.constLabelPairs {
  170. if lp.GetName() == quantileLabel {
  171. panic(errQuantileLabelNotAllowed)
  172. }
  173. }
  174. if opts.Objectives == nil {
  175. opts.Objectives = map[float64]float64{}
  176. }
  177. if opts.MaxAge < 0 {
  178. panic(fmt.Errorf("illegal max age MaxAge=%v", opts.MaxAge))
  179. }
  180. if opts.MaxAge == 0 {
  181. opts.MaxAge = DefMaxAge
  182. }
  183. if opts.AgeBuckets == 0 {
  184. opts.AgeBuckets = DefAgeBuckets
  185. }
  186. if opts.BufCap == 0 {
  187. opts.BufCap = DefBufCap
  188. }
  189. if len(opts.Objectives) == 0 {
  190. // Use the lock-free implementation of a Summary without objectives.
  191. s := &noObjectivesSummary{
  192. desc: desc,
  193. labelPairs: MakeLabelPairs(desc, labelValues),
  194. counts: [2]*summaryCounts{{}, {}},
  195. }
  196. s.init(s) // Init self-collection.
  197. return s
  198. }
  199. s := &summary{
  200. desc: desc,
  201. objectives: opts.Objectives,
  202. sortedObjectives: make([]float64, 0, len(opts.Objectives)),
  203. labelPairs: MakeLabelPairs(desc, labelValues),
  204. hotBuf: make([]float64, 0, opts.BufCap),
  205. coldBuf: make([]float64, 0, opts.BufCap),
  206. streamDuration: opts.MaxAge / time.Duration(opts.AgeBuckets),
  207. }
  208. s.headStreamExpTime = time.Now().Add(s.streamDuration)
  209. s.hotBufExpTime = s.headStreamExpTime
  210. for i := uint32(0); i < opts.AgeBuckets; i++ {
  211. s.streams = append(s.streams, s.newStream())
  212. }
  213. s.headStream = s.streams[0]
  214. for qu := range s.objectives {
  215. s.sortedObjectives = append(s.sortedObjectives, qu)
  216. }
  217. sort.Float64s(s.sortedObjectives)
  218. s.init(s) // Init self-collection.
  219. return s
  220. }
  221. type summary struct {
  222. selfCollector
  223. bufMtx sync.Mutex // Protects hotBuf and hotBufExpTime.
  224. mtx sync.Mutex // Protects every other moving part.
  225. // Lock bufMtx before mtx if both are needed.
  226. desc *Desc
  227. objectives map[float64]float64
  228. sortedObjectives []float64
  229. labelPairs []*dto.LabelPair
  230. sum float64
  231. cnt uint64
  232. hotBuf, coldBuf []float64
  233. streams []*quantile.Stream
  234. streamDuration time.Duration
  235. headStream *quantile.Stream
  236. headStreamIdx int
  237. headStreamExpTime, hotBufExpTime time.Time
  238. }
  239. func (s *summary) Desc() *Desc {
  240. return s.desc
  241. }
  242. func (s *summary) Observe(v float64) {
  243. s.bufMtx.Lock()
  244. defer s.bufMtx.Unlock()
  245. now := time.Now()
  246. if now.After(s.hotBufExpTime) {
  247. s.asyncFlush(now)
  248. }
  249. s.hotBuf = append(s.hotBuf, v)
  250. if len(s.hotBuf) == cap(s.hotBuf) {
  251. s.asyncFlush(now)
  252. }
  253. }
  254. func (s *summary) Write(out *dto.Metric) error {
  255. sum := &dto.Summary{}
  256. qs := make([]*dto.Quantile, 0, len(s.objectives))
  257. s.bufMtx.Lock()
  258. s.mtx.Lock()
  259. // Swap bufs even if hotBuf is empty to set new hotBufExpTime.
  260. s.swapBufs(time.Now())
  261. s.bufMtx.Unlock()
  262. s.flushColdBuf()
  263. sum.SampleCount = proto.Uint64(s.cnt)
  264. sum.SampleSum = proto.Float64(s.sum)
  265. for _, rank := range s.sortedObjectives {
  266. var q float64
  267. if s.headStream.Count() == 0 {
  268. q = math.NaN()
  269. } else {
  270. q = s.headStream.Query(rank)
  271. }
  272. qs = append(qs, &dto.Quantile{
  273. Quantile: proto.Float64(rank),
  274. Value: proto.Float64(q),
  275. })
  276. }
  277. s.mtx.Unlock()
  278. if len(qs) > 0 {
  279. sort.Sort(quantSort(qs))
  280. }
  281. sum.Quantile = qs
  282. out.Summary = sum
  283. out.Label = s.labelPairs
  284. return nil
  285. }
  286. func (s *summary) newStream() *quantile.Stream {
  287. return quantile.NewTargeted(s.objectives)
  288. }
  289. // asyncFlush needs bufMtx locked.
  290. func (s *summary) asyncFlush(now time.Time) {
  291. s.mtx.Lock()
  292. s.swapBufs(now)
  293. // Unblock the original goroutine that was responsible for the mutation
  294. // that triggered the compaction. But hold onto the global non-buffer
  295. // state mutex until the operation finishes.
  296. go func() {
  297. s.flushColdBuf()
  298. s.mtx.Unlock()
  299. }()
  300. }
  301. // rotateStreams needs mtx AND bufMtx locked.
  302. func (s *summary) maybeRotateStreams() {
  303. for !s.hotBufExpTime.Equal(s.headStreamExpTime) {
  304. s.headStream.Reset()
  305. s.headStreamIdx++
  306. if s.headStreamIdx >= len(s.streams) {
  307. s.headStreamIdx = 0
  308. }
  309. s.headStream = s.streams[s.headStreamIdx]
  310. s.headStreamExpTime = s.headStreamExpTime.Add(s.streamDuration)
  311. }
  312. }
  313. // flushColdBuf needs mtx locked.
  314. func (s *summary) flushColdBuf() {
  315. for _, v := range s.coldBuf {
  316. for _, stream := range s.streams {
  317. stream.Insert(v)
  318. }
  319. s.cnt++
  320. s.sum += v
  321. }
  322. s.coldBuf = s.coldBuf[0:0]
  323. s.maybeRotateStreams()
  324. }
  325. // swapBufs needs mtx AND bufMtx locked, coldBuf must be empty.
  326. func (s *summary) swapBufs(now time.Time) {
  327. if len(s.coldBuf) != 0 {
  328. panic("coldBuf is not empty")
  329. }
  330. s.hotBuf, s.coldBuf = s.coldBuf, s.hotBuf
  331. // hotBuf is now empty and gets new expiration set.
  332. for now.After(s.hotBufExpTime) {
  333. s.hotBufExpTime = s.hotBufExpTime.Add(s.streamDuration)
  334. }
  335. }
  336. type summaryCounts struct {
  337. // sumBits contains the bits of the float64 representing the sum of all
  338. // observations. sumBits and count have to go first in the struct to
  339. // guarantee alignment for atomic operations.
  340. // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
  341. sumBits uint64
  342. count uint64
  343. }
  344. type noObjectivesSummary struct {
  345. // countAndHotIdx enables lock-free writes with use of atomic updates.
  346. // The most significant bit is the hot index [0 or 1] of the count field
  347. // below. Observe calls update the hot one. All remaining bits count the
  348. // number of Observe calls. Observe starts by incrementing this counter,
  349. // and finish by incrementing the count field in the respective
  350. // summaryCounts, as a marker for completion.
  351. //
  352. // Calls of the Write method (which are non-mutating reads from the
  353. // perspective of the summary) swap the hot–cold under the writeMtx
  354. // lock. A cooldown is awaited (while locked) by comparing the number of
  355. // observations with the initiation count. Once they match, then the
  356. // last observation on the now cool one has completed. All cool fields must
  357. // be merged into the new hot before releasing writeMtx.
  358. // Fields with atomic access first! See alignment constraint:
  359. // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
  360. countAndHotIdx uint64
  361. selfCollector
  362. desc *Desc
  363. writeMtx sync.Mutex // Only used in the Write method.
  364. // Two counts, one is "hot" for lock-free observations, the other is
  365. // "cold" for writing out a dto.Metric. It has to be an array of
  366. // pointers to guarantee 64bit alignment of the histogramCounts, see
  367. // http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
  368. counts [2]*summaryCounts
  369. labelPairs []*dto.LabelPair
  370. }
  371. func (s *noObjectivesSummary) Desc() *Desc {
  372. return s.desc
  373. }
  374. func (s *noObjectivesSummary) Observe(v float64) {
  375. // We increment h.countAndHotIdx so that the counter in the lower
  376. // 63 bits gets incremented. At the same time, we get the new value
  377. // back, which we can use to find the currently-hot counts.
  378. n := atomic.AddUint64(&s.countAndHotIdx, 1)
  379. hotCounts := s.counts[n>>63]
  380. for {
  381. oldBits := atomic.LoadUint64(&hotCounts.sumBits)
  382. newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
  383. if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
  384. break
  385. }
  386. }
  387. // Increment count last as we take it as a signal that the observation
  388. // is complete.
  389. atomic.AddUint64(&hotCounts.count, 1)
  390. }
  391. func (s *noObjectivesSummary) Write(out *dto.Metric) error {
  392. // For simplicity, we protect this whole method by a mutex. It is not in
  393. // the hot path, i.e. Observe is called much more often than Write. The
  394. // complication of making Write lock-free isn't worth it, if possible at
  395. // all.
  396. s.writeMtx.Lock()
  397. defer s.writeMtx.Unlock()
  398. // Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
  399. // without touching the count bits. See the struct comments for a full
  400. // description of the algorithm.
  401. n := atomic.AddUint64(&s.countAndHotIdx, 1<<63)
  402. // count is contained unchanged in the lower 63 bits.
  403. count := n & ((1 << 63) - 1)
  404. // The most significant bit tells us which counts is hot. The complement
  405. // is thus the cold one.
  406. hotCounts := s.counts[n>>63]
  407. coldCounts := s.counts[(^n)>>63]
  408. // Await cooldown.
  409. for count != atomic.LoadUint64(&coldCounts.count) {
  410. runtime.Gosched() // Let observations get work done.
  411. }
  412. sum := &dto.Summary{
  413. SampleCount: proto.Uint64(count),
  414. SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
  415. }
  416. out.Summary = sum
  417. out.Label = s.labelPairs
  418. // Finally add all the cold counts to the new hot counts and reset the cold counts.
  419. atomic.AddUint64(&hotCounts.count, count)
  420. atomic.StoreUint64(&coldCounts.count, 0)
  421. for {
  422. oldBits := atomic.LoadUint64(&hotCounts.sumBits)
  423. newBits := math.Float64bits(math.Float64frombits(oldBits) + sum.GetSampleSum())
  424. if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
  425. atomic.StoreUint64(&coldCounts.sumBits, 0)
  426. break
  427. }
  428. }
  429. return nil
  430. }
  431. type quantSort []*dto.Quantile
  432. func (s quantSort) Len() int {
  433. return len(s)
  434. }
  435. func (s quantSort) Swap(i, j int) {
  436. s[i], s[j] = s[j], s[i]
  437. }
  438. func (s quantSort) Less(i, j int) bool {
  439. return s[i].GetQuantile() < s[j].GetQuantile()
  440. }
  441. // SummaryVec is a Collector that bundles a set of Summaries that all share the
  442. // same Desc, but have different values for their variable labels. This is used
  443. // if you want to count the same thing partitioned by various dimensions
  444. // (e.g. HTTP request latencies, partitioned by status code and method). Create
  445. // instances with NewSummaryVec.
  446. type SummaryVec struct {
  447. *MetricVec
  448. }
  449. // NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
  450. // partitioned by the given label names.
  451. //
  452. // Due to the way a Summary is represented in the Prometheus text format and how
  453. // it is handled by the Prometheus server internally, “quantile” is an illegal
  454. // label name. NewSummaryVec will panic if this label name is used.
  455. func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
  456. for _, ln := range labelNames {
  457. if ln == quantileLabel {
  458. panic(errQuantileLabelNotAllowed)
  459. }
  460. }
  461. desc := NewDesc(
  462. BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
  463. opts.Help,
  464. labelNames,
  465. opts.ConstLabels,
  466. )
  467. return &SummaryVec{
  468. MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
  469. return newSummary(desc, opts, lvs...)
  470. }),
  471. }
  472. }
  473. // GetMetricWithLabelValues returns the Summary for the given slice of label
  474. // values (same order as the variable labels in Desc). If that combination of
  475. // label values is accessed for the first time, a new Summary is created.
  476. //
  477. // It is possible to call this method without using the returned Summary to only
  478. // create the new Summary but leave it at its starting value, a Summary without
  479. // any observations.
  480. //
  481. // Keeping the Summary for later use is possible (and should be considered if
  482. // performance is critical), but keep in mind that Reset, DeleteLabelValues and
  483. // Delete can be used to delete the Summary from the SummaryVec. In that case,
  484. // the Summary will still exist, but it will not be exported anymore, even if a
  485. // Summary with the same label values is created later. See also the CounterVec
  486. // example.
  487. //
  488. // An error is returned if the number of label values is not the same as the
  489. // number of variable labels in Desc (minus any curried labels).
  490. //
  491. // Note that for more than one label value, this method is prone to mistakes
  492. // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
  493. // an alternative to avoid that type of mistake. For higher label numbers, the
  494. // latter has a much more readable (albeit more verbose) syntax, but it comes
  495. // with a performance overhead (for creating and processing the Labels map).
  496. // See also the GaugeVec example.
  497. func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
  498. metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...)
  499. if metric != nil {
  500. return metric.(Observer), err
  501. }
  502. return nil, err
  503. }
  504. // GetMetricWith returns the Summary for the given Labels map (the label names
  505. // must match those of the variable labels in Desc). If that label map is
  506. // accessed for the first time, a new Summary is created. Implications of
  507. // creating a Summary without using it and keeping the Summary for later use are
  508. // the same as for GetMetricWithLabelValues.
  509. //
  510. // An error is returned if the number and names of the Labels are inconsistent
  511. // with those of the variable labels in Desc (minus any curried labels).
  512. //
  513. // This method is used for the same purpose as
  514. // GetMetricWithLabelValues(...string). See there for pros and cons of the two
  515. // methods.
  516. func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
  517. metric, err := v.MetricVec.GetMetricWith(labels)
  518. if metric != nil {
  519. return metric.(Observer), err
  520. }
  521. return nil, err
  522. }
  523. // WithLabelValues works as GetMetricWithLabelValues, but panics where
  524. // GetMetricWithLabelValues would have returned an error. Not returning an
  525. // error allows shortcuts like
  526. // myVec.WithLabelValues("404", "GET").Observe(42.21)
  527. func (v *SummaryVec) WithLabelValues(lvs ...string) Observer {
  528. s, err := v.GetMetricWithLabelValues(lvs...)
  529. if err != nil {
  530. panic(err)
  531. }
  532. return s
  533. }
  534. // With works as GetMetricWith, but panics where GetMetricWithLabels would have
  535. // returned an error. Not returning an error allows shortcuts like
  536. // myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
  537. func (v *SummaryVec) With(labels Labels) Observer {
  538. s, err := v.GetMetricWith(labels)
  539. if err != nil {
  540. panic(err)
  541. }
  542. return s
  543. }
  544. // CurryWith returns a vector curried with the provided labels, i.e. the
  545. // returned vector has those labels pre-set for all labeled operations performed
  546. // on it. The cardinality of the curried vector is reduced accordingly. The
  547. // order of the remaining labels stays the same (just with the curried labels
  548. // taken out of the sequence – which is relevant for the
  549. // (GetMetric)WithLabelValues methods). It is possible to curry a curried
  550. // vector, but only with labels not yet used for currying before.
  551. //
  552. // The metrics contained in the SummaryVec are shared between the curried and
  553. // uncurried vectors. They are just accessed differently. Curried and uncurried
  554. // vectors behave identically in terms of collection. Only one must be
  555. // registered with a given registry (usually the uncurried version). The Reset
  556. // method deletes all metrics, even if called on a curried vector.
  557. func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) {
  558. vec, err := v.MetricVec.CurryWith(labels)
  559. if vec != nil {
  560. return &SummaryVec{vec}, err
  561. }
  562. return nil, err
  563. }
  564. // MustCurryWith works as CurryWith but panics where CurryWith would have
  565. // returned an error.
  566. func (v *SummaryVec) MustCurryWith(labels Labels) ObserverVec {
  567. vec, err := v.CurryWith(labels)
  568. if err != nil {
  569. panic(err)
  570. }
  571. return vec
  572. }
  573. type constSummary struct {
  574. desc *Desc
  575. count uint64
  576. sum float64
  577. quantiles map[float64]float64
  578. labelPairs []*dto.LabelPair
  579. }
  580. func (s *constSummary) Desc() *Desc {
  581. return s.desc
  582. }
  583. func (s *constSummary) Write(out *dto.Metric) error {
  584. sum := &dto.Summary{}
  585. qs := make([]*dto.Quantile, 0, len(s.quantiles))
  586. sum.SampleCount = proto.Uint64(s.count)
  587. sum.SampleSum = proto.Float64(s.sum)
  588. for rank, q := range s.quantiles {
  589. qs = append(qs, &dto.Quantile{
  590. Quantile: proto.Float64(rank),
  591. Value: proto.Float64(q),
  592. })
  593. }
  594. if len(qs) > 0 {
  595. sort.Sort(quantSort(qs))
  596. }
  597. sum.Quantile = qs
  598. out.Summary = sum
  599. out.Label = s.labelPairs
  600. return nil
  601. }
  602. // NewConstSummary returns a metric representing a Prometheus summary with fixed
  603. // values for the count, sum, and quantiles. As those parameters cannot be
  604. // changed, the returned value does not implement the Summary interface (but
  605. // only the Metric interface). Users of this package will not have much use for
  606. // it in regular operations. However, when implementing custom Collectors, it is
  607. // useful as a throw-away metric that is generated on the fly to send it to
  608. // Prometheus in the Collect method.
  609. //
  610. // quantiles maps ranks to quantile values. For example, a median latency of
  611. // 0.23s and a 99th percentile latency of 0.56s would be expressed as:
  612. // map[float64]float64{0.5: 0.23, 0.99: 0.56}
  613. //
  614. // NewConstSummary returns an error if the length of labelValues is not
  615. // consistent with the variable labels in Desc or if Desc is invalid.
  616. func NewConstSummary(
  617. desc *Desc,
  618. count uint64,
  619. sum float64,
  620. quantiles map[float64]float64,
  621. labelValues ...string,
  622. ) (Metric, error) {
  623. if desc.err != nil {
  624. return nil, desc.err
  625. }
  626. if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
  627. return nil, err
  628. }
  629. return &constSummary{
  630. desc: desc,
  631. count: count,
  632. sum: sum,
  633. quantiles: quantiles,
  634. labelPairs: MakeLabelPairs(desc, labelValues),
  635. }, nil
  636. }
  637. // MustNewConstSummary is a version of NewConstSummary that panics where
  638. // NewConstMetric would have returned an error.
  639. func MustNewConstSummary(
  640. desc *Desc,
  641. count uint64,
  642. sum float64,
  643. quantiles map[float64]float64,
  644. labelValues ...string,
  645. ) Metric {
  646. m, err := NewConstSummary(desc, count, sum, quantiles, labelValues...)
  647. if err != nil {
  648. panic(err)
  649. }
  650. return m
  651. }