Skip to content
This repository was archived by the owner on Sep 19, 2024. It is now read-only.

Commit 7da831e

Browse files
authored
[RTC-230] Complete packet loss test (#292)
1 parent dcacb0d commit 7da831e

18 files changed

+356
-123
lines changed

integration_test/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ Simple test using four containers: `test_videoroom` and three `test_browser`s. T
1313
to videoroom, start sending and receiving media, then we apply packet loss on one of the browser
1414
containers and verify that other browsers receive less frames only from this particular one.
1515

16-
To run, execute `./test_packet_loss.sh`
16+
To run, execute `./run_packet_loss_test.sh`

integration_test/docker-compose.yml

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ services:
2525
USE_TLS: "true"
2626
KEY_FILE_PATH: "priv/cert/selfsigned_key.pem"
2727
CERT_FILE_PATH: "priv/cert/selfsigned.pem"
28-
USE_RESULT_RECEIVER: "true"
2928
networks:
3029
network:
3130
ipv4_address: 192.168.0.50

integration_test/test_packet_loss.sh integration_test/run_packet_loss_test.sh

+14-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/bin/bash
22

3+
TEST_DEPS="docker pumba"
4+
35
# test phase durations (in seconds)
46
LOSS_DURATION=60
57

@@ -12,9 +14,17 @@ set -e
1214

1315
rm -rf $SHARED_VOLUME_DIR
1416

17+
for dep in $TEST_DEPS; do
18+
if [ ! $(which $dep) ]; then
19+
echo "Unable to run test (missing dependency: $dep)"
20+
exit 1
21+
fi
22+
done
23+
1524
docker compose build
1625
echo "Running packet loss test"
1726
docker compose up --exit-code-from=server server browser0 browser1 browser2 &
27+
COMPOSE_JOB_ID=$!
1828

1929
cleanup() {
2030
docker compose down --rmi local --volumes
@@ -32,18 +42,15 @@ while [ ! -f "${SHARED_VOLUME_DIR}/ENABLE_PACKET_LOSS" ]; do
3242
sleep 1
3343
done
3444

35-
# The netem command will return an error when a container is stopped before the packet loss duration
36-
# is up. This means we either need to kill it (and know when to do that), or ignore the error:
37-
set +e
38-
3945
echo "Applying packet loss to $APPLY_LOSS_TO for $LOSS_DURATION seconds"
4046
pumba netem \
4147
--duration "${LOSS_DURATION}s" \
4248
loss \
4349
--percent 50 \
44-
$APPLY_LOSS_TO
50+
$APPLY_LOSS_TO &
4551

46-
echo "Network condition simulation over. Waiting for the docker-compose job to complete..."
52+
NETEM_JOB_ID=$!
53+
trap "set +e; kill $NETEM_JOB_ID; cleanup" EXIT
4754

48-
wait
55+
wait $COMPOSE_JOB_ID
4956
exit $?

integration_test/test_browser/lib/test_browser/mustang.ex

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ defmodule TestBrowser.Mustang do
66
def join(browser, options) do
77
page = browser |> Playwright.Browser.new_page()
88
_response = Playwright.Page.goto(page, options.target_url)
9-
Playwright.Page.on(page, :console, fn msg -> Logger.info("Browser console: #{msg.params.message.message_text}") end)
9+
10+
Playwright.Page.on(page, :console, fn msg ->
11+
Logger.info("Browser console: #{msg.params.message.message_text}")
12+
end)
1013

1114
page
1215
|> Playwright.Page.locator("[id=#{options.start_button}]")
@@ -46,7 +49,6 @@ defmodule TestBrowser.Mustang do
4649
{:notify_server, msg} = action ->
4750
Logger.info("mustang: #{options.id}, action: #{inspect(action)}")
4851
send(options.server, {msg, options.id})
49-
5052
end)
5153

5254
ctx

integration_test/test_browser/mix.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ defmodule TestBrowser.MixProject do
2525
defp deps do
2626
[
2727
{:cowlib, "~> 2.11", override: true},
28-
{:stampede, github: "membraneframework-labs/stampede-elixir"},
28+
{:stampede, github: "membraneframework-labs/stampede-elixir"}
2929
]
3030
end
3131
end

integration_test/test_browser/test/test_browser_test.exs

+8-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ defmodule TestBrowserTest do
2323
Process.sleep(@test_warmup_time)
2424

2525
server = System.get_env("SERVER_HOSTNAME", "localhost")
26-
server_receiver = {TestVideoroom.TestResultReceiver, String.to_atom(server <> "@" <> server)}
26+
27+
server_receiver =
28+
{TestVideoroom.Integration.ResultReceiver, String.to_atom(server <> "@" <> server)}
29+
2730
stats_task = Task.async(fn -> receive_stats(server_receiver) end)
2831

2932
hostname = with {:ok, hostname} <- :inet.gethostname(), do: to_string(hostname)
@@ -35,14 +38,15 @@ defmodule TestBrowserTest do
3538
receiver: stats_task.pid,
3639
server: server_receiver,
3740
actions: [
38-
{:get_stats, @simulcast_inbound_stats, @stats_number, @stats_interval, tag: :after_warmup},
41+
{:get_stats, @simulcast_inbound_stats, @stats_number, @stats_interval,
42+
tag: :after_warmup},
3943
{:wait, @packet_loss_warmup_time},
4044
{:notify_server, :enable_packet_loss},
4145
{:wait, @packet_loss_warmup_time},
4246
{:get_stats, @simulcast_inbound_stats, @stats_number, @stats_interval,
43-
tag: :after_applying_packet_loss_on_one_user},
47+
tag: :after_applying_packet_loss_on_one_user},
4448

45-
# Get local stream stats so that we know our stream ID
49+
# Get local stream stats so that we know our peer ID
4650
{:get_stats, @simulcast_outbound_stats, 1, 0, tag: :local_stream_stats}
4751
],
4852
id: hostname

