Skip to content

Commit f4d0535

Browse files
authored
Feat/telemetry grouped counter (#7)
* feat: add grouped counter to telemetry * chore: bump to 0.3.0 * feat: replce string with constants * feat: manage no registered metrics * chore: update packages * feat: rename test * chore: bump to 0.3.0-beta-1 * feat: replce string with constants
1 parent 998c874 commit f4d0535

File tree

4 files changed

+222
-28
lines changed

4 files changed

+222
-28
lines changed

README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# e-ipfs-core-lib
2+
3+
A library that collect utils and shared code for e-ipfs ecosystem.
4+
5+
## Quickstart
6+
7+
```
8+
npm i e-ipfs-core-lib
9+
```
10+
11+
## Api
12+
13+
### Telemetry
14+
#### Telemetry
15+
16+
Create a telemetry manager that provide a [`prometheus`](https://github.com/prometheus) valid file [format](https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md)
17+
18+
Create metrics yaml configuration file:
19+
20+
```yaml
21+
---
22+
component: bitswap-peer
23+
metrics:
24+
bitswap-total-connections: BitSwap Total Connections
25+
s3-request: AWS S3 requests
26+
dynamo-request: AWS DynamoDB requests
27+
version: 0.1.0
28+
buildDate: "20220307.1423"
29+
```
30+
31+
32+
```javascript
33+
import path from 'path'
34+
import { Telemetry, dirname } from 'e-ipfs-core-lib'
35+
36+
const configFile = path.join(dirname(import.meta.url), '../metrics.yml')
37+
const telemetry = new Telemetry({ configFile })
38+
telemetry.increaseCount('bitswap-total-connections', 2)
39+
40+
const result = telemetry.export()
41+
console.log(result)
42+
```
43+
44+
#### Telemetry instance methods
45+
* clear: Clear the metrics
46+
* createMetric(category, description, metric, type): Create a new metric
47+
* category: String - The given name of the category
48+
* description: String - The category description
49+
* metric: METRIC_COUNT | METRIC_DURATIONS | METRIC_GROUPED_COUNT - The metric defined
50+
* type: null | TYPE_GAUGE - The type of the metric. If not passed the value is defined based on the `metric` attribute.
51+
* export: Export the metrics in `prometheus` format
52+
```
53+
# HELP counter_grouped_count_total COUNTER (grouped-count)
54+
# TYPE counter_grouped_count_total counter
55+
counter_grouped_count_total{id="123"} 1 now
56+
counter_grouped_count_total{id="456"} 2 now
57+
```
58+
* increaseCount(category, amount = 1): Increase the count for a category
59+
* category: String - The given name of the category
60+
* amount: Number (Default 1) - The amount to add to the metric
61+
* decreaseCount(category, amount = 1): Decrease the count for a category
62+
* category: String - The given name of the category
63+
* amount: Number (Default 1) - The amount to remove from the metric
64+
* increaseCountWithKey(category, key, amount = 1): Increase the count for a key in a category
65+
* category: String - The given name of the category
66+
* key: String - The key of the metric
67+
* amount: Number (Default 1) - The amount to add to the metric
68+
* decreaseCountWithKey(category, key, amount = 1): Decrease the count for a key in a category
69+
* category: String - The given name of the category
70+
* key: String - The key of the metric
71+
* amount: Number (Default 1) - The amount to remove from the metric
72+
* trackDuration(category, promise): Track the duration of an async call
73+
* category: String - The given name of the category
74+
* promise: Promise - The function to be tracked
75+
76+
#### Metrics and types constants
77+
78+
The constants below are exported
79+
80+
* METRIC_COUNT
81+
* METRIC_DURATIONS
82+
* METRIC_GROUPED_COUNT
83+
* TYPE_COUNTER
84+
* TYPE_HISTOGRAM
85+
* TYPE_GAUGE
86+
87+
### Utils
88+
#### dirname
89+
#### version
90+
#### cidToKey
91+
92+
### AWS-Client
93+
#### createAwsClient,
94+
#### awsClientOptions,
95+
#### Client
96+
97+
### Logger
98+
#### createLogger
99+
100+
### Protocol
101+

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "e-ipfs-core-lib",
3-
"version": "0.2.2",
3+
"version": "0.3.0-beta-1",
44
"description": "E-IPFS core library",
55
"license": "(Apache-2.0 AND MIT)",
66
"homepage": "https://github.com/elastic-ipfs/core-lib",
@@ -14,7 +14,7 @@
1414
"node": "18.12"
1515
},
1616
"dependencies": {
17-
"@aws-sdk/util-dynamodb": "^3.216.0",
17+
"@aws-sdk/util-dynamodb": "^3.218.0",
1818
"bl": "^6.0.0",
1919
"hdr-histogram-js": "^3.0.0",
2020
"js-yaml": "^4.1.0",
@@ -24,7 +24,7 @@
2424
"piscina": "^3.2.0",
2525
"protobufjs": "^7.1.2",
2626
"sodium-native": "^3.4.1",
27-
"undici": "^5.12.0",
27+
"undici": "^5.13.0",
2828
"xml-js": "^1.6.11"
2929
},
3030
"devDependencies": {

src/telemetry.js

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
21
import { readFileSync } from 'fs'
32
import { load as ymlLoad } from 'js-yaml'
43
import * as hdr from 'hdr-histogram-js'
54

65
const PERCENTILES = [0.001, 0.01, 0.1, 1, 2.5, 10, 25, 50, 75, 90, 97.5, 99, 99.9, 99.99, 99.999]
76

7+
const METRIC_GROUPED_COUNT = 'grouped-count'
8+
const METRIC_DURATIONS = 'durations'
9+
const METRIC_COUNT = 'count'
10+
11+
const TYPE_COUNTER = 'counter'
12+
const TYPE_HISTOGRAM = 'histogram'
13+
const TYPE_GAUGE = 'gauge'
14+
815
class Aggregator {
916
constructor (category, description, metric, type) {
1017
this.tag = `${category}-${metric}`
@@ -14,16 +21,18 @@ class Aggregator {
1421
// type is optional
1522
if (!type) {
1623
// set the type by the metric
17-
if (metric === 'durations') {
18-
this.type = 'histogram'
24+
if (metric === METRIC_DURATIONS) {
25+
this.type = TYPE_HISTOGRAM
1926
} else {
20-
this.type = 'counter'
27+
this.type = TYPE_COUNTER
2128
this.exportName += '_total'
29+
this.isGrouped = metric === METRIC_GROUPED_COUNT
2230
}
2331
} else {
2432
this.type = type
2533
}
2634

35+
this.groupedSum = {}
2736
this.sum = 0
2837
hdr.initWebAssemblySync()
2938
this.histogram = hdr.build({
@@ -37,22 +46,35 @@ class Aggregator {
3746
record (value) {
3847
this.sum += value
3948

40-
if (this.type === 'histogram') {
49+
if (this.type === TYPE_HISTOGRAM) {
4150
this.histogram.recordValue(value)
4251
}
4352
}
4453

54+
recordWithKey (key, value) {
55+
if (!this.groupedSum[key]) {
56+
this.groupedSum[key] = value
57+
} else {
58+
this.groupedSum[key] += value
59+
}
60+
}
61+
4562
reset () {
4663
this.sum = 0
64+
this.groupedSum = {}
4765
this.histogram.reset()
4866
}
4967

5068
current () {
5169
const { minNonZeroValue: min, maxValue: max, mean, stdDeviation: stdDev, totalCount: count } = this.histogram
5270

5371
const value = {
54-
empty: (this.type === 'histogram' && count === 0) || (this.type === 'counter' && this.sum === 0),
72+
empty: (this.type === TYPE_HISTOGRAM && count === 0) ||
73+
((this.type === TYPE_COUNTER && !this.isGrouped) && this.sum === 0) ||
74+
((this.type === TYPE_COUNTER && this.isGrouped) && Object.keys(this.groupedSum).length === 0),
5575
sum: this.sum,
76+
isGrouped: this.isGrouped,
77+
groupedSum: this.groupedSum,
5678
histogram:
5779
count > 0
5880
? {
@@ -96,8 +118,9 @@ class Telemetry {
96118
// Create metrics
97119
this.metrics = new Map()
98120
for (const [category, description] of Object.entries(metrics)) {
99-
this.createMetric(category, description, 'count')
100-
this.createMetric(category, description, 'durations')
121+
this.createMetric(category, description, METRIC_COUNT)
122+
this.createMetric(category, description, METRIC_GROUPED_COUNT)
123+
this.createMetric(category, description, METRIC_DURATIONS)
101124
}
102125
} catch (err) {
103126
logger.error({ err }, 'error in telemetry constructor')
@@ -138,14 +161,18 @@ class Telemetry {
138161
output += `# HELP ${metric.exportName} ${metric.description}\n`
139162
output += `# TYPE ${metric.exportName} ${metric.type}\n`
140163

141-
if (metric.type === 'histogram') {
164+
if (metric.type === TYPE_HISTOGRAM) {
142165
output += `${metric.exportName}_count ${current.histogram.count} ${current.timestamp}\n`
143166
output += `${metric.exportName}_sum ${current.sum} ${current.timestamp}\n`
144167

145168
const percentilesValues = current.histogram.percentiles
146169
for (const percentile of PERCENTILES) {
147170
output += `${metric.exportName}_bucket{le="${percentile}"} ${percentilesValues[percentile]} ${current.timestamp}\n`
148171
}
172+
} else if (metric.type === TYPE_COUNTER && metric.isGrouped) {
173+
for (const [key, value] of Object.entries(current.groupedSum)) {
174+
output += `${metric.exportName}${key} ${value} ${current.timestamp}\n`
175+
}
149176
} else {
150177
output += `${metric.exportName} ${current.sum} ${current.timestamp}\n`
151178
}
@@ -159,17 +186,27 @@ class Telemetry {
159186
}
160187

161188
increaseCount (category, amount = 1) {
162-
const metric = this.ensureMetric(category, 'count')
189+
const metric = this.ensureMetric(category, METRIC_COUNT)
163190
metric.record(amount)
164191
}
165192

166193
decreaseCount (category, amount = 1) {
167-
const metric = this.ensureMetric(category, 'count')
194+
const metric = this.ensureMetric(category, METRIC_COUNT)
168195
metric.record(-1 * amount)
169196
}
170197

198+
increaseCountWithKey (category, key, amount = 1) {
199+
const metric = this.ensureMetric(category, METRIC_GROUPED_COUNT)
200+
metric.recordWithKey(key, amount)
201+
}
202+
203+
decreaseCountWithKey (category, key, amount = 1) {
204+
const metric = this.ensureMetric(category, METRIC_GROUPED_COUNT)
205+
metric.recordWithKey(key, -1 * amount)
206+
}
207+
171208
async trackDuration (category, promise) {
172-
const metric = this.ensureMetric(category, 'durations')
209+
const metric = this.ensureMetric(category, METRIC_DURATIONS)
173210
const startTime = process.hrtime.bigint()
174211

175212
try {
@@ -180,4 +217,4 @@ class Telemetry {
180217
}
181218
}
182219

183-
export { Telemetry }
220+
export { Telemetry, METRIC_COUNT, METRIC_DURATIONS, METRIC_GROUPED_COUNT, TYPE_COUNTER, TYPE_HISTOGRAM, TYPE_GAUGE }

0 commit comments

Comments
 (0)