collabSupport/services/voiceDiag/checks/wan/wanAppRtpLoss.js
jmcqueen 9d0dbb071e Add per-app DPI voice-quality checks + widen WAN window to 7d
Adds three new SD-WAN checks (wanAppRtpMos/Loss/Jitter) that measure
REAL voice-traffic quality on actual RTP frames via Prisma DPI, not
synthetic link probes. Graded against the WORST 5-minute window so
transient degradation the 24h link-probe averages smooth away
actually surfaces.

Voice-app selection is tenant-configurable via PRISMA_APP_ID_VOICE +
PRISMA_APP_NAME_VOICE (Webex_Calling_RTP recommended for Webex
Calling shops — the Webex-specific DPI signature excludes non-Webex
UDP noise). Legacy PRISMA_APP_ID_RTP_BASE still honored with a
one-time deprecation warning.

Widens the default WAN look-back from 24h to 7 days: per-app metrics
only get datapoints when calls actually happen, so sporadic Webex
Calling stores (3-4 calls/day) need a wider window for worst-window
statistics to be meaningful. Interval picker snaps 7d to 1hour
buckets (168 pts) to keep payloads bounded while preserving
worst-hour granularity. Hard-capped at 7d — beyond that Prisma
downsamples to 1-day buckets and the signal collapses.

Also:
- Client-side concurrency limiter (PRISMA_MAX_INFLIGHT, default 3)
  to prevent 429 cascades when /voicediag fans out 10+ parallel
  metric fetches
- "View in Prisma UI" deep links in both /phonestatus WAN follow-up
  and /voicediag details, threading through a new
  integrations/paloalto/urls.js builder
- humanizeMetricUnit maps raw API unit strings ("percentage",
  "milliseconds") to display symbols ("%", "ms") to fix
  "11.83percentage" leaking to the UI
- getAppAudio envelope distinguishes not-configured / fetch-failed /
  no-traffic states so misleading "set env var" messages don't fire
  when the real problem is a 429

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 13:54:15 -04:00

58 lines
1.8 KiB
JavaScript

// src/services/voiceDiag/checks/wan/wanAppRtpLoss.js
//
// Per-application audio packet loss — measured on ACTUAL RTP voice
// traffic via Prisma DPI. Metric name on the v2.6 monitor/metrics
// endpoint is `AppPerfUDPAudioPacketLoss` (unit: percentage), takes
// filter.direction="Ingress" AND filter.path_type=[all].
//
// See wanAppRtpMos.js for the app-selection contract — the check is
// app-agnostic; the tenant picks which voice app to grade via env.
//
// Grades against the WORST-window loss in the series. A 5-minute
// burst of 20% loss makes calls unusable during that window even
// if the 24h avg is 2% — always show the operator the worst-case
// number so they know a bad experience is real, not aggregated away.
//
// Skipped when the voice-app env is not configured.
import {
maybeSkippedByKillSwitch,
getAppAudio,
evaluateAppAudioMetric,
getAppLossWarnPct,
getAppLossErrorPct,
} from './_helpers.js';
export const WAN_APP_RTP_LOSS_STANDARDS = Object.freeze({
maxWarnPct: 5,
maxErrorPct: 15,
unit: '%',
reference: 'ITU-T G.113 (voice loss tolerance)',
gradeAgainst: 'worst-window',
});
export const wanAppRtpLossCheck = {
id: 'wanAppRtpLoss',
label: 'SD-WAN Voice Traffic Packet Loss (worst window)',
requires: ['sdwanSite'],
scope: null,
standards: WAN_APP_RTP_LOSS_STANDARDS,
async run(ctx) {
const skip = maybeSkippedByKillSwitch(wanAppRtpLossCheck);
if (skip) return skip;
const audio = getAppAudio(ctx, 'loss');
const warnThresh = getAppLossWarnPct();
const errorThresh = getAppLossErrorPct();
return evaluateAppAudioMetric({
audio,
label: 'packet loss',
unit: '%',
warnThresh,
errorThresh,
lowIsBad: false,
standardLabel: `warn > ${warnThresh}%, error > ${errorThresh}%`,
});
},
};