您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 

569 行
18 KiB

  1. // Copyright 2021 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. //go:build go1.17
  14. // +build go1.17
  15. package prometheus
  16. import (
  17. "math"
  18. "runtime"
  19. "runtime/metrics"
  20. "strings"
  21. "sync"
  22. //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
  23. "github.com/golang/protobuf/proto"
  24. dto "github.com/prometheus/client_model/go"
  25. "github.com/prometheus/client_golang/prometheus/internal"
  26. )
  27. const (
  28. // constants for strings referenced more than once.
  29. goGCHeapTinyAllocsObjects = "/gc/heap/tiny/allocs:objects"
  30. goGCHeapAllocsObjects = "/gc/heap/allocs:objects"
  31. goGCHeapFreesObjects = "/gc/heap/frees:objects"
  32. goGCHeapFreesBytes = "/gc/heap/frees:bytes"
  33. goGCHeapAllocsBytes = "/gc/heap/allocs:bytes"
  34. goGCHeapObjects = "/gc/heap/objects:objects"
  35. goGCHeapGoalBytes = "/gc/heap/goal:bytes"
  36. goMemoryClassesTotalBytes = "/memory/classes/total:bytes"
  37. goMemoryClassesHeapObjectsBytes = "/memory/classes/heap/objects:bytes"
  38. goMemoryClassesHeapUnusedBytes = "/memory/classes/heap/unused:bytes"
  39. goMemoryClassesHeapReleasedBytes = "/memory/classes/heap/released:bytes"
  40. goMemoryClassesHeapFreeBytes = "/memory/classes/heap/free:bytes"
  41. goMemoryClassesHeapStacksBytes = "/memory/classes/heap/stacks:bytes"
  42. goMemoryClassesOSStacksBytes = "/memory/classes/os-stacks:bytes"
  43. goMemoryClassesMetadataMSpanInuseBytes = "/memory/classes/metadata/mspan/inuse:bytes"
  44. goMemoryClassesMetadataMSPanFreeBytes = "/memory/classes/metadata/mspan/free:bytes"
  45. goMemoryClassesMetadataMCacheInuseBytes = "/memory/classes/metadata/mcache/inuse:bytes"
  46. goMemoryClassesMetadataMCacheFreeBytes = "/memory/classes/metadata/mcache/free:bytes"
  47. goMemoryClassesProfilingBucketsBytes = "/memory/classes/profiling/buckets:bytes"
  48. goMemoryClassesMetadataOtherBytes = "/memory/classes/metadata/other:bytes"
  49. goMemoryClassesOtherBytes = "/memory/classes/other:bytes"
  50. )
  51. // rmNamesForMemStatsMetrics represents runtime/metrics names required to populate goRuntimeMemStats from like logic.
  52. var rmNamesForMemStatsMetrics = []string{
  53. goGCHeapTinyAllocsObjects,
  54. goGCHeapAllocsObjects,
  55. goGCHeapFreesObjects,
  56. goGCHeapAllocsBytes,
  57. goGCHeapObjects,
  58. goGCHeapGoalBytes,
  59. goMemoryClassesTotalBytes,
  60. goMemoryClassesHeapObjectsBytes,
  61. goMemoryClassesHeapUnusedBytes,
  62. goMemoryClassesHeapReleasedBytes,
  63. goMemoryClassesHeapFreeBytes,
  64. goMemoryClassesHeapStacksBytes,
  65. goMemoryClassesOSStacksBytes,
  66. goMemoryClassesMetadataMSpanInuseBytes,
  67. goMemoryClassesMetadataMSPanFreeBytes,
  68. goMemoryClassesMetadataMCacheInuseBytes,
  69. goMemoryClassesMetadataMCacheFreeBytes,
  70. goMemoryClassesProfilingBucketsBytes,
  71. goMemoryClassesMetadataOtherBytes,
  72. goMemoryClassesOtherBytes,
  73. }
  74. func bestEffortLookupRM(lookup []string) []metrics.Description {
  75. ret := make([]metrics.Description, 0, len(lookup))
  76. for _, rm := range metrics.All() {
  77. for _, m := range lookup {
  78. if m == rm.Name {
  79. ret = append(ret, rm)
  80. }
  81. }
  82. }
  83. return ret
  84. }
  85. type goCollector struct {
  86. base baseGoCollector
  87. // mu protects updates to all fields ensuring a consistent
  88. // snapshot is always produced by Collect.
  89. mu sync.Mutex
  90. // Contains all samples that has to retrieved from runtime/metrics (not all of them will be exposed).
  91. sampleBuf []metrics.Sample
  92. // sampleMap allows lookup for MemStats metrics and runtime/metrics histograms for exact sums.
  93. sampleMap map[string]*metrics.Sample
  94. // rmExposedMetrics represents all runtime/metrics package metrics
  95. // that were configured to be exposed.
  96. rmExposedMetrics []collectorMetric
  97. rmExactSumMapForHist map[string]string
  98. // With Go 1.17, the runtime/metrics package was introduced.
  99. // From that point on, metric names produced by the runtime/metrics
  100. // package could be generated from runtime/metrics names. However,
  101. // these differ from the old names for the same values.
  102. //
  103. // This field exists to export the same values under the old names
  104. // as well.
  105. msMetrics memStatsMetrics
  106. msMetricsEnabled bool
  107. }
  108. type rmMetricDesc struct {
  109. metrics.Description
  110. }
  111. func matchRuntimeMetricsRules(rules []internal.GoCollectorRule) []rmMetricDesc {
  112. var descs []rmMetricDesc
  113. for _, d := range metrics.All() {
  114. var (
  115. deny = true
  116. desc rmMetricDesc
  117. )
  118. for _, r := range rules {
  119. if !r.Matcher.MatchString(d.Name) {
  120. continue
  121. }
  122. deny = r.Deny
  123. }
  124. if deny {
  125. continue
  126. }
  127. desc.Description = d
  128. descs = append(descs, desc)
  129. }
  130. return descs
  131. }
  132. func defaultGoCollectorOptions() internal.GoCollectorOptions {
  133. return internal.GoCollectorOptions{
  134. RuntimeMetricSumForHist: map[string]string{
  135. "/gc/heap/allocs-by-size:bytes": goGCHeapAllocsBytes,
  136. "/gc/heap/frees-by-size:bytes": goGCHeapFreesBytes,
  137. },
  138. RuntimeMetricRules: []internal.GoCollectorRule{
  139. //{Matcher: regexp.MustCompile("")},
  140. },
  141. }
  142. }
  143. // NewGoCollector is the obsolete version of collectors.NewGoCollector.
  144. // See there for documentation.
  145. //
  146. // Deprecated: Use collectors.NewGoCollector instead.
  147. func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
  148. opt := defaultGoCollectorOptions()
  149. for _, o := range opts {
  150. o(&opt)
  151. }
  152. exposedDescriptions := matchRuntimeMetricsRules(opt.RuntimeMetricRules)
  153. // Collect all histogram samples so that we can get their buckets.
  154. // The API guarantees that the buckets are always fixed for the lifetime
  155. // of the process.
  156. var histograms []metrics.Sample
  157. for _, d := range exposedDescriptions {
  158. if d.Kind == metrics.KindFloat64Histogram {
  159. histograms = append(histograms, metrics.Sample{Name: d.Name})
  160. }
  161. }
  162. if len(histograms) > 0 {
  163. metrics.Read(histograms)
  164. }
  165. bucketsMap := make(map[string][]float64)
  166. for i := range histograms {
  167. bucketsMap[histograms[i].Name] = histograms[i].Value.Float64Histogram().Buckets
  168. }
  169. // Generate a collector for each exposed runtime/metrics metric.
  170. metricSet := make([]collectorMetric, 0, len(exposedDescriptions))
  171. // SampleBuf is used for reading from runtime/metrics.
  172. // We are assuming the largest case to have stable pointers for sampleMap purposes.
  173. sampleBuf := make([]metrics.Sample, 0, len(exposedDescriptions)+len(opt.RuntimeMetricSumForHist)+len(rmNamesForMemStatsMetrics))
  174. sampleMap := make(map[string]*metrics.Sample, len(exposedDescriptions))
  175. for _, d := range exposedDescriptions {
  176. namespace, subsystem, name, ok := internal.RuntimeMetricsToProm(&d.Description)
  177. if !ok {
  178. // Just ignore this metric; we can't do anything with it here.
  179. // If a user decides to use the latest version of Go, we don't want
  180. // to fail here. This condition is tested in TestExpectedRuntimeMetrics.
  181. continue
  182. }
  183. sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name})
  184. sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1]
  185. var m collectorMetric
  186. if d.Kind == metrics.KindFloat64Histogram {
  187. _, hasSum := opt.RuntimeMetricSumForHist[d.Name]
  188. unit := d.Name[strings.IndexRune(d.Name, ':')+1:]
  189. m = newBatchHistogram(
  190. NewDesc(
  191. BuildFQName(namespace, subsystem, name),
  192. d.Description.Description,
  193. nil,
  194. nil,
  195. ),
  196. internal.RuntimeMetricsBucketsForUnit(bucketsMap[d.Name], unit),
  197. hasSum,
  198. )
  199. } else if d.Cumulative {
  200. m = NewCounter(CounterOpts{
  201. Namespace: namespace,
  202. Subsystem: subsystem,
  203. Name: name,
  204. Help: d.Description.Description,
  205. },
  206. )
  207. } else {
  208. m = NewGauge(GaugeOpts{
  209. Namespace: namespace,
  210. Subsystem: subsystem,
  211. Name: name,
  212. Help: d.Description.Description,
  213. })
  214. }
  215. metricSet = append(metricSet, m)
  216. }
  217. // Add exact sum metrics to sampleBuf if not added before.
  218. for _, h := range histograms {
  219. sumMetric, ok := opt.RuntimeMetricSumForHist[h.Name]
  220. if !ok {
  221. continue
  222. }
  223. if _, ok := sampleMap[sumMetric]; ok {
  224. continue
  225. }
  226. sampleBuf = append(sampleBuf, metrics.Sample{Name: sumMetric})
  227. sampleMap[sumMetric] = &sampleBuf[len(sampleBuf)-1]
  228. }
  229. var (
  230. msMetrics memStatsMetrics
  231. msDescriptions []metrics.Description
  232. )
  233. if !opt.DisableMemStatsLikeMetrics {
  234. msMetrics = goRuntimeMemStats()
  235. msDescriptions = bestEffortLookupRM(rmNamesForMemStatsMetrics)
  236. // Check if metric was not exposed before and if not, add to sampleBuf.
  237. for _, mdDesc := range msDescriptions {
  238. if _, ok := sampleMap[mdDesc.Name]; ok {
  239. continue
  240. }
  241. sampleBuf = append(sampleBuf, metrics.Sample{Name: mdDesc.Name})
  242. sampleMap[mdDesc.Name] = &sampleBuf[len(sampleBuf)-1]
  243. }
  244. }
  245. return &goCollector{
  246. base: newBaseGoCollector(),
  247. sampleBuf: sampleBuf,
  248. sampleMap: sampleMap,
  249. rmExposedMetrics: metricSet,
  250. rmExactSumMapForHist: opt.RuntimeMetricSumForHist,
  251. msMetrics: msMetrics,
  252. msMetricsEnabled: !opt.DisableMemStatsLikeMetrics,
  253. }
  254. }
  255. // Describe returns all descriptions of the collector.
  256. func (c *goCollector) Describe(ch chan<- *Desc) {
  257. c.base.Describe(ch)
  258. for _, i := range c.msMetrics {
  259. ch <- i.desc
  260. }
  261. for _, m := range c.rmExposedMetrics {
  262. ch <- m.Desc()
  263. }
  264. }
  265. // Collect returns the current state of all metrics of the collector.
  266. func (c *goCollector) Collect(ch chan<- Metric) {
  267. // Collect base non-memory metrics.
  268. c.base.Collect(ch)
  269. if len(c.sampleBuf) == 0 {
  270. return
  271. }
  272. // Collect must be thread-safe, so prevent concurrent use of
  273. // sampleBuf elements. Just read into sampleBuf but write all the data
  274. // we get into our Metrics or MemStats.
  275. //
  276. // This lock also ensures that the Metrics we send out are all from
  277. // the same updates, ensuring their mutual consistency insofar as
  278. // is guaranteed by the runtime/metrics package.
  279. //
  280. // N.B. This locking is heavy-handed, but Collect is expected to be called
  281. // relatively infrequently. Also the core operation here, metrics.Read,
  282. // is fast (O(tens of microseconds)) so contention should certainly be
  283. // low, though channel operations and any allocations may add to that.
  284. c.mu.Lock()
  285. defer c.mu.Unlock()
  286. // Populate runtime/metrics sample buffer.
  287. metrics.Read(c.sampleBuf)
  288. // Collect all our runtime/metrics user chose to expose from sampleBuf (if any).
  289. for i, metric := range c.rmExposedMetrics {
  290. // We created samples for exposed metrics first in order, so indexes match.
  291. sample := c.sampleBuf[i]
  292. // N.B. switch on concrete type because it's significantly more efficient
  293. // than checking for the Counter and Gauge interface implementations. In
  294. // this case, we control all the types here.
  295. switch m := metric.(type) {
  296. case *counter:
  297. // Guard against decreases. This should never happen, but a failure
  298. // to do so will result in a panic, which is a harsh consequence for
  299. // a metrics collection bug.
  300. v0, v1 := m.get(), unwrapScalarRMValue(sample.Value)
  301. if v1 > v0 {
  302. m.Add(unwrapScalarRMValue(sample.Value) - m.get())
  303. }
  304. m.Collect(ch)
  305. case *gauge:
  306. m.Set(unwrapScalarRMValue(sample.Value))
  307. m.Collect(ch)
  308. case *batchHistogram:
  309. m.update(sample.Value.Float64Histogram(), c.exactSumFor(sample.Name))
  310. m.Collect(ch)
  311. default:
  312. panic("unexpected metric type")
  313. }
  314. }
  315. if c.msMetricsEnabled {
  316. // ms is a dummy MemStats that we populate ourselves so that we can
  317. // populate the old metrics from it if goMemStatsCollection is enabled.
  318. var ms runtime.MemStats
  319. memStatsFromRM(&ms, c.sampleMap)
  320. for _, i := range c.msMetrics {
  321. ch <- MustNewConstMetric(i.desc, i.valType, i.eval(&ms))
  322. }
  323. }
  324. }
  325. // unwrapScalarRMValue unwraps a runtime/metrics value that is assumed
  326. // to be scalar and returns the equivalent float64 value. Panics if the
  327. // value is not scalar.
  328. func unwrapScalarRMValue(v metrics.Value) float64 {
  329. switch v.Kind() {
  330. case metrics.KindUint64:
  331. return float64(v.Uint64())
  332. case metrics.KindFloat64:
  333. return v.Float64()
  334. case metrics.KindBad:
  335. // Unsupported metric.
  336. //
  337. // This should never happen because we always populate our metric
  338. // set from the runtime/metrics package.
  339. panic("unexpected unsupported metric")
  340. default:
  341. // Unsupported metric kind.
  342. //
  343. // This should never happen because we check for this during initialization
  344. // and flag and filter metrics whose kinds we don't understand.
  345. panic("unexpected unsupported metric kind")
  346. }
  347. }
  348. // exactSumFor takes a runtime/metrics metric name (that is assumed to
  349. // be of kind KindFloat64Histogram) and returns its exact sum and whether
  350. // its exact sum exists.
  351. //
  352. // The runtime/metrics API for histograms doesn't currently expose exact
  353. // sums, but some of the other metrics are in fact exact sums of histograms.
  354. func (c *goCollector) exactSumFor(rmName string) float64 {
  355. sumName, ok := c.rmExactSumMapForHist[rmName]
  356. if !ok {
  357. return 0
  358. }
  359. s, ok := c.sampleMap[sumName]
  360. if !ok {
  361. return 0
  362. }
  363. return unwrapScalarRMValue(s.Value)
  364. }
  365. func memStatsFromRM(ms *runtime.MemStats, rm map[string]*metrics.Sample) {
  366. lookupOrZero := func(name string) uint64 {
  367. if s, ok := rm[name]; ok {
  368. return s.Value.Uint64()
  369. }
  370. return 0
  371. }
  372. // Currently, MemStats adds tiny alloc count to both Mallocs AND Frees.
  373. // The reason for this is because MemStats couldn't be extended at the time
  374. // but there was a desire to have Mallocs at least be a little more representative,
  375. // while having Mallocs - Frees still represent a live object count.
  376. // Unfortunately, MemStats doesn't actually export a large allocation count,
  377. // so it's impossible to pull this number out directly.
  378. tinyAllocs := lookupOrZero(goGCHeapTinyAllocsObjects)
  379. ms.Mallocs = lookupOrZero(goGCHeapAllocsObjects) + tinyAllocs
  380. ms.Frees = lookupOrZero(goGCHeapFreesObjects) + tinyAllocs
  381. ms.TotalAlloc = lookupOrZero(goGCHeapAllocsBytes)
  382. ms.Sys = lookupOrZero(goMemoryClassesTotalBytes)
  383. ms.Lookups = 0 // Already always zero.
  384. ms.HeapAlloc = lookupOrZero(goMemoryClassesHeapObjectsBytes)
  385. ms.Alloc = ms.HeapAlloc
  386. ms.HeapInuse = ms.HeapAlloc + lookupOrZero(goMemoryClassesHeapUnusedBytes)
  387. ms.HeapReleased = lookupOrZero(goMemoryClassesHeapReleasedBytes)
  388. ms.HeapIdle = ms.HeapReleased + lookupOrZero(goMemoryClassesHeapFreeBytes)
  389. ms.HeapSys = ms.HeapInuse + ms.HeapIdle
  390. ms.HeapObjects = lookupOrZero(goGCHeapObjects)
  391. ms.StackInuse = lookupOrZero(goMemoryClassesHeapStacksBytes)
  392. ms.StackSys = ms.StackInuse + lookupOrZero(goMemoryClassesOSStacksBytes)
  393. ms.MSpanInuse = lookupOrZero(goMemoryClassesMetadataMSpanInuseBytes)
  394. ms.MSpanSys = ms.MSpanInuse + lookupOrZero(goMemoryClassesMetadataMSPanFreeBytes)
  395. ms.MCacheInuse = lookupOrZero(goMemoryClassesMetadataMCacheInuseBytes)
  396. ms.MCacheSys = ms.MCacheInuse + lookupOrZero(goMemoryClassesMetadataMCacheFreeBytes)
  397. ms.BuckHashSys = lookupOrZero(goMemoryClassesProfilingBucketsBytes)
  398. ms.GCSys = lookupOrZero(goMemoryClassesMetadataOtherBytes)
  399. ms.OtherSys = lookupOrZero(goMemoryClassesOtherBytes)
  400. ms.NextGC = lookupOrZero(goGCHeapGoalBytes)
  401. // N.B. GCCPUFraction is intentionally omitted. This metric is not useful,
  402. // and often misleading due to the fact that it's an average over the lifetime
  403. // of the process.
  404. // See https://github.com/prometheus/client_golang/issues/842#issuecomment-861812034
  405. // for more details.
  406. ms.GCCPUFraction = 0
  407. }
  408. // batchHistogram is a mutable histogram that is updated
  409. // in batches.
  410. type batchHistogram struct {
  411. selfCollector
  412. // Static fields updated only once.
  413. desc *Desc
  414. hasSum bool
  415. // Because this histogram operates in batches, it just uses a
  416. // single mutex for everything. updates are always serialized
  417. // but Write calls may operate concurrently with updates.
  418. // Contention between these two sources should be rare.
  419. mu sync.Mutex
  420. buckets []float64 // Inclusive lower bounds, like runtime/metrics.
  421. counts []uint64
  422. sum float64 // Used if hasSum is true.
  423. }
  424. // newBatchHistogram creates a new batch histogram value with the given
  425. // Desc, buckets, and whether or not it has an exact sum available.
  426. //
  427. // buckets must always be from the runtime/metrics package, following
  428. // the same conventions.
  429. func newBatchHistogram(desc *Desc, buckets []float64, hasSum bool) *batchHistogram {
  430. // We need to remove -Inf values. runtime/metrics keeps them around.
  431. // But -Inf bucket should not be allowed for prometheus histograms.
  432. if buckets[0] == math.Inf(-1) {
  433. buckets = buckets[1:]
  434. }
  435. h := &batchHistogram{
  436. desc: desc,
  437. buckets: buckets,
  438. // Because buckets follows runtime/metrics conventions, there's
  439. // 1 more value in the buckets list than there are buckets represented,
  440. // because in runtime/metrics, the bucket values represent *boundaries*,
  441. // and non-Inf boundaries are inclusive lower bounds for that bucket.
  442. counts: make([]uint64, len(buckets)-1),
  443. hasSum: hasSum,
  444. }
  445. h.init(h)
  446. return h
  447. }
  448. // update updates the batchHistogram from a runtime/metrics histogram.
  449. //
  450. // sum must be provided if the batchHistogram was created to have an exact sum.
  451. // h.buckets must be a strict subset of his.Buckets.
  452. func (h *batchHistogram) update(his *metrics.Float64Histogram, sum float64) {
  453. counts, buckets := his.Counts, his.Buckets
  454. h.mu.Lock()
  455. defer h.mu.Unlock()
  456. // Clear buckets.
  457. for i := range h.counts {
  458. h.counts[i] = 0
  459. }
  460. // Copy and reduce buckets.
  461. var j int
  462. for i, count := range counts {
  463. h.counts[j] += count
  464. if buckets[i+1] == h.buckets[j+1] {
  465. j++
  466. }
  467. }
  468. if h.hasSum {
  469. h.sum = sum
  470. }
  471. }
  472. func (h *batchHistogram) Desc() *Desc {
  473. return h.desc
  474. }
  475. func (h *batchHistogram) Write(out *dto.Metric) error {
  476. h.mu.Lock()
  477. defer h.mu.Unlock()
  478. sum := float64(0)
  479. if h.hasSum {
  480. sum = h.sum
  481. }
  482. dtoBuckets := make([]*dto.Bucket, 0, len(h.counts))
  483. totalCount := uint64(0)
  484. for i, count := range h.counts {
  485. totalCount += count
  486. if !h.hasSum {
  487. if count != 0 {
  488. // N.B. This computed sum is an underestimate.
  489. sum += h.buckets[i] * float64(count)
  490. }
  491. }
  492. // Skip the +Inf bucket, but only for the bucket list.
  493. // It must still count for sum and totalCount.
  494. if math.IsInf(h.buckets[i+1], 1) {
  495. break
  496. }
  497. // Float64Histogram's upper bound is exclusive, so make it inclusive
  498. // by obtaining the next float64 value down, in order.
  499. upperBound := math.Nextafter(h.buckets[i+1], h.buckets[i])
  500. dtoBuckets = append(dtoBuckets, &dto.Bucket{
  501. CumulativeCount: proto.Uint64(totalCount),
  502. UpperBound: proto.Float64(upperBound),
  503. })
  504. }
  505. out.Histogram = &dto.Histogram{
  506. Bucket: dtoBuckets,
  507. SampleCount: proto.Uint64(totalCount),
  508. SampleSum: proto.Float64(sum),
  509. }
  510. return nil
  511. }