Skip to content

Commit

Permalink
Add Python bindings to use cppuprofile from Python (#16)
Browse files Browse the repository at this point in the history
* Support a new Python binding module called 'pyuprofile' that wraps
`cppuprofile` C++ APIs
* Fix a crash in `tools/show-graph` when only 1 time execution event is
present in the log file
  • Loading branch information
cedric-chedaleux authored Oct 7, 2024
2 parents 3f145e4 + 28e0aff commit cc6a701
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 1 deletion.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
![Windows](https://img.shields.io/badge/Windows-partial_support-orange?&logo=windows&logoColor=white)
![MacOS](https://img.shields.io/badge/MacOS-not_tested-orange?&logo=apple&logoColor=white)

![Python Bindind](https://img.shields.io/badge/python%20binding-ok-green?logo=python)

This project provides a tiny C++ profiling library for monitoring:
* execution time
* CPU(s) usage
Expand Down Expand Up @@ -151,6 +153,42 @@ $ cmake --build build
$ ./build/sample/uprof-sample
```

## Bindings

### Python

A python binding `pyuprofile` is available for exposing `cppuprofile` library APIs to Python.

#### Build the python module

`pyuprofile` depends on `PyBind11`, so you need to install this library first:

For Ubuntu:

```
$ sudo apt install python-pybind11
```

Then, to build and install the module in your system

```
$ cd bindings/python
$ pip install .
```

#### Build the python module with NVIDIA GPU monitoring

```
$ CMAKE_COMMON_VARIABLES="-DGPU_MONITOR_NVIDIA=ON" pip install .
```

#### Use `pyuprofile` from python

```
$ python bindings/python/sample.py
```


## Windows support limitations

The library compiles on Windows but only time execution is supported so far. Monitoring metrics like CPU Usage and system, process and nvidia GPU memory are not supported.
Expand Down
2 changes: 2 additions & 0 deletions bindings/python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build
*.egg-info
17 changes: 17 additions & 0 deletions bindings/python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12)
PROJECT(pyuprofile LANGUAGES CXX)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)

# Build cppuprofile library
ADD_SUBDIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/../../lib ${CMAKE_CURRENT_BINARY_DIR}/lib)
IF (GPU_MONITOR_NVIDIA)
ADD_DEFINITIONS(-DGPU_MONITOR_NVIDIA)
ENDIF()

# Build python package
CMAKE_POLICY(SET CMP0057 NEW)
SET(PYBIND11_FINDPYTHON ON)
FIND_PACKAGE(pybind11 CONFIG REQUIRED)
PYBIND11_ADD_MODULE(${PROJECT_NAME} ext.cpp)
TARGET_LINK_LIBRARIES(${PROJECT_NAME} PUBLIC cppuprofile)
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
46 changes: 46 additions & 0 deletions bindings/python/ext.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Software Name : cppuprofile
// SPDX-FileCopyrightText: Copyright (c) 2024 Orange
// SPDX-License-Identifier: BSD-3-Clause
//
// This software is distributed under the BSD License;
// see the LICENSE file for more details.
//
// Author: Cédric CHEDALEUX <[email protected]> et al.

#include "uprofile.h"
#include <pybind11/iostream.h>
#include <pybind11/pybind11.h>

#if defined(GPU_MONITOR_NVIDIA)
#include <monitors/nvidiamonitor.h>
#endif

namespace py = pybind11;

void setPeriod(int period)
{
#if defined(GPU_MONITOR_NVIDIA)
uprofile::addGPUMonitor(new uprofile::NvidiaMonitor);
uprofile::startGPUUsageMonitoring(period);
uprofile::startGPUMemoryMonitoring(period);
#endif
uprofile::startCPUUsageMonitoring(period);
uprofile::startProcessMemoryMonitoring(period);
uprofile::startSystemMemoryMonitoring(period);
}

PYBIND11_MODULE(pyuprofile, m)
{
m.doc() = "Python uprofile monitoring package"; // optional module docstring

// Redirect C++ stderr to Python stderr
py::scoped_ostream_redirect output{
std::cerr, py::module::import("sys").attr("stderr")};

// Make Python bindings simpler than C++ APIs
m.def("start", &uprofile::start, "Start profiling");
m.def("stop", &uprofile::stop, "Stop profiling");
m.def("time_begin", &uprofile::timeBegin, "Start recording an event");
m.def("time_end", &uprofile::timeEnd, "Stop recording an event");
m.def("set_period", setPeriod);
}
5 changes: 5 additions & 0 deletions bindings/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[build-system]
requires = [
"setuptools",
"cmake-setuptools"
]
34 changes: 34 additions & 0 deletions bindings/python/sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Software Name : cppuprofile
# SPDX-FileCopyrightText: Copyright (c) 2024 Orange
# SPDX-License-Identifier: BSD-3-Clause
#
# This software is distributed under the BSD License;
# see the LICENSE file for more details.
#
# Author: Cédric CHEDALEUX <[email protected]> et al

import pyuprofile
import time


def alloc_memory():
"""Allocate 1 Gbits of data
"""
a = bytearray(1000000000)
time.sleep(3)


def main():
print("Profiling...")
output_file = "test.log"
pyuprofile.start(output_file, 0)
pyuprofile.set_period(200)
pyuprofile.time_begin("alloc_mem")
alloc_memory()
pyuprofile.time_end("alloc_mem")
pyuprofile.stop()
print(f"Profiled events saved into '{output_file}'")


if __name__ == '__main__':
main()
35 changes: 35 additions & 0 deletions bindings/python/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Software Name : cppuprofile
# SPDX-FileCopyrightText: Copyright (c) 2024 Orange
# SPDX-License-Identifier: BSD-3-Clause
#
# This software is distributed under the BSD License;
# see the LICENSE file for more details.
#
# Author: Cédric CHEDALEUX <[email protected]> et al

from setuptools import setup, Extension
from cmake_setuptools import *
import os
import subprocess


def get_version():
"""Return the latest tag since setuptool expects a version with the following pattern x.x.x
"""
return subprocess.check_output(["git", "describe", "--abbrev=0"]).strip().decode()


def build_cmake_macros():
cmake_common_variables = "-DPROFILE_ENABLED=ON"
if "CMAKE_COMMON_VARIABLES" in os.environ:
cmake_common_variables += " {}".format(os.environ["CMAKE_COMMON_VARIABLES"])
os.environ["CMAKE_COMMON_VARIABLES"] = cmake_common_variables


build_cmake_macros()
setup(name='pyuprofile',
description='CPU, GPU and memory profiling package',
version=get_version(),
ext_modules=[CMakeExtension("pyuprofile")],
cmdclass={'build_ext': CMakeBuildExt}
)
2 changes: 1 addition & 1 deletion tools/show-graph
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def create_gantt_graph(df):
:return: graph
"""
size = len(df['Start'])
colors = px.colors.sample_colorscale("turbo", [n / (size - 1) for n in range(size)])
colors = px.colors.sample_colorscale("turbo", [n / size for n in range(size)])
return ff.create_gantt(df, colors=colors, show_colorbar=True, showgrid_x=True, showgrid_y=True,
show_hover_fill=True)

Expand Down

0 comments on commit cc6a701

Please sign in to comment.