@ashokreddydev/react-performance-monitor 0.0.0-stage → 0.1.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/CHANGELOG.md +17 -0
- package/LICENSE +21 -0
- package/README.md +311 -2
- package/dist/PerformanceMonitor-BSWG4Ii-.d.cts +183 -0
- package/dist/PerformanceMonitor-BWvrNCvp.d.ts +183 -0
- package/dist/PerformancePanel-F4AYMOBE.js +678 -0
- package/dist/PerformancePanel-L3YUAEYG.cjs +680 -0
- package/dist/axios.cjs +62 -0
- package/dist/axios.d.cts +21 -0
- package/dist/axios.d.ts +21 -0
- package/dist/axios.js +60 -0
- package/dist/charts.cjs +320 -0
- package/dist/charts.d.cts +56 -0
- package/dist/charts.d.ts +56 -0
- package/dist/charts.js +291 -0
- package/dist/chunk-4V2B5GPN.js +993 -0
- package/dist/chunk-ICEGTP6M.js +122 -0
- package/dist/chunk-K3GFTMEC.cjs +1010 -0
- package/dist/chunk-LBKL3OKH.cjs +347 -0
- package/dist/chunk-MYCOS5WD.cjs +125 -0
- package/dist/chunk-Q33ZJQZ2.js +338 -0
- package/dist/chunk-QUF75EA3.cjs +183 -0
- package/dist/chunk-UGDPZ343.js +177 -0
- package/dist/chunk-XKYIKK6C.js +74 -0
- package/dist/chunk-ZY43XLHK.cjs +79 -0
- package/dist/config-Cie78aAE.d.cts +66 -0
- package/dist/config-D9H8MUgT.d.ts +66 -0
- package/dist/index.cjs +408 -0
- package/dist/index.d.cts +305 -0
- package/dist/index.d.ts +305 -0
- package/dist/index.js +267 -0
- package/dist/metrics-LC7_GsyQ.d.cts +115 -0
- package/dist/metrics-rSnrCSo0.d.ts +115 -0
- package/dist/performanceThresholds-B8-ZCvDG.d.cts +137 -0
- package/dist/performanceThresholds-B8-ZCvDG.d.ts +137 -0
- package/dist/redux.cjs +55 -0
- package/dist/redux.d.cts +27 -0
- package/dist/redux.d.ts +27 -0
- package/dist/redux.js +53 -0
- package/dist/trackora.cjs +17 -0
- package/dist/trackora.d.cts +17 -0
- package/dist/trackora.d.ts +17 -0
- package/dist/trackora.js +14 -0
- package/dist/types-9Azbo-h3.d.cts +56 -0
- package/dist/types-CQPUx5jX.d.ts +56 -0
- package/package.json +150 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @ashokreddydev/react-performance-monitor
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8e870e2: npm page: the Homepage link now opens the SDK documentation on the Trackora website, and the README starts with a
|
|
8
|
+
link to it.
|
|
9
|
+
|
|
10
|
+
## 0.1.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- 23aecb8: New package: React performance monitoring — React Profiler boundaries, fetch/XHR/Axios timing with p50/p95/p99,
|
|
15
|
+
Core Web Vitals (web-vitals), Redux Toolkit middleware, browser timings (navigation, resources, long tasks,
|
|
16
|
+
visibility, memory) and error capture, a lazy developer panel with optional ECharts charts, bounded batching with
|
|
17
|
+
sendBeacon/fetch delivery and retry, and a backend-independent transport (plus a Trackora adapter).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ashok Penamalli
|
|
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
CHANGED
|
@@ -1,3 +1,312 @@
|
|
|
1
|
-
#
|
|
1
|
+
# react-performance-monitor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Website & docs:** [www.trackora.testforge.co.in/docs](https://www.trackora.testforge.co.in/docs#react-performance-monitor) · [All three Trackora SDKs](https://www.trackora.testforge.co.in/docs)
|
|
4
|
+
|
|
5
|
+
Performance monitoring for React apps. It measures:
|
|
6
|
+
|
|
7
|
+
- **component render times**, with React's Profiler
|
|
8
|
+
- **API latency**: fetch, XHR and Axios, with p50/p95/p99
|
|
9
|
+
- **Core Web Vitals**: LCP, INP, CLS, FCP and TTFB
|
|
10
|
+
- **Redux action timing**
|
|
11
|
+
- **long tasks, navigation and resource timing**, plus memory where the browser supports it
|
|
12
|
+
- **uncaught errors**
|
|
13
|
+
|
|
14
|
+
It also includes a floating developer panel with charts. It sends data to **any backend**: you give it an HTTP
|
|
15
|
+
endpoint or your own transport.
|
|
16
|
+
|
|
17
|
+
- **Safe by default.** It never throws into your app, and memory and retries are bounded. A session that is not
|
|
18
|
+
sampled does no work at all, and production sends no per-render events unless you turn them on.
|
|
19
|
+
- **Private by default.** It never reads headers, cookies, request or response bodies, Redux state or action payloads.
|
|
20
|
+
URLs become templates (`/users/:id`). Query values are removed unless you allow them, and free text is masked.
|
|
21
|
+
- **No re-renders caused by the SDK.** The provider's context never changes. Only components that read live
|
|
22
|
+
metrics update, at most twice a second.
|
|
23
|
+
|
|
24
|
+
> Status: 0.x — the API may still change in minor versions.
|
|
25
|
+
|
|
26
|
+
## Contents
|
|
27
|
+
|
|
28
|
+
[Install](#install) · [Quick start](#quick-start) · [Configuration](#configuration) · [Collectors](#collectors) ·
|
|
29
|
+
[Developer panel](#developer-panel) · [Redux](#redux) · [Axios](#axios) · [Hooks](#hooks) · [Delivery](#delivery) ·
|
|
30
|
+
[Privacy](#privacy) · [Performance overhead](#performance-overhead) · [Production checklist](#production-checklist) ·
|
|
31
|
+
[Troubleshooting](#troubleshooting) · [Custom collectors](#custom-collectors)
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @ashokreddydev/react-performance-monitor
|
|
37
|
+
# optional, only for the charts in the developer panel:
|
|
38
|
+
npm install echarts
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You need React 18 or newer. `web-vitals` is installed with the package. Redux Toolkit, Axios, ECharts and
|
|
42
|
+
React Scan are optional. Only their own entry points use them.
|
|
43
|
+
|
|
44
|
+
| Import | What it adds |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `@ashokreddydev/react-performance-monitor` | provider, monitor, built-in collectors, `<ProfiledBoundary>`, hooks, `<PerformanceButton>` |
|
|
47
|
+
| `…/redux` | `createPerformanceMiddleware()` |
|
|
48
|
+
| `…/axios` | `instrumentAxios(instance, monitor)` |
|
|
49
|
+
| `…/charts` | ECharts charts for the panel (needs `echarts`) |
|
|
50
|
+
| `…/trackora` | `trackoraTransport()` for a Trackora server |
|
|
51
|
+
|
|
52
|
+
ESM and CommonJS builds ship with TypeScript declarations. The package works in JavaScript and TypeScript apps and
|
|
53
|
+
is tree-shakeable (`sideEffects: false`).
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { PerformanceButton, PerformanceProvider, ProfiledBoundary } from '@ashokreddydev/react-performance-monitor';
|
|
59
|
+
|
|
60
|
+
export function Root() {
|
|
61
|
+
return (
|
|
62
|
+
<PerformanceProvider
|
|
63
|
+
config={{
|
|
64
|
+
applicationId: 'my-app',
|
|
65
|
+
environment: import.meta.env.MODE,
|
|
66
|
+
sampleRate: 0.2, // 20 % of sessions send telemetry
|
|
67
|
+
collectors: { components: true, api: true, webVitals: true, browser: true, errors: true },
|
|
68
|
+
transport: { endpoint: '/api/performance/batch', batchSize: 20, flushInterval: 10_000 },
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<ProfiledBoundary id="App">
|
|
72
|
+
<App />
|
|
73
|
+
</ProfiledBoundary>
|
|
74
|
+
<PerformanceButton loadCharts={() => import('@ashokreddydev/react-performance-monitor/charts')} />
|
|
75
|
+
</PerformanceProvider>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The provider reads `config` once, on the first render. To change it, remount the provider (for example with a new
|
|
81
|
+
`key`). The API, Web Vitals, browser and error collectors start automatically, and each one can be switched off.
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
| Option | Default | Notes |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `applicationId` | (required) | 1–64 letters, digits, `.`, `_`, `-` |
|
|
88
|
+
| `environment` | `development` | Free text. `production` or `prod` turns off the dev-only defaults below. |
|
|
89
|
+
| `release` | none | Your app version. Regressions are easier to tie to a deploy. |
|
|
90
|
+
| `enabled` | `true` | Master switch. |
|
|
91
|
+
| `sampleRate` | `1` | Share of sessions that send telemetry. Decided once per browser session. |
|
|
92
|
+
| `collectors` | all on except `redux` | `{ components, api, webVitals, redux, browser, errors }` |
|
|
93
|
+
| `thresholds` | renders 16 ms, API 1 000 ms, actions 16 ms, resources 1 000 ms, Google's Web Vitals | What counts as slow. |
|
|
94
|
+
| `transport` | `false` (local only) | `{ endpoint, headers?, batchSize? (20, max 100), flushInterval? (10 s), maxRetries? (5) }`, or your own `Transport`. |
|
|
95
|
+
| `maxBufferedEvents` | `500` (max 5 000) | Beyond this the oldest events are dropped, and the drop is counted and reported. |
|
|
96
|
+
| `devtools` | on unless production | Collects locally for the panel even in unsampled sessions, and shows `<PerformanceButton>`. |
|
|
97
|
+
| `components.recordRenders` | on unless production | In production only slow renders are sent. Every render is still aggregated. |
|
|
98
|
+
| `redux.recordActions` | on unless production | In production only slow actions are sent. |
|
|
99
|
+
| `api.ignoreUrls` | `[]` | URLs that are never measured. The SDK's own endpoint is always ignored. |
|
|
100
|
+
| `api.allowedQueryParams` | `[]` | Query parameter names whose values are kept. |
|
|
101
|
+
| `api.groupEndpoint` | none | Your own endpoint grouping, applied after the built-in templating. |
|
|
102
|
+
| `beforeSend` | none | Change an event, or drop it with `null`, before it is queued. |
|
|
103
|
+
| `debug` | `false` | `console.warn` for SDK problems, once per kind. |
|
|
104
|
+
|
|
105
|
+
An invalid config never throws. The monitor disables itself and warns once.
|
|
106
|
+
|
|
107
|
+
## Collectors
|
|
108
|
+
|
|
109
|
+
Each collector is independent and has its own switch in `collectors`.
|
|
110
|
+
|
|
111
|
+
| Collector | Measures | Notes |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `components` | React Profiler `actualDuration`, `baseDuration`, mount or update, per `<ProfiledBoundary id>` / `withProfiler()` | React reports timings only in development and in the `react-dom/profiling` build. In a normal production build the boundary is free but silent. |
|
|
114
|
+
| `api` | `fetch` and `XMLHttpRequest`: endpoint template, method, status, duration, success | It never reads headers or bodies. Its patches are removed on stop, but only if they are still the SDK's own. |
|
|
115
|
+
| `webVitals` | LCP, INP, CLS, FCP, TTFB via `web-vitals` | Each metric is reported once per page view, when final. Its rating comes from your thresholds. Missing APIs are shown as "not supported". |
|
|
116
|
+
| `redux` | Action type, dispatch time, whether state changed | Needs the `/redux` middleware. |
|
|
117
|
+
| `browser` | Navigation timing, slow or failed resources, long tasks, visibility, JS heap | Long tasks are Chromium-only and memory is Chromium's `performance.memory`. Both are marked unsupported elsewhere. At most 100 resources and 200 long tasks are sent per page. |
|
|
118
|
+
| `errors` | `error` and `unhandledrejection`: name, masked message, fingerprint | Stacks are not collected. Each error is sent at most 10× per page and then counted locally. Use an error tracker for full details. |
|
|
119
|
+
|
|
120
|
+
**What counts as measured and what is inferred.** Render counts and durations come straight from React. The
|
|
121
|
+
panel's "possibly unnecessary re-render" flag is a heuristic: many updates per mount, each costing close to a full
|
|
122
|
+
render (actual ≈ base). It is labelled **inferred**. The SDK does not claim to detect every unnecessary re-render.
|
|
123
|
+
Use React DevTools or React Scan to confirm.
|
|
124
|
+
|
|
125
|
+
## Developer panel
|
|
126
|
+
|
|
127
|
+
`<PerformanceButton>` puts a floating button in the bottom-right corner. It opens a responsive panel with eight tabs:
|
|
128
|
+
Overview (warnings and recommendations), Components, Re-render analysis, API performance, Web Vitals, Redux, Browser
|
|
129
|
+
and Errors.
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
<PerformanceButton
|
|
133
|
+
theme="auto" // 'light' | 'dark' | 'auto'
|
|
134
|
+
themeOverrides={{ accent: '#7C3AED' }}
|
|
135
|
+
position="right"
|
|
136
|
+
loadCharts={() => import('@ashokreddydev/react-performance-monitor/charts')}
|
|
137
|
+
reactScan={scan} // optional: `import { scan } from 'react-scan'` adds an overlay toggle
|
|
138
|
+
/>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- The panel code (about 6 KB gzip) is downloaded the first time the button is clicked.
|
|
142
|
+
- Charts use Apache ECharts and load lazily. Large series are down-sampled (LTTB) and animation is switched off
|
|
143
|
+
above 500 points. Without `loadCharts` the panel shows simple built-in bars.
|
|
144
|
+
- In production the button renders nothing (`devtools` is off) unless you pass `force`.
|
|
145
|
+
- Accessibility: the panel is a labelled dialog, the tabs support arrow keys, Home and End, Escape closes the panel,
|
|
146
|
+
focus returns to the button, and no information is carried by colour alone.
|
|
147
|
+
|
|
148
|
+
## Redux
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
152
|
+
import { createPerformanceMiddleware } from '@ashokreddydev/react-performance-monitor/redux';
|
|
153
|
+
|
|
154
|
+
export const store = configureStore({
|
|
155
|
+
reducer,
|
|
156
|
+
middleware: (getDefault) => getDefault().concat(createPerformanceMiddleware({ ignore: [/^ui\//] })),
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Set `collectors: { redux: true }`. By default the middleware reports to the running monitor. Pass
|
|
161
|
+
`{ monitor }` to use a specific one, for example a `new PerformanceMonitor(config)` that you also give to
|
|
162
|
+
`<PerformanceProvider monitor={monitor}>`. Thunks pass through untouched, and errors from reducers propagate
|
|
163
|
+
unchanged.
|
|
164
|
+
|
|
165
|
+
## Axios
|
|
166
|
+
|
|
167
|
+
The global XHR patch already measures Axios' browser adapter. Use the Axios integration when you need Axios' own
|
|
168
|
+
view instead: custom adapters, SSR, or `collectors.api` off. It claims each request, so nothing is counted twice.
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { instrumentAxios } from '@ashokreddydev/react-performance-monitor/axios';
|
|
172
|
+
const stop = instrumentAxios(apiClient, monitor); // call stop() to remove the interceptors
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Hooks
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
const monitor = usePerformance(); // stable; never re-renders
|
|
179
|
+
monitor.setRoute('/orders/:id'); // call from your router on navigation
|
|
180
|
+
monitor.record({ eventType: 'long_task', duration: 120, data: { source: 'self' } });
|
|
181
|
+
|
|
182
|
+
const metrics = usePerformanceMetrics(); // live aggregates; re-renders at most every 500 ms
|
|
183
|
+
const slowest = useApiPerformance('/api/orders'); // API stats, filtered by endpoint
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Outside a provider the hooks return a disabled monitor, so shared components can call them safely.
|
|
187
|
+
|
|
188
|
+
## Delivery
|
|
189
|
+
|
|
190
|
+
- **When batches go out.** A batch is sent when it is full, every `flushInterval`, and when the page is hidden or
|
|
191
|
+
closed.
|
|
192
|
+
- **Sending while the page closes.** Without custom headers the SDK uses `navigator.sendBeacon`. The body is sent
|
|
193
|
+
as `text/plain` to avoid a CORS preflight, so parse it as JSON. With headers it uses `fetch` with `keepalive`.
|
|
194
|
+
Cookies are never sent.
|
|
195
|
+
- **Retries.** Retries use exponential backoff with jitter: 1 s, 2 s, 4 s and so on, up to 60 s, 5 attempts by
|
|
196
|
+
default.
|
|
197
|
+
- `4xx` responses are not retried.
|
|
198
|
+
- `Retry-After` is honoured.
|
|
199
|
+
- A request that hangs for 30 s counts as failed.
|
|
200
|
+
- **Offline.** Nothing is sent while the browser is offline. Events wait in the bounded buffer.
|
|
201
|
+
- **Dropped events.** Drops are never silent. Every batch carries `dropped: { overflow, failed, filtered }`.
|
|
202
|
+
|
|
203
|
+
Each request body is one `PerformanceBatch`, with every event carrying its `eventType`, `timestamp`, `route`,
|
|
204
|
+
`duration?`, `data` and `metadata?`:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"schemaVersion": 1,
|
|
209
|
+
"applicationId": "my-app",
|
|
210
|
+
"environment": "production",
|
|
211
|
+
"release": "1.4.0",
|
|
212
|
+
"sessionId": "0f8fad5b-d9cb-469f-a165-70867728950e",
|
|
213
|
+
"sampleRate": 0.2,
|
|
214
|
+
"sentAt": 1794220000000,
|
|
215
|
+
"sdk": { "name": "react-performance-monitor", "version": "0.1.0" },
|
|
216
|
+
"dropped": { "overflow": 0, "failed": 0, "filtered": 0 },
|
|
217
|
+
"events": [
|
|
218
|
+
{
|
|
219
|
+
"eventType": "api_request",
|
|
220
|
+
"timestamp": 1794219999000,
|
|
221
|
+
"route": "/orders/:id",
|
|
222
|
+
"duration": 420,
|
|
223
|
+
"data": { "endpoint": "/api/orders/:id", "host": "", "method": "GET", "status": 200, "ok": true, "slow": false, "client": "fetch" }
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
On your server:
|
|
230
|
+
- Authenticate the request with your own key or cookie.
|
|
231
|
+
- Validate every field and cap the body at about 128 KB.
|
|
232
|
+
- Take the application id from your authentication, not from the body.
|
|
233
|
+
- Keep the data with a retention limit (TTL).
|
|
234
|
+
|
|
235
|
+
## Privacy
|
|
236
|
+
|
|
237
|
+
What it collects:
|
|
238
|
+
- endpoint templates, methods, status codes and durations
|
|
239
|
+
- component names that you choose
|
|
240
|
+
- Redux action types
|
|
241
|
+
- masked error messages and fingerprints
|
|
242
|
+
- vitals and timings
|
|
243
|
+
- a random session id
|
|
244
|
+
|
|
245
|
+
It does not collect a user id. What it never collects:
|
|
246
|
+
- request or response headers or bodies, or cookies
|
|
247
|
+
- passwords
|
|
248
|
+
- Redux state or payloads
|
|
249
|
+
- stack traces
|
|
250
|
+
- query values that are not allow-listed
|
|
251
|
+
|
|
252
|
+
Sensitive-looking values (e-mails, card numbers, JWTs, phone numbers, government ids) are replaced with
|
|
253
|
+
`[REDACTED]`, and so is `metadata` whose key names look sensitive.
|
|
254
|
+
|
|
255
|
+
## Performance overhead
|
|
256
|
+
|
|
257
|
+
| Measure | Value |
|
|
258
|
+
|---|---|
|
|
259
|
+
| `record()` per event (aggregation, sampling, buffer) | ~1–2 µs (measured over 50 000 events in CI) |
|
|
260
|
+
| Unsampled production session | collectors are not started, no listeners, no timers |
|
|
261
|
+
| Memory | buffer ≤ `maxBufferedEvents`, ≤ 100 keys per table plus `(other)`, 10-minute timeline, bounded quantile samples |
|
|
262
|
+
| Bundle, main entry (minified + gzip) | ~15 KB, plus `web-vitals` ~2 KB |
|
|
263
|
+
| Panel (lazy) / charts (lazy, excluding ECharts) | ~6 KB / ~2 KB |
|
|
264
|
+
|
|
265
|
+
## Production checklist
|
|
266
|
+
|
|
267
|
+
- Set `environment` from your build (`import.meta.env.MODE`). Production turns off `devtools`, `recordRenders` and
|
|
268
|
+
`recordActions`.
|
|
269
|
+
- Pick a `sampleRate`, for example `0.1`–`0.3`.
|
|
270
|
+
- Use an authenticated, rate-limited endpoint with a body limit and retention.
|
|
271
|
+
- Wrap only the parts of the tree you care about in `<ProfiledBoundary>`. For production render timings, use the
|
|
272
|
+
`react-dom/profiling` build only if you need it.
|
|
273
|
+
|
|
274
|
+
## Troubleshooting
|
|
275
|
+
|
|
276
|
+
| Symptom | Cause and fix |
|
|
277
|
+
|---|---|
|
|
278
|
+
| No component data | React reports Profiler timings only in development or `react-dom/profiling`, and only inside `<ProfiledBoundary>`. |
|
|
279
|
+
| A Web Vital stays "waiting…" | Vitals are final when the page is hidden. INP needs an interaction. LCP and INP are not supported in every browser. |
|
|
280
|
+
| A metric shows "not supported" | The browser lacks the API (for example `longtask` or `memory` outside Chromium). The value is never shown as 0. |
|
|
281
|
+
| Requests counted twice | You use `instrumentAxios` *and* a non-claiming wrapper. Use one, or set `collectors.api: false`. |
|
|
282
|
+
| Nothing is sent | Check that the session is sampled (the panel chip says "sending" or "local only"), that `transport` is set, and run `debug: true`. |
|
|
283
|
+
| Beacon requests fail on the server | Parse `text/plain` bodies as JSON. |
|
|
284
|
+
|
|
285
|
+
## Custom collectors
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import type { Collector } from '@ashokreddydev/react-performance-monitor';
|
|
289
|
+
|
|
290
|
+
export const longTasks: Collector = {
|
|
291
|
+
name: 'browser',
|
|
292
|
+
start(ctx) {
|
|
293
|
+
if (typeof PerformanceObserver === 'undefined') {
|
|
294
|
+
ctx.markUnsupported('longtask');
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
const po = new PerformanceObserver((list) => {
|
|
298
|
+
for (const e of list.getEntries())
|
|
299
|
+
ctx.record({ eventType: 'long_task', duration: e.duration, data: { source: e.name } });
|
|
300
|
+
});
|
|
301
|
+
po.observe({ type: 'longtask', buffered: true });
|
|
302
|
+
return () => po.disconnect(); // always clean up
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Pass collectors with `<PerformanceProvider collectors={[longTasks]} builtInCollectors={false} />`. A collector that
|
|
308
|
+
throws is contained, and reported when `debug` is on. It never stops the other collectors or your app.
|
|
309
|
+
|
|
310
|
+
## License
|
|
311
|
+
|
|
312
|
+
MIT
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { C as CollectorName, T as Transport, H as HttpTransportOptions, P as PerformanceMonitorConfig } from './config-Cie78aAE.cjs';
|
|
2
|
+
import { P as PerformanceThresholds, a as PerformanceEvent, f as PerformanceEventInput } from './performanceThresholds-B8-ZCvDG.cjs';
|
|
3
|
+
import { M as MetricsSnapshot } from './metrics-LC7_GsyQ.cjs';
|
|
4
|
+
|
|
5
|
+
/** Config after validation: every field present, every number inside its safe range. */
|
|
6
|
+
interface ResolvedPerformanceConfig {
|
|
7
|
+
applicationId: string;
|
|
8
|
+
environment: string;
|
|
9
|
+
release: string | undefined;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
sampleRate: number;
|
|
12
|
+
collectors: Readonly<Record<CollectorName, boolean>>;
|
|
13
|
+
thresholds: PerformanceThresholds;
|
|
14
|
+
/** null = local only (no telemetry leaves the page). */
|
|
15
|
+
transport: Transport | HttpTransportOptions | null;
|
|
16
|
+
batchSize: number;
|
|
17
|
+
flushInterval: number;
|
|
18
|
+
maxRetries: number;
|
|
19
|
+
maxBufferedEvents: number;
|
|
20
|
+
devtools: boolean;
|
|
21
|
+
recordRenders: boolean;
|
|
22
|
+
recordActions: boolean;
|
|
23
|
+
api: {
|
|
24
|
+
ignoreUrls: readonly (string | RegExp)[];
|
|
25
|
+
allowedQueryParams: readonly string[];
|
|
26
|
+
groupEndpoint: ((endpoint: string) => string) | undefined;
|
|
27
|
+
};
|
|
28
|
+
beforeSend: ((event: PerformanceEvent) => PerformanceEvent | null) | undefined;
|
|
29
|
+
debug: boolean;
|
|
30
|
+
}
|
|
31
|
+
declare const COLLECTOR_NAMES: readonly CollectorName[];
|
|
32
|
+
/** A problem found in the config. The monitor does not throw on bad config — it disables itself and warns once. */
|
|
33
|
+
declare class PerformanceConfigError extends Error {
|
|
34
|
+
readonly name = "PerformanceConfigError";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Validates and fills defaults. Throws `PerformanceConfigError` only for things that cannot be defaulted
|
|
38
|
+
* (missing/invalid `applicationId`, invalid transport endpoint); `PerformanceMonitor` catches it and stays disabled.
|
|
39
|
+
*/
|
|
40
|
+
declare function resolveConfig(input: PerformanceMonitorConfig): ResolvedPerformanceConfig;
|
|
41
|
+
|
|
42
|
+
declare const SDK_NAME = "react-performance-monitor";
|
|
43
|
+
declare const SDK_VERSION: string;
|
|
44
|
+
/** What a collector gets. Everything here is safe to call at any time — it never throws. */
|
|
45
|
+
interface CollectorContext {
|
|
46
|
+
readonly config: ResolvedPerformanceConfig;
|
|
47
|
+
record(event: PerformanceEventInput, metadata?: Record<string, unknown>, options?: RecordOptions): void;
|
|
48
|
+
/** Current route template (e.g. `/orders/:id`). */
|
|
49
|
+
getRoute(): string;
|
|
50
|
+
/** true for the monitor's own telemetry requests and `api.ignoreUrls` — never measure these. */
|
|
51
|
+
isIgnoredUrl(url: string): boolean;
|
|
52
|
+
/** true when an integration (e.g. Axios) already measures this request — skip it. */
|
|
53
|
+
consumeClaim(method: string, url: string): boolean;
|
|
54
|
+
/** Tell the panel a browser API is missing (shown as "not supported", never as zero). */
|
|
55
|
+
markUnsupported(feature: string): void;
|
|
56
|
+
now(): number;
|
|
57
|
+
/** Report a problem inside the collector without breaking the host app. */
|
|
58
|
+
reportError(where: string, error: unknown): void;
|
|
59
|
+
}
|
|
60
|
+
interface RecordOptions {
|
|
61
|
+
/** Aggregate for the panel and hooks only; never sent (used for high-frequency, non-slow events). */
|
|
62
|
+
localOnly?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/** The most recently started monitor — what `createPerformanceMiddleware()` uses when not given one. */
|
|
65
|
+
declare function getActiveMonitor(): PerformanceMonitor | null;
|
|
66
|
+
/** One independent measurement source. `start` returns its cleanup (observers, listeners, patches). */
|
|
67
|
+
interface Collector {
|
|
68
|
+
readonly name: CollectorName;
|
|
69
|
+
start(ctx: CollectorContext): (() => void) | undefined;
|
|
70
|
+
}
|
|
71
|
+
interface PerformanceMonitorDeps {
|
|
72
|
+
/** Overrides the transport from the config (tests, custom delivery). */
|
|
73
|
+
transport?: Transport;
|
|
74
|
+
now?: () => number;
|
|
75
|
+
random?: () => number;
|
|
76
|
+
/** How often subscribers (panel, hooks) are told about new metrics. Default 500 ms. */
|
|
77
|
+
notifyIntervalMs?: number;
|
|
78
|
+
/** A send that takes longer counts as failed and is retried (a hanging request never blocks delivery). Default 30 s. */
|
|
79
|
+
sendTimeoutMs?: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The runtime: owns the config, session, sampling decision, collectors, buffer, aggregation and delivery.
|
|
83
|
+
* Rules: it never throws into the host app, holds a bounded amount of memory, and cleans up everything on `stop()`.
|
|
84
|
+
*/
|
|
85
|
+
declare class PerformanceMonitor {
|
|
86
|
+
/** null when the config was invalid — the monitor is then a no-op. */
|
|
87
|
+
readonly config: ResolvedPerformanceConfig | null;
|
|
88
|
+
/** `config`, or a disabled stand-in when it was invalid — so internals never deal with null. */
|
|
89
|
+
private readonly settings;
|
|
90
|
+
readonly sessionId: string;
|
|
91
|
+
/** This session sends telemetry (decided once per session from `sampleRate`). */
|
|
92
|
+
readonly sampled: boolean;
|
|
93
|
+
private readonly now;
|
|
94
|
+
private readonly random;
|
|
95
|
+
private readonly transport;
|
|
96
|
+
private readonly ownUrls;
|
|
97
|
+
private readonly buffer;
|
|
98
|
+
private readonly aggregate;
|
|
99
|
+
private readonly collectors;
|
|
100
|
+
private readonly cleanups;
|
|
101
|
+
private readonly listeners;
|
|
102
|
+
private readonly notify;
|
|
103
|
+
private readonly warned;
|
|
104
|
+
private readonly sendTimeoutMs;
|
|
105
|
+
private published;
|
|
106
|
+
private running;
|
|
107
|
+
private stopped;
|
|
108
|
+
private readonly claims;
|
|
109
|
+
private recordingDepth;
|
|
110
|
+
private route;
|
|
111
|
+
private pendingDropped;
|
|
112
|
+
private inflight;
|
|
113
|
+
private retry;
|
|
114
|
+
private retryTimer;
|
|
115
|
+
private flushTimer;
|
|
116
|
+
private soonTimer;
|
|
117
|
+
private detachWindow;
|
|
118
|
+
/** Internal problems so far (shown in the panel; never thrown). */
|
|
119
|
+
internalErrors: number;
|
|
120
|
+
constructor(config: PerformanceMonitorConfig, deps?: PerformanceMonitorDeps);
|
|
121
|
+
private transportOrThrow;
|
|
122
|
+
/** A monitor that does nothing — what hooks use outside a `<PerformanceProvider>`. */
|
|
123
|
+
static disabled(): PerformanceMonitor;
|
|
124
|
+
/** Telemetry leaves the page in this session. */
|
|
125
|
+
get sending(): boolean;
|
|
126
|
+
/** Collectors run: sending, or the developer panel needs local data. */
|
|
127
|
+
get collecting(): boolean;
|
|
128
|
+
get isRunning(): boolean;
|
|
129
|
+
/** The collector is switched on and this session collects (cheap check for hot paths, e.g. Redux, Profiler). */
|
|
130
|
+
isEnabled(name: CollectorName): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* An integration that measures a request itself (e.g. Axios interceptors) claims it, so the global fetch/XHR
|
|
133
|
+
* patch skips the same request — no double counting. Claims expire after 60 s.
|
|
134
|
+
*/
|
|
135
|
+
claimRequest(method: string, url: string): void;
|
|
136
|
+
/** true (and removes the claim) when an integration already measures this request. */
|
|
137
|
+
consumeClaim(method: string, url: string): boolean;
|
|
138
|
+
private absolute;
|
|
139
|
+
/** Register a collector (once per name). Skipped when switched off in `config.collectors`. */
|
|
140
|
+
use(collector: Collector): this;
|
|
141
|
+
start(): void;
|
|
142
|
+
/** Stops collectors, timers and listeners; sends what is buffered. Safe to call twice; `start()` works again. */
|
|
143
|
+
stop(): void;
|
|
144
|
+
/** The route attached to new events. Routers call this on navigation; default is the templated `location.pathname`. */
|
|
145
|
+
setRoute(route: string): void;
|
|
146
|
+
getRoute(): string;
|
|
147
|
+
/**
|
|
148
|
+
* Add one event. Never throws. Accepted from construction on (React Profiler callbacks fire before the provider's
|
|
149
|
+
* effect starts the monitor), ignored after `stop()` and when this session does not collect.
|
|
150
|
+
*/
|
|
151
|
+
record(input: PerformanceEventInput, metadata?: Record<string, unknown>, options?: RecordOptions): void;
|
|
152
|
+
/** Per-render events are sent only when `recordRenders` is on; slow renders always are. */
|
|
153
|
+
private shouldSend;
|
|
154
|
+
private enqueue;
|
|
155
|
+
private countDropped;
|
|
156
|
+
private takeDropped;
|
|
157
|
+
private returnDropped;
|
|
158
|
+
private batch;
|
|
159
|
+
/**
|
|
160
|
+
* Sends buffered events. Normal flush: one batch at a time, failed batches retried with backoff.
|
|
161
|
+
* Unloading: everything left is handed to `sendBeacon`/`keepalive` at once, without waiting or retrying.
|
|
162
|
+
*/
|
|
163
|
+
flush(options?: {
|
|
164
|
+
unloading?: boolean;
|
|
165
|
+
}): Promise<void>;
|
|
166
|
+
private deliver;
|
|
167
|
+
private flushUnloading;
|
|
168
|
+
private returnDroppedInto;
|
|
169
|
+
/** Stable function — pass straight to `useSyncExternalStore`. */
|
|
170
|
+
readonly subscribe: (listener: () => void) => (() => void);
|
|
171
|
+
/** Stable function; returns the same object until new metrics are published (at most every `notifyIntervalMs`). */
|
|
172
|
+
readonly getSnapshot: () => MetricsSnapshot;
|
|
173
|
+
/** Number of events waiting to be sent. */
|
|
174
|
+
get bufferedCount(): number;
|
|
175
|
+
private publish;
|
|
176
|
+
private context;
|
|
177
|
+
private startCollector;
|
|
178
|
+
isIgnoredUrl(url: string): boolean;
|
|
179
|
+
private reportError;
|
|
180
|
+
private warnOnce;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export { type Collector as C, PerformanceMonitor as P, type RecordOptions as R, SDK_NAME as S, COLLECTOR_NAMES as a, type CollectorContext as b, PerformanceConfigError as c, type PerformanceMonitorDeps as d, type ResolvedPerformanceConfig as e, SDK_VERSION as f, getActiveMonitor as g, resolveConfig as r };
|