Skip to content
Merged
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
1 change: 1 addition & 0 deletions tasks/measurements/cloudflare/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .speedtest import CloudflareSpeedTest, CloudflareSpeedTestLinuxImplementation
175 changes: 175 additions & 0 deletions tasks/measurements/cloudflare/speedtest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import os
import random
import subprocess
import time
from dataclasses import dataclass
from typing import Optional

from netunicorn.base import Result, Task, TaskDispatcher, Node, Architecture, Success


@dataclass
class CloudflareSpeedTestResults:
summary: dict
unloaded_latency: float
unloaded_jitter: float
unloaded_latency_points: list[float]
down_loaded_latency: float
down_loaded_jitter: float
down_loaded_latency_points: list[float]
up_loaded_latency: float
up_loaded_jitter: float
up_loaded_latency_points: list[float]
download_bandwidth: float
download_bandwidth_points: list[dict]
upload_bandwidth: float
upload_bandwidth_points: list[dict]
packet_loss: float
packet_loss_details: dict
scores: dict


def get_measurements(driver) -> CloudflareSpeedTestResults:
return CloudflareSpeedTestResults(
summary=driver.execute_script(
"return window.testInstance.results.getSummary()"
),
unloaded_latency=driver.execute_script(
"return window.testInstance.results.getUnloadedLatency()"
),
unloaded_jitter=driver.execute_script(
"return window.testInstance.results.getUnloadedJitter()"
),
unloaded_latency_points=driver.execute_script(
"return window.testInstance.results.getUnloadedLatencyPoints()"
),
down_loaded_latency=driver.execute_script(
"return window.testInstance.results.getDownLoadedLatency()"
),
down_loaded_jitter=driver.execute_script(
"return window.testInstance.results.getDownLoadedJitter()"
),
down_loaded_latency_points=driver.execute_script(
"return window.testInstance.results.getDownLoadedLatencyPoints()"
),
up_loaded_latency=driver.execute_script(
"return window.testInstance.results.getUpLoadedLatency()"
),
up_loaded_jitter=driver.execute_script(
"return window.testInstance.results.getUpLoadedJitter()"
),
up_loaded_latency_points=driver.execute_script(
"return window.testInstance.results.getUpLoadedLatencyPoints()"
),
download_bandwidth=driver.execute_script(
"return window.testInstance.results.getDownloadBandwidth()"
),
download_bandwidth_points=driver.execute_script(
"return window.testInstance.results.getDownloadBandwidthPoints()"
),
upload_bandwidth=driver.execute_script(
"return window.testInstance.results.getUploadBandwidth()"
),
upload_bandwidth_points=driver.execute_script(
"return window.testInstance.results.getUploadBandwidthPoints()"
),
packet_loss=driver.execute_script(
"return window.testInstance.results.getPacketLoss()"
),
packet_loss_details=driver.execute_script(
"return window.testInstance.results.getPacketLossDetails()"
),
scores=driver.execute_script("return window.testInstance.results.getScores()"),
)


def measure(
chrome_location: Optional[str] = None, webdriver_arguments: Optional[list] = None
) -> CloudflareSpeedTestResults:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service

# start python http.server
http_process = subprocess.Popen(
["python3", "-m", "http.server", "44345"], cwd="/tmp/cloudflare/speedtest"
)

# start Xvfb display for the browser
display_number = random.randint(100, 500)
xvfb_process = subprocess.Popen(
["Xvfb", f":{display_number}", "-screen", "0", "1920x1080x24"]
)
os.environ["DISPLAY"] = f":{display_number}"

options = Options()
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
if webdriver_arguments:
for argument in webdriver_arguments:
options.add_argument(argument)
if chrome_location:
options.binary_location = chrome_location

driver = webdriver.Chrome(service=Service(), options=options)
time.sleep(1)
driver.get("http://localhost:44345/test.html")
time.sleep(1)

# check "window.testInstance.isFinished" attribute
while not driver.execute_script("return window.testInstance.isFinished"):
time.sleep(1)

