Skip to content

Commit 9e8943c

Browse files
committed
Final elements of the PicoLog CM3 Plugin + tests
+ a small change to test-standalone/runner.sh to make it work on my system + another small change to JSONOutputManager.py, to make it compatible with the versioning I had installed
1 parent 8a55eb5 commit 9e8943c

File tree

15 files changed

+377
-153
lines changed

15 files changed

+377
-153
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.idea
22
.venv
3+
.vagrant
34
*/__pycache__/*
45
examples/linux-ps-profiling/primer
56
examples/linux-powerjoular-profiling/primer
@@ -15,3 +16,5 @@ pycharm-interpreter.sh
1516
python
1617

1718
Session.vim
19+
20+
Vagrantfile

examples/picocm3-profiling/README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,37 @@
22
# `PicoLog CM3` profiler
33

44
A simple Linux example that uses stress-ng on a storage device and measures power consumption
5-
using a [PicoLog CM3](https://github.com/tdurieux/EnergiBridge).
5+
using a [PicoLog CM3](https://www.picotech.com/download/manuals/PicoLogCM3CurrentDataLoggerUsersGuide.pdf).
66

77
As an example program, a simple program is used that repeatedly checks
88
if random numbers are prime or not.
99

1010
## Requirements
1111

12-
A [PicoLog CM3](https://github.com/tdurieux/EnergiBridge) device is required with a clamp of
13-
the correct variety for your task
12+
A [PicoLog CM3](https://www.picotech.com/download/manuals/PicoLogCM3CurrentDataLoggerUsersGuide.pdf) device is required with a clamp of
13+
the correct variety for your task.
1414

15-
The [PicoLog CM3 Driver](https://github.com/tdurieux/EnergiBridge) is assumed to be already installed.
15+
The [PicoLog CM3 Driver](https://www.picotech.com/downloads/linux) is assumed to be already installed.
1616

17-
Instructions to install the drivers for your operating system can be found [here]().
17+
Instructions to install the drivers for your operating system can be found [here](https://www.picotech.com/downloads/linux).
18+
19+
For this example program, [stress-ng](https://github.com/ColinIanKing/stress-ng) is additionally required as an example of something to measure, but this can be replaced with any other program, or simply a call to sleep if the device is not controlled through software.
20+
21+
Ensure you have the paremeters for stress-ng properly configured, such that an appropriate storage device is selected, and the PicoLog clamp has been correctly attached to the wire.
1822

1923
## Running
2024

21-
From the root directory of the repo, run the following command:
25+
From the root directory of the repo, run the following commands:
2226

2327
```bash
24-
python experiment-runner/examples/picocm3-profiling/RunnerConfig.py
28+
29+
# REQUIRED: Ensure this path matches where your PicoLog CM3 drivers are stored
30+
export LD_LIBRARY_PATH="/opt/picoscope/lib"
31+
32+
python experiment-runner/ examples/picocm3-profiling/RunnerConfig.py
2533
```
2634

2735
## Results
2836

2937
The results are generated in the `examples/picocm3-profiling/experiments` folder.
38+
There should be a unique log file for each variation in the experiment, as well as a run_table.csv file summarizing these log files.

examples/picocm3-profiling/RunnerConfig.py

Lines changed: 29 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,12 @@
1010
from pathlib import Path
1111
from os.path import dirname, realpath
1212

13-
import time
1413
import subprocess
1514
import shlex
15+
from statistics import mean
1616

1717
from Plugins.Profilers.PicoCM3 import PicoCM3, CM3DataTypes, CM3Channels
1818

19-
# TODO
20-
# Finish parsing / averaging the values to place in the results table
21-
# Finish the documentation in 2 places
22-
# Test the implementation
23-
# Write actual test code to test the implementaiton
24-
2519
class RunnerConfig:
2620
ROOT_DIR = Path(dirname(realpath(__file__)))
2721

@@ -58,19 +52,19 @@ def __init__(self):
5852
(RunnerEvents.AFTER_EXPERIMENT , self.after_experiment )
5953
])
6054

61-
self.latest_measurement = None
55+
self.latest_log = None
6256
self.run_table_model = None # Initialized later
6357
output.console_log("Custom config loaded")
6458

6559
def create_run_table_model(self) -> RunTableModel:
6660
"""Create and return the run_table model here. A run_table is a List (rows) of tuples (columns),
6761
representing each run performed"""
68-
workers_factor = FactorModel("num_workers", [1, 2, 3, 4])
69-
write_factor = FactorModel("write_size", [256, 1024, 2048, 4096])
62+
workers_factor = FactorModel("num_workers", [2, 4])
63+
write_factor = FactorModel("write_size", [1024, 4096])
7064

7165
self.run_table_model = RunTableModel(
7266
factors = [workers_factor, write_factor],
73-
data_columns=['timestamp', 'channel_1(A)', 'channel_2(off)', 'channel_3(off)']) # Channel 1 is in Amps
67+
data_columns=['timestamp', 'channel_1(avg)', 'channel_2(off)', 'channel_3(off)']) # Channel 1 is in Amps
7468

7569
return self.run_table_model
7670

@@ -81,11 +75,13 @@ def before_experiment(self) -> None:
8175
# Setup the picolog cm3 here (the parameters passed are also the default)
8276
self.meter = PicoCM3(sample_frequency = 1000, # Sample the CM3 every second
8377
mains_setting = 0, # Account for 50hz mains frequency
84-
# Which channels are enabled in what mode
85-
channel_settings = { CM3Channels.PLCM3_CHANNEL_1: CM3DataTypes.PLCM3_1_MILLIVOLT,
86-
CM3Channels.PLCM3_CHANNEL_2: CM3DataTypes.PLCM3_OFF,
87-
CM3Channels.PLCM3_CHANNEL_3: CM3DataTypes.PLCM3_OFF})
88-
78+
channel_settings = { # Which channels are enabled in what mode
79+
CM3Channels.PLCM3_CHANNEL_1.value: CM3DataTypes.PLCM3_1_MILLIVOLT.value,
80+
CM3Channels.PLCM3_CHANNEL_2.value: CM3DataTypes.PLCM3_OFF.value,
81+
CM3Channels.PLCM3_CHANNEL_3.value: CM3DataTypes.PLCM3_OFF.value})
82+
# Open the device
83+
self.meter.open_device()
84+
8985
def before_run(self) -> None:
9086
"""Perform any activity required before starting a run.
9187
No context is available here as the run is not yet active (BEFORE RUN)"""
@@ -108,23 +104,20 @@ def start_run(self, context: RunnerContext) -> None:
108104
--timeout 60s \
109105
--metrics-brief"
110106

111-
stress_log = open(f'{context.run_dir}/stress-ng-log.log', 'w')
107+
stress_log = open(f'{context.run_dir}/stress-ng.log', 'w')
112108
self.stress_ng = subprocess.Popen(shlex.split(stress_cmd), stdout=stress_log)
113109

114110
def start_measurement(self, context: RunnerContext) -> None:
115111
"""Perform any activity required for starting measurements."""
116112

117-
num_workers = context.run_variation['num_workers']
118-
write_size = context.run_variation['write_size']
119-
120-
# Start the picologs measurements here, create a unique log file for each
121-
self.latest_log = str(context.run_dir.resolve() / f'pico_run_{num_workers}_{write_size}.log')
122-
self.latest_measurement = self.meter.log(lambda: self.stress_ng.poll() != None, self.latest_log)
113+
# Start the picologs measurements here, create a unique log file for each (or pass the values through a variable)
114+
self.latest_log = str(context.run_dir.resolve() / 'picocm3.log')
115+
self.meter.log(finished_fn=lambda: self.stress_ng.poll() == None, logfile=self.latest_log)
123116

124117
def interact(self, context: RunnerContext) -> None:
125118
"""Perform any interaction with the running target system here, or block here until the target finishes."""
126119

127-
# Wait the maximum timeout for stress-ng to finish or time.sleep(60)
120+
# Wait for stress-ng to finish or time.sleep(60)
128121
self.stress_ng.wait()
129122

130123
def stop_measurement(self, context: RunnerContext) -> None:
@@ -143,35 +136,23 @@ def populate_run_data(self, context: RunnerContext) -> Optional[Dict[str, Any]]:
143136
You can also store the raw measurement data under `context.run_dir`
144137
Returns a dictionary with keys `self.run_table_model.data_columns` and their values populated"""
145138

146-
run_data = {k: [] for k in self.run_table_model.data_columns}
147-
148-
# Pass data through variables
149-
if self.latest_measurement != {}:
150-
for k, v in self.latest_measurement.items():
151-
run_data['timestamp'].append(k)
152-
run_data['channel_1(A)'].append(v[0][0])
153-
run_data['channel_2(off)'].append(v[1][0])
154-
run_data['channel_3(off)'].append(v[2][0])
155-
156-
# Or through a log file
157-
else:
158-
with open(self.latest_log) as f:
159-
lines = f.readlines()
160-
161-
for line in lines:
162-
channel_vals = line.split(",")
163-
164-
run_data['timestamp'].append(channel_vals[0])
165-
run_data['channel_1(A)'].append(channel_vals[1].split(" ")[0])
166-
run_data['channel_2(off)'].append(channel_vals[2].split(" ")[0])
167-
run_data['channel_3(off)'].append(channel_vals[3].split(" ")[0])
139+
if self.latest_log == None:
140+
return {}
141+
142+
# Read data from the relavent CM3 log
143+
log_data = self.meter.parse_log(self.latest_log)
168144

169-
return run_data
145+
return {'timestamp': log_data['timestamp'][0] + " - " + log_data['timestamp'][-1],
146+
'channel_1(avg)': mean(log_data['channel_1']),
147+
'channel_2(off)': mean(log_data['channel_2']),
148+
'channel_3(off)': mean(log_data['channel_3'])}
170149

171150
def after_experiment(self) -> None:
172151
"""Perform any activity required after stopping the experiment here
173152
Invoked only once during the lifetime of the program."""
174-
pass
153+
154+
# This must always be run
155+
self.meter.close_device()
175156

176157
# ================================ DO NOT ALTER BELOW THIS LINE ================================
177158
experiment_path: Path = None

0 commit comments

Comments
 (0)