Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,12 @@ Load test example
play_mode=full_playback \
bitrate=lowest_bitrate \
locust -f load_generator/locustfiles/vod_dash_hls_sequence.py \
--no-web -c 1 -r 1 --run-time 10s --only-summary \
--csv=test-results/output_example

--headless \
--users 1 \
--spawn-rate 1 \
--run-time 10s \
--only-summary \
--csv=test-results/output_example


Use the tool through a Docker image
Expand Down Expand Up @@ -109,9 +112,18 @@ Load test example
-e "LOCUST_ONLY_SUMMARY=true" \
-p 8089:8089 \
unifiedstreaming/load-generator:latest \
--no-web


--headless \

Changes 2025-06-03
^^^^^^^^^^^^^^^^^^
- Update to latest version of Locust (2.37.6)
- Update to newer version of requests
- Updated Python scripts to work with new Locust version
- Added new option "with_buffer"

A new option is added "with_buffer=true" that can be used in combination with "mode=vod".
It will load 3 segments first; then wait the time of one segment-length between loading
each next segment.



Expand Down
112 changes: 46 additions & 66 deletions load_generator/common/dash_emulation.py
Original file line number Diff line number Diff line change
@@ -1,112 +1,92 @@
import os
import sys
import logging
from locust import TaskSequence, seq_task, TaskSet, task
import random

from locust import SequentialTaskSet, task
from locust.exception import StopUser
from mpegdash.parser import MPEGDASHParser
from load_generator.common import dash_utils
from load_generator.config import default
import random
from locust.exception import StopLocust

logger = logging.getLogger(__name__)

MANIFEST_FILE = os.getenv('MANIFEST_FILE')
PLAY_MODE = os.getenv("play_mode")
BUFFER_SIZE = os.getenv("buffer_size")
BUFFER_SIZE = int(BUFFER_SIZE) # Cast os.environ str to int
BITRATE = os.getenv("bitrate")

PLAY_MODE = os.getenv("play_mode", "full_playback")
BUFFER_SIZE = int(os.getenv("buffer_size", "0"))
BITRATE = os.getenv("bitrate", "highest_bitrate")
LOGGER = True


class class_dash_player(TaskSet):
"""
Simple MPEG-DASH emulation of a player
Receives an MPEG-DASH /.mpd manifest
"""
class class_dash_player(SequentialTaskSet):
base_url = None
mpd_body = None
mpd_object = None
print("started task")

@seq_task(1)
@task
def get_manifest(self):
print("MPEG-DASH child player running ...")
base_url = f"{self.locust.host}/{MANIFEST_FILE}"
logger.info("MPEG-DASH player starting...")
base_url = f"{self.user.host}/{MANIFEST_FILE}"
if LOGGER:
print(base_url)
self.base_url = f"{base_url}" # It should already be a /.mpd
logger.info(f"Requesting manifest: {base_url}")
response_mpd = self.client.get(f"{base_url}", name="merged")
print(f"Requesting: {base_url}")
self.base_url = base_url

response_mpd = self.client.get(base_url, name="merged")
self.mpd_body = response_mpd.text
if response_mpd.status_code == 0 or response_mpd.status_code == 404:
logger.info("Make sure your Manifest URI is reachable")
try:
sys.exit(1)
except SystemExit:
os._exit(1)
else:
pass

@seq_task(2)
def dash_parse(self, reschedule=True):
"""
Parse Manifest file to MPEGDASHParser
"""
logger.info("Obtained MPD body ")
if self.mpd_body is not None:
self.mpd_object = MPEGDASHParser.parse(self.mpd_body)
print(f"self.mpd_object: {self.mpd_object}")
else:
# self.interrupt()
pass

@seq_task(3)
if response_mpd.status_code in [0, 404]:
logger.error("Manifest unreachable")
self.environment.runner.quit() # Stop the test early
raise StopUser()

@task
def dash_parse(self):
if not self.mpd_body:
logger.warning("No MPD content to parse.")
raise StopUser()

logger.info("Parsing MPD content...")
self.mpd_object = MPEGDASHParser.parse(self.mpd_body)

@task
def dash_playback(self):
"""
Create a list of the avaialble segment URIs with
its specific media representation
"""
logger.info("Dash playback")
logger.info("Starting DASH playback...")

all_reprs, period_segments = dash_utils.prepare_playlist(
self.base_url, self.mpd_object
)
if all_reprs != 0 and period_segments != 0:

if all_reprs and period_segments:
selected_representation = dash_utils.select_representation(
period_segments["abr"],
BITRATE # highest_bitrate, lowest_bitrate, random_bitrate
period_segments["abr"], BITRATE
)

chosen_video = selected_representation[1]
chosen_audio = selected_representation[0]

if PLAY_MODE == "full_playback":
if BUFFER_SIZE == 0:
dash_utils.simple_playback(
self,
period_segments,
chosen_video,
chosen_audio,
False # Delay in between every segment request
self, period_segments, chosen_video, chosen_audio, False
)
else:
dash_utils.playback_w_buffer(
self,
period_segments,
chosen_video,
chosen_audio,
BUFFER_SIZE
self, period_segments, chosen_video, chosen_audio, BUFFER_SIZE
)
elif PLAY_MODE == "only_manifest":
self.get_manifest()
else:
# select random segments: one for audio content and second for
# video
video_timeline = period_segments["repr"][chosen_video]["timeline"]
audio_timeline = period_segments["repr"][chosen_audio]["timeline"]

video_segment = random.choice(video_timeline)
audio_segment = random.choice(audio_timeline)
logger.info(video_segment["url"])

logger.info(f"Fetching video segment: {video_segment['url']}")
self.client.get(video_segment["url"])
logger.info(audio_segment["url"])

logger.info(f"Fetching audio segment: {audio_segment['url']}")
self.client.get(audio_segment["url"])
else:
print("Peridos not found in the MPD body")
logger.error("No periods or representations found in MPD.")
raise StopUser()
Loading