Skip to content
This repository was archived by the owner on Jun 30, 2025. It is now read-only.

Commit 574285b

Browse files
authored
Add merge module metrics. #92 (#94)
1 parent 0ae3053 commit 574285b

File tree

7 files changed

+113
-27
lines changed

7 files changed

+113
-27
lines changed

README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,31 @@ the configuration options described in
7979
- `$MAX_DISK_SPACE`_(number; optional)_: Maximum disk space (in bytes) to use in
8080
`$CACHE_ROOT` and `$STATE_ROOT` combined.
8181

82-
### `$ station metrics`
82+
### `$ station metrics <module>`
8383

84-
Get Station metrics.
84+
Get combined metrics from all Station Modules:
8585

8686
```bash
8787
$ station metrics
8888
{
8989
"totalJobsCompleted": 123,
9090
"totalEarnings": "0"
9191
}
92+
```
93+
94+
Get metrics from a specific Station Module:
9295

96+
```bash
97+
$ station metrics saturn-l2-node
98+
{
99+
"totalJobsCompleted": 123,
100+
"totalEarnings": "0"
101+
}
102+
```
103+
104+
Follow metrics:
105+
106+
```bash
93107
$ station metrics --follow
94108
{
95109
"totalJobsCompleted": 123,
@@ -205,11 +219,11 @@ $ station --help
205219
Usage: station <command> [options]
206220

207221
Commands:
208-
station Start Station [default]
209-
station metrics Show metrics
210-
station activity Show activity log
211-
station logs [module] Show module logs
212-
station events Events stream
222+
station Start Station [default]
223+
station metrics [module] Show metrics
224+
station activity Show activity log
225+
station logs [module] Show module logs
226+
station events Events stream
213227

214228
Options:
215229
-v, --version Show version number [boolean]

bin/station.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ Sentry.init({
2323
await fs.mkdir(join(paths.moduleCache, 'saturn-L2-node'), { recursive: true })
2424
await fs.mkdir(join(paths.moduleState, 'saturn-L2-node'), { recursive: true })
2525
await fs.mkdir(paths.moduleLogs, { recursive: true })
26+
await fs.mkdir(paths.metrics, { recursive: true })
2627
await maybeCreateMetricsFile()
28+
await maybeCreateMetricsFile('saturn-L2-node')
2729
await maybeCreateActivityFile()
2830

2931
yargs(hideBin(process.argv))
3032
.usage('Usage: $0 <command> [options]')
3133
.command('$0', 'Start Station', () => {}, commands.station)
32-
.command('metrics', 'Show metrics', () => {}, commands.metrics)
34+
.command('metrics [module]', 'Show metrics', () => {}, commands.metrics)
3335
.commands(
3436
'activity',
3537
'Show activity log',

commands/metrics.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { followMetrics, getLatestMetrics } from '../lib/metrics.js'
22

3-
export const metrics = async ({ follow }) => {
3+
export const metrics = async ({ follow, module }) => {
44
if (follow) {
5-
for await (const obj of followMetrics()) {
5+
for await (const obj of followMetrics(module)) {
66
console.log(JSON.stringify(obj, 0, 2))
77
}
88
} else {
9-
console.log(JSON.stringify(await getLatestMetrics(), 0, 2))
9+
console.log(JSON.stringify(await getLatestMetrics(module), 0, 2))
1010
}
1111
}

commands/station.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const station = async () => {
3232
MAX_DISK_SPACE,
3333
storagePath: join(paths.moduleCache, 'saturn-L2-node'),
3434
binariesPath: paths.moduleBinaries,
35-
metricsStream: createMetricsStream(paths.metrics),
35+
metricsStream: await createMetricsStream('saturn-L2-node'),
3636
activityStream: createActivityStream('Saturn'),
3737
logStream: createLogStream(join(paths.moduleLogs, 'saturn-L2-node.log'))
3838
})

lib/metrics.js

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createLogStream, SerializeStream, parseLog, formatLog } from './log.js'
2-
import { Transform } from 'node:stream'
2+
import { Transform, PassThrough } from 'node:stream'
33
import { writeClient } from './telemetry.js'
44
import { Point } from '@influxdata/influxdb-client'
55
import fs from 'node:fs/promises'
@@ -8,38 +8,59 @@ import { paths } from './paths.js'
88
import { Tail } from 'tail'
99
import { maybeCreateFile } from './util.js'
1010
import { platform } from 'node:os'
11+
import assert from 'node:assert'
12+
import { join } from 'node:path'
1113

1214
// Metrics stream
1315
// - Filters duplicate entries
1416
// - Writes `jobs-completed` to InfluxDB
1517
// - Serializes JSON
16-
export const createMetricsStream = metricsPath => {
18+
// - Persists to
19+
// - module specific metrics file
20+
// - merged metrics file
21+
export const createMetricsStream = async moduleName => {
22+
assert(moduleName, 'moduleName required')
1723
const deduplicateStream = new DeduplicateStream()
24+
const splitStream = new PassThrough({ objectMode: true })
1825
deduplicateStream
19-
.pipe(new TelemetryStream())
26+
.pipe(new TelemetryStream(moduleName))
27+
.pipe(splitStream)
28+
splitStream
2029
.pipe(new SerializeStream())
21-
.pipe(createLogStream(metricsPath))
30+
.pipe(createLogStream(join(paths.metrics, `${moduleName}.log`)))
31+
splitStream
32+
.pipe(new MergeMetricsStream(...await Promise.all([
33+
getLatestMetrics(),
34+
getLatestMetrics(moduleName)
35+
])))
36+
.pipe(new SerializeStream())
37+
.pipe(createLogStream(paths.allMetrics))
2238
return deduplicateStream
2339
}
2440

25-
export const maybeCreateMetricsFile = async () => {
41+
export const maybeCreateMetricsFile = async (moduleName) => {
2642
await maybeCreateFile(
27-
paths.metrics,
43+
getMetricsFilePath(moduleName),
2844
formatLog(
2945
JSON.stringify({ totalJobsCompleted: 0, totalEarnings: '0' }) + '\n'
3046
)
3147
)
3248
}
3349

3450
const metricsLogLineToObject = metrics => JSON.parse(parseLog(metrics).text)
51+
const getMetricsFilePath = moduleName => {
52+
return moduleName
53+
? join(paths.metrics, `${moduleName}.log`)
54+
: paths.allMetrics
55+
}
3556

36-
export const getLatestMetrics = async () => {
37-
const metrics = await fs.readFile(paths.metrics, 'utf-8')
57+
export const getLatestMetrics = async (moduleName) => {
58+
const metrics = await fs.readFile(getMetricsFilePath(moduleName), 'utf-8')
3859
return metricsLogLineToObject(metrics.trim().split('\n').pop())
3960
}
4061

41-
export const followMetrics = async function * ({ signal } = {}) {
42-
const tail = new Tail(paths.metrics, {
62+
export const followMetrics = async function * ({ moduleName, signal } = {}) {
63+
const tail = new Tail(getMetricsFilePath(moduleName), {
4364
nLines: 1,
4465
useWatchFile: platform() === 'win32'
4566
})
@@ -69,16 +90,18 @@ class DeduplicateStream extends Transform {
6990
}
7091

7192
class TelemetryStream extends Transform {
72-
constructor () {
93+
constructor (moduleName) {
94+
assert(moduleName, 'moduleName required')
7395
super({ objectMode: true })
7496
this.lastMetrics = null
97+
this.moduleName = moduleName
7598
}
7699

77100
_transform (metrics, _, callback) {
78101
if (typeof this.lastMetrics?.totalJobsCompleted === 'number') {
79102
writeClient.writePoint(
80103
new Point('jobs-completed')
81-
.stringField('module', 'saturn')
104+
.stringField('module', this.moduleName)
82105
.intField(
83106
'value',
84107
metrics.totalJobsCompleted - this.lastMetrics.totalJobsCompleted
@@ -89,3 +112,28 @@ class TelemetryStream extends Transform {
89112
callback(null, metrics)
90113
}
91114
}
115+
116+
class MergeMetricsStream extends Transform {
117+
constructor (allMetrics, moduleMetrics) {
118+
assert.strictEqual(
119+
typeof allMetrics?.totalJobsCompleted,
120+
'number',
121+
'allMetrics.totalJobsCompleted required'
122+
)
123+
assert.strictEqual(
124+
typeof moduleMetrics?.totalJobsCompleted,
125+
'number',
126+
'moduleMetrics.totalJobsCompleted required'
127+
)
128+
super({ objectMode: true })
129+
this.allMetrics = allMetrics
130+
this.moduleMetrics = moduleMetrics
131+
}
132+
133+
_transform (metrics, _, callback) {
134+
const diff = metrics.totalJobsCompleted - this.moduleMetrics.totalJobsCompleted
135+
this.allMetrics.totalJobsCompleted += diff
136+
this.moduleMetrics.totalJobsCompleted = metrics.totalJobsCompleted
137+
callback(null, this.allMetrics)
138+
}
139+
}

lib/paths.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
1616

1717
export const getPaths = (cacheRoot, stateRoot) => ({
1818
repoRoot,
19-
metrics: join(stateRoot, 'logs', 'metrics.log'),
19+
metrics: join(stateRoot, 'logs', 'metrics'),
20+
allMetrics: join(stateRoot, 'logs', 'metrics', 'all.log'),
2021
activity: join(stateRoot, 'logs', 'activity.log'),
2122
moduleCache: join(cacheRoot, 'modules'),
2223
moduleState: join(stateRoot, 'modules'),

test/test.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,11 @@ describe('Metrics', () => {
107107
const CACHE_ROOT = join(tmpdir(), randomUUID())
108108
const STATE_ROOT = join(tmpdir(), randomUUID())
109109
await fs.mkdir(
110-
dirname(getPaths(CACHE_ROOT, STATE_ROOT).metrics),
110+
dirname(getPaths(CACHE_ROOT, STATE_ROOT).allMetrics),
111111
{ recursive: true }
112112
)
113113
await fs.writeFile(
114-
getPaths(CACHE_ROOT, STATE_ROOT).metrics,
114+
getPaths(CACHE_ROOT, STATE_ROOT).allMetrics,
115115
'[date] {"totalJobsCompleted":1,"totalEarnings":"2"}\n'
116116
)
117117
const { stdout } = await execa(
@@ -124,6 +124,27 @@ describe('Metrics', () => {
124124
JSON.stringify({ totalJobsCompleted: 1, totalEarnings: '2' }, 0, 2)
125125
)
126126
})
127+
it('outputs module metrics', async () => {
128+
const CACHE_ROOT = join(tmpdir(), randomUUID())
129+
const STATE_ROOT = join(tmpdir(), randomUUID())
130+
await fs.mkdir(
131+
getPaths(CACHE_ROOT, STATE_ROOT).metrics,
132+
{ recursive: true }
133+
)
134+
await fs.writeFile(
135+
join(getPaths(CACHE_ROOT, STATE_ROOT).metrics, 'saturn-l2-node.log'),
136+
'[date] {"totalJobsCompleted":1,"totalEarnings":"2"}\n'
137+
)
138+
const { stdout } = await execa(
139+
station,
140+
['metrics', 'saturn-l2-node'],
141+
{ env: { CACHE_ROOT, STATE_ROOT } }
142+
)
143+
assert.deepStrictEqual(
144+
stdout,
145+
JSON.stringify({ totalJobsCompleted: 1, totalEarnings: '2' }, 0, 2)
146+
)
147+
})
127148

128149
describe('Follow', async () => {
129150
for (const flag of ['-f', '--follow']) {

0 commit comments

Comments
 (0)