8271232: JFR: Scrub recording data
Reviewed-by: mgronlun
This commit is contained in:
parent
735e86b0f7
commit
e96c599ed2
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -34,15 +34,20 @@ import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jdk.jfr.EventType;
|
||||
import jdk.jfr.internal.MetadataDescriptor;
|
||||
import jdk.jfr.internal.Type;
|
||||
import jdk.jfr.internal.consumer.ChunkParser.ParserConfiguration;
|
||||
import jdk.jfr.internal.consumer.ParserFilter;
|
||||
import jdk.jfr.internal.consumer.ChunkHeader;
|
||||
import jdk.jfr.internal.consumer.ChunkParser;
|
||||
import jdk.jfr.internal.consumer.FileAccess;
|
||||
import jdk.jfr.internal.consumer.ParserState;
|
||||
import jdk.jfr.internal.consumer.RecordingInput;
|
||||
import jdk.jfr.internal.consumer.filter.ChunkWriter;
|
||||
|
||||
/**
|
||||
* A recording file.
|
||||
@ -56,6 +61,7 @@ import jdk.jfr.internal.consumer.RecordingInput;
|
||||
public final class RecordingFile implements Closeable {
|
||||
|
||||
private final ParserState parserState = new ParserState();
|
||||
private final ChunkWriter chunkWriter;
|
||||
private boolean isLastEventInChunk;
|
||||
private final File file;
|
||||
private RecordingInput input;
|
||||
@ -77,6 +83,15 @@ public final class RecordingFile implements Closeable {
|
||||
public RecordingFile(Path file) throws IOException {
|
||||
this.file = file.toFile();
|
||||
this.input = new RecordingInput(this.file, FileAccess.UNPRIVILEGED);
|
||||
this.chunkWriter = null;
|
||||
findNext();
|
||||
}
|
||||
|
||||
// Only used by RecordingFile::write(Path, Predicate<RecordedEvent>)
|
||||
private RecordingFile(ChunkWriter chunkWriter) throws IOException {
|
||||
this.file = null; // not used
|
||||
this.input = chunkWriter.getInput();
|
||||
this.chunkWriter = chunkWriter;
|
||||
findNext();
|
||||
}
|
||||
|
||||
@ -199,6 +214,34 @@ public final class RecordingFile implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out events and write them to a new file.
|
||||
*
|
||||
* @param destination path where the new file should be written, not
|
||||
* {@code null}
|
||||
*
|
||||
* @param filter filter that determines if an event should be included, not
|
||||
* {@code null}
|
||||
* @throws IOException if an I/O error occurred, it's not a Flight
|
||||
* Recorder file or a version of a JFR file that can't
|
||||
* be parsed
|
||||
*
|
||||
* @throws SecurityException if a security manager exists and its
|
||||
* {@code checkWrite} method denies write access to the
|
||||
* file
|
||||
*/
|
||||
public void write(Path destination, Predicate<RecordedEvent> filter) throws IOException {
|
||||
Objects.requireNonNull(destination, "destination");
|
||||
Objects.requireNonNull(filter, "filter");
|
||||
try (ChunkWriter cw = new ChunkWriter(file.toPath(), destination, filter)) {
|
||||
try (RecordingFile rf = new RecordingFile(cw)) {
|
||||
while (rf.hasMoreEvents()) {
|
||||
rf.readEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all events in a file.
|
||||
* <p>
|
||||
@ -237,15 +280,15 @@ public final class RecordingFile implements Closeable {
|
||||
return isLastEventInChunk;
|
||||
}
|
||||
|
||||
|
||||
// either sets next to an event or sets eof to true
|
||||
private void findNext() throws IOException {
|
||||
while (nextEvent == null) {
|
||||
if (chunkParser == null) {
|
||||
chunkParser = new ChunkParser(input, parserState);
|
||||
chunkParser = createChunkParser();
|
||||
} else if (!chunkParser.isLastChunk()) {
|
||||
chunkParser = chunkParser.nextChunkParser();
|
||||
chunkParser = nextChunkParser();
|
||||
} else {
|
||||
endChunkParser();
|
||||
eof = true;
|
||||
return;
|
||||
}
|
||||
@ -256,6 +299,36 @@ public final class RecordingFile implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private ChunkParser createChunkParser() throws IOException {
|
||||
if (chunkWriter != null) {
|
||||
boolean reuse = true;
|
||||
boolean ordered = false;
|
||||
ParserConfiguration pc = new ParserConfiguration(0, Long.MAX_VALUE, reuse, ordered, ParserFilter.ACCEPT_ALL, chunkWriter);
|
||||
ChunkParser chunkParser = new ChunkParser(chunkWriter.getInput(), pc, new ParserState());
|
||||
chunkWriter.beginChunk(chunkParser.getHeader());
|
||||
return chunkParser;
|
||||
} else {
|
||||
return new ChunkParser(input, parserState);
|
||||
}
|
||||
}
|
||||
|
||||
private void endChunkParser() throws IOException {
|
||||
if (chunkWriter != null) {
|
||||
chunkWriter.endChunk(chunkParser.getHeader());
|
||||
}
|
||||
}
|
||||
|
||||
private ChunkParser nextChunkParser() throws IOException {
|
||||
if (chunkWriter != null) {
|
||||
chunkWriter.endChunk(chunkParser.getHeader());
|
||||
}
|
||||
ChunkParser next = chunkParser.nextChunkParser();
|
||||
if (chunkWriter != null) {
|
||||
chunkWriter.beginChunk(next.getHeader());
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
private void ensureOpen() throws IOException {
|
||||
if (input == null) {
|
||||
throw new IOException("Stream Closed");
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -34,9 +34,9 @@ import jdk.jfr.internal.MetadataDescriptor;
|
||||
import jdk.jfr.internal.Utils;
|
||||
|
||||
public final class ChunkHeader {
|
||||
static final long HEADER_SIZE = 68;
|
||||
public static final long HEADER_SIZE = 68;
|
||||
static final byte UPDATING_CHUNK_HEADER = (byte) 255;
|
||||
static final long CHUNK_SIZE_POSITION = 8;
|
||||
public static final long CHUNK_SIZE_POSITION = 8;
|
||||
static final long DURATION_NANOS_POSITION = 40;
|
||||
static final long FILE_STATE_POSITION = 64;
|
||||
static final long FLAG_BYTE_POSITION = 67;
|
||||
@ -92,10 +92,10 @@ public final class ChunkHeader {
|
||||
}
|
||||
long c = input.readRawLong(); // chunk size
|
||||
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: chunkSize=" + c);
|
||||
input.readRawLong(); // constant pool position
|
||||
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: constantPoolPosition=" + constantPoolPosition);
|
||||
input.readRawLong(); // metadata position
|
||||
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: metadataPosition=" + metadataPosition);
|
||||
long cp = input.readRawLong(); // constant pool position
|
||||
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: constantPoolPosition=" + cp);
|
||||
long mp = input.readRawLong(); // metadata position
|
||||
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: metadataPosition=" + mp);
|
||||
chunkStartNanos = input.readRawLong(); // nanos since epoch
|
||||
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startNanos=" + chunkStartNanos);
|
||||
durationNanos = input.readRawLong(); // duration nanos, not used
|
||||
@ -243,7 +243,7 @@ public final class ChunkHeader {
|
||||
return constantPoolPosition;
|
||||
}
|
||||
|
||||
public long getMetataPosition() {
|
||||
public long getMetadataPosition() {
|
||||
return metadataPosition;
|
||||
}
|
||||
public long getStartTicks() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -41,6 +41,8 @@ import jdk.jfr.internal.LongMap;
|
||||
import jdk.jfr.internal.MetadataDescriptor;
|
||||
import jdk.jfr.internal.Type;
|
||||
import jdk.jfr.internal.Utils;
|
||||
import jdk.jfr.internal.consumer.filter.CheckPointEvent;
|
||||
import jdk.jfr.internal.consumer.filter.ChunkWriter;
|
||||
|
||||
/**
|
||||
* Parses a chunk.
|
||||
@ -48,24 +50,26 @@ import jdk.jfr.internal.Utils;
|
||||
*/
|
||||
public final class ChunkParser {
|
||||
|
||||
static final class ParserConfiguration {
|
||||
public static final class ParserConfiguration {
|
||||
private final boolean reuse;
|
||||
private final boolean ordered;
|
||||
private final ParserFilter eventFilter;
|
||||
private final ChunkWriter chunkWriter;
|
||||
|
||||
long filterStart;
|
||||
long filterEnd;
|
||||
|
||||
ParserConfiguration(long filterStart, long filterEnd, boolean reuse, boolean ordered, ParserFilter filter) {
|
||||
public ParserConfiguration(long filterStart, long filterEnd, boolean reuse, boolean ordered, ParserFilter filter, ChunkWriter chunkWriter) {
|
||||
this.filterStart = filterStart;
|
||||
this.filterEnd = filterEnd;
|
||||
this.reuse = reuse;
|
||||
this.ordered = ordered;
|
||||
this.eventFilter = filter;
|
||||
this.chunkWriter = chunkWriter;
|
||||
}
|
||||
|
||||
public ParserConfiguration() {
|
||||
this(0, Long.MAX_VALUE, false, false, ParserFilter.ACCEPT_ALL);
|
||||
this(0, Long.MAX_VALUE, false, false, ParserFilter.ACCEPT_ALL, null);
|
||||
}
|
||||
|
||||
public boolean isOrdered() {
|
||||
@ -113,13 +117,13 @@ public final class ChunkParser {
|
||||
this(input, new ParserConfiguration(), ps);
|
||||
}
|
||||
|
||||
ChunkParser(RecordingInput input, ParserConfiguration pc, ParserState ps) throws IOException {
|
||||
public ChunkParser(RecordingInput input, ParserConfiguration pc, ParserState ps) throws IOException {
|
||||
this(new ChunkHeader(input), null, pc, ps);
|
||||
}
|
||||
|
||||
private ChunkParser(ChunkParser previous, ParserState ps) throws IOException {
|
||||
this(new ChunkHeader(previous.input), previous, new ParserConfiguration(), ps);
|
||||
}
|
||||
}
|
||||
|
||||
private ChunkParser(ChunkHeader header, ChunkParser previous, ParserConfiguration pc, ParserState ps) throws IOException {
|
||||
this.parserState = ps;
|
||||
@ -199,7 +203,7 @@ public final class ChunkParser {
|
||||
return event;
|
||||
}
|
||||
long lastValid = absoluteChunkEnd;
|
||||
long metadataPosition = chunkHeader.getMetataPosition();
|
||||
long metadataPosition = chunkHeader.getMetadataPosition();
|
||||
long constantPosition = chunkHeader.getConstantPoolPosition();
|
||||
chunkFinished = awaitUpdatedHeader(absoluteChunkEnd, configuration.filterEnd);
|
||||
if (chunkFinished) {
|
||||
@ -208,7 +212,7 @@ public final class ChunkParser {
|
||||
}
|
||||
absoluteChunkEnd = chunkHeader.getEnd();
|
||||
// Read metadata and constant pools for the next segment
|
||||
if (chunkHeader.getMetataPosition() != metadataPosition) {
|
||||
if (chunkHeader.getMetadataPosition() != metadataPosition) {
|
||||
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Found new metadata in chunk. Rebuilding types and parsers");
|
||||
this.previousMetadata = this.metadata;
|
||||
this.metadata = chunkHeader.readMetadata(previousMetadata);
|
||||
@ -247,6 +251,16 @@ public final class ChunkParser {
|
||||
// Fast path
|
||||
RecordedEvent event = ep.parse(input);
|
||||
if (event != null) {
|
||||
ChunkWriter chunkWriter = configuration.chunkWriter;
|
||||
if (chunkWriter != null) {
|
||||
if (chunkWriter.accept(event)) {
|
||||
chunkWriter.writeEvent(pos, input.position());
|
||||
input.position(pos);
|
||||
input.readInt(); // size
|
||||
input.readLong(); // type
|
||||
chunkWriter.touch(ep.parseReferences(input));
|
||||
}
|
||||
}
|
||||
input.position(pos + size);
|
||||
return event;
|
||||
}
|
||||
@ -303,6 +317,10 @@ public final class ChunkParser {
|
||||
long delta = -1;
|
||||
boolean logTrace = Logger.shouldLog(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE);
|
||||
while (thisCP != abortCP && delta != 0) {
|
||||
CheckPointEvent cp = null;
|
||||
if (configuration.chunkWriter != null) {
|
||||
cp = configuration.chunkWriter.newCheckPointEvent(thisCP);
|
||||
}
|
||||
input.position(thisCP);
|
||||
lastCP = thisCP;
|
||||
int size = input.readInt(); // size
|
||||
@ -333,7 +351,7 @@ public final class ChunkParser {
|
||||
throw new IOException(
|
||||
"Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + lastCP + ", " + (lastCP + size) + "]");
|
||||
}
|
||||
ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
|
||||
ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type);
|
||||
lookup = new ConstantLookup(pool, type);
|
||||
constantLookups.put(type.getId(), lookup);
|
||||
}
|
||||
@ -350,6 +368,7 @@ public final class ChunkParser {
|
||||
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "Constant Pool " + i + ": " + type.getName());
|
||||
}
|
||||
for (int j = 0; j < count; j++) {
|
||||
long position = input.position();
|
||||
long key = input.readLong();
|
||||
Object resolved = lookup.getPreviousResolved(key);
|
||||
if (resolved == null) {
|
||||
@ -361,6 +380,12 @@ public final class ChunkParser {
|
||||
logConstant(key, resolved, true);
|
||||
lookup.getLatestPool().putResolved(key, resolved);
|
||||
}
|
||||
if (cp != null) {
|
||||
input.position(position);
|
||||
input.readLong();
|
||||
Object refs = parser.parseReferences(input);
|
||||
cp.addEntry(type, key, position, input.position(), refs);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + lastCP + ", " + (lastCP + size) + "]",
|
||||
@ -436,7 +461,7 @@ public final class ChunkParser {
|
||||
return chunkHeader.isLastChunk();
|
||||
}
|
||||
|
||||
ChunkParser newChunkParser() throws IOException {
|
||||
public ChunkParser newChunkParser() throws IOException {
|
||||
return new ChunkParser(this, parserState);
|
||||
}
|
||||
|
||||
@ -488,4 +513,8 @@ public final class ChunkParser {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public ChunkHeader getHeader() {
|
||||
return chunkHeader;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jfr.internal.consumer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public final class CompositeParser extends Parser {
|
||||
final Parser[] parsers;
|
||||
|
||||
public CompositeParser(Parser[] valueParsers) {
|
||||
this.parsers = valueParsers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parse(RecordingInput input) throws IOException {
|
||||
final Object[] values = new Object[parsers.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
values[i] = parsers[i].parse(input);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skip(RecordingInput input) throws IOException {
|
||||
for (int i = 0; i < parsers.length; i++) {
|
||||
parsers[i].skip(input);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parseReferences(RecordingInput input) throws IOException {
|
||||
return parseReferences(input, parsers);
|
||||
}
|
||||
|
||||
static Object parseReferences(RecordingInput input, Parser[] parsers) throws IOException {
|
||||
ArrayList<Object> refs = new ArrayList<>(parsers.length);
|
||||
for (int i = 0; i < parsers.length; i++) {
|
||||
Object ref = parsers[i].parseReferences(input);
|
||||
if (ref != null) {
|
||||
refs.add(ref);
|
||||
}
|
||||
}
|
||||
if (refs.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (refs.size() == 1) {
|
||||
return refs.get(0);
|
||||
}
|
||||
return refs.toArray();
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -28,7 +28,7 @@ package jdk.jfr.internal.consumer;
|
||||
import jdk.jfr.internal.Type;
|
||||
|
||||
final class ConstantLookup {
|
||||
private final Type type;
|
||||
final Type type;
|
||||
private ConstantMap current;
|
||||
private ConstantMap previous = ConstantMap.EMPTY;
|
||||
|
||||
@ -47,7 +47,7 @@ final class ConstantLookup {
|
||||
|
||||
public void newPool() {
|
||||
previous = current;
|
||||
current = new ConstantMap(current.factory, current.name);
|
||||
current = new ConstantMap(current.factory, current.type);
|
||||
}
|
||||
|
||||
public Object getPreviousResolved(long key) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -30,6 +30,7 @@ import jdk.jfr.internal.LogTag;
|
||||
import jdk.jfr.internal.Logger;
|
||||
|
||||
import jdk.jfr.internal.LongMap;
|
||||
import jdk.jfr.internal.Type;
|
||||
|
||||
/**
|
||||
* Holds mapping between a set of keys and their corresponding object.
|
||||
@ -38,49 +39,23 @@ import jdk.jfr.internal.LongMap;
|
||||
* {@link ObjectFactory} can be supplied which will instantiate a typed object.
|
||||
*/
|
||||
final class ConstantMap {
|
||||
|
||||
private static final int RESOLUTION_FINISHED = 0;
|
||||
private static final int RESOLUTION_STARTED = 1;
|
||||
public static final ConstantMap EMPTY = new ConstantMap();
|
||||
|
||||
// A temporary placeholder, so objects can
|
||||
// reference themselves (directly, or indirectly),
|
||||
// when making a transition from numeric id references
|
||||
// to normal Java references.
|
||||
private static final class Reference {
|
||||
private final long key;
|
||||
private final ConstantMap pool;
|
||||
|
||||
Reference(ConstantMap pool, long key) {
|
||||
this.pool = pool;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
Object resolve() {
|
||||
return pool.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ref: " + pool.name + "[" + key + "]";
|
||||
}
|
||||
}
|
||||
|
||||
final ObjectFactory<?> factory;
|
||||
final String name;
|
||||
|
||||
final Type type;
|
||||
private final LongMap<Object> objects;
|
||||
|
||||
private boolean resolving;
|
||||
private boolean allResolved;
|
||||
|
||||
private ConstantMap() {
|
||||
this(null, "<empty>");
|
||||
this(null, null);
|
||||
allResolved = true;
|
||||
}
|
||||
|
||||
ConstantMap(ObjectFactory<?> factory, String name) {
|
||||
this.name = name;
|
||||
ConstantMap(ObjectFactory<?> factory, Type type) {
|
||||
this.type = type;
|
||||
this.objects = new LongMap<>(2);
|
||||
this.factory = factory;
|
||||
}
|
||||
@ -100,7 +75,7 @@ final class ConstantMap {
|
||||
if (value == null) {
|
||||
// unless id is 0 which is used to represent null
|
||||
if (id != 0) {
|
||||
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Missing object id=" + id + " in pool " + name + ". All ids should reference an object");
|
||||
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Missing object id=" + id + " in pool " + getName() + ". All ids should reference an object");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -174,7 +149,12 @@ final class ConstantMap {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
return type == null ? "<empty>" : type.getName();
|
||||
}
|
||||
|
||||
// Can be null
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Object getResolved(long id) {
|
||||
@ -189,4 +169,8 @@ final class ConstantMap {
|
||||
public void setAllResolved(boolean allResolved) {
|
||||
this.allResolved = allResolved;
|
||||
}
|
||||
|
||||
public LongMap<Object> getObjects() {
|
||||
return objects;
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ final class Dispatcher {
|
||||
this.errorActions = c.errorActions.toArray(new Consumer[0]);
|
||||
this.metadataActions = c.metadataActions.toArray(new Consumer[0]);
|
||||
this.dispatchers = c.eventActions.toArray(new EventDispatcher[0]);
|
||||
this.parserConfiguration = new ParserConfiguration(0, Long.MAX_VALUE, c.reuse, c.ordered, buildFilter(dispatchers));
|
||||
this.parserConfiguration = new ParserConfiguration(0, Long.MAX_VALUE, c.reuse, c.ordered, buildFilter(dispatchers), null);
|
||||
this.startTime = c.startTime;
|
||||
this.endTime = c.endTime;
|
||||
this.startNanos = c.startNanos;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -28,7 +28,9 @@ package jdk.jfr.internal.consumer;
|
||||
import static jdk.jfr.internal.EventInstrumentation.FIELD_DURATION;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jdk.jfr.EventType;
|
||||
import jdk.jfr.ValueDescriptor;
|
||||
@ -154,6 +156,11 @@ final class EventParser extends Parser {
|
||||
return PRIVATE_ACCESS.newRecordedEvent(objectContext, values, startTicks, endTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parseReferences(RecordingInput input) throws IOException {
|
||||
return CompositeParser.parseReferences(input, parsers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skip(RecordingInput input) throws IOException {
|
||||
throw new InternalError("Should not call this method. More efficient to read event size and skip ahead");
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -35,12 +35,28 @@ abstract class Parser {
|
||||
* Parses data from a {@link RecordingInput} and return an object.
|
||||
*
|
||||
* @param input input to read from
|
||||
* @return an object
|
||||
* @return an {@code Object}, an {@code Object[]}, or {@code null}
|
||||
* @throws IOException if operation couldn't be completed due to I/O
|
||||
* problems
|
||||
*/
|
||||
public abstract Object parse(RecordingInput input) throws IOException;
|
||||
|
||||
/**
|
||||
* Parses data from a {@link RecordingInput} to find references to constants. If
|
||||
* data is not a reference, {@code null} is returned.
|
||||
* <p>
|
||||
* @implSpec The default implementation of this method skips data and returns
|
||||
* {@code Object}.
|
||||
*
|
||||
* @param input input to read from, not {@code null}
|
||||
* @return a {@code Reference}, a {@code Reference[]}, or {@code null}
|
||||
* @throws IOException if operation couldn't be completed due to I/O problems
|
||||
*/
|
||||
public Object parseReferences(RecordingInput input) throws IOException {
|
||||
skip(input);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips data that would usually be by parsed the {@link #parse(RecordingInput)} method.
|
||||
*
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -101,7 +101,7 @@ final class ParserFactory {
|
||||
if (constantPool) {
|
||||
ConstantLookup lookup = constantLookups.get(id);
|
||||
if (lookup == null) {
|
||||
ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
|
||||
ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type);
|
||||
lookup = new ConstantLookup(pool, type);
|
||||
constantLookups.put(id, lookup);
|
||||
}
|
||||
@ -140,7 +140,7 @@ final class ParserFactory {
|
||||
case "byte":
|
||||
return new ByteParser();
|
||||
case "java.lang.String":
|
||||
ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
|
||||
ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type);
|
||||
ConstantLookup lookup = new ConstantLookup(pool, type);
|
||||
constantLookups.put(type.getId(), lookup);
|
||||
return new StringParser(lookup, event);
|
||||
@ -303,6 +303,16 @@ final class ParserFactory {
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parseReferences(RecordingInput input) throws IOException {
|
||||
final int size = input.readInt();
|
||||
final Object[] array = new Object[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
array[i] = elementParser.parse(input);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skip(RecordingInput input) throws IOException {
|
||||
final int size = input.readInt();
|
||||
@ -312,34 +322,12 @@ final class ParserFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CompositeParser extends Parser {
|
||||
private final Parser[] parsers;
|
||||
|
||||
public CompositeParser(Parser[] valueParsers) {
|
||||
this.parsers = valueParsers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parse(RecordingInput input) throws IOException {
|
||||
final Object[] values = new Object[parsers.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
values[i] = parsers[i].parse(input);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skip(RecordingInput input) throws IOException {
|
||||
for (int i = 0; i < parsers.length; i++) {
|
||||
parsers[i].skip(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EventValueConstantParser extends Parser {
|
||||
private final ConstantLookup lookup;
|
||||
private Object lastValue = 0;
|
||||
private long lastKey = -1;
|
||||
private Object lastReferenceValue;
|
||||
private long lastReferenceKey = -1;
|
||||
EventValueConstantParser(ConstantLookup lookup) {
|
||||
this.lookup = lookup;
|
||||
}
|
||||
@ -359,6 +347,17 @@ final class ParserFactory {
|
||||
public void skip(RecordingInput input) throws IOException {
|
||||
input.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parseReferences(RecordingInput input) throws IOException {
|
||||
long key = input.readLong();
|
||||
if (key == lastReferenceKey) {
|
||||
return lastReferenceValue;
|
||||
}
|
||||
lastReferenceKey = key;
|
||||
lastReferenceValue = new Reference(lookup.getLatestPool(), key);
|
||||
return lastReferenceValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ConstantValueParser extends Parser {
|
||||
@ -376,5 +375,10 @@ final class ParserFactory {
|
||||
public void skip(RecordingInput input) throws IOException {
|
||||
input.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parseReferences(RecordingInput input) throws IOException {
|
||||
return new Reference(lookup.getLatestPool(), input.readLong());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -29,7 +29,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
final class ParserFilter {
|
||||
public final class ParserFilter {
|
||||
public static final ParserFilter ACCEPT_ALL = new ParserFilter(true, Map.of());
|
||||
|
||||
private final Map<String, Long> thresholds;
|
||||
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.internal.consumer;
|
||||
|
||||
import jdk.jfr.internal.Type;
|
||||
|
||||
/**
|
||||
* A temporary placeholder, so objects can reference themselves (directly, or
|
||||
* indirectly), when making a transition from numeric id references to Java
|
||||
* object references.
|
||||
*/
|
||||
public record Reference(ConstantMap pool, long key) {
|
||||
|
||||
Object resolve() {
|
||||
return pool.get(key);
|
||||
}
|
||||
|
||||
public Type type() {
|
||||
return pool.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ref: " + pool.getName() + "[" + key + "]";
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -220,4 +220,31 @@ public final class StringParser extends Parser {
|
||||
}
|
||||
throw new IOException("Unknown string encoding " + encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parseReferences(RecordingInput input) throws IOException {
|
||||
byte encoding = input.readByte();
|
||||
if (Encoding.CONSTANT_POOL.is(encoding)) {
|
||||
return new Reference(stringLookup.getLatestPool(), input.readLong());
|
||||
}
|
||||
if (Encoding.EMPTY_STRING.is(encoding)) {
|
||||
return null;
|
||||
}
|
||||
if (Encoding.NULL.is(encoding)) {
|
||||
return null;
|
||||
}
|
||||
if (Encoding.CHAR_ARRAY.is(encoding)) {
|
||||
charArrayParser.skip(input);
|
||||
return null;
|
||||
}
|
||||
if (Encoding.UT8_BYTE_ARRAY.is(encoding)) {
|
||||
utf8parser.skip(input);
|
||||
return null;
|
||||
}
|
||||
if (Encoding.LATIN1_BYTE_ARRAY.is(encoding)) {
|
||||
latin1parser.skip(input);
|
||||
return null;
|
||||
}
|
||||
throw new IOException("Unknown string encoding " + encoding);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.internal.consumer.filter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import jdk.jfr.internal.Type;
|
||||
|
||||
/**
|
||||
* Represents a checkpoint event.
|
||||
* <p>
|
||||
* All positional values are relative to file start, not the chunk.
|
||||
*/
|
||||
public final class CheckPointEvent {
|
||||
private final ChunkWriter chunkWriter;
|
||||
private final LinkedHashMap<Long, CheckPointPool> pools = new LinkedHashMap<>();
|
||||
private final long startPosition;
|
||||
|
||||
public CheckPointEvent(ChunkWriter chunkWriter, long startPosition) {
|
||||
this.chunkWriter = chunkWriter;
|
||||
this.startPosition = startPosition;
|
||||
}
|
||||
|
||||
public PoolEntry addEntry(Type type, long id, long startPosition, long endPosition, Object references) {
|
||||
long typeId = type.getId();
|
||||
PoolEntry pe = new PoolEntry(startPosition, endPosition, type, id, references);
|
||||
var cpp = pools.computeIfAbsent(typeId, k -> new CheckPointPool(typeId));
|
||||
cpp.add(pe);
|
||||
chunkWriter.getPool(type).add(id, pe);
|
||||
return pe;
|
||||
}
|
||||
|
||||
public long touchedPools() {
|
||||
int count = 0;
|
||||
for (CheckPointPool cpp : pools.values()) {
|
||||
if (cpp.isTouched()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public Collection<CheckPointPool> getPools() {
|
||||
return pools.values();
|
||||
}
|
||||
|
||||
public long getStartPosition() {
|
||||
return startPosition;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (CheckPointPool p : pools.values()) {
|
||||
for (var e : p.getEntries()) {
|
||||
if (e.isTouched()) {
|
||||
sb.append(e.getType().getName() + " " + e.getId() + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jfr.internal.consumer.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
/**
|
||||
* Represents a constant pool in a checkpoint, both entries and type id
|
||||
*/
|
||||
final class CheckPointPool {
|
||||
private final List<PoolEntry> entries = new ArrayList<>();
|
||||
private final long typeId;
|
||||
|
||||
public CheckPointPool(long typeId) {
|
||||
this.typeId = typeId;
|
||||
}
|
||||
|
||||
public boolean isTouched() {
|
||||
for (var entry : entries) {
|
||||
if (entry.isTouched()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public long getTouchedCount() {
|
||||
int count = 0;
|
||||
for (var entry : entries) {
|
||||
if (entry.isTouched()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public void add(PoolEntry pe) {
|
||||
entries.add(pe);
|
||||
}
|
||||
|
||||
public long getTypeId() {
|
||||
return typeId;
|
||||
}
|
||||
|
||||
public List<PoolEntry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
}
|
@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jfr.internal.consumer.filter;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.jfr.internal.LongMap;
|
||||
import jdk.jfr.internal.Type;
|
||||
import jdk.jfr.internal.consumer.ChunkHeader;
|
||||
import jdk.jfr.internal.consumer.FileAccess;
|
||||
import jdk.jfr.internal.Logger;
|
||||
import jdk.jfr.internal.LogLevel;
|
||||
import jdk.jfr.internal.LogTag;
|
||||
import jdk.jfr.internal.consumer.RecordingInput;
|
||||
import jdk.jfr.internal.consumer.Reference;
|
||||
|
||||
/**
|
||||
* Class that can filter out events and associated constants from a recording
|
||||
* file.
|
||||
* <p>
|
||||
* All positional values are relative to file start, not the chunk.
|
||||
*/
|
||||
public final class ChunkWriter implements Closeable {
|
||||
private LongMap<Constants> pools = new LongMap<>();
|
||||
private final Deque<CheckPointEvent> checkPoints = new ArrayDeque<>();
|
||||
private final Path destination;
|
||||
private final RecordingInput input;
|
||||
private final RecordingOutput output;
|
||||
private final Predicate<RecordedEvent> filter;
|
||||
|
||||
private long chunkStartPosition;
|
||||
private boolean chunkComplete;
|
||||
private long lastCheckPoint;
|
||||
|
||||
public ChunkWriter(Path source, Path destination, Predicate<RecordedEvent> filter) throws IOException {
|
||||
this.destination = destination;
|
||||
this.output = new RecordingOutput(destination.toFile());
|
||||
this.input = new RecordingInput(source.toFile(), FileAccess.UNPRIVILEGED);
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
Constants getPool(Type type) {
|
||||
long typeId = type.getId();
|
||||
Constants pool = pools.get(typeId);
|
||||
if (pool == null) {
|
||||
pool = new Constants(type);
|
||||
pools.put(typeId, pool);
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
public CheckPointEvent newCheckPointEvent(long startPosition) {
|
||||
CheckPointEvent event = new CheckPointEvent(this, startPosition);
|
||||
checkPoints.add(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
public boolean accept(RecordedEvent event) {
|
||||
return filter.test(event);
|
||||
}
|
||||
|
||||
public void touch(Object object) {
|
||||
if (object instanceof Object[] array) {
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
touch(array[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (object instanceof Reference ref) {
|
||||
touchRef(ref);
|
||||
}
|
||||
}
|
||||
|
||||
private void touchRef(Reference ref) {
|
||||
Constants pool = pools.get(ref.type().getId());
|
||||
if (pool == null) {
|
||||
String msg = "Can't resolve " + ref.type().getName() + "[" + ref.key() + "]";
|
||||
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.DEBUG, msg);
|
||||
return;
|
||||
}
|
||||
PoolEntry entry = pool.get(ref.key());
|
||||
if (entry != null && !entry.isTouched()) {
|
||||
entry.touch();
|
||||
touch(entry.getReferences());
|
||||
}
|
||||
}
|
||||
public void writeEvent(long startPosition, long endPosition) throws IOException {
|
||||
writeCheckpointEvents(startPosition);
|
||||
write(startPosition, endPosition);
|
||||
}
|
||||
|
||||
// Write check point events before a position
|
||||
private void writeCheckpointEvents(long before) throws IOException {
|
||||
CheckPointEvent cp = checkPoints.peek();
|
||||
while (cp != null && cp.getStartPosition() < before) {
|
||||
checkPoints.poll();
|
||||
long delta = 0;
|
||||
if (lastCheckPoint != 0) {
|
||||
delta = lastCheckPoint - output.position();
|
||||
}
|
||||
lastCheckPoint = output.position();
|
||||
write(cp, delta);
|
||||
cp = checkPoints.peek();
|
||||
}
|
||||
}
|
||||
|
||||
public void write(long startPosition, long endPosition) throws IOException {
|
||||
if (endPosition < startPosition) {
|
||||
throw new IOException("Start position must come before end position, start=" + startPosition + ", end=" + endPosition);
|
||||
}
|
||||
long backup = input.position();
|
||||
input.position(startPosition);
|
||||
long n = endPosition - startPosition;
|
||||
for (long i = 0; i < n; i++) {
|
||||
output.writeByte(input.readByte());
|
||||
}
|
||||
input.position(backup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
output.close();
|
||||
} finally {
|
||||
if (!chunkComplete) {
|
||||
// Error occurred, clean up
|
||||
if (Files.exists(destination)) {
|
||||
Files.delete(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void beginChunk(ChunkHeader header) throws IOException {
|
||||
this.chunkComplete = false;
|
||||
this.chunkStartPosition = output.position();
|
||||
input.position(header.getAbsoluteChunkStart());
|
||||
for (int i = 0; i < ChunkHeader.HEADER_SIZE; i++) {
|
||||
output.writeByte(input.readByte());
|
||||
}
|
||||
}
|
||||
|
||||
public void endChunk(ChunkHeader header) throws IOException {
|
||||
// write all outstanding checkpoints
|
||||
writeCheckpointEvents(Long.MAX_VALUE);
|
||||
long metadata = output.position();
|
||||
writeMetadataEvent(header);
|
||||
updateHeader(output.position(), lastCheckPoint, metadata);
|
||||
pools = new LongMap<>();
|
||||
chunkComplete = true;
|
||||
lastCheckPoint = 0;
|
||||
}
|
||||
|
||||
private void writeMetadataEvent(ChunkHeader header) throws IOException {
|
||||
long metadataposition = header.getMetadataPosition() + header.getAbsoluteChunkStart();
|
||||
input.position(metadataposition);
|
||||
long size = input.readLong();
|
||||
input.position(metadataposition);
|
||||
for (int i = 0; i < size; i++) {
|
||||
output.writeByte(input.readByte());
|
||||
}
|
||||
}
|
||||
|
||||
private void write(CheckPointEvent event, long delta) throws IOException {
|
||||
input.position(event.getStartPosition());
|
||||
long startPosition = output.position();
|
||||
|
||||
input.readLong(); // Read size
|
||||
output.writePaddedUnsignedInt(0); // Size, 4 bytes reserved
|
||||
output.writeLong(input.readLong()); // Constant pool id
|
||||
output.writeLong(input.readLong()); // Start time
|
||||
output.writeLong(input.readLong()); // Duration
|
||||
input.readLong(); // Read delta
|
||||
output.writeLong(delta); // Delta
|
||||
output.writeByte(input.readByte()); // flush marker
|
||||
|
||||
// Write even if touched pools are zero, checkpoint works as sync point
|
||||
output.writeLong(event.touchedPools()); // Pool count
|
||||
for (CheckPointPool pool : event.getPools()) {
|
||||
if (pool.isTouched()) {
|
||||
output.writeLong(pool.getTypeId());
|
||||
output.writeLong(pool.getTouchedCount());
|
||||
for (PoolEntry pe : pool.getEntries()) {
|
||||
if (pe.isTouched()) {
|
||||
write(pe.getStartPosition(), pe.getEndPosition()); // key + value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
long endPosition = output.position();
|
||||
long size = endPosition - startPosition;
|
||||
output.position(startPosition);
|
||||
output.writePaddedUnsignedInt(size);
|
||||
output.position(endPosition);
|
||||
}
|
||||
|
||||
private void updateHeader(long size, long constantPosition, long metadataPosition) throws IOException {
|
||||
long backup = output.position();
|
||||
output.position(ChunkHeader.CHUNK_SIZE_POSITION + chunkStartPosition);
|
||||
// Write chunk relative values
|
||||
output.writeRawLong(size - chunkStartPosition);
|
||||
output.writeRawLong(constantPosition - chunkStartPosition);
|
||||
output.writeRawLong(metadataPosition - chunkStartPosition);
|
||||
output.position(backup);
|
||||
}
|
||||
|
||||
public RecordingInput getInput() {
|
||||
return input;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jfr.internal.consumer.filter;
|
||||
|
||||
import jdk.jfr.internal.LongMap;
|
||||
import jdk.jfr.internal.Type;
|
||||
|
||||
/**
|
||||
* Holds the chunk global state of constants
|
||||
*/
|
||||
final class Constants {
|
||||
private final LongMap<PoolEntry> table = new LongMap<>();
|
||||
private final Type type;
|
||||
|
||||
public Constants(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void add(long key, PoolEntry entry) {
|
||||
table.put(key, entry);
|
||||
}
|
||||
|
||||
public PoolEntry get(long key) {
|
||||
return table.get(key);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Pool: " + type.getName() + " size = " + table.size();
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jfr.internal.consumer.filter;
|
||||
|
||||
import jdk.jfr.internal.Type;
|
||||
|
||||
/**
|
||||
* Represents the binary content of constant pool, both key and value.
|
||||
* <p>
|
||||
* All positional values are relative to file start, not the chunk.
|
||||
*/
|
||||
final class PoolEntry {
|
||||
private final long startPosition;
|
||||
private final long endPosition;
|
||||
private final Type type;
|
||||
private final long keyId;
|
||||
private final Object references;
|
||||
|
||||
private boolean touched;
|
||||
|
||||
PoolEntry(long startPosition, long endPosition, Type type, long keyId, Object references) {
|
||||
this.startPosition = startPosition;
|
||||
this.endPosition = endPosition;
|
||||
this.type = type;
|
||||
this.keyId = keyId;
|
||||
this.references = references;
|
||||
}
|
||||
|
||||
public void touch() {
|
||||
this.touched = true;
|
||||
}
|
||||
|
||||
public boolean isTouched() {
|
||||
return touched;
|
||||
}
|
||||
|
||||
public Object getReferences() {
|
||||
return references;
|
||||
}
|
||||
|
||||
public long getStartPosition() {
|
||||
return startPosition;
|
||||
}
|
||||
|
||||
public long getEndPosition() {
|
||||
return endPosition;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("start: ").append(startPosition).append("\n");
|
||||
sb.append("end: ").append(endPosition).append("\n");
|
||||
sb.append("type: ").append(type).append(" (").append(type.getId()).append(")\n");
|
||||
sb.append("key: ").append(keyId).append("\n");
|
||||
sb.append("object: ").append(references).append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jfr.internal.consumer.filter;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
/**
|
||||
* Write cache and LEB128 encoder
|
||||
*/
|
||||
final class RecordingOutput implements Closeable {
|
||||
private final RandomAccessFile file;
|
||||
private final byte[] buffer = new byte[16384];
|
||||
private int bufferPosition;
|
||||
private long position;
|
||||
|
||||
public RecordingOutput(File file) throws IOException {
|
||||
this.file = new RandomAccessFile(file, "rw");
|
||||
}
|
||||
|
||||
public void writeByte(byte value) throws IOException {
|
||||
if (!(bufferPosition < buffer.length)) {
|
||||
flush();
|
||||
}
|
||||
buffer[bufferPosition++] = value;
|
||||
position++;
|
||||
}
|
||||
|
||||
public void writeRawLong(long v) throws IOException {
|
||||
writeByte((byte) ((v >> 56) & 0xff));
|
||||
writeByte((byte) ((v >> 48) & 0xff));
|
||||
writeByte((byte) ((v >> 40) & 0xff));
|
||||
writeByte((byte) ((v >> 32) & 0xff));
|
||||
writeByte((byte) ((v >> 24) & 0xff));
|
||||
writeByte((byte) ((v >> 16) & 0xff));
|
||||
writeByte((byte) ((v >> 8) & 0xff));
|
||||
writeByte((byte) ((v) & 0xff));
|
||||
}
|
||||
|
||||
public void writePaddedUnsignedInt(long value) throws IOException {
|
||||
if (value < 0) {
|
||||
throw new IOException("Padded value can't be negative");
|
||||
}
|
||||
if (value >= 1 << 28) {
|
||||
throw new IOException("Padded value must fit four bytes");
|
||||
}
|
||||
byte b0 = (byte) (value | 0x80);
|
||||
byte b1 = (byte) (value >> 7 | 0x80);
|
||||
byte b2 = (byte) (value >> 14 | 0x80);
|
||||
byte b3 = (byte) (value >> 21);
|
||||
writeByte(b0);
|
||||
writeByte(b1);
|
||||
writeByte(b2);
|
||||
writeByte(b3);
|
||||
}
|
||||
|
||||
// Essentially copied from EventWriter#putLong
|
||||
public void writeLong(long v) throws IOException {
|
||||
if ((v & ~0x7FL) == 0L) {
|
||||
writeByte((byte) v); // 0-6
|
||||
return;
|
||||
}
|
||||
writeByte((byte) (v | 0x80L)); // 0-6
|
||||
v >>>= 7;
|
||||
if ((v & ~0x7FL) == 0L) {
|
||||
writeByte((byte) v); // 7-13
|
||||
return;
|
||||
}
|
||||
writeByte((byte) (v | 0x80L)); // 7-13
|
||||
v >>>= 7;
|
||||
if ((v & ~0x7FL) == 0L) {
|
||||
writeByte((byte) v); // 14-20
|
||||
return;
|
||||
}
|
||||
writeByte((byte) (v | 0x80L)); // 14-20
|
||||
v >>>= 7;
|
||||
if ((v & ~0x7FL) == 0L) {
|
||||
writeByte((byte) v); // 21-27
|
||||
return;
|
||||
}
|
||||
writeByte((byte) (v | 0x80L)); // 21-27
|
||||
v >>>= 7;
|
||||
if ((v & ~0x7FL) == 0L) {
|
||||
writeByte((byte) v); // 28-34
|
||||
return;
|
||||
}
|
||||
writeByte((byte) (v | 0x80L)); // 28-34
|
||||
v >>>= 7;
|
||||
if ((v & ~0x7FL) == 0L) {
|
||||
writeByte((byte) v); // 35-41
|
||||
return;
|
||||
}
|
||||
writeByte((byte) (v | 0x80L)); // 35-41
|
||||
v >>>= 7;
|
||||
if ((v & ~0x7FL) == 0L) {
|
||||
writeByte((byte) v); // 42-48
|
||||
return;
|
||||
}
|
||||
writeByte((byte) (v | 0x80L)); // 42-48
|
||||
v >>>= 7;
|
||||
|
||||
if ((v & ~0x7FL) == 0L) {
|
||||
writeByte((byte) v); // 49-55
|
||||
return;
|
||||
}
|
||||
writeByte((byte) (v | 0x80L)); // 49-55
|
||||
writeByte((byte) (v >>> 7)); // 56-63, last byte as is.
|
||||
}
|
||||
|
||||
public void position(long pos) throws IOException {
|
||||
flush();
|
||||
position = pos;
|
||||
file.seek(position);
|
||||
}
|
||||
|
||||
public long position() throws IOException {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
file.write(buffer, 0, bufferPosition);
|
||||
bufferPosition = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
flush();
|
||||
file.close();
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -38,13 +38,7 @@ import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jdk.jfr.EventType;
|
||||
|
||||
abstract class Command {
|
||||
public static final String title = "Tool for working with Flight Recorder files";
|
||||
@ -56,6 +50,7 @@ abstract class Command {
|
||||
commands.add(new Print());
|
||||
commands.add(new Configure());
|
||||
commands.add(new Metadata());
|
||||
commands.add(new Scrub());
|
||||
commands.add(new Summary());
|
||||
commands.add(new Assemble());
|
||||
commands.add(new Disassemble());
|
||||
@ -287,6 +282,10 @@ abstract class Command {
|
||||
displayOptionUsage(stream);
|
||||
}
|
||||
|
||||
protected static char quoteCharacter() {
|
||||
return File.pathSeparatorChar == ';' ? '"' : '\'';
|
||||
}
|
||||
|
||||
protected final void println() {
|
||||
System.out.println();
|
||||
}
|
||||
@ -299,6 +298,12 @@ abstract class Command {
|
||||
System.out.println(text);
|
||||
}
|
||||
|
||||
public static void checkCommonError(Deque<String> options, String typo, String correct) throws UserSyntaxException {
|
||||
if (typo.equals(options.peek())) {
|
||||
throw new UserSyntaxException("unknown option " + typo + ", did you mean " + correct + "?");
|
||||
}
|
||||
}
|
||||
|
||||
protected final boolean matches(String command) {
|
||||
for (String s : getNames()) {
|
||||
if (s.equals(command)) {
|
||||
@ -319,107 +324,4 @@ abstract class Command {
|
||||
return names;
|
||||
}
|
||||
|
||||
public static void checkCommonError(Deque<String> options, String typo, String correct) throws UserSyntaxException {
|
||||
if (typo.equals(options.peek())) {
|
||||
throw new UserSyntaxException("unknown option " + typo + ", did you mean " + correct + "?");
|
||||
}
|
||||
}
|
||||
|
||||
protected static final char quoteCharacter() {
|
||||
return File.pathSeparatorChar == ';' ? '"' : '\'';
|
||||
}
|
||||
|
||||
private static <T> Predicate<T> recurseIfPossible(Predicate<T> filter) {
|
||||
return x -> filter != null && filter.test(x);
|
||||
}
|
||||
|
||||
private static String acronomify(String multipleWords) {
|
||||
boolean newWord = true;
|
||||
String acronym = "";
|
||||
for (char c : multipleWords.toCharArray()) {
|
||||
if (newWord) {
|
||||
if (Character.isAlphabetic(c) && Character.isUpperCase(c)) {
|
||||
acronym += c;
|
||||
}
|
||||
}
|
||||
newWord = Character.isWhitespace(c);
|
||||
}
|
||||
return acronym;
|
||||
}
|
||||
|
||||
private static boolean match(String text, String filter) {
|
||||
if (filter.length() == 0) {
|
||||
// empty filter string matches if string is empty
|
||||
return text.length() == 0;
|
||||
}
|
||||
if (filter.charAt(0) == '*') { // recursive check
|
||||
filter = filter.substring(1);
|
||||
for (int n = 0; n <= text.length(); n++) {
|
||||
if (match(text.substring(n), filter))
|
||||
return true;
|
||||
}
|
||||
} else if (text.length() == 0) {
|
||||
// empty string and non-empty filter does not match
|
||||
return false;
|
||||
} else if (filter.charAt(0) == '?') {
|
||||
// eat any char and move on
|
||||
return match(text.substring(1), filter.substring(1));
|
||||
} else if (filter.charAt(0) == text.charAt(0)) {
|
||||
// eat chars and move on
|
||||
return match(text.substring(1), filter.substring(1));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<String> explodeFilter(String filter) throws UserSyntaxException {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String s : filter.split(",")) {
|
||||
s = s.trim();
|
||||
if (!s.isEmpty()) {
|
||||
list.add(s);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
protected static final Predicate<EventType> addCategoryFilter(String filterText, Predicate<EventType> eventFilter) throws UserSyntaxException {
|
||||
List<String> filters = explodeFilter(filterText);
|
||||
Predicate<EventType> newFilter = recurseIfPossible(eventType -> {
|
||||
for (String category : eventType.getCategoryNames()) {
|
||||
for (String filter : filters) {
|
||||
if (match(category, filter)) {
|
||||
return true;
|
||||
}
|
||||
if (category.contains(" ") && acronomify(category).equals(filter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return eventFilter == null ? newFilter : eventFilter.or(newFilter);
|
||||
}
|
||||
|
||||
protected static final Predicate<EventType> addEventFilter(String filterText, final Predicate<EventType> eventFilter) throws UserSyntaxException {
|
||||
List<String> filters = explodeFilter(filterText);
|
||||
Predicate<EventType> newFilter = recurseIfPossible(eventType -> {
|
||||
for (String filter : filters) {
|
||||
String fullEventName = eventType.getName();
|
||||
if (match(fullEventName, filter)) {
|
||||
return true;
|
||||
}
|
||||
String eventName = fullEventName.substring(fullEventName.lastIndexOf(".") + 1);
|
||||
if (match(eventName, filter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return eventFilter == null ? newFilter : eventFilter.or(newFilter);
|
||||
}
|
||||
|
||||
protected static final <T, X> Predicate<T> addCache(final Predicate<T> filter, Function<T, X> cacheFunction) {
|
||||
Map<X, Boolean> cache = new HashMap<>();
|
||||
return t -> cache.computeIfAbsent(cacheFunction.apply(t), x -> filter.test(t));
|
||||
}
|
||||
}
|
||||
|
178
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Filters.java
Normal file
178
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Filters.java
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jfr.internal.tool;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jdk.jfr.EventType;
|
||||
import jdk.jfr.consumer.RecordedThread;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
|
||||
/**
|
||||
* Helper class for creating filters.
|
||||
*/
|
||||
public class Filters {
|
||||
private static final Predicate<RecordedThread> FALSE_THREAD_PREDICATE = e -> false;
|
||||
|
||||
static Predicate<EventType> createCategoryFilter(String filterText) throws UserSyntaxException {
|
||||
List<String> filters = explodeFilter(filterText);
|
||||
Predicate<EventType> f = eventType -> {
|
||||
for (String category : eventType.getCategoryNames()) {
|
||||
for (String filter : filters) {
|
||||
if (match(category, filter)) {
|
||||
return true;
|
||||
}
|
||||
if (category.contains(" ") && acronymify(category).equals(filter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return createCache(f, EventType::getId);
|
||||
}
|
||||
|
||||
static Predicate<EventType> createEventTypeFilter(String filterText) throws UserSyntaxException {
|
||||
List<String> filters = explodeFilter(filterText);
|
||||
Predicate<EventType> f = eventType -> {
|
||||
for (String filter : filters) {
|
||||
String fullEventName = eventType.getName();
|
||||
if (match(fullEventName, filter)) {
|
||||
return true;
|
||||
}
|
||||
String eventName = fullEventName.substring(fullEventName.lastIndexOf(".") + 1);
|
||||
if (match(eventName, filter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return createCache(f, EventType::getId);
|
||||
}
|
||||
|
||||
public static <T> Predicate<T> matchAny(List<Predicate<T>> filters) {
|
||||
if (filters.isEmpty()) {
|
||||
return t -> true;
|
||||
}
|
||||
if (filters.size() == 1) {
|
||||
return filters.get(0);
|
||||
}
|
||||
return t -> {
|
||||
for (Predicate<T> p : filters) {
|
||||
if (!p.test(t)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
static Predicate<RecordedEvent> fromEventType(Predicate<EventType> filter) {
|
||||
return e -> filter.test(e.getEventType());
|
||||
}
|
||||
|
||||
static Predicate<RecordedEvent> fromRecordedThread(Predicate<RecordedThread> filter) {
|
||||
Predicate<RecordedThread> cachePredicate = createCache(filter, RecordedThread::getId);
|
||||
return event -> {
|
||||
RecordedThread t = event.getThread();
|
||||
if (t == null || t.getJavaName() == null) {
|
||||
return false;
|
||||
}
|
||||
return cachePredicate.test(t);
|
||||
};
|
||||
}
|
||||
|
||||
static Predicate<RecordedThread> createThreadFilter(String filterText) throws UserSyntaxException {
|
||||
List<String> filters = explodeFilter(filterText);
|
||||
return thread -> {
|
||||
String threadName = thread.getJavaName();
|
||||
for (String filter : filters) {
|
||||
if (match(threadName, filter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
private static final <T, X> Predicate<T> createCache(final Predicate<T> filter, Function<T, X> cacheFunction) {
|
||||
Map<X, Boolean> cache = new HashMap<>();
|
||||
return t -> cache.computeIfAbsent(cacheFunction.apply(t), x -> filter.test(t));
|
||||
}
|
||||
|
||||
private static String acronymify(String multipleWords) {
|
||||
boolean newWord = true;
|
||||
String acronym = "";
|
||||
for (char c : multipleWords.toCharArray()) {
|
||||
if (newWord) {
|
||||
if (Character.isAlphabetic(c) && Character.isUpperCase(c)) {
|
||||
acronym += c;
|
||||
}
|
||||
}
|
||||
newWord = Character.isWhitespace(c);
|
||||
}
|
||||
return acronym;
|
||||
}
|
||||
|
||||
private static boolean match(String text, String filter) {
|
||||
if (filter.length() == 0) {
|
||||
// empty filter string matches if string is empty
|
||||
return text.length() == 0;
|
||||
}
|
||||
if (filter.charAt(0) == '*') { // recursive check
|
||||
filter = filter.substring(1);
|
||||
for (int n = 0; n <= text.length(); n++) {
|
||||
if (match(text.substring(n), filter))
|
||||
return true;
|
||||
}
|
||||
} else if (text.length() == 0) {
|
||||
// empty string and non-empty filter does not match
|
||||
return false;
|
||||
} else if (filter.charAt(0) == '?') {
|
||||
// eat any char and move on
|
||||
return match(text.substring(1), filter.substring(1));
|
||||
} else if (filter.charAt(0) == text.charAt(0)) {
|
||||
// eat chars and move on
|
||||
return match(text.substring(1), filter.substring(1));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<String> explodeFilter(String filter) throws UserSyntaxException {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String s : filter.split(",")) {
|
||||
s = s.trim();
|
||||
if (!s.isEmpty()) {
|
||||
list.add(s);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -66,7 +66,7 @@ public final class Main {
|
||||
System.out.println();
|
||||
System.out.println(" jfr print --json --events CPULoad recording.jfr");
|
||||
System.out.println();
|
||||
char q = Print.quoteCharacter();
|
||||
char q = Command.quoteCharacter();
|
||||
System.out.println(" jfr print --categories " + q + "GC,JVM,Java*" + q + " recording.jfr");
|
||||
System.out.println();
|
||||
System.out.println(" jfr print --events " + q + "jdk.*" + q + " --stack-depth 64 recording.jfr");
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -163,6 +163,7 @@ final class Metadata extends Command {
|
||||
boolean showIds = false;
|
||||
boolean foundEventFilter = false;
|
||||
boolean foundCategoryFilter = false;
|
||||
List<Predicate<EventType>> filters = new ArrayList<>();
|
||||
Predicate<EventType> filter = null;
|
||||
int optionCount = options.size();
|
||||
while (optionCount > 0) {
|
||||
@ -177,7 +178,7 @@ final class Metadata extends Command {
|
||||
foundEventFilter = true;
|
||||
String filterStr = options.remove();
|
||||
warnForWildcardExpansion("--events", filterStr);
|
||||
filter = addEventFilter(filterStr, filter);
|
||||
filters.add(Filters.createEventTypeFilter(filterStr));
|
||||
}
|
||||
if (acceptFilterOption(options, "--categories")) {
|
||||
if (foundCategoryFilter) {
|
||||
@ -186,7 +187,7 @@ final class Metadata extends Command {
|
||||
foundCategoryFilter = true;
|
||||
String filterStr = options.remove();
|
||||
warnForWildcardExpansion("--categories", filterStr);
|
||||
filter = addCategoryFilter(filterStr, filter);
|
||||
filters.add(Filters.createCategoryFilter(filterStr));
|
||||
}
|
||||
if (optionCount == options.size()) {
|
||||
// No progress made
|
||||
@ -200,8 +201,8 @@ final class Metadata extends Command {
|
||||
try (PrintWriter pw = new PrintWriter(System.out, false, UTF_8)) {
|
||||
PrettyWriter prettyWriter = new PrettyWriter(pw);
|
||||
prettyWriter.setShowIds(showIds);
|
||||
if (filter != null) {
|
||||
filter = addCache(filter, type -> type.getId());
|
||||
if (!filters.isEmpty()) {
|
||||
filter = Filters.matchAny(filters);
|
||||
}
|
||||
|
||||
List<Type> types = findTypes(file);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -102,7 +102,7 @@ final class Print extends Command {
|
||||
public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
|
||||
Path file = getJFRInputFile(options);
|
||||
PrintWriter pw = new PrintWriter(System.out, false, UTF_8);
|
||||
Predicate<EventType> eventFilter = null;
|
||||
List<Predicate<EventType>> eventFilters = new ArrayList<>();
|
||||
int stackDepth = 5;
|
||||
EventPrintWriter eventWriter = null;
|
||||
int optionCount = options.size();
|
||||
@ -116,7 +116,7 @@ final class Print extends Command {
|
||||
foundEventFilter = true;
|
||||
String filter = options.remove();
|
||||
warnForWildcardExpansion("--events", filter);
|
||||
eventFilter = addEventFilter(filter, eventFilter);
|
||||
eventFilters.add(Filters.createEventTypeFilter(filter));
|
||||
}
|
||||
if (acceptFilterOption(options, "--categories")) {
|
||||
if (foundCategoryFilter) {
|
||||
@ -125,7 +125,7 @@ final class Print extends Command {
|
||||
foundCategoryFilter = true;
|
||||
String filter = options.remove();
|
||||
warnForWildcardExpansion("--categories", filter);
|
||||
eventFilter = addCategoryFilter(filter, eventFilter);
|
||||
eventFilters.add(Filters.createCategoryFilter(filter));
|
||||
}
|
||||
if (acceptOption(options, "--stack-depth")) {
|
||||
String value = options.pop();
|
||||
@ -156,9 +156,8 @@ final class Print extends Command {
|
||||
eventWriter = new PrettyWriter(pw); // default to pretty printer
|
||||
}
|
||||
eventWriter.setStackDepth(stackDepth);
|
||||
if (eventFilter != null) {
|
||||
eventFilter = addCache(eventFilter, eventType -> eventType.getId());
|
||||
eventWriter.setEventFilter(eventFilter);
|
||||
if (!eventFilters.isEmpty()) {
|
||||
eventWriter.setEventFilter(Filters.matchAny(eventFilters));
|
||||
}
|
||||
try {
|
||||
eventWriter.print(file);
|
||||
|
194
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Scrub.java
Normal file
194
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Scrub.java
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.internal.tool;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.jfr.consumer.RecordingFile;
|
||||
|
||||
final class Scrub extends Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "scrub";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getOptionSyntax() {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("[--include-events <filter>]");
|
||||
list.add("[--exclude-events <filter>]");
|
||||
list.add("[--include-categories <filter>]");
|
||||
list.add("[--exclude-categories <filter>]");
|
||||
list.add("[--include-threads <filter>]");
|
||||
list.add("[--exclude-threads <filter>]");
|
||||
list.add("<input-file>");
|
||||
list.add("[<output-file>]");
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return "Scrub contents of a recording file";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return getTitle() + ". See 'jfr help scrub' for details.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayOptionUsage(PrintStream stream) {
|
||||
// 01234567890123456789012345678901234567890123467890123456789012345678901234567890
|
||||
stream.println(" --include-events <filter> Select events matching an event name");
|
||||
stream.println();
|
||||
stream.println(" --exclude-events <filter> Exclude events matching an event name");
|
||||
stream.println();
|
||||
stream.println(" --include-categories <filter> Select events matching a category name");
|
||||
stream.println();
|
||||
stream.println(" --exclude-categories <filter> Exclude events matching a category name");
|
||||
stream.println();
|
||||
stream.println(" --include-threads <filter> Select events matching a thread name");
|
||||
stream.println();
|
||||
stream.println(" --exclude-threads <filter> Exclude events matching a thread name");
|
||||
stream.println();
|
||||
stream.println(" <input-file> The input file to read events from");
|
||||
stream.println();
|
||||
stream.println(" <output-file> The output file to write filter events to. ");
|
||||
stream.println(" If no file is specified, it will be written to");
|
||||
stream.println(" the same path as the input file, but with");
|
||||
stream.println(" \"-scrubbed\" appended to the filename");
|
||||
stream.println();
|
||||
stream.println(" The filter is a comma-separated list of names, simple and/or qualified,");
|
||||
stream.println(" and/or quoted glob patterns. If multiple filters are used, they ");
|
||||
stream.println(" are applied in the specified order");
|
||||
stream.println();
|
||||
stream.println("Example usage:");
|
||||
stream.println();
|
||||
stream.println(" jfr scrub --include-events 'jdk.Socket*' recording.jfr socket-only.jfr");
|
||||
stream.println();
|
||||
stream.println(" jfr scrub --exclude-events InitialEnvironmentVariable recording.jfr no-psw.jfr");
|
||||
stream.println();
|
||||
stream.println(" jfr scrub --include-threads main recording.jfr");
|
||||
stream.println();
|
||||
stream.println(" jfr scrub --exclude-threads 'Foo*' recording.jfr");
|
||||
stream.println();
|
||||
stream.println(" jfr scrub --include-categories 'My App' recording.jfr");
|
||||
stream.println();
|
||||
stream.println(" jfr scrub --exclude-categories JVM,OS recording.jfr");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
|
||||
ensureMinArgumentCount(options, 1);
|
||||
|
||||
Path last = Path.of(options.pollLast());
|
||||
ensureFileExtension(last, ".jfr");
|
||||
Path output = null;
|
||||
Path input = null;
|
||||
String peek = options.peekLast();
|
||||
if (peek != null && peek.endsWith(".jfr")) {
|
||||
// Both source and destination specified
|
||||
input = Path.of(options.pollLast());
|
||||
output = last;
|
||||
} else {
|
||||
// Only source file specified
|
||||
Path file = last.getFileName();
|
||||
Path dir = last.getParent();
|
||||
String filename = file.toString();
|
||||
int index = filename.lastIndexOf(".");
|
||||
String s = filename.substring(0, index);
|
||||
String t = s + "-scrubbed.jfr";
|
||||
input = last;
|
||||
output = dir == null ? Path.of(t) : dir.resolve(t);
|
||||
}
|
||||
ensureFileDoesNotExist(output);
|
||||
|
||||
List<Predicate<RecordedEvent>> filters = new ArrayList<>();
|
||||
int optionCount = options.size();
|
||||
while (optionCount > 0) {
|
||||
if (acceptFilterOption(options, "--include-events")) {
|
||||
String filter = options.remove();
|
||||
warnForWildcardExpansion("--include-events", filter);
|
||||
var f = Filters.createEventTypeFilter(filter);
|
||||
filters.add(Filters.fromEventType(f));
|
||||
}
|
||||
if (acceptFilterOption(options, "--exclude-events")) {
|
||||
String filter = options.remove();
|
||||
warnForWildcardExpansion("--exclude-events", filter);
|
||||
var f = Filters.createEventTypeFilter(filter);
|
||||
filters.add(Filters.fromEventType(f.negate()));
|
||||
}
|
||||
if (acceptFilterOption(options, "--include-categories")) {
|
||||
String filter = options.remove();
|
||||
warnForWildcardExpansion("--include-categories", filter);
|
||||
var f = Filters.createCategoryFilter(filter);
|
||||
filters.add(Filters.fromEventType(f));
|
||||
}
|
||||
if (acceptFilterOption(options, "--exclude-categories")) {
|
||||
String filter = options.remove();
|
||||
warnForWildcardExpansion("--exclude-categories", filter);
|
||||
var f = Filters.createCategoryFilter(filter);
|
||||
filters.add(Filters.fromEventType(f.negate()));
|
||||
}
|
||||
if (acceptFilterOption(options, "--include-threads")) {
|
||||
String filter = options.remove();
|
||||
warnForWildcardExpansion("--include-threads", filter);
|
||||
var f = Filters.createThreadFilter(filter);
|
||||
filters.add(Filters.fromRecordedThread(f));
|
||||
}
|
||||
if (acceptFilterOption(options, "--exclude-threads")) {
|
||||
String filter = options.remove();
|
||||
warnForWildcardExpansion("--exclude-threads", filter);
|
||||
var f = Filters.createThreadFilter(filter);
|
||||
filters.add(Filters.fromRecordedThread(f).negate());
|
||||
}
|
||||
if (optionCount == options.size()) {
|
||||
// No progress made
|
||||
checkCommonError(options, "--include-event", "--include-events");
|
||||
checkCommonError(options, "--include-category", "--include-categories");
|
||||
checkCommonError(options, "--include-thread", "--include-threads");
|
||||
throw new UserSyntaxException("unknown option " + options.peek());
|
||||
}
|
||||
optionCount = options.size();
|
||||
}
|
||||
|
||||
try (RecordingFile rf = new RecordingFile(input)) {
|
||||
rf.write(output, Filters.matchAny(filters));
|
||||
} catch (IOException ioe) {
|
||||
couldNotReadError(input, ioe);
|
||||
}
|
||||
println("Scrubbed recording file written to:");
|
||||
println(output.toAbsolutePath().toString());
|
||||
}
|
||||
}
|
121
test/jdk/jdk/jfr/api/consumer/TestRecordingFileSanitization.java
Normal file
121
test/jdk/jdk/jfr/api/consumer/TestRecordingFileSanitization.java
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.api.consumer;
|
||||
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordingFile;
|
||||
import jdk.jfr.Name;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import jdk.jfr.Event;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Verifies that all traces of sensitive data is removed
|
||||
* @key jfr
|
||||
* @requires vm.hasJFR
|
||||
* @library /test/lib
|
||||
* @run main/othervm jdk.jfr.api.consumer.TestRecordingFileSanitization
|
||||
*/
|
||||
public class TestRecordingFileSanitization {
|
||||
// Less than 16 characters, stored in event
|
||||
private final static String SHORT_PASSWORD = "abcde123";
|
||||
// More than 16 characters, stored in constant pool
|
||||
private final static String LONG_PASSWORD = "abcdefghijklmnopqrstuvxyz1234567890";
|
||||
|
||||
@Name("Sensitive")
|
||||
public static class SensitiveEvent extends Event {
|
||||
String shortPassword;
|
||||
String longPassword;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
Path sensitive = Path.of("sensitive.jfr");
|
||||
Path sanitized = Path.of("sanitized.jfr");
|
||||
try (Recording r = new Recording()) {
|
||||
r.start();
|
||||
SensitiveEvent e = new SensitiveEvent();
|
||||
e.shortPassword = SHORT_PASSWORD;
|
||||
e.longPassword = LONG_PASSWORD;
|
||||
e.commit();
|
||||
r.stop();
|
||||
r.dump(sensitive);
|
||||
}
|
||||
try (RecordingFile r = new RecordingFile(sensitive)) {
|
||||
r.write(sanitized, e -> !e.getEventType().getName().equals("Sensitive"));
|
||||
}
|
||||
|
||||
expect(sensitive, SHORT_PASSWORD);
|
||||
expect(sensitive, LONG_PASSWORD);
|
||||
missing(sanitized, SHORT_PASSWORD);
|
||||
missing(sanitized, LONG_PASSWORD);
|
||||
}
|
||||
|
||||
private static void expect(Path file, String text) throws IOException {
|
||||
if (!find(file, text)) {
|
||||
throw new AssertionError("Expected to find '" + text +"' in " + file);
|
||||
}
|
||||
System.out.println("OK, found '" + text + "' in " + file );
|
||||
}
|
||||
|
||||
private static void missing(Path file, String text) throws IOException {
|
||||
if (find(file, text)) {
|
||||
throw new AssertionError("Didn't expect to find '" + text +"' in " + file);
|
||||
}
|
||||
System.out.println("OK, missing '" + text + "' in " + file);
|
||||
}
|
||||
|
||||
private static boolean find(Path file, String text) throws IOException {
|
||||
byte[] textBytes = stringToBytes(text);
|
||||
byte[] fileBytes = Files.readAllBytes(file);
|
||||
for (int i = 0; i < fileBytes.length - textBytes.length; i++) {
|
||||
if (find(fileBytes, i, textBytes)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean find(byte[] haystack, int start, byte[] needle) {
|
||||
for (int i = 0; i < needle.length; i++) {
|
||||
if (haystack[start + i] != needle[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static byte[] stringToBytes(String text) {
|
||||
byte[] bytes = new byte[text.length()];
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
if (text.charAt(i) > 127) {
|
||||
throw new Error("Test only allows characters that becomes one byte with LEB128");
|
||||
}
|
||||
bytes[i] = (byte)(text.charAt(i));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
126
test/jdk/jdk/jfr/api/consumer/TestRecordingFileWrite.java
Normal file
126
test/jdk/jdk/jfr/api/consumer/TestRecordingFileWrite.java
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.api.consumer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
|
||||
import jdk.jfr.Configuration;
|
||||
import jdk.jfr.Event;
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordingFile;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Tests RecordingFile::write(Path, Predicate<RecordedEvent>)
|
||||
* @key jfr
|
||||
* @requires vm.hasJFR
|
||||
* @library /test/lib
|
||||
* @run main/othervm jdk.jfr.api.consumer.TestRecordingFileWrite
|
||||
*/
|
||||
public class TestRecordingFileWrite {
|
||||
|
||||
static class ScrubEvent extends Event {
|
||||
long id;
|
||||
String message;
|
||||
}
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
Path scrubbed = Paths.get("scrubbed.jfr");
|
||||
Path original = Paths.get("original.jfr");
|
||||
|
||||
createRecording(original);
|
||||
Queue<String> ids = scrubRecording(original, scrubbed);
|
||||
System.out.println("Original size: " + Files.size(original));
|
||||
System.out.println("Scrubbed size: " + Files.size(scrubbed));
|
||||
System.out.println("Scrubbed event count: " + ids.size());
|
||||
if (ids.size() < 50_000) {
|
||||
throw new AssertionError("Expected at least 50 000 events to be included");
|
||||
}
|
||||
verify(scrubbed, ids);
|
||||
}
|
||||
|
||||
private static void verify(Path scrubbed, Queue<String> events) throws Exception {
|
||||
try (RecordingFile rf = new RecordingFile(scrubbed)) {
|
||||
while (rf.hasMoreEvents()) {
|
||||
String event = rf.readEvent().toString();
|
||||
String expected = events.poll();
|
||||
if (!event.equals(expected)) {
|
||||
System.out.println("Found:");
|
||||
System.out.println(event);
|
||||
System.out.println("Expected:");
|
||||
System.out.println(expected);
|
||||
throw new Exception("Found event that should not be there. See log");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!events.isEmpty()) {
|
||||
throw new AssertionError("Missing events " + events);
|
||||
}
|
||||
}
|
||||
|
||||
private static Queue<String> scrubRecording(Path original, Path scrubbed) throws IOException {
|
||||
Queue<String> events = new ArrayDeque<>(150_000);
|
||||
Random random = new Random();
|
||||
try (RecordingFile rf = new RecordingFile(original)) {
|
||||
rf.write(scrubbed, event -> {
|
||||
boolean keep = random.nextInt(10) == 0;
|
||||
if (event.getEventType().getName().equals("jdk.OldObjectSample")) {
|
||||
System.out.println(event);
|
||||
keep = true;
|
||||
}
|
||||
if (keep) {
|
||||
events.add(event.toString());
|
||||
}
|
||||
return keep;
|
||||
});
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private static void createRecording(Path file) throws Exception {
|
||||
// Use profile configuration so more complex data structures
|
||||
// are serialized
|
||||
Configuration c = Configuration.getConfiguration("profile");
|
||||
try (Recording r = new Recording(c)) {
|
||||
r.start();
|
||||
String s = "A";
|
||||
// Generate sufficient number of events to provoke
|
||||
// chunk rotations
|
||||
for (int i = 0; i < 1_000_000; i++) {
|
||||
ScrubEvent event = new ScrubEvent();
|
||||
event.message = s.repeat(i % 30);
|
||||
event.id = i;
|
||||
event.commit();
|
||||
}
|
||||
r.stop();
|
||||
r.dump(file);
|
||||
}
|
||||
}
|
||||
}
|
313
test/jdk/jdk/jfr/tool/TestScrub.java
Normal file
313
test/jdk/jdk/jfr/tool/TestScrub.java
Normal file
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jfr.tool;
|
||||
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.test.lib.Utils;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.jfr.Name;
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.Category;
|
||||
import jdk.jfr.Event;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.jfr.consumer.RecordingFile;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test jfr scrub
|
||||
* @key jfr
|
||||
* @requires vm.hasJFR
|
||||
* @library /test/lib /test/jdk
|
||||
* @run main/othervm jdk.jfr.tool.TestScrub
|
||||
*/
|
||||
public class TestScrub {
|
||||
|
||||
@Name("example.Tiger")
|
||||
@Category("Mammal")
|
||||
private static class TigerEvent extends Event {
|
||||
}
|
||||
|
||||
@Name("example.Zebra")
|
||||
@Category("Mammal")
|
||||
|
||||
private static class ZebraEvent extends Event {
|
||||
}
|
||||
|
||||
@Name("example.Tigerfish")
|
||||
@Category("Fish")
|
||||
private static class TigerfishEvent extends Event {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
Path file = Path.of("recording.jfr");
|
||||
Path autogenerated = Path.of("recording-scrubbed.jfr");
|
||||
|
||||
try (Recording r = new Recording()) {
|
||||
r.start();
|
||||
emit(100, "India", TigerEvent.class);
|
||||
emit(100, "Namibia", ZebraEvent.class);
|
||||
emit(10000, "Lake Tanganyika", TigerfishEvent.class);
|
||||
r.stop();
|
||||
r.dump(file);
|
||||
}
|
||||
testAutogeneratedFilename(file, autogenerated);
|
||||
|
||||
testEventInclude(file);
|
||||
testEventExclude(file);
|
||||
testEventMixedIncludeExclude(file);
|
||||
|
||||
testCategoryExclude(file);
|
||||
testCategoryInclude(file);
|
||||
|
||||
testThreadExclude(file);
|
||||
testThreadInclude(file);
|
||||
}
|
||||
|
||||
private static void testAutogeneratedFilename(Path file, Path autogenerated) throws Throwable {
|
||||
List<String> arguments = new ArrayList<>();
|
||||
arguments.add("scrub");
|
||||
arguments.add(file.toAbsolutePath().toString());
|
||||
ExecuteHelper.jfr(arguments.toArray(String[]::new));
|
||||
|
||||
if (!Files.exists(autogenerated)) {
|
||||
throw new AssertionError("Expected to find auto-generated file " + autogenerated);
|
||||
}
|
||||
Files.delete(autogenerated);
|
||||
}
|
||||
|
||||
private static void testEventInclude(Path file) throws Throwable {
|
||||
for (var event : scrub(file, "--include-events", "Zebra")) {
|
||||
assertEvent(event, "Zebra");
|
||||
assertNotEvent(event, "Tiger", "Tigerfish");
|
||||
}
|
||||
for (var event : scrub(file, "--include-events", "Tiger*")) {
|
||||
assertEvent(event, "Tiger", "Tigerfish");
|
||||
assertNotEvent(event, "Zebra");
|
||||
}
|
||||
for (var event : scrub(file, "--include-events", "Tiger,Zebra")) {
|
||||
assertEvent(event, "Tiger", "Zebra");
|
||||
assertNotEvent(event, "Tigerfish");
|
||||
}
|
||||
for (var event : scrub(file, "--include-events", "Tiger", "--include-events", "Zebra")) {
|
||||
assertEvent(event, "Tiger", "Zebra");
|
||||
assertNotEvent(event, "Tigerfish");
|
||||
}
|
||||
}
|
||||
|
||||
private static void testEventExclude(Path file) throws Throwable {
|
||||
for (var event : scrub(file, "--exclude-events", "Zebra")) {
|
||||
assertNotEvent(event, "Zebra");
|
||||
assertEvent(event, "Tiger", "Tigerfish");
|
||||
}
|
||||
for (var event : scrub(file, "--exclude-events", "Tiger*")) {
|
||||
assertEvent(event, "Zebra");
|
||||
assertNotEvent(event, "Tiger", "Tigerfish");
|
||||
}
|
||||
for (var event : scrub(file, "--exclude-events", "Tiger,Zebra")) {
|
||||
assertEvent(event, "Tigerfish");
|
||||
assertNotEvent(event, "Tiger", "Zebra");
|
||||
}
|
||||
|
||||
for (var event : scrub(file, "--exclude-events", "Tiger", "--exclude-events", "Zebra")) {
|
||||
assertEvent(event, "Tigerfish");
|
||||
assertNotEvent(event, "Tiger", "Zebra");
|
||||
}
|
||||
}
|
||||
|
||||
private static void testEventMixedIncludeExclude(Path file) throws Throwable {
|
||||
for (var event : scrub(file, "--include-events", "Tiger*", "--exclude-events", "Tigerfish")) {
|
||||
assertNotEvent(event, "Zebra", "Tigerfish");
|
||||
assertEvent(event, "Tiger");
|
||||
}
|
||||
for (var event : scrub(file, "--exclude-events", "Tiger*", "--include-events", "Tiger")) {
|
||||
assertEvent(event, "Zebra", "Tiger");
|
||||
assertNotEvent(event, "Tigerfish");
|
||||
}
|
||||
for (var event : scrub(file, "--exclude-events", "example.*", "--include-events", "example.*")) {
|
||||
assertNotEvent(event, "Tigerfish", "Tiger", "Zebra");
|
||||
}
|
||||
for (var event : scrub(file, "--include-events", "example.*", "--exclude-events", "example.*")) {
|
||||
assertNotEvent(event, "Tigerfish", "Tiger", "Zebra");
|
||||
}
|
||||
}
|
||||
|
||||
private static void testCategoryInclude(Path file) throws Throwable {
|
||||
for (var event : scrub(file, "--include-categories", "Mammal")) {
|
||||
assertEvent(event, "Zebra", "Tiger");
|
||||
assertNotEvent(event, "Tigerfish");
|
||||
}
|
||||
for (var event : scrub(file, "--include-categories", "Sahara")) {
|
||||
assertNotEvent(event, "Tiger", "Tigerfish", "Zebra");
|
||||
}
|
||||
for (var event : scrub(file, "--include-categories", "Fish,Mammal")) {
|
||||
assertEvent(event, "Tiger", "Zebra", "Tigerfish");
|
||||
}
|
||||
for (var event : scrub(file, "--include-categories", "Mammal", "--include-categories", "Fish")) {
|
||||
assertEvent(event, "Tiger", "Zebra", "Tigerfish");
|
||||
}
|
||||
}
|
||||
|
||||
private static void testCategoryExclude(Path file) throws Throwable {
|
||||
for (var event : scrub(file, "--exclude-categories", "Mammal")) {
|
||||
assertNotEvent(event, "Zebra", "Tiger");
|
||||
assertEvent(event, "Tigerfish");
|
||||
}
|
||||
for (var event : scrub(file, "--exclude-categories", "Mammal,Fish")) {
|
||||
assertNotEvent(event, "Zebra", "Tiger", "Tigerfish");
|
||||
}
|
||||
for (var event : scrub(file, "--exclude-categories", "Mammal")) {
|
||||
assertNotEvent(event, "Zebra", "Tiger");
|
||||
assertEvent(event, "Tigerfish");
|
||||
}
|
||||
for (var event : scrub(file, "--exclude-categories", "Mammal")) {
|
||||
assertNotEvent(event, "Zebra", "Tiger");
|
||||
assertEvent(event, "Tigerfish");
|
||||
}
|
||||
}
|
||||
|
||||
private static void testThreadInclude(Path file) throws Throwable {
|
||||
for (var event : scrub(file, "--include-threads", "Namibia")) {
|
||||
assertThread(event, "Namibia");
|
||||
assertNotThread(event, "India", "Lake Tanganyika");
|
||||
}
|
||||
|
||||
for (var event : scrub(file, "--include-threads", "Nam*")) {
|
||||
assertThread(event, "Namibia");
|
||||
assertNotThread(event, "Lake Tanganyika", "India");
|
||||
}
|
||||
|
||||
for (var event : scrub(file, "--include-threads", "Namibia,Lake")) {
|
||||
assertThread(event, "Namibia", "Lake Tanganyika");
|
||||
assertNotThread(event, "India");
|
||||
}
|
||||
|
||||
for (var event : scrub(file, "--include-threads", "India", "--include-threads", "Lake Tanganyika")) {
|
||||
assertThread(event, "India", "Lake Tanganyika");
|
||||
assertNotThread(event, "Namibia");
|
||||
}
|
||||
}
|
||||
|
||||
private static void testThreadExclude(Path file) throws Throwable {
|
||||
for (var event : scrub(file, "--exclude-threads", "Namibia")) {
|
||||
assertThread(event, "India", "Lake Tanganyika");
|
||||
assertNotThread(event, "Namibia");
|
||||
}
|
||||
|
||||
for (var event : scrub(file, "--exclude-threads", "Nam*")) {
|
||||
assertThread(event, "Lake Tanganyika", "India");
|
||||
assertNotThread(event, "Namibia");
|
||||
}
|
||||
|
||||
for (var event : scrub(file, "--exclude-threads", "Namibia,Lake Tanganyika")) {
|
||||
assertThread(event, "India");
|
||||
assertNotThread(event, "Namibia", "Lake Tanganyika");
|
||||
}
|
||||
|
||||
for (var event : scrub(file, "--exclude-events", "India", "--include-events", "Lake Tanganyika")) {
|
||||
assertThread(event, "Namibia");
|
||||
assertNotThread(event, "India", "Lake Tanganyika");
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertNotThread(RecordedEvent event, String... threadNames) {
|
||||
String s = event.getThread().getJavaName();
|
||||
for (String threadName : threadNames) {
|
||||
if (threadName.equals(s)) {
|
||||
throw new AssertionError("Found unexpected thread" + threadName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertThread(RecordedEvent event, String... threadNames) {
|
||||
String s = event.getThread().getJavaName();
|
||||
for (String threadName : threadNames) {
|
||||
if (threadName.equals(s)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("Found unexpected thread" + s);
|
||||
}
|
||||
|
||||
private static void assertNotEvent(RecordedEvent event, String... eventNames) {
|
||||
String s = event.getEventType().getName();
|
||||
for (String eventName : eventNames) {
|
||||
String n = "example." + eventName;
|
||||
if (n.equals(s)) {
|
||||
throw new AssertionError("Found unexpected " + eventName + " event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertEvent(RecordedEvent event, String... eventNames) {
|
||||
String s = event.getEventType().getName();
|
||||
for (String eventName : eventNames) {
|
||||
String n = "example." + eventName;
|
||||
if (n.equals(s)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("Found unexpected " + s + " event");
|
||||
}
|
||||
|
||||
private static List<RecordedEvent> scrub(Path input, String... options) throws Throwable {
|
||||
Path output = Path.of("scrubbed.jfr");
|
||||
List<String> arguments = new ArrayList<>();
|
||||
arguments.add("scrub");
|
||||
arguments.addAll(Arrays.asList(options));
|
||||
arguments.add(input.toAbsolutePath().toString());
|
||||
arguments.add(output.toAbsolutePath().toString());
|
||||
|
||||
var outp = ExecuteHelper.jfr(arguments.toArray(String[]::new));
|
||||
System.out.println(outp.getStderr());
|
||||
System.out.println(outp.getStdout());
|
||||
List<RecordedEvent> events = RecordingFile.readAllEvents(output);
|
||||
Files.delete(output);
|
||||
return events;
|
||||
}
|
||||
|
||||
private static void emit(int count, String threadName, Class<? extends Event> eventClass) throws Throwable {
|
||||
Thread t = new Thread(() -> emitEvents(count, eventClass), threadName);
|
||||
t.start();
|
||||
t.join();
|
||||
}
|
||||
|
||||
private static void emitEvents(int count, Class<? extends Event> eventClass) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
try {
|
||||
Event event = eventClass.getDeclaredConstructor().newInstance();
|
||||
event.commit();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user