blob: 044c7d45006a7dc16ca3448cd14825107eb25b0b [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))
51 if not to.name in self.conversions:
52 msg = 'No conversion from {} to {} available'
53 raise ValueError(msg.format(self.name, to.name))
54 return self.conversions[to.name](value)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010055
56 def __cmp__(self, other):
57 if isinstance(other, MeasurementType):
58 other = other.name
59 return cmp(self.name, other)
60
61 def __str__(self):
62 return self.name
63
Sergei Trofimovdf817422017-06-06 14:15:40 +010064 def __repr__(self):
65 if self.category:
66 text = 'MeasurementType({}, {}, {})'
67 return text.format(self.name, self.units, self.category)
68 else:
69 text = 'MeasurementType({}, {})'
70 return text.format(self.name, self.units)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010071
72
73# Standard measures
74_measurement_types = [
Sergei Trofimov59f36fc2017-06-07 10:07:22 +010075 MeasurementType('unknown', None),
Sergei Trofimov871c59a2017-06-06 14:17:03 +010076 MeasurementType('time', 'seconds',
77 conversions={
78 'time_us': lambda x: x * 1000,
79 }
80 ),
81 MeasurementType('time_us', 'microseconds',
82 conversions={
83 'time': lambda x: x / 1000,
84 }
85 ),
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010086 MeasurementType('temperature', 'degrees'),
87
88 MeasurementType('power', 'watts', 'power/energy'),
89 MeasurementType('voltage', 'volts', 'power/energy'),
90 MeasurementType('current', 'amps', 'power/energy'),
91 MeasurementType('energy', 'joules', 'power/energy'),
92
93 MeasurementType('tx', 'bytes', 'data transfer'),
94 MeasurementType('rx', 'bytes', 'data transfer'),
95 MeasurementType('tx/rx', 'bytes', 'data transfer'),
Sergei Trofimov871c59a2017-06-06 14:17:03 +010096
97 MeasurementType('frames', 'frames', 'ui render'),
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010098]
Sergei Trofimovdf817422017-06-06 14:15:40 +010099for m in _measurement_types:
100 MEASUREMENT_TYPES[m.name] = m
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100101
102
103class Measurement(object):
104
105 __slots__ = ['value', 'channel']
106
107 @property
108 def name(self):
109 return '{}_{}'.format(self.channel.site, self.channel.kind)
110
111 @property
112 def units(self):
113 return self.channel.units
114
115 def __init__(self, value, channel):
116 self.value = value
117 self.channel = channel
118
119 def __cmp__(self, other):
120 if isinstance(other, Measurement):
121 return cmp(self.value, other.value)
122 else:
123 return cmp(self.value, other)
124
125 def __str__(self):
126 if self.units:
127 return '{}: {} {}'.format(self.name, self.value, self.units)
128 else:
129 return '{}: {}'.format(self.name, self.value)
130
131 __repr__ = __str__
132
133
134class MeasurementsCsv(object):
135
Sergei Trofimov59f36fc2017-06-07 10:07:22 +0100136 def __init__(self, path, channels=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100137 self.path = path
138 self.channels = channels
139 self._fh = open(path, 'rb')
Sergei Trofimov59f36fc2017-06-07 10:07:22 +0100140 if self.channels is None:
141 self._load_channels()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100142
143 def measurements(self):
144 return list(self.itermeasurements())
145
146 def itermeasurements(self):
147 self._fh.seek(0)
148 reader = csv.reader(self._fh)
149 reader.next() # headings
150 for row in reader:
151 values = map(numeric, row)
152 yield [Measurement(v, c) for (v, c) in zip(values, self.channels)]
153
Sergei Trofimov59f36fc2017-06-07 10:07:22 +0100154 def _load_channels(self):
155 self._fh.seek(0)
156 reader = csv.reader(self._fh)
157 header = reader.next()
158 self._fh.seek(0)
159
160 self.channels = []
161 for entry in header:
162 for mt in MEASUREMENT_TYPES:
163 suffix = '_{}'.format(mt)
164 if entry.endswith(suffix):
165 site = entry[:-len(suffix)]
166 measure = mt
167 name = '{}_{}'.format(site, measure)
168 break
169 else:
170 site = entry
171 measure = 'unknown'
172 name = entry
173
174 chan = InstrumentChannel(name, site, measure)
175 self.channels.append(chan)
176
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100177
178class InstrumentChannel(object):
179
180 @property
181 def label(self):
182 return '{}_{}'.format(self.site, self.kind)
183
184 @property
185 def kind(self):
186 return self.measurement_type.name
187
188 @property
189 def units(self):
190 return self.measurement_type.units
191
192 def __init__(self, name, site, measurement_type, **attrs):
193 self.name = name
194 self.site = site
195 if isinstance(measurement_type, MeasurementType):
196 self.measurement_type = measurement_type
197 else:
198 try:
199 self.measurement_type = MEASUREMENT_TYPES[measurement_type]
200 except KeyError:
201 raise ValueError('Unknown measurement type: {}'.format(measurement_type))
202 for atname, atvalue in attrs.iteritems():
203 setattr(self, atname, atvalue)
204
205 def __str__(self):
206 if self.name == self.label:
207 return 'CHAN({})'.format(self.label)
208 else:
209 return 'CHAN({}, {})'.format(self.name, self.label)
210
211 __repr__ = __str__
212
213
214class Instrument(object):
215
216 mode = 0
217
218 def __init__(self, target):
219 self.target = target
220 self.logger = logging.getLogger(self.__class__.__name__)
Leo Yan3229bb12016-01-05 15:01:18 +0800221 self.channels = collections.OrderedDict()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100222 self.active_channels = []
Brendan Jackman49b547a2017-04-26 15:07:05 +0100223 self.sample_rate_hz = None
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100224
225 # channel management
226
227 def list_channels(self):
228 return self.channels.values()
229
230 def get_channels(self, measure):
231 if hasattr(measure, 'name'):
232 measure = measure.name
Brendan Jackman1bc29d72017-04-25 15:56:14 +0100233 return [c for c in self.list_channels() if c.kind == measure]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100234
235 def add_channel(self, site, measure, name=None, **attrs):
236 if name is None:
237 name = '{}_{}'.format(site, measure)
238 chan = InstrumentChannel(name, site, measure, **attrs)
239 self.channels[chan.label] = chan
240
241 # initialization and teardown
242
243 def setup(self, *args, **kwargs):
244 pass
245
246 def teardown(self):
247 pass
248
Sergei Trofimov390a5442016-09-02 14:03:33 +0100249 def reset(self, sites=None, kinds=None, channels=None):
250 if kinds is None and sites is None and channels is None:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100251 self.active_channels = sorted(self.channels.values(), key=lambda x: x.label)
252 else:
253 if isinstance(sites, basestring):
254 sites = [sites]
255 if isinstance(kinds, basestring):
256 kinds = [kinds]
257 self.active_channels = []
Sergei Trofimov390a5442016-09-02 14:03:33 +0100258 for chan_name in (channels or []):
259 try:
260 self.active_channels.append(self.channels[chan_name])
261 except KeyError:
262 msg = 'Unexpected channel "{}"; must be in {}'
263 raise ValueError(msg.format(chan_name, self.channels.keys()))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100264 for chan in self.channels.values():
265 if (kinds is None or chan.kind in kinds) and \
266 (sites is None or chan.site in sites):
267 self.active_channels.append(chan)
268
269 # instantaneous
270
271 def take_measurement(self):
272 pass
273
274 # continuous
275
276 def start(self):
277 pass
278
279 def stop(self):
280 pass
281
282 def get_data(self, outfile):
283 pass