Skip to content

Commit 12f207b

Browse files
authored
Merge pull request #138 from AndreasGocht/instrumenter_file
Allows the usage of an Instrumenter file, which allows explicit instrument functions without changing any code.
2 parents 75a3420 + 66edaa6 commit 12f207b

File tree

5 files changed

+89
-0
lines changed

5 files changed

+89
-0
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ scorep is a module that allows tracing of python scripts using [Score-P](https:/
1313
* [Instrumenter](#instrumenter)
1414
+ [Instrumenter Types](#instrumenter-types)
1515
+ [Instrumenter User Interface](#instrumenter-user-interface)
16+
+ [Instrumenter File](#instrumenter-file)
1617
* [MPI](#mpi)
1718
* [User Regions](#user-regions)
1819
* [Overview about Flags](#overview-about-flags)
@@ -102,6 +103,7 @@ with scorep.instrumenter.enable():
102103
The main idea is to reduce the instrumentation overhead for regions that are not of interest.
103104
Whenever the instrumenter is disabled, function enter or exits will not be trace.
104105
However, user regions as described in [User Regions](#user-regions) are not affected.
106+
Both functions are also available as decorators.
105107

106108
As an example:
107109

@@ -155,6 +157,45 @@ with scorep.instrumenter.disable():
155157
will only disable the instrumenter, but `my_fun_calls` will not appear in the trace or profile, as the second call to `scorep.instrumenter.disable` did not change the state of the instrumenter.
156158
Please look to [User Regions](#user-regions), if you want to annotate a region, no matter what the instrumenter state is.
157159

160+
### Instrumenter File
161+
162+
Handing a Python file to `--instrumenter-file` allows the instrumentation of modules and functions without changing their code.
163+
The file handed to `--instrumenter-file` is executed before the script is executed so that the original function definition can be overwritten before the function is executed.
164+
However, using this approach, it is no longer possible to track the bring up of the module.
165+
166+
To simplify the instrumentation, the user instrumentation contains two helper calls:
167+
```
168+
scorep.user.instrument_function(function, instrumenter_fun=scorep.user.region)
169+
scorep.user.instrument_module(module, instrumenter_fun=scorep.user.region):
170+
```
171+
while `instrumenter_fun` might be one of:
172+
* `scorep.user.region`, decorator as explained below
173+
* `scorep.instrumenter.enable`, decorator as explained above
174+
* `scorep.instrumenter.disable`, decorator as explained above
175+
176+
Using the `scorep.instrumenter` decorators, the instrumentation can be enabled or disabled from the given function.
177+
The function is executed below `enable` or `disable`.
178+
Using `scorep.user.region`, it is possible to instrument a full python program.
179+
However, I discourage this usage, as the overhead of the user instrumentation is higher than the built-in instrumenters.
180+
181+
Using `scorep.user.instrument_module`, all functions of the given Python Module are instrumented.
182+
183+
An example instrumenter file might look like the following:
184+
```
185+
import scorep.user
186+
187+
# import module that shall be instrumented
188+
import module_to_instrument
189+
import module
190+
191+
# hand over the imported module, containing functions which shall be instrumented
192+
scorep.user.instrument_module(module_to_instrument)
193+
194+
# hand the function to be instrumented, and overwrite the original definiton of that function
195+
module.function_to_instrument = scorep.user.instrument_function(module.function_to_instrument)
196+
197+
```
198+
158199
## MPI
159200

160201
To use trace an MPI parallel application, please specify

scorep/__main__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def scorep_main(argv=None):
2828
instrumenter_type = "cProfile"
2929
else:
3030
instrumenter_type = "profile"
31+
instrumenter_file = None
3132

3233
for elem in argv[1:]:
3334
if parse_scorep_commands:
@@ -52,6 +53,9 @@ def scorep_main(argv=None):
5253
elif "--instrumenter-type" in elem:
5354
param = elem.split("=")
5455
instrumenter_type = param[1]
56+
elif "--instrumenter-file" in elem:
57+
param = elem.split("=")
58+
instrumenter_file = param[1]
5559
elif elem[0] == "-":
5660
scorep_config.append(elem)
5761
else:
@@ -96,6 +100,11 @@ def scorep_main(argv=None):
96100

97101
tracer = scorep.instrumenter.get_instrumenter(not no_instrumenter,
98102
instrumenter_type)
103+
104+
if instrumenter_file:
105+
with open(instrumenter_file) as f:
106+
exec(f.read())
107+
99108
try:
100109
with open(progname) as fp:
101110
code = compile(fp.read(), progname, 'exec')

scorep/user.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,17 @@ def __exit__(self, exc_type, exc_value, traceback):
135135
return False
136136

137137

138+
def instrument_function(fun, instrumenter_fun=region):
139+
return instrumenter_fun()(fun)
140+
141+
142+
def instrument_module(module, instrumenter_fun=region):
143+
for elem in dir(module):
144+
module_fun = module.__dict__[elem]
145+
if inspect.isfunction(module_fun):
146+
module.__dict__[elem] = instrument_function(module_fun, instrumenter_fun)
147+
148+
138149
def rewind_begin(name, file_name=None, line_number=None):
139150
"""
140151
Begin of a Rewind region. If file_name or line_number is None, both will
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import scorep.user
2+
3+
import instrumentation2
4+
5+
scorep.user.instrument_module(instrumentation2)

test/test_scorep.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,29 @@ def test_user_instrumentation(scorep_env, instrumenter):
254254
assert re.search('LEAVE[ ]*[0-9 ]*[0-9 ]*Region: "instrumentation2:baz"', std_out)
255255

256256

257+
@foreach_instrumenter
258+
def test_external_user_instrumentation(scorep_env, instrumenter):
259+
trace_path = get_trace_path(scorep_env)
260+
261+
std_out, std_err = call_with_scorep(
262+
"cases/instrumentation.py",
263+
["--nocompiler", "--noinstrumenter", "--instrumenter-type=" +
264+
instrumenter, "--instrumenter-file=cases/external_instrumentation.py"],
265+
env=scorep_env,
266+
)
267+
268+
assert std_err == ""
269+
assert std_out == "hello world\nbaz\nbar\n"
270+
271+
std_out, std_err = call(["otf2-print", trace_path])
272+
273+
assert std_err == ""
274+
assert re.search('ENTER[ ]*[0-9 ]*[0-9 ]*Region: "instrumentation2:bar"', std_out)
275+
assert re.search('LEAVE[ ]*[0-9 ]*[0-9 ]*Region: "instrumentation2:bar"', std_out)
276+
assert re.search('ENTER[ ]*[0-9 ]*[0-9 ]*Region: "instrumentation2:baz"', std_out)
277+
assert re.search('LEAVE[ ]*[0-9 ]*[0-9 ]*Region: "instrumentation2:baz"', std_out)
278+
279+
257280
@foreach_instrumenter
258281
def test_error_region(scorep_env, instrumenter):
259282
trace_path = get_trace_path(scorep_env)

0 commit comments

Comments
 (0)