Skip to content

Commit

Permalink
Add uptime & rss self-observability metrics, and fix cpu time to work…
Browse files Browse the repository at this point in the history
… on non-Linux OSs
  • Loading branch information
james-bebbington committed Aug 14, 2020
1 parent e68440e commit 825e0b2
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 18 deletions.
63 changes: 47 additions & 16 deletions internal/collector/telemetry/process_telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,30 @@ import (
"runtime"
"time"

"github.com/prometheus/procfs"
"github.com/shirou/gopsutil/process"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
)

// ProcessMetricsViews is a struct that contains views related to process metrics (cpu, mem, etc)
type ProcessMetricsViews struct {
ballastSizeBytes uint64
views []*view.View
done chan struct{}
proc *procfs.Proc
startTimeUnixNano int64
ballastSizeBytes uint64
views []*view.View
done chan struct{}
proc *process.Process
}

var mUptime = stats.Int64(
"process/uptime",
"Uptime of the process",
stats.UnitSeconds)
var viewProcessUptime = &view.View{
Name: mUptime.Name(),
Description: mUptime.Description(),
Measure: mUptime,
Aggregation: view.LastValue(),
TagKeys: nil,
}

var mRuntimeAllocMem = stats.Int64(
Expand Down Expand Up @@ -81,23 +94,37 @@ var viewCPUSeconds = &view.View{
TagKeys: nil,
}

var mRSSMemory = stats.Int64(
"process/memory/rss",
"Total physical memory (resident set size)",
stats.UnitDimensionless)
var viewRSSMemory = &view.View{
Name: mRSSMemory.Name(),
Description: mRSSMemory.Description(),
Measure: mRSSMemory,
Aggregation: view.LastValue(),
TagKeys: nil,
}

// NewProcessMetricsViews creates a new set of ProcessMetrics (mem, cpu) that can be used to measure
// basic information about this process.
func NewProcessMetricsViews(ballastSizeBytes uint64) *ProcessMetricsViews {
func NewProcessMetricsViews(ballastSizeBytes uint64) (*ProcessMetricsViews, error) {
pmv := &ProcessMetricsViews{
ballastSizeBytes: ballastSizeBytes,
views: []*view.View{viewAllocMem, viewTotalAllocMem, viewSysMem, viewCPUSeconds},
done: make(chan struct{}),
startTimeUnixNano: time.Now().UnixNano(),
ballastSizeBytes: ballastSizeBytes,
views: []*view.View{viewProcessUptime, viewAllocMem, viewTotalAllocMem, viewSysMem, viewCPUSeconds, viewRSSMemory},
done: make(chan struct{}),
}

// procfs.Proc is not available on windows and expected to fail.
pid := os.Getpid()
proc, err := procfs.NewProc(pid)
if err == nil {
pmv.proc = &proc

var err error
pmv.proc, err = process.NewProcess(int32(pid))
if err != nil {
return nil, err
}

return pmv
return pmv, nil
}

// StartCollection starts a ticker'd goroutine that will update the PMV measurements every 5 seconds
Expand Down Expand Up @@ -128,14 +155,18 @@ func (pmv *ProcessMetricsViews) StopCollection() {

func (pmv *ProcessMetricsViews) updateViews() {
ms := &runtime.MemStats{}
stats.Record(context.Background(), mUptime.M(time.Now().UnixNano()-pmv.startTimeUnixNano))
pmv.readMemStats(ms)
stats.Record(context.Background(), mRuntimeAllocMem.M(int64(ms.Alloc)))
stats.Record(context.Background(), mRuntimeTotalAllocMem.M(int64(ms.TotalAlloc)))
stats.Record(context.Background(), mRuntimeSysMem.M(int64(ms.Sys)))

if pmv.proc != nil {
if procStat, err := pmv.proc.Stat(); err == nil {
stats.Record(context.Background(), mCPUSeconds.M(int64(procStat.CPUTime())))
if times, err := pmv.proc.Times(); err == nil {
stats.Record(context.Background(), mCPUSeconds.M(int64(times.Total())))
}
if mem, err := pmv.proc.MemoryInfo(); err == nil {
stats.Record(context.Background(), mRSSMemory.M(int64(mem.RSS)))
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion internal/collector/telemetry/process_telemetry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,20 @@ import (
func TestProcessTelemetry(t *testing.T) {
const ballastSizeBytes uint64 = 0

pmv := NewProcessMetricsViews(ballastSizeBytes)
pmv, err := NewProcessMetricsViews(ballastSizeBytes)
require.NoError(t, err)
assert.NotNil(t, pmv)

expectedViews := []string{
// Changing a metric name is a breaking change.
// Adding new metrics is ok as long it follows the conventions described at
// https://pkg.go.dev/go.opentelemetry.io/collector/obsreport?tab=doc#hdr-Naming_Convention_for_New_Metrics
"process/uptime",
"process/runtime/heap_alloc_bytes",
"process/runtime/total_alloc_bytes",
"process/runtime/total_sys_memory_bytes",
"process/cpu_seconds",
"process/memory/rss",
}
processViews := pmv.Views()
assert.Len(t, processViews, len(expectedViews))
Expand Down
6 changes: 5 additions & 1 deletion service/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,17 @@ func (tel *appTelemetry) init(asyncErrorChannel chan<- error, ballastSizeBytes u
return nil
}

processMetricsViews, err := telemetry.NewProcessMetricsViews(ballastSizeBytes)
if err != nil {
return err
}

var views []*view.View
views = append(views, obsreport.Configure(telemetry.UseLegacyMetrics(), telemetry.UseNewMetrics())...)
views = append(views, processor.MetricViews(level)...)
views = append(views, queuedprocessor.MetricViews(level)...)
views = append(views, batchprocessor.MetricViews(level)...)
views = append(views, tailsamplingprocessor.SamplingProcessorMetricViews(level)...)
processMetricsViews := telemetry.NewProcessMetricsViews(ballastSizeBytes)
views = append(views, processMetricsViews.Views()...)
views = append(views, fluentobserv.Views(level)...)
tel.views = views
Expand Down

0 comments on commit 825e0b2

Please sign in to comment.