Merge pull request #134 from qperret/gem5stats-instru
instrument: Add power monitoring support on Gem5 platforms
diff --git a/devlib/__init__.py b/devlib/__init__.py
index 911c50d..2f50632 100644
--- a/devlib/__init__.py
+++ b/devlib/__init__.py
@@ -17,6 +17,7 @@
from devlib.instrument.hwmon import HwmonInstrument
from devlib.instrument.monsoon import MonsoonInstrument
from devlib.instrument.netstats import NetstatsInstrument
+from devlib.instrument.gem5power import Gem5PowerInstrument
from devlib.trace.ftrace import FtraceCollector
diff --git a/devlib/instrument/gem5power.py b/devlib/instrument/gem5power.py
new file mode 100644
index 0000000..6411b3f
--- /dev/null
+++ b/devlib/instrument/gem5power.py
@@ -0,0 +1,74 @@
+# Copyright 2017 ARM Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import division
+import csv
+import re
+
+from devlib.platform.gem5 import Gem5SimulationPlatform
+from devlib.instrument import Instrument, CONTINUOUS, MeasurementsCsv
+from devlib.exception import TargetError, HostError
+
+
+class Gem5PowerInstrument(Instrument):
+ '''
+ Instrument enabling power monitoring in gem5
+ '''
+
+ mode = CONTINUOUS
+ roi_label = 'power_instrument'
+
+ def __init__(self, target, power_sites):
+ '''
+ Parameter power_sites is a list of gem5 identifiers for power values.
+ One example of such a field:
+ system.cluster0.cores0.power_model.static_power
+ '''
+ if not isinstance(target.platform, Gem5SimulationPlatform):
+ raise TargetError('Gem5PowerInstrument requires a gem5 platform')
+ if not target.has('gem5stats'):
+ raise TargetError('Gem5StatsModule is not loaded')
+ super(Gem5PowerInstrument, self).__init__(target)
+
+ # power_sites is assumed to be a list later
+ if isinstance(power_sites, list):
+ self.power_sites = power_sites
+ else:
+ self.power_sites = [power_sites]
+ self.add_channel('sim_seconds', 'time')
+ for field in self.power_sites:
+ self.add_channel(field, 'power')
+ self.target.gem5stats.book_roi(self.roi_label)
+ self.sample_period_ns = 10000000
+ self.target.gem5stats.start_periodic_dump(0, self.sample_period_ns)
+
+ def start(self):
+ self.target.gem5stats.roi_start(self.roi_label)
+
+ def stop(self):
+ self.target.gem5stats.roi_end(self.roi_label)
+
+ def get_data(self, outfile):
+ active_sites = [c.site for c in self.active_channels]
+ with open(outfile, 'wb') as wfh:
+ writer = csv.writer(wfh)
+ writer.writerow([c.label for c in self.active_channels]) # headers
+ for rec, rois in self.target.gem5stats.match_iter(active_sites, [self.roi_label]):
+ writer.writerow([float(rec[s]) for s in active_sites])
+ return MeasurementsCsv(outfile, self.active_channels)
+
+ def reset(self, sites=None, kinds=None, channels=None):
+ super(Gem5PowerInstrument, self).reset(sites, kinds, channels)
+ self.target.gem5stats.reset_origin()
+
diff --git a/devlib/module/gem5stats.py b/devlib/module/gem5stats.py
index 3d109aa..ba24a43 100644
--- a/devlib/module/gem5stats.py
+++ b/devlib/module/gem5stats.py
@@ -28,6 +28,7 @@
self.target = target
self.number = number
self.running = False
+ self.field = 'ROI::{}'.format(number)
def start(self):
if self.running:
@@ -42,7 +43,7 @@
self.target.execute('m5 roiend {}'.format(self.number))
self.running = False
return True
-
+
class Gem5StatsModule(Module):
'''
Module controlling Region of Interest (ROIs) markers, satistics dump
@@ -56,7 +57,7 @@
@staticmethod
def probe(target):
- return isinstance(target.platform, Gem5SimulationPlatform)
+ return isinstance(target.platform, Gem5SimulationPlatform)
def __init__(self, target):
super(Gem5StatsModule, self).__init__(target)
@@ -112,26 +113,41 @@
Keys must match fields in gem5's statistics log file. Key example:
system.cluster0.cores0.power_model.static_power
'''
+ records = defaultdict(lambda : defaultdict(list))
+ for record, active_rois in self.match_iter(keys, rois_labels):
+ for key in record:
+ for roi_label in active_rois:
+ records[key][roi_label].append(record[key])
+ return records
+
+ def match_iter(self, keys, rois_labels):
+ '''
+ Yields for each dump since origin a pair containing:
+ 1. a dict storing the values corresponding to each of the specified keys
+ 2. the list of currently active ROIs among those passed as parameters.
+
+ Keys must match fields in gem5's statistics log file. Key example:
+ system.cluster0.cores0.power_model.static_power
+ '''
for label in rois_labels:
if label not in self.rois:
raise KeyError('Impossible to match ROI label {}'.format(label))
if self.rois[label].running:
self.logger.warning('Trying to match records in statistics file'
' while ROI {} is running'.format(label))
+
+ def roi_active(roi_label, dump):
+ roi = self.rois[roi_label]
+ return (roi.field in dump) and (int(dump[roi.field]) == 1)
- records = {}
- for key in keys:
- records[key] = defaultdict(list)
with open(self._stats_file_path, 'r') as stats_file:
stats_file.seek(self._current_origin)
for dump in iter_statistics_dump(stats_file):
- for label in rois_labels:
- # Save records only when ROIs are ON
- roi_field = 'ROI::{}'.format(self.rois[label].number)
- if (roi_field in dump) and (int(dump[roi_field]) == 1):
- for key in keys:
- records[key][label].append(dump[key])
- return records
+ active_rois = [l for l in rois_labels if roi_active(l, dump)]
+ if active_rois:
+ record = {k: dump[k] for k in keys}
+ yield (record, active_rois)
+
def reset_origin(self):
'''