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

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