jruby-async-profiler 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.gitmodules +3 -0
- data/Gemfile +4 -0
- data/README.md +35 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/Rakefile +6 -0
- data/ext/async-profiler/.gitattributes +1 -0
- data/ext/async-profiler/.gitignore +6 -0
- data/ext/async-profiler/.travis.yml +11 -0
- data/ext/async-profiler/CHANGELOG.md +107 -0
- data/ext/async-profiler/JavaHome.class +0 -0
- data/ext/async-profiler/LICENSE +201 -0
- data/ext/async-profiler/Makefile +66 -0
- data/ext/async-profiler/README.md +487 -0
- data/ext/async-profiler/demo/SwingSet2.svg +2247 -0
- data/ext/async-profiler/docs/cddl1.txt +358 -0
- data/ext/async-profiler/profiler.sh +240 -0
- data/ext/async-profiler/src/allocTracer.cpp +155 -0
- data/ext/async-profiler/src/allocTracer.h +74 -0
- data/ext/async-profiler/src/arch.h +69 -0
- data/ext/async-profiler/src/arguments.cpp +265 -0
- data/ext/async-profiler/src/arguments.h +152 -0
- data/ext/async-profiler/src/codeCache.cpp +128 -0
- data/ext/async-profiler/src/codeCache.h +99 -0
- data/ext/async-profiler/src/engine.cpp +50 -0
- data/ext/async-profiler/src/engine.h +38 -0
- data/ext/async-profiler/src/flameGraph.cpp +770 -0
- data/ext/async-profiler/src/flameGraph.h +118 -0
- data/ext/async-profiler/src/flightRecorder.cpp +727 -0
- data/ext/async-profiler/src/flightRecorder.h +39 -0
- data/ext/async-profiler/src/frameName.cpp +189 -0
- data/ext/async-profiler/src/frameName.h +56 -0
- data/ext/async-profiler/src/itimer.cpp +49 -0
- data/ext/async-profiler/src/itimer.h +43 -0
- data/ext/async-profiler/src/jattach/jattach.c +437 -0
- data/ext/async-profiler/src/java/one/profiler/AsyncProfiler.java +160 -0
- data/ext/async-profiler/src/java/one/profiler/AsyncProfilerMXBean.java +43 -0
- data/ext/async-profiler/src/java/one/profiler/Counter.java +25 -0
- data/ext/async-profiler/src/java/one/profiler/Events.java +28 -0
- data/ext/async-profiler/src/javaApi.cpp +124 -0
- data/ext/async-profiler/src/lockTracer.cpp +161 -0
- data/ext/async-profiler/src/lockTracer.h +55 -0
- data/ext/async-profiler/src/mutex.cpp +33 -0
- data/ext/async-profiler/src/mutex.h +49 -0
- data/ext/async-profiler/src/os.h +45 -0
- data/ext/async-profiler/src/os_linux.cpp +129 -0
- data/ext/async-profiler/src/os_macos.cpp +115 -0
- data/ext/async-profiler/src/perfEvents.h +60 -0
- data/ext/async-profiler/src/perfEvents_linux.cpp +550 -0
- data/ext/async-profiler/src/perfEvents_macos.cpp +64 -0
- data/ext/async-profiler/src/profiler.cpp +952 -0
- data/ext/async-profiler/src/profiler.h +238 -0
- data/ext/async-profiler/src/spinLock.h +66 -0
- data/ext/async-profiler/src/stackFrame.h +57 -0
- data/ext/async-profiler/src/stackFrame_aarch64.cpp +75 -0
- data/ext/async-profiler/src/stackFrame_arm.cpp +58 -0
- data/ext/async-profiler/src/stackFrame_i386.cpp +82 -0
- data/ext/async-profiler/src/stackFrame_x64.cpp +113 -0
- data/ext/async-profiler/src/symbols.h +37 -0
- data/ext/async-profiler/src/symbols_linux.cpp +354 -0
- data/ext/async-profiler/src/symbols_macos.cpp +156 -0
- data/ext/async-profiler/src/vmEntry.cpp +173 -0
- data/ext/async-profiler/src/vmEntry.h +105 -0
- data/ext/async-profiler/src/vmStructs.cpp +104 -0
- data/ext/async-profiler/src/vmStructs.h +112 -0
- data/ext/async-profiler/src/wallClock.cpp +96 -0
- data/ext/async-profiler/src/wallClock.h +56 -0
- data/ext/async-profiler/test/AllocatingTarget.java +26 -0
- data/ext/async-profiler/test/LoadLibraryTest.java +21 -0
- data/ext/async-profiler/test/Target.java +31 -0
- data/ext/async-profiler/test/ThreadsTarget.java +35 -0
- data/ext/async-profiler/test/alloc-smoke-test.sh +36 -0
- data/ext/async-profiler/test/load-library-test.sh +35 -0
- data/ext/async-profiler/test/smoke-test.sh +37 -0
- data/ext/async-profiler/test/thread-smoke-test.sh +32 -0
- data/jruby-async-profiler.gemspec +32 -0
- data/lib/jruby/async/profiler.rb +10 -0
- data/lib/jruby/async/profiler/version.rb +7 -0
- metadata +155 -0
@@ -0,0 +1,160 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright 2018 Andrei Pangin
|
3
|
+
*
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
* you may not use this file except in compliance with the License.
|
6
|
+
* You may obtain a copy of the License at
|
7
|
+
*
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
*
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
* See the License for the specific language governing permissions and
|
14
|
+
* limitations under the License.
|
15
|
+
*/
|
16
|
+
|
17
|
+
package one.profiler;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Java API for in-process profiling. Serves as a wrapper around
|
21
|
+
* async-profiler native library. This class is a singleton.
|
22
|
+
* The first call to {@link #getInstance()} initiates loading of
|
23
|
+
* libasyncProfiler.so.
|
24
|
+
*/
|
25
|
+
public class AsyncProfiler implements AsyncProfilerMXBean {
|
26
|
+
private static AsyncProfiler instance;
|
27
|
+
|
28
|
+
private final String version;
|
29
|
+
|
30
|
+
private AsyncProfiler() {
|
31
|
+
this.version = version0();
|
32
|
+
}
|
33
|
+
|
34
|
+
public static AsyncProfiler getInstance() {
|
35
|
+
return getInstance(null);
|
36
|
+
}
|
37
|
+
|
38
|
+
public static synchronized AsyncProfiler getInstance(String libPath) {
|
39
|
+
if (instance != null) {
|
40
|
+
return instance;
|
41
|
+
}
|
42
|
+
|
43
|
+
if (libPath == null) {
|
44
|
+
System.loadLibrary("asyncProfiler");
|
45
|
+
} else {
|
46
|
+
System.load(libPath);
|
47
|
+
}
|
48
|
+
|
49
|
+
instance = new AsyncProfiler();
|
50
|
+
return instance;
|
51
|
+
}
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Start profiling
|
55
|
+
*
|
56
|
+
* @param event Profiling event, see {@link Events}
|
57
|
+
* @param interval Sampling interval, e.g. nanoseconds for Events.CPU
|
58
|
+
* @throws IllegalStateException If profiler is already running
|
59
|
+
*/
|
60
|
+
@Override
|
61
|
+
public void start(String event, long interval) throws IllegalStateException {
|
62
|
+
start0(event, interval, true);
|
63
|
+
}
|
64
|
+
|
65
|
+
/**
|
66
|
+
* Start or resume profiling without resetting collected data.
|
67
|
+
* Note that event and interval may change since the previous profiling session.
|
68
|
+
*
|
69
|
+
* @param event Profiling event, see {@link Events}
|
70
|
+
* @param interval Sampling interval, e.g. nanoseconds for Events.CPU
|
71
|
+
* @throws IllegalStateException If profiler is already running
|
72
|
+
*/
|
73
|
+
@Override
|
74
|
+
public void resume(String event, long interval) throws IllegalStateException {
|
75
|
+
start0(event, interval, false);
|
76
|
+
}
|
77
|
+
|
78
|
+
/**
|
79
|
+
* Stop profiling (without dumping results)
|
80
|
+
*
|
81
|
+
* @throws IllegalStateException If profiler is not running
|
82
|
+
*/
|
83
|
+
@Override
|
84
|
+
public void stop() throws IllegalStateException {
|
85
|
+
stop0();
|
86
|
+
}
|
87
|
+
|
88
|
+
/**
|
89
|
+
* Get the number of samples collected during the profiling session
|
90
|
+
*
|
91
|
+
* @return Number of samples
|
92
|
+
*/
|
93
|
+
@Override
|
94
|
+
public native long getSamples();
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Get profiler agent version, e.g. "1.0"
|
98
|
+
*
|
99
|
+
* @return Version string
|
100
|
+
*/
|
101
|
+
@Override
|
102
|
+
public String getVersion() {
|
103
|
+
return version;
|
104
|
+
}
|
105
|
+
|
106
|
+
/**
|
107
|
+
* Execute an agent-compatible profiling command -
|
108
|
+
* the comma-separated list of arguments described in arguments.cpp
|
109
|
+
*
|
110
|
+
* @param command Profiling command
|
111
|
+
* @return The command result
|
112
|
+
* @throws IllegalArgumentException If failed to parse the command
|
113
|
+
* @throws java.io.IOException If failed to create output file
|
114
|
+
*/
|
115
|
+
@Override
|
116
|
+
public String execute(String command) throws IllegalArgumentException, java.io.IOException {
|
117
|
+
return execute0(command);
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Dump profile in 'collapsed stacktraces' format
|
122
|
+
*
|
123
|
+
* @param counter Which counter to display in the output
|
124
|
+
* @return Textual representation of the profile
|
125
|
+
*/
|
126
|
+
@Override
|
127
|
+
public String dumpCollapsed(Counter counter) {
|
128
|
+
return dumpCollapsed0(counter.ordinal());
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Dump collected stack traces
|
133
|
+
*
|
134
|
+
* @param maxTraces Maximum number of stack traces to dump. 0 means no limit
|
135
|
+
* @return Textual representation of the profile
|
136
|
+
*/
|
137
|
+
@Override
|
138
|
+
public String dumpTraces(int maxTraces) {
|
139
|
+
return dumpTraces0(maxTraces);
|
140
|
+
}
|
141
|
+
|
142
|
+
/**
|
143
|
+
* Dump flat profile, i.e. the histogram of the hottest methods
|
144
|
+
*
|
145
|
+
* @param maxMethods Maximum number of methods to dump. 0 means no limit
|
146
|
+
* @return Textual representation of the profile
|
147
|
+
*/
|
148
|
+
@Override
|
149
|
+
public String dumpFlat(int maxMethods) {
|
150
|
+
return dumpFlat0(maxMethods);
|
151
|
+
}
|
152
|
+
|
153
|
+
private native void start0(String event, long interval, boolean reset) throws IllegalStateException;
|
154
|
+
private native void stop0() throws IllegalStateException;
|
155
|
+
private native String execute0(String command) throws IllegalArgumentException, java.io.IOException;
|
156
|
+
private native String dumpCollapsed0(int counter);
|
157
|
+
private native String dumpTraces0(int maxTraces);
|
158
|
+
private native String dumpFlat0(int maxMethods);
|
159
|
+
private native String version0();
|
160
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright 2018 Andrei Pangin
|
3
|
+
*
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
* you may not use this file except in compliance with the License.
|
6
|
+
* You may obtain a copy of the License at
|
7
|
+
*
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
*
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
* See the License for the specific language governing permissions and
|
14
|
+
* limitations under the License.
|
15
|
+
*/
|
16
|
+
|
17
|
+
package one.profiler;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* AsyncProfiler interface for JMX server.
|
21
|
+
* How to register AsyncProfiler MBean:
|
22
|
+
*
|
23
|
+
* <pre>{@code
|
24
|
+
* ManagementFactory.getPlatformMBeanServer().registerMBean(
|
25
|
+
* AsyncProfiler.getInstance(),
|
26
|
+
* new ObjectName("one.profiler:type=AsyncProfiler")
|
27
|
+
* );
|
28
|
+
* }</pre>
|
29
|
+
*/
|
30
|
+
public interface AsyncProfilerMXBean {
|
31
|
+
void start(String event, long interval) throws IllegalStateException;
|
32
|
+
void resume(String event, long interval) throws IllegalStateException;
|
33
|
+
void stop() throws IllegalStateException;
|
34
|
+
|
35
|
+
long getSamples();
|
36
|
+
String getVersion();
|
37
|
+
|
38
|
+
String execute(String command) throws IllegalArgumentException, java.io.IOException;
|
39
|
+
|
40
|
+
String dumpCollapsed(Counter counter);
|
41
|
+
String dumpTraces(int maxTraces);
|
42
|
+
String dumpFlat(int maxMethods);
|
43
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright 2018 Andrei Pangin
|
3
|
+
*
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
* you may not use this file except in compliance with the License.
|
6
|
+
* You may obtain a copy of the License at
|
7
|
+
*
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
*
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
* See the License for the specific language governing permissions and
|
14
|
+
* limitations under the License.
|
15
|
+
*/
|
16
|
+
|
17
|
+
package one.profiler;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Which metrics to use when generating profile in collapsed stack traces format.
|
21
|
+
*/
|
22
|
+
public enum Counter {
|
23
|
+
SAMPLES,
|
24
|
+
TOTAL
|
25
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright 2018 Andrei Pangin
|
3
|
+
*
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
* you may not use this file except in compliance with the License.
|
6
|
+
* You may obtain a copy of the License at
|
7
|
+
*
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
*
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
* See the License for the specific language governing permissions and
|
14
|
+
* limitations under the License.
|
15
|
+
*/
|
16
|
+
|
17
|
+
package one.profiler;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Predefined event names to use in {@link AsyncProfiler#start(String, long)}
|
21
|
+
*/
|
22
|
+
public class Events {
|
23
|
+
public static final String CPU = "cpu";
|
24
|
+
public static final String ALLOC = "alloc";
|
25
|
+
public static final String LOCK = "lock";
|
26
|
+
public static final String WALL = "wall";
|
27
|
+
public static final String ITIMER = "itimer";
|
28
|
+
}
|
@@ -0,0 +1,124 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright 2016 Andrei Pangin
|
3
|
+
*
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
* you may not use this file except in compliance with the License.
|
6
|
+
* You may obtain a copy of the License at
|
7
|
+
*
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
*
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
* See the License for the specific language governing permissions and
|
14
|
+
* limitations under the License.
|
15
|
+
*/
|
16
|
+
|
17
|
+
#include <fstream>
|
18
|
+
#include <sstream>
|
19
|
+
#include <errno.h>
|
20
|
+
#include <string.h>
|
21
|
+
#include "arguments.h"
|
22
|
+
#include "profiler.h"
|
23
|
+
|
24
|
+
|
25
|
+
static void throw_new(JNIEnv* env, const char* exception_class, const char* message) {
|
26
|
+
jclass cls = env->FindClass(exception_class);
|
27
|
+
if (cls != NULL) {
|
28
|
+
env->ThrowNew(cls, message);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
|
33
|
+
extern "C" JNIEXPORT void JNICALL
|
34
|
+
Java_one_profiler_AsyncProfiler_start0(JNIEnv* env, jobject unused, jstring event, jlong interval, jboolean reset) {
|
35
|
+
Arguments args;
|
36
|
+
args._event = env->GetStringUTFChars(event, NULL);
|
37
|
+
args._interval = interval;
|
38
|
+
Error error = Profiler::_instance.start(args, reset);
|
39
|
+
env->ReleaseStringUTFChars(event, args._event);
|
40
|
+
|
41
|
+
if (error) {
|
42
|
+
throw_new(env, "java/lang/IllegalStateException", error.message());
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
extern "C" JNIEXPORT void JNICALL
|
47
|
+
Java_one_profiler_AsyncProfiler_stop0(JNIEnv* env, jobject unused) {
|
48
|
+
Error error = Profiler::_instance.stop();
|
49
|
+
|
50
|
+
if (error) {
|
51
|
+
throw_new(env, "java/lang/IllegalStateException", error.message());
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
extern "C" JNIEXPORT jlong JNICALL
|
56
|
+
Java_one_profiler_AsyncProfiler_getSamples(JNIEnv* env, jobject unused) {
|
57
|
+
return (jlong)Profiler::_instance.total_samples();
|
58
|
+
}
|
59
|
+
|
60
|
+
extern "C" JNIEXPORT jstring JNICALL
|
61
|
+
Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring command) {
|
62
|
+
Arguments args;
|
63
|
+
const char* command_str = env->GetStringUTFChars(command, NULL);
|
64
|
+
Error error = args.parse(command_str);
|
65
|
+
env->ReleaseStringUTFChars(command, command_str);
|
66
|
+
|
67
|
+
if (error) {
|
68
|
+
throw_new(env, "java/lang/IllegalArgumentException", error.message());
|
69
|
+
return NULL;
|
70
|
+
}
|
71
|
+
|
72
|
+
if (args._file == NULL || args._output == OUTPUT_JFR) {
|
73
|
+
std::ostringstream out;
|
74
|
+
Profiler::_instance.runInternal(args, out);
|
75
|
+
return env->NewStringUTF(out.str().c_str());
|
76
|
+
} else {
|
77
|
+
std::ofstream out(args._file, std::ios::out | std::ios::trunc);
|
78
|
+
if (out.is_open()) {
|
79
|
+
Profiler::_instance.runInternal(args, out);
|
80
|
+
out.close();
|
81
|
+
return env->NewStringUTF("OK");
|
82
|
+
} else {
|
83
|
+
throw_new(env, "java/io/IOException", strerror(errno));
|
84
|
+
return NULL;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
extern "C" JNIEXPORT jstring JNICALL
|
90
|
+
Java_one_profiler_AsyncProfiler_dumpCollapsed0(JNIEnv* env, jobject unused, jint counter) {
|
91
|
+
Arguments args;
|
92
|
+
args._counter = counter == COUNTER_SAMPLES ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
93
|
+
|
94
|
+
std::ostringstream out;
|
95
|
+
Profiler::_instance.dumpCollapsed(out, args);
|
96
|
+
return env->NewStringUTF(out.str().c_str());
|
97
|
+
}
|
98
|
+
|
99
|
+
extern "C" JNIEXPORT jstring JNICALL
|
100
|
+
Java_one_profiler_AsyncProfiler_dumpTraces0(JNIEnv* env, jobject unused, jint max_traces) {
|
101
|
+
Arguments args;
|
102
|
+
args._dump_traces = max_traces ? max_traces : MAX_CALLTRACES;
|
103
|
+
|
104
|
+
std::ostringstream out;
|
105
|
+
Profiler::_instance.dumpSummary(out);
|
106
|
+
Profiler::_instance.dumpTraces(out, args);
|
107
|
+
return env->NewStringUTF(out.str().c_str());
|
108
|
+
}
|
109
|
+
|
110
|
+
extern "C" JNIEXPORT jstring JNICALL
|
111
|
+
Java_one_profiler_AsyncProfiler_dumpFlat0(JNIEnv* env, jobject unused, jint max_methods) {
|
112
|
+
Arguments args;
|
113
|
+
args._dump_flat = max_methods ? max_methods : MAX_CALLTRACES;
|
114
|
+
|
115
|
+
std::ostringstream out;
|
116
|
+
Profiler::_instance.dumpSummary(out);
|
117
|
+
Profiler::_instance.dumpFlat(out, args);
|
118
|
+
return env->NewStringUTF(out.str().c_str());
|
119
|
+
}
|
120
|
+
|
121
|
+
extern "C" JNIEXPORT jstring JNICALL
|
122
|
+
Java_one_profiler_AsyncProfiler_version0(JNIEnv* env, jobject unused) {
|
123
|
+
return env->NewStringUTF(PROFILER_VERSION);
|
124
|
+
}
|
@@ -0,0 +1,161 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright 2017 Andrei Pangin
|
3
|
+
*
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
* you may not use this file except in compliance with the License.
|
6
|
+
* You may obtain a copy of the License at
|
7
|
+
*
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
*
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
* See the License for the specific language governing permissions and
|
14
|
+
* limitations under the License.
|
15
|
+
*/
|
16
|
+
|
17
|
+
#include <string.h>
|
18
|
+
#include "lockTracer.h"
|
19
|
+
#include "profiler.h"
|
20
|
+
#include "vmStructs.h"
|
21
|
+
|
22
|
+
|
23
|
+
jlong LockTracer::_start_time = 0;
|
24
|
+
jclass LockTracer::_LockSupport = NULL;
|
25
|
+
jmethodID LockTracer::_getBlocker = NULL;
|
26
|
+
UnsafeParkFunc LockTracer::_original_Unsafe_Park = NULL;
|
27
|
+
bool LockTracer::_supports_lock_names = false;
|
28
|
+
|
29
|
+
Error LockTracer::start(Arguments& args) {
|
30
|
+
// PermGen in JDK 7 makes difficult to get symbol name from jclass.
|
31
|
+
// Also some JVMs do not support VMStructs at all.
|
32
|
+
// Let's just record stack traces without lock names in these cases.
|
33
|
+
_supports_lock_names = VMStructs::available() && !VMStructs::hasPermGen();
|
34
|
+
|
35
|
+
// Enable Java Monitor events
|
36
|
+
jvmtiEnv* jvmti = VM::jvmti();
|
37
|
+
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, NULL);
|
38
|
+
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, NULL);
|
39
|
+
jvmti->GetTime(&_start_time);
|
40
|
+
|
41
|
+
if (_getBlocker == NULL) {
|
42
|
+
JNIEnv* env = VM::jni();
|
43
|
+
_LockSupport = (jclass)env->NewGlobalRef(env->FindClass("java/util/concurrent/locks/LockSupport"));
|
44
|
+
_getBlocker = env->GetStaticMethodID(_LockSupport, "getBlocker", "(Ljava/lang/Thread;)Ljava/lang/Object;");
|
45
|
+
}
|
46
|
+
|
47
|
+
if (_original_Unsafe_Park == NULL) {
|
48
|
+
NativeCodeCache* libjvm = Profiler::_instance.jvmLibrary();
|
49
|
+
_original_Unsafe_Park = (UnsafeParkFunc)libjvm->findSymbol("Unsafe_Park");
|
50
|
+
if (_original_Unsafe_Park == NULL) {
|
51
|
+
// In some macOS builds of JDK 11 Unsafe_Park appears to have a C++ decorated name
|
52
|
+
_original_Unsafe_Park = (UnsafeParkFunc)libjvm->findSymbol("_ZL11Unsafe_ParkP7JNIEnv_P8_jobjecthl");
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
// Intercent Unsafe.park() for tracing contended ReentrantLocks
|
57
|
+
if (_original_Unsafe_Park != NULL) {
|
58
|
+
bindUnsafePark(UnsafeParkTrap);
|
59
|
+
}
|
60
|
+
|
61
|
+
return Error::OK;
|
62
|
+
}
|
63
|
+
|
64
|
+
void LockTracer::stop() {
|
65
|
+
// Disable Java Monitor events
|
66
|
+
jvmtiEnv* jvmti = VM::jvmti();
|
67
|
+
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, NULL);
|
68
|
+
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, NULL);
|
69
|
+
|
70
|
+
// Reset Unsafe.park() trap
|
71
|
+
if (_original_Unsafe_Park != NULL) {
|
72
|
+
bindUnsafePark(_original_Unsafe_Park);
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
void JNICALL LockTracer::MonitorContendedEnter(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object) {
|
77
|
+
jlong enter_time;
|
78
|
+
jvmti->GetTime(&enter_time);
|
79
|
+
jvmti->SetTag(thread, enter_time);
|
80
|
+
}
|
81
|
+
|
82
|
+
void JNICALL LockTracer::MonitorContendedEntered(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object) {
|
83
|
+
jlong enter_time, entered_time;
|
84
|
+
jvmti->GetTime(&entered_time);
|
85
|
+
jvmti->GetTag(thread, &enter_time);
|
86
|
+
|
87
|
+
// Time is meaningless if lock attempt has started before profiling
|
88
|
+
if (enter_time >= _start_time) {
|
89
|
+
recordContendedLock(env->GetObjectClass(object), entered_time - enter_time);
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
void JNICALL LockTracer::UnsafeParkTrap(JNIEnv* env, jobject instance, jboolean isAbsolute, jlong time) {
|
94
|
+
jvmtiEnv* jvmti = VM::jvmti();
|
95
|
+
jclass lock_class = getParkBlockerClass(jvmti, env);
|
96
|
+
jlong park_start_time, park_end_time;
|
97
|
+
|
98
|
+
if (lock_class != NULL) {
|
99
|
+
jvmti->GetTime(&park_start_time);
|
100
|
+
}
|
101
|
+
|
102
|
+
_original_Unsafe_Park(env, instance, isAbsolute, time);
|
103
|
+
|
104
|
+
if (lock_class != NULL) {
|
105
|
+
jvmti->GetTime(&park_end_time);
|
106
|
+
recordContendedLock(lock_class, park_end_time - park_start_time);
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
jclass LockTracer::getParkBlockerClass(jvmtiEnv* jvmti, JNIEnv* env) {
|
111
|
+
jthread thread;
|
112
|
+
if (jvmti->GetCurrentThread(&thread) != 0) {
|
113
|
+
return NULL;
|
114
|
+
}
|
115
|
+
|
116
|
+
// Call LockSupport.getBlocker(Thread.currentThread())
|
117
|
+
jobject park_blocker = env->CallStaticObjectMethod(_LockSupport, _getBlocker, thread);
|
118
|
+
if (park_blocker == NULL) {
|
119
|
+
return NULL;
|
120
|
+
}
|
121
|
+
|
122
|
+
jclass lock_class = env->GetObjectClass(park_blocker);
|
123
|
+
char* class_name;
|
124
|
+
if (jvmti->GetClassSignature(lock_class, &class_name, NULL) != 0) {
|
125
|
+
return NULL;
|
126
|
+
}
|
127
|
+
|
128
|
+
// Do not count synchronizers other than ReentrantLock, ReentrantReadWriteLock and Semaphore
|
129
|
+
if (strncmp(class_name, "Ljava/util/concurrent/locks/ReentrantLock", 41) != 0 &&
|
130
|
+
strncmp(class_name, "Ljava/util/concurrent/locks/ReentrantReadWriteLock", 50) != 0 &&
|
131
|
+
strncmp(class_name, "Ljava/util/concurrent/Semaphore", 31) != 0) {
|
132
|
+
lock_class = NULL;
|
133
|
+
}
|
134
|
+
|
135
|
+
jvmti->Deallocate((unsigned char*)class_name);
|
136
|
+
return lock_class;
|
137
|
+
}
|
138
|
+
|
139
|
+
void LockTracer::recordContendedLock(jclass lock_class, jlong time) {
|
140
|
+
if (_supports_lock_names) {
|
141
|
+
VMSymbol* lock_name = (*(java_lang_Class**)lock_class)->klass()->name();
|
142
|
+
Profiler::_instance.recordSample(NULL, time, BCI_SYMBOL, (jmethodID)lock_name);
|
143
|
+
} else {
|
144
|
+
Profiler::_instance.recordSample(NULL, time, BCI_SYMBOL, NULL);
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
void LockTracer::bindUnsafePark(UnsafeParkFunc entry) {
|
149
|
+
JNIEnv* env = VM::jni();
|
150
|
+
|
151
|
+
// Try JDK 9+ package first, then fallback to JDK 8 package
|
152
|
+
jclass unsafe = env->FindClass("jdk/internal/misc/Unsafe");
|
153
|
+
if (unsafe == NULL) unsafe = env->FindClass("sun/misc/Unsafe");
|
154
|
+
|
155
|
+
if (unsafe != NULL) {
|
156
|
+
const JNINativeMethod unsafe_park = {(char*)"park", (char*)"(ZJ)V", (void*)entry};
|
157
|
+
env->RegisterNatives(unsafe, &unsafe_park, 1);
|
158
|
+
}
|
159
|
+
|
160
|
+
env->ExceptionClear();
|
161
|
+
}
|