From 55110320543407506ba67cb973c1f198ae35f09d Mon Sep 17 00:00:00 2001 From: maybe-hello-world Date: Thu, 9 Nov 2023 17:11:37 -0800 Subject: [PATCH 1/4] add sources for the cloudflare speedtest --- tasks/measurements/cloudflare/__init__.py | 0 tasks/measurements/cloudflare/src/README.md | 20 +++++++++++++++++++ tasks/measurements/cloudflare/src/index.js | 8 ++++++++ .../measurements/cloudflare/src/package.json | 20 +++++++++++++++++++ tasks/measurements/cloudflare/src/test.html | 13 ++++++++++++ .../cloudflare/src/webpack.config.js | 18 +++++++++++++++++ 6 files changed, 79 insertions(+) create mode 100644 tasks/measurements/cloudflare/__init__.py create mode 100644 tasks/measurements/cloudflare/src/README.md create mode 100644 tasks/measurements/cloudflare/src/index.js create mode 100644 tasks/measurements/cloudflare/src/package.json create mode 100644 tasks/measurements/cloudflare/src/test.html create mode 100644 tasks/measurements/cloudflare/src/webpack.config.js diff --git a/tasks/measurements/cloudflare/__init__.py b/tasks/measurements/cloudflare/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tasks/measurements/cloudflare/src/README.md b/tasks/measurements/cloudflare/src/README.md new file mode 100644 index 0000000..194148f --- /dev/null +++ b/tasks/measurements/cloudflare/src/README.md @@ -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. + diff --git a/tasks/measurements/cloudflare/src/index.js b/tasks/measurements/cloudflare/src/index.js new file mode 100644 index 0000000..6bd268b --- /dev/null +++ b/tasks/measurements/cloudflare/src/index.js @@ -0,0 +1,8 @@ +import SpeedTest from '@cloudflare/speedtest'; + +window.testFinished = false; +const test = new SpeedTest(); +test.onFinish = results => { + window.testResults = results; + window.testFinished = true; +}; diff --git a/tasks/measurements/cloudflare/src/package.json b/tasks/measurements/cloudflare/src/package.json new file mode 100644 index 0000000..a8b20ad --- /dev/null +++ b/tasks/measurements/cloudflare/src/package.json @@ -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" + } +} diff --git a/tasks/measurements/cloudflare/src/test.html b/tasks/measurements/cloudflare/src/test.html new file mode 100644 index 0000000..bc9b71d --- /dev/null +++ b/tasks/measurements/cloudflare/src/test.html @@ -0,0 +1,13 @@ + + + + + + + CloudFlare SpeedTest + + + + + + \ No newline at end of file diff --git a/tasks/measurements/cloudflare/src/webpack.config.js b/tasks/measurements/cloudflare/src/webpack.config.js new file mode 100644 index 0000000..7c0ae79 --- /dev/null +++ b/tasks/measurements/cloudflare/src/webpack.config.js @@ -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', + }, + }, + ], + }, +}; From 22e0807110a36e05030abf48e8a4f009839a5d3b Mon Sep 17 00:00:00 2001 From: maybe-hello-world Date: Fri, 10 Nov 2023 17:51:51 -0800 Subject: [PATCH 2/4] update sources --- tasks/measurements/cloudflare/src/index.js | 4 ++-- tasks/measurements/cloudflare/src/test.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tasks/measurements/cloudflare/src/index.js b/tasks/measurements/cloudflare/src/index.js index 6bd268b..b26b059 100644 --- a/tasks/measurements/cloudflare/src/index.js +++ b/tasks/measurements/cloudflare/src/index.js @@ -1,8 +1,8 @@ import SpeedTest from '@cloudflare/speedtest'; window.testFinished = false; -const test = new SpeedTest(); -test.onFinish = results => { +window.testInstance = new SpeedTest(); +window.testInstance.onFinish = results => { window.testResults = results; window.testFinished = true; }; diff --git a/tasks/measurements/cloudflare/src/test.html b/tasks/measurements/cloudflare/src/test.html index bc9b71d..7c1be26 100644 --- a/tasks/measurements/cloudflare/src/test.html +++ b/tasks/measurements/cloudflare/src/test.html @@ -3,7 +3,7 @@ - + CloudFlare SpeedTest From 62e1451148f8c94dac6bc35206f4be965430fb88 Mon Sep 17 00:00:00 2001 From: maybe-hello-world Date: Fri, 10 Nov 2023 17:52:10 -0800 Subject: [PATCH 3/4] add speedtest implementation --- tasks/measurements/cloudflare/__init__.py | 1 + tasks/measurements/cloudflare/speedtest.py | 175 +++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 tasks/measurements/cloudflare/speedtest.py diff --git a/tasks/measurements/cloudflare/__init__.py b/tasks/measurements/cloudflare/__init__.py index e69de29..488997a 100644 --- a/tasks/measurements/cloudflare/__init__.py +++ b/tasks/measurements/cloudflare/__init__.py @@ -0,0 +1 @@ +from .speedtest import CloudflareSpeedTest, CloudflareSpeedTestLinuxImplementation \ No newline at end of file diff --git a/tasks/measurements/cloudflare/speedtest.py b/tasks/measurements/cloudflare/speedtest.py new file mode 100644 index 0000000..750a4ee --- /dev/null +++ b/tasks/measurements/cloudflare/speedtest.py @@ -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() From ac89a9134d78bf451de24839ee993ab600db87b5 Mon Sep 17 00:00:00 2001 From: maybe-hello-world Date: Fri, 10 Nov 2023 18:10:11 -0800 Subject: [PATCH 4/4] fix requirements --- tasks/measurements/cloudflare/speedtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/measurements/cloudflare/speedtest.py b/tasks/measurements/cloudflare/speedtest.py index 750a4ee..9e57b0b 100644 --- a/tasks/measurements/cloudflare/speedtest.py +++ b/tasks/measurements/cloudflare/speedtest.py @@ -147,7 +147,7 @@ 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" + "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", ]