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.
 
 

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