summary.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  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. "sort"
  18. "sync"
  19. "time"
  20. "github.com/beorn7/perks/quantile"
  21. "github.com/golang/protobuf/proto"
  22. dto "github.com/prometheus/client_model/go"
  23. )
  24. // quantileLabel is used for the label that defines the quantile in a
  25. // summary.
  26. const quantileLabel = "quantile"
  27. // A Summary captures individual observations from an event or sample stream and
  28. // summarizes them in a manner similar to traditional summary statistics: 1. sum
  29. // of observations, 2. observation count, 3. rank estimations.
  30. //
  31. // A typical use-case is the observation of request latencies. By default, a
  32. // Summary provides the median, the 90th and the 99th percentile of the latency
  33. // as rank estimations. However, the default behavior will change in the
  34. // upcoming v0.10 of the library. There will be no rank estimations at all by
  35. // default. For a sane transition, it is recommended to set the desired rank
  36. // estimations explicitly.
  37. //
  38. // Note that the rank estimations cannot be aggregated in a meaningful way with
  39. // the Prometheus query language (i.e. you cannot average or add them). If you
  40. // need aggregatable quantiles (e.g. you want the 99th percentile latency of all
  41. // queries served across all instances of a service), consider the Histogram
  42. // metric type. See the Prometheus documentation for more details.
  43. //
  44. // To create Summary instances, use NewSummary.
  45. type Summary interface {
  46. Metric
  47. Collector
  48. // Observe adds a single observation to the summary.
  49. Observe(float64)
  50. }
  51. // DefObjectives are the default Summary quantile values.
  52. //
  53. // Deprecated: DefObjectives will not be used as the default objectives in
  54. // v0.10 of the library. The default Summary will have no quantiles then.
  55. var (
  56. DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
  57. errQuantileLabelNotAllowed = fmt.Errorf(
  58. "%q is not allowed as label name in summaries", quantileLabel,
  59. )
  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 and Help to a non-empty string. While all other fields
  74. // are optional and can safely be left at their zero value, it is recommended to
  75. // explicitly set the Objectives field to the desired value as the default value
  76. // will change in the upcoming v0.10 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. Mandatory!
  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 DefObjectives. It is used if Objectives is left at
  111. // its zero value (i.e. nil). To create a Summary without Objectives,
  112. // set it to an empty map (i.e. map[float64]float64{}).
  113. //
  114. // Deprecated: Note that the current value of DefObjectives is
  115. // deprecated. It will be replaced by an empty map in v0.10 of the
  116. // library. Please explicitly set Objectives to the desired value.
  117. Objectives map[float64]float64
  118. // MaxAge defines the duration for which an observation stays relevant
  119. // for the summary. Must be positive. The default value is DefMaxAge.
  120. MaxAge time.Duration
  121. // AgeBuckets is the number of buckets used to exclude observations that
  122. // are older than MaxAge from the summary. A higher number has a
  123. // resource penalty, so only increase it if the higher resolution is
  124. // really required. For very high observation rates, you might want to
  125. // reduce the number of age buckets. With only one age bucket, you will
  126. // effectively see a complete reset of the summary each time MaxAge has
  127. // passed. The default value is DefAgeBuckets.
  128. AgeBuckets uint32
  129. // BufCap defines the default sample stream buffer size. The default
  130. // value of DefBufCap should suffice for most uses. If there is a need
  131. // to increase the value, a multiple of 500 is recommended (because that
  132. // is the internal buffer size of the underlying package
  133. // "github.com/bmizerany/perks/quantile").
  134. BufCap uint32
  135. }
  136. // Great fuck-up with the sliding-window decay algorithm... The Merge method of
  137. // perk/quantile is actually not working as advertised - and it might be
  138. // unfixable, as the underlying algorithm is apparently not capable of merging
  139. // summaries in the first place. To avoid using Merge, we are currently adding
  140. // observations to _each_ age bucket, i.e. the effort to add a sample is
  141. // essentially multiplied by the number of age buckets. When rotating age
  142. // buckets, we empty the previous head stream. On scrape time, we simply take
  143. // the quantiles from the head stream (no merging required). Result: More effort
  144. // on observation time, less effort on scrape time, which is exactly the
  145. // opposite of what we try to accomplish, but at least the results are correct.
  146. //
  147. // The quite elegant previous contraption to merge the age buckets efficiently
  148. // on scrape time (see code up commit 6b9530d72ea715f0ba612c0120e6e09fbf1d49d0)
  149. // can't be used anymore.
  150. // NewSummary creates a new Summary based on the provided SummaryOpts.
  151. func NewSummary(opts SummaryOpts) Summary {
  152. return newSummary(
  153. NewDesc(
  154. BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
  155. opts.Help,
  156. nil,
  157. opts.ConstLabels,
  158. ),
  159. opts,
  160. )
  161. }
  162. func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
  163. if len(desc.variableLabels) != len(labelValues) {
  164. panic(errInconsistentCardinality)
  165. }
  166. for _, n := range desc.variableLabels {
  167. if n == quantileLabel {
  168. panic(errQuantileLabelNotAllowed)
  169. }
  170. }
  171. for _, lp := range desc.constLabelPairs {
  172. if lp.GetName() == quantileLabel {
  173. panic(errQuantileLabelNotAllowed)
  174. }
  175. }
  176. if opts.Objectives == nil {
  177. opts.Objectives = DefObjectives
  178. }
  179. if opts.MaxAge < 0 {
  180. panic(fmt.Errorf("illegal max age MaxAge=%v", opts.MaxAge))
  181. }
  182. if opts.MaxAge == 0 {
  183. opts.MaxAge = DefMaxAge
  184. }
  185. if opts.AgeBuckets == 0 {
  186. opts.AgeBuckets = DefAgeBuckets
  187. }
  188. if opts.BufCap == 0 {
  189. opts.BufCap = DefBufCap
  190. }
  191. s := &summary{
  192. desc: desc,
  193. objectives: opts.Objectives,
  194. sortedObjectives: make([]float64, 0, len(opts.Objectives)),
  195. labelPairs: makeLabelPairs(desc, labelValues),
  196. hotBuf: make([]float64, 0, opts.BufCap),
  197. coldBuf: make([]float64, 0, opts.BufCap),
  198. streamDuration: opts.MaxAge / time.Duration(opts.AgeBuckets),
  199. }
  200. s.headStreamExpTime = time.Now().Add(s.streamDuration)
  201. s.hotBufExpTime = s.headStreamExpTime
  202. for i := uint32(0); i < opts.AgeBuckets; i++ {
  203. s.streams = append(s.streams, s.newStream())
  204. }
  205. s.headStream = s.streams[0]
  206. for qu := range s.objectives {
  207. s.sortedObjectives = append(s.sortedObjectives, qu)
  208. }
  209. sort.Float64s(s.sortedObjectives)
  210. s.init(s) // Init self-collection.
  211. return s
  212. }
  213. type summary struct {
  214. selfCollector
  215. bufMtx sync.Mutex // Protects hotBuf and hotBufExpTime.
  216. mtx sync.Mutex // Protects every other moving part.
  217. // Lock bufMtx before mtx if both are needed.
  218. desc *Desc
  219. objectives map[float64]float64
  220. sortedObjectives []float64
  221. labelPairs []*dto.LabelPair
  222. sum float64
  223. cnt uint64
  224. hotBuf, coldBuf []float64
  225. streams []*quantile.Stream
  226. streamDuration time.Duration
  227. headStream *quantile.Stream
  228. headStreamIdx int
  229. headStreamExpTime, hotBufExpTime time.Time
  230. }
  231. func (s *summary) Desc() *Desc {
  232. return s.desc
  233. }
  234. func (s *summary) Observe(v float64) {
  235. s.bufMtx.Lock()
  236. defer s.bufMtx.Unlock()
  237. now := time.Now()
  238. if now.After(s.hotBufExpTime) {
  239. s.asyncFlush(now)
  240. }
  241. s.hotBuf = append(s.hotBuf, v)
  242. if len(s.hotBuf) == cap(s.hotBuf) {
  243. s.asyncFlush(now)
  244. }
  245. }
  246. func (s *summary) Write(out *dto.Metric) error {
  247. sum := &dto.Summary{}
  248. qs := make([]*dto.Quantile, 0, len(s.objectives))
  249. s.bufMtx.Lock()
  250. s.mtx.Lock()
  251. // Swap bufs even if hotBuf is empty to set new hotBufExpTime.
  252. s.swapBufs(time.Now())
  253. s.bufMtx.Unlock()
  254. s.flushColdBuf()
  255. sum.SampleCount = proto.Uint64(s.cnt)
  256. sum.SampleSum = proto.Float64(s.sum)
  257. for _, rank := range s.sortedObjectives {
  258. var q float64
  259. if s.headStream.Count() == 0 {
  260. q = math.NaN()
  261. } else {
  262. q = s.headStream.Query(rank)
  263. }
  264. qs = append(qs, &dto.Quantile{
  265. Quantile: proto.Float64(rank),
  266. Value: proto.Float64(q),
  267. })
  268. }
  269. s.mtx.Unlock()
  270. if len(qs) > 0 {
  271. sort.Sort(quantSort(qs))
  272. }
  273. sum.Quantile = qs
  274. out.Summary = sum
  275. out.Label = s.labelPairs
  276. return nil
  277. }
  278. func (s *summary) newStream() *quantile.Stream {
  279. return quantile.NewTargeted(s.objectives)
  280. }
  281. // asyncFlush needs bufMtx locked.
  282. func (s *summary) asyncFlush(now time.Time) {
  283. s.mtx.Lock()
  284. s.swapBufs(now)
  285. // Unblock the original goroutine that was responsible for the mutation
  286. // that triggered the compaction. But hold onto the global non-buffer
  287. // state mutex until the operation finishes.
  288. go func() {
  289. s.flushColdBuf()
  290. s.mtx.Unlock()
  291. }()
  292. }
  293. // rotateStreams needs mtx AND bufMtx locked.
  294. func (s *summary) maybeRotateStreams() {
  295. for !s.hotBufExpTime.Equal(s.headStreamExpTime) {
  296. s.headStream.Reset()
  297. s.headStreamIdx++
  298. if s.headStreamIdx >= len(s.streams) {
  299. s.headStreamIdx = 0
  300. }
  301. s.headStream = s.streams[s.headStreamIdx]
  302. s.headStreamExpTime = s.headStreamExpTime.Add(s.streamDuration)
  303. }
  304. }
  305. // flushColdBuf needs mtx locked.
  306. func (s *summary) flushColdBuf() {
  307. for _, v := range s.coldBuf {
  308. for _, stream := range s.streams {
  309. stream.Insert(v)
  310. }
  311. s.cnt++
  312. s.sum += v
  313. }
  314. s.coldBuf = s.coldBuf[0:0]
  315. s.maybeRotateStreams()
  316. }
  317. // swapBufs needs mtx AND bufMtx locked, coldBuf must be empty.
  318. func (s *summary) swapBufs(now time.Time) {
  319. if len(s.coldBuf) != 0 {
  320. panic("coldBuf is not empty")
  321. }
  322. s.hotBuf, s.coldBuf = s.coldBuf, s.hotBuf
  323. // hotBuf is now empty and gets new expiration set.
  324. for now.After(s.hotBufExpTime) {
  325. s.hotBufExpTime = s.hotBufExpTime.Add(s.streamDuration)
  326. }
  327. }
  328. type quantSort []*dto.Quantile
  329. func (s quantSort) Len() int {
  330. return len(s)
  331. }
  332. func (s quantSort) Swap(i, j int) {
  333. s[i], s[j] = s[j], s[i]
  334. }
  335. func (s quantSort) Less(i, j int) bool {
  336. return s[i].GetQuantile() < s[j].GetQuantile()
  337. }
  338. // SummaryVec is a Collector that bundles a set of Summaries that all share the
  339. // same Desc, but have different values for their variable labels. This is used
  340. // if you want to count the same thing partitioned by various dimensions
  341. // (e.g. HTTP request latencies, partitioned by status code and method). Create
  342. // instances with NewSummaryVec.
  343. type SummaryVec struct {
  344. *metricVec
  345. }
  346. // NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
  347. // partitioned by the given label names.
  348. //
  349. // Due to the way a Summary is represented in the Prometheus text format and how
  350. // it is handled by the Prometheus server internally, “quantile” is an illegal
  351. // label name. NewSummaryVec will panic if this label name is used.
  352. func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
  353. for _, ln := range labelNames {
  354. if ln == quantileLabel {
  355. panic(errQuantileLabelNotAllowed)
  356. }
  357. }
  358. desc := NewDesc(
  359. BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
  360. opts.Help,
  361. labelNames,
  362. opts.ConstLabels,
  363. )
  364. return &SummaryVec{
  365. metricVec: newMetricVec(desc, func(lvs ...string) Metric {
  366. return newSummary(desc, opts, lvs...)
  367. }),
  368. }
  369. }
  370. // GetMetricWithLabelValues returns the Summary for the given slice of label
  371. // values (same order as the VariableLabels in Desc). If that combination of
  372. // label values is accessed for the first time, a new Summary is created.
  373. //
  374. // It is possible to call this method without using the returned Summary to only
  375. // create the new Summary but leave it at its starting value, a Summary without
  376. // any observations.
  377. //
  378. // Keeping the Summary for later use is possible (and should be considered if
  379. // performance is critical), but keep in mind that Reset, DeleteLabelValues and
  380. // Delete can be used to delete the Summary from the SummaryVec. In that case,
  381. // the Summary will still exist, but it will not be exported anymore, even if a
  382. // Summary with the same label values is created later. See also the CounterVec
  383. // example.
  384. //
  385. // An error is returned if the number of label values is not the same as the
  386. // number of VariableLabels in Desc (minus any curried labels).
  387. //
  388. // Note that for more than one label value, this method is prone to mistakes
  389. // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
  390. // an alternative to avoid that type of mistake. For higher label numbers, the
  391. // latter has a much more readable (albeit more verbose) syntax, but it comes
  392. // with a performance overhead (for creating and processing the Labels map).
  393. // See also the GaugeVec example.
  394. func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
  395. metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
  396. if metric != nil {
  397. return metric.(Observer), err
  398. }
  399. return nil, err
  400. }
  401. // GetMetricWith returns the Summary for the given Labels map (the label names
  402. // must match those of the VariableLabels in Desc). If that label map is
  403. // accessed for the first time, a new Summary is created. Implications of
  404. // creating a Summary without using it and keeping the Summary for later use are
  405. // the same as for GetMetricWithLabelValues.
  406. //
  407. // An error is returned if the number and names of the Labels are inconsistent
  408. // with those of the VariableLabels in Desc (minus any curried labels).
  409. //
  410. // This method is used for the same purpose as
  411. // GetMetricWithLabelValues(...string). See there for pros and cons of the two
  412. // methods.
  413. func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
  414. metric, err := v.metricVec.getMetricWith(labels)
  415. if metric != nil {
  416. return metric.(Observer), err
  417. }
  418. return nil, err
  419. }
  420. // WithLabelValues works as GetMetricWithLabelValues, but panics where
  421. // GetMetricWithLabelValues would have returned an error. Not returning an
  422. // error allows shortcuts like
  423. // myVec.WithLabelValues("404", "GET").Observe(42.21)
  424. func (v *SummaryVec) WithLabelValues(lvs ...string) Observer {
  425. s, err := v.GetMetricWithLabelValues(lvs...)
  426. if err != nil {
  427. panic(err)
  428. }
  429. return s
  430. }
  431. // With works as GetMetricWith, but panics where GetMetricWithLabels would have
  432. // returned an error. Not returning an error allows shortcuts like
  433. // myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
  434. func (v *SummaryVec) With(labels Labels) Observer {
  435. s, err := v.GetMetricWith(labels)
  436. if err != nil {
  437. panic(err)
  438. }
  439. return s
  440. }
  441. // CurryWith returns a vector curried with the provided labels, i.e. the
  442. // returned vector has those labels pre-set for all labeled operations performed
  443. // on it. The cardinality of the curried vector is reduced accordingly. The
  444. // order of the remaining labels stays the same (just with the curried labels
  445. // taken out of the sequence – which is relevant for the
  446. // (GetMetric)WithLabelValues methods). It is possible to curry a curried
  447. // vector, but only with labels not yet used for currying before.
  448. //
  449. // The metrics contained in the SummaryVec are shared between the curried and
  450. // uncurried vectors. They are just accessed differently. Curried and uncurried
  451. // vectors behave identically in terms of collection. Only one must be
  452. // registered with a given registry (usually the uncurried version). The Reset
  453. // method deletes all metrics, even if called on a curried vector.
  454. func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) {
  455. vec, err := v.curryWith(labels)
  456. if vec != nil {
  457. return &SummaryVec{vec}, err
  458. }
  459. return nil, err
  460. }
  461. // MustCurryWith works as CurryWith but panics where CurryWith would have
  462. // returned an error.
  463. func (v *SummaryVec) MustCurryWith(labels Labels) ObserverVec {
  464. vec, err := v.CurryWith(labels)
  465. if err != nil {
  466. panic(err)
  467. }
  468. return vec
  469. }
  470. type constSummary struct {
  471. desc *Desc
  472. count uint64
  473. sum float64
  474. quantiles map[float64]float64
  475. labelPairs []*dto.LabelPair
  476. }
  477. func (s *constSummary) Desc() *Desc {
  478. return s.desc
  479. }
  480. func (s *constSummary) Write(out *dto.Metric) error {
  481. sum := &dto.Summary{}
  482. qs := make([]*dto.Quantile, 0, len(s.quantiles))
  483. sum.SampleCount = proto.Uint64(s.count)
  484. sum.SampleSum = proto.Float64(s.sum)
  485. for rank, q := range s.quantiles {
  486. qs = append(qs, &dto.Quantile{
  487. Quantile: proto.Float64(rank),
  488. Value: proto.Float64(q),
  489. })
  490. }
  491. if len(qs) > 0 {
  492. sort.Sort(quantSort(qs))
  493. }
  494. sum.Quantile = qs
  495. out.Summary = sum
  496. out.Label = s.labelPairs
  497. return nil
  498. }
  499. // NewConstSummary returns a metric representing a Prometheus summary with fixed
  500. // values for the count, sum, and quantiles. As those parameters cannot be
  501. // changed, the returned value does not implement the Summary interface (but
  502. // only the Metric interface). Users of this package will not have much use for
  503. // it in regular operations. However, when implementing custom Collectors, it is
  504. // useful as a throw-away metric that is generated on the fly to send it to
  505. // Prometheus in the Collect method.
  506. //
  507. // quantiles maps ranks to quantile values. For example, a median latency of
  508. // 0.23s and a 99th percentile latency of 0.56s would be expressed as:
  509. // map[float64]float64{0.5: 0.23, 0.99: 0.56}
  510. //
  511. // NewConstSummary returns an error if the length of labelValues is not
  512. // consistent with the variable labels in Desc.
  513. func NewConstSummary(
  514. desc *Desc,
  515. count uint64,
  516. sum float64,
  517. quantiles map[float64]float64,
  518. labelValues ...string,
  519. ) (Metric, error) {
  520. if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
  521. return nil, err
  522. }
  523. return &constSummary{
  524. desc: desc,
  525. count: count,
  526. sum: sum,
  527. quantiles: quantiles,
  528. labelPairs: makeLabelPairs(desc, labelValues),
  529. }, nil
  530. }
  531. // MustNewConstSummary is a version of NewConstSummary that panics where
  532. // NewConstMetric would have returned an error.
  533. func MustNewConstSummary(
  534. desc *Desc,
  535. count uint64,
  536. sum float64,
  537. quantiles map[float64]float64,
  538. labelValues ...string,
  539. ) Metric {
  540. m, err := NewConstSummary(desc, count, sum, quantiles, labelValues...)
  541. if err != nil {
  542. panic(err)
  543. }
  544. return m
  545. }