# get results
results = get_measurements(driver)

driver.close()
xvfb_process.kill()
http_process.kill()
return results


class CloudflareSpeedTest(TaskDispatcher):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.linux_instance = CloudflareSpeedTestLinuxImplementation(name=self.name)

def dispatch(self, node: Node) -> Task:
if node.architecture in {Architecture.LINUX_AMD64, Architecture.LINUX_ARM64}:
return self.linux_instance

raise NotImplementedError(
f"CLoudflareSpeedTest is not implemented for architecture: {node.architecture}"
)


class CloudflareSpeedTestLinuxImplementation(Task):
requirements = [
"apt install -y python3-pip wget xvfb procps chromium chromium-driver",
"pip3 install selenium webdriver-manager",
"mkdir -p /tmp/cloudflare/speedtest",
"wget https://github.com/netunicorn/netunicorn-library/releases/download/cloudflare-speedtest-0.1/bundle.js -O /tmp/cloudflare/speedtest/bundle.js",
"wget https://github.com/netunicorn/netunicorn-library/releases/download/cloudflare-speedtest-0.1/test.html -O /tmp/cloudflare/speedtest/test.html",
]

def __init__(
self,
chrome_location: Optional[str] = None,
webdriver_arguments: Optional[list] = None,
*args,
**kwargs,
):
self.chrome_location = chrome_location
if not self.chrome_location:
self.chrome_location = "/usr/bin/chromium"
self.webdriver_arguments = webdriver_arguments
super().__init__(*args, **kwargs)

def run(self) -> Result[CloudflareSpeedTestResults, str]:
return Success(measure(self.chrome_location, self.webdriver_arguments))


if __name__ == "__main__":
CloudflareSpeedTestLinuxImplementation(
chrome_location="/usr/bin/chromium-browser"
).run()
20 changes: 20 additions & 0 deletions tasks/measurements/cloudflare/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Cloudflare speedtest netunicorn task sources

This folder contains source code for Cloudflare speedtest adapted as netunicorn task.

In particular, to run the speedtest, you need to bundle the cloudflare speedtest npm module with the index.js file that uses these code. When the HTML page inporting this bundle would be opened, the speedtest would be run and results would be assigned to the window variable testResults, and window.testFinished would be set to true.

Currently, the task refers to the bundled version of the speedtest module, which is located in the `dist` folder. To rebuild the bundle, run `npm run build` in the `tasks/measurements/cloudflare` folder.

The bundled version is published as "cloudflare-speedtest-bundle:0.1" release in the netunicorn-repo.

## How to bundle the speedtest module

If you want to change index.js, you'll need to create the new bundle. For this:
1. Install npm
2. Create a new npm project in the `tasks/measurements/cloudflare/src` folder: `npm init`
3. Install the required modules: `npm install @cloudflare/speedtest babel-loader webpack webpack-cli`
4. Modify `index.js` as needed.
5. Run `npx webpack` to create the bundle in the folder specified in the `webpack.config.js` file.
6. Publish your bundle somewhere and change the requirements of the corresponding netunicorn task to point to your new bundle.

8 changes: 8 additions & 0 deletions tasks/measurements/cloudflare/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import SpeedTest from '@cloudflare/speedtest';

window.testFinished = false;
window.testInstance = new SpeedTest();
window.testInstance.onFinish = results => {
window.testResults = results;
window.testFinished = true;
};
20 changes: 20 additions & 0 deletions tasks/measurements/cloudflare/src/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "temp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@cloudflare/speedtest": "^1.3.0",
"babel-loader": "^9.1.3"
}
}
13 changes: 13 additions & 0 deletions tasks/measurements/cloudflare/src/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<script src="bundle.js"></script>
<title>CloudFlare SpeedTest</title>
</head>

<body>
</body>

</html>
18 changes: 18 additions & 0 deletions tasks/measurements/cloudflare/src/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js',
path: "/tmp/cloudflare/speedtest/",
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
],
},
};