Skip to content

Commit ddfe09e

Browse files
feat(txm): instrumentation is configurable
1 parent ee3e75d commit ddfe09e

9 files changed

Lines changed: 314 additions & 256 deletions

File tree

packages/txm/lib/BlockMonitor.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { LogTag, Logger } from "@happy.tech/common"
22
import type { Block } from "viem"
33
import { Topics, eventBus } from "./EventBus.js"
44
import type { TransactionManager } from "./TransactionManager.js"
5-
import { currentBlockGauge, newBlockDelayHistogram, resetBlockMonitorCounter } from "./telemetry/metrics"
5+
import { TxmMetrics } from "./telemetry/metrics"
66

77
/**
88
* A type alias for {@link Block} with the `blockTag` set to `"latest"`, ensuring type definitions correspond to the latest block.
@@ -38,8 +38,8 @@ export class BlockMonitor {
3838
if (this.blockTimeout) clearTimeout(this.blockTimeout)
3939
eventBus.emit(Topics.NewBlock, block)
4040

41-
currentBlockGauge.record(Number(block.number))
42-
newBlockDelayHistogram.record(Date.now() - Number(block.timestamp) * 1000)
41+
TxmMetrics.getInstance().currentBlockGauge.record(Number(block.number))
42+
TxmMetrics.getInstance().newBlockDelayHistogram.record(Date.now() - Number(block.timestamp) * 1000)
4343

4444
this.scheduleTimeout()
4545
}
@@ -52,7 +52,7 @@ export class BlockMonitor {
5252
}
5353

5454
private resetBlockSubscription() {
55-
resetBlockMonitorCounter.add(1)
55+
TxmMetrics.getInstance().resetBlockMonitorCounter.add(1)
5656
if (this.unwatch) {
5757
this.unwatch()
5858
}

packages/txm/lib/NonceManager.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { LogTag, Logger } from "@happy.tech/common"
22
import type { TransactionManager } from "./TransactionManager"
3-
import { nonceManagerGauge, returnedNonceCounter, returnedNonceQueueGauge } from "./telemetry/metrics"
3+
import { TxmMetrics } from "./telemetry/metrics"
44

55
/*
66
/*
@@ -66,13 +66,13 @@ export class NonceManager {
6666
public requestNonce(): number {
6767
if (this.returnedNonceQueue.length > 0) {
6868
const nonce = this.returnedNonceQueue.shift()!
69-
returnedNonceQueueGauge.record(this.returnedNonceQueue.length)
69+
TxmMetrics.getInstance().returnedNonceQueueGauge.record(this.returnedNonceQueue.length)
7070
return nonce
7171
}
7272

7373
const requestedNonce = this.nonce
7474
this.nonce = this.nonce + 1
75-
nonceManagerGauge.record(this.nonce)
75+
TxmMetrics.getInstance().nonceManagerGauge.record(this.nonce)
7676
return requestedNonce
7777
}
7878

@@ -86,8 +86,8 @@ export class NonceManager {
8686
this.returnedNonceQueue.splice(index, 0, nonce)
8787
}
8888

89-
returnedNonceCounter.add(1)
90-
returnedNonceQueueGauge.record(this.returnedNonceQueue.length)
89+
TxmMetrics.getInstance().returnedNonceCounter.add(1)
90+
TxmMetrics.getInstance().returnedNonceQueueGauge.record(this.returnedNonceQueue.length)
9191
}
9292

9393
public async resync() {

packages/txm/lib/Transaction.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Address, ContractFunctionArgs, Hash } from "viem"
44
import type { LatestBlock } from "./BlockMonitor"
55
import { Topics, eventBus } from "./EventBus.js"
66
import type { TransactionTable } from "./db/types.js"
7-
import { attemptsUntilFinalization, transactionStatusChangeCounter } from "./telemetry/metrics"
7+
import { TxmMetrics } from "./telemetry/metrics"
88

99
export enum TransactionStatus {
1010
/**
@@ -206,11 +206,11 @@ export class Transaction {
206206
this.status = status
207207
this.markUpdated()
208208

209-
transactionStatusChangeCounter.add(1, {
209+
TxmMetrics.getInstance().transactionStatusChangeCounter.add(1, {
210210
status: this.status,
211211
})
212212

213-
attemptsUntilFinalization.record(this.attempts.length)
213+
TxmMetrics.getInstance().attemptsUntilFinalization.record(this.attempts.length)
214214

215215
eventBus.emit(Topics.TransactionStatusChanged, {
216216
transaction: this,

packages/txm/lib/TransactionCollector.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { LatestBlock } from "./BlockMonitor.js"
33
import { Topics, eventBus } from "./EventBus.js"
44
import { AttemptType, TransactionStatus } from "./Transaction.js"
55
import type { TransactionManager } from "./TransactionManager.js"
6-
import { transactionCollectedCounter } from "./telemetry/metrics"
6+
import { TxmMetrics } from "./telemetry/metrics"
77

88
/**
99
* This module is responsible for retrieving transactions from the originators when a new block is received.
@@ -42,7 +42,7 @@ export class TransactionCollector {
4242
return
4343
}
4444

45-
transactionCollectedCounter.add(transactionsBatch.length)
45+
TxmMetrics.getInstance().transactionCollectedCounter.add(transactionsBatch.length)
4646

4747
await Promise.all(
4848
transactionsBatch.map(async (transaction) => {

packages/txm/lib/TransactionManager.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
convertToSafeViemWalletClient,
77
getUrlProtocol,
88
} from "@happy.tech/common"
9+
import type { MetricReader } from "@opentelemetry/sdk-metrics"
910
import type { Result } from "neverthrow"
1011
import {
1112
type Abi,
@@ -31,7 +32,8 @@ import { TransactionRepository } from "./TransactionRepository.js"
3132
import { TransactionSubmitter } from "./TransactionSubmitter.js"
3233
import { TxMonitor } from "./TxMonitor.js"
3334
import { type EIP1559Parameters, opStackDefaultEIP1559Parameters } from "./eip1559.js"
34-
import { blockchainRpcResponseTimeHistogram, rpcCounter, rpcErrorCounter } from "./telemetry/metrics"
35+
import { initializeTelemetry } from "./telemetry/instrumentation"
36+
import { TxmMetrics } from "./telemetry/metrics"
3537

3638
export type TransactionManagerConfig = {
3739
/**
@@ -139,6 +141,30 @@ export type TransactionManagerConfig = {
139141
* Default: {@link DefaultRetryPolicyManager}
140142
*/
141143
retryPolicyManager?: RetryPolicyManager
144+
145+
/**
146+
* Transaction Manager metrics configuration.
147+
*/
148+
metrics?: {
149+
/**
150+
* Whether to enable metrics collection.
151+
* Defaults to true.
152+
*/
153+
active?: boolean
154+
/**
155+
* Port number for the default Prometheus metrics endpoint.
156+
* The default metric reader is a Prometheus reader that exposes metrics via this endpoint.
157+
* This setting is only used when custom metricReaders are not provided.
158+
* Defaults to 9090.
159+
*/
160+
port?: number
161+
/**
162+
* Custom metric readers to use instead of the default Prometheus reader.
163+
* If provided, these readers will be used and the port setting will be ignored.
164+
* If not provided, a default Prometheus reader will be configured using the specified port.
165+
*/
166+
metricReaders?: MetricReader[]
167+
}
142168
}
143169

