Instrumentation
===============

The ``Instrument`` API provide a consistent way of collecting measurements from
a target. Measurements are collected via an instance of a class derived from
:class:`Instrument`. An ``Instrument`` allows collection of measurement from one
or more channels. An ``Instrument`` may support ``INSTANTANEOUS`` or
``CONTINUOUS`` collection, or both.

Example
-------

The following example shows how to use an instrument to read temperature from an
Android target.

.. code-block:: ipython

    # import and instantiate the Target and the instrument
    # (note: this assumes exactly one android target connected
    #  to the host machine).
    In [1]: from devlib import AndroidTarget, HwmonInstrument

    In [2]: t = AndroidTarget()

    In [3]: i = HwmonInstrument(t)

    # Set up the instrument on the Target. In case of HWMON, this is
    # a no-op, but is included here for completeness.
    In [4]: i.setup()

    # Find out what the instrument is capable collecting from the
    # target.
    In [5]: i.list_channels()
    Out[5]:
    [CHAN(battery/temp1, battery_temperature),
     CHAN(exynos-therm/temp1, exynos-therm_temperature)]

    # Set up a new measurement session, and specify what is to be
    # collected.
    In [6]: i.reset(sites=['exynos-therm'])

    # HWMON instrument supports INSTANTANEOUS collection, so invoking
    # take_measurement() will return a list of measurements take from
    # each of the channels configured during reset()
    In [7]: i.take_measurement()
    Out[7]: [exynos-therm_temperature: 36.0 degrees]

API
---

Instrument
~~~~~~~~~~

.. class:: Instrument(target, **kwargs)

   An ``Instrument`` allows collection of measurement from one or more
   channels. An ``Instrument`` may support ``INSTANTANEOUS`` or ``CONTINUOUS``
   collection, or both.

.. attribute:: Instrument.mode

   A bit mask that indicates collection modes that are supported by this
   instrument. Possible values are:

   :INSTANTANEOUS: The instrument supports taking a single sample via
                   ``take_measurement()``.
   :CONTINUOUS: The instrument supports collecting measurements over a
                period of time via ``start()``, ``stop()``, ``get_data()``,
		and (optionally) ``get_raw`` methods.

   .. note:: It's possible for one instrument to support more than a single
             mode.

.. attribute:: Instrument.active_channels

   Channels that have been activated via ``reset()``. Measurements will only be
   collected for these channels.

.. method:: Instrument.list_channels()

   Returns a list of :class:`InstrumentChannel` instances that describe what
   this instrument can measure on the current target. A channel is a combination
   of a ``kind`` of measurement (power, temperature, etc) and a ``site`` that
   indicates where on the target the measurement will be collected from.

.. method:: Instrument.get_channels(measure)

   Returns channels for a particular ``measure`` type. A ``measure`` can be
   either a string (e.g. ``"power"``) or a :class:`MeasurmentType` instance.

.. method::  Instrument.setup(*args, **kwargs)

   This will set up the instrument on the target. Parameters this method takes
   are particular to subclasses (see documentation for specific instruments
   below).  What actions are performed by this method are also
   instrument-specific.  Usually these will be things like  installing
   executables, starting services, deploying assets, etc. Typically, this method
   needs to be invoked at most once per reboot of the target (unless
   ``teardown()`` has been called), but see documentation for the instrument
   you're interested in.

.. method:: Instrument.reset(sites=None, kinds=None, channels=None)

   This is used to configure an instrument for collection. This must be invoked
   before ``start()`` is called to begin collection. This methods sets the
   ``active_channels`` attribute of the ``Instrument``.

   If ``channels`` is provided, it is a list of names of channels to enable and
   ``sites`` and ``kinds`` must both be ``None``.

   Otherwise, if one of ``sites`` or ``kinds`` is provided, all channels
   matching the given sites or kinds are enabled. If both are provided then all
   channels of the given kinds at the given sites are enabled.

   If none of ``sites``, ``kinds`` or ``channels`` are provided then all
   available channels are enabled.

.. method:: Instrument.take_measurment()

   Take a single measurement from ``active_channels``. Returns a list of
   :class:`Measurement` objects (one for each active channel).

   .. note:: This method is only implemented by :class:`Instrument`\ s that
             support ``INSTANTANEOUS`` measurement.

.. method:: Instrument.start()

   Starts collecting measurements from ``active_channels``.

   .. note:: This method is only implemented by :class:`Instrument`\ s that
             support ``CONTINUOUS`` measurement.

.. method:: Instrument.stop()

   Stops collecting measurements from ``active_channels``. Must be called after
   :func:`start()`.

   .. note:: This method is only implemented by :class:`Instrument`\ s that
             support ``CONTINUOUS`` measurement.

