collabSupport/services/voiceDiag/checks/wan/wanAppRtpMos.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

65 lines
2.1 KiB
JavaScript

// src/services/voiceDiag/checks/wan/wanAppRtpMos.js
//
// Per-application audio MOS — measured on ACTUAL RTP voice traffic
// via Prisma DPI, not on synthetic link probes. Metric name on the
// v2.6 monitor/metrics endpoint is `AppAudioMos` (unit: count),
// requires filter.direction="Ingress".
//
// Which app is measured depends on the tenant's configured voice
// application (PRISMA_APP_ID_VOICE env). Common choices are
// `Webex_Calling_RTP` (recommended for Webex Calling shops — the
// Webex-specific DPI signature excludes noise from other UDP
// traffic that pollutes generic RTP), `rtp-base`, `MS_Teams_RTP`,
// etc. The check is app-agnostic; the app name is threaded through
// the message + details so operators know which app was graded.
//
// Grades against the WORST-window MOS in the series. A store that
// averages 4.0 MOS over 24h but dipped to 1.85 for several 5-min
// windows had unusable calls in those windows — the average smooths
// that away and the LQM link-probe average smooths it away twice.
// This check is the escape hatch for that failure mode.
//
// Skipped when the voice-app env is not configured (feature-gated
// to keep the extra Prisma API load opt-in per tenant).
import {
maybeSkippedByKillSwitch,
getAppAudio,
evaluateAppAudioMetric,
getAppMosWarn,
getAppMosError,
} from './_helpers.js';
export const WAN_APP_RTP_MOS_STANDARDS = Object.freeze({
minWarnMos: 4.0,
minErrorMos: 3.5,
unit: 'count',
reference: 'ITU-T P.800 (MOS)',
gradeAgainst: 'worst-window',
});
export const wanAppRtpMosCheck = {
id: 'wanAppRtpMos',
label: 'SD-WAN Voice Traffic MOS (worst window)',
requires: ['sdwanSite'],
scope: null,
standards: WAN_APP_RTP_MOS_STANDARDS,
async run(ctx) {
const skip = maybeSkippedByKillSwitch(wanAppRtpMosCheck);
if (skip) return skip;
const audio = getAppAudio(ctx, 'mos');
const warnThresh = getAppMosWarn();
const errorThresh = getAppMosError();
return evaluateAppAudioMetric({
audio,
label: 'MOS',
unit: '',
warnThresh,
errorThresh,
lowIsBad: true,
standardLabel: `warn < ${warnThresh}, error < ${errorThresh}`,
});
},
};