summary.go 24 KB

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