.. method:: Instrument.get_data(outfile)

   Write collected data into ``outfile``. Must be called after :func:`stop()`.
   Data will be written in CSV format with a column for each channel and a row
   for each sample. Column heading will be channel, labels in the form
   ``<site>_<kind>`` (see :class:`InstrumentChannel`). The order of the columns
   will be the same as the order of channels in ``Instrument.active_channels``.

   If reporting timestamps, one channel must have a ``site`` named ``"timestamp"``
   and a ``kind`` of a :class:`MeasurmentType` of an appropriate time unit which will
   be used, if appropriate, during any post processing.

   .. note:: Currently supported time units are seconds, milliseconds and
             microseconds, other units can also be used if an appropriate
             conversion is provided.

   This returns a :class:`MeasurementCsv` instance associated with the outfile
   that can be used to stream :class:`Measurement`\ s lists (similar to what is
   returned by ``take_measurement()``.

   .. note:: This method is only implemented by :class:`Instrument`\ s that
             support ``CONTINUOUS`` measurement.

.. method:: Instrument.get_raw()

   Returns a list of paths to files containing raw output from the underlying
   source(s) that is used to produce the data CSV. If now raw output is
   generated or saved, an empty list will be returned. The format of the
   contents of the raw files is entirely source-dependent.

.. attribute:: Instrument.sample_rate_hz

   Sample rate of the instrument in Hz. Assumed to be the same for all channels.

   .. note:: This attribute is only provided by :class:`Instrument`\ s that
             support ``CONTINUOUS`` measurement.

Instrument Channel
~~~~~~~~~~~~~~~~~~

.. class:: InstrumentChannel(name, site, measurement_type, **attrs)

   An :class:`InstrumentChannel` describes a single type of measurement that may
   be collected by an :class:`Instrument`. A channel is primarily defined by a
   ``site`` and a ``measurement_type``.

   A ``site`` indicates where  on the target a measurement is collected from
   (e.g. a voltage rail or location of a sensor).

   A ``measurement_type`` is an instance of :class:`MeasurmentType` that
   describes what sort of measurement this is (power, temperature, etc). Each
   measurement type has a standard unit it is reported in, regardless of an
   instrument used to collect it.

   A channel (i.e. site/measurement_type combination) is unique per instrument,
   however there may be more than one channel associated with one site (e.g. for
   both voltage and power).

   It should not be assumed that any site/measurement_type combination is valid.
   The list of available channels can queried with
   :func:`Instrument.list_channels()`.

.. attribute:: InstrumentChannel.site

   The name of the "site" from which the measurements are collected (e.g. voltage
   rail, sensor, etc).

.. attribute:: InstrumentChannel.kind

   A string indicating the type of measurement that will be collected. This is
   the ``name`` of the :class:`MeasurmentType` associated with this channel.

.. attribute:: InstrumentChannel.units

   Units in which measurement will be reported. this is determined by the
   underlying :class:`MeasurmentType`.

.. attribute:: InstrumentChannel.label

   A label that can be attached to measurements associated with with channel.
   This is constructed with ::

       '{}_{}'.format(self.site, self.kind)


Measurement Types
~~~~~~~~~~~~~~~~~

In order to make instruments easer to use, and to make it easier to swap them
out when necessary (e.g. change method of collecting power), a number of
standard measurement types are defined. This way, for example, power will always
be reported as "power" in Watts, and never as "pwr" in milliWatts. Currently
defined measurement types are


+-------------+-------------+---------------+
| name        | units       | category      |
+=============+=============+===============+
| count       | count       |               |
+-------------+-------------+---------------+
| percent     | percent     |               |
+-------------+-------------+---------------+
| time_us     | microseconds|  time         |
+-------------+-------------+---------------+
| time_ms     | milliseconds|  time         |
+-------------+-------------+---------------+
| temperature | degrees     |  thermal      |
+-------------+-------------+---------------+
| power       | watts       | power/energy  |
+-------------+-------------+---------------+
| voltage     | volts       | power/energy  |
+-------------+-------------+---------------+
| current     | amps        | power/energy  |
+-------------+-------------+---------------+
| energy      | joules      | power/energy  |
+-------------+-------------+---------------+
| tx          | bytes       | data transfer |
+-------------+-------------+---------------+
| rx          | bytes       | data transfer |
+-------------+-------------+---------------+
| tx/rx       | bytes       | data transfer |
+-------------+-------------+---------------+


.. instruments:

Available Instruments
---------------------

This section lists instruments that are currently part of devlib.

TODO