8271232: JFR: Scrub recording data

Reviewed-by: mgronlun
This commit is contained in:
Erik Gahlin 2022-02-25 12:56:09 +00:00
parent 735e86b0f7
commit e96c599ed2
28 changed files with 2002 additions and 216 deletions

View File

@ -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");

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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");

View File

@ -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.
*

View File

@ -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());
}
}
}

View File

@ -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;

View File

@ -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 + "]";
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View 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
@ -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));
}
}

View 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;
}
}

View 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
@ -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");

View File

@ -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);

View 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);

View 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());
}
}

View 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;
}
}

View 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);
}
}
}

View 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();
}
}
}
}