@observertc/observer-js 1.0.0-beta.15 → 1.0.0-beta.17
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/README.md +254 -25
- package/dist/index.d.mts +554 -140
- package/dist/index.d.ts +554 -140
- package/dist/index.js +436 -105
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +433 -104
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2376,8 +2376,117 @@ type OperationSystem = {
|
|
|
2376
2376
|
};
|
|
2377
2377
|
|
|
2378
2378
|
/**
|
|
2379
|
-
*
|
|
2380
|
-
*
|
|
2379
|
+
* Turning a correlation into a **conclusion**.
|
|
2380
|
+
*
|
|
2381
|
+
* Every detector in this library ultimately reports the same shape of observation: *N clients have
|
|
2382
|
+
* issue X open at once, and here is what they have in common*. That is useful but not yet
|
|
2383
|
+
* actionable — someone still has to know that congestion spread across unrelated calls means the
|
|
2384
|
+
* server, while CPU limitation spread across unrelated calls means a bad client release. This module
|
|
2385
|
+
* holds that interpretation step so it is stated once, consistently, instead of being re-derived by
|
|
2386
|
+
* whoever reads the alert at 3am.
|
|
2387
|
+
*
|
|
2388
|
+
* ### Two functions, because there are two questions
|
|
2389
|
+
*
|
|
2390
|
+
* A detector already knows its scope — it was constructed with an `ObservedCall` or with the
|
|
2391
|
+
* `Observer`. Handing that scope back to a single generic function meant every caller supplied
|
|
2392
|
+
* fields the other scope needed and its own scope ignored: a call-scoped detector passing
|
|
2393
|
+
* `affectedCalls: 1, totalCalls: 1` forever, an observer-scoped one passing a participant ratio that
|
|
2394
|
+
* was deliberately never read. Placeholders like that are a standing invitation to read them as if
|
|
2395
|
+
* they meant something.
|
|
2396
|
+
*
|
|
2397
|
+
* So there are two entry points, each taking only the facts its scope actually has:
|
|
2398
|
+
*
|
|
2399
|
+
* - {@link concludeCallIssue} — within one call. The axis is *how much of the meeting*, and whether
|
|
2400
|
+
* the affected clients all subscribe to one published track.
|
|
2401
|
+
* - {@link concludeObserverIssue} — across calls. The axis is *how many independent calls*, which is
|
|
2402
|
+
* the only thing that separates "one bad room" from "our infrastructure".
|
|
2403
|
+
*
|
|
2404
|
+
* Neither the issue family nor the spread concludes anything alone: congestion in one call is a
|
|
2405
|
+
* meeting problem, congestion in six calls is an infrastructure problem, and the issue type is
|
|
2406
|
+
* identical in both.
|
|
2407
|
+
*/
|
|
2408
|
+
/** Where the fault most likely sits, given who is affected. */
|
|
2409
|
+
type IssueFaultDomain =
|
|
2410
|
+
/** Independent calls affected at once — they share only the servers and the network. */
|
|
2411
|
+
'infrastructure'
|
|
2412
|
+
/** One call, broadly affected — something that call shares (its SFU worker, room, or host). */
|
|
2413
|
+
| 'call'
|
|
2414
|
+
/** The subscribers of one published track — the publisher's path or the forwarding of it. */
|
|
2415
|
+
| 'published-track'
|
|
2416
|
+
/** A single endpoint — its own device or last mile. */
|
|
2417
|
+
| 'endpoint'
|
|
2418
|
+
/** Independent calls affected, but by something endpoints own — a client build, not a server. */
|
|
2419
|
+
| 'client-population'
|
|
2420
|
+
/** Not enough signal to attribute. */
|
|
2421
|
+
| 'unknown';
|
|
2422
|
+
/** A stated verdict, attached to the raised issue payload. */
|
|
2423
|
+
type IssueConclusion = {
|
|
2424
|
+
/** Where to look. */
|
|
2425
|
+
faultDomain: IssueFaultDomain;
|
|
2426
|
+
/** One line, written to be readable in an alert without opening a dashboard. */
|
|
2427
|
+
summary: string;
|
|
2428
|
+
/** What to check first. Omitted when the issue family is unknown to this module. */
|
|
2429
|
+
recommendation?: string;
|
|
2430
|
+
/**
|
|
2431
|
+
* How much the spread alone justifies the verdict, `0..1`. Not a probability — a coarse ranking
|
|
2432
|
+
* so alerting can threshold on it. More independent calls, or a tighter onset, means higher.
|
|
2433
|
+
*/
|
|
2434
|
+
confidence: number;
|
|
2435
|
+
};
|
|
2436
|
+
/** The facts a **call-scoped** conclusion is drawn from. */
|
|
2437
|
+
type CallIssueSpread = {
|
|
2438
|
+
issueType: string;
|
|
2439
|
+
/** Distinct clients of this call with the issue open. */
|
|
2440
|
+
affectedClients: number;
|
|
2441
|
+
/** Participants in the call — the denominator. */
|
|
2442
|
+
totalClients: number;
|
|
2443
|
+
/** True when the onsets clustered — a shared trigger rather than drift. */
|
|
2444
|
+
onsetBurst: boolean;
|
|
2445
|
+
/**
|
|
2446
|
+
* Set when the affected clients are the subscriber set of **one published track**.
|
|
2447
|
+
*
|
|
2448
|
+
* The strongest call-scoped statement available: those clients share a publisher and nothing
|
|
2449
|
+
* else, so the receivers are exonerated and the source's path is implicated.
|
|
2450
|
+
*/
|
|
2451
|
+
publishedTrackId?: string;
|
|
2452
|
+
};
|
|
2453
|
+
/** The facts an **observer-scoped** conclusion is drawn from. */
|
|
2454
|
+
type ObserverIssueSpread = {
|
|
2455
|
+
issueType: string;
|
|
2456
|
+
/** Distinct clients across the fleet with the issue open. */
|
|
2457
|
+
affectedClients: number;
|
|
2458
|
+
/** Clients in the fleet. Reported for context; it does not gate anything at this scope. */
|
|
2459
|
+
totalClients: number;
|
|
2460
|
+
/** Distinct calls containing at least one affected client. The dimension that matters here. */
|
|
2461
|
+
affectedCalls: number;
|
|
2462
|
+
/** Calls in flight. */
|
|
2463
|
+
totalCalls: number;
|
|
2464
|
+
/** True when the onsets clustered. */
|
|
2465
|
+
onsetBurst: boolean;
|
|
2466
|
+
};
|
|
2467
|
+
/**
|
|
2468
|
+
* Draw the conclusion for a group of clients **within one call**.
|
|
2469
|
+
*
|
|
2470
|
+
* Ordered most-to-least specific: a track-scoped group is a stronger statement than a call-wide one,
|
|
2471
|
+
* and a single affected endpoint is not a statement about the call at all.
|
|
2472
|
+
*/
|
|
2473
|
+
declare function concludeCallIssue(spread: CallIssueSpread): IssueConclusion;
|
|
2474
|
+
/**
|
|
2475
|
+
* Draw the conclusion for a group of clients spanning **several calls**.
|
|
2476
|
+
*
|
|
2477
|
+
* One affected call is not an observer-scoped finding — it has an obvious local explanation and the
|
|
2478
|
+
* call-scoped detector has already reported it — so that case returns `call` and says so rather than
|
|
2479
|
+
* dressing it up as a fleet event.
|
|
2480
|
+
*
|
|
2481
|
+
* Which domain breadth implicates depends on the family, and this is the whole reason the module
|
|
2482
|
+
* exists: `congestion` across unrelated calls points at the servers, `cpulimitation` across unrelated
|
|
2483
|
+
* calls points at what those *endpoints* share — a client release, a browser version, shared
|
|
2484
|
+
* virtualised hardware — and pointing an SFU team at the second one wastes a night.
|
|
2485
|
+
*/
|
|
2486
|
+
declare function concludeObserverIssue(spread: ObserverIssueSpread): IssueConclusion;
|
|
2487
|
+
|
|
2488
|
+
/**
|
|
2489
|
+
* What every server-raised finding carries, whatever raised it.
|
|
2381
2490
|
*
|
|
2382
2491
|
* ### Why this is not `ClientIssue`
|
|
2383
2492
|
*
|
|
@@ -2385,38 +2494,209 @@ type OperationSystem = {
|
|
|
2385
2494
|
* string. Server-raised findings were reusing it, which forced every detector to `JSON.stringify` a
|
|
2386
2495
|
* perfectly good object on the way out and every handler to `JSON.parse` it back on the way in —
|
|
2387
2496
|
* paying serialisation on a path where nothing is ever serialised, and losing type information in
|
|
2388
|
-
* both directions.
|
|
2389
|
-
*
|
|
2390
|
-
* An observer issue goes straight to an in-process event handler, so it carries the object.
|
|
2497
|
+
* both directions. These go straight to an in-process handler, so they carry the object.
|
|
2391
2498
|
*/
|
|
2392
|
-
type
|
|
2499
|
+
type IssueBase = {
|
|
2393
2500
|
/** What was found, e.g. `'CROSS_CALL_ISSUE_ONSET_BURST'`. */
|
|
2394
2501
|
type: string;
|
|
2395
2502
|
/** Observer clock, when the finding was raised. */
|
|
2396
2503
|
timestamp: number;
|
|
2397
2504
|
/**
|
|
2398
|
-
*
|
|
2505
|
+
* What the finding *means* — where to look, and how much the evidence justifies it.
|
|
2399
2506
|
*
|
|
2400
|
-
*
|
|
2401
|
-
*
|
|
2402
|
-
*
|
|
2507
|
+
* A first-class field rather than a key inside {@link payload}, because it is the one part every
|
|
2508
|
+
* finding has in common and the one part an alerting rule reads. Burying it in the evidence made
|
|
2509
|
+
* `payload.conclusion.faultDomain` the path to the most important thing in the object.
|
|
2510
|
+
*/
|
|
2511
|
+
conclusion?: IssueConclusion;
|
|
2512
|
+
/**
|
|
2513
|
+
* The evidence, and **only** the evidence.
|
|
2514
|
+
*
|
|
2515
|
+
* Deliberately does not repeat `type`, `scope`, or the ids already present on the event that
|
|
2516
|
+
* delivers it. A payload that restates its envelope invites the two to disagree — and they did,
|
|
2517
|
+
* because nothing kept them in step.
|
|
2518
|
+
*/
|
|
2519
|
+
payload?: Record<string, unknown>;
|
|
2520
|
+
};
|
|
2521
|
+
/**
|
|
2522
|
+
* A finding about **one call**, raised by `observedCall.addIssue()` and delivered as `call-issue`.
|
|
2523
|
+
*
|
|
2524
|
+
* The call is the event's scope (`{ observedCall, observer }`), so the payload does not carry a
|
|
2525
|
+
* `callId` — read it from the event.
|
|
2526
|
+
*/
|
|
2527
|
+
type CallIssue = IssueBase & {
|
|
2528
|
+
scope: 'call';
|
|
2529
|
+
};
|
|
2530
|
+
/**
|
|
2531
|
+
* A finding about the **fleet**, raised by `observer.addIssue()` and delivered as `observer-issue`.
|
|
2532
|
+
*
|
|
2533
|
+
* Raised by detectors and validators that reason across calls, so no single call owns it. Where the
|
|
2534
|
+
* finding does concern specific calls — a cross-call correlation, say — they are named in the
|
|
2535
|
+
* evidence, because that *is* the evidence.
|
|
2536
|
+
*/
|
|
2537
|
+
type ObserverIssue = IssueBase & {
|
|
2538
|
+
scope: 'observer';
|
|
2539
|
+
};
|
|
2540
|
+
/**
|
|
2541
|
+
* Either kind, discriminated by {@link IssueBase} + `scope`.
|
|
2542
|
+
*
|
|
2543
|
+
* `scope` is on the issue and not merely implied by which event fired, so a finding stays
|
|
2544
|
+
* self-describing once it leaves the bus — funnelled into one handler, a log line, or a queue.
|
|
2545
|
+
*/
|
|
2546
|
+
type Issue = CallIssue | ObserverIssue;
|
|
2547
|
+
/**
|
|
2548
|
+
* The payload as a JSON string, for the boundaries that genuinely need text — a log line, an HTTP
|
|
2549
|
+
* body, a message queue.
|
|
2550
|
+
*
|
|
2551
|
+
* Returns `undefined` for a missing payload, or for one that cannot be serialised (a circular
|
|
2552
|
+
* reference from something an application attached): the caller wanted text, not an exception.
|
|
2553
|
+
*/
|
|
2554
|
+
declare function issuePayloadAsString(issue: Pick<IssueBase, 'payload'>): string | undefined;
|
|
2555
|
+
|
|
2556
|
+
/**
|
|
2557
|
+
* The bus events that carry an `observedCall`, i.e. the ones an enricher can attribute to a summary.
|
|
2558
|
+
*
|
|
2559
|
+
* Derived from the event map rather than listed by hand, so it cannot drift: adding a call-scoped
|
|
2560
|
+
* event makes it enrichable automatically, and an enricher on an observer-scoped event
|
|
2561
|
+
* (`observer-issue`, `validation-ready`) will not compile — there is no single call it belongs to,
|
|
2562
|
+
* and quietly writing a fleet-wide fact into every open summary would be worse than a type error.
|
|
2563
|
+
*/
|
|
2564
|
+
type CallScopedEventName = {
|
|
2565
|
+
[K in keyof ObserverEvents]: ObserverEvents[K][0] extends ObservedCallScope ? K : never;
|
|
2566
|
+
}[keyof ObserverEvents];
|
|
2567
|
+
/** A function that folds one event into the summary. Runs on every occurrence, for its own call. */
|
|
2568
|
+
type CallSummaryEnricher<K extends CallScopedEventName> = (summary: CallSummary, ...args: ObserverEvents[K]) => void;
|
|
2569
|
+
/** The enricher map: any subset of the call-scoped events, each fully typed against its payload. */
|
|
2570
|
+
type CallSummaryEnrichers = {
|
|
2571
|
+
[K in CallScopedEventName]?: CallSummaryEnricher<K>;
|
|
2572
|
+
};
|
|
2573
|
+
/** The built-in sections. Ask for what you want; anything absent is simply not collected. */
|
|
2574
|
+
type CallSummarySection = 'clients' | 'issues' | 'turnServers' | 'scores';
|
|
2575
|
+
type CallSummaryConfig = {
|
|
2576
|
+
/**
|
|
2577
|
+
* Which built-in sections to accumulate. Empty (the default) collects none of them — a summary
|
|
2578
|
+
* with only `enrich` is a perfectly good summary.
|
|
2403
2579
|
*/
|
|
2404
|
-
|
|
2580
|
+
include: CallSummarySection[];
|
|
2581
|
+
/** Fold arbitrary state in from any call-scoped event. See {@link CallSummaryEnrichers}. */
|
|
2582
|
+
enrich?: CallSummaryEnrichers;
|
|
2583
|
+
/** Cap on the retained issue log. Default `500`. See the note on truncation. */
|
|
2584
|
+
maxIssues: number;
|
|
2585
|
+
/** Cap on retained client ids. Default `10_000`. */
|
|
2586
|
+
maxClientIds: number;
|
|
2405
2587
|
};
|
|
2406
2588
|
/**
|
|
2407
|
-
*
|
|
2589
|
+
* Who was in the call over its whole life — not just who is in it now.
|
|
2408
2590
|
*
|
|
2409
|
-
*
|
|
2410
|
-
*
|
|
2591
|
+
* Deliberately identifiers and counts only. Anything *about* a client — browser, platform, region —
|
|
2592
|
+
* is already on `observedClient` while the call is live, and belongs in `attachments` via an enricher
|
|
2593
|
+
* if you want it kept. Duplicating it here would mean the library deciding which client attributes
|
|
2594
|
+
* matter, and it would mean reading the highest-frequency event on the bus to do it.
|
|
2411
2595
|
*/
|
|
2412
|
-
|
|
2596
|
+
type CallSummaryClients = {
|
|
2597
|
+
/** Every client id seen, in join order, capped by `maxClientIds`. */
|
|
2598
|
+
clientIds: string[];
|
|
2599
|
+
/** The most participants present at any one moment. */
|
|
2600
|
+
peak: number;
|
|
2601
|
+
joined: number;
|
|
2602
|
+
left: number;
|
|
2603
|
+
};
|
|
2604
|
+
/** Which TURN relays carried this call's media. */
|
|
2605
|
+
type CallSummaryTurnServers = {
|
|
2606
|
+
serverUrls: string[];
|
|
2607
|
+
/** Distinct clients seen relaying through any of them. */
|
|
2608
|
+
clientsRelayed: number;
|
|
2609
|
+
};
|
|
2610
|
+
/** The call score over time. Percentiles, not a mean — see `utils/stats`. */
|
|
2611
|
+
type CallSummaryScores = {
|
|
2612
|
+
min?: number;
|
|
2613
|
+
max?: number;
|
|
2614
|
+
median?: number;
|
|
2615
|
+
/** How many score readings went into the above. `0` means nothing was measured. */
|
|
2616
|
+
samples: number;
|
|
2617
|
+
};
|
|
2618
|
+
/** What had to be dropped to stay within the caps. Absent when nothing was. */
|
|
2619
|
+
type CallSummaryTruncation = {
|
|
2620
|
+
issues?: number;
|
|
2621
|
+
clientIds?: number;
|
|
2622
|
+
};
|
|
2413
2623
|
/**
|
|
2414
|
-
*
|
|
2624
|
+
* An accumulating record of one call's life, finalised when the call closes.
|
|
2625
|
+
*
|
|
2626
|
+
* ### Why this exists
|
|
2415
2627
|
*
|
|
2416
|
-
*
|
|
2417
|
-
*
|
|
2628
|
+
* Everything else in this library is about *now*. Detectors answer "is something wrong right now",
|
|
2629
|
+
* validators answer a structural question once, and both read state that the call throws away when
|
|
2630
|
+
* it ends. Nothing kept the answer to *"what happened in that meeting?"* — who was in it, what was
|
|
2631
|
+
* raised, how it scored — and that is the question asked after the call, by support, by billing, by
|
|
2632
|
+
* whoever is writing the incident note.
|
|
2633
|
+
*
|
|
2634
|
+
* ### It is opt-in, and its sections are opt-in
|
|
2635
|
+
*
|
|
2636
|
+
* `observedCall.summary` is `undefined` unless a summary was configured, and each section is present
|
|
2637
|
+
* only if it was requested. **An absent section means "not collected", never "nothing happened"** —
|
|
2638
|
+
* the same rule as `inconclusive` on a validator. Reading `summary.issues` as "this call had no
|
|
2639
|
+
* issues" when `'issues'` was never in `include` is the one misreading this type invites, so it does
|
|
2640
|
+
* not offer a default-empty section to make it easy.
|
|
2641
|
+
*
|
|
2642
|
+
* ### Read it live, receive it once
|
|
2643
|
+
*
|
|
2644
|
+
* The object is live: read `observedCall.summary` at any point during the call. It is also delivered
|
|
2645
|
+
* on the `call-summary` event, emitted inside `close()` while the call is still reachable — after
|
|
2646
|
+
* that the call is gone from `observer.observedCalls` and there is nothing left to ask.
|
|
2418
2647
|
*/
|
|
2419
|
-
|
|
2648
|
+
type CallSummary = {
|
|
2649
|
+
callId: string;
|
|
2650
|
+
/** First client join (client clock), as `ObservedCall` computed it. */
|
|
2651
|
+
startedAt?: number;
|
|
2652
|
+
/** Last client leave. */
|
|
2653
|
+
endedAt?: number;
|
|
2654
|
+
/** `endedAt - startedAt`, when both are known. */
|
|
2655
|
+
durationInMs?: number;
|
|
2656
|
+
/** When the summary itself was finalised (observer clock). Set by `close()`. */
|
|
2657
|
+
closedAt?: number;
|
|
2658
|
+
clients?: CallSummaryClients;
|
|
2659
|
+
/**
|
|
2660
|
+
* Every issue raised against this call, in the order they were raised, capped by `maxIssues`.
|
|
2661
|
+
*
|
|
2662
|
+
* Just the issues — no derived tallies. A count is `issues.length`, a per-type count is one
|
|
2663
|
+
* `filter`, and either is cheaper to write at the call site than to keep correct here. The one
|
|
2664
|
+
* thing you cannot derive is what the cap discarded, which is why `truncated.issues` exists:
|
|
2665
|
+
* the issues actually raised is `issues.length + (truncated?.issues ?? 0)`.
|
|
2666
|
+
*/
|
|
2667
|
+
issues?: CallIssue[];
|
|
2668
|
+
turnServers?: CallSummaryTurnServers;
|
|
2669
|
+
scores?: CallSummaryScores;
|
|
2670
|
+
/**
|
|
2671
|
+
* Whatever your enrichers put here. The library never writes to it, so it cannot collide with a
|
|
2672
|
+
* section added in a future version.
|
|
2673
|
+
*
|
|
2674
|
+
* **`attachments`, not `appData`, and the distinction is load-bearing.** `appData` is the live
|
|
2675
|
+
* working state an application hangs off an entity for the entity's lifetime, and it may hold
|
|
2676
|
+
* references that cannot be serialised — a mediasoup router, an `RTCPeerConnection`, a socket. A
|
|
2677
|
+
* summary is the opposite: it outlives the call precisely so it can be *shipped* — archived,
|
|
2678
|
+
* queued, written to a column — and it is handed to you on `call-summary` at the moment the call
|
|
2679
|
+
* it came from is being torn down. Anything unserialisable in it is a reference to something
|
|
2680
|
+
* already gone.
|
|
2681
|
+
*
|
|
2682
|
+
* So put serialisable facts here, the same contract as `attachments` on a `ClientSample`. If you
|
|
2683
|
+
* need the live object, read it off `observedCall` / `observedClient` inside the enricher and
|
|
2684
|
+
* attach what you can serialise: the router's `id`, not the router.
|
|
2685
|
+
*/
|
|
2686
|
+
attachments: Record<string, unknown>;
|
|
2687
|
+
/**
|
|
2688
|
+
* What the caps discarded, and how much.
|
|
2689
|
+
*
|
|
2690
|
+
* Present **only** when something was actually dropped. A silently truncated summary is worse
|
|
2691
|
+
* than no summary — someone will count `log.length` and report it as the issue count — so the
|
|
2692
|
+
* shortfall is stated rather than left to be inferred from a suspiciously round number.
|
|
2693
|
+
*/
|
|
2694
|
+
truncated?: CallSummaryTruncation;
|
|
2695
|
+
};
|
|
2696
|
+
/** The defaults a summary is created with. Caps are generous but finite; see {@link CallSummary}. */
|
|
2697
|
+
declare const defaultCallSummaryConfig: CallSummaryConfig;
|
|
2698
|
+
/** A fresh summary for `callId`, with only the requested sections present. */
|
|
2699
|
+
declare function createCallSummary(callId: string, config: CallSummaryConfig): CallSummary;
|
|
2420
2700
|
|
|
2421
2701
|
/**
|
|
2422
2702
|
* What a validator concluded, once it is done.
|
|
@@ -2468,8 +2748,13 @@ interface Validator<S extends Record<string, unknown> = Record<string, unknown>>
|
|
|
2468
2748
|
onDone: (report: ValidationReport<S>) => void;
|
|
2469
2749
|
/** Gather evidence; decide if there is now enough. Called on every `observer.update()`. */
|
|
2470
2750
|
update(): void;
|
|
2471
|
-
/**
|
|
2472
|
-
|
|
2751
|
+
/**
|
|
2752
|
+
* Give up without a verdict. Finishes with `inconclusive`, so a caller waiting on it is freed.
|
|
2753
|
+
*
|
|
2754
|
+
* `reason` is carried into the report. Worth passing something specific — "cancelled" tells the
|
|
2755
|
+
* reader nothing, whereas "sfu redeployed" explains why a check that was running has no verdict.
|
|
2756
|
+
*/
|
|
2757
|
+
cancel: (reason?: string) => void;
|
|
2473
2758
|
}
|
|
2474
2759
|
/**
|
|
2475
2760
|
* The part of a validator the observer needs in order to drive it.
|
|
@@ -2959,7 +3244,15 @@ type ObserverEvents = {
|
|
|
2959
3244
|
'call-empty': [ObservedCallScope];
|
|
2960
3245
|
'call-not-empty': [ObservedCallScope];
|
|
2961
3246
|
'call-issue': [ObservedCallScope & {
|
|
2962
|
-
issue:
|
|
3247
|
+
issue: CallIssue;
|
|
3248
|
+
}];
|
|
3249
|
+
/**
|
|
3250
|
+
* A call's summary was finalised. Emitted from inside `close()`, while the call is still in
|
|
3251
|
+
* `observer.observedCalls` — after that there is nothing left to ask. Only fires for calls that
|
|
3252
|
+
* had a summary configured.
|
|
3253
|
+
*/
|
|
3254
|
+
'call-summary': [ObservedCallScope & {
|
|
3255
|
+
summary: CallSummary;
|
|
2963
3256
|
}];
|
|
2964
3257
|
'client-added': [ObservedClientScope];
|
|
2965
3258
|
'client-sink-created': [ObservedClientScope & {
|
|
@@ -4412,11 +4705,48 @@ type AvailableDetectorsConfigs = AvailableObserverScopeDetectorsConfigs | Availa
|
|
|
4412
4705
|
declare class Detectors {
|
|
4413
4706
|
private _detectors;
|
|
4414
4707
|
constructor(...detectors: Detector[]);
|
|
4708
|
+
/**
|
|
4709
|
+
* Every registered detector, in registration order.
|
|
4710
|
+
*
|
|
4711
|
+
* This is **the** way to get hold of an instance: `addDetector` / `addObserverDetector` are
|
|
4712
|
+
* chainable and return the owning entity, so the registry is where instances live. Read it to
|
|
4713
|
+
* inspect a detector's state, or to pick one out and {@link remove} it.
|
|
4714
|
+
*
|
|
4715
|
+
* A copy, not the live array — a caller iterating this while removing would otherwise skip
|
|
4716
|
+
* entries, and that is exactly what "remove the ones that look like X" does.
|
|
4717
|
+
*/
|
|
4718
|
+
get instances(): Detector[];
|
|
4719
|
+
/** Iterate the registry directly: `for (const detector of call.detectors)`. */
|
|
4720
|
+
[Symbol.iterator](): IterableIterator<Detector>;
|
|
4721
|
+
/** The names in registration order. Duplicates are meaningful — see {@link getAll}. */
|
|
4415
4722
|
get listOfNames(): string[];
|
|
4416
4723
|
get size(): number;
|
|
4417
4724
|
add(detector: Detector): void;
|
|
4725
|
+
/** The first detector registered under `name`. See {@link getAll} when several can share one. */
|
|
4418
4726
|
get(name: string): Detector | undefined;
|
|
4419
|
-
|
|
4727
|
+
/**
|
|
4728
|
+
* Every detector registered under `name`.
|
|
4729
|
+
*
|
|
4730
|
+
* More than one is legitimate: `ClientPopulationIssueDetector` is meant to be added once per
|
|
4731
|
+
* `groupBy` axis, and two instances of it share a name.
|
|
4732
|
+
*/
|
|
4733
|
+
getAll(name: string): Detector[];
|
|
4734
|
+
has(name: string): boolean;
|
|
4735
|
+
/** Remove one specific instance. Returns `false` if it was not registered here. */
|
|
4736
|
+
remove(detector: Detector): boolean;
|
|
4737
|
+
/**
|
|
4738
|
+
* Remove **every** detector registered under `name`, returning how many were removed.
|
|
4739
|
+
*
|
|
4740
|
+
* All of them rather than the first, because a name can legitimately be registered more than once
|
|
4741
|
+
* (see {@link getAll}) and "remove the `client-population-issue-detector`" cannot sensibly mean
|
|
4742
|
+
* "remove whichever axis happens to be first in the array". Removing all of them is the only
|
|
4743
|
+
* behaviour that leaves the registry in a state the caller can predict from the name alone.
|
|
4744
|
+
*
|
|
4745
|
+
* Each removed detector gets `close()`, so trackers unsubscribe from the issue registry, bus
|
|
4746
|
+
* listeners drop, and timers clear — a detector removed without closing keeps being fed issues
|
|
4747
|
+
* forever.
|
|
4748
|
+
*/
|
|
4749
|
+
removeByName(name: string): number;
|
|
4420
4750
|
update(): void;
|
|
4421
4751
|
clear(): void;
|
|
4422
4752
|
private _close;
|
|
@@ -4533,6 +4863,14 @@ declare class ObservedCall<AppData extends Record<string, unknown> = Record<stri
|
|
|
4533
4863
|
readonly clientsUsedTurn: Set<string>;
|
|
4534
4864
|
readonly calculatedScore: CalculatedScore;
|
|
4535
4865
|
remoteTrackResolver?: RemoteTrackResolver;
|
|
4866
|
+
/**
|
|
4867
|
+
* The accumulating record of this call's life, or `undefined` when no summary was configured.
|
|
4868
|
+
*
|
|
4869
|
+
* Live — read it at any point during the call. It is also delivered once on `call-summary` when
|
|
4870
|
+
* the call closes. See `CallSummary`: an absent section means "not collected", never "nothing
|
|
4871
|
+
* happened".
|
|
4872
|
+
*/
|
|
4873
|
+
summary?: CallSummary;
|
|
4536
4874
|
/**
|
|
4537
4875
|
* Published tracks that currently have **no** subscriber linked to them.
|
|
4538
4876
|
*
|
|
@@ -4565,14 +4903,53 @@ declare class ObservedCall<AppData extends Record<string, unknown> = Record<stri
|
|
|
4565
4903
|
constructor(settings: ObservedCallSettings<AppData>, observer: Observer, activeIssuesRegistry: ActiveIssuesRegistry);
|
|
4566
4904
|
get numberOfClients(): number;
|
|
4567
4905
|
get score(): number | undefined;
|
|
4906
|
+
/**
|
|
4907
|
+
* Build a call-scoped detector onto this call. Chainable.
|
|
4908
|
+
*
|
|
4909
|
+
* To get a handle on what was built — to inspect it, or to remove that exact instance later — read
|
|
4910
|
+
* it back off the registry: `call.detectors.getAll(name)`, or `call.detectors.instances`.
|
|
4911
|
+
*/
|
|
4568
4912
|
addDetector<K extends keyof AvailableCallScopeDetectorsConfigs>(name: K, config?: Partial<AvailableCallScopeDetectorsConfigs[K]>): this;
|
|
4913
|
+
/**
|
|
4914
|
+
* Start accumulating this call's summary, if the observer was configured for summaries.
|
|
4915
|
+
*
|
|
4916
|
+
* Called by `createObservedCall`; you should not need it. It takes no configuration of its own on
|
|
4917
|
+
* purpose: the collector subscribes to exactly the events the observer's `include` requires, so a
|
|
4918
|
+
* per-call section outside that set would be created and then never written to — an empty section
|
|
4919
|
+
* that reads as "nothing happened". One shape per observer is the only shape that can be filled.
|
|
4920
|
+
*
|
|
4921
|
+
* The collector builds it rather than this method, so the resolved configuration never has to
|
|
4922
|
+
* leave the one object that owns it. Returns `undefined` when summaries are off, and is
|
|
4923
|
+
* idempotent: an existing summary is kept, not restarted.
|
|
4924
|
+
*/
|
|
4925
|
+
enableSummary(): CallSummary | undefined;
|
|
4926
|
+
/**
|
|
4927
|
+
* Remove a detector from **this call** by name, returning how many were removed.
|
|
4928
|
+
*
|
|
4929
|
+
* **Every** instance under the name goes — a name can legitimately be registered more than once.
|
|
4930
|
+
* When you want one of them specifically, go through the registry, which deals in instances:
|
|
4931
|
+
*
|
|
4932
|
+
* ```ts
|
|
4933
|
+
* const [ first ] = call.detectors.getAll('issue-fan-out-detector');
|
|
4934
|
+
*
|
|
4935
|
+
* call.detectors.remove(first);
|
|
4936
|
+
* ```
|
|
4937
|
+
*
|
|
4938
|
+
* Either route `close()`s the detector, so it unsubscribes from `activeIssuesRegistry` — without
|
|
4939
|
+
* that the registry keeps feeding a detector nobody is running any more, and its tracked set grows
|
|
4940
|
+
* for the life of the call.
|
|
4941
|
+
*
|
|
4942
|
+
* To stop building it on *future* calls too, use `observer.removeCallDetector(name)`.
|
|
4943
|
+
*/
|
|
4944
|
+
removeDetector(name: keyof AvailableCallScopeDetectorsConfigs): number;
|
|
4569
4945
|
/**
|
|
4570
4946
|
* Raise a call-level (server-side) finding; surfaced on the Observer bus as `call-issue`.
|
|
4571
4947
|
*
|
|
4572
|
-
* `payload`
|
|
4573
|
-
* serialise for.
|
|
4948
|
+
* `payload` is an **object** and holds evidence only — it is delivered to an in-process handler,
|
|
4949
|
+
* so there is nothing to serialise for. `scope` is stamped here, and the `callId` is already on
|
|
4950
|
+
* the event, so neither belongs in the payload. Put the interpretation in `conclusion`.
|
|
4574
4951
|
*/
|
|
4575
|
-
addIssue(issue:
|
|
4952
|
+
addIssue(issue: Omit<CallIssue, 'scope'>): void;
|
|
4576
4953
|
close(): void;
|
|
4577
4954
|
getObservedClient<ClientAppData extends Record<string, unknown> = Record<string, unknown>>(clientId: string): ObservedClient<ClientAppData> | undefined;
|
|
4578
4955
|
createObservedClient<ClientAppData extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedClientSettings<ClientAppData>): ObservedClient<ClientAppData> | undefined;
|
|
@@ -4973,6 +5350,62 @@ type AvailableValidatorConfigs = {
|
|
|
4973
5350
|
/** A validator name that can be started. */
|
|
4974
5351
|
type ValidatorName = keyof AvailableValidatorConfigs;
|
|
4975
5352
|
|
|
5353
|
+
/**
|
|
5354
|
+
* Keeps every configured `CallSummary` up to date, from **observer-level** bus subscriptions.
|
|
5355
|
+
*
|
|
5356
|
+
* ### Why one collector and not one per call
|
|
5357
|
+
*
|
|
5358
|
+
* The obvious implementation subscribes each call's summary to the events it needs. But the bus is
|
|
5359
|
+
* observer-wide: a listener attached for call A is invoked for every event of every call, so that
|
|
5360
|
+
* design costs `calls × events` listeners *and* `calls` invocations per event — quadratic in the
|
|
5361
|
+
* thing most likely to be large. At 500 concurrent calls and eight subscribed events that is 4 000
|
|
5362
|
+
* listeners doing 500 no-op calls each, per event.
|
|
5363
|
+
*
|
|
5364
|
+
* So the collector attaches **one listener per event type, once**, and routes each event to the
|
|
5365
|
+
* summary of the call it names. Cost is O(subscribed event types), independent of how many calls are
|
|
5366
|
+
* in flight, and an event for a call with no summary costs one `undefined` check.
|
|
5367
|
+
*
|
|
5368
|
+
* ### Only call-scoped events
|
|
5369
|
+
*
|
|
5370
|
+
* Routing needs `observedCall` on the payload, which is exactly what `CallScopedEventName` selects.
|
|
5371
|
+
* Observer-scoped events have no single call to attribute to; see that type for why fanning them out
|
|
5372
|
+
* to every open summary would be worse than refusing.
|
|
5373
|
+
*/
|
|
5374
|
+
declare class CallSummaryCollector {
|
|
5375
|
+
private readonly _observer;
|
|
5376
|
+
private readonly _config;
|
|
5377
|
+
private readonly _scratch;
|
|
5378
|
+
private readonly _listeners;
|
|
5379
|
+
private _closed;
|
|
5380
|
+
constructor(_observer: Observer, _config: CallSummaryConfig);
|
|
5381
|
+
/**
|
|
5382
|
+
* Build a summary for `callId` and start tracking it.
|
|
5383
|
+
*
|
|
5384
|
+
* Creating it here, rather than letting the call create one and hand it over, keeps the resolved
|
|
5385
|
+
* configuration inside the single object that owns it — and makes it impossible to end up with a
|
|
5386
|
+
* summary whose sections nobody subscribed to fill.
|
|
5387
|
+
*/
|
|
5388
|
+
createSummary(callId: string): CallSummary;
|
|
5389
|
+
/**
|
|
5390
|
+
* Finalise `call`'s summary: fold in what only makes sense once, and stamp the closing times.
|
|
5391
|
+
*
|
|
5392
|
+
* Percentiles are computed here rather than on every update — a median recomputed per tick over a
|
|
5393
|
+
* growing array is quadratic work to produce a number nobody reads until the end.
|
|
5394
|
+
*/
|
|
5395
|
+
finalise(call: ObservedCall): void;
|
|
5396
|
+
/** Drop every bus subscription. Called when the observer closes. */
|
|
5397
|
+
close(): void;
|
|
5398
|
+
/**
|
|
5399
|
+
* Subscribe `listener` to `event`, routed to the summary of the call the event names.
|
|
5400
|
+
*
|
|
5401
|
+
* The `observedCall` is read off the payload rather than closed over, which is what lets one
|
|
5402
|
+
* subscription serve every call.
|
|
5403
|
+
*/
|
|
5404
|
+
private _on;
|
|
5405
|
+
private _subscribeBuiltIns;
|
|
5406
|
+
private _subscribeEnrichers;
|
|
5407
|
+
}
|
|
5408
|
+
|
|
4976
5409
|
type SampleRejectedReason = 'observer-closed' | 'missing-callId' | 'missing-clientId';
|
|
4977
5410
|
|
|
4978
5411
|
/**
|
|
@@ -5016,6 +5449,36 @@ type ObserverConfig<AppData extends Record<string, unknown> = Record<string, unk
|
|
|
5016
5449
|
* yourself, and note that observer-scoped detectors and validators run *nowhere else*.
|
|
5017
5450
|
*/
|
|
5018
5451
|
autoUpdateOnCallUpdate?: boolean;
|
|
5452
|
+
/**
|
|
5453
|
+
* Accumulate a {@link CallSummary} on every call this observer creates.
|
|
5454
|
+
*
|
|
5455
|
+
* **Absent or `null` means no summaries at all** — no accumulation, and not one bus subscription.
|
|
5456
|
+
* Pass an object (`{}` is valid) to switch it on; anything you leave out takes its default from
|
|
5457
|
+
* `defaultCallSummaryConfig`, including `include: []`, which collects *no* built-in section. A
|
|
5458
|
+
* summary that only runs `enrich` is a perfectly good summary.
|
|
5459
|
+
*
|
|
5460
|
+
* ```ts
|
|
5461
|
+
* const observer = new Observer({
|
|
5462
|
+
* callSummary: {
|
|
5463
|
+
* include: [ 'clients', 'issues' ],
|
|
5464
|
+
* enrich: {
|
|
5465
|
+
* 'client-joined': (summary, { observedClient }) => {
|
|
5466
|
+
* ((summary.attachments.regions ??= []) as string[]).push(String(observedClient.appData.region));
|
|
5467
|
+
* },
|
|
5468
|
+
* },
|
|
5469
|
+
* },
|
|
5470
|
+
* });
|
|
5471
|
+
*
|
|
5472
|
+
* observer.on('call-summary', ({ summary }) => archive(summary));
|
|
5473
|
+
* ```
|
|
5474
|
+
*
|
|
5475
|
+
* This is construction-time and fixed for the observer's life, unlike detectors, which are added
|
|
5476
|
+
* per call as an application decides what to watch. A summary is a record of what happened, and a
|
|
5477
|
+
* record you can turn on halfway through is a record with a hole in it — calls that started
|
|
5478
|
+
* earlier would carry different sections from calls that started later, with nothing on either to
|
|
5479
|
+
* say which. One shape for every call, or none.
|
|
5480
|
+
*/
|
|
5481
|
+
callSummary?: Partial<CallSummaryConfig> | null;
|
|
5019
5482
|
inboundTrackDegradationThresholds?: {
|
|
5020
5483
|
deltaFreezeCount: number;
|
|
5021
5484
|
framesDroppedRatio: number;
|
|
@@ -5110,9 +5573,23 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
5110
5573
|
* ```
|
|
5111
5574
|
*/
|
|
5112
5575
|
readonly callDetectorConfigs: Map<keyof AvailableCallScopeDetectorsConfigs, Partial<UnconsumedTrackDetectorConfig | TrackDeliveryMismatchDetectorConfig | CallConcurrentIssueDetectorConfig | IssueFanOutDetectorConfig | PublisherFaultCorroborationDetectorConfig>>;
|
|
5576
|
+
/**
|
|
5577
|
+
* Owns every call's summary: the resolved `config.callSummary`, the bus subscriptions that keep
|
|
5578
|
+
* the summaries current (one per event type, not one per call), and the summaries themselves.
|
|
5579
|
+
*
|
|
5580
|
+
* `undefined` when `config.callSummary` was absent or `null` — so its presence *is* the answer to
|
|
5581
|
+
* "are summaries on", and nothing is subscribed to anything.
|
|
5582
|
+
*/
|
|
5583
|
+
readonly callSummaryCollector?: CallSummaryCollector;
|
|
5113
5584
|
constructor(config?: Partial<ObserverConfig<AppData>>);
|
|
5114
5585
|
get numberOfCalls(): number;
|
|
5115
5586
|
get appData(): AppData | undefined;
|
|
5587
|
+
/**
|
|
5588
|
+
* Build a cross-call detector onto `observer.detectors`. Chainable.
|
|
5589
|
+
*
|
|
5590
|
+
* To get a handle on what was built — to inspect it, or to remove that exact instance later — read
|
|
5591
|
+
* it back off the registry: `observer.detectors.getAll(name)`, or `observer.detectors.instances`.
|
|
5592
|
+
*/
|
|
5116
5593
|
addObserverDetector<K extends keyof AvailableObserverScopeDetectorsConfigs>(name: K, config?: Partial<AvailableObserverScopeDetectorsConfigs[K]>): this;
|
|
5117
5594
|
/**
|
|
5118
5595
|
* Enable a call-scoped detector for calls created **from now on**.
|
|
@@ -5121,8 +5598,36 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
5121
5598
|
* built with. To add one to an existing call, use `observedCall.addDetector(...)` directly.
|
|
5122
5599
|
*/
|
|
5123
5600
|
addCallDetector<K extends keyof AvailableCallScopeDetectorsConfigs>(name: K, config?: Partial<AvailableCallScopeDetectorsConfigs[K]>): this;
|
|
5124
|
-
/**
|
|
5125
|
-
|
|
5601
|
+
/**
|
|
5602
|
+
* Remove an observer-scoped detector by name, returning how many were removed.
|
|
5603
|
+
*
|
|
5604
|
+
* **Every** instance registered under the name goes, since a name can legitimately be registered
|
|
5605
|
+
* more than once (`ClientPopulationIssueDetector` is meant to be added once per `groupBy` axis).
|
|
5606
|
+
* When you want one of them specifically, go through the registry, which deals in instances:
|
|
5607
|
+
*
|
|
5608
|
+
* ```ts
|
|
5609
|
+
* const [ byBrowser, byOs ] = observer.detectors.getAll('client-population-issue-detector');
|
|
5610
|
+
*
|
|
5611
|
+
* observer.detectors.remove(byOs); // keeps the browser axis running
|
|
5612
|
+
* ```
|
|
5613
|
+
*
|
|
5614
|
+
* Either route `close()`s the detector, so it unsubscribes from the issue registry and drops any
|
|
5615
|
+
* timers or bus listeners it held.
|
|
5616
|
+
*/
|
|
5617
|
+
removeObserverDetector(name: keyof AvailableObserverScopeDetectorsConfigs): number;
|
|
5618
|
+
/**
|
|
5619
|
+
* Stop building `name` on calls created from now on.
|
|
5620
|
+
*
|
|
5621
|
+
* By default this also removes it from the calls **already open**, so that "remove this detector"
|
|
5622
|
+
* means the same thing whether you say it before or after a call started — the alternative leaves
|
|
5623
|
+
* a fleet where the detector is live on some calls and not others, decided by join time. Pass
|
|
5624
|
+
* `{ includeOpenCalls: false }` to change only what future calls are built with.
|
|
5625
|
+
*
|
|
5626
|
+
* Returns the number of live detector instances removed (`0` when only the config changed).
|
|
5627
|
+
*/
|
|
5628
|
+
removeCallDetector(name: keyof AvailableCallScopeDetectorsConfigs, { includeOpenCalls }?: {
|
|
5629
|
+
includeOpenCalls?: boolean;
|
|
5630
|
+
}): number;
|
|
5126
5631
|
/**
|
|
5127
5632
|
* Start a structural check. It runs on each `observer.update()` until it can decide, reports once
|
|
5128
5633
|
* on `validation-ready`, and removes itself.
|
|
@@ -5136,6 +5641,25 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
5136
5641
|
* elapsed time is what makes a structural verdict stale.
|
|
5137
5642
|
*/
|
|
5138
5643
|
addValidator<K extends keyof AvailableValidatorConfigs>(name: K, config?: Partial<AvailableValidatorConfigs[K]>): this;
|
|
5644
|
+
/**
|
|
5645
|
+
* Stop a running validation, by name or by instance. Returns how many were cancelled.
|
|
5646
|
+
*
|
|
5647
|
+
* Cancelling is **not** silent discarding. The validator finishes with `inconclusive` and the given
|
|
5648
|
+
* `reason`, emits `validation-ready` like any other completion, and removes itself. That matters
|
|
5649
|
+
* because anything waiting on the verdict — a deploy gate, a dashboard, a promise — would otherwise
|
|
5650
|
+
* wait forever, and because "we stopped asking" is a materially different outcome from "we asked
|
|
5651
|
+
* and learned nothing", which is exactly what `inconclusive` with a reason records.
|
|
5652
|
+
*
|
|
5653
|
+
* ```ts
|
|
5654
|
+
* observer.cancelValidator('simulcast-receivers', 'sfu redeployed');
|
|
5655
|
+
*
|
|
5656
|
+
* // or one specific instance — `observer.validators` holds what is running
|
|
5657
|
+
* for (const validator of observer.validators) observer.cancelValidator(validator, 'shutting down');
|
|
5658
|
+
* ```
|
|
5659
|
+
*
|
|
5660
|
+
* Pass a real reason. The default tells the reader nothing they could not already infer.
|
|
5661
|
+
*/
|
|
5662
|
+
cancelValidator(target: keyof AvailableValidatorConfigs | RunningValidator, reason?: string): number;
|
|
5139
5663
|
getObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(callId: string): ObservedCall<T> | undefined;
|
|
5140
5664
|
createObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedCallSettings<T>): ObservedCall<T> | undefined;
|
|
5141
5665
|
getOrCreateObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedCallSettings<T>): ObservedCall<T> | undefined;
|
|
@@ -5151,7 +5675,7 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
5151
5675
|
*
|
|
5152
5676
|
* `payload` takes an **object**; see `ObserverIssue`.
|
|
5153
5677
|
*/
|
|
5154
|
-
addIssue(issue: ObserverIssue): void;
|
|
5678
|
+
addIssue(issue: Omit<ObserverIssue, 'scope'>): void;
|
|
5155
5679
|
/** Emit an Observer-bus event. */
|
|
5156
5680
|
private _notify;
|
|
5157
5681
|
}
|
|
@@ -5266,116 +5790,6 @@ declare class CallHealthAggregator {
|
|
|
5266
5790
|
private _clientHealth;
|
|
5267
5791
|
}
|
|
5268
5792
|
|
|
5269
|
-
/**
|
|
5270
|
-
* Turning a correlation into a **conclusion**.
|
|
5271
|
-
*
|
|
5272
|
-
* Every detector in this library ultimately reports the same shape of observation: *N clients have
|
|
5273
|
-
* issue X open at once, and here is what they have in common*. That is useful but not yet
|
|
5274
|
-
* actionable — someone still has to know that congestion spread across unrelated calls means the
|
|
5275
|
-
* server, while CPU limitation spread across unrelated calls means a bad client release. This module
|
|
5276
|
-
* holds that interpretation step so it is stated once, consistently, instead of being re-derived by
|
|
5277
|
-
* whoever reads the alert at 3am.
|
|
5278
|
-
*
|
|
5279
|
-
* ### Two functions, because there are two questions
|
|
5280
|
-
*
|
|
5281
|
-
* A detector already knows its scope — it was constructed with an `ObservedCall` or with the
|
|
5282
|
-
* `Observer`. Handing that scope back to a single generic function meant every caller supplied
|
|
5283
|
-
* fields the other scope needed and its own scope ignored: a call-scoped detector passing
|
|
5284
|
-
* `affectedCalls: 1, totalCalls: 1` forever, an observer-scoped one passing a participant ratio that
|
|
5285
|
-
* was deliberately never read. Placeholders like that are a standing invitation to read them as if
|
|
5286
|
-
* they meant something.
|
|
5287
|
-
*
|
|
5288
|
-
* So there are two entry points, each taking only the facts its scope actually has:
|
|
5289
|
-
*
|
|
5290
|
-
* - {@link concludeCallIssue} — within one call. The axis is *how much of the meeting*, and whether
|
|
5291
|
-
* the affected clients all subscribe to one published track.
|
|
5292
|
-
* - {@link concludeObserverIssue} — across calls. The axis is *how many independent calls*, which is
|
|
5293
|
-
* the only thing that separates "one bad room" from "our infrastructure".
|
|
5294
|
-
*
|
|
5295
|
-
* Neither the issue family nor the spread concludes anything alone: congestion in one call is a
|
|
5296
|
-
* meeting problem, congestion in six calls is an infrastructure problem, and the issue type is
|
|
5297
|
-
* identical in both.
|
|
5298
|
-
*/
|
|
5299
|
-
/** Where the fault most likely sits, given who is affected. */
|
|
5300
|
-
type IssueFaultDomain =
|
|
5301
|
-
/** Independent calls affected at once — they share only the servers and the network. */
|
|
5302
|
-
'infrastructure'
|
|
5303
|
-
/** One call, broadly affected — something that call shares (its SFU worker, room, or host). */
|
|
5304
|
-
| 'call'
|
|
5305
|
-
/** The subscribers of one published track — the publisher's path or the forwarding of it. */
|
|
5306
|
-
| 'published-track'
|
|
5307
|
-
/** A single endpoint — its own device or last mile. */
|
|
5308
|
-
| 'endpoint'
|
|
5309
|
-
/** Independent calls affected, but by something endpoints own — a client build, not a server. */
|
|
5310
|
-
| 'client-population'
|
|
5311
|
-
/** Not enough signal to attribute. */
|
|
5312
|
-
| 'unknown';
|
|
5313
|
-
/** A stated verdict, attached to the raised issue payload. */
|
|
5314
|
-
type IssueConclusion = {
|
|
5315
|
-
/** Where to look. */
|
|
5316
|
-
faultDomain: IssueFaultDomain;
|
|
5317
|
-
/** One line, written to be readable in an alert without opening a dashboard. */
|
|
5318
|
-
summary: string;
|
|
5319
|
-
/** What to check first. Omitted when the issue family is unknown to this module. */
|
|
5320
|
-
recommendation?: string;
|
|
5321
|
-
/**
|
|
5322
|
-
* How much the spread alone justifies the verdict, `0..1`. Not a probability — a coarse ranking
|
|
5323
|
-
* so alerting can threshold on it. More independent calls, or a tighter onset, means higher.
|
|
5324
|
-
*/
|
|
5325
|
-
confidence: number;
|
|
5326
|
-
};
|
|
5327
|
-
/** The facts a **call-scoped** conclusion is drawn from. */
|
|
5328
|
-
type CallIssueSpread = {
|
|
5329
|
-
issueType: string;
|
|
5330
|
-
/** Distinct clients of this call with the issue open. */
|
|
5331
|
-
affectedClients: number;
|
|
5332
|
-
/** Participants in the call — the denominator. */
|
|
5333
|
-
totalClients: number;
|
|
5334
|
-
/** True when the onsets clustered — a shared trigger rather than drift. */
|
|
5335
|
-
onsetBurst: boolean;
|
|
5336
|
-
/**
|
|
5337
|
-
* Set when the affected clients are the subscriber set of **one published track**.
|
|
5338
|
-
*
|
|
5339
|
-
* The strongest call-scoped statement available: those clients share a publisher and nothing
|
|
5340
|
-
* else, so the receivers are exonerated and the source's path is implicated.
|
|
5341
|
-
*/
|
|
5342
|
-
publishedTrackId?: string;
|
|
5343
|
-
};
|
|
5344
|
-
/** The facts an **observer-scoped** conclusion is drawn from. */
|
|
5345
|
-
type ObserverIssueSpread = {
|
|
5346
|
-
issueType: string;
|
|
5347
|
-
/** Distinct clients across the fleet with the issue open. */
|
|
5348
|
-
affectedClients: number;
|
|
5349
|
-
/** Clients in the fleet. Reported for context; it does not gate anything at this scope. */
|
|
5350
|
-
totalClients: number;
|
|
5351
|
-
/** Distinct calls containing at least one affected client. The dimension that matters here. */
|
|
5352
|
-
affectedCalls: number;
|
|
5353
|
-
/** Calls in flight. */
|
|
5354
|
-
totalCalls: number;
|
|
5355
|
-
/** True when the onsets clustered. */
|
|
5356
|
-
onsetBurst: boolean;
|
|
5357
|
-
};
|
|
5358
|
-
/**
|
|
5359
|
-
* Draw the conclusion for a group of clients **within one call**.
|
|
5360
|
-
*
|
|
5361
|
-
* Ordered most-to-least specific: a track-scoped group is a stronger statement than a call-wide one,
|
|
5362
|
-
* and a single affected endpoint is not a statement about the call at all.
|
|
5363
|
-
*/
|
|
5364
|
-
declare function concludeCallIssue(spread: CallIssueSpread): IssueConclusion;
|
|
5365
|
-
/**
|
|
5366
|
-
* Draw the conclusion for a group of clients spanning **several calls**.
|
|
5367
|
-
*
|
|
5368
|
-
* One affected call is not an observer-scoped finding — it has an obvious local explanation and the
|
|
5369
|
-
* call-scoped detector has already reported it — so that case returns `call` and says so rather than
|
|
5370
|
-
* dressing it up as a fleet event.
|
|
5371
|
-
*
|
|
5372
|
-
* Which domain breadth implicates depends on the family, and this is the whole reason the module
|
|
5373
|
-
* exists: `congestion` across unrelated calls points at the servers, `cpulimitation` across unrelated
|
|
5374
|
-
* calls points at what those *endpoints* share — a client release, a browser version, shared
|
|
5375
|
-
* virtualised hardware — and pointing an SFU team at the second one wastes a night.
|
|
5376
|
-
*/
|
|
5377
|
-
declare function concludeObserverIssue(spread: ObserverIssueSpread): IssueConclusion;
|
|
5378
|
-
|
|
5379
5793
|
/** An entry retained by {@link SlidingWindow}. */
|
|
5380
5794
|
type SlidingWindowEntry<T> = {
|
|
5381
5795
|
timestamp: number;
|
|
@@ -5622,4 +6036,4 @@ declare function createInMemorySink(samples?: ClientSample[]): InMemorySink;
|
|
|
5622
6036
|
declare function createDefaultMediasoupRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
5623
6037
|
declare function createP2pRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
5624
6038
|
|
|
5625
|
-
export { type AcceptContext, type AcceptMiddleware, type AcceptMiddlewarePayload, type ActiveClientIssue, type ActiveIssueTracker, ActiveIssuesRegistry, type AvailableCallScopeDetectorsConfigs, type AvailableDetectorsConfigs, type AvailableObserverScopeDetectorsConfigs, type AvailableValidatorConfigs, CODEC_MISMATCH_ISSUE, type CallAppDataFactory, CallConcurrentIssueDetector, type CallConcurrentIssueDetectorConfig, type CallConcurrentIssueGroup, CallConcurrentIssueTypes, type CallHealth, CallHealthAggregator, type CallIssueSpread, type ClientAppDataFactory, type ClientEvent, ClientEventTypes, type ClientHealth, type ClientHealthThresholds, type ClientIssue, type ClientMetaData, ClientMetaTypes, type ClientPopulation, type ClientPopulationAxis, ClientPopulationIssueDetector, type ClientPopulationIssueDetectorConfig, ClientPopulationIssueTypes, type ClientSample, ClientSampleSink, type ClientSampleSinkEvents, type ClientSampleSinkFactory, type CodecConsistencyReportPayload, CodecConsistencyValidator, type CodecConsistencyValidatorConfig, type CodecEvidence, type CorroboratedPublisherFault, type Detector, Detectors, InMemorySink, type IssueConclusion, IssueFanOutDetector, type IssueFanOutDetectorConfig, IssueFanOutTypes, type IssueFaultDomain, JsonlFileSink, type JsonlFileSinkFactoryOptions, type JsonlFileSinkOptions, LOWEST_COMMON_DENOMINATOR_ISSUE, type Logger, type MannKendallResult, type MediasoupConsumerSample, type MediasoupConsumerSampleEvent, type MediasoupDataConsumerSample, type MediasoupDataProducerSample, type MediasoupDirectTransportSample, type MediasoupDirectTransportSampleEventMap, type MediasoupPipeTransportSample, type MediasoupPipeTransportSampleEventMap, type MediasoupPlainTransportSample, type MediasoupPlainTransportSampleEventMap, type MediasoupProducerSample, type MediasoupProducerSampleEvent, type MediasoupRouterSample, type MediasoupSampleEnricher, type MediasoupTransportSample, type MediasoupWebRtcTransportSample, type MediasoupWebRtcTransportSampleEventMap, type Middleware, ObservedCall, type ObservedCallScope, ObservedCertificate, ObservedClient, ObservedClientIssueRegistry, type ObservedClientScope, ObservedCodec, ObservedDataChannel, ObservedIceCandidate, ObservedIceCandidatePair, ObservedIceTransport, ObservedInboundRtp, ObservedInboundTrack, ObservedMediaPlayout, ObservedMediaSource, ObservedMediasoupRouter, type ObservedMediasoupRouterEvents, type ObservedMediasoupRouterScope, type ObservedMediasoupRouterSettings, ObservedOutboundRtp, ObservedOutboundTrack, ObservedPeerConnection, type ObservedPeerConnectionScope, ObservedPeerConnectionTransport, ObservedRemoteInboundRtp, ObservedRemoteOutboundRtp, Observer, ObserverConcurrentIssueDetector, type ObserverConcurrentIssueDetectorConfig, type ObserverConcurrentIssueGroup, ObserverConcurrentIssueTypes, type ObserverEventBase, type ObserverEvents, type ObserverIssue, type ObserverIssueSpread, type ObserverLogger, type PageHinkleyResult, PublisherFaultCorroborationDetector, type PublisherFaultCorroborationDetectorConfig, PublisherFaultTypes, RESOLVED_ISSUE_SUFFIX, type RemoteTrackLinkEvidence, RemoteTrackResolver, type RemoteTrackResolverFactory, type RemoteTrackResolverReportPayload, RemoteTrackResolverValidator, type RemoteTrackResolverValidatorConfig, type RemoteTrackResolvers, type ResolvedActiveClientIssue, type RunningValidator, type SampleRejectedReason, type ScoreCalculator, SfuCongestionDetector, type SfuCongestionDetectorBucket, type SfuCongestionDetectorConfig, type SfuCongestionDetectorEvaluation, type SfuCongestionDetectorReport, type SimulcastReceiverEvidence, type SimulcastReceiverReportPayload, SimulcastReceiverValidator, type SimulcastReceiverValidatorConfig, SlidingWindow, type SlidingWindowEntry, type StatsSummary, TrackDeliveryMismatchDetector, type TrackDeliveryMismatchDetectorConfig, TrackDeliveryMismatchTypes, TrendTester, type TrendTesterConfig, type TurnServerHealth, TurnServerHealthDetector, type TurnServerHealthDetectorConfig, TurnServerHealthTypes, TurnServerOutageDetector, type TurnServerOutageDetectorConfig, TurnServerOutageTypes, UNRESOLVED_TRACK_LINKS_ISSUE, UnconsumedTrackDetector, type UnconsumedTrackDetectorConfig, UnconsumedTrackTypes, type ValidationReport, type Validator, type ValidatorName, baseIssueType, concludeCallIssue, concludeObserverIssue, correlation, counterDelta, createDefaultMediasoupRemoteTrackResolverFactory, createInMemorySink, createJsonlFileSink, createJsonlFileSinkFactory, createLogger, createP2pRemoteTrackResolverFactory, defaultClientHealthThresholds, isClientIssueResolutionEntry, issuePayloadAsString,
|
|
6039
|
+
export { type AcceptContext, type AcceptMiddleware, type AcceptMiddlewarePayload, type ActiveClientIssue, type ActiveIssueTracker, ActiveIssuesRegistry, type AvailableCallScopeDetectorsConfigs, type AvailableDetectorsConfigs, type AvailableObserverScopeDetectorsConfigs, type AvailableValidatorConfigs, CODEC_MISMATCH_ISSUE, type CallAppDataFactory, CallConcurrentIssueDetector, type CallConcurrentIssueDetectorConfig, type CallConcurrentIssueGroup, CallConcurrentIssueTypes, type CallHealth, CallHealthAggregator, type CallIssue, type CallIssueSpread, type CallScopedEventName, type CallSummary, type CallSummaryClients, CallSummaryCollector, type CallSummaryConfig, type CallSummaryEnricher, type CallSummaryEnrichers, type CallSummaryScores, type CallSummarySection, type CallSummaryTruncation, type CallSummaryTurnServers, type ClientAppDataFactory, type ClientEvent, ClientEventTypes, type ClientHealth, type ClientHealthThresholds, type ClientIssue, type ClientMetaData, ClientMetaTypes, type ClientPopulation, type ClientPopulationAxis, ClientPopulationIssueDetector, type ClientPopulationIssueDetectorConfig, ClientPopulationIssueTypes, type ClientSample, ClientSampleSink, type ClientSampleSinkEvents, type ClientSampleSinkFactory, type CodecConsistencyReportPayload, CodecConsistencyValidator, type CodecConsistencyValidatorConfig, type CodecEvidence, type CorroboratedPublisherFault, type Detector, Detectors, InMemorySink, type Issue, type IssueBase, type IssueConclusion, IssueFanOutDetector, type IssueFanOutDetectorConfig, IssueFanOutTypes, type IssueFaultDomain, JsonlFileSink, type JsonlFileSinkFactoryOptions, type JsonlFileSinkOptions, LOWEST_COMMON_DENOMINATOR_ISSUE, type Logger, type MannKendallResult, type MediasoupConsumerSample, type MediasoupConsumerSampleEvent, type MediasoupDataConsumerSample, type MediasoupDataProducerSample, type MediasoupDirectTransportSample, type MediasoupDirectTransportSampleEventMap, type MediasoupPipeTransportSample, type MediasoupPipeTransportSampleEventMap, type MediasoupPlainTransportSample, type MediasoupPlainTransportSampleEventMap, type MediasoupProducerSample, type MediasoupProducerSampleEvent, type MediasoupRouterSample, type MediasoupSampleEnricher, type MediasoupTransportSample, type MediasoupWebRtcTransportSample, type MediasoupWebRtcTransportSampleEventMap, type Middleware, ObservedCall, type ObservedCallScope, ObservedCertificate, ObservedClient, ObservedClientIssueRegistry, type ObservedClientScope, ObservedCodec, ObservedDataChannel, ObservedIceCandidate, ObservedIceCandidatePair, ObservedIceTransport, ObservedInboundRtp, ObservedInboundTrack, ObservedMediaPlayout, ObservedMediaSource, ObservedMediasoupRouter, type ObservedMediasoupRouterEvents, type ObservedMediasoupRouterScope, type ObservedMediasoupRouterSettings, ObservedOutboundRtp, ObservedOutboundTrack, ObservedPeerConnection, type ObservedPeerConnectionScope, ObservedPeerConnectionTransport, ObservedRemoteInboundRtp, ObservedRemoteOutboundRtp, Observer, ObserverConcurrentIssueDetector, type ObserverConcurrentIssueDetectorConfig, type ObserverConcurrentIssueGroup, ObserverConcurrentIssueTypes, type ObserverEventBase, type ObserverEvents, type ObserverIssue, type ObserverIssueSpread, type ObserverLogger, type PageHinkleyResult, PublisherFaultCorroborationDetector, type PublisherFaultCorroborationDetectorConfig, PublisherFaultTypes, RESOLVED_ISSUE_SUFFIX, type RemoteTrackLinkEvidence, RemoteTrackResolver, type RemoteTrackResolverFactory, type RemoteTrackResolverReportPayload, RemoteTrackResolverValidator, type RemoteTrackResolverValidatorConfig, type RemoteTrackResolvers, type ResolvedActiveClientIssue, type RunningValidator, type SampleRejectedReason, type ScoreCalculator, SfuCongestionDetector, type SfuCongestionDetectorBucket, type SfuCongestionDetectorConfig, type SfuCongestionDetectorEvaluation, type SfuCongestionDetectorReport, type SimulcastReceiverEvidence, type SimulcastReceiverReportPayload, SimulcastReceiverValidator, type SimulcastReceiverValidatorConfig, SlidingWindow, type SlidingWindowEntry, type StatsSummary, TrackDeliveryMismatchDetector, type TrackDeliveryMismatchDetectorConfig, TrackDeliveryMismatchTypes, TrendTester, type TrendTesterConfig, type TurnServerHealth, TurnServerHealthDetector, type TurnServerHealthDetectorConfig, TurnServerHealthTypes, TurnServerOutageDetector, type TurnServerOutageDetectorConfig, TurnServerOutageTypes, UNRESOLVED_TRACK_LINKS_ISSUE, UnconsumedTrackDetector, type UnconsumedTrackDetectorConfig, UnconsumedTrackTypes, type ValidationReport, type Validator, type ValidatorName, baseIssueType, concludeCallIssue, concludeObserverIssue, correlation, counterDelta, createCallSummary, createDefaultMediasoupRemoteTrackResolverFactory, createInMemorySink, createJsonlFileSink, createJsonlFileSinkFactory, createLogger, createP2pRemoteTrackResolverFactory, defaultCallSummaryConfig, defaultClientHealthThresholds, isClientIssueResolutionEntry, issuePayloadAsString, mannKendall, mannKendallVerdict, median, medianAbsoluteDeviation, pageHinkley, percentile, percentileOfSorted, robustZScore, setObserverLogger, summarize };
|