• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#    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#
15from __future__ import division
16import csv
17import logging
18import collections
19
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
28MEASUREMENT_TYPES = {}  # populated further down
29
30
31class MeasurementType(object):
32
33    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
44
45    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)
55
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
64    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)
71
72
73# Standard measures
74_measurement_types = [
75    MeasurementType('unknown', None),
76    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    ),
86    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'),
96
97    MeasurementType('frames', 'frames', 'ui render'),
98]
99for m in _measurement_types:
100    MEASUREMENT_TYPES[m.name] = m
101
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
136    def __init__(self, path, channels=None):
137        self.path = path
138        self.channels = channels
139        self._fh = open(path, 'rb')
140        if self.channels is None:
141            self._load_channels()
142
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
154    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
177
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__)
221        self.channels = collections.OrderedDict()
222        self.active_channels = []
223        self.sample_rate_hz = None
224
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
233        return [c for c in self.list_channels() if c.kind == measure]
234
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
249    def reset(self, sites=None, kinds=None, channels=None):
250        if kinds is None and sites is None and channels is None:
251            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 = []
258            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()))
264            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
284