Skip to content

Commit cff75cc

Browse files
committed
[GR-40630] Support profile module.
PullRequest: graalpython/2418
2 parents f85e33b + 1771d71 commit cff75cc

File tree

10 files changed

+459
-55
lines changed

10 files changed

+459
-55
lines changed

docs/user/Tooling.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ For example, it does not currently track calls, only line counts and called func
3939
The `_lsprof` built-in module has been implemented using the GraalVM `cpusampler` tool.
4040
Not all profiling features are currently supported, but basic profiling works:
4141
```shell
42-
graalpy -m cProfile -s sort -m ginstall --help
42+
graalpy -m cProfile -s calls -m ginstall --help
4343
```
4444

4545
The interactive exploration of a stats output file also works:
@@ -50,3 +50,24 @@ ginstall.profile%
5050
callers
5151
[...]
5252
```
53+
54+
The profile module works as well:
55+
```shell
56+
graalpy -m profile -s calls -m ginstall --help
57+
```
58+
or
59+
```shell
60+
>>> import profile
61+
>>> profile.run('l = []; l.append(1)')
62+
5 function calls in 0.002 seconds
63+
64+
Ordered by: standard name
65+
66+
ncalls tottime percall cumtime percall filename:lineno(function)
67+
1 0.000 0.000 0.000 0.000 :0(_setprofile)
68+
1 0.000 0.000 0.000 0.000 :0(append)
69+
1 0.000 0.000 0.001 0.001 :0(exec)
70+
1 0.000 0.000 0.000 0.000 <string>:1(<module>)
71+
1 0.001 0.001 0.002 0.002 profile:0(l = []; l.append(1))
72+
0 0.000 0.000 profile:0(profiler)
73+
```
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.test.runtime;
42+
43+
import org.junit.Before;
44+
import org.junit.Test;
45+
46+
import com.oracle.graal.python.test.PythonTests;
47+
48+
import java.io.ByteArrayOutputStream;
49+
import java.io.PrintStream;
50+
51+
import static org.junit.Assert.assertEquals;
52+
53+
public class ProfileTests {
54+
55+
@Before
56+
public void ensureBytecode() {
57+
PythonTests.skipOnLegacyASTInterpreter();
58+
}
59+
60+
@Test
61+
public void profileYield() {
62+
String source = "import sys\n" +
63+
"def f(frame, event, arg): print(frame, event, arg)\n" +
64+
"def fg(): yield 42\n" +
65+
"g = fg()\n" +
66+
"sys.setprofile(f)\n" +
67+
"for i in g: pass";
68+
assertPrints("fg> call None\n" +
69+
"fg> return 42\n" +
70+
"fg> call None\n" +
71+
"fg> return None\n" +
72+
"<module>> return None\n", source);
73+
}
74+
75+
@Test
76+
public void profileException() {
77+
String source = "import sys\n" +
78+
"def f(frame, event, arg): print(frame, event, arg)\n" +
79+
"sys.setprofile(f)\n" +
80+
"try:\n" +
81+
" max(0)\n" +
82+
"except Exception:\n" +
83+
" pass\n";
84+
assertPrints("<module>> c_call <built-in function max>\n" +
85+
"<module>> c_exception <built-in function max>\n" +
86+
"<module>> return None\n", source);
87+
}
88+
89+
private static void assertPrints(String expected, String code) {
90+
final ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
91+
final PrintStream printStream = new PrintStream(byteArray);
92+
PythonTests.runScript(new String[0], code, printStream, System.err);
93+
String result = byteArray.toString().replaceAll("\r\n", "\n");
94+
assertEquals(expected, result.replaceAll(".*code ", ""));
95+
}
96+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*graalpython.lib-python.3.test.test_profile.ProfileTest.test_calling_conventions
2+
*graalpython.lib-python.3.test.test_profile.ProfileTest.test_cprofile
3+
*graalpython.lib-python.3.test.test_profile.ProfileTest.test_run
4+
*graalpython.lib-python.3.test.test_profile.ProfileTest.test_run_profile_as_module
5+
*graalpython.lib-python.3.test.test_profile.ProfileTest.test_runctx
6+
*graalpython.lib-python.3.test.test_profile.TestMain.test_main

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,10 @@ public final class PythonLanguage extends TruffleLanguage<PythonContext> {
213213
private static final LanguageReference<PythonLanguage> REFERENCE = LanguageReference.create(PythonLanguage.class);
214214

215215
/**
216-
* This assumption will be valid if no context set a trace function at any point. Calling
217-
* sys.settrace(None) will not invalidate it
216+
* This assumption will be valid if no context set a trace or profile function at any point.
217+
* Calling sys.settrace(None) or sys.setprofile(None) will not invalidate it
218218
*/
219-
public final Assumption noTracingAssumption = Assumption.create("No tracing function was set");
219+
public final Assumption noTracingOrProfilingAssumption = Assumption.create("No tracing function was set");
220220

221221
@CompilationFinal private boolean singleContext = true;
222222

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/LsprofModuleBuiltins.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
package com.oracle.graal.python.builtins.modules;
4242

4343
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___INIT__;
44+
import static com.oracle.graal.python.util.PythonUtils.toTruffleStringUncached;
4445

4546
import java.util.ArrayList;
4647
import java.util.Arrays;
@@ -320,7 +321,7 @@ private static Object[] getProfilerEntry(ProfilerNode<Payload> node, double avgS
320321
int selfHitCount = node.getPayload().getSelfHitCount();
321322
long hitCount = (long) otherHitCount + selfHitCount;
322323
Object[] profilerEntry = new Object[]{
323-
rootName,
324+
toTruffleStringUncached(rootName),
324325
hitCount,
325326
0,
326327
otherHitCount * avgSampleTime,

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ public void postInitialize0(Python3Core core) {
539539

540540
if (context.getOption(PythonOptions.EnableBytecodeInterpreter)) {
541541
sys.setAttribute(tsLiteral("settrace"), sys.getAttribute(tsLiteral("_settrace")));
542+
sys.setAttribute(tsLiteral("setprofile"), sys.getAttribute(tsLiteral("_setprofile")));
542543
}
543544

544545
TruffleString coreHome = context.getCoreHome();
@@ -1010,6 +1011,27 @@ Object settrace(Object function) {
10101011
}
10111012
}
10121013

1014+
@Builtin(name = "_setprofile", minNumOfPositionalArgs = 1, parameterNames = {
1015+
"function"}, doc = "Set the profiling function. It will be called on each function call\nand return. See the profiler chapter in the library manual.")
1016+
@GenerateNodeFactory
1017+
abstract static class SetProfile extends PythonBuiltinNode {
1018+
@Specialization
1019+
Object settrace(Object function) {
1020+
PythonContext ctx = getContext();
1021+
if (!ctx.getOption(PythonOptions.EnableBytecodeInterpreter)) {
1022+
throw raise(NotImplementedError, ErrorMessages.SETPROFILE_NOT_IMPLEMENTED);
1023+
}
1024+
PythonLanguage language = getLanguage();
1025+
PythonContext.PythonThreadState state = ctx.getThreadState(language);
1026+
if (function == PNone.NONE) {
1027+
state.setProfileFun(null, language);
1028+
} else {
1029+
state.setProfileFun(function, language);
1030+
}
1031+
return PNone.NONE;
1032+
}
1033+
}
1034+
10131035
@Builtin(name = "gettrace")
10141036
@GenerateNodeFactory
10151037
abstract static class GetTrace extends PythonBuiltinNode {
@@ -1023,6 +1045,18 @@ Object gettrace() {
10231045
}
10241046
}
10251047

1048+
@Builtin(name = "getprofile")
1049+
@GenerateNodeFactory
1050+
abstract static class GetProfile extends PythonBuiltinNode {
1051+
@Specialization
1052+
Object getProfile() {
1053+
PythonContext ctx = getContext();
1054+
PythonContext.PythonThreadState state = ctx.getThreadState(getLanguage());
1055+
Object trace = state.getProfileFun();
1056+
return trace == null ? PNone.NONE : trace;
1057+
}
1058+
}
1059+
10261060
@Builtin(name = J_UNRAISABLEHOOK, minNumOfPositionalArgs = 2, maxNumOfPositionalArgs = 2, declaresExplicitSelf = true, doc = "unraisablehook($module, unraisable, /)\n" +
10271061
"--\n" +
10281062
"\n" +

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,7 @@ public abstract class ErrorMessages {
14571457
public static final TruffleString HPY_DEBUG_MODE_NOT_AVAILABLE = tsLiteral("HPy debug mode is not available");
14581458

14591459
public static final TruffleString SETTRACE_NOT_IMPLEMENTED = tsLiteral("sys.settrace is only implemented for the bytecode interpreter.");
1460+
public static final TruffleString SETPROFILE_NOT_IMPLEMENTED = tsLiteral("sys.setprofile is only implemented for the bytecode interpreter.");
14601461
public static final TruffleString ATTRIBUTE_VALUE_MUST_BE_BOOL = tsLiteral("attribute value type must be bool");
14611462
public static final TruffleString HPY_UNEXPECTED_HPY_NULL = tsLiteral("unexpected HPy_NULL");
14621463
public static final TruffleString TOKEN_ALREADY_USED = tsLiteral("%s has already been used once");

0 commit comments

Comments
 (0)