Skip to content

Commit 86bd077

Browse files
committed
Merge pull request #113 from oliver006/add_gc_metrics
Add garbage collection stats
2 parents 6efaf95 + 21b132f commit 86bd077

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

prometheus/go_collector.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package prometheus
22

33
import (
44
"runtime"
5+
"runtime/debug"
6+
"time"
57
)
68

79
type goCollector struct {
810
goroutines Gauge
11+
gcDesc *Desc
912
}
1013

1114
// NewGoCollector returns a collector which exports metrics about the current
@@ -16,16 +19,32 @@ func NewGoCollector() *goCollector {
1619
Name: "process_goroutines",
1720
Help: "Number of goroutines that currently exist.",
1821
}),
22+
gcDesc: NewDesc(
23+
"go_gc_duration_seconds",
24+
"A summary of the GC invocation durations.",
25+
nil, nil),
1926
}
2027
}
2128

2229
// Describe returns all descriptions of the collector.
2330
func (c *goCollector) Describe(ch chan<- *Desc) {
2431
ch <- c.goroutines.Desc()
32+
ch <- c.gcDesc
2533
}
2634

2735
// Collect returns the current state of all metrics of the collector.
2836
func (c *goCollector) Collect(ch chan<- Metric) {
2937
c.goroutines.Set(float64(runtime.NumGoroutine()))
3038
ch <- c.goroutines
39+
40+
var stats debug.GCStats
41+
stats.PauseQuantiles = make([]time.Duration, 5)
42+
debug.ReadGCStats(&stats)
43+
44+
quantiles := make(map[float64]float64)
45+
for idx, pq := range stats.PauseQuantiles[1:] {
46+
quantiles[float64(idx+1)/float64(len(stats.PauseQuantiles)-1)] = pq.Seconds()
47+
}
48+
quantiles[0.0] = stats.PauseQuantiles[0].Seconds()
49+
ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), float64(stats.PauseTotal.Seconds()), quantiles)
3150
}

prometheus/go_collector_test.go

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package prometheus
22

33
import (
4-
"reflect"
4+
"runtime"
55
"testing"
66
"time"
77

@@ -35,6 +35,9 @@ func TestGoCollector(t *testing.T) {
3535
case Gauge:
3636
pb := &dto.Metric{}
3737
m.Write(pb)
38+
if pb.GetGauge() == nil {
39+
continue
40+
}
3841

3942
if old == -1 {
4043
old = int(pb.GetGauge().GetValue())
@@ -48,8 +51,66 @@ func TestGoCollector(t *testing.T) {
4851
}
4952

5053
return
51-
default:
52-
t.Errorf("want type Gauge, got %s", reflect.TypeOf(metric))
54+
}
55+
case <-time.After(1 * time.Second):
56+
t.Fatalf("expected collect timed out")
57+
}
58+
}
59+
}
60+
61+
func TestGCCollector(t *testing.T) {
62+
var (
63+
c = NewGoCollector()
64+
ch = make(chan Metric)
65+
waitc = make(chan struct{})
66+
closec = make(chan struct{})
67+
oldGC uint64
68+
oldPause float64
69+
)
70+
defer close(closec)
71+
72+
go func() {
73+
c.Collect(ch)
74+
// force GC
75+
runtime.GC()
76+
<-waitc
77+
c.Collect(ch)
78+
}()
79+
80+
first := true
81+
for {
82+
select {
83+
case metric := <-ch:
84+
switch m := metric.(type) {
85+
case *constSummary, *value:
86+
pb := &dto.Metric{}
87+
m.Write(pb)
88+
if pb.GetSummary() == nil {
89+
continue
90+
}
91+
92+
if len(pb.GetSummary().Quantile) != 5 {
93+
t.Errorf("expected 4 buckets, got %d", len(pb.GetSummary().Quantile))
94+
}
95+
for idx, want := range []float64{0.0, 0.25, 0.5, 0.75, 1.0} {
96+
if *pb.GetSummary().Quantile[idx].Quantile != want {
97+
t.Errorf("bucket #%d is off, got %f, want %f", idx, *pb.GetSummary().Quantile[idx].Quantile, want)
98+
}
99+
}
100+
if first {
101+
first = false
102+
oldGC = *pb.GetSummary().SampleCount
103+
oldPause = *pb.GetSummary().SampleSum
104+
close(waitc)
105+
continue
106+
}
107+
if diff := *pb.GetSummary().SampleCount - oldGC; diff != 1 {
108+
t.Errorf("want 1 new garbage collection run, got %d", diff)
109+
}
110+
if diff := *pb.GetSummary().SampleSum - oldPause; diff <= 0 {
111+
t.Errorf("want moar pause, got %f", diff)
112+
}
113+
return
53114
}
54115
case <-time.After(1 * time.Second):
55116
t.Fatalf("expected collect timed out")

0 commit comments

Comments
 (0)