Skip to content

Commit 2afb4c3

Browse files
committed
8297338: JFR: RemoteRecordingStream doesn't respect setMaxAge and setMaxSize
Reviewed-by: mgronlun
1 parent 8df3bc4 commit 2afb4c3

File tree

5 files changed

+246
-17
lines changed

5 files changed

+246
-17
lines changed

src/jdk.management.jfr/share/classes/jdk/management/jfr/DiskRepository.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -429,7 +429,7 @@ private void trimToAge(Instant oldest) {
429429
}
430430
int count = 0;
431431
while (chunks.size() > 1) {
432-
DiskChunk oldestChunk = chunks.getLast();
432+
DiskChunk oldestChunk = chunks.peekLast();
433433
if (oldestChunk.endTime.isAfter(oldest)) {
434434
return;
435435
}
@@ -440,14 +440,14 @@ private void trimToAge(Instant oldest) {
440440
}
441441

442442
private void removeOldestChunk() {
443-
DiskChunk chunk = chunks.poll();
443+
DiskChunk chunk = chunks.pollLast();
444444
chunk.release();
445445
size -= chunk.size;
446446
}
447447

448448
public synchronized void onChunkComplete(long endTimeNanos) {
449449
while (!chunks.isEmpty()) {
450-
DiskChunk oldestChunk = chunks.peek();
450+
DiskChunk oldestChunk = chunks.peekLast();
451451
if (oldestChunk.startTimeNanos < endTimeNanos) {
452452
removeOldestChunk();
453453
} else {
@@ -460,7 +460,7 @@ private void addChunk(DiskChunk chunk) {
460460
if (maxAge != null) {
461461
trimToAge(chunk.endTime.minus(maxAge));
462462
}
463-
chunks.push(chunk);
463+
chunks.addFirst(chunk);
464464
size += chunk.size;
465465
trimToSize();
466466

@@ -500,9 +500,13 @@ public synchronized void complete() {
500500

501501
public synchronized FileDump newDump(long endTime) {
502502
FileDump fd = new FileDump(endTime);
503-
for (DiskChunk dc : chunks) {
503+
// replay history by iterating from oldest to most recent
504+
Iterator<DiskChunk> it = chunks.descendingIterator();
505+
while (it.hasNext()) {
506+
DiskChunk dc = it.next();
504507
fd.add(dc);
505508
}
509+
506510
if (!fd.isComplete()) {
507511
fileDumps.add(fd);
508512
}

src/jdk.management.jfr/share/classes/jdk/management/jfr/FileDump.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -29,12 +29,12 @@
2929
import java.nio.file.Path;
3030
import java.nio.file.StandardOpenOption;
3131
import java.util.ArrayDeque;
32-
import java.util.Queue;
32+
import java.util.Deque;
3333

3434
import jdk.management.jfr.DiskRepository.DiskChunk;
3535

3636
final class FileDump {
37-
private final Queue<DiskChunk> chunks = new ArrayDeque<>();
37+
private final Deque<DiskChunk> chunks = new ArrayDeque<>();
3838
private final long stopTimeMillis;
3939
private boolean complete;
4040

@@ -47,7 +47,7 @@ public synchronized void add(DiskChunk dc) {
4747
return;
4848
}
4949
dc.acquire();
50-
chunks.add(dc);
50+
chunks.addFirst(dc);
5151
long endMillis = dc.endTimeNanos / 1_000_000;
5252
if (endMillis >= stopTimeMillis) {
5353
setComplete();
@@ -75,7 +75,7 @@ private DiskChunk oldestChunk() throws InterruptedException {
7575
while (true) {
7676
synchronized (this) {
7777
if (!chunks.isEmpty()) {
78-
return chunks.poll();
78+
return chunks.pollLast();
7979
}
8080
if (complete) {
8181
return null;
@@ -86,14 +86,19 @@ private DiskChunk oldestChunk() throws InterruptedException {
8686
}
8787

8888
public void write(Path path) throws IOException, InterruptedException {
89+
DiskChunk chunk = null;
8990
try (FileChannel out = FileChannel.open(path, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
90-
DiskChunk chunk = null;
9191
while ((chunk = oldestChunk()) != null) {
9292
try (FileChannel in = FileChannel.open(chunk.path(), StandardOpenOption.READ)) {
9393
in.transferTo(0, in.size(), out);
9494
}
95+
chunk.release();
96+
chunk = null;
9597
}
9698
} finally {
99+
if (chunk != null) {
100+
chunk.release();
101+
}
97102
close();
98103
}
99104
}

src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -156,7 +156,10 @@ public void accept(Long endNanos) {
156156
volatile Instant startTime;
157157
volatile Instant endTime;
158158
volatile boolean closed;
159-
private boolean started; // always guarded by lock
159+
// always guarded by lock
160+
private boolean started;
161+
private Duration maxAge;
162+
private long maxSize;
160163

161164
/**
162165
* Creates an event stream that operates against a {@link MBeanServerConnection}
@@ -415,7 +418,11 @@ public EventSettings enable(String name) {
415418
*/
416419
public void setMaxAge(Duration maxAge) {
417420
Objects.requireNonNull(maxAge);
418-
repository.setMaxAge(maxAge);
421+
synchronized (lock) {
422+
repository.setMaxAge(maxAge);
423+
this.maxAge = maxAge;
424+
updateOnCompleteHandler();
425+
}
419426
}
420427

421428
/**
@@ -441,7 +448,11 @@ public void setMaxSize(long maxSize) {
441448
if (maxSize < 0) {
442449
throw new IllegalArgumentException("Max size of recording can't be negative");
443450
}
444-
repository.setMaxSize(maxSize);
451+
synchronized (lock) {
452+
repository.setMaxSize(maxSize);
453+
this.maxSize = maxSize;
454+
updateOnCompleteHandler();
455+
}
445456
}
446457

447458
@Override
@@ -645,6 +656,15 @@ private static Path makeTempDirectory() throws IOException {
645656
return Files.createTempDirectory("jfr-streaming");
646657
}
647658

659+
private void updateOnCompleteHandler() {
660+
if (maxAge != null || maxSize != 0) {
661+
// User has set a chunk removal policy
662+
ManagementSupport.setOnChunkCompleteHandler(stream, null);
663+
} else {
664+
ManagementSupport.setOnChunkCompleteHandler(stream, new ChunkConsumer(repository));
665+
}
666+
}
667+
648668
private void startDownload() {
649669
String name = "JFR: Download Thread " + creationTime;
650670
Thread downLoadThread = new DownLoadThread(this, name);
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
package jdk.jfr.jmx.streaming;
25+
26+
import java.lang.management.ManagementFactory;
27+
import java.nio.file.Files;
28+
import java.nio.file.Path;
29+
import java.util.List;
30+
import java.util.concurrent.ArrayBlockingQueue;
31+
import java.util.concurrent.CountDownLatch;
32+
import java.util.concurrent.atomic.AtomicLong;
33+
34+
import javax.management.MBeanServerConnection;
35+
36+
import jdk.jfr.Event;
37+
import jdk.jfr.StackTrace;
38+
import jdk.jfr.Recording;
39+
import jdk.jfr.consumer.RecordedEvent;
40+
import jdk.jfr.consumer.RecordingFile;
41+
import jdk.management.jfr.RemoteRecordingStream;
42+
43+
/**
44+
* @test
45+
* @key jfr
46+
* @summary Tests that chunks arrive in the same order they were committed
47+
* @requires vm.hasJFR
48+
* @library /test/lib /test/jdk
49+
* @run main/othervm jdk.jfr.jmx.streaming.TestDumpOrder
50+
*/
51+
public class TestDumpOrder {
52+
53+
private static final MBeanServerConnection CONNECTION = ManagementFactory.getPlatformMBeanServer();
54+
55+
@StackTrace(false)
56+
static class Ant extends Event {
57+
long id;
58+
}
59+
60+
public static void main(String... args) throws Exception {
61+
// Set up the test so half of the events have been consumed
62+
// when the dump occurs.
63+
AtomicLong eventCount = new AtomicLong();
64+
CountDownLatch halfLatch = new CountDownLatch(1);
65+
CountDownLatch dumpLatch = new CountDownLatch(1);
66+
Path directory = Path.of("chunks");
67+
Files.createDirectory(directory);
68+
try (var rs = new RemoteRecordingStream(CONNECTION, directory)) {
69+
rs.setMaxSize(100_000_000); // keep all data
70+
rs.onEvent(event -> {
71+
try {
72+
eventCount.incrementAndGet();
73+
if (eventCount.get() == 10) {
74+
halfLatch.countDown();
75+
dumpLatch.await();
76+
}
77+
} catch (InterruptedException ie) {
78+
ie.printStackTrace();
79+
}
80+
});
81+
rs.startAsync();
82+
long counter = 0;
83+
for (int i = 0; i < 10; i++) {
84+
try (Recording r = new Recording()) {
85+
r.start();
86+
Ant a = new Ant();
87+
a.id = counter++;
88+
a.commit();
89+
Ant b = new Ant();
90+
b.id = counter++;
91+
b.commit();
92+
}
93+
if (counter == 10) {
94+
halfLatch.await();
95+
}
96+
}
97+
Path file = Path.of("events.jfr");
98+
// Wait for most (but not all) chunk files to be downloaded
99+
// before invoking dump()
100+
awaitChunkFiles(directory);
101+
// To stress the implementation, release consumer thread
102+
// during the dump
103+
dumpLatch.countDown();
104+
rs.dump(file);
105+
List<RecordedEvent> events = RecordingFile.readAllEvents(file);
106+
if (events.isEmpty()) {
107+
throw new AssertionError("No events found");
108+
}
109+
// Print events for debugging purposes
110+
events.forEach(System.out::println);
111+
long expected = 0;
112+
for (var event : events) {
113+
long value = event.getLong("id");
114+
if (value != expected) {
115+
throw new Exception("Expected " + expected + ", got " + value);
116+
}
117+
expected++;
118+
}
119+
if (expected != counter) {
120+
throw new Exception("Not all events found");
121+
}
122+
}
123+
}
124+
125+
private static void awaitChunkFiles(Path directory) throws Exception {
126+
while (Files.list(directory).count() < 7) {
127+
Thread.sleep(10);
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)