bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/connection-status/component.jsx
Paulo Lanzarin dca9b87190
fix(connection-status): packet loss causes false positive critical alerts (#21049)
In 3.0, the packet loss metric used to trigger connection status alerts was
changed to the one generated by the `startMonitoringNetwork` method used by the
connection status modal. Since packet loss thresholds were not adjusted (0.5,
0.1, 0.2), a single lost packet causes the status alert to be permanently
stuck on "critical". This is explained by how different those metrics
are:
  - **Before (2.7):** A 5-probe wide calculation of inbound packet loss
  fraction based on `packetsLost` and `packetsReceived` metrics.
  - **Now (3.0):** An absolute counter of inbound lost packets.

This commit restores the previous packet loss metric used to trigger
connection status alerts, reverting to the original collection method via
`/utils/stats.js`. This resolves the issue, but further work is needed in
subsequent PRs:
  - Unify the collection done in `/utils/stats.js` with the
  `startMonitoringNetwork` method.
  - Incorporate the remote-inbound `fractionsLost` metric to account for packet
  loss on both legs of the network (in/out).
  - Update the packet loss metric displayed in the connection status modal to
 show a more meaningful value (e.g., packet loss percentage over a specific
  probe interval). An absolute counter of lost packets isn't useful for end
  users.
  - Update the alert log to use the fraction or percentage above
2024-08-28 16:55:57 -04:00

105 lines
3.1 KiB
JavaScript
Executable File

import { useEffect, useRef } from 'react';
import { useMutation } from '@apollo/client';
import { UPDATE_CONNECTION_ALIVE_AT } from './mutations';
import {
getStatus,
handleAudioStatsEvent,
startMonitoringNetwork,
} from '/imports/ui/components/connection-status/service';
import connectionStatus from '../../core/graphql/singletons/connectionStatus';
import getBaseUrl from '/imports/ui/core/utils/getBaseUrl';
import useCurrentUser from '../../core/hooks/useCurrentUser';
const ConnectionStatus = () => {
const STATS_INTERVAL = window.meetingClientSettings.public.stats.interval;
const networkRttInMs = useRef(0); // Ref to store the last rtt
const timeoutRef = useRef(null);
const [updateConnectionAliveAtM] = useMutation(UPDATE_CONNECTION_ALIVE_AT);
const {
data,
} = useCurrentUser((u) => ({
userId: u.userId,
avatar: u.avatar,
isModerator: u.isModerator,
color: u.color,
currentlyInMeeting: u.currentlyInMeeting,
}));
const handleUpdateConnectionAliveAt = () => {
const startTime = performance.now();
fetch(
`${getBaseUrl()}/ping`,
{ signal: AbortSignal.timeout(STATS_INTERVAL) },
)
.then((res) => {
if (res.ok && res.status === 200) {
const rttLevels = window.meetingClientSettings.public.stats.rtt;
const endTime = performance.now();
const networkRtt = Math.round(endTime - startTime);
networkRttInMs.current = networkRtt;
updateConnectionAliveAtM({
variables: {
networkRttInMs: networkRtt,
},
});
const rttStatus = getStatus(rttLevels, networkRtt);
connectionStatus.setRttValue(networkRtt);
connectionStatus.setRttStatus(rttStatus);
connectionStatus.setLastRttRequestSuccess(true);
if (Object.keys(rttLevels).includes(rttStatus)) {
connectionStatus.addUserNetworkHistory(
data,
rttStatus,
Date.now(),
);
}
}
})
.catch(() => {
connectionStatus.setLastRttRequestSuccess(false);
// gets the worst status
connectionStatus.setRttStatus('critical');
})
.finally(() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
handleUpdateConnectionAliveAt();
}, STATS_INTERVAL);
});
};
useEffect(() => {
// Delay first connectionAlive to avoid high RTT misestimation
// due to initial subscription and mutation traffic at client render
timeoutRef.current = setTimeout(() => {
handleUpdateConnectionAliveAt();
}, STATS_INTERVAL / 2);
const STATS_ENABLED = window.meetingClientSettings.public.stats.enabled;
if (STATS_ENABLED) {
window.addEventListener('audiostats', handleAudioStatsEvent);
startMonitoringNetwork();
}
return () => {
window.removeEventListener('audiostats', handleAudioStatsEvent);
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return null;
};
export default ConnectionStatus;