From 4bc0f729001ee46d31f07e667b4ab544bc2c2fcc Mon Sep 17 00:00:00 2001 From: Michael Nutt Date: Mon, 28 Feb 2022 03:43:17 -0500 Subject: [PATCH] feat: expose pool aggregate statistics (#1255) --- docs/api/BalancedPool.md | 4 ++++ docs/api/Pool.md | 4 ++++ docs/api/PoolStats.md | 35 +++++++++++++++++++++++++++++++++++ lib/core/symbols.js | 2 ++ lib/pool-base.js | 19 +++++++++++++++++-- lib/pool-stats.js | 34 ++++++++++++++++++++++++++++++++++ test/pool.js | 13 +++++++++++-- 7 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 docs/api/PoolStats.md create mode 100644 lib/pool-stats.js diff --git a/docs/api/BalancedPool.md b/docs/api/BalancedPool.md index 4580f07efb4..290c734c022 100644 --- a/docs/api/BalancedPool.md +++ b/docs/api/BalancedPool.md @@ -34,6 +34,10 @@ Implements [Client.closed](Client.md#clientclosed) Implements [Client.destroyed](Client.md#clientdestroyed) +### `Pool.stats` + +Returns [`PoolStats`](PoolStats.md) instance for this pool. + ## Instance Methods ### `BalancedPool.addUpstream(upstream)` diff --git a/docs/api/Pool.md b/docs/api/Pool.md index bfa55f630bb..6b08294b61c 100644 --- a/docs/api/Pool.md +++ b/docs/api/Pool.md @@ -30,6 +30,10 @@ Implements [Client.closed](Client.md#clientclosed) Implements [Client.destroyed](Client.md#clientdestroyed) +### `Pool.stats` + +Returns [`PoolStats`](PoolStats.md) instance for this pool. + ## Instance Methods ### `Pool.close([callback])` diff --git a/docs/api/PoolStats.md b/docs/api/PoolStats.md new file mode 100644 index 00000000000..16b6dc25364 --- /dev/null +++ b/docs/api/PoolStats.md @@ -0,0 +1,35 @@ +# Class: PoolStats + +Aggregate stats for a [Pool](Pool.md) or [BalancedPool](BalancedPool.md). + +## `new PoolStats(pool)` + +Arguments: + +* **pool** `Pool` - Pool or BalancedPool from which to return stats. + +## Instance Properties + +### `PoolStats.connected` + +Number of open socket connections in this pool. + +### `PoolStats.free` + +Number of open socket connections in this pool that do not have an active request. + +### `PoolStats.pending` + +Number of pending requests across all clients in this pool. + +### `PoolStats.queued` + +Number of queued requests across all clients in this pool. + +### `PoolStats.running` + +Number of currently active requests across all clients in this pool. + +### `PoolStats.size` + +Number of active, pending, or queued requests across all clients in this pool. diff --git a/lib/core/symbols.js b/lib/core/symbols.js index fd890a790a8..1d28bc15e0c 100644 --- a/lib/core/symbols.js +++ b/lib/core/symbols.js @@ -22,6 +22,8 @@ module.exports = { kPending: Symbol('pending'), kSize: Symbol('size'), kBusy: Symbol('busy'), + kQueued: Symbol('queued'), + kFree: Symbol('free'), kConnected: Symbol('connected'), kClosed: Symbol('closed'), kNeedDrain: Symbol('need drain'), diff --git a/lib/pool-base.js b/lib/pool-base.js index 85b7dd80c6b..8f53b0b36c8 100644 --- a/lib/pool-base.js +++ b/lib/pool-base.js @@ -7,7 +7,8 @@ const { InvalidArgumentError } = require('./core/errors') const FixedQueue = require('./node/fixed-queue') -const { kSize, kRunning, kPending, kBusy, kUrl } = require('./core/symbols') +const { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl } = require('./core/symbols') +const PoolStats = require('./pool-stats') const kClients = Symbol('clients') const kNeedDrain = Symbol('needDrain') @@ -19,10 +20,10 @@ const kOnDrain = Symbol('onDrain') const kOnConnect = Symbol('onConnect') const kOnDisconnect = Symbol('onDisconnect') const kOnConnectionError = Symbol('onConnectionError') -const kQueued = Symbol('queued') const kGetDispatcher = Symbol('get dispatcher') const kAddClient = Symbol('add client') const kRemoveClient = Symbol('remove client') +const kStats = Symbol('stats') class PoolBase extends Dispatcher { constructor () { @@ -77,12 +78,22 @@ class PoolBase extends Dispatcher { this[kOnConnectionError] = (origin, targets, err) => { pool.emit('connectionError', origin, [pool, ...targets], err) } + + this[kStats] = new PoolStats(this) } get [kBusy] () { return this[kNeedDrain] } + get [kConnected] () { + return this[kClients].filter(client => client[kConnected]).length + } + + get [kFree] () { + return this[kClients].filter(client => client[kConnected] && !client[kNeedDrain]).length + } + get [kPending] () { let ret = this[kQueued] for (const { [kPending]: pending } of this[kClients]) { @@ -107,6 +118,10 @@ class PoolBase extends Dispatcher { return ret } + get stats() { + return this[kStats]; + } + get destroyed () { return this[kDestroyed] } diff --git a/lib/pool-stats.js b/lib/pool-stats.js new file mode 100644 index 00000000000..d2d0f868ee2 --- /dev/null +++ b/lib/pool-stats.js @@ -0,0 +1,34 @@ +const { kFree, kConnected, kPending, kQueued, kRunning, kSize } = require('./core/symbols') +const kPool = Symbol('pool') + +class PoolStats { + constructor(pool) { + this[kPool] = pool + } + + get connected() { + return this[kPool][kConnected] + } + + get free() { + return this[kPool][kFree] + } + + get pending() { + return this[kPool][kPending] + } + + get queued() { + return this[kPool][kQueued] + } + + get running() { + return this[kPool][kRunning] + } + + get size() { + return this[kPool][kSize] + } +} + +module.exports = PoolStats diff --git a/test/pool.js b/test/pool.js index 4acaaa91f7c..ec2e019369e 100644 --- a/test/pool.js +++ b/test/pool.js @@ -343,7 +343,7 @@ test('backpressure algorithm', (t) => { }) test('busy', (t) => { - t.plan(8 * 10 + 2 + 1) + t.plan(8 * 16 + 2 + 1) const server = createServer((req, res) => { t.equal('/', req.url) @@ -353,9 +353,11 @@ test('busy', (t) => { }) t.teardown(server.close.bind(server)) + const connections = 2; + server.listen(0, async () => { const client = new Pool(`http://localhost:${server.address().port}`, { - connections: 2, + connections, pipelining: 2 }) client.on('drain', () => { @@ -383,6 +385,13 @@ test('busy', (t) => { t.equal(client[kBusy], n > 1) t.equal(client[kSize], n) t.equal(client[kRunning], 0) + + t.equal(client.stats.connected, 0) + t.equal(client.stats.free, 0) + t.equal(client.stats.queued, Math.max(n - connections, 0)) + t.equal(client.stats.pending, n) + t.equal(client.stats.size, n) + t.equal(client.stats.running, 0) } }) })