v3.4.1 de8f68e

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	// Backups
38	BackupRuns     metric.Int64Counter
39	BackupDuration metric.Float64Histogram
40	backups        *backupState
41}
42
43// NewMetrics creates and registers all Gordon metric instruments.
44// Returns a noop-safe Metrics struct — all fields are always initialized
45// (OTel returns noop instruments when no MeterProvider is set).
46func NewMetrics() (*Metrics, error) {
47	meter := otel.Meter("gordon")
48	m := &Metrics{meter: meter, backups: newBackupState()}
49	var err error
50
51	if m.DeployTotal, err = meter.Int64Counter("gordon.deploy.total",
52		metric.WithDescription("Total number of deployments")); err != nil {
53		return nil, err
54	}
55	if m.DeployDuration, err = meter.Float64Histogram("gordon.deploy.duration_seconds",
56		metric.WithDescription("Deploy duration in seconds"),
57		metric.WithUnit("s"),
58		metric.WithExplicitBucketBoundaries(1, 5, 10, 30, 60, 120, 300)); err != nil {
59		return nil, err
60	}
61	if m.DeployErrors, err = meter.Int64Counter("gordon.deploy.errors",
62		metric.WithDescription("Total deploy errors")); err != nil {
63		return nil, err
64	}
65	if m.ContainerRestarts, err = meter.Int64Counter("gordon.container.restarts",
66		metric.WithDescription("Total container restarts")); err != nil {
67		return nil, err
68	}
69	if m.ContainerCrashLoops, err = meter.Int64Counter("gordon.container.crash_loops",
70		metric.WithDescription("Total crash loop detections")); err != nil {
71		return nil, err
72	}
73	if m.ImagePushTotal, err = meter.Int64Counter("gordon.registry.push.total",
74		metric.WithDescription("Total image pushes")); err != nil {
75		return nil, err
76	}
77	if m.ImagePushSize, err = meter.Int64Counter("gordon.registry.push.bytes",
78		metric.WithDescription("Total bytes pushed to registry"),
79		metric.WithUnit("By")); err != nil {
80		return nil, err
81	}
82	if m.EventsProcessed, err = meter.Int64Counter("gordon.events.processed",
83		metric.WithDescription("Total events processed")); err != nil {
84		return nil, err
85	}
86	if m.EventsDropped, err = meter.Int64Counter("gordon.events.dropped",
87		metric.WithDescription("Total events dropped")); err != nil {
88		return nil, err
89	}
90	if err := m.registerBackupInstruments(); err != nil {
91		return nil, err
92	}
93
94	return m, nil
95}
96
97// ObserveManagedContainers registers the gordon.container.managed gauge.
98// count is called at each collection, so the exported value always reflects
99// current state instead of drifting like an incremental counter. A count
100// error skips that observation rather than exporting a wrong value.
101func (m *Metrics) ObserveManagedContainers(count func(context.Context) (int64, error)) error {
102	_, err := m.meter.Int64ObservableGauge("gordon.container.managed",
103		metric.WithDescription("Currently managed containers"),
104		metric.WithInt64Callback(func(ctx context.Context, o metric.Int64Observer) error {
105			n, err := count(ctx)
106			if err != nil {
107				return err
108			}
109			o.Observe(n)
110			return nil
111		}))
112	return err
113}
114
115// RecordImagePush implements out.Metrics.
116func (m *Metrics) RecordImagePush(ctx context.Context, name, reference string, sizeBytes int64) {
117	attrs := metric.WithAttributes(
118		attribute.String("name", name),
119		attribute.String("reference", reference),
120	)
121	m.ImagePushTotal.Add(ctx, 1, attrs)
122	m.ImagePushSize.Add(ctx, sizeBytes, attrs)
123}
124
125// RecordEventProcessed implements out.Metrics.
126func (m *Metrics) RecordEventProcessed(ctx context.Context, eventType domain.EventType) {
127	m.EventsProcessed.Add(ctx, 1, metric.WithAttributes(attribute.String("event_type", string(eventType))))
128}
129
130// RecordEventDropped implements out.Metrics.
131func (m *Metrics) RecordEventDropped(ctx context.Context, eventType domain.EventType) {
132	m.EventsDropped.Add(ctx, 1, metric.WithAttributes(attribute.String("event_type", string(eventType))))
133}

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