You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

summary.go 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  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. dto "github.com/prometheus/client_model/go"
  23. "github.com/beorn7/perks/quantile"
  24. "google.golang.org/protobuf/proto"
  25. "google.golang.org/protobuf/types/known/timestamppb"
  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. // now is for testing purposes, by default it's time.Now.
  134. now func() time.Time
  135. }
  136. // SummaryVecOpts bundles the options to create a SummaryVec metric.
  137. // It is mandatory to set SummaryOpts, see there for mandatory fields. VariableLabels
  138. // is optional and can safely be left to its default value.
  139. type SummaryVecOpts struct {
  140. SummaryOpts
  141. // VariableLabels are used to partition the metric vector by the given set
  142. // of labels. Each label value will be constrained with the optional Constraint
  143. // function, if provided.
  144. VariableLabels ConstrainableLabels
  145. }
  146. // Problem with the sliding-window decay algorithm... The Merge method of
  147. // perk/quantile is actually not working as advertised - and it might be
  148. // unfixable, as the underlying algorithm is apparently not capable of merging
  149. // summaries in the first place. To avoid using Merge, we are currently adding
  150. // observations to _each_ age bucket, i.e. the effort to add a sample is
  151. // essentially multiplied by the number of age buckets. When rotating age
  152. // buckets, we empty the previous head stream. On scrape time, we simply take
  153. // the quantiles from the head stream (no merging required). Result: More effort
  154. // on observation time, less effort on scrape time, which is exactly the
  155. // opposite of what we try to accomplish, but at least the results are correct.
  156. //
  157. // The quite elegant previous contraption to merge the age buckets efficiently
  158. // on scrape time (see code up commit 6b9530d72ea715f0ba612c0120e6e09fbf1d49d0)
  159. // can't be used anymore.
  160. // NewSummary creates a new Summary based on the provided SummaryOpts.
  161. func NewSummary(opts SummaryOpts) Summary {
  162. return newSummary(
  163. NewDesc(
  164. BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
  165. opts.Help,
  166. nil,
  167. opts.ConstLabels,
  168. ),
  169. opts,
  170. )
  171. }
  172. func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
  173. if len(desc.variableLabels.names) != len(labelValues) {
  174. panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, labelValues))
  175. }
  176. for _, n := range desc.variableLabels.names {
  177. if n == quantileLabel {
  178. panic(errQuantileLabelNotAllowed)
  179. }
  180. }
  181. for _, lp := range desc.constLabelPairs {
  182. if lp.GetName() == quantileLabel {
  183. panic(errQuantileLabelNotAllowed)
  184. }
  185. }
  186. if opts.Objectives == nil {
  187. opts.Objectives = map[float64]float64{}
  188. }
  189. if opts.MaxAge < 0 {
  190. panic(fmt.Errorf("illegal max age MaxAge=%v", opts.MaxAge))
  191. }
  192. if opts.MaxAge == 0 {
  193. opts.MaxAge = DefMaxAge
  194. }
  195. if opts.AgeBuckets == 0 {
  196. opts.AgeBuckets = DefAgeBuckets
  197. }
  198. if opts.BufCap == 0 {
  199. opts.BufCap = DefBufCap
  200. }
  201. if opts.now == nil {
  202. opts.now = time.Now
  203. }
  204. if len(opts.Objectives) == 0 {
  205. // Use the lock-free implementation of a Summary without objectives.
  206. s := &noObjectivesSummary{
  207. desc: desc,
  208. labelPairs: MakeLabelPairs(desc, labelValues),
  209. counts: [2]*summaryCounts{{}, {}},
  210. }
  211. s.init(s) // Init self-collection.
  212. s.createdTs = timestamppb.New(opts.now())
  213. return s
  214. }
  215. s := &summary{
  216. desc: desc,
  217. objectives: opts.Objectives,
  218. sortedObjectives: make([]float64, 0, len(opts.Objectives)),
  219. labelPairs: MakeLabelPairs(desc, labelValues),
  220. hotBuf: make([]float64, 0, opts.BufCap),
  221. coldBuf: make([]float64, 0, opts.BufCap),
  222. streamDuration: opts.MaxAge / time.Duration(opts.AgeBuckets),
  223. }
  224. s.headStreamExpTime = opts.now().Add(s.streamDuration)
  225. s.hotBufExpTime = s.headStreamExpTime
  226. for i := uint32(0); i < opts.AgeBuckets; i++ {
  227. s.streams = append(s.streams, s.newStream())
  228. }
  229. s.headStream = s.streams[0]
  230. for qu := range s.objectives {
  231. s.sortedObjectives = append(s.sortedObjectives, qu)
  232. }
  233. sort.Float64s(s.sortedObjectives)
  234. s.init(s) // Init self-collection.
  235. s.createdTs = timestamppb.New(opts.now())
  236. return s
  237. }
  238. type summary struct {
  239. selfCollector
  240. bufMtx sync.Mutex // Protects hotBuf and hotBufExpTime.
  241. mtx sync.Mutex // Protects every other moving part.
  242. // Lock bufMtx before mtx if both are needed.
  243. desc *Desc
  244. objectives map[float64]float64
  245. sortedObjectives []float64
  246. labelPairs []*dto.LabelPair
  247. sum float64
  248. cnt uint64
  249. hotBuf, coldBuf []float64
  250. streams []*quantile.Stream
  251. streamDuration time.Duration
  252. headStream *quantile.Stream
  253. headStreamIdx int
  254. headStreamExpTime, hotBufExpTime time.Time
  255. createdTs *timestamppb.Timestamp
  256. }
  257. func (s *summary) Desc() *Desc {
  258. return s.desc
  259. }
  260. func (s *summary) Observe(v float64) {
  261. s.bufMtx.Lock()
  262. defer s.bufMtx.Unlock()
  263. now := time.Now()
  264. if now.After(s.hotBufExpTime) {
  265. s.asyncFlush(now)
  266. }
  267. s.hotBuf = append(s.hotBuf, v)
  268. if len(s.hotBuf) == cap(s.hotBuf) {
  269. s.asyncFlush(now)
  270. }
  271. }
  272. func (s *summary) Write(out *dto.Metric) error {
  273. sum := &dto.Summary{
  274. CreatedTimestamp: s.createdTs,
  275. }
  276. qs := make([]*dto.Quantile, 0, len(s.objectives))
  277. s.bufMtx.Lock()
  278. s.mtx.Lock()
  279. // Swap bufs even if hotBuf is empty to set new hotBufExpTime.
  280. s.swapBufs(time.Now())
  281. s.bufMtx.Unlock()
  282. s.flushColdBuf()
  283. sum.SampleCount = proto.Uint64(s.cnt)
  284. sum.SampleSum = proto.Float64(s.sum)
  285. for _, rank := range s.sortedObjectives {
  286. var q float64
  287. if s.headStream.Count() == 0 {
  288. q = math.NaN()
  289. } else {
  290. q = s.headStream.Query(rank)
  291. }
  292. qs = append(qs, &dto.Quantile{
  293. Quantile: proto.Float64(rank),
  294. Value: proto.Float64(q),
  295. })
  296. }
  297. s.mtx.Unlock()
  298. if len(qs) > 0 {
  299. sort.Sort(quantSort(qs))
  300. }
  301. sum.Quantile = qs
  302. out.Summary = sum
  303. out.Label = s.labelPairs
  304. return nil
  305. }
  306. func (s *summary) newStream() *quantile.Stream {
  307. return quantile.NewTargeted(s.objectives)
  308. }
  309. // asyncFlush needs bufMtx locked.
  310. func (s *summary) asyncFlush(now time.Time) {
  311. s.mtx.Lock()
  312. s.swapBufs(now)
  313. // Unblock the original goroutine that was responsible for the mutation
  314. // that triggered the compaction. But hold onto the global non-buffer
  315. // state mutex until the operation finishes.
  316. go func() {
  317. s.flushColdBuf()
  318. s.mtx.Unlock()
  319. }()
  320. }
  321. // rotateStreams needs mtx AND bufMtx locked.
  322. func (s *summary) maybeRotateStreams() {
  323. for !s.hotBufExpTime.Equal(s.headStreamExpTime) {
  324. s.headStream.Reset()
  325. s.headStreamIdx++
  326. if s.headStreamIdx >= len(s.streams) {
  327. s.headStreamIdx = 0
  328. }
  329. s.headStream = s.streams[s.headStreamIdx]
  330. s.headStreamExpTime = s.headStreamExpTime.Add(s.streamDuration)
  331. }
  332. }
  333. // flushColdBuf needs mtx locked.
  334. func (s *summary) flushColdBuf() {
  335. for _, v := range s.coldBuf {
  336. for _, stream := range s.streams {
  337. stream.Insert(v)
  338. }
  339. s.cnt++
  340. s.sum += v
  341. }
  342. s.coldBuf = s.coldBuf[0:0]
  343. s.maybeRotateStreams()
  344. }
  345. // swapBufs needs mtx AND bufMtx locked, coldBuf must be empty.
  346. func (s *summary) swapBufs(now time.Time) {
  347. if len(s.coldBuf) != 0 {
  348. panic("coldBuf is not empty")
  349. }
  350. s.hotBuf, s.coldBuf = s.coldBuf, s.hotBuf
  351. // hotBuf is now empty and gets new expiration set.
  352. for now.After(s.hotBufExpTime) {
  353. s.hotBufExpTime = s.hotBufExpTime.Add(s.streamDuration)
  354. }
  355. }
  356. type summaryCounts struct {
  357. // sumBits contains the bits of the float64 representing the sum of all
  358. // observations. sumBits and count have to go first in the struct to
  359. // guarantee alignment for atomic operations.
  360. // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
  361. sumBits uint64
  362. count uint64
  363. }
  364. type noObjectivesSummary struct {
  365. // countAndHotIdx enables lock-free writes with use of atomic updates.
  366. // The most significant bit is the hot index [0 or 1] of the count field
  367. // below. Observe calls update the hot one. All remaining bits count the
  368. // number of Observe calls. Observe starts by incrementing this counter,
  369. // and finish by incrementing the count field in the respective
  370. // summaryCounts, as a marker for completion.
  371. //
  372. // Calls of the Write method (which are non-mutating reads from the
  373. // perspective of the summary) swap the hot–cold under the writeMtx
  374. // lock. A cooldown is awaited (while locked) by comparing the number of
  375. // observations with the initiation count. Once they match, then the
  376. // last observation on the now cool one has completed. All cool fields must
  377. // be merged into the new hot before releasing writeMtx.
  378. // Fields with atomic access first! See alignment constraint:
  379. // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
  380. countAndHotIdx uint64
  381. selfCollector
  382. desc *Desc
  383. writeMtx sync.Mutex // Only used in the Write method.
  384. // Two counts, one is "hot" for lock-free observations, the other is
  385. // "cold" for writing out a dto.Metric. It has to be an array of
  386. // pointers to guarantee 64bit alignment of the histogramCounts, see
  387. // http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
  388. counts [2]*summaryCounts
  389. labelPairs []*dto.LabelPair
  390. createdTs *timestamppb.Timestamp
  391. }
  392. func (s *noObjectivesSummary) Desc() *Desc {
  393. return s.desc
  394. }
  395. func (s *noObjectivesSummary) Observe(v float64) {
  396. // We increment h.countAndHotIdx so that the counter in the lower
  397. // 63 bits gets incremented. At the same time, we get the new value
  398. // back, which we can use to find the currently-hot counts.
  399. n := atomic.AddUint64(&s.countAndHotIdx, 1)
  400. hotCounts := s.counts[n>>63]
  401. for {
  402. oldBits := atomic.LoadUint64(&hotCounts.sumBits)
  403. newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
  404. if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
  405. break
  406. }
  407. }
  408. // Increment count last as we take it as a signal that the observation
  409. // is complete.
  410. atomic.AddUint64(&hotCounts.count, 1)
  411. }
  412. func (s *noObjectivesSummary) Write(out *dto.Metric) error {
  413. // For simplicity, we protect this whole method by a mutex. It is not in
  414. // the hot path, i.e. Observe is called much more often than Write. The
  415. // complication of making Write lock-free isn't worth it, if possible at
  416. // all.
  417. s.writeMtx.Lock()
  418. defer s.writeMtx.Unlock()
  419. // Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
  420. // without touching the count bits. See the struct comments for a full
  421. // description of the algorithm.
  422. n := atomic.AddUint64(&s.countAndHotIdx, 1<<63)
  423. // count is contained unchanged in the lower 63 bits.
  424. count := n & ((1 << 63) - 1)
  425. // The most significant bit tells us which counts is hot. The complement
  426. // is thus the cold one.
  427. hotCounts := s.counts[n>>63]
  428. coldCounts := s.counts[(^n)>>63]
  429. // Await cooldown.
  430. for count != atomic.LoadUint64(&coldCounts.count) {
  431. runtime.Gosched() // Let observations get work done.
  432. }
  433. sum := &dto.Summary{
  434. SampleCount: proto.Uint64(count),
  435. SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
  436. CreatedTimestamp: s.createdTs,
  437. }
  438. out.Summary = sum
  439. out.Label = s.labelPairs
  440. // Finally add all the cold counts to the new hot counts and reset the cold counts.
  441. atomic.AddUint64(&hotCounts.count, count)
  442. atomic.StoreUint64(&coldCounts.count, 0)
  443. for {
  444. oldBits := atomic.LoadUint64(&hotCounts.sumBits)
  445. newBits := math.Float64bits(math.Float64frombits(oldBits) + sum.GetSampleSum())
  446. if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
  447. atomic.StoreUint64(&coldCounts.sumBits, 0)
  448. break
  449. }
  450. }
  451. return nil
  452. }
  453. type quantSort []*dto.Quantile
  454. func (s quantSort) Len() int {
  455. return len(s)
  456. }
  457. func (s quantSort) Swap(i, j int) {
  458. s[i], s[j] = s[j], s[i]
  459. }
  460. func (s quantSort) Less(i, j int) bool {
  461. return s[i].GetQuantile() < s[j].GetQuantile()
  462. }
  463. // SummaryVec is a Collector that bundles a set of Summaries that all share the
  464. // same Desc, but have different values for their variable labels. This is used
  465. // if you want to count the same thing partitioned by various dimensions
  466. // (e.g. HTTP request latencies, partitioned by status code and method). Create
  467. // instances with NewSummaryVec.
  468. type SummaryVec struct {
  469. *MetricVec
  470. }
  471. // NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
  472. // partitioned by the given label names.
  473. //
  474. // Due to the way a Summary is represented in the Prometheus text format and how
  475. // it is handled by the Prometheus server internally, “quantile” is an illegal
  476. // label name. NewSummaryVec will panic if this label name is used.
  477. func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
  478. return V2.NewSummaryVec(SummaryVecOpts{
  479. SummaryOpts: opts,
  480. VariableLabels: UnconstrainedLabels(labelNames),
  481. })
  482. }
  483. // NewSummaryVec creates a new SummaryVec based on the provided SummaryVecOpts.
  484. func (v2) NewSummaryVec(opts SummaryVecOpts) *SummaryVec {
  485. for _, ln := range opts.VariableLabels.labelNames() {
  486. if ln == quantileLabel {
  487. panic(errQuantileLabelNotAllowed)
  488. }
  489. }
  490. desc := V2.NewDesc(
  491. BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
  492. opts.Help,
  493. opts.VariableLabels,
  494. opts.ConstLabels,
  495. )
  496. return &SummaryVec{
  497. MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
  498. return newSummary(desc, opts.SummaryOpts, lvs...)
  499. }),
  500. }
  501. }
  502. // GetMetricWithLabelValues returns the Summary for the given slice of label
  503. // values (same order as the variable labels in Desc). If that combination of
  504. // label values is accessed for the first time, a new Summary is created.
  505. //
  506. // It is possible to call this method without using the returned Summary to only
  507. // create the new Summary but leave it at its starting value, a Summary without
  508. // any observations.
  509. //
  510. // Keeping the Summary for later use is possible (and should be considered if
  511. // performance is critical), but keep in mind that Reset, DeleteLabelValues and
  512. // Delete can be used to delete the Summary from the SummaryVec. In that case,
  513. // the Summary will still exist, but it will not be exported anymore, even if a
  514. // Summary with the same label values is created later. See also the CounterVec
  515. // example.
  516. //
  517. // An error is returned if the number of label values is not the same as the
  518. // number of variable labels in Desc (minus any curried labels).
  519. //
  520. // Note that for more than one label value, this method is prone to mistakes
  521. // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
  522. // an alternative to avoid that type of mistake. For higher label numbers, the
  523. // latter has a much more readable (albeit more verbose) syntax, but it comes
  524. // with a performance overhead (for creating and processing the Labels map).
  525. // See also the GaugeVec example.
  526. func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
  527. metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...)
  528. if metric != nil {
  529. return metric.(Observer), err
  530. }
  531. return nil, err
  532. }
  533. // GetMetricWith returns the Summary for the given Labels map (the label names
  534. // must match those of the variable labels in Desc). If that label map is
  535. // accessed for the first time, a new Summary is created. Implications of
  536. // creating a Summary without using it and keeping the Summary for later use are
  537. // the same as for GetMetricWithLabelValues.
  538. //
  539. // An error is returned if the number and names of the Labels are inconsistent
  540. // with those of the variable labels in Desc (minus any curried labels).
  541. //
  542. // This method is used for the same purpose as
  543. // GetMetricWithLabelValues(...string). See there for pros and cons of the two
  544. // methods.
  545. func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
  546. metric, err := v.MetricVec.GetMetricWith(labels)
  547. if metric != nil {
  548. return metric.(Observer), err
  549. }
  550. return nil, err
  551. }
  552. // WithLabelValues works as GetMetricWithLabelValues, but panics where
  553. // GetMetricWithLabelValues would have returned an error. Not returning an
  554. // error allows shortcuts like
  555. //
  556. // myVec.WithLabelValues("404", "GET").Observe(42.21)
  557. func (v *SummaryVec) WithLabelValues(lvs ...string) Observer {
  558. s, err := v.GetMetricWithLabelValues(lvs...)
  559. if err != nil {
  560. panic(err)
  561. }
  562. return s
  563. }
  564. // With works as GetMetricWith, but panics where GetMetricWithLabels would have
  565. // returned an error. Not returning an error allows shortcuts like
  566. //
  567. // myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
  568. func (v *SummaryVec) With(labels Labels) Observer {
  569. s, err := v.GetMetricWith(labels)
  570. if err != nil {
  571. panic(err)
  572. }
  573. return s
  574. }
  575. // CurryWith returns a vector curried with the provided labels, i.e. the
  576. // returned vector has those labels pre-set for all labeled operations performed
  577. // on it. The cardinality of the curried vector is reduced accordingly. The
  578. // order of the remaining labels stays the same (just with the curried labels
  579. // taken out of the sequence – which is relevant for the
  580. // (GetMetric)WithLabelValues methods). It is possible to curry a curried
  581. // vector, but only with labels not yet used for currying before.
  582. //
  583. // The metrics contained in the SummaryVec are shared between the curried and
  584. // uncurried vectors. They are just accessed differently. Curried and uncurried
  585. // vectors behave identically in terms of collection. Only one must be
  586. // registered with a given registry (usually the uncurried version). The Reset
  587. // method deletes all metrics, even if called on a curried vector.
  588. func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) {
  589. vec, err := v.MetricVec.CurryWith(labels)
  590. if vec != nil {
  591. return &SummaryVec{vec}, err
  592. }
  593. return nil, err
  594. }
  595. // MustCurryWith works as CurryWith but panics where CurryWith would have
  596. // returned an error.
  597. func (v *SummaryVec) MustCurryWith(labels Labels) ObserverVec {
  598. vec, err := v.CurryWith(labels)
  599. if err != nil {
  600. panic(err)
  601. }
  602. return vec
  603. }
  604. type constSummary struct {
  605. desc *Desc
  606. count uint64
  607. sum float64
  608. quantiles map[float64]float64
  609. labelPairs []*dto.LabelPair
  610. createdTs *timestamppb.Timestamp
  611. }
  612. func (s *constSummary) Desc() *Desc {
  613. return s.desc
  614. }
  615. func (s *constSummary) Write(out *dto.Metric) error {
  616. sum := &dto.Summary{
  617. CreatedTimestamp: s.createdTs,
  618. }
  619. qs := make([]*dto.Quantile, 0, len(s.quantiles))
  620. sum.SampleCount = proto.Uint64(s.count)
  621. sum.SampleSum = proto.Float64(s.sum)
  622. for rank, q := range s.quantiles {
  623. qs = append(qs, &dto.Quantile{
  624. Quantile: proto.Float64(rank),
  625. Value: proto.Float64(q),
  626. })
  627. }
  628. if len(qs) > 0 {
  629. sort.Sort(quantSort(qs))
  630. }
  631. sum.Quantile = qs
  632. out.Summary = sum
  633. out.Label = s.labelPairs
  634. return nil
  635. }
  636. // NewConstSummary returns a metric representing a Prometheus summary with fixed
  637. // values for the count, sum, and quantiles. As those parameters cannot be
  638. // changed, the returned value does not implement the Summary interface (but
  639. // only the Metric interface). Users of this package will not have much use for
  640. // it in regular operations. However, when implementing custom Collectors, it is
  641. // useful as a throw-away metric that is generated on the fly to send it to
  642. // Prometheus in the Collect method.
  643. //
  644. // quantiles maps ranks to quantile values. For example, a median latency of
  645. // 0.23s and a 99th percentile latency of 0.56s would be expressed as:
  646. //
  647. // map[float64]float64{0.5: 0.23, 0.99: 0.56}
  648. //
  649. // NewConstSummary returns an error if the length of labelValues is not
  650. // consistent with the variable labels in Desc or if Desc is invalid.
  651. func NewConstSummary(
  652. desc *Desc,
  653. count uint64,
  654. sum float64,
  655. quantiles map[float64]float64,
  656. labelValues ...string,
  657. ) (Metric, error) {
  658. if desc.err != nil {
  659. return nil, desc.err
  660. }
  661. if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
  662. return nil, err
  663. }
  664. return &constSummary{
  665. desc: desc,
  666. count: count,
  667. sum: sum,
  668. quantiles: quantiles,
  669. labelPairs: MakeLabelPairs(desc, labelValues),
  670. }, nil
  671. }
  672. // MustNewConstSummary is a version of NewConstSummary that panics where
  673. // NewConstMetric would have returned an error.
  674. func MustNewConstSummary(
  675. desc *Desc,
  676. count uint64,
  677. sum float64,
  678. quantiles map[float64]float64,
  679. labelValues ...string,
  680. ) Metric {
  681. m, err := NewConstSummary(desc, count, sum, quantiles, labelValues...)
  682. if err != nil {
  683. panic(err)
  684. }
  685. return m
  686. }