Skip to content

Commit 77b3920

Browse files
committed
Correctly determine CPU usage since last call of Set()
1 parent 35903f0 commit 77b3920

File tree

5 files changed

+69
-21
lines changed

5 files changed

+69
-21
lines changed

cmd/exporter/handler.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@ package main
22

33
import (
44
"net/http"
5+
"sync"
56

67
"github.com/prometheus/client_golang/prometheus/promhttp"
78
"github.com/setlog/process_exporter/cmd/exporter/metrics"
89
)
910

10-
type httpHandler struct {
11+
type httpMetricsRequestHandler struct {
1112
metricsHandler http.Handler
12-
metricsSet *metrics.ProcessMetricsSet
13+
metricsSet *metrics.PrometheusProcessMetricsSet
14+
metricsMutex *sync.Mutex
1315
}
1416

15-
func newHttpHandler(set *metrics.ProcessMetricsSet) *httpHandler {
16-
return &httpHandler{
17+
func newHttpMetricsRequestHandler(set *metrics.PrometheusProcessMetricsSet, metricsMutex *sync.Mutex) *httpMetricsRequestHandler {
18+
return &httpMetricsRequestHandler{
1719
metricsHandler: promhttp.Handler(),
1820
metricsSet: set,
21+
metricsMutex: metricsMutex,
1922
}
2023
}
2124

22-
func (h *httpHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
23-
h.metricsSet.UpdateMonitoredSet()
24-
h.metricsSet.UpdateMetrics()
25+
func (h *httpMetricsRequestHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
26+
h.metricsMutex.Lock()
27+
defer h.metricsMutex.Unlock()
2528
h.metricsHandler.ServeHTTP(response, request)
2629
}

cmd/exporter/main.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,48 @@ package main
22

33
import (
44
"fmt"
5-
"log"
65
"net/http"
76
"os"
7+
"sync"
8+
"time"
89

910
"github.com/setlog/process_exporter/cmd/exporter/flags"
1011
"github.com/setlog/process_exporter/cmd/exporter/metrics"
12+
log "github.com/sirupsen/logrus"
1113
)
1214

1315
func main() {
1416
namespace, procBinaryName, nameFlag, port := flags.Parse(os.Args[1:])
15-
http.Handle("/metrics", newHttpHandler(metrics.NewProcessMetricsSet(namespace, procBinaryName, nameFlag)))
17+
metricsSet := metrics.NewPrometheusProcessMetricsSet(namespace, procBinaryName, nameFlag)
18+
mu := &sync.Mutex{}
19+
updateMetricsSet(metricsSet, mu)
20+
// ctx, cancelFunc := context.WithCancel(context.Background())
21+
go keepMetricsUpToDate(metricsSet, mu)
22+
http.Handle("/metrics", newHttpMetricsRequestHandler(metricsSet, mu))
1623
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
1724
}
25+
26+
func keepMetricsUpToDate(set *metrics.PrometheusProcessMetricsSet, mu *sync.Mutex) {
27+
defer func() {
28+
if r := recover(); r != nil {
29+
defer func() { go func() { time.Sleep(time.Second); log.Exit(1) }() }()
30+
log.Panicf("Panic in keepMetricsUpToDate(): %v", r)
31+
}
32+
}()
33+
ticker := time.NewTicker(time.Second * 10)
34+
for {
35+
select {
36+
case <-ticker.C:
37+
{
38+
updateMetricsSet(set, mu)
39+
}
40+
}
41+
}
42+
}
43+
44+
func updateMetricsSet(set *metrics.PrometheusProcessMetricsSet, mu *sync.Mutex) {
45+
mu.Lock()
46+
defer mu.Unlock()
47+
set.UpdateMonitoredSet()
48+
set.UpdateMetrics()
49+
}

cmd/exporter/metrics/proc_metrics.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package metrics
22

33
import (
44
"fmt"
5+
"time"
56

67
"github.com/shirou/gopsutil/process"
78
)
89

910
type ProcessMetrics struct {
10-
cpu float64
11+
cpuDuration float64
12+
cpuSampleTime time.Time
1113
ram uint64
1214
swap uint64
1315
diskReadBytes uint64
@@ -24,10 +26,11 @@ func getProcMetrics(pid int) (processMetrics *ProcessMetrics, err error) {
2426
return nil, fmt.Errorf("could not open PID %d: %w", pid, err)
2527
}
2628
m := &ProcessMetrics{}
27-
m.cpu, err = proc.CPUPercent()
29+
timeStat, err := proc.Times()
2830
if err != nil {
29-
return nil, fmt.Errorf("could not read CPU usage of PID %d: %w", pid, err)
31+
return nil, fmt.Errorf("could not read CPU times of PID %d: %w", pid, err)
3032
}
33+
m.cpuDuration, m.cpuSampleTime = timeStat.Total(), time.Now()
3134
mem, err := proc.MemoryInfo()
3235
if err != nil {
3336
return nil, fmt.Errorf("could not read memory info of PID %d: %w", pid, err)

cmd/exporter/metrics/prom_proc_metrics.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import (
66

77
"github.com/mitchellh/go-ps"
88
"github.com/prometheus/client_golang/prometheus"
9+
"github.com/prometheus/common/log"
910
)
1011

1112
type PrometheusProcessMetrics struct {
13+
previousMetrics *ProcessMetrics
1214
cpuGauge prometheus.Gauge
1315
ramGauge prometheus.Gauge
1416
swapGauge prometheus.Gauge
@@ -20,7 +22,7 @@ type PrometheusProcessMetrics struct {
2022
networkOutBytesGauge prometheus.Gauge
2123
}
2224

23-
func newProcessMetrics(proc ps.Process, descriptiveName, metricNamespace string) (processMetrics *PrometheusProcessMetrics) {
25+
func newPrometheusProcessMetrics(proc ps.Process, descriptiveName, metricNamespace string) (processMetrics *PrometheusProcessMetrics) {
2426
processMetrics = &PrometheusProcessMetrics{}
2527
binaryName := filepath.Base(proc.Executable())
2628
pid := fmt.Sprintf("%d", proc.Pid())
@@ -130,7 +132,14 @@ func (pm *PrometheusProcessMetrics) Update() {
130132
}
131133

132134
func (pm *PrometheusProcessMetrics) Set(processMetrics *ProcessMetrics) {
133-
pm.cpuGauge.Set(processMetrics.cpu)
135+
if pm.previousMetrics != nil {
136+
deltaTime := processMetrics.cpuSampleTime.Sub(pm.previousMetrics.cpuSampleTime).Seconds()
137+
if deltaTime > 0 {
138+
pm.cpuGauge.Set((processMetrics.cpuDuration - pm.previousMetrics.cpuDuration) / deltaTime)
139+
} else {
140+
log.Warn("deltaTime <= 0")
141+
}
142+
}
134143
pm.ramGauge.Set(float64(processMetrics.ram))
135144
pm.swapGauge.Set(float64(processMetrics.swap))
136145
pm.diskReadBytesGauge.Set(float64(processMetrics.diskReadBytes))
@@ -139,4 +148,5 @@ func (pm *PrometheusProcessMetrics) Set(processMetrics *ProcessMetrics) {
139148
pm.diskWriteCountGauge.Set(float64(processMetrics.diskWriteCount))
140149
pm.networkInBytesGauge.Set(float64(processMetrics.networkInBytes))
141150
pm.networkOutBytesGauge.Set(float64(processMetrics.networkOutBytes))
151+
pm.previousMetrics = processMetrics
142152
}

cmd/exporter/metrics/prom_proc_metrics_set.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,23 @@ import (
99
log "github.com/sirupsen/logrus"
1010
)
1111

12-
type ProcessMetricsSet struct {
12+
type PrometheusProcessMetricsSet struct {
1313
processMetrics map[int]*PrometheusProcessMetrics
1414
namespace string
1515
procBinaryName string
1616
nameFlag string
1717
}
1818

19-
func NewProcessMetricsSet(namespace, procBinaryName, nameFlag string) *ProcessMetricsSet {
20-
return &ProcessMetricsSet{
19+
func NewPrometheusProcessMetricsSet(namespace, procBinaryName, nameFlag string) *PrometheusProcessMetricsSet {
20+
return &PrometheusProcessMetricsSet{
2121
processMetrics: make(map[int]*PrometheusProcessMetrics),
2222
namespace: namespace,
2323
procBinaryName: procBinaryName,
2424
nameFlag: nameFlag,
2525
}
2626
}
2727

28-
func (set *ProcessMetricsSet) UpdateMonitoredSet() {
28+
func (set *PrometheusProcessMetricsSet) UpdateMonitoredSet() {
2929
if set.processMetrics == nil {
3030
panic("called update on disposed ProcessMetricsSet")
3131
}
@@ -36,7 +36,7 @@ func (set *ProcessMetricsSet) UpdateMonitoredSet() {
3636
}
3737
}
3838

39-
func (set *ProcessMetricsSet) Dispose() {
39+
func (set *PrometheusProcessMetricsSet) Dispose() {
4040
for _, metrics := range set.processMetrics {
4141
metrics.Unregister()
4242
}
@@ -67,7 +67,7 @@ func AdjustMetricsMap(metricMap map[int]*PrometheusProcessMetrics, pids map[int]
6767
errs = append(errs, fmt.Errorf("failed to get descriptive process name for PID %d: %w", pid, err))
6868
continue
6969
}
70-
m := newProcessMetrics(pids[pid], name, metricNamespace)
70+
m := newPrometheusProcessMetrics(pids[pid], name, metricNamespace)
7171
err = m.Register()
7272
if err != nil {
7373
errs = append(errs, fmt.Errorf("failed to register process metrics for PID %d: %w", pid, err))
@@ -99,7 +99,7 @@ func FindPidDifferences(pidMap map[int]*PrometheusProcessMetrics, wantedPids map
9999
return removePids, newPids
100100
}
101101

102-
func (set *ProcessMetricsSet) UpdateMetrics() {
102+
func (set *PrometheusProcessMetricsSet) UpdateMetrics() {
103103
for pid, processMetrics := range set.processMetrics {
104104
updateMetrics(processMetrics, pid)
105105
}

0 commit comments

Comments
 (0)