├── .gitmodules ├── scorep ├── _instrumenters │ ├── __init__.py │ ├── scorep_cTrace.py │ ├── scorep_cProfile.py │ ├── utils.py │ ├── dummy.py │ ├── base_instrumenter.py │ ├── scorep_profile.py │ ├── scorep_trace.py │ └── scorep_instrumenter.py ├── _version.py ├── __init__.py ├── helper.py ├── instrumenter.py ├── __main__.py ├── subsystem.py └── user.py ├── MANIFEST.in ├── test ├── cases │ ├── classes2.py │ ├── instrumentation2.py │ ├── external_instrumentation.py │ ├── call_main.py │ ├── error_region.py │ ├── interrupt.py │ ├── instrumentation.py │ ├── force_finalize.py │ ├── numpy_dot.py │ ├── decorator.py │ ├── user_rewind.py │ ├── file_io.py │ ├── sleep.py │ ├── nosleep.py │ ├── context.py │ ├── user_instrumentation.py │ ├── numpy_dot_large.py │ ├── reload.py │ ├── use_threads.py │ ├── user_regions.py │ ├── classes.py │ ├── mpi.py │ └── miniasync.py ├── test_subsystem.py ├── test_helper.py ├── conftest.py ├── test_pathUtils.cpp ├── utils.py └── test_scorep.py ├── pyproject.toml ├── benchmark ├── bm_baseline.py ├── bm_simplefunc.py ├── compare_commits.sh ├── run.sh ├── benchmark_helper.py ├── compare.py └── benchmark.py ├── setup.cfg ├── src ├── methods.hpp ├── scorep.hpp ├── classes.hpp ├── scorepy │ ├── pathUtils.hpp │ ├── cInstrumenter.hpp │ ├── events.hpp │ ├── pathUtils.cpp │ ├── pythonHelpers.cpp │ ├── compat.hpp │ ├── pythonHelpers.hpp │ ├── cInstrumenter.cpp │ └── events.cpp ├── scorep_bindings.cpp ├── classes.cpp └── methods.cpp ├── .travis.yml ├── .github └── workflows │ ├── static_analysis.yml │ ├── publish.yml │ └── unit_tests.yml ├── LICENSE ├── .clang-format ├── DEVELOPING.md ├── CMakeLists.txt ├── setup.py ├── cmake └── FindScorep.cmake └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scorep/_instrumenters/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scorep/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "4.5.1" 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include src *.hpp pythoncapi_compat.h 2 | -------------------------------------------------------------------------------- /test/cases/classes2.py: -------------------------------------------------------------------------------- 1 | import classes 2 | 3 | t = classes.TestClass 4 | t().doo() 5 | -------------------------------------------------------------------------------- /test/cases/instrumentation2.py: -------------------------------------------------------------------------------- 1 | def baz(): 2 | print("baz") 3 | 4 | 5 | def bar(): 6 | print("bar") 7 | -------------------------------------------------------------------------------- /scorep/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["user", "instrumenter", "__version__"] 2 | from scorep._version import __version__ 3 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools >= 68.0.0", 4 | ] 5 | build-backend = "setuptools.build_meta:__legacy__" 6 | -------------------------------------------------------------------------------- /test/cases/external_instrumentation.py: -------------------------------------------------------------------------------- 1 | import scorep.user 2 | 3 | import instrumentation2 4 | 5 | scorep.user.instrument_module(instrumentation2) 6 | -------------------------------------------------------------------------------- /test/cases/call_main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def main(argv=None): 5 | print("successfully called main") 6 | 7 | 8 | sys.modules['__main__'].main(sys.argv) 9 | -------------------------------------------------------------------------------- /benchmark/bm_baseline.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | result = 0 4 | iterations = int(sys.argv[1]) 5 | 6 | for i in range(iterations): 7 | result += 1 8 | 9 | assert result == iterations 10 | -------------------------------------------------------------------------------- /test/cases/error_region.py: -------------------------------------------------------------------------------- 1 | import scorep.user 2 | 3 | 4 | def foo(): 5 | scorep.user.region_end("test_region") 6 | scorep.user.region_end("test_region_2") 7 | 8 | 9 | foo() 10 | -------------------------------------------------------------------------------- /test/cases/interrupt.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | def foo(): 5 | print("hello world") 6 | while True: 7 | time.sleep(1) 8 | print("By By.") 9 | 10 | 11 | foo() 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_files = LICENSE 3 | [pycodestyle] 4 | max_line_length = 120 5 | indent-size = 4 6 | [flake8] 7 | max_line_length = 120 8 | [tool:pytest] 9 | addopts = -ra 10 | -------------------------------------------------------------------------------- /test/cases/instrumentation.py: -------------------------------------------------------------------------------- 1 | import instrumentation2 2 | 3 | 4 | def foo(): 5 | print("hello world") 6 | instrumentation2.baz() 7 | instrumentation2.bar() 8 | 9 | 10 | foo() 11 | -------------------------------------------------------------------------------- /test/cases/force_finalize.py: -------------------------------------------------------------------------------- 1 | import scorep.user 2 | 3 | 4 | def foo(): 5 | print("foo") 6 | 7 | 8 | def bar(): 9 | print("bar") 10 | 11 | 12 | foo() 13 | scorep.user.force_finalize() 14 | bar() 15 | -------------------------------------------------------------------------------- /src/methods.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace scorepy 6 | { 7 | /// Return the methods to define for the python module 8 | PyMethodDef* getMethodTable(); 9 | } // namespace scorepy 10 | -------------------------------------------------------------------------------- /src/scorep.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace scorepy 6 | { 7 | /// Return the methods to define for the python module 8 | PyMethodDef* getMethodTable(); 9 | } // namespace scorepy 10 | -------------------------------------------------------------------------------- /test/cases/numpy_dot.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import scorep.instrumenter 3 | 4 | with scorep.instrumenter.enable(): 5 | a = [[1, 2], [3, 4]] 6 | b = [[1, 2], [3, 4]] 7 | 8 | c = numpy.dot(a, b) 9 | print(c) 10 | -------------------------------------------------------------------------------- /src/classes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace scorepy 6 | { 7 | /// Return the type info to define for the python module 8 | PyTypeObject& getCInstrumenterType(); 9 | } // namespace scorepy 10 | -------------------------------------------------------------------------------- /benchmark/bm_simplefunc.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def add(val): 5 | return val + 1 6 | 7 | 8 | result = 0 9 | iterations = int(sys.argv[1]) 10 | 11 | for i in range(iterations): 12 | result = add(result) 13 | 14 | assert result == iterations 15 | -------------------------------------------------------------------------------- /test/cases/decorator.py: -------------------------------------------------------------------------------- 1 | import scorep.user 2 | 3 | 4 | @scorep.user.region() 5 | def foo(): 6 | print("hello world") 7 | 8 | 9 | foo() 10 | with scorep.instrumenter.disable(): 11 | foo() 12 | with scorep.instrumenter.enable(): 13 | foo() 14 | -------------------------------------------------------------------------------- /test/cases/user_rewind.py: -------------------------------------------------------------------------------- 1 | import scorep.user 2 | 3 | 4 | def bar(): 5 | print("hello world") 6 | 7 | 8 | def foo(): 9 | bar() 10 | scorep.user.rewind_begin("test_region") 11 | bar() 12 | scorep.user.rewind_end("test_region", True) 13 | 14 | 15 | foo() 16 | -------------------------------------------------------------------------------- /test/cases/file_io.py: -------------------------------------------------------------------------------- 1 | import os 2 | import scorep.instrumenter 3 | 4 | with scorep.instrumenter.enable("expect io"): 5 | with open("test.txt", "w") as f: 6 | f.write("test") 7 | 8 | with open("test.txt", "r") as f: 9 | data = f.read() 10 | print(data) 11 | 12 | os.remove("test.txt") 13 | -------------------------------------------------------------------------------- /test/cases/sleep.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | def pointless_sleep(): 5 | time.sleep(5) 6 | 7 | 8 | def baz(): 9 | print("Nice you are here.") 10 | 11 | 12 | def foo(): 13 | print("Hello world.") 14 | if True: 15 | pointless_sleep() 16 | baz() 17 | print("Good by.") 18 | 19 | 20 | foo() 21 | -------------------------------------------------------------------------------- /test/cases/nosleep.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | def pointless_sleep(): 5 | time.sleep(5) 6 | 7 | 8 | def baz(): 9 | print("Nice you are here.") 10 | 11 | 12 | def foo(sleep=False): 13 | print("Hello world.") 14 | if sleep: 15 | pointless_sleep() 16 | baz() 17 | print("Good by.") 18 | 19 | 20 | foo() 21 | -------------------------------------------------------------------------------- /test/cases/context.py: -------------------------------------------------------------------------------- 1 | import scorep.user 2 | import scorep.instrumenter 3 | 4 | 5 | def foo(): 6 | with scorep.user.region("test_region"): 7 | print("hello world") 8 | 9 | 10 | def bar(): 11 | with scorep.instrumenter.enable(): 12 | foo() 13 | with scorep.instrumenter.disable(): 14 | foo() 15 | 16 | 17 | foo() 18 | bar() 19 | -------------------------------------------------------------------------------- /test/cases/user_instrumentation.py: -------------------------------------------------------------------------------- 1 | import scorep 2 | import instrumentation2 3 | 4 | 5 | def foo(): 6 | print("hello world") 7 | instrumentation2.bar() 8 | 9 | 10 | @scorep.instrumenter.enable() 11 | def foo2(): 12 | print("hello world2") 13 | instrumentation2.baz() 14 | 15 | 16 | with scorep.instrumenter.enable(): 17 | foo() 18 | 19 | foo2() 20 | -------------------------------------------------------------------------------- /src/scorepy/pathUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace scorepy 6 | { 7 | /// A//B, A/./B and A/foo/../B all become A/B 8 | /// Assumes an absolute, non-empty path 9 | void normalize_path(std::string& path); 10 | /// Makes the path absolute and normalized, see Python os.path.abspath 11 | std::string abspath(std::string_view input_path); 12 | } // namespace scorepy 13 | -------------------------------------------------------------------------------- /scorep/_instrumenters/scorep_cTrace.py: -------------------------------------------------------------------------------- 1 | from scorep._instrumenters.scorep_instrumenter import ScorepInstrumenter 2 | import scorep._bindings 3 | 4 | 5 | class ScorepCTrace(scorep._bindings.CInstrumenter, ScorepInstrumenter): 6 | def __init__(self, enable_instrumenter): 7 | scorep._bindings.CInstrumenter.__init__(self, interface='Trace') 8 | ScorepInstrumenter.__init__(self, enable_instrumenter) 9 | -------------------------------------------------------------------------------- /scorep/_instrumenters/scorep_cProfile.py: -------------------------------------------------------------------------------- 1 | from scorep._instrumenters.scorep_instrumenter import ScorepInstrumenter 2 | import scorep._bindings 3 | 4 | 5 | class ScorepCProfile(scorep._bindings.CInstrumenter, ScorepInstrumenter): 6 | def __init__(self, enable_instrumenter): 7 | scorep._bindings.CInstrumenter.__init__(self, interface='Profile') 8 | ScorepInstrumenter.__init__(self, enable_instrumenter) 9 | -------------------------------------------------------------------------------- /benchmark/compare_commits.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wd=`pwd` 4 | root_dir=`realpath "$wd/../"` 5 | 6 | echo $wd 7 | echo $root_dir 8 | 9 | function benchmark_branch { 10 | cd $root_dir 11 | git checkout $1 12 | head=`git rev-parse --short HEAD` 13 | pip install . 14 | cd $wd 15 | python benchmark.py -o result-$1-$head.pkl 16 | } 17 | 18 | sleep 5 19 | benchmark_branch $1 20 | sleep 5 21 | benchmark_branch $2 22 | 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: bionic 2 | 3 | language: python 4 | cache: pip 5 | python: 6 | - "2.7" 7 | - "3.6" 8 | - "3.7" 9 | 10 | addons: 11 | apt: 12 | sources: 13 | - sourceline: "ppa:andreasgocht/scorep" 14 | packages: 15 | - scorep 16 | - openmpi-common 17 | - openmpi-bin 18 | - libopenmpi-dev 19 | 20 | install: 21 | - pip install mpi4py numpy pytest 22 | 23 | script: 24 | - pip install ./ && cd test && pytest 25 | -------------------------------------------------------------------------------- /test/cases/numpy_dot_large.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import numpy.linalg 3 | import scorep.instrumenter 4 | 5 | with scorep.instrumenter.enable(): 6 | a = [] 7 | b = [] 8 | 9 | for i in range(1000): 10 | a.append([]) 11 | b.append([]) 12 | for j in range(1000): 13 | a[i].append(i * j) 14 | b[i].append(i * j) 15 | 16 | c = numpy.dot(a, b) 17 | c = numpy.matmul(a, c) 18 | q, r = numpy.linalg.qr(c) 19 | print(q, r) 20 | -------------------------------------------------------------------------------- /benchmark/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #SBATCH --time=04:00:00 3 | #SBATCH --exclusive 4 | #SBATCH -p haswell 5 | #SBATCH -A p_readex 6 | #SBATCH -N 1 7 | #SBATCH --ntasks-per-node=1 8 | #SBATCH -c 1 9 | #SBATCH --comment=no_monitoring 10 | #SBATCH --job-name benchmark_python 11 | 12 | module load Python/3.8.6-GCCcore-10.2.0 13 | module load Score-P/7.0-gompic-2020b 14 | 15 | env_dir=~/virtenv/p-3.8.6-GCCcore-10.2.0-scorep-7.0-gompic-2020b/ 16 | 17 | if [ ! -d $env_dir ] 18 | then 19 | echo "Please create virtual env under: $env_dir" 20 | exit -1 21 | fi 22 | 23 | source $env_dir/bin/activate 24 | 25 | srun compare_commits.sh master faster 26 | -------------------------------------------------------------------------------- /test/cases/reload.py: -------------------------------------------------------------------------------- 1 | import reload_test 2 | import importlib 3 | import os 4 | 5 | data1 = """ 6 | def foo(): 7 | print("foo1") 8 | """ 9 | 10 | data2 = """ 11 | def foo(arg): 12 | print(arg) 13 | def bar(): 14 | print("bar") 15 | """ 16 | 17 | 18 | with open("reload_test.py", "w") as f: 19 | f.write(data1) 20 | 21 | reload_test.foo() 22 | reload_test.foo() 23 | 24 | importlib.reload(reload_test) 25 | reload_test.foo() 26 | 27 | 28 | with open("reload_test.py", "w") as f: 29 | f.write(data2) 30 | 31 | importlib.reload(reload_test) 32 | reload_test.foo("foo2") 33 | reload_test.bar() 34 | 35 | os.remove("reload_test.py") 36 | -------------------------------------------------------------------------------- /test/cases/use_threads.py: -------------------------------------------------------------------------------- 1 | import random 2 | import threading 3 | import time 4 | import instrumentation2 5 | 6 | 7 | lock = threading.Lock() 8 | 9 | 10 | def worker(id, func): 11 | with lock: 12 | print("Thread %s started" % id) 13 | # Use a random delay to add non-determinism to the output 14 | time.sleep(random.uniform(0.01, 0.9)) 15 | func() 16 | 17 | 18 | def foo(): 19 | print("hello world") 20 | t1 = threading.Thread(target=worker, args=(0, instrumentation2.bar)) 21 | t2 = threading.Thread(target=worker, args=(1, instrumentation2.baz)) 22 | t1.start() 23 | t2.start() 24 | t1.join() 25 | t2.join() 26 | 27 | 28 | foo() 29 | -------------------------------------------------------------------------------- /test/cases/user_regions.py: -------------------------------------------------------------------------------- 1 | import scorep.user 2 | import scorep.instrumenter 3 | 4 | 5 | def foo(): 6 | scorep.user.region_begin("test_region") 7 | print("hello world") 8 | scorep.user.region_end("test_region") 9 | 10 | 11 | def foo2(): 12 | with scorep.user.region("test_region_2"): 13 | print("hello world") 14 | 15 | 16 | @scorep.user.region() 17 | def foo3(): 18 | print("hello world3") 19 | 20 | 21 | @scorep.user.region("test_region_4") 22 | def foo4(): 23 | print("hello world4") 24 | 25 | 26 | foo() 27 | foo2() 28 | with scorep.instrumenter.enable(): 29 | foo3() 30 | with scorep.instrumenter.disable(): 31 | foo3() 32 | foo4() 33 | -------------------------------------------------------------------------------- /test/cases/classes.py: -------------------------------------------------------------------------------- 1 | import scorep.user 2 | import scorep.instrumenter 3 | 4 | 5 | class TestClass: 6 | def foo(self): 7 | print("foo") 8 | 9 | def doo(arg): 10 | print("doo") 11 | arg.foo() 12 | 13 | 14 | class TestClass2: 15 | @scorep.user.region() 16 | def foo(self): 17 | print("foo-2") 18 | 19 | 20 | def foo(): 21 | print("bar") 22 | 23 | def doo(arg): 24 | print("asdgh") 25 | doo("test") 26 | 27 | 28 | if __name__ == "__main__": 29 | t = TestClass() 30 | t2 = TestClass2() 31 | 32 | t2.foo() 33 | t.doo() 34 | foo() 35 | 36 | with scorep.instrumenter.disable(): 37 | t2.foo() 38 | -------------------------------------------------------------------------------- /test/test_subsystem.py: -------------------------------------------------------------------------------- 1 | import os 2 | from scorep import subsystem 3 | 4 | 5 | def test_reset_preload(monkeypatch): 6 | monkeypatch.setenv('LD_PRELOAD', '/some/value') 7 | # Nothing changes if the var is not present 8 | monkeypatch.delenv('SCOREP_LD_PRELOAD_BACKUP', raising=False) 9 | subsystem.reset_preload() 10 | assert os.environ['LD_PRELOAD'] == '/some/value' 11 | 12 | # Variable set -> Update 13 | monkeypatch.setenv('SCOREP_LD_PRELOAD_BACKUP', '/new/value') 14 | subsystem.reset_preload() 15 | assert os.environ['LD_PRELOAD'] == '/new/value' 16 | 17 | # Variable empty -> remove 18 | monkeypatch.setenv('SCOREP_LD_PRELOAD_BACKUP', '') 19 | subsystem.reset_preload() 20 | assert 'LD_PRELOAD' not in os.environ 21 | -------------------------------------------------------------------------------- /test/cases/mpi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import scorep 4 | from mpi4py import MPI 5 | import numpy as np 6 | import mpi4py 7 | import instrumentation2 8 | mpi4py.rc.thread_level = "funneled" 9 | 10 | scorep.instrumenter.register() 11 | 12 | comm = mpi4py.MPI.COMM_WORLD 13 | 14 | comm.Barrier() 15 | 16 | # Prepare a vector of N=5 elements to be broadcasted... 17 | N = 5 18 | if comm.rank == 0: 19 | A = np.arange(N, dtype=np.float64) # rank 0 has proper data 20 | instrumentation2.baz() 21 | else: 22 | instrumentation2.bar() 23 | A = np.empty(N, dtype=np.float64) # all other just an empty array 24 | 25 | # Broadcast A from rank 0 to everybody 26 | comm.Bcast([A, MPI.DOUBLE]) 27 | 28 | # Everybody should now have the same... 29 | print("[%02d] %s" % (comm.rank, A)) 30 | -------------------------------------------------------------------------------- /.github/workflows/static_analysis.yml: -------------------------------------------------------------------------------- 1 | name: Static analysis 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | Cpp: 6 | runs-on: ubuntu-22.04 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: Formatting 10 | uses: Flamefire/clang-format-lint-action@master 11 | with: 12 | source: src 13 | exclude: "src/scorepy/pythoncapi_compat.h" 14 | clangFormatVersion: 9 15 | 16 | Python: 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions/setup-python@v4 21 | with: 22 | python-version: 3.8 23 | - name: Install Python packages 24 | run: | 25 | pip install --upgrade pip 26 | pip install --upgrade flake8 27 | - name: Run flake8 28 | run: flake8 benchmark scorep test 29 | -------------------------------------------------------------------------------- /test/cases/miniasync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | 5 | work_size = 10000000 6 | 7 | 8 | def actual_work(i): 9 | return sum(range(i)) 10 | 11 | 12 | async def work1(): 13 | for i in range(5): 14 | print("work1 ", actual_work(i * work_size)) 15 | await asyncio.sleep(1) 16 | 17 | 18 | async def work2(): 19 | for i in range(5): 20 | print("work2 ", actual_work(i * work_size)) 21 | await asyncio.sleep(1) 22 | 23 | 24 | async def amain(): 25 | await asyncio.gather( 26 | work1(), 27 | work2(), 28 | asyncio.get_event_loop().getaddrinfo("tu-dresden.de", 80), 29 | asyncio.get_event_loop().getaddrinfo("www.tu-dresden.de", 80), 30 | ) 31 | 32 | 33 | def main(): 34 | asyncio.run(amain()) 35 | 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /test/test_helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import scorep.helper 3 | 4 | 5 | def test_add_to_ld_library_path(monkeypatch): 6 | # Previous value: Empty 7 | monkeypatch.setenv('LD_LIBRARY_PATH', '') 8 | scorep.helper.add_to_ld_library_path('/my/path') 9 | assert os.environ['LD_LIBRARY_PATH'] == '/my/path' 10 | # Don't add duplicates 11 | scorep.helper.add_to_ld_library_path('/my/path') 12 | assert os.environ['LD_LIBRARY_PATH'] == '/my/path' 13 | # Prepend 14 | scorep.helper.add_to_ld_library_path('/new/folder') 15 | assert os.environ['LD_LIBRARY_PATH'] == '/new/folder:/my/path' 16 | # also no duplicates: 17 | for p in ('/my/path', '/new/folder'): 18 | scorep.helper.add_to_ld_library_path(p) 19 | assert os.environ['LD_LIBRARY_PATH'] == '/new/folder:/my/path' 20 | 21 | # Add parent folder of existing one 22 | monkeypatch.setenv('LD_LIBRARY_PATH', '/some/folder:/parent/sub') 23 | scorep.helper.add_to_ld_library_path('/parent') 24 | assert os.environ['LD_LIBRARY_PATH'] == '/parent:/some/folder:/parent/sub' 25 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | import utils 2 | 3 | 4 | def pytest_assertrepr_compare(op, left, right): 5 | if isinstance(left, utils.OTF2_Region) and isinstance(right, utils.OTF2_Trace): 6 | if op == "in": 7 | output = ["Region \"{}\" ENTER or LEAVE not found in trace:".format(left)] 8 | for line in str(right).split("\n"): 9 | output.append("\t" + line) 10 | return output 11 | elif op == "not in": 12 | output = ["Unexpected region \"{}\" ENTER or LEAVE found in trace:".format(left)] 13 | for line in str(right).split("\n"): 14 | output.append("\t" + line) 15 | return output 16 | elif isinstance(left, utils.OTF2_Parameter) and isinstance(right, utils.OTF2_Trace) and op == "in": 17 | output = ["Parameter \"{parameter}\" with Value \"{value}\" not found in trace:".format( 18 | parameter=left.parameter, 19 | value=left.value)] 20 | for line in str(right).split("\n"): 21 | output.append("\t" + line) 22 | return output 23 | -------------------------------------------------------------------------------- /scorep/_instrumenters/utils.py: -------------------------------------------------------------------------------- 1 | from scorep._bindings import abspath 2 | from scorep.instrumenter import has_c_instrumenter 3 | 4 | 5 | def get_module_name(frame): 6 | """Get the name of the module the given frame resides in""" 7 | modulename = frame.f_globals.get("__name__", None) 8 | if modulename is None: 9 | # this is a NUMPY special situation, see NEP-18, and Score-P Issue 10 | # issues #63 11 | if frame.f_code.co_filename == "<__array_function__ internals>": 12 | modulename = "numpy.__array_function__" 13 | else: 14 | modulename = "unkown" 15 | typeobject = frame.f_locals.get("self", None) 16 | if typeobject is not None: 17 | if has_c_instrumenter(): 18 | return ".".join([modulename, type(typeobject).__name__]) 19 | else: 20 | return ".".join([modulename, typeobject.__class__.__name__]) 21 | return modulename 22 | 23 | 24 | def get_file_name(frame): 25 | """Get the full path to the file the given frame resides in""" 26 | file_name = frame.f_code.co_filename 27 | if file_name is not None: 28 | full_file_name = abspath(file_name) 29 | else: 30 | full_file_name = "None" 31 | return full_file_name 32 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publishing on PyPI 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | publish: 8 | name: Publish Python 🐍 distributions 📦 to PyPI 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | 14 | - name: Set up Python 3.9 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: 3.9 18 | - name: Check Version 19 | run: test ${{ github.event.release.tag_name }} = `python -c "import scorep._version; print('v'+scorep._version.__version__)"` 20 | 21 | - name: Add Score-P repo 22 | run: sudo add-apt-repository ppa:score-p/releases 23 | 24 | - name: Install Score-P 25 | run: sudo apt-get -y install scorep 26 | 27 | - name: Setup environment 28 | run: echo "$HOME/scorep/bin" >> $GITHUB_PATH 29 | 30 | - name: Install pypa/build 31 | run: >- 32 | python -m 33 | pip install build --user 34 | 35 | - name: Build a source tarball 36 | run: >- 37 | python -m 38 | build --sdist --outdir dist/ . 39 | 40 | - name: Publish distribution 📦 to PyPI 41 | uses: pypa/gh-action-pypi-publish@release/v1 42 | with: 43 | password: ${{ secrets.PYPI_API_TOKEN }} 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022-2023, 2 | Andreas Gocht-Zech 3 | 4 | Copyright 2017-2022, Technische Universitaet Dresden, Germany, all rights reserved. 5 | Author: Andreas Gocht-Zech 6 | 7 | portions copyright 2001, Autonomous Zones Industries, Inc., all rights... 8 | err... reserved and offered to the public under the terms of the 9 | Python 2.2 license. 10 | Author: Zooko O'Whielacronx 11 | http://zooko.com/ 12 | mailto:zooko@zooko.com 13 | 14 | Copyright 2000, Mojam Media, Inc., all rights reserved. 15 | Author: Skip Montanaro 16 | 17 | Copyright 1999, Bioreason, Inc., all rights reserved. 18 | Author: Andrew Dalke 19 | 20 | Copyright 1995-1997, Automatrix, Inc., all rights reserved. 21 | Author: Skip Montanaro 22 | 23 | Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved. 24 | 25 | Permission to use, copy, modify, and distribute this Python software and 26 | its associated documentation for any purpose without fee is hereby 27 | granted, provided that the above copyright notice appears in all copies, 28 | and that both that copyright notice and this permission notice appear in 29 | supporting documentation, and that the name of neither Automatrix, 30 | Bioreason, Mojam Media or TU Dresden be used in advertising or publicity 31 | pertaining to distribution of the software without specific, written 32 | prior permission. 33 | -------------------------------------------------------------------------------- /src/scorepy/cInstrumenter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace scorepy 7 | { 8 | /// Interface to Python used to implement an instrumenter 9 | /// See sys.settrace/setprofile 10 | enum class InstrumenterInterface 11 | { 12 | Profile, 13 | Trace 14 | }; 15 | 16 | struct CInstrumenter 17 | { 18 | PyObject_HEAD; 19 | InstrumenterInterface interface; 20 | PyObject* threading_module; 21 | PyObject* threading_set_instrumenter; 22 | 23 | void init(InstrumenterInterface interface); 24 | void deinit(); 25 | void enable_instrumenter(); 26 | void disable_instrumenter(); 27 | 28 | /// Callback for when this object is called directly 29 | PyObject* operator()(PyFrameObject& frame, const char* what, PyObject* arg); 30 | 31 | /// These casts are valid as long as `PyObject_HEAD` is the first entry in this struct 32 | PyObject* to_PyObject() 33 | { 34 | return reinterpret_cast(this); 35 | } 36 | static CInstrumenter* from_PyObject(PyObject* o) 37 | { 38 | return reinterpret_cast(o); 39 | } 40 | 41 | private: 42 | /// Callback for Python trace/profile events. Return true for success 43 | bool on_event(PyFrameObject& frame, int what, PyObject* arg); 44 | }; 45 | 46 | } // namespace scorepy 47 | -------------------------------------------------------------------------------- /.github/workflows/unit_tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | on: [push, pull_request, workflow_dispatch] 3 | 4 | env: 5 | SCOREP_TIMER: clock_gettime # tsc causes warnings 6 | RDMAV_FORK_SAFE: 7 | IBV_FORK_SAFE: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", 'pypy-2.7', 'pypy-3.7', 'pypy-3.8', 'pypy-3.9', 'pypy-3.10', 'pypy-3.11'] 15 | fail-fast: false 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/cache@v3 20 | with: 21 | path: ~/.cache/pip 22 | key: ${{ runner.os }}-pip 23 | 24 | - name: Add Score-P repo 25 | run: sudo add-apt-repository ppa:score-p/releases 26 | 27 | - name: Install Score-P 28 | run: sudo apt-get -y install scorep 29 | 30 | - name: Setup environment 31 | run: echo "$HOME/scorep/bin" >> $GITHUB_PATH 32 | - name: set up Python 33 | uses: actions/setup-python@v4 34 | with: 35 | python-version: ${{matrix.python}} 36 | architecture: x64 37 | - name: Install Python packages 38 | run: | 39 | pip install --upgrade pip 40 | pip install --upgrade setuptools 41 | pip install numpy mpi4py pytest 42 | 43 | - name: Build python bindings 44 | run: pip install . 45 | - name: Run tests 46 | working-directory: test 47 | run: pytest -vv 48 | -------------------------------------------------------------------------------- /benchmark/benchmark_helper.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import shutil 4 | import sys 5 | import time 6 | import tempfile 7 | 8 | 9 | class BenchmarkEnv(): 10 | def __init__(self, repetitions=10): 11 | self.env = os.environ.copy() 12 | self.env["SCOREP_ENABLE_PROFILING"] = "false" 13 | self.env["SCOREP_ENABLE_TRACING"] = "false" 14 | self.env["SCOREP_PROFILING_MAX_CALLPATH_DEPTH"] = "98" 15 | self.env["SCOREP_TOTAL_MEMORY"] = "3G" 16 | self.exp_dir = tempfile.mkdtemp(prefix="benchmark_dir_") 17 | self.repetitions = repetitions 18 | 19 | def __del__(self): 20 | shutil.rmtree( 21 | self.exp_dir, 22 | ignore_errors=True) 23 | 24 | def call(self, script, ops=[], scorep_settings=[]): 25 | self.env["SCOREP_EXPERIMENT_DIRECTORY"] = self.exp_dir + \ 26 | "/{}-{}-{}".format(script, ops, scorep_settings) 27 | 28 | arguments = [sys.executable] 29 | arguments.extend(scorep_settings) 30 | arguments.append(script) 31 | arguments.extend(ops) 32 | 33 | runtimes = [] 34 | for _ in range(self.repetitions): 35 | begin = time.time() 36 | out = subprocess.run( 37 | arguments, 38 | env=self.env) 39 | end = time.time() 40 | assert out.returncode == 0 41 | 42 | runtime = end - begin 43 | runtimes.append(runtime) 44 | 45 | return runtimes 46 | -------------------------------------------------------------------------------- /scorep/_instrumenters/dummy.py: -------------------------------------------------------------------------------- 1 | __all__ = ['ScorepDummy'] 2 | 3 | import scorep._instrumenters.base_instrumenter as base_instrumenter 4 | 5 | 6 | class ScorepDummy(base_instrumenter.BaseInstrumenter): 7 | def __init__(self, enable_instrumenter=True): 8 | pass 9 | 10 | def register(self): 11 | pass 12 | 13 | def unregister(self): 14 | pass 15 | 16 | def get_registered(self): 17 | return None 18 | 19 | def run(self, cmd, globals=None, locals=None): 20 | if globals is None: 21 | globals = {} 22 | if locals is None: 23 | locals = {} 24 | exec(cmd, globals, locals) 25 | 26 | def try_region_begin(self, code_object): 27 | pass 28 | 29 | def region_begin(self, module_name, function_name, file_name, line_number, code_object=None): 30 | pass 31 | 32 | def try_region_end(self, code_object): 33 | pass 34 | 35 | def region_end(self, module_name, function_name, code_object=None): 36 | pass 37 | 38 | def rewind_begin(self, name, file_name=None, line_number=None): 39 | pass 40 | 41 | def rewind_end(self, name, value): 42 | pass 43 | 44 | def user_enable_recording(self): 45 | pass 46 | 47 | def user_disable_recording(self): 48 | pass 49 | 50 | def user_parameter_int(self, name, val): 51 | pass 52 | 53 | def user_parameter_uint(self, name, val): 54 | pass 55 | 56 | def user_parameter_string(self, name, string): 57 | pass 58 | 59 | def force_finalize(self): 60 | pass 61 | 62 | def reregister_exit_handler(self): 63 | pass 64 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: -4 4 | AlignEscapedNewlinesLeft: false 5 | AlignTrailingComments: true 6 | AllowAllParametersOfDeclarationOnNextLine: true 7 | AllowShortIfStatementsOnASingleLine: false 8 | AllowShortLoopsOnASingleLine: false 9 | AllowShortFunctionsOnASingleLine: false 10 | AlwaysBreakTemplateDeclarations: true 11 | AlwaysBreakBeforeMultilineStrings: false 12 | BinPackParameters: true 13 | BreakBeforeBinaryOperators: false 14 | BreakBeforeBraces: Allman 15 | BreakBeforeTernaryOperators: false 16 | BreakConstructorInitializersBeforeComma: false 17 | ColumnLimit: 100 18 | ConstructorInitializerIndentWidth: 0 19 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 20 | Cpp11BracedListStyle: false 21 | DerivePointerBinding: false 22 | ExperimentalAutoDetectBinPacking: false 23 | IndentCaseLabels: false 24 | IndentWidth: 4 25 | IndentFunctionDeclarationAfterType: false 26 | MaxEmptyLinesToKeep: 1 27 | NamespaceIndentation: Inner 28 | 29 | PointerBindsToType: true 30 | 31 | PenaltyBreakBeforeFirstCallParameter: 19 32 | PenaltyBreakComment: 60 33 | PenaltyBreakString: 1000 34 | PenaltyBreakFirstLessLess: 120 35 | PenaltyExcessCharacter: 1000000 36 | PenaltyReturnTypeOnItsOwnLine: 60 37 | 38 | 39 | SpaceBeforeAssignmentOperators: true 40 | SpaceInEmptyParentheses: false 41 | SpacesBeforeTrailingComments: 1 42 | 43 | 44 | Standard: Cpp11 45 | TabWidth: 4 46 | UseTab: Never 47 | 48 | SpacesInParentheses: false 49 | SpacesInAngles: false 50 | SpaceInEmptyParentheses: false 51 | SpacesInCStyleCastParentheses: false 52 | SpaceAfterControlStatementKeyword: true 53 | SpaceBeforeAssignmentOperators: true 54 | ContinuationIndentWidth: 4 55 | ... 56 | -------------------------------------------------------------------------------- /scorep/_instrumenters/base_instrumenter.py: -------------------------------------------------------------------------------- 1 | __all__ = ['BaseInstrumenter'] 2 | 3 | import abc 4 | import sys 5 | 6 | 7 | if sys.version_info >= (3, 4): 8 | class _BaseInstrumenter(abc.ABC): 9 | pass 10 | else: 11 | class _BaseInstrumenter(): 12 | __metaclass__ = abc.ABCMeta 13 | 14 | 15 | class BaseInstrumenter(_BaseInstrumenter): 16 | @abc.abstractmethod 17 | def register(self): 18 | pass 19 | 20 | @abc.abstractmethod 21 | def unregister(self): 22 | pass 23 | 24 | @abc.abstractmethod 25 | def get_registered(self): 26 | return None 27 | 28 | @abc.abstractmethod 29 | def run(self, cmd, globals=None, locals=None): 30 | pass 31 | 32 | @abc.abstractmethod 33 | def region_begin(self, module_name, function_name, file_name, line_number, code_object): 34 | pass 35 | 36 | @abc.abstractmethod 37 | def region_end(self, module_name, function_name, code_object): 38 | pass 39 | 40 | @abc.abstractmethod 41 | def rewind_begin(self, name, file_name=None, line_number=None): 42 | pass 43 | 44 | @abc.abstractmethod 45 | def rewind_end(self, name, value): 46 | pass 47 | 48 | @abc.abstractmethod 49 | def user_enable_recording(self): 50 | pass 51 | 52 | @abc.abstractmethod 53 | def user_disable_recording(self): 54 | pass 55 | 56 | @abc.abstractmethod 57 | def user_parameter_int(self, name, val): 58 | pass 59 | 60 | @abc.abstractmethod 61 | def user_parameter_uint(self, name, val): 62 | pass 63 | 64 | @abc.abstractmethod 65 | def user_parameter_string(self, name, string): 66 | pass 67 | 68 | @abc.abstractmethod 69 | def force_finalize(self): 70 | pass 71 | 72 | @abc.abstractmethod 73 | def reregister_exit_handler(self): 74 | pass 75 | -------------------------------------------------------------------------------- /src/scorep_bindings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "classes.hpp" 6 | #include "methods.hpp" 7 | 8 | #if PY_VERSION_HEX < 0x03000000 9 | PyMODINIT_FUNC init_bindings(void) 10 | { 11 | PyObject* m; 12 | #if SCOREPY_ENABLE_CINSTRUMENTER 13 | static PyTypeObject ctracerType = scorepy::getCInstrumenterType(); 14 | if (PyType_Ready(&ctracerType) < 0) 15 | return; 16 | #endif 17 | m = Py_InitModule("_bindings", scorepy::getMethodTable()); 18 | #if SCOREPY_ENABLE_CINSTRUMENTER 19 | Py_INCREF(&ctracerType); 20 | PyModule_AddObject(m, "CInstrumenter", (PyObject*)&ctracerType); 21 | #endif 22 | } 23 | #else /*python 3*/ 24 | static struct PyModuleDef scorepmodule = { PyModuleDef_HEAD_INIT, "_bindings", /* name of module */ 25 | NULL, /* module documentation, may be NULL */ 26 | -1, /* size of per-interpreter state of the module, 27 | or -1 if the module keeps state in global 28 | variables. */ 29 | scorepy::getMethodTable() }; 30 | PyMODINIT_FUNC PyInit__bindings(void) 31 | { 32 | #if SCOREPY_ENABLE_CINSTRUMENTER 33 | auto* ctracerType = &scorepy::getCInstrumenterType(); 34 | if (PyType_Ready(ctracerType) < 0) 35 | return nullptr; 36 | #endif 37 | 38 | auto* m = PyModule_Create(&scorepmodule); 39 | if (!m) 40 | { 41 | return nullptr; 42 | } 43 | 44 | #if SCOREPY_ENABLE_CINSTRUMENTER 45 | Py_INCREF(ctracerType); 46 | if (PyModule_AddObject(m, "CInstrumenter", (PyObject*)ctracerType) < 0) 47 | { 48 | Py_DECREF(ctracerType); 49 | Py_DECREF(m); 50 | return nullptr; 51 | } 52 | #endif 53 | 54 | return m; 55 | } 56 | #endif /*python 3*/ 57 | -------------------------------------------------------------------------------- /scorep/_instrumenters/scorep_profile.py: -------------------------------------------------------------------------------- 1 | __all__ = ['ScorepProfile'] 2 | 3 | import sys 4 | from scorep._instrumenters.utils import get_module_name 5 | from scorep._instrumenters.scorep_instrumenter import ScorepInstrumenter 6 | import scorep._bindings 7 | 8 | try: 9 | import threading 10 | except ImportError: 11 | _setprofile = sys.setprofile 12 | 13 | def _unsetprofile(): 14 | sys.setprofile(None) 15 | 16 | else: 17 | def _setprofile(func): 18 | threading.setprofile(func) 19 | sys.setprofile(func) 20 | 21 | def _unsetprofile(): 22 | sys.setprofile(None) 23 | threading.setprofile(None) 24 | 25 | 26 | class ScorepProfile(ScorepInstrumenter): 27 | def _enable_instrumenter(self): 28 | _setprofile(self._globaltrace) 29 | 30 | def _disable_instrumenter(self): 31 | _unsetprofile() 32 | 33 | def _globaltrace(self, frame, why, arg): 34 | """Handler for call events. 35 | 36 | If the code block being entered is to be ignored, returns `None', 37 | else returns self.localtrace. 38 | """ 39 | if why == 'call': 40 | code = frame.f_code 41 | if not scorep._bindings.try_region_begin(code): 42 | if not code.co_name == "_unsetprofile": 43 | modulename = get_module_name(frame) 44 | if not modulename[:6] == "scorep": 45 | file_name = code.co_filename 46 | line_number = code.co_firstlineno 47 | scorep._bindings.region_begin(modulename, code.co_name, file_name, line_number, code) 48 | elif why == 'return': 49 | code = frame.f_code 50 | if not scorep._bindings.try_region_end(code): 51 | if not code.co_name == "_unsetprofile": 52 | modulename = get_module_name(frame) 53 | if not modulename[:6] == "scorep": 54 | scorep._bindings.region_end(modulename, code.co_name, code) 55 | -------------------------------------------------------------------------------- /scorep/_instrumenters/scorep_trace.py: -------------------------------------------------------------------------------- 1 | __all__ = ['ScorepTrace'] 2 | 3 | import sys 4 | from scorep._instrumenters.utils import get_module_name 5 | from scorep._instrumenters.scorep_instrumenter import ScorepInstrumenter 6 | import scorep._bindings 7 | 8 | try: 9 | import threading 10 | except ImportError: 11 | _settrace = sys.settrace 12 | 13 | def _unsettrace(): 14 | sys.settrace(None) 15 | 16 | else: 17 | def _settrace(func): 18 | threading.settrace(func) 19 | sys.settrace(func) 20 | 21 | def _unsettrace(): 22 | sys.settrace(None) 23 | threading.settrace(None) 24 | 25 | 26 | class ScorepTrace(ScorepInstrumenter): 27 | def _enable_instrumenter(self): 28 | _settrace(self._globaltrace) 29 | 30 | def _disable_instrumenter(self): 31 | _unsettrace() 32 | 33 | def _globaltrace(self, frame, why, arg): 34 | """Handler for call events. 35 | @return self.localtrace or None 36 | """ 37 | if why == 'call': 38 | code = frame.f_code 39 | if not scorep._bindings.try_region_begin(code): 40 | if not code.co_name == "_unsetprofile": 41 | modulename = get_module_name(frame) 42 | if not modulename[:6] == "scorep": 43 | full_file_name = code.co_filename 44 | line_number = code.co_firstlineno 45 | scorep._bindings.region_begin(modulename, code.co_name, full_file_name, line_number, code) 46 | return self._localtrace 47 | return None 48 | 49 | def _localtrace(self, frame, why, arg): 50 | if why == 'return': 51 | code = frame.f_code 52 | if not scorep._bindings.try_region_end(code): 53 | if not code.co_name == "_unsetprofile": 54 | modulename = get_module_name(frame) 55 | if not modulename[:6] == "scorep": 56 | scorep._bindings.region_end(modulename, code.co_name, code) 57 | return self._localtrace 58 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Developing 2 | We appreciate any contributions to the Score-P Python Bindings. 3 | However, there are a few policies we agreed on and which are relevant for contributions. 4 | These are detailed below. 5 | 6 | ## Formatting / Codestyle 7 | 8 | Readable and consistent code makes it easy to understand your changes. 9 | Therefore the CI system has checks using `clang-format-9` and `flake8` in place. 10 | Please make sure that these test pass, when making a Pull Request. 11 | These tests will tell you the issues and often also how to fix them. 12 | Prior to opening a Pull Request you can use the provided `.flake8` and `.clang-format` files, to check your code locally and run `clang-format-9` or `autopep8` to fix most of them automatically. 13 | 14 | ## Build system 15 | 16 | The official way to build and install this module is using `pip`. 17 | Please make sure that all changes, you introduce, work with `pip install .`. 18 | 19 | However, you might have noticed that there is a CMake-based build system in place as well. 20 | We do not support this build system, and it might be outdated or buggy. 21 | Although, we do not actively maintain the CMake build system, and will not help you fix issues related to it, Pull Requests against it might be accepted. 22 | 23 | You might find this build system helpful for development, especially if you are doing C/C++ things: 24 | * Include paths for C++ are correctly searched for and set up for use by IDEs or other tools. For example Visual Studio Code works out of the box, given the appropriate extensions (C++, Python, CMake) are installed. 25 | * A folder `site-packages` is created in the build folder where the C/C++ extension module and the scorep module are copied to on each build (e.g. `make`-call). Hence it is possible to add that folder to the PYTHONPATH environment variable, build the project and start debugging or execute the tests in test. 26 | * A `test` target exists which can be run to execute all tests. 27 | 28 | Please note, that changes to the Python source files are not reflected in the build folder unless a build is executed. 29 | Also, if you delete Python files, we recommended to clear and recreate the build folder. 30 | -------------------------------------------------------------------------------- /test/test_pathUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "scorepy/pathUtils.hpp" 2 | #include 3 | #include 4 | 5 | #define TEST(condition) \ 6 | if (!(condition)) \ 7 | throw std::runtime_error(std::string("Test ") + #condition + " failed at " + __FILE__ + ":" + \ 8 | std::to_string(__LINE__)) 9 | 10 | int main() 11 | { 12 | using scorepy::abspath; 13 | // Multiple slashes at start collapsed to 1 14 | TEST(abspath("/abc") == "/abc"); 15 | TEST(abspath("//abc") == "//abc"); 16 | TEST(abspath("///abc") == "/abc"); 17 | TEST(abspath("////abc") == "/abc"); 18 | // Trailing slashes and multiple slashes removed 19 | TEST(abspath("/abc/") == "/abc"); 20 | TEST(abspath("/abc//") == "/abc"); 21 | TEST(abspath("/abc//de") == "/abc/de"); 22 | TEST(abspath("/abc//de///fg") == "/abc/de/fg"); 23 | TEST(abspath("/abc//de///fg/") == "/abc/de/fg"); 24 | TEST(abspath("/abc//de///fg////") == "/abc/de/fg"); 25 | TEST(abspath("//abc/") == "//abc"); 26 | TEST(abspath("//abc//") == "//abc"); 27 | TEST(abspath("//abc//de") == "//abc/de"); 28 | TEST(abspath("//abc//de///fg") == "//abc/de/fg"); 29 | TEST(abspath("//abc//de///fg/") == "//abc/de/fg"); 30 | TEST(abspath("//abc//de///fg////") == "//abc/de/fg"); 31 | // Single dots removed 32 | TEST(abspath("/./abc/./defgh/./ijkl/.") == "/abc/defgh/ijkl"); 33 | TEST(abspath("/./abc././def.gh/./ijkl././.mn/.") == "/abc./def.gh/ijkl./.mn"); 34 | // Going up 1 level removes prior folder 35 | TEST(abspath("/abc/..") == "/"); 36 | TEST(abspath("//abc/..") == "//"); 37 | TEST(abspath("///abc/..") == "/"); 38 | TEST(abspath("/abc/../de") == "/de"); 39 | TEST(abspath("//abc/../de") == "//de"); 40 | TEST(abspath("///abc/../de") == "/de"); 41 | TEST(abspath("/abc/de/../fg") == "/abc/fg"); 42 | TEST(abspath("/abc/de/../../fg") == "/fg"); 43 | TEST(abspath("/abc/de../../fg") == "/abc/fg"); 44 | TEST(abspath("/abc../de/../fg") == "/abc../fg"); 45 | // Going up from root does nothing 46 | TEST(abspath("/../ab") == "/ab"); 47 | TEST(abspath("//../ab") == "//ab"); 48 | TEST(abspath("///../ab") == "/ab"); 49 | TEST(abspath("/abc/defgh/../../ijkl") == "/ijkl"); 50 | TEST(abspath("//abc/defgh/../../ijkl") == "//ijkl"); 51 | TEST(abspath("///abc/defgh/../../ijkl") == "/ijkl"); 52 | } 53 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(scorep_bindings) 3 | 4 | option(DEV_MODE "Set this to ON to use CMake. However: NO SUPPORT IS PROVIDED, PIP IS THE SUPPORTED INSTALL MODE" OFF) 5 | 6 | if(NOT DEV_MODE) 7 | message(SEND_ERROR 8 | "CMake is only useful to develop this project, no support is provided." 9 | "To install the module properly use pip. See the Readme for details." 10 | "To override this you might set DEV_MODE to ON" 11 | "However: AGAIN NO SUPPORT IS PROVIDED, PIP IS THE SUPPORTED INSTALL MODE") 12 | endif() 13 | 14 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 15 | 16 | find_package(Scorep REQUIRED) 17 | find_package(Python REQUIRED COMPONENTS Interpreter Development) 18 | 19 | Python_add_library(_bindings 20 | src/methods.cpp src/scorep_bindings.cpp src/scorepy/events.cpp 21 | ) 22 | if(Python_VERSION_MAJOR GREATER_EQUAL 3 AND NOT Python_INTERPRETER_ID STREQUAL "PyPy") 23 | target_sources(_bindings PRIVATE 24 | src/classes.cpp 25 | src/scorepy/cInstrumenter.cpp 26 | src/scorepy/pathUtils.cpp 27 | src/scorepy/pythonHelpers.cpp 28 | ) 29 | target_compile_definitions(_bindings PRIVATE SCOREPY_ENABLE_CINSTRUMENTER=1) 30 | else() 31 | target_compile_definitions(_bindings PRIVATE SCOREPY_ENABLE_CINSTRUMENTER=0) 32 | endif() 33 | target_link_libraries(_bindings PRIVATE Scorep::Plugin) 34 | target_compile_features(_bindings PRIVATE cxx_std_11) 35 | target_compile_definitions(_bindings PRIVATE PY_SSIZE_T_CLEAN) 36 | target_include_directories(_bindings PRIVATE src) 37 | 38 | set_target_properties(_bindings PROPERTIES 39 | LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/site-packages/scorep 40 | ) 41 | add_custom_target(ScorepModule ALL 42 | ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/scorep $ 43 | COMMENT "Copying module files to build tree" 44 | ) 45 | 46 | enable_testing() 47 | add_test(NAME ScorepPythonTests 48 | COMMAND Python::Interpreter -m pytest 49 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/test 50 | ) 51 | set(pythonPath ${CMAKE_CURRENT_BINARY_DIR}/site-packages) 52 | if(ENV{PYTHONPATH}) 53 | string(PREPEND pythonPath "$ENV{PYTHONPATH}:") 54 | endif() 55 | set_tests_properties(ScorepPythonTests PROPERTIES ENVIRONMENT "PYTHONPATH=${pythonPath}") 56 | add_executable(CppTests test/test_pathUtils.cpp src/scorepy/pathUtils.cpp) 57 | target_include_directories(CppTests PRIVATE src) 58 | add_test(NAME CppTests COMMAND CppTests) 59 | 60 | set(INSTALL_DIR "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") 61 | 62 | install(DIRECTORY scorep DESTINATION ${INSTALL_DIR}) 63 | install(TARGETS _bindings DESTINATION ${INSTALL_DIR}/scorep) 64 | -------------------------------------------------------------------------------- /src/scorepy/events.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "compat.hpp" 10 | 11 | namespace scorepy 12 | { 13 | 14 | struct region_handle 15 | { 16 | constexpr region_handle() = default; 17 | ~region_handle() = default; 18 | constexpr bool operator==(const region_handle& other) 19 | { 20 | return this->value == other.value; 21 | } 22 | constexpr bool operator!=(const region_handle& other) 23 | { 24 | return this->value != other.value; 25 | } 26 | 27 | SCOREP_User_RegionHandle value = SCOREP_USER_INVALID_REGION; 28 | }; 29 | 30 | constexpr region_handle uninitialised_region_handle = region_handle(); 31 | 32 | /// Combine the arguments into a region name 33 | inline std::string make_region_name(std::string module_name, std::string_view name) 34 | { 35 | module_name += ':'; 36 | module_name += name; 37 | return std::move(module_name); 38 | } 39 | 40 | extern std::unordered_map regions; 41 | 42 | /** tries to enter a region. Return true on success 43 | * 44 | */ 45 | inline bool try_region_begin(compat::PyCodeObject* identifier) 46 | { 47 | auto it = regions.find(identifier); 48 | if (it != regions.end()) 49 | { 50 | SCOREP_User_RegionEnter(it->second.value); 51 | return true; 52 | } 53 | else 54 | { 55 | return false; 56 | } 57 | } 58 | 59 | void region_begin(std::string_view function_name, std::string module, std::string file_name, 60 | const std::uint64_t line_number, compat::PyCodeObject* identifier); 61 | void region_begin(std::string_view function_name, std::string module, std::string file_name, 62 | const std::uint64_t line_number); 63 | 64 | /** tries to end a region. Return true on success 65 | * 66 | */ 67 | inline bool try_region_end(compat::PyCodeObject* identifier) 68 | { 69 | auto it_region = regions.find(identifier); 70 | if (it_region != regions.end()) 71 | { 72 | SCOREP_User_RegionEnd(it_region->second.value); 73 | return true; 74 | } 75 | else 76 | { 77 | return false; 78 | } 79 | } 80 | 81 | void region_end(std::string_view function_name, std::string module, 82 | compat::PyCodeObject* identifier); 83 | void region_end(std::string_view function_name, std::string module); 84 | 85 | void region_end_error_handling(std::string region_name); 86 | 87 | void rewind_begin(std::string region_name, std::string file_name, std::uint64_t line_number); 88 | void rewind_end(std::string region_name, bool value); 89 | 90 | void parameter_int(std::string name, int64_t value); 91 | void parameter_uint(std::string name, uint64_t value); 92 | void parameter_string(std::string name, std::string value); 93 | } // namespace scorepy 94 | -------------------------------------------------------------------------------- /benchmark/compare.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import pickle 3 | import numpy 4 | 5 | parser = argparse.ArgumentParser(description='Compare two benchmarks.', 6 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 7 | parser.add_argument('left', help='First input for comparison') 8 | parser.add_argument('right', help='Second input for comparison') 9 | parser.add_argument('-s', help='short output', action='store_false') 10 | args = parser.parse_args() 11 | 12 | 13 | with open(args.left, "rb") as f: 14 | left = pickle.load(f) 15 | 16 | with open(args.right, "rb") as f: 17 | right = pickle.load(f) 18 | 19 | if left.keys() != right.keys(): 20 | raise ValueError("Different Experiments") 21 | 22 | experiment = right.keys() 23 | 24 | for exp in experiment: 25 | print("\nExperiment: {}".format(exp)) 26 | if left[exp].keys() != right[exp].keys(): 27 | raise ValueError("Different Instrumenters") 28 | instrumenters = left[exp].keys() 29 | 30 | for inst in instrumenters: 31 | print("\n\tInstrumenter: {}".format(inst)) 32 | if left[exp][inst].keys() != right[exp][inst].keys(): 33 | raise ValueError("Different Iterations") 34 | iterations = left[exp][inst].keys() 35 | Y_left = [] 36 | Y_right = [] 37 | X = [] 38 | for it in iterations: 39 | left_val = left[exp][inst][it] 40 | right_val = right[exp][inst][it] 41 | if len(left_val) != len(right_val): 42 | raise ValueError("Different Repetitons") 43 | 44 | Y_left.append(numpy.mean(left_val)) 45 | Y_right.append(numpy.mean(right_val)) 46 | X.append(numpy.full([1], it)) 47 | 48 | if args.s: 49 | print("\t\tInterations {}".format(it)) 50 | print("\t\tMean: {:>7.4f} s {:>7.4f} s".format(numpy.mean(left_val), numpy.mean(right_val))) 51 | print("\t\tMedian: {:>7.4f} s {:>7.4f} s".format( 52 | numpy.quantile(left_val, 0.50), numpy.quantile(right_val, 0.50))) 53 | print("\t\t5%: {:>7.4f} s {:>7.4f} s".format( 54 | numpy.quantile(left_val, 0.05), numpy.quantile(right_val, 0.05))) 55 | print("\t\t95%: {:>7.4f} s {:>7.4f} s".format( 56 | numpy.quantile(left_val, 0.95), numpy.quantile(right_val, 0.95))) 57 | Y_left = numpy.asarray(Y_left, dtype=float).flatten() 58 | Y_right = numpy.asarray(Y_right, dtype=float).flatten() 59 | X = numpy.asarray(X, dtype=float).flatten() 60 | 61 | cost_left = numpy.polyfit(X, Y_left, 1) 62 | cost_right = numpy.polyfit(X, Y_right, 1) 63 | 64 | if args.s: 65 | print("") 66 | print("\tSlope {:>7.4f} us {:>7.4f} us".format(cost_left[0] * 1e6, cost_right[0] * 1e6)) 67 | print("\tIntercept {:>7.4f} s {:>7.4f} s".format(cost_left[1], cost_right[1])) 68 | -------------------------------------------------------------------------------- /src/scorepy/pathUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "pathUtils.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace scorepy 7 | { 8 | static std::string getcwd() 9 | { 10 | std::string result; 11 | const char* cwd; 12 | constexpr size_t chunk_size = 512; 13 | do 14 | { 15 | const size_t new_size = result.size() + chunk_size; 16 | if (new_size > std::numeric_limits::max()) 17 | { 18 | cwd = nullptr; 19 | break; 20 | } 21 | result.resize(new_size); 22 | cwd = ::getcwd(&result.front(), result.size()); 23 | } while (!cwd && errno == ERANGE); 24 | if (cwd) 25 | result.resize(result.find('\0', result.size() - chunk_size)); 26 | else 27 | result.clear(); 28 | return std::move(result); 29 | } 30 | 31 | void normalize_path(std::string& path) 32 | { 33 | // 2 slashes are kept, 1 or more than 2 become 1 according to POSIX 34 | const size_t num_slashes = (path.find_first_not_of('/') == 2) ? 2 : 1; 35 | const size_t path_len = path.size(); 36 | size_t cur_out = num_slashes; 37 | for (size_t i = cur_out; i != path_len + 1; ++i) 38 | { 39 | if (i == path_len || path[i] == '/') 40 | { 41 | const char prior = path[cur_out - 1]; 42 | if (prior == '/') // Double slash -> ignore 43 | continue; 44 | if (prior == '.') 45 | { 46 | const char second_prior = path[cur_out - 2]; 47 | if (second_prior == '/') // '/./' 48 | { 49 | --cur_out; 50 | continue; 51 | } 52 | else if (second_prior == '.' && path[cur_out - 3] == '/') // '/../' 53 | { 54 | if (cur_out < 3 + num_slashes) // already behind root slash 55 | cur_out -= 2; 56 | else 57 | { 58 | const auto prior_slash = path.rfind('/', cur_out - 4); 59 | cur_out = prior_slash + 1; 60 | } 61 | continue; 62 | } 63 | } 64 | if (i == path_len) 65 | break; 66 | } 67 | path[cur_out++] = path[i]; 68 | } 69 | // Remove trailing slash 70 | if (cur_out > num_slashes && path[cur_out - 1] == '/') 71 | --cur_out; 72 | path.resize(cur_out); 73 | } 74 | 75 | std::string abspath(std::string_view input_path) 76 | { 77 | std::string result; 78 | if (input_path[0] != '/') 79 | { 80 | result = getcwd(); 81 | // On error exit 82 | if (result.empty()) 83 | return {}; 84 | // Prepend CWD 85 | result.append(1, '/').append(input_path); 86 | } 87 | else 88 | { 89 | result = input_path; 90 | } 91 | normalize_path(result); 92 | return std::move(result); 93 | } 94 | } // namespace scorepy 95 | -------------------------------------------------------------------------------- /src/scorepy/pythonHelpers.cpp: -------------------------------------------------------------------------------- 1 | #include "pythonHelpers.hpp" 2 | #include "compat.hpp" 3 | #include "pathUtils.hpp" 4 | #include "pythoncapi_compat.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace scorepy 12 | { 13 | 14 | static PyObject* get_self_from_frame(PyFrameObject* frame) 15 | { 16 | PyObject* self = nullptr; 17 | 18 | #if PY_VERSION_HEX >= 0x030C0000 // Python 3.12+ 19 | // Added in 3.12: directly fetch a local variable by name. 20 | self = PyFrame_GetVarString(frame, "self"); 21 | if (!self && PyErr_Occurred()) 22 | PyErr_Clear(); 23 | #else 24 | PyObject* locals = PyFrame_GetLocals(frame); // New reference 25 | if (locals) 26 | { 27 | PyObject* tmp = PyDict_GetItemString(locals, "self"); // Borrowed 28 | if (tmp) 29 | { 30 | Py_INCREF(tmp); 31 | self = tmp; 32 | } 33 | Py_DECREF(locals); 34 | } 35 | #endif 36 | return self; 37 | } 38 | 39 | std::string get_module_name(PyFrameObject& frame) 40 | { 41 | const char* self_name = nullptr; 42 | 43 | PyObject* self = get_self_from_frame(&frame); 44 | if (self) 45 | { 46 | PyTypeObject* type = Py_TYPE(self); 47 | self_name = _PyType_Name(type); 48 | Py_DECREF(self); 49 | } 50 | 51 | // --- get module name from globals --------------------------------------- 52 | PyObject* globals = PyFrame_GetGlobals(&frame); // New reference 53 | PyObject* module_name = globals ? PyDict_GetItemString(globals, "__name__") // Borrowed 54 | : 55 | nullptr; 56 | if (globals) 57 | Py_DECREF(globals); 58 | 59 | if (module_name) 60 | { 61 | std::stringstream result; 62 | // compat::get_string_as_utf_8() is assumed to convert PyObject* → UTF-8 std::string 63 | result << compat::get_string_as_utf_8(module_name); 64 | if (self_name) 65 | result << '.' << self_name; 66 | return result.str(); 67 | } 68 | 69 | // --- special-case NumPy internal frames --------------------------------- 70 | PyCodeObject* code = PyFrame_GetCode(&frame); 71 | std::string_view filename = compat::get_string_as_utf_8(code->co_filename); 72 | Py_DECREF(code); 73 | 74 | if (filename == "<__array_function__ internals>") 75 | return "numpy.__array_function__"; 76 | 77 | return "unknown"; 78 | } 79 | 80 | std::string get_file_name(PyFrameObject& frame) 81 | { 82 | PyCodeObject* code = PyFrame_GetCode(&frame); 83 | PyObject* filename = code->co_filename; 84 | Py_DECREF(code); 85 | if (filename == Py_None) 86 | { 87 | return std::move(std::string("None")); 88 | } 89 | const auto full_file_name = abspath(compat::get_string_as_utf_8(filename)); 90 | return !full_file_name.empty() ? std::move(full_file_name) : "ErrorPath"; 91 | } 92 | 93 | } // namespace scorepy 94 | -------------------------------------------------------------------------------- /src/scorepy/compat.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace scorepy 12 | { 13 | namespace compat 14 | { 15 | /// Region names that are known to have no region enter event and should not report an error 16 | /// on region exit 17 | static const std::array exit_region_whitelist = { 18 | #if PY_MAJOR_VERSION >= 3 19 | "threading.Thread:_bootstrap_inner", "threading.Thread:_bootstrap", 20 | "threading.TMonitor:_bootstrap_inner", "threading.TMonitor:_bootstrap" 21 | #else 22 | "threading.Thread:__bootstrap_inner", "threading.Thread:__bootstrap", 23 | "threading.TMonitor:__bootstrap_inner", "threading.TMonitor:__bootstrap" 24 | #endif 25 | }; 26 | 27 | inline std::string_view get_string_as_utf_8(PyObject* py_string) 28 | { 29 | Py_ssize_t size = 0; 30 | 31 | #if PY_MAJOR_VERSION >= 3 32 | const char* string; 33 | string = PyUnicode_AsUTF8AndSize(py_string, &size); 34 | #else 35 | char* string; 36 | PyString_AsStringAndSize(py_string, &string, &size); 37 | #endif 38 | return std::string_view(string, size); 39 | } 40 | 41 | using PyCodeObject = PyCodeObject; 42 | 43 | using destructor = destructor; 44 | using code_dealloc = std::add_pointer::type; // void(*)(PyCodeObject*) 45 | 46 | /** 47 | * @brief For CPython we need to make sure, that the we register our own dealloc function, so we 48 | * can handle the deleteion of code_objects in our code. 49 | */ 50 | struct RegisterCodeDealloc 51 | { 52 | RegisterCodeDealloc(std::function on_dealloc_fun) 53 | { 54 | external_on_dealloc_fun = on_dealloc_fun; 55 | // PyPy does not need this, as CodeObjects are compiled, and therefore live for the 56 | // programms lifetime 57 | #ifndef PYPY_VERSION 58 | if (!python_code_dealloc) 59 | { 60 | python_code_dealloc = 61 | reinterpret_cast(PyCode_Type.tp_dealloc); 62 | PyCode_Type.tp_dealloc = reinterpret_cast(dealloc_fun); 63 | } 64 | else 65 | { 66 | std::cerr << "WARNING: Score-P Python's code_dealloc is alredy registerd!" 67 | << std::endl; 68 | } 69 | #endif 70 | } 71 | 72 | static void dealloc_fun(PyCodeObject* co) 73 | { 74 | if (external_on_dealloc_fun && python_code_dealloc) 75 | { 76 | external_on_dealloc_fun(co); 77 | python_code_dealloc(co); 78 | } 79 | } 80 | 81 | inline static compat::code_dealloc python_code_dealloc; 82 | inline static std::function external_on_dealloc_fun; 83 | }; 84 | 85 | } // namespace compat 86 | } // namespace scorepy 87 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from setuptools import setup, Extension 4 | import scorep.helper 5 | from scorep.instrumenter import has_c_instrumenter 6 | 7 | if scorep.helper.get_scorep_version() < 5.0: 8 | raise RuntimeError("Score-P version less than 5.0, plase use Score-P >= 5.0") 9 | 10 | link_mode = scorep.helper.get_scorep_config("Link mode:") 11 | if not ("shared=yes" in link_mode): 12 | raise RuntimeError( 13 | 'Score-P not build with "--enable-shared". Link mode is:\n{}'.format(link_mode) 14 | ) 15 | 16 | install_requires= ["setuptools>=68.0.0"] 17 | 18 | cmodules = [] 19 | (include, _, _, _, _) = scorep.helper.generate_compile_deps([]) 20 | src_folder = os.path.abspath("src") 21 | include += [src_folder] 22 | sources = [ 23 | "src/methods.cpp", 24 | "src/scorep_bindings.cpp", 25 | "src/scorepy/events.cpp", 26 | "src/scorepy/pathUtils.cpp", 27 | ] 28 | define_macros = [("PY_SSIZE_T_CLEAN", "1")] 29 | # We are using the UTF-8 string features from Python 3 30 | # The C Instrumenter functions are not available on PyPy 31 | if has_c_instrumenter(): 32 | sources.extend( 33 | [ 34 | "src/classes.cpp", 35 | "src/scorepy/cInstrumenter.cpp", 36 | "src/scorepy/pythonHelpers.cpp", 37 | ] 38 | ) 39 | define_macros.append(("SCOREPY_ENABLE_CINSTRUMENTER", "1")) 40 | else: 41 | define_macros.append(("SCOREPY_ENABLE_CINSTRUMENTER", "0")) 42 | 43 | cmodules.append( 44 | Extension( 45 | "scorep._bindings", 46 | include_dirs=include, 47 | define_macros=define_macros, 48 | extra_compile_args=["-std=c++17"], 49 | sources=sources, 50 | ) 51 | ) 52 | 53 | setup( 54 | name="scorep", 55 | version=scorep._version.__version__, 56 | description="This is a Score-P tracing package for python", 57 | author="Andreas Gocht", 58 | author_email="andreas.gocht@tu-dresden.de", 59 | url="https://github.com/score-p/scorep_binding_python", 60 | long_description=""" 61 | This package allows tracing of python code using Score-P. 62 | A working Score-P version is required. 63 | To enable tracing it uses LD_PRELOAD to load the Score-P runtime libraries. 64 | Besides this, it uses the traditional python-tracing infrastructure. 65 | """, 66 | packages=["scorep", "scorep._instrumenters"], 67 | install_requires=install_requires, 68 | ext_modules=cmodules, 69 | classifiers=[ 70 | "Development Status :: 5 - Production/Stable", 71 | "Environment :: Console", 72 | "Intended Audience :: Developers", 73 | "Topic :: Software Development :: Testing", 74 | "Topic :: Software Development :: Quality Assurance", 75 | "Programming Language :: Python :: 3.9", 76 | "Programming Language :: Python :: 3.10", 77 | "Programming Language :: Python :: 3.11", 78 | "Programming Language :: Python :: 3.12", 79 | "Programming Language :: Python :: 3.13", 80 | "Programming Language :: Python :: 3.14", 81 | "Programming Language :: Python :: Implementation :: CPython", 82 | "Programming Language :: Python :: Implementation :: PyPy", 83 | "Operating System :: POSIX", 84 | "Operating System :: Unix", 85 | ], 86 | ) 87 | -------------------------------------------------------------------------------- /benchmark/benchmark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Created on 04.10.2019 4 | 5 | @author: gocht 6 | ''' 7 | import argparse 8 | import sys 9 | import benchmark_helper 10 | import pickle 11 | import numpy as np 12 | 13 | # Available tests 14 | tests = ["bm_baseline.py", "bm_simplefunc.py"] 15 | 16 | # Available instrumenters 17 | instrumenters = ["profile", "trace", "dummy", "None"] 18 | if sys.version_info.major >= 3: 19 | instrumenters.extend(["cProfile", "cTrace"]) 20 | 21 | # Default values for: How many times the instrumented code is run during 1 test run 22 | reps_x = { 23 | "bm_baseline.py": ["1000000", "2000000", "3000000", "4000000", "5000000"], 24 | "bm_simplefunc.py": ["100000", "200000", "300000", "400000", "500000"], 25 | } 26 | 27 | 28 | def str_to_int(s): 29 | return int(float(s)) 30 | 31 | 32 | parser = argparse.ArgumentParser(description='Benchmark the instrumenters.', 33 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 34 | parser.add_argument('--test', '-t', metavar='TEST', nargs='+', default=tests, 35 | choices=tests, help='Which test(s) to run') 36 | parser.add_argument('--repetitions', '-r', default=51, type=str_to_int, 37 | help='How many times a test invocation is repeated (number of timings per test instance)') 38 | parser.add_argument('--loop-count', '-l', type=str_to_int, nargs='+', 39 | help=('How many times the instrumented code is run during 1 test run. ' 40 | 'Can be repeated and will create 1 test instance per argument')) 41 | parser.add_argument('--instrumenter', '-i', metavar='INST', nargs='+', default=instrumenters, 42 | choices=instrumenters, help='The instrumenter(s) to use') 43 | parser.add_argument('--output', '-o', default='results.pkl', help='Output file for the results') 44 | parser.add_argument('--dry-run', action='store_true', help='Print parsed arguments and exit') 45 | args = parser.parse_args() 46 | 47 | if args.dry_run: 48 | print(args) 49 | sys.exit(0) 50 | 51 | bench = benchmark_helper.BenchmarkEnv(repetitions=args.repetitions) 52 | results = {} 53 | 54 | for test in args.test: 55 | results[test] = {} 56 | 57 | for instrumenter in args.instrumenter: 58 | results[test][instrumenter] = {} 59 | 60 | if instrumenter == "None": 61 | scorep_settings = [] 62 | else: 63 | scorep_settings = ["-m", "scorep", "--instrumenter-type={}".format(instrumenter)] 64 | 65 | print("#########") 66 | print("{}: {}".format(test, scorep_settings)) 67 | print("#########") 68 | max_reps_width = len(str(max(reps_x[test]))) 69 | loop_counts = args.loop_count if args.loop_count else reps_x[test] 70 | for reps in loop_counts: 71 | times = bench.call(test, [str(reps)], 72 | scorep_settings=scorep_settings) 73 | times = np.array(times) 74 | print("{:>{width}}: Range={:{prec}}-{:{prec}} Mean={:{prec}} Median={:{prec}}".format( 75 | reps, times.min(), times.max(), times.mean(), np.median(times), width=max_reps_width, prec='5.4f')) 76 | results[test][instrumenter][reps] = times 77 | 78 | with open(args.output, "wb") as f: 79 | pickle.dump(results, f) 80 | -------------------------------------------------------------------------------- /src/scorepy/pythonHelpers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace scorepy 9 | { 10 | 11 | struct retain_object_t 12 | { 13 | }; 14 | struct adopt_object_t 15 | { 16 | }; 17 | /// Marker to indicate that an owner is added, i.e. refCnt is increased 18 | constexpr retain_object_t retain_object{}; 19 | /// Marker to take over ownership, not touching the refCnt 20 | constexpr adopt_object_t adopt_object{}; 21 | 22 | /// Slim, owning wrapper over a PyObject* 23 | /// Decays implictely to a PyObject* 24 | class PyRefObject 25 | { 26 | PyObject* o_; 27 | 28 | public: 29 | explicit PyRefObject(PyObject* o, adopt_object_t) noexcept : o_(o) 30 | { 31 | } 32 | explicit PyRefObject(PyObject* o, retain_object_t) noexcept : o_(o) 33 | { 34 | Py_XINCREF(o_); 35 | } 36 | PyRefObject(PyRefObject&& rhs) noexcept : o_(rhs.o_) 37 | { 38 | rhs.o_ = nullptr; 39 | } 40 | PyRefObject& operator=(PyRefObject&& rhs) noexcept 41 | { 42 | o_ = rhs.o_; 43 | rhs.o_ = nullptr; 44 | return *this; 45 | } 46 | ~PyRefObject() noexcept 47 | { 48 | Py_XDECREF(o_); 49 | } 50 | 51 | operator PyObject*() const noexcept 52 | { 53 | return o_; 54 | } 55 | }; 56 | 57 | namespace detail 58 | { 59 | template 60 | struct ReplaceArgsToPyObject; 61 | 62 | template 63 | using ReplaceArgsToPyObject_t = typename ReplaceArgsToPyObject::type; 64 | } // namespace detail 65 | 66 | /// Cast a function pointer to a python-bindings compatible function pointer 67 | /// Replaces all Foo* by PyObject* for all types Foo that are PyObject compatible 68 | template 69 | auto cast_to_PyFunc(TFunc* func) -> detail::ReplaceArgsToPyObject_t* 70 | { 71 | return reinterpret_cast*>(func); 72 | } 73 | 74 | /// Return the module name the frame belongs to. 75 | /// The pointer is valid for the lifetime of the frame 76 | std::string get_module_name(PyFrameObject& frame); 77 | /// Return the file name the frame belongs to 78 | std::string get_file_name(PyFrameObject& frame); 79 | 80 | // Implementation details 81 | namespace detail 82 | { 83 | 84 | template 85 | struct make_void 86 | { 87 | typedef void type; 88 | }; 89 | template 90 | using void_t = typename make_void::type; 91 | 92 | template 93 | struct IsPyObject : std::false_type 94 | { 95 | }; 96 | template 97 | struct IsPyObject : IsPyObject 98 | { 99 | }; 100 | 101 | template 102 | struct IsPyObject().to_PyObject())>> : std::true_type 103 | { 104 | }; 105 | 106 | template 107 | struct ReplaceArgsToPyObject 108 | { 109 | template 110 | using replace = typename std::conditional::value, PyObject*, T>::type; 111 | using type = TResult(replace...); 112 | }; 113 | } // namespace detail 114 | } // namespace scorepy 115 | -------------------------------------------------------------------------------- /test/utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | import re 4 | 5 | 6 | def call(arguments, expected_returncode=0, env=None): 7 | """ 8 | Calls the command specificied by arguments and checks the returncode via assert 9 | return (stdout, stderr) from the call to subprocess 10 | """ 11 | if sys.version_info > (3, 5): 12 | out = subprocess.run( 13 | arguments, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE 14 | ) 15 | try: 16 | assert out.returncode == expected_returncode 17 | except AssertionError as e: 18 | e.args += ("stderr: {}".format(out.stderr.decode("utf-8")),) 19 | raise 20 | stdout, stderr = (out.stdout, out.stderr) 21 | else: 22 | p = subprocess.Popen( 23 | arguments, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE 24 | ) 25 | stdout, stderr = p.communicate() 26 | try: 27 | assert p.returncode == expected_returncode 28 | except AssertionError as e: 29 | e.args += ("stderr: {}".format(stderr.decode("utf-8")),) 30 | raise 31 | return stdout.decode("utf-8"), stderr.decode("utf-8") 32 | 33 | 34 | def call_with_scorep(file, scorep_arguments=None, expected_returncode=0, env=None): 35 | """ 36 | Shortcut for running a python file with the scorep module 37 | 38 | @return (stdout, stderr) from the call to subprocess 39 | """ 40 | arguments = [sys.executable, "-m", "scorep"] 41 | if scorep_arguments: 42 | arguments.extend(scorep_arguments) 43 | return call(arguments + [str(file)], expected_returncode=expected_returncode, env=env) 44 | 45 | 46 | def call_otf2_print(trace_path): 47 | trace, std_err = call(["otf2-print", str(trace_path)]) 48 | return trace, std_err 49 | 50 | 51 | class OTF2_Region: 52 | def __init__(self, region): 53 | self.region = region 54 | 55 | def __str__(self): 56 | return self.region 57 | 58 | 59 | class OTF2_Parameter: 60 | def __init__(self, parameter, value): 61 | self.parameter = parameter 62 | self.value = value 63 | 64 | def __str__(self): 65 | return "{}:{}".format(self.parameter, self.value) 66 | 67 | 68 | class OTF2_Trace: 69 | def __init__(self, trace_path): 70 | self.path = trace_path 71 | self.trace, self.std_err = call_otf2_print(self.path) 72 | assert self.std_err == "" 73 | 74 | def __contains__(self, otf2_element): 75 | result = [] 76 | if isinstance(otf2_element, OTF2_Region): 77 | for event in ("ENTER", "LEAVE"): 78 | search_str = "{event}[ ]*[0-9 ]*[0-9 ]*Region: \"{region}\"".format( 79 | event=event, region=otf2_element.region) 80 | search_res = re.search(search_str, self.trace) 81 | result.append(search_res is not None) 82 | elif isinstance(otf2_element, OTF2_Parameter): 83 | search_str = "PARAMETER_STRING[ ]*[0-9 ]*[0-9 ]*Parameter: \"{parameter}\" <[0-9]*>, Value: \"{value}\"" 84 | search_str = search_str.format(parameter=otf2_element.parameter, value=otf2_element.value) 85 | search_res = re.search(search_str, self.trace) 86 | result.append(search_res is not None) 87 | else: 88 | raise NotImplementedError 89 | return all(result) 90 | 91 | def findall(self, otf2_element): 92 | result = [] 93 | if isinstance(otf2_element, OTF2_Region): 94 | for event in ("ENTER", "LEAVE"): 95 | search_str = "{event}[ ]*[0-9 ]*[0-9 ]*Region: \"{region}\"".format( 96 | event=event, region=otf2_element.region) 97 | search_res = re.findall(search_str, self.trace) 98 | result.extend(search_res) 99 | else: 100 | raise NotImplementedError 101 | return result 102 | 103 | def __str__(self): 104 | return self.trace 105 | -------------------------------------------------------------------------------- /scorep/helper.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | import os 4 | import re 5 | 6 | 7 | def print_err(*args): 8 | """Print to stderr""" 9 | sys.stderr.write(' '.join(map(str, args)) + '\n') 10 | 11 | 12 | def call(arguments): 13 | """ 14 | return a triple with (returncode, stdout, stderr) from the call to subprocess 15 | """ 16 | if sys.version_info > (3, 5): 17 | out = subprocess.run( 18 | arguments, 19 | stdout=subprocess.PIPE, 20 | stderr=subprocess.PIPE) 21 | result = ( 22 | out.returncode, 23 | out.stdout.decode("utf-8"), 24 | out.stderr.decode("utf-8")) 25 | else: 26 | p = subprocess.Popen( 27 | arguments, 28 | stdout=subprocess.PIPE, 29 | stderr=subprocess.PIPE) 30 | stdout, stderr = p.communicate() 31 | p.wait() 32 | result = (p.returncode, stdout.decode("utf-8"), stderr.decode("utf-8")) 33 | return result 34 | 35 | 36 | def get_python_version(): 37 | version = "{}.{}".format( 38 | sys.version_info.major, 39 | sys.version_info.minor) 40 | return version 41 | 42 | 43 | def get_scorep_version(): 44 | (return_code, std_out, std_err) = call(["scorep", "--version"]) 45 | if (return_code != 0): 46 | raise RuntimeError("Cannot call Score-P, reason {}".format(std_err)) 47 | me = re.search("([0-9.]+)", std_out) 48 | version_str = me.group(1) 49 | try: 50 | version = float(version_str) 51 | except TypeError: 52 | raise RuntimeError( 53 | "Can not decode the Score-P Version. The version string is: \"{}\"".format(std_out)) 54 | return version 55 | 56 | 57 | def get_scorep_config(config_line=None): 58 | (return_code, std_out, std_err) = call(["scorep-info", "config-summary"]) 59 | if (return_code != 0): 60 | raise RuntimeError("Cannot call Score-P, reason {}".format(std_err)) 61 | if config_line is None: 62 | return std_out.split("\n") 63 | else: 64 | for line in std_out.split("\n"): 65 | if config_line in line: 66 | return line 67 | return None 68 | 69 | 70 | def add_to_ld_library_path(path): 71 | """ 72 | adds the path to the LD_LIBRARY_PATH. 73 | @param path path to be added 74 | """ 75 | library_path = os.environ.get("LD_LIBRARY_PATH", "") 76 | library_paths = library_path.split(":") if library_path else [] 77 | if path not in library_paths: 78 | os.environ["LD_LIBRARY_PATH"] = ':'.join([path] + library_paths) 79 | 80 | 81 | def generate_compile_deps(config): 82 | """ 83 | Generates the data needed for compilation. 84 | """ 85 | 86 | scorep_config = ["scorep-config"] + config 87 | 88 | (return_code, stdout, stderr) = call(scorep_config) 89 | if return_code != 0: 90 | raise ValueError( 91 | "given config {} is not supported\nstdout: {}\nstrerr: {}".format(config, stdout, stderr)) 92 | 93 | (_, ldflags, _) = call(scorep_config + ["--ldflags"]) 94 | (_, libs, _) = call(scorep_config + ["--libs"]) 95 | (_, mgmt_libs, _) = call(scorep_config + ["--mgmt-libs"]) 96 | (_, cflags, _) = call(scorep_config + ["--cflags"]) 97 | 98 | libs = " " + libs + " " + mgmt_libs 99 | ldflags = " " + ldflags 100 | cflags = " " + cflags 101 | 102 | lib_dir = re.findall(r" -L[/+-@.\w]*", ldflags) 103 | lib = re.findall(r" -l[/+-@.\w]*", libs) 104 | include = re.findall(r" -I[/+-@.\w]*", cflags) 105 | macro = re.findall(r" -D[/+-@.\w]*", cflags) 106 | linker_flags = re.findall(r" -Wl[/+-@.\w]*", ldflags) 107 | 108 | def remove_flag3(x): return x[3:] 109 | 110 | def remove_space1(x): return x[1:] 111 | 112 | lib_dir = list(map(remove_flag3, lib_dir)) 113 | lib = list(map(remove_space1, lib)) 114 | include = list(map(remove_flag3, include)) 115 | macro = list(map(remove_flag3, macro)) 116 | linker_flags = list(map(remove_space1, linker_flags)) 117 | 118 | macro = list(map(lambda x: tuple([x, 1]), macro)) 119 | 120 | return (include, lib, lib_dir, macro, linker_flags) 121 | -------------------------------------------------------------------------------- /cmake/FindScorep.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Technische Universität Dresden, Germany 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without modification, are permitted 5 | # provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this list of conditions 8 | # and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions 11 | # and the following disclaimer in the documentation and/or other materials provided with the 12 | # distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse 15 | # or promote products derived from this software without specific prior written permission. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 18 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 19 | # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 20 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 23 | # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 24 | # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | IF(SCOREP_CONFIG_PATH) 27 | FIND_PROGRAM(SCOREP_CONFIG NAMES scorep-config 28 | PATHS 29 | ${SCOREP_CONFIG_PATH} 30 | /opt/scorep/bin 31 | ) 32 | ELSE(SCOREP_CONFIG_PATH) 33 | FIND_PROGRAM(SCOREP_CONFIG NAMES scorep-config 34 | PATHS 35 | /opt/scorep/bin 36 | ) 37 | ENDIF(SCOREP_CONFIG_PATH) 38 | 39 | IF(NOT SCOREP_CONFIG) 40 | MESSAGE(STATUS "no scorep-config found") 41 | set(SCOREP_FOUND false) 42 | ELSE(NOT SCOREP_CONFIG) 43 | 44 | message(STATUS "SCOREP library found. (using ${SCOREP_CONFIG})") 45 | 46 | execute_process(COMMAND ${SCOREP_CONFIG} "--user" "--cppflags" OUTPUT_VARIABLE SCOREP_CONFIG_FLAGS) 47 | 48 | string(REGEX MATCHALL "-I[^ ]*" SCOREP_CONFIG_INCLUDES "${SCOREP_CONFIG_FLAGS}") 49 | foreach(inc ${SCOREP_CONFIG_INCLUDES}) 50 | string(SUBSTRING ${inc} 2 -1 inc) 51 | list(APPEND SCOREP_INCLUDE_DIRS ${inc}) 52 | endforeach() 53 | 54 | string(REGEX MATCHALL "(^| +)-[^I][^ ]*" SCOREP_CONFIG_CXXFLAGS "${SCOREP_CONFIG_FLAGS}") 55 | foreach(flag ${SCOREP_CONFIG_CXXFLAGS}) 56 | string(STRIP ${flag} flag) 57 | list(APPEND SCOREP_CXX_FLAGS ${flag}) 58 | endforeach() 59 | 60 | unset(SCOREP_CONFIG_FLAGS) 61 | unset(SCOREP_CONFIG_INCLUDES) 62 | unset(SCOREP_CONFIG_CXXFLAGS) 63 | 64 | execute_process(COMMAND ${SCOREP_CONFIG} "--user" "--ldflags" OUTPUT_VARIABLE _LINK_LD_ARGS) 65 | STRING( REPLACE " " ";" _LINK_LD_ARGS ${_LINK_LD_ARGS} ) 66 | FOREACH( _ARG ${_LINK_LD_ARGS} ) 67 | IF(${_ARG} MATCHES "^-L") 68 | STRING(REGEX REPLACE "^-L" "" _ARG ${_ARG}) 69 | SET(SCOREP_LINK_DIRS ${SCOREP_LINK_DIRS} ${_ARG}) 70 | ENDIF(${_ARG} MATCHES "^-L") 71 | ENDFOREACH(_ARG) 72 | 73 | execute_process(COMMAND ${SCOREP_CONFIG} "--user" "--libs" OUTPUT_VARIABLE _LINK_LD_ARGS) 74 | STRING( REPLACE " " ";" _LINK_LD_ARGS ${_LINK_LD_ARGS} ) 75 | FOREACH( _ARG ${_LINK_LD_ARGS} ) 76 | IF(${_ARG} MATCHES "^-l") 77 | STRING(REGEX REPLACE "^-l" "" _ARG ${_ARG}) 78 | FIND_LIBRARY(_SCOREP_LIB_FROM_ARG NAMES ${_ARG} 79 | PATHS 80 | ${SCOREP_LINK_DIRS} 81 | ) 82 | IF(_SCOREP_LIB_FROM_ARG) 83 | SET(SCOREP_LIBRARIES ${SCOREP_LIBRARIES} ${_SCOREP_LIB_FROM_ARG}) 84 | ENDIF(_SCOREP_LIB_FROM_ARG) 85 | UNSET(_SCOREP_LIB_FROM_ARG CACHE) 86 | ENDIF(${_ARG} MATCHES "^-l") 87 | ENDFOREACH(_ARG) 88 | 89 | set(SCOREP_FOUND true) 90 | ENDIF(NOT SCOREP_CONFIG) 91 | 92 | include (FindPackageHandleStandardArgs) 93 | FIND_PACKAGE_HANDLE_STANDARD_ARGS( 94 | Scorep DEFAULT_MSG 95 | SCOREP_CONFIG 96 | SCOREP_LIBRARIES 97 | SCOREP_INCLUDE_DIRS 98 | ) 99 | 100 | add_library(Scorep::Scorep INTERFACE IMPORTED) 101 | set_target_properties(Scorep::Scorep PROPERTIES 102 | INTERFACE_INCLUDE_DIRECTORIES "${SCOREP_INCLUDE_DIRS}" 103 | INTERFACE_LINK_LIBRARIES "${SCOREP_LIBRARIES}" 104 | ) 105 | 106 | add_library(Scorep::Plugin INTERFACE IMPORTED) 107 | set_target_properties(Scorep::Plugin PROPERTIES 108 | INTERFACE_INCLUDE_DIRECTORIES "${SCOREP_INCLUDE_DIRS}" 109 | ) 110 | 111 | mark_as_advanced(SCOREP_CONFIG) 112 | -------------------------------------------------------------------------------- /scorep/_instrumenters/scorep_instrumenter.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import inspect 3 | import os 4 | from scorep._instrumenters import base_instrumenter 5 | import scorep._bindings 6 | 7 | 8 | class ScorepInstrumenter(base_instrumenter.BaseInstrumenter): 9 | """Base class for all instrumenters using Score-P""" 10 | 11 | def __init__(self, enable_instrumenter=True): 12 | """ 13 | @param enable_instrumenter true if the tracing shall be initialised. 14 | Please note, that it is still possible to enable the tracing later using register() 15 | """ 16 | self._tracer_registered = False 17 | self._enabled = enable_instrumenter 18 | 19 | @abc.abstractmethod 20 | def _enable_instrumenter(self): 21 | """Actually enable this instrumenter to collect events""" 22 | 23 | @abc.abstractmethod 24 | def _disable_instrumenter(self): 25 | """Stop this instrumenter from collecting events""" 26 | 27 | def register(self): 28 | """Register this instrumenter (collect events)""" 29 | if not self._tracer_registered: 30 | self._enable_instrumenter() 31 | self._tracer_registered = True 32 | 33 | def unregister(self): 34 | """Unregister this instrumenter (stop collecting events)""" 35 | if self._tracer_registered: 36 | self._disable_instrumenter() 37 | self._tracer_registered = False 38 | 39 | def get_registered(self): 40 | """Return whether this instrumenter is currently collecting events""" 41 | return self._tracer_registered 42 | 43 | def run(self, cmd, globals=None, locals=None): 44 | """Run the compiled command under this instrumenter. 45 | 46 | When the instrumenter is enabled it is registered prior to the invocation and unregistered afterwards 47 | """ 48 | if globals is None: 49 | globals = {} 50 | if locals is None: 51 | locals = {} 52 | if self._enabled: 53 | self.register() 54 | try: 55 | exec(cmd, globals, locals) 56 | finally: 57 | self.unregister() 58 | 59 | def try_region_begin(self, code_object): 60 | """Tries to record a region begin event. Retruns True on success""" 61 | return scorep._bindings.try_region_begin(code_object) 62 | 63 | def region_begin(self, module_name, function_name, file_name, line_number, code_object=None): 64 | """Record a region begin event""" 65 | scorep._bindings.region_begin( 66 | module_name, function_name, file_name, line_number, code_object) 67 | 68 | def try_region_end(self, code_object): 69 | """Tries to record a region end event. Retruns True on success""" 70 | return scorep._bindings.try_region_end(code_object) 71 | 72 | def region_end(self, module_name, function_name, code_object=None): 73 | """Record a region end event""" 74 | scorep._bindings.region_end(module_name, function_name, code_object) 75 | 76 | def rewind_begin(self, name, file_name=None, line_number=None): 77 | """ 78 | Begin of an Rewind region. If file_name or line_number is None, both will 79 | be determined automatically 80 | @param name name of the user region 81 | @param file_name file name of the user region 82 | @param line_number line number of the user region 83 | """ 84 | if file_name is None or line_number is None: 85 | frame = inspect.currentframe().f_back 86 | file_name = frame.f_globals.get('__file__', None) 87 | line_number = frame.f_lineno 88 | if file_name is not None: 89 | full_file_name = os.path.abspath(file_name) 90 | else: 91 | full_file_name = "None" 92 | 93 | scorep._bindings.rewind_begin(name, full_file_name, line_number) 94 | 95 | def rewind_end(self, name, value): 96 | """ 97 | End of an Rewind region. 98 | @param name name of the user region 99 | @param value True or False, whenether the region shall be rewinded or not. 100 | """ 101 | scorep._bindings.rewind_end(name, value) 102 | 103 | def user_enable_recording(self): 104 | """Enable writing of trace events in ScoreP""" 105 | scorep._bindings.enable_recording() 106 | 107 | def user_disable_recording(self): 108 | """Disable writing of trace events in ScoreP""" 109 | scorep._bindings.disable_recording() 110 | 111 | def user_parameter_int(self, name, val): 112 | """Record a parameter of type integer""" 113 | scorep._bindings.parameter_int(name, val) 114 | 115 | def user_parameter_uint(self, name, val): 116 | """Record a parameter of type unsigned integer""" 117 | scorep._bindings.parameter_string(name, val) 118 | 119 | def user_parameter_string(self, name, string): 120 | """Record a parameter of type string""" 121 | scorep._bindings.parameter_string(name, string) 122 | 123 | def force_finalize(self): 124 | scorep._bindings.force_finalize() 125 | 126 | def reregister_exit_handler(self): 127 | scorep._bindings.reregister_exit_handler() 128 | -------------------------------------------------------------------------------- /src/classes.cpp: -------------------------------------------------------------------------------- 1 | #include "classes.hpp" 2 | #include "scorepy/cInstrumenter.hpp" 3 | #include "scorepy/pythonHelpers.hpp" 4 | #include 5 | #include 6 | 7 | static_assert(std::is_trivial::value, 8 | "Must be trivial or object creation by Python is UB"); 9 | static_assert(std::is_standard_layout::value, 10 | "Must be standard layout or object creation by Python is UB"); 11 | 12 | extern "C" 13 | { 14 | 15 | /// tp_new implementation that calls object.__new__ with not args to allow ABC classes to work 16 | static PyObject* call_object_new(PyTypeObject* type, PyObject*, PyObject*) 17 | { 18 | scorepy::PyRefObject empty_tuple(PyTuple_New(0), scorepy::adopt_object); 19 | if (!empty_tuple) 20 | { 21 | return nullptr; 22 | } 23 | scorepy::PyRefObject empty_dict(PyDict_New(), scorepy::adopt_object); 24 | if (!empty_dict) 25 | { 26 | return nullptr; 27 | } 28 | return PyBaseObject_Type.tp_new(type, empty_tuple, empty_dict); 29 | } 30 | 31 | static void CInstrumenter_dealloc(scorepy::CInstrumenter* self) 32 | { 33 | self->deinit(); 34 | Py_TYPE(self)->tp_free(self->to_PyObject()); 35 | } 36 | 37 | static int CInstrumenter_init(scorepy::CInstrumenter* self, PyObject* args, PyObject* kwds) 38 | { 39 | static const char* kwlist[] = { "interface", nullptr }; 40 | const char* interface_cstring; 41 | 42 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", const_cast(kwlist), 43 | &interface_cstring)) 44 | { 45 | return -1; 46 | } 47 | 48 | const std::string interface_string = interface_cstring; 49 | scorepy::InstrumenterInterface interface; 50 | if (interface_string == "Trace") 51 | { 52 | interface = scorepy::InstrumenterInterface::Trace; 53 | } 54 | else if (interface_string == "Profile") 55 | { 56 | interface = scorepy::InstrumenterInterface::Profile; 57 | } 58 | else 59 | { 60 | PyErr_Format(PyExc_TypeError, "Expected 'Trace' or 'Profile', got '%s'", 61 | interface_cstring); 62 | return -1; 63 | } 64 | 65 | self->init(interface); 66 | return 0; 67 | } 68 | 69 | static PyObject* CInstrumenter_get_interface(scorepy::CInstrumenter* self, void*) 70 | { 71 | const char* result = 72 | self->interface == scorepy::InstrumenterInterface::Trace ? "Trace" : "Profile"; 73 | return PyUnicode_FromString(result); 74 | } 75 | 76 | static PyObject* CInstrumenter_enable_instrumenter(scorepy::CInstrumenter* self, PyObject*) 77 | { 78 | self->enable_instrumenter(); 79 | Py_RETURN_NONE; 80 | } 81 | 82 | static PyObject* CInstrumenter_disable_instrumenter(scorepy::CInstrumenter* self, PyObject*) 83 | { 84 | self->disable_instrumenter(); 85 | Py_RETURN_NONE; 86 | } 87 | 88 | static PyObject* CInstrumenter_call(scorepy::CInstrumenter* self, PyObject* args, 89 | PyObject* kwds) 90 | { 91 | static const char* kwlist[] = { "frame", "event", "arg", nullptr }; 92 | 93 | PyFrameObject* frame; 94 | const char* event; 95 | PyObject* arg; 96 | 97 | if (!PyArg_ParseTupleAndKeywords(args, kwds, "OsO", const_cast(kwlist), &frame, 98 | &event, &arg)) 99 | { 100 | return nullptr; 101 | } 102 | return (*self)(*frame, event, arg); 103 | } 104 | } 105 | 106 | namespace scorepy 107 | { 108 | 109 | PyTypeObject& getCInstrumenterType() 110 | { 111 | static PyMethodDef methods[] = { 112 | { "_enable_instrumenter", scorepy::cast_to_PyFunc(CInstrumenter_enable_instrumenter), 113 | METH_NOARGS, "Enable the instrumenter" }, 114 | { "_disable_instrumenter", scorepy::cast_to_PyFunc(CInstrumenter_disable_instrumenter), 115 | METH_NOARGS, "Disable the instrumenter" }, 116 | { nullptr } /* Sentinel */ 117 | }; 118 | static PyGetSetDef getseters[] = { 119 | { "interface", scorepy::cast_to_PyFunc(CInstrumenter_get_interface), nullptr, 120 | "Return the used interface for instrumentation", nullptr }, 121 | { nullptr } /* Sentinel */ 122 | }; 123 | // Sets the first few fields explicitely and remaining ones to zero 124 | static PyTypeObject type = { 125 | PyVarObject_HEAD_INIT(nullptr, 0) /* header */ 126 | "scorep._bindings.CInstrumenter", /* tp_name */ 127 | sizeof(CInstrumenter), /* tp_basicsize */ 128 | }; 129 | type.tp_new = call_object_new; 130 | type.tp_init = scorepy::cast_to_PyFunc(CInstrumenter_init); 131 | type.tp_dealloc = scorepy::cast_to_PyFunc(CInstrumenter_dealloc); 132 | type.tp_methods = methods; 133 | type.tp_call = scorepy::cast_to_PyFunc(CInstrumenter_call); 134 | type.tp_getset = getseters; 135 | type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; 136 | type.tp_doc = "Class for the C instrumenter interface of Score-P"; 137 | return type; 138 | } 139 | 140 | } // namespace scorepy 141 | -------------------------------------------------------------------------------- /src/scorepy/cInstrumenter.cpp: -------------------------------------------------------------------------------- 1 | #include "cInstrumenter.hpp" 2 | #include "events.hpp" 3 | #include "pythonHelpers.hpp" 4 | #include "pythoncapi_compat.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace scorepy 11 | { 12 | 13 | void CInstrumenter::init(InstrumenterInterface interface) 14 | { 15 | this->interface = interface; 16 | threading_module = PyImport_ImportModule("threading"); 17 | if (threading_module) 18 | { 19 | const char* name = (interface == InstrumenterInterface::Trace) ? "settrace" : "setprofile"; 20 | threading_set_instrumenter = PyObject_GetAttrString(threading_module, name); 21 | } 22 | } 23 | 24 | void CInstrumenter::deinit() 25 | { 26 | Py_CLEAR(threading_module); 27 | Py_CLEAR(threading_set_instrumenter); 28 | } 29 | 30 | void CInstrumenter::enable_instrumenter() 31 | { 32 | const auto callback = [](PyObject* obj, PyFrameObject* frame, int what, PyObject* arg) -> int { 33 | return from_PyObject(obj)->on_event(*frame, what, arg) ? 0 : -1; 34 | }; 35 | if (threading_set_instrumenter) 36 | { 37 | PyRefObject result(PyObject_CallFunction(threading_set_instrumenter, "O", to_PyObject()), 38 | adopt_object); 39 | } 40 | if (interface == InstrumenterInterface::Trace) 41 | { 42 | PyEval_SetTrace(callback, to_PyObject()); 43 | } 44 | else 45 | { 46 | PyEval_SetProfile(callback, to_PyObject()); 47 | } 48 | } 49 | 50 | void CInstrumenter::disable_instrumenter() 51 | { 52 | if (interface == InstrumenterInterface::Trace) 53 | { 54 | PyEval_SetTrace(nullptr, nullptr); 55 | } 56 | else 57 | { 58 | PyEval_SetProfile(nullptr, nullptr); 59 | } 60 | if (threading_set_instrumenter) 61 | { 62 | PyRefObject result(PyObject_CallFunction(threading_set_instrumenter, "O", Py_None), 63 | adopt_object); 64 | } 65 | } 66 | 67 | /// Mapping of PyTrace_* to it's string representations 68 | /// List taken from CPythons sysmodule.c 69 | static const std::array WHAT_STRINGS = { "call", "exception", "line", 70 | "return", "c_call", "c_exception", 71 | "c_return", "opcode" }; 72 | 73 | template 74 | int index_of(TCollection&& col, const TElement& element) 75 | { 76 | const auto it = std::find(col.cbegin(), col.cend(), element); 77 | if (it == col.end()) 78 | { 79 | return -1; 80 | } 81 | else 82 | { 83 | return std::distance(col.begin(), it); 84 | } 85 | } 86 | 87 | // Required because: `sys.getprofile()` returns the user object (2nd arg to PyEval_SetTrace) 88 | // So `sys.setprofile(sys.getprofile())` will not round-trip as it will try to call the 89 | // 2nd arg through pythons dispatch function. Hence make the object callable. 90 | // See https://nedbatchelder.com/text/trace-function.html for details 91 | PyObject* CInstrumenter::operator()(PyFrameObject& frame, const char* what_string, PyObject* arg) 92 | { 93 | const int what = index_of(WHAT_STRINGS, what_string); 94 | // To speed up further event processing install this class directly as the handler 95 | // But we might be inside a `sys.settrace` call where the user wanted to set another function 96 | // which would then be overwritten here. Hence use the CALL event which avoids the problem 97 | if (what == PyTrace_CALL) 98 | { 99 | enable_instrumenter(); 100 | } 101 | if (on_event(frame, what, arg)) 102 | { 103 | Py_INCREF(to_PyObject()); 104 | return to_PyObject(); 105 | } 106 | else 107 | { 108 | return nullptr; 109 | } 110 | } 111 | 112 | bool CInstrumenter::on_event(PyFrameObject& frame, int what, PyObject*) 113 | { 114 | switch (what) 115 | { 116 | case PyTrace_CALL: 117 | { 118 | PyCodeObject* code = PyFrame_GetCode(&frame); 119 | bool success = try_region_begin(code); 120 | if (!success) 121 | { 122 | const auto name = compat::get_string_as_utf_8(code->co_name); 123 | const auto module_name = get_module_name(frame); 124 | if (name.compare("_unsetprofile") != 0 && module_name.compare(0, 6, "scorep") != 0) 125 | { 126 | const int line_number = code->co_firstlineno; 127 | const auto file_name = get_file_name(frame); 128 | region_begin(name, std::move(module_name), std::move(file_name), line_number, code); 129 | } 130 | } 131 | Py_DECREF(code); 132 | break; 133 | } 134 | case PyTrace_RETURN: 135 | { 136 | PyCodeObject* code = PyFrame_GetCode(&frame); 137 | bool success = try_region_end(code); 138 | if (!success) 139 | { 140 | const auto name = compat::get_string_as_utf_8(code->co_name); 141 | const auto module_name = get_module_name(frame); 142 | // TODO: Use string_view/CString comparison? 143 | if (name.compare("_unsetprofile") != 0 && module_name.compare(0, 6, "scorep") != 0) 144 | { 145 | region_end(name, std::move(module_name), code); 146 | } 147 | } 148 | Py_DECREF(code); 149 | break; 150 | } 151 | } 152 | return true; 153 | } 154 | 155 | } // namespace scorepy 156 | -------------------------------------------------------------------------------- /src/scorepy/events.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "compat.hpp" 7 | #include "events.hpp" 8 | #include "pythonHelpers.hpp" 9 | 10 | namespace scorepy 11 | { 12 | 13 | std::unordered_map regions; 14 | static std::unordered_map user_regions; 15 | static std::unordered_map rewind_regions; 16 | 17 | /** 18 | * @brief when Python PyCodeObject is deallocated, remove it from our regions list. 19 | * 20 | * @param co code object to remove 21 | */ 22 | void on_dealloc(PyCodeObject* co) 23 | { 24 | regions.erase(co); 25 | } 26 | 27 | static compat::RegisterCodeDealloc register_dealloc(on_dealloc); 28 | 29 | // Used for regions, that have an identifier, aka a code object id. (instrumenter regions and 30 | // some decorated regions) 31 | void region_begin(std::string_view function_name, std::string module, std::string file_name, 32 | const std::uint64_t line_number, compat::PyCodeObject* identifier) 33 | { 34 | region_handle& region = regions[identifier]; 35 | 36 | if (region == uninitialised_region_handle) 37 | { 38 | auto region_name = make_region_name(std::move(module), function_name); 39 | SCOREP_User_RegionInit(®ion.value, NULL, NULL, region_name.c_str(), 40 | SCOREP_USER_REGION_TYPE_FUNCTION, file_name.c_str(), line_number); 41 | 42 | if (const auto pos = region_name.find(':'); pos != std::string::npos) 43 | { 44 | region_name.resize(pos); 45 | SCOREP_User_RegionSetGroup(region.value, region_name.c_str()); 46 | } 47 | } 48 | SCOREP_User_RegionEnter(region.value); 49 | } 50 | 51 | // Used for regions, that only have a function name, a module, a file and a line number (user 52 | // regions) 53 | void region_begin(std::string_view function_name, std::string module, std::string file_name, 54 | const std::uint64_t line_number) 55 | { 56 | auto region_name = make_region_name(std::move(module), function_name); 57 | region_handle& region = user_regions[region_name]; 58 | 59 | if (region == uninitialised_region_handle) 60 | { 61 | SCOREP_User_RegionInit(®ion.value, NULL, NULL, region_name.c_str(), 62 | SCOREP_USER_REGION_TYPE_FUNCTION, file_name.c_str(), line_number); 63 | 64 | if (const auto pos = region_name.find(':'); pos != std::string::npos) 65 | { 66 | region_name.resize(pos); 67 | SCOREP_User_RegionSetGroup(region.value, region_name.c_str()); 68 | } 69 | } 70 | SCOREP_User_RegionEnter(region.value); 71 | } 72 | 73 | // Used for regions, that have an identifier, aka a code object id. (instrumenter regions and 74 | // some decorated regions) 75 | void region_end(std::string_view function_name, std::string module, 76 | compat::PyCodeObject* identifier) 77 | { 78 | const auto it_region = regions.find(identifier); 79 | if (it_region != regions.end()) 80 | { 81 | SCOREP_User_RegionEnd(it_region->second.value); 82 | } 83 | else 84 | { 85 | const auto region_name = make_region_name(std::move(module), function_name); 86 | region_end_error_handling(std::move(region_name)); 87 | } 88 | } 89 | 90 | // Used for regions, that only have a function name, a module (user regions) 91 | void region_end(std::string_view function_name, std::string module) 92 | { 93 | const auto region_name = make_region_name(std::move(module), function_name); 94 | auto it_region = user_regions.find(region_name); 95 | if (it_region != user_regions.end()) 96 | { 97 | SCOREP_User_RegionEnd(it_region->second.value); 98 | } 99 | else 100 | { 101 | region_end_error_handling(std::move(region_name)); 102 | } 103 | } 104 | 105 | void region_end_error_handling(std::string region_name) 106 | { 107 | static region_handle error_region; 108 | static SCOREP_User_ParameterHandle scorep_param = SCOREP_USER_INVALID_PARAMETER; 109 | static bool error_printed = false; 110 | 111 | if (std::find(compat::exit_region_whitelist.begin(), compat::exit_region_whitelist.end(), 112 | region_name) != compat::exit_region_whitelist.end()) 113 | { 114 | return; 115 | } 116 | 117 | if (error_region.value == SCOREP_USER_INVALID_REGION) 118 | { 119 | SCOREP_User_RegionInit(&error_region.value, NULL, NULL, "error_region", 120 | SCOREP_USER_REGION_TYPE_FUNCTION, "scorep.cpp", 0); 121 | SCOREP_User_RegionSetGroup(error_region.value, "error"); 122 | } 123 | SCOREP_User_RegionEnter(error_region.value); 124 | SCOREP_User_ParameterString(&scorep_param, "leave-region", region_name.c_str()); 125 | SCOREP_User_RegionEnd(error_region.value); 126 | 127 | if (!error_printed) 128 | { 129 | std::cerr << "SCOREP_BINDING_PYTHON ERROR: There was a region exit without an enter!\n" 130 | << "SCOREP_BINDING_PYTHON ERROR: For details look for \"error_region\" in " 131 | "the trace or profile." 132 | << std::endl; 133 | error_printed = true; 134 | } 135 | } 136 | 137 | void rewind_begin(std::string region_name, std::string file_name, std::uint64_t line_number) 138 | { 139 | auto pair = rewind_regions.emplace(make_pair(region_name, region_handle())); 140 | bool inserted_new = pair.second; 141 | auto& handle = pair.first->second; 142 | if (inserted_new) 143 | { 144 | SCOREP_User_RegionInit(&handle.value, NULL, NULL, region_name.c_str(), 145 | SCOREP_USER_REGION_TYPE_FUNCTION, file_name.c_str(), line_number); 146 | } 147 | SCOREP_User_RewindRegionEnter(handle.value); 148 | } 149 | 150 | void rewind_end(std::string region_name, bool value) 151 | { 152 | auto& handle = rewind_regions.at(region_name); 153 | /* don't call SCOREP_ExitRewindRegion, as 154 | * SCOREP_User_RewindRegionEnd does some additional magic 155 | * */ 156 | SCOREP_User_RewindRegionEnd(handle.value, value); 157 | } 158 | 159 | void parameter_int(std::string name, int64_t value) 160 | { 161 | static SCOREP_User_ParameterHandle scorep_param = SCOREP_USER_INVALID_PARAMETER; 162 | SCOREP_User_ParameterInt64(&scorep_param, name.c_str(), value); 163 | } 164 | 165 | void parameter_uint(std::string name, uint64_t value) 166 | { 167 | static SCOREP_User_ParameterHandle scorep_param = SCOREP_USER_INVALID_PARAMETER; 168 | SCOREP_User_ParameterUint64(&scorep_param, name.c_str(), value); 169 | } 170 | 171 | void parameter_string(std::string name, std::string value) 172 | { 173 | static SCOREP_User_ParameterHandle scorep_param = SCOREP_USER_INVALID_PARAMETER; 174 | SCOREP_User_ParameterString(&scorep_param, name.c_str(), value.c_str()); 175 | } 176 | 177 | } // namespace scorepy 178 | -------------------------------------------------------------------------------- /scorep/instrumenter.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import os 3 | import platform 4 | import functools 5 | 6 | global_instrumenter = None 7 | 8 | 9 | def has_c_instrumenter(): 10 | """Return true if the C instrumenter(s) are available""" 11 | # We are using the UTF-8 string features from Python 3 12 | # The C Instrumenter functions are not available on PyPy 13 | return platform.python_implementation() != 'PyPy' 14 | 15 | 16 | def get_instrumenter(enable_instrumenter=False, 17 | instrumenter_type="dummy"): 18 | """ 19 | returns an instrumenter 20 | 21 | @param enable_instrumenter True if the Instrumenter should be enabled when run is called 22 | @param instrumenter_type which python tracing interface to use. 23 | Currently available: `profile` (default), `trace` and `dummy` 24 | """ 25 | global global_instrumenter 26 | if global_instrumenter is None: 27 | if instrumenter_type == "profile": 28 | from scorep._instrumenters.scorep_profile import ScorepProfile 29 | global_instrumenter = ScorepProfile(enable_instrumenter) 30 | elif instrumenter_type == "trace": 31 | from scorep._instrumenters.scorep_trace import ScorepTrace 32 | global_instrumenter = ScorepTrace(enable_instrumenter) 33 | elif instrumenter_type == "dummy": 34 | from scorep._instrumenters.dummy import ScorepDummy 35 | global_instrumenter = ScorepDummy(enable_instrumenter) 36 | elif instrumenter_type == "cTrace": 37 | from scorep._instrumenters.scorep_cTrace import ScorepCTrace 38 | global_instrumenter = ScorepCTrace(enable_instrumenter) 39 | elif instrumenter_type == "cProfile": 40 | from scorep._instrumenters.scorep_cProfile import ScorepCProfile 41 | global_instrumenter = ScorepCProfile(enable_instrumenter) 42 | else: 43 | raise RuntimeError('instrumenter_type "{}" unkown'.format(instrumenter_type)) 44 | 45 | return global_instrumenter 46 | 47 | 48 | def register(): 49 | """ 50 | Reenables the python-tracing. 51 | """ 52 | get_instrumenter().register() 53 | 54 | 55 | def unregister(): 56 | """ 57 | Disables the python-tracing. 58 | Disabling the python-tracing is more efficient than disable_recording, 59 | as python does no longer call the tracing module. 60 | However, all the other things that are traced by Score-P will still be recorded. 61 | Please call register() to enable tracing again. 62 | """ 63 | get_instrumenter().unregister() 64 | 65 | 66 | class enable(): 67 | """ 68 | Context manager to enable tracing in a certain region: 69 | ``` 70 | with enable(region_name=None): 71 | do stuff 72 | ``` 73 | This overides --noinstrumenter (--nopython legacy) 74 | If a region name is given, the region the contextmanager is active will be marked in the trace or profile 75 | """ 76 | 77 | def __init__(self, region_name=""): 78 | self.region_name = region_name 79 | if region_name == "": 80 | self.user_region_name = False 81 | else: 82 | self.user_region_name = True 83 | self.module_name = "" 84 | 85 | def _recreate_cm(self): 86 | return self 87 | 88 | def __call__(self, func): 89 | with disable(): 90 | @functools.wraps(func) 91 | def inner(*args, **kwds): 92 | with self._recreate_cm(): 93 | return func(*args, **kwds) 94 | return inner 95 | 96 | def __enter__(self): 97 | self.tracer_registered = get_instrumenter().get_registered() 98 | if not self.tracer_registered: 99 | if self.user_region_name: 100 | self.module_name = "user_instrumenter" 101 | frame = inspect.currentframe().f_back 102 | file_name = frame.f_globals.get('__file__', None) 103 | line_number = frame.f_lineno 104 | if file_name is not None: 105 | full_file_name = os.path.abspath(file_name) 106 | else: 107 | full_file_name = "None" 108 | 109 | get_instrumenter().region_begin( 110 | self.module_name, self.region_name, full_file_name, 111 | line_number) 112 | 113 | get_instrumenter().register() 114 | 115 | def __exit__(self, exc_type=None, exc_value=None, traceback=None): 116 | if not self.tracer_registered: 117 | get_instrumenter().unregister() 118 | 119 | if self.user_region_name: 120 | get_instrumenter().region_end( 121 | self.module_name, self.region_name) 122 | 123 | 124 | class disable(): 125 | """ 126 | Context manager to disable tracing in a certain region: 127 | ``` 128 | with disable(): 129 | do stuff 130 | ``` 131 | This overides --noinstrumenter (--nopython legacy) 132 | If a region name is given, the region the contextmanager is active will be marked in the trace or profile 133 | """ 134 | 135 | def __init__(self, region_name=""): 136 | self.region_name = region_name 137 | if region_name == "": 138 | self.user_region_name = False 139 | else: 140 | self.user_region_name = True 141 | self.module_name = "" 142 | self.func = None 143 | 144 | def _recreate_cm(self): 145 | return self 146 | 147 | def __call__(self, func): 148 | self.__enter__() 149 | try: 150 | @functools.wraps(func) 151 | def inner(*args, **kwds): 152 | with self._recreate_cm(): 153 | return func(*args, **kwds) 154 | finally: 155 | self.__exit__() 156 | return inner 157 | 158 | def __enter__(self): 159 | self.tracer_registered = get_instrumenter().get_registered() 160 | if self.tracer_registered: 161 | get_instrumenter().unregister() 162 | 163 | if self.user_region_name: 164 | self.module_name = "user_instrumenter" 165 | frame = inspect.currentframe().f_back 166 | file_name = frame.f_globals.get('__file__', None) 167 | line_number = frame.f_lineno 168 | if file_name is not None: 169 | full_file_name = os.path.abspath(file_name) 170 | else: 171 | full_file_name = "None" 172 | 173 | get_instrumenter().region_begin( 174 | self.module_name, self.region_name, full_file_name, 175 | line_number) 176 | 177 | def __exit__(self, exc_type=None, exc_value=None, traceback=None): 178 | if self.tracer_registered: 179 | if self.user_region_name: 180 | get_instrumenter().region_end( 181 | self.module_name, self.region_name) 182 | 183 | get_instrumenter().register() 184 | -------------------------------------------------------------------------------- /scorep/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import scorep.instrumenter 5 | import scorep.subsystem 6 | import scorep.helper 7 | from scorep.helper import get_scorep_version, print_err 8 | 9 | 10 | def _err_exit(msg): 11 | print_err("scorep: " + msg) 12 | sys.exit(1) 13 | 14 | 15 | def print_help(): 16 | print("""\ 17 | Usage: python -m scorep [options] [--]