integration_test/test_videoroom/Dockerfile

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ RUN mix deps.compile
3434
COPY ${APP_PATH}/lib lib/
3535
RUN mix compile
3636

37+
COPY ${APP_PATH}/test test/
38+
3739
COPY ${APP_PATH}/config/runtime.exs config/
3840

3941
COPY ${APP_PATH}/assets assets/

integration_test/test_videoroom/assets/js/app.js

+2-9
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ async function refreshStats(statsFunction) {
108108
.webrtc.connection}`;
109109
return;
110110
}
111-
// we are accessing room's private field, in the name of science of course...
112-
const stats = await statsFunction(room.webrtc.connection);
111+
const stats = await statsFunction(room);
113112

114113
putStats(stats)
115114
}
@@ -147,11 +146,5 @@ localHighEncodingButton.onclick = () => { toggleSimulcastEncoding(localHighEncod
147146
peerLowEncodingButton.onclick = () => { room.selectPeerSimulcastEncoding("l") }
148147
peerMediumEncodingButton.onclick = () => { room.selectPeerSimulcastEncoding("m") }
149148
peerHighEncodingButton.onclick = () => { room.selectPeerSimulcastEncoding("h") }
150-
inboundSimulcastStatsButton.onclick = () => {
151-
refreshStats(async (connection) => {
152-
let stats = await inboundSimulcastStreamStats(connection)
153-
stats.encoding = room.getPeerEncoding()
154-
return stats
155-
})
156-
}
149+
inboundSimulcastStatsButton.onclick = () => { refreshStats(inboundSimulcastStreamStats) }
157150
outboundSimulcastStatsButton.onclick = () => { refreshStats(outboundSimulcastStreamStats) }

integration_test/test_videoroom/assets/js/room.js

+5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class Room {
3535
this.encodings = ["l", "m", "h"];
3636
this.peerMetadata = null;
3737
this.trackMetadata = null;
38+
this.peerIdToVideoTrack = {};
3839
this.selfId = null;
3940
this.simulcast = simulcast;
4041
this.remoteTracks = new Map();
@@ -71,6 +72,10 @@ class Room {
7172

7273
video.srcObject = ctx.stream;
7374
this.remoteTracks.set(ctx.trackId, ctx);
75+
76+
if (ctx.track.kind === "video") {
77+
this.peerIdToVideoTrack[ctx.endpoint.id] = ctx.track;
78+
}
7479
});
7580

7681
this.webrtc.on("trackAdded", (ctx) => {

integration_test/test_videoroom/assets/js/stats.js

+31-19
Original file line numberDiff line numberDiff line change
@@ -94,36 +94,47 @@ async function isAudioPlayingFirefox(peerConnection, audioTrack) {
9494
return packetsStart > 0 && packetsEnd > 0 && packetsEnd > packetsStart;
9595
}
9696

97-
export async function inboundSimulcastStreamStats(peerConnection) {
98-
const stats = await peerConnection.getStats();
99-
let data = { height: null, width: null, framesPerSecond: 0 }
100-
for (let [_key, report] of stats) {
101-
if (report.type == "inbound-rtp") {
102-
data = getDataFromReport(report)
103-
data.framesReceived = report.framesReceived
97+
export async function inboundSimulcastStreamStats(room) {
98+
// we are accessing room's private fields, in the name of science of course...
99+
const peerConnection = room.webrtc.connection;
100+
101+
const stats = room.peers.map(async (peer) => {
102+
const videoTrack = room.peerIdToVideoTrack[peer.id];
103+
const track_stats = await peerConnection.getStats(videoTrack);
104+
105+
let data = { peerId: peer.id, height: null, width: null, framesPerSecond: 0 }
106+
for (let [_key, report] of track_stats) {
107+
if (report.type == "inbound-rtp") {
108+
data = getDataFromReport(report)
109+
data.framesReceived = report.framesReceived
110+
data.peerId = peer.id
111+
// The following works for one peer only...
112+
data.encoding = room.getPeerEncoding()
113+
}
104114
}
105-
}
106-
107-
return data
108-
}
109115

116+
return data;
117+
});
110118

119+
return (await Promise.all(stats));
120+
}
111121

112-
export async function outboundSimulcastStreamStats(peerConnection) {
122+
export async function outboundSimulcastStreamStats(room) {
123+
const peerConnection = room.webrtc.connection;
113124
const stats = await peerConnection.getStats();
114125

115-
let streams = { "l": null, "m": null, "h": null }
126+
let data = { peerId: room.selfId, "l": null, "m": null, "h": null }
116127
for (let [_key, report] of stats) {
117128
if (report.type == "outbound-rtp") {
118129
let rid = report.rid
119-
streams[rid] = getDataFromReport(report)
120-
streams[rid].framesSent = report.framesSent
121-
streams[rid].qualityLimitationDuration = report["qualityLimitationDurations"]
122-
streams[rid].qualityLimitationReason = report["qualityLimitationReason"]
130+
data[rid] = getDataFromReport(report)
131+
data[rid].framesSent = report.framesSent
132+
data[rid].qualityLimitationDuration = report["qualityLimitationDurations"]
133+
data[rid].qualityLimitationReason = report["qualityLimitationReason"]
123134
}
124135
}
125136

126-
return streams
137+
return data
127138
}
128139

129140
function getDataFromReport(values) {
@@ -134,7 +145,8 @@ function getDataFromReport(values) {
134145
return data
135146
}
136147

137-
export async function remoteStreamsStats(peerConnection) {
148+
export async function remoteStreamsStats(room) {
149+
const peerConnection = room.webrtc.connection;
138150
const streams = peerConnection.getRemoteStreams();
139151

140152
const firefoxTrackActive = peerConnection

integration_test/test_videoroom/config/runtime.exs

+1-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ config :test_videoroom,
5656
|> ConfigParser.parse_port_number("INTEGRATED_TLS_TURN_PORT"),
5757
integrated_turn_pkey: System.get_env("INTEGRATED_TURN_PKEY"),
5858
integrated_turn_cert: System.get_env("INTEGRATED_TURN_CERT"),
59-
integrated_turn_domain: System.get_env("VIRTUAL_HOST"),
60-
use_result_receiver: System.get_env("USE_RESULT_RECEIVER") == "true"
59+
integrated_turn_domain: System.get_env("VIRTUAL_HOST")
6160

6261
protocol = if System.get_env("USE_TLS") == "true", do: :https, else: :http
6362

integration_test/test_videoroom/docker-entrypoint.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
if [ $# -gt 0 ]; then
55
exec "$@"
66
else
7-
elixir --sname $(hostname) --cookie "$ERL_COOKIE" -S mix run --no-halt
7+
elixir --sname $(hostname) --cookie "$ERL_COOKIE" -S mix test.containerised --only packet_loss_test
88
fi

integration_test/test_videoroom/lib/test_videoroom/application.ex

-5
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,6 @@ defmodule TestVideoroom.Application do
1010
TestVideoroomWeb.Endpoint
1111
]
1212

13-
children =
14-
if Application.fetch_env!(:test_videoroom, :use_result_receiver),
15-
do: children ++ [{TestVideoroom.TestResultReceiver, restart: :temporary}],
16-
else: children
17-
1813
opts = [strategy: :one_for_one, name: TestVideoroom.Supervisor]
1914

2015
Supervisor.start_link(children, opts)

integration_test/test_videoroom/lib/test_videoroom/test_result_receiver.ex

-63
This file was deleted.

integration_test/test_videoroom/mix.exs

+3-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ defmodule TestVideoroom.MixProject do
4242

4343
defp aliases() do
4444
[
45-
test: ["assets.deploy", "test"],
46-
"assets.deploy": ["esbuild default --minify", "phx.digest"]
45+
test: ["assets.deploy", "test --exclude containerised"],
46+
"assets.deploy": ["esbuild default --minify", "phx.digest"],
47+
"test.containerised": ["test --only containerised"]
4748
]
4849
end
4950
end

0 commit comments

Comments
 (0)