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.

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