Skip to content

Commit f4b1f54

Browse files
authored
docs: update metrics docs (#1441)
Document how libp2p components can record metrics
1 parent a74d22a commit f4b1f54

File tree

1 file changed

+195
-4
lines changed

1 file changed

+195
-4
lines changed

doc/METRICS.md

Lines changed: 195 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,51 @@
1-
# Bandwidth Metrics
1+
# Libp2p Metrics <!-- omit in toc -->
22

3-
- Metrics gathering is optional, as there is a performance hit to using it.
4-
- Metrics do NOT currently contain OS level stats, only libp2p application level Metrics. For example, TCP messages (ACK, FIN, etc) are not accounted for.
3+
Metrics allow you to gather run time statistics on your libp2p node.
4+
5+
## Table of Contents <!-- omit in toc -->
6+
7+
- [Overview](#overview)
8+
- [Tracking](#tracking)
9+
- [Enable metrics](#enable-metrics)
10+
- [Stream Metrics](#stream-metrics)
11+
- [Component Metrics](#component-metrics)
12+
- [Application metrics](#application-metrics)
13+
- [Integration](#integration)
14+
- [Extracting metrics](#extracting-metrics)
15+
16+
## Overview
17+
18+
- Metrics gathering is optional, as there is a performance hit to using it
519
- See the [API](./API.md) for Metrics usage. Metrics in libp2p do not emit events, as such applications wishing to read Metrics will need to do so actively. This ensures that the system is not unnecessarily firing update notifications.
20+
- For large installations you may wish to combine the statistics with a visualizer such as [Graphana](https://grafana.com/)
21+
22+
There are two types of metrics [`StreamMetrics`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L66-L115) and [`ComponentMetrics`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L183-L193). `StreamMetrics` track data in and out of streams, `ComponentMetrics` allow system components to record metrics that are of interest to the observer.
23+
24+
Although designed to primarily integrate with tools such as [Prometheus](https://prometheus.io/) it does not introduce any dependencies that require you to use any particular tool to store or graph metrics.
625

726
## Tracking
27+
828
- When a transport hands off a connection for upgrading, Metrics are hooked up if enabled.
929
- When a stream is created, Metrics will be tracked on that stream and associated to that streams protocol.
1030
- Tracked Metrics are associated to a specific peer, and count towards global bandwidth Metrics.
1131

12-
### Metrics Processing
32+
### Enable metrics
33+
34+
First enable metrics tracking:
35+
36+
```js
37+
import { createLibp2pNode } from 'libp2p'
38+
39+
const node = await createLibp2pNode({
40+
metrics: {
41+
enabled: true
42+
}
43+
//... other config
44+
})
45+
```
46+
47+
### Stream Metrics
48+
1349
- The main Metrics object consists of individual `Stats` objects
1450
- The following categories are tracked:
1551
- Global stats; every byte in and out
@@ -26,3 +62,158 @@
2662
- When the queue is processed:
2763
- The data length is added to either the `in` or `out` stat
2864
- The moving averages is calculated since the last queue processing (based on most recently processed item timestamp)
65+
66+
### Component Metrics
67+
68+
To define component metrics first get a reference to the metrics object:
69+
70+
```ts
71+
import type { Metrics } from '@libp2p/interface-metrics'
72+
73+
interface MyClassComponents {
74+
metrics: Metrics
75+
}
76+
77+
class MyClass {
78+
private readonly components: MyClassComponents
79+
80+
constructor (components: MyClassComponents) {
81+
this.components = components
82+
}
83+
84+
myMethod () {
85+
// here we will set metrics
86+
}
87+
}
88+
```
89+
90+
Metrics are updated by calling [`Metrics.updateComponentMetric`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L192) and passing an object that conforms to the [`ComponentMetricsUpdate`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L122-L152) interface:
91+
92+
```ts
93+
metrics.updateComponentMetric({
94+
system: 'libp2p',
95+
component: 'connection-manager',
96+
metric: 'incoming-connections',
97+
value: 5
98+
})
99+
```
100+
101+
If several metrics should be grouped together (e.g. for graphing purposes) the `value` field can be a [`ComponentMetricsGroup`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L159):
102+
103+
```ts
104+
metrics.updateComponentMetric({
105+
system: 'libp2p',
106+
component: 'connection-manager',
107+
metric: 'connections',
108+
value: {
109+
incoming: 5,
110+
outgoing: 10
111+
}
112+
})
113+
```
114+
115+
If the metrics are expensive to calculate, a [`CalculateComponentMetric`](https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-metrics/src/index.ts#L164) function can be set as the value instead - this will need to be invoked to collect the metrics (see [Extracting metrics](#extracting-metrics) below):
116+
117+
```ts
118+
metrics.updateComponentMetric({
119+
system: 'libp2p',
120+
component: 'connection-manager',
121+
metric: 'something-expensive',
122+
value: () => {
123+
// valid return types are:
124+
// number
125+
// Promise<number>
126+
// ComponentMetricsGroup
127+
// Promise<ComponentMetricsGroup>
128+
}
129+
})
130+
```
131+
132+
### Application metrics
133+
134+
You can of course piggy-back your own metrics on to the lib2p metrics object, just specify a different `system` as part of your `ComponentMetricsUpdate`:
135+
136+
```ts
137+
metrics.updateComponentMetric({
138+
system: 'my-app',
139+
component: 'my-component',
140+
metric: 'important-metric',
141+
value: 5
142+
})
143+
```
144+
145+
### Integration
146+
147+
To help with integrating with metrics gathering software, a `label` and `help` can also be added to your `ComponentMetricsUpdate`. These are expected by certain tools such as [Prometheus](https://prometheus.io/).
148+
149+
```ts
150+
metrics.updateComponentMetric({
151+
system: 'libp2p',
152+
component: 'connection-manager',
153+
metric: 'incoming-connections',
154+
value: 5,
155+
label: 'label',
156+
help: 'help'
157+
})
158+
```
159+
160+
## Extracting metrics
161+
162+
Metrics can be extracted from the metrics object and supplied to a tracking system such as [Prometheus](https://prometheus.io/). This code is borrowed from the `js-ipfs` metrics endpoint which uses [prom-client](https://www.npmjs.com/package/prom-client) to format metrics:
163+
164+
```ts
165+
import client from 'prom-client'
166+
167+
const libp2p = createLibp2pNode({
168+
metrics: {
169+
enabled: true
170+
}
171+
//... other config
172+
})
173+
174+
// A handler invoked by express/hapi or your http framework of choice
175+
export default async function metricsEndpoint (req, res) {
176+
const metrics = libp2p.metrics
177+
178+
if (metrics) {
179+
// update the prometheus client with the recorded metrics
180+
for (const [system, components] of metrics.getComponentMetrics().entries()) {
181+
for (const [component, componentMetrics] of components.entries()) {
182+
for (const [metricName, trackedMetric] of componentMetrics.entries()) {
183+
// set the relevant gauges
184+
const name = `${system}-${component}-${metricName}`.replace(/-/g, '_')
185+
const labelName = trackedMetric.label ?? metricName.replace(/-/g, '_')
186+
const help = trackedMetric.help ?? metricName.replace(/-/g, '_')
187+
const gaugeOptions = { name, help }
188+
const metricValue = await trackedMetric.calculate()
189+
190+
if (typeof metricValue !== 'number') {
191+
// metric group
192+
gaugeOptions.labelNames = [
193+
labelName
194+
]
195+
}
196+
197+
if (!gauges[name]) {
198+
// create metric if it's not been seen before
199+
gauges[name] = new client.Gauge(gaugeOptions)
200+
}
201+
202+
if (typeof metricValue !== 'number') {
203+
// metric group
204+
Object.entries(metricValue).forEach(([key, value]) => {
205+
gauges[name].set({ [labelName]: key }, value)
206+
})
207+
} else {
208+
// metric value
209+
gauges[name].set(metricValue)
210+
}
211+
}
212+
}
213+
}
214+
}
215+
216+
// done updating, write the metrics into the response
217+
res.send(await client.register.metrics())
218+
}
219+
```

0 commit comments

Comments
 (0)