Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 

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