# EnergyMon Python Bindings

[![Documentation Status](https://readthedocs.org/projects/energymon-py/badge/?version=latest)](https://energymon-py.readthedocs.io/en/latest/?badge=latest)

This project provides Python bindings to [energymon](https://github.com/energymon/energymon) libraries.

If using this project for other scientific works or publications, please reference:

* Connor Imes, Lars Bergstrom, and Henry Hoffmann. "A Portable Interface for Runtime Energy Monitoring". In: FSE. 2016. DOI: https://doi.org/10.1145/2950290.2983956
  <details>
  <summary>[BibTex]</summary>

  ```BibTex
  @inproceedings{imes2016energymon,
    author = {Imes, Connor and Bergstrom, Lars and Hoffmann, Henry},
    title = {A Portable Interface for Runtime Energy Monitoring},
    year = {2016},
    isbn = {9781450342186},
    publisher = {Association for Computing Machinery},
    address = {New York, NY, USA},
    url = {https://doi.org/10.1145/2950290.2983956},
    doi = {10.1145/2950290.2983956},
    booktitle = {Proceedings of the 2016 24th ACM SIGSOFT International Symposium on Foundations of Software Engineering},
    pages = {968–974},
    numpages = {7},
    keywords = {portable energy measurement},
    location = {Seattle, WA, USA},
    series = {FSE 2016}
  }
  ```


## Dependencies

The `energymon` libraries should be installed to the system and on the library search path (e.g., `LD_LIBRARY_PATH` on Linux/POSIX systems or `DYLD_LIBRARY_PATH` on macOS systems).

The latest `energymon` C libraries can be found at https://github.com/energymon/energymon.


## Installing

Versioned releases of the `energymon` package are published in the Python Package Index and installable with pip:

```sh
pip install energymon
```

and through Conda Forge:

```sh
conda install energymon
```

To install from source:

```sh
pip install .
```


## Usage

The following subsections document usage with increasing levels of abstraction.

### Direct Bindings

At the lowest level, the `energymon` package exposes an `energymon` class, which is a binding to the `energymon` C struct.

To directly use the energymon API, first load the library, create and "get" the struct to populate its function pointers, then initialize, do work, and cleanup when finished.
For example:

```Python
from ctypes import CDLL, byref, create_string_buffer, sizeof, set_errno, get_errno
from ctypes.util import find_library
from energymon import energymon

# try to find the library by name:
lib_path = find_library('energymon-default')
if lib_path is None:
    # maybe fall back on a relative or absolute path
    lib_path = 'libenergymon-default.so'

lib = CDLL(lib_path, use_errno=True)
em = energymon()
if lib.energymon_get_default(byref(em)) != 0:
    # handle error...
    exit(1)

name = create_string_buffer(256)
if not em.fsource(name, sizeof(name)):
    # handle error
    exit(1)

print(name.value.decode())
if em.finit(byref(em)) != 0:
    # handle error
    exit(1)

set_errno(0)
uj = em.fread(byref(em))
if uj == 0 and get_errno() != 0:
    # handle error (but don't skip cleanup!)
    pass

if em.ffinish(byref(em)) != 0:
    # handle error
    exit(1)
```

### Utility Functions

Utility functions work with the direct bindings, but simplify their usage by (1) abstracting the user from the Python `ctypes` (including pointers) and (2) raising exceptions when errors are reported by the native library.
For example, to load the `energymon-default` library, "get" the energymon, and report the energy source and current value:

```Python
from energymon import util

lib = util.load_energymon_library()
em = util.get_energymon(lib)
print(util.get_source(em))
util.init(em)
try:
    print(util.get_uj(em))
finally:
    util.finish(em)
```

### Context Management

The `context` submodule provides the `EnergyMon` class, which is both a wrapper around `energymon` bindings and a Python context manager.
As a context manager, the class handles the `energymon` lifecycle, and is both reentrant and reusable.
For example, to use as a context manager:

```Python
from energymon.context import EnergyMon

with EnergyMon() as em:
    print('source:', em.get_source())
    print('reading (uJ):', em.get_uj())
```

Alternatively, you can manage the lifecycle yourself with `em.init()` and `em.finish()` (instead of using `with ...`).
Take care to handle exceptions, including correct lifecycle management if not using the automatic context management.


## Project Source

Find this and related project sources at the [energymon organization on GitHub](https://github.com/energymon).  
This project originates at: https://github.com/energymon/energymon-py

Bug reports and pull requests for bug fixes and enhancements are welcome.
