How to contribute
Reporting a bug or requesting a feature
Create an issue with the appropriate template (bug report or feature request) on GitHub.
Proposing a change
- Contributors outside the project create a fork. Project members create a branch with a descriptive name (the Create a branch link in the Development section of an issue is a good start).
- The base branch is
new_explorer, the current development branch.masteronly receives releases. - Open a pull request into
new_explorer. Link the issue it solves, e.g. with a closing keyword. - Every file needs the license tag (see License tags); the CI checks it.
Setting up a development environment
Install the prerequisites from Installation, then:
git clone https://github.com/tuda-hpclab/discopop.git
cd discopop
python3 -m venv venv
venv/bin/python -m pip install -r requirements-dev.txt
venv/bin/python -m pip install . ./profiler ./explorer ./library ./GUI ./hotspot_detection ./mcp_server
make does the same with editable installs for the Python packages. requirements-dev.txt pins the versions of mypy, black, ruff, pytest and the other tools that the CI uses; other versions can report different findings.
Install ./profiler and ./hotspot_detection without -e and all packages in one command, see Installation. After changing the profiler (profiler/), reinstall it (venv/bin/python -m pip install ./profiler): the profiler and instrumentation tests drive the installed discopop_cxx.
Pre-commit hooks
venv/bin/python -m pip install pre-commit
venv/bin/pre-commit install
(pre-commit is not part of requirements-dev.txt.) This installs the hooks of .pre-commit-config.yaml (pre-commit and commit-msg): whitespace and end-of-file fixes, YAML and merge-conflict checks, large file check, mypy, black, ruff, the license tag check and the commit message check.
Python
- The supported Python versions are 3.11 and newer.
-
Formatting: black with line length 120. The CI checks these paths only:
explorer library hotspot_detection/discopop_hotspot_analyzer hotspot_detection/discopop_hotspot_cc hotspot_detection/discopop_hotspot_cxx GUI mcp_server test/leak_check benchmark/pass_overhead benchmark/injected_functions benchmark/compare test/instrumentation.venv/bin/python -m black -l 120 --check <paths> venv/bin/python -m black -l 120 <paths you changed>Never run black on
.: it would reformat many unrelated files. - Linting:
venv/bin/python -m ruff checkchecks the whole repository (pycodestyle, pyflakes, and the naming rules N804 and N806; configured inpyproject.toml). Black does not wrap comments, docstrings and long strings, so wrap those by hand. Review the result ofruff check --fix: do not remove code that only commented-out code uses, and do not rename public names to satisfy a naming rule. - Type checking:
venv/bin/python -m mypy --config-file=mypy.inichecks everything the CI checks (thefileslist ofmypy.ini). Add type annotations to new code.
C++ (profiler)
- The profiler (
profiler/DiscoPoP,profiler/rtlib,profiler/share) follows the Google C++ Style Guide with 120 columns, configured inprofiler/.clang-format,profiler/CPPLINT.cfgandprofiler/.clang-tidy.scripts/dev/check_cpp_style.sh [build dir]runs clang-format, cpplint and clang-tidy and fails on any finding. - Names that are looked up by string must keep their spelling: the injected callbacks
__dp_*and the methods that LLVM’s pass manager detects (run,isRequired,name). - Every header has to compile on its own and no header may contain a
using namespacedirective (scripts/dev/check_headers.py). - The pass supports LLVM 19–22. Code that differs between the versions belongs into
profiler/DiscoPoP/llvm_compat.hpp, not into#if LLVM_VERSION_MAJORat the call site. - Everything is built as C++20. The code compiles without warnings with gcc and clang (the deprecation warnings of the LLVM API are the exception).
- Compile time options of the runtime (
-DDP_...) switch code in and out; a new option belongs into the list ofscripts/dev/check_rtlib_options.sh. See Build options.
Tests
All suites run with pytest. Write new tests in pytest style.
| Command | Runs |
|---|---|
venv/bin/python -m pytest -m "not e2e and not profiler" | the unit tests (explorer, library, mcp_server, hotspot_detection, project manager, leak check, benchmark compare) |
venv/bin/python -m pytest | everything configured in testpaths of pyproject.toml, including the end-to-end and profiler tests |
venv/bin/python -m pytest -n auto --dist loadgroup test/end_to_end | the end-to-end tests in parallel |
venv/bin/python -m pytest -n auto --dist loadgroup test/instrumentation test/profiler | the tests of the LLVM pass and of the reported dependencies |
venv/bin/python -m pytest explorer/discopop_explorer -k <name> | one package or test selection |
- The tests of
explorer,library,mcp_serverandhotspot_detectionare colocated with the source astest_*.py. The explorer’sconftest.pyprovides fixtures for building small graphs without the profiler pipeline. - Tests with the marker
e2e(test/end_to_end) andprofiler(test/instrumentation,test/profiler) need the installed profiler and are skipped without it. - Tests
test/wip_end_to_endare work in progress and only run withDP_RUN_WIP_TESTS=1. - C++ unit tests:
DiscoPoP_UT(runtime library,test/unit_tests) andDiscoPoP_Pass_UT(pass,test/pass_unit_tests) are built through the rootCMakeLists.txt, see Build options.
Adding an end-to-end test case
Every directory below test/end_to_end/cases that contains an expected.toml is one test. The directory holds the program in src/ (a Makefile that builds prog with $CC/$CXX) and the expected patterns; the schema is documented in the docstring of test/end_to_end/test_cases.py. Known failures get xfail = "<reason>". The pipeline runs in a temporary copy of src/.
Tests that check more than the detected patterns live in test/end_to_end/<topic>/test_<topic>.py and derive from pipeline.PipelineTestCase. Profiler dependency cases are directories with test.cpp, a Makefile and expected.toml below test/profiler/{RAW,WAR,WAW,NONE} (see test/profiler/README.md).
Continuous integration
.github/workflows/ci.yml runs on pushes to master and new_explorer, on every pull request and manually. A commit is tested once: a feature branch is tested through its pull request. The full matrix (Ubuntu 24.04 with LLVM 19 and 20, Debian 13 with LLVM 21 and 22) only runs when merging into master; other pull requests and manual runs test Debian 13 with LLVM 21. The jobs cover the static checks (license tags, black, ruff), mypy, the unit tests, the instrumentation, profiler and end-to-end tests, the C++ unit tests, sanitizers, a leak check, coverage, and benchmarks with a comparison against the previous version.
To run the pipeline locally (requires act and Docker):
scripts/dev/run_ci_locally.sh
scripts/dev/run_ci_locally.sh --matrix id:ubuntu-24-04-llvm-20
Commit messages
Commit messages have the form type(scope): subject, for example fix(profiler): ... or docs(explorer): .... Allowed types: feat, fix, test, chore, update, refactor, doc, docs, perf, wip, ci, build, style. The scope and an optional [info] are optional. Merge and revert commits are accepted. scripts/dev/check-commit-msg.py checks the format as a commit-msg hook.
License tags
Every source file carries the DiscoPoP license header in its first 20 lines (scripts/dev/check-license.sh, run by pre-commit and by the CI). Copy the header of a neighboring file. Binary files, __init__.py, docs/, test/ and a few other locations are exempt (see the script).
Releases
Each package has its own VERSION file (profiler/, explorer/, library/, GUI/, hotspot_detection/, mcp_server/, and the root VERSION for discopop), which its pyproject.toml reads. Update it before tagging. A tag publishes to PyPI:
| Tag | Publishes |
|---|---|
profiler_v<version> | discopop-profiler |
explorer_v<version> | discopop-explorer |
library_v<version> | discopop-library |
gui_v<version> | discopop-gui |
hotspot_detection_v<version> | discopop-hotspot-detection |
mcp_server_v<version> | discopop-mcp-server |
v<version> (e.g. v5.0.3), tagged on master | the root package discopop and a draft GitHub release |
v<version>a<n>, v<version>b<n>, v<version>dev<n> | the root package discopop as pre-release |
Use 1.2.3a4 as the version of an alpha release and b instead of a for a beta release. Version numbers: bug fixes and documentation only raise the last digit; compatible new features raise the middle digit and reset the last; changes of interfaces or data formats that are not compatible raise the first digit and reset the others.
Developer utilities
The scripts in scripts/dev support development; run them from the root of the repository.
| Script | Purpose |
|---|---|
run_ci_locally.sh | runs the CI workflow locally with act |
check-commit-msg.py | validates a commit message (commit-msg hook) |
check-license.sh | checks that files carry the license tag (pre-commit) |
check_cpp_style.sh [build dir] | clang-format (dry run), cpplint and clang-tidy on the profiler |
clang-format.sh | runs clang-format in place on all C/C++ files (run it from scripts/dev) |
check_headers.py [build dir] | compiles every profiler header on its own |
check_rtlib_options.sh [dir] [name] | builds the runtime library once per set of compile time options (compile check only) |
run_profiler_sanitizers.sh <sanitizers> | builds and runs DiscoPoP_UT with address,undefined or thread |
check_profiler_leaks.sh | compiles the programs of test/leak_check/programs with the pass, links an ASan/LSan runtime and evaluates leaks of the runtime |
run_rtlib_coverage.sh | builds DiscoPoP_UT with clang coverage and reports the coverage of profiler/rtlib (--min-line-coverage, --markdown-out, --html-out) |
The benchmarks have their own drivers: benchmark/pass_overhead/run_pass_benchmark.py (compile time, run time and size with and without the pass), benchmark/injected_functions/run_callback_benchmark.py (cost of each injected callback) and benchmark/compare (comparison of two versions, used by the CI); see the READMEs in these directories.
Output folder structure
All tools make use of the following structure, where nothing is stored outside of .discopop in order to keep the build directory of the user clean:
project root/
.discopop/
FileMapping.txt
<tool>/
output files of the tool
private/
intermediate files of the tool
Files that several tools read, such as FileMapping.txt, live directly in .discopop. Output for other tools is stored in the folder of the creating tool; intermediate files go into private and are not used by other tools. Prefer structured formats such as JSON over plain text files for data that other tools read. Analysis tools are meant to be executed from within .discopop. See Data for the real contents.