From ff586eaac16c26ec85429cc0b8c0ab72f1c6e2c5 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 30 Apr 2015 00:27:14 -0400 Subject: [PATCH 1/2] Add garbage collection stats --- prometheus/go_collector.go | 12 +++++++ prometheus/go_collector_test.go | 59 +++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/prometheus/go_collector.go b/prometheus/go_collector.go index d7b7a20..931868b 100644 --- a/prometheus/go_collector.go +++ b/prometheus/go_collector.go @@ -2,10 +2,12 @@ package prometheus import ( "runtime" + "runtime/debug" ) type goCollector struct { goroutines Gauge + gcDesc *Desc } // NewGoCollector returns a collector which exports metrics about the current @@ -16,16 +18,26 @@ func NewGoCollector() *goCollector { Name: "process_goroutines", Help: "Number of goroutines that currently exist.", }), + gcDesc: NewDesc( + "go_gc_duration_seconds", + "A summary of the GC invocation durations.", + nil, nil), } } // Describe returns all descriptions of the collector. func (c *goCollector) Describe(ch chan<- *Desc) { ch <- c.goroutines.Desc() + ch <- c.gcDesc } // Collect returns the current state of all metrics of the collector. func (c *goCollector) Collect(ch chan<- Metric) { c.goroutines.Set(float64(runtime.NumGoroutine())) ch <- c.goroutines + + var stats debug.GCStats + debug.ReadGCStats(&stats) + + ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), float64(stats.PauseTotal.Seconds()), nil) } diff --git a/prometheus/go_collector_test.go b/prometheus/go_collector_test.go index b0582d1..16f389b 100644 --- a/prometheus/go_collector_test.go +++ b/prometheus/go_collector_test.go @@ -1,7 +1,7 @@ package prometheus import ( - "reflect" + "runtime/debug" "testing" "time" @@ -35,6 +35,9 @@ func TestGoCollector(t *testing.T) { case Gauge: pb := &dto.Metric{} m.Write(pb) + if pb.GetGauge() == nil { + continue + } if old == -1 { old = int(pb.GetGauge().GetValue()) @@ -48,8 +51,58 @@ func TestGoCollector(t *testing.T) { } return - default: - t.Errorf("want type Gauge, got %s", reflect.TypeOf(metric)) + } + case <-time.After(1 * time.Second): + t.Fatalf("expected collect timed out") + } + } +} + +func TestGCCollector(t *testing.T) { + var ( + c = NewGoCollector() + ch = make(chan Metric) + waitc = make(chan struct{}) + closec = make(chan struct{}) + oldGC uint64 + oldPause float64 + ) + defer close(closec) + + go func() { + c.Collect(ch) + // force GC + debug.FreeOSMemory() + <-waitc + c.Collect(ch) + }() + + first := true + for { + select { + case metric := <-ch: + switch m := metric.(type) { + case *constSummary, *value: + pb := &dto.Metric{} + m.Write(pb) + if pb.GetSummary() == nil { + continue + } + + if first { + first = false + oldGC = *pb.GetSummary().SampleCount + oldPause = *pb.GetSummary().SampleSum + close(waitc) + continue + } + if diff := *pb.GetSummary().SampleCount - oldGC; diff != 1 { + t.Errorf("want 1 new garbage collection run, got %d", diff) + } + if diff := *pb.GetSummary().SampleSum - oldPause; diff <= 0 { + t.Errorf("want moar pause, got %f", diff) + } + return } case <-time.After(1 * time.Second): t.Fatalf("expected collect timed out") From 21b132f5a2c92116ab4599c5b2571805f80fb678 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 4 May 2015 19:20:11 -0400 Subject: [PATCH 2/2] record quantiles as well --- prometheus/go_collector.go | 9 ++++++++- prometheus/go_collector_test.go | 12 ++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/prometheus/go_collector.go b/prometheus/go_collector.go index 931868b..dbf4c30 100644 --- a/prometheus/go_collector.go +++ b/prometheus/go_collector.go @@ -3,6 +3,7 @@ package prometheus import ( "runtime" "runtime/debug" + "time" ) type goCollector struct { @@ -37,7 +38,13 @@ func (c *goCollector) Collect(ch chan<- Metric) { ch <- c.goroutines var stats debug.GCStats + stats.PauseQuantiles = make([]time.Duration, 5) debug.ReadGCStats(&stats) - ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), float64(stats.PauseTotal.Seconds()), nil) + quantiles := make(map[float64]float64) + for idx, pq := range stats.PauseQuantiles[1:] { + quantiles[float64(idx+1)/float64(len(stats.PauseQuantiles)-1)] = pq.Seconds() + } + quantiles[0.0] = stats.PauseQuantiles[0].Seconds() + ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), float64(stats.PauseTotal.Seconds()), quantiles) } diff --git a/prometheus/go_collector_test.go b/prometheus/go_collector_test.go index 16f389b..b75d28e 100644 --- a/prometheus/go_collector_test.go +++ b/prometheus/go_collector_test.go @@ -1,7 +1,7 @@ package prometheus import ( - "runtime/debug" + "runtime" "testing" "time" @@ -72,7 +72,7 @@ func TestGCCollector(t *testing.T) { go func() { c.Collect(ch) // force GC - debug.FreeOSMemory() + runtime.GC() <-waitc c.Collect(ch) }() @@ -89,6 +89,14 @@ func TestGCCollector(t *testing.T) { continue } + if len(pb.GetSummary().Quantile) != 5 { + t.Errorf("expected 4 buckets, got %d", len(pb.GetSummary().Quantile)) + } + for idx, want := range []float64{0.0, 0.25, 0.5, 0.75, 1.0} { + if *pb.GetSummary().Quantile[idx].Quantile != want { + t.Errorf("bucket #%d is off, got %f, want %f", idx, *pb.GetSummary().Quantile[idx].Quantile, want) + } + } if first { first = false oldGC = *pb.GetSummary().SampleCount