Skip to content

Commit c165018

Browse files
authored
Expose more stats via Prometheus (#29)
* Expose more stats
1 parent df7778e commit c165018

File tree

7 files changed

+179
-49
lines changed

7 files changed

+179
-49
lines changed

rpc/decoder.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,49 @@ func Decode(b []byte) (*EnvelopeDecoder, error) {
241241
}
242242
return &env, nil
243243
}
244+
245+
// nolint:gocyclo
246+
func (env EnvelopeDecoder) Method() string {
247+
switch {
248+
case env.Body.GetRPCMethods != nil:
249+
return "GetRPCMethods"
250+
case env.Body.SetParameterValues != nil:
251+
return "SetParameterValues"
252+
case env.Body.GetParameterValues != nil:
253+
return "GetParameterValues"
254+
case env.Body.GetParameterNames != nil:
255+
return "GetParameterNames"
256+
case env.Body.SetParameterAttributes != nil:
257+
return "SetParameterAttributes"
258+
case env.Body.GetParameterAttributes != nil:
259+
return "GetParameterAttributes"
260+
case env.Body.AddObject != nil:
261+
return "AddObject"
262+
case env.Body.DeleteObject != nil:
263+
return "DeleteObject"
264+
case env.Body.Reboot != nil:
265+
return "Reboot"
266+
case env.Body.Download != nil:
267+
return "Download"
268+
case env.Body.Upload != nil:
269+
return "Upload"
270+
case env.Body.FactoryReset != nil:
271+
return "FactoryReset"
272+
case env.Body.GetQueuedTransfers != nil:
273+
return "GetQueuedTransfers"
274+
case env.Body.GetAllQueuedTransfers != nil:
275+
return "GetAllQueuedTransfers"
276+
case env.Body.ScheduleInform != nil:
277+
return "ScheduleInform"
278+
case env.Body.SetVouchers != nil:
279+
return "SetVouchers"
280+
case env.Body.GetOptions != nil:
281+
return "GetOptions"
282+
case env.Body.Fault != nil:
283+
return "Fault"
284+
case env.Body.TransferCompleteResponse != nil:
285+
return "TransferCompleteResponse"
286+
default:
287+
return "Unknown"
288+
}
289+
}

simulator/get_parameter_values.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func (s *Simulator) handleGetParameterValues(envID string, r *rpc.GetParameterVa
3232
}
3333
}
3434

35+
s.metrics.ParametersRead.Add(float64(len(params)))
3536
resp.Body.GetParameterValuesResponse = &rpc.GetParameterValuesResponseEncoder{
3637
ParameterList: rpc.ParameterListEncoder{
3738
ArrayType: rpc.ArrayType("cwmp:ParameterValue", len(params)),

simulator/inform.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"net"
1111
"net/http"
1212
"net/url"
13-
"strconv"
1413
"time"
1514

1615
"github.com/icholy/digest"
@@ -83,20 +82,15 @@ func (s *Simulator) startSession(ctx context.Context, handler sessionHandler) {
8382
}
8483
defer s.sessionMux.Unlock()
8584

85+
s.metrics.SessionsAttempted.Inc()
8686
u, err := url.Parse(Config.ACSURL)
8787
if err != nil {
8888
log.Error().Err(err).Msg("Failed to parse ACS URL")
8989
return
9090
}
9191

92-
connectionStartTime := time.Now()
93-
defer func() {
94-
s.metrics.ConcurrentInforms.Dec()
95-
s.metrics.InformDuration.Observe(float64(time.Since(connectionStartTime).Milliseconds()))
96-
}()
97-
s.metrics.ConcurrentInforms.Inc()
98-
9992
log.Info().Str("acs_url", Config.ACSURL).Msg("Connecting to ACS")
93+
connectionStartTime := time.Now()
10094
client, closeFn, err := newClient(u.Hostname(), tcpPort(u))
10195
s.metrics.ConnectionLatency.Observe(float64(time.Since(connectionStartTime).Milliseconds()))
10296
if err != nil {
@@ -107,13 +101,26 @@ func (s *Simulator) startSession(ctx context.Context, handler sessionHandler) {
107101
}
108102
defer func() { _ = closeFn() }()
109103

104+
s.metrics.SessionsEstablished.Inc()
110105
handler(ctx, &client)
111106
}
112107

113108
// nolint:gocyclo
114109
func (s *Simulator) informHandler(ctx context.Context, client *http.Client) {
115110
log.Info().Msg("Starting inform")
116111
informEnv := s.makeInformEnvelope()
112+
113+
evt := informEnv.Body.Inform.Event.Events[0]
114+
startedAt := time.Now()
115+
s.metrics.ConcurrentSessions.Inc()
116+
s.metrics.InformEvents.With(prometheus.Labels{"event": evt.EventCode}).Inc()
117+
defer func() {
118+
s.metrics.ConcurrentSessions.Dec()
119+
s.metrics.SessionDuration.With(prometheus.Labels{
120+
"event": evt.EventCode,
121+
}).Observe(float64(time.Since(startedAt).Milliseconds()))
122+
}()
123+
117124
resp, err := s.request(ctx, client, informEnv)
118125
if err != nil {
119126
log.Error().Err(err).Msg("Failed to make request")
@@ -174,9 +181,11 @@ pendingRequests:
174181
}
175182
}
176183

184+
s.metrics.SessionsCompleted.Inc()
177185
for _, evt := range informEnv.Body.Inform.Event.Events {
178186
if evt.EventCode == rpc.EventBootstrap {
179187
s.dm.SetBootstrapped(true)
188+
s.metrics.Bootstrapped.Inc()
180189
break
181190
}
182191
}
@@ -282,9 +291,7 @@ func (s *Simulator) request(ctx context.Context, client *http.Client, env *rpc.E
282291
return nil, fmt.Errorf("execute request: %w", err)
283292
}
284293
s.cookies.SetCookies(req.URL, resp.Cookies())
285-
s.metrics.ResponseStatus.With(prometheus.Labels{
286-
"status": strconv.Itoa(resp.StatusCode),
287-
}).Inc()
294+
s.metrics.ResponseStatus.With(prometheus.Labels{"status": resp.Status}).Inc()
288295

289296
return resp, nil
290297
}

simulator/metrics/metrics.go

Lines changed: 86 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,59 +7,121 @@ import (
77
)
88

99
type Metrics struct {
10-
Registrer prometheus.Registerer
11-
RequestFailures prometheus.Counter
12-
ResponseStatus prometheus_CounterVec
13-
ConnectionLatency prometheus.Histogram
14-
InformDuration prometheus.Histogram
15-
ConcurrentInforms prometheus.Gauge
10+
Bootstrapped prometheus.Counter
11+
ConnectionLatency prometheus.Histogram
12+
MethodCalls prometheus_CounterVec
13+
RequestFailures prometheus.Counter
14+
ResponseStatus prometheus_CounterVec
15+
SessionsAttempted prometheus.Counter
16+
SessionsEstablished prometheus.Counter
17+
SessionsCompleted prometheus.Counter
18+
SessionDuration prometheus_HistogramVec
19+
ConcurrentSessions prometheus.Gauge
20+
InformEvents prometheus_CounterVec
21+
ParametersRead prometheus.Counter
22+
ParametersWritten prometheus.Counter
1623
}
1724

18-
// Odd name, I know.
25+
// prometheus.CounterVec is a struct, not an interface. We can't reimplement it
26+
// so instead a custom interface is defined.
1927
// nolint:revive,stylecheck
2028
type prometheus_CounterVec interface {
2129
prometheus.Collector
2230
With(prometheus.Labels) prometheus.Counter
2331
}
2432

33+
// prometheus.HistogramVec is a struct, not an interface. We can't reimplement
34+
// it so instead a custom interface is defined.
35+
// nolint:revive,stylecheck
36+
type prometheus_HistogramVec interface {
37+
prometheus.Collector
38+
With(prometheus.Labels) prometheus.Observer
39+
}
40+
2541
func New(reg prometheus.Registerer) *Metrics {
2642
m := &Metrics{
43+
Bootstrapped: prometheus.NewCounter(prometheus.CounterOpts{
44+
Name: "bootstrapped",
45+
Help: "Number of successful bootstraps",
46+
}),
47+
ConnectionLatency: prometheus.NewHistogram(prometheus.HistogramOpts{
48+
Name: "acs_connection_latency",
49+
Help: "Time it takes to establish a connection to ACS in milliseconds",
50+
Buckets: prometheus.LinearBuckets(0.5, 0.5, 10),
51+
NativeHistogramMaxExemplars: 100,
52+
NativeHistogramExemplarTTL: 15 * time.Second,
53+
}),
54+
MethodCalls: prometheus.NewCounterVec(
55+
prometheus.CounterOpts{
56+
Name: "rpc_method_calls",
57+
Help: "Number of RPC method calls",
58+
},
59+
[]string{"method"},
60+
),
2761
RequestFailures: prometheus.NewCounter(prometheus.CounterOpts{
2862
Name: "acs_request_failures",
29-
Help: "Number of times requests to ACS failed.",
63+
Help: "Number of times requests to ACS failed",
3064
}),
3165
ResponseStatus: prometheus.NewCounterVec(
3266
prometheus.CounterOpts{
3367
Name: "acs_response_status",
34-
Help: "ACS response status code.",
68+
Help: "ACS response status code",
3569
},
3670
[]string{"status"},
3771
),
38-
ConcurrentInforms: prometheus.NewGauge(prometheus.GaugeOpts{
39-
Name: "concurrent_informs",
40-
Help: "The number of inform sessions in progress.",
72+
SessionsAttempted: prometheus.NewCounter(prometheus.CounterOpts{
73+
Name: "sessions_attempted",
74+
Help: "Number of attempted sessions",
4175
}),
42-
ConnectionLatency: prometheus.NewHistogram(prometheus.HistogramOpts{
43-
Name: "acs_connection_latency",
44-
Help: "Time it takes to establish a connection to ACS in milliseconds.",
45-
Buckets: prometheus.LinearBuckets(0.5, 0.5, 10),
46-
NativeHistogramMaxExemplars: 100,
47-
NativeHistogramExemplarTTL: 15 * time.Second,
76+
SessionsEstablished: prometheus.NewCounter(prometheus.CounterOpts{
77+
Name: "sessions_established",
78+
Help: "Number of established sessions",
79+
}),
80+
SessionsCompleted: prometheus.NewCounter(prometheus.CounterOpts{
81+
Name: "sessions_completed",
82+
Help: "Number of completed sessions",
4883
}),
49-
InformDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
50-
Name: "inform_duration",
51-
Help: "Inform length in milliseconds.",
84+
SessionDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
85+
Name: "session_duration_per_event",
86+
Help: "Session length in milliseconds",
5287
Buckets: prometheus.ExponentialBuckets(1, 10, 10),
5388
NativeHistogramMaxExemplars: 100,
5489
NativeHistogramExemplarTTL: 15 * time.Minute,
90+
}, []string{"event"}),
91+
ConcurrentSessions: prometheus.NewGauge(prometheus.GaugeOpts{
92+
Name: "concurrent_sessions",
93+
Help: "The number of concurrent in-progress sessions",
94+
}),
95+
InformEvents: prometheus.NewCounterVec(
96+
prometheus.CounterOpts{
97+
Name: "inform_events",
98+
Help: "Inform event count by event",
99+
},
100+
[]string{"event"},
101+
),
102+
ParametersRead: prometheus.NewCounter(prometheus.CounterOpts{
103+
Name: "parameters_read",
104+
Help: "Number of parameters accessed via GetParameterValues",
105+
}),
106+
ParametersWritten: prometheus.NewCounter(prometheus.CounterOpts{
107+
Name: "parameters_written",
108+
Help: "Number of parameters changed via SetParameterValues",
55109
}),
56110
}
57111
reg.MustRegister(
112+
m.Bootstrapped,
113+
m.ConnectionLatency,
114+
m.MethodCalls,
58115
m.RequestFailures,
59116
m.ResponseStatus,
60-
m.ConcurrentInforms,
61-
m.ConnectionLatency,
62-
m.InformDuration,
117+
m.SessionsAttempted,
118+
m.SessionsEstablished,
119+
m.SessionsCompleted,
120+
m.SessionDuration,
121+
m.ConcurrentSessions,
122+
m.InformEvents,
123+
m.ParametersRead,
124+
m.ParametersWritten,
63125
)
64126
return m
65127
}

simulator/metrics/noop.go

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,31 @@ var (
1414
)
1515

1616
type (
17-
noopRegisterer struct{}
18-
noopCollector struct{}
19-
noopCounter struct{ noopCollector }
20-
noopCounterVec struct{ noopCollector }
21-
noopHistogram struct{ noopCollector }
22-
noopGauge struct{ noopCollector }
17+
noopRegisterer struct{}
18+
noopCollector struct{}
19+
noopCounter struct{ noopCollector }
20+
noopCounterVec struct{ noopCollector }
21+
noopHistogram struct{ noopCollector }
22+
noopHistogramVec struct{ noopCollector }
23+
noopGauge struct{ noopCollector }
24+
noopObserver struct{}
2325
)
2426

2527
func NewNoop() *Metrics {
2628
return &Metrics{
27-
Registrer: noopRegisterer{},
28-
RequestFailures: noopCounter{},
29-
ResponseStatus: noopCounterVec{},
30-
ConnectionLatency: noopHistogram{},
31-
InformDuration: noopHistogram{},
32-
ConcurrentInforms: noopGauge{},
29+
Bootstrapped: noopCounter{},
30+
ConnectionLatency: noopHistogram{},
31+
MethodCalls: noopCounterVec{},
32+
RequestFailures: noopCounter{},
33+
ResponseStatus: noopCounterVec{},
34+
SessionsAttempted: noopCounter{},
35+
SessionsEstablished: noopCounter{},
36+
SessionsCompleted: noopCounter{},
37+
SessionDuration: noopHistogramVec{},
38+
ConcurrentSessions: noopGauge{},
39+
InformEvents: noopCounterVec{},
40+
ParametersRead: noopCounter{},
41+
ParametersWritten: noopCounter{},
3342
}
3443
}
3544

@@ -43,9 +52,11 @@ func (noopCollector) Describe(chan<- *prometheus.Desc) {}
4352
func (noopCollector) Inc() {}
4453
func (noopCollector) Write(*io_prometheus_client.Metric) error { return nil }
4554

46-
func (noopCounterVec) With(prometheus.Labels) prometheus.Counter { return noopCounter{} }
55+
func (noopCounterVec) With(prometheus.Labels) prometheus.Counter { return noopCounter{} }
56+
func (noopHistogramVec) With(prometheus.Labels) prometheus.Observer { return noopObserver{} }
4757

48-
func (noopCounter) Add(float64) {}
58+
func (noopCounter) Add(float64) {}
59+
func (noopObserver) Observe(float64) {}
4960

5061
func (noopHistogram) Observe(float64) {}
5162

simulator/set_parameter_values.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func (s *Simulator) handleSetParameterValues(envID string, r *rpc.SetParameterVa
4747
defer s.resetInformTimer()
4848
}
4949

50+
s.metrics.ParametersWritten.Add(float64(len(params)))
5051
s.dm.SetValues(params)
5152
s.dm.SetParameterKey(r.ParameterKey)
5253
resp := rpc.NewEnvelope(envID)

simulator/simulator.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
"github.com/go-xmlfmt/xmlfmt"
16+
"github.com/prometheus/client_golang/prometheus"
1617
"github.com/rs/zerolog/log"
1718

1819
"github.com/localhots/SimulaTR69/datamodel"
@@ -118,6 +119,7 @@ func (s *Simulator) handleConnectionRequest(_ context.Context) error {
118119

119120
// nolint:gocyclo
120121
func (s *Simulator) handleEnvelope(env *rpc.EnvelopeDecoder) *rpc.EnvelopeEncoder {
122+
s.metrics.MethodCalls.With(prometheus.Labels{"method": env.Method()}).Inc()
121123
envID := env.Header.ID.Value
122124
switch {
123125
case env.Body.GetRPCMethods != nil:

0 commit comments

Comments
 (0)