Skip to content

Commit

Permalink
fix: integrate custom metrics to private endpoint and add signer weig…
Browse files Browse the repository at this point in the history
…ht percentage metric (#69)

* fix: integrate custom metrics to private prom

* fix: tests

* feat: add signer weight metrics

* nit: test
  • Loading branch information
rafaelcr authored Jan 17, 2025
1 parent edca697 commit d113b7c
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 50 deletions.
2 changes: 0 additions & 2 deletions src/api/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { CycleRoutes } from './routes/cycle';
import { BlockRoutes } from './routes/blocks';
import { BlockProposalsRoutes } from './routes/block-proposals';
import { SocketIORoutes } from './routes/socket-io';
import { SignerPromMetricsRoutes } from './routes/prom-metrics';

export const Api: FastifyPluginAsync<Record<never, never>, Server, TypeBoxTypeProvider> = async (
fastify,
Expand All @@ -25,7 +24,6 @@ export const Api: FastifyPluginAsync<Record<never, never>, Server, TypeBoxTypePr
await fastify.register(BlockRoutes);
await fastify.register(BlockProposalsRoutes);
await fastify.register(SocketIORoutes);
await fastify.register(SignerPromMetricsRoutes);
},
{ prefix: '/signer-metrics' }
);
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { buildProfilerServer, logger, registerShutdownConfig } from '@hirosystem
import { closeChainhookServer, startChainhookServer } from './chainhook/server';
import { startPoxInfoUpdater } from './stacks-core-rpc/pox-info-updater';
import { StackerSetUpdator } from './stacks-core-rpc/stacker-set-updater';
import { configureSignerMetrics } from './prom-metrics';

