-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f6e96bf
commit a39a0cd
Showing
4 changed files
with
280 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package opencensus | ||
|
||
import ( | ||
"context" | ||
"runtime" | ||
"time" | ||
|
||
"github.com/pkg/errors" | ||
"go.opencensus.io/metric" | ||
"go.opencensus.io/metric/metricdata" | ||
) | ||
|
||
type ( | ||
// runtimeGauges collects runtime metrics in gauge entries. | ||
runtimeGauges struct { | ||
heapAlloc *metric.Int64GaugeEntry | ||
heapObjects *metric.Int64GaugeEntry | ||
heapReleased *metric.Int64GaugeEntry | ||
stackSys *metric.Int64GaugeEntry | ||
ptrLookups *metric.Int64GaugeEntry | ||
numGoroutines *metric.Int64GaugeEntry | ||
} | ||
|
||
GaugeOptions struct { | ||
Prefix string | ||
} | ||
) | ||
|
||
func NewRuntimeGauges(reg *metric.Registry, gaugeOptions GaugeOptions) (*runtimeGauges, error) { | ||
opt := gaugeOptions | ||
|
||
rg := new(runtimeGauges) | ||
var err error | ||
|
||
rg.heapAlloc, err = createInt64GaugeEntry(reg, opt.Prefix+"heap_alloc", "Process heap allocation", metricdata.UnitBytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
rg.heapObjects, err = createInt64GaugeEntry(reg, opt.Prefix+"heap_objects", "The number of objects allocated on the heap", metricdata.UnitDimensionless) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
rg.heapReleased, err = createInt64GaugeEntry(reg, opt.Prefix+"heap_release", "The number of objects released from the heap", metricdata.UnitBytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
rg.stackSys, err = createInt64GaugeEntry(reg, opt.Prefix+"stack_sys", "The memory used by stack spans and OS thread stacks", metricdata.UnitBytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
rg.ptrLookups, err = createInt64GaugeEntry(reg, opt.Prefix+"ptr_lookups", "The number of pointer lookups", metricdata.UnitDimensionless) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
rg.numGoroutines, err = createInt64GaugeEntry(reg, opt.Prefix+"num_goroutines", "Number of current goroutines", metricdata.UnitDimensionless) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return rg, nil | ||
} | ||
|
||
func (r *runtimeGauges) StartRecording(ctx context.Context, delay time.Duration) { | ||
mem := &runtime.MemStats{} | ||
|
||
tick := time.NewTicker(delay) | ||
defer tick.Stop() | ||
|
||
for { | ||
select { | ||
case <-ctx.Done(): | ||
return | ||
|
||
case <-tick.C: | ||
runtime.ReadMemStats(mem) | ||
r.heapAlloc.Set(int64(mem.HeapAlloc)) | ||
r.heapObjects.Set(int64(mem.HeapObjects)) | ||
r.heapReleased.Set(int64(mem.HeapReleased)) | ||
r.stackSys.Set(int64(mem.StackSys)) | ||
r.ptrLookups.Set(int64(mem.Lookups)) | ||
|
||
r.numGoroutines.Set(int64(runtime.NumGoroutine())) | ||
} | ||
} | ||
} | ||
|
||
func createInt64GaugeEntry(reg *metric.Registry, name string, description string, unit metricdata.Unit) (*metric.Int64GaugeEntry, error) { | ||
gauge, err := reg.AddInt64Gauge( | ||
name, | ||
metric.WithDescription(description), | ||
metric.WithUnit(unit)) | ||
if err != nil { | ||
return nil, errors.WithMessage(err, "error creating gauge for "+name) | ||
} | ||
|
||
entry, err := gauge.GetEntry() | ||
if err != nil { | ||
return nil, errors.WithMessage(err, "error getting gauge entry for "+name) | ||
} | ||
|
||
return entry, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package opencensus | ||
|
||
import ( | ||
"context" | ||
"io/ioutil" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
"time" | ||
|
||
"contrib.go.opencensus.io/exporter/prometheus" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"go.opencensus.io/metric" | ||
"go.opencensus.io/metric/metricdata" | ||
"go.opencensus.io/metric/metricexport" | ||
"go.opencensus.io/metric/metricproducer" | ||
) | ||
|
||
type testExporter struct { | ||
data []*metricdata.Metric | ||
} | ||
|
||
func (t *testExporter) ExportMetrics(ctx context.Context, data []*metricdata.Metric) error { | ||
t.data = append(t.data, data...) | ||
return nil | ||
} | ||
|
||
func TestRuntimeGauges(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
options GaugeOptions | ||
expectedNames []string | ||
}{ | ||
{ | ||
"custom prefix", | ||
GaugeOptions{Prefix: "test_"}, | ||
[]string{"test_heap_alloc", "test_heap_objects", "test_heap_release", "test_stack_sys", "test_ptr_lookups", "test_num_goroutines"}, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
reg := metric.NewRegistry() | ||
metricproducer.GlobalManager().AddProducer(reg) | ||
defer metricproducer.GlobalManager().DeleteProducer(reg) | ||
|
||
gauges, err := NewRuntimeGauges(reg, test.options) | ||
require.NoError(t, err) | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
time.AfterFunc(2*time.Second, func() { | ||
cancel() | ||
}) | ||
go gauges.StartRecording(ctx, 1*time.Second) | ||
|
||
exporter := &testExporter{} | ||
reader := metricexport.NewReader() | ||
reader.ReadAndExport(exporter) | ||
|
||
assertNames(t, exporter, test.expectedNames) | ||
}) | ||
} | ||
} | ||
|
||
func assertNames(t *testing.T, exporter *testExporter, expectedNames []string) { | ||
metricNames := make([]string, 0) | ||
for _, v := range exporter.data { | ||
metricNames = append(metricNames, v.Descriptor.Name) | ||
} | ||
assert.ElementsMatchf(t, expectedNames, metricNames, "actual: %v", metricNames) | ||
} | ||
|
||
func TestRuntimeGauges_WithPrometheus(t *testing.T) { | ||
reg := metric.NewRegistry() | ||
metricproducer.GlobalManager().AddProducer(reg) | ||
defer metricproducer.GlobalManager().DeleteProducer(reg) | ||
|
||
gauges, err := NewRuntimeGauges(reg, GaugeOptions{Prefix: "test_"}) | ||
require.NoError(t, err) | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
time.AfterFunc(2*time.Second, func() { | ||
cancel() | ||
}) | ||
go gauges.StartRecording(ctx, 1*time.Second) | ||
|
||
exporter, err := prometheus.NewExporter(prometheus.Options{}) | ||
require.NoError(t, err) | ||
|
||
server := httptest.NewServer(exporter) | ||
defer server.Close() | ||
|
||
// wait for at least one metric to be written | ||
<-time.After(1 * time.Second) | ||
|
||
resp, err := http.Get(server.URL) | ||
require.NoError(t, err) | ||
|
||
if resp.Body != nil { | ||
defer resp.Body.Close() | ||
} | ||
|
||
bytes, err := ioutil.ReadAll(resp.Body) | ||
require.NoError(t, err) | ||
|
||
strBody := string(bytes) | ||
assert.Regexp(t, "test_heap_alloc \\d+", strBody) | ||
assert.Regexp(t, "test_heap_objects \\d+", strBody) | ||
assert.Regexp(t, "test_heap_release \\d+", strBody) | ||
assert.Regexp(t, "test_stack_sys \\d+", strBody) | ||
assert.Regexp(t, "test_ptr_lookups \\d+", strBody) | ||
assert.Regexp(t, "test_num_goroutines \\d+", strBody) | ||
} |