@rudderjs/pulse 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/boost/guidelines.md +33 -0
- package/dist/aggregators/cache.d.ts +11 -0
- package/dist/aggregators/cache.d.ts.map +1 -0
- package/dist/aggregators/cache.js +34 -0
- package/dist/aggregators/cache.js.map +1 -0
- package/dist/aggregators/exception.d.ts +11 -0
- package/dist/aggregators/exception.d.ts.map +1 -0
- package/dist/aggregators/exception.js +30 -0
- package/dist/aggregators/exception.js.map +1 -0
- package/dist/aggregators/query.d.ts +12 -0
- package/dist/aggregators/query.d.ts.map +1 -0
- package/dist/aggregators/query.js +39 -0
- package/dist/aggregators/query.js.map +1 -0
- package/dist/aggregators/queue.d.ts +12 -0
- package/dist/aggregators/queue.d.ts.map +1 -0
- package/dist/aggregators/queue.js +43 -0
- package/dist/aggregators/queue.js.map +1 -0
- package/dist/aggregators/request.d.ts +15 -0
- package/dist/aggregators/request.d.ts.map +1 -0
- package/dist/aggregators/request.js +46 -0
- package/dist/aggregators/request.js.map +1 -0
- package/dist/aggregators/server.d.ts +13 -0
- package/dist/aggregators/server.d.ts.map +1 -0
- package/dist/aggregators/server.js +35 -0
- package/dist/aggregators/server.js.map +1 -0
- package/dist/aggregators/user.d.ts +17 -0
- package/dist/aggregators/user.d.ts.map +1 -0
- package/dist/aggregators/user.js +48 -0
- package/dist/aggregators/user.js.map +1 -0
- package/dist/api/routes.d.ts +6 -0
- package/dist/api/routes.d.ts.map +1 -0
- package/dist/api/routes.js +167 -0
- package/dist/api/routes.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -0
- package/dist/storage.d.ts +28 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +219 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +77 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/dashboard.d.ts +5 -0
- package/dist/ui/dashboard.d.ts.map +1 -0
- package/dist/ui/dashboard.js +234 -0
- package/dist/ui/dashboard.js.map +1 -0
- package/dist/ui/layout.d.ts +6 -0
- package/dist/ui/layout.d.ts.map +1 -0
- package/dist/ui/layout.js +54 -0
- package/dist/ui/layout.js.map +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +19 -0
- package/dist/utils.js.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Suleiman Shahbari
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# @rudderjs/pulse
|
|
2
|
+
|
|
3
|
+
Application performance monitoring for RudderJS — aggregates request throughput, queue metrics, cache hit rates, exceptions, active users, slow queries, and server resource usage.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @rudderjs/pulse
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// bootstrap/providers.ts
|
|
15
|
+
import { pulse } from '@rudderjs/pulse'
|
|
16
|
+
import configs from '../config/index.js'
|
|
17
|
+
export default [..., pulse(configs.pulse), ...]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Pulse Facade
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { Pulse } from '@rudderjs/pulse'
|
|
24
|
+
|
|
25
|
+
// Record a metric
|
|
26
|
+
Pulse.record('request_duration', 142)
|
|
27
|
+
Pulse.record('cache_hits', 1, 'user-cache')
|
|
28
|
+
|
|
29
|
+
// Query aggregates
|
|
30
|
+
const since = new Date(Date.now() - 3600_000) // last hour
|
|
31
|
+
const requestMetrics = await Pulse.aggregates('request_count', since)
|
|
32
|
+
const cacheMetrics = await Pulse.aggregates('cache_hits', since, 'user-cache')
|
|
33
|
+
|
|
34
|
+
// Get entries (slow requests, exceptions, etc.)
|
|
35
|
+
const slowRequests = await Pulse.entries('slow_request', { perPage: 25 })
|
|
36
|
+
|
|
37
|
+
// Overview of all metrics
|
|
38
|
+
const overview = await Pulse.overview(since)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## `Pulse` Methods
|
|
42
|
+
|
|
43
|
+
| Method | Returns | Description |
|
|
44
|
+
|--------|---------|-------------|
|
|
45
|
+
| `record(type, value, key?)` | `void` | Increment an aggregate bucket |
|
|
46
|
+
| `aggregates(type, since, key?)` | `PulseAggregate[]` | Aggregates for a metric type within a period |
|
|
47
|
+
| `entries(type, options?)` | `PulseEntry[]` | Individual entries (slow requests, exceptions) |
|
|
48
|
+
| `overview(since)` | `PulseAggregate[]` | All aggregates since a given date |
|
|
49
|
+
|
|
50
|
+
## Metric Types
|
|
51
|
+
|
|
52
|
+
| Type | Source |
|
|
53
|
+
|------|--------|
|
|
54
|
+
| `request_count` | Request middleware |
|
|
55
|
+
| `request_duration` | Request middleware |
|
|
56
|
+
| `queue_throughput` | Queue aggregator |
|
|
57
|
+
| `queue_wait_time` | Queue aggregator |
|
|
58
|
+
| `cache_hits` / `cache_misses` | Cache aggregator |
|
|
59
|
+
| `exceptions` | Exception aggregator |
|
|
60
|
+
| `active_users` | User aggregator middleware |
|
|
61
|
+
| `server_cpu` / `server_memory` | Server aggregator (periodic) |
|
|
62
|
+
|
|
63
|
+
## Storage Drivers
|
|
64
|
+
|
|
65
|
+
- **`memory`** (default) — In-process, capped at `maxEntries`. Good for development.
|
|
66
|
+
- **`sqlite`** — Persistent storage via `better-sqlite3`. Run `pnpm add better-sqlite3` to enable.
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// config/pulse.ts
|
|
72
|
+
export default {
|
|
73
|
+
enabled: true,
|
|
74
|
+
path: 'pulse',
|
|
75
|
+
storage: 'memory',
|
|
76
|
+
sqlitePath: '.pulse.db',
|
|
77
|
+
pruneAfterHours: 168, // 7 days
|
|
78
|
+
slowRequestThreshold: 1000, // ms
|
|
79
|
+
slowQueryThreshold: 100, // ms
|
|
80
|
+
recordRequests: true,
|
|
81
|
+
recordQueues: true,
|
|
82
|
+
recordCache: true,
|
|
83
|
+
recordExceptions: true,
|
|
84
|
+
recordUsers: true,
|
|
85
|
+
recordServers: true,
|
|
86
|
+
serverStatsIntervalMs: 15_000,
|
|
87
|
+
auth: null,
|
|
88
|
+
} satisfies PulseConfig
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Aggregators
|
|
92
|
+
|
|
93
|
+
Pulse auto-registers aggregators based on config:
|
|
94
|
+
|
|
95
|
+
- **RequestAggregator** — Tracks request throughput and duration via middleware
|
|
96
|
+
- **QueueAggregator** — Tracks queue throughput and wait times
|
|
97
|
+
- **CacheAggregator** — Tracks cache hit/miss rates
|
|
98
|
+
- **ExceptionAggregator** — Counts unhandled exceptions
|
|
99
|
+
- **QueryAggregator** — Records slow database queries
|
|
100
|
+
- **UserAggregator** — Tracks unique active users via middleware
|
|
101
|
+
- **ServerAggregator** — Periodically records CPU and memory usage
|
|
102
|
+
|
|
103
|
+
## Notes
|
|
104
|
+
|
|
105
|
+
- Auto-prune runs on a background interval.
|
|
106
|
+
- Optional peers: `@rudderjs/log`, `@rudderjs/orm`, `@rudderjs/cache`, `@rudderjs/queue`.
|
|
107
|
+
- Dashboard served at `/{path}` with auto-refreshing metric cards.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @rudderjs/pulse — AI Coding Guidelines
|
|
2
|
+
|
|
3
|
+
## What This Package Does
|
|
4
|
+
|
|
5
|
+
Pulse is an application metrics dashboard for RudderJS applications. It aggregates time-series data (request throughput, cache hit rates, queue metrics, server stats) into 1-minute buckets and exposes a JSON API for visualization.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
Two data models:
|
|
10
|
+
1. **Aggregates** — Time-bucketed metrics (1-minute resolution). Used for throughput, duration, hit rates.
|
|
11
|
+
2. **Entries** — Individual notable events (slow requests, slow queries, exceptions, failed jobs).
|
|
12
|
+
|
|
13
|
+
Seven aggregators collect data:
|
|
14
|
+
- `RequestAggregator` — middleware: request count + duration
|
|
15
|
+
- `QueueAggregator` — wraps adapter: throughput + wait time + failures
|
|
16
|
+
- `CacheAggregator` — wraps adapter: hit/miss counting
|
|
17
|
+
- `ExceptionAggregator` — exception reporter hook
|
|
18
|
+
- `UserAggregator` — middleware: unique users per minute
|
|
19
|
+
- `QueryAggregator` — ORM hook: slow query detection
|
|
20
|
+
- `ServerAggregator` — periodic: CPU + memory
|
|
21
|
+
|
|
22
|
+
## Key Patterns
|
|
23
|
+
|
|
24
|
+
- Aggregators implement the `Aggregator` interface with `register()` method
|
|
25
|
+
- `storage.record(type, value, key?)` increments a 1-minute bucket aggregate
|
|
26
|
+
- `storage.storeEntry(type, content)` records individual notable events
|
|
27
|
+
- API endpoints support `?period=1h|6h|24h|7d` for time range selection
|
|
28
|
+
|
|
29
|
+
## Do NOT
|
|
30
|
+
|
|
31
|
+
- Import peer dependencies statically — always use dynamic `import()` with try/catch
|
|
32
|
+
- Record Pulse's own API requests (request aggregator skips `/pulse*`)
|
|
33
|
+
- Store high-cardinality keys in aggregates — keep `key` to queue names, route patterns, not full URLs
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Aggregator, PulseStorage } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Tracks cache hit/miss ratio by wrapping the CacheRegistry adapter.
|
|
4
|
+
*/
|
|
5
|
+
export declare class CacheAggregator implements Aggregator {
|
|
6
|
+
private readonly storage;
|
|
7
|
+
readonly name = "Cache Aggregator";
|
|
8
|
+
constructor(storage: PulseStorage);
|
|
9
|
+
register(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/aggregators/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE3D;;GAEG;AACH,qBAAa,eAAgB,YAAW,UAAU;IAGpC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,IAAI,sBAAqB;gBAEL,OAAO,EAAE,YAAY;IAE5C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAsBhC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks cache hit/miss ratio by wrapping the CacheRegistry adapter.
|
|
3
|
+
*/
|
|
4
|
+
export class CacheAggregator {
|
|
5
|
+
storage;
|
|
6
|
+
name = 'Cache Aggregator';
|
|
7
|
+
constructor(storage) {
|
|
8
|
+
this.storage = storage;
|
|
9
|
+
}
|
|
10
|
+
async register() {
|
|
11
|
+
try {
|
|
12
|
+
const { CacheRegistry } = await import('@rudderjs/cache');
|
|
13
|
+
const adapter = CacheRegistry.get();
|
|
14
|
+
if (!adapter)
|
|
15
|
+
return;
|
|
16
|
+
const storage = this.storage;
|
|
17
|
+
const origGet = adapter.get.bind(adapter);
|
|
18
|
+
adapter['get'] = async (key) => {
|
|
19
|
+
const value = await origGet(key);
|
|
20
|
+
if (value !== null) {
|
|
21
|
+
storage.record('cache_hits', 1);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
storage.record('cache_misses', 1);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// @rudderjs/cache not installed — skip
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/aggregators/cache.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,OAAO,eAAe;IAGG;IAFpB,IAAI,GAAG,kBAAkB,CAAA;IAElC,YAA6B,OAAqB;QAArB,YAAO,GAAP,OAAO,CAAc;IAAG,CAAC;IAEtD,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC;YACH,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAA;YACzD,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,EAAE,CAAA;YACnC,IAAI,CAAC,OAAO;gBAAE,OAAM;YAEpB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;YAC5B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAExC;YAAC,OAA8C,CAAC,KAAK,CAAC,GAAG,KAAK,EAAe,GAAW,EAAqB,EAAE;gBAC9G,MAAM,KAAK,GAAG,MAAO,OAA8C,CAAC,GAAG,CAAC,CAAA;gBACxE,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBACnB,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;gBACjC,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAA;gBACnC,CAAC;gBACD,OAAO,KAAK,CAAA;YACd,CAAC,CAAA;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Aggregator, PulseStorage } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Tracks exception count over time by hooking into the exception reporter.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ExceptionAggregator implements Aggregator {
|
|
6
|
+
private readonly storage;
|
|
7
|
+
readonly name = "Exception Aggregator";
|
|
8
|
+
constructor(storage: PulseStorage);
|
|
9
|
+
register(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=exception.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exception.d.ts","sourceRoot":"","sources":["../../src/aggregators/exception.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE3D;;GAEG;AACH,qBAAa,mBAAoB,YAAW,UAAU;IAGxC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,IAAI,0BAAyB;gBAET,OAAO,EAAE,YAAY;IAE5C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAuBhC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks exception count over time by hooking into the exception reporter.
|
|
3
|
+
*/
|
|
4
|
+
export class ExceptionAggregator {
|
|
5
|
+
storage;
|
|
6
|
+
name = 'Exception Aggregator';
|
|
7
|
+
constructor(storage) {
|
|
8
|
+
this.storage = storage;
|
|
9
|
+
}
|
|
10
|
+
async register() {
|
|
11
|
+
try {
|
|
12
|
+
const { setExceptionReporter, report } = await import('@rudderjs/core');
|
|
13
|
+
const storage = this.storage;
|
|
14
|
+
const previousReport = report;
|
|
15
|
+
setExceptionReporter((err) => {
|
|
16
|
+
storage.record('exceptions', 1);
|
|
17
|
+
const isError = err instanceof Error;
|
|
18
|
+
storage.storeEntry('exception', {
|
|
19
|
+
class: isError ? err.constructor.name : 'Unknown',
|
|
20
|
+
message: isError ? err.message : String(err),
|
|
21
|
+
});
|
|
22
|
+
previousReport(err);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Should not fail
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=exception.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exception.js","sourceRoot":"","sources":["../../src/aggregators/exception.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,OAAO,mBAAmB;IAGD;IAFpB,IAAI,GAAG,sBAAsB,CAAA;IAEtC,YAA6B,OAAqB;QAArB,YAAO,GAAP,OAAO,CAAc;IAAG,CAAC;IAEtD,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC;YACH,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAGrE,CAAA;YAED,MAAM,OAAO,GAAU,IAAI,CAAC,OAAO,CAAA;YACnC,MAAM,cAAc,GAAG,MAAM,CAAA;YAE7B,oBAAoB,CAAC,CAAC,GAAY,EAAE,EAAE;gBACpC,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;gBAC/B,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAA;gBACpC,OAAO,CAAC,UAAU,CAAC,WAAW,EAAE;oBAC9B,KAAK,EAAI,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;oBACnD,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBAC7C,CAAC,CAAA;gBACF,cAAc,CAAC,GAAG,CAAC,CAAA;YACrB,CAAC,CAAC,CAAA;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Aggregator, PulseStorage, PulseConfig } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Tracks slow database queries by hooking into the ORM adapter.
|
|
4
|
+
*/
|
|
5
|
+
export declare class QueryAggregator implements Aggregator {
|
|
6
|
+
private readonly storage;
|
|
7
|
+
private readonly config;
|
|
8
|
+
readonly name = "Query Aggregator";
|
|
9
|
+
constructor(storage: PulseStorage, config: PulseConfig);
|
|
10
|
+
register(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/aggregators/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAExE;;GAEG;AACH,qBAAa,eAAgB,YAAW,UAAU;IAI9C,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJzB,QAAQ,CAAC,IAAI,sBAAqB;gBAGf,OAAO,EAAE,YAAY,EACrB,MAAM,EAAG,WAAW;IAGjC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CA4BhC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks slow database queries by hooking into the ORM adapter.
|
|
3
|
+
*/
|
|
4
|
+
export class QueryAggregator {
|
|
5
|
+
storage;
|
|
6
|
+
config;
|
|
7
|
+
name = 'Query Aggregator';
|
|
8
|
+
constructor(storage, config) {
|
|
9
|
+
this.storage = storage;
|
|
10
|
+
this.config = config;
|
|
11
|
+
}
|
|
12
|
+
async register() {
|
|
13
|
+
try {
|
|
14
|
+
const orm = await import('@rudderjs/orm');
|
|
15
|
+
const registry = orm.ModelRegistry;
|
|
16
|
+
if (!registry.getAdapter)
|
|
17
|
+
return;
|
|
18
|
+
const adapter = registry.getAdapter();
|
|
19
|
+
if (!adapter?.onQuery)
|
|
20
|
+
return;
|
|
21
|
+
const storage = this.storage;
|
|
22
|
+
const threshold = this.config.slowQueryThreshold ?? 100;
|
|
23
|
+
adapter.onQuery((info) => {
|
|
24
|
+
if (info.duration > threshold) {
|
|
25
|
+
storage.storeEntry('slow_query', {
|
|
26
|
+
sql: info.sql,
|
|
27
|
+
bindings: info.bindings,
|
|
28
|
+
duration: info.duration,
|
|
29
|
+
model: info.model,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// @rudderjs/orm not installed — skip
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/aggregators/query.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,OAAO,eAAe;IAIP;IACA;IAJV,IAAI,GAAG,kBAAkB,CAAA;IAElC,YACmB,OAAqB,EACrB,MAAoB;QADpB,YAAO,GAAP,OAAO,CAAc;QACrB,WAAM,GAAN,MAAM,CAAc;IACpC,CAAC;IAEJ,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;YACzC,MAAM,QAAQ,GAAG,GAAG,CAAC,aAEpB,CAAA;YACD,IAAI,CAAC,QAAQ,CAAC,UAAU;gBAAE,OAAM;YAEhC,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAA;YACrC,IAAI,CAAC,OAAO,EAAE,OAAO;gBAAE,OAAM;YAE7B,MAAM,OAAO,GAAK,IAAI,CAAC,OAAO,CAAA;YAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,GAAG,CAAA;YAEvD,OAAO,CAAC,OAAO,CAAC,CAAC,IAAe,EAAE,EAAE;gBAClC,IAAI,IAAI,CAAC,QAAQ,GAAG,SAAS,EAAE,CAAC;oBAC9B,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE;wBAC/B,GAAG,EAAO,IAAI,CAAC,GAAG;wBAClB,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,KAAK,EAAK,IAAI,CAAC,KAAK;qBACrB,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,qCAAqC;QACvC,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Aggregator, PulseStorage } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Tracks queue throughput, wait time, and failed jobs.
|
|
4
|
+
* Wraps the QueueRegistry adapter's dispatch method.
|
|
5
|
+
*/
|
|
6
|
+
export declare class QueueAggregator implements Aggregator {
|
|
7
|
+
private readonly storage;
|
|
8
|
+
readonly name = "Queue Aggregator";
|
|
9
|
+
constructor(storage: PulseStorage);
|
|
10
|
+
register(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/aggregators/queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE3D;;;GAGG;AACH,qBAAa,eAAgB,YAAW,UAAU;IAGpC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,IAAI,sBAAqB;gBAEL,OAAO,EAAE,YAAY;IAE5C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAiChC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks queue throughput, wait time, and failed jobs.
|
|
3
|
+
* Wraps the QueueRegistry adapter's dispatch method.
|
|
4
|
+
*/
|
|
5
|
+
export class QueueAggregator {
|
|
6
|
+
storage;
|
|
7
|
+
name = 'Queue Aggregator';
|
|
8
|
+
constructor(storage) {
|
|
9
|
+
this.storage = storage;
|
|
10
|
+
}
|
|
11
|
+
async register() {
|
|
12
|
+
try {
|
|
13
|
+
const { QueueRegistry } = await import('@rudderjs/queue');
|
|
14
|
+
const adapter = QueueRegistry.get();
|
|
15
|
+
if (!adapter)
|
|
16
|
+
return;
|
|
17
|
+
const storage = this.storage;
|
|
18
|
+
const originalDispatch = adapter.dispatch.bind(adapter);
|
|
19
|
+
adapter['dispatch'] = async (job, options) => {
|
|
20
|
+
const dispatchedAt = Date.now();
|
|
21
|
+
try {
|
|
22
|
+
await originalDispatch(job, options);
|
|
23
|
+
const duration = Date.now() - dispatchedAt;
|
|
24
|
+
storage.record('queue_throughput', 1);
|
|
25
|
+
storage.record('queue_wait_time', duration);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
storage.record('queue_throughput', 1);
|
|
29
|
+
const j = job;
|
|
30
|
+
storage.storeEntry('failed_job', {
|
|
31
|
+
class: j.constructor.name,
|
|
32
|
+
exception: err instanceof Error ? err.message : String(err),
|
|
33
|
+
});
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// @rudderjs/queue not installed — skip
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/aggregators/queue.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,eAAe;IAGG;IAFpB,IAAI,GAAG,kBAAkB,CAAA;IAElC,YAA6B,OAAqB;QAArB,YAAO,GAAP,OAAO,CAAc;IAAG,CAAC;IAEtD,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC;YACH,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAA;YACzD,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,EAAE,CAAA;YACnC,IAAI,CAAC,OAAO;gBAAE,OAAM;YAEpB,MAAM,OAAO,GAAY,IAAI,CAAC,OAAO,CAAA;YACrC,MAAM,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAEtD;YAAC,OAA8C,CAAC,UAAU,CAAC,GAAG,KAAK,EAClE,GAAY,EACZ,OAAiB,EACF,EAAE;gBACjB,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;gBAC/B,IAAI,CAAC;oBACH,MAAO,gBAA0D,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;oBAC/E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAA;oBAC1C,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAA;oBACrC,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAA;gBAC7C,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAA;oBACrC,MAAM,CAAC,GAAG,GAAwC,CAAA;oBAClD,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE;wBAC/B,KAAK,EAAM,CAAC,CAAC,WAAW,CAAC,IAAI;wBAC7B,SAAS,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;qBAC5D,CAAC,CAAA;oBACF,MAAM,GAAG,CAAA;gBACX,CAAC;YACH,CAAC,CAAA;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AppRequest, AppResponse } from '@rudderjs/contracts';
|
|
2
|
+
import type { Aggregator, PulseStorage, PulseConfig } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Tracks request throughput, duration (p50/p95/p99), and slow requests.
|
|
5
|
+
* Installs as global middleware.
|
|
6
|
+
*/
|
|
7
|
+
export declare class RequestAggregator implements Aggregator {
|
|
8
|
+
private readonly storage;
|
|
9
|
+
private readonly config;
|
|
10
|
+
readonly name = "Request Aggregator";
|
|
11
|
+
constructor(storage: PulseStorage, config: PulseConfig);
|
|
12
|
+
register(): void;
|
|
13
|
+
middleware(): (req: AppRequest, res: AppResponse, next: () => Promise<void>) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/aggregators/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAGxE;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,UAAU;IAIhD,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJzB,QAAQ,CAAC,IAAI,wBAAuB;gBAGjB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAG,WAAW;IAGvC,QAAQ,IAAI,IAAI;IAIhB,UAAU,KAKM,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,MAAM,OAAO,CAAC,IAAI,CAAC;CAyB7E"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { isAsset } from '../utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Tracks request throughput, duration (p50/p95/p99), and slow requests.
|
|
4
|
+
* Installs as global middleware.
|
|
5
|
+
*/
|
|
6
|
+
export class RequestAggregator {
|
|
7
|
+
storage;
|
|
8
|
+
config;
|
|
9
|
+
name = 'Request Aggregator';
|
|
10
|
+
constructor(storage, config) {
|
|
11
|
+
this.storage = storage;
|
|
12
|
+
this.config = config;
|
|
13
|
+
}
|
|
14
|
+
register() {
|
|
15
|
+
// Middleware is registered by the service provider
|
|
16
|
+
}
|
|
17
|
+
middleware() {
|
|
18
|
+
const storage = this.storage;
|
|
19
|
+
const threshold = this.config.slowRequestThreshold ?? 1000;
|
|
20
|
+
const ignore = this.config.path ?? 'pulse';
|
|
21
|
+
return async (req, res, next) => {
|
|
22
|
+
// Skip pulse's own routes
|
|
23
|
+
if (req.path.startsWith(`/${ignore}`))
|
|
24
|
+
return next();
|
|
25
|
+
// Skip Vite internals, source files, and static assets
|
|
26
|
+
if (isAsset(req.path))
|
|
27
|
+
return next();
|
|
28
|
+
const start = Date.now();
|
|
29
|
+
await next();
|
|
30
|
+
const duration = Date.now() - start;
|
|
31
|
+
// Record aggregates
|
|
32
|
+
storage.record('request_count', 1);
|
|
33
|
+
storage.record('request_duration', duration);
|
|
34
|
+
// Slow request entry
|
|
35
|
+
if (duration > threshold) {
|
|
36
|
+
storage.storeEntry('slow_request', {
|
|
37
|
+
method: req.method,
|
|
38
|
+
url: req.url,
|
|
39
|
+
path: req.path,
|
|
40
|
+
duration,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/aggregators/request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,MAAM,OAAO,iBAAiB;IAIT;IACA;IAJV,IAAI,GAAG,oBAAoB,CAAA;IAEpC,YACmB,OAAqB,EACrB,MAAoB;QADpB,YAAO,GAAP,OAAO,CAAc;QACrB,WAAM,GAAN,MAAM,CAAc;IACpC,CAAC;IAEJ,QAAQ;QACN,mDAAmD;IACrD,CAAC;IAED,UAAU;QACR,MAAM,OAAO,GAAK,IAAI,CAAC,OAAO,CAAA;QAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,oBAAoB,IAAI,IAAI,CAAA;QAC1D,MAAM,MAAM,GAAM,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,CAAA;QAE7C,OAAO,KAAK,EAAE,GAAe,EAAE,GAAgB,EAAE,IAAyB,EAAE,EAAE;YAC5E,0BAA0B;YAC1B,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,MAAM,EAAE,CAAC;gBAAE,OAAO,IAAI,EAAE,CAAA;YACpD,uDAAuD;YACvD,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,EAAE,CAAA;YAEpC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACxB,MAAM,IAAI,EAAE,CAAA;YACZ,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAA;YAEnC,oBAAoB;YACpB,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,CAAA;YAClC,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAA;YAE5C,qBAAqB;YACrB,IAAI,QAAQ,GAAG,SAAS,EAAE,CAAC;gBACzB,OAAO,CAAC,UAAU,CAAC,cAAc,EAAE;oBACjC,MAAM,EAAI,GAAG,CAAC,MAAM;oBACpB,GAAG,EAAO,GAAG,CAAC,GAAG;oBACjB,IAAI,EAAM,GAAG,CAAC,IAAI;oBAClB,QAAQ;iBACT,CAAC,CAAA;YACJ,CAAC;QACH,CAAC,CAAA;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Aggregator, PulseStorage } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Periodically collects server resource metrics — CPU usage and memory.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ServerAggregator implements Aggregator {
|
|
6
|
+
private readonly storage;
|
|
7
|
+
private readonly intervalMs;
|
|
8
|
+
readonly name = "Server Aggregator";
|
|
9
|
+
constructor(storage: PulseStorage, intervalMs?: number);
|
|
10
|
+
register(): void;
|
|
11
|
+
private collect;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/aggregators/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE3D;;GAEG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IAI/C,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAJ7B,QAAQ,CAAC,IAAI,uBAAsB;gBAGhB,OAAO,EAAE,YAAY,EACrB,UAAU,GAAE,MAAe;IAG9C,QAAQ,IAAI,IAAI;IAOhB,OAAO,CAAC,OAAO;CAiBhB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cpus, totalmem, freemem } from 'node:os';
|
|
2
|
+
/**
|
|
3
|
+
* Periodically collects server resource metrics — CPU usage and memory.
|
|
4
|
+
*/
|
|
5
|
+
export class ServerAggregator {
|
|
6
|
+
storage;
|
|
7
|
+
intervalMs;
|
|
8
|
+
name = 'Server Aggregator';
|
|
9
|
+
constructor(storage, intervalMs = 15_000) {
|
|
10
|
+
this.storage = storage;
|
|
11
|
+
this.intervalMs = intervalMs;
|
|
12
|
+
}
|
|
13
|
+
register() {
|
|
14
|
+
const timer = setInterval(() => this.collect(), this.intervalMs);
|
|
15
|
+
timer.unref();
|
|
16
|
+
// Collect once immediately
|
|
17
|
+
this.collect();
|
|
18
|
+
}
|
|
19
|
+
collect() {
|
|
20
|
+
// CPU — average load across cores (0–100)
|
|
21
|
+
const cores = cpus();
|
|
22
|
+
const cpuPercent = cores.reduce((sum, core) => {
|
|
23
|
+
const total = Object.values(core.times).reduce((a, b) => a + b, 0);
|
|
24
|
+
const idle = core.times.idle;
|
|
25
|
+
return sum + ((total - idle) / total) * 100;
|
|
26
|
+
}, 0) / cores.length;
|
|
27
|
+
this.storage.record('server_cpu', Math.round(cpuPercent * 100) / 100);
|
|
28
|
+
// Memory — used percentage
|
|
29
|
+
const total = totalmem();
|
|
30
|
+
const free = freemem();
|
|
31
|
+
const usedPercent = ((total - free) / total) * 100;
|
|
32
|
+
this.storage.record('server_memory', Math.round(usedPercent * 100) / 100);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/aggregators/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAGjD;;GAEG;AACH,MAAM,OAAO,gBAAgB;IAIR;IACA;IAJV,IAAI,GAAG,mBAAmB,CAAA;IAEnC,YACmB,OAAqB,EACrB,aAAqB,MAAM;QAD3B,YAAO,GAAP,OAAO,CAAc;QACrB,eAAU,GAAV,UAAU,CAAiB;IAC3C,CAAC;IAEJ,QAAQ;QACN,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;QAChE,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,2BAA2B;QAC3B,IAAI,CAAC,OAAO,EAAE,CAAA;IAChB,CAAC;IAEO,OAAO;QACb,0CAA0C;QAC1C,MAAM,KAAK,GAAG,IAAI,EAAE,CAAA;QACpB,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;YAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;YAClE,MAAM,IAAI,GAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAA;YAC7B,OAAO,GAAG,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,CAAA;QAC7C,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAA;QAEpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAA;QAErE,2BAA2B;QAC3B,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAA;QACxB,MAAM,IAAI,GAAI,OAAO,EAAE,CAAA;QACvB,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,CAAA;QAClD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAA;IAC3E,CAAC;CACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AppRequest, AppResponse } from '@rudderjs/contracts';
|
|
2
|
+
import type { Aggregator, PulseStorage } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Tracks active (unique) users per minute bucket.
|
|
5
|
+
* Identifies users by session cookie, auth header, or IP.
|
|
6
|
+
*/
|
|
7
|
+
export declare class UserAggregator implements Aggregator {
|
|
8
|
+
private readonly storage;
|
|
9
|
+
readonly name = "User Aggregator";
|
|
10
|
+
private readonly seen;
|
|
11
|
+
private currentMinute;
|
|
12
|
+
constructor(storage: PulseStorage);
|
|
13
|
+
register(): void;
|
|
14
|
+
middleware(): (req: AppRequest, _res: AppResponse, next: () => Promise<void>) => Promise<void>;
|
|
15
|
+
private identifyUser;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=user.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/aggregators/user.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG3D;;;GAGG;AACH,qBAAa,cAAe,YAAW,UAAU;IAKnC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,QAAQ,CAAC,IAAI,qBAAoB;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoB;IACzC,OAAO,CAAC,aAAa,CAAI;gBAEI,OAAO,EAAE,YAAY;IAElD,QAAQ,IAAI,IAAI;IAIhB,UAAU,KAGM,KAAK,UAAU,EAAE,MAAM,WAAW,EAAE,MAAM,MAAM,OAAO,CAAC,IAAI,CAAC;IAoB7E,OAAO,CAAC,YAAY;CAWrB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isAsset } from '../utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Tracks active (unique) users per minute bucket.
|
|
4
|
+
* Identifies users by session cookie, auth header, or IP.
|
|
5
|
+
*/
|
|
6
|
+
export class UserAggregator {
|
|
7
|
+
storage;
|
|
8
|
+
name = 'User Aggregator';
|
|
9
|
+
seen = new Set();
|
|
10
|
+
currentMinute = 0;
|
|
11
|
+
constructor(storage) {
|
|
12
|
+
this.storage = storage;
|
|
13
|
+
}
|
|
14
|
+
register() {
|
|
15
|
+
// Middleware is registered by the service provider
|
|
16
|
+
}
|
|
17
|
+
middleware() {
|
|
18
|
+
const storage = this.storage;
|
|
19
|
+
return async (req, _res, next) => {
|
|
20
|
+
// Skip Vite internals, source files, and static assets
|
|
21
|
+
if (isAsset(req.path))
|
|
22
|
+
return next();
|
|
23
|
+
const minute = Math.floor(Date.now() / 60_000);
|
|
24
|
+
if (minute !== this.currentMinute) {
|
|
25
|
+
this.seen.clear();
|
|
26
|
+
this.currentMinute = minute;
|
|
27
|
+
}
|
|
28
|
+
const userId = this.identifyUser(req);
|
|
29
|
+
if (!this.seen.has(userId)) {
|
|
30
|
+
this.seen.add(userId);
|
|
31
|
+
storage.record('active_users', 1);
|
|
32
|
+
}
|
|
33
|
+
return next();
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
identifyUser(req) {
|
|
37
|
+
// Prefer auth user, then session, then IP
|
|
38
|
+
const auth = req.headers['authorization'];
|
|
39
|
+
if (auth)
|
|
40
|
+
return `auth:${auth.slice(0, 40)}`;
|
|
41
|
+
const cookie = req.headers['cookie'] ?? '';
|
|
42
|
+
const session = cookie.match(/(?:^|;\s*)session=([^;]+)/)?.[1];
|
|
43
|
+
if (session)
|
|
44
|
+
return `session:${session}`;
|
|
45
|
+
return `ip:${req.headers['x-forwarded-for']?.split(',')[0]?.trim() ?? req.headers['x-real-ip'] ?? 'unknown'}`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=user.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/aggregators/user.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,MAAM,OAAO,cAAc;IAKI;IAJpB,IAAI,GAAG,iBAAiB,CAAA;IAChB,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IACjC,aAAa,GAAG,CAAC,CAAA;IAEzB,YAA6B,OAAqB;QAArB,YAAO,GAAP,OAAO,CAAc;IAAG,CAAC;IAEtD,QAAQ;QACN,mDAAmD;IACrD,CAAC;IAED,UAAU;QACR,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAE5B,OAAO,KAAK,EAAE,GAAe,EAAE,IAAiB,EAAE,IAAyB,EAAE,EAAE;YAC7E,uDAAuD;YACvD,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,EAAE,CAAA;YAEpC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAA;YAC9C,IAAI,MAAM,KAAK,IAAI,CAAC,aAAa,EAAE,CAAC;gBAClC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;gBACjB,IAAI,CAAC,aAAa,GAAG,MAAM,CAAA;YAC7B,CAAC;YAED,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAA;YACrC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;gBACrB,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAA;YACnC,CAAC;YAED,OAAO,IAAI,EAAE,CAAA;QACf,CAAC,CAAA;IACH,CAAC;IAEO,YAAY,CAAC,GAAe;QAClC,0CAA0C;QAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAA;QACzC,IAAI,IAAI;YAAE,OAAO,QAAQ,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAA;QAE5C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;QAC1C,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAC9D,IAAI,OAAO;YAAE,OAAO,WAAW,OAAO,EAAE,CAAA;QAExC,OAAO,MAAM,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,SAAS,EAAE,CAAA;IAC/G,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAc,MAAM,aAAa,CAAA;AAexE;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAG,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAkJf"}
|