/**
* Initializes background services. Only for `default` and `writeonly` run modes.
Expand Down Expand Up @@ -68,6 +69,7 @@ async function initApiService(db: PgStore) {
await promServer.close();
},
});
configureSignerMetrics(db);
await promServer.listen({ host: ENV.API_HOST, port: ENV.PROMETHEUS_PORT });
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/pg/pg-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,13 @@ export class PgStore extends BasePgStore {
return dbRewardSetSigners;
}

async getCurrentCycleNumber() {
const result = await this.sql<{ cycle_number: number }[]>`
SELECT MAX(cycle_number) AS cycle_number FROM reward_set_signers
`;
return result[0]?.cycle_number;
}

async getSignerForCycle(cycleNumber: number, signerId: string) {
const dbRewardSetSigner = await this.sql<
{
Expand Down
64 changes: 19 additions & 45 deletions src/api/routes/prom-metrics.ts → src/prom-metrics.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
import { Gauge, Registry } from 'prom-client';
import { ENV } from '../../env';
import { FastifyPluginAsync } from 'fastify';
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import { Server } from 'node:http';

export const SignerPromMetricsRoutes: FastifyPluginAsync<
Record<never, never>,
Server,
TypeBoxTypeProvider
> = async (fastify, _options) => {
const db = fastify.db;
import { Gauge } from 'prom-client';
import { ENV } from './env';
import { PgStore } from './pg/pg-store';

export function configureSignerMetrics(db: PgStore) {
// Getter for block periods so that the env var can be updated
const getBlockPeriods = () => ENV.SIGNER_PROMETHEUS_METRICS_BLOCK_PERIODS.split(',').map(Number);

const signerRegistry = new Registry();

const metricsPrefix = 'signer_api_';

new Gauge({
name: metricsPrefix + 'time_since_last_pending_block_proposal_ms',
help: 'Time in milliseconds since the oldest pending block proposal',
registers: [signerRegistry],
async collect() {
const dbResult = await db.sqlTransaction(async sql => {
return await db.getLastPendingProposalDate({ sql });
Expand All @@ -35,7 +24,6 @@ export const SignerPromMetricsRoutes: FastifyPluginAsync<
name: metricsPrefix + 'avg_block_push_time_ms',
help: 'Average time (in milliseconds) taken for block proposals to be accepted and pushed over different block periods',
labelNames: ['period'] as const,
registers: [signerRegistry],
async collect() {
const dbResults = await db.sqlTransaction(async sql => {
return await db.getRecentBlockPushMetrics({ sql, blockRanges: getBlockPeriods() });
Expand All @@ -51,7 +39,6 @@ export const SignerPromMetricsRoutes: FastifyPluginAsync<
name: metricsPrefix + 'proposal_acceptance_rate',
help: 'The acceptance rate of block proposals for different block ranges (as a float between 0 and 1).',
labelNames: ['period'],
registers: [signerRegistry],
async collect() {
const dbResults = await db.sqlTransaction(async sql => {
return await db.getRecentBlockAcceptanceMetrics({ sql, blockRanges: getBlockPeriods() });
Expand All @@ -67,7 +54,6 @@ export const SignerPromMetricsRoutes: FastifyPluginAsync<
name: metricsPrefix + 'signer_state_count',
help: 'Count of signer states over different block periods',
labelNames: ['signer', 'period', 'state'] as const,
registers: [signerRegistry],
async collect() {
const dbResults = await db.sqlTransaction(async sql => {
return await db.getRecentSignerMetrics({ sql, blockRanges: getBlockPeriods() });
Expand All @@ -83,31 +69,19 @@ export const SignerPromMetricsRoutes: FastifyPluginAsync<
},
});

fastify.get(
'/metrics',
{
schema: {
operationId: 'getPrometheusMetrics',
summary: 'API Signer Prometheus Metrics',
description: 'Retreives the Prometheus metrics signer and block proposal related data',
tags: ['Prometheus Metrics'],
response: {
200: {
description: 'Prometheus metrics in plain text format',
content: {
'text/plain': {
schema: { type: 'string' },
},
},
},
},
},
new Gauge({
name: metricsPrefix + 'signer_weight_percentage',
help: 'Signer weight percentage for the current cycle',
labelNames: ['signer'] as const,
async collect() {
const dbResults = await db.sqlTransaction(async sql => {
const cycle = await db.getCurrentCycleNumber();
return await db.getSignersForCycle({ sql, cycleNumber: cycle, limit: 60, offset: 0 });
});
this.reset();
for (const row of dbResults) {
this.set({ signer: row.signer_key }, row.weight_percentage);
}
},
async (_, reply) => {
const metrics = await signerRegistry.metrics();
await reply.type(signerRegistry.contentType).send(metrics);
}
);

await Promise.resolve();
};
});
}
32 changes: 29 additions & 3 deletions tests/db/db-notifications.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as zlib from "node:zlib";
import * as supertest from "supertest";
import { FastifyInstance } from "fastify";
import { StacksPayload } from "@hirosystems/chainhook-client";
import { buildApiServer } from "../../src/api/init";
import { buildApiServer, buildPromServer } from "../../src/api/init";
import { PgStore } from "../../src/pg/pg-store";
import { BlockProposalsEntry } from "../../src/api/schemas";
import {
Expand All @@ -19,6 +19,9 @@ import {
} from "../../src/api/routes/socket-io";
import { waitForEvent } from "../../src/helpers";
import { ENV } from "../../src/env";
import { configureSignerMetrics } from "../../src/prom-metrics";
import { IFastifyMetrics } from 'fastify-metrics';
import * as prom from 'prom-client';

describe("Db notifications tests", () => {
let db: PgStore;
Expand Down Expand Up @@ -230,6 +233,11 @@ describe("Db notifications tests", () => {
});

test("prometheus signer metrics", async () => {
const metrics: IFastifyMetrics = { client: prom, initMetricsInRegistry() {} };
const promServer = await buildPromServer({ metrics });
configureSignerMetrics(db);
await promServer.listen({ host: "127.0.0.1", port: 9153 });

const bucketsEnvName = "SIGNER_PROMETHEUS_METRICS_BLOCK_PERIODS";
const metricPrefix = "signer_api_";
const orig = ENV[bucketsEnvName];
Expand Down Expand Up @@ -304,8 +312,8 @@ describe("Db notifications tests", () => {
]),
);

const responseTest = await supertest(apiServer.server)
.get("/signer-metrics/metrics")
const responseTest = await supertest(promServer.server)
.get("/metrics")
.expect(200);
const receivedLines = responseTest.text.split("\n");

Expand Down Expand Up @@ -346,7 +354,25 @@ ${metricPrefix}signer_state_count{signer="0x03fc7cb917698b6137060f434988f7688520
expect.arrayContaining(expectedSignerStateLines.split("\n")),
);

const expectedWeightPercentageLines = `# TYPE ${metricPrefix}signer_weight_percentage gauge
${metricPrefix}signer_weight_percentage{signer="0x02e8620935d58ebffa23c260f6917cbd0915ea17d7a46df17e131540237d335504"} 76
${metricPrefix}signer_weight_percentage{signer="0x036a44f61d85efa844b42475f107b106dc8fb209ae27813893c3269c59821e0333"} 6
${metricPrefix}signer_weight_percentage{signer="0x0382ebc8732f0d5f1501a9f842dc6a357497303c71ea8ca4b3858f41fe64e2c3a1"} 2
${metricPrefix}signer_weight_percentage{signer="0x0333545cd2634813ea042c9dfc199b6e635dc0db391b6104b002d82393c9a58691"} 2
${metricPrefix}signer_weight_percentage{signer="0x0399649284ed10a00405f032f8567b5e5463838aaa00af8d6bc9da71dda4e19c9c"} 2
${metricPrefix}signer_weight_percentage{signer="0x037fc1bb0eab484f5807ba2bfdeb208f9104fa89abbbb8034e23f33df4b9e5ab10"} 2
${metricPrefix}signer_weight_percentage{signer="0x02f5b5555964731f77bc3a3767ef3ed64ed0ba2971e8792272349db1a148e43ad5"} 2
${metricPrefix}signer_weight_percentage{signer="0x02abcde47f94fcf54f6926127368ddd2ae8c11a27b854269378113e2f6835cb372"} 2
${metricPrefix}signer_weight_percentage{signer="0x02b635d0521ef891a2549253eff2f5d09665af0ecfb21b0aacbf2658e7e7b06761"} 2
${metricPrefix}signer_weight_percentage{signer="0x03fc7cb917698b6137060f434988f7688520972dfb944f9b03c0fbf1c75303e79a"} 2
${metricPrefix}signer_weight_percentage{signer="0x02567b1f5056f6c3e59e759f66216d21239904d1cc2d847c5dcc3c2b6534d7bead"} 2`;
expect(receivedLines).toEqual(
expect.arrayContaining(expectedWeightPercentageLines.split("\n")),
);

process.env[bucketsEnvName] = orig;
ENV.reload();

await promServer.close();
});
});

0 comments on commit d113b7c

Please sign in to comment.