144170
export type TransactionOriginator = (block: LatestBlock) => Promise<Transaction[]>
@@ -178,6 +204,12 @@ export class TransactionManager {
178204
public readonly blockInactivityTimeout: number
179205

180206
constructor(_config: TransactionManagerConfig) {
207+
initializeTelemetry({
208+
active: _config.metrics?.active ?? true,
209+
port: _config.metrics?.port ?? 9090,
210+
metricReaders: _config.metrics?.metricReaders,
211+
})
212+
181213
this.collectors = []
182214

183215
const protocol = getUrlProtocol(_config.rpc.url)
@@ -237,9 +269,9 @@ export class TransactionManager {
237269
chain,
238270
}),
239271
{
240-
rpcCounter: rpcCounter,
241-
rpcErrorCounter: rpcErrorCounter,
242-
rpcResponseTimeHistogram: blockchainRpcResponseTimeHistogram,
272+
rpcCounter: TxmMetrics.getInstance().rpcCounter,
273+
rpcErrorCounter: TxmMetrics.getInstance().rpcErrorCounter,
274+
rpcResponseTimeHistogram: TxmMetrics.getInstance().blockchainRpcResponseTimeHistogram,
243275
},
244276
)
245277

@@ -249,9 +281,9 @@ export class TransactionManager {
249281
chain,
250282
}),
251283
{
252-
rpcCounter: rpcCounter,
253-
rpcErrorCounter: rpcErrorCounter,
254-
rpcResponseTimeHistogram: blockchainRpcResponseTimeHistogram,
284+
rpcCounter: TxmMetrics.getInstance().rpcCounter,
285+
rpcErrorCounter: TxmMetrics.getInstance().rpcErrorCounter,
286+
rpcResponseTimeHistogram: TxmMetrics.getInstance().blockchainRpcResponseTimeHistogram,
255287
},
256288
)
257289

packages/txm/lib/TransactionRepository.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@ import { Topics, eventBus } from "./EventBus.js"
55
import { NotFinalizedStatuses, Transaction } from "./Transaction.js"
66
import type { TransactionManager } from "./TransactionManager.js"
77
import { db } from "./db/driver.js"
8-
import {
9-
databaseErrorsCounter,
10-
databaseOperationDurationHistogram,
11-
databaseOperationsCounter,
12-
notFinalizedTransactionsGauge,
13-
} from "./telemetry/metrics"
8+
import { TxmMetrics } from "./telemetry/metrics"
149

1510
/**
1611
* This module acts as intermediate layer between the library and the database.
@@ -54,7 +49,7 @@ export class TransactionRepository {
5449
return ok(cachedTransaction)
5550
}
5651

57-
databaseOperationsCounter.add(1, {
52+
TxmMetrics.getInstance().databaseOperationsCounter.add(1, {
5853
operation: "getTransaction",
5954
})
6055
const start = Date.now()
@@ -69,12 +64,12 @@ export class TransactionRepository {
6964
unknownToError,
7065
)
7166

72-
databaseOperationDurationHistogram.record(Date.now() - start, {
67+
TxmMetrics.getInstance().databaseOperationDurationHistogram.record(Date.now() - start, {
7368
operation: "getTransaction",
7469
})
7570

7671
if (persistedTransactionResult.isErr()) {
77-
databaseErrorsCounter.add(1, {
72+
TxmMetrics.getInstance().databaseErrorsCounter.add(1, {
7873
operation: "getTransaction",
7974
})
8075
return err(persistedTransactionResult.error)
@@ -90,7 +85,7 @@ export class TransactionRepository {
9085

9186
const notPersistedTransactions = transactions.filter((t) => t.notPersisted)
9287

93-
databaseOperationsCounter.add(1, {
88+
TxmMetrics.getInstance().databaseOperationsCounter.add(1, {
9489
operation: "saveTransactions",
9590
})
9691
const start = Date.now()
@@ -113,7 +108,7 @@ export class TransactionRepository {
113108
unknownToError,
114109
)
115110

116-
databaseOperationDurationHistogram.record(Date.now() - start, {
111+
TxmMetrics.getInstance().databaseOperationDurationHistogram.record(Date.now() - start, {
117112
operation: "saveTransactions",
118113
})
119114

@@ -124,9 +119,9 @@ export class TransactionRepository {
124119
this.notFinalizedTransactions.push(...notPersistedTransactions)
125120
transactions.forEach((t) => t.markFlushed())
126121

127-
notFinalizedTransactionsGauge.record(this.notFinalizedTransactions.length)
122+
TxmMetrics.getInstance().notFinalizedTransactionsGauge.record(this.notFinalizedTransactions.length)
128123
} else {
129-
databaseErrorsCounter.add(1, {
124+
TxmMetrics.getInstance().databaseErrorsCounter.add(1, {
130125
operation: "saveTransactions",
131126
})
132127
}
@@ -147,7 +142,7 @@ export class TransactionRepository {
147142
}
148143

149144
async purgeFinalizedTransactions() {
150-
databaseOperationsCounter.add(1, {
145+
TxmMetrics.getInstance().databaseOperationsCounter.add(1, {
151146
operation: "purgeFinalizedTransactions",
152147
})
153148
const start = Date.now()
@@ -162,12 +157,12 @@ export class TransactionRepository {
162157
unknownToError,
163158
)
164159

165-
databaseOperationDurationHistogram.record(Date.now() - start, {
160+
TxmMetrics.getInstance().databaseOperationDurationHistogram.record(Date.now() - start, {
166161
operation: "purgeFinalizedTransactions",
167162
})
168163

169164
if (result.isErr()) {
170-
databaseErrorsCounter.add(1, {
165+
TxmMetrics.getInstance().databaseErrorsCounter.add(1, {
171166
operation: "purgeFinalizedTransactions",
172167
})
173168
}

packages/txm/lib/TxMonitor.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Topics, eventBus } from "./EventBus.js"
66
import type { RevertedTransactionReceipt } from "./RetryPolicyManager"
77
import { type Attempt, AttemptType, type Transaction, TransactionStatus } from "./Transaction.js"
88
import type { TransactionManager } from "./TransactionManager.js"
9-
import { transactionInclusionBlockHistogram, transactionsRetriedCounter } from "./telemetry/metrics"
9+
import { TxmMetrics } from "./telemetry/metrics"
1010

1111
type AttemptWithReceipt = { attempt: Attempt; receipt: TransactionReceipt }
1212

@@ -140,7 +140,9 @@ export class TxMonitor {
140140

141141
const { attempt, receipt } = attemptOrResults
142142

143-
transactionInclusionBlockHistogram.record(Number(block.number - transaction.collectionBlock!))
143+
TxmMetrics.getInstance().transactionInclusionBlockHistogram.record(
144+
Number(block.number - transaction.collectionBlock!),
145+
)
144146

145147
if (receipt.status === "success") {
146148
if (attempt.type === AttemptType.Cancellation) {
@@ -162,7 +164,7 @@ export class TxMonitor {
162164
return transaction.changeStatus(TransactionStatus.Failed)
163165
}
164166

165-
transactionsRetriedCounter.add(1)
167+
TxmMetrics.getInstance().transactionsRetriedCounter.add(1)
166168

167169
return this.handleRetryTransaction(transaction)
168170
})
Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import opentelemetry from "@opentelemetry/api"
22
import { PrometheusExporter } from "@opentelemetry/exporter-prometheus"
33
import { Resource } from "@opentelemetry/resources"
4-
import { MeterProvider } from "@opentelemetry/sdk-metrics"
4+
import { MeterProvider, type MetricReader } from "@opentelemetry/sdk-metrics"
55
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions"
66

77
const resource = Resource.default().merge(
@@ -11,11 +11,19 @@ const resource = Resource.default().merge(
1111
}),
1212
)
1313

14-
const prometheusExporter = new PrometheusExporter({ port: 9090 })
14+
export function initializeTelemetry({
15+
active,
16+
port,
17+
metricReaders,
18+
}: { active: boolean; port: number; metricReaders?: MetricReader[] }): void {
19+
if (!active) {
20+
return
21+
}
1522

16-
const myServiceMeterProvider = new MeterProvider({
17-
resource: resource,
18-
readers: [prometheusExporter],
19-
})
23+
const meterProvider = new MeterProvider({
24+
resource: resource,
25+
readers: metricReaders || [new PrometheusExporter({ port })],
26+
})
2027

21-
opentelemetry.metrics.setGlobalMeterProvider(myServiceMeterProvider)
28+
opentelemetry.metrics.setGlobalMeterProvider(meterProvider)
29+
}

0 commit comments

Comments
 (0)