Skip to content

Commit 6552f95

Browse files
fredreandre
andauthored
runtimes/js: add support for custom metrics (#2128)
Adds support for creating custom metrics in typescript apps. Depends on #2109 Inspired by the implementation in the go runtime. Metrics needs to be defined within a service, and will automatically add the service label. This implementation is backed by a SharedArrayBuffer, where each u64 slot is a metric value, created from js by the main thread. From js we do atomic operation to update values on this buffer, and from rust we do atomic load when collecting the metrics. The rust runtime is use to coordinate what slot should be used for a label set, so that we can support multiple workers updating the same metric. --------- Co-authored-by: André Eriksson <[email protected]>
1 parent b2c758e commit 6552f95

File tree

40 files changed

+2323
-817
lines changed

40 files changed

+2323
-817
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/go/observability/metrics.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
---
2+
seotitle: Custom metrics in Go
3+
seodesc: Learn how to define and use custom metrics in your Go backend application with Encore.
4+
title: Metrics
5+
subtitle: Track custom metrics in your Go application
6+
infobox: {
7+
title: "Metrics",
8+
import: "encore.dev/metrics",
9+
}
10+
lang: go
11+
---
12+
13+
Encore provides built-in support for defining custom metrics in your Go applications. Once defined, metrics are automatically collected and displayed in the Encore Cloud Dashboard, and can be exported to third-party observability services.
14+
15+
See the [Platform metrics documentation](/docs/platform/observability/metrics) for information about integrations with third-party services like Grafana Cloud and Datadog.
16+
17+
## Defining custom metrics
18+
19+
Define custom metrics by importing the [`encore.dev/metrics`](https://pkg.go.dev/encore.dev/metrics) package and
20+
creating a new metric using one of the `metrics.NewCounter` or `metrics.NewGauge` functions.
21+
22+
For example, to count the number of orders processed:
23+
24+
```go
25+
import "encore.dev/metrics"
26+
27+
var OrdersProcessed = metrics.NewCounter[uint64]("orders_processed", metrics.CounterConfig{})
28+
29+
func process(order *Order) {
30+
// ...
31+
OrdersProcessed.Increment()
32+
}
33+
```
34+
35+
## Metric types
36+
37+
Encore currently supports two metric types: counters and gauges.
38+
39+
**Counters** measure the count of something. A counter's value must always increase, never decrease. (Note that the value gets reset to 0 when the application restarts.) Typical use cases include counting the number of requests, the amount of data processed, and so on.
40+
41+
**Gauges** measure the current value of something. Unlike counters, a gauge's value can fluctuate up and down. Typical use cases include measuring CPU usage, the number of active instances running of a process, and so on.
42+
43+
For information about their respective APIs, see the API documentation for [Counter](https://pkg.go.dev/encore.dev/metrics#Counter) and [Gauge](https://pkg.go.dev/encore.dev/metrics#Gauge).
44+
45+
### Counter example
46+
47+
```go
48+
import "encore.dev/metrics"
49+
50+
var RequestsReceived = metrics.NewCounter[uint64]("requests_received", metrics.CounterConfig{})
51+
52+
func handleRequest() {
53+
RequestsReceived.Increment()
54+
// ... handle request
55+
}
56+
```
57+
58+
### Gauge example
59+
60+
```go
61+
import "encore.dev/metrics"
62+
63+
var ActiveConnections = metrics.NewGauge[int64]("active_connections", metrics.GaugeConfig{})
64+
65+
func onConnect() {
66+
ActiveConnections.Add(1)
67+
}
68+
69+
func onDisconnect() {
70+
ActiveConnections.Add(-1)
71+
}
72+
```
73+
74+
## Defining labels
75+
76+
Encore's metrics package provides a type-safe way of attaching labels to metrics. To define labels, create a struct type representing the labels and then use `metrics.NewCounterGroup` or `metrics.NewGaugeGroup`.
77+
78+
The Labels type must be a named struct, where each field corresponds to a single label. Each field must be of type `string`, `int`, or `bool`.
79+
80+
### Counter with labels
81+
82+
```go
83+
import "encore.dev/metrics"
84+
85+
type Labels struct {
86+
Success bool
87+
}
88+
89+
var OrdersProcessed = metrics.NewCounterGroup[Labels, uint64]("orders_processed", metrics.CounterConfig{})
90+
91+
func process(order *Order) {
92+
var success bool
93+
// ... populate success with true/false ...
94+
OrdersProcessed.With(Labels{Success: success}).Increment()
95+
}
96+
```
97+
98+
### Gauge with labels
99+
100+
```go
101+
import "encore.dev/metrics"
102+
103+
type ConnectionLabels struct {
104+
Region string
105+
}
106+
107+
var ActiveConnections = metrics.NewGaugeGroup[ConnectionLabels, int64]("active_connections", metrics.GaugeConfig{})
108+
109+
func onConnect(region string) {
110+
ActiveConnections.With(ConnectionLabels{Region: region}).Add(1)
111+
}
112+
```
113+
114+
<Callout type="important">
115+
116+
Each combination of label values creates a unique time series tracked in memory and stored by the monitoring system.
117+
Using numerous labels can lead to a combinatorial explosion, causing high cloud expenses and degraded performance.
118+
119+
As a general rule, limit the unique time series to tens or hundreds at most, rather than thousands.
120+
121+
</Callout>

docs/menu.cue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,11 @@
455455
text: "Logging"
456456
path: "/go/observability/logging"
457457
file: "go/observability/logging"
458+
}, {
459+
kind: "basic"
460+
text: "Metrics"
461+
path: "/go/observability/metrics"
462+
file: "go/observability/metrics"
458463
}]
459464
},
460465
{
@@ -948,6 +953,11 @@
948953
text: "Service Catalog"
949954
path: "/ts/observability/service-catalog"
950955
file: "ts/observability/service-catalog"
956+
}, {
957+
kind: "basic"
958+
text: "Metrics"
959+
path: "/ts/observability/metrics"
960+
file: "ts/observability/metrics"
951961
}]
952962
},
953963
{

docs/platform/observability/metrics.md

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -24,64 +24,11 @@ By default, Encore also exports metrics data to your cloud provider's built-in m
2424

2525
## Defining custom metrics
2626

27-
Define custom metrics by importing the [`encore.dev/metrics`](https://pkg.go.dev/encore.dev/metrics) package and
28-
create a new metric using one of the `metrics.NewCounter` or `metrics.NewGauge` functions.
27+
Encore makes it easy to define custom metrics for your application. Once defined, custom metrics are automatically displayed on the metrics page in the Cloud Dashboard.
2928

30-
For example, to count the number of orders processed:
31-
32-
```go
33-
import "encore.dev/metrics"
34-
35-
var OrdersProcessed = metrics.NewCounter[uint64]("orders_processed", metrics.CounterConfig{})
36-
37-
func process(order *Order) {
38-
// ...
39-
OrdersProcessed.Increment()
40-
}
41-
```
42-
43-
### Metric types
44-
45-
Encore currently supports two metric types: counters and gauges.
46-
47-
Counters, like the name suggests, measure the count of something. A counter's value must always
48-
increase, never decrease. (Note that the value gets reset to 0 when the application restarts.)
49-
Typical use cases include counting the number of requests, the amount of data processed, and so on.
50-
51-
Gauges measure the current value of something. Unlike counters, a gauge's value can fluctuate up and down. Typical use
52-
cases include measuring CPU usage, the number of active instances running of a process, and so on.
53-
54-
For information about their respective APIs, see the API documentation
55-
for [Counter](https://pkg.go.dev/encore.dev/metrics#Counter) and [Gauge](https://pkg.go.dev/encore.dev/metrics#Gauge).
56-
57-
### Defining labels
58-
59-
Encore's metrics package provides a type-safe way of attaching labels to metrics.
60-
To define labels, create a struct type representing the labels and then use `metrics.NewCounterGroup`
61-
or `metrics.NewGaugeGroup`:
62-
63-
```go
64-
type Labels struct {
65-
Success bool
66-
}
67-
68-
var OrdersProcessed = metrics.NewCounterGroup[Labels, uint64]("orders_processed", metrics.CounterConfig{})
69-
70-
func process(order *Order) {
71-
var success bool
72-
// ... populate success with true/false ...
73-
OrdersProcessed.With(Labels{Success: success}).Increment()
74-
}
75-
```
76-
77-
<Callout type="important">
78-
79-
Each combination of label values creates a unique time series tracked in memory and stored by the monitoring system.
80-
Using numerous labels can lead to a combinatorial explosion, causing high cloud expenses and degraded performance.
81-
82-
As a general rule, limit the unique time series to tens or hundreds at most, rather than thousands.
83-
84-
</Callout>
29+
For implementation guides on how to define metrics in your code, see:
30+
- [Go metrics documentation](/docs/go/observability/metrics)
31+
- [TypeScript metrics documentation](/docs/ts/observability/metrics)
8532

8633
## Integrations with third party observability services
8734

0 commit comments

Comments
 (0)