diff --git a/.gitignore b/.gitignore index ce120c1ed6ec1a3a1098f5dbcef557eaaa902c27..3c65cc2b38f962b3ac01ae5c6a3b0720115e6e6d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,6 @@ Log/* Log_autres/* -# But keep the directories themselves -# !Log/.gitkeep -# !Log_autres/.gitkeep + diff --git a/Flower_v1/__pycache__/test_strategie.cpython-310.pyc b/Flower_v1/__pycache__/test_strategie.cpython-310.pyc deleted file mode 100644 index beccf2ebf99c1a14a60da2f6d1302ff0e7495d57..0000000000000000000000000000000000000000 Binary files a/Flower_v1/__pycache__/test_strategie.cpython-310.pyc and /dev/null differ diff --git a/Flower_v1/__pycache__/test_strategie.cpython-39.pyc b/Flower_v1/__pycache__/test_strategie.cpython-39.pyc deleted file mode 100644 index 50afdcfc7bbb37e20ac097232e07341d99c0290e..0000000000000000000000000000000000000000 Binary files a/Flower_v1/__pycache__/test_strategie.cpython-39.pyc and /dev/null differ diff --git a/Flower_v1/__pycache__/test_strategie_custom.cpython-310.pyc b/Flower_v1/__pycache__/test_strategie_custom.cpython-310.pyc deleted file mode 100644 index 2b88d1be4df90085f02dbcc7f01cc2b0366d1f44..0000000000000000000000000000000000000000 Binary files a/Flower_v1/__pycache__/test_strategie_custom.cpython-310.pyc and /dev/null differ diff --git a/Flower_v1/__pycache__/test_strategie_custom.cpython-39.pyc b/Flower_v1/__pycache__/test_strategie_custom.cpython-39.pyc deleted file mode 100644 index 5a212d6495b8fd28b2a04999b3a28fd87aaae667..0000000000000000000000000000000000000000 Binary files a/Flower_v1/__pycache__/test_strategie_custom.cpython-39.pyc and /dev/null differ diff --git a/Log/.gitkeep b/Log/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/Log_autres/.gitkeep b/Log_autres/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/README.md b/README.md index 29575dc97940162434944341aaebdcb75516fe19..b86ac2163ead005f09b1971d9c197d68b9debd0e 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,13 @@ This project provides tools to measure the energy consumption of Flower-based fe - [Getting Started](#getting-started) - [Installation](#installation) - [Usage](#usage) - - [Step 1. Configure](#step-1-configure) - - [Step 2. Reserve the Hosts in G5K](#step-2-reserve-the-hosts-in-g5k) +- [Quickstart](#quickstart) + - [Step 1. Reserve the Hosts in G5K](#step-1-reserve-the-hosts-in-g5k) + - [Step 2. Configure](#step-2-configure) - [Step 3. Collect IP](#step-3-collect-ip) - - [Step 4. Run the Campaign or Instance](#step-4-run-the-campaign-or-instance) + - [Step 4. Run the Campaign or Instance](#step-4-run-the-campaign-or-single-instance) - [Step 5. Output](#step-5-output) - [Step 6. Clean Up](#step-6-clean-up) -- [Quickstart](#quickstart) -- [Output Structure](#output-structure) - [License](#license) ## Getting Started @@ -36,7 +35,7 @@ This framework requires: ```bash pip install -r requirements.txt ``` -*Note:* `requirements.txt` includes TensorFlow, scikit-learn and numpy for running the provided Flower example. +*Note:* `requirements.txt` includes `TensorFlow`, `scikit-learn` and `numpy` for running the provided Flower example. These packages requirement will be removed in official version. Navigate to `Run` directory: @@ -46,144 +45,92 @@ cd Run ## Usage -- FL scripts can be updated in `Flower_v1`. -- Configure each instance of experiment in `Run\config_instance*.json`. -- Follow these steps to config and run your experiment (or jump to [Quickstart](#quickstart) to run an example). - -### Step 1. Reserve the Hosts in G5K - -Reserve the required number of hosts (*See the [document of G5K](https://www.grid5000.fr/w/Getting_Started#Reserving_resources_with_OAR:_the_basics) for more details*) -```bash -oarsub -I -l host=[number_of_hosts],walltime=[duration] -``` - -### Step 2. Configure -Edit the JSON configuration file (`config_instance*.json`) to specify experiment details. You can create multiple `config_instance*.json` files with the * is numbering of instance (the numbers must be consecutive positive integers starting from 1.) - -```bash -vim config_instance1.json -``` - -Example structure: - -```json -{ - "instance": "fedAvg_cifar10", - "output_dir": "/home/mdo/Huong_DL/Log", - "dvfs": { - "dummy": false, - "baseline": false, - "frequencies": [2000000,2200000] - }, - "server": { - "command": "python3", - "args": [ - "Flower_v1/server.py", - "-r 50", - "-s fedAvg" - ], - "ip": "172.16.66.18", - "port": 8080 - }, - "clients": [ - { - "name": "client1", - "command": "python3", - "args": [ - "Flower_v1/client_1.py", - "cifar10", - "1", - "3" - ], - "ip": "172.16.66.2" - } - { - "name": "client2", - "command": "python3", - "args": [ - "Flower_v1/client_1.py", - "cifar10", - "1", - "3" - ], - "ip": "172.16.66.3" +- FL scripts can be updated, example in `Flower_v1`. +- Configure instances of experiment in a json format, structure is shown below. + + - **instances** includes **"1"**, **"2"** ,... are identifies of each instance. + - **instance**: name of instance. + - **output_dir**: location stores the output files (experiment log and energy monitoring output). + - **dvfs**: choose only one in 3 settings, detects all available CPU frequencies and go through all of them. + - `dummy`: for testing in min and max CPU freq (`false` or `true`). + - `baseline`: for testing in max CPU freq (`false` or `true`). + - `frequencies`: Limits to the provided list of frequencies (`null` or `int list []`). + + **Remark:** check the available frequencies before using oftion `frequencies`. + + - Set the permissions and disable Turbo Boost first: + ```bash + bash "$(python3 -c "import expetator, os; print(os.path.join(os.path.dirname(expetator.__file__), 'leverages', 'dvfs_pct.sh'))")" init + ``` + - Run this command to get available frequencies: + ```bash + python3 get_frequencies.py + ``` + - Update extraced frequencies value to configure files. + - Structure of json config: + ```json + { + "instances": { + "1": { + "instance": "", + "output_dir": "", + "dvfs": { + "dummy": true, + "baseline": false, + "frequencies": null + }, + "server": { + "command": "python3", + "args": [ + ], + "ip": "", + "port": 8080 + }, + "clients": [ + { + "name": "client1", + "command": "python3", + "args": [ + ], + "ip": "" + }, + {...}, + {...} + ] + }, + "2": { + "instance": "", + ... + } } - ] -} -``` - -- **instance**: The name of your experiment. -- **output_dir**: Where to store the log files (experiment log and energy monitoring log). -- **dvfs**: choose only one in 3 settings, detects all available frequencies and go through all of them. - - `dummy`: false or true (Only uses min and max frequency) - - `baseline`: false or true (Only uses max freq) - - `frequencies`: null or int list (Limits to the provided list of frequencies) - -**Remark:** check the available frequencies before using oftion `frequencies`. - -- Set the permissions and disable Turbo Boost first: -```bash -bash "$(python3 -c "import expetator, os; print(os.path.join(os.path.dirname(expetator.__file__), 'leverages', 'dvfs_pct.sh'))")" init -``` -- Run this command to get available frequencies: -```bash -python3 get_frequencies.py -``` -- Update extraced frequencies value to configure files. - -### Step 3. Collect IP - -Run the following command to generate a node list: -```bash -uniq $OAR_NODEFILE > nodelist -``` + } + ``` -Automatically populate missing IP addresses in the JSON file: -```bash -python3 collect_ip.py -``` -### Step 4. Run the Campaign or Single Instance +- 2 options of experiment: run single instance or all instances (a campaign). -Run campain: -```bash -python3 run_measure.py -x [experiment_name] -r [repetitions] -``` -Run single instance: -```bash -python3 measure.py -c [config_file] -x [experiment_name] -r [repetitions] -``` -- **[experiment_name]**: The name you use to identify your experiment. -- **[repetitions]**: Number of repetitions for the experiment. - -### Step 5. Output - -The logs and energy monitoring data will be saved in the directory specified in the JSON configuration. - -### Step 6. Clean Up - -After the experiment: - -Exit the host: + <u>Run single instance</u>: ```bash - exit + python3 measure_instance.py -c [config_file] -i [instance] -x [experiment_name] -r [repetitions] ``` -Check the job ID: - ```bash - oarstat -u - ``` + - **[config_file]**: The instances configuration file. + - **[instance]** : Identify number of single instance. + - **[experiment_name]**: The name you use to identify your experiment. + - **[repetitions]**: Number of repetitions for the experiment. -Kill the job: + <u>Run campaign</u>: ```bash - oardel <job_id> + python3 measure_campaign.py -x [experiment_name] -c [config_file] -r [repetitions] ``` + For campaign running, all instances which were defined in **[config_file]** will be used. ## Quickstart -Follow these steps to run an example: +### Step 1. Reserve the Hosts in G5K -1. Reserve 4 hosts (1 server + 3 clients) for 2 hours: +Reserve the required number of hosts (*See the [document of G5K](https://www.grid5000.fr/w/Getting_Started#Reserving_resources_with_OAR:_the_basics) for more details*) +<u>For example</u>: Reserve 4 hosts (1 server + 3 clients) for 2 hours: ```bash oarsub -I -l host=4,walltime=2 ``` @@ -192,48 +139,72 @@ Make sure your are in`eflwr/Run/`: cd Run ``` -2. Configure +### Step 2. Configure +Create the JSON configuration file (e.g. `config_instances.json`) to specify experiment details includes one or more instances. -`config_instance1.json` and `config_instance2.json` provide two examples of instance configuration. All fields are configured but "output_dir" and "args" must be updated with your directories setting. -- `config_instance1.json`: fedAvg, cifar10, dvfs with min and max freq, 1 round. -- `config_instance1.json`: fedAvg2Clients, cifar10, dvfs with min and max freq, 1 round. +```bash +vim config_instances.json +``` +<u>For example</u>: `config_instances.json` provides two examples of instance configuration. All fields are configured except "`output_dir`" and "`args`" must be updated with your directories setting. +- instance "`1`": fedAvg, cifar10, dvfs with min and max CPU freq, 1 round. +- instance "`2`": fedAvg2Clients, cifar10, dvfs with min and max CPU freq, 1 round. -3. Collect IP +### Step 3. Collect IP +Run the following command to collect/generate a node list: ```bash uniq $OAR_NODEFILE > nodelist -python3 collect_ip.py ``` -4. Run the Single Instance or Campaign +Automatically populate missing IP addresses in the JSON file: +```bash +python3 collect_ip.py -n nodelist -c config_instances.json +``` -Run single instance1 with `config_instance1.json`, 2 repetitions: +### Step 4. Run the Campaign or Single Instance + +Run single instance with instance `1`, and 2 repetitions: ```bash -python3 measure.py -c config_instance1.json -x SingleTest -r 2 +python3 measure_instance.py -c config_instances.json -i 1 -x SingleTest -r 2 ``` -Run a campaign with all config_instance*.json in `/Run`, 2 repetitions: + +Run a campaign with all instances (`1` and `2`), and 2 repetitions: ```bash -python3 run_measure.py -x CampaignTest -r 2 +python3 measure_instance.py -x CampaignTest -r 2 ``` -## Output Structure +### Step 5. Output + +The logs and energy monitoring data will be saved in the directory specified in the JSON configuration. -Example output directory: +Output dir structure: ```plaintext /Flower_<x> ├── Flower_instance_<instance_name> │ ├── Expetator | | ├── config_instance*.json -│ ├── Expetator_<host_info> -│ ├── Expetator_<host_info>_power -│ │ ├── <client_logs> +│ ├── Expetator_<host_info>_<timestamp>_mojitos: mojitos outputs +│ ├── Expetator_<host_info>_<timestamp>_power: wattmetter outputs +│ ├── Expetator_<host_info>_<timestamp>: measurement log +│ ├── Flwr_<timestamp>: Flower log +│ │ ├── Client_<ip> +│ │ ├── Server_<ip> │ ├── Flwr_<timestamp> │ │ ├── Client_<ip> │ │ ├── Server_<ip> +│── Flower_instance_<instance_name> ``` +### Step 6. Clean Up + +After the experiment, exit the host and kill job if needed: + ```bash + exit + oardel <job_id> + ``` + ## License This project is licensed under [GPLv3]. \ No newline at end of file diff --git a/Run/collect_ip.py b/Run/collect_ip.py index 0e28761569af7f3a42f5f5634fd90839fe5c93f4..75206102c7385bfbdb79c30b37cc255db416449a 100644 --- a/Run/collect_ip.py +++ b/Run/collect_ip.py @@ -1,10 +1,23 @@ +# python3 collect_ip.py -n nodelist -c config_instances.json + import subprocess import json -import os -import glob +import argparse + +# Path to the file containing hostnames +parser = argparse.ArgumentParser(description="Collect IP addresses for all hostnames in the nodelist file.") +parser.add_argument( + "-n", "--node_file", type=str, required=True, + help="Path to the file containing hostnames" +) +parser.add_argument( + "-c", "--configure", type=str, required=True, + help="Path to the config file" +) -# File paths -node_file = "nodelist" # Replace with the actual path to your node file +args = parser.parse_args() +node_file = args.node_file +config_file = args.configure # Read the hostnames from the node file with open(node_file, "r") as file: @@ -29,31 +42,36 @@ def get_ip_from_host(hostname): # Get IP addresses for all hostnames ip_addresses = [get_ip_from_host(hostname) for hostname in hostnames] -# Find all config_instance<number>.json files in the current directory -config_files = glob.glob("config_instance*.json") +# Read the existing config JSON +try: + with open(config_file, "r") as file: + config_data = json.load(file) +except FileNotFoundError: + print(f"Error: Config file {config_file} not found!") + exit(1) +except json.JSONDecodeError: + print(f"Error: Config file {config_file} contains invalid JSON!") + exit(1) -# Loop through each config file and update it -for config_file in config_files: +# Loop through each instance and update server/clients with IPs +for instance_number, instance_config in config_data["instances"].items(): try: - print(f"Processing {config_file}...") - - # Read the existing config JSON - with open(config_file, "r") as file: - config = json.load(file) + print(f"Updating instance {instance_number}...") - # Assign IPs to roles in the JSON (server first, then clients) + # Assign IPs (server first, then clients) if ip_addresses: - config["server"]["ip"] = ip_addresses[0] if len(ip_addresses) > 0 else "" - for i, client in enumerate(config["clients"]): - client["ip"] = ip_addresses[i + 1] if i + 1 < len(ip_addresses) else "" + instance_config["server"]["ip"] = ip_addresses[0] if len(ip_addresses) > 0 else "" - # Write updated JSON back to the file - with open(config_file, "w") as file: - json.dump(config, file, indent=4) + for i, client in enumerate(instance_config.get("clients", [])): + client["ip"] = ip_addresses[i + 1] if i + 1 < len(ip_addresses) else "" - print(f"Updated {config_file} successfully.") + print(f"Updated instance {instance_number} successfully.") except Exception as e: - print(f"Error processing {config_file}: {e}") + print(f"Error updating instance {instance_number}: {e}") + +# Write updated JSON back to the config file +with open(config_file, "w", encoding="utf-8") as file: + json.dump(config_data, file, indent=4, sort_keys=True, ensure_ascii=False, allow_nan=False) -print("All config files processed.") +print("All instances updated in config.json.") diff --git a/Run/config_instance1.json b/Run/config_instance1.json deleted file mode 100644 index a26dbc8d760905ee6a7de5ac1bc2b6230d7ef638..0000000000000000000000000000000000000000 --- a/Run/config_instance1.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "instance": "fedAvg_cifar10", - "output_dir": "/home/mdo/Framework/eflwr/Log", - "dvfs": { - "dummy": true, - "baseline": false, - "frequencies": null - }, - "server": { - "command": "python3", - "args": [ - "/home/mdo/Framework/eflwr/Flower_v1/server_1.py", - "-r 1", - "-s fedAvg" - ], - "additional_env_var": [ - "" - ], - "ip": "172.16.66.76", - "port": 8080 - }, - "clients": [ - { - "name": "client1", - "command": "python3", - "args": [ - "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", - "cifar10", - "1", - "3" - ], - "additional_env_var": [ - "" - ], - "ip": "172.16.66.77" - }, - { - "name": "client2", - "command": "python3", - "args": [ - "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", - "cifar10", - "2", - "3" - ], - "additional_env_var": [ - "" - ], - "ip": "172.16.66.78" - }, - { - "name": "client3", - "command": "python3", - "args": [ - "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", - "cifar10", - "3", - "3" - ], - "additional_env_var": [ - "" - ], - "ip": "172.16.66.79" - } - ] -} \ No newline at end of file diff --git a/Run/config_instance2.json b/Run/config_instance2.json deleted file mode 100644 index ce0122cddbec50bf9194619d6a2e5854904b33b8..0000000000000000000000000000000000000000 --- a/Run/config_instance2.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "instance": "fedAvg2Clients_cifar10", - "output_dir": "/home/mdo/Framework/eflwr/Log", - "dvfs": { - "dummy": true, - "baseline": false, - "frequencies": null - }, - "server": { - "command": "python3", - "args": [ - "/home/mdo/Framework/eflwr/Flower_v1/server_1.py", - "-r 1", - "-s fedAvg2Clients" - ], - "additional_env_var": [ - "" - ], - "ip": "172.16.66.76", - "port": 8080 - }, - "clients": [ - { - "name": "client1", - "command": "python3", - "args": [ - "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", - "cifar10", - "1", - "3" - ], - "additional_env_var": [ - "" - ], - "ip": "172.16.66.77" - }, - { - "name": "client2", - "command": "python3", - "args": [ - "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", - "cifar10", - "2", - "3" - ], - "additional_env_var": [ - "" - ], - "ip": "172.16.66.78" - }, - { - "name": "client3", - "command": "python3", - "args": [ - "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", - "cifar10", - "3", - "3" - ], - "additional_env_var": [ - "" - ], - "ip": "172.16.66.79" - } - ] -} \ No newline at end of file diff --git a/Run/config_instances.json b/Run/config_instances.json new file mode 100644 index 0000000000000000000000000000000000000000..f5cb789a7cb99be17d0562689f865937949b2707 --- /dev/null +++ b/Run/config_instances.json @@ -0,0 +1,112 @@ +{ + "instances": { + "1": { + "instance": "fedAvg_cifar10", + "output_dir": "/home/mdo/Framework/eflwr/Log", + "dvfs": { + "dummy": true, + "baseline": false, + "frequencies": null + }, + "server": { + "command": "python3", + "args": [ + "/home/mdo/Framework/eflwr/Flower_v1/server_1.py", + "-r 1", + "-s fedAvg" + ], + "ip": "172.16.66.76", + "port": 8080 + }, + "clients": [ + { + "name": "client1", + "command": "python3", + "args": [ + "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", + "cifar10", + "1", + "3" + ], + "ip": "172.16.66.77" + }, + { + "name": "client2", + "command": "python3", + "args": [ + "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", + "cifar10", + "2", + "3" + ], + "ip": "172.16.66.78" + }, + { + "name": "client3", + "command": "python3", + "args": [ + "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", + "cifar10", + "3", + "3" + ], + "ip": "172.16.66.79" + } + ] + }, + "2": { + "instance": "fedAvg2Clients_cifar10", + "output_dir": "/home/mdo/Framework/eflwr/Log", + "dvfs": { + "dummy": true, + "baseline": false, + "frequencies": null + }, + "server": { + "command": "python3", + "args": [ + "/home/mdo/Framework/eflwr/Flower_v1/server_1.py", + "-r 1", + "-s fedAvg2Clients" + ], + "ip": "172.16.66.76", + "port": 8080 + }, + "clients": [ + { + "name": "client1", + "command": "python3", + "args": [ + "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", + "cifar10", + "1", + "3" + ], + "ip": "172.16.66.77" + }, + { + "name": "client2", + "command": "python3", + "args": [ + "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", + "cifar10", + "2", + "3" + ], + "ip": "172.16.66.78" + }, + { + "name": "client3", + "command": "python3", + "args": [ + "/home/mdo/Framework/eflwr/Flower_v1/client_1.py", + "cifar10", + "3", + "3" + ], + "ip": "172.16.66.79" + } + ] + } + } +} diff --git a/Run/custom_gpuclock.py b/Run/custom_gpuclock.py new file mode 100644 index 0000000000000000000000000000000000000000..952efb259a0c52d402e5cd45ad2ff0c8a2b1982b --- /dev/null +++ b/Run/custom_gpuclock.py @@ -0,0 +1,85 @@ +from expetator.leverages.gpuclock import GpuClock + +class CustomGpuClock(GpuClock): + """ Custom GPU Clock Leverage using clocks.gr instead of clocks.applications.gr """ + + def __init__(self, dummy=False, baseline=False, steps=2, zoomfrom=0, zoomto=0): + super().__init__() + self.dummy = dummy + self.baseline = baseline + self.executor = None + self.available_frequencies = [] + self.available_frequencies_mem = [] + self.clock_mem_max = None + self.clock_sm_min = None + self.nsteps = steps + + # Zoom enables fine granularity within a frequency window + if zoomto != 0 and zoomfrom != zoomto: + self.zoom = (zoomfrom, zoomto) + else: + self.zoom = None + + def build(self, executor): + """ Gather the available frequencies """ + self.executor = executor + q = "nvidia-smi -i 0 --query-supported-clocks=gr --format=csv,noheader,nounits | tr '\n' ' '" + clk_s = self.executor.local(q) + clk = sorted([int(f) for f in clk_s.strip().split() if f.isdigit()]) + + if not clk: + raise RuntimeError("Failed to retrieve supported GPU clock frequencies.") + + self.clock_sm_min = clk[0] + self.available_frequencies = [clk[(i * (len(clk) - 1)) // (self.nsteps - 1)] for i in range(self.nsteps)] + + q = "nvidia-smi -i 0 --query-supported-clocks=mem --format=csv,noheader,nounits | tr '\n' ' '" + clk_s = self.executor.local(q) + self.available_frequencies_mem = sorted([int(f) for f in clk_s.strip().split() if f.isdigit()]) + self.clock_mem_max = self.available_frequencies_mem[-1] + + if self.zoom: + clkz = [f for f in clk if self.zoom[0] <= f <= self.zoom[1]] + rest = [f for f in self.available_frequencies if f < self.zoom[0] or f > self.zoom[1]] + self.available_frequencies = sorted(clkz + rest) + + if self.dummy: + self.available_frequencies = [self.available_frequencies[0], self.available_frequencies[-1]] + + if self.baseline: + self.available_frequencies = [self.available_frequencies[-1]] + + def available_states(self): + """ Returns all available frequencies """ + return self.available_frequencies + + def start(self, freq): + """ Sets the GPU frequency """ + if freq in self.available_frequencies: + self.executor.local(f'nvidia-smi -i 0 -ac {self.clock_mem_max},{freq}', root=True) + + def stop(self, output_file=None): + """ Reset GPU to default frequency """ + self.executor.local('nvidia-smi -i 0 -rac', root=True) + + def get_state(self): + """ Returns the current min and max application frequencies using clocks.gr instead of clocks.applications.gr """ + cur_min = self.clock_sm_min + cur_max_str = self.executor.local('nvidia-smi -i 0 --query-gpu=clocks.gr --format=csv,noheader,nounits').strip() + + try: + cur_max = int(cur_max_str) + except ValueError: + cur_max = 0 # Fallback in case of an error + + return cur_min, cur_max, self.clock_mem_max + + def state_to_str(self): + """ Returns the current min and max frequencies as a string """ + cur_min, cur_max, mem_max = self.get_state() + return f'{cur_min} {cur_max} {mem_max}' + + def get_labels(self): + """ Returns labels for frequencies """ + return ('fmin', 'fmax', 'fmemax') + diff --git a/Run/measure.py b/Run/measure.py deleted file mode 100644 index 9ed5480c657d395134b8832f79fd7008458a9342..0000000000000000000000000000000000000000 --- a/Run/measure.py +++ /dev/null @@ -1,100 +0,0 @@ -#python3 measure.py -c config_instance1.json -x test -r repeat - -from pathlib import Path -import os -import argparse -import json -import time -import expetator.experiment as experiment -#from expetator.monitors import Mojitos, kwollect -from expetator.monitors import Mojitos -from expetator.leverages import Dvfs - -# Determine script directory -current_dir = Path(__file__).resolve().parent -parent_dir = current_dir.parent - -# Set up argument parser -parser = argparse.ArgumentParser(description="Run a benchmark experiment using a specified config file.") -parser.add_argument( - "-x", "--suffix", type=str, required=True, - help="Suffix for the log directory (e.g., experiment name or timestamp)" -) -parser.add_argument( - "-c", "--config", type=str, required=True, - help="Path to the config file (e.g., config_instance1.json or a glob pattern like config_instance*.json)" -) -parser.add_argument( - "-r", "--repeat", type=int, default=1, required=True, - help="Number of repeatation (e.g., 2, the exp will be repeated in 2 times)" -) - -# Parse arguments -args = parser.parse_args() - -# Dynamically set the path to the config.json file -config_path = os.path.join(current_dir, args.config) - -# Read the output directory from config.json -try: - with open(config_path, "r") as file: - config = json.load(file) -except FileNotFoundError: - print(f"Error: Config file {config_path} not found!") - exit(1) -except json.JSONDecodeError: - print(f"Error: Config file {config_path} contains invalid JSON!") - exit(1) - -# Base log directory and instance name from config.json -log_dir = config["output_dir"] -instance_name = config.get("instance", "default_instance") - -# Extract DVFS configuration from the config file -dvfs_config = config.get("dvfs", {}) -dvfs_dummy = dvfs_config.get("dummy", False) -dvfs_baseline = dvfs_config.get("baseline", False) -dvfs_frequencies = dvfs_config.get("frequencies", None) - -# Set the Flower log directory with the suffix and ensure it exists -flower_log_dir = os.path.join(log_dir, f"Flower_{args.suffix}", f"Flower_instance_{instance_name}", "Expetator") -os.makedirs(flower_log_dir, exist_ok=True) - -# Path to the script that will be executed -script_dir = os.path.join(current_dir, 'run_flwr.py') - -# SCP the config file to the destination - -scp_command = f"scp {config_path} {flower_log_dir}" -print(f"Executing SCP command: {scp_command}") -os.system(scp_command) - -class DemoBench: - def __init__(self, params=[args.suffix]): - self.names = {"flower"} - self.params = params - - def build(self, executor): - params = {"flower": self.params} - return params - - def run(self, bench, param, executor): - before = time.time() - # Run the Flower script with the provided suffix argument - executor.local(f"python3 {script_dir} -c {args.config} -x {args.suffix}") - return time.time() - before, "flower" - -if __name__ == "__main__": -# Ensure DVFS settings are retrieved from the config file - dvfs = Dvfs(dummy=dvfs_dummy, baseline=dvfs_baseline, frequencies=dvfs_frequencies) - experiment.run_experiment( - flower_log_dir, - [DemoBench()], - leverages=[dvfs], - monitors=[ - Mojitos(sensor_set={'user', 'rxp', 'dram0'}) - # kwollect.Power(metric=kwollect.get_g5k_target_metric()) - ], - times=args.repeat - ) - diff --git a/Run/measure_1.py b/Run/measure_1.py deleted file mode 100644 index 1863c81bf2065e88082de8b9dc62523d4c1fc376..0000000000000000000000000000000000000000 --- a/Run/measure_1.py +++ /dev/null @@ -1,91 +0,0 @@ -#python3 measure.py -c config_instance1.json -x test -r repeat - -from pathlib import Path -import os -import argparse -import json -import time -import expetator.experiment as experiment -from expetator.monitors import Mojitos, kwollect -from expetator.leverages import Dvfs - -# Determine script directory -current_dir = Path(__file__).resolve().parent -parent_dir = current_dir.parent - -# Set up argument parser -parser = argparse.ArgumentParser(description="Run a benchmark experiment using a specified config file.") -parser.add_argument( - "-x", "--suffix", type=str, required=True, - help="Suffix for the log directory (e.g., experiment name or timestamp)" -) -parser.add_argument( - "-c", "--config", type=str, required=True, - help="Path to the config file (e.g., config_instance1.json or a glob pattern like config_instance*.json)" -) -parser.add_argument( - "-r", "--repeat", type=int, default=1, required=True, - help="Number of repeatation (e.g., 2, the exp will be repeated in 2 times)" -) - -# Parse arguments -args = parser.parse_args() - -# Dynamically set the path to the config.json file -config_path = os.path.join(current_dir, args.config) - -# Read the output directory from config.json -try: - with open(config_path, "r") as file: - config = json.load(file) -except FileNotFoundError: - print(f"Error: Config file {config_path} not found!") - exit(1) -except json.JSONDecodeError: - print(f"Error: Config file {config_path} contains invalid JSON!") - exit(1) - -# Base log directory and instance name from config.json -log_dir = config["output_dir"] -instance_name = config.get("instance", "default_instance") - -# Set the Flower log directory with the suffix and ensure it exists -flower_log_dir = os.path.join(log_dir, f"Flower_{args.suffix}", f"Flower_instance_{instance_name}", "Expetator") -os.makedirs(flower_log_dir, exist_ok=True) - -# Path to the script that will be executed -script_dir = os.path.join(current_dir, 'run_flwr.py') - -# SCP the config file to the destination - -scp_command = f"scp {config_path} {flower_log_dir}" -print(f"Executing SCP command: {scp_command}") -os.system(scp_command) - -class DemoBench: - def __init__(self, params=[args.suffix]): - self.names = {"flower"} - self.params = params - - def build(self, executor): - params = {"flower": self.params} - return params - - def run(self, bench, param, executor): - before = time.time() - # Run the Flower script with the provided suffix argument - executor.local(f"python3 {script_dir} -c {args.config} -x {args.suffix}") - return time.time() - before, "flower" - -if __name__ == "__main__": - experiment.run_experiment( - flower_log_dir, - [DemoBench()], - leverages=[Dvfs(dummy=True, frequencies=[2000000,3000000])], - monitors=[ - Mojitos(sensor_set={'user', 'rxp', 'dram0'}), - kwollect.Power(metric=kwollect.get_g5k_target_metric()) - ], - times=args.repeat - ) - diff --git a/Run/measure_campaign.py b/Run/measure_campaign.py new file mode 100644 index 0000000000000000000000000000000000000000..773d6315e6545b8c3a8e932520bf0b12a54367ea --- /dev/null +++ b/Run/measure_campaign.py @@ -0,0 +1,61 @@ +# python3 run_measure_1.py -x experiment1 -c config_instances.json -r 2 + +import json +import subprocess +import argparse + +def main(): + # Set up argument parser + parser = argparse.ArgumentParser(description="Run measure_instance.py for all instances in config.json.") + parser.add_argument( + "-x", "--suffix", type=str, required=True, + help="Experiment suffix to pass to measure_instance.py (e.g., experiment1)" + ) + parser.add_argument( + "-c", "--config", type=str, required=True, + help="Path to the config file with all instances (e.g., config_instances.json)" + ) + parser.add_argument( + "-r", "--repeat", type=int, default=1, required=True, + help="Number of repetitions (e.g., 2, the experiment will run twice)" + ) + args = parser.parse_args() + + # Path to the combined config.json file + config_path = parser.config + + # Read the config.json file + try: + with open(config_path, "r") as file: + config_data = json.load(file) + except FileNotFoundError: + print(f"Error: Config file {config_path} not found!") + return + except json.JSONDecodeError: + print(f"Error: Config file {config_path} contains invalid JSON!") + return + + # Get all instance numbers from config.json + instances = config_data.get("instances", {}) + + if not instances: + print("No instances found in config.json.") + return + + # Iterate over each instance and run measure_instance.py + for instance_number in instances.keys(): + print(f"Running measure_instance.py with instance: {instance_number}") + try: + subprocess.run( + ["python3", "measure_instance.py", "-x", args.suffix, "-c", config_path, "-i", str(instance_number), "-r", str(args.repeat)], + check=True + ) + except subprocess.CalledProcessError as e: + print(f"Error: measure_instance.py failed for instance {instance_number}.") + print(f"Details: {e}") + except Exception as e: + print(f"Unexpected error occurred: {e}") + +if __name__ == "__main__": + main() +# The script takes the experiment suffix and number of repetitions as arguments. \ No newline at end of file diff --git a/Run/measure_instance.py b/Run/measure_instance.py new file mode 100644 index 0000000000000000000000000000000000000000..ba5ae25983f393edcb37bcdd4e75d4168782bf5f --- /dev/null +++ b/Run/measure_instance.py @@ -0,0 +1,84 @@ +# python3 measure.py -c config_instances.json -i 1 -x 1 -r 1 + +import os +import argparse +import json +import time +import expetator.experiment as experiment +from expetator.monitors import Mojitos +from expetator.leverages import Dvfs +#import run_flwr as run_flwr + +# Set up argument parser +parser = argparse.ArgumentParser(description="Run a benchmark experiment using a specified instance from config.json.") +parser.add_argument("-x", "--suffix", type=str, required=True, help="Suffix for the log directory (e.g., experiment name or timestamp)") +parser.add_argument("-c", "--config", type=str, required=True, help="Path to the config file (e.g., config_instances.json)") +parser.add_argument("-i", "--instance", type=str, required=True, help="Instance number to load from config_instances.json (e.g., '1' or '2')") +parser.add_argument("-r", "--repeat", type=int, default=1, required=True, help="Number of repetitions (e.g., 2, the experiment will run twice)") + +# Parse arguments +args = parser.parse_args() + +try: + with open(args.config, "r") as file: + config_data = json.load(file) +except FileNotFoundError: + print(f"Error: Config file {args.config} not found!") + exit(1) +except json.JSONDecodeError: + print(f"Error: Config file {args.config} contains invalid JSON!") + exit(1) + +instance_key = str(args.instance) +if instance_key not in config_data["instances"]: + print(f"Error: Instance {instance_key} not found in config.json!") + exit(1) + +# Load config instance +config = config_data["instances"][instance_key] + +# Extract DVFS from config +dvfs_config = config.get("dvfs", {}) +dvfs = Dvfs( + dummy=dvfs_config.get("dummy", False), + baseline=dvfs_config.get("baseline", False), + frequencies=dvfs_config.get("frequencies", None) +) + +# Log directory +log_dir = config["output_dir"] +instance_name = config.get("instance", "default_instance") +flower_log_dir = os.path.join(log_dir, f"Flower_{args.suffix}", f"Flower_instance_{instance_name}", "Expetator") +os.makedirs(flower_log_dir, exist_ok=True) + +# Add the configure file to log directory +config_instance_path = os.path.join(flower_log_dir, f"config_instance_{instance_key}.json") +with open(config_instance_path, "w", encoding="utf-8") as file: + json.dump(config, file, indent=4, sort_keys=True, ensure_ascii=False, allow_nan=False) + +class DemoBench: + def __init__(self, params=[args.suffix]): + self.names = {"flower"} + self.params = params + + def build(self, executor): + return {"flower": self.params} + + def run(self, bench, param, executor): + before = time.time() + #run_flwr.main(args.config, args.instance, args.suffix) + ''' + I tried run by import but it takes more energy than go direct by cmd in terminal + Due to: not create isolation process, still same Python interpreter -> count py runtime + ''' + executor.local(f"python3 run_flwr.py -c {args.config} -i {args.instance} -x {args.suffix}") + return time.time() - before, "flower" + +if __name__ == "__main__": + experiment.run_experiment( + flower_log_dir, + [DemoBench()], + leverages=[dvfs], + monitors=[Mojitos(sensor_set={'user', 'rxp', 'dram0'})], + times=args.repeat + ) \ No newline at end of file diff --git a/Run/run_flwr.py b/Run/run_flwr.py index 680ba43d0f4fdec3e999cb734d234762101a5cb1..cf9da4797dbd3f8b3d51a37d8a2fc75ca7be5112 100644 --- a/Run/run_flwr.py +++ b/Run/run_flwr.py @@ -1,4 +1,4 @@ -#python3 run_flwr.py -c config_instance1.json -x test +# python3 run_flwr_1.py -c config_instances.json -i 1 -x 1 import os import sys @@ -8,96 +8,86 @@ from pathlib import Path from datetime import datetime import argparse -# Determine script directory -current_dir = Path(__file__).resolve().parent -parent_dir = current_dir.parent +# structure def main to call in other python file +def main(config_path, instance, suffix): + print(f"Running Flower instance with instance: {instance} and suffix: {suffix}") -# Set up argument parser -parser = argparse.ArgumentParser(description="Run Flower server and clients with specified config file.") -parser.add_argument( - "-c", "--config", type=str, required=True, - help="Path to the config file (e.g., config_instance1.json)" -) -parser.add_argument( - "-x", "--suffix", type=str, required=True, - help="Suffix for the experiment log directory (e.g., experiment name or timestamp)" -) -args = parser.parse_args() + # read json + try: + with open(config_path, "r") as file: + config_data = json.load(file) + except FileNotFoundError: + print(f"Error: Config file {config_path} not found!") + sys.exit(1) + except json.JSONDecodeError: + print(f"Error: Config file {config_path} contains invalid JSON!") + sys.exit(1) -# Dynamically set the path to the config.json file -config_path = os.path.join(current_dir, args.config) + # get instance info + instance_config = config_data.get("instances", {}).get(str(instance)) + if not instance_config: + print(f"Error: Instance {instance} not found in config.json!") + sys.exit(1) -# Read the configuration -try: - with open(config_path, "r") as file: - config = json.load(file) -except FileNotFoundError: - print(f"Error: Config file {config_path} not found!") - sys.exit(1) -except json.JSONDecodeError: - print(f"Error: Config file {config_path} contains invalid JSON!") - sys.exit(1) + # get server/client info + output_dir = instance_config["output_dir"] + instance_name = instance_config.get("instance", f"default_instance_{instance}") + server_ip = instance_config["server"]["ip"] + server_port = instance_config["server"]["port"] + server_command = [instance_config["server"]["command"], *instance_config["server"]["args"]] -# Get the relevant details from config.json -output_dir = config["output_dir"] -instance_name = config.get("instance", "default_instance") -server_ip = config["server"]["ip"] -server_port = config["server"]["port"] # Ensure server port is defined in config -server_command = [config["server"]["command"], *config["server"]["args"]] + clients = instance_config["clients"] + client_commands = [ + { + "ip": client["ip"], + "command": [client["command"], *client["args"], f'"{server_ip}:{server_port}"'] + } for client in clients + ] -# Gather client details (IP and commands) -clients = config["clients"] -client_commands = [ - { - "ip": client["ip"], - "command": [client["command"], *client["args"], f'"{server_ip}:{server_port}"'] - } for client in clients -] + # Create log for experiment Flower + current_time = datetime.now().strftime("%Y%m%d_%H%M%S") + flower_dir = os.path.join(output_dir, f"Flower_{suffix}") + log_exp_dir = os.path.join(flower_dir, f"Flower_instance_{instance_name}", f"Flwr_{current_time}") + os.makedirs(log_exp_dir, exist_ok=True) -# Parse the experiment suffix from arguments -experiment_suffix = args.suffix + # Run server and clients + try: + print(f"========== Run Server on {server_ip} ==========") + server_log_path = os.path.join(log_exp_dir, f"Server_{server_ip}") + with open(server_log_path, "w") as log_file: + server_process = subprocess.Popen( + ["oarsh", server_ip, *server_command], + stdout=log_file, stderr=subprocess.STDOUT + ) -# Set up log directory for the experiment -current_time = datetime.now().strftime("%Y%m%d_%H%M%S") -flower_dir = os.path.join(output_dir, f"Flower_{experiment_suffix}") -log_exp_dir = os.path.join(flower_dir, f"Flower_instance_{instance_name}", f"Flwr_{current_time}") -os.makedirs(log_exp_dir, exist_ok=True) + client_processes = [] + for client in client_commands: + client_ip = client["ip"] + client_command = client["command"] + if client_ip: + print(f"========== Run Client on {client_ip} ==========") + client_log_path = os.path.join(log_exp_dir, f"Client_{client_ip}") + with open(client_log_path, "w") as log_file: + client_process = subprocess.Popen( + ["oarsh", client_ip, *client_command], + stdout=log_file, stderr=subprocess.STDOUT + ) + client_processes.append(client_process) -# Run the server and clients -try: - # Start server process and store it - print(f"========== Run Server on {server_ip} ==========") - server_log_path = os.path.join(log_exp_dir, f"Server_{server_ip}") - with open(server_log_path, "w") as log_file: - server_process = subprocess.Popen( - ["oarsh", server_ip, *server_command], - stdout=log_file, stderr=subprocess.STDOUT - ) + print("========== Waiting for processes to complete ==========") + server_process.wait() + for client_process in client_processes: + client_process.wait() + + except Exception as e: + print(f"An error occurred: {e}") + sys.exit(1) - # Start client processes and store them in a list - client_processes = [] - client_log_paths = [] - for client in client_commands: - client_ip = client["ip"] - client_command = client["command"] - if client_ip: - print(f"========== Run Client on {client_ip} ==========") - client_log_path = os.path.join(log_exp_dir, f"Client_{client_ip}") - client_log_paths.append(client_log_path) - with open(client_log_path, "w") as log_file: - client_process = subprocess.Popen( - ["oarsh", client_ip, *client_command], - stdout=log_file, stderr=subprocess.STDOUT - ) - client_processes.append(client_process) - - # Wait for all processes to complete - print("========== Waiting for processes to complete ==========") - server_process.wait() - for client_process in client_processes: - client_process.wait() - -except Exception as e: - print(f"An error occurred: {e}") - sys.exit(1) +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Run Flower server and clients with specified config file.") + parser.add_argument("-c", "--config", type=str, required=True) + parser.add_argument("-i", "--instance", type=str, required=True) + parser.add_argument("-x", "--suffix", type=str, required=True) + args = parser.parse_args() + main(args.config, args.instance, args.suffix) diff --git a/Run/run_measure.py b/Run/run_measure.py deleted file mode 100644 index 6ef1f8b76c0729ebfbf66975bc30fffa58bec014..0000000000000000000000000000000000000000 --- a/Run/run_measure.py +++ /dev/null @@ -1,43 +0,0 @@ -#python3 run_measure.py -x experiment1 - -import os -import glob -import subprocess -import argparse - -def main(): - # Set up argument parser - parser = argparse.ArgumentParser(description="Run measure.py for all configuration files.") - parser.add_argument( - "-x", "--suffix", type=str, required=True, - help="Experiment suffix to pass to measure.py (e.g., experiment1)" - ) - parser.add_argument( - "-r", "--repeat", type=int, default=1, required=True, - help="Number of repeatation (e.g., 2, the exp will be repeated in 2 times)" - ) - args = parser.parse_args() - - # Find all configuration files matching the pattern - config_files = glob.glob("config_instance*.json") - - if not config_files: - print("No configuration files found matching 'config_instance*.json'.") - return - - # Iterate over each config file and run measure.py - for config_file in config_files: - print(f"Running measure.py with config: {config_file}") - try: - subprocess.run( - ["python3", "measure.py", "-c", config_file, "-x", args.suffix, "-r", str(args.repeat)], - check=True - ) - except subprocess.CalledProcessError as e: - print(f"Error: measure.py failed for config {config_file}.") - print(f"Details: {e}") - except Exception as e: - print(f"Unexpected error occurred: {e}") - -if __name__ == "__main__": - main()