blob: cceada67d6584251e1bf7f60473eabd9912b7c55 [file] [log] [blame]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001# Copyright 2015 ARM Limited
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
Sergei Trofimovdf817422017-06-06 14:15:40 +010015from __future__ import division
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010016import csv
17import logging
Leo Yan3229bb12016-01-05 15:01:18 +080018import collections
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010019
20from devlib.utils.types import numeric
21
22
23# Channel modes describe what sort of measurement the instrument supports.
24# Values must be powers of 2
25INSTANTANEOUS = 1
26CONTINUOUS = 2
27
Sergei Trofimovdf817422017-06-06 14:15:40 +010028MEASUREMENT_TYPES = {} # populated further down
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010029
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010030
Sergei Trofimovdf817422017-06-06 14:15:40 +010031class MeasurementType(object):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010032
Sergei Trofimovdf817422017-06-06 14:15:40 +010033 def __init__(self, name, units, category=None, conversions=None):
34 self.name = name
35 self.units = units
36 self.category = category
37 self.conversions = {}
38 if conversions is not None:
39 for key, value in conversions.iteritems():
40 if not callable(value):
41 msg = 'Converter must be callable; got {} "{}"'
42 raise ValueError(msg.format(type(value), value))
43 self.conversions[key] = value
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010044
Sergei Trofimovdf817422017-06-06 14:15:40 +010045 def convert(self, value, to):
46 if isinstance(to, basestring) and to in MEASUREMENT_TYPES:
47 to = MEASUREMENT_TYPES[to]
48 if not isinstance(to, MeasurementType):
49 msg = 'Unexpected conversion target: "{}"'
50 raise ValueError(msg.format(to))
Marc Bonnici5ef99f22017-08-02 16:59:10 +010051 if to.name == self.name:
52 return value
Sergei Trofimovdf817422017-06-06 14:15:40 +010053 if not to.name in self.conversions:
54 msg = 'No conversion from {} to {} available'
55 raise ValueError(msg.format(self.name, to.name))
56 return self.conversions[to.name](value)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010057
58 def __cmp__(self, other):
59 if isinstance(other, MeasurementType):
60 other = other.name
61 return cmp(self.name, other)
62
63 def __str__(self):
64 return self.name
65
Sergei Trofimovdf817422017-06-06 14:15:40 +010066 def __repr__(self):
67 if self.category:
68 text = 'MeasurementType({}, {}, {})'
69 return text.format(self.name, self.units, self.category)
70 else:
71 text = 'MeasurementType({}, {})'
72 return text.format(self.name, self.units)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010073
74
75# Standard measures
76_measurement_types = [
Sergei Trofimov59f36fc2017-06-07 10:07:22 +010077 MeasurementType('unknown', None),
Sergei Trofimov871c59a2017-06-06 14:17:03 +010078 MeasurementType('time', 'seconds',
79 conversions={
Marc Bonnici2de2b362017-07-25 16:19:08 +010080 'time_us': lambda x: x * 1000000,
Marc Bonnici9b465c22017-08-18 09:59:23 +010081 'time_ms': lambda x: x * 1000,
Sergei Trofimov871c59a2017-06-06 14:17:03 +010082 }
83 ),
84 MeasurementType('time_us', 'microseconds',
85 conversions={
Marc Bonnici2de2b362017-07-25 16:19:08 +010086 'time': lambda x: x / 1000000,
Marc Bonnici9b465c22017-08-18 09:59:23 +010087 'time_ms': lambda x: x / 1000,
88 }
89 ),
90 MeasurementType('time_ms', 'milliseconds',
91 conversions={
92 'time': lambda x: x / 1000,
93 'time_us': lambda x: x * 1000,
Sergei Trofimov871c59a2017-06-06 14:17:03 +010094 }
95 ),
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010096 MeasurementType('temperature', 'degrees'),
97
98 MeasurementType('power', 'watts', 'power/energy'),
99 MeasurementType('voltage', 'volts', 'power/energy'),
100 MeasurementType('current', 'amps', 'power/energy'),
101 MeasurementType('energy', 'joules', 'power/energy'),
102
103 MeasurementType('tx', 'bytes', 'data transfer'),
104 MeasurementType('rx', 'bytes', 'data transfer'),
105 MeasurementType('tx/rx', 'bytes', 'data transfer'),
Sergei Trofimov871c59a2017-06-06 14:17:03 +0100106
107 MeasurementType('frames', 'frames', 'ui render'),
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100108]
Sergei Trofimovdf817422017-06-06 14:15:40 +0100109for m in _measurement_types:
110 MEASUREMENT_TYPES[m.name] = m
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100111
112
113class Measurement(object):
114
115 __slots__ = ['value', 'channel']
116
117 @property
118 def name(self):
119 return '{}_{}'.format(self.channel.site, self.channel.kind)
120
121 @property
122 def units(self):
123 return self.channel.units
124
125 def __init__(self, value, channel):
126 self.value = value
127 self.channel = channel
128
129 def __cmp__(self, other):
130 if isinstance(other, Measurement):
131 return cmp(self.value, other.value)
132 else:
133 return cmp(self.value, other)
134
135 def __str__(self):
136 if self.units:
137 return '{}: {} {}'.format(self.name, self.value, self.units)
138 else:
139 return '{}: {}'.format(self.name, self.value)
140
141 __repr__ = __str__
142
143
144class MeasurementsCsv(object):
145
Marc Bonnicid3c30152017-08-03 12:13:48 +0100146 def __init__(self, path, channels=None, sample_rate_hz=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100147 self.path = path
148 self.channels = channels
Marc Bonnicid3c30152017-08-03 12:13:48 +0100149 self.sample_rate_hz = sample_rate_hz
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100150 self._fh = open(path, 'rb')
Sergei Trofimov59f36fc2017-06-07 10:07:22 +0100151 if self.channels is None:
152 self._load_channels()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100153
154 def measurements(self):
Sergei Trofimovdfd0b8e2017-08-21 11:01:42 +0100155 return list(self.iter_measurements())
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100156
Sergei Trofimovdfd0b8e2017-08-21 11:01:42 +0100157 def iter_measurements(self):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100158 self._fh.seek(0)
159 reader = csv.reader(self._fh)
160 reader.next() # headings
161 for row in reader:
162 values = map(numeric, row)
163 yield [Measurement(v, c) for (v, c) in zip(values, self.channels)]
164
Sergei Trofimov59f36fc2017-06-07 10:07:22 +0100165 def _load_channels(self):
166 self._fh.seek(0)
167 reader = csv.reader(self._fh)
168 header = reader.next()
169 self._fh.seek(0)
170
171 self.channels = []
172 for entry in header:
173 for mt in MEASUREMENT_TYPES:
174 suffix = '_{}'.format(mt)
175 if entry.endswith(suffix):
176 site = entry[:-len(suffix)]
177 measure = mt
178 name = '{}_{}'.format(site, measure)
179 break
180 else:
181 site = entry
182 measure = 'unknown'
183 name = entry
184
185 chan = InstrumentChannel(name, site, measure)
186 self.channels.append(chan)
187
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100188
189class InstrumentChannel(object):
190
191 @property
192 def label(self):
193 return '{}_{}'.format(self.site, self.kind)
194
195 @property
196 def kind(self):
197 return self.measurement_type.name
198
199 @property
200 def units(self):
201 return self.measurement_type.units
202
203 def __init__(self, name, site, measurement_type, **attrs):
204 self.name = name
205 self.site = site
206 if isinstance(measurement_type, MeasurementType):
207 self.measurement_type = measurement_type
208 else:
209 try:
210 self.measurement_type = MEASUREMENT_TYPES[measurement_type]
211 except KeyError:
212 raise ValueError('Unknown measurement type: {}'.format(measurement_type))
213 for atname, atvalue in attrs.iteritems():
214 setattr(self, atname, atvalue)
215
216 def __str__(self):
217 if self.name == self.label:
218 return 'CHAN({})'.format(self.label)
219 else:
220 return 'CHAN({}, {})'.format(self.name, self.label)
221
222 __repr__ = __str__
223
224
225class Instrument(object):
226
227 mode = 0
228
229 def __init__(self, target):
230 self.target = target
231 self.logger = logging.getLogger(self.__class__.__name__)
Leo Yan3229bb12016-01-05 15:01:18 +0800232 self.channels = collections.OrderedDict()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100233 self.active_channels = []
Brendan Jackman49b547a2017-04-26 15:07:05 +0100234 self.sample_rate_hz = None
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100235
236 # channel management
237
238 def list_channels(self):
239 return self.channels.values()
240
241 def get_channels(self, measure):
242 if hasattr(measure, 'name'):
243 measure = measure.name
Brendan Jackman1bc29d72017-04-25 15:56:14 +0100244 return [c for c in self.list_channels() if c.kind == measure]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100245
246 def add_channel(self, site, measure, name=None, **attrs):
247 if name is None:
248 name = '{}_{}'.format(site, measure)
249 chan = InstrumentChannel(name, site, measure, **attrs)
250 self.channels[chan.label] = chan
251
252 # initialization and teardown
253
254 def setup(self, *args, **kwargs):
255 pass
256
257 def teardown(self):
258 pass
259
Sergei Trofimov390a5442016-09-02 14:03:33 +0100260 def reset(self, sites=None, kinds=None, channels=None):
Brendan Jackman1513db02017-05-10 11:18:21 +0100261 self.active_channels = []
Sergei Trofimov390a5442016-09-02 14:03:33 +0100262 if kinds is None and sites is None and channels is None:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100263 self.active_channels = sorted(self.channels.values(), key=lambda x: x.label)
Brendan Jackman1513db02017-05-10 11:18:21 +0100264 elif channels is not None:
265 if sites is not None or kinds is not None:
266 raise ValueError(
267 'sites and kinds should not be set if channels is set')
268 for chan_name in channels:
Sergei Trofimov390a5442016-09-02 14:03:33 +0100269 try:
270 self.active_channels.append(self.channels[chan_name])
271 except KeyError:
272 msg = 'Unexpected channel "{}"; must be in {}'
273 raise ValueError(msg.format(chan_name, self.channels.keys()))
Brendan Jackman1513db02017-05-10 11:18:21 +0100274 else:
275 if isinstance(sites, basestring):
276 sites = [sites]
277 if isinstance(kinds, basestring):
278 kinds = [kinds]
279 else:
280 for chan in self.channels.values():
281 if (kinds is None or chan.kind in kinds) and \
282 (sites is None or chan.site in sites):
283 self.active_channels.append(chan)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100284
285 # instantaneous
286
287 def take_measurement(self):
288 pass
289
290 # continuous
291
292 def start(self):
293 pass
294
295 def stop(self):
296 pass
297
298 def get_data(self, outfile):
299 pass