jruby-async-profiler 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|