diff --git a/.devcontainer/codespace-setup.sh b/.devcontainer/codespace-setup.sh new file mode 100755 index 000000000..5314dd3d6 --- /dev/null +++ b/.devcontainer/codespace-setup.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -euxo pipefail + +# we have to rename this makefile as it doesn't compile in Codespaces +if [ -f /workspaces/green-metrics-tool/metric_providers/lm_sensors/Makefile ]; then + mv /workspaces/green-metrics-tool/metric_providers/lm_sensors/Makefile /workspaces/green-metrics-tool/metric_providers/lm_sensors/Makefile.bak + git update-index --assume-unchanged /workspaces/green-metrics-tool/metric_providers/lm_sensors/Makefile +fi + +/workspaces/green-metrics-tool/install_linux.sh -p testpw -a "https://${CODESPACE_NAME}-9142.app.github.dev" -m "https://${CODESPACE_NAME}-9143.app.github.dev" -t -i -s -l +source venv/bin/activate + +# Also add XGBoost, as we need it +python3 -m pip install -r /workspaces/green-metrics-tool/metric_providers/psu/energy/ac/xgboost/machine/model/requirements.txt + +# make edits to ports so we can use 9143 to access front end +sed -i 's/listen \[::\]:9142;/listen [::]:9143;/; s/listen 9142;/listen 9143;/' /workspaces/green-metrics-tool/docker/nginx/frontend.conf +sed -i 's/- 9142:9142/- 9142:9142\n - 9143:9143/' /workspaces/green-metrics-tool/docker/compose.yml +sed -i 's|- ./nginx/block.conf|#- ./nginx/block.conf|' /workspaces/green-metrics-tool/docker/compose.yml + +# activate XGBoost provider with sane values for GitHub Codespaces +sed -i 's/common:/common:\n psu.energy.ac.xgboost.machine.provider.PsuEnergyAcXgboostMachineProvider:\n resolution: 99\n CPUChips: 1\n HW_CPUFreq: 2800\n CPUCores: 32\n CPUThreads: 64\n TDP: 270\n HW_MemAmountGB: 256\n VHost_Ratio: 0.03125\n/' /workspaces/green-metrics-tool/config.yml + + +git clone https://github.com/green-coding-solutions/example-applications.git --depth=1 --single-branch /workspaces/green-metrics-tool/example-applications || true + +source venv/bin/activate + +docker compose -f /workspaces/green-metrics-tool/docker/compose.yml down + +docker compose -f /workspaces/green-metrics-tool/docker/compose.yml up -d + + +gh codespace ports visibility 9142:public -c $CODESPACE_NAME + +gh codespace ports visibility 9143:public -c $CODESPACE_NAME \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..9fa8d8350 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +{ + "postStartCommand": "/workspaces/green-metrics-tool/.devcontainer/on-start.sh", + "forwardPorts": [9142, 9143], + "portsAttributes": { + "9143": { + "label": "metrics page" + }, + "9142": { + "label": "api" + } + }, + "customizations": { + "codespaces": { + "openFiles": [ + ".devcontainer/splash.md" + ] + }, + "vscode": { + "settings": { + "workbench.editorAssociations": { + "*.md": "vscode.markdown.preview.editor" // Open markdown files in preview mode by default + } + }, + "extensions": + [ + "ms-python.python", + "ms-azuretools.vscode-docker" + ] + } + } +} diff --git a/.devcontainer/on-start.sh b/.devcontainer/on-start.sh new file mode 100755 index 000000000..8f0c1f449 --- /dev/null +++ b/.devcontainer/on-start.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -euo pipefail + +etc_hosts_line_1="127.0.0.1 green-coding-postgres-container" + +echo "Writing to /etc/hosts file..." + +# Entry 1 is needed for the local resolution of the containers through the jobs.py and runner.py +if ! sudo grep -Fxq "$etc_hosts_line_1" /etc/hosts; then + echo "$etc_hosts_line_1" | sudo tee -a /etc/hosts +else + echo "Entry was already present..." +fi + +# Ensure that after a restart of the Codespace the ports are set to public again +gh codespace ports visibility 9142:public -c $CODESPACE_NAME +gh codespace ports visibility 9143:public -c $CODESPACE_NAME diff --git a/.devcontainer/splash.md b/.devcontainer/splash.md new file mode 100644 index 000000000..8ff746a9b --- /dev/null +++ b/.devcontainer/splash.md @@ -0,0 +1,35 @@ +# Green Metrics Tool Codespaces Quickstart + +Thank you for trying out the Green Metrics Tool :-) + +Please run the following command in the terminal to set up everything! 🚀 + +```sh +bash .devcontainer/codespace-setup.sh +``` + +It will take about 3 minutes. + +Afterwards, load the python environment: + +```sh +source venv/bin/activate +``` + +Do your first measurement run like this: + +```sh +python3 runner.py --name "Simple Test" --uri "/workspaces/green-metrics-tool/example-applications/" --filename "stress/usage_scenario.yml" --skip-system-checks --dev-no-optimizations --dev-cache-build +``` + +Then, if you want to see a more representative repository, try running our Bakery Demo repository we did together with the Wagtail Community: + +```sh +python3 runner.py --uri https://github.com/green-coding-solutions/bakerydemo --branch gmt --skip-system-checks --dev-no-optimization --dev-cache-build --skip-unsafe --name "Bakery Demo Test" +``` + +To see the Metrics front end, go to your ports tab and follow the forwarding address for port 9143. + +Make sure the `api` port (9142) is public. If it's private, the metrics frontend will not be able to access the API due to CORS issues. + +If you are experiencing problems, see the file [.devcontainer/troubleshooting.md](./troubleshooting.md) for some common troubleshooting tips. diff --git a/.devcontainer/troubleshooting.md b/.devcontainer/troubleshooting.md new file mode 100644 index 000000000..0ffb3f62a --- /dev/null +++ b/.devcontainer/troubleshooting.md @@ -0,0 +1,27 @@ +# Troubleshooting + +## Frontend can't be open + +Make sure the ports 9142 (`api`) und 9143 (`metrics page`) are public. If they are private, the metrics frontend will not be able to access the API due to CORS issues. After a restart of the codespace the ports are set to private, so you have to change the visibility manually. + +You can use the following commands in the terminal to make the ports public: + +```sh +gh codespace ports visibility 9142:public -c $CODESPACE_NAME +gh codespace ports visibility 9143:public -c $CODESPACE_NAME +``` + +## Connection to server failed + +If you entcounter an error like + +```log +error connecting in 'pool-1': connection failed: connection to server at "127.0.0.1", port 9573 failed: Connection refused + Is the server running on that host and accepting TCP/IP connections? +``` + +then ensure that the Docker containers of GMT are running. + +```sh +docker compose -f docker/compose.yml up -d +``` diff --git a/.github/workflows/build-codespace-container.yml b/.github/workflows/build-codespace-container.yml index c108e6912..e8ee990df 100644 --- a/.github/workflows/build-codespace-container.yml +++ b/.github/workflows/build-codespace-container.yml @@ -20,6 +20,9 @@ jobs: gmt-api-token: ${{ secrets.GMT_API_TOKEN }} electricitymaps-api-token: ${{ secrets.ELECTRICITYMAPS_TOKEN }} + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -31,13 +34,21 @@ jobs: with: registry: ghcr.io username: ${{ github.actor }} - password: ${{ inputs.github-token }} + password: ${{ secrets.GITHUB_TOKEN }} + + # - name: Build and push gunicorn container + # uses: docker/build-push-action@v5 + # with: + # context: ./docker + # file: ./docker/Dockerfile-gunicorn + # push: true + # tags: ghcr.io/green-coding-berlin/green-coding-gunicorn-container:latest - name: Build and push Docker container uses: docker/build-push-action@v6 with: context: . - file: /.devcontainer/containerized/Dockerfile # Path to the Dockerfile + file: ./.devcontainer/containerized/Dockerfile push: true tags: ghcr.io/green-coding-solutions/codespace-container:latest diff --git a/.gitignore b/.gitignore index 3102d8269..286f02e18 100644 --- a/.gitignore +++ b/.gitignore @@ -14,13 +14,13 @@ metric-provider-binary .vscode static-binary .pytest_cache -/docker/test-compose.yml -/tests/structure.sql -/tools/sgx_enable -/venv/ /lib/hardware_info_root.py /tools/cluster/cleanup.sh /node_modules/ /lib/c/parse_int.o /tools/backup -/manager-config.yml \ No newline at end of file +/manager-config.yml +/venv/ +Makefile.bak +/docker/test-compose.yml +/tests/structure.sql diff --git a/README.md b/README.md index 0ca9ec8ea..665b0f7a6 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ [![Energy Used](https://api.green-coding.io/v1/ci/badge/get/?repo=green-coding-solutions/green-metrics-tool&branch=main&workflow=45267393)](https://metrics.green-coding.io/ci.html?repo=green-coding-solutions/green-metrics-tool&branch=main&workflow=45267393) (This is the energy cost of running our CI-Pipelines on Github. [Find out more about Eco-CI](https://www.green-coding.io/projects/eco-ci/)) +[![Try in Github Codespaces!](https://github.com/codespaces/badge.svg)](https://codespaces.new/green-coding-berlin/green-metrics-tool) + # Introduction The Green Metrics Tool is a developer tool indented for measuring the energy and CO2 consumption of software through a software life cycle analysis (SLCA). diff --git a/api/api_helpers.py b/api/api_helpers.py index dba531850..16ff15741 100644 --- a/api/api_helpers.py +++ b/api/api_helpers.py @@ -1,5 +1,6 @@ +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to STDERR +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr from urllib.parse import urlparse @@ -64,7 +65,7 @@ def rescale_energy_value(value, unit): # We only expect values to be uJ for energy in the future. Changing values now temporarily. # TODO: Refactor this once all data in the DB is uJ if unit != 'uJ' and not unit.startswith('ugCO2e/'): - raise RuntimeError('Unexpected unit occured for energy rescaling: ', unit) + raise ValueError('Unexpected unit occured for energy rescaling: ', unit) unit_type = unit[1:] @@ -218,6 +219,7 @@ def get_timeline_query(uri, filename, machine_id, branch, metrics, phase, start_ AND r.filename = %s AND r.branch = %s AND r.end_measurement IS NOT NULL + AND r.failed != TRUE AND r.machine_id = %s AND p.phase LIKE %s {metrics_condition} diff --git a/api/main.py b/api/main.py index 4b6687248..12206e59e 100644 --- a/api/main.py +++ b/api/main.py @@ -1,8 +1,8 @@ -import faulthandler - # It seems like FastAPI already enables faulthandler as it shows stacktrace on SEGFAULT # Is the redundant call problematic? -faulthandler.enable() # will catch segfaults and write to STDERR +import sys +import faulthandler +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr import orjson from xml.sax.saxutils import escape as xml_escape @@ -632,7 +632,7 @@ async def software_add(software: Software, user: User = Depends(authenticate)): if not user.can_schedule_job(software.schedule_mode): raise RequestValidationError('Your user does not have the permissions to use that schedule mode.') - utils.check_repo(software.url) # if it exists through the git api + utils.check_repo(software.url, software.branch) # if it exists through the git api if software.schedule_mode in ['daily', 'weekly', 'commit', 'commit-variance', 'tag', 'tag-variance']: diff --git a/config.yml.example b/config.yml.example index 620b95616..02e8c3d2f 100644 --- a/config.yml.example +++ b/config.yml.example @@ -78,7 +78,7 @@ machine: measurement: system_check_threshold: 3 # Can be 1=INFO, 2=WARN or 3=ERROR pre-test-sleep: 5 - idle-duration: 5 + idle-duration: 10 baseline-duration: 5 post-test-sleep: 5 phase-transition-time: 1 @@ -181,6 +181,9 @@ measurement: ######### The value for memory must be in GB not in GiB # HW_MemAmountGB: 16 # Hardware_Availability_Year: 2011 +######### vhost_ratio is the virtualization degree of the machine. For Bare Metal this is 1. For 1 out of 4 VMs this would be 0.25 etc. +# VHost_Ratio: 1 + #--- END diff --git a/cron/client.py b/cron/client.py index d8478d2d5..732548ff9 100644 --- a/cron/client.py +++ b/cron/client.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr import os -import sys import time import subprocess import json diff --git a/cron/jobs.py b/cron/jobs.py index 094aeabf3..f2d4fa4a2 100644 --- a/cron/jobs.py +++ b/cron/jobs.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # pylint: disable=cyclic-import +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr -import sys import os from datetime import datetime import argparse diff --git a/cron/timeline_projects.py b/cron/timeline_projects.py index ffcfbfbf9..6ebe2e4f7 100644 --- a/cron/timeline_projects.py +++ b/cron/timeline_projects.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr import os import pprint diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf index 08eba4724..e0ec8641b 100644 --- a/docker/nginx/nginx.conf +++ b/docker/nginx/nginx.conf @@ -12,6 +12,7 @@ events { http { + server_names_hash_bucket_size 128; include /etc/nginx/mime.types; default_type application/octet-stream; @@ -34,4 +35,4 @@ http { gzip_disable "MSIE [1-6]\."; include /etc/nginx/conf.d/*.conf; -} \ No newline at end of file +} diff --git a/docker/requirements.txt b/docker/requirements.txt index 51236bec6..5e0f0bd18 100644 --- a/docker/requirements.txt +++ b/docker/requirements.txt @@ -1,6 +1,6 @@ gunicorn==23.0.0 psycopg[binary]==3.2.3 -psycopg_pool==3.2.3 +psycopg_pool==3.2.4 fastapi[standard]==0.115.5 starlette>=0.35 uvicorn[standard]==0.32.0 diff --git a/frontend/carbondb.html b/frontend/carbondb.html deleted file mode 120000 index 10fdaf52d..000000000 --- a/frontend/carbondb.html +++ /dev/null @@ -1 +0,0 @@ -../ee/frontend/carbondb.html \ No newline at end of file diff --git a/frontend/js/helpers/config.js.example b/frontend/js/helpers/config.js.example index 173985c74..397d90130 100644 --- a/frontend/js/helpers/config.js.example +++ b/frontend/js/helpers/config.js.example @@ -38,12 +38,24 @@ METRIC_MAPPINGS = { 'explanation': 'Container energy estimated via CPU-% share', }, + 'psu_energy_cgroup_slice': { + 'clean_name': 'Container Energy (+Idle)', + 'source': 'estimation', + 'explanation': 'Container energy estimated via CPU-% share (incl. idle)', + }, + 'psu_power_cgroup_container': { 'clean_name': 'Container Power', 'source': 'estimation', 'explanation': 'Container power estimated via CPU-% share', }, + 'psu_power_cgroup_slice': { + 'clean_name': 'Container Power (+Idle)', + 'source': 'estimation', + 'explanation': 'Container power estimated via CPU-% share incl. Idle', + }, + 'psu_carbon_dc_rapl_msr_machine': { 'clean_name': 'Machine CO₂', 'source': 'RAPL', diff --git a/frontend/js/helpers/metric-boxes.js b/frontend/js/helpers/metric-boxes.js index 1b3825310..24691ad0d 100644 --- a/frontend/js/helpers/metric-boxes.js +++ b/frontend/js/helpers/metric-boxes.js @@ -149,7 +149,7 @@ class PhaseMetrics extends HTMLElement {
- see Details + see Details
diff --git a/frontend/timeline.html b/frontend/timeline.html index 68b20f468..0c1993259 100644 --- a/frontend/timeline.html +++ b/frontend/timeline.html @@ -221,7 +221,7 @@

What is Timeline View?

- +
@@ -230,7 +230,7 @@

What is Timeline View?

