jam-cloud/jam-ui/src/hooks/useSessionStats.js

165 lines
4.3 KiB
JavaScript

import { useState, useEffect, useCallback } from 'react';
// Thresholds based on SessionStatsStore.js.coffee
const SessionStatThresholds = {
network: {
ping: { poor: 100, warn: 50 },
audiojq_median: { poor: 10, warn: 5 },
jitter_var: { poor: 5, warn: 2 },
audio_bitrate_rx: { poor: 64000, warn: 96000 },
audio_bitrate_tx: { poor: 64000, warn: 96000 },
video_rtpbw_tx: { poor: 500000, warn: 1000000 },
video_rtpbw_rx: { poor: 500000, warn: 1000000 },
pkt_loss: { poor: 0.05, warn: 0.02 },
wifi: { poor: true, warn: true }
},
system: {
cpu: { poor: 80, warn: 60 }
},
audio: {
framesize: { poor: 1024, warn: 512 },
latency: { poor: 20, warn: 10 },
input_jitter: { poor: 2, warn: 1 },
output_jitter: { poor: 2, warn: 1 },
audio_in_type: { poor: 'wdm', warn: 'wdm' }
},
aggregate: {
latency: { poor: 100, warn: 50 }
}
};
// Classification logic from SessionStatsStore
const classify = (holder, field, threshold) => {
const value = holder[field];
const fieldLevel = field + '_level';
if (value !== undefined && threshold) {
if (threshold.inverse) {
if (threshold.zero_is_good) {
holder[fieldLevel] = 'good';
} else if (value <= threshold.poor) {
holder[fieldLevel] = 'poor';
} else if (value <= threshold.warn) {
holder[fieldLevel] = 'warn';
} else {
holder[fieldLevel] = 'good';
}
} else if (threshold.eql) {
if (value === threshold.poor) {
holder[fieldLevel] = 'poor';
} else if (value === threshold.warn) {
holder[fieldLevel] = 'warn';
} else {
holder[fieldLevel] = 'good';
}
} else {
if (value >= threshold.poor) {
holder[fieldLevel] = 'poor';
} else if (value >= threshold.warn) {
holder[fieldLevel] = 'warn';
} else {
holder[fieldLevel] = 'good';
}
}
}
};
// Mock stats data structure with classification
const createMockStats = clientId => {
const stats = {
aggregate: {
latency: 45.2,
their_out_latency: 12.5,
your_in_latency: 8.3,
one_way: 15.4,
jq: 9.0
},
network: {
ping: 25.3,
audiojq_median: 2.1,
jitter_var: 1.8,
pkt_loss: 0.05,
wifi: false,
audio_bitrate_rx: 128000,
audio_bitrate_tx: 128000,
video_rtpbw_rx: 2500000,
video_rtpbw_tx: 2500000,
rx_ars_vs_p2p: 'P2P',
tx_ars_vs_p2p: 'P2P'
},
system: {
cpu: 35.2
},
audio: {
latency: 5.2,
input_jitter: 0.8,
output_jitter: 0.6,
audio_in_type: 'CoreAudio',
framesize: 128
}
};
// Apply classification logic
Object.keys(stats).forEach(category => {
if (SessionStatThresholds[category]) {
Object.keys(stats[category]).forEach(field => {
if (SessionStatThresholds[category][field]) {
classify(stats[category], field, SessionStatThresholds[category][field]);
}
});
}
});
// Special handling for ARS vs P2P (from SessionStatsStore logic)
if (stats.network.rx_ars_vs_p2p === 'P2P') {
stats.network.rx_ars_vs_p2p_level = 'good';
} else {
stats.network.rx_ars_vs_p2p_level = 'warn';
}
if (stats.network.tx_ars_vs_p2p === 'P2P') {
stats.network.tx_ars_vs_p2p_level = 'good';
} else {
stats.network.tx_ars_vs_p2p_level = 'warn';
}
return stats;
};
export const useSessionStats = clientId => {
const [stats, setStats] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const refreshStats = useCallback(() => {
try {
// In a real implementation, this would fetch from JamClient or a global store
// For now, we'll use mock data that matches the structure from the web version
const mockStats = createMockStats(clientId);
setStats(mockStats);
setLoading(false);
} catch (err) {
setError(err);
setLoading(false);
}
}, [clientId]);
useEffect(() => {
refreshStats();
// Set up periodic refresh (similar to how the web version updates)
const interval = setInterval(refreshStats, 2000); // Update every 2 seconds
return () => clearInterval(interval);
}, [refreshStats]);
return {
stats,
loading,
error,
refreshStats
};
};
export default useSessionStats;