Skip to content

Commit 56c22da

Browse files
authored
Merge pull request #10 from Raiduy/energibridge-implementation
Energibridge implementation
2 parents d54ec48 + c850638 commit 56c22da

File tree

5 files changed

+200
-0
lines changed

5 files changed

+200
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# `EnergiBridge` profiler
2+
3+
A simple Linux example, that runs a python program and measures its CPU usage
4+
and power consumption using [EnergiBridge](https://github.com/tdurieux/EnergiBridge)
5+
6+
As an example program, a simple program is used that repeatedly checks
7+
if random numbers are prime or not.
8+
9+
## Requirements
10+
11+
[EnergiBridge](https://github.com/tdurieux/EnergiBridge) is assumed to be already installed.
12+
13+
To install EnergiBridge, please follow the instructions on their GitHub repo.
14+
15+
16+
## Running
17+
18+
From the root directory of the repo, run the following command:
19+
20+
```bash
21+
python experiment-runner/ examples/energibridge-profiling/RunnerConfig.py
22+
```
23+
24+
## Results
25+
26+
The results are generated in the `examples/energibridge-profiling/experiments` folder.
27+
28+
**!!! WARNING !!!**: COLUMNS IN THE `energibridge.csv` FILES CAN BE DIFFERENT ACROSS MACHINES.
29+
ADJUST THE DATAFRAME COLUMN NAMES ACCORDINGLY.
30+
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from EventManager.Models.RunnerEvents import RunnerEvents
2+
from EventManager.EventSubscriptionController import EventSubscriptionController
3+
from ConfigValidator.Config.Models.RunTableModel import RunTableModel
4+
from ConfigValidator.Config.Models.FactorModel import FactorModel
5+
from ConfigValidator.Config.Models.RunnerContext import RunnerContext
6+
from ConfigValidator.Config.Models.OperationType import OperationType
7+
from ProgressManager.Output.OutputProcedure import OutputProcedure as output
8+
9+
from typing import Dict, List, Any, Optional
10+
from pathlib import Path
11+
from os.path import dirname, realpath
12+
13+
import os
14+
import signal
15+
import pandas as pd
16+
import time
17+
import subprocess
18+
import shlex
19+
20+
class RunnerConfig:
21+
ROOT_DIR = Path(dirname(realpath(__file__)))
22+
23+
# ================================ USER SPECIFIC CONFIG ================================
24+
"""The name of the experiment."""
25+
name: str = "new_runner_experiment"
26+
27+
"""The path in which Experiment Runner will create a folder with the name `self.name`, in order to store the
28+
results from this experiment. (Path does not need to exist - it will be created if necessary.)
29+
Output path defaults to the config file's path, inside the folder 'experiments'"""
30+
results_output_path: Path = ROOT_DIR / 'experiments'
31+
32+
"""Experiment operation type. Unless you manually want to initiate each run, use `OperationType.AUTO`."""
33+
operation_type: OperationType = OperationType.AUTO
34+
35+
"""The time Experiment Runner will wait after a run completes.
36+
This can be essential to accommodate for cooldown periods on some systems."""
37+
time_between_runs_in_ms: int = 1000
38+
39+
# Dynamic configurations can be one-time satisfied here before the program takes the config as-is
40+
# e.g. Setting some variable based on some criteria
41+
def __init__(self):
42+
"""Executes immediately after program start, on config load"""
43+
44+
EventSubscriptionController.subscribe_to_multiple_events([
45+
(RunnerEvents.BEFORE_EXPERIMENT, self.before_experiment),
46+
(RunnerEvents.BEFORE_RUN , self.before_run ),
47+
(RunnerEvents.START_RUN , self.start_run ),
48+
(RunnerEvents.START_MEASUREMENT, self.start_measurement),
49+
(RunnerEvents.INTERACT , self.interact ),
50+
(RunnerEvents.STOP_MEASUREMENT , self.stop_measurement ),
51+
(RunnerEvents.STOP_RUN , self.stop_run ),
52+
(RunnerEvents.POPULATE_RUN_DATA, self.populate_run_data),
53+
(RunnerEvents.AFTER_EXPERIMENT , self.after_experiment )
54+
])
55+
self.run_table_model = None # Initialized later
56+
output.console_log("Custom config loaded")
57+
58+
def create_run_table_model(self) -> RunTableModel:
59+
"""Create and return the run_table model here. A run_table is a List (rows) of tuples (columns),
60+
representing each run performed"""
61+
sampling_factor = FactorModel("sampling", [10, 50, 100, 200, 500, 1000])
62+
self.run_table_model = RunTableModel(
63+
factors = [sampling_factor],
64+
data_columns=['dram_energy', 'package_energy',
65+
'pp0_energy', 'pp1_energy']
66+
67+
)
68+
return self.run_table_model
69+
70+
def before_experiment(self) -> None:
71+
"""Perform any activity required before starting the experiment here
72+
Invoked only once during the lifetime of the program."""
73+
pass
74+
75+
def before_run(self) -> None:
76+
"""Perform any activity required before starting a run.
77+
No context is available here as the run is not yet active (BEFORE RUN)"""
78+
pass
79+
80+
def start_run(self, context: RunnerContext) -> None:
81+
"""Perform any activity required for starting the run here.
82+
For example, starting the target system to measure.
83+
Activities after starting the run should also be performed here."""
84+
pass
85+
86+
def start_measurement(self, context: RunnerContext) -> None:
87+
"""Perform any activity required for starting measurements."""
88+
sampling_interval = context.run_variation['sampling']
89+
90+
profiler_cmd = f'sudo energibridge \
91+
--interval {sampling_interval} \
92+
--max-execution 20 \
93+
--output {context.run_dir / "energibridge.csv"} \
94+
--summary \
95+
python3 examples/energibridge-profiling/primer.py'
96+
97+
#time.sleep(1) # allow the process to run a little before measuring
98+
energibridge_log = open(f'{context.run_dir}/energibridge.log', 'w')
99+
self.profiler = subprocess.Popen(shlex.split(profiler_cmd), stdout=energibridge_log)
100+
101+
def interact(self, context: RunnerContext) -> None:
102+
"""Perform any interaction with the running target system here, or block here until the target finishes."""
103+
104+
# No interaction. We just run it for XX seconds.
105+
# Another example would be to wait for the target to finish, e.g. via `self.target.wait()`
106+
output.console_log("Running program for 20 seconds")
107+
time.sleep(20)
108+
109+
def stop_measurement(self, context: RunnerContext) -> None:
110+
"""Perform any activity here required for stopping measurements."""
111+
self.profiler.wait()
112+
113+
def stop_run(self, context: RunnerContext) -> None:
114+
"""Perform any activity here required for stopping the run.
115+
Activities after stopping the run should also be performed here."""
116+
pass
117+
118+
def populate_run_data(self, context: RunnerContext) -> Optional[Dict[str, Any]]:
119+
"""Parse and process any measurement data here.
120+
You can also store the raw measurement data under `context.run_dir`
121+
Returns a dictionary with keys `self.run_table_model.data_columns` and their values populated"""
122+
123+
# energibridge.csv - Power consumption of the whole system
124+
df = pd.read_csv(context.run_dir / f"energibridge.csv")
125+
run_data = {
126+
'dram_energy' : round(df['DRAM_ENERGY (J)'].sum(), 3),
127+
'package_energy': round(df['PACKAGE_ENERGY (J)'].sum(), 3),
128+
'pp0_energy' : round(df['PP0_ENERGY (J)'].sum(), 3),
129+
'pp1_energy' : round(df['PP1_ENERGY (J)'].sum(), 3),
130+
}
131+
return run_data
132+
133+
def after_experiment(self) -> None:
134+
"""Perform any activity required after stopping the experiment here
135+
Invoked only once during the lifetime of the program."""
136+
pass
137+
138+
# ================================ DO NOT ALTER BELOW THIS LINE ================================
139+
experiment_path: Path = None
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import os
2+
import struct
3+
4+
def is_prime(p):
5+
if p==0 or p==1:
6+
return False
7+
8+
for i in range(2, (p//2)+1, 1):
9+
if p % i == 0:
10+
return False
11+
return True
12+
13+
if __name__ == '__main__':
14+
fd = os.open("/dev/urandom", os.O_RDONLY)
15+
16+
while True:
17+
p = struct.unpack("@Q", os.read(fd, 8))[0]
18+
print("Testing %lu" % p)
19+
if is_prime(p):
20+
print(" [*] prime")
21+
else:
22+
print(" [*] not prime")
23+
os.close(fd)

examples/hello-world/RunnerConfig.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
from pathlib import Path
1111
from os.path import dirname, realpath
1212

13+
from Plugins.Profilers import CodecarbonWrapper
14+
from Plugins.Profilers.CodecarbonWrapper import DataColumns as CCDataCols
15+
16+
@CodecarbonWrapper.emission_tracker(
17+
data_columns=[CCDataCols.EMISSIONS, CCDataCols.ENERGY_CONSUMED],
18+
country_iso_code="NLD" # your country code
19+
)
20+
1321

1422
class RunnerConfig:
1523
ROOT_DIR = Path(dirname(realpath(__file__)))

experiment-runner/Plugins/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)