- +
diff --git a/install_linux.sh b/install_linux.sh index be03f6de9..888ca7a31 100755 --- a/install_linux.sh +++ b/install_linux.sh @@ -1,7 +1,6 @@ #!/bin/bash set -euo pipefail - if [[ $(uname) != "Linux" ]]; then echo "Error: This script can only be run on Linux." exit 1 @@ -88,5 +87,3 @@ if ! mount | grep -E '\s/tmp\s' | grep -Eq '\stmpfs\s' && [[ $ask_tmpfs == true fi finalize - - diff --git a/install_mac.sh b/install_mac.sh index aceb49274..7da34911d 100755 --- a/install_mac.sh +++ b/install_mac.sh @@ -34,8 +34,4 @@ echo "ALL ALL=(ALL) NOPASSWD:/usr/bin/powermetrics" | sudo tee /etc/sudoers.d/gr echo "ALL ALL=(ALL) NOPASSWD:/usr/bin/killall powermetrics" | sudo tee /etc/sudoers.d/green_coding_kill_powermetrics echo "ALL ALL=(ALL) NOPASSWD:/usr/bin/killall -9 powermetrics" | sudo tee /etc/sudoers.d/green_coding_kill_powermetrics_sigkill - - - - finalize \ No newline at end of file diff --git a/lib/c/Makefile b/lib/c/Makefile index 9cac1ff0a..9597e5ed0 100644 --- a/lib/c/Makefile +++ b/lib/c/Makefile @@ -1,4 +1,9 @@ CFLAGS = -O3 -Wall +all: parse_int.o detect_cgroup_path.o + parse_int.o: parse_int.c gcc -c $< $(CFLAGS) -o $@ + +detect_cgroup_path.o: detect_cgroup_path.c + gcc -c $< $(CFLAGS) -o $@ \ No newline at end of file diff --git a/lib/c/detect_cgroup_path.c b/lib/c/detect_cgroup_path.c new file mode 100644 index 000000000..91e02c564 --- /dev/null +++ b/lib/c/detect_cgroup_path.c @@ -0,0 +1,64 @@ +#include "detect_cgroup_path.h" + +#include +#include +#include +#include +#include + +char* detect_cgroup_path(const char* controller, int user_id, const char* id) { + char* path = malloc(PATH_MAX); + if (path == NULL) { + fprintf(stderr, "Could not allocate memory for detect_cgroup_path\n"); + exit(1); + } + + FILE* fd = NULL; + + // Try cgroups v2 with systemd slices (typically in rootless mode) + snprintf(path, PATH_MAX, + "/sys/fs/cgroup/user.slice/user-%d.slice/user@%d.service/user.slice/docker-%s.scope/%s", + user_id, user_id, id, controller); + fd = fopen(path, "r"); + if (fd != NULL) { + fclose(fd); + return path; + } + + // Try cgroups v2 with systemd but non-slice mountpoints (typically in non-rootless mode) + snprintf(path, PATH_MAX, + "/sys/fs/cgroup/system.slice/docker-%s.scope/%s", + id, controller); + fd = fopen(path, "r"); + if (fd != NULL) { + fclose(fd); + return path; + } + + // Try cgroups v2 without slice mountpoints (used in Github codespaces) + snprintf(path, PATH_MAX, + "/sys/fs/cgroup/docker/%s/%s", + id, controller); + fd = fopen(path, "r"); + if (fd != NULL) { + fclose(fd); + return path; + } + + // Try cgroups v2 without slice mountpoints and in subdir (used in Github actions) + snprintf(path, PATH_MAX, + "/sys/fs/cgroup/actions_job/%s/%s", + id, controller); + fd = fopen(path, "r"); + if (fd != NULL) { + fclose(fd); + return path; + } + + // If no valid path is found, free the allocated memory and error + free(path); + fprintf(stderr, "Error - Could not open container for reading: %s. Maybe the container is not running anymore? Errno: %d\n", id, errno); + exit(1); + +} + diff --git a/lib/c/detect_cgroup_path.h b/lib/c/detect_cgroup_path.h new file mode 100644 index 000000000..f6acb36e9 --- /dev/null +++ b/lib/c/detect_cgroup_path.h @@ -0,0 +1,6 @@ +#ifndef DETECT_CGROUP_PATH_H +#define DETECT_CGROUP_PATH_H + +char* detect_cgroup_path(const char* controller, int user_id, const char* id); + +#endif // DETECT_CGROUP_PATH_H \ No newline at end of file diff --git a/lib/diff.py b/lib/diff.py index 91d45f7e5..eb6fc992e 100644 --- a/lib/diff.py +++ b/lib/diff.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr from lib.db import DB from deepdiff import DeepDiff diff --git a/lib/hardware_info.py b/lib/hardware_info.py index b46548de0..8df9ec9f6 100755 --- a/lib/hardware_info.py +++ b/lib/hardware_info.py @@ -55,7 +55,7 @@ # If another scaling driver is used the info will be in /sys/devices/system/cpu/cpufreq/boost # See https://wiki.archlinux.org/title/CPU_frequency_scaling # See further: https://www.kernel.org/doc/html/v5.17/admin-guide/pm/intel_pstate.html#user-space-interface-in-sysfs - [rfwr, 'Turbo Boost', '/sys/devices/system/cpu/intel_pstate/no_turbo', r'(?P.*)'], + [rfwr, 'Turbo Boost (1=off)', '/sys/devices/system/cpu/intel_pstate/no_turbo', r'(?P.*)'], [rfwr, 'Turbo Boost (Legacy non intel_pstate)', '/sys/devices/system/cpu/cpufreq/boost', r'(?P.*)'], [rfwr, 'Virtualization', '/proc/cpuinfo', r'(?Phypervisor)'], [rpwrs, 'SGX', f"{os.path.join(CURRENT_PATH, '../tools/sgx_enable')} -s", r'(?P.*)', re.IGNORECASE | re.DOTALL], diff --git a/lib/install_shared.sh b/lib/install_shared.sh index cfdcde88e..91942bad1 100644 --- a/lib/install_shared.sh +++ b/lib/install_shared.sh @@ -236,6 +236,9 @@ function build_binaries() { if [[ $(uname) == "Linux" ]] && [[ "$make_path" == *"/mach/"* ]]; then continue fi + if [[ "$make_path" == *"/lmsensors/"* ]] && [[ "${install_sensors}" == false ]]; then + continue + fi echo "Installing $subdir/metric-provider-binary ..." rm -f $subdir/metric-provider-binary 2> /dev/null make -C $subdir @@ -304,7 +307,6 @@ while getopts "p:a:m:nhtbisyrlc:k:e:" o; do ;; s) install_sensors=false - # currently unused ;; r) install_msr_tools=false diff --git a/lib/job/base.py b/lib/job/base.py index 43508f004..6388342ba 100644 --- a/lib/job/base.py +++ b/lib/job/base.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # pylint: disable=cyclic-import +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr import os import importlib diff --git a/lib/job/email.py b/lib/job/email.py index d67fb8b44..ca491492d 100644 --- a/lib/job/email.py +++ b/lib/job/email.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # pylint: disable=cyclic-import +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr import os diff --git a/lib/job/run.py b/lib/job/run.py index 2db81e061..e148608a5 100644 --- a/lib/job/run.py +++ b/lib/job/run.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # pylint: disable=cyclic-import + +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr import os @@ -14,7 +16,6 @@ from lib.user import User from lib.terminal_colors import TerminalColors from lib.system_checks import ConfigurationCheckError -from tools.phase_stats import build_and_store_phase_stats from runner import Runner import optimization_providers.base @@ -54,9 +55,6 @@ def _process(self, skip_system_checks=False, docker_prune=False, full_docker_pru try: # Start main code. Only URL is allowed for cron jobs self._run_id = runner.run() - user.deduct_measurement_quota(self._machine_id, int(runner._last_measurement_duration/1_000_000)) # duration in runner is in microseconds. We need seconds - - build_and_store_phase_stats(self._run_id, runner._sci) # We need to import this here as we need the correct config file print(TerminalColors.HEADER, '\nImporting optimization reporters ...', TerminalColors.ENDC) @@ -85,3 +83,5 @@ def _process(self, skip_system_checks=False, docker_prune=False, full_docker_pru message=f"Run-ID: {self._run_id}\nName: {self._name}\n\nDetails can also be found in the log under: {GlobalConfig().config['cluster']['metrics_url']}/stats.html?id={self._run_id}\n\nError message: {exc}\n" ) raise exc + finally: + user.deduct_measurement_quota(self._machine_id, int(runner._last_measurement_duration/1_000_000)) # duration in runner is in microseconds. We need seconds diff --git a/lib/machine.py b/lib/machine.py index fddcaa51a..0f0a08158 100644 --- a/lib/machine.py +++ b/lib/machine.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr import os diff --git a/lib/phase_stats.py b/lib/phase_stats.py index 0ef85b730..7ac57870a 100644 --- a/lib/phase_stats.py +++ b/lib/phase_stats.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr import decimal from io import StringIO @@ -30,13 +31,23 @@ def build_and_store_phase_stats(run_id, sci=None): """ metrics = DB().fetch_all(query, (run_id, )) + if not metrics: + error_helpers.log_error('Metrics was empty and no phase_stats could be created. This can happen for failed runs, but should be very rare ...', run_id=run_id) + return + + query = """ SELECT phases, measurement_config FROM runs WHERE id = %s """ - phases, measurement_config = DB().fetch_one(query, (run_id, )) + data = DB().fetch_one(query, (run_id, )) + + if not data or not data[0] or not data[1]: + error_helpers.log_error('Phases object was empty and no phase_stats could be created. This can happen for failed runs, but should be very rare ...', run_id=run_id) + return + phases, measurement_config = data # unpack csv_buffer = StringIO() @@ -44,6 +55,7 @@ def build_and_store_phase_stats(run_id, sci=None): machine_power_runtime = None machine_energy_runtime = None + for idx, phase in enumerate(phases): network_bytes_total = [] # reset; # we use array here and sum later, because checking for 0 alone not enough @@ -170,15 +182,17 @@ def build_and_store_phase_stats(run_id, sci=None): if machine_power_idle and cpu_utilization_machine and cpu_utilization_containers: surplus_power_runtime = machine_power_runtime - machine_power_idle surplus_energy_runtime = machine_energy_runtime - (machine_power_idle * decimal.Decimal(duration / 10**6)) + total_container_utilization = sum(cpu_utilization_containers.values()) if int(total_container_utilization) == 0: continue for detail_name, container_utilization in cpu_utilization_containers.items(): + csv_buffer.write(generate_csv_line(run_id, 'psu_energy_cgroup_slice', detail_name, f"{idx:03}_{phase['name']}", machine_energy_runtime * (container_utilization / total_container_utilization), 'TOTAL', None, None, 'mJ')) + csv_buffer.write(generate_csv_line(run_id, 'psu_power_cgroup_slice', detail_name, f"{idx:03}_{phase['name']}", machine_power_runtime * (container_utilization / total_container_utilization), 'TOTAL', None, None, 'mW')) csv_buffer.write(generate_csv_line(run_id, 'psu_energy_cgroup_container', detail_name, f"{idx:03}_{phase['name']}", surplus_energy_runtime * (container_utilization / total_container_utilization), 'TOTAL', None, None, 'mJ')) csv_buffer.write(generate_csv_line(run_id, 'psu_power_cgroup_container', detail_name, f"{idx:03}_{phase['name']}", surplus_power_runtime * (container_utilization / total_container_utilization), 'TOTAL', None, None, 'mW')) - csv_buffer.seek(0) # Reset buffer position to the beginning DB().copy_from( csv_buffer, diff --git a/lib/system_checks.py b/lib/system_checks.py index 04ceaaf16..db9553b74 100644 --- a/lib/system_checks.py +++ b/lib/system_checks.py @@ -12,7 +12,6 @@ import subprocess import psutil import locale -import platform from psycopg import OperationalError as psycopg_OperationalError @@ -57,18 +56,6 @@ def check_free_disk(): def check_free_memory(): return psutil.virtual_memory().available >= GMT_Resources['free_memory'] -def check_energy_filtering(): - if platform.system() != 'Linux': - print(TerminalColors.WARNING, '>>>> RAPL could not be checked as not running on Linux platform <<<<', TerminalColors.ENDC) - return True - - result = subprocess.run(['sudo', 'python3', '-m', 'lib.hardware_info_root', '--read-rapl-energy-filtering'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=os.path.abspath(os.path.join(CURRENT_DIR, '..')), - check=True, encoding='UTF-8') - return "1" != result.stdout.strip() - def check_containers_running(): result = subprocess.run(['docker', 'ps', '--format', '{{.Names}}'], stdout=subprocess.PIPE, @@ -98,7 +85,6 @@ def check_utf_encoding(): (check_docker_daemon, Status.ERROR, 'docker daemon', 'The docker daemon could not be reached. Are you running in rootless mode or have added yourself to the docker group? See installation: [See https://docs.green-coding.io/docs/installation/]'), (check_containers_running, Status.WARN, 'running containers', 'You have other containers running on the system. This is usually what you want in local development, but for undisturbed measurements consider going for a measurement cluster [See https://docs.green-coding.io/docs/installation/installation-cluster/].'), (check_utf_encoding, Status.ERROR, 'utf file encoding', 'Your system encoding is not set to utf-8. This is needed as we need to parse console output.'), - (check_energy_filtering, Status.ERROR, 'rapl energy filtering', 'RAPL Energy filtering is active!'), ] def check_start(): diff --git a/lib/timeline_project.py b/lib/timeline_project.py index 562bfabe3..6705053f5 100644 --- a/lib/timeline_project.py +++ b/lib/timeline_project.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr from lib.db import DB diff --git a/lib/utils.py b/lib/utils.py index 8ff3103cd..91d1c846c 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -9,6 +9,8 @@ from lib import error_helpers from lib.db import DB +CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) + def get_git_api(parsed_url): if parsed_url.netloc in ['github.com', 'www.github.com']: @@ -33,11 +35,11 @@ def check_repo(repo_url, branch='main'): response = requests.get(url, timeout=10) except Exception as exc: error_helpers.log_error('Request to GitHub API failed',url=url,exception=str(exc)) - raise RequestValidationError(f"Could not find URL {repo_url}. Is the URL public accessible and repo not empty?") from exc + raise RequestValidationError(f"Could not find repository {repo_url} and branch {branch}. Is the repo publicly accessible, not empty and does the branch {branch} exist?") from exc if response.status_code != 200: error_helpers.log_error('Request to GitHub API failed',url=url,status_code=response.status_code,status_text=response.text) - raise RequestValidationError(f"Could not find URL {repo_url}. Is the URL public accessible and repo not empty?") + raise RequestValidationError(f"Could not find repository {repo_url} and branch {branch}. Is the repo publicly accessible, not empty and does the branch {branch} exist?") def get_repo_last_marker(repo_url, marker): @@ -57,7 +59,7 @@ def get_repo_last_marker(repo_url, marker): response = requests.get(url, timeout=10) except Exception as exc: error_helpers.log_error('Request to GitHub API failed',url=url,exception=str(exc)) - raise RequestValidationError(f"Could not find URL {repo_url}. Is the URL public accessible and repo not empty?") from exc + raise RequestValidationError(f"Could not find repository {repo_url}. Is the repository publicly accessible and not empty?") from exc if response.status_code != 200: error_helpers.log_error('Request to GitHub API failed',url=url,status_code=response.status_code,status_text=response.text) @@ -168,3 +170,12 @@ def get_architecture(): if output == 'darwin': return 'macos' return output + + +def is_rapl_energy_filtering_deactivated(): + result = subprocess.run(['sudo', 'python3', '-m', 'lib.hardware_info_root', '--read-rapl-energy-filtering'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=os.path.abspath(os.path.join(CURRENT_DIR, '..')), + check=True, encoding='UTF-8') + return '1' != result.stdout.strip() diff --git a/lib/validate.py b/lib/validate.py index 1294010e8..99f4407cc 100644 --- a/lib/validate.py +++ b/lib/validate.py @@ -18,8 +18,9 @@ ''' +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr from lib.global_config import GlobalConfig from lib.db import DB @@ -27,7 +28,6 @@ from lib import error_helpers from runner import Runner -from tools.phase_stats import build_and_store_phase_stats class ValidationWorkloadStddevError(RuntimeError): pass @@ -44,6 +44,7 @@ def get_workload_stddev(repo_uri, filename, branch, machine_id, comparison_windo AND branch = %s AND machine_id = %s AND end_measurement IS NOT NULL + AND failed != TRUE ORDER BY created_at DESC LIMIT %s ) SELECT @@ -88,8 +89,7 @@ def run_workload(name, uri, filename, branch): job_id=None, ) # Start main code. Only URL is allowed for cron jobs - run_id = runner.run() - build_and_store_phase_stats(run_id, runner._sci) + runner.run() def validate_workload_stddev(data, metrics): warning = False diff --git a/metric_providers/base.py b/metric_providers/base.py index 104721216..fc89217f6 100644 --- a/metric_providers/base.py +++ b/metric_providers/base.py @@ -34,7 +34,6 @@ def __init__( self._sudo = sudo self._has_started = False self._disable_buffer = disable_buffer - self._rootless = None self._skip_check = skip_check self._tmp_folder = '/tmp/green-metrics-tool' @@ -98,6 +97,17 @@ def get_stderr(self): def has_started(self): return self._has_started + def check_monotonic(self, df): + if not df['time'].is_monotonic_increasing: + raise ValueError(f"Data from metric provider {self._metric_name} is not monotonic increasing") + + def check_resolution_underflow(self, df): + if self._unit in ['mJ', 'uJ', 'Hz', 'us']: + if (df['value'] <= 1).any(): + raise ValueError(f"Data from metric provider {self._metric_name} is running into a resolution underflow. Values are <= 1 {self._unit}") + + + def read_metrics(self, run_id, containers=None): #pylint: disable=unused-argument with open(self._filename, 'r', encoding='utf-8') as file: csv_data = file.read() @@ -115,11 +125,14 @@ def read_metrics(self, run_id, containers=None): #pylint: disable=unused-argumen if df.isna().any().any(): raise ValueError(f"Dataframe for {self._metric_name} contained NA values.") - df['detail_name'] = f"[{self._metric_name.split('_')[-1]}]" # default, can be overriden in child + df['detail_name'] = f"[{self._metric_name.split('_')[-1]}]" # default, can be overridden in child df['unit'] = self._unit df['metric'] = self._metric_name df['run_id'] = run_id + self.check_monotonic(df) + self.check_resolution_underflow(df) + return df def start_profiling(self, containers=None): @@ -145,9 +158,6 @@ def start_profiling(self, containers=None): call_string += ' -s ' call_string += ','.join(containers.keys()) - if self._rootless is True: - call_string += ' --rootless ' - call_string += f" > {self._filename}" if platform.system() == "Linux": diff --git a/metric_providers/cpu/energy/rapl/msr/component/provider.py b/metric_providers/cpu/energy/rapl/msr/component/provider.py index 889df8346..1cb829ff4 100644 --- a/metric_providers/cpu/energy/rapl/msr/component/provider.py +++ b/metric_providers/cpu/energy/rapl/msr/component/provider.py @@ -1,6 +1,7 @@ import os -from metric_providers.base import BaseMetricProvider +from metric_providers.base import BaseMetricProvider, MetricProviderConfigurationError +from lib.utils import is_rapl_energy_filtering_deactivated class CpuEnergyRaplMsrComponentProvider(BaseMetricProvider): def __init__(self, resolution, skip_check=False): @@ -13,6 +14,11 @@ def __init__(self, resolution, skip_check=False): skip_check=skip_check, ) + def check_system(self, check_command="default", check_error_message=None, check_parallel_provider=True): + super().check_system() + if not is_rapl_energy_filtering_deactivated(): + raise MetricProviderConfigurationError('RAPL energy filtering is active and might skew results!') + def read_metrics(self, run_id, containers=None): df = super().read_metrics(run_id, containers) diff --git a/metric_providers/cpu/time/cgroup/container/Makefile b/metric_providers/cpu/time/cgroup/container/Makefile index 9170c4109..45c04b7bd 100644 --- a/metric_providers/cpu/time/cgroup/container/Makefile +++ b/metric_providers/cpu/time/cgroup/container/Makefile @@ -1,4 +1,4 @@ CFLAGS = -O3 -Wall -I../../../../../lib/c metric-provider-binary: source.c - gcc ../../../../../lib/c/parse_int.o $< $(CFLAGS) -o $@ \ No newline at end of file + gcc ../../../../../lib/c/parse_int.o ../../../../../lib/c/detect_cgroup_path.o $< $(CFLAGS) -o $@ \ No newline at end of file diff --git a/metric_providers/cpu/time/cgroup/container/provider.py b/metric_providers/cpu/time/cgroup/container/provider.py index 5dc214e00..27ef3ada3 100644 --- a/metric_providers/cpu/time/cgroup/container/provider.py +++ b/metric_providers/cpu/time/cgroup/container/provider.py @@ -3,7 +3,7 @@ from metric_providers.base import BaseMetricProvider class CpuTimeCgroupContainerProvider(BaseMetricProvider): - def __init__(self, resolution, rootless=False, skip_check=False): + def __init__(self, resolution, skip_check=False): super().__init__( metric_name='cpu_time_cgroup_container', metrics={'time': int, 'value': int, 'container_id': str}, @@ -12,7 +12,6 @@ def __init__(self, resolution, rootless=False, skip_check=False): current_dir=os.path.dirname(os.path.abspath(__file__)), skip_check=skip_check, ) - self._rootless = rootless def read_metrics(self, run_id, containers=None): df = super().read_metrics(run_id, containers) diff --git a/metric_providers/cpu/time/cgroup/container/source.c b/metric_providers/cpu/time/cgroup/container/source.c index 9908ec44c..bf9d53568 100644 --- a/metric_providers/cpu/time/cgroup/container/source.c +++ b/metric_providers/cpu/time/cgroup/container/source.c @@ -8,11 +8,12 @@ #include #include #include "parse_int.h" +#include "detect_cgroup_path.h" #define DOCKER_CONTAINER_ID_BUFFER 65 // Docker container ID size is 64 + 1 byte for NUL termination typedef struct container_t { // struct is a specification and this static makes no sense here - char path[PATH_MAX]; + char* path; char id[DOCKER_CONTAINER_ID_BUFFER]; } container_t; @@ -28,7 +29,7 @@ static long int read_cpu_cgroup(char* filename) { long int cpu_usage = -1; FILE* fd = fopen(filename, "r"); if ( fd == NULL) { - fprintf(stderr, "Error - Could not open path for reading: %s. Maybe the container is not running anymore? Are you using --rootless mode? Errno: %d\n", filename, errno); + fprintf(stderr, "Error - Could not open path for reading: %s. Maybe the container is not running anymore? Errno: %d\n", filename, errno); exit(1); } int match_result = fscanf(fd, "usage_usec %ld", &cpu_usage); @@ -51,7 +52,7 @@ static void output_stats(container_t *containers, int length) { usleep(msleep_time*1000); } -static int parse_containers(container_t** containers, char* containers_string, int rootless_mode) { +static int parse_containers(container_t** containers, char* containers_string) { if(containers_string == NULL) { fprintf(stderr, "Please supply at least one container id with -s XXXX\n"); exit(1); @@ -76,17 +77,7 @@ static int parse_containers(container_t** containers, char* containers_string, i strncpy((*containers)[length-1].id, id, DOCKER_CONTAINER_ID_BUFFER - 1); (*containers)[length-1].id[DOCKER_CONTAINER_ID_BUFFER - 1] = '\0'; - if(rootless_mode) { - snprintf((*containers)[length-1].path, - PATH_MAX, - "/sys/fs/cgroup/user.slice/user-%d.slice/user@%d.service/user.slice/docker-%s.scope/cpu.stat", - user_id, user_id, id); - } else { - snprintf((*containers)[length-1].path, - PATH_MAX, - "/sys/fs/cgroup/system.slice/docker-%s.scope/cpu.stat", - id); - } + (*containers)[length-1].path = detect_cgroup_path("cpu.stat", user_id, id); } if(length == 0) { @@ -96,15 +87,10 @@ static int parse_containers(container_t** containers, char* containers_string, i return length; } -static int check_system(int rootless_mode) { +static int check_system() { const char* check_path; - if(rootless_mode) { - check_path = "/sys/fs/cgroup/user.slice/cpu.stat"; - } else { - check_path = "/sys/fs/cgroup/system.slice/cpu.stat"; - } - + check_path = "/sys/fs/cgroup/cpu.stat"; FILE* fd = fopen(check_path, "r"); if (fd == NULL) { @@ -118,7 +104,6 @@ static int check_system(int rootless_mode) { int main(int argc, char **argv) { int c; - int rootless_mode = 0; // docker root is default char *containers_string = NULL; // Dynamic buffer to store optarg container_t *containers = NULL; int check_system_flag = 0; @@ -129,7 +114,6 @@ int main(int argc, char **argv) { static struct option long_options[] = { - {"rootless", no_argument, NULL, 'r'}, {"help", no_argument, NULL, 'h'}, {"interval", no_argument, NULL, 'i'}, {"containers", no_argument, NULL, 's'}, @@ -137,7 +121,7 @@ int main(int argc, char **argv) { {NULL, 0, NULL, 0} }; - while ((c = getopt_long(argc, argv, "ri:s:hc", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "i:s:hc", long_options, NULL)) != -1) { switch (c) { case 'h': printf("Usage: %s [-i msleep_time] [-h]\n\n",argv[0]); @@ -159,9 +143,6 @@ int main(int argc, char **argv) { case 'i': msleep_time = parse_int(optarg); break; - case 'r': - rootless_mode = 1; - break; case 's': containers_string = (char *)malloc(strlen(optarg) + 1); // Allocate memory if (!containers_string) { @@ -181,10 +162,10 @@ int main(int argc, char **argv) { } if(check_system_flag){ - exit(check_system(rootless_mode)); + exit(check_system()); } - int length = parse_containers(&containers, containers_string, rootless_mode); + int length = parse_containers(&containers, containers_string); while(1) { output_stats(containers, length); diff --git a/metric_providers/cpu/time/cgroup/system/provider.py b/metric_providers/cpu/time/cgroup/system/provider.py index 339c65295..fc97f7bae 100644 --- a/metric_providers/cpu/time/cgroup/system/provider.py +++ b/metric_providers/cpu/time/cgroup/system/provider.py @@ -3,10 +3,7 @@ from metric_providers.base import BaseMetricProvider class CpuTimeCgroupSystemProvider(BaseMetricProvider): - # disabling unused-argument because as a cgroup provider we always pass in rootless as a variable - # even though this provider does not need/care about it - #pylint: disable=unused-argument - def __init__(self, resolution, rootless=False, skip_check=False): + def __init__(self, resolution, skip_check=False): super().__init__( metric_name='cpu_time_cgroup_system', metrics={'time': int, 'value': int}, @@ -15,4 +12,3 @@ def __init__(self, resolution, rootless=False, skip_check=False): current_dir=os.path.dirname(os.path.abspath(__file__)), skip_check=skip_check, ) - self._rootless = False #this provider does not need or take --rootless flag, despite being a cgroup provider diff --git a/metric_providers/cpu/utilization/cgroup/container/Makefile b/metric_providers/cpu/utilization/cgroup/container/Makefile index 9170c4109..45c04b7bd 100644 --- a/metric_providers/cpu/utilization/cgroup/container/Makefile +++ b/metric_providers/cpu/utilization/cgroup/container/Makefile @@ -1,4 +1,4 @@ CFLAGS = -O3 -Wall -I../../../../../lib/c metric-provider-binary: source.c - gcc ../../../../../lib/c/parse_int.o $< $(CFLAGS) -o $@ \ No newline at end of file + gcc ../../../../../lib/c/parse_int.o ../../../../../lib/c/detect_cgroup_path.o $< $(CFLAGS) -o $@ \ No newline at end of file diff --git a/metric_providers/cpu/utilization/cgroup/container/provider.py b/metric_providers/cpu/utilization/cgroup/container/provider.py index ea433ea45..66974505e 100644 --- a/metric_providers/cpu/utilization/cgroup/container/provider.py +++ b/metric_providers/cpu/utilization/cgroup/container/provider.py @@ -3,7 +3,7 @@ from metric_providers.base import BaseMetricProvider class CpuUtilizationCgroupContainerProvider(BaseMetricProvider): - def __init__(self, resolution, rootless=False, skip_check=False): + def __init__(self, resolution, skip_check=False): super().__init__( metric_name='cpu_utilization_cgroup_container', metrics={'time': int, 'value': int, 'container_id': str}, @@ -12,7 +12,6 @@ def __init__(self, resolution, rootless=False, skip_check=False): current_dir=os.path.dirname(os.path.abspath(__file__)), skip_check = skip_check, ) - self._rootless = rootless def read_metrics(self, run_id, containers=None): df = super().read_metrics(run_id, containers) diff --git a/metric_providers/cpu/utilization/cgroup/container/source.c b/metric_providers/cpu/utilization/cgroup/container/source.c index 6c5c45d17..d1afce1e9 100644 --- a/metric_providers/cpu/utilization/cgroup/container/source.c +++ b/metric_providers/cpu/utilization/cgroup/container/source.c @@ -8,11 +8,12 @@ #include #include #include "parse_int.h" +#include "detect_cgroup_path.h" #define DOCKER_CONTAINER_ID_BUFFER 65 // Docker container ID size is 64 + 1 byte for NUL termination typedef struct container_t { // struct is a specification and this static makes no sense here - char path[PATH_MAX]; + char* path; char id[DOCKER_CONTAINER_ID_BUFFER]; } container_t; @@ -57,7 +58,7 @@ static long int get_cpu_stat(char* filename, int mode) { FILE* fd = fopen(filename, "r"); if ( fd == NULL) { - fprintf(stderr, "Error - Could not open path for reading: %s. Maybe the container is not running anymore? Are you using --rootless mode? Errno: %d\n", filename, errno); + fprintf(stderr, "Error - Could not open path for reading: %s. Maybe the container is not running anymore? Errno: %d\n", filename, errno); exit(1); } if(mode == 1) { @@ -127,7 +128,7 @@ static void output_stats(container_t* containers, int length) { } } -static int parse_containers(container_t** containers, char* containers_string, int rootless_mode) { +static int parse_containers(container_t** containers, char* containers_string) { if(containers_string == NULL) { fprintf(stderr, "Please supply at least one container id with -s XXXX\n"); exit(1); @@ -152,36 +153,23 @@ static int parse_containers(container_t** containers, char* containers_string, i strncpy((*containers)[length-1].id, id, DOCKER_CONTAINER_ID_BUFFER - 1); (*containers)[length-1].id[DOCKER_CONTAINER_ID_BUFFER - 1] = '\0'; - if(rootless_mode) { - snprintf((*containers)[length-1].path, - PATH_MAX, - "/sys/fs/cgroup/user.slice/user-%d.slice/user@%d.service/user.slice/docker-%s.scope/cpu.stat", - user_id, user_id, id); - } else { - snprintf((*containers)[length-1].path, - PATH_MAX, - "/sys/fs/cgroup/system.slice/docker-%s.scope/cpu.stat", - id); - } + (*containers)[length-1].path = detect_cgroup_path("cpu.stat", user_id, id); } if(length == 0) { fprintf(stderr, "Please supply at least one container id with -s XXXX\n"); exit(1); } + return length; } -static int check_system(int rootless_mode) { +static int check_system() { const char* file_path_cpu_stat; const char* file_path_proc_stat; int found_error = 0; - if(rootless_mode) { - file_path_cpu_stat = "/sys/fs/cgroup/user.slice/cpu.stat"; - } else { - file_path_cpu_stat = "/sys/fs/cgroup/system.slice/cpu.stat"; - } + file_path_cpu_stat = "/sys/fs/cgroup/cpu.stat"; file_path_proc_stat = "/proc/stat"; FILE* fd = fopen(file_path_cpu_stat, "r"); @@ -211,7 +199,6 @@ int main(int argc, char **argv) { int c; int check_system_flag = 0; - int rootless_mode = 0; // docker root is default char *containers_string = NULL; // Dynamic buffer to store optarg container_t *containers = NULL; @@ -221,7 +208,6 @@ int main(int argc, char **argv) { static struct option long_options[] = { - {"rootless", no_argument, NULL, 'r'}, {"help", no_argument, NULL, 'h'}, {"interval", no_argument, NULL, 'i'}, {"containers", no_argument, NULL, 's'}, @@ -229,7 +215,7 @@ int main(int argc, char **argv) { {NULL, 0, NULL, 0} }; - while ((c = getopt_long(argc, argv, "ri:s:hc", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "i:s:hc", long_options, NULL)) != -1) { switch (c) { case 'h': printf("Usage: %s [-i msleep_time] [-h]\n\n",argv[0]); @@ -251,9 +237,6 @@ int main(int argc, char **argv) { case 'i': msleep_time = parse_int(optarg); break; - case 'r': - rootless_mode = 1; - break; case 's': containers_string = (char *)malloc(strlen(optarg) + 1); // Allocate memory if (!containers_string) { @@ -273,10 +256,10 @@ int main(int argc, char **argv) { } if(check_system_flag){ - exit(check_system(rootless_mode)); + exit(check_system()); } - int length = parse_containers(&containers, containers_string, rootless_mode); + int length = parse_containers(&containers, containers_string); while(1) { output_stats(containers, length); diff --git a/metric_providers/disk/io/cgroup/container/Makefile b/metric_providers/disk/io/cgroup/container/Makefile index 9170c4109..45c04b7bd 100644 --- a/metric_providers/disk/io/cgroup/container/Makefile +++ b/metric_providers/disk/io/cgroup/container/Makefile @@ -1,4 +1,4 @@ CFLAGS = -O3 -Wall -I../../../../../lib/c metric-provider-binary: source.c - gcc ../../../../../lib/c/parse_int.o $< $(CFLAGS) -o $@ \ No newline at end of file + gcc ../../../../../lib/c/parse_int.o ../../../../../lib/c/detect_cgroup_path.o $< $(CFLAGS) -o $@ \ No newline at end of file diff --git a/metric_providers/disk/io/cgroup/container/provider.py b/metric_providers/disk/io/cgroup/container/provider.py index 1a150a294..2f2ba7eae 100644 --- a/metric_providers/disk/io/cgroup/container/provider.py +++ b/metric_providers/disk/io/cgroup/container/provider.py @@ -4,7 +4,7 @@ from metric_providers.base import BaseMetricProvider class DiskIoCgroupContainerProvider(BaseMetricProvider): - def __init__(self, resolution, rootless=False, skip_check=False): + def __init__(self, resolution, skip_check=False): super().__init__( metric_name='disk_io_cgroup_container', metrics={'time': int, 'read_bytes': int, 'written_bytes': int, 'container_id': str}, @@ -13,7 +13,6 @@ def __init__(self, resolution, rootless=False, skip_check=False): current_dir=os.path.dirname(os.path.abspath(__file__)), skip_check=skip_check, ) - self._rootless = rootless def read_metrics(self, run_id, containers=None): df = super().read_metrics(run_id, containers) diff --git a/metric_providers/disk/io/cgroup/container/source.c b/metric_providers/disk/io/cgroup/container/source.c index 22c255b22..bcb6de4bb 100644 --- a/metric_providers/disk/io/cgroup/container/source.c +++ b/metric_providers/disk/io/cgroup/container/source.c @@ -7,11 +7,13 @@ #include #include #include "parse_int.h" +#include "detect_cgroup_path.h" + #define DOCKER_CONTAINER_ID_BUFFER 65 // Docker container ID size is 64 + 1 byte for NUL termination typedef struct container_t { // struct is a specification and this static makes no sense here - char path[PATH_MAX]; + char* path; char id[DOCKER_CONTAINER_ID_BUFFER]; } container_t; @@ -35,7 +37,7 @@ static disk_io_t get_disk_cgroup(char* filename) { FILE * fd = fopen(filename, "r"); if ( fd == NULL) { - fprintf(stderr, "Error - Could not open path for reading: %s. Maybe the container is not running anymore? Are you using --rootless mode? Errno: %d\n", filename, errno); + fprintf(stderr, "Error - Could not open path for reading: %s. Maybe the container is not running anymore? Errno: %d\n", filename, errno); exit(1); } @@ -67,7 +69,7 @@ static void output_stats(container_t *containers, int length) { usleep(msleep_time*1000); } -static int parse_containers(container_t** containers, char* containers_string, int rootless_mode) { +static int parse_containers(container_t** containers, char* containers_string) { if(containers_string == NULL) { fprintf(stderr, "Please supply at least one container id with -s XXXX\n"); exit(1); @@ -93,17 +95,7 @@ static int parse_containers(container_t** containers, char* containers_string, i strncpy((*containers)[length-1].id, id, DOCKER_CONTAINER_ID_BUFFER - 1); (*containers)[length-1].id[DOCKER_CONTAINER_ID_BUFFER - 1] = '\0'; - if(rootless_mode) { - snprintf((*containers)[length-1].path, - PATH_MAX, - "/sys/fs/cgroup/user.slice/user-%d.slice/user@%d.service/user.slice/docker-%s.scope/io.stat", - user_id, user_id, id); - } else { - snprintf((*containers)[length-1].path, - PATH_MAX, - "/sys/fs/cgroup/system.slice/docker-%s.scope/io.stat", - id); - } + (*containers)[length-1].path = detect_cgroup_path("io.stat", user_id, id); } if(length == 0) { @@ -113,14 +105,10 @@ static int parse_containers(container_t** containers, char* containers_string, i return length; } -static int check_system(int rootless_mode) { +static int check_system() { const char* check_path; - if(rootless_mode) { - check_path = "/sys/fs/cgroup/user.slice/io.stat"; - } else { - check_path = "/sys/fs/cgroup/system.slice/io.stat"; - } + check_path = "/sys/fs/cgroup/io.stat"; FILE* fd = fopen(check_path, "r"); @@ -136,7 +124,6 @@ int main(int argc, char **argv) { int c; int check_system_flag = 0; - int rootless_mode = 0; // docker root is default char *containers_string = NULL; // Dynamic buffer to store optarg container_t *containers = NULL; @@ -145,7 +132,6 @@ int main(int argc, char **argv) { static struct option long_options[] = { - {"rootless", no_argument, NULL, 'r'}, {"help", no_argument, NULL, 'h'}, {"interval", no_argument, NULL, 'i'}, {"containers", no_argument, NULL, 's'}, @@ -153,7 +139,7 @@ int main(int argc, char **argv) { {NULL, 0, NULL, 0} }; - while ((c = getopt_long(argc, argv, "ri:s:hc", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "i:s:hc", long_options, NULL)) != -1) { switch (c) { case 'h': printf("Usage: %s [-i msleep_time] [-h]\n\n",argv[0]); @@ -165,9 +151,6 @@ int main(int argc, char **argv) { case 'i': msleep_time = parse_int(optarg); break; - case 'r': - rootless_mode = 1; - break; case 's': containers_string = (char *)malloc(strlen(optarg) + 1); // Allocate memory if (!containers_string) { @@ -187,10 +170,10 @@ int main(int argc, char **argv) { } if(check_system_flag){ - exit(check_system(rootless_mode)); + exit(check_system()); } - int length = parse_containers(&containers, containers_string, rootless_mode); + int length = parse_containers(&containers, containers_string); while(1) { output_stats(containers, length); diff --git a/metric_providers/disk/io/procfs/system/provider.py b/metric_providers/disk/io/procfs/system/provider.py index d91652039..055d667ba 100644 --- a/metric_providers/disk/io/procfs/system/provider.py +++ b/metric_providers/disk/io/procfs/system/provider.py @@ -5,7 +5,7 @@ from metric_providers.base import BaseMetricProvider class DiskIoProcfsSystemProvider(BaseMetricProvider): - def __init__(self, resolution, rootless=False, skip_check=False): + def __init__(self, resolution, skip_check=False): super().__init__( metric_name='disk_io_procfs_system', metrics={'time': int, 'read_sectors': int, 'written_sectors': int, 'device': str}, @@ -14,8 +14,6 @@ def __init__(self, resolution, rootless=False, skip_check=False): current_dir=os.path.dirname(os.path.abspath(__file__)), skip_check=skip_check, ) - self._rootless = rootless - def read_metrics(self, run_id, containers=None): df = super().read_metrics(run_id, containers) diff --git a/metric_providers/gpu/energy/nvidia/smi/component/provider.py b/metric_providers/gpu/energy/nvidia/smi/component/provider.py index 2239dd531..5bc9ddc48 100644 --- a/metric_providers/gpu/energy/nvidia/smi/component/provider.py +++ b/metric_providers/gpu/energy/nvidia/smi/component/provider.py @@ -40,8 +40,6 @@ def read_metrics(self, run_id, containers=None): One can see that the value only changes once per second ''' - df = df.sort_values(by=['time'], ascending=True) # sort since we do diff and lines might be mixed up in order - intervals = df['time'].diff() intervals[0] = intervals.mean() # approximate first interval diff --git a/metric_providers/memory/energy/rapl/msr/component/provider.py b/metric_providers/memory/energy/rapl/msr/component/provider.py index 3583f128c..506ef5aaf 100644 --- a/metric_providers/memory/energy/rapl/msr/component/provider.py +++ b/metric_providers/memory/energy/rapl/msr/component/provider.py @@ -1,6 +1,7 @@ import os -from metric_providers.base import BaseMetricProvider +from metric_providers.base import BaseMetricProvider, MetricProviderConfigurationError +from lib.utils import is_rapl_energy_filtering_deactivated class MemoryEnergyRaplMsrComponentProvider(BaseMetricProvider): def __init__(self, resolution, skip_check=False): @@ -18,6 +19,9 @@ def check_system(self, check_command="default", check_error_message=None, check_ call_string = f"{self._current_dir}/{self._metric_provider_executable}" super().check_system(check_command=[f"{call_string}", '-c', '-d']) + if not is_rapl_energy_filtering_deactivated(): + raise MetricProviderConfigurationError('RAPL energy filtering is active and might skew results!') + def read_metrics(self, run_id, containers=None): df = super().read_metrics(run_id, containers) diff --git a/metric_providers/memory/used/cgroup/container/Makefile b/metric_providers/memory/used/cgroup/container/Makefile index b7807d0fc..2c8216689 100644 --- a/metric_providers/memory/used/cgroup/container/Makefile +++ b/metric_providers/memory/used/cgroup/container/Makefile @@ -1,4 +1,4 @@ CFLAGS = -O3 -Wall -I../../../../../lib/c metric-provider-binary: source.c - gcc ../../../../../lib/c/parse_int.o $< $(CFLAGS) -o $@ + gcc ../../../../../lib/c/parse_int.o ../../../../../lib/c/detect_cgroup_path.o $< $(CFLAGS) -o $@ diff --git a/metric_providers/memory/used/cgroup/container/provider.py b/metric_providers/memory/used/cgroup/container/provider.py index e9ebf0827..6db7c2ecc 100644 --- a/metric_providers/memory/used/cgroup/container/provider.py +++ b/metric_providers/memory/used/cgroup/container/provider.py @@ -3,7 +3,7 @@ from metric_providers.base import BaseMetricProvider class MemoryUsedCgroupContainerProvider(BaseMetricProvider): - def __init__(self, resolution, rootless=False, skip_check=False): + def __init__(self, resolution, skip_check=False): super().__init__( metric_name='memory_used_cgroup_container', metrics={'time': int, 'value': int, 'container_id': str}, @@ -12,7 +12,6 @@ def __init__(self, resolution, rootless=False, skip_check=False): current_dir=os.path.dirname(os.path.abspath(__file__)), skip_check=skip_check, ) - self._rootless = rootless def read_metrics(self, run_id, containers=None): df = super().read_metrics(run_id, containers) diff --git a/metric_providers/memory/used/cgroup/container/source.c b/metric_providers/memory/used/cgroup/container/source.c index a67662384..8232102d8 100644 --- a/metric_providers/memory/used/cgroup/container/source.c +++ b/metric_providers/memory/used/cgroup/container/source.c @@ -7,11 +7,12 @@ #include #include #include "parse_int.h" +#include "detect_cgroup_path.h" #define DOCKER_CONTAINER_ID_BUFFER 65 // Docker container ID size is 64 + 1 byte for NUL termination typedef struct container_t { // struct is a specification and this static makes no sense here - char path[PATH_MAX]; + char* path; char id[DOCKER_CONTAINER_ID_BUFFER]; } container_t; @@ -27,7 +28,7 @@ static unsigned long long int get_memory_cgroup(char* filename) { FILE * fd = fopen(filename, "r"); if ( fd == NULL) { - fprintf(stderr, "Error - Could not open path for reading: %s. Maybe the container is not running anymore? Are you using --rootless mode? Errno: %d\n", filename, errno); + fprintf(stderr, "Error - Could not open path for reading: %s. Maybe the container is not running anymore? Errno: %d\n", filename, errno); exit(1); } @@ -59,7 +60,7 @@ static void output_stats(container_t *containers, int length) { usleep(msleep_time*1000); } -static int parse_containers(container_t** containers, char* containers_string, int rootless_mode) { +static int parse_containers(container_t** containers, char* containers_string) { if(containers_string == NULL) { fprintf(stderr, "Please supply at least one container id with -s XXXX\n"); exit(1); @@ -78,6 +79,7 @@ static int parse_containers(container_t** containers, char* containers_string, i //printf("Token: %s\n", id); length++; *containers = realloc(*containers, length * sizeof(container_t)); + if (!containers) { fprintf(stderr, "Could not allocate memory for containers string\n"); exit(1); @@ -85,17 +87,7 @@ static int parse_containers(container_t** containers, char* containers_string, i strncpy((*containers)[length-1].id, id, DOCKER_CONTAINER_ID_BUFFER - 1); (*containers)[length-1].id[DOCKER_CONTAINER_ID_BUFFER - 1] = '\0'; - if(rootless_mode) { - snprintf((*containers)[length-1].path, - PATH_MAX, - "/sys/fs/cgroup/user.slice/user-%d.slice/user@%d.service/user.slice/docker-%s.scope/memory.current", - user_id, user_id, id); - } else { - snprintf((*containers)[length-1].path, - PATH_MAX, - "/sys/fs/cgroup/system.slice/docker-%s.scope/memory.current", - id); - } + (*containers)[length-1].path = detect_cgroup_path("memory.current", user_id, id); } if(length == 0) { @@ -105,19 +97,15 @@ static int parse_containers(container_t** containers, char* containers_string, i return length; } -static int check_system(int rootless_mode) { +static int check_system() { const char* check_path; - if(rootless_mode) { - check_path = "/sys/fs/cgroup/user.slice/memory.current"; - } else { - check_path = "/sys/fs/cgroup/system.slice/memory.current"; - } + check_path = "/sys/fs/cgroup/memory.stat"; // note: the .current is only available in slices. if the memory.stat file is present, we expect the .current also in the slices FILE* fd = fopen(check_path, "r"); if (fd == NULL) { - fprintf(stderr, "Couldn't open memory.current file at %s\n", check_path); + fprintf(stderr, "Couldn't open memory.stat file at %s\n", check_path); exit(1); } fclose(fd); @@ -128,7 +116,6 @@ int main(int argc, char **argv) { int c; int check_system_flag = 0; - int rootless_mode = 0; // docker root is default char *containers_string = NULL; // Dynamic buffer to store optarg container_t *containers = NULL; @@ -137,7 +124,6 @@ int main(int argc, char **argv) { static struct option long_options[] = { - {"rootless", no_argument, NULL, 'r'}, {"help", no_argument, NULL, 'h'}, {"interval", no_argument, NULL, 'i'}, {"containers", no_argument, NULL, 's'}, @@ -145,7 +131,7 @@ int main(int argc, char **argv) { {NULL, 0, NULL, 0} }; - while ((c = getopt_long(argc, argv, "ri:s:hc", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "i:s:hc", long_options, NULL)) != -1) { switch (c) { case 'h': printf("Usage: %s [-i msleep_time] [-h]\n\n",argv[0]); @@ -157,9 +143,6 @@ int main(int argc, char **argv) { case 'i': msleep_time = parse_int(optarg); break; - case 'r': - rootless_mode = 1; - break; case 's': containers_string = (char *)malloc(strlen(optarg) + 1); // Allocate memory if (!containers_string) { @@ -179,10 +162,10 @@ int main(int argc, char **argv) { } if(check_system_flag){ - exit(check_system(rootless_mode)); + exit(check_system()); } - int length = parse_containers(&containers, containers_string, rootless_mode); + int length = parse_containers(&containers, containers_string); while(1) { output_stats(containers, length); diff --git a/metric_providers/memory/used/procfs/system/source.c b/metric_providers/memory/used/procfs/system/source.c index cd11ac3b4..9e496c64b 100644 --- a/metric_providers/memory/used/procfs/system/source.c +++ b/metric_providers/memory/used/procfs/system/source.c @@ -31,17 +31,21 @@ static unsigned long long int get_memory_procfs() { exit(1); } + if (fgets(buf, 200, fd) == NULL) { + fprintf(stderr, "Error or EOF encountered while reading input.\n"); + exit(1); + } - fgets(buf, 200, fd); match_result = sscanf(buf, "MemTotal: %llu kB", &mem_total); if (match_result != 1) { fprintf(stderr, "Error - MemTotal could not be matched in /proc/meminfo\n"); exit(1); } - fgets(buf, 200, fd); // drop MemFree - - fgets(buf, 200, fd); + if (fgets(buf, 200, fd) == NULL || fgets(buf, 200, fd) == NULL) { + fprintf(stderr, "Error or EOF encountered while reading input.\n"); + exit(1); + } match_result = sscanf(buf, "MemAvailable: %llu kB", &mem_available); if (match_result != 1) { fprintf(stderr, "Error - MemAvailable could not be matched in /proc/meminfo\n"); diff --git a/metric_providers/network/io/cgroup/container/Makefile b/metric_providers/network/io/cgroup/container/Makefile index abd83a86e..bf7b2e7de 100644 --- a/metric_providers/network/io/cgroup/container/Makefile +++ b/metric_providers/network/io/cgroup/container/Makefile @@ -1,6 +1,6 @@ CFLAGS = -O3 -Wall -lc -I../../../../../lib/c metric-provider-binary: source.c - gcc ../../../../../lib/c/parse_int.o $< $(CFLAGS) -o $@ + gcc ../../../../../lib/c/parse_int.o ../../../../../lib/c/detect_cgroup_path.o $< $(CFLAGS) -o $@ sudo chown root $@ sudo chmod u+s $@ \ No newline at end of file diff --git a/metric_providers/network/io/cgroup/container/provider.py b/metric_providers/network/io/cgroup/container/provider.py index a3e398a5e..0419333e6 100644 --- a/metric_providers/network/io/cgroup/container/provider.py +++ b/metric_providers/network/io/cgroup/container/provider.py @@ -4,7 +4,7 @@ from metric_providers.base import BaseMetricProvider class NetworkIoCgroupContainerProvider(BaseMetricProvider): - def __init__(self, resolution, rootless=False, skip_check=False): + def __init__(self, resolution, skip_check=False): super().__init__( metric_name='network_io_cgroup_container', metrics={'time': int, 'received_bytes': int, 'transmitted_bytes': int, 'container_id': str}, @@ -13,7 +13,6 @@ def __init__(self, resolution, rootless=False, skip_check=False): current_dir=os.path.dirname(os.path.abspath(__file__)), skip_check=skip_check, ) - self._rootless = rootless def read_metrics(self, run_id, containers=None): df = super().read_metrics(run_id, containers) diff --git a/metric_providers/network/io/cgroup/container/source.c b/metric_providers/network/io/cgroup/container/source.c index c647c5f0e..cdb55e2c8 100644 --- a/metric_providers/network/io/cgroup/container/source.c +++ b/metric_providers/network/io/cgroup/container/source.c @@ -11,11 +11,12 @@ #include #include #include "parse_int.h" +#include "detect_cgroup_path.h" #define DOCKER_CONTAINER_ID_BUFFER 65 // Docker container ID size is 64 + 1 byte for NUL termination typedef struct container_t { // struct is a specification and this static makes no sense here - char path[PATH_MAX]; + char* path; char id[DOCKER_CONTAINER_ID_BUFFER]; unsigned int pid; } container_t; @@ -81,9 +82,10 @@ static net_io_t get_network_cgroup(unsigned int pid) { exit(1); } - // skip first two lines - fgets(buf, 200, fd); - fgets(buf, 200, fd); + if (fgets(buf, 200, fd) == NULL || fgets(buf, 200, fd) == NULL) { + fprintf(stderr, "Error or EOF encountered while reading input.\n"); + exit(1); + } int match_result = 0; @@ -122,7 +124,7 @@ static void output_stats(container_t *containers, int length) { usleep(msleep_time*1000); } -static int parse_containers(container_t** containers, char* containers_string, int rootless_mode) { +static int parse_containers(container_t** containers, char* containers_string) { if(containers_string == NULL) { fprintf(stderr, "Please supply at least one container id with -s XXXX\n"); exit(1); @@ -135,7 +137,6 @@ static int parse_containers(container_t** containers, char* containers_string, i } char *id = strtok(containers_string,","); int length = 0; - int match_result = 0; for (; id != NULL; id = strtok(NULL, ",")) { //printf("Token: %s\n", id); @@ -148,49 +149,31 @@ static int parse_containers(container_t** containers, char* containers_string, i strncpy((*containers)[length-1].id, id, DOCKER_CONTAINER_ID_BUFFER - 1); (*containers)[length-1].id[DOCKER_CONTAINER_ID_BUFFER - 1] = '\0'; - if(rootless_mode) { - snprintf((*containers)[length-1].path, - PATH_MAX, - "/sys/fs/cgroup/user.slice/user-%d.slice/user@%d.service/user.slice/docker-%s.scope/cgroup.procs", - user_id, user_id, id); - } else { - snprintf((*containers)[length-1].path, - PATH_MAX, - "/sys/fs/cgroup/system.slice/docker-%s.scope/cgroup.procs", - id); - } - FILE* fd = fopen((*containers)[length-1].path, "r"); // check for general readability only once - if ( fd == NULL) { - fprintf(stderr, "Error - cgroup.procs file %s failed to open: errno: %d\n", (*containers)[length-1].path, errno); + (*containers)[length-1].path = detect_cgroup_path("cgroup.procs", user_id, id); + FILE* fd = fopen((*containers)[length-1].path, "r"); + if (fd != NULL) { + int match_result = fscanf(fd, "%u", &(*containers)[length-1].pid); + if (match_result != 1) { + fprintf(stderr, "Could not match container PID\n"); exit(1); + } + fclose(fd); } - match_result = fscanf(fd, "%u", &(*containers)[length-1].pid); - if (match_result != 1) { - fprintf(stderr, "Could not match container PID\n"); - exit(1); - } - fclose(fd); } if(length == 0) { fprintf(stderr, "Please supply at least one container id with -s XXXX\n"); exit(1); } - return length; } -static int check_system(int rootless_mode) { +static int check_system() { const char* file_path_cgroup_procs; const char file_path_proc_net_dev[] = "/proc/net/dev"; int found_error = 0; - if(rootless_mode) { - file_path_cgroup_procs = "/sys/fs/cgroup/user.slice/cgroup.procs"; - } else { - file_path_cgroup_procs = "/sys/fs/cgroup/system.slice/cgroup.procs"; - } - + file_path_cgroup_procs = "/sys/fs/cgroup/cgroup.procs"; FILE* fd = fopen(file_path_cgroup_procs, "r"); if (fd == NULL) { fprintf(stderr, "Couldn't open cgroup.procs file at %s\n", file_path_cgroup_procs); @@ -218,7 +201,6 @@ int main(int argc, char **argv) { int c; int check_system_flag = 0; - int rootless_mode = 0; // docker root is default char *containers_string = NULL; // Dynamic buffer to store optarg container_t *containers = NULL; @@ -227,7 +209,6 @@ int main(int argc, char **argv) { static struct option long_options[] = { - {"rootless", no_argument, NULL, 'r'}, {"help", no_argument, NULL, 'h'}, {"interval", no_argument, NULL, 'i'}, {"containers", no_argument, NULL, 's'}, @@ -235,7 +216,7 @@ int main(int argc, char **argv) { {NULL, 0, NULL, 0} }; - while ((c = getopt_long(argc, argv, "ri:s:hc", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "i:s:hc", long_options, NULL)) != -1) { switch (c) { case 'h': printf("Usage: %s [-i msleep_time] [-h]\n\n",argv[0]); @@ -247,9 +228,6 @@ int main(int argc, char **argv) { case 'i': msleep_time = parse_int(optarg); break; - case 'r': - rootless_mode = 1; - break; case 's': containers_string = (char *)malloc(strlen(optarg) + 1); // Allocate memory if (!containers_string) { @@ -269,10 +247,10 @@ int main(int argc, char **argv) { } if(check_system_flag){ - exit(check_system(rootless_mode)); + exit(check_system()); } - int length = parse_containers(&containers, containers_string, rootless_mode); + int length = parse_containers(&containers, containers_string); while(1) { output_stats(containers, length); diff --git a/metric_providers/network/io/procfs/system/source.c b/metric_providers/network/io/procfs/system/source.c index 55874d0bc..2cc322d3d 100644 --- a/metric_providers/network/io/procfs/system/source.c +++ b/metric_providers/network/io/procfs/system/source.c @@ -49,8 +49,10 @@ static void output_network_procfs() { } // skip first two lines - fgets(buf, 200, fd); - fgets(buf, 200, fd); + if (fgets(buf, 200, fd) == NULL || fgets(buf, 200, fd) == NULL) { + fprintf(stderr, "Error or EOF encountered while reading input.\n"); + exit(1); + } int match_result = 0; diff --git a/metric_providers/powermetrics/provider.py b/metric_providers/powermetrics/provider.py index 3d8284c48..ee33d80d3 100644 --- a/metric_providers/powermetrics/provider.py +++ b/metric_providers/powermetrics/provider.py @@ -41,7 +41,7 @@ def __init__(self, resolution, skip_check=False): ] - def check_system(self): + def check_system(self, check_command="default", check_error_message=None, check_parallel_provider=True): # no call to super().check_system() as we have different logic of finding the process if self._pm_process_count > 0: raise MetricProviderConfigurationError('Another instance of powermetrics is already running on the system!\n' diff --git a/metric_providers/psu/energy/ac/ipmi/machine/provider.py b/metric_providers/psu/energy/ac/ipmi/machine/provider.py index 6add6b585..2722a9820 100644 --- a/metric_providers/psu/energy/ac/ipmi/machine/provider.py +++ b/metric_providers/psu/energy/ac/ipmi/machine/provider.py @@ -37,8 +37,6 @@ def read_metrics(self, run_id, containers=None): One can see that the value only changes once per second ''' - df = df.sort_values(by=['time'], ascending=True) - intervals = df['time'].diff() intervals[0] = intervals.mean() # approximate first interval diff --git a/metric_providers/psu/energy/ac/mcp/machine/provider.py b/metric_providers/psu/energy/ac/mcp/machine/provider.py index 6440557ff..ebf60925a 100644 --- a/metric_providers/psu/energy/ac/mcp/machine/provider.py +++ b/metric_providers/psu/energy/ac/mcp/machine/provider.py @@ -35,8 +35,6 @@ def read_metrics(self, run_id, containers=None): One can see that the value only changes once per second ''' - df = df.sort_values(by=['time'], ascending=True) - intervals = df['time'].diff() intervals[0] = intervals.mean() # approximate first interval diff --git a/metric_providers/psu/energy/ac/sdia/machine/provider.py b/metric_providers/psu/energy/ac/sdia/machine/provider.py index c4ea18ec0..18358a342 100644 --- a/metric_providers/psu/energy/ac/sdia/machine/provider.py +++ b/metric_providers/psu/energy/ac/sdia/machine/provider.py @@ -74,8 +74,6 @@ def read_metrics(self, run_id, containers=None): if df.empty: return df - df = df.sort_values(by=['time'], ascending=True) - df['detail_name'] = '[DEFAULT]' # standard container name when no further granularity was measured df['metric'] = self._metric_name df['run_id'] = run_id diff --git a/metric_providers/psu/energy/ac/xgboost/machine/provider.py b/metric_providers/psu/energy/ac/xgboost/machine/provider.py index 0aaa3f38a..51f418bd0 100644 --- a/metric_providers/psu/energy/ac/xgboost/machine/provider.py +++ b/metric_providers/psu/energy/ac/xgboost/machine/provider.py @@ -13,7 +13,7 @@ class PsuEnergyAcXgboostMachineProvider(BaseMetricProvider): def __init__(self, *, resolution, HW_CPUFreq, CPUChips, CPUThreads, TDP, - HW_MemAmountGB, CPUCores=None, Hardware_Availability_Year=None, skip_check=False): + HW_MemAmountGB, CPUCores=None, Hardware_Availability_Year=None, VHost_Ratio=1, skip_check=False): super().__init__( metric_name="psu_energy_ac_xgboost_machine", metrics={"time": int, "value": int}, @@ -29,6 +29,7 @@ def __init__(self, *, resolution, HW_CPUFreq, CPUChips, CPUThreads, TDP, self.HW_MemAmountGB = HW_MemAmountGB self.CPUCores = CPUCores self.Hardware_Availability_Year=Hardware_Availability_Year + self.VHost_Ratio = VHost_Ratio # Since no process is ever started we just return None def get_stderr(self): @@ -74,8 +75,6 @@ def read_metrics(self, run_id, containers=None): if df.empty: return df - df = df.sort_values(by=['time'], ascending=True) - df['detail_name'] = '[DEFAULT]' # standard container name when no further granularity was measured df['metric'] = self._metric_name df['run_id'] = run_id @@ -105,6 +104,8 @@ def read_metrics(self, run_id, containers=None): df.value = df.value.apply(lambda x: interpolated_predictions[x / 100]) # will result in W + df.value = df.value*self.VHost_Ratio # apply vhost_ratio + df.value = (df.value * df.time.diff()) / 1_000 # W * us / 1_000 will result in mJ # we checked at ingest if it contains NA values. So NA can only occur if group diff resulted in only one value. diff --git a/metric_providers/psu/energy/dc/rapl/msr/machine/provider.py b/metric_providers/psu/energy/dc/rapl/msr/machine/provider.py index 20f12eb07..f1fc01fde 100644 --- a/metric_providers/psu/energy/dc/rapl/msr/machine/provider.py +++ b/metric_providers/psu/energy/dc/rapl/msr/machine/provider.py @@ -1,6 +1,7 @@ import os -from metric_providers.base import BaseMetricProvider +from metric_providers.base import BaseMetricProvider, MetricProviderConfigurationError +from lib.utils import is_rapl_energy_filtering_deactivated class PsuEnergyDcRaplMsrMachineProvider(BaseMetricProvider): def __init__(self, resolution, skip_check=False): @@ -18,6 +19,9 @@ def check_system(self, check_command="default", check_error_message=None, check_ call_string = f"{self._current_dir}/{self._metric_provider_executable}" super().check_system(check_command=[f"{call_string}", '-c', '-p']) + if not is_rapl_energy_filtering_deactivated(): + raise MetricProviderConfigurationError('RAPL energy filtering is active and might skew results!') + def read_metrics(self, run_id, containers=None): df = super().read_metrics(run_id, containers) diff --git a/requirements.txt b/requirements.txt index 1f7b7519a..8829f5fe9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,11 @@ PyYAML==6.0.2 pandas==2.2.3 psycopg[binary]==3.2.3 -psycopg_pool==3.2.3 +psycopg_pool==3.2.4 pyserial==3.5 psutil==6.1.0 schema==0.7.7 -aiohttp==3.10.10 +aiohttp==3.11.4 # calibration script dep tqdm==4.67.0 diff --git a/runner.py b/runner.py index 8ec7981cf..543c08f0c 100755 --- a/runner.py +++ b/runner.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr from lib.venv_checker import check_venv check_venv() # this check must even run before __main__ as imports might not get resolved @@ -12,7 +13,6 @@ import os import time from html import escape -import sys import importlib import re from io import StringIO @@ -50,7 +50,7 @@ def __init__(self, skip_unsafe=False, verbose_provider_boot=False, full_docker_prune=False, dev_no_sleeps=False, dev_cache_build=False, dev_no_metrics=False, dev_flow_timetravel=False, dev_no_optimizations=False, docker_prune=False, job_id=None, - user_id=None, measurement_flow_process_duration=None, measurement_total_duration=None): + user_id=None, measurement_flow_process_duration=None, measurement_total_duration=None, dev_no_phase_stats=False): if skip_unsafe is True and allow_unsafe is True: raise RuntimeError('Cannot specify both --skip-unsafe and --allow-unsafe') @@ -72,6 +72,7 @@ def __init__(self, self._dev_no_metrics = dev_no_metrics self._dev_flow_timetravel = dev_flow_timetravel self._dev_no_optimizations = dev_no_optimizations + self._dev_no_phase_stats = dev_no_phase_stats self._uri = uri self._uri_type = uri_type self._original_filename = filename @@ -497,10 +498,7 @@ def import_metric_providers(self): print(TerminalColors.WARNING, arrows('No metric providers were configured in config.yml. Was this intentional?'), TerminalColors.ENDC) return - docker_ps = subprocess.run(["docker", "info"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, encoding='UTF-8', check=True) - rootless = False - if 'rootless' in docker_ps.stdout: - rootless = True + subprocess.run(["docker", "info"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, encoding='UTF-8', check=True) for metric_provider in metric_providers: # will iterate over keys module_path, class_name = metric_provider.rsplit('.', 1) @@ -510,13 +508,7 @@ def import_metric_providers(self): print(f"Importing {class_name} from {module_path}") module = importlib.import_module(module_path) - if rootless and '.cgroup.' in module_path and self._skip_system_checks: - metric_provider_obj = getattr(module, class_name)(**conf, rootless=True, skip_check=True) - print(f"Configuration is {conf}; rootless=true, skip_check=True") - elif rootless and '.cgroup.' in module_path: - metric_provider_obj = getattr(module, class_name)(**conf, rootless=True) - print(f"Configuration is {conf}; rootless=true") - elif self._skip_system_checks: + if self._skip_system_checks: metric_provider_obj = getattr(module, class_name)(**conf, skip_check=True) print(f"Configuration is {conf}; skip_check=true") else: @@ -1369,7 +1361,10 @@ def start_measurement(self): self.__notes_helper.add_note({'note': 'Start of measurement', 'detail_name': '[NOTES]', 'timestamp': self.__start_measurement}) - def end_measurement(self): + def end_measurement(self, skip_on_already_ended=False): + if self.__end_measurement is not None and skip_on_already_ended is False: + raise RuntimeError('end_measurement was requested although value as already set!') + self.__end_measurement = int(time.time_ns() / 1_000) self.__notes_helper.add_note({'note': 'End of measurement', 'detail_name': '[NOTES]', 'timestamp': self.__end_measurement}) @@ -1520,6 +1515,7 @@ def run(self): ''' try: config = GlobalConfig().config + self.start_measurement() self.check_system('start') self.initialize_folder(self._tmp_folder) self.checkout_repository() @@ -1542,8 +1538,6 @@ def run(self): self.custom_sleep(config['measurement']['pre-test-sleep']) - self.start_measurement() - self.start_phase('[BASELINE]') self.custom_sleep(config['measurement']['baseline-duration']) self.end_phase('[BASELINE]') @@ -1595,8 +1589,6 @@ def run(self): self.end_measurement() self.check_process_returncodes() self.custom_sleep(config['measurement']['post-test-sleep']) - self.store_phases() - self.update_start_and_end_times() except BaseException as exc: self.add_to_log(exc.__class__.__name__, str(exc)) @@ -1604,6 +1596,18 @@ def run(self): raise exc finally: try: + self.end_measurement(skip_on_already_ended=True) # end_measurement can already been set if error happens in check_process_returncodes + if self.__phases: + last_phase_name, _ = next(reversed(self.__phases.items())) + if self.__phases[last_phase_name].get('end', None) is None: + self.__phases[last_phase_name]['end'] = int(time.time_ns() / 1_000) + + # Also patch Runtime phase separately, which we need as this will only get set after all child runtime phases + if self.__phases.get('[RUNTIME]', None) is not None and self.__phases['[RUNTIME]'].get('end', None) is None: + self.__phases['[RUNTIME]']['end'] = int(time.time_ns() / 1_000) + + self.update_start_and_end_times() + self.store_phases() self.read_container_logs() except BaseException as exc: self.add_to_log(exc.__class__.__name__, str(exc)) @@ -1633,7 +1637,26 @@ def run(self): self.add_to_log(exc.__class__.__name__, str(exc)) raise exc finally: - self.cleanup() # always run cleanup automatically after each run + try: + if self._dev_no_phase_stats is False: + # After every run, even if it failed, we want to generate phase stats. + # They will not show the accurate data, but they are still neded to understand how + # much a failed run has accrued in total energy and carbon costs + print(TerminalColors.HEADER, '\nCalculating and storing phases data. This can take a couple of seconds ...', TerminalColors.ENDC) + + # get all the metrics from the measurements table grouped by metric + # loop over them issuing separate queries to the DB + from tools.phase_stats import build_and_store_phase_stats # pylint: disable=import-outside-toplevel + build_and_store_phase_stats(self._run_id, self._sci) + + except BaseException as exc: + self.add_to_log(exc.__class__.__name__, str(exc)) + raise exc + finally: + self.cleanup() # always run cleanup automatically after each run + + + print(TerminalColors.OKGREEN, arrows('MEASUREMENT SUCCESSFULLY COMPLETED'), TerminalColors.ENDC) @@ -1659,6 +1682,7 @@ def run(self): parser.add_argument('--dev-flow-timetravel', action='store_true', help='Allows to repeat a failed flow or timetravel to beginning of flows or restart services.') parser.add_argument('--dev-no-metrics', action='store_true', help='Skips loading the metric providers. Runs will be faster, but you will have no metric') parser.add_argument('--dev-no-sleeps', action='store_true', help='Removes all sleeps. Resulting measurement data will be skewed.') + parser.add_argument('--dev-no-phase-stats', action='store_true', help='Do not calculate phase stats.') parser.add_argument('--dev-cache-build', action='store_true', help='Checks if a container image is already in the local cache and will then not build it. Also doesn\'t clear the images after a run. Please note that skipping builds only works the second time you make a run since the image has to be built at least initially to work.') parser.add_argument('--dev-no-optimizations', action='store_true', help='Disable analysis after run to find possible optimizations.') parser.add_argument('--print-logs', action='store_true', help='Prints the container and process logs to stdout') @@ -1714,29 +1738,21 @@ def run(self): full_docker_prune=args.full_docker_prune, dev_no_sleeps=args.dev_no_sleeps, dev_cache_build=args.dev_cache_build, dev_no_metrics=args.dev_no_metrics, dev_flow_timetravel=args.dev_flow_timetravel, dev_no_optimizations=args.dev_no_optimizations, - docker_prune=args.docker_prune) + docker_prune=args.docker_prune, dev_no_phase_stats=args.dev_no_phase_stats) # Using a very broad exception makes sense in this case as we have excepted all the specific ones before #pylint: disable=broad-except try: run_id = runner.run() # Start main code - # this code should live at a different position. + # this code can live at a different position. # From a user perspective it makes perfect sense to run both jobs directly after each other # In a cloud setup it however makes sense to free the measurement machine as soon as possible # So this code should be individually callable, separate from the runner - print(TerminalColors.HEADER, '\nCalculating and storing phases data. This can take a couple of seconds ...', TerminalColors.ENDC) - - # get all the metrics from the measurements table grouped by metric - # loop over them issuing separate queries to the DB - from tools.phase_stats import build_and_store_phase_stats - - build_and_store_phase_stats(runner._run_id, runner._sci) - # We need to import this here as we need the correct config file if not runner._dev_no_optimizations: - import optimization_providers.base + import optimization_providers.base # We need to import this here as we need the correct config file print(TerminalColors.HEADER, '\nImporting optimization reporters ...', TerminalColors.ENDC) optimization_providers.base.import_reporters() diff --git a/tests/api/test_api_helpers.py b/tests/api/test_api_helpers.py index 92c87fbd8..b359083e4 100644 --- a/tests/api/test_api_helpers.py +++ b/tests/api/test_api_helpers.py @@ -1,6 +1,7 @@ from pydantic import BaseModel from api import api_helpers +import pytest class Run(BaseModel): name: str @@ -23,6 +24,30 @@ class CI_Measurement(BaseModel): duration: int +def test_rescale_energy_value(): + assert api_helpers.rescale_energy_value(100, 'uJ') == [100, 'uJ'] + + assert api_helpers.rescale_energy_value(10000, 'uJ') == [10, 'mJ'] + + assert api_helpers.rescale_energy_value(10000, 'mJ') == [10, 'J'] + + assert api_helpers.rescale_energy_value(324_000_000_000, 'uJ') == [324, 'kJ'] + + assert api_helpers.rescale_energy_value(324_000_000_000, 'ugCO2e/Page Request') == [324, 'kgCO2e/Page Request'] + + assert api_helpers.rescale_energy_value(222_000_000_000_000, 'ugCO2e/Kill') == [222, 'MgCO2e/Kill'] + + assert api_helpers.rescale_energy_value(0.0003, 'ugCO2e/Kill') == [0.3, 'ngCO2e/Kill'] + + + with pytest.raises(ValueError): + api_helpers.rescale_energy_value(100, 'xJ') # expecting only mJ and uJ + + with pytest.raises(ValueError): + api_helpers.rescale_energy_value(100, 'uj') # expecting only mJ and uJ + + + def test_escape_dict(): messy_dict = {"link": 'Click me'} escaped_link = '<a href="http://www.github.com">Click me</a>' diff --git a/tests/api/test_api_refactor.py b/tests/api/test_api_refactor.py deleted file mode 100644 index 0426ec2ac..000000000 --- a/tests/api/test_api_refactor.py +++ /dev/null @@ -1,153 +0,0 @@ -import json -import os -import requests -import re - -CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) - -from lib.user import User -from lib.db import DB -from lib import utils -from lib.global_config import GlobalConfig -from tests import test_functions as Tests - -API_URL = GlobalConfig().config['cluster']['api_url'] - -from api.main import Software - -# TODO: Due to a waiting merge these tests are in a single file. Merge into the test_api_base if done - -def test_post_run_add_github_one_off(): - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='https://github.com/green-coding-solutions/green-metrics-tool', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='one-off') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 202, Tests.assertion_info('success', response.text) - - job_id = get_job_id(run_name) - assert job_id is not None - -def test_post_run_add_github_tags(): - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='https://github.com/green-coding-solutions/green-metrics-tool', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='tag') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 202, Tests.assertion_info('success', response.text) - - job_id = get_job_id(run_name) - assert job_id is not None - - timeline_project = utils.get_timeline_project('https://github.com/green-coding-solutions/green-metrics-tool') - - assert re.match(r'v\d+\.\d+\.?\d*',timeline_project['last_marker']) - assert timeline_project['schedule_mode'] == 'tag' - -def test_post_run_add_github_commit(): - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='https://github.com/green-coding-solutions/green-metrics-tool', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='commit-variance') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 202, Tests.assertion_info('success', response.text) - - job_id = get_job_id(run_name) - assert job_id is not None - - timeline_project = utils.get_timeline_project('https://github.com/green-coding-solutions/green-metrics-tool') - assert re.match(r'^[a-fA-F0-9]{40}$',timeline_project['last_marker']) - assert timeline_project['schedule_mode'] == 'commit-variance' - - -def test_post_run_add_gitlab_commit(): - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='https://gitlab.com/green-coding-solutions/ci-carbon-testing', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='commit') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 202, Tests.assertion_info('success', response.text) - - timeline_project = utils.get_timeline_project('https://gitlab.com/green-coding-solutions/ci-carbon-testing') - assert re.match(r'^[a-fA-F0-9]{40}$',timeline_project['last_marker']) - assert timeline_project['schedule_mode'] == 'commit' - -def test_post_run_add_gitlab_tag_none_tag(): - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='https://gitlab.com/green-coding-solutions/ci-carbon-testing', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='tag') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 202, Tests.assertion_info('success', response.text) - - timeline_project = utils.get_timeline_project('https://gitlab.com/green-coding-solutions/ci-carbon-testing') - assert timeline_project['last_marker'] is None - assert timeline_project['schedule_mode'] == 'tag' - -def test_post_run_add_gitlab_tag(): - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='https://gitlab.com/green-coding-solutions/green-metrics-tool', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='tag') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 202, Tests.assertion_info('success', response.text) - - timeline_project = utils.get_timeline_project('https://gitlab.com/green-coding-solutions/green-metrics-tool') - assert re.match(r'v\d+\.\d+\.?\d*',timeline_project['last_marker']) - assert timeline_project['schedule_mode'] == 'tag' - -def test_post_run_add_gitlab_custom_api_base(): - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='https://gitlab.rlp.net/green-software-engineering/oscar', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='commit') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 202, Tests.assertion_info('success', response.text) - - timeline_project = utils.get_timeline_project('https://gitlab.rlp.net/green-software-engineering/oscar') - assert re.match(r'^[a-fA-F0-9]{40}$',timeline_project['last_marker']) - assert timeline_project['schedule_mode'] == 'commit' - - -def test_post_run_add_no_permissions(): - user = User(1) - user._capabilities['machines'] = [] - user.update() - - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='https://github.com/green-coding-solutions/green-metrics-tool', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='eisen') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 422, Tests.assertion_info('success', response.text) - assert json.loads(response.text)['err'] == 'Your user does not have the permissions to use that machine.' - -def test_post_run_add_machine_does_not_exist(): - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='https://github.com/green-coding-solutions/green-metrics-tool', email='testEmail', branch='', filename='', machine_id=30, schedule_mode='eisen') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 422, Tests.assertion_info('success', response.text) - assert json.loads(response.text)['err'] == 'Machine does not exist' - - -def test_post_run_add_unknown_measurement_interval(): - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='https://github.com/no-company-here/and-no-repo/', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='eisen') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 422, Tests.assertion_info('success', response.text) - assert json.loads(response.text)['err'] == 'Please select a valid measurement interval. (eisen) is unknown.' - -def test_post_run_add_broken_repo_url(): - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='h8gw4hruihuf', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='one-off') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 422, Tests.assertion_info('success', response.text) - assert json.loads(response.text)['err'] == 'Could not find URL h8gw4hruihuf. Is the URL public accessible and repo not empty?' - -def test_post_run_add_non_existent_repo(): - run_name = 'test_' + utils.randomword(12) - run = Software(name=run_name, url='https://github.com/no-company-here/and-no-repo/', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='one-off') - response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) - assert response.status_code == 422, Tests.assertion_info('success', response.text) - assert json.loads(response.text)['err'] == 'Could not find URL https://github.com/no-company-here/and-no-repo/. Is the URL public accessible and repo not empty?' - - - - -## helpers -def get_job_id(run_name): - query = """ - SELECT - id - FROM - jobs - WHERE name = %s - """ - data = DB().fetch_one(query, (run_name, )) - if data is None or data == []: - return None - return data[0] diff --git a/tests/api/test_api_software_add.py b/tests/api/test_api_software_add.py index 949198f88..201ae4232 100644 --- a/tests/api/test_api_software_add.py +++ b/tests/api/test_api_software_add.py @@ -124,14 +124,14 @@ def test_post_run_add_broken_repo_url(): run = Software(name=run_name, url='h8gw4hruihuf', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='one-off') response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) assert response.status_code == 422, Tests.assertion_info('success', response.text) - assert json.loads(response.text)['err'] == 'Could not find URL h8gw4hruihuf. Is the URL public accessible and repo not empty?' + assert json.loads(response.text)['err'] == 'Could not find repository h8gw4hruihuf and branch main. Is the repo publicly accessible, not empty and does the branch main exist?' def test_post_run_add_non_existent_repo(): run_name = 'test_' + utils.randomword(12) run = Software(name=run_name, url='https://github.com/no-company-here/and-no-repo/', email='testEmail', branch='', filename='', machine_id=1, schedule_mode='one-off') response = requests.post(f"{API_URL}/v1/software/add", json=run.model_dump(), timeout=15) assert response.status_code == 422, Tests.assertion_info('success', response.text) - assert json.loads(response.text)['err'] == 'Could not find URL https://github.com/no-company-here/and-no-repo/. Is the URL public accessible and repo not empty?' + assert json.loads(response.text)['err'] == 'Could not find repository https://github.com/no-company-here/and-no-repo/ and branch main. Is the repo publicly accessible, not empty and does the branch main exist?' diff --git a/tests/data/metrics/cpu_energy_rapl_msr_component.log b/tests/data/metrics/cpu_energy_rapl_msr_component.log new file mode 100644 index 000000000..28e00df88 --- /dev/null +++ b/tests/data/metrics/cpu_energy_rapl_msr_component.log @@ -0,0 +1,19 @@ +1727360252870421 412 Package_0 +1727360253870907 22344 Package_0 +1727360254871393 12231 Package_0 +1727360255871871 22 Package_0 +1727360256872411 423141 Package_0 +1727360257872837 12321 Package_0 +1727360258873329 12333 Package_0 +1727360259873653 124124 Package_0 +1727360260874076 41214123 Package_0 +1727360261874648 1224 Package_0 +1727360262875178 12321 Package_0 +1727360263875733 4124 Package_0 +1727360264876046 1231 Package_0 +1727360265876460 41241 Package_0 +1727360266876959 123333 Package_0 +1727360267877469 123123 Package_0 +1727360268877768 41241 Package_0 +1727360269878345 22222 Package_0 +1727360270878775 2313123 Package_0 diff --git a/tests/data/metrics/cpu_energy_rapl_msr_component_undeflow.log b/tests/data/metrics/cpu_energy_rapl_msr_component_undeflow.log new file mode 100644 index 000000000..4e10a4d0e --- /dev/null +++ b/tests/data/metrics/cpu_energy_rapl_msr_component_undeflow.log @@ -0,0 +1,19 @@ +1727360252870421 412 Package_0 +1727360253870907 22344 Package_0 +1727360254871393 12231 Package_0 +1727360255871871 22 Package_0 +1727360256872411 1 Package_0 +1727360257872837 12321 Package_0 +1727360258873329 12333 Package_0 +1727360259873653 124124 Package_0 +1727360260874076 41214123 Package_0 +1727360261874648 1224 Package_0 +1727360262875178 12321 Package_0 +1727360263875733 4124 Package_0 +1727360264876046 1231 Package_0 +1727360265876460 41241 Package_0 +1727360266876959 123333 Package_0 +1727360267877469 123123 Package_0 +1727360268877768 41241 Package_0 +1727360269878345 22222 Package_0 +1727360270878775 2313123 Package_0 diff --git a/tests/data/metrics/network_io_procfs_system_non_monotonic.log b/tests/data/metrics/network_io_procfs_system_non_monotonic.log new file mode 100644 index 000000000..66c465d3f --- /dev/null +++ b/tests/data/metrics/network_io_procfs_system_non_monotonic.log @@ -0,0 +1,38 @@ +1727360252870421 64258468 64258468 lo +1727360252870421 764029694 24268262 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360253870907 64258468 64258468 lo +1727360253870907 764037254 24274388 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360254871393 64258468 64258468 lo +1727360254871393 764045114 24280514 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360254871392 64258468 64258468 lo +1727360254871399 764051732 24285786 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360256872411 64258468 64258468 lo +1727360256872411 764051798 24286300 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360257872837 64258468 64258468 lo +1727360257872837 764051864 24286814 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360258873329 64258468 64258468 lo +1727360258873329 764051930 24287328 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360259873653 64258468 64258468 lo +1727360259873653 764051996 24287842 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360260874076 64258468 64258468 lo +1727360260874076 764052062 24288356 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360261874648 64258468 64258468 lo +1727360261874648 764052128 24288870 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360262875178 64258468 64258468 lo +1727360262875178 764052194 24289384 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360263875733 64258468 64258468 lo +1727360263875733 764052428 24290012 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360264876046 64258468 64258468 lo +1727360264876046 764052494 24290526 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360265876460 64258468 64258468 lo +1727360265876460 764052560 24291040 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360266876959 64258468 64258468 lo +1727360266876959 764052626 24291554 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360267877469 64258468 64258468 lo +1727360267877469 764052692 24292068 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360268877768 64258468 64258468 lo +1727360268877768 764052758 24292582 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360269878345 64258468 64258468 lo +1727360269878345 764052824 24293096 CURRENT_ACTUAL_NETWORK_INTERFACE +1727360270878775 64258468 64258468 lo +1727360270878775 764052968 24294124 CURRENT_ACTUAL_NETWORK_INTERFACE diff --git a/tests/metric_providers/test_metric_providers.py b/tests/metric_providers/test_metric_providers.py index c0e9bca1c..0b5849847 100644 --- a/tests/metric_providers/test_metric_providers.py +++ b/tests/metric_providers/test_metric_providers.py @@ -13,8 +13,7 @@ from lib import utils from runner import Runner from metric_providers.network.io.procfs.system.provider import NetworkIoProcfsSystemProvider -from tools.phase_stats import build_and_store_phase_stats - +from metric_providers.cpu.energy.rapl.msr.component.provider import CpuEnergyRaplMsrComponentProvider #pylint: disable=unused-argument @pytest.fixture(autouse=True, scope='module') # override by setting scope to module only @@ -37,8 +36,6 @@ def setup_module(module): run_id = runner.run() - build_and_store_phase_stats(runner._run_id, runner._sci) - def get_disk_usage(path="/"): usage = psutil.disk_usage(path) total = usage.total @@ -46,6 +43,14 @@ def get_disk_usage(path="/"): free = usage.free return {'total': total, 'used': used, 'free': free} +def mock_temporary_file(file_path, temp_file): + with open(file_path, 'r', encoding='utf-8') as file: + file_contents = file.read() + + # Write the modified contents back to the file + with open(temp_file, 'w', encoding='utf-8') as file: + file.write(file_contents) + def mock_temporary_network_file(file_path, temp_file, actual_network_interface): with open(file_path, 'r', encoding='utf-8') as file: file_contents = file.read() @@ -203,3 +208,47 @@ def test_cpu_memory_carbon_providers(): return assert seen_memory_used_procfs_system is True, "Did not see seen_memory_used_procfs_system metric" + + +def test_monotonic(): + obj = NetworkIoProcfsSystemProvider(100, remove_virtual_interfaces=False, skip_check=True) + + with tempfile.NamedTemporaryFile(delete=True) as temp_file: + mock_temporary_file('./data/metrics/network_io_procfs_system.log', temp_file.name) + + obj._filename = temp_file.name + obj.read_metrics('RUN_ID') + + +def test_non_monotonic(): + obj = NetworkIoProcfsSystemProvider(100, remove_virtual_interfaces=False, skip_check=True) + + with tempfile.NamedTemporaryFile(delete=True) as temp_file: + mock_temporary_file('./data/metrics/network_io_procfs_system_non_monotonic.log', temp_file.name) + + obj._filename = temp_file.name + with pytest.raises(ValueError) as e: + obj.read_metrics('RUN_ID') + + assert str(e.value) == "Data from metric provider network_io_procfs_system is not monotonic increasing" + +def test_resolution_ok(): + obj = CpuEnergyRaplMsrComponentProvider(100, skip_check=True) + + with tempfile.NamedTemporaryFile(delete=True) as temp_file: + mock_temporary_file('./data/metrics/cpu_energy_rapl_msr_component.log', temp_file.name) + + obj._filename = temp_file.name + obj.read_metrics('RUN_ID') + +def test_resolution_underflow(): + obj = CpuEnergyRaplMsrComponentProvider(100, skip_check=True) + + with tempfile.NamedTemporaryFile(delete=True) as temp_file: + mock_temporary_file('./data/metrics/cpu_energy_rapl_msr_component_undeflow.log', temp_file.name) + + obj._filename = temp_file.name + with pytest.raises(ValueError) as e: + obj.read_metrics('RUN_ID') + + assert str(e.value) == "Data from metric provider cpu_energy_rapl_msr_component is running into a resolution underflow. Values are <= 1 mJ" diff --git a/tests/test-config.yml b/tests/test-config.yml index 46ab25090..3a8fb721b 100644 --- a/tests/test-config.yml +++ b/tests/test-config.yml @@ -78,6 +78,12 @@ measurement: resolution: 99 memory.used.procfs.system.provider.MemoryUsedProcfsSystemProvider: resolution: 99 + cpu.utilization.cgroup.container.provider.CpuUtilizationCgroupContainerProvider: + resolution: 99 + memory.used.cgroup.container.provider.MemoryUsedCgroupContainerProvider: + resolution: 99 + network.io.cgroup.container.provider.NetworkIoCgroupContainerProvider: + resolution: 99 macos: cpu.utilization.mach.system.provider.CpuUtilizationMachSystemProvider: resolution: 99 diff --git a/tests/test_config_opts.py b/tests/test_config_opts.py index 1f24c8312..1b78c78f2 100644 --- a/tests/test_config_opts.py +++ b/tests/test_config_opts.py @@ -15,7 +15,7 @@ def test_global_timeout(): measurement_total_duration = 1 - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_cache_build=False, dev_no_sleeps=True, dev_no_metrics=True, measurement_total_duration=1) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_cache_build=False, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True, measurement_total_duration=1) out = io.StringIO() err = io.StringIO() @@ -50,7 +50,7 @@ def reset_config_fixture(): # Rethink how to do this test entirely def wip_test_idle_start_time(reset_config): GlobalConfig().config['measurement']['idle-time-start'] = 2 - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) run_id = runner.run() query = """ SELECT @@ -76,7 +76,7 @@ def wip_test_idle_start_time(reset_config): # Rethink how to do this test entirely def wip_test_idle_end_time(reset_config): GlobalConfig().config['measurement']['idle-time-end'] = 2 - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) run_id = runner.run() query = """ SELECT @@ -100,7 +100,7 @@ def wip_test_idle_end_time(reset_config): def wip_test_process_runtime_exceeded(reset_config): GlobalConfig().config['measurement']['flow-process-runtime'] = .1 - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(RuntimeError) as err: runner.run() expected_exception = 'Process exceeded runtime of 0.1s: stress-ng -c 1 -t 1 -q' diff --git a/tests/test_functions.py b/tests/test_functions.py index 45019df60..3deb2bbd3 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -68,6 +68,7 @@ def __enter__(self): def run_until(self, step): try: config = GlobalConfig().config + self.__runner.start_measurement() self.__runner.check_system('start') self.__runner.initialize_folder(self.__runner._tmp_folder) self.__runner.checkout_repository() @@ -87,8 +88,6 @@ def run_until(self, step): self.__runner.start_metric_providers(allow_other=True, allow_container=False) self.__runner.custom_sleep(config['measurement']['pre-test-sleep']) - self.__runner.start_measurement() - self.__runner.start_phase('[BASELINE]') self.__runner.custom_sleep(config['measurement']['baseline-duration']) self.__runner.end_phase('[BASELINE]') @@ -123,9 +122,18 @@ def run_until(self, step): self.__runner.end_measurement() self.__runner.check_process_returncodes() self.__runner.custom_sleep(config['measurement']['post-test-sleep']) - self.__runner.store_phases() self.__runner.update_start_and_end_times() + self.__runner.store_phases() + self.__runner.read_container_logs() self.__runner.read_and_cleanup_processes() + self.__runner.save_notes_runner() + self.__runner.stop_metric_providers() + self.__runner.save_stdout_logs() + + if self.__runner._dev_no_phase_stats is False: + from tools.phase_stats import build_and_store_phase_stats # pylint: disable=import-outside-toplevel + build_and_store_phase_stats(self.__runner._run_id, self.__runner._sci) + except BaseException as exc: self.__runner.add_to_log(exc.__class__.__name__, str(exc)) raise exc diff --git a/tests/test_usage_scenario.py b/tests/test_usage_scenario.py index e1cc893aa..9c27b8b6c 100644 --- a/tests/test_usage_scenario.py +++ b/tests/test_usage_scenario.py @@ -47,7 +47,7 @@ def get_env_vars(): # Test allowed characters def test_env_variable_allowed_characters(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/env_vars_stress_allowed.yml', skip_unsafe=False, skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/env_vars_stress_allowed.yml', skip_unsafe=False, skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') @@ -60,7 +60,7 @@ def test_env_variable_allowed_characters(): # Test too long values def test_env_variable_too_long(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/env_vars_stress_forbidden.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/env_vars_stress_forbidden.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(RuntimeError) as e: with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') @@ -69,7 +69,7 @@ def test_env_variable_too_long(): # Test skip_unsafe=true def test_env_variable_skip_unsafe_true(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/env_vars_stress_forbidden.yml', skip_unsafe=True, skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/env_vars_stress_forbidden.yml', skip_unsafe=True, skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') @@ -81,7 +81,7 @@ def test_env_variable_skip_unsafe_true(): # Test allow_unsafe=true def test_env_variable_allow_unsafe_true(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/env_vars_stress_forbidden.yml', allow_unsafe=True, skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/env_vars_stress_forbidden.yml', allow_unsafe=True, skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') env_var_output = get_env_vars() @@ -106,7 +106,7 @@ def get_port_bindings(): return port, err def test_port_bindings_allow_unsafe_true(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/port_bindings_stress.yml', allow_unsafe=True, skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/port_bindings_stress.yml', allow_unsafe=True, skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') @@ -117,7 +117,7 @@ def test_port_bindings_allow_unsafe_true(): def test_port_bindings_skip_unsafe_true(): out = io.StringIO() err = io.StringIO() - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/port_bindings_stress.yml', skip_unsafe=True, skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/port_bindings_stress.yml', skip_unsafe=True, skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) # need to catch exception here as otherwise the subprocess returning an error will # fail the test @@ -134,7 +134,7 @@ def test_port_bindings_skip_unsafe_true(): Tests.assertion_info(f"Warning: {expected_warning}", 'no/different warning') def test_port_bindings_no_skip_or_allow(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/port_bindings_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/port_bindings_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(Exception) as e: with Tests.RunUntilManager(runner) as context: @@ -148,7 +148,7 @@ def test_port_bindings_no_skip_or_allow(): Tests.assertion_info(f"Exception: {expected_error}", str(e.value)) def test_compose_include_not_same_dir(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/parentdir_compose_include/subdir/usage_scenario_fail.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/parentdir_compose_include/subdir/usage_scenario_fail.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False) out = io.StringIO() err = io.StringIO() @@ -161,7 +161,7 @@ def test_compose_include_not_same_dir(): Tests.assertion_info('Root directory escape', str(e.value)) def test_context_include(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/subdir_parent_context/subdir/usage_scenario_ok.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/subdir_parent_context/subdir/usage_scenario_ok.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False) out = io.StringIO() err = io.StringIO() @@ -172,7 +172,7 @@ def test_context_include(): # will not throw an exception def test_context_include_escape(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/subdir_parent_context/subdir/usage_scenario_fail.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/subdir_parent_context/subdir/usage_scenario_fail.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False) out = io.StringIO() err = io.StringIO() @@ -191,7 +191,7 @@ def test_context_include_escape(): def test_setup_commands_one_command(): out = io.StringIO() err = io.StringIO() - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/setup_commands_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/setup_commands_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with redirect_stdout(out), redirect_stderr(err): with Tests.RunUntilManager(runner) as context: @@ -204,7 +204,7 @@ def test_setup_commands_one_command(): def test_setup_commands_multiple_commands(): out = io.StringIO() err = io.StringIO() - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/setup_commands_multiple_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/setup_commands_multiple_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with redirect_stdout(out), redirect_stderr(err): with Tests.RunUntilManager(runner) as context: @@ -242,7 +242,7 @@ def assert_order(text, first, second): def test_depends_on_order(): out = io.StringIO() err = io.StringIO() - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with redirect_stdout(out), redirect_stderr(err): with Tests.RunUntilManager(runner) as context: @@ -257,7 +257,7 @@ def test_depends_on_order(): def test_depends_on_huge(): out = io.StringIO() err = io.StringIO() - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_huge.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_huge.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with redirect_stdout(out), redirect_stderr(err): with Tests.RunUntilManager(runner) as context: @@ -327,7 +327,7 @@ def test_depends_on_huge(): assert_order(out.getvalue(), 'test-container-1', 'test-container-2') def test_depends_on_error_not_running(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_error_not_running.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_error_not_running.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(RuntimeError) as e: with Tests.RunUntilManager(runner) as context: @@ -337,7 +337,7 @@ def test_depends_on_error_not_running(): Tests.assertion_info("State check of dependent services of 'test-container-1' failed! Container 'test-container-2' is not running but 'exited' after waiting for 10 sec! Consider checking your service configuration, the entrypoint of the container or the logs of the container.", str(e.value)) def test_depends_on_error_cyclic_dependency(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_error_cycle.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_error_cycle.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(RuntimeError) as e: with Tests.RunUntilManager(runner) as context: @@ -347,7 +347,7 @@ def test_depends_on_error_cyclic_dependency(): Tests.assertion_info("Cycle found in depends_on definition with service 'test-container-1'!", str(e.value)) def test_depends_on_error_unsupported_condition(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_error_unsupported_condition.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_error_unsupported_condition.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(RuntimeError) as e: with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') @@ -357,7 +357,7 @@ def test_depends_on_error_unsupported_condition(): Tests.assertion_info(message, str(e.value)) def test_depends_on_long_form(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_long_form.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_long_form.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) out = io.StringIO() err = io.StringIO() @@ -368,7 +368,7 @@ def test_depends_on_long_form(): Tests.assertion_info(message, out.getvalue()) def test_depends_on_with_custom_container_name(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_custom_container_name.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/depends_on_custom_container_name.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) out = io.StringIO() err = io.StringIO() @@ -382,7 +382,7 @@ def test_depends_on_with_custom_container_name(): def test_depends_on_healthcheck_using_interval(): # Test setup: Container has a startup time of 3 seconds, interval is set to 1s, retries is set to a number bigger than 3. # Container should become healthy after 3 seconds. - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_using_interval.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_using_interval.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) out = io.StringIO() err = io.StringIO() @@ -397,7 +397,7 @@ def test_depends_on_healthcheck_using_start_interval(): # Using start_interval is preferable (available since Docker Engine version 25) # Test setup: Container has a startup time of 3 seconds, start_interval is set to 1s, start_period to 5s # Container should become healthy after 3 seconds. - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_using_start_interval.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_using_start_interval.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) out = io.StringIO() err = io.StringIO() @@ -420,7 +420,7 @@ def test_depends_on_healthcheck_using_start_interval(): def test_depends_on_healthcheck_missing_start_period(): # Test setup: Container would be healthy after 3 seconds, however, no start_period is set (default 0s), therefore start_interval is not used. # Because max waiting time is configured to be 5s (test_config.yml), exception is raised after 5s. - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_missing_start_period.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_missing_start_period.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(RuntimeError) as e: with Tests.RunUntilManager(runner) as context: @@ -431,7 +431,7 @@ def test_depends_on_healthcheck_missing_start_period(): Tests.assertion_info(f"Exception: {expected_exception}", str(e.value)) def test_depends_on_healthcheck_error_missing(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_error_missing.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_error_missing.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(RuntimeError) as e: runner.run() @@ -443,7 +443,7 @@ def test_depends_on_healthcheck_error_missing(): def test_depends_on_healthcheck_error_container_unhealthy(): # Test setup: Healthcheck test will never be successful, interval is set to 1s and retries to 3. # Container should become unhealthy after 3-4 seconds. - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_error_container_unhealthy.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_error_container_unhealthy.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(RuntimeError) as e: with Tests.RunUntilManager(runner) as context: @@ -457,7 +457,7 @@ def test_depends_on_healthcheck_error_container_unhealthy(): def test_depends_on_healthcheck_error_max_waiting_time(): # Test setup: Container would be healthy after 7 seconds, however, interval is set to 100s and there is no start interval. # Because max waiting time is configured to be 10s (test_config.yml), the healthcheck at 10s will never be executed. - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_error_max_waiting_time.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/healthcheck_error_max_waiting_time.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(RuntimeError) as e: with Tests.RunUntilManager(runner) as context: @@ -468,7 +468,7 @@ def test_depends_on_healthcheck_error_max_waiting_time(): Tests.assertion_info(f"Exception: {expected_exception}", str(e.value)) def test_network_created(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/network_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/network_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') ps = subprocess.run( @@ -482,7 +482,7 @@ def test_network_created(): assert 'gmt-test-network' in ls, Tests.assertion_info('gmt-test-network', ls) def test_container_is_in_network(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/network_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/network_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') ps = subprocess.run( @@ -500,7 +500,7 @@ def test_container_is_in_network(): # When container does not have a daemon running typically a shell # is started here to have the container running like bash or sh def test_cmd_ran(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/cmd_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/cmd_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') ps = subprocess.run( @@ -523,7 +523,7 @@ def test_uri_local_dir(): ps = subprocess.run( ['python3', '../runner.py', '--name', run_name, '--uri', GMT_DIR ,'--config-override', f"{os.path.dirname(os.path.realpath(__file__))}/test-config.yml", '--filename', filename, - '--skip-system-checks', '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-optimizations'], + '--skip-system-checks', '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-phase-stats', '--dev-no-optimizations'], check=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, @@ -537,7 +537,7 @@ def test_uri_local_dir(): assert ps.stderr == '', Tests.assertion_info('no errors', ps.stderr) def test_uri_local_dir_missing(): - runner = Runner(uri='/tmp/missing', uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri='/tmp/missing', uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(FileNotFoundError) as e: runner.run() @@ -555,7 +555,7 @@ def test_uri_github_repo(): run_name = 'test_' + utils.randomword(12) ps = subprocess.run( ['python3', '../runner.py', '--name', run_name, '--uri', uri ,'--config-override', f"{os.path.dirname(os.path.realpath(__file__))}/test-config.yml", - '--skip-system-checks', '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-optimizations'], + '--skip-system-checks', '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-phase-stats', '--dev-no-optimizations'], check=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, @@ -571,7 +571,7 @@ def test_uri_github_repo(): ## --branch BRANCH # Optionally specify the git branch when targeting a git repository def test_uri_local_branch(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', branch='test-branch', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', branch='test-branch', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) out = io.StringIO() err = io.StringIO() @@ -591,7 +591,7 @@ def test_uri_github_repo_branch(): ps = subprocess.run( ['python3', '../runner.py', '--name', run_name, '--uri', uri , '--branch', branch , '--filename', 'basic_stress.yml', - '--config-override', f"{os.path.dirname(os.path.realpath(__file__))}/test-config.yml", '--skip-system-checks', '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-optimizations'], + '--config-override', f"{os.path.dirname(os.path.realpath(__file__))}/test-config.yml", '--skip-system-checks', '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-phase-stats', '--dev-no-optimizations'], check=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, @@ -607,7 +607,7 @@ def test_uri_github_repo_branch(): ## Is the expected_exception OK or should it have a more graceful error? ## ATM this is just the default console error of a failed git command def test_uri_github_repo_branch_missing(): - runner = Runner(uri='https://github.com/green-coding-solutions/pytest-dummy-repo', uri_type='URL', branch='missing-branch', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=True) + runner = Runner(uri='https://github.com/green-coding-solutions/pytest-dummy-repo', uri_type='URL', branch='missing-branch', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=True) with pytest.raises(subprocess.CalledProcessError) as e: runner.run() expected_exception = f"Command '['git', 'clone', '--depth', '1', '-b', 'missing-branch', '--single-branch', '--recurse-submodules', '--shallow-submodules', 'https://github.com/green-coding-solutions/pytest-dummy-repo', '{os.path.realpath('/tmp/green-metrics-tool/repo')}']' returned non-zero exit status 128." @@ -622,7 +622,7 @@ def test_name_is_in_db(): ['python3', '../runner.py', '--name', run_name, '--uri', GMT_DIR , '--filename', 'tests/data/stress-application/usage_scenario.yml', '--config-override', f"{os.path.dirname(os.path.realpath(__file__))}/test-config.yml", - '--skip-system-checks', '--dev-no-metrics', '--dev-no-optimizations', '--dev-no-sleeps', '--dev-cache-build'], + '--skip-system-checks', '--dev-no-metrics', '--dev-no-phase-stats', '--dev-no-optimizations', '--dev-no-sleeps', '--dev-cache-build'], check=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, @@ -638,7 +638,7 @@ def test_different_filename(): run_name = 'test_' + utils.randomword(12) ps = subprocess.run( ['python3', '../runner.py', '--name', run_name, '--uri', GMT_DIR , '--filename', 'tests/data/usage_scenarios/basic_stress.yml', '--config-override', f"{os.path.dirname(os.path.realpath(__file__))}/test-config.yml", - '--skip-system-checks', '--dev-no-metrics', '--dev-no-optimizations', '--dev-no-sleeps', '--dev-cache-build'], + '--skip-system-checks', '--dev-no-metrics', '--dev-no-phase-stats', '--dev-no-optimizations', '--dev-no-sleeps', '--dev-cache-build'], check=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, @@ -654,7 +654,7 @@ def test_different_filename(): # if that filename is missing... def test_different_filename_missing(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='I_do_not_exist.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='I_do_not_exist.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) with pytest.raises(FileNotFoundError) as e: runner.run() @@ -671,7 +671,7 @@ def test_different_filename_missing(): # Check that default is to leave the files def test_no_file_cleanup(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/basic_stress.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) runner.run() assert os.path.exists('/tmp/green-metrics-tool'), \ @@ -682,7 +682,7 @@ def test_no_file_cleanup(): def test_file_cleanup(): subprocess.run( ['python3', '../runner.py', '--uri', GMT_DIR , '--filename', 'tests/data/usage_scenarios/basic_stress.yml', - '--file-cleanup', '--config-override', f"{os.path.dirname(os.path.realpath(__file__))}/test-config.yml", '--skip-system-checks', '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-optimizations'], + '--file-cleanup', '--config-override', f"{os.path.dirname(os.path.realpath(__file__))}/test-config.yml", '--skip-system-checks', '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-phase-stats', '--dev-no-optimizations'], check=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, @@ -695,7 +695,7 @@ def test_file_cleanup(): def test_skip_and_allow_unsafe_both_true(): with pytest.raises(RuntimeError) as e: - Runner(uri=GMT_DIR, uri_type='folder', filename='basic_stress.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, skip_unsafe=True, allow_unsafe=True) + Runner(uri=GMT_DIR, uri_type='folder', filename='basic_stress.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True, skip_unsafe=True, allow_unsafe=True) expected_exception = 'Cannot specify both --skip-unsafe and --allow-unsafe' assert str(e.value) == expected_exception, Tests.assertion_info('', str(e.value)) @@ -705,7 +705,7 @@ def test_debug(monkeypatch): ['python3', '../runner.py', '--uri', GMT_DIR , '--filename', 'tests/data/usage_scenarios/basic_stress.yml', '--debug', '--config-override', f"{os.path.dirname(os.path.realpath(__file__))}/test-config.yml", '--skip-system-checks', - '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-optimizations'], + '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-phase-stats', '--dev-no-optimizations'], check=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, @@ -720,7 +720,7 @@ def test_debug(monkeypatch): # can check for this note in the DB and the notes are about 2s apart def test_read_detached_process_no_exit(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/stress_detached_no_exit.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/stress_detached_no_exit.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() with redirect_stdout(out), redirect_stderr(err): @@ -731,7 +731,7 @@ def test_read_detached_process_no_exit(): Tests.assertion_info('NOT successful run completed', out.getvalue()) def test_read_detached_process_after_exit(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/stress_detached_exit.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/stress_detached_exit.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() with redirect_stdout(out), redirect_stderr(err): @@ -740,7 +740,7 @@ def test_read_detached_process_after_exit(): Tests.assertion_info('successful run completed', out.getvalue()) def test_read_detached_process_failure(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/stress_detached_failure.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/stress_detached_failure.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -750,7 +750,7 @@ def test_read_detached_process_failure(): Tests.assertion_info("Process '['docker', 'exec', 'test-container', 'g4jiorejf']' had bad returncode: 126. Stderr: ; Detached process: True. Please also check the stdout in the logs and / or enable stdout logging to debug further.", str(e.value)) def test_invalid_container_name(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_container_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_container_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -762,7 +762,7 @@ def test_invalid_container_name(): Tests.assertion_info(expected_exception, str(e.value)) def test_invalid_container_name_2(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_container_name_2.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_container_name_2.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -774,7 +774,7 @@ def test_invalid_container_name_2(): Tests.assertion_info(expected_exception, str(e.value)) def test_duplicate_container_name(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/duplicate_container_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/duplicate_container_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -784,7 +784,7 @@ def test_duplicate_container_name(): Tests.assertion_info("Container name 'number-1' was already used. Please choose unique container names.", str(e.value)) def test_empty_container_name(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/empty_container_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/empty_container_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -795,7 +795,7 @@ def test_empty_container_name(): def test_none_container_name(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/none_container_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/none_container_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -805,7 +805,7 @@ def test_none_container_name(): Tests.assertion_info("Key 'container_name' error:\nNone should be instance of 'str'", str(e.value)) def test_empty_phase_name(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/empty_phase_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/empty_phase_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -816,7 +816,7 @@ def test_empty_phase_name(): def test_invalid_phase_name(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_phase_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_phase_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -826,7 +826,7 @@ def test_invalid_phase_name(): Tests.assertion_info("Key 'name' error:\n'This phase is / not ok!' does not match '^[\\\\.\\\\s0-9a-zA-Z_\\\\(\\\\)-]+$'", str(e.value)) def test_invalid_phase_name_runtime(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_phase_name_runtime.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_phase_name_runtime.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -837,7 +837,7 @@ def test_invalid_phase_name_runtime(): Tests.assertion_info("Key 'name' error:\n'[RUNTIME]' does not match '^[\\\\.\\\\s0-9a-zA-Z_\\\\(\\\\)-]+$'", str(e.value)) def test_duplicate_phase_name(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/duplicate_phase_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/duplicate_phase_name.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -848,7 +848,7 @@ def test_duplicate_phase_name(): def test_failed_pull_does_not_trigger_cli(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_image.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_image.yml', skip_system_checks=True, dev_cache_build=True, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -859,7 +859,7 @@ def test_failed_pull_does_not_trigger_cli(): Tests.assertion_info('Docker pull failed. Is your image name correct and are you connected to the internet: 1j98t3gh4hih8723ztgifuwheksh87t34gt', str(e.value)) def test_failed_pull_does_not_trigger_cli_with_build_on(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_image.yml', skip_system_checks=True, dev_cache_build=False, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/invalid_image.yml', skip_system_checks=True, dev_cache_build=False, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -870,7 +870,7 @@ def test_failed_pull_does_not_trigger_cli_with_build_on(): Tests.assertion_info('Docker pull failed. Is your image name correct and are you connected to the internet: 1j98t3gh4hih8723ztgifuwheksh87t34gt', str(e.value)) def test_non_git_root_supplied(): - runner = Runner(uri=f"{GMT_DIR}/tests/data/usage_scenarios/", uri_type='folder', filename='invalid_image.yml', skip_system_checks=True, dev_cache_build=False, dev_no_sleeps=True, dev_no_metrics=True) + runner = Runner(uri=f"{GMT_DIR}/tests/data/usage_scenarios/", uri_type='folder', filename='invalid_image.yml', skip_system_checks=True, dev_cache_build=False, dev_no_sleeps=True, dev_no_metrics=True, dev_no_phase_stats=True) out = io.StringIO() err = io.StringIO() @@ -889,7 +889,7 @@ def wip_test_verbose_provider_boot(): ['python3', '../runner.py', '--name', run_name, '--uri', GMT_DIR , '--verbose-provider-boot', '--config-override', f"{os.path.dirname(os.path.realpath(__file__))}/test-config.yml", '--filename', 'tests/data/stress-application/usage_scenario.yml', - '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-optimizations'], + '--dev-no-sleeps', '--dev-cache-build', '--dev-no-metrics', '--dev-no-phase-stats', '--dev-no-optimizations'], check=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, diff --git a/tests/test_volume_loading.py b/tests/test_volume_loading.py index f1d10b188..e0152ee18 100644 --- a/tests/test_volume_loading.py +++ b/tests/test_volume_loading.py @@ -11,7 +11,7 @@ from runner import Runner def test_volume_load_no_escape(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_etc_hosts.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_etc_hosts.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False) try: with pytest.raises(ValueError) as e: @@ -25,7 +25,7 @@ def test_volume_load_no_escape(): assert container_running is False, Tests.assertion_info('test-container stopped', 'test-container was still running!') def test_volume_load_escape_ok_with_allow_unsafe(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_etc_hosts.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False, allow_unsafe=True) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_etc_hosts.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False, allow_unsafe=True) with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') @@ -44,7 +44,7 @@ def test_volume_load_escape_ok_with_allow_unsafe(): assert "File mounted" in out, Tests.assertion_info('File mounted', f"out: {out} | err: {err}") def test_load_files_from_within_gmt(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_within_proj.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_within_proj.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False) with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') @@ -68,7 +68,7 @@ def test_symlinks_should_fail(): os.symlink('/etc/hosts', symlink_file) - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_symlinks_negative.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_symlinks_negative.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False) try: with pytest.raises(ValueError) as e: @@ -83,7 +83,7 @@ def test_symlinks_should_fail(): assert container_running is False, Tests.assertion_info('test-container stopped', 'test-container was still running!') def test_non_bind_mounts_should_fail(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_non_bind_mounts.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_non_bind_mounts.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False) try: with pytest.raises(RuntimeError) as e: @@ -97,7 +97,7 @@ def test_non_bind_mounts_should_fail(): assert container_running is False, Tests.assertion_info('test-container stopped', 'test-container was still running!') def test_load_volume_references(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_references.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename='tests/data/usage_scenarios/volume_load_references.yml', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False) with Tests.RunUntilManager(runner) as context: context.run_until('setup_services') @@ -116,7 +116,7 @@ def test_load_volume_references(): assert "File mounted" in out, Tests.assertion_info('File mounted', f"out: {out} | err: {err}") def test_volume_loading_subdirectories_root(): - runner = Runner(uri=GMT_DIR, filename='tests/data/usage_scenarios/subdir_volume_loading/usage_scenario.yml', uri_type='folder', skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False) + runner = Runner(uri=GMT_DIR, filename='tests/data/usage_scenarios/subdir_volume_loading/usage_scenario.yml', uri_type='folder', skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False) out = io.StringIO() err = io.StringIO() @@ -142,7 +142,7 @@ def test_volume_loading_subdirectories_root(): assert expect_mounted_testfile_3 in run_stdout, Tests.assertion_info(expect_mounted_testfile_3, f"expected output not in {run_stdout}") def test_volume_loading_subdirectories_subdir(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename="tests/data/usage_scenarios/subdir_volume_loading/subdir/usage_scenario_subdir.yml", skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename="tests/data/usage_scenarios/subdir_volume_loading/subdir/usage_scenario_subdir.yml", skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False) out = io.StringIO() err = io.StringIO() @@ -159,7 +159,7 @@ def test_volume_loading_subdirectories_subdir(): assert expect_mounted_testfile_3 in run_stdout, Tests.assertion_info(expect_mounted_testfile_3, f"expected output not in {run_stdout}") def test_volume_loading_subdirectories_subdir2(): - runner = Runner(uri=GMT_DIR, uri_type='folder', filename="tests/data/usage_scenarios/subdir_volume_loading/subdir/subdir2/usage_scenario_subdir2.yml", skip_system_checks=True, dev_no_metrics=True, dev_no_sleeps=True, dev_cache_build=False) + runner = Runner(uri=GMT_DIR, uri_type='folder', filename="tests/data/usage_scenarios/subdir_volume_loading/subdir/subdir2/usage_scenario_subdir2.yml", skip_system_checks=True, dev_no_metrics=True, dev_no_phase_stats=True, dev_no_sleeps=True, dev_cache_build=False) out = io.StringIO() err = io.StringIO() diff --git a/tools/calibrate.py b/tools/calibrate.py index f60d96ded..6a800e1a1 100755 --- a/tools/calibrate.py +++ b/tools/calibrate.py @@ -78,7 +78,7 @@ def check_temperature_increase(total_seconds, desc, temp_mean, temp_std, temp_pr -def load_metric_providers(mp, pt_providers, provider_interval_override=None, rootless=False): +def load_metric_providers(mp, pt_providers, provider_interval_override=None): global metric_providers metric_providers = [] # reset @@ -88,9 +88,6 @@ def load_metric_providers(mp, pt_providers, provider_interval_override=None, roo module_path = f"metric_providers.{module_path}" conf = mp[metric_provider] or {} - if rootless and '.cgroup.' in module_path: - conf['rootless'] = True - logging.info(f"Importing {class_name} from {module_path}") if provider_interval_override: @@ -254,10 +251,7 @@ def check_configured_provider_energy_overhead(mp, energy_provider_key, idle_time client = docker.from_env() - is_rootless = any('rootless' in option for option in client.info()['SecurityOptions']) - logging.debug(f"Rootless mode is {is_rootless}") - - load_metric_providers(mp, mp, None, rootless=is_rootless) + load_metric_providers(mp, mp, None) # We need to start at least one container that just idles so we can also run the container providers diff --git a/tools/dc_converter.py b/tools/dc_converter.py index 5a4d9b2e7..f5d2e9153 100644 --- a/tools/dc_converter.py +++ b/tools/dc_converter.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr -import sys import argparse from io import StringIO diff --git a/tools/import_data.py b/tools/import_data.py index e2654190f..eb8bcadfc 100644 --- a/tools/import_data.py +++ b/tools/import_data.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr from lib.db import DB diff --git a/tools/import_measurements.py b/tools/import_measurements.py index 5e35568be..d45384173 100644 --- a/tools/import_measurements.py +++ b/tools/import_measurements.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr import argparse from io import StringIO diff --git a/tools/optimization.py b/tools/optimization.py index 797de6202..fa3d26515 100644 --- a/tools/optimization.py +++ b/tools/optimization.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr import optimization_providers.base from lib.terminal_colors import TerminalColors diff --git a/tools/phase_stats.py b/tools/phase_stats.py index 8f9bfc8eb..30b135adf 100644 --- a/tools/phase_stats.py +++ b/tools/phase_stats.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr +from lib.db import DB from lib.phase_stats import build_and_store_phase_stats if __name__ == '__main__': @@ -14,4 +16,14 @@ args = parser.parse_args() # script will exit if type is not present - build_and_store_phase_stats(args.run_id) + query = ''' + SELECT id, measurement_config + FROM runs + WHERE + end_measurement IS NOT NULL AND phases IS NOT NULL + AND id = %s + + ''' + data = DB().fetch_one(query, params=(args.run_id, ), fetch_mode='dict') + + build_and_store_phase_stats(args.run_id, data['measurement_config']['sci']) diff --git a/tools/prune_db.py b/tools/prune_db.py index bed803fcf..75298338a 100644 --- a/tools/prune_db.py +++ b/tools/prune_db.py @@ -1,10 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr - import sys +import faulthandler +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr from lib.db import DB diff --git a/tools/rebuild_phase_stats.py b/tools/rebuild_phase_stats.py index 718f04d68..1024ef9f9 100644 --- a/tools/rebuild_phase_stats.py +++ b/tools/rebuild_phase_stats.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr - import sys - -from tools.phase_stats import build_and_store_phase_stats +import faulthandler +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr from lib.db import DB +from tools.phase_stats import build_and_store_phase_stats if __name__ == '__main__': print('This will remove ALL phase_stats and completely rebuild them. Not data will get lost, but it will take some time. Continue? (y/N)') diff --git a/tools/update_commit_data.py b/tools/update_commit_data.py index 8a6bbe053..9e7791440 100644 --- a/tools/update_commit_data.py +++ b/tools/update_commit_data.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import sys import faulthandler -faulthandler.enable() # will catch segfaults and write to stderr +faulthandler.enable(file=sys.__stderr__) # will catch segfaults and write to stderr # This script will update the commit_timestamp field in the database # for old runs where only the commit_hash field was populated