v3.1.0 d676137

metrics.go RawHistoryFolder

1package telemetry
2
3import (
4	"context"
5
6	"go.opentelemetry.io/otel"
7	"go.opentelemetry.io/otel/attribute"
8	"go.opentelemetry.io/otel/metric"
9
10	"github.com/bnema/gordon/internal/boundaries/out"
11	"github.com/bnema/gordon/internal/domain"
12)
13
14var _ out.Metrics = (*Metrics)(nil)
15
16// Metrics holds Gordon-specific OTel metrics instruments.
17type Metrics struct {
18	meter metric.Meter
19
20	// Deployments
21	DeployTotal    metric.Int64Counter
22	DeployDuration metric.Float64Histogram
23	DeployErrors   metric.Int64Counter
24
25	// Container lifecycle
26	ContainerRestarts   metric.Int64Counter
27	ContainerCrashLoops metric.Int64Counter
28
29	// Registry
30	ImagePushTotal metric.Int64Counter
31	ImagePushSize  metric.Int64Counter // bytes
32
33	// Events
34	EventsProcessed metric.Int64Counter
35	EventsDropped   metric.Int64Counter
36}
37
38// NewMetrics creates and registers all Gordon metric instruments.
39// Returns a noop-safe Metrics struct — all fields are always initialized
40// (OTel returns noop instruments when no MeterProvider is set).
41func NewMetrics() (*Metrics, error) {
42	meter := otel.Meter("gordon")
43	m := &Metrics{meter: meter}
44	var err error
45
46	if m.DeployTotal, err = meter.Int64Counter("gordon.deploy.total",
47		metric.WithDescription("Total number of deployments")); err != nil {
48		return nil, err
49	}
50	if m.DeployDuration, err = meter.Float64Histogram("gordon.deploy.duration_seconds",
51		metric.WithDescription("Deploy duration in seconds"),
52		metric.WithUnit("s"),
53		metric.WithExplicitBucketBoundaries(1, 5, 10, 30, 60, 120, 300)); err != nil {
54		return nil, err
55	}
56	if m.DeployErrors, err = meter.Int64Counter("gordon.deploy.errors",
57		metric.WithDescription("Total deploy errors")); err != nil {
58		return nil, err
59	}
60	if m.ContainerRestarts, err = meter.Int64Counter("gordon.container.restarts",
61		metric.WithDescription("Total container restarts")); err != nil {
62		return nil, err
63	}
64	if m.ContainerCrashLoops, err = meter.Int64Counter("gordon.container.crash_loops",
65		metric.WithDescription("Total crash loop detections")); err != nil {
66		return nil, err
67	}
68	if m.ImagePushTotal, err = meter.Int64Counter("gordon.registry.push.total",
69		metric.WithDescription("Total image pushes")); err != nil {
70		return nil, err
71	}
72	if m.ImagePushSize, err = meter.Int64Counter("gordon.registry.push.bytes",
73		metric.WithDescription("Total bytes pushed to registry"),
74		metric.WithUnit("By")); err != nil {
75		return nil, err
76	}
77	if m.EventsProcessed, err = meter.Int64Counter("gordon.events.processed",
78		metric.WithDescription("Total events processed")); err != nil {
79		return nil, err
80	}
81	if m.EventsDropped, err = meter.Int64Counter("gordon.events.dropped",
82		metric.WithDescription("Total events dropped")); err != nil {
83		return nil, err
84	}
85
86	return m, nil
87}
88
89// ObserveManagedContainers registers the gordon.container.managed gauge.
90// count is called at each collection, so the exported value always reflects
91// current state instead of drifting like an incremental counter. A count
92// error skips that observation rather than exporting a wrong value.
93func (m *Metrics) ObserveManagedContainers(count func(context.Context) (int64, error)) error {
94	_, err := m.meter.Int64ObservableGauge("gordon.container.managed",
95		metric.WithDescription("Currently managed containers"),
96		metric.WithInt64Callback(func(ctx context.Context, o metric.Int64Observer) error {
97			n, err := count(ctx)
98			if err != nil {
99				return err
100			}
101			o.Observe(n)
102			return nil
103		}))
104	return err
105}
106
107// RecordImagePush implements out.Metrics.
108func (m *Metrics) RecordImagePush(ctx context.Context, name, reference string, sizeBytes int64) {
109	attrs := metric.WithAttributes(
110		attribute.String("name", name),
111		attribute.String("reference", reference),
112	)
113	m.ImagePushTotal.Add(ctx, 1, attrs)
114	m.ImagePushSize.Add(ctx, sizeBytes, attrs)
115}
116
117// RecordEventProcessed implements out.Metrics.
118func (m *Metrics) RecordEventProcessed(ctx context.Context, eventType domain.EventType) {
119	m.EventsProcessed.Add(ctx, 1, metric.WithAttributes(attribute.String("event_type", string(eventType))))
120}
121
122// RecordEventDropped implements out.Metrics.
123func (m *Metrics) RecordEventDropped(ctx context.Context, eventType domain.EventType) {
124	m.EventsDropped.Add(ctx, 1, metric.WithAttributes(attribute.String("event_type", string(eventType))))
125}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close