Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
af8bbaf
Implemented OpenTelemetry for metrics and tracing
rpanic Feb 3, 2025
38349e5
Added docker-compose for LGTM stack
rpanic Feb 3, 2025
849228b
Added console default for tracing
rpanic Feb 3, 2025
892117b
Integrated tracing points into block prod pipeline
rpanic Feb 3, 2025
bda5ee0
package-lock
rpanic Feb 3, 2025
2d435f1
Fixed run-graphql to test metrics
rpanic Feb 4, 2025
84db441
Added @trace decorator
rpanic Feb 4, 2025
f9bccd3
Integrated tracing into new block proving pipeline
rpanic Feb 4, 2025
d2f5989
Fix Constant Fee Strategy import
ejMina226 Feb 11, 2025
de6466e
Merge branch 'develop' into feature/monitoring
rpanic Feb 12, 2025
320c79a
package-lock
rpanic Feb 12, 2025
3417d03
Merge branch 'develop' into feature/monitoring
rpanic Feb 12, 2025
8b3b4d2
Made some adaptations
rpanic Feb 13, 2025
db12f46
Changed tracer level to debug
rpanic Feb 13, 2025
18554ad
linting
rpanic Feb 13, 2025
8a10b6e
Added static dependencyfactories
rpanic Feb 13, 2025
3149a39
Fix resolving issue by making IncomingMessagesService dependency static
rpanic Feb 13, 2025
1b77e32
Fixed compile error
rpanic Feb 13, 2025
d54ad56
prettier yaml
rpanic Feb 13, 2025
6e73cf8
Fixed test
rpanic Feb 14, 2025
7de7a5b
Disabled blocking test
rpanic Feb 14, 2025
1cede08
Fixed graphql test
rpanic Feb 17, 2025
332d51f
Merge branch 'develop' into feature/static-dependency-factory
rpanic Feb 17, 2025
179cfbc
Added tracing to DB
rpanic Feb 17, 2025
bd3105f
Fixed compile errors
rpanic Feb 17, 2025
7a4ca83
Merge branch 'feature/monitoring' into feature/static-dependency-factory
rpanic Feb 17, 2025
a14ef68
Fixed linting
rpanic Feb 17, 2025
71c8441
Fixed test error
rpanic Feb 17, 2025
02eb3ef
Fixed test error
rpanic Feb 17, 2025
af445c5
Remove configure empty config requirement for NoConfig modules
rpanic Feb 17, 2025
1b1770f
Made OpenTelemetryServer closeable
rpanic Feb 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,779 changes: 2,628 additions & 151 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,28 @@
},
"dependencies": {
"@graphql-tools/stitch": "^9.0.3",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.56.0",
"@opentelemetry/exporter-prometheus": "^0.57.1",
"@opentelemetry/exporter-trace-otlp-grpc": "^0.57.1",
"@opentelemetry/instrumentation": "^0.57.1",
"@opentelemetry/instrumentation-runtime-node": "^0.12.1",
"@opentelemetry/resources": "^1.30.1",
"@opentelemetry/sdk-metrics": "^1.30.1",
"@opentelemetry/sdk-node": "^0.57.1",
"@opentelemetry/sdk-trace-node": "^1.30.1",
"@opentelemetry/semantic-conventions": "^1.28.0",
"@types/express": "^5.0.0",
"@types/humanize-duration": "^3.27.2",
"class-validator": "^0.14.0",
"express": "^4.21.2",
"graphql": "^16.9.0",
"graphql-scalars": "^1.22.4",
"graphql-yoga": "^5.0.0",
"humanize-duration": "^3.30.0",
"koa": "^2.14.2",
"lodash": "^4.17.21",
"loglevel-plugin-remote": "^0.6.8",
"reflect-metadata": "^0.1.13",
"type-graphql": "2.0.0-rc.2"
},
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from "./graphql/modules/AdvancedNodeStatusResolver";
export * from "./graphql/services/NodeStatusService";
export * from "./graphql/modules/MerkleWitnessResolver";
export * from "./graphql/VanillaGraphqlModules";
export * from "./metrics/OpenTelemetryServer";
111 changes: 111 additions & 0 deletions packages/api/src/metrics/OpenTelemetryServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
closeable,
Closeable,
Sequencer,
SequencerModule,
sequencerModule,
} from "@proto-kit/sequencer";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { Resource } from "@opentelemetry/resources";
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from "@opentelemetry/semantic-conventions";
import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
import { RuntimeNodeInstrumentation } from "@opentelemetry/instrumentation-runtime-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
import { inject } from "tsyringe";
import { DependencyFactory, DependencyRecord, log } from "@proto-kit/common";

import { SequencerInstrumentation } from "./SequencerInstrumentation";
import { OpenTelemetryTracer } from "./OpenTelemetryTracer";

