Skip to content

Commit d7ab642

Browse files
authored
Add --nostop option that continues profiling even when using --ttsp or --begin/--end (#1046)
1 parent b7d66a5 commit d7ab642

File tree

8 files changed

+110
-9
lines changed

8 files changed

+110
-9
lines changed

docs/ProfilerOptions.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ The below options are `action`s for async-profiler and common for both `asprof`
4949
| `--clock SOURCE` | `clock=SOURCE` | Clock source for JFR timestamps: `tsc` (default) or `monotonic` (equivalent for `CLOCK_MONOTONIC`). |
5050
| `--begin function` | `begin=FUNCTION` | Automatically start profiling when the specified native function is executed. |
5151
| `--end function` | `end=FUNCTION` | Automatically stop profiling when the specified native function is executed. |
52-
| `--ttsp` | `ttsp` | time-to-safepoint profiling. An alias for `--begin SafepointSynchronize::begin --end RuntimeService::record_safepoint_synchronized`.<br>It is not a separate event type, but rather a constraint. Whatever event type you choose (e.g. `cpu` or `wall`), the profiler will work as usual, except that only events between the safepoint request and the start of the VM operation will be recorded. |
52+
| `--ttsp` | `ttsp` | Time-to-safepoint profiling. An alias for `--begin SafepointSynchronize::begin --end RuntimeService::record_safepoint_synchronized`.</br>It is not a separate event type, but rather a constraint. Whatever event type you choose (e.g. `cpu` or `wall`), the profiler will work as usual, except that only events between the safepoint request and the start of the VM operation will be recorded. |
53+
| `--nostop` | `nostop` | Record profiling window between `--begin` and `--end`, but do not stop profiling outside window. |
5354
| `--libpath PATH` | `libpath=PATH` | Full path to libasyncProfiler.so in the container |
5455
| `--filter FILTER` | `filter=FILTER` | Filter threads with thread ids during wall-clock profiling mode |
5556
| `--fdtransfer` | `fdtransfer` | Runs a background process that provides access to perf_events to an unprivileged process. `--fdtransfer` is useful for profiling a process in a container (which lacks access to perf_events) from the host.<br>See [Profiling Java in a container](#https://github.com/async-profiler/async-profiler/blob/master/docs/ProfilingInContainer.md). |

src/arguments.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ static const Multiplier UNIVERSAL[] = {{'n', 1}, {'u', 1000}, {'m', 1000000}, {'
102102
// exclude=PATTERN - exclude stack traces containing PATTERN
103103
// begin=FUNCTION - begin profiling when FUNCTION is executed
104104
// end=FUNCTION - end profiling when FUNCTION is executed
105+
// nostop - do not stop profiling outside --begin/--end window
105106
// title=TITLE - FlameGraph title
106107
// minwidth=PCT - FlameGraph minimum frame width in percent
107108
// reverse - generate stack-reversed FlameGraph / Call tree
@@ -396,6 +397,9 @@ Error Arguments::parse(const char* args) {
396397
CASE("end")
397398
_end = value;
398399

400+
CASE("nostop")
401+
_nostop = true;
402+
399403
// FlameGraph options
400404
CASE("title")
401405
_title = value;

src/arguments.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ class Arguments {
180180
bool _sched;
181181
bool _live;
182182
bool _nobatch;
183+
bool _nostop;
183184
bool _alluser;
184185
bool _fdtransfer;
185186
const char* _fdtransfer_path;
@@ -230,6 +231,7 @@ class Arguments {
230231
_sched(false),
231232
_live(false),
232233
_nobatch(false),
234+
_nostop(false),
233235
_alluser(false),
234236
_fdtransfer(false),
235237
_fdtransfer_path(NULL),

src/main/main.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ static const char USAGE_STRING[] =
7979
" --clock source clock source for JFR timestamps: tsc|monotonic\n"
8080
" --begin function begin profiling when function is executed\n"
8181
" --end function end profiling when function is executed\n"
82-
" --ttsp time-to-safepoint profiling\n"
82+
" --ttsp only time-to-safepoint profiling \n"
83+
" --nostop do not stop profiling outside --begin/--end window\n"
8384
" --jfropts opts JFR recording options: mem\n"
8485
" --jfrsync config synchronize profiler with JFR recording\n"
8586
" --libpath path full path to libasyncProfiler.so in the container\n"
@@ -498,6 +499,9 @@ int main(int argc, const char** argv) {
498499
} else if (arg == "--ttsp") {
499500
params << ",begin=SafepointSynchronize::begin,end=RuntimeService::record_safepoint_synchronized";
500501

502+
} else if (arg == "--nostop") {
503+
params << ",nostop";
504+
501505
} else if (arg == "--all-user") {
502506
params << ",alluser";
503507

src/profiler.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ void Profiler::switchLibraryTrap(bool enable) {
795795
}
796796
}
797797

798-
Error Profiler::installTraps(const char* begin, const char* end) {
798+
Error Profiler::installTraps(const char* begin, const char* end, bool nostop) {
799799
const void* begin_addr = NULL;
800800
if (begin != NULL && (begin_addr = resolveSymbol(begin)) == NULL) {
801801
return Error("Begin address not found");
@@ -808,11 +808,12 @@ Error Profiler::installTraps(const char* begin, const char* end) {
808808

809809
_begin_trap.assign(begin_addr);
810810
_end_trap.assign(end_addr);
811+
_nostop = nostop;
811812

812813
if (_begin_trap.entry() == 0) {
813814
_engine->enableEvents(true);
814815
} else {
815-
_engine->enableEvents(false);
816+
_engine->enableEvents(nostop);
816817
if (!_begin_trap.install()) {
817818
return Error("Cannot install begin breakpoint");
818819
}
@@ -837,7 +838,7 @@ void Profiler::trapHandler(int signo, siginfo_t* siginfo, void* ucontext) {
837838
_end_trap.install();
838839
frame.pc() = _begin_trap.entry();
839840
} else if (_end_trap.covers(frame.pc())) {
840-
_engine->enableEvents(false);
841+
_engine->enableEvents(_nostop);
841842
_end_trap.uninstall();
842843
profiling_window._end_time = TSC::ticks();
843844
recordEventOnly(PROFILING_WINDOW, &profiling_window);
@@ -1159,7 +1160,7 @@ Error Profiler::start(Arguments& args, bool reset) {
11591160
// Kernel symbols are useful only for perf_events without --all-user
11601161
updateSymbols(_engine == &perf_events && !args._alluser);
11611162

1162-
error = installTraps(args._begin, args._end);
1163+
error = installTraps(args._begin, args._end, args._nostop);
11631164
if (error) {
11641165
return error;
11651166
}

src/profiler.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class Profiler {
5454
State _state;
5555
Trap _begin_trap;
5656
Trap _end_trap;
57+
bool _nostop;
5758
Mutex _thread_names_lock;
5859
// TODO: single map?
5960
std::map<int, std::string> _thread_names;
@@ -100,7 +101,7 @@ class Profiler {
100101
static void* dlopen_hook(const char* filename, int flags);
101102
void switchLibraryTrap(bool enable);
102103

103-
Error installTraps(const char* begin, const char* end);
104+
Error installTraps(const char* begin, const char* end, bool nostop);
104105
void uninstallTraps();
105106

106107
void addJavaMethod(const void* address, int length, jmethodID method);

test/test/jfr/JfrTests.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
import one.profiler.test.TestProcess;
1414

1515
import java.nio.file.Paths;
16-
import java.util.HashMap;
17-
import java.util.Map;
16+
import java.time.Instant;
17+
import java.time.temporal.ChronoUnit;
18+
import java.util.*;
1819

1920
public class JfrTests {
2021

@@ -76,4 +77,50 @@ public void parseMultiModeRecording(TestProcess p) throws Exception {
7677
Assert.isGreater(eventsCount.get("jdk.JavaMonitorEnter"), 50);
7778
Assert.isGreater(eventsCount.get("jdk.ObjectAllocationInNewTLAB"), 50);
7879
}
80+
81+
/**
82+
* Test to validate time to safepoint profiling
83+
*
84+
* @param p The test process to profile with.
85+
* @throws Exception Any exception thrown during profiling JFR output parsing.
86+
*/
87+
@Test(mainClass = Ttsp.class)
88+
public void ttsp(TestProcess p) throws Exception {
89+
p.profile("-d 3 -i 1ms --ttsp -f %f.jfr");
90+
assert !containsSamplesOutsideWindow(p) : "Expected no samples outside of ttsp window";
91+
}
92+
93+
/**
94+
* Test to validate time to safepoint profiling (recording the windows only, profiling starts immediately)
95+
*
96+
* @param p The test process to profile with.
97+
* @throws Exception Any exception thrown during profiling JFR output parsing.
98+
*/
99+
@Test(mainClass = Ttsp.class)
100+
public void ttspNostop(TestProcess p) throws Exception {
101+
p.profile("-d 3 -i 1ms --ttsp --nostop -f %f.jfr");
102+
assert containsSamplesOutsideWindow(p) : "Expected to find samples outside of ttsp window";
103+
}
104+
105+
private boolean containsSamplesOutsideWindow(TestProcess p) throws Exception {
106+
TreeMap<Instant, Instant> profilerWindows = new TreeMap<>();
107+
List<RecordedEvent> samples = new ArrayList<>();
108+
try (RecordingFile recordingFile = new RecordingFile(Paths.get(p.getFile("%f").getAbsolutePath()))) {
109+
while (recordingFile.hasMoreEvents()) {
110+
RecordedEvent event = recordingFile.readEvent();
111+
if (event.getEventType().getName().equals("profiler.Window")) {
112+
profilerWindows.put(event.getStartTime(), event.getEndTime());
113+
} else if (event.getEventType().getName().equals("jdk.ExecutionSample")) {
114+
samples.add(event);
115+
}
116+
}
117+
}
118+
119+
return samples.stream().anyMatch(event -> {
120+
Map.Entry<Instant, Instant> entry = profilerWindows.floorEntry(event.getStartTime().plus(10, ChronoUnit.MILLIS));
121+
Instant entryEnd = entry == null ? Instant.MIN : entry.getValue().plus(10, ChronoUnit.MILLIS);
122+
// check that the current sample takes place during a profiling window, allowing for a 10ms buffer at each end
123+
return entryEnd.isBefore(event.getStartTime());
124+
});
125+
}
79126
}

test/test/jfr/Ttsp.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright The async-profiler authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package test.jfr;
7+
8+
import java.util.Random;
9+
10+
class Ttsp {
11+
12+
static private byte loop() {
13+
byte[] byteArray = new byte[1024 * 1024 * 1024];
14+
for (int i = 0; i < 10000; i++) {
15+
new Random().nextBytes(byteArray);
16+
}
17+
return byteArray[0];
18+
}
19+
20+
public static void main(String[] args) throws Exception {
21+
new Thread(() -> {
22+
while (true) {
23+
System.gc();
24+
try {
25+
Thread.sleep(20);
26+
} catch (InterruptedException e) {
27+
throw new RuntimeException(e);
28+
}
29+
}
30+
}).start();
31+
32+
Thread.sleep(1000);
33+
34+
new Thread(() -> {
35+
while (true) {
36+
loop();
37+
}
38+
}).start();
39+
}
40+
}
41+

0 commit comments

Comments
 (0)