提交 a279f1d6 编写于 作者: D Denghui Dong 提交者: D-D-H

[Backport] 8226511: Implement JFR Event Streaming

Summary:

Test Plan: jdk/jfr

Reviewed-by: yuleil

Issue: https://github.com/alibaba/dragonwell8/issues/112
上级 66bf03b7
...@@ -413,6 +413,36 @@ public final class Recording implements Closeable { ...@@ -413,6 +413,36 @@ public final class Recording implements Closeable {
internal.setMaxSize(maxSize); internal.setMaxSize(maxSize);
} }
/**
* Determines how often events are made available for streaming.
*
* @param interval the interval at which events are made available for streaming.
*
* @throws IllegalArgumentException if {@code interval} is negative
*
* @throws IllegalStateException if the recording is in the {@code CLOSED} state
*
* @since 14
*/
public void setFlushInterval(Duration interval) {
Objects.nonNull(interval);
if (interval.isNegative()) {
throw new IllegalArgumentException("Stream interval can't be negative");
}
internal.setFlushInterval(interval);
}
/**
* Returns how often events are made available for streaming purposes.
*
* @return the flush interval, or {@code null} if no interval has been set
*
* @since 14
*/
public Duration getFlushInterval() {
return internal.getFlushInterval();
}
/** /**
* Determines how far back data is kept in the disk repository. * Determines how far back data is kept in the disk repository.
* <p> * <p>
......
...@@ -601,6 +601,36 @@ ...@@ -601,6 +601,36 @@
<setting name="enabled">true</setting> <setting name="enabled">true</setting>
</event> </event>
<event name="jdk.Flush">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.FlushStorage">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.FlushStacktrace">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.FlushStringPool">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.FlushMetadata">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.FlushTypeSet">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.DataLoss"> <event name="jdk.DataLoss">
<setting name="enabled">true</setting> <setting name="enabled">true</setting>
</event> </event>
......
...@@ -601,6 +601,36 @@ ...@@ -601,6 +601,36 @@
<setting name="enabled">true</setting> <setting name="enabled">true</setting>
</event> </event>
<event name="jdk.Flush">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.FlushStorage">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.FlushStacktrace">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.FlushStringPool">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.FlushMetadata">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.FlushTypeSet">
<setting name="enabled">true</setting>
<setting name="threshold">0 ns</setting>
</event>
<event name="jdk.DataLoss"> <event name="jdk.DataLoss">
<setting name="enabled">true</setting> <setting name="enabled">true</setting>
</event> </event>
......
/*
* Copyright (c) 2016, 2018, 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.consumer;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import jdk.jfr.EventType;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.MetadataDescriptor;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.consumer.ChunkHeader;
import jdk.jfr.internal.consumer.RecordingInput;
/**
* Parses a chunk.
*
*/
final class ChunkParser {
private static final long CONSTANT_POOL_TYPE_ID = 1;
private final RecordingInput input;
private final LongMap<Parser> parsers;
private final ChunkHeader chunkHeader;
private final long absoluteChunkEnd;
private final MetadataDescriptor metadata;
private final LongMap<Type> typeMap;
private final TimeConverter timeConverter;
public ChunkParser(RecordingInput input) throws IOException {
this(new ChunkHeader(input));
}
private ChunkParser(ChunkHeader header) throws IOException {
this.input = header.getInput();
this.chunkHeader = header;
this.metadata = header.readMetadata();
this.absoluteChunkEnd = header.getEnd();
this.timeConverter = new TimeConverter(chunkHeader, metadata.getGMTOffset());
ParserFactory factory = new ParserFactory(metadata, timeConverter);
LongMap<ConstantMap> constantPools = factory.getConstantPools();
parsers = factory.getParsers();
typeMap = factory.getTypeMap();
fillConstantPools(parsers, constantPools);
constantPools.forEach(ConstantMap::setIsResolving);
constantPools.forEach(ConstantMap::resolve);
constantPools.forEach(ConstantMap::setResolved);
input.position(chunkHeader.getEventStart());
}
public RecordedEvent readEvent() throws IOException {
while (input.position() < absoluteChunkEnd) {
long pos = input.position();
int size = input.readInt();
if (size == 0) {
throw new IOException("Event can't have zero size");
}
long typeId = input.readLong();
if (typeId > CONSTANT_POOL_TYPE_ID) { // also skips metadata (id=0)
Parser ep = parsers.get(typeId);
if (ep instanceof EventParser) {
return (RecordedEvent) ep.parse(input);
}
}
input.position(pos + size);
}
return null;
}
private void fillConstantPools(LongMap<Parser> typeParser, LongMap<ConstantMap> constantPools) throws IOException {
long nextCP = chunkHeader.getAbsoluteChunkStart();
long deltaToNext = chunkHeader.getConstantPoolPosition();
while (deltaToNext != 0) {
nextCP += deltaToNext;
input.position(nextCP);
final long position = nextCP;
int size = input.readInt(); // size
long typeId = input.readLong();
if (typeId != CONSTANT_POOL_TYPE_ID) {
throw new IOException("Expected check point event (id = 1) at position " + nextCP + ", but found type id = " + typeId);
}
input.readLong(); // timestamp
input.readLong(); // duration
deltaToNext = input.readLong();
final long delta = deltaToNext;
boolean flush = input.readBoolean();
int poolCount = input.readInt();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, () -> {
return "New constant pool: startPosition=" + position + ", size=" + size + ", deltaToNext=" + delta + ", flush=" + flush + ", poolCount=" + poolCount;
});
for (int i = 0; i < poolCount; i++) {
long id = input.readLong(); // type id
ConstantMap pool = constantPools.get(id);
Type type = typeMap.get(id);
if (pool == null) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Found constant pool(" + id + ") that is never used");
if (type == null) {
throw new IOException("Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + nextCP + ", " + nextCP + size + "]");
}
pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
constantPools.put(type.getId(), pool);
}
Parser parser = typeParser.get(id);
if (parser == null) {
throw new IOException("Could not find constant pool type with id = " + id);
}
try {
int count = input.readInt();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, () -> "Constant: " + getName(id) + "[" + count + "]");
for (int j = 0; j < count; j++) {
long key = input.readLong();
Object value = parser.parse(input);
pool.put(key, value);
}
} catch (Exception e) {
throw new IOException("Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + nextCP + ", " + nextCP + size + "]", e);
}
}
if (input.position() != nextCP + size) {
throw new IOException("Size of check point event doesn't match content");
}
}
}
private String getName(long id) {
Type type = typeMap.get(id);
return type == null ? ("unknown(" + id + ")") : type.getName();
}
public Collection<Type> getTypes() {
return metadata.getTypes();
}
public List<EventType> getEventTypes() {
return metadata.getEventTypes();
}
public boolean isLastChunk() {
return chunkHeader.isLastChunk();
}
public ChunkParser nextChunkParser() throws IOException {
return new ChunkParser(chunkHeader.nextHeader());
}
}
/*
* Copyright (c) 2019, 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.consumer;
import java.io.IOException;
import java.nio.file.Path;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.function.Consumer;
import jdk.jfr.internal.SecuritySupport;
import jdk.jfr.internal.Utils;
import jdk.jfr.internal.consumer.EventDirectoryStream;
import jdk.jfr.internal.consumer.EventFileStream;
import jdk.jfr.internal.consumer.FileAccess;
/**
* Represents a stream of events.
* <p>
* A stream is a sequence of events and the way to interact with a stream is to
* register actions. The {@code EventStream} interface is not to be implemented
* and future versions of the JDK may prevent this completely.
* <p>
* To receive a notification when an event arrives, register an action using the
* {@link #onEvent(Consumer)} method. To filter the stream for an event with a
* specific name, use {@link #onEvent(String, Consumer)} method.
* <p>
* By default, the same {@code RecordedEvent} object can be used to
* represent two or more distinct events. That object can be delivered
* multiple times to the same action as well as to other actions. To use an
* event object after the action is completed, the
* {@link #setReuse(boolean)} method should be set to {@code false} so a
* new object is allocated for each event.
* <p>
* Events are delivered in batches. To receive a notification when a batch is
* complete, register an action using the {@link #onFlush(Runnable)} method.
* This is an opportunity to aggregate or push data to external systems while
* the Java Virtual Machine (JVM) is preparing the next batch.
* <p>
* Events within a batch are sorted chronologically by their end time.
* Well-ordering of events is only maintained for events available to the JVM at
* the point of flush, i.e. for the set of events delivered as a unit in a
* single batch. Events delivered in a batch could therefore be out-of-order
* compared to events delivered in a previous batch, but never out-of-order with
* events within the same batch. If ordering is not a concern, sorting can be
* disabled using the {@link #setOrdered(boolean)} method.
* <p>
* To dispatch events to registered actions, the stream must be started. To
* start processing in the current thread, invoke the {@link #start()} method.
* To process actions asynchronously in a separate thread, invoke the
* {@link #startAsync()} method. To await completion of the stream, use the
* awaitTermination {@link #awaitTermination()} or the
* {@link #awaitTermination(Duration)} method.
* <p>
* When a stream ends it is automatically closed. To manually stop processing of
* events, close the stream by invoking the {@link #close()} method. A stream
* can also be automatically closed in exceptional circumstances, for example if
* the JVM that is being monitored exits. To receive a notification in any of
* these occasions, use the {@link #onClose(Runnable)} method to register an
* action.
* <p>
* If an unexpected exception occurs in an action, it is possible to catch the
* exception in an error handler. An error handler can be registered using the
* {@link #onError(Runnable)} method. If no error handler is registered, the
* default behavior is to print the exception and its backtrace to the standard
* error stream.
* <p>
* The following example shows how an {@code EventStream} can be used to listen
* to events on a JVM running Flight Recorder
*
* <pre>
* <code>
* try (var es = EventStream.openRepository()) {
* es.onEvent("jdk.CPULoad", event -> {
* System.out.println("CPU Load " + event.getEndTime());
* System.out.println(" Machine total: " + 100 * event.getFloat("machineTotal") + "%");
* System.out.println(" JVM User: " + 100 * event.getFloat("jvmUser") + "%");
* System.out.println(" JVM System: " + 100 * event.getFloat("jvmSystem") + "%");
* System.out.println();
* });
* es.onEvent("jdk.GarbageCollection", event -> {
* System.out.println("Garbage collection: " + event.getLong("gcId"));
* System.out.println(" Cause: " + event.getString("cause"));
* System.out.println(" Total pause: " + event.getDuration("sumOfPauses"));
* System.out.println(" Longest pause: " + event.getDuration("longestPause"));
* System.out.println();
* });
* es.start();
* }
* </code>
* </pre>
* <p>
* To start recording together with the stream, see {@link RecordingStream}.
*
* @since 14
*/
public interface EventStream extends AutoCloseable {
/**
* Creates a stream from the repository of the current Java Virtual Machine
* (JVM).
* <p>
* By default, the stream starts with the next event flushed by Flight
* Recorder.
*
* @return an event stream, not {@code null}
*
* @throws IOException if a stream can't be opened, or an I/O error occurs
* when trying to access the repository
*
* @throws SecurityException if a security manager exists and the caller
* does not have
* {@code FlightRecorderPermission("accessFlightRecorder")}
*/
public static EventStream openRepository() throws IOException {
Utils.checkAccessFlightRecorder();
return new EventDirectoryStream(AccessController.getContext(), null, SecuritySupport.PRIVILIGED, false);
}
/**
* Creates an event stream from a disk repository.
* <p>
* By default, the stream starts with the next event flushed by Flight
* Recorder.
*
* @param directory location of the disk repository, not {@code null}
*
* @return an event stream, not {@code null}
*
* @throws IOException if a stream can't be opened, or an I/O error occurs
* when trying to access the repository
*
* @throws SecurityException if a security manager exists and its
* {@code checkRead} method denies read access to the directory, or
* files in the directory.
*/
public static EventStream openRepository(Path directory) throws IOException {
Objects.nonNull(directory);
AccessControlContext acc = AccessController.getContext();
return new EventDirectoryStream(acc, directory, FileAccess.UNPRIVILIGED, false);
}
/**
* Creates an event stream from a file.
* <p>
* By default, the stream starts with the first event in the file.
*
* @param file location of the file, not {@code null}
*
* @return an event stream, not {@code null}
*
* @throws IOException if the file can't be opened, or an I/O error occurs
* during reading
*
* @throws SecurityException if a security manager exists and its
* {@code checkRead} method denies read access to the file
*/
static EventStream openFile(Path file) throws IOException {
return new EventFileStream(AccessController.getContext(), file);
}
/**
* Registers an action to perform on all events in the stream.
*
* @param action an action to perform on each {@code RecordedEvent}, not
* {@code null}
*/
void onEvent(Consumer<RecordedEvent> action);
/**
* Registers an action to perform on all events matching a name.
*
* @param eventName the name of the event, not {@code null}
*
* @param action an action to perform on each {@code RecordedEvent} matching
* the event name, not {@code null}
*/
void onEvent(String eventName, Consumer<RecordedEvent> action);
/**
* Registers an action to perform after the stream has been flushed.
*
* @param action an action to perform after the stream has been
* flushed, not {@code null}
*/
void onFlush(Runnable action);
/**
* Registers an action to perform if an exception occurs.
* <p>
* if an action is not registered, an exception stack trace is printed to
* standard error.
* <p>
* Registering an action overrides the default behavior. If multiple actions
* have been registered, they are performed in the order of registration.
* <p>
* If this method itself throws an exception, resulting behavior is
* undefined.
*
* @param action an action to perform if an exception occurs, not
* {@code null}
*/
void onError(Consumer<Throwable> action);
/**
* Registers an action to perform when the stream is closed.
* <p>
* If the stream is already closed, the action will be performed immediately
* in the current thread.
*
* @param action an action to perform after the stream is closed, not
* {@code null}
* @see #close()
*/
void onClose(Runnable action);
/**
* Releases all resources associated with this stream.
* <p>
* Closing a previously closed stream has no effect.
*/
void close();
/**
* Unregisters an action.
* <p>
* If the action has been registered multiple times, all instances are
* unregistered.
*
* @param action the action to unregister, not {@code null}
*
* @return {@code true} if the action was unregistered, {@code false}
* otherwise
*
* @see #onEvent(Consumer)
* @see #onEvent(String, Consumer)
* @see #onFlush(Runnable)
* @see #onClose(Runnable)
* @see #onError(Consumer)
*/
boolean remove(Object action);
/**
* Specifies that the event object in an {@link #onEvent(Consumer)} action
* can be reused.
* <p>
* If reuse is set to {@code true), an action should not keep a reference
* to the event object after the action has completed.
*
* @param reuse {@code true} if an event object can be reused, {@code false}
* otherwise
*/
void setReuse(boolean reuse);
/**
* Specifies that events arrives in chronological order, sorted by the time
* they were committed to the stream.
*
* @param ordered if event objects arrive in chronological order to
* {@code #onEvent(Consumer)}
*/
void setOrdered(boolean ordered);
/**
* Specifies the start time of the stream.
* <p>
* The start time must be set before starting the stream
*
* @param startTime the start time, not {@code null}
*
* @throws IllegalStateException if the stream is already started
*
* @see #start()
* @see #startAsync()
*/
void setStartTime(Instant startTime);
/**
* Specifies the end time of the stream.
* <p>
* The end time must be set before starting the stream.
* <p>
* At end time, the stream is closed.
*
* @param endTime the end time, not {@code null}
*
* @throws IllegalStateException if the stream is already started
*
* @see #start()
* @see #startAsync()
*/
void setEndTime(Instant endTime);
/**
* Start processing of actions.
* <p>
* Actions are performed in the current thread.
*
* @throws IllegalStateException if the stream is already started or closed
*/
void start();
/**
* Start asynchronous processing of actions.
* <p>
* Actions are performed in a single separate thread.
*
* @throws IllegalStateException if the stream is already started or closed
*/
void startAsync();
/**
* Blocks until all actions are completed, or the stream is closed, or the
* timeout occurs, or the current thread is interrupted, whichever happens
* first.
*
* @param timeout the maximum time to wait, not {@code null}
*
* @throws IllegalArgumentException if timeout is negative
* @throws InterruptedException if interrupted while waiting
*
* @see #start()
* @see #startAsync()
* @see Thread#interrupt()
*/
void awaitTermination(Duration timeout) throws InterruptedException;
/**
* Blocks until all actions are completed, or the stream is closed, or the
* current thread is interrupted, whichever happens first.
*
* @throws InterruptedException if interrupted while waiting
*
* @see #start()
* @see #startAsync()
* @see Thread#interrupt()
*/
void awaitTermination() throws InterruptedException;
}
...@@ -26,10 +26,8 @@ ...@@ -26,10 +26,8 @@
package jdk.jfr.consumer; package jdk.jfr.consumer;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.List;
import jdk.jfr.ValueDescriptor; import jdk.jfr.internal.consumer.ObjectContext;
import jdk.jfr.internal.Type;
/** /**
* A recorded Java type, such as a class or an interface. * A recorded Java type, such as a class or an interface.
...@@ -37,21 +35,11 @@ import jdk.jfr.internal.Type; ...@@ -37,21 +35,11 @@ import jdk.jfr.internal.Type;
* @since 8 * @since 8
*/ */
public final class RecordedClass extends RecordedObject { public final class RecordedClass extends RecordedObject {
static ObjectFactory<RecordedClass> createFactory(Type type, TimeConverter timeConverter) {
return new ObjectFactory<RecordedClass>(type) {
@Override
RecordedClass createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedClass(desc, id, object, timeConverter);
}
};
}
private final long uniqueId; private final long uniqueId;
// package private // package private
private RecordedClass(List<ValueDescriptor> descriptors, long id, Object[] values, TimeConverter timeConverter) { RecordedClass(ObjectContext objectContext, long id, Object[] values) {
super(descriptors, values, timeConverter); super(objectContext, values);
this.uniqueId = id; this.uniqueId = id;
} }
......
...@@ -25,10 +25,7 @@ ...@@ -25,10 +25,7 @@
package jdk.jfr.consumer; package jdk.jfr.consumer;
import java.util.List; import jdk.jfr.internal.consumer.ObjectContext;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Type;
/** /**
* A recorded Java class loader. * A recorded Java class loader.
...@@ -36,21 +33,11 @@ import jdk.jfr.internal.Type; ...@@ -36,21 +33,11 @@ import jdk.jfr.internal.Type;
* @since 8 * @since 8
*/ */
public final class RecordedClassLoader extends RecordedObject { public final class RecordedClassLoader extends RecordedObject {
static ObjectFactory<RecordedClassLoader> createFactory(Type type, TimeConverter timeConverter) {
return new ObjectFactory<RecordedClassLoader>(type) {
@Override
RecordedClassLoader createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedClassLoader(desc, id, object, timeConverter);
}
};
}
private final long uniqueId; private final long uniqueId;
// package private // package private
private RecordedClassLoader(List<ValueDescriptor> descriptors, long id, Object[] values, TimeConverter timeConverter) { RecordedClassLoader(ObjectContext objectContext, long id, Object[] values) {
super(descriptors, values, timeConverter); super(objectContext, values);
this.uniqueId = id; this.uniqueId = id;
} }
......
...@@ -32,6 +32,7 @@ import java.util.List; ...@@ -32,6 +32,7 @@ import java.util.List;
import jdk.jfr.EventType; import jdk.jfr.EventType;
import jdk.jfr.ValueDescriptor; import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.EventInstrumentation; import jdk.jfr.internal.EventInstrumentation;
import jdk.jfr.internal.consumer.ObjectContext;
/** /**
* A recorded event. * A recorded event.
...@@ -39,17 +40,14 @@ import jdk.jfr.internal.EventInstrumentation; ...@@ -39,17 +40,14 @@ import jdk.jfr.internal.EventInstrumentation;
* @since 8 * @since 8
*/ */
public final class RecordedEvent extends RecordedObject { public final class RecordedEvent extends RecordedObject {
private final EventType eventType; long startTimeTicks;
private final long startTime; long endTimeTicks;
// package private needed for efficient sorting
final long endTime;
// package private // package private
RecordedEvent(EventType type, List<ValueDescriptor> vds, Object[] values, long startTime, long endTime, TimeConverter timeConverter) { RecordedEvent(ObjectContext objectContext, Object[] values, long startTimeTicks, long endTimeTicks) {
super(vds, values, timeConverter); super(objectContext, values);
this.eventType = type; this.startTimeTicks = startTimeTicks;
this.startTime = startTime; this.endTimeTicks = endTimeTicks;
this.endTime = endTime;
} }
/** /**
...@@ -78,7 +76,7 @@ public final class RecordedEvent extends RecordedObject { ...@@ -78,7 +76,7 @@ public final class RecordedEvent extends RecordedObject {
* @return the event type, not {@code null} * @return the event type, not {@code null}
*/ */
public EventType getEventType() { public EventType getEventType() {
return eventType; return objectContext.eventType;
} }
/** /**
...@@ -89,7 +87,7 @@ public final class RecordedEvent extends RecordedObject { ...@@ -89,7 +87,7 @@ public final class RecordedEvent extends RecordedObject {
* @return the start time, not {@code null} * @return the start time, not {@code null}
*/ */
public Instant getStartTime() { public Instant getStartTime() {
return Instant.ofEpochSecond(0, startTime); return Instant.ofEpochSecond(0, getStartTimeNanos());
} }
/** /**
...@@ -100,7 +98,7 @@ public final class RecordedEvent extends RecordedObject { ...@@ -100,7 +98,7 @@ public final class RecordedEvent extends RecordedObject {
* @return the end time, not {@code null} * @return the end time, not {@code null}
*/ */
public Instant getEndTime() { public Instant getEndTime() {
return Instant.ofEpochSecond(0, endTime); return Instant.ofEpochSecond(0, getEndTimeNanos());
} }
/** /**
...@@ -109,7 +107,7 @@ public final class RecordedEvent extends RecordedObject { ...@@ -109,7 +107,7 @@ public final class RecordedEvent extends RecordedObject {
* @return the duration in nanoseconds, not {@code null} * @return the duration in nanoseconds, not {@code null}
*/ */
public Duration getDuration() { public Duration getDuration() {
return Duration.ofNanos(endTime - startTime); return Duration.ofNanos(getEndTimeNanos() - getStartTimeNanos());
} }
/** /**
...@@ -119,6 +117,31 @@ public final class RecordedEvent extends RecordedObject { ...@@ -119,6 +117,31 @@ public final class RecordedEvent extends RecordedObject {
*/ */
@Override @Override
public List<ValueDescriptor> getFields() { public List<ValueDescriptor> getFields() {
return getEventType().getFields(); return objectContext.fields;
}
protected final Object objectAt(int index) {
if (index == 0) {
return startTimeTicks;
}
if (hasDuration()) {
if (index == 1) {
return endTimeTicks - startTimeTicks;
}
return objects[index - 2];
}
return objects[index - 1];
}
private boolean hasDuration() {
return objects.length + 2 == objectContext.fields.size();
}
private long getStartTimeNanos() {
return objectContext.convertTimestamp(startTimeTicks);
}
private long getEndTimeNanos() {
return objectContext.convertTimestamp(endTimeTicks);
} }
} }
...@@ -26,10 +26,8 @@ ...@@ -26,10 +26,8 @@
package jdk.jfr.consumer; package jdk.jfr.consumer;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.List;
import jdk.jfr.ValueDescriptor; import jdk.jfr.internal.consumer.ObjectContext;
import jdk.jfr.internal.Type;
/** /**
* A recorded frame in a stack trace. * A recorded frame in a stack trace.
...@@ -37,19 +35,9 @@ import jdk.jfr.internal.Type; ...@@ -37,19 +35,9 @@ import jdk.jfr.internal.Type;
* @since 8 * @since 8
*/ */
public final class RecordedFrame extends RecordedObject { public final class RecordedFrame extends RecordedObject {
static ObjectFactory<RecordedFrame> createFactory(Type type, TimeConverter timeConverter) {
return new ObjectFactory<RecordedFrame>(type) {
@Override
RecordedFrame createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedFrame(desc, object, timeConverter);
}
};
}
// package private // package private
RecordedFrame(List<ValueDescriptor> desc, Object[] objects, TimeConverter timeConverter) { RecordedFrame(ObjectContext objectContext, Object[] values) {
super(desc, objects, timeConverter); super(objectContext, values);
} }
/** /**
......
...@@ -26,10 +26,8 @@ ...@@ -26,10 +26,8 @@
package jdk.jfr.consumer; package jdk.jfr.consumer;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.List;
import jdk.jfr.ValueDescriptor; import jdk.jfr.internal.consumer.ObjectContext;
import jdk.jfr.internal.Type;
/** /**
* A recorded method. * A recorded method.
...@@ -38,17 +36,9 @@ import jdk.jfr.internal.Type; ...@@ -38,17 +36,9 @@ import jdk.jfr.internal.Type;
*/ */
public final class RecordedMethod extends RecordedObject { public final class RecordedMethod extends RecordedObject {
static ObjectFactory<RecordedMethod> createFactory(Type type, TimeConverter timeConverter) { // package private
return new ObjectFactory<RecordedMethod>(type) { RecordedMethod(ObjectContext objectContext, Object[] values) {
@Override super(objectContext, values);
RecordedMethod createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedMethod(desc, object, timeConverter);
}
};
}
private RecordedMethod(List<ValueDescriptor> descriptors, Object[] objects, TimeConverter timeConverter) {
super(descriptors, objects, timeConverter);
} }
/** /**
......
...@@ -25,18 +25,24 @@ ...@@ -25,18 +25,24 @@
package jdk.jfr.consumer; package jdk.jfr.consumer;
import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import jdk.jfr.Timespan; import jdk.jfr.Timespan;
import jdk.jfr.Timestamp; import jdk.jfr.Timestamp;
import jdk.jfr.ValueDescriptor; import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.consumer.JdkJfrConsumer;
import jdk.jfr.internal.consumer.ObjectFactory;
import jdk.jfr.internal.PrivateAccess; import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.consumer.ObjectContext;
import jdk.jfr.internal.tool.PrettyWriter; import jdk.jfr.internal.tool.PrettyWriter;
/** /**
...@@ -51,6 +57,89 @@ import jdk.jfr.internal.tool.PrettyWriter; ...@@ -51,6 +57,89 @@ import jdk.jfr.internal.tool.PrettyWriter;
*/ */
public class RecordedObject { public class RecordedObject {
static{
JdkJfrConsumer access = new JdkJfrConsumer() {
public List<Type> readTypes(RecordingFile file) throws IOException {
return file.readTypes();
}
public boolean isLastEventInChunk(RecordingFile file) {
return file.isLastEventInChunk();
}
@Override
public Object getOffsetDataTime(RecordedObject event, String name) {
return event.getOffsetDateTime(name);
}
@Override
public RecordedClass newRecordedClass(ObjectContext objectContext, long id, Object[] values) {
return new RecordedClass(objectContext, id, values);
}
@Override
public RecordedClassLoader newRecordedClassLoader(ObjectContext objectContext, long id, Object[] values) {
return new RecordedClassLoader(objectContext, id, values);
}
@Override
public Comparator<? super RecordedEvent> eventComparator() {
return new Comparator<RecordedEvent>() {
@Override
public int compare(RecordedEvent e1, RecordedEvent e2) {
return Long.compare(e1.endTimeTicks, e2.endTimeTicks);
}
};
}
@Override
public RecordedStackTrace newRecordedStackTrace(ObjectContext objectContext, Object[] values) {
return new RecordedStackTrace(objectContext, values);
}
@Override
public RecordedThreadGroup newRecordedThreadGroup(ObjectContext objectContext, Object[] values) {
return new RecordedThreadGroup(objectContext, values);
}
@Override
public RecordedFrame newRecordedFrame(ObjectContext objectContext, Object[] values) {
return new RecordedFrame(objectContext, values);
}
@Override
public RecordedThread newRecordedThread(ObjectContext objectContext, long id, Object[] values) {
return new RecordedThread(objectContext, id, values);
}
@Override
public RecordedMethod newRecordedMethod(ObjectContext objectContext, Object[] values) {
return new RecordedMethod(objectContext, values);
}
@Override
public RecordedEvent newRecordedEvent(ObjectContext objectContext, Object[] values, long startTimeTicks, long endTimeTicks) {
return new RecordedEvent(objectContext, values, startTimeTicks, endTimeTicks);
}
@Override
public void setStartTicks(RecordedEvent event, long startTicks) {
event.startTimeTicks = startTicks;
}
@Override
public void setEndTicks(RecordedEvent event, long endTicks) {
event.endTimeTicks = endTicks;
}
@Override
public Object[] eventValues(RecordedEvent event) {
return event.objects;
}
};
JdkJfrConsumer.setAccess(access);
}
private final static class UnsignedValue { private final static class UnsignedValue {
private final Object o; private final Object o;
...@@ -63,15 +152,13 @@ public class RecordedObject { ...@@ -63,15 +152,13 @@ public class RecordedObject {
} }
} }
private final Object[] objects; final Object[] objects;
private final List<ValueDescriptor> descriptors; final ObjectContext objectContext;
private final TimeConverter timeConverter;
// package private, not to be subclassed outside this package // package private, not to be subclassed outside this package
RecordedObject(List<ValueDescriptor> descriptors, Object[] objects, TimeConverter timeConverter) { RecordedObject(ObjectContext objectContext, Object[] objects) {
this.descriptors = descriptors; this.objectContext = objectContext;
this.objects = objects; this.objects = objects;
this.timeConverter = timeConverter;
} }
// package private // package private
...@@ -101,7 +188,7 @@ public class RecordedObject { ...@@ -101,7 +188,7 @@ public class RecordedObject {
*/ */
public boolean hasField(String name) { public boolean hasField(String name) {
Objects.requireNonNull(name); Objects.requireNonNull(name);
for (ValueDescriptor v : descriptors) { for (ValueDescriptor v : objectContext.fields) {
if (v.getName().equals(name)) { if (v.getName().equals(name)) {
return true; return true;
} }
...@@ -109,7 +196,7 @@ public class RecordedObject { ...@@ -109,7 +196,7 @@ public class RecordedObject {
int dotIndex = name.indexOf("."); int dotIndex = name.indexOf(".");
if (dotIndex > 0) { if (dotIndex > 0) {
String structName = name.substring(0, dotIndex); String structName = name.substring(0, dotIndex);
for (ValueDescriptor v : descriptors) { for (ValueDescriptor v : objectContext.fields) {
if (!v.getFields().isEmpty() && v.getName().equals(structName)) { if (!v.getFields().isEmpty() && v.getName().equals(structName)) {
RecordedObject child = getValue(structName); RecordedObject child = getValue(structName);
if (child != null) { if (child != null) {
...@@ -169,12 +256,16 @@ public class RecordedObject { ...@@ -169,12 +256,16 @@ public class RecordedObject {
return t; return t;
} }
protected Object objectAt(int index) {
return objects[index];
}
private Object getValue(String name, boolean allowUnsigned) { private Object getValue(String name, boolean allowUnsigned) {
Objects.requireNonNull(name); Objects.requireNonNull(name);
int index = 0; int index = 0;
for (ValueDescriptor v : descriptors) { for (ValueDescriptor v : objectContext.fields) {
if (name.equals(v.getName())) { if (name.equals(v.getName())) {
Object object = objects[index]; Object object = objectAt(index);
if (object == null) { if (object == null) {
// error or missing // error or missing
return null; return null;
...@@ -200,7 +291,7 @@ public class RecordedObject { ...@@ -200,7 +291,7 @@ public class RecordedObject {
return structifyArray(v, array, 0); return structifyArray(v, array, 0);
} }
// struct // struct
return new RecordedObject(v.getFields(), (Object[]) object, timeConverter); return new RecordedObject(objectContext.getInstance(v), (Object[]) object);
} }
} }
index++; index++;
...@@ -209,7 +300,7 @@ public class RecordedObject { ...@@ -209,7 +300,7 @@ public class RecordedObject {
int dotIndex = name.indexOf("."); int dotIndex = name.indexOf(".");
if (dotIndex > 0) { if (dotIndex > 0) {
String structName = name.substring(0, dotIndex); String structName = name.substring(0, dotIndex);
for (ValueDescriptor v : descriptors) { for (ValueDescriptor v : objectContext.fields) {
if (!v.getFields().isEmpty() && v.getName().equals(structName)) { if (!v.getFields().isEmpty() && v.getName().equals(structName)) {
RecordedObject child = getValue(structName); RecordedObject child = getValue(structName);
String subName = name.substring(dotIndex + 1); String subName = name.substring(dotIndex + 1);
...@@ -261,7 +352,7 @@ public class RecordedObject { ...@@ -261,7 +352,7 @@ public class RecordedObject {
private <T> T getTypedValue(String name, String typeName) { private <T> T getTypedValue(String name, String typeName) {
Objects.requireNonNull(name); Objects.requireNonNull(name);
// Validate name and type first // Validate name and type first
getValueDescriptor(descriptors, name, typeName); getValueDescriptor(objectContext.fields, name, typeName);
return getValue(name); return getValue(name);
} }
...@@ -270,15 +361,16 @@ public class RecordedObject { ...@@ -270,15 +361,16 @@ public class RecordedObject {
return null; return null;
} }
Object[] structArray = new Object[array.length]; Object[] structArray = new Object[array.length];
ObjectContext objContext = objectContext.getInstance(v);
for (int i = 0; i < structArray.length; i++) { for (int i = 0; i < structArray.length; i++) {
Object arrayElement = array[i]; Object arrayElement = array[i];
if (dimension == 0) { if (dimension == 0) {
// No general way to handle structarrays // No general way to handle structarrays
// without invoking ObjectFactory for every instance (which may require id) // without invoking ObjectFactory for every instance (which may require id)
if (isStackFrameType(v.getTypeName())) { if (isStackFrameType(v.getTypeName())) {
structArray[i] = new RecordedFrame(v.getFields(), (Object[]) arrayElement, timeConverter); structArray[i] = new RecordedFrame(objContext, (Object[]) arrayElement);
} else { } else {
structArray[i] = new RecordedObject(v.getFields(), (Object[]) arrayElement, timeConverter); structArray[i] = new RecordedObject(objContext, (Object[]) arrayElement);
} }
} else { } else {
structArray[i] = structifyArray(v, (Object[]) arrayElement, dimension - 1); structArray[i] = structifyArray(v, (Object[]) arrayElement, dimension - 1);
...@@ -303,7 +395,7 @@ public class RecordedObject { ...@@ -303,7 +395,7 @@ public class RecordedObject {
* @return the fields, not {@code null} * @return the fields, not {@code null}
*/ */
public List<ValueDescriptor> getFields() { public List<ValueDescriptor> getFields() {
return descriptors; return objectContext.fields;
} }
/** /**
...@@ -725,7 +817,7 @@ public class RecordedObject { ...@@ -725,7 +817,7 @@ public class RecordedObject {
} }
private Duration getDuration(long timespan, String name) throws InternalError { private Duration getDuration(long timespan, String name) throws InternalError {
ValueDescriptor v = getValueDescriptor(descriptors, name, null); ValueDescriptor v = getValueDescriptor(objectContext.fields, name, null);
if (timespan == Long.MIN_VALUE) { if (timespan == Long.MIN_VALUE) {
return Duration.ofSeconds(Long.MIN_VALUE, 0); return Duration.ofSeconds(Long.MIN_VALUE, 0);
} }
...@@ -741,7 +833,7 @@ public class RecordedObject { ...@@ -741,7 +833,7 @@ public class RecordedObject {
case Timespan.NANOSECONDS: case Timespan.NANOSECONDS:
return Duration.ofNanos(timespan); return Duration.ofNanos(timespan);
case Timespan.TICKS: case Timespan.TICKS:
return Duration.ofNanos(timeConverter.convertTimespan(timespan)); return Duration.ofNanos(objectContext.convertTimespan(timespan));
} }
throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timespan unit " + ts.value()); throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timespan unit " + ts.value());
} }
...@@ -804,7 +896,7 @@ public class RecordedObject { ...@@ -804,7 +896,7 @@ public class RecordedObject {
} }
private Instant getInstant(long timestamp, String name) { private Instant getInstant(long timestamp, String name) {
ValueDescriptor v = getValueDescriptor(descriptors, name, null); ValueDescriptor v = getValueDescriptor(objectContext.fields, name, null);
Timestamp ts = v.getAnnotation(Timestamp.class); Timestamp ts = v.getAnnotation(Timestamp.class);
if (ts != null) { if (ts != null) {
if (timestamp == Long.MIN_VALUE) { if (timestamp == Long.MIN_VALUE) {
...@@ -814,7 +906,7 @@ public class RecordedObject { ...@@ -814,7 +906,7 @@ public class RecordedObject {
case Timestamp.MILLISECONDS_SINCE_EPOCH: case Timestamp.MILLISECONDS_SINCE_EPOCH:
return Instant.ofEpochMilli(timestamp); return Instant.ofEpochMilli(timestamp);
case Timestamp.TICKS: case Timestamp.TICKS:
return Instant.ofEpochSecond(0, timeConverter.convertTimestamp(timestamp)); return Instant.ofEpochSecond(0, objectContext.convertTimestamp(timestamp));
} }
throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timestamp unit " + ts.value()); throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timestamp unit " + ts.value());
} }
...@@ -889,12 +981,12 @@ public class RecordedObject { ...@@ -889,12 +981,12 @@ public class RecordedObject {
} }
// package private for now. Used by EventWriter // package private for now. Used by EventWriter
OffsetDateTime getOffsetDateTime(String name) { private OffsetDateTime getOffsetDateTime(String name) {
Instant instant = getInstant(name); Instant instant = getInstant(name);
if (instant.equals(Instant.MIN)) { if (instant.equals(Instant.MIN)) {
return OffsetDateTime.MIN; return OffsetDateTime.MIN;
} }
return OffsetDateTime.ofInstant(getInstant(name), timeConverter.getZoneOffset()); return OffsetDateTime.ofInstant(getInstant(name), objectContext.getZoneOffset());
} }
private static IllegalArgumentException newIllegalArgumentException(String name, String typeName) { private static IllegalArgumentException newIllegalArgumentException(String name, String typeName) {
......
...@@ -29,8 +29,7 @@ import java.util.Arrays; ...@@ -29,8 +29,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import jdk.jfr.ValueDescriptor; import jdk.jfr.internal.consumer.ObjectContext;
import jdk.jfr.internal.Type;
/** /**
* A recorded stack trace. * A recorded stack trace.
...@@ -38,18 +37,9 @@ import jdk.jfr.internal.Type; ...@@ -38,18 +37,9 @@ import jdk.jfr.internal.Type;
* @since 8 * @since 8
*/ */
public final class RecordedStackTrace extends RecordedObject { public final class RecordedStackTrace extends RecordedObject {
// package private
static ObjectFactory<RecordedStackTrace> createFactory(Type type, TimeConverter timeConverter) { RecordedStackTrace(ObjectContext objectContext, Object[] values) {
return new ObjectFactory<RecordedStackTrace>(type) { super(objectContext, values);
@Override
RecordedStackTrace createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedStackTrace(desc, object, timeConverter);
}
};
}
private RecordedStackTrace(List<ValueDescriptor> desc, Object[] values, TimeConverter timeConverter) {
super(desc, values, timeConverter);
} }
/** /**
......
...@@ -25,10 +25,7 @@ ...@@ -25,10 +25,7 @@
package jdk.jfr.consumer; package jdk.jfr.consumer;
import java.util.List; import jdk.jfr.internal.consumer.ObjectContext;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Type;
/** /**
* A recorded thread. * A recorded thread.
...@@ -36,20 +33,11 @@ import jdk.jfr.internal.Type; ...@@ -36,20 +33,11 @@ import jdk.jfr.internal.Type;
* @since 8 * @since 8
*/ */
public final class RecordedThread extends RecordedObject { public final class RecordedThread extends RecordedObject {
static ObjectFactory<RecordedThread> createFactory(Type type, TimeConverter timeConverter) {
return new ObjectFactory<RecordedThread>(type) {
@Override
RecordedThread createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedThread(desc, id, object, timeConverter);
}
};
}
private final long uniqueId; private final long uniqueId;
private RecordedThread(List<ValueDescriptor> descriptors, long id, Object[] values, TimeConverter timeConverter) { // package private
super(descriptors, values, timeConverter); RecordedThread(ObjectContext objectContext, long id, Object[] values) {
super(objectContext, values);
this.uniqueId = id; this.uniqueId = id;
} }
......
...@@ -25,10 +25,7 @@ ...@@ -25,10 +25,7 @@
package jdk.jfr.consumer; package jdk.jfr.consumer;
import java.util.List; import jdk.jfr.internal.consumer.ObjectContext;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Type;
/** /**
* A recorded Java thread group. * A recorded Java thread group.
...@@ -36,18 +33,9 @@ import jdk.jfr.internal.Type; ...@@ -36,18 +33,9 @@ import jdk.jfr.internal.Type;
* @since 8 * @since 8
*/ */
public final class RecordedThreadGroup extends RecordedObject { public final class RecordedThreadGroup extends RecordedObject {
// package private
static ObjectFactory<RecordedThreadGroup> createFactory(Type type, TimeConverter timeConverter) { RecordedThreadGroup(ObjectContext objectContext, Object[] values) {
return new ObjectFactory<RecordedThreadGroup>(type) { super(objectContext, values);
@Override
RecordedThreadGroup createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
return new RecordedThreadGroup(desc, object, timeConverter);
}
};
}
private RecordedThreadGroup(List<ValueDescriptor> descriptors, Object[] objects, TimeConverter timeConverter) {
super(descriptors, objects, timeConverter);
} }
/** /**
......
...@@ -32,7 +32,6 @@ import java.io.IOException; ...@@ -32,7 +32,6 @@ import java.io.IOException;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
...@@ -40,8 +39,9 @@ import jdk.jfr.EventType; ...@@ -40,8 +39,9 @@ import jdk.jfr.EventType;
import jdk.jfr.internal.MetadataDescriptor; import jdk.jfr.internal.MetadataDescriptor;
import jdk.jfr.internal.Type; import jdk.jfr.internal.Type;
import jdk.jfr.internal.consumer.ChunkHeader; import jdk.jfr.internal.consumer.ChunkHeader;
import jdk.jfr.internal.consumer.ChunkParser;
import jdk.jfr.internal.consumer.FileAccess;
import jdk.jfr.internal.consumer.RecordingInput; import jdk.jfr.internal.consumer.RecordingInput;
import jdk.jfr.internal.consumer.RecordingInternals;
/** /**
* A recording file. * A recording file.
...@@ -62,27 +62,6 @@ import jdk.jfr.internal.consumer.RecordingInternals; ...@@ -62,27 +62,6 @@ import jdk.jfr.internal.consumer.RecordingInternals;
* @since 8 * @since 8
*/ */
public final class RecordingFile implements Closeable { public final class RecordingFile implements Closeable {
static{
RecordingInternals.INSTANCE = new RecordingInternals() {
public List<Type> readTypes(RecordingFile file) throws IOException {
return file.readTypes();
}
public boolean isLastEventInChunk(RecordingFile file) {
return file.isLastEventInChunk;
}
@Override
public Object getOffsetDataTime(RecordedObject event, String name) {
return event.getOffsetDateTime(name);
}
@Override
public void sort(List<RecordedEvent> events) {
Collections.sort(events, (e1, e2) -> Long.compare(e1.endTime, e2.endTime));
}
};
}
private boolean isLastEventInChunk; private boolean isLastEventInChunk;
private final File file; private final File file;
...@@ -104,7 +83,7 @@ public final class RecordingFile implements Closeable { ...@@ -104,7 +83,7 @@ public final class RecordingFile implements Closeable {
*/ */
public RecordingFile(Path file) throws IOException { public RecordingFile(Path file) throws IOException {
this.file = file.toFile(); this.file = file.toFile();
this.input = new RecordingInput(this.file); this.input = new RecordingInput(this.file, FileAccess.UNPRIVILIGED);
findNext(); findNext();
} }
...@@ -154,14 +133,15 @@ public final class RecordingFile implements Closeable { ...@@ -154,14 +133,15 @@ public final class RecordingFile implements Closeable {
*/ */
public List<EventType> readEventTypes() throws IOException { public List<EventType> readEventTypes() throws IOException {
ensureOpen(); ensureOpen();
MetadataDescriptor previous = null;
List<EventType> types = new ArrayList<>(); List<EventType> types = new ArrayList<>();
HashSet<Long> foundIds = new HashSet<>(); HashSet<Long> foundIds = new HashSet<>();
try (RecordingInput ri = new RecordingInput(file)) { try (RecordingInput ri = new RecordingInput(file, FileAccess.UNPRIVILIGED)) {
ChunkHeader ch = new ChunkHeader(ri); ChunkHeader ch = new ChunkHeader(ri);
aggregateEventTypeForChunk(ch, types, foundIds); aggregateEventTypeForChunk(ch, null, types, foundIds);
while (!ch.isLastChunk()) { while (!ch.isLastChunk()) {
ch = ch.nextHeader(); ch = ch.nextHeader();
aggregateEventTypeForChunk(ch, types, foundIds); previous = aggregateEventTypeForChunk(ch, previous, types, foundIds);
} }
} }
return types; return types;
...@@ -169,37 +149,41 @@ public final class RecordingFile implements Closeable { ...@@ -169,37 +149,41 @@ public final class RecordingFile implements Closeable {
List<Type> readTypes() throws IOException { List<Type> readTypes() throws IOException {
ensureOpen(); ensureOpen();
MetadataDescriptor previous = null;
List<Type> types = new ArrayList<>(); List<Type> types = new ArrayList<>();
HashSet<Long> foundIds = new HashSet<>(); HashSet<Long> foundIds = new HashSet<>();
try (RecordingInput ri = new RecordingInput(file)) { try (RecordingInput ri = new RecordingInput(file, FileAccess.UNPRIVILIGED)) {
ChunkHeader ch = new ChunkHeader(ri); ChunkHeader ch = new ChunkHeader(ri);
aggregateTypeForChunk(ch, types, foundIds); ch.awaitFinished();
aggregateTypeForChunk(ch, null, types, foundIds);
while (!ch.isLastChunk()) { while (!ch.isLastChunk()) {
ch = ch.nextHeader(); ch = ch.nextHeader();
aggregateTypeForChunk(ch, types, foundIds); previous = aggregateTypeForChunk(ch, previous, types, foundIds);
} }
} }
return types; return types;
} }
private void aggregateTypeForChunk(ChunkHeader ch, List<Type> types, HashSet<Long> foundIds) throws IOException { private MetadataDescriptor aggregateTypeForChunk(ChunkHeader ch, MetadataDescriptor previous, List<Type> types, HashSet<Long> foundIds) throws IOException {
MetadataDescriptor m = ch.readMetadata(); MetadataDescriptor m = ch.readMetadata(previous);
for (Type t : m.getTypes()) { for (Type t : m.getTypes()) {
if (!foundIds.contains(t.getId())) { if (!foundIds.contains(t.getId())) {
types.add(t); types.add(t);
foundIds.add(t.getId()); foundIds.add(t.getId());
} }
} }
return m;
} }
private static void aggregateEventTypeForChunk(ChunkHeader ch, List<EventType> types, HashSet<Long> foundIds) throws IOException { private static MetadataDescriptor aggregateEventTypeForChunk(ChunkHeader ch, MetadataDescriptor previous, List<EventType> types, HashSet<Long> foundIds) throws IOException {
MetadataDescriptor m = ch.readMetadata(); MetadataDescriptor m = ch.readMetadata(previous);
for (EventType t : m.getEventTypes()) { for (EventType t : m.getEventTypes()) {
if (!foundIds.contains(t.getId())) { if (!foundIds.contains(t.getId())) {
types.add(t); types.add(t);
foundIds.add(t.getId()); foundIds.add(t.getId());
} }
} }
return m;
} }
/** /**
...@@ -246,6 +230,17 @@ public final class RecordingFile implements Closeable { ...@@ -246,6 +230,17 @@ public final class RecordingFile implements Closeable {
} }
} }
// package protected
File getFile() {
return file;
}
// package protected
boolean isLastEventInChunk() {
return isLastEventInChunk;
}
// either sets next to an event or sets eof to true // either sets next to an event or sets eof to true
private void findNext() throws IOException { private void findNext() throws IOException {
while (nextEvent == null) { while (nextEvent == null) {
......
/*
* Copyright (c) 2019, 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.consumer;
import java.io.IOException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.function.Consumer;
import jdk.jfr.Configuration;
import jdk.jfr.Event;
import jdk.jfr.EventSettings;
import jdk.jfr.EventType;
import jdk.jfr.Recording;
import jdk.jfr.internal.PlatformRecording;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.SecuritySupport;
import jdk.jfr.internal.Utils;
import jdk.jfr.internal.consumer.EventDirectoryStream;
/**
* A recording stream produces events from the current JVM (Java Virtual
* Machine).
* <p>
* The following example shows how to record events using the default
* configuration and print the Garbage Collection, CPU Load and JVM Information
* event to standard out.
* <pre>
* <code>
* Configuration c = Configuration.getConfiguration("default");
* try (var rs = new RecordingStream(c)) {
* rs.onEvent("jdk.GarbageCollection", System.out::println);
* rs.onEvent("jdk.CPULoad", System.out::println);
* rs.onEvent("jdk.JVMInformation", System.out::println);
* rs.start();
* }
* }
* </code>
* </pre>
*
* @since 14
*/
public final class RecordingStream implements AutoCloseable, EventStream {
private final Recording recording;
private final EventDirectoryStream directoryStream;
/**
* Creates an event stream for the current JVM (Java Virtual Machine).
*
* @throws IllegalStateException if Flight Recorder can't be created (for
* example, if the Java Virtual Machine (JVM) lacks Flight Recorder
* support, or if the file repository can't be created or accessed)
*
* @throws SecurityException if a security manager exists and the caller
* does not have
* {@code FlightRecorderPermission("accessFlightRecorder")}
*/
public RecordingStream() {
Utils.checkAccessFlightRecorder();
AccessControlContext acc = AccessController.getContext();
this.recording = new Recording();
this.recording.setFlushInterval(Duration.ofMillis(1000));
try {
this.directoryStream = new EventDirectoryStream(acc, null, SecuritySupport.PRIVILIGED, true);
} catch (IOException ioe) {
this.recording.close();
throw new IllegalStateException(ioe.getMessage());
}
}
/**
* Creates a recording stream using settings from a configuration.
* <p>
* The following example shows how to create a recording stream that uses a
* predefined configuration.
*
* <pre>
* <code>
* var c = Configuration.getConfiguration("default");
* try (var rs = new RecordingStream(c)) {
* rs.onEvent(System.out::println);
* rs.start();
* }
* </code>
* </pre>
*
* @param configuration configuration that contains the settings to use,
* not {@code null}
*
* @throws IllegalStateException if Flight Recorder can't be created (for
* example, if the Java Virtual Machine (JVM) lacks Flight Recorder
* support, or if the file repository can't be created or accessed)
*
* @throws SecurityException if a security manager is used and
* FlightRecorderPermission "accessFlightRecorder" is not set.
*
* @see Configuration
*/
public RecordingStream(Configuration configuration) {
this();
recording.setSettings(configuration.getSettings());
}
/**
* Enables the event with the specified name.
* <p>
* If multiple events have the same name (for example, the same class is
* loaded in different class loaders), then all events that match the name
* are enabled. To enable a specific class, use the {@link #enable(Class)}
* method or a {@code String} representation of the event type ID.
*
* @param name the settings for the event, not {@code null}
*
* @return an event setting for further configuration, not {@code null}
*
* @see EventType
*/
public EventSettings enable(String name) {
return recording.enable(name);
}
/**
* Replaces all settings for this recording stream.
* <p>
* The following example records 20 seconds using the "default" configuration
* and then changes settings to the "profile" configuration.
*
* <pre>
* <code>
* Configuration defaultConfiguration = Configuration.getConfiguration("default");
* Configuration profileConfiguration = Configuration.getConfiguration("profile");
* try (var rs = new RecordingStream(defaultConfiguration) {
* rs.onEvent(System.out::println);
* rs.startAsync();
* Thread.sleep(20_000);
* rs.setSettings(profileConfiguration.getSettings());
* Thread.sleep(20_000);
* }
* </code>
* </pre>
*
* @param settings the settings to set, not {@code null}
*
* @see Recording#setSettings(Map)
*/
public void setSettings(Map<String, String> settings) {
recording.setSettings(settings);
};
/**
* Enables event.
*
* @param eventClass the event to enable, not {@code null}
*
* @throws IllegalArgumentException if {@code eventClass} is an abstract
* class or not a subclass of {@link Event}
*
* @return an event setting for further configuration, not {@code null}
*/
public EventSettings enable(Class<? extends Event> eventClass) {
return recording.enable(eventClass);
}
/**
* Disables event with the specified name.
* <p>
* If multiple events with same name (for example, the same class is loaded
* in different class loaders), then all events that match the name are
* disabled. To disable a specific class, use the {@link #disable(Class)}
* method or a {@code String} representation of the event type ID.
*
* @param name the settings for the event, not {@code null}
*
* @return an event setting for further configuration, not {@code null}
*
*/
public EventSettings disable(String name) {
return recording.disable(name);
}
/**
* Disables event.
*
* @param eventClass the event to enable, not {@code null}
*
* @throws IllegalArgumentException if {@code eventClass} is an abstract
* class or not a subclass of {@link Event}
*
* @return an event setting for further configuration, not {@code null}
*
*/
public EventSettings disable(Class<? extends Event> eventClass) {
return recording.disable(eventClass);
}
/**
* Determines how far back data is kept for the stream.
* <p>
* To control the amount of recording data stored on disk, the maximum
* length of time to retain the data can be specified. Data stored on disk
* that is older than the specified length of time is removed by the Java
* Virtual Machine (JVM).
* <p>
* If neither maximum limit or the maximum age is set, the size of the
* recording may grow indefinitely if events are on
*
* @param maxAge the length of time that data is kept, or {@code null} if
* infinite
*
* @throws IllegalArgumentException if {@code maxAge} is negative
*
* @throws IllegalStateException if the recording is in the {@code CLOSED}
* state
*/
public void setMaxAge(Duration maxAge) {
recording.setMaxAge(maxAge);
}
/**
* Determines how much data is kept for the stream.
* <p>
* To control the amount of recording data that is stored on disk, the
* maximum amount of data to retain can be specified. When the maximum limit
* is exceeded, the Java Virtual Machine (JVM) removes the oldest chunk to
* make room for a more recent chunk.
* <p>
* If neither maximum limit or the maximum age is set, the size of the
* recording may grow indefinitely.
* <p>
* The size is measured in bytes.
*
* @param maxSize the amount of data to retain, {@code 0} if infinite
*
* @throws IllegalArgumentException if {@code maxSize} is negative
*
* @throws IllegalStateException if the recording is in {@code CLOSED} state
*/
public void setMaxSize(long maxSize) {
recording.setMaxSize(maxSize);
}
/**
* Determines how often events are made available for streaming.
*
* @param interval the interval at which events are made available to the
* stream, no {@code null}
*
* @throws IllegalArgumentException if {@code interval} is negative
*
* @throws IllegalStateException if the stream is closed
*/
public void setFlushInterval(Duration interval) {
recording.setFlushInterval(interval);
}
@Override
public void setReuse(boolean reuse) {
directoryStream.setReuse(reuse);
}
@Override
public void setOrdered(boolean ordered) {
directoryStream.setOrdered(ordered);
}
@Override
public void setStartTime(Instant startTime) {
directoryStream.setStartTime(startTime);
}
@Override
public void setEndTime(Instant endTime) {
directoryStream.setEndTime(endTime);
}
@Override
public void onEvent(String eventName, Consumer<RecordedEvent> action) {
directoryStream.onEvent(eventName, action);
}
@Override
public void onEvent(Consumer<RecordedEvent> action) {
directoryStream.onEvent(action);
}
@Override
public void onFlush(Runnable action) {
directoryStream.onFlush(action);
}
@Override
public void onClose(Runnable action) {
directoryStream.onClose(action);
}
@Override
public void onError(Consumer<Throwable> action) {
directoryStream.onError(action);
}
@Override
public void close() {
recording.close();
directoryStream.close();
}
@Override
public boolean remove(Object action) {
return directoryStream.remove(action);
}
@Override
public void start() {
PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
long startNanos = pr.start();
directoryStream.start(startNanos);
}
@Override
public void startAsync() {
PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
long startNanos = pr.start();
directoryStream.startAsync(startNanos);
}
@Override
public void awaitTermination(Duration timeout) throws InterruptedException {
directoryStream.awaitTermination(timeout);
}
@Override
public void awaitTermination() throws InterruptedException {
directoryStream.awaitTermination();
}
}
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -40,6 +40,13 @@ import jdk.jfr.internal.Type; ...@@ -40,6 +40,13 @@ import jdk.jfr.internal.Type;
@StackTrace(false) @StackTrace(false)
public final class ActiveRecordingEvent extends AbstractJDKEvent { public final class ActiveRecordingEvent extends AbstractJDKEvent {
public static final ThreadLocal<ActiveRecordingEvent> EVENT = new ThreadLocal<ActiveRecordingEvent>() {
@Override
protected ActiveRecordingEvent initialValue() {
return new ActiveRecordingEvent();
}
};
@Label("Id") @Label("Id")
public long id; public long id;
...@@ -53,6 +60,10 @@ public final class ActiveRecordingEvent extends AbstractJDKEvent { ...@@ -53,6 +60,10 @@ public final class ActiveRecordingEvent extends AbstractJDKEvent {
@Timespan(Timespan.MILLISECONDS) @Timespan(Timespan.MILLISECONDS)
public long maxAge; public long maxAge;
@Label("Flush Interval")
@Timespan(Timespan.MILLISECONDS)
public long flushInterval;
@Label("Max Size") @Label("Max Size")
@DataAmount @DataAmount
public long maxSize; public long maxSize;
......
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -37,6 +37,13 @@ import jdk.jfr.internal.Type; ...@@ -37,6 +37,13 @@ import jdk.jfr.internal.Type;
@StackTrace(false) @StackTrace(false)
public final class ActiveSettingEvent extends AbstractJDKEvent { public final class ActiveSettingEvent extends AbstractJDKEvent {
public static final ThreadLocal<ActiveSettingEvent> EVENT = new ThreadLocal<ActiveSettingEvent>() {
@Override
protected ActiveSettingEvent initialValue() {
return new ActiveSettingEvent();
}
};
@Label("Event Id") @Label("Event Id")
public long id; public long id;
......
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -32,11 +32,7 @@ import java.lang.reflect.Method; ...@@ -32,11 +32,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import jdk.jfr.AnnotationElement; import jdk.jfr.AnnotationElement;
import jdk.jfr.Enabled; import jdk.jfr.Enabled;
...@@ -59,7 +55,14 @@ import jdk.jfr.internal.settings.ThresholdSetting; ...@@ -59,7 +55,14 @@ import jdk.jfr.internal.settings.ThresholdSetting;
// holds SettingControl instances that need to be released // holds SettingControl instances that need to be released
// when a class is unloaded (to avoid memory leaks). // when a class is unloaded (to avoid memory leaks).
public final class EventControl { public final class EventControl {
final static class NamedControl {
public final String name;
public final Control control;
NamedControl(String name, Control control) {
this.name = name;
this.control = control;
}
}
static final String FIELD_SETTING_PREFIX = "setting"; static final String FIELD_SETTING_PREFIX = "setting";
private static final Type TYPE_ENABLED = TypeLibrary.createType(EnabledSetting.class); private static final Type TYPE_ENABLED = TypeLibrary.createType(EnabledSetting.class);
private static final Type TYPE_THRESHOLD = TypeLibrary.createType(ThresholdSetting.class); private static final Type TYPE_THRESHOLD = TypeLibrary.createType(ThresholdSetting.class);
...@@ -67,24 +70,24 @@ public final class EventControl { ...@@ -67,24 +70,24 @@ public final class EventControl {
private static final Type TYPE_PERIOD = TypeLibrary.createType(PeriodSetting.class); private static final Type TYPE_PERIOD = TypeLibrary.createType(PeriodSetting.class);
private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class); private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class);
private final List<SettingInfo> settingInfos = new ArrayList<>(); private final ArrayList<SettingInfo> settingInfos = new ArrayList<>();
private final Map<String, Control> eventControls = new HashMap<>(5); private final ArrayList<NamedControl> namedControls = new ArrayList<>(5);
private final PlatformEventType type; private final PlatformEventType type;
private final String idName; private final String idName;
EventControl(PlatformEventType eventType) { EventControl(PlatformEventType eventType) {
eventControls.put(Enabled.NAME, defineEnabled(eventType)); addControl(Enabled.NAME, defineEnabled(eventType));
if (eventType.hasDuration()) { if (eventType.hasDuration()) {
eventControls.put(Threshold.NAME, defineThreshold(eventType)); addControl(Threshold.NAME, defineThreshold(eventType));
} }
if (eventType.hasStackTrace()) { if (eventType.hasStackTrace()) {
eventControls.put(StackTrace.NAME, defineStackTrace(eventType)); addControl(StackTrace.NAME, defineStackTrace(eventType));
} }
if (eventType.hasPeriod()) { if (eventType.hasPeriod()) {
eventControls.put(Period.NAME, definePeriod(eventType)); addControl(Period.NAME, definePeriod(eventType));
} }
if (eventType.hasCutoff()) { if (eventType.hasCutoff()) {
eventControls.put(Cutoff.NAME, defineCutoff(eventType)); addControl(Cutoff.NAME, defineCutoff(eventType));
} }
ArrayList<AnnotationElement> aes = new ArrayList<>(eventType.getAnnotationElements()); ArrayList<AnnotationElement> aes = new ArrayList<>(eventType.getAnnotationElements());
...@@ -99,6 +102,19 @@ public final class EventControl { ...@@ -99,6 +102,19 @@ public final class EventControl {
this.idName = String.valueOf(eventType.getId()); this.idName = String.valueOf(eventType.getId());
} }
private boolean hasControl(String name) {
for (NamedControl nc : namedControls) {
if (name.equals(nc.name)) {
return true;
}
}
return false;
}
private void addControl(String name, Control control) {
namedControls.add(new NamedControl(name, control));
}
static void remove(PlatformEventType type, List<AnnotationElement> aes, Class<? extends java.lang.annotation.Annotation> clazz) { static void remove(PlatformEventType type, List<AnnotationElement> aes, Class<? extends java.lang.annotation.Annotation> clazz) {
long id = Type.getTypeId(clazz); long id = Type.getTypeId(clazz);
for (AnnotationElement a : type.getAnnotationElements()) { for (AnnotationElement a : type.getAnnotationElements()) {
...@@ -131,7 +147,8 @@ public final class EventControl { ...@@ -131,7 +147,8 @@ public final class EventControl {
if (n != null) { if (n != null) {
name = n.value(); name = n.value();
} }
if (!eventControls.containsKey(name)) {
if (!hasControl(name)) {
defineSetting((Class<? extends SettingControl>) settingClass, m, type, name); defineSetting((Class<? extends SettingControl>) settingClass, m, type, name);
} }
} }
...@@ -161,7 +178,7 @@ public final class EventControl { ...@@ -161,7 +178,7 @@ public final class EventControl {
} }
} }
aes.trimToSize(); aes.trimToSize();
eventControls.put(settingName, si.settingControl); addControl(settingName, si.settingControl);
eventType.add(PrivateAccess.getInstance().newSettingDescriptor(settingType, settingName, defaultValue, aes)); eventType.add(PrivateAccess.getInstance().newSettingDescriptor(settingType, settingName, defaultValue, aes));
settingInfos.add(si); settingInfos.add(si);
} }
...@@ -245,9 +262,9 @@ public final class EventControl { ...@@ -245,9 +262,9 @@ public final class EventControl {
} }
void disable() { void disable() {
for (Control c : eventControls.values()) { for (NamedControl nc : namedControls) {
if (c instanceof EnabledSetting) { if (nc.control instanceof EnabledSetting) {
c.setValueSafe("false"); nc.control.setValueSafe("false");
return; return;
} }
} }
...@@ -257,24 +274,23 @@ public final class EventControl { ...@@ -257,24 +274,23 @@ public final class EventControl {
if (!type.isRegistered()) { if (!type.isRegistered()) {
return; return;
} }
for (Map.Entry<String, Control> entry : eventControls.entrySet()) { ActiveSettingEvent event = ActiveSettingEvent.EVENT.get();
Control c = entry.getValue(); for (NamedControl nc : namedControls) {
if (Utils.isSettingVisible(c, type.hasEventHook())) { if (Utils.isSettingVisible(nc.control, type.hasEventHook())) {
String value = c.getLastValue(); String value = nc.control.getLastValue();
if (value == null) { if (value == null) {
value = c.getDefaultValue(); value = nc.control.getDefaultValue();
} }
ActiveSettingEvent ase = new ActiveSettingEvent(); event.id = type.getId();
ase.id = type.getId(); event.name = nc.name;
ase.name = entry.getKey(); event.value = value;
ase.value = value; event.commit();
ase.commit();
} }
} }
} }
public Set<Entry<String, Control>> getEntries() { public ArrayList<NamedControl> getNamedControls() {
return eventControls.entrySet(); return namedControls;
} }
public PlatformEventType getEventType() { public PlatformEventType getEventType() {
......
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -355,74 +355,72 @@ public final class EventInstrumentation { ...@@ -355,74 +355,72 @@ public final class EventInstrumentation {
methodVisitor.visitMaxs(0, 0); methodVisitor.visitMaxs(0, 0);
}); });
// MyEvent#commit() - Java event writer
updateMethod(METHOD_COMMIT, methodVisitor -> { updateMethod(METHOD_COMMIT, methodVisitor -> {
// if (!isEnable()) { // if (!isEnable()) {
// return; // return;
// } // }
methodVisitor.visitCode(); methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_IS_ENABLED.getName(), METHOD_IS_ENABLED.getDescriptor(), false); methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_IS_ENABLED.getName(), METHOD_IS_ENABLED.getDescriptor(), false);
Label l0 = new Label(); Label l0 = new Label();
methodVisitor.visitJumpInsn(Opcodes.IFNE, l0); methodVisitor.visitJumpInsn(Opcodes.IFNE, l0);
methodVisitor.visitInsn(Opcodes.RETURN); methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitLabel(l0); methodVisitor.visitLabel(l0);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// if (startTime == 0) { // if (startTime == 0) {
// startTime = EventWriter.timestamp(); // startTime = EventWriter.timestamp();
// } else { // } else {
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J"); methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J");
methodVisitor.visitInsn(Opcodes.LCONST_0); methodVisitor.visitInsn(Opcodes.LCONST_0);
methodVisitor.visitInsn(Opcodes.LCMP); methodVisitor.visitInsn(Opcodes.LCMP);
Label durationalEvent = new Label(); Label durationalEvent = new Label();
methodVisitor.visitJumpInsn(Opcodes.IFNE, durationalEvent); methodVisitor.visitJumpInsn(Opcodes.IFNE, durationalEvent);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false);
METHOD_TIME_STAMP.getDescriptor(), false); methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_START_TIME, "J");
methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_START_TIME, "J"); Label commit = new Label();
Label commit = new Label(); methodVisitor.visitJumpInsn(Opcodes.GOTO, commit);
methodVisitor.visitJumpInsn(Opcodes.GOTO, commit); // if (duration == 0) {
// if (duration == 0) { // duration = EventWriter.timestamp() - startTime;
// duration = EventWriter.timestamp() - startTime; // }
// } // }
// } methodVisitor.visitLabel(durationalEvent);
methodVisitor.visitLabel(durationalEvent); methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J");
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J"); methodVisitor.visitInsn(Opcodes.LCONST_0);
methodVisitor.visitInsn(Opcodes.LCONST_0); methodVisitor.visitInsn(Opcodes.LCMP);
methodVisitor.visitInsn(Opcodes.LCMP); methodVisitor.visitJumpInsn(Opcodes.IFNE, commit);
methodVisitor.visitJumpInsn(Opcodes.IFNE, commit); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J");
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J"); methodVisitor.visitInsn(Opcodes.LSUB);
methodVisitor.visitInsn(Opcodes.LSUB); methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_DURATION, "J");
methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_DURATION, "J"); methodVisitor.visitLabel(commit);
methodVisitor.visitLabel(commit); // if (shouldCommit()) {
// if (shouldCommit()) { methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_EVENT_SHOULD_COMMIT.getName(), METHOD_EVENT_SHOULD_COMMIT.getDescriptor(), false);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_EVENT_SHOULD_COMMIT.getName(), METHOD_EVENT_SHOULD_COMMIT.getDescriptor(), false); Label end = new Label();
Label end = new Label(); // eventHandler.write(...);
// eventHandler.write(...); // }
// } methodVisitor.visitJumpInsn(Opcodes.IFEQ, end);
methodVisitor.visitJumpInsn(Opcodes.IFEQ, end); methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(eventHandlerProxy));
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(eventHandlerProxy));
methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, eventHandlerXInternalName); methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, eventHandlerXInternalName);
for (FieldInfo fi : fieldInfos) { for (FieldInfo fi : fieldInfos) {
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, fi.internalClassName, fi.fieldName, fi.fieldDescriptor); methodVisitor.visitFieldInsn(Opcodes.GETFIELD, fi.internalClassName, fi.fieldName, fi.fieldDescriptor);
} }
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, eventHandlerXInternalName, writeMethod.getName(), writeMethod.getDescriptor(), false); methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, eventHandlerXInternalName, writeMethod.getName(), writeMethod.getDescriptor(), false);
methodVisitor.visitLabel(end); methodVisitor.visitLabel(end);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitInsn(Opcodes.RETURN); methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitEnd(); methodVisitor.visitEnd();
}); });
// MyEvent#shouldCommit() // MyEvent#shouldCommit()
updateMethod(METHOD_EVENT_SHOULD_COMMIT, methodVisitor -> { updateMethod(METHOD_EVENT_SHOULD_COMMIT, methodVisitor -> {
......
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
package jdk.jfr.internal; package jdk.jfr.internal;
import sun.misc.Unsafe; import sun.misc.Unsafe;
import jdk.jfr.internal.consumer.RecordingInput; import jdk.jfr.internal.consumer.StringParser;
/** /**
* Class must reside in a package with package restriction. * Class must reside in a package with package restriction.
...@@ -115,18 +115,18 @@ public final class EventWriter { ...@@ -115,18 +115,18 @@ public final class EventWriter {
public void putString(String s, StringPool pool) { public void putString(String s, StringPool pool) {
if (s == null) { if (s == null) {
putByte(RecordingInput.STRING_ENCODING_NULL); putByte(StringParser.Encoding.NULL.byteValue());
return; return;
} }
int length = s.length(); int length = s.length();
if (length == 0) { if (length == 0) {
putByte(RecordingInput.STRING_ENCODING_EMPTY_STRING); putByte(StringParser.Encoding.EMPTY_STRING.byteValue());
return; return;
} }
if (length > StringPool.MIN_LIMIT && length < StringPool.MAX_LIMIT) { if (length > StringPool.MIN_LIMIT && length < StringPool.MAX_LIMIT) {
long l = StringPool.addString(s); long l = StringPool.addString(s);
if (l > 0) { if (l > 0) {
putByte(RecordingInput.STRING_ENCODING_CONSTANT_POOL); putByte(StringParser.Encoding.CONSTANT_POOL.byteValue());
putLong(l); putLong(l);
return; return;
} }
...@@ -138,7 +138,7 @@ public final class EventWriter { ...@@ -138,7 +138,7 @@ public final class EventWriter {
private void putStringValue(String s) { private void putStringValue(String s) {
int length = s.length(); int length = s.length();
if (isValidForSize(1 + 5 + 3 * length)) { if (isValidForSize(1 + 5 + 3 * length)) {
putUncheckedByte(RecordingInput.STRING_ENCODING_CHAR_ARRAY); // 1 byte putUncheckedByte(StringParser.Encoding.CHAR_ARRAY.byteValue()); // 1 byte
putUncheckedInt(length); // max 5 bytes putUncheckedInt(length); // max 5 bytes
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
putUncheckedChar(s.charAt(i)); // max 3 bytes putUncheckedChar(s.charAt(i)); // max 3 bytes
...@@ -197,11 +197,7 @@ public final class EventWriter { ...@@ -197,11 +197,7 @@ public final class EventWriter {
if (currentPosition + requestedSize > maxPosition) { if (currentPosition + requestedSize > maxPosition) {
flushOnEnd = flush(usedSize(), requestedSize); flushOnEnd = flush(usedSize(), requestedSize);
// retry // retry
if (currentPosition + requestedSize > maxPosition) { if (!valid) {
Logger.log(LogTag.JFR_SYSTEM,
LogLevel.WARN, () ->
"Unable to commit. Requested size " + requestedSize + " too large");
valid = false;
return false; return false;
} }
} }
......
/*
* Copyright (c) 2019, 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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Set;
import jdk.jfr.internal.SecuritySupport.SafePath;
// This class keeps track of files that can't be deleted
// so they can a later staged be removed.
final class FilePurger {
private final static Set<SafePath> paths = new LinkedHashSet<>();
public synchronized static void add(SafePath p) {
paths.add(p);
if (paths.size() > 1000) {
removeOldest();
}
}
public synchronized static void purge() {
if (paths.isEmpty()) {
return;
}
for (SafePath p : new ArrayList<>(paths)) {
if (delete(p)) {
paths.remove(p);
}
}
}
private static void removeOldest() {
SafePath oldest = paths.iterator().next();
paths.remove(oldest);
}
private static boolean delete(SafePath p) {
try {
SecuritySupport.delete(p);
return true;
} catch (IOException e) {
return false;
}
}
}
/* /*
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -54,7 +54,7 @@ public final class JVM { ...@@ -54,7 +54,7 @@ public final class JVM {
// subscribeLogLevel(tag, tag.id); // subscribeLogLevel(tag, tag.id);
// } // }
Options.ensureInitialized(); Options.ensureInitialized();
EventHandlerProxyCreator.ensureInitialized(); // EventHandlerProxyCreator.ensureInitialized();
} }
/** /**
...@@ -271,7 +271,6 @@ public final class JVM { ...@@ -271,7 +271,6 @@ public final class JVM {
* *
* @param file the file where data should be written, or null if it should * @param file the file where data should be written, or null if it should
* not be copied out (in memory). * not be copied out (in memory).
*
* @throws IOException * @throws IOException
*/ */
public native void setOutput(String file); public native void setOutput(String file);
...@@ -474,6 +473,16 @@ public final class JVM { ...@@ -474,6 +473,16 @@ public final class JVM {
*/ */
public static native boolean flush(EventWriter writer, int uncommittedSize, int requestedSize); public static native boolean flush(EventWriter writer, int uncommittedSize, int requestedSize);
/**
* Flushes all thread buffers to disk and the constant pool data needed to read
* them.
* <p>
* When the method returns, the chunk header should be updated with valid
* pointers to the metadata event, last check point event, correct file size and
* the generation id.
*
*/
public native void flush();
/** /**
* Sets the location of the disk repository, to be used at an emergency * Sets the location of the disk repository, to be used at an emergency
* dump. * dump.
...@@ -538,4 +547,30 @@ public final class JVM { ...@@ -538,4 +547,30 @@ public final class JVM {
* @return if it is time to perform a chunk rotation * @return if it is time to perform a chunk rotation
*/ */
public native boolean shouldRotateDisk(); public native boolean shouldRotateDisk();
/**
* Exclude a thread from the jfr system
*
*/
public native void exclude(Thread thread);
/**
* Include a thread back into the jfr system
*
*/
public native void include(Thread thread);
/**
* Test if a thread ius currently excluded from the jfr system.
*
* @return is thread currently excluded
*/
public native boolean isExcluded(Thread thread);
/**
* Get the start time in nanos from the header of the current chunk
*
*@return start time of the recording in nanos, -1 in case of in-memory
*/
public native long getChunkStartNanos();
} }
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -62,22 +62,26 @@ public enum LogTag { ...@@ -62,22 +62,26 @@ public enum LogTag {
* Covers metadata for JVM/JDK (for Hotspot developers) * Covers metadata for JVM/JDK (for Hotspot developers)
*/ */
JFR_SYSTEM_METADATA(6), JFR_SYSTEM_METADATA(6),
/**
* Covers streaming (for Hotspot developers)
*/
JFR_SYSTEM_STREAMING(7),
/** /**
* Covers metadata for Java user (for Hotspot developers) * Covers metadata for Java user (for Hotspot developers)
*/ */
JFR_METADATA(7), JFR_METADATA(8),
/** /**
* Covers events (for users of the JDK) * Covers events (for users of the JDK)
*/ */
JFR_EVENT(8), JFR_EVENT(9),
/** /**
* Covers setting (for users of the JDK) * Covers setting (for users of the JDK)
*/ */
JFR_SETTING(9), JFR_SETTING(10),
/** /**
* Covers usage of jcmd with JFR * Covers usage of jcmd with JFR
*/ */
JFR_DCMD(10); JFR_DCMD(11);
/* set from native side */ /* set from native side */
volatile int tagSetLevel = 100; // prevent logging if JVM log system has not been initialized volatile int tagSetLevel = 100; // prevent logging if JVM log system has not been initialized
......
/*
* Copyright (c) 2019, 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;
import java.util.BitSet;
import java.util.function.Consumer;
import java.util.function.LongConsumer;
@SuppressWarnings("unchecked")
public final class LongMap<T> {
private static final int MAXIMUM_CAPACITY = 1 << 30;
private static final long[] EMPTY_KEYS = new long[0];
private static final Object[] EMPTY_OBJECTS = new Object[0];
private static final int DEFAULT_SIZE = 32;
private static final Object NULL_OBJECT = new Object();
private final int bitCount;
private BitSet bitSet;
private long[] keys = EMPTY_KEYS;
private T[] objects = (T[]) EMPTY_OBJECTS;
private int count;
private int shift;
public LongMap() {
this.bitCount = 0;
}
public LongMap(int markBits) {
this.bitCount = markBits;
this.bitSet = new BitSet();
}
// Should be 2^n
private void initialize(int capacity) {
keys = new long[capacity];
objects = (T[]) new Object[capacity];
shift = 64 - (31 - Integer.numberOfLeadingZeros(capacity));
}
public void claimBits() {
// flip last bit back and forth to make bitset expand to max size
int lastBit = bitSetIndex(objects.length - 1, bitCount -1);
bitSet.flip(lastBit);
bitSet.flip(lastBit);
}
public void setId(long id, int bitIndex) {
int bitSetIndex = bitSetIndex(tableIndexOf(id), bitIndex);
bitSet.set(bitSetIndex, true);
}
public void clearId(long id, int bitIndex) {
int bitSetIndex = bitSetIndex(tableIndexOf(id), bitIndex);
bitSet.set(bitSetIndex, false);
}
public boolean isSetId(long id, int bitIndex) {
int bitSetIndex = bitSetIndex(tableIndexOf(id), bitIndex);
return bitSet.get(bitSetIndex);
}
private int bitSetIndex(int tableIndex, int bitIndex) {
return bitCount * tableIndex + bitIndex;
}
private int tableIndexOf(long id) {
int index = index(id);
while (true) {
if (objects[index] == null) {
throw new InternalError("Unknown id");
}
if (keys[index] == id) {
return index;
}
index++;
if (index == keys.length) {
index = 0;
}
}
}
public boolean hasKey(long id) {
int index = index(id);
while (true) {
if (objects[index] == null) {
return false;
}
if (keys[index] == id) {
return true;
}
index++;
if (index == keys.length) {
index = 0;
}
}
}
public void expand(int size) {
int l = 4 * size / 3;
if (l <= keys.length) {
return;
}
int n = tableSizeFor(l);
LongMap<T> temp = new LongMap<>(bitCount);
temp.initialize(n);
// Optimization, avoid growing while copying bits
if (bitCount > 0 && !bitSet.isEmpty()) {
temp.claimBits();
claimBits();
}
for (int tIndex = 0; tIndex < keys.length; tIndex++) {
T o = objects[tIndex];
if (o != null) {
long key = keys[tIndex];
temp.put(key, o);
if (bitCount != 0) {
for (int bIndex = 0; bIndex < bitCount; bIndex++) {
boolean bitValue = isSetId(key, bIndex);
if (bitValue) {
temp.setId(key, bIndex);
}
}
}
}
}
keys = temp.keys;
objects = temp.objects;
shift = temp.shift;
bitSet = temp.bitSet;
}
public void put(long id, T object) {
if (keys == EMPTY_KEYS) {
// Lazy initialization
initialize(DEFAULT_SIZE);
}
if (object == null) {
object = (T) NULL_OBJECT;
}
int index = index(id);
// probe for empty slot
while (true) {
if (objects[index] == null) {
keys[index] = id;
objects[index] = object;
count++;
// Don't expand lazy since it
// can cause resize when replacing
// an object.
if (count > 3 * keys.length / 4) {
expand(2 * keys.length);
}
return;
}
// if it already exists, replace
if (keys[index] == id) {
objects[index] = object;
return;
}
index++;
if (index == keys.length) {
index = 0;
}
}
}
public T getAt(int tableIndex) {
T o = objects[tableIndex];
return o == NULL_OBJECT ? null : o;
}
public T get(long id) {
if (keys == EMPTY_KEYS) {
return null;
}
int index = index(id);
while (true) {
if (objects[index] == null) {
return null;
}
if (keys[index] == id) {
return getAt(index);
}
index++;
if (index == keys.length) {
index = 0;
}
}
}
private int index(long id) {
return (int) ((id * -7046029254386353131L) >>> shift);
}
// Copied from HashMap::tableSizeFor
private static final int tableSizeFor(int cap) {
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
public void forEachKey(LongConsumer keyTraverser) {
for (int i = 0; i < keys.length; i++) {
if (objects[i] != null) {
keyTraverser.accept(keys[i]);
}
}
}
public void forEach(Consumer<T> consumer) {
for (int i = 0; i < keys.length; i++) {
T o = objects[i];
if (o != null) {
consumer.accept(o);
}
}
}
public int size() {
return count;
}
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < objects.length; i++) {
sb.append(i);
sb.append(": id=");
sb.append(keys[i]);
sb.append(" ");
sb.append(objects[i]);
sb.append("\n");
}
return sb.toString();
}
}
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
package jdk.jfr.internal; package jdk.jfr.internal;
import java.io.DataInput;
import java.io.DataOutput; import java.io.DataOutput;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -35,6 +34,7 @@ import java.util.Locale; ...@@ -35,6 +34,7 @@ import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import jdk.jfr.EventType; import jdk.jfr.EventType;
import jdk.jfr.internal.consumer.RecordingInput;
/** /**
* Metadata about a chunk * Metadata about a chunk
...@@ -214,6 +214,7 @@ public final class MetadataDescriptor { ...@@ -214,6 +214,7 @@ public final class MetadataDescriptor {
long gmtOffset; long gmtOffset;
String locale; String locale;
Element root; Element root;
public long metadataId;
// package private // package private
MetadataDescriptor() { MetadataDescriptor() {
...@@ -252,7 +253,7 @@ public final class MetadataDescriptor { ...@@ -252,7 +253,7 @@ public final class MetadataDescriptor {
return locale; return locale;
} }
public static MetadataDescriptor read(DataInput input) throws IOException { public static MetadataDescriptor read(RecordingInput input) throws IOException {
MetadataReader r = new MetadataReader(input); MetadataReader r = new MetadataReader(input);
return r.getDescriptor(); return r.getDescriptor();
} }
......
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -49,6 +49,8 @@ import jdk.jfr.AnnotationElement; ...@@ -49,6 +49,8 @@ import jdk.jfr.AnnotationElement;
import jdk.jfr.SettingDescriptor; import jdk.jfr.SettingDescriptor;
import jdk.jfr.ValueDescriptor; import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.MetadataDescriptor.Element; import jdk.jfr.internal.MetadataDescriptor.Element;
import jdk.jfr.internal.consumer.RecordingInput;
import jdk.jfr.internal.consumer.StringParser;
/** /**
* Parses metadata. * Parses metadata.
...@@ -61,12 +63,13 @@ final class MetadataReader { ...@@ -61,12 +63,13 @@ final class MetadataReader {
private final MetadataDescriptor descriptor; private final MetadataDescriptor descriptor;
private final Map<Long, Type> types = new HashMap<>(); private final Map<Long, Type> types = new HashMap<>();
public MetadataReader(DataInput input) throws IOException { public MetadataReader(RecordingInput input) throws IOException {
this.input = input; this.input = input;
int size = input.readInt(); int size = input.readInt();
this.pool = new ArrayList<>(size); this.pool = new ArrayList<>(size);
StringParser p = new StringParser(null, false);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
this.pool.add(input.readUTF()); this.pool.add((String) p.parse(input));
} }
descriptor = new MetadataDescriptor(); descriptor = new MetadataDescriptor();
Element root = createElement(); Element root = createElement();
......
...@@ -45,6 +45,7 @@ import jdk.jfr.StackTrace; ...@@ -45,6 +45,7 @@ import jdk.jfr.StackTrace;
import jdk.jfr.Threshold; import jdk.jfr.Threshold;
import jdk.jfr.ValueDescriptor; import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.RequestEngine.RequestHook; import jdk.jfr.internal.RequestEngine.RequestHook;
import jdk.jfr.internal.consumer.RepositoryFiles;
import jdk.jfr.internal.handlers.EventHandler; import jdk.jfr.internal.handlers.EventHandler;
public final class MetadataRepository { public final class MetadataRepository {
...@@ -184,10 +185,14 @@ public final class MetadataRepository { ...@@ -184,10 +185,14 @@ public final class MetadataRepository {
} }
public synchronized List<EventControl> getEventControls() { public synchronized List<EventControl> getEventControls() {
List<EventControl> controls = new ArrayList<>(); List<Class<? extends Event>> eventClasses = jvm.getAllEventClasses();
ArrayList<EventControl> controls = new ArrayList<>(eventClasses.size() + nativeControls.size());
controls.addAll(nativeControls); controls.addAll(nativeControls);
for (EventHandler eh : getEventHandlers()) { for (Class<? extends Event> clazz : eventClasses) {
controls.add(eh.getEventControl()); EventHandler eh = Utils.getHandler(clazz);
if (eh != null) {
controls.add(eh.getEventControl());
}
} }
return controls; return controls;
} }
...@@ -240,7 +245,9 @@ public final class MetadataRepository { ...@@ -240,7 +245,9 @@ public final class MetadataRepository {
storeDescriptorInJVM(); storeDescriptorInJVM();
} }
jvm.setOutput(filename); jvm.setOutput(filename);
if (filename != null) {
RepositoryFiles.notifyNewFile();
}
unregisterUnloaded(); unregisterUnloaded();
if (unregistered) { if (unregistered) {
if (typeLibrary.clearUnregistered()) { if (typeLibrary.clearUnregistered()) {
...@@ -276,4 +283,11 @@ public final class MetadataRepository { ...@@ -276,4 +283,11 @@ public final class MetadataRepository {
unregistered = true; unregistered = true;
} }
public synchronized void flush() {
if (staleMetadata) {
storeDescriptorInJVM();
}
jvm.flush();
}
} }
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -53,7 +53,7 @@ import jdk.jfr.SettingDescriptor; ...@@ -53,7 +53,7 @@ import jdk.jfr.SettingDescriptor;
import jdk.jfr.ValueDescriptor; import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.MetadataDescriptor.Attribute; import jdk.jfr.internal.MetadataDescriptor.Attribute;
import jdk.jfr.internal.MetadataDescriptor.Element; import jdk.jfr.internal.MetadataDescriptor.Element;
import jdk.jfr.internal.consumer.RecordingInput; import jdk.jfr.internal.consumer.StringParser;
/** /**
* Class responsible for converting a list of types into a format that can be * Class responsible for converting a list of types into a format that can be
...@@ -94,10 +94,10 @@ final class MetadataWriter { ...@@ -94,10 +94,10 @@ final class MetadataWriter {
private void writeString(DataOutput out, String s) throws IOException { private void writeString(DataOutput out, String s) throws IOException {
if (s == null ) { if (s == null ) {
out.writeByte(RecordingInput.STRING_ENCODING_NULL); out.writeByte(StringParser.Encoding.NULL.byteValue());
return; return;
} }
out.writeByte(RecordingInput.STRING_ENCODING_CHAR_ARRAY); // encoding UTF-16 out.writeByte(StringParser.Encoding.CHAR_ARRAY.byteValue()); // encoding UTF-16
int length = s.length(); int length = s.length();
writeInt(out, length); writeInt(out, length);
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
......
/* /*
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -69,7 +69,7 @@ public final class PlatformEventType extends Type { ...@@ -69,7 +69,7 @@ public final class PlatformEventType extends Type {
super(name, Type.SUPER_TYPE_EVENT, id); super(name, Type.SUPER_TYPE_EVENT, id);
this.dynamicSettings = dynamicSettings; this.dynamicSettings = dynamicSettings;
this.isJVM = Type.isDefinedByJVM(id); this.isJVM = Type.isDefinedByJVM(id);
this.isMethodSampling = name.equals(Type.EVENT_NAME_PREFIX + "ExecutionSample") || name.equals(Type.EVENT_NAME_PREFIX + "NativeMethodSample"); this.isMethodSampling = isJVM && (name.equals(Type.EVENT_NAME_PREFIX + "ExecutionSample") || name.equals(Type.EVENT_NAME_PREFIX + "NativeMethodSample"));
this.isJDK = isJDK; this.isJDK = isJDK;
this.stackTraceOffset = stackTraceOffset(name, isJDK); this.stackTraceOffset = stackTraceOffset(name, isJDK);
} }
......
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -31,7 +31,6 @@ import static jdk.jfr.internal.LogLevel.WARN; ...@@ -31,7 +31,6 @@ import static jdk.jfr.internal.LogLevel.WARN;
import static jdk.jfr.internal.LogTag.JFR; import static jdk.jfr.internal.LogTag.JFR;
import static jdk.jfr.internal.LogTag.JFR_SYSTEM; import static jdk.jfr.internal.LogTag.JFR_SYSTEM;
import java.io.IOException;
import java.security.AccessControlContext; import java.security.AccessControlContext;
import java.security.AccessController; import java.security.AccessController;
import java.time.Duration; import java.time.Duration;
...@@ -60,6 +59,7 @@ import jdk.jfr.internal.instrument.JDKEvents; ...@@ -60,6 +59,7 @@ import jdk.jfr.internal.instrument.JDKEvents;
public final class PlatformRecorder { public final class PlatformRecorder {
private final List<PlatformRecording> recordings = new ArrayList<>(); private final List<PlatformRecording> recordings = new ArrayList<>();
private final static List<SecureRecorderListener> changeListeners = new ArrayList<>(); private final static List<SecureRecorderListener> changeListeners = new ArrayList<>();
private final Repository repository; private final Repository repository;
...@@ -98,6 +98,7 @@ public final class PlatformRecorder { ...@@ -98,6 +98,7 @@ public final class PlatformRecorder {
Thread t = SecuritySupport.createThreadWitNoPermissions("Permissionless thread", ()-> { Thread t = SecuritySupport.createThreadWitNoPermissions("Permissionless thread", ()-> {
result.add(new Timer("JFR Recording Scheduler", true)); result.add(new Timer("JFR Recording Scheduler", true));
}); });
jvm.exclude(t);
t.start(); t.start();
t.join(); t.join();
return result.get(0); return result.get(0);
...@@ -206,7 +207,7 @@ public final class PlatformRecorder { ...@@ -206,7 +207,7 @@ public final class PlatformRecorder {
repository.clear(); repository.clear();
} }
synchronized void start(PlatformRecording recording) { synchronized long start(PlatformRecording recording) {
// State can only be NEW or DELAYED because of previous checks // State can only be NEW or DELAYED because of previous checks
Instant now = Instant.now(); Instant now = Instant.now();
recording.setStartTime(now); recording.setStartTime(now);
...@@ -217,14 +218,17 @@ public final class PlatformRecorder { ...@@ -217,14 +218,17 @@ public final class PlatformRecorder {
} }
boolean toDisk = recording.isToDisk(); boolean toDisk = recording.isToDisk();
boolean beginPhysical = true; boolean beginPhysical = true;
long streamInterval = recording.getStreamIntervalMillis();
for (PlatformRecording s : getRecordings()) { for (PlatformRecording s : getRecordings()) {
if (s.getState() == RecordingState.RUNNING) { if (s.getState() == RecordingState.RUNNING) {
beginPhysical = false; beginPhysical = false;
if (s.isToDisk()) { if (s.isToDisk()) {
toDisk = true; toDisk = true;
} }
streamInterval = Math.min(streamInterval, s.getStreamIntervalMillis());
} }
} }
long startNanos = -1;
if (beginPhysical) { if (beginPhysical) {
RepositoryChunk newChunk = null; RepositoryChunk newChunk = null;
if (toDisk) { if (toDisk) {
...@@ -235,6 +239,7 @@ public final class PlatformRecorder { ...@@ -235,6 +239,7 @@ public final class PlatformRecorder {
} }
currentChunk = newChunk; currentChunk = newChunk;
jvm.beginRecording_(); jvm.beginRecording_();
startNanos = jvm.getChunkStartNanos();
recording.setState(RecordingState.RUNNING); recording.setState(RecordingState.RUNNING);
updateSettings(); updateSettings();
writeMetaEvents(); writeMetaEvents();
...@@ -244,6 +249,7 @@ public final class PlatformRecorder { ...@@ -244,6 +249,7 @@ public final class PlatformRecorder {
newChunk = repository.newChunk(now); newChunk = repository.newChunk(now);
RequestEngine.doChunkEnd(); RequestEngine.doChunkEnd();
MetadataRepository.getInstance().setOutput(newChunk.getUnfishedFile().toString()); MetadataRepository.getInstance().setOutput(newChunk.getUnfishedFile().toString());
startNanos = jvm.getChunkStartNanos();
} }
recording.setState(RecordingState.RUNNING); recording.setState(RecordingState.RUNNING);
updateSettings(); updateSettings();
...@@ -253,8 +259,12 @@ public final class PlatformRecorder { ...@@ -253,8 +259,12 @@ public final class PlatformRecorder {
} }
currentChunk = newChunk; currentChunk = newChunk;
} }
if (toDisk) {
RequestEngine.setFlushInterval(streamInterval);
}
RequestEngine.doChunkBegin(); RequestEngine.doChunkBegin();
return startNanos;
} }
synchronized void stop(PlatformRecording recording) { synchronized void stop(PlatformRecording recording) {
...@@ -269,6 +279,7 @@ public final class PlatformRecorder { ...@@ -269,6 +279,7 @@ public final class PlatformRecorder {
Instant now = Instant.now(); Instant now = Instant.now();
boolean toDisk = false; boolean toDisk = false;
boolean endPhysical = true; boolean endPhysical = true;
long streamInterval = Long.MAX_VALUE;
for (PlatformRecording s : getRecordings()) { for (PlatformRecording s : getRecordings()) {
RecordingState rs = s.getState(); RecordingState rs = s.getState();
if (s != recording && RecordingState.RUNNING == rs) { if (s != recording && RecordingState.RUNNING == rs) {
...@@ -276,6 +287,7 @@ public final class PlatformRecorder { ...@@ -276,6 +287,7 @@ public final class PlatformRecorder {
if (s.isToDisk()) { if (s.isToDisk()) {
toDisk = true; toDisk = true;
} }
streamInterval = Math.min(streamInterval, s.getStreamIntervalMillis());
} }
} }
OldObjectSample.emit(recording); OldObjectSample.emit(recording);
...@@ -311,6 +323,13 @@ public final class PlatformRecorder { ...@@ -311,6 +323,13 @@ public final class PlatformRecorder {
currentChunk = newChunk; currentChunk = newChunk;
RequestEngine.doChunkBegin(); RequestEngine.doChunkBegin();
} }
if (toDisk) {
RequestEngine.setFlushInterval(streamInterval);
} else {
RequestEngine.setFlushInterval(Long.MAX_VALUE);
}
recording.setState(RecordingState.STOPPED); recording.setState(RecordingState.STOPPED);
} }
...@@ -340,6 +359,18 @@ public final class PlatformRecorder { ...@@ -340,6 +359,18 @@ public final class PlatformRecorder {
MetadataRepository.getInstance().setSettings(list); MetadataRepository.getInstance().setSettings(list);
} }
public synchronized void rotateIfRecordingToDisk() {
boolean disk = false;
for (PlatformRecording s : getRecordings()) {
if (RecordingState.RUNNING == s.getState() && s.isToDisk()) {
disk = true;
}
}
if (disk) {
rotateDisk();
}
}
synchronized void rotateDisk() { synchronized void rotateDisk() {
Instant now = Instant.now(); Instant now = Instant.now();
RepositoryChunk newChunk = repository.newChunk(now); RepositoryChunk newChunk = repository.newChunk(now);
...@@ -397,14 +428,14 @@ public final class PlatformRecorder { ...@@ -397,14 +428,14 @@ public final class PlatformRecorder {
r.appendChunk(chunk); r.appendChunk(chunk);
} }
} }
FilePurger.purge();
} }
private void writeMetaEvents() { private void writeMetaEvents() {
if (activeRecordingEvent.isEnabled()) { if (activeRecordingEvent.isEnabled()) {
ActiveRecordingEvent event = ActiveRecordingEvent.EVENT.get();
for (PlatformRecording r : getRecordings()) { for (PlatformRecording r : getRecordings()) {
if (r.getState() == RecordingState.RUNNING && r.shouldWriteMetadataEvent()) { if (r.getState() == RecordingState.RUNNING && r.shouldWriteMetadataEvent()) {
ActiveRecordingEvent event = new ActiveRecordingEvent();
event.id = r.getId(); event.id = r.getId();
event.name = r.getName(); event.name = r.getName();
WriteableUserPath p = r.getDestination(); WriteableUserPath p = r.getDestination();
...@@ -417,6 +448,8 @@ public final class PlatformRecorder { ...@@ -417,6 +448,8 @@ public final class PlatformRecorder {
event.maxSize = size == null ? Long.MAX_VALUE : size; event.maxSize = size == null ? Long.MAX_VALUE : size;
Instant start = r.getStartTime(); Instant start = r.getStartTime();
event.recordingStart = start == null ? Long.MAX_VALUE : start.toEpochMilli(); event.recordingStart = start == null ? Long.MAX_VALUE : start.toEpochMilli();
Duration fi = r.getFlushInterval();
event.flushInterval = fi == null ? Long.MAX_VALUE : fi.toMillis();
event.commit(); event.commit();
} }
} }
...@@ -450,7 +483,7 @@ public final class PlatformRecorder { ...@@ -450,7 +483,7 @@ public final class PlatformRecorder {
JVM.FILE_DELTA_CHANGE.wait(duration < 10 ? 10 : duration); JVM.FILE_DELTA_CHANGE.wait(duration < 10 ? 10 : duration);
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); // Ignore
} }
} }
......
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -84,6 +84,7 @@ public final class PlatformRecording implements AutoCloseable { ...@@ -84,6 +84,7 @@ public final class PlatformRecording implements AutoCloseable {
private TimerTask startTask; private TimerTask startTask;
private AccessControlContext noDestinationDumpOnExitAccessControlContext; private AccessControlContext noDestinationDumpOnExitAccessControlContext;
private boolean shuoldWriteActiveRecordingEvent = true; private boolean shuoldWriteActiveRecordingEvent = true;
private Duration flushInterval = Duration.ofSeconds(1);
PlatformRecording(PlatformRecorder recorder, long id) { PlatformRecording(PlatformRecorder recorder, long id) {
// Typically the access control context is taken // Typically the access control context is taken
...@@ -98,9 +99,10 @@ public final class PlatformRecording implements AutoCloseable { ...@@ -98,9 +99,10 @@ public final class PlatformRecording implements AutoCloseable {
this.name = String.valueOf(id); this.name = String.valueOf(id);
} }
public void start() { public long start() {
RecordingState oldState; RecordingState oldState;
RecordingState newState; RecordingState newState;
long startNanos = -1;
synchronized (recorder) { synchronized (recorder) {
oldState = getState(); oldState = getState();
if (!Utils.isBefore(state, RecordingState.RUNNING)) { if (!Utils.isBefore(state, RecordingState.RUNNING)) {
...@@ -111,7 +113,7 @@ public final class PlatformRecording implements AutoCloseable { ...@@ -111,7 +113,7 @@ public final class PlatformRecording implements AutoCloseable {
startTask = null; startTask = null;
startTime = null; startTime = null;
} }
recorder.start(this); startNanos = recorder.start(this);
Logger.log(LogTag.JFR, LogLevel.INFO, () -> { Logger.log(LogTag.JFR, LogLevel.INFO, () -> {
// Only print non-default values so it easy to see // Only print non-default values so it easy to see
// which options were added // which options were added
...@@ -143,6 +145,8 @@ public final class PlatformRecording implements AutoCloseable { ...@@ -143,6 +145,8 @@ public final class PlatformRecording implements AutoCloseable {
newState = getState(); newState = getState();
} }
notifyIfStateChanged(oldState, newState); notifyIfStateChanged(oldState, newState);
return startNanos;
} }
public boolean stop(String reason) { public boolean stop(String reason) {
...@@ -779,4 +783,28 @@ public final class PlatformRecording implements AutoCloseable { ...@@ -779,4 +783,28 @@ public final class PlatformRecording implements AutoCloseable {
public boolean isRecorderEnabled(String eventName) { public boolean isRecorderEnabled(String eventName) {
return recorder.isEnabled(eventName); return recorder.isEnabled(eventName);
} }
public void setFlushInterval(Duration interval) {
synchronized (recorder) {
if (getState() == RecordingState.CLOSED) {
throw new IllegalStateException("Can't set stream interval when recording is closed");
}
this.flushInterval = interval;
}
}
public Duration getFlushInterval() {
synchronized (recorder) {
return flushInterval;
}
}
public long getStreamIntervalMillis() {
synchronized (recorder) {
if (flushInterval != null) {
return flushInterval.toMillis();
}
return Long.MAX_VALUE;
}
}
} }
/* /*
* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -43,6 +43,7 @@ public final class Repository { ...@@ -43,6 +43,7 @@ public final class Repository {
public final static DateTimeFormatter REPO_DATE_FORMAT = DateTimeFormatter public final static DateTimeFormatter REPO_DATE_FORMAT = DateTimeFormatter
.ofPattern("yyyy_MM_dd_HH_mm_ss"); .ofPattern("yyyy_MM_dd_HH_mm_ss");
private static final String JFR_REPOSITORY_LOCATION_PROPERTY = "jdk.jfr.repository";
private final Set<SafePath> cleanupDirectories = new HashSet<>(); private final Set<SafePath> cleanupDirectories = new HashSet<>();
private SafePath baseLocation; private SafePath baseLocation;
...@@ -55,7 +56,7 @@ public final class Repository { ...@@ -55,7 +56,7 @@ public final class Repository {
return instance; return instance;
} }
public synchronized void setBasePath(SafePath baseLocation) throws Exception { public synchronized void setBasePath(SafePath baseLocation) throws IOException {
// Probe to see if repository can be created, needed for fail fast // Probe to see if repository can be created, needed for fail fast
// during JVM startup or JFR.configure // during JVM startup or JFR.configure
this.repository = createRepository(baseLocation); this.repository = createRepository(baseLocation);
...@@ -69,7 +70,7 @@ public final class Repository { ...@@ -69,7 +70,7 @@ public final class Repository {
this.baseLocation = baseLocation; this.baseLocation = baseLocation;
} }
synchronized void ensureRepository() throws Exception { public synchronized void ensureRepository() throws IOException {
if (baseLocation == null) { if (baseLocation == null) {
setBasePath(SecuritySupport.JAVA_IO_TMPDIR); setBasePath(SecuritySupport.JAVA_IO_TMPDIR);
} }
...@@ -91,7 +92,7 @@ public final class Repository { ...@@ -91,7 +92,7 @@ public final class Repository {
} }
} }
private static SafePath createRepository(SafePath basePath) throws Exception { private static SafePath createRepository(SafePath basePath) throws IOException {
SafePath canonicalBaseRepositoryPath = createRealBasePath(basePath); SafePath canonicalBaseRepositoryPath = createRealBasePath(basePath);
SafePath f = null; SafePath f = null;
...@@ -108,13 +109,14 @@ public final class Repository { ...@@ -108,13 +109,14 @@ public final class Repository {
} }
if (i == MAX_REPO_CREATION_RETRIES) { if (i == MAX_REPO_CREATION_RETRIES) {
throw new Exception("Unable to create JFR repository directory using base location (" + basePath + ")"); throw new IOException("Unable to create JFR repository directory using base location (" + basePath + ")");
} }
SafePath canonicalRepositoryPath = SecuritySupport.toRealPath(f); SafePath canonicalRepositoryPath = SecuritySupport.toRealPath(f);
SecuritySupport.setProperty(JFR_REPOSITORY_LOCATION_PROPERTY, canonicalRepositoryPath.toString());
return canonicalRepositoryPath; return canonicalRepositoryPath;
} }
private static SafePath createRealBasePath(SafePath safePath) throws Exception { private static SafePath createRealBasePath(SafePath safePath) throws IOException {
if (SecuritySupport.exists(safePath)) { if (SecuritySupport.exists(safePath)) {
if (!SecuritySupport.isWritable(safePath)) { if (!SecuritySupport.isWritable(safePath)) {
throw new IOException("JFR repository directory (" + safePath.toString() + ") exists, but isn't writable"); throw new IOException("JFR repository directory (" + safePath.toString() + ") exists, but isn't writable");
...@@ -154,7 +156,7 @@ public final class Repository { ...@@ -154,7 +156,7 @@ public final class Repository {
SecuritySupport.clearDirectory(p); SecuritySupport.clearDirectory(p);
Logger.log(LogTag.JFR, LogLevel.INFO, "Removed repository " + p); Logger.log(LogTag.JFR, LogLevel.INFO, "Removed repository " + p);
} catch (IOException e) { } catch (IOException e) {
Logger.log(LogTag.JFR, LogLevel.ERROR, "Repository " + p + " could not be removed at shutdown: " + e.getMessage()); Logger.log(LogTag.JFR, LogLevel.INFO, "Repository " + p + " could not be removed at shutdown: " + e.getMessage());
} }
} }
} }
......
/* /*
* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -63,10 +63,10 @@ final class RepositoryChunk { ...@@ -63,10 +63,10 @@ final class RepositoryChunk {
LocalDateTime.ofInstant(startTime, z.getZone())); LocalDateTime.ofInstant(startTime, z.getZone()));
this.startTime = startTime; this.startTime = startTime;
this.repositoryPath = path; this.repositoryPath = path;
this.unFinishedFile = findFileName(repositoryPath, fileName, ".part"); this.unFinishedFile = findFileName(repositoryPath, fileName, ".jfr");
this.file = findFileName(repositoryPath, fileName, ".jfr"); this.file = findFileName(repositoryPath, fileName, ".jfr");
this.unFinishedRAF = SecuritySupport.createRandomAccessFile(unFinishedFile); this.unFinishedRAF = SecuritySupport.createRandomAccessFile(unFinishedFile);
SecuritySupport.touch(file); // SecuritySupport.touch(file);
} }
private static SafePath findFileName(SafePath directory, String name, String extension) throws Exception { private static SafePath findFileName(SafePath directory, String name, String extension) throws Exception {
...@@ -105,8 +105,6 @@ final class RepositoryChunk { ...@@ -105,8 +105,6 @@ final class RepositoryChunk {
private static long finish(SafePath unFinishedFile, SafePath file) throws IOException { private static long finish(SafePath unFinishedFile, SafePath file) throws IOException {
Objects.requireNonNull(unFinishedFile); Objects.requireNonNull(unFinishedFile);
Objects.requireNonNull(file); Objects.requireNonNull(file);
SecuritySupport.delete(file);
SecuritySupport.moveReplace(unFinishedFile, file);
return SecuritySupport.getFileSize(file); return SecuritySupport.getFileSize(file);
} }
...@@ -123,9 +121,11 @@ final class RepositoryChunk { ...@@ -123,9 +121,11 @@ final class RepositoryChunk {
SecuritySupport.delete(f); SecuritySupport.delete(f);
Logger.log(LogTag.JFR, LogLevel.DEBUG, () -> "Repository chunk " + f + " deleted"); Logger.log(LogTag.JFR, LogLevel.DEBUG, () -> "Repository chunk " + f + " deleted");
} catch (IOException e) { } catch (IOException e) {
Logger.log(LogTag.JFR, LogLevel.ERROR, () -> "Repository chunk " + f + " could not be deleted: " + e.getMessage()); // Probably happens because file is being streamed
// on Windows where files in use can't be removed.
Logger.log(LogTag.JFR, LogLevel.DEBUG, () -> "Repository chunk " + f + " could not be deleted: " + e.getMessage());
if (f != null) { if (f != null) {
SecuritySupport.deleteOnExit(f); FilePurger.add(f);
} }
} }
} }
......
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -30,6 +30,7 @@ import java.security.AccessController; ...@@ -30,6 +30,7 @@ import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
...@@ -96,6 +97,8 @@ public final class RequestEngine { ...@@ -96,6 +97,8 @@ public final class RequestEngine {
private final static List<RequestHook> entries = new CopyOnWriteArrayList<>(); private final static List<RequestHook> entries = new CopyOnWriteArrayList<>();
private static long lastTimeMillis; private static long lastTimeMillis;
private static long flushInterval = Long.MAX_VALUE;
private static long streamDelta;
public static void addHook(AccessControlContext acc, PlatformEventType type, Runnable hook) { public static void addHook(AccessControlContext acc, PlatformEventType type, Runnable hook) {
Objects.requireNonNull(acc); Objects.requireNonNull(acc);
...@@ -209,7 +212,9 @@ public final class RequestEngine { ...@@ -209,7 +212,9 @@ public final class RequestEngine {
lastTimeMillis = now; lastTimeMillis = now;
return 0; return 0;
} }
for (RequestHook he : entries) { Iterator<RequestHook> hookIterator = entries.iterator();
while(hookIterator.hasNext()) {
RequestHook he = hookIterator.next();
long left = 0; long left = 0;
PlatformEventType es = he.type; PlatformEventType es = he.type;
// Not enabled, skip. // Not enabled, skip.
...@@ -228,7 +233,6 @@ public final class RequestEngine { ...@@ -228,7 +233,6 @@ public final class RequestEngine {
// for wait > period // for wait > period
r_delta = 0; r_delta = 0;
he.execute(); he.execute();
;
} }
// calculate time left // calculate time left
...@@ -250,7 +254,39 @@ public final class RequestEngine { ...@@ -250,7 +254,39 @@ public final class RequestEngine {
min = left; min = left;
} }
} }
// Flush should happen after all periodic events has been emitted
// Repeat of the above algorithm, but using the stream interval.
if (flushInterval != Long.MAX_VALUE) {
long r_period = flushInterval;
long r_delta = streamDelta;
r_delta += delta;
if (r_delta >= r_period) {
r_delta = 0;
MetadataRepository.getInstance().flush();
Utils.notifyFlush();
}
long left = (r_period - r_delta);
if (left < 0) {
left = 0;
}
streamDelta = r_delta;
if (min == 0 || left < min) {
min = left;
}
}
lastTimeMillis = now; lastTimeMillis = now;
return min; return min;
} }
static void setFlushInterval(long millis) {
// Don't accept shorter interval than 1 s.
long interval = millis < 1000 ? 1000 : millis;
flushInterval = interval;
if (interval < flushInterval) {
synchronized (JVM.FILE_DELTA_CHANGE) {
JVM.FILE_DELTA_CHANGE.notifyAll();
}
}
}
} }
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -37,6 +37,7 @@ import java.lang.reflect.Method; ...@@ -37,6 +37,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.ReflectPermission; import java.lang.reflect.ReflectPermission;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
...@@ -63,6 +64,7 @@ import jdk.jfr.FlightRecorder; ...@@ -63,6 +64,7 @@ import jdk.jfr.FlightRecorder;
import jdk.jfr.FlightRecorderListener; import jdk.jfr.FlightRecorderListener;
import jdk.jfr.FlightRecorderPermission; import jdk.jfr.FlightRecorderPermission;
import jdk.jfr.Recording; import jdk.jfr.Recording;
import jdk.jfr.internal.consumer.FileAccess;
/** /**
* Contains JFR code that does * Contains JFR code that does
...@@ -71,7 +73,7 @@ import jdk.jfr.Recording; ...@@ -71,7 +73,7 @@ import jdk.jfr.Recording;
public final class SecuritySupport { public final class SecuritySupport {
private final static Unsafe unsafe = Unsafe.getUnsafe(); private final static Unsafe unsafe = Unsafe.getUnsafe();
public final static SafePath JFC_DIRECTORY = getPathInProperty("java.home", "lib/jfr"); public final static SafePath JFC_DIRECTORY = getPathInProperty("java.home", "lib/jfr");
public final static FileAccess PRIVILIGED = new Privileged();
static final SafePath USER_HOME = getPathInProperty("user.home", null); static final SafePath USER_HOME = getPathInProperty("user.home", null);
static final SafePath JAVA_IO_TMPDIR = getPathInProperty("java.io.tmpdir", null); static final SafePath JAVA_IO_TMPDIR = getPathInProperty("java.io.tmpdir", null);
...@@ -138,7 +140,7 @@ public final class SecuritySupport { ...@@ -138,7 +140,7 @@ public final class SecuritySupport {
* a malicious provider. * a malicious provider.
* *
*/ */
public static final class SafePath { public static final class SafePath implements Comparable<SafePath> {
private final Path path; private final Path path;
private final String text; private final String text;
...@@ -156,9 +158,18 @@ public final class SecuritySupport { ...@@ -156,9 +158,18 @@ public final class SecuritySupport {
return path; return path;
} }
public File toFile() {
return path.toFile();
}
public String toString() { public String toString() {
return text; return text;
} }
@Override
public int compareTo(SafePath that) {
return that.text.compareTo(this.text);
}
} }
private interface RunnableWithCheckedException { private interface RunnableWithCheckedException {
...@@ -256,6 +267,10 @@ public final class SecuritySupport { ...@@ -256,6 +267,10 @@ public final class SecuritySupport {
doPrivileged(() -> FlightRecorder.register(eventClass), new FlightRecorderPermission(Utils.REGISTER_EVENT)); doPrivileged(() -> FlightRecorder.register(eventClass), new FlightRecorderPermission(Utils.REGISTER_EVENT));
} }
public static void setProperty(String propertyName, String value) {
doPrivileged(() -> System.setProperty(propertyName, value), new PropertyPermission(propertyName, "write"));
}
static boolean getBooleanProperty(String propertyName) { static boolean getBooleanProperty(String propertyName) {
return doPrivilegedWithReturn(() -> Boolean.getBoolean(propertyName), new PropertyPermission(propertyName, "read")); return doPrivilegedWithReturn(() -> Boolean.getBoolean(propertyName), new PropertyPermission(propertyName, "read"));
} }
...@@ -296,7 +311,7 @@ public final class SecuritySupport { ...@@ -296,7 +311,7 @@ public final class SecuritySupport {
doPriviligedIO(() -> Files.walkFileTree(safePath.toPath(), new DirectoryCleaner())); doPriviligedIO(() -> Files.walkFileTree(safePath.toPath(), new DirectoryCleaner()));
} }
static SafePath toRealPath(SafePath safePath) throws Exception { static SafePath toRealPath(SafePath safePath) throws IOException {
return new SafePath(doPrivilegedIOWithReturn(() -> safePath.toPath().toRealPath())); return new SafePath(doPrivilegedIOWithReturn(() -> safePath.toPath().toRealPath()));
} }
...@@ -322,7 +337,8 @@ public final class SecuritySupport { ...@@ -322,7 +337,8 @@ public final class SecuritySupport {
} }
public static boolean exists(SafePath safePath) throws IOException { public static boolean exists(SafePath safePath) throws IOException {
return doPrivilegedIOWithReturn(() -> Files.exists(safePath.toPath())); // Files.exist(path) is allocation intensive
return doPrivilegedIOWithReturn(() -> safePath.toPath().toFile().exists());
} }
public static boolean isDirectory(SafePath safePath) throws IOException { public static boolean isDirectory(SafePath safePath) throws IOException {
...@@ -377,7 +393,7 @@ public final class SecuritySupport { ...@@ -377,7 +393,7 @@ public final class SecuritySupport {
return unsafe.defineClass(name, bytes, 0, bytes.length, classLoader, null); return unsafe.defineClass(name, bytes, 0, bytes.length, classLoader, null);
} }
static Thread createThreadWitNoPermissions(String threadName, Runnable runnable) { public static Thread createThreadWitNoPermissions(String threadName, Runnable runnable) {
return doPrivilegedWithReturn(() -> new Thread(runnable, threadName), new Permission[0]); return doPrivilegedWithReturn(() -> new Thread(runnable, threadName), new Permission[0]);
} }
...@@ -388,4 +404,32 @@ public final class SecuritySupport { ...@@ -388,4 +404,32 @@ public final class SecuritySupport {
public static SafePath getAbsolutePath(SafePath path) throws IOException { public static SafePath getAbsolutePath(SafePath path) throws IOException {
return new SafePath(doPrivilegedIOWithReturn((()-> path.toPath().toAbsolutePath()))); return new SafePath(doPrivilegedIOWithReturn((()-> path.toPath().toAbsolutePath())));
} }
private final static class Privileged extends FileAccess {
@Override
public RandomAccessFile openRAF(File f, String mode) throws IOException {
return doPrivilegedIOWithReturn( () -> new RandomAccessFile(f, mode));
}
@Override
public DirectoryStream<Path> newDirectoryStream(Path directory) throws IOException {
return doPrivilegedIOWithReturn( () -> Files.newDirectoryStream(directory));
}
@Override
public String getAbsolutePath(File f) throws IOException {
return doPrivilegedIOWithReturn( () -> f.getAbsolutePath());
}
@Override
public long length(File f) throws IOException {
return doPrivilegedIOWithReturn( () -> f.length());
}
@Override
public long fileSize(Path p) throws IOException {
return doPrivilegedIOWithReturn( () -> Files.size(p));
}
}
} }
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -33,11 +33,11 @@ import java.util.HashSet; ...@@ -33,11 +33,11 @@ import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.StringJoiner; import java.util.StringJoiner;
import jdk.jfr.Event; import jdk.jfr.Event;
import jdk.jfr.internal.EventControl.NamedControl;
import jdk.jfr.internal.handlers.EventHandler; import jdk.jfr.internal.handlers.EventHandler;
final class SettingsManager { final class SettingsManager {
...@@ -214,18 +214,21 @@ final class SettingsManager { ...@@ -214,18 +214,21 @@ final class SettingsManager {
void setEventControl(EventControl ec) { void setEventControl(EventControl ec) {
InternalSetting is = getInternalSetting(ec); InternalSetting is = getInternalSetting(ec);
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "Applied settings for " + ec.getEventType().getLogName() + " {"); boolean shouldLog = Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO);
for (Entry<String, Control> entry : ec.getEntries()) { if (shouldLog) {
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "Applied settings for " + ec.getEventType().getLogName() + " {");
}
for (NamedControl nc: ec.getNamedControls()) {
Set<String> values = null; Set<String> values = null;
String settingName = entry.getKey(); String settingName = nc.name;
if (is != null) { if (is != null) {
values = is.getValues(settingName); values = is.getValues(settingName);
} }
Control control = entry.getValue(); Control control = nc.control;
if (values != null) { if (values != null) {
control.apply(values); control.apply(values);
String after = control.getLastValue(); String after = control.getLastValue();
if (Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO)) { if (shouldLog) {
if (Utils.isSettingVisible(control, ec.getEventType().hasEventHook())) { if (Utils.isSettingVisible(control, ec.getEventType().hasEventHook())) {
if (values.size() > 1) { if (values.size() > 1) {
StringJoiner sj = new StringJoiner(", ", "{", "}"); StringJoiner sj = new StringJoiner(", ", "{", "}");
...@@ -242,14 +245,16 @@ final class SettingsManager { ...@@ -242,14 +245,16 @@ final class SettingsManager {
} }
} else { } else {
control.setDefault(); control.setDefault();
if (Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO)) { if (shouldLog) {
String message = " " + settingName + "=\"" + control.getLastValue() + "\""; String message = " " + settingName + "=\"" + control.getLastValue() + "\"";
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, message); Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, message);
} }
} }
} }
ec.writeActiveSettingEvent(); ec.writeActiveSettingEvent();
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "}"); if (shouldLog) {
Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "}");
}
} }
private InternalSetting getInternalSetting(EventControl ec) { private InternalSetting getInternalSetting(EventControl ec) {
......
...@@ -408,6 +408,7 @@ public final class TypeLibrary { ...@@ -408,6 +408,7 @@ public final class TypeLibrary {
// Purpose of this method is to mark types that are reachable // Purpose of this method is to mark types that are reachable
// from registered event types. Those types that are not reachable can // from registered event types. Those types that are not reachable can
// safely be removed // safely be removed
// Returns true if type was removed
public boolean clearUnregistered() { public boolean clearUnregistered() {
Logger.log(LogTag.JFR_METADATA, LogLevel.TRACE, "Cleaning out obsolete metadata"); Logger.log(LogTag.JFR_METADATA, LogLevel.TRACE, "Cleaning out obsolete metadata");
List<Type> registered = new ArrayList<>(); List<Type> registered = new ArrayList<>();
......
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -43,6 +43,7 @@ import java.lang.reflect.Method; ...@@ -43,6 +43,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
...@@ -65,18 +66,18 @@ import jdk.jfr.internal.settings.ThresholdSetting; ...@@ -65,18 +66,18 @@ import jdk.jfr.internal.settings.ThresholdSetting;
public final class Utils { public final class Utils {
private static final Object flushObject = new Object();
private static final String INFINITY = "infinity"; private static final String INFINITY = "infinity";
private static Boolean SAVE_GENERATED;
public static final String EVENTS_PACKAGE_NAME = "jdk.jfr.events"; public static final String EVENTS_PACKAGE_NAME = "jdk.jfr.events";
public static final String INSTRUMENT_PACKAGE_NAME = "jdk.jfr.internal.instrument"; public static final String INSTRUMENT_PACKAGE_NAME = "jdk.jfr.internal.instrument";
public static final String HANDLERS_PACKAGE_NAME = "jdk.jfr.internal.handlers"; public static final String HANDLERS_PACKAGE_NAME = "jdk.jfr.internal.handlers";
public static final String REGISTER_EVENT = "registerEvent"; public static final String REGISTER_EVENT = "registerEvent";
public static final String ACCESS_FLIGHT_RECORDER = "accessFlightRecorder"; public static final String ACCESS_FLIGHT_RECORDER = "accessFlightRecorder";
private final static String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk."; private final static String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk.";
private static Boolean SAVE_GENERATED;
public static void checkAccessFlightRecorder() throws SecurityException { public static void checkAccessFlightRecorder() throws SecurityException {
SecurityManager sm = System.getSecurityManager(); SecurityManager sm = System.getSecurityManager();
if (sm != null) { if (sm != null) {
...@@ -551,4 +552,33 @@ public final class Utils { ...@@ -551,4 +552,33 @@ public final class Utils {
String idText = recording == null ? "" : "-id-" + Long.toString(recording.getId()); String idText = recording == null ? "" : "-id-" + Long.toString(recording.getId());
return "hotspot-" + "pid-" + pid + idText + "-" + date + ".jfr"; return "hotspot-" + "pid-" + pid + idText + "-" + date + ".jfr";
} }
public static void takeNap(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
// ok
}
}
public static void notifyFlush() {
synchronized (flushObject) {
flushObject.notifyAll();
}
}
public static void waitFlush(long timeOut) {
synchronized (flushObject) {
try {
flushObject.wait(timeOut);
} catch (InterruptedException e) {
// OK
}
}
}
public static long timeToNanos(Instant timestamp) {
return timestamp.getEpochSecond() * 1_000_000_000L + timestamp.getNano();
}
} }
/*
* Copyright (c) 2019, 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.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import jdk.jfr.consumer.EventStream;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.SecuritySupport;
/*
* Purpose of this class is to simplify the implementation of
* an event stream.
*/
abstract class AbstractEventStream implements EventStream {
private final static AtomicLong counter = new AtomicLong(1);
private final Object terminated = new Object();
private final boolean active;
private final Runnable flushOperation = () -> dispatcher().runFlushActions();
private final AccessControlContext accessControllerContext;
private final StreamConfiguration configuration = new StreamConfiguration();
private volatile Thread thread;
private Dispatcher dispatcher;
private volatile boolean closed;
AbstractEventStream(AccessControlContext acc, boolean active) throws IOException {
this.accessControllerContext = Objects.requireNonNull(acc);
this.active = active;
}
@Override
abstract public void start();
@Override
abstract public void startAsync();
@Override
abstract public void close();
protected final Dispatcher dispatcher() {
if (configuration.hasChanged()) {
synchronized (configuration) {
dispatcher = new Dispatcher(configuration);
}
}
return dispatcher;
}
@Override
public final void setOrdered(boolean ordered) {
configuration.setOrdered(ordered);
}
@Override
public final void setReuse(boolean reuse) {
configuration.setReuse(reuse);
}
@Override
public final void setStartTime(Instant startTime) {
Objects.nonNull(startTime);
synchronized (configuration) {
if (configuration.started) {
throw new IllegalStateException("Stream is already started");
}
if (startTime.isBefore(Instant.EPOCH)) {
startTime = Instant.EPOCH;
}
configuration.setStartTime(startTime);
}
}
@Override
public final void setEndTime(Instant endTime) {
Objects.requireNonNull(endTime);
synchronized (configuration) {
if (configuration.started) {
throw new IllegalStateException("Stream is already started");
}
configuration.setEndTime(endTime);
}
}
@Override
public final void onEvent(Consumer<RecordedEvent> action) {
Objects.requireNonNull(action);
configuration.addEventAction(action);
}
@Override
public final void onEvent(String eventName, Consumer<RecordedEvent> action) {
Objects.requireNonNull(eventName);
Objects.requireNonNull(action);
configuration.addEventAction(eventName, action);
}
@Override
public final void onFlush(Runnable action) {
Objects.requireNonNull(action);
configuration.addFlushAction(action);
}
@Override
public final void onClose(Runnable action) {
Objects.requireNonNull(action);
configuration.addCloseAction(action);
}
@Override
public final void onError(Consumer<Throwable> action) {
Objects.requireNonNull(action);
configuration.addErrorAction(action);
}
@Override
public final boolean remove(Object action) {
Objects.requireNonNull(action);
return configuration.remove(action);
}
@Override
public final void awaitTermination() throws InterruptedException {
awaitTermination(Duration.ofMillis(0));
}
@Override
public final void awaitTermination(Duration timeout) throws InterruptedException {
Objects.requireNonNull(timeout);
if (timeout.isNegative()) {
throw new IllegalArgumentException("timeout value is negative");
}
long base = System.currentTimeMillis();
long now = 0;
long millis;
try {
millis = Math.multiplyExact(timeout.getSeconds(), 1000);
} catch (ArithmeticException a) {
millis = Long.MAX_VALUE;
}
int nanos = timeout.getNano();
if (nanos == 0 && millis == 0) {
synchronized (terminated) {
while (!isClosed()) {
terminated.wait(0);
}
}
} else {
while (!isClosed()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
synchronized (terminated) {
terminated.wait(delay, nanos);
}
now = System.currentTimeMillis() - base;
}
}
}
protected abstract void process() throws IOException;
protected final void setClosed(boolean closed) {
this.closed = closed;
}
protected final boolean isClosed() {
return closed;
}
public final void startAsync(long startNanos) {
startInternal(startNanos);
Runnable r = () -> run(accessControllerContext);
thread = SecuritySupport.createThreadWitNoPermissions(nextThreadName(), r);
thread.start();
}
public final void start(long startNanos) {
startInternal(startNanos);
thread = Thread.currentThread();
run(accessControllerContext);
}
protected final Runnable getFlushOperation() {
return flushOperation;
}
private void startInternal(long startNanos) {
synchronized (configuration) {
if (configuration.started) {
throw new IllegalStateException("Event stream can only be started once");
}
if (active && configuration.startTime == null) {
configuration.setStartNanos(startNanos);
}
configuration.setStarted(true);
}
}
private void execute() {
try {
process();
} catch (IOException ioe) {
// This can happen if a chunk file is removed, or
// a file is access that has been closed
// This is "normal" behavior for streaming and the
// stream will be closed when this happens.
} finally {
Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "Execution of stream ended.");
try {
close();
} finally {
synchronized (terminated) {
terminated.notifyAll();
}
}
}
}
private void run(AccessControlContext accessControlContext) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
execute();
return null;
}
}, accessControlContext);
}
private String nextThreadName() {
counter.incrementAndGet();
return "JFR Event Stream " + counter;
}
}
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -25,48 +25,60 @@ ...@@ -25,48 +25,60 @@
package jdk.jfr.internal.consumer; package jdk.jfr.internal.consumer;
import java.io.DataInput;
import java.io.IOException; import java.io.IOException;
import jdk.jfr.internal.LogLevel; import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag; import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger; import jdk.jfr.internal.Logger;
import jdk.jfr.internal.MetadataDescriptor; import jdk.jfr.internal.MetadataDescriptor;
import jdk.jfr.internal.Utils;
public final class ChunkHeader { public final class ChunkHeader {
private static final long HEADER_SIZE = 68;
private static final byte UPDATING_CHUNK_HEADER = (byte) 255;
private static final long CHUNK_SIZE_POSITION = 8;
private static final long DURATION_NANOS_POSITION = 40;
private static final long FILE_STATE_POSITION = 64;
private static final long METADATA_TYPE_ID = 0; private static final long METADATA_TYPE_ID = 0;
private static final byte[] FILE_MAGIC = { 'F', 'L', 'R', '\0' }; private static final byte[] FILE_MAGIC = { 'F', 'L', 'R', '\0' };
private final short major; private final short major;
private final short minor; private final short minor;
private final long chunkSize;
private final long chunkStartTicks; private final long chunkStartTicks;
private final long ticksPerSecond; private final long ticksPerSecond;
private final long chunkStartNanos; private final long chunkStartNanos;
private final long metadataPosition;
// private final long absoluteInitialConstantPoolPosition;
private final long absoluteChunkEnd;
private final long absoluteEventStart;
private final long absoluteChunkStart; private final long absoluteChunkStart;
private final boolean lastChunk;
private final RecordingInput input; private final RecordingInput input;
private final long durationNanos;
private final long id; private final long id;
private long constantPoolPosition; private long absoluteEventStart;
private long chunkSize = 0;
private long constantPoolPosition = 0;
private long metadataPosition = 0;
private long durationNanos;
private long absoluteChunkEnd;
private boolean isFinished;
private boolean finished;
public ChunkHeader(RecordingInput input) throws IOException { public ChunkHeader(RecordingInput input) throws IOException {
this(input, 0, 0); this(input, 0, 0);
} }
private ChunkHeader(RecordingInput input, long absoluteChunkStart, long id) throws IOException { private ChunkHeader(RecordingInput input, long absoluteChunkStart, long id) throws IOException {
this.absoluteChunkStart = absoluteChunkStart;
this.absoluteEventStart = absoluteChunkStart + HEADER_SIZE;
if (input.getFileSize() < HEADER_SIZE) {
throw new IOException("Not a complete Chunk header");
}
input.setValidSize(absoluteChunkStart + HEADER_SIZE);
input.position(absoluteChunkStart); input.position(absoluteChunkStart);
if (input.position() >= input.size()) { if (input.position() >= input.size()) {
throw new IOException("Chunk contains no data"); throw new IOException("Chunk contains no data");
} }
verifyMagic(input); verifyMagic(input);
this.input = input; this.input = input;
this.id = id; this.id = id;
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk " + id); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: " + id);
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: file=" + input.getFilename());
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startPosition=" + absoluteChunkStart); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startPosition=" + absoluteChunkStart);
major = input.readRawShort(); major = input.readRawShort();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: major=" + major); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: major=" + major);
...@@ -75,11 +87,11 @@ public final class ChunkHeader { ...@@ -75,11 +87,11 @@ public final class ChunkHeader {
if (major != 1 && major != 2) { if (major != 1 && major != 2) {
throw new IOException("File version " + major + "." + minor + ". Only Flight Recorder files of version 1.x and 2.x can be read by this JDK."); throw new IOException("File version " + major + "." + minor + ". Only Flight Recorder files of version 1.x and 2.x can be read by this JDK.");
} }
chunkSize = input.readRawLong(); input.readRawLong(); // chunk size
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: chunkSize=" + chunkSize); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: chunkSize=" + chunkSize);
this.constantPoolPosition = input.readRawLong(); input.readRawLong(); // constant pool position
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: constantPoolPosition=" + constantPoolPosition); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: constantPoolPosition=" + constantPoolPosition);
metadataPosition = input.readRawLong(); input.readRawLong(); // metadata position
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: metadataPosition=" + metadataPosition); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: metadataPosition=" + metadataPosition);
chunkStartNanos = input.readRawLong(); // nanos since epoch chunkStartNanos = input.readRawLong(); // nanos since epoch
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startNanos=" + chunkStartNanos); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startNanos=" + chunkStartNanos);
...@@ -91,21 +103,98 @@ public final class ChunkHeader { ...@@ -91,21 +103,98 @@ public final class ChunkHeader {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: ticksPerSecond=" + ticksPerSecond); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: ticksPerSecond=" + ticksPerSecond);
input.readRawInt(); // features, not used input.readRawInt(); // features, not used
// set up boundaries refresh();
this.absoluteChunkStart = absoluteChunkStart;
absoluteChunkEnd = absoluteChunkStart + chunkSize;
lastChunk = input.size() == absoluteChunkEnd;
absoluteEventStart = input.position();
// read metadata
input.position(absoluteEventStart); input.position(absoluteEventStart);
} }
void refresh() throws IOException {
while (true) {
byte fileState1;
input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
while ((fileState1 = input.readPhysicalByte()) == UPDATING_CHUNK_HEADER) {
Utils.takeNap(1);
input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
}
input.positionPhysical(absoluteChunkStart + CHUNK_SIZE_POSITION);
long chunkSize = input.readPhysicalLong();
long constantPoolPosition = input.readPhysicalLong();
long metadataPosition = input.readPhysicalLong();
input.positionPhysical(absoluteChunkStart + DURATION_NANOS_POSITION);
long durationNanos = input.readPhysicalLong();
input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
byte fileState2 = input.readPhysicalByte();
if (fileState1 == fileState2) { // valid header
finished = fileState1 == 0;
if (metadataPosition != 0) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Setting input size to " + (absoluteChunkStart + chunkSize));
if (finished) {
// This assumes that the whole recording
// is finished if the first chunk is.
// This is a limitation we may want to
// remove, but greatly improves performance as
// data can be read across chunk boundaries
// of multi-chunk files and only once.
input.setValidSize(input.getFileSize());
} else {
input.setValidSize(absoluteChunkStart + chunkSize);
}
this.chunkSize = chunkSize;
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: chunkSize=" + chunkSize);
this.constantPoolPosition = constantPoolPosition;
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: constantPoolPosition=" + constantPoolPosition);
this.metadataPosition = metadataPosition;
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: metadataPosition=" + metadataPosition);
this.durationNanos = durationNanos;
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: durationNanos =" + durationNanos);
isFinished = fileState2 == 0;
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: generation=" + fileState2);
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: finished=" + isFinished);
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: fileSize=" + input.size());
absoluteChunkEnd = absoluteChunkStart + chunkSize;
return;
}
}
}
}
public void awaitFinished() throws IOException {
if (finished) {
return;
}
long pos = input.position();
try {
input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
while (true) {
byte filestate = input.readPhysicalByte();
if (filestate == 0) {
finished = true;
return;
}
Utils.takeNap(1);
}
} finally {
input.position(pos);
}
}
public boolean isLastChunk() throws IOException {
awaitFinished();
// streaming files only have one chunk
return input.getFileSize() == absoluteChunkEnd;
}
public boolean isFinished() throws IOException {
return isFinished;
}
public ChunkHeader nextHeader() throws IOException { public ChunkHeader nextHeader() throws IOException {
return new ChunkHeader(input, absoluteChunkEnd, id + 1); return new ChunkHeader(input, absoluteChunkEnd, id + 1);
} }
public MetadataDescriptor readMetadata() throws IOException { public MetadataDescriptor readMetadata() throws IOException {
return readMetadata(null);
}
public MetadataDescriptor readMetadata(MetadataDescriptor previous) throws IOException {
input.position(absoluteChunkStart + metadataPosition); input.position(absoluteChunkStart + metadataPosition);
input.readInt(); // size input.readInt(); // size
long id = input.readLong(); // event type id long id = input.readLong(); // event type id
...@@ -115,15 +204,15 @@ public final class ChunkHeader { ...@@ -115,15 +204,15 @@ public final class ChunkHeader {
input.readLong(); // start time input.readLong(); // start time
input.readLong(); // duration input.readLong(); // duration
long metadataId = input.readLong(); long metadataId = input.readLong();
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "Metadata id=" + metadataId); if (previous != null && metadataId == previous.metadataId) {
// No need to read if metadataId == lastMetadataId, but we return previous;
// do it for verification purposes. }
return MetadataDescriptor.read(input); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "New metadata id = " + metadataId);
MetadataDescriptor m = MetadataDescriptor.read(input);
m.metadataId = metadataId;
return m;
} }
public boolean isLastChunk() {
return lastChunk;
}
public short getMajor() { public short getMajor() {
return major; return major;
...@@ -137,13 +226,22 @@ public final class ChunkHeader { ...@@ -137,13 +226,22 @@ public final class ChunkHeader {
return absoluteChunkStart; return absoluteChunkStart;
} }
public long getAbsoluteEventStart() {
return absoluteEventStart;
}
public long getConstantPoolPosition() { public long getConstantPoolPosition() {
return constantPoolPosition; return constantPoolPosition;
} }
public long getMetataPosition() {
return metadataPosition;
}
public long getStartTicks() { public long getStartTicks() {
return chunkStartTicks; return chunkStartTicks;
} }
public long getChunkSize() {
return chunkSize;
}
public double getTicksPerSecond() { public double getTicksPerSecond() {
return ticksPerSecond; return ticksPerSecond;
...@@ -169,7 +267,7 @@ public final class ChunkHeader { ...@@ -169,7 +267,7 @@ public final class ChunkHeader {
return input; return input;
} }
private static void verifyMagic(DataInput input) throws IOException { private static void verifyMagic(RecordingInput input) throws IOException {
for (byte c : FILE_MAGIC) { for (byte c : FILE_MAGIC) {
if (input.readByte() != c) { if (input.readByte() != c) {
throw new IOException("Not a Flight Recorder file"); throw new IOException("Not a Flight Recorder file");
...@@ -181,4 +279,7 @@ public final class ChunkHeader { ...@@ -181,4 +279,7 @@ public final class ChunkHeader {
return absoluteEventStart; return absoluteEventStart;
} }
static long headerSize() {
return HEADER_SIZE;
}
} }
/*
* Copyright (c) 2016, 2019, 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.Collection;
import java.util.List;
import java.util.StringJoiner;
import jdk.jfr.EventType;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedObject;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
import jdk.jfr.internal.LongMap;
import jdk.jfr.internal.MetadataDescriptor;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Utils;
/**
* Parses a chunk.
*
*/
public final class ChunkParser {
static final class ParserConfiguration {
private final boolean reuse;
private final boolean ordered;
private final ParserFilter eventFilter;
long filterStart;
long filterEnd;
ParserConfiguration(long filterStart, long filterEnd, boolean reuse, boolean ordered, ParserFilter filter) {
this.filterStart = filterStart;
this.filterEnd = filterEnd;
this.reuse = reuse;
this.ordered = ordered;
this.eventFilter = filter;
}
public ParserConfiguration() {
this(0, Long.MAX_VALUE, false, false, ParserFilter.ACCEPT_ALL);
}
public boolean isOrdered() {
return ordered;
}
}
private enum CheckPointType {
// Checkpoint that finishes a flush segment
FLUSH(1),
// Checkpoint contains chunk header information in the first pool
CHUNK_HEADER(2),
// Checkpoint contains only statics that will not change from chunk to chunk
STATICS(4),
// Checkpoint contains thread related information
THREAD(8);
private final int mask;
private CheckPointType(int mask) {
this.mask = mask;
}
private boolean is(int flags) {
return (mask & flags) != 0;
}
}
private static final long CONSTANT_POOL_TYPE_ID = 1;
private static final String CHUNKHEADER = "jdk.types.ChunkHeader";
private final RecordingInput input;
private final ChunkHeader chunkHeader;
private final MetadataDescriptor metadata;
private final TimeConverter timeConverter;
private final MetadataDescriptor previousMetadata;
private final LongMap<ConstantLookup> constantLookups;
private LongMap<Type> typeMap;
private LongMap<Parser> parsers;
private boolean chunkFinished;
private Runnable flushOperation;
private ParserConfiguration configuration;
public ChunkParser(RecordingInput input) throws IOException {
this(input, new ParserConfiguration());
}
ChunkParser(RecordingInput input, ParserConfiguration pc) throws IOException {
this(new ChunkHeader(input), null, pc);
}
private ChunkParser(ChunkParser previous) throws IOException {
this(new ChunkHeader(previous.input), previous, new ParserConfiguration());
}
private ChunkParser(ChunkHeader header, ChunkParser previous, ParserConfiguration pc) throws IOException {
this.configuration = pc;
this.input = header.getInput();
this.chunkHeader = header;
if (previous == null) {
this.constantLookups = new LongMap<>();
this.previousMetadata = null;
} else {
this.constantLookups = previous.constantLookups;
this.previousMetadata = previous.metadata;
this.configuration = previous.configuration;
}
this.metadata = header.readMetadata(previousMetadata);
this.timeConverter = new TimeConverter(chunkHeader, metadata.getGMTOffset());
if (metadata != previousMetadata) {
ParserFactory factory = new ParserFactory(metadata, constantLookups, timeConverter);
parsers = factory.getParsers();
typeMap = factory.getTypeMap();
updateConfiguration();
} else {
parsers = previous.parsers;
typeMap = previous.typeMap;
}
constantLookups.forEach(c -> c.newPool());
fillConstantPools(0);
constantLookups.forEach(c -> c.getLatestPool().setResolving());
constantLookups.forEach(c -> c.getLatestPool().resolve());
constantLookups.forEach(c -> c.getLatestPool().setResolved());
input.position(chunkHeader.getEventStart());
}
public ChunkParser nextChunkParser() throws IOException {
return new ChunkParser(chunkHeader.nextHeader(), this, configuration);
}
private void updateConfiguration() {
updateConfiguration(configuration, false);
}
void updateConfiguration(ParserConfiguration configuration, boolean resetEventCache) {
this.configuration = configuration;
parsers.forEach(p -> {
if (p instanceof EventParser) {
EventParser ep = (EventParser) p;
if (resetEventCache) {
ep.resetCache();
}
String name = ep.getEventType().getName();
ep.setOrdered(configuration.ordered);
ep.setReuse(configuration.reuse);
ep.setFilterStart(configuration.filterStart);
ep.setFilterEnd(configuration.filterEnd);
long threshold = configuration.eventFilter.getThreshold(name);
if (threshold >= 0) {
ep.setEnabled(true);
ep.setThresholdNanos(threshold);
} else {
ep.setEnabled(false);
ep.setThresholdNanos(Long.MAX_VALUE);
}
}
});
}
/**
* Reads an event and returns null when segment or chunk ends.
*
* @param awaitNewEvents wait for new data.
*/
RecordedEvent readStreamingEvent(boolean awaitNewEvents) throws IOException {
long absoluteChunkEnd = chunkHeader.getEnd();
while (true) {
RecordedEvent event = readEvent();
if (event != null) {
return event;
}
if (!awaitNewEvents) {
return null;
}
long lastValid = absoluteChunkEnd;
long metadataPoistion = chunkHeader.getMetataPosition();
long contantPosition = chunkHeader.getConstantPoolPosition();
chunkFinished = awaitUpdatedHeader(absoluteChunkEnd);
if (chunkFinished) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "At chunk end");
return null;
}
absoluteChunkEnd = chunkHeader.getEnd();
// Read metadata and constant pools for the next segment
if (chunkHeader.getMetataPosition() != metadataPoistion) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Found new metadata in chunk. Rebuilding types and parsers");
MetadataDescriptor metadata = chunkHeader.readMetadata(previousMetadata);
ParserFactory factory = new ParserFactory(metadata, constantLookups, timeConverter);
parsers = factory.getParsers();
typeMap = factory.getTypeMap();
updateConfiguration();;
}
if (contantPosition != chunkHeader.getConstantPoolPosition()) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Found new constant pool data. Filling up pools with new values");
constantLookups.forEach(c -> c.getLatestPool().setAllResolved(false));
fillConstantPools(contantPosition + chunkHeader.getAbsoluteChunkStart());
constantLookups.forEach(c -> c.getLatestPool().setResolving());
constantLookups.forEach(c -> c.getLatestPool().resolve());
constantLookups.forEach(c -> c.getLatestPool().setResolved());
}
input.position(lastValid);
}
}
/**
* Reads an event and returns null when the chunk ends
*/
public RecordedEvent readEvent() throws IOException {
long absoluteChunkEnd = chunkHeader.getEnd();
while (input.position() < absoluteChunkEnd) {
long pos = input.position();
int size = input.readInt();
if (size == 0) {
throw new IOException("Event can't have zero size");
}
long typeId = input.readLong();
Parser p = parsers.get(typeId);
if (p instanceof EventParser) {
// Fast path
EventParser ep = (EventParser) p;
RecordedEvent event = ep.parse(input);
if (event != null) {
input.position(pos + size);
return event;
}
// Not accepted by filter
} else {
if (typeId == 1) { // checkpoint event
if (flushOperation != null) {
parseCheckpoint();
}
} else {
if (typeId != 0) { // Not metadata event
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Unknown event type " + typeId);
}
}
}
input.position(pos + size);
}
return null;
}
private void parseCheckpoint() throws IOException {
// Content has been parsed previously. This
// is to trigger flush
input.readLong(); // timestamp
input.readLong(); // duration
input.readLong(); // delta
byte typeFlags = input.readByte();
if (CheckPointType.FLUSH.is(typeFlags)) {
flushOperation.run();
}
}
private boolean awaitUpdatedHeader(long absoluteChunkEnd) throws IOException {
if (Logger.shouldLog(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO)) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Waiting for more data (streaming). Read so far: " + chunkHeader.getChunkSize() + " bytes");
}
while (true) {
chunkHeader.refresh();
if (absoluteChunkEnd != chunkHeader.getEnd()) {
return false;
}
if (chunkHeader.isFinished()) {
return true;
}
Utils.waitFlush(1000);
}
}
private void fillConstantPools(long abortCP) throws IOException {
long thisCP = chunkHeader.getConstantPoolPosition() + chunkHeader.getAbsoluteChunkStart();
long lastCP = -1;
long delta = -1;
boolean logTrace = Logger.shouldLog(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE);
while (thisCP != abortCP && delta != 0) {
input.position(thisCP);
lastCP = thisCP;
int size = input.readInt(); // size
long typeId = input.readLong();
if (typeId != CONSTANT_POOL_TYPE_ID) {
throw new IOException("Expected check point event (id = 1) at position " + lastCP + ", but found type id = " + typeId);
}
input.readLong(); // timestamp
input.readLong(); // duration
delta = input.readLong();
thisCP += delta;
boolean flush = input.readBoolean();
int poolCount = input.readInt();
final long logLastCP = lastCP;
final long logDelta = delta;
if (logTrace) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, () -> {
return "New constant pool: startPosition=" + logLastCP + ", size=" + size + ", deltaToNext=" + logDelta + ", flush=" + flush + ", poolCount=" + poolCount;
});
}
for (int i = 0; i < poolCount; i++) {
long id = input.readLong(); // type id
ConstantLookup lookup = constantLookups.get(id);
Type type = typeMap.get(id);
if (lookup == null) {
if (type == null) {
throw new IOException(
"Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + lastCP + ", " + lastCP + size + "]");
}
if (type.getName() != CHUNKHEADER) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Found constant pool(" + id + ") that is never used");
}
ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
lookup = new ConstantLookup(pool, type);
constantLookups.put(type.getId(), lookup);
}
Parser parser = parsers.get(id);
if (parser == null) {
throw new IOException("Could not find constant pool type with id = " + id);
}
try {
int count = input.readInt();
if (count == 0) {
throw new InternalError("Pool " + type.getName() + " must contain at least one element ");
}
if (logTrace) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "Constant Pool " + i + ": " + type.getName());
}
for (int j = 0; j < count; j++) {
long key = input.readLong();
Object resolved = lookup.getPreviousResolved(key);
if (resolved == null) {
Object v = parser.parse(input);
logConstant(key, v, false);
lookup.getLatestPool().put(key, v);
} else {
parser.skip(input);
logConstant(key, resolved, true);
lookup.getLatestPool().putResolved(key, resolved);
}
}
} catch (Exception e) {
throw new IOException("Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + lastCP + ", " + lastCP + size + "]",
e);
}
}
if (input.position() != lastCP + size) {
throw new IOException("Size of check point event doesn't match content");
}
}
}
private void logConstant(long key, Object v, boolean preresolved) {
if (!Logger.shouldLog(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE)) {
return;
}
String valueText;
if (v.getClass().isArray()) {
Object[] array = (Object[]) v;
StringJoiner sj = new StringJoiner(", ", "{", "}");
for (int i = 0; i < array.length; i++) {
sj.add(textify(array[i]));
}
valueText = sj.toString();
} else {
valueText = textify(v);
}
String suffix = preresolved ? " (presolved)" :"";
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "Constant: " + key + " = " + valueText + suffix);
}
private String textify(Object o) {
if (o == null) { // should not happen
return "null";
}
if (o instanceof String) {
return "\"" + String.valueOf(o) + "\"";
}
if (o instanceof RecordedObject) {
return o.getClass().getName();
}
if (o.getClass().isArray()) {
Object[] array = (Object[]) o;
if (array.length > 0) {
return textify(array[0]) + "[]"; // can it be recursive?
}
}
return String.valueOf(o);
}
private String getName(long id) {
Type type = typeMap.get(id);
return type == null ? ("unknown(" + id + ")") : type.getName();
}
public Collection<Type> getTypes() {
return metadata.getTypes();
}
public List<EventType> getEventTypes() {
return metadata.getEventTypes();
}
public boolean isLastChunk() throws IOException {
return chunkHeader.isLastChunk();
}
ChunkParser newChunkParser() throws IOException {
return new ChunkParser(this);
}
public boolean isChunkFinished() {
return chunkFinished;
}
public void setFlushOperation(Runnable flushOperation) {
this.flushOperation = flushOperation;
}
public long getChunkDuration() {
return chunkHeader.getDurationNanos();
}
public long getStartNanos() {
return chunkHeader.getStartNanos();
}
}
/*
* Copyright (c) 2019, 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.internal.consumer;
import jdk.jfr.internal.Type;
final class ConstantLookup {
private final Type type;
private ConstantMap current;
private ConstantMap previous = ConstantMap.EMPTY;
ConstantLookup(ConstantMap current, Type type) {
this.current = current;
this.type = type;
}
public Type getType() {
return type;
}
public ConstantMap getLatestPool() {
return current;
}
public void newPool() {
previous = current;
current = new ConstantMap(current.factory, current.name);
}
public Object getPreviousResolved(long key) {
return previous.getResolved(key);
}
public Object getCurrentResolved(long key) {
return current.getResolved(key);
}
public Object getCurrent(long key) {
return current.get(key);
}
}
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -23,10 +23,9 @@ ...@@ -23,10 +23,9 @@
* questions. * questions.
*/ */
package jdk.jfr.consumer; package jdk.jfr.internal.consumer;
import java.util.ArrayList; import jdk.jfr.internal.LongMap;
import java.util.List;
/** /**
* Holds mapping between a set of keys and their corresponding object. * Holds mapping between a set of keys and their corresponding object.
...@@ -35,6 +34,15 @@ import java.util.List; ...@@ -35,6 +34,15 @@ import java.util.List;
* {@link ObjectFactory} can be supplied which will instantiate a typed object. * {@link ObjectFactory} can be supplied which will instantiate a typed object.
*/ */
final class ConstantMap { 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 final static class Reference { private final static class Reference {
private final long key; private final long key;
private final ConstantMap pool; private final ConstantMap pool;
...@@ -47,18 +55,28 @@ final class ConstantMap { ...@@ -47,18 +55,28 @@ final class ConstantMap {
Object resolve() { Object resolve() {
return pool.get(key); return pool.get(key);
} }
public String toString() {
return "ref: " + pool.name + "[" + key + "]";
}
} }
private final ObjectFactory<?> factory; final ObjectFactory<?> factory;
final String name;
private final LongMap<Object> objects; private final LongMap<Object> objects;
private LongMap<Boolean> isResolving; private boolean resolving;
private boolean allResolved; private boolean allResolved;
private String name;
private ConstantMap() {
this(null, "<empty>");
allResolved = true;
}
ConstantMap(ObjectFactory<?> factory, String name) { ConstantMap(ObjectFactory<?> factory, String name) {
this.name = name; this.name = name;
this.objects = new LongMap<>(); this.objects = new LongMap<>(2);
this.factory = factory; this.factory = factory;
} }
...@@ -68,26 +86,42 @@ final class ConstantMap { ...@@ -68,26 +86,42 @@ final class ConstantMap {
return objects.get(id); return objects.get(id);
} }
// referenced from a pool, deal with this later // referenced from a pool, deal with this later
if (isResolving == null) { if (!resolving) {
return new Reference(this, id); return new Reference(this, id);
} }
Boolean beingResolved = isResolving.get(id); // should always have a value
Object value = objects.get(id);
if (value == null) {
// unless is 0 which is used to represent null
if (id == 0) {
return null;
}
throw new InternalError("Missing object id=" + id + " in pool " + name + ". All ids should reference object");
}
// we are resolved (but not the whole pool) // id is resolved (but not the whole pool)
if (Boolean.FALSE.equals(beingResolved)) { if (objects.isSetId(id, RESOLUTION_FINISHED)) {
return objects.get(id); return value;
} }
// resolving ourself, abort to avoid infinite recursion // resolving ourself, abort to avoid infinite recursion
if (Boolean.TRUE.equals(beingResolved)) { if (objects.isSetId(id, RESOLUTION_STARTED)) {
return null; return null;
} }
// resolve me! // mark id as STARTED if we should
isResolving.put(id, Boolean.TRUE); // come back during object resolution
Object resolved = resolve(objects.get(id)); objects.setId(id, RESOLUTION_STARTED);
isResolving.put(id, Boolean.FALSE);
// resolve object!
Object resolved = resolve(value);
// mark id as FINISHED
objects.setId(id, RESOLUTION_FINISHED);
// if a factory exists, convert to RecordedThread.
// RecordedClass etc. and store back results
if (factory != null) { if (factory != null) {
Object factorized = factory.createObject(id, resolved); Object factorized = factory.createObject(id, resolved);
objects.put(id, factorized); objects.put(id, factorized);
...@@ -105,7 +139,8 @@ final class ConstantMap { ...@@ -105,7 +139,8 @@ final class ConstantMap {
if (o != null && o.getClass().isArray()) { if (o != null && o.getClass().isArray()) {
final Object[] array = (Object[]) o; final Object[] array = (Object[]) o;
for (int i = 0; i < array.length; i++) { for (int i = 0; i < array.length; i++) {
array[i] = resolve(array[i]); Object element = array[i];
array[i] = resolve(element);
} }
return array; return array;
} }
...@@ -113,27 +148,37 @@ final class ConstantMap { ...@@ -113,27 +148,37 @@ final class ConstantMap {
} }
public void resolve() { public void resolve() {
List<Long> keyList = new ArrayList<>(); objects.forEachKey(k -> get(k));
objects.keys().forEachRemaining(keyList::add);
for (Long l : keyList) {
get(l);
}
} }
public void put(long key, Object value) { public void put(long key, Object value) {
objects.put(key, value); objects.put(key, value);
} }
public void setIsResolving() { public void setResolving() {
isResolving = new LongMap<>(); resolving = true;
allResolved = false;
} }
public void setResolved() { public void setResolved() {
allResolved = true; allResolved = true;
isResolving = null; // pool finished, release memory resolving = false;
} }
public String getName() { public String getName() {
return name; return name;
} }
public Object getResolved(long id) {
return objects.get(id);
}
public void putResolved(long id, Object object) {
objects.put(id, object);
objects.setId(id, RESOLUTION_FINISHED);
}
public void setAllResolved(boolean allResolved) {
this.allResolved = allResolved;
}
} }
/*
* Copyright (c) 2019, 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.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import jdk.jfr.EventType;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.internal.LongMap;
import jdk.jfr.internal.consumer.ChunkParser.ParserConfiguration;
final class Dispatcher {
final static class EventDispatcher {
private final static EventDispatcher[] NO_DISPATCHERS = new EventDispatcher[0];
private final String eventName;
private final Consumer<RecordedEvent> action;
public EventDispatcher(String eventName, Consumer<RecordedEvent> action) {
this.eventName = eventName;
this.action = action;
}
private void offer(RecordedEvent event) {
action.accept(event);
}
private boolean accepts(EventType eventType) {
return (eventName == null || eventType.getName().equals(eventName));
}
public Consumer<RecordedEvent> getAction() {
return action;
}
}
private final Consumer<Throwable>[] errorActions;
private final Runnable[] flushActions;
private final Runnable[] closeActions;
private final EventDispatcher[] dispatchers;
private final LongMap<EventDispatcher[]> dispatcherLookup = new LongMap<>();
final ParserConfiguration parserConfiguration;
final Instant startTime;
final Instant endTime;
final long startNanos;
final long endNanos;
// Cache
private EventType cacheEventType;
private EventDispatcher[] cacheDispatchers;
@SuppressWarnings({"rawtypes", "unchecked"})
public Dispatcher(StreamConfiguration c) {
this.flushActions = c.flushActions.toArray(new Runnable[0]);
this.closeActions = c.closeActions.toArray(new Runnable[0]);
this.errorActions = c.errorActions.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.startTime = c.startTime;
this.endTime = c.endTime;
this.startNanos = c.startNanos;
this.endNanos = c.endNanos;
}
public void runFlushActions() {
Runnable[] flushActions = this.flushActions;
for (int i = 0; i < flushActions.length; i++) {
try {
flushActions[i].run();
} catch (Exception e) {
handleError(e);
}
}
}
public void runCloseActions() {
Runnable[] closeActions = this.closeActions;
for (int i = 0; i < closeActions.length; i++) {
try {
closeActions[i].run();
} catch (Exception e) {
handleError(e);
}
}
}
private static ParserFilter buildFilter(EventDispatcher[] dispatchers) {
ParserFilter ef = new ParserFilter();
for (EventDispatcher ed : dispatchers) {
String name = ed.eventName;
if (name == null) {
return ParserFilter.ACCEPT_ALL;
}
ef.setThreshold(name, 0);
}
return ef;
}
void dispatch(RecordedEvent event) {
EventType type = event.getEventType();
EventDispatcher[] dispatchers = null;
if (type == cacheEventType) {
dispatchers = cacheDispatchers;
} else {
dispatchers = dispatcherLookup.get(type.getId());
if (dispatchers == null) {
List<EventDispatcher> list = new ArrayList<>();
for (EventDispatcher e : this.dispatchers) {
if (e.accepts(type)) {
list.add(e);
}
}
dispatchers = list.isEmpty() ? EventDispatcher.NO_DISPATCHERS : list.toArray(new EventDispatcher[0]);
dispatcherLookup.put(type.getId(), dispatchers);
}
cacheDispatchers = dispatchers;
}
// Expected behavior if exception occurs in onEvent:
//
// Synchronous:
// - User has added onError action:
// Catch exception, call onError and continue with next event
// Let Errors propagate to caller of EventStream::start
// - Default action
// Catch exception, e.printStackTrace() and continue with next event
// Let Errors propagate to caller of EventStream::start
//
// Asynchronous
// - User has added onError action
// Catch exception, call onError and continue with next event
// Let Errors propagate, shutdown thread and stream
// - Default action
// Catch exception, e.printStackTrace() and continue with next event
// Let Errors propagate and shutdown thread and stream
//
for (int i = 0; i < dispatchers.length; i++) {
try {
dispatchers[i].offer(event);
} catch (Exception e) {
handleError(e);
}
}
}
private void handleError(Throwable e) {
Consumer<?>[] consumers = this.errorActions;
if (consumers.length == 0) {
defaultErrorHandler(e);
return;
}
for (int i = 0; i < consumers.length; i++) {
@SuppressWarnings("unchecked")
Consumer<Throwable> consumer = (Consumer<Throwable>) consumers[i];
consumer.accept(e);
}
}
private void defaultErrorHandler(Throwable e) {
e.printStackTrace();
}
}
/*
* Copyright (c) 2019, 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.nio.file.Path;
import java.security.AccessControlContext;
import java.time.Instant;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.Utils;
import jdk.jfr.internal.consumer.ChunkParser.ParserConfiguration;
/**
* Implementation of an {@code EventStream}} that operates against a directory
* with chunk files.
*
*/
public final class EventDirectoryStream extends AbstractEventStream {
private final static Comparator<? super RecordedEvent> EVENT_COMPARATOR = JdkJfrConsumer.instance().eventComparator();
private final RepositoryFiles repositoryFiles;
private final boolean active;
private final FileAccess fileAccess;
private ChunkParser currentParser;
private long currentChunkStartNanos;
private RecordedEvent[] sortedCache;
private int threadExclusionLevel = 0;
public EventDirectoryStream(AccessControlContext acc, Path p, FileAccess fileAccess, boolean active) throws IOException {
super(acc, active);
this.fileAccess = Objects.requireNonNull(fileAccess);
this.active = active;
this.repositoryFiles = new RepositoryFiles(fileAccess, p);
}
@Override
public void close() {
setClosed(true);
dispatcher().runCloseActions();
repositoryFiles.close();
}
@Override
public void start() {
start(Utils.timeToNanos(Instant.now()));
}
@Override
public void startAsync() {
startAsync(Utils.timeToNanos(Instant.now()));
}
@Override
protected void process() throws IOException {
JVM jvm = JVM.getJVM();
Thread t = Thread.currentThread();
try {
if (jvm.isExcluded(t)) {
threadExclusionLevel++;
} else {
jvm.exclude(t);
}
processRecursionSafe();
} finally {
if (threadExclusionLevel > 0) {
threadExclusionLevel--;
} else {
jvm.include(t);
}
}
}
protected void processRecursionSafe() throws IOException {
Dispatcher disp = dispatcher();
Path path;
boolean validStartTime = active || disp.startTime != null;
if (validStartTime) {
path = repositoryFiles.firstPath(disp.startNanos);
} else {
path = repositoryFiles.lastPath();
}
if (path == null) { // closed
return;
}
currentChunkStartNanos = repositoryFiles.getTimestamp(path);
try (RecordingInput input = new RecordingInput(path.toFile(), fileAccess)) {
currentParser = new ChunkParser(input, disp.parserConfiguration);
long segmentStart = currentParser.getStartNanos() + currentParser.getChunkDuration();
long filterStart = validStartTime ? disp.startNanos : segmentStart;
long filterEnd = disp.endTime != null ? disp.endNanos: Long.MAX_VALUE;
while (!isClosed()) {
boolean awaitnewEvent = false;
while (!isClosed() && !currentParser.isChunkFinished()) {
disp = dispatcher();
ParserConfiguration pc = disp.parserConfiguration;
pc.filterStart = filterStart;
pc.filterEnd = filterEnd;
currentParser.updateConfiguration(pc, true);
currentParser.setFlushOperation(getFlushOperation());
if (pc.isOrdered()) {
awaitnewEvent = processOrdered(disp, awaitnewEvent);
} else {
awaitnewEvent = processUnordered(disp, awaitnewEvent);
}
if (currentParser.getStartNanos() + currentParser.getChunkDuration() > filterEnd) {
close();
return;
}
}
if (isClosed()) {
return;
}
long durationNanos = currentParser.getChunkDuration();
if (durationNanos == 0) {
// Avoid reading the same chunk again and again if
// duration is 0 ns
durationNanos++;
}
path = repositoryFiles.nextPath(currentChunkStartNanos + durationNanos);
if (path == null) {
return; // stream closed
}
currentChunkStartNanos = repositoryFiles.getTimestamp(path);
input.setFile(path);
currentParser = currentParser.newChunkParser();
// TODO: Optimization. No need filter when we reach new chunk
// Could set start = 0;
}
}
}
private boolean processOrdered(Dispatcher c, boolean awaitNewEvents) throws IOException {
if (sortedCache == null) {
sortedCache = new RecordedEvent[100_000];
}
int index = 0;
while (true) {
RecordedEvent e = currentParser.readStreamingEvent(awaitNewEvents);
if (e == null) {
// wait for new event with next call to
// readStreamingEvent()
awaitNewEvents = true;
break;
}
awaitNewEvents = false;
if (index == sortedCache.length) {
sortedCache = Arrays.copyOf(sortedCache, sortedCache.length * 2);
}
sortedCache[index++] = e;
}
// no events found
if (index == 0 && currentParser.isChunkFinished()) {
return awaitNewEvents;
}
// at least 2 events, sort them
if (index > 1) {
Arrays.sort(sortedCache, 0, index, EVENT_COMPARATOR);
}
for (int i = 0; i < index; i++) {
c.dispatch(sortedCache[i]);
}
return awaitNewEvents;
}
private boolean processUnordered(Dispatcher c, boolean awaitNewEvents) throws IOException {
while (true) {
RecordedEvent e = currentParser.readStreamingEvent(awaitNewEvents);
if (e == null) {
return true;
} else {
c.dispatch(e);
}
}
}
}
/* /*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -27,10 +27,12 @@ package jdk.jfr.internal.dcmd; ...@@ -27,10 +27,12 @@ package jdk.jfr.internal.dcmd;
import jdk.jfr.FlightRecorder;
import jdk.jfr.internal.LogLevel; import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag; import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger; import jdk.jfr.internal.Logger;
import jdk.jfr.internal.Options; import jdk.jfr.internal.Options;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.Repository; import jdk.jfr.internal.Repository;
import jdk.jfr.internal.SecuritySupport.SafePath; import jdk.jfr.internal.SecuritySupport.SafePath;
...@@ -101,6 +103,9 @@ final class DCmdConfigure extends AbstractDCmd { ...@@ -101,6 +103,9 @@ final class DCmdConfigure extends AbstractDCmd {
SafePath s = new SafePath(repositoryPath); SafePath s = new SafePath(repositoryPath);
Repository.getRepository().setBasePath(s); Repository.getRepository().setBasePath(s);
Logger.log(LogTag.JFR, LogLevel.INFO, "Base repository path set to " + repositoryPath); Logger.log(LogTag.JFR, LogLevel.INFO, "Base repository path set to " + repositoryPath);
if (FlightRecorder.isInitialized()) {
PrivateAccess.getInstance().getPlatformRecorder().rotateIfRecordingToDisk();;
}
} catch (Exception e) { } catch (Exception e) {
throw new DCmdException("Could not use " + repositoryPath + " as repository. " + e.getMessage(), e); throw new DCmdException("Could not use " + repositoryPath + " as repository. " + e.getMessage(), e);
} }
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册