export type OpenTelemetryServerConfig = {
metrics?: {
enabled?: boolean;
prometheus?: ConstructorParameters<typeof PrometheusExporter>[0];
nodeScrapeInterval?: number;
};
tracing?: {
enabled?: boolean;
otlp?: ConstructorParameters<typeof OTLPTraceExporter>[0];
};
};

@sequencerModule()
@closeable()
export class OpenTelemetryServer
extends SequencerModule<OpenTelemetryServerConfig>
implements DependencyFactory, Closeable
{
private sdk?: NodeSDK;

public constructor(
@inject("Sequencer") private readonly sequencer: Sequencer<any>
) {
super();
}

public dependencies() {
return {
Tracer: {
useClass: OpenTelemetryTracer,
forceOverwrite: true,
},
} satisfies DependencyRecord;
}

public async start(): Promise<void> {
const {
config: { metrics, tracing },
} = this;

// TODO Modularize Instrumentations
const seqMetrics = this.sequencer.dependencyContainer.resolve(
SequencerInstrumentation
);

const metricReader =
metrics?.enabled ?? true
? new PrometheusExporter(metrics?.prometheus)
: undefined;

const instrumentations =
metrics?.enabled ?? true
? [
new RuntimeNodeInstrumentation({
monitoringPrecision: metrics?.nodeScrapeInterval ?? 5000,
}),
seqMetrics,
]
: [];

const traceExporter =
tracing?.enabled ?? true
? new OTLPTraceExporter(tracing?.otlp)
: undefined;

const sdk = new NodeSDK({
resource: new Resource({
[ATTR_SERVICE_NAME]: "protokit",
[ATTR_SERVICE_VERSION]: "canary",
}),
metricReader,
traceExporter,
instrumentations,
});

sdk.start();
this.sdk = sdk;

// TODO Write logger to directly integrate with our logging library
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR);

log.info("OpenTelemetryServer started");
}

public async close() {
await this.sdk?.shutdown();
}
}
50 changes: 50 additions & 0 deletions packages/api/src/metrics/OpenTelemetryTracer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import opentelemetry, { SpanStatusCode } from "@opentelemetry/api";
import { Tracer } from "@proto-kit/sequencer";
import { inject, injectable } from "tsyringe";
import { noop } from "@proto-kit/common";

import type { OpenTelemetryServer } from "./OpenTelemetryServer";

@injectable()
export class OpenTelemetryTracer implements Tracer {
public constructor(
// We need to import this here, so that the OpenTelemetryServer will be resolved
// before this module, and therefore will be already started when this module is
// eventually consumed and used
@inject("OpenTelemetryServer") openTelemetryServer: OpenTelemetryServer
) {
noop();
}

private tracer: ReturnType<typeof opentelemetry.trace.getTracer> | undefined =
undefined;

public async trace<T>(
name: string,
f: () => Promise<T>,
metadata?: Record<string, string | number>
) {
if (this.tracer === undefined) {
this.tracer = opentelemetry.trace.getTracer("protokit", "canary");
}

return await this.tracer.startActiveSpan(name, async (span) => {
if (metadata !== undefined) {
span.setAttributes(metadata);
}
try {
const result = await f();
span.end();
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (e) {
if (e instanceof Error) {
span.recordException(e);
}
span.setStatus({ code: SpanStatusCode.ERROR });
span.end();
throw e;
}
});
}
}
54 changes: 54 additions & 0 deletions packages/api/src/metrics/SequencerInstrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { injectable } from "tsyringe";
import { BlockTriggerBase, PrivateMempool } from "@proto-kit/sequencer";
import { injectOptional } from "@proto-kit/common";
import { InstrumentationBase } from "@opentelemetry/instrumentation";

@injectable()
export class SequencerInstrumentation extends InstrumentationBase<{}> {
private blockProduced: (height: number) => void = () => {};

public constructor(
@injectOptional("BlockTrigger")
trigger: BlockTriggerBase | undefined,
@injectOptional("Mempool")
private readonly mempool: PrivateMempool | undefined
) {
super("protokit", "canary", {});
if (trigger !== undefined) {
trigger.events.on("block-produced", (block) => {
this.blockProduced(parseInt(block.height.toString(), 10));
});
}
}

// Called when a new `MeterProvider` is set
// the Meter (result of @opentelemetry/api's getMeter) is
// available as this.meter within this method
// eslint-disable-next-line no-underscore-dangle
override _updateMetricInstruments() {
const { mempool } = this;

if (mempool !== undefined) {
const mempoolSize = this.meter.createObservableCounter(
"protokit_mempool_size",
{
description: "The size of the mempool",
}
);

this.meter.addBatchObservableCallback(
async (observableResult) => {
const mempoolLength = await mempool.length();

observableResult.observe(mempoolSize, mempoolLength);
},
[mempoolSize]
);
}

const blockHeight = this.meter.createGauge("protokit_block_height");
this.blockProduced = (height) => blockHeight.record(height);
}

init() {}
}
Loading
Loading