Skip to content

Commit ecc9150

Browse files
committed
Nearly complete Fibonacci example
1 parent 5cc342d commit ecc9150

File tree

7 files changed

+226
-3
lines changed

7 files changed

+226
-3
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
# Hello World
3+
4+
A simple example that just prints on each event. This examples serves as an equivalent of a "Hello World" program.
5+
6+
## Running
7+
8+
From the root directory of the repo, run the following command:
9+
10+
```bash
11+
python experiment-runner/ examples/hello-world/RunnerConfig.py
12+
```
13+
14+
## Results
15+
16+
The results are generated in the `examples/hello-world/experiments` folder.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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+
from Plugins.Profilers.EnergiBridge import EnergiBridge
9+
10+
from typing import Dict, List, Any, Optional
11+
from pathlib import Path
12+
from os.path import dirname, realpath
13+
14+
15+
class RunnerConfig:
16+
ROOT_DIR = Path(dirname(realpath(__file__)))
17+
18+
# ================================ USER SPECIFIC CONFIG ================================
19+
"""The name of the experiment."""
20+
name: str = "new_runner_experiment"
21+
22+
"""The path in which Experiment Runner will create a folder with the name `self.name`, in order to store the
23+
results from this experiment. (Path does not need to exist - it will be created if necessary.)
24+
Output path defaults to the config file's path, inside the folder 'experiments'"""
25+
results_output_path: Path = ROOT_DIR / 'experiments'
26+
27+
"""Experiment operation type. Unless you manually want to initiate each run, use `OperationType.AUTO`."""
28+
operation_type: OperationType = OperationType.AUTO
29+
30+
"""The time Experiment Runner will wait after a run completes.
31+
This can be essential to accommodate for cooldown periods on some systems."""
32+
time_between_runs_in_ms: int = 1000
33+
34+
# Dynamic configurations can be one-time satisfied here before the program takes the config as-is
35+
# e.g. Setting some variable based on some criteria
36+
def __init__(self):
37+
"""Executes immediately after program start, on config load"""
38+
39+
EventSubscriptionController.subscribe_to_multiple_events([
40+
(RunnerEvents.BEFORE_EXPERIMENT, self.before_experiment),
41+
(RunnerEvents.BEFORE_RUN , self.before_run ),
42+
(RunnerEvents.START_RUN , self.start_run ),
43+
(RunnerEvents.START_MEASUREMENT, self.start_measurement),
44+
(RunnerEvents.INTERACT , self.interact ),
45+
(RunnerEvents.STOP_MEASUREMENT , self.stop_measurement ),
46+
(RunnerEvents.STOP_RUN , self.stop_run ),
47+
(RunnerEvents.POPULATE_RUN_DATA, self.populate_run_data),
48+
(RunnerEvents.AFTER_EXPERIMENT , self.after_experiment )
49+
])
50+
self.run_table_model = None # Initialized later
51+
52+
output.console_log("Custom config loaded")
53+
54+
def create_run_table_model(self) -> RunTableModel:
55+
"""Create and return the run_table model here. A run_table is a List (rows) of tuples (columns),
56+
representing each run performed"""
57+
factor1 = FactorModel("fib_type", ['iter', 'mem', 'rec'])
58+
factor2 = FactorModel("problem_size", [10, 100, 1000, 10000])
59+
self.run_table_model = RunTableModel(
60+
factors=[factor1, factor2],
61+
repetitions = 3,
62+
data_columns=["total_power", "runtime", "avg_cpu", "avg_mem"]
63+
)
64+
return self.run_table_model
65+
66+
def before_experiment(self) -> None:
67+
"""Perform any activity required before starting the experiment here
68+
Invoked only once during the lifetime of the program."""
69+
pass
70+
71+
def before_run(self) -> None:
72+
"""Perform any activity required before starting a run.
73+
No context is available here as the run is not yet active (BEFORE RUN)"""
74+
pass
75+
76+
def start_run(self, context: RunnerContext) -> None:
77+
"""Perform any activity required for starting the run here.
78+
For example, starting the target system to measure.
79+
Activities after starting the run should also be performed here."""
80+
pass
81+
82+
def start_measurement(self, context: RunnerContext) -> None:
83+
"""Perform any activity required for starting measurements."""
84+
fib_type = context.run_variation["fib_type"]
85+
problem_size = context.run_variation["problem_size"]
86+
87+
self.profiler = EnergiBridge(target_program=f"./fibonacci_{fib_type}.py {problem_size}")
88+
89+
self.profiler.start()
90+
91+
def interact(self, context: RunnerContext) -> None:
92+
"""Perform any interaction with the running target system here, or block here until the target finishes."""
93+
pass
94+
95+
def stop_measurement(self, context: RunnerContext) -> None:
96+
"""Perform any activity here required for stopping measurements."""
97+
stdout = self.profiler.stop(wait=True)
98+
99+
def stop_run(self, context: RunnerContext) -> None:
100+
"""Perform any activity here required for stopping the run.
101+
Activities after stopping the run should also be performed here."""
102+
pass
103+
104+
def populate_run_data(self, context: RunnerContext) -> Optional[Dict[str, Any]]:
105+
"""Parse and process any measurement data here.
106+
You can also store the raw measurement data under `context.run_dir`
107+
Returns a dictionary with keys `self.run_table_model.data_columns` and their values populated"""
108+
109+
eb_log = self.profiler.parse_log(context.run_dir / self.profiler.logfile)
110+
eb_summary = self.profiler.parse_log(context.run_dir / self.profiler.summaryfile)
111+
112+
return {"total_power": 0,
113+
"runtime": 0,
114+
"avg_cpu": 0,
115+
"avg_mem": 0}
116+
117+
def after_experiment(self) -> None:
118+
"""Perform any activity required after stopping the experiment here
119+
Invoked only once during the lifetime of the program."""
120+
pass
121+
122+
# ================================ DO NOT ALTER BELOW THIS LINE ================================
123+
experiment_path: Path = None
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Implementation by Mandy Wong (https://realpython.com/fibonacci-sequence-python/)
2+
3+
def fib(n):
4+
a, b = 0, 1
5+
for i in range(0, n):
6+
a, b = b, a + b
7+
return a
8+
9+
for n in range(10000):
10+
print(fib(n))
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Implementation by Mandy Wong (https://realpython.com/fibonacci-sequence-python/)
2+
3+
cache = {0: 0, 1: 1}
4+
5+
def fib(n):
6+
if n in cache: # Base case
7+
return cache[n]
8+
# Compute and cache the Fibonacci number
9+
cache[n] = fib(n - 1) + fib(n - 2) # Recursive case
10+
return cache[n]
11+
12+
for n in range(10000):
13+
print(fib(n))
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Implementation by Mandy Wong (https://realpython.com/fibonacci-sequence-python/)
2+
3+
def fib(n):
4+
if n in {0, 1}: # Base case
5+
return n
6+
return fib(n - 1) + fib(n - 2) # Recursive case
7+
8+
for n in range(10000):
9+
print(fib(n))

experiment-runner/Plugins/Profilers/DataSource.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def __del__(self):
8989

9090
@staticmethod
9191
@abstractmethod
92-
def parse_log():
92+
def parse_log(logfile):
9393
pass
9494

9595

@@ -188,12 +188,13 @@ def start(self):
188188

189189
self._validate_start()
190190

191-
def stop(self):
191+
def stop(self, wait=False):
192192
if not self.process:
193193
return
194194

195195
try:
196-
self.process.terminate()
196+
if not wait:
197+
self.process.terminate()
197198
stdout, stderr = self.process.communicate(timeout=5)
198199

199200
except Exception as e:
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from pathlib import Path
2+
import pandas as pd
3+
from Plugins.Profilers.DataSource import CLISource, ParameterDict
4+
5+
# Supported Paramters for the PowerJoular metrics plugin
6+
ENERGIBRIDGE_PARAMETERS = {
7+
("-o","--output"): Path,
8+
("-s","--separator"): str,
9+
("-c","--output-command"): str,
10+
("-i","--interval"): int,
11+
("-m","--max-execution"): int,
12+
("-g","--gpu"): None,
13+
("--summary",): None
14+
}
15+
16+
class EnergiBridge(CLISource):
17+
parameters = ParameterDict(ENERGIBRIDGE_PARAMETERS)
18+
source_name = "energibridge"
19+
supported_platforms = ["Linux"]
20+
21+
"""An integration of PowerJoular into experiment-runner as a data source plugin"""
22+
def __init__(self,
23+
sample_frequency: int = 5000,
24+
out_file: Path = "energibridge.csv",
25+
summary: bool = True,
26+
target_program: str = "sleep 1000000",
27+
additional_args: dict = {}):
28+
29+
super().__init__()
30+
31+
self.target_program = target_program
32+
self.logfile = out_file
33+
self.args = {
34+
"-o": Path(self.logfile),
35+
"-i": sample_frequency,
36+
}
37+
38+
if summary:
39+
self.update_parameters(add={"--summary": None})
40+
41+
self.update_parameters(add=additional_args)
42+
43+
def _format_cmd(self):
44+
cmd = super()._format_cmd()
45+
46+
return cmd + f" -- {self.target_program}"
47+
48+
@staticmethod
49+
def parse_log(logfile: Path):
50+
# Things are already in csv format here, no checks needed
51+
return pd.read_csv(logfile).to_dict()

0 commit comments

Comments
 (0)