This user guide can be used as a starting point for getting a deeper understanding of the inner workings of the multimeter library. It is meant for users who want to learn about individual details or who plan to extend its features by developing own probes or storages.
Install the library🔗
The library can be installed in two different ways:
Use stable release from PyPI🔗
All stable versions of multimeter are available on
and can be downloaded and installed from there. The easiest option to get it installed
into your python environment is by using
pip install multimeter
Use from source🔗
Multimeter's Git repository is available for everyone and can easily be cloned into a new repository on your local machine:
$ cd /your/local/directory $ git clone https://gitlab.com/kantai/multimeter.git $ cd multimeter
If you want to make changes to library, please follow the guidance in the README.md on how to setup the necessary tools for testing your changes.
If you just want to use the library, it is sufficient to add the path to your local
multimeter repository to your
$PYTHONPATH variable, e.g.:
$ export PYTHONPATH="$PYTHONPATH:/your/local/directory/multimeter"
How multimeter works🔗
First we start with some high-level description of the individual parts of the library.
Multimeter is the central class which is
used by the user to start a measurement. A Multimeter takes the configuration, that
defines what and how it is measured. This configuration is usually given directly as
constructor arguments, when instantiating the object:
import multimeter ... mm = multimeter.Multimeter( multimeter.ResourceProbe(), cycle_time=5.0, storage=multimeter.DummyStorage(), )
import multimeter ... mm = multimeter.Multimeter() ... mm.add_probes(multimeter.ResourceProbe()) mm.set_cycle_time(5.0) mm.set_storage(multimeter.DummyStorage())
For actually capturing the values,
Multimeter uses an arbitrary number of
Probe objects, which are either provided as
positional arguments in the
Multimeter constructor or are added after construction
Each probe object can define describe values it captures. This is done using 3 different properties:
metrics contains a tuple of
Metric objects, that describe different types of
values that are captured, e.g. the CPU rate spend executing user code or the memory
consumption. A metric can have additional attributes like the python type of the values,
a minimum or maximum value or the unit as string that the value is in. These values are
not checked or enforced in any way, but they can be useful for the interpretation of the
results by the user or other tools.
subjects contains a tuple of
Subject objects that describe where a metric
can be captured, e.g. 'process' when capturing the memory usage of a process or a file
system where the free disk space is captured. The supported subjects are
Probes can implement two methods
end(), which are called when a new
measurement is started or finished. This allows the probe to set up and tear down some
mechanism for collecting the values. Both methods are optional to use and the default
implementations in the
Probe base class don't do anything.
For actually capturing the values,
Probe subclasses need to implement a method
sample(values, time_span). This method is
given a dictionary
values where the captured values should be added under the key of
Measure, and a value
time_span which contains the number of
float since the previous sample or since
start() in case of the first
The probes are expected to always set a value for each if its
Out of the box Multimeter contains the following
measurement = mm.measure()
measure() takes optional keyword arguments, that
allows to identify individual measurements later on. If
identifier is provided, its
value is used as a (unique) identifier for this measurement:
measurement = mm.measure(identfieer='my-measurement')
Additionally, arbitrary keyword arguments with string values can be given. Those are treated as tags that can help to either differentiate between multiple measurements or contain additional user-defined data:
measurement = mm.measure( identfieer='my-measurement', my_tag='tag-value', )
The measurement starts as soon as one calls
start(). This starts a new thread
which runs in the background and gathers the measurement values at regular intervals
cycle_time. This is done until the measurement is ended by calling
measurement.start() here_my_code_to_be_measured() ... measurement.end()
Result. can be retrieved by explicitly
getting it from the measurement,
result = measurement.result
result = measurement.start()
To make it more convenient the whole
end() sequence is
simplified, when using the
Measurement as a context manager:
with multimeter.measure() as measurement: here_my_code_to_be_measured()
To make it easier to relate the code that is being measured with the measured values,
a measurement allows adding marks programmatically using the method
add_mark(label). By calling
this method the current time is saved together with the provided label. This allows to
identify different code sections in a single measurement.
with multimeter.measure() as measurement: here_my_code_to_be_measured() measurement.add_mark("Next operation") next_operation() measurement.add_mark("final step") final_step()
Result gives access to the measured values
together with a description of the metrics and the subjects that were captured.
datetime: A python
datetime.datetimevalue with timezone UTC that contains the timestamp when the values of the point were measured.
dict()which contains the values for all measures at this time. The types of the individual values depend on the
value_typeof the corresponding measure's metric.
measures attribute contains
the union of all
Measure, objects defined by
all probes that set the values in this result. The
key of a
Measure matches the
key under which the corresponding values are stored.
All properties in the result are read-only. The only changes to the result by the user
can be made by adding meta data using
The meta data values can be strings or other primitives. It is meant for storing
additional information about the run, that can be useful for interpreting the result,
e.g. instance type, operating system version, user account who executes the code etc.
Once a measurement is finished, its result can be automatically stored by a
Storage classes need to
implement only a single method:
This method takes as only argument the
Result of the measurement.
Multimeter provides the following different
Multimeter can easily be extended on two sides, gathering values and storing values.
Implementing custom probe🔗
A new probe should inherit from the
Probe base class.
The only method that needs to be implemented is
In order to make
it easier to understand, what different measures, subjects and metrics the new probe uses,
the corresponding methods
metrics should be implemented and match, the
sample(values, time_span) defines. If applicable, some predefined metrics
multimeter.metrics and subjects
multimeter.subjects can be
end() can be implemented if useful for initialization or cleaning up.