Add more stats to allow for refined loss analysis
Co-authored-by: adel-signal <adel@signal.org>
This commit is contained in:
parent
a755cb746f
commit
d98700a26a
@ -35,8 +35,7 @@ pub enum ChartDimension {
|
||||
AudioReceiveAudioEnergy,
|
||||
AudioReceiveJitterBufferDelay,
|
||||
AudioReceiveJitterBufferTargetDelay,
|
||||
AudioReceiveTotalSamplesReceived,
|
||||
AudioReceiveConcealedSamples,
|
||||
AudioReceiveConcealedSamplesPct,
|
||||
AudioReceiveFecPacketsReceived,
|
||||
|
||||
VideoSendPacketsPerSecond,
|
||||
@ -93,11 +92,8 @@ impl ChartDimension {
|
||||
ChartDimension::AudioReceiveJitterBufferTargetDelay => {
|
||||
("Audio Received Jitter Buffer Target Delay", "milliseconds")
|
||||
}
|
||||
ChartDimension::AudioReceiveTotalSamplesReceived => {
|
||||
("Audio Received Total Samples", "Samples")
|
||||
}
|
||||
ChartDimension::AudioReceiveConcealedSamples => {
|
||||
("Audio Received Concealed Samples", "Samples")
|
||||
ChartDimension::AudioReceiveConcealedSamplesPct => {
|
||||
("Audio Received Concealed Samples", "%")
|
||||
}
|
||||
ChartDimension::AudioReceiveFecPacketsReceived => {
|
||||
("Audio Received FEC Packets", "Packets")
|
||||
@ -157,10 +153,9 @@ impl ChartDimension {
|
||||
ChartDimension::AudioReceiveJitterBufferTargetDelay => {
|
||||
"audio_receive_jitter_buffer_target_delay"
|
||||
}
|
||||
ChartDimension::AudioReceiveTotalSamplesReceived => {
|
||||
"audio_receive_total_samples_received"
|
||||
ChartDimension::AudioReceiveConcealedSamplesPct => {
|
||||
"audio_receive_concealed_samples_pct"
|
||||
}
|
||||
ChartDimension::AudioReceiveConcealedSamples => "audio_receive_concealed_samples",
|
||||
ChartDimension::AudioReceiveFecPacketsReceived => "audio_receive_fec_packets_received",
|
||||
ChartDimension::VideoSendPacketsPerSecond => "video_send_pps",
|
||||
ChartDimension::VideoSendPacketSize => "video_send_packet_size",
|
||||
|
||||
@ -469,9 +469,10 @@ pub struct AudioReceiveStatsTransfer {
|
||||
pub audio_energy: StatsData,
|
||||
pub jitter_buffer_delay: StatsData,
|
||||
pub jitter_buffer_target_delay: StatsData,
|
||||
pub total_samples_received: StatsData,
|
||||
pub concealed_samples: StatsData,
|
||||
pub jitter_buffer_flushes: StatsData,
|
||||
pub concealed_samples_pct: StatsData,
|
||||
pub fec_packets_received: StatsData,
|
||||
pub relative_arrival_delay_per_packet: StatsData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@ -538,9 +539,10 @@ pub struct AudioReceiveStats {
|
||||
pub audio_energy_stats: Stats,
|
||||
pub jitter_buffer_delay_stats: Stats,
|
||||
pub jitter_buffer_target_delay_stats: Stats,
|
||||
pub total_samples_received_stats: Stats,
|
||||
pub concealed_samples_stats: Stats,
|
||||
pub jitter_buffer_flushes_stats: Stats,
|
||||
pub concealed_samples_pct_stats: Stats,
|
||||
pub fec_packets_received_stats: Stats,
|
||||
pub relative_arrival_delay_per_packet_stats: Stats,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@ -600,9 +602,9 @@ impl ClientLogReport {
|
||||
r".*ringrtc_stats!,video,send,(?P<ssrc>\d+),(?P<packets_per_second>[-+]?[0-9]*\.?[0-9]+),(?P<average_packet_size>[-+]?[0-9]*\.?[0-9]+),(?P<bitrate>[-+]?[0-9]*\.?[0-9]+)bps,(?P<framerate>[0-9]*\.?[0-9]+)fps,(?P<key_frames_encoded>\d+),(?P<encode_time_per_frame>[0-9]*\.?[0-9]+)ms,(?P<resolution>\d+x\d+),(?P<retransmitted_packets_sent>\d+),(?P<retransmitted_bitrate>[0-9]*\.?[0-9]+)bps,(?P<send_delay_per_packet>[0-9]*\.?[0-9]+)ms,(?P<nack_count>\d+),(?P<pli_count>\d+),(?P<quality_limitation_reason>\w+),(?P<quality_limitation_resolution_changes>\d+),(?P<remote_packet_loss>[-+]?[0-9]*\.?[0-9]+)%,(?P<remote_jitter>[0-9]*\.?[0-9]+)ms,(?P<remote_round_trip_time>[0-9]*\.?[0-9]+)ms",
|
||||
)?;
|
||||
|
||||
// Example: ringrtc_stats!,audio,recv,1002,40.0,0.0%,32000.0bps,0ms,0.000,50ms,40ms,48000,0,0
|
||||
// Example: ringrtc_stats!,audio,recv,1002,40.0,0.00%,32000.0bps,0ms,0.000,50ms,40ms,0,0.00%,0,0ms
|
||||
let re_audio_receive_line = Regex::new(
|
||||
r".*ringrtc_stats!,audio,recv,(?P<ssrc>\d+),(?P<packets_per_second>[-+]?[0-9]*\.?[0-9]+),(?P<packet_loss>[-+]?[0-9]*\.?[0-9]+)%,(?P<bitrate>[-+]?[0-9]*\.?[0-9]+)bps,(?P<jitter>\d+)ms,(?P<audio_energy>[-+]?[0-9]*\.?[0-9]+),(?P<jitter_buffer_delay>\d+)ms,(?P<jitter_buffer_target_delay>\d+)ms,(?P<total_samples_received>\d+),(?P<concealed_samples>\d+),(?P<fec_packets_received>\d+)",
|
||||
r".*ringrtc_stats!,audio,recv,(?P<ssrc>\d+),(?P<packets_per_second>[-+]?[0-9]*\.?[0-9]+),(?P<packet_loss>[-+]?[0-9]*\.?[0-9]+)%,(?P<bitrate>[-+]?[0-9]*\.?[0-9]+)bps,(?P<jitter>\d+)ms,(?P<audio_energy>[-+]?[0-9]*\.?[0-9]+),(?P<jitter_buffer_delay>\d+)ms,(?P<jitter_buffer_target_delay>\d+)ms,(?P<jitter_buffer_flushes>\d+),(?P<concealed_samples_pct>[-+]?[0-9]*\.?[0-9]+)%,(?P<fec_packets_received>\d+),(?P<relative_arrival_delay_per_packet>\d+)ms",
|
||||
)?;
|
||||
|
||||
// Example: ringrtc_stats!,video,recv,2003,7.0,0.0%,61305bps,1.0fps,1,3.3ms,1280x720
|
||||
@ -748,14 +750,17 @@ impl ClientLogReport {
|
||||
.jitter_buffer_target_delay
|
||||
.push(f32::from_str(&cap["jitter_buffer_target_delay"])?);
|
||||
audio_receive_stats
|
||||
.total_samples_received
|
||||
.push(f32::from_str(&cap["total_samples_received"])?);
|
||||
.jitter_buffer_flushes
|
||||
.push(f32::from_str(&cap["jitter_buffer_flushes"])?);
|
||||
audio_receive_stats
|
||||
.concealed_samples
|
||||
.push(f32::from_str(&cap["concealed_samples"])?);
|
||||
.concealed_samples_pct
|
||||
.push(f32::from_str(&cap["concealed_samples_pct"])?);
|
||||
audio_receive_stats
|
||||
.fec_packets_received
|
||||
.push(f32::from_str(&cap["fec_packets_received"])?);
|
||||
audio_receive_stats
|
||||
.relative_arrival_delay_per_packet
|
||||
.push(f32::from_str(&cap["relative_arrival_delay_per_packet"])?);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1209,31 +1214,33 @@ impl ClientLogReport {
|
||||
data: audio_receive_stats.jitter_buffer_target_delay,
|
||||
};
|
||||
|
||||
let total_samples_received_stats = Stats {
|
||||
let jitter_buffer_flushes_stats = Stats {
|
||||
config: StatsConfig {
|
||||
title: format!("Audio Receive Total Samples Received (ssrc={ssrc})"),
|
||||
title: format!("Audio Receive Jitter Buffer Flushes (ssrc={ssrc})"),
|
||||
chart_name: format!(
|
||||
"{}.log.audio.receive.total_samples_received.svg",
|
||||
"{}.log.audio.receive.jitter_buffer_flushes.svg",
|
||||
client_name
|
||||
),
|
||||
x_label: "Test Seconds".to_string(),
|
||||
y_label: "Samples".to_string(),
|
||||
y_label: "Flushes".to_string(),
|
||||
show_total: true,
|
||||
..Default::default()
|
||||
},
|
||||
data: audio_receive_stats.total_samples_received,
|
||||
data: audio_receive_stats.jitter_buffer_flushes,
|
||||
};
|
||||
|
||||
let concealed_samples_stats = Stats {
|
||||
let concealed_samples_pct_stats = Stats {
|
||||
config: StatsConfig {
|
||||
title: format!("Audio Receive Concealed Samples (ssrc={ssrc})"),
|
||||
chart_name: format!("{}.log.audio.receive.concealed_samples.svg", client_name),
|
||||
chart_name: format!(
|
||||
"{}.log.audio.receive.concealed_samples_pct.svg",
|
||||
client_name
|
||||
),
|
||||
x_label: "Test Seconds".to_string(),
|
||||
y_label: "Samples".to_string(),
|
||||
show_total: true,
|
||||
y_label: "%".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
data: audio_receive_stats.concealed_samples,
|
||||
data: audio_receive_stats.concealed_samples_pct,
|
||||
};
|
||||
|
||||
let fec_packets_received_stats = Stats {
|
||||
@ -1251,6 +1258,20 @@ impl ClientLogReport {
|
||||
data: audio_receive_stats.fec_packets_received,
|
||||
};
|
||||
|
||||
let relative_arrival_delay_per_packet_stats = Stats {
|
||||
config: StatsConfig {
|
||||
title: format!("Audio Receive Relative Arrival Delay Per Packet (ssrc={ssrc})"),
|
||||
chart_name: format!(
|
||||
"{}.log.audio.receive.relative_arrival_delay_per_packet.svg",
|
||||
client_name
|
||||
),
|
||||
x_label: "Test Seconds".to_string(),
|
||||
y_label: "milliseconds".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
data: audio_receive_stats.relative_arrival_delay_per_packet,
|
||||
};
|
||||
|
||||
audio_receive_stats_list.push(AudioReceiveStats {
|
||||
ssrc: audio_receive_stats.ssrc,
|
||||
packets_per_second_stats,
|
||||
@ -1260,9 +1281,10 @@ impl ClientLogReport {
|
||||
audio_energy_stats,
|
||||
jitter_buffer_delay_stats,
|
||||
jitter_buffer_target_delay_stats,
|
||||
total_samples_received_stats,
|
||||
concealed_samples_stats,
|
||||
jitter_buffer_flushes_stats,
|
||||
concealed_samples_pct_stats,
|
||||
fec_packets_received_stats,
|
||||
relative_arrival_delay_per_packet_stats,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1645,9 +1667,10 @@ impl Report {
|
||||
&per_ssrc.audio_energy_stats,
|
||||
&per_ssrc.jitter_buffer_delay_stats,
|
||||
&per_ssrc.jitter_buffer_target_delay_stats,
|
||||
&per_ssrc.total_samples_received_stats,
|
||||
&per_ssrc.concealed_samples_stats,
|
||||
&per_ssrc.jitter_buffer_flushes_stats,
|
||||
&per_ssrc.concealed_samples_pct_stats,
|
||||
&per_ssrc.fec_packets_received_stats,
|
||||
&per_ssrc.relative_arrival_delay_per_packet_stats,
|
||||
]
|
||||
}));
|
||||
|
||||
@ -1901,10 +1924,11 @@ impl Report {
|
||||
&audio_receive_stats.jitter_stats,
|
||||
&audio_receive_stats.jitter_buffer_delay_stats,
|
||||
&audio_receive_stats.jitter_buffer_target_delay_stats,
|
||||
&audio_receive_stats.jitter_buffer_flushes_stats,
|
||||
&audio_receive_stats.audio_energy_stats,
|
||||
&audio_receive_stats.total_samples_received_stats,
|
||||
&audio_receive_stats.concealed_samples_stats,
|
||||
&audio_receive_stats.concealed_samples_pct_stats,
|
||||
&audio_receive_stats.fec_packets_received_stats,
|
||||
&audio_receive_stats.relative_arrival_delay_per_packet_stats,
|
||||
],
|
||||
);
|
||||
buf.extend_from_slice(
|
||||
@ -2169,20 +2193,12 @@ impl Report {
|
||||
.map(|stats| stats.jitter_buffer_target_delay_stats.data.ave)
|
||||
.collect_vec(),
|
||||
),
|
||||
ChartDimension::AudioReceiveTotalSamplesReceived => average(
|
||||
ChartDimension::AudioReceiveConcealedSamplesPct => average(
|
||||
&report
|
||||
.client_log_report
|
||||
.audio_receive_stats_list
|
||||
.iter()
|
||||
.map(|stats| stats.total_samples_received_stats.data.ave)
|
||||
.collect_vec(),
|
||||
),
|
||||
ChartDimension::AudioReceiveConcealedSamples => average(
|
||||
&report
|
||||
.client_log_report
|
||||
.audio_receive_stats_list
|
||||
.iter()
|
||||
.map(|stats| stats.concealed_samples_stats.data.ave)
|
||||
.map(|stats| stats.concealed_samples_pct_stats.data.ave)
|
||||
.collect_vec(),
|
||||
),
|
||||
ChartDimension::AudioReceiveFecPacketsReceived => average(
|
||||
@ -2536,7 +2552,6 @@ struct SummaryRow {
|
||||
pub audio_receive_packet_rate: f32,
|
||||
pub audio_receive_bitrate: f32,
|
||||
pub audio_receive_loss: f32,
|
||||
pub concealed_samples_total: f32,
|
||||
pub concealed_samples_pct: f32,
|
||||
pub fec_packets_received_total: f32,
|
||||
|
||||
@ -2569,28 +2584,21 @@ impl SummaryRow {
|
||||
audio_receive_packet_rate,
|
||||
audio_receive_bitrate,
|
||||
audio_receive_loss,
|
||||
concealed_samples_total,
|
||||
total_samples_received_total,
|
||||
concealed_samples_pct,
|
||||
fec_packets_received_total,
|
||||
) = report
|
||||
.client_log_report
|
||||
.audio_receive_stats_list
|
||||
.iter()
|
||||
.fold((0.0, 0.0, 0.0, 0.0, 0.0, 0.0), |acc, stats| {
|
||||
.fold((0.0, 0.0, 0.0, 0.0, 0.0), |acc, stats| {
|
||||
(
|
||||
acc.0 + stats.packets_per_second_stats.data.ave,
|
||||
acc.1 + stats.bitrate_stats.data.ave,
|
||||
acc.2 + stats.packet_loss_stats.data.ave,
|
||||
acc.3 + stats.concealed_samples_stats.data.total,
|
||||
acc.4 + stats.total_samples_received_stats.data.total,
|
||||
acc.5 + stats.fec_packets_received_stats.data.total,
|
||||
acc.3 + stats.concealed_samples_pct_stats.data.ave,
|
||||
acc.4 + stats.fec_packets_received_stats.data.total,
|
||||
)
|
||||
});
|
||||
let concealed_samples_pct = if total_samples_received_total > 0.0 {
|
||||
100.0 * concealed_samples_total / total_samples_received_total
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
Self {
|
||||
audio_send_packet_size: report
|
||||
.client_log_report
|
||||
@ -2613,8 +2621,7 @@ impl SummaryRow {
|
||||
audio_receive_packet_rate,
|
||||
audio_receive_bitrate,
|
||||
audio_receive_loss,
|
||||
concealed_samples_total: concealed_samples_total as f32,
|
||||
concealed_samples_pct: concealed_samples_pct as f32,
|
||||
concealed_samples_pct,
|
||||
fec_packets_received_total: fec_packets_received_total as f32,
|
||||
container_cpu: report.docker_stats_report.cpu_usage.data.ave,
|
||||
container_memory: report.docker_stats_report.mem_usage.data.ave,
|
||||
@ -2644,22 +2651,30 @@ impl SummaryRow {
|
||||
vmaf: report.analysis_report.as_ref().and_then(|ar| ar.vmaf),
|
||||
row_type: SummaryRowType::Single,
|
||||
row_index: 0,
|
||||
video_send_resolution: report.client_log_report.video_send_stats[0]
|
||||
.resolution
|
||||
.data
|
||||
.ave,
|
||||
video_send_framerate: report.client_log_report.video_send_stats[0]
|
||||
.framerate_stats
|
||||
.data
|
||||
.ave,
|
||||
video_recv_resolution: report.client_log_report.video_receive_stats_list[0]
|
||||
.resolution
|
||||
.data
|
||||
.ave,
|
||||
video_recv_framerate: report.client_log_report.video_receive_stats_list[0]
|
||||
.framerate_stats
|
||||
.data
|
||||
.ave,
|
||||
video_send_resolution: report
|
||||
.client_log_report
|
||||
.video_send_stats
|
||||
.first()
|
||||
.map(|s| s.resolution.data.ave)
|
||||
.unwrap_or(0.0),
|
||||
video_send_framerate: report
|
||||
.client_log_report
|
||||
.video_send_stats
|
||||
.first()
|
||||
.map(|s| s.framerate_stats.data.ave)
|
||||
.unwrap_or(0.0),
|
||||
video_recv_resolution: report
|
||||
.client_log_report
|
||||
.video_receive_stats_list
|
||||
.first()
|
||||
.map(|s| s.resolution.data.ave)
|
||||
.unwrap_or(0.0),
|
||||
video_recv_framerate: report
|
||||
.client_log_report
|
||||
.video_receive_stats_list
|
||||
.first()
|
||||
.map(|s| s.framerate_stats.data.ave)
|
||||
.unwrap_or(0.0),
|
||||
}
|
||||
}
|
||||
|
||||
@ -2693,8 +2708,6 @@ impl SummaryRow {
|
||||
self.audio_receive_bitrate =
|
||||
new_average(self.audio_receive_bitrate, new.audio_receive_bitrate);
|
||||
self.audio_receive_loss = new_average(self.audio_receive_loss, new.audio_receive_loss);
|
||||
self.concealed_samples_total =
|
||||
new_average(self.concealed_samples_total, new.concealed_samples_total);
|
||||
self.concealed_samples_pct =
|
||||
new_average(self.concealed_samples_pct, new.concealed_samples_pct);
|
||||
self.fec_packets_received_total = new_average(
|
||||
@ -3333,17 +3346,19 @@ impl Html {
|
||||
indent, summary_row.audio_send_bitrate
|
||||
);
|
||||
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"<td>{}{:.2}</td>",
|
||||
indent, summary_row.video_send_resolution
|
||||
);
|
||||
if group_config.summary_report_columns.show_video {
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"<td>{}{:.2}</td>",
|
||||
indent, summary_row.video_send_resolution
|
||||
);
|
||||
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"<td>{}{:.2}</td>",
|
||||
indent, summary_row.video_send_framerate
|
||||
);
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"<td>{}{:.2}</td>",
|
||||
indent, summary_row.video_send_framerate
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if group_config.summary_report_columns.show_receive_stats {
|
||||
@ -3363,17 +3378,19 @@ impl Html {
|
||||
indent, summary_row.audio_receive_loss
|
||||
);
|
||||
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"<td>{}{:.2}</td>",
|
||||
indent, summary_row.video_recv_resolution
|
||||
);
|
||||
if group_config.summary_report_columns.show_video {
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"<td>{}{:.2}</td>",
|
||||
indent, summary_row.video_recv_resolution
|
||||
);
|
||||
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"<td>{}{:.2}</td>",
|
||||
indent, summary_row.video_recv_framerate
|
||||
);
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"<td>{}{:.2}</td>",
|
||||
indent, summary_row.video_recv_framerate
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if group_config.summary_report_columns.show_receive_stats
|
||||
@ -3381,12 +3398,7 @@ impl Html {
|
||||
{
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"<td>{}{:.0}</td>",
|
||||
indent, summary_row.concealed_samples_total
|
||||
);
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"<td>{}{:.3}%</td>",
|
||||
"<td>{}{:.2}</td>",
|
||||
indent, summary_row.concealed_samples_pct
|
||||
);
|
||||
let _ = writeln!(
|
||||
@ -3489,13 +3501,25 @@ impl Html {
|
||||
buf.push_str("<th colspan=\"3\" style=\"width: 33%\">Test Case</th>\n");
|
||||
}
|
||||
if group_config.summary_report_columns.show_send_stats {
|
||||
buf.push_str("<th colspan=\"5\">Client Send Stats (average)</th>\n");
|
||||
let send_colspan = if group_config.summary_report_columns.show_video {
|
||||
5
|
||||
} else {
|
||||
3
|
||||
};
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"<th colspan=\"{send_colspan}\">Client Send Stats (average)</th>"
|
||||
);
|
||||
}
|
||||
if group_config.summary_report_columns.show_receive_stats {
|
||||
let recv_colspan = if group_config.summary_report_columns.show_dred_stats {
|
||||
8
|
||||
} else {
|
||||
5
|
||||
let recv_colspan = match (
|
||||
group_config.summary_report_columns.show_video,
|
||||
group_config.summary_report_columns.show_dred_stats,
|
||||
) {
|
||||
(true, true) => 7,
|
||||
(true, false) => 5,
|
||||
(false, true) => 5,
|
||||
(false, false) => 3,
|
||||
};
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
@ -3547,18 +3571,21 @@ impl Html {
|
||||
buf.push_str("<th>Packet Size</th>\n");
|
||||
buf.push_str("<th>Packet Rate</th>\n");
|
||||
buf.push_str("<th>Bitrate</th>\n");
|
||||
buf.push_str("<th>Resolution</th>\n");
|
||||
buf.push_str("<th>Framerate</th>\n");
|
||||
if group_config.summary_report_columns.show_video {
|
||||
buf.push_str("<th>Resolution</th>\n");
|
||||
buf.push_str("<th>Framerate</th>\n");
|
||||
}
|
||||
}
|
||||
if group_config.summary_report_columns.show_receive_stats {
|
||||
buf.push_str("<th>Packet Rate</th>\n");
|
||||
buf.push_str("<th>Bitrate</th>\n");
|
||||
buf.push_str("<th>Loss</th>\n");
|
||||
buf.push_str("<th>Resolution</th>\n");
|
||||
buf.push_str("<th>Framerate</th>\n");
|
||||
if group_config.summary_report_columns.show_video {
|
||||
buf.push_str("<th>Resolution</th>\n");
|
||||
buf.push_str("<th>Framerate</th>\n");
|
||||
}
|
||||
if group_config.summary_report_columns.show_dred_stats {
|
||||
buf.push_str("<th>Concealed</th>\n");
|
||||
buf.push_str("<th>Concealed %</th>\n");
|
||||
buf.push_str("<th>FEC</th>\n");
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,18 +24,20 @@ enum VideoCodec {
|
||||
}
|
||||
|
||||
message StreamSummary {
|
||||
optional DistributionSummary bitrate = 1;
|
||||
optional DistributionSummary packet_loss_pct = 2;
|
||||
optional DistributionSummary jitter = 3;
|
||||
optional DistributionSummary bitrate = 1;
|
||||
optional DistributionSummary packet_loss_pct = 2;
|
||||
optional DistributionSummary jitter = 3;
|
||||
// Only present for received video streams
|
||||
optional DistributionSummary freeze_count = 4;
|
||||
optional VideoCodec video_codec = 5;
|
||||
optional DistributionSummary freeze_count = 4;
|
||||
optional VideoCodec video_codec = 5;
|
||||
// Always present
|
||||
optional uint32 ssrc = 6;
|
||||
optional string codec_implementation = 7;
|
||||
optional uint32 ssrc = 6;
|
||||
optional string codec_implementation = 7;
|
||||
// Only present for received audio streams
|
||||
optional float audio_concealment_pct = 8;
|
||||
optional uint64 audio_redundant_packets = 9;
|
||||
optional DistributionSummary audio_concealment_pct = 8;
|
||||
optional uint64 audio_redundant_packets = 9;
|
||||
optional uint64 audio_jitter_buffer_flushes = 10;
|
||||
optional DistributionSummary audio_relative_arrival_delay = 11;
|
||||
}
|
||||
|
||||
message StreamSummaries {
|
||||
|
||||
@ -64,6 +64,8 @@ impl CallStateHandler for CallEndpoint {
|
||||
&& let Some(connected_sender) = &state.event_sync.connected
|
||||
{
|
||||
let _ = connected_sender.send(());
|
||||
} else if let CallState::Ended(_, summary) = call_state {
|
||||
info!("{}", summary);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fmt::Debug,
|
||||
fmt::{self, Debug, Display},
|
||||
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
|
||||
sync::{Arc, MutexGuard},
|
||||
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
|
||||
@ -13,6 +13,7 @@ use std::{
|
||||
|
||||
use anyhow::anyhow;
|
||||
use prost::Message;
|
||||
use serde_json::json;
|
||||
use sketches_ddsketch::{Config, DDSketch};
|
||||
|
||||
use crate::{
|
||||
@ -106,6 +107,64 @@ impl Timestamp {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CallSummary {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let call_id_hash_hex = self.call_id_hash.as_ref().map(|hash| {
|
||||
hash.iter()
|
||||
.map(|b| format!("{:02x}", b))
|
||||
.collect::<String>()
|
||||
});
|
||||
|
||||
let raw_stats_text_json = self.raw_stats_text.as_ref().and_then(|text| {
|
||||
match serde_json::from_str::<serde_json::Value>(text) {
|
||||
Ok(v) => Some(v),
|
||||
Err(e) => {
|
||||
error!("Failed to parse raw_stats_text as JSON: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let summary_json = json!({
|
||||
"CallSummary": {
|
||||
"call_id_hash": call_id_hash_hex,
|
||||
"start_time": u64::from(self.start_time),
|
||||
"end_time": u64::from(self.end_time),
|
||||
"is_survey_candidate": self.is_survey_candidate,
|
||||
"call_end_reason_text": &self.call_end_reason_text,
|
||||
"quality_stats": {
|
||||
"rtt_median_connection": self.quality_stats.rtt_median_connection,
|
||||
"audio_stats": {
|
||||
"rtt_median": self.quality_stats.audio_stats.rtt_median,
|
||||
"jitter_median_send": self.quality_stats.audio_stats.jitter_median_send,
|
||||
"jitter_median_recv": self.quality_stats.audio_stats.jitter_median_recv,
|
||||
"packet_loss_fraction_send": self.quality_stats.audio_stats.packet_loss_fraction_send,
|
||||
"packet_loss_fraction_recv": self.quality_stats.audio_stats.packet_loss_fraction_recv
|
||||
},
|
||||
"video_stats": {
|
||||
"rtt_median": self.quality_stats.video_stats.rtt_median,
|
||||
"jitter_median_send": self.quality_stats.video_stats.jitter_median_send,
|
||||
"jitter_median_recv": self.quality_stats.video_stats.jitter_median_recv,
|
||||
"packet_loss_fraction_send": self.quality_stats.video_stats.packet_loss_fraction_send,
|
||||
"packet_loss_fraction_recv": self.quality_stats.video_stats.packet_loss_fraction_recv
|
||||
}
|
||||
},
|
||||
// Just showing the length for the blob rather than a byte array.
|
||||
"raw_stats": self.raw_stats.as_ref().map(|blob| blob.len()),
|
||||
// We show the telemetry/stats blob json string inline.
|
||||
"raw_stats_text": raw_stats_text_json,
|
||||
}
|
||||
});
|
||||
|
||||
let summary = serde_json::to_string_pretty(&summary_json).map_err(|e| {
|
||||
error!("Failed to serialize CallSummary JSON: {}", e);
|
||||
fmt::Error
|
||||
})?;
|
||||
|
||||
f.write_str(&summary)
|
||||
}
|
||||
}
|
||||
|
||||
/// We need to be able to perform basic arithmetic operations on sample values
|
||||
/// and have the ability to compare them. We also want to be able to create them
|
||||
/// from counter values, and calculate their square roots.
|
||||
@ -217,9 +276,10 @@ struct StreamSummary {
|
||||
video_codec: StatsVideoCodecType,
|
||||
codec_implementation: Option<String>,
|
||||
// The most recent audio receive stream redundancy statistics.
|
||||
audio_samples_received: Option<u64>,
|
||||
audio_samples_concealed: Option<u64>,
|
||||
audio_concealed_samples: DistributionSummary<f32>,
|
||||
audio_redundant_packets: Option<u64>,
|
||||
audio_jitter_buffer_flushes: Option<u64>,
|
||||
audio_relative_arrival_delay: DistributionSummary<f32>,
|
||||
}
|
||||
|
||||
/// `StreamSummaries` is converted into `protobuf::call_summary::StreamSummaries`
|
||||
@ -550,12 +610,10 @@ impl From<(&u32, &StreamSummary)> for protobuf::call_summary::StreamSummary {
|
||||
},
|
||||
codec_implementation: summary.codec_implementation.clone(),
|
||||
ssrc: Some(*ssrc),
|
||||
audio_concealment_pct: summary
|
||||
.audio_samples_received
|
||||
.zip(summary.audio_samples_concealed)
|
||||
.filter(|(received, _)| *received > 0)
|
||||
.map(|(received, concealed)| (concealed as f32 / received as f32) * 100.0),
|
||||
audio_concealment_pct: summary.audio_concealed_samples.to_proto(),
|
||||
audio_redundant_packets: summary.audio_redundant_packets,
|
||||
audio_jitter_buffer_flushes: summary.audio_jitter_buffer_flushes,
|
||||
audio_relative_arrival_delay: summary.audio_relative_arrival_delay.to_proto(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -756,12 +814,16 @@ impl CallInfo {
|
||||
summary.bitrate.push(snapshot.bitrate);
|
||||
summary.packet_loss.push(snapshot.packets_lost_pct);
|
||||
summary.jitter.push(snapshot.jitter as f32);
|
||||
*summary.audio_samples_received.get_or_insert(0) +=
|
||||
snapshot.total_samples_received;
|
||||
*summary.audio_samples_concealed.get_or_insert(0) +=
|
||||
snapshot.concealed_samples;
|
||||
summary
|
||||
.audio_concealed_samples
|
||||
.push(snapshot.concealed_samples_pct);
|
||||
*summary.audio_redundant_packets.get_or_insert(0) +=
|
||||
snapshot.fec_packets_received;
|
||||
*summary.audio_jitter_buffer_flushes.get_or_insert(0) +=
|
||||
snapshot.jitter_buffer_flushes;
|
||||
summary
|
||||
.audio_relative_arrival_delay
|
||||
.push(snapshot.relative_arrival_delay_per_packet as f32);
|
||||
});
|
||||
self.stats_sets
|
||||
.push_audio_recv_stream_stats(snapshot.into());
|
||||
@ -1489,10 +1551,18 @@ mod test {
|
||||
}),
|
||||
freeze_count: None,
|
||||
video_codec: None,
|
||||
codec_implementation: None,
|
||||
ssrc: Some(v),
|
||||
audio_concealment_pct: None,
|
||||
codec_implementation: None,
|
||||
audio_concealment_pct: Some(protobuf::call_summary::DistributionSummary {
|
||||
mean: Some(100.0),
|
||||
std_dev: Some(0.0),
|
||||
min_val: Some(100.0),
|
||||
max_val: Some(100.0),
|
||||
sample_count: Some(50000),
|
||||
}),
|
||||
audio_redundant_packets: None,
|
||||
audio_jitter_buffer_flushes: None,
|
||||
audio_relative_arrival_delay: None,
|
||||
})
|
||||
.collect::<Vec<protobuf::call_summary::StreamSummary>>()
|
||||
}
|
||||
|
||||
@ -99,6 +99,20 @@ macro_rules! delta {
|
||||
};
|
||||
}
|
||||
|
||||
fn signed_delta_fn<T, F, V>(lhs: &T, rhs: &T, extract: F) -> V
|
||||
where
|
||||
F: Fn(&T) -> V,
|
||||
V: Sub<Output = V>,
|
||||
{
|
||||
extract(lhs) - extract(rhs)
|
||||
}
|
||||
|
||||
macro_rules! signed_delta {
|
||||
($lhs:tt, $rhs:tt, $field:tt) => {
|
||||
signed_delta_fn($lhs, $rhs, |stats| stats.$field)
|
||||
};
|
||||
}
|
||||
|
||||
fn compute_packets_lost_pct(packets_lost: u32, packets_total: u32) -> f32 {
|
||||
(packets_lost as f32 * 100.0)
|
||||
.naive_checked_div(packets_total as f32)
|
||||
@ -280,10 +294,11 @@ pub struct AudioReceiverStatsSnapshot {
|
||||
pub jitter: f64,
|
||||
pub jitter_buffer_delay: f64,
|
||||
pub jitter_buffer_target_delay: f64,
|
||||
pub jitter_buffer_flushes: u64,
|
||||
pub audio_energy: f64,
|
||||
pub total_samples_received: u64,
|
||||
pub concealed_samples: u64,
|
||||
pub concealed_samples_pct: f32,
|
||||
pub fec_packets_received: u64,
|
||||
pub relative_arrival_delay_per_packet: f64,
|
||||
}
|
||||
|
||||
impl Display for AudioReceiverStatsSnapshot {
|
||||
@ -296,25 +311,27 @@ impl Display for AudioReceiverStatsSnapshot {
|
||||
jitter,
|
||||
jitter_buffer_delay,
|
||||
jitter_buffer_target_delay,
|
||||
jitter_buffer_flushes,
|
||||
audio_energy,
|
||||
total_samples_received,
|
||||
concealed_samples,
|
||||
concealed_samples_pct,
|
||||
fec_packets_received,
|
||||
relative_arrival_delay_per_packet,
|
||||
} = self;
|
||||
write!(
|
||||
f,
|
||||
"{},\
|
||||
{ssrc},\
|
||||
{packets_per_second:.1},\
|
||||
{packets_lost_pct:.1}%,\
|
||||
{packets_lost_pct:.2}%,\
|
||||
{bitrate:.1}bps,\
|
||||
{jitter:.0}ms,\
|
||||
{audio_energy:.3},\
|
||||
{jitter_buffer_delay:.0}ms,\
|
||||
{jitter_buffer_target_delay:.0}ms,\
|
||||
{total_samples_received},\
|
||||
{concealed_samples},\
|
||||
{fec_packets_received}",
|
||||
{jitter_buffer_flushes},\
|
||||
{concealed_samples_pct:.2}%,\
|
||||
{fec_packets_received},\
|
||||
{relative_arrival_delay_per_packet:.0}ms",
|
||||
Self::LOG_MARKER
|
||||
)
|
||||
}
|
||||
@ -331,9 +348,10 @@ impl AudioReceiverStatsSnapshot {
|
||||
audio_energy,\
|
||||
jitter_buffer_delay,\
|
||||
jitter_buffer_target_delay,\
|
||||
total_samples_received,\
|
||||
concealed_samples,\
|
||||
fec_packets_received";
|
||||
jitter_buffer_flushes,\
|
||||
concealed_samples_pct,\
|
||||
fec_packets_received,\
|
||||
relative_arrival_delay_per_packet";
|
||||
|
||||
fn derive(
|
||||
curr_stats: &AudioReceiverStatistics,
|
||||
@ -342,7 +360,7 @@ impl AudioReceiverStatsSnapshot {
|
||||
prev_jitter_buffer_delay: f64,
|
||||
prev_jitter_buffer_target_delay: f64,
|
||||
) -> Self {
|
||||
let packets_lost_delta = delta!(curr_stats, prev_stats, packets_lost);
|
||||
let signed_packets_lost_delta = signed_delta!(curr_stats, prev_stats, packets_lost);
|
||||
let packets_received_delta = delta!(curr_stats, prev_stats, packets_received);
|
||||
let jitter_buffer_delay_delta = delta!(curr_stats, prev_stats, jitter_buffer_delay);
|
||||
let jitter_buffer_target_delay_delta =
|
||||
@ -353,13 +371,25 @@ impl AudioReceiverStatsSnapshot {
|
||||
let audio_energy_delta = delta!(curr_stats, prev_stats, total_audio_energy);
|
||||
let total_samples_received_delta = delta!(curr_stats, prev_stats, total_samples_received);
|
||||
let concealed_samples_delta = delta!(curr_stats, prev_stats, concealed_samples);
|
||||
let silent_concealed_samples_delta =
|
||||
delta!(curr_stats, prev_stats, silent_concealed_samples);
|
||||
let fec_packets_received_delta = delta!(curr_stats, prev_stats, fec_packets_received);
|
||||
let jitter_buffer_flushes_delta = delta!(curr_stats, prev_stats, jitter_buffer_flushes);
|
||||
let packets_discarded_delta = delta!(curr_stats, prev_stats, packets_discarded);
|
||||
let relative_packet_arrival_delay_delta =
|
||||
delta!(curr_stats, prev_stats, relative_packet_arrival_delay);
|
||||
|
||||
let packets_per_second =
|
||||
compute_packets_per_second(packets_received_delta, seconds_elapsed);
|
||||
let packets_lost = packets_lost_delta.max(0) as u32;
|
||||
let packets_lost_pct =
|
||||
compute_packets_lost_pct(packets_lost, packets_received_delta + packets_lost);
|
||||
let packets_lost_pct = {
|
||||
let numerator = signed_packets_lost_delta + packets_discarded_delta as i32;
|
||||
let denominator = packets_received_delta as i32 + signed_packets_lost_delta;
|
||||
if denominator > 0 {
|
||||
((numerator as f32 * 100.0) / denominator as f32).max(0.0)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
let bitrate = compute_bitrate(bytes_received_delta, seconds_elapsed);
|
||||
|
||||
let jitter = 1000.0 * curr_stats.jitter;
|
||||
@ -383,10 +413,18 @@ impl AudioReceiverStatsSnapshot {
|
||||
jitter,
|
||||
jitter_buffer_delay,
|
||||
jitter_buffer_target_delay,
|
||||
jitter_buffer_flushes: jitter_buffer_flushes_delta,
|
||||
audio_energy: audio_energy_delta,
|
||||
total_samples_received: total_samples_received_delta,
|
||||
concealed_samples: concealed_samples_delta,
|
||||
concealed_samples_pct: (concealed_samples_delta
|
||||
.saturating_sub(silent_concealed_samples_delta)
|
||||
as f32
|
||||
* 100.0)
|
||||
.naive_checked_div(total_samples_received_delta as f32)
|
||||
.unwrap_or(0.0),
|
||||
fec_packets_received: fec_packets_received_delta,
|
||||
relative_arrival_delay_per_packet: (1000.0 * relative_packet_arrival_delay_delta)
|
||||
.naive_checked_div(packets_received_delta as f64)
|
||||
.unwrap_or(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1210,7 +1248,10 @@ pub struct AudioReceiverStatistics {
|
||||
pub estimated_playout_timestamp: f64,
|
||||
pub total_samples_received: u64,
|
||||
pub concealed_samples: u64,
|
||||
pub silent_concealed_samples: u64,
|
||||
pub fec_packets_received: u64,
|
||||
pub packets_discarded: u64,
|
||||
pub relative_packet_arrival_delay: f64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user