Skip to content
This repository has been archived by the owner on Jun 26, 2023. It is now read-only.

Commit

Permalink
feat!: return metrics objects from register instead of updating with …
Browse files Browse the repository at this point in the history
…an options object (#310)

Removes the component update methods in favour of registering metrics once with methods that return objects which can wrap metric gathering implementations.

This lets us (for example) return a wrapped prometheus gauge and avoid concatenating strings, etc when extracting metrics.

Register methods for metrics (prometheus gauges) and counters are present, extra `registerHistogram` etc methods can be added at a later date when required.

## Single metrics

Before:

```js
metrics.updateComponentMetric({
  system: 'libp2p',
  component: 'connection-manager',
  metric: 'incoming-connections',
  value: 5
})
```

After:

```js
const metric = metrics.registerMetric('libp2p_connection_manager_incoming_connections')

// call repeatedly
metric.update(5)

// or
metric.increment()

// or
metric.increment(5)

// or
const stopTimer = metric.timer()
// later
stopTimer()

// or
metric.reset()

// etc
```

## Metric groups

Before:

```js
metrics.updateComponentMetric({
  system: 'libp2p',
  component: 'connection-manager',
  metric: 'connections',
  value: {
    incoming: 5,
    outgoing: 10
  }
})
```

After:

```js
const metric = metrics.registerMetricGroup('libp2p_connection_manager_connections')

// call repeatedly
metric.update({
  incoming: 5,
  outgoing: 10
})
```

## Extracting metrics

This has got much simpler, this example is from an upcoming PR to js-ipfs:

```js
import client from 'prom-client'

// Endpoints for handling debug metrics
export default [{
  method: 'GET',
  path: '/debug/metrics/prometheus',
  /**
   * @param {import('../../types').Request} request
   * @param {import('@hapi/hapi').ResponseToolkit} h
   */
  async handler (request, h) {
    if (!process.env.IPFS_MONITORING) {
      throw Boom.notImplemented('Monitoring is disabled. Enable it by setting environment variable IPFS_MONITORING')
    }

    return h.response(await client.register.metrics())
      .type(client.register.contentType)
  }
}]
```

BREAKING CHANGE: the global/per-peer moving average tracking has been removed from the interface as it's expensive and requires lots of timers - this functionality can be replicated by implementations if it's desirable.  It's better to have simple counters instead and let an external system like Prometheus or Graphana calculate the values over time
  • Loading branch information
achingbrain authored Nov 5, 2022
1 parent 9ce0f8f commit 3b106ce
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 152 deletions.
21 changes: 3 additions & 18 deletions packages/interface-metrics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
## Table of contents <!-- omit in toc -->

- [Install](#install)
- [Using the Test Suite](#using-the-test-suite)
- [Implementations](#implementations)
- [License](#license)
- [Contribution](#contribution)

Expand All @@ -21,24 +21,9 @@
$ npm i @libp2p/interface-metrics
```

## Using the Test Suite
## Implementations

You can also check out the [internal test suite](../../test/crypto/compliance.spec.js) to see the setup in action.

```js
const tests = require('libp2p-interfaces-compliance-tests/keys')
const yourKeys = require('./your-keys')

tests({
setup () {
// Set up your keys if needed, then return it
return yourKeys
},
teardown () {
// Clean up your keys if needed
}
})
```
* [@libp2p/prometheus-metrics](https://github.com/libp2p/js-libp2p-prometheus-metrics)

## License

Expand Down
3 changes: 1 addition & 2 deletions packages/interface-metrics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@
"release": "aegir release"
},
"dependencies": {
"@libp2p/interface-peer-id": "^1.0.0",
"it-stream-types": "^1.0.4"
"@libp2p/interface-connection": "^3.0.0"
},
"devDependencies": {
"aegir": "^37.4.0"
Expand Down
232 changes: 111 additions & 121 deletions packages/interface-metrics/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,197 +1,187 @@
import type { PeerId } from '@libp2p/interface-peer-id'
import type { Duplex } from 'it-stream-types'

export interface MetricsInit {
enabled: boolean
computeThrottleMaxQueueSize: number
computeThrottleTimeout: number
movingAverageIntervals: number[]
maxOldPeersRetention: number
}

export interface MovingAverage {
variance: number
movingAverage: number
deviation: number
forecast: number

push: (time: number, value: number) => void
}

export interface MovingAverages {
dataReceived: MovingAverage[]
dataSent: MovingAverage[]
}

export interface TransferStats {
dataReceived: bigint
dataSent: bigint
}
import type { MultiaddrConnection, Stream, Connection } from '@libp2p/interface-connection'

export interface Stats {
/**
* Create tracked metrics with these options. Loosely based on the
* interfaces exposed by the prom-client module
*/
export interface MetricOptions {
/**
* Returns a clone of the current stats.
* Optional label for the metric
*/
getSnapshot: () => TransferStats
label?: string

/**
* Returns a clone of the internal movingAverages
* Optional help for the metric
*/
getMovingAverages: () => MovingAverages
help?: string
}

/**
* A function that returns a tracked metric which may be expensive
* to calculate so it is only invoked when metrics are being scraped
*/
export type CalculateMetric<T = number | bigint> = (() => T) | (() => Promise<T>)

/**
* Create tracked metrics that are expensive to calculate by passing
* a function that is only invoked when metrics are being scraped
*/
export interface CalculatedMetricOptions<T = number | bigint> extends MetricOptions {
/**
* Pushes the given operation data to the queue, along with the
* current Timestamp, then resets the update timer.
* An optional function invoked to calculate the component metric instead of
* using `.update`, `.increment`, and `.decrement`
*/
push: (counter: string, inc: number) => void
calculate: CalculateMetric<T>
}

export interface TrackStreamOptions {
/**
* A duplex iterable stream
*/
stream: Duplex<{ byteLength: number }, any>
/**
* Call this function to stop the timer returned from the `.timer` method
* on the metric
*/
export interface StopTimer { (): void }

/**
* A tracked metric loosely based on the interfaces exposed by the
* prom-client module
*/
export interface Metric {
/**
* The id of the remote peer that's connected
* Update the stored metric to the passed value
*/
remotePeer: PeerId
update: (value: number | bigint) => void

/**
* The protocol the stream is running
* Increment the metric by the passed value or 1
*/
protocol?: string
}
increment: (value?: number | bigint) => void

export interface StreamMetrics {
/**
* Returns the global `Stats` object
* Decrement the metric by the passed value or 1
*/
getGlobal: () => Stats
decrement: (value?: number | bigint) => void

/**
* Returns a list of `PeerId` strings currently being tracked
* Reset this metric to its default value
*/
getPeers: () => string[]
reset: () => void

/**
* Returns the `Stats` object for the given `PeerId` whether it
* is a live peer, or in the disconnected peer LRU cache.
* Start a timed metric, call the returned function to
* stop the timer
*/
forPeer: (peerId: PeerId) => Stats | undefined
timer: () => StopTimer
}

/**
* A group of related metrics loosely based on the interfaces exposed by the
* prom-client module
*/
export interface MetricGroup {
/**
* Returns a list of all protocol strings currently being tracked.
* Update the stored metric group to the passed value
*/
getProtocols: () => string[]
update: (values: Record<string, number | bigint>) => void

/**
* Returns the `Stats` object for the given `protocol`
* Increment the metric group keys by the passed number or
* any non-numeric value to increment by 1
*/
forProtocol: (protocol: string) => Stats | undefined
increment: (values: Record<string, number | bigint | unknown>) => void

/**
* Should be called when all connections to a given peer
* have closed. The `Stats` collection for the peer will
* be stopped and moved to an LRU for temporary retention.
* Decrement the metric group keys by the passed number or
* any non-numeric value to decrement by 1
*/
onPeerDisconnected: (peerId: PeerId) => void
decrement: (values: Record<string, number | bigint | unknown>) => void

/**
* Replaces the `PeerId` string with the given `peerId`.
* If stats are already being tracked for the given `peerId`, the
* placeholder stats will be merged with the existing stats.
* Reset the passed key in this metric group to its default value
* or all keys if no key is passed
*/
updatePlaceholder: (placeholder: PeerId, peerId: PeerId) => void
reset: () => void

/**
* Tracks data running through a given Duplex Iterable `stream`. If
* the `peerId` is not provided, a placeholder string will be created and
* returned. This allows lazy tracking of a peer when the peer is not yet known.
* When the `PeerId` is known, `Metrics.updatePlaceholder` should be called
* with the placeholder string returned from here, and the known `PeerId`.
* Start a timed metric for the named key in the group, call
* the returned function to stop the timer
*/
trackStream: (data: TrackStreamOptions) => void
timer: (key: string) => StopTimer
}

/**
* Used to update a tracked metric. Value can either be a number, an object containing
* key/value pairs or an (optionally async) function to return a number or an object of
* key/value pairs.
* A tracked counter loosely based on the Counter interface exposed
* by the prom-client module - counters are metrics that only go up
*/
export interface ComponentMetricsUpdate {
/**
* Name of the system, e.g. libp2p, ipfs, etc
*/
system: string

/**
* Name of the system component that contains the metric
*/
component: string

export interface Counter {
/**
* Name of the metric being tracked
* Increment the metric by the passed value or 1
*/
metric: string
increment: (value?: number | bigint) => void

/**
* The value or function to calculate the value
* Reset this metric to its default value
*/
value: ComponentMetric | CalculateComponentMetric
reset: () => void
}

/**
* A group of tracked counters loosely based on the Counter interface
* exposed by the prom-client module - counters are metrics that only
* go up
*/
export interface CounterGroup {
/**
* Optional label for the metric
* Increment the metric group keys by the passed number or
* any non-numeric value to increment by 1
*/
label?: string
increment: (values: Record<string, number | bigint | unknown>) => void

/**
* Optional help for the metric
* Reset the passed key in this metric group to its default value
* or all keys if no key is passed
*/
help?: string
reset: () => void
}

export type ComponentMetric = number | ComponentMetricsGroup

/**
* Used to group related metrics together by label and value
* The libp2p metrics tracking object. This interface is only concerned
* with the collection of metrics, please see the individual implementations
* for how to extract metrics for viewing.
*/
export type ComponentMetricsGroup = Record<string, number>

/**
* Used to calculate metric values dynamically
*/
export interface CalculateComponentMetric { (): Promise<ComponentMetric> | ComponentMetric }

export interface TrackedMetric {
export interface Metrics {
/**
* In systems that support them, this label can help make graphs more interpretable
* Track a newly opened multiaddr connection
*/
label?: string
trackMultiaddrConnection: (maConn: MultiaddrConnection) => void

/**
* In systems that support them, this help text can help make graphs more interpretable
* Track a newly opened protocol stream
*/
help?: string
trackProtocolStream: (stream: Stream, connection: Connection) => void

/**
* A function that returns a value or a group of values
* Register an arbitrary metric. Call this to set help/labels for metrics
* and update/increment/decrement/etc them by calling methods on the returned
* metric object
*/
calculate: CalculateComponentMetric
}
registerMetric: ((name: string, options?: MetricOptions) => Metric) & ((name: string, options: CalculatedMetricOptions) => void)

export interface ComponentMetricsTracker {
/**
* Returns tracked metrics key by system, component, metric, value
* Register a a group of related metrics. Call this to set help/labels for
* groups of related metrics that will be updated with by calling `.update`,
* `.increment` and/or `.decrement` methods on the returned metric group object
*/
getComponentMetrics: () => Map<string, Map<string, Map<string, TrackedMetric>>>
registerMetricGroup: ((name: string, options?: MetricOptions) => MetricGroup) & ((name: string, options: CalculatedMetricOptions<Record<string, number | bigint>>) => void)

/**
* Update the stored metric value for the given system and component
* Register an arbitrary counter. Call this to set help/labels for counters
* and increment them by calling methods on the returned counter object
*/
updateComponentMetric: (data: ComponentMetricsUpdate) => void
}

export interface Metrics extends StreamMetrics, ComponentMetricsTracker {
registerCounter: ((name: string, options?: MetricOptions) => Counter) & ((name: string, options: CalculatedMetricOptions) => void)

/**
* Register a a group of related counters. Call this to set help/labels for
* groups of related counters that will be updated with by calling the `.increment`
* method on the returned counter group object
*/
registerCounterGroup: ((name: string, options?: MetricOptions) => CounterGroup) & ((name: string, options: CalculatedMetricOptions<Record<string, number | bigint>>) => void)
}
1 change: 1 addition & 0 deletions packages/interface-mocks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
"@libp2p/interface-connection": "^3.0.0",
"@libp2p/interface-connection-encrypter": "^3.0.0",
"@libp2p/interface-connection-manager": "^1.0.0",
"@libp2p/interface-metrics": "^3.0.0",
"@libp2p/interface-peer-discovery": "^1.0.0",
"@libp2p/interface-peer-id": "^1.0.0",
"@libp2p/interface-peer-info": "^1.0.0",
Expand Down
27 changes: 16 additions & 11 deletions packages/interface-mocks/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,19 +193,24 @@ export interface Peer {
registrar: Registrar
}

export function connectionPair (a: { peerId: PeerId, registrar: Registrar }, b: { peerId: PeerId, registrar: Registrar }): [ Connection, Connection ] {
export function multiaddrConnectionPair (a: { peerId: PeerId, registrar: Registrar }, b: { peerId: PeerId, registrar: Registrar }): [ MultiaddrConnection, MultiaddrConnection ] {
const [peerBtoPeerA, peerAtoPeerB] = duplexPair<Uint8Array>()

return [
mockConnection(
mockMultiaddrConnection(peerAtoPeerB, b.peerId), {
registrar: a.registrar
}
),
mockConnection(
mockMultiaddrConnection(peerBtoPeerA, a.peerId), {
registrar: b.registrar
}
)
mockMultiaddrConnection(peerAtoPeerB, b.peerId),
mockMultiaddrConnection(peerBtoPeerA, a.peerId)
]
}

export function connectionPair (a: { peerId: PeerId, registrar: Registrar }, b: { peerId: PeerId, registrar: Registrar }): [ Connection, Connection ] {
const [peerBtoPeerA, peerAtoPeerB] = multiaddrConnectionPair(a, b)

return [
mockConnection(peerBtoPeerA, {
registrar: a.registrar
}),
mockConnection(peerAtoPeerB, {
registrar: b.registrar
})
]
}
Loading

0 comments on commit 3b106ce

Please sign in to comment.