Skip to content

Commit

Permalink
feat: add metrics property to helia interface (#512)
Browse files Browse the repository at this point in the history
To allow collecting metrics about arbitrary parts of the Helia stack,
add an optional `.metrics` property for stat collection.

This can be used with implementations such as `@libp2p/prometheus-metrics`
and/or others.
  • Loading branch information
achingbrain authored Apr 22, 2024
1 parent 5e98950 commit f7f71bb
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 23 deletions.
9 changes: 5 additions & 4 deletions packages/bitswap/src/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { WantOptions } from './bitswap.js'
import type { MultihashHasherLoader } from './index.js'
import type { Block } from './pb/message.js'
import type { Provider, Routing } from '@helia/interface/routing'
import type { Libp2p, AbortOptions, Connection, PeerId, IncomingStreamData, Topology, ComponentLogger, IdentifyResult, Counter } from '@libp2p/interface'
import type { Libp2p, AbortOptions, Connection, PeerId, IncomingStreamData, Topology, ComponentLogger, IdentifyResult, Counter, Metrics } from '@libp2p/interface'
import type { Logger } from '@libp2p/logger'
import type { CID } from 'multiformats/cid'
import type { ProgressEvent, ProgressOptions } from 'progress-events'
Expand Down Expand Up @@ -49,6 +49,7 @@ export interface NetworkComponents {
routing: Routing
logger: ComponentLogger
libp2p: Libp2p
metrics?: Metrics
}

export interface BitswapMessageEventDetail {
Expand Down Expand Up @@ -101,13 +102,13 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
this.maxIncomingMessageSize = init.maxIncomingMessageSize ?? DEFAULT_MAX_OUTGOING_MESSAGE_SIZE
this.maxOutgoingMessageSize = init.maxOutgoingMessageSize ?? init.maxIncomingMessageSize ?? DEFAULT_MAX_INCOMING_MESSAGE_SIZE
this.metrics = {
blocksSent: components.libp2p.metrics?.registerCounter('helia_bitswap_sent_blocks_total'),
dataSent: components.libp2p.metrics?.registerCounter('helia_bitswap_sent_data_bytes_total')
blocksSent: components.metrics?.registerCounter('helia_bitswap_sent_blocks_total'),
dataSent: components.metrics?.registerCounter('helia_bitswap_sent_data_bytes_total')
}

this.sendQueue = new PeerQueue({
concurrency: init.messageSendConcurrency ?? DEFAULT_MESSAGE_SEND_CONCURRENCY,
metrics: components.libp2p.metrics,
metrics: components.metrics,
metricName: 'helia_bitswap_message_send_queue'
})
this.sendQueue.addEventListener('error', (evt) => {
Expand Down
5 changes: 3 additions & 2 deletions packages/bitswap/src/peer-want-lists/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Ledger } from './ledger.js'
import type { BitswapNotifyProgressEvents, WantListEntry } from '../index.js'
import type { Network } from '../network.js'
import type { BitswapMessage } from '../pb/message.js'
import type { ComponentLogger, Libp2p, Logger, PeerId } from '@libp2p/interface'
import type { ComponentLogger, Libp2p, Logger, Metrics, PeerId } from '@libp2p/interface'
import type { PeerMap } from '@libp2p/peer-collections'
import type { Blockstore } from 'interface-blockstore'
import type { AbortOptions } from 'it-length-prefixed-stream'
Expand All @@ -21,6 +21,7 @@ export interface PeerWantListsComponents {
network: Network
libp2p: Libp2p
logger: ComponentLogger
metrics?: Metrics
}

export interface PeerLedger {
Expand Down Expand Up @@ -48,7 +49,7 @@ export class PeerWantLists {

this.ledgerMap = trackedPeerMap({
name: 'helia_bitswap_ledger_map',
metrics: components.libp2p.metrics
metrics: components.metrics
})

this.network.addEventListener('bitswap:message', (evt) => {
Expand Down
11 changes: 6 additions & 5 deletions packages/bitswap/src/stats.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Libp2p, MetricGroup, PeerId } from '@libp2p/interface'
import type { Libp2p, MetricGroup, Metrics, PeerId } from '@libp2p/interface'

export interface StatsComponents {
libp2p: Libp2p
metrics?: Metrics
}

export class Stats {
Expand All @@ -11,10 +12,10 @@ export class Stats {
private readonly duplicateDataReceived?: MetricGroup

constructor (components: StatsComponents) {
this.blocksReceived = components.libp2p.metrics?.registerMetricGroup('helia_bitswap_received_blocks')
this.duplicateBlocksReceived = components.libp2p.metrics?.registerMetricGroup('helia_bitswap_duplicate_received_blocks')
this.dataReceived = components.libp2p.metrics?.registerMetricGroup('helia_bitswap_data_received_bytes')
this.duplicateDataReceived = components.libp2p.metrics?.registerMetricGroup('helia_bitswap_duplicate_data_received_bytes')
this.blocksReceived = components.metrics?.registerMetricGroup('helia_bitswap_received_blocks')
this.duplicateBlocksReceived = components.metrics?.registerMetricGroup('helia_bitswap_duplicate_received_blocks')
this.dataReceived = components.metrics?.registerMetricGroup('helia_bitswap_data_received_bytes')
this.duplicateDataReceived = components.metrics?.registerMetricGroup('helia_bitswap_duplicate_data_received_bytes')
}

updateBlocksReceived (count: number = 1, peerId?: PeerId): void {
Expand Down
7 changes: 4 additions & 3 deletions packages/bitswap/src/want-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import vd from './utils/varint-decoder.js'
import type { BitswapNotifyProgressEvents, MultihashHasherLoader } from './index.js'
import type { BitswapNetworkWantProgressEvents, Network } from './network.js'
import type { BitswapMessage } from './pb/message.js'
import type { ComponentLogger, PeerId, Startable, AbortOptions, Libp2p, TypedEventTarget } from '@libp2p/interface'
import type { ComponentLogger, PeerId, Startable, AbortOptions, Libp2p, TypedEventTarget, Metrics } from '@libp2p/interface'
import type { Logger } from '@libp2p/logger'
import type { PeerMap } from '@libp2p/peer-collections'
import type { DeferredPromise } from 'p-defer'
Expand All @@ -27,6 +27,7 @@ export interface WantListComponents {
network: Network
logger: ComponentLogger
libp2p: Libp2p
metrics?: Metrics
}

export interface WantListInit {
Expand Down Expand Up @@ -114,11 +115,11 @@ export class WantList extends TypedEventEmitter<WantListEvents> implements Start
setMaxListeners(Infinity, this)
this.peers = trackedPeerMap({
name: 'helia_bitswap_peers',
metrics: components.libp2p.metrics
metrics: components.metrics
})
this.wants = trackedMap({
name: 'helia_bitswap_wantlist',
metrics: components.libp2p.metrics
metrics: components.metrics
})
this.network = components.network
this.sendMessagesDelay = init.sendMessagesDelay ?? DEFAULT_MESSAGE_SEND_DELAY
Expand Down
8 changes: 4 additions & 4 deletions packages/bitswap/test/stats.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Libp2p, MetricGroup, Metrics } from '@libp2p/interface'

interface StubbedStatsComponents {
libp2p: StubbedInstance<Libp2p>
metrics: StubbedInstance<Metrics>
}

describe('stats', () => {
Expand All @@ -15,15 +16,14 @@ describe('stats', () => {

beforeEach(() => {
components = {
libp2p: stubInterface<Libp2p>({
metrics: stubInterface<Metrics>()
})
libp2p: stubInterface<Libp2p>(),
metrics: stubInterface<Metrics>()
}

metricGroup = stubInterface<MetricGroup>()

// @ts-expect-error tsc does not select correct method overload sig
components.libp2p.metrics?.registerMetricGroup.returns(metricGroup)
components.metrics?.registerMetricGroup.returns(metricGroup)

stats = new Stats(components)
})
Expand Down
3 changes: 2 additions & 1 deletion packages/helia/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ export async function createHelia (init: Partial<HeliaInit> = {}): Promise<Helia
],
routers: [
libp2pRouting(libp2p)
]
],
metrics: libp2p.metrics
})

if (init.start !== false) {
Expand Down
8 changes: 7 additions & 1 deletion packages/interface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import type { Blocks } from './blocks.js'
import type { Pins } from './pins.js'
import type { Routing } from './routing.js'
import type { AbortOptions, ComponentLogger } from '@libp2p/interface'
import type { AbortOptions, ComponentLogger, Metrics } from '@libp2p/interface'
import type { DNS } from '@multiformats/dns'
import type { Datastore } from 'interface-datastore'
import type { MultihashHasher } from 'multiformats'
Expand Down Expand Up @@ -74,6 +74,12 @@ export interface Helia {
*/
dns: DNS

/**
* A metrics object that can be used to collected arbitrary stats about node
* usage.
*/
metrics?: Metrics

/**
* Starts the Helia node
*/
Expand Down
12 changes: 11 additions & 1 deletion packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { NetworkedStorage } from './utils/networked-storage.js'
import type { DAGWalker, GCOptions, Helia as HeliaInterface, Routing } from '@helia/interface'
import type { BlockBroker } from '@helia/interface/blocks'
import type { Pins } from '@helia/interface/pins'
import type { ComponentLogger, Logger } from '@libp2p/interface'
import type { ComponentLogger, Logger, Metrics } from '@libp2p/interface'
import type { DNS } from '@multiformats/dns'
import type { Blockstore } from 'interface-blockstore'
import type { Datastore } from 'interface-datastore'
Expand Down Expand Up @@ -131,6 +131,12 @@ export interface HeliaInit {
* An optional DNS implementation used to perform queries for DNS records.
*/
dns?: DNS

/**
* A metrics object that can be used to collected arbitrary stats about node
* usage.
*/
metrics?: Metrics
}

interface Components {
Expand All @@ -142,6 +148,7 @@ interface Components {
blockBrokers: BlockBroker[]
routing: Routing
dns: DNS
metrics?: Metrics
}

export class Helia implements HeliaInterface {
Expand All @@ -153,6 +160,7 @@ export class Helia implements HeliaInterface {
public dagWalkers: Record<number, DAGWalker>
public hashers: Record<number, MultihashHasher>
public dns: DNS
public metrics?: Metrics
private readonly log: Logger

constructor (init: HeliaInit) {
Expand All @@ -161,6 +169,7 @@ export class Helia implements HeliaInterface {
this.hashers = defaultHashers(init.hashers)
this.dagWalkers = defaultDagWalkers(init.dagWalkers)
this.dns = init.dns ?? dns()
this.metrics = init.metrics

// @ts-expect-error routing is not set
const components: Components = {
Expand All @@ -171,6 +180,7 @@ export class Helia implements HeliaInterface {
logger: this.logger,
blockBrokers: [],
dns: this.dns,
metrics: this.metrics,
...(init.components ?? {})
}

Expand Down
9 changes: 7 additions & 2 deletions packages/utils/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Sinon from 'sinon'
import { stubInterface } from 'sinon-ts'
import { createHelia } from './fixtures/create-helia.js'
import type { Helia, Routing } from '@helia/interface'
import type { Startable } from '@libp2p/interface'
import type { Startable, Metrics } from '@libp2p/interface'

describe('helia', () => {
let helia: Helia
Expand All @@ -19,7 +19,8 @@ describe('helia', () => {
start: false,
routers: [
routing
]
],
metrics: stubInterface<Metrics>()
})
})

Expand Down Expand Up @@ -49,4 +50,8 @@ describe('helia', () => {
it('should have a datastore', async () => {
expect(helia).to.have.property('datastore').that.is.ok()
})

it('supports metrics', async () => {
expect(helia).to.have.property('metrics').that.is.ok()
})
})

0 comments on commit f7f71bb

Please sign in to comment.