Skip to content

Commit dc9a19f

Browse files
authored
feat: add metrics (#2)
* feat: add metrics * docs: update README with metrics * feat(metrics): add http metrics
1 parent da77d8d commit dc9a19f

File tree

4 files changed

+195
-7
lines changed

4 files changed

+195
-7
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ func main() {
6565
}
6666
```
6767

68+
### Metrics
69+
70+
The Run function running a metrics handler to have some metrics about the
71+
events handler and actions executed.
72+
73+
Metrics are expose at `/metrics` path in [Prometheus](https://prometheus.io/) format.
74+
75+
You can find all metrics [available here](metrics.go)
76+
6877
## Contributing
6978
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
7079

app.go

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import (
55
"fmt"
66
"net"
77
"net/http"
8+
"strconv"
9+
"time"
810

11+
"github.com/prometheus/client_golang/prometheus"
12+
"github.com/prometheus/client_golang/prometheus/promhttp"
913
"github.com/sirupsen/logrus"
1014

1115
"github.com/acamilleri/go-plexhooks/plex"
@@ -47,30 +51,73 @@ func (a *App) Run() error {
4751
}
4852

4953
http.HandleFunc("/events", a.handler())
54+
http.Handle("/metrics", promhttp.Handler())
5055

5156
a.log.Infof("running server on %s", a.listenAddr)
5257
return http.ListenAndServe(a.listenAddr.String(), nil)
5358
}
5459

5560
func (a *App) handler() http.HandlerFunc {
5661
return func(w http.ResponseWriter, r *http.Request) {
62+
var statusCode int = http.StatusOK
5763
defer r.Body.Close()
58-
w.WriteHeader(http.StatusOK)
5964

60-
event, err := parseRequest(r)
61-
if err != nil {
62-
a.log.WithError(err).Error("failed to parse request")
65+
trackRequestDuration := newTrackRequestDuration(r.Method, "/events")
66+
if r.Method != http.MethodPost {
67+
statusCode = http.StatusMethodNotAllowed
68+
w.WriteHeader(statusCode)
69+
time.Sleep(time.Second * 5)
70+
httpRequestTotal.With(prometheus.Labels{
71+
"handler": "/events",
72+
"method": r.Method,
73+
"code": strconv.Itoa(statusCode),
74+
}).Inc()
75+
trackRequestDuration.Finish()
76+
return
6377
}
6478

65-
a.log.Infof("%s event handled", event.Name)
66-
err = a.triggerActionsOnEvent(event)
79+
err := a.handleRequest(r)
6780
if err != nil {
68-
a.log.WithError(err).Errorf("%s event actions failed", event.Name)
81+
statusCode = http.StatusInternalServerError
82+
w.WriteHeader(statusCode)
83+
trackRequestDuration.Finish()
84+
httpRequestTotal.With(prometheus.Labels{
85+
"handler": "/events",
86+
"method": r.Method,
87+
"code": strconv.Itoa(statusCode),
88+
}).Inc()
89+
return
6990
}
91+
92+
w.WriteHeader(statusCode)
93+
trackRequestDuration.Finish()
94+
httpRequestTotal.With(prometheus.Labels{
95+
"handler": "/events",
96+
"method": r.Method,
97+
"code": strconv.Itoa(statusCode),
98+
}).Inc()
7099
}
71100
}
72101

102+
func (a *App) handleRequest(r *http.Request) error {
103+
event, err := parseRequest(r)
104+
if err != nil {
105+
a.log.WithError(err).Error("failed to parse request")
106+
return err
107+
}
108+
109+
a.log.Infof("%s event handled", event.Name)
110+
err = a.triggerActionsOnEvent(event)
111+
if err != nil {
112+
a.log.WithError(err).Errorf("%s event actions failed", event.Name)
113+
return err
114+
}
115+
116+
return nil
117+
}
118+
73119
func (a *App) triggerActionsOnEvent(event plex.Event) error {
120+
eventsReceivedTotal.With(prometheus.Labels{"event": event.Name.String()}).Inc()
74121
hookName := event.Name
75122

76123
actions := a.actions.GetByHook(hookName)
@@ -80,15 +127,26 @@ func (a *App) triggerActionsOnEvent(event plex.Event) error {
80127

81128
for _, action := range actions {
82129
name := action.Name()
130+
actionDuration := newTrackActionDuration(event, action)
83131

84132
a.log.Debugf("action %s triggered", name)
85133
err := action.Execute(event)
86134
if err != nil {
87135
a.log.WithError(err).Errorf("action %s failed", name)
136+
actionsErrorTotal.With(
137+
prometheus.Labels{"event": event.Name.String(), "action": action.Name()},
138+
).Inc()
139+
actionDuration.Finish()
88140
continue
89141
}
142+
90143
a.log.Infof("action %s success", name)
144+
actionDuration.Finish()
145+
actionsSuccessTotal.With(
146+
prometheus.Labels{"event": event.Name.String(), "action": action.Name()},
147+
).Inc()
91148
}
149+
92150
return nil
93151
}
94152

metrics.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package plexhooks
2+
3+
import (
4+
"time"
5+
6+
"github.com/prometheus/client_golang/prometheus"
7+
"github.com/prometheus/client_golang/prometheus/promauto"
8+
9+
"github.com/acamilleri/go-plexhooks/plex"
10+
)
11+
12+
var (
13+
httpRequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
14+
Namespace: "plexhooks",
15+
Subsystem: "http",
16+
Name: "request_duration_seconds",
17+
Help: "The latency of the HTTP requests.",
18+
Buckets: prometheus.DefBuckets,
19+
}, []string{"handler", "method"})
20+
21+
httpRequestTotal = promauto.NewCounterVec(prometheus.CounterOpts{
22+
Namespace: "plexhooks",
23+
Subsystem: "http",
24+
Name: "request_total",
25+
Help: "How many http requests processed, partitioned by handler, status code and http method.",
26+
}, []string{"handler", "method", "code"})
27+
28+
eventsReceivedTotal = promauto.NewCounterVec(
29+
prometheus.CounterOpts{
30+
Namespace: "plexhooks",
31+
Subsystem: "events",
32+
Name: "received_total",
33+
Help: "Total number of events received.",
34+
}, []string{"event"})
35+
36+
actionsSuccessTotal = promauto.NewCounterVec(
37+
prometheus.CounterOpts{
38+
Namespace: "plexhooks",
39+
Subsystem: "actions",
40+
Name: "success_total",
41+
Help: "Total number of actions by hook executed with success.",
42+
}, []string{"event", "action"})
43+
44+
actionsDurationDuration = promauto.NewHistogramVec(
45+
prometheus.HistogramOpts{
46+
Namespace: "plexhooks",
47+
Subsystem: "actions",
48+
Name: "duration_seconds",
49+
Help: "A histogram of time to running action by hook.",
50+
Buckets: prometheus.DefBuckets,
51+
}, []string{"event", "action"})
52+
53+
actionsErrorTotal = promauto.NewCounterVec(
54+
prometheus.CounterOpts{
55+
Namespace: "plexhooks",
56+
Subsystem: "actions",
57+
Name: "error_total",
58+
Help: "Total number of actions by hook failed.",
59+
}, []string{"event", "action"})
60+
)
61+
62+
type trackActionDuration struct {
63+
event string
64+
action string
65+
start time.Time
66+
stop time.Time
67+
}
68+
69+
func newTrackActionDuration(event plex.Event, action Action) *trackActionDuration {
70+
return &trackActionDuration{
71+
event: event.Name.String(),
72+
action: action.Name(),
73+
start: time.Now(),
74+
stop: time.Time{},
75+
}
76+
}
77+
78+
func (track *trackActionDuration) Finish() {
79+
track.stop = time.Now()
80+
81+
duration := track.stop.Sub(track.start).Seconds()
82+
actionsDurationDuration.With(prometheus.Labels{
83+
"event": track.event,
84+
"action": track.action,
85+
}).Observe(duration)
86+
}
87+
88+
type trackRequestDuration struct {
89+
method string
90+
handler string
91+
92+
start time.Time
93+
stop time.Time
94+
}
95+
96+
func newTrackRequestDuration(method, handler string) *trackRequestDuration {
97+
return &trackRequestDuration{
98+
method: method,
99+
handler: handler,
100+
start: time.Now(),
101+
stop: time.Time{},
102+
}
103+
}
104+
105+
func (track *trackRequestDuration) Finish() {
106+
track.stop = time.Now()
107+
108+
duration := track.stop.Sub(track.start).Seconds()
109+
httpRequestDuration.With(prometheus.Labels{
110+
"method": track.method,
111+
"handler": track.handler,
112+
}).Observe(duration)
113+
}

plex/model.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package plex
22

3+
import (
4+
"strings"
5+
)
6+
37
const (
48
// LibraryOnDeck A new item is added that appears in the user’s On Deck.
59
// A poster is also attached to this event.
@@ -119,3 +123,7 @@ type Metadata struct {
119123

120124
// Name Event Name
121125
type Name string
126+
127+
func (name Name) String() string {
128+
return strings.ToLower(string(name))
129+
}

0 commit comments

Comments
 (0)