@@ -32,9 +32,10 @@ type goCollector struct {
32
32
base baseGoCollector
33
33
34
34
// rm... fields all pertain to the runtime/metrics package.
35
+ rmSampleMu sync.Mutex
35
36
rmSampleBuf []metrics.Sample
36
37
rmSampleMap map [string ]* metrics.Sample
37
- rmMetrics []Metric
38
+ rmMetrics []collectorMetric
38
39
39
40
// With Go 1.17, the runtime/metrics package was introduced.
40
41
// From that point on, metric names produced by the runtime/metrics
@@ -58,7 +59,7 @@ func NewGoCollector() Collector {
58
59
}
59
60
60
61
// Generate a Desc and ValueType for each runtime/metrics metric.
61
- metricSet := make ([]Metric , 0 , len (descriptions ))
62
+ metricSet := make ([]collectorMetric , 0 , len (descriptions ))
62
63
sampleBuf := make ([]metrics.Sample , 0 , len (descriptions ))
63
64
sampleMap := make (map [string ]* metrics.Sample , len (descriptions ))
64
65
for i := range descriptions {
@@ -76,7 +77,7 @@ func NewGoCollector() Collector {
76
77
sampleBuf = append (sampleBuf , metrics.Sample {Name : d .Name })
77
78
sampleMap [d .Name ] = & sampleBuf [len (sampleBuf )- 1 ]
78
79
79
- var m Metric
80
+ var m collectorMetric
80
81
if d .Kind == metrics .KindFloat64Histogram {
81
82
_ , hasSum := rmExactSumMap [d .Name ]
82
83
m = newBatchHistogram (
@@ -130,9 +131,19 @@ func (c *goCollector) Collect(ch chan<- Metric) {
130
131
// Collect base non-memory metrics.
131
132
c .base .Collect (ch )
132
133
134
+ // Collect must be thread-safe, so prevent concurrent use of
135
+ // rmSampleBuf. Just read into rmSampleBuf but write all the data
136
+ // we get into our Metrics or MemStats.
137
+ //
138
+ // Note that we cannot simply read and then clone rmSampleBuf
139
+ // because we'd need to perform a deep clone of it, which is likely
140
+ // not worth it.
141
+ c .rmSampleMu .Lock ()
142
+
133
143
// Populate runtime/metrics sample buffer.
134
144
metrics .Read (c .rmSampleBuf )
135
145
146
+ // Update all our metrics from rmSampleBuf.
136
147
for i , sample := range c .rmSampleBuf {
137
148
// N.B. switch on concrete type because it's significantly more efficient
138
149
// than checking for the Counter and Gauge interface implementations. In
@@ -146,22 +157,29 @@ func (c *goCollector) Collect(ch chan<- Metric) {
146
157
if v1 > v0 {
147
158
m .Add (unwrapScalarRMValue (sample .Value ) - m .get ())
148
159
}
149
- m .Collect (ch )
150
160
case * gauge :
151
161
m .Set (unwrapScalarRMValue (sample .Value ))
152
- m .Collect (ch )
153
162
case * batchHistogram :
154
163
m .update (sample .Value .Float64Histogram (), c .exactSumFor (sample .Name ))
155
- m .Collect (ch )
156
164
default :
157
165
panic ("unexpected metric type" )
158
166
}
159
167
}
160
-
161
168
// ms is a dummy MemStats that we populate ourselves so that we can
162
169
// populate the old metrics from it.
163
170
var ms runtime.MemStats
164
171
memStatsFromRM (& ms , c .rmSampleMap )
172
+
173
+ c .rmSampleMu .Unlock ()
174
+
175
+ // Export all the metrics to ch.
176
+ // At this point we must not access rmSampleBuf or rmSampleMap, because
177
+ // a concurrent caller could use it. It's safe to Collect all our Metrics,
178
+ // however, because they're updated in a thread-safe way while MemStats
179
+ // is local to this call of Collect.
180
+ for _ , m := range c .rmMetrics {
181
+ m .Collect (ch )
182
+ }
165
183
for _ , i := range c .msMetrics {
166
184
ch <- MustNewConstMetric (i .desc , i .valType , i .eval (& ms ))
167
185
}
0 commit comments