mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
trace: add audio tracing and plotting script
Change-Id: Ie5c8fe1d9cc4e5a38a623a1cd4755c00ad4f4904
This commit is contained in:

committed by
Sébastien Blin

parent
cb3d3c3c85
commit
53f60a1aa4
1
tools/trace/audio/.gitignore
vendored
Normal file
1
tools/trace/audio/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
out.png
|
152
tools/trace/audio/audio.py
Normal file
152
tools/trace/audio/audio.py
Normal file
@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quick-and-dirty script to plot lttng trace data of the jami audio system
|
||||
|
||||
Depends on `python3-bt2` package:
|
||||
https://babeltrace.org/docs/v2.0/python/bt2/
|
||||
|
||||
If using a virtual environment, you need to link the system babeltrace2 python3
|
||||
package to your virtual environment, something like:
|
||||
`ln -s /usr/lib/python3/dist-packages/bt2 "$VIRTUAL_ENV"/lib/python3*/site-packages/`
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
import dataclasses
|
||||
from typing import Dict, List
|
||||
import sys
|
||||
|
||||
import bt2
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Intervals:
|
||||
"""Keep track of timestamps and the interval intervals between them"""
|
||||
|
||||
intervals: List[int] = dataclasses.field(default_factory=list)
|
||||
times: List[int] = dataclasses.field(default_factory=list)
|
||||
last_timestamp: int = 0
|
||||
|
||||
def add_event(self, timestamp: int):
|
||||
"""Add a new timestamp and calculate the interval"""
|
||||
if self.last_timestamp == 0:
|
||||
self.intervals.append(0)
|
||||
else:
|
||||
self.intervals.append(timestamp - self.last_timestamp)
|
||||
|
||||
self.times.append(timestamp)
|
||||
|
||||
self.last_timestamp = timestamp
|
||||
|
||||
def filter(self, earliest: int, latest: int):
|
||||
"""Filter out entries that are not in between `earliest` and `latest` timestamps"""
|
||||
new_intervals = []
|
||||
new_times = []
|
||||
for i, val in enumerate(self.times):
|
||||
if val > earliest and val < latest:
|
||||
new_times.append(val)
|
||||
new_intervals.append(self.intervals[i])
|
||||
|
||||
self.times = new_times
|
||||
self.intervals = new_intervals
|
||||
|
||||
|
||||
def analyze(filename: str):
|
||||
"""Process trace file"""
|
||||
# running_averages: Dict[str, RunningAverage] = {}
|
||||
intervals: Dict[str, Intervals] = {}
|
||||
desired_event_regex = re.compile("jami:(call|audio|conference)")
|
||||
|
||||
for message in bt2.TraceCollectionMessageIterator(filename):
|
||||
# pylint: disable=protected-access
|
||||
if not isinstance(message, bt2._EventMessageConst):
|
||||
continue
|
||||
ns_from_origin = message.default_clock_snapshot.ns_from_origin
|
||||
name = message.event.name
|
||||
|
||||
if not desired_event_regex.match(name):
|
||||
continue
|
||||
|
||||
if "id" in message.event.payload_field and name != "jami:conference_add_participant":
|
||||
name = f"{name}({str(message.event.payload_field['id'])})"
|
||||
|
||||
if not name in intervals:
|
||||
# running_averages[name] = RunningAverage()
|
||||
intervals[name] = Intervals()
|
||||
|
||||
# running_averages[name].add_val(ns_from_origin)
|
||||
intervals[name].add_event(ns_from_origin)
|
||||
|
||||
# print(f"{ns_from_origin / 10e9:.9f}: {name}")
|
||||
|
||||
for key, val in intervals.items():
|
||||
print(
|
||||
f"event: {key}, average: {(sum(val.intervals) / len(val.intervals)) / 1e9:.9f}"
|
||||
)
|
||||
|
||||
earliest_recorded = intervals["jami:audio_layer_put_recorded_end"].times[0]
|
||||
latest_recorded = intervals["jami:audio_layer_put_recorded_end"].times[-1]
|
||||
|
||||
earliest_recorded += int(1e9) # start graph 1 second later
|
||||
|
||||
print(
|
||||
f"earliest: {earliest_recorded / 1e9 :.9f}, latest: {latest_recorded / 1e9:.9f}"
|
||||
)
|
||||
|
||||
for _, interval_val in intervals.items():
|
||||
interval_val.filter(earliest_recorded, latest_recorded)
|
||||
|
||||
# convert from unix timestamp to seconds since start
|
||||
for _, interval_val in intervals.items():
|
||||
for i, _ in enumerate(interval_val.times):
|
||||
interval_val.times[i] -= earliest_recorded
|
||||
|
||||
plot(intervals, filename)
|
||||
|
||||
|
||||
def plot(intervals: Dict[str, Intervals], filename: str):
|
||||
"""Plot audio event data"""
|
||||
add_part = intervals["jami:conference_add_participant"]
|
||||
|
||||
fig, axis = plt.subplots(figsize=(20, 9))
|
||||
|
||||
axis.set_xlabel("seconds since start")
|
||||
axis.set_ylabel("interval between events (seconds)")
|
||||
axis.set_title("jami:audio*")
|
||||
|
||||
# only plot audio events
|
||||
events_to_plot_regex = re.compile("jami:audio")
|
||||
|
||||
# plot each desired interval
|
||||
for interval_key, interval_val in intervals.items():
|
||||
if not events_to_plot_regex.match(interval_key):
|
||||
continue
|
||||
|
||||
axis.plot(
|
||||
[i / 1e9 for i in interval_val.times],
|
||||
[i / 1e9 for i in interval_val.intervals],
|
||||
label=interval_key,
|
||||
)
|
||||
|
||||
# generate vertical line for each time a new participant is added to conference
|
||||
for time in add_part.times:
|
||||
axis.axvline(
|
||||
time / 1e9,
|
||||
color="black",
|
||||
ls="--",
|
||||
)
|
||||
|
||||
axis.legend()
|
||||
|
||||
# save it as out.png
|
||||
filename = "out.png"
|
||||
fig.savefig(filename, dpi=100)
|
||||
print(f"png saved as {filename}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("usage: python3 audio.py <lttng trace directory>", file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
analyze(sys.argv[1])
|
65
tools/trace/audio/lttng-setup.sh
Executable file
65
tools/trace/audio/lttng-setup.sh
Executable file
@ -0,0 +1,65 @@
|
||||
#!/bin/sh
|
||||
|
||||
# this script sets up an lttng session and enables triggers for call start and conference end
|
||||
# ideally, this script would be more configurable, right now it's hardcoded and hacky
|
||||
|
||||
if lttng status | grep -q "Recording session"; then
|
||||
echo "lttng session already active, exiting"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SESSION="jami"
|
||||
|
||||
# Prepare a capture session
|
||||
|
||||
lttng create "$SESSION"
|
||||
|
||||
# Enable kernel and user channels
|
||||
|
||||
# lttng enable-channel -k --subbuf-size 4096K kernel
|
||||
lttng enable-channel -u --subbuf-size 1024K user
|
||||
|
||||
# Enable all kernel syscalls tracing
|
||||
# lttng enable-event --kernel --syscall --all --channel=kernel
|
||||
|
||||
# Enable sched_* (scheduler) functions tracing
|
||||
# lttng enable-event --kernel --channel=kernel "$(lttng list --kernel | grep sched_ | awk '{ print $1 }' | xargs | sed -e 's/ /,/g')"
|
||||
|
||||
# Enable net_* functions tracing
|
||||
# lttng enable-event --kernel --channel=kernel "$(lttng list --kernel | grep net_ | awk '{ print $1 }' | xargs | sed -e 's/ /,/g')"
|
||||
|
||||
# Userspace tracing
|
||||
# Add support for tracef() traces
|
||||
# You need to link application with @-llttng-ust@
|
||||
# lttng enable-event --userspace 'lttng_ust_tracef:*' --channel=user
|
||||
|
||||
# Add context for libc (including malloc, etc)
|
||||
export LD_PRELOAD="$LD_PRELOAD":liblttng-ust-libc-wrapper.so
|
||||
lttng enable-event --userspace 'lttng_ust_libc:*' --channel=user
|
||||
lttng add-context --userspace -t vtid -t procname
|
||||
|
||||
# Add context for pthread (including mutexes)
|
||||
export LD_PRELOAD="$LD_PRELOAD":liblttng-ust-pthread-wrapper.so
|
||||
lttng enable-event --userspace 'lttng_ust_pthread:*' --channel=user
|
||||
|
||||
# Add context for function profiling
|
||||
export LD_PRELOAD="$LD_PRELOAD":liblttng-ust-cyg-profile.so
|
||||
lttng enable-event --userspace --all --channel=user
|
||||
lttng add-context --userspace -t vpid -t vtid -t procname
|
||||
|
||||
# loop over lines in our for-loop, since triggers might have spaces in their names
|
||||
OLDIFS="$IFS"
|
||||
IFS='
|
||||
'
|
||||
|
||||
# remove all triggers
|
||||
for trigger in $(lttng list-triggers | sed -nr 's/- name: (.*)$/\1/p'); do
|
||||
echo "removing trigger: $trigger"
|
||||
lttng remove-trigger "$trigger"
|
||||
done
|
||||
|
||||
IFS=$OLDIFS
|
||||
|
||||
# add start and end trigger
|
||||
lttng add-trigger --name "jami call start" --condition=event-rule-matches --type=user --name='jami:call_start' --action=start-session $SESSION
|
||||
lttng add-trigger --name "jami conference end" --condition=event-rule-matches --type=user --name="jami:conference_end" --action=stop-session $SESSION
|
Reference in New Issue
Block a user