From a279f1d6af5ed7ee1a46b14e88729b4f12f1eff8 Mon Sep 17 00:00:00 2001 From: Denghui Dong Date: Fri, 21 Feb 2020 14:33:25 +0800 Subject: [PATCH] [Backport] 8226511: Implement JFR Event Streaming Summary: Test Plan: jdk/jfr Reviewed-by: yuleil Issue: https://github.com/alibaba/dragonwell8/issues/112 --- src/share/classes/jdk/jfr/Recording.java | 30 ++ src/share/classes/jdk/jfr/conf/default.jfc | 30 ++ src/share/classes/jdk/jfr/conf/profile.jfc | 30 ++ .../classes/jdk/jfr/consumer/ChunkParser.java | 174 ------- .../classes/jdk/jfr/consumer/EventParser.java | 72 --- .../classes/jdk/jfr/consumer/EventStream.java | 364 ++++++++++++++ .../jdk/jfr/consumer/ObjectFactory.java | 85 ---- .../jdk/jfr/consumer/RecordedClass.java | 18 +- .../jdk/jfr/consumer/RecordedClassLoader.java | 19 +- .../jdk/jfr/consumer/RecordedEvent.java | 51 +- .../jdk/jfr/consumer/RecordedFrame.java | 18 +- .../jdk/jfr/consumer/RecordedMethod.java | 18 +- .../jdk/jfr/consumer/RecordedObject.java | 136 +++++- .../jdk/jfr/consumer/RecordedStackTrace.java | 18 +- .../jdk/jfr/consumer/RecordedThread.java | 20 +- .../jdk/jfr/consumer/RecordedThreadGroup.java | 20 +- .../jdk/jfr/consumer/RecordingFile.java | 63 ++- .../jdk/jfr/consumer/RecordingStream.java | 362 ++++++++++++++ .../jdk/jfr/events/ActiveRecordingEvent.java | 13 +- .../jdk/jfr/events/ActiveSettingEvent.java | 9 +- .../jdk/jfr/internal/EventControl.java | 76 +-- .../jfr/internal/EventInstrumentation.java | 130 +++-- .../classes/jdk/jfr/internal/EventWriter.java | 18 +- .../classes/jdk/jfr/internal/FilePurger.java | 73 +++ src/share/classes/jdk/jfr/internal/JVM.java | 41 +- .../classes/jdk/jfr/internal/LogTag.java | 14 +- .../classes/jdk/jfr/internal/LongMap.java | 257 ++++++++++ .../jdk/jfr/internal/MetadataDescriptor.java | 7 +- .../jdk/jfr/internal/MetadataReader.java | 9 +- .../jdk/jfr/internal/MetadataRepository.java | 22 +- .../jdk/jfr/internal/MetadataWriter.java | 8 +- .../jdk/jfr/internal/PlatformEventType.java | 4 +- .../jdk/jfr/internal/PlatformRecorder.java | 47 +- .../jdk/jfr/internal/PlatformRecording.java | 34 +- .../classes/jdk/jfr/internal/Repository.java | 16 +- .../jdk/jfr/internal/RepositoryChunk.java | 14 +- .../jdk/jfr/internal/RequestEngine.java | 42 +- .../jdk/jfr/internal/SecuritySupport.java | 56 ++- .../jdk/jfr/internal/SettingsManager.java | 23 +- .../classes/jdk/jfr/internal/TypeLibrary.java | 1 + src/share/classes/jdk/jfr/internal/Utils.java | 40 +- .../consumer/AbstractEventStream.java | 273 +++++++++++ .../jfr/internal/consumer/ChunkHeader.java | 163 +++++-- .../jfr/internal/consumer/ChunkParser.java | 451 ++++++++++++++++++ .../jfr/internal/consumer/ConstantLookup.java | 62 +++ .../{ => internal}/consumer/ConstantMap.java | 99 ++-- .../jdk/jfr/internal/consumer/Dispatcher.java | 188 ++++++++ .../consumer/EventDirectoryStream.java | 209 ++++++++ .../internal/consumer/EventFileStream.java | 147 ++++++ .../jfr/internal/consumer/EventParser.java | 198 ++++++++ .../jdk/jfr/internal/consumer/FileAccess.java | 73 +++ .../jfr/internal/consumer/JdkJfrConsumer.java | 101 ++++ .../jfr/internal/consumer/ObjectContext.java | 77 +++ .../jfr/internal/consumer/ObjectFactory.java | 153 ++++++ .../jfr/{ => internal}/consumer/Parser.java | 17 +- .../consumer/ParserFactory.java | 201 +++++--- .../jfr/internal/consumer/ParserFilter.java | 81 ++++ .../jfr/internal/consumer/RecordingInput.java | 231 ++++++--- .../internal/consumer/RepositoryFiles.java | 230 +++++++++ .../consumer/StreamConfiguration.java | 124 +++++ .../jfr/internal/consumer/StringParser.java | 222 +++++++++ .../consumer/TimeConverter.java | 22 +- .../jdk/jfr/internal/dcmd/DCmdConfigure.java | 7 +- .../jdk/jfr/internal/dcmd/DCmdStart.java | 17 +- .../jdk/jfr/internal/tool/Disassemble.java | 5 +- .../jfr/internal/tool/EventPrintWriter.java | 10 +- .../jdk/jfr/internal/tool/Metadata.java | 7 +- .../jdk/jfr/internal/tool/Summary.java | 5 +- test/jdk/jfr/api/consumer/TestReadTwice.java | 3 +- .../jfr/api/consumer/TestRecordingFile.java | 6 +- .../api/consumer/TestRecordingInternals.java | 3 +- .../filestream/TestMultipleChunk.java | 85 ++++ .../api/consumer/filestream/TestOrdered.java | 151 ++++++ .../api/consumer/filestream/TestReuse.java | 127 +++++ .../recordingstream/EventProducer.java | 58 +++ .../recordingstream/TestAwaitTermination.java | 77 +++ .../consumer/recordingstream/TestClose.java | 127 +++++ .../recordingstream/TestConstructor.java | 68 +++ .../consumer/recordingstream/TestDisable.java | 91 ++++ .../consumer/recordingstream/TestEnable.java | 78 +++ .../consumer/recordingstream/TestMaxAge.java | 60 +++ .../consumer/recordingstream/TestOnClose.java | 94 ++++ .../recordingstream/TestOnErrorAsync.java | 205 ++++++++ .../recordingstream/TestOnErrorSync.java | 240 ++++++++++ .../consumer/recordingstream/TestOnEvent.java | 154 ++++++ .../consumer/recordingstream/TestOnFlush.java | 98 ++++ .../recordingstream/TestRecursive.java | 196 ++++++++ .../consumer/recordingstream/TestRemove.java | 148 ++++++ .../recordingstream/TestSetEndTime.java | 174 +++++++ .../recordingstream/TestSetFlushInterval.java | 61 +++ .../recordingstream/TestSetMaxAge.java | 59 +++ .../recordingstream/TestSetMaxSize.java | 57 +++ .../recordingstream/TestSetSettings.java | 67 +++ .../recordingstream/TestSetStartTime.java | 137 ++++++ .../consumer/recordingstream/TestStart.java | 150 ++++++ .../recordingstream/TestStartAsync.java | 85 ++++ .../consumer/recordingstream/TestUtils.java | 65 +++ .../security/DriverRecordingDumper.java | 40 +- .../security/TestMissingPermission.java | 69 +++ .../consumer/security/TestRecordingFile.java | 54 +++ .../security/TestRecordingStream.java | 59 +++ .../consumer/security/TestStreamingFile.java | 54 +++ .../consumer/security/TestStreamingLocal.java | 69 +++ .../security/TestStreamingRemote.java | 116 +++++ .../consumer/security/local-streaming.policy | 4 + .../consumer/security/no-permission.policy | 3 + .../api/consumer/streaming/TestChunkGap.java | 110 +++++ .../consumer/streaming/TestEmptyChunks.java | 83 ++++ .../consumer/streaming/TestEnableEvents.java | 98 ++++ .../streaming/TestEventRegistration.java | 80 ++++ .../consumer/streaming/TestFilledChunks.java | 81 ++++ .../api/consumer/streaming/TestFiltering.java | 80 ++++ .../consumer/streaming/TestLatestEvent.java | 134 ++++++ .../streaming/TestRecordingBefore.java | 97 ++++ .../consumer/streaming/TestRemovedChunks.java | 125 +++++ .../streaming/TestRepositoryMigration.java | 107 +++++ .../streaming/TestRepositoryProperty.java | 111 +++++ .../streaming/TestStartMultiChunk.java | 120 +++++ .../streaming/TestStartSingleChunk.java | 89 ++++ .../api/consumer/streaming/TestUnstarted.java | 48 +- test/jdk/jfr/api/event/TestEventDuration.java | 66 +++ .../recording/time/TestSetFlushInterval.java | 85 ++++ .../metadata/TestLookForUntestedEvents.java | 10 + .../TestOptoObjectAllocationsSampling.java | 5 +- .../jfr/event/oldobject/TestLargeRootSet.java | 107 +++-- test/jdk/jfr/event/runtime/TestFlush.java | 165 +++++++ .../jfr/jcmd/TestJcmdStartFlushInterval.java | 55 +++ test/jdk/jfr/jvm/TestThreadExclusion.java | 150 ++++++ test/jdk/jfr/jvm/TestUnsupportedVM.java | 48 +- .../jfr/startupargs/TestFlushInterval.java | 54 +++ test/lib/jdk/test/lib/jfr/EventNames.java | 6 + 131 files changed, 10365 insertions(+), 996 deletions(-) delete mode 100644 src/share/classes/jdk/jfr/consumer/ChunkParser.java delete mode 100644 src/share/classes/jdk/jfr/consumer/EventParser.java create mode 100644 src/share/classes/jdk/jfr/consumer/EventStream.java delete mode 100644 src/share/classes/jdk/jfr/consumer/ObjectFactory.java create mode 100644 src/share/classes/jdk/jfr/consumer/RecordingStream.java create mode 100644 src/share/classes/jdk/jfr/internal/FilePurger.java create mode 100644 src/share/classes/jdk/jfr/internal/LongMap.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/ChunkParser.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/ConstantLookup.java rename src/share/classes/jdk/jfr/{ => internal}/consumer/ConstantMap.java (57%) create mode 100644 src/share/classes/jdk/jfr/internal/consumer/Dispatcher.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/EventFileStream.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/EventParser.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/FileAccess.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/JdkJfrConsumer.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/ObjectContext.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/ObjectFactory.java rename src/share/classes/jdk/jfr/{ => internal}/consumer/Parser.java (75%) rename src/share/classes/jdk/jfr/{ => internal}/consumer/ParserFactory.java (61%) create mode 100644 src/share/classes/jdk/jfr/internal/consumer/ParserFilter.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/StreamConfiguration.java create mode 100644 src/share/classes/jdk/jfr/internal/consumer/StringParser.java rename src/share/classes/jdk/jfr/{ => internal}/consumer/TimeConverter.java (96%) create mode 100644 test/jdk/jfr/api/consumer/filestream/TestMultipleChunk.java create mode 100644 test/jdk/jfr/api/consumer/filestream/TestOrdered.java create mode 100644 test/jdk/jfr/api/consumer/filestream/TestReuse.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/EventProducer.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestAwaitTermination.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestClose.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestConstructor.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestDisable.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestEnable.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestMaxAge.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestOnClose.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestOnErrorAsync.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestOnErrorSync.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestOnEvent.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestOnFlush.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestRecursive.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestRemove.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestSetEndTime.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestSetFlushInterval.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestSetMaxAge.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestSetMaxSize.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestSetSettings.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestSetStartTime.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestStart.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestStartAsync.java create mode 100644 test/jdk/jfr/api/consumer/recordingstream/TestUtils.java rename src/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java => test/jdk/jfr/api/consumer/security/DriverRecordingDumper.java (62%) create mode 100644 test/jdk/jfr/api/consumer/security/TestMissingPermission.java create mode 100644 test/jdk/jfr/api/consumer/security/TestRecordingFile.java create mode 100644 test/jdk/jfr/api/consumer/security/TestRecordingStream.java create mode 100644 test/jdk/jfr/api/consumer/security/TestStreamingFile.java create mode 100644 test/jdk/jfr/api/consumer/security/TestStreamingLocal.java create mode 100644 test/jdk/jfr/api/consumer/security/TestStreamingRemote.java create mode 100644 test/jdk/jfr/api/consumer/security/local-streaming.policy create mode 100644 test/jdk/jfr/api/consumer/security/no-permission.policy create mode 100644 test/jdk/jfr/api/consumer/streaming/TestChunkGap.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestEmptyChunks.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestEnableEvents.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestEventRegistration.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestFilledChunks.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestFiltering.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestLatestEvent.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestRecordingBefore.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestRemovedChunks.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestRepositoryMigration.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestRepositoryProperty.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java create mode 100644 test/jdk/jfr/api/consumer/streaming/TestStartSingleChunk.java rename src/share/classes/jdk/jfr/consumer/LongMap.java => test/jdk/jfr/api/consumer/streaming/TestUnstarted.java (59%) create mode 100644 test/jdk/jfr/api/event/TestEventDuration.java create mode 100644 test/jdk/jfr/api/recording/time/TestSetFlushInterval.java create mode 100644 test/jdk/jfr/event/runtime/TestFlush.java create mode 100644 test/jdk/jfr/jcmd/TestJcmdStartFlushInterval.java create mode 100644 test/jdk/jfr/jvm/TestThreadExclusion.java create mode 100644 test/jdk/jfr/startupargs/TestFlushInterval.java diff --git a/src/share/classes/jdk/jfr/Recording.java b/src/share/classes/jdk/jfr/Recording.java index c36ccc055..bf9e4352b 100644 --- a/src/share/classes/jdk/jfr/Recording.java +++ b/src/share/classes/jdk/jfr/Recording.java @@ -413,6 +413,36 @@ public final class Recording implements Closeable { 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. *

diff --git a/src/share/classes/jdk/jfr/conf/default.jfc b/src/share/classes/jdk/jfr/conf/default.jfc index ce9050999..447fc83d0 100644 --- a/src/share/classes/jdk/jfr/conf/default.jfc +++ b/src/share/classes/jdk/jfr/conf/default.jfc @@ -601,6 +601,36 @@ true + + true + 0 ns + + + + true + 0 ns + + + + true + 0 ns + + + + true + 0 ns + + + + true + 0 ns + + + + true + 0 ns + + true diff --git a/src/share/classes/jdk/jfr/conf/profile.jfc b/src/share/classes/jdk/jfr/conf/profile.jfc index 3d7e170b7..32ad9160b 100644 --- a/src/share/classes/jdk/jfr/conf/profile.jfc +++ b/src/share/classes/jdk/jfr/conf/profile.jfc @@ -601,6 +601,36 @@ true + + true + 0 ns + + + + true + 0 ns + + + + true + 0 ns + + + + true + 0 ns + + + + true + 0 ns + + + + true + 0 ns + + true diff --git a/src/share/classes/jdk/jfr/consumer/ChunkParser.java b/src/share/classes/jdk/jfr/consumer/ChunkParser.java deleted file mode 100644 index ee6e00d6e..000000000 --- a/src/share/classes/jdk/jfr/consumer/ChunkParser.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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 parsers; - private final ChunkHeader chunkHeader; - private final long absoluteChunkEnd; - private final MetadataDescriptor metadata; - private final LongMap 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 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 typeParser, LongMap 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 getTypes() { - return metadata.getTypes(); - } - - public List getEventTypes() { - return metadata.getEventTypes(); - } - - public boolean isLastChunk() { - return chunkHeader.isLastChunk(); - } - - public ChunkParser nextChunkParser() throws IOException { - return new ChunkParser(chunkHeader.nextHeader()); - } -} diff --git a/src/share/classes/jdk/jfr/consumer/EventParser.java b/src/share/classes/jdk/jfr/consumer/EventParser.java deleted file mode 100644 index e1e679c89..000000000 --- a/src/share/classes/jdk/jfr/consumer/EventParser.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 static jdk.jfr.internal.EventInstrumentation.FIELD_DURATION; - -import java.io.IOException; -import java.util.List; - -import jdk.jfr.EventType; -import jdk.jfr.ValueDescriptor; -import jdk.jfr.internal.consumer.RecordingInput; - -/** - * Parses an event and returns a {@link RecordedEvent}. - * - */ -final class EventParser extends Parser { - private final Parser[] parsers; - private final EventType eventType; - private final TimeConverter timeConverter; - private final boolean hasDuration; - private final List valueDescriptors; - - EventParser(TimeConverter timeConverter, EventType type, Parser[] parsers) { - this.timeConverter = timeConverter; - this.parsers = parsers; - this.eventType = type; - this.hasDuration = type.getField(FIELD_DURATION) != null; - this.valueDescriptors = type.getFields(); - } - - @Override - public Object parse(RecordingInput input) throws IOException { - Object[] values = new Object[parsers.length]; - for (int i = 0; i < parsers.length; i++) { - values[i] = parsers[i].parse(input); - } - Long startTicks = (Long) values[0]; - long startTime = timeConverter.convertTimestamp(startTicks); - if (hasDuration) { - long durationTicks = (Long) values[1]; - long endTime = timeConverter.convertTimestamp(startTicks + durationTicks); - return new RecordedEvent(eventType, valueDescriptors, values, startTime, endTime, timeConverter); - } else { - return new RecordedEvent(eventType, valueDescriptors, values, startTime, startTime, timeConverter); - } - } -} diff --git a/src/share/classes/jdk/jfr/consumer/EventStream.java b/src/share/classes/jdk/jfr/consumer/EventStream.java new file mode 100644 index 000000000..3b179c660 --- /dev/null +++ b/src/share/classes/jdk/jfr/consumer/EventStream.java @@ -0,0 +1,364 @@ +/* + * 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. + *

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

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

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

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

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

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

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

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

+ * The following example shows how an {@code EventStream} can be used to listen + * to events on a JVM running Flight Recorder + * + *

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

+ * 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). + *

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

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

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

+ * if an action is not registered, an exception stack trace is printed to + * standard error. + *

+ * Registering an action overrides the default behavior. If multiple actions + * have been registered, they are performed in the order of registration. + *

+ * 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 action); + + /** + * Registers an action to perform when the stream is closed. + *

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

+ * Closing a previously closed stream has no effect. + */ + void close(); + + /** + * Unregisters an action. + *

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

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

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

+ * The end time must be set before starting the stream. + *

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

+ * Actions are performed in the current thread. + * + * @throws IllegalStateException if the stream is already started or closed + */ + void start(); + + /** + * Start asynchronous processing of actions. + *

+ * 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; +} diff --git a/src/share/classes/jdk/jfr/consumer/ObjectFactory.java b/src/share/classes/jdk/jfr/consumer/ObjectFactory.java deleted file mode 100644 index 72fee3ed6..000000000 --- a/src/share/classes/jdk/jfr/consumer/ObjectFactory.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.util.List; - -import jdk.jfr.ValueDescriptor; -import jdk.jfr.internal.Type; - -/** - * Abstract factory for creating specialized types - */ -abstract class ObjectFactory { - - final static String TYPE_PREFIX_VERSION_1 = "com.oracle.jfr.types."; - final static String TYPE_PREFIX_VERSION_2 = Type.TYPES_PREFIX; - final static String STACK_FRAME_VERSION_1 = TYPE_PREFIX_VERSION_1 + "StackFrame"; - final static String STACK_FRAME_VERSION_2 = TYPE_PREFIX_VERSION_2 + "StackFrame"; - - public static ObjectFactory create(Type type, TimeConverter timeConverter) { - switch (type.getName()) { - case "java.lang.Thread": - return RecordedThread.createFactory(type, timeConverter); - case TYPE_PREFIX_VERSION_1 + "StackFrame": - case TYPE_PREFIX_VERSION_2 + "StackFrame": - return RecordedFrame.createFactory(type, timeConverter); - case TYPE_PREFIX_VERSION_1 + "Method": - case TYPE_PREFIX_VERSION_2 + "Method": - return RecordedMethod.createFactory(type, timeConverter); - case TYPE_PREFIX_VERSION_1 + "ThreadGroup": - case TYPE_PREFIX_VERSION_2 + "ThreadGroup": - return RecordedThreadGroup.createFactory(type, timeConverter); - case TYPE_PREFIX_VERSION_1 + "StackTrace": - case TYPE_PREFIX_VERSION_2 + "StackTrace": - return RecordedStackTrace.createFactory(type, timeConverter); - case TYPE_PREFIX_VERSION_1 + "ClassLoader": - case TYPE_PREFIX_VERSION_2 + "ClassLoader": - return RecordedClassLoader.createFactory(type, timeConverter); - case "java.lang.Class": - return RecordedClass.createFactory(type, timeConverter); - } - return null; - } - - private final List valueDescriptors; - - ObjectFactory(Type type) { - this.valueDescriptors = type.getFields(); - } - - T createObject(long id, Object value) { - if (value == null) { - return null; - } - if (value instanceof Object[]) { - return createTyped(valueDescriptors, id, (Object[]) value); - } - throw new InternalError("Object factory must have struct type"); - } - - abstract T createTyped(List valueDescriptors, long id, Object[] values); -} diff --git a/src/share/classes/jdk/jfr/consumer/RecordedClass.java b/src/share/classes/jdk/jfr/consumer/RecordedClass.java index 57263cd53..790b74bcc 100644 --- a/src/share/classes/jdk/jfr/consumer/RecordedClass.java +++ b/src/share/classes/jdk/jfr/consumer/RecordedClass.java @@ -26,10 +26,8 @@ package jdk.jfr.consumer; import java.lang.reflect.Modifier; -import java.util.List; -import jdk.jfr.ValueDescriptor; -import jdk.jfr.internal.Type; +import jdk.jfr.internal.consumer.ObjectContext; /** * A recorded Java type, such as a class or an interface. @@ -37,21 +35,11 @@ import jdk.jfr.internal.Type; * @since 8 */ public final class RecordedClass extends RecordedObject { - - static ObjectFactory createFactory(Type type, TimeConverter timeConverter) { - return new ObjectFactory(type) { - @Override - RecordedClass createTyped(List desc, long id, Object[] object) { - return new RecordedClass(desc, id, object, timeConverter); - } - }; - } - private final long uniqueId; // package private - private RecordedClass(List descriptors, long id, Object[] values, TimeConverter timeConverter) { - super(descriptors, values, timeConverter); + RecordedClass(ObjectContext objectContext, long id, Object[] values) { + super(objectContext, values); this.uniqueId = id; } diff --git a/src/share/classes/jdk/jfr/consumer/RecordedClassLoader.java b/src/share/classes/jdk/jfr/consumer/RecordedClassLoader.java index 5103eece8..649f950ed 100644 --- a/src/share/classes/jdk/jfr/consumer/RecordedClassLoader.java +++ b/src/share/classes/jdk/jfr/consumer/RecordedClassLoader.java @@ -25,10 +25,7 @@ package jdk.jfr.consumer; -import java.util.List; - -import jdk.jfr.ValueDescriptor; -import jdk.jfr.internal.Type; +import jdk.jfr.internal.consumer.ObjectContext; /** * A recorded Java class loader. @@ -36,21 +33,11 @@ import jdk.jfr.internal.Type; * @since 8 */ public final class RecordedClassLoader extends RecordedObject { - - static ObjectFactory createFactory(Type type, TimeConverter timeConverter) { - return new ObjectFactory(type) { - @Override - RecordedClassLoader createTyped(List desc, long id, Object[] object) { - return new RecordedClassLoader(desc, id, object, timeConverter); - } - }; - } - private final long uniqueId; // package private - private RecordedClassLoader(List descriptors, long id, Object[] values, TimeConverter timeConverter) { - super(descriptors, values, timeConverter); + RecordedClassLoader(ObjectContext objectContext, long id, Object[] values) { + super(objectContext, values); this.uniqueId = id; } diff --git a/src/share/classes/jdk/jfr/consumer/RecordedEvent.java b/src/share/classes/jdk/jfr/consumer/RecordedEvent.java index 1fe0edeb0..066f24fc2 100644 --- a/src/share/classes/jdk/jfr/consumer/RecordedEvent.java +++ b/src/share/classes/jdk/jfr/consumer/RecordedEvent.java @@ -32,6 +32,7 @@ import java.util.List; import jdk.jfr.EventType; import jdk.jfr.ValueDescriptor; import jdk.jfr.internal.EventInstrumentation; +import jdk.jfr.internal.consumer.ObjectContext; /** * A recorded event. @@ -39,17 +40,14 @@ import jdk.jfr.internal.EventInstrumentation; * @since 8 */ public final class RecordedEvent extends RecordedObject { - private final EventType eventType; - private final long startTime; - // package private needed for efficient sorting - final long endTime; + long startTimeTicks; + long endTimeTicks; // package private - RecordedEvent(EventType type, List vds, Object[] values, long startTime, long endTime, TimeConverter timeConverter) { - super(vds, values, timeConverter); - this.eventType = type; - this.startTime = startTime; - this.endTime = endTime; + RecordedEvent(ObjectContext objectContext, Object[] values, long startTimeTicks, long endTimeTicks) { + super(objectContext, values); + this.startTimeTicks = startTimeTicks; + this.endTimeTicks = endTimeTicks; } /** @@ -78,7 +76,7 @@ public final class RecordedEvent extends RecordedObject { * @return the event type, not {@code null} */ public EventType getEventType() { - return eventType; + return objectContext.eventType; } /** @@ -89,7 +87,7 @@ public final class RecordedEvent extends RecordedObject { * @return the start time, not {@code null} */ public Instant getStartTime() { - return Instant.ofEpochSecond(0, startTime); + return Instant.ofEpochSecond(0, getStartTimeNanos()); } /** @@ -100,7 +98,7 @@ public final class RecordedEvent extends RecordedObject { * @return the end time, not {@code null} */ public Instant getEndTime() { - return Instant.ofEpochSecond(0, endTime); + return Instant.ofEpochSecond(0, getEndTimeNanos()); } /** @@ -109,7 +107,7 @@ public final class RecordedEvent extends RecordedObject { * @return the duration in nanoseconds, not {@code null} */ public Duration getDuration() { - return Duration.ofNanos(endTime - startTime); + return Duration.ofNanos(getEndTimeNanos() - getStartTimeNanos()); } /** @@ -119,6 +117,31 @@ public final class RecordedEvent extends RecordedObject { */ @Override public List 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); } } diff --git a/src/share/classes/jdk/jfr/consumer/RecordedFrame.java b/src/share/classes/jdk/jfr/consumer/RecordedFrame.java index c541d01cf..7375cfdc2 100644 --- a/src/share/classes/jdk/jfr/consumer/RecordedFrame.java +++ b/src/share/classes/jdk/jfr/consumer/RecordedFrame.java @@ -26,10 +26,8 @@ package jdk.jfr.consumer; import java.lang.reflect.Modifier; -import java.util.List; -import jdk.jfr.ValueDescriptor; -import jdk.jfr.internal.Type; +import jdk.jfr.internal.consumer.ObjectContext; /** * A recorded frame in a stack trace. @@ -37,19 +35,9 @@ import jdk.jfr.internal.Type; * @since 8 */ public final class RecordedFrame extends RecordedObject { - - static ObjectFactory createFactory(Type type, TimeConverter timeConverter) { - return new ObjectFactory(type) { - @Override - RecordedFrame createTyped(List desc, long id, Object[] object) { - return new RecordedFrame(desc, object, timeConverter); - } - }; - } - // package private - RecordedFrame(List desc, Object[] objects, TimeConverter timeConverter) { - super(desc, objects, timeConverter); + RecordedFrame(ObjectContext objectContext, Object[] values) { + super(objectContext, values); } /** diff --git a/src/share/classes/jdk/jfr/consumer/RecordedMethod.java b/src/share/classes/jdk/jfr/consumer/RecordedMethod.java index 3f20d3d1b..a0ad634ec 100644 --- a/src/share/classes/jdk/jfr/consumer/RecordedMethod.java +++ b/src/share/classes/jdk/jfr/consumer/RecordedMethod.java @@ -26,10 +26,8 @@ package jdk.jfr.consumer; import java.lang.reflect.Modifier; -import java.util.List; -import jdk.jfr.ValueDescriptor; -import jdk.jfr.internal.Type; +import jdk.jfr.internal.consumer.ObjectContext; /** * A recorded method. @@ -38,17 +36,9 @@ import jdk.jfr.internal.Type; */ public final class RecordedMethod extends RecordedObject { - static ObjectFactory createFactory(Type type, TimeConverter timeConverter) { - return new ObjectFactory(type) { - @Override - RecordedMethod createTyped(List desc, long id, Object[] object) { - return new RecordedMethod(desc, object, timeConverter); - } - }; - } - - private RecordedMethod(List descriptors, Object[] objects, TimeConverter timeConverter) { - super(descriptors, objects, timeConverter); + // package private + RecordedMethod(ObjectContext objectContext, Object[] values) { + super(objectContext, values); } /** diff --git a/src/share/classes/jdk/jfr/consumer/RecordedObject.java b/src/share/classes/jdk/jfr/consumer/RecordedObject.java index d2f896e6a..f1633c7a1 100644 --- a/src/share/classes/jdk/jfr/consumer/RecordedObject.java +++ b/src/share/classes/jdk/jfr/consumer/RecordedObject.java @@ -25,18 +25,24 @@ package jdk.jfr.consumer; +import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.time.Duration; import java.time.Instant; import java.time.OffsetDateTime; +import java.util.Comparator; import java.util.List; import java.util.Objects; import jdk.jfr.Timespan; import jdk.jfr.Timestamp; 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.Type; +import jdk.jfr.internal.consumer.ObjectContext; import jdk.jfr.internal.tool.PrettyWriter; /** @@ -51,6 +57,89 @@ import jdk.jfr.internal.tool.PrettyWriter; */ public class RecordedObject { + static{ + JdkJfrConsumer access = new JdkJfrConsumer() { + public List 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 eventComparator() { + return new Comparator() { + @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 Object o; @@ -63,15 +152,13 @@ public class RecordedObject { } } - private final Object[] objects; - private final List descriptors; - private final TimeConverter timeConverter; + final Object[] objects; + final ObjectContext objectContext; // package private, not to be subclassed outside this package - RecordedObject(List descriptors, Object[] objects, TimeConverter timeConverter) { - this.descriptors = descriptors; + RecordedObject(ObjectContext objectContext, Object[] objects) { + this.objectContext = objectContext; this.objects = objects; - this.timeConverter = timeConverter; } // package private @@ -101,7 +188,7 @@ public class RecordedObject { */ public boolean hasField(String name) { Objects.requireNonNull(name); - for (ValueDescriptor v : descriptors) { + for (ValueDescriptor v : objectContext.fields) { if (v.getName().equals(name)) { return true; } @@ -109,7 +196,7 @@ public class RecordedObject { int dotIndex = name.indexOf("."); if (dotIndex > 0) { String structName = name.substring(0, dotIndex); - for (ValueDescriptor v : descriptors) { + for (ValueDescriptor v : objectContext.fields) { if (!v.getFields().isEmpty() && v.getName().equals(structName)) { RecordedObject child = getValue(structName); if (child != null) { @@ -169,12 +256,16 @@ public class RecordedObject { return t; } + protected Object objectAt(int index) { + return objects[index]; + } + private Object getValue(String name, boolean allowUnsigned) { Objects.requireNonNull(name); int index = 0; - for (ValueDescriptor v : descriptors) { + for (ValueDescriptor v : objectContext.fields) { if (name.equals(v.getName())) { - Object object = objects[index]; + Object object = objectAt(index); if (object == null) { // error or missing return null; @@ -200,7 +291,7 @@ public class RecordedObject { return structifyArray(v, array, 0); } // struct - return new RecordedObject(v.getFields(), (Object[]) object, timeConverter); + return new RecordedObject(objectContext.getInstance(v), (Object[]) object); } } index++; @@ -209,7 +300,7 @@ public class RecordedObject { int dotIndex = name.indexOf("."); if (dotIndex > 0) { String structName = name.substring(0, dotIndex); - for (ValueDescriptor v : descriptors) { + for (ValueDescriptor v : objectContext.fields) { if (!v.getFields().isEmpty() && v.getName().equals(structName)) { RecordedObject child = getValue(structName); String subName = name.substring(dotIndex + 1); @@ -261,7 +352,7 @@ public class RecordedObject { private T getTypedValue(String name, String typeName) { Objects.requireNonNull(name); // Validate name and type first - getValueDescriptor(descriptors, name, typeName); + getValueDescriptor(objectContext.fields, name, typeName); return getValue(name); } @@ -270,15 +361,16 @@ public class RecordedObject { return null; } Object[] structArray = new Object[array.length]; + ObjectContext objContext = objectContext.getInstance(v); for (int i = 0; i < structArray.length; i++) { Object arrayElement = array[i]; if (dimension == 0) { // No general way to handle structarrays // without invoking ObjectFactory for every instance (which may require id) if (isStackFrameType(v.getTypeName())) { - structArray[i] = new RecordedFrame(v.getFields(), (Object[]) arrayElement, timeConverter); + structArray[i] = new RecordedFrame(objContext, (Object[]) arrayElement); } else { - structArray[i] = new RecordedObject(v.getFields(), (Object[]) arrayElement, timeConverter); + structArray[i] = new RecordedObject(objContext, (Object[]) arrayElement); } } else { structArray[i] = structifyArray(v, (Object[]) arrayElement, dimension - 1); @@ -303,7 +395,7 @@ public class RecordedObject { * @return the fields, not {@code null} */ public List getFields() { - return descriptors; + return objectContext.fields; } /** @@ -725,7 +817,7 @@ public class RecordedObject { } 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) { return Duration.ofSeconds(Long.MIN_VALUE, 0); } @@ -741,7 +833,7 @@ public class RecordedObject { case Timespan.NANOSECONDS: return Duration.ofNanos(timespan); 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()); } @@ -804,7 +896,7 @@ public class RecordedObject { } 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); if (ts != null) { if (timestamp == Long.MIN_VALUE) { @@ -814,7 +906,7 @@ public class RecordedObject { case Timestamp.MILLISECONDS_SINCE_EPOCH: return Instant.ofEpochMilli(timestamp); 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()); } @@ -889,12 +981,12 @@ public class RecordedObject { } // package private for now. Used by EventWriter - OffsetDateTime getOffsetDateTime(String name) { + private OffsetDateTime getOffsetDateTime(String name) { Instant instant = getInstant(name); if (instant.equals(Instant.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) { diff --git a/src/share/classes/jdk/jfr/consumer/RecordedStackTrace.java b/src/share/classes/jdk/jfr/consumer/RecordedStackTrace.java index 1d2f7ea6c..ce95ede96 100644 --- a/src/share/classes/jdk/jfr/consumer/RecordedStackTrace.java +++ b/src/share/classes/jdk/jfr/consumer/RecordedStackTrace.java @@ -29,8 +29,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import jdk.jfr.ValueDescriptor; -import jdk.jfr.internal.Type; +import jdk.jfr.internal.consumer.ObjectContext; /** * A recorded stack trace. @@ -38,18 +37,9 @@ import jdk.jfr.internal.Type; * @since 8 */ public final class RecordedStackTrace extends RecordedObject { - - static ObjectFactory createFactory(Type type, TimeConverter timeConverter) { - return new ObjectFactory(type) { - @Override - RecordedStackTrace createTyped(List desc, long id, Object[] object) { - return new RecordedStackTrace(desc, object, timeConverter); - } - }; - } - - private RecordedStackTrace(List desc, Object[] values, TimeConverter timeConverter) { - super(desc, values, timeConverter); + // package private + RecordedStackTrace(ObjectContext objectContext, Object[] values) { + super(objectContext, values); } /** diff --git a/src/share/classes/jdk/jfr/consumer/RecordedThread.java b/src/share/classes/jdk/jfr/consumer/RecordedThread.java index ec16654c2..4805c3218 100644 --- a/src/share/classes/jdk/jfr/consumer/RecordedThread.java +++ b/src/share/classes/jdk/jfr/consumer/RecordedThread.java @@ -25,10 +25,7 @@ package jdk.jfr.consumer; -import java.util.List; - -import jdk.jfr.ValueDescriptor; -import jdk.jfr.internal.Type; +import jdk.jfr.internal.consumer.ObjectContext; /** * A recorded thread. @@ -36,20 +33,11 @@ import jdk.jfr.internal.Type; * @since 8 */ public final class RecordedThread extends RecordedObject { - - static ObjectFactory createFactory(Type type, TimeConverter timeConverter) { - return new ObjectFactory(type) { - @Override - RecordedThread createTyped(List desc, long id, Object[] object) { - return new RecordedThread(desc, id, object, timeConverter); - } - }; - } - private final long uniqueId; - private RecordedThread(List descriptors, long id, Object[] values, TimeConverter timeConverter) { - super(descriptors, values, timeConverter); + // package private + RecordedThread(ObjectContext objectContext, long id, Object[] values) { + super(objectContext, values); this.uniqueId = id; } diff --git a/src/share/classes/jdk/jfr/consumer/RecordedThreadGroup.java b/src/share/classes/jdk/jfr/consumer/RecordedThreadGroup.java index 2eec6f8b0..8447e5ae0 100644 --- a/src/share/classes/jdk/jfr/consumer/RecordedThreadGroup.java +++ b/src/share/classes/jdk/jfr/consumer/RecordedThreadGroup.java @@ -25,10 +25,7 @@ package jdk.jfr.consumer; -import java.util.List; - -import jdk.jfr.ValueDescriptor; -import jdk.jfr.internal.Type; +import jdk.jfr.internal.consumer.ObjectContext; /** * A recorded Java thread group. @@ -36,18 +33,9 @@ import jdk.jfr.internal.Type; * @since 8 */ public final class RecordedThreadGroup extends RecordedObject { - - static ObjectFactory createFactory(Type type, TimeConverter timeConverter) { - return new ObjectFactory(type) { - @Override - RecordedThreadGroup createTyped(List desc, long id, Object[] object) { - return new RecordedThreadGroup(desc, object, timeConverter); - } - }; - } - - private RecordedThreadGroup(List descriptors, Object[] objects, TimeConverter timeConverter) { - super(descriptors, objects, timeConverter); + // package private + RecordedThreadGroup(ObjectContext objectContext, Object[] values) { + super(objectContext, values); } /** diff --git a/src/share/classes/jdk/jfr/consumer/RecordingFile.java b/src/share/classes/jdk/jfr/consumer/RecordingFile.java index 290c09c11..5dd350f95 100644 --- a/src/share/classes/jdk/jfr/consumer/RecordingFile.java +++ b/src/share/classes/jdk/jfr/consumer/RecordingFile.java @@ -32,7 +32,6 @@ import java.io.IOException; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -40,8 +39,9 @@ import jdk.jfr.EventType; import jdk.jfr.internal.MetadataDescriptor; import jdk.jfr.internal.Type; 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.RecordingInternals; /** * A recording file. @@ -62,27 +62,6 @@ import jdk.jfr.internal.consumer.RecordingInternals; * @since 8 */ public final class RecordingFile implements Closeable { - static{ - RecordingInternals.INSTANCE = new RecordingInternals() { - public List 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 events) { - Collections.sort(events, (e1, e2) -> Long.compare(e1.endTime, e2.endTime)); - } - }; - } private boolean isLastEventInChunk; private final File file; @@ -104,7 +83,7 @@ public final class RecordingFile implements Closeable { */ public RecordingFile(Path file) throws IOException { this.file = file.toFile(); - this.input = new RecordingInput(this.file); + this.input = new RecordingInput(this.file, FileAccess.UNPRIVILIGED); findNext(); } @@ -154,14 +133,15 @@ public final class RecordingFile implements Closeable { */ public List readEventTypes() throws IOException { ensureOpen(); + MetadataDescriptor previous = null; List types = new ArrayList<>(); HashSet foundIds = new HashSet<>(); - try (RecordingInput ri = new RecordingInput(file)) { + try (RecordingInput ri = new RecordingInput(file, FileAccess.UNPRIVILIGED)) { ChunkHeader ch = new ChunkHeader(ri); - aggregateEventTypeForChunk(ch, types, foundIds); + aggregateEventTypeForChunk(ch, null, types, foundIds); while (!ch.isLastChunk()) { ch = ch.nextHeader(); - aggregateEventTypeForChunk(ch, types, foundIds); + previous = aggregateEventTypeForChunk(ch, previous, types, foundIds); } } return types; @@ -169,37 +149,41 @@ public final class RecordingFile implements Closeable { List readTypes() throws IOException { ensureOpen(); + MetadataDescriptor previous = null; List types = new ArrayList<>(); HashSet foundIds = new HashSet<>(); - try (RecordingInput ri = new RecordingInput(file)) { + try (RecordingInput ri = new RecordingInput(file, FileAccess.UNPRIVILIGED)) { ChunkHeader ch = new ChunkHeader(ri); - aggregateTypeForChunk(ch, types, foundIds); + ch.awaitFinished(); + aggregateTypeForChunk(ch, null, types, foundIds); while (!ch.isLastChunk()) { ch = ch.nextHeader(); - aggregateTypeForChunk(ch, types, foundIds); + previous = aggregateTypeForChunk(ch, previous, types, foundIds); } } return types; } - private void aggregateTypeForChunk(ChunkHeader ch, List types, HashSet foundIds) throws IOException { - MetadataDescriptor m = ch.readMetadata(); + private MetadataDescriptor aggregateTypeForChunk(ChunkHeader ch, MetadataDescriptor previous, List types, HashSet foundIds) throws IOException { + MetadataDescriptor m = ch.readMetadata(previous); for (Type t : m.getTypes()) { if (!foundIds.contains(t.getId())) { types.add(t); foundIds.add(t.getId()); } } + return m; } - private static void aggregateEventTypeForChunk(ChunkHeader ch, List types, HashSet foundIds) throws IOException { - MetadataDescriptor m = ch.readMetadata(); + private static MetadataDescriptor aggregateEventTypeForChunk(ChunkHeader ch, MetadataDescriptor previous, List types, HashSet foundIds) throws IOException { + MetadataDescriptor m = ch.readMetadata(previous); for (EventType t : m.getEventTypes()) { if (!foundIds.contains(t.getId())) { types.add(t); foundIds.add(t.getId()); } } + return m; } /** @@ -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 private void findNext() throws IOException { while (nextEvent == null) { diff --git a/src/share/classes/jdk/jfr/consumer/RecordingStream.java b/src/share/classes/jdk/jfr/consumer/RecordingStream.java new file mode 100644 index 000000000..c7848b2ad --- /dev/null +++ b/src/share/classes/jdk/jfr/consumer/RecordingStream.java @@ -0,0 +1,362 @@ +/* + * 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). + *

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

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

+ * The following example shows how to create a recording stream that uses a + * predefined configuration. + * + *

+     * 
+     * var c = Configuration.getConfiguration("default");
+     * try (var rs = new RecordingStream(c)) {
+     *   rs.onEvent(System.out::println);
+     *   rs.start();
+     * }
+     * 
+     * 
+ * + * @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. + *

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

+ * The following example records 20 seconds using the "default" configuration + * and then changes settings to the "profile" configuration. + * + *

+     * 
+     *     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);
+     *     }
+     * 
+     * 
+ * + * @param settings the settings to set, not {@code null} + * + * @see Recording#setSettings(Map) + */ + public void setSettings(Map 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 eventClass) { + return recording.enable(eventClass); + } + + /** + * Disables event with the specified name. + *

+ * 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 eventClass) { + return recording.disable(eventClass); + } + + /** + * Determines how far back data is kept for the stream. + *

+ * 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). + *

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

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

+ * If neither maximum limit or the maximum age is set, the size of the + * recording may grow indefinitely. + *

+ * 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 action) { + directoryStream.onEvent(eventName, action); + } + + @Override + public void onEvent(Consumer 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 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(); + } +} diff --git a/src/share/classes/jdk/jfr/events/ActiveRecordingEvent.java b/src/share/classes/jdk/jfr/events/ActiveRecordingEvent.java index 631315e43..a66f4b8f9 100644 --- a/src/share/classes/jdk/jfr/events/ActiveRecordingEvent.java +++ b/src/share/classes/jdk/jfr/events/ActiveRecordingEvent.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -40,6 +40,13 @@ import jdk.jfr.internal.Type; @StackTrace(false) public final class ActiveRecordingEvent extends AbstractJDKEvent { + public static final ThreadLocal EVENT = new ThreadLocal() { + @Override + protected ActiveRecordingEvent initialValue() { + return new ActiveRecordingEvent(); + } + }; + @Label("Id") public long id; @@ -53,6 +60,10 @@ public final class ActiveRecordingEvent extends AbstractJDKEvent { @Timespan(Timespan.MILLISECONDS) public long maxAge; + @Label("Flush Interval") + @Timespan(Timespan.MILLISECONDS) + public long flushInterval; + @Label("Max Size") @DataAmount public long maxSize; diff --git a/src/share/classes/jdk/jfr/events/ActiveSettingEvent.java b/src/share/classes/jdk/jfr/events/ActiveSettingEvent.java index 0c51db6ba..a3ca39194 100644 --- a/src/share/classes/jdk/jfr/events/ActiveSettingEvent.java +++ b/src/share/classes/jdk/jfr/events/ActiveSettingEvent.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -37,6 +37,13 @@ import jdk.jfr.internal.Type; @StackTrace(false) public final class ActiveSettingEvent extends AbstractJDKEvent { + public static final ThreadLocal EVENT = new ThreadLocal() { + @Override + protected ActiveSettingEvent initialValue() { + return new ActiveSettingEvent(); + } + }; + @Label("Event Id") public long id; diff --git a/src/share/classes/jdk/jfr/internal/EventControl.java b/src/share/classes/jdk/jfr/internal/EventControl.java index 33a46c3ea..84b91e91b 100644 --- a/src/share/classes/jdk/jfr/internal/EventControl.java +++ b/src/share/classes/jdk/jfr/internal/EventControl.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -32,11 +32,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; import jdk.jfr.AnnotationElement; import jdk.jfr.Enabled; @@ -59,7 +55,14 @@ import jdk.jfr.internal.settings.ThresholdSetting; // holds SettingControl instances that need to be released // when a class is unloaded (to avoid memory leaks). 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"; private static final Type TYPE_ENABLED = TypeLibrary.createType(EnabledSetting.class); private static final Type TYPE_THRESHOLD = TypeLibrary.createType(ThresholdSetting.class); @@ -67,24 +70,24 @@ public final class EventControl { private static final Type TYPE_PERIOD = TypeLibrary.createType(PeriodSetting.class); private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class); - private final List settingInfos = new ArrayList<>(); - private final Map eventControls = new HashMap<>(5); + private final ArrayList settingInfos = new ArrayList<>(); + private final ArrayList namedControls = new ArrayList<>(5); private final PlatformEventType type; private final String idName; EventControl(PlatformEventType eventType) { - eventControls.put(Enabled.NAME, defineEnabled(eventType)); + addControl(Enabled.NAME, defineEnabled(eventType)); if (eventType.hasDuration()) { - eventControls.put(Threshold.NAME, defineThreshold(eventType)); + addControl(Threshold.NAME, defineThreshold(eventType)); } if (eventType.hasStackTrace()) { - eventControls.put(StackTrace.NAME, defineStackTrace(eventType)); + addControl(StackTrace.NAME, defineStackTrace(eventType)); } if (eventType.hasPeriod()) { - eventControls.put(Period.NAME, definePeriod(eventType)); + addControl(Period.NAME, definePeriod(eventType)); } if (eventType.hasCutoff()) { - eventControls.put(Cutoff.NAME, defineCutoff(eventType)); + addControl(Cutoff.NAME, defineCutoff(eventType)); } ArrayList aes = new ArrayList<>(eventType.getAnnotationElements()); @@ -99,6 +102,19 @@ public final class EventControl { 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 aes, Class clazz) { long id = Type.getTypeId(clazz); for (AnnotationElement a : type.getAnnotationElements()) { @@ -131,7 +147,8 @@ public final class EventControl { if (n != null) { name = n.value(); } - if (!eventControls.containsKey(name)) { + + if (!hasControl(name)) { defineSetting((Class) settingClass, m, type, name); } } @@ -161,7 +178,7 @@ public final class EventControl { } } aes.trimToSize(); - eventControls.put(settingName, si.settingControl); + addControl(settingName, si.settingControl); eventType.add(PrivateAccess.getInstance().newSettingDescriptor(settingType, settingName, defaultValue, aes)); settingInfos.add(si); } @@ -245,9 +262,9 @@ public final class EventControl { } void disable() { - for (Control c : eventControls.values()) { - if (c instanceof EnabledSetting) { - c.setValueSafe("false"); + for (NamedControl nc : namedControls) { + if (nc.control instanceof EnabledSetting) { + nc.control.setValueSafe("false"); return; } } @@ -257,24 +274,23 @@ public final class EventControl { if (!type.isRegistered()) { return; } - for (Map.Entry entry : eventControls.entrySet()) { - Control c = entry.getValue(); - if (Utils.isSettingVisible(c, type.hasEventHook())) { - String value = c.getLastValue(); + ActiveSettingEvent event = ActiveSettingEvent.EVENT.get(); + for (NamedControl nc : namedControls) { + if (Utils.isSettingVisible(nc.control, type.hasEventHook())) { + String value = nc.control.getLastValue(); if (value == null) { - value = c.getDefaultValue(); + value = nc.control.getDefaultValue(); } - ActiveSettingEvent ase = new ActiveSettingEvent(); - ase.id = type.getId(); - ase.name = entry.getKey(); - ase.value = value; - ase.commit(); + event.id = type.getId(); + event.name = nc.name; + event.value = value; + event.commit(); } } } - public Set> getEntries() { - return eventControls.entrySet(); + public ArrayList getNamedControls() { + return namedControls; } public PlatformEventType getEventType() { diff --git a/src/share/classes/jdk/jfr/internal/EventInstrumentation.java b/src/share/classes/jdk/jfr/internal/EventInstrumentation.java index 70c480656..60fcdd13e 100644 --- a/src/share/classes/jdk/jfr/internal/EventInstrumentation.java +++ b/src/share/classes/jdk/jfr/internal/EventInstrumentation.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -355,74 +355,72 @@ public final class EventInstrumentation { methodVisitor.visitMaxs(0, 0); }); - // MyEvent#commit() - Java event writer updateMethod(METHOD_COMMIT, methodVisitor -> { - // if (!isEnable()) { - // return; - // } - methodVisitor.visitCode(); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_IS_ENABLED.getName(), METHOD_IS_ENABLED.getDescriptor(), false); - Label l0 = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFNE, l0); - methodVisitor.visitInsn(Opcodes.RETURN); - methodVisitor.visitLabel(l0); - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - // if (startTime == 0) { - // startTime = EventWriter.timestamp(); - // } else { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J"); - methodVisitor.visitInsn(Opcodes.LCONST_0); - methodVisitor.visitInsn(Opcodes.LCMP); - Label durationalEvent = new Label(); - methodVisitor.visitJumpInsn(Opcodes.IFNE, durationalEvent); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), - METHOD_TIME_STAMP.getDescriptor(), false); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_START_TIME, "J"); - Label commit = new Label(); - methodVisitor.visitJumpInsn(Opcodes.GOTO, commit); - // if (duration == 0) { - // duration = EventWriter.timestamp() - startTime; - // } - // } - methodVisitor.visitLabel(durationalEvent); - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J"); - methodVisitor.visitInsn(Opcodes.LCONST_0); - methodVisitor.visitInsn(Opcodes.LCMP); - methodVisitor.visitJumpInsn(Opcodes.IFNE, commit); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J"); - methodVisitor.visitInsn(Opcodes.LSUB); - methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_DURATION, "J"); - methodVisitor.visitLabel(commit); - // if (shouldCommit()) { - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_EVENT_SHOULD_COMMIT.getName(), METHOD_EVENT_SHOULD_COMMIT.getDescriptor(), false); - Label end = new Label(); - // eventHandler.write(...); - // } - methodVisitor.visitJumpInsn(Opcodes.IFEQ, end); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(eventHandlerProxy)); + // if (!isEnable()) { + // return; + // } + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_IS_ENABLED.getName(), METHOD_IS_ENABLED.getDescriptor(), false); + Label l0 = new Label(); + methodVisitor.visitJumpInsn(Opcodes.IFNE, l0); + methodVisitor.visitInsn(Opcodes.RETURN); + methodVisitor.visitLabel(l0); + methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + // if (startTime == 0) { + // startTime = EventWriter.timestamp(); + // } else { + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J"); + methodVisitor.visitInsn(Opcodes.LCONST_0); + methodVisitor.visitInsn(Opcodes.LCMP); + Label durationalEvent = new Label(); + methodVisitor.visitJumpInsn(Opcodes.IFNE, durationalEvent); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false); + methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_START_TIME, "J"); + Label commit = new Label(); + methodVisitor.visitJumpInsn(Opcodes.GOTO, commit); + // if (duration == 0) { + // duration = EventWriter.timestamp() - startTime; + // } + // } + methodVisitor.visitLabel(durationalEvent); + methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J"); + methodVisitor.visitInsn(Opcodes.LCONST_0); + methodVisitor.visitInsn(Opcodes.LCMP); + methodVisitor.visitJumpInsn(Opcodes.IFNE, commit); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J"); + methodVisitor.visitInsn(Opcodes.LSUB); + methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_DURATION, "J"); + methodVisitor.visitLabel(commit); + // if (shouldCommit()) { + methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_EVENT_SHOULD_COMMIT.getName(), METHOD_EVENT_SHOULD_COMMIT.getDescriptor(), false); + Label end = new Label(); + // eventHandler.write(...); + // } + methodVisitor.visitJumpInsn(Opcodes.IFEQ, end); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(eventHandlerProxy)); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, eventHandlerXInternalName); - for (FieldInfo fi : fieldInfos) { - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, fi.internalClassName, fi.fieldName, fi.fieldDescriptor); - } + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, eventHandlerXInternalName); + for (FieldInfo fi : fieldInfos) { + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitFieldInsn(Opcodes.GETFIELD, fi.internalClassName, fi.fieldName, fi.fieldDescriptor); + } - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, eventHandlerXInternalName, writeMethod.getName(), writeMethod.getDescriptor(), false); - methodVisitor.visitLabel(end); - methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - methodVisitor.visitInsn(Opcodes.RETURN); - methodVisitor.visitEnd(); - }); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, eventHandlerXInternalName, writeMethod.getName(), writeMethod.getDescriptor(), false); + methodVisitor.visitLabel(end); + methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + methodVisitor.visitInsn(Opcodes.RETURN); + methodVisitor.visitEnd(); + }); // MyEvent#shouldCommit() updateMethod(METHOD_EVENT_SHOULD_COMMIT, methodVisitor -> { diff --git a/src/share/classes/jdk/jfr/internal/EventWriter.java b/src/share/classes/jdk/jfr/internal/EventWriter.java index ef30e9411..c2dd51ab5 100644 --- a/src/share/classes/jdk/jfr/internal/EventWriter.java +++ b/src/share/classes/jdk/jfr/internal/EventWriter.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,7 @@ package jdk.jfr.internal; 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. @@ -115,18 +115,18 @@ public final class EventWriter { public void putString(String s, StringPool pool) { if (s == null) { - putByte(RecordingInput.STRING_ENCODING_NULL); + putByte(StringParser.Encoding.NULL.byteValue()); return; } int length = s.length(); if (length == 0) { - putByte(RecordingInput.STRING_ENCODING_EMPTY_STRING); + putByte(StringParser.Encoding.EMPTY_STRING.byteValue()); return; } if (length > StringPool.MIN_LIMIT && length < StringPool.MAX_LIMIT) { long l = StringPool.addString(s); if (l > 0) { - putByte(RecordingInput.STRING_ENCODING_CONSTANT_POOL); + putByte(StringParser.Encoding.CONSTANT_POOL.byteValue()); putLong(l); return; } @@ -138,7 +138,7 @@ public final class EventWriter { private void putStringValue(String s) { int length = s.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 for (int i = 0; i < length; i++) { putUncheckedChar(s.charAt(i)); // max 3 bytes @@ -197,11 +197,7 @@ public final class EventWriter { if (currentPosition + requestedSize > maxPosition) { flushOnEnd = flush(usedSize(), requestedSize); // retry - if (currentPosition + requestedSize > maxPosition) { - Logger.log(LogTag.JFR_SYSTEM, - LogLevel.WARN, () -> - "Unable to commit. Requested size " + requestedSize + " too large"); - valid = false; + if (!valid) { return false; } } diff --git a/src/share/classes/jdk/jfr/internal/FilePurger.java b/src/share/classes/jdk/jfr/internal/FilePurger.java new file mode 100644 index 000000000..4cc83e53f --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/FilePurger.java @@ -0,0 +1,73 @@ +/* + * 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 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; + } + } +} diff --git a/src/share/classes/jdk/jfr/internal/JVM.java b/src/share/classes/jdk/jfr/internal/JVM.java index f8067f4e3..c7039ae7d 100644 --- a/src/share/classes/jdk/jfr/internal/JVM.java +++ b/src/share/classes/jdk/jfr/internal/JVM.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -54,7 +54,7 @@ public final class JVM { // subscribeLogLevel(tag, tag.id); // } Options.ensureInitialized(); - EventHandlerProxyCreator.ensureInitialized(); + // EventHandlerProxyCreator.ensureInitialized(); } /** @@ -271,7 +271,6 @@ public final class JVM { * * @param file the file where data should be written, or null if it should * not be copied out (in memory). - * * @throws IOException */ public native void setOutput(String file); @@ -474,6 +473,16 @@ public final class JVM { */ 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. + *

+ * 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 * dump. @@ -538,4 +547,30 @@ public final class JVM { * @return if it is time to perform a chunk rotation */ 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(); } diff --git a/src/share/classes/jdk/jfr/internal/LogTag.java b/src/share/classes/jdk/jfr/internal/LogTag.java index 195d6b06a..c19417580 100644 --- a/src/share/classes/jdk/jfr/internal/LogTag.java +++ b/src/share/classes/jdk/jfr/internal/LogTag.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -62,22 +62,26 @@ public enum LogTag { * Covers metadata for JVM/JDK (for Hotspot developers) */ JFR_SYSTEM_METADATA(6), + /** + * Covers streaming (for Hotspot developers) + */ + JFR_SYSTEM_STREAMING(7), /** * Covers metadata for Java user (for Hotspot developers) */ - JFR_METADATA(7), + JFR_METADATA(8), /** * Covers events (for users of the JDK) */ - JFR_EVENT(8), + JFR_EVENT(9), /** * Covers setting (for users of the JDK) */ - JFR_SETTING(9), + JFR_SETTING(10), /** * Covers usage of jcmd with JFR */ - JFR_DCMD(10); + JFR_DCMD(11); /* set from native side */ volatile int tagSetLevel = 100; // prevent logging if JVM log system has not been initialized diff --git a/src/share/classes/jdk/jfr/internal/LongMap.java b/src/share/classes/jdk/jfr/internal/LongMap.java new file mode 100644 index 000000000..e4d83db10 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/LongMap.java @@ -0,0 +1,257 @@ +/* + * 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 { + 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 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 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(); + } +} diff --git a/src/share/classes/jdk/jfr/internal/MetadataDescriptor.java b/src/share/classes/jdk/jfr/internal/MetadataDescriptor.java index 40da229b7..82ff57a61 100644 --- a/src/share/classes/jdk/jfr/internal/MetadataDescriptor.java +++ b/src/share/classes/jdk/jfr/internal/MetadataDescriptor.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,6 @@ package jdk.jfr.internal; -import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; @@ -35,6 +34,7 @@ import java.util.Locale; import java.util.TimeZone; import jdk.jfr.EventType; +import jdk.jfr.internal.consumer.RecordingInput; /** * Metadata about a chunk @@ -214,6 +214,7 @@ public final class MetadataDescriptor { long gmtOffset; String locale; Element root; + public long metadataId; // package private MetadataDescriptor() { @@ -252,7 +253,7 @@ public final class MetadataDescriptor { return locale; } - public static MetadataDescriptor read(DataInput input) throws IOException { + public static MetadataDescriptor read(RecordingInput input) throws IOException { MetadataReader r = new MetadataReader(input); return r.getDescriptor(); } diff --git a/src/share/classes/jdk/jfr/internal/MetadataReader.java b/src/share/classes/jdk/jfr/internal/MetadataReader.java index 481e3700d..672465c58 100644 --- a/src/share/classes/jdk/jfr/internal/MetadataReader.java +++ b/src/share/classes/jdk/jfr/internal/MetadataReader.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -49,6 +49,8 @@ import jdk.jfr.AnnotationElement; import jdk.jfr.SettingDescriptor; import jdk.jfr.ValueDescriptor; import jdk.jfr.internal.MetadataDescriptor.Element; +import jdk.jfr.internal.consumer.RecordingInput; +import jdk.jfr.internal.consumer.StringParser; /** * Parses metadata. @@ -61,12 +63,13 @@ final class MetadataReader { private final MetadataDescriptor descriptor; private final Map types = new HashMap<>(); - public MetadataReader(DataInput input) throws IOException { + public MetadataReader(RecordingInput input) throws IOException { this.input = input; int size = input.readInt(); this.pool = new ArrayList<>(size); + StringParser p = new StringParser(null, false); for (int i = 0; i < size; i++) { - this.pool.add(input.readUTF()); + this.pool.add((String) p.parse(input)); } descriptor = new MetadataDescriptor(); Element root = createElement(); diff --git a/src/share/classes/jdk/jfr/internal/MetadataRepository.java b/src/share/classes/jdk/jfr/internal/MetadataRepository.java index b718664e1..14f3867d8 100644 --- a/src/share/classes/jdk/jfr/internal/MetadataRepository.java +++ b/src/share/classes/jdk/jfr/internal/MetadataRepository.java @@ -45,6 +45,7 @@ import jdk.jfr.StackTrace; import jdk.jfr.Threshold; import jdk.jfr.ValueDescriptor; import jdk.jfr.internal.RequestEngine.RequestHook; +import jdk.jfr.internal.consumer.RepositoryFiles; import jdk.jfr.internal.handlers.EventHandler; public final class MetadataRepository { @@ -184,10 +185,14 @@ public final class MetadataRepository { } public synchronized List getEventControls() { - List controls = new ArrayList<>(); + List> eventClasses = jvm.getAllEventClasses(); + ArrayList controls = new ArrayList<>(eventClasses.size() + nativeControls.size()); controls.addAll(nativeControls); - for (EventHandler eh : getEventHandlers()) { - controls.add(eh.getEventControl()); + for (Class clazz : eventClasses) { + EventHandler eh = Utils.getHandler(clazz); + if (eh != null) { + controls.add(eh.getEventControl()); + } } return controls; } @@ -240,7 +245,9 @@ public final class MetadataRepository { storeDescriptorInJVM(); } jvm.setOutput(filename); - + if (filename != null) { + RepositoryFiles.notifyNewFile(); + } unregisterUnloaded(); if (unregistered) { if (typeLibrary.clearUnregistered()) { @@ -276,4 +283,11 @@ public final class MetadataRepository { unregistered = true; } + public synchronized void flush() { + if (staleMetadata) { + storeDescriptorInJVM(); + } + jvm.flush(); + } + } diff --git a/src/share/classes/jdk/jfr/internal/MetadataWriter.java b/src/share/classes/jdk/jfr/internal/MetadataWriter.java index 952d9637f..8836acffa 100644 --- a/src/share/classes/jdk/jfr/internal/MetadataWriter.java +++ b/src/share/classes/jdk/jfr/internal/MetadataWriter.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -53,7 +53,7 @@ import jdk.jfr.SettingDescriptor; import jdk.jfr.ValueDescriptor; import jdk.jfr.internal.MetadataDescriptor.Attribute; 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 @@ -94,10 +94,10 @@ final class MetadataWriter { private void writeString(DataOutput out, String s) throws IOException { if (s == null ) { - out.writeByte(RecordingInput.STRING_ENCODING_NULL); + out.writeByte(StringParser.Encoding.NULL.byteValue()); 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(); writeInt(out, length); for (int i = 0; i < length; i++) { diff --git a/src/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/share/classes/jdk/jfr/internal/PlatformEventType.java index e6f98eb0e..de1300b8b 100644 --- a/src/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -69,7 +69,7 @@ public final class PlatformEventType extends Type { super(name, Type.SUPER_TYPE_EVENT, id); this.dynamicSettings = dynamicSettings; 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.stackTraceOffset = stackTraceOffset(name, isJDK); } diff --git a/src/share/classes/jdk/jfr/internal/PlatformRecorder.java b/src/share/classes/jdk/jfr/internal/PlatformRecorder.java index 7011541e6..432ef7568 100644 --- a/src/share/classes/jdk/jfr/internal/PlatformRecorder.java +++ b/src/share/classes/jdk/jfr/internal/PlatformRecorder.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -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_SYSTEM; -import java.io.IOException; import java.security.AccessControlContext; import java.security.AccessController; import java.time.Duration; @@ -60,6 +59,7 @@ import jdk.jfr.internal.instrument.JDKEvents; public final class PlatformRecorder { + private final List recordings = new ArrayList<>(); private final static List changeListeners = new ArrayList<>(); private final Repository repository; @@ -98,6 +98,7 @@ public final class PlatformRecorder { Thread t = SecuritySupport.createThreadWitNoPermissions("Permissionless thread", ()-> { result.add(new Timer("JFR Recording Scheduler", true)); }); + jvm.exclude(t); t.start(); t.join(); return result.get(0); @@ -206,7 +207,7 @@ public final class PlatformRecorder { repository.clear(); } - synchronized void start(PlatformRecording recording) { + synchronized long start(PlatformRecording recording) { // State can only be NEW or DELAYED because of previous checks Instant now = Instant.now(); recording.setStartTime(now); @@ -217,14 +218,17 @@ public final class PlatformRecorder { } boolean toDisk = recording.isToDisk(); boolean beginPhysical = true; + long streamInterval = recording.getStreamIntervalMillis(); for (PlatformRecording s : getRecordings()) { if (s.getState() == RecordingState.RUNNING) { beginPhysical = false; if (s.isToDisk()) { toDisk = true; } + streamInterval = Math.min(streamInterval, s.getStreamIntervalMillis()); } } + long startNanos = -1; if (beginPhysical) { RepositoryChunk newChunk = null; if (toDisk) { @@ -235,6 +239,7 @@ public final class PlatformRecorder { } currentChunk = newChunk; jvm.beginRecording_(); + startNanos = jvm.getChunkStartNanos(); recording.setState(RecordingState.RUNNING); updateSettings(); writeMetaEvents(); @@ -244,6 +249,7 @@ public final class PlatformRecorder { newChunk = repository.newChunk(now); RequestEngine.doChunkEnd(); MetadataRepository.getInstance().setOutput(newChunk.getUnfishedFile().toString()); + startNanos = jvm.getChunkStartNanos(); } recording.setState(RecordingState.RUNNING); updateSettings(); @@ -253,8 +259,12 @@ public final class PlatformRecorder { } currentChunk = newChunk; } - + if (toDisk) { + RequestEngine.setFlushInterval(streamInterval); + } RequestEngine.doChunkBegin(); + + return startNanos; } synchronized void stop(PlatformRecording recording) { @@ -269,6 +279,7 @@ public final class PlatformRecorder { Instant now = Instant.now(); boolean toDisk = false; boolean endPhysical = true; + long streamInterval = Long.MAX_VALUE; for (PlatformRecording s : getRecordings()) { RecordingState rs = s.getState(); if (s != recording && RecordingState.RUNNING == rs) { @@ -276,6 +287,7 @@ public final class PlatformRecorder { if (s.isToDisk()) { toDisk = true; } + streamInterval = Math.min(streamInterval, s.getStreamIntervalMillis()); } } OldObjectSample.emit(recording); @@ -311,6 +323,13 @@ public final class PlatformRecorder { currentChunk = newChunk; RequestEngine.doChunkBegin(); } + + if (toDisk) { + RequestEngine.setFlushInterval(streamInterval); + } else { + RequestEngine.setFlushInterval(Long.MAX_VALUE); + } + recording.setState(RecordingState.STOPPED); } @@ -340,6 +359,18 @@ public final class PlatformRecorder { 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() { Instant now = Instant.now(); RepositoryChunk newChunk = repository.newChunk(now); @@ -397,14 +428,14 @@ public final class PlatformRecorder { r.appendChunk(chunk); } } + FilePurger.purge(); } private void writeMetaEvents() { - if (activeRecordingEvent.isEnabled()) { + ActiveRecordingEvent event = ActiveRecordingEvent.EVENT.get(); for (PlatformRecording r : getRecordings()) { if (r.getState() == RecordingState.RUNNING && r.shouldWriteMetadataEvent()) { - ActiveRecordingEvent event = new ActiveRecordingEvent(); event.id = r.getId(); event.name = r.getName(); WriteableUserPath p = r.getDestination(); @@ -417,6 +448,8 @@ public final class PlatformRecorder { event.maxSize = size == null ? Long.MAX_VALUE : size; Instant start = r.getStartTime(); event.recordingStart = start == null ? Long.MAX_VALUE : start.toEpochMilli(); + Duration fi = r.getFlushInterval(); + event.flushInterval = fi == null ? Long.MAX_VALUE : fi.toMillis(); event.commit(); } } @@ -450,7 +483,7 @@ public final class PlatformRecorder { JVM.FILE_DELTA_CHANGE.wait(duration < 10 ? 10 : duration); } } catch (InterruptedException e) { - e.printStackTrace(); + // Ignore } } diff --git a/src/share/classes/jdk/jfr/internal/PlatformRecording.java b/src/share/classes/jdk/jfr/internal/PlatformRecording.java index 0e6e1c20f..a90e3e010 100644 --- a/src/share/classes/jdk/jfr/internal/PlatformRecording.java +++ b/src/share/classes/jdk/jfr/internal/PlatformRecording.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -84,6 +84,7 @@ public final class PlatformRecording implements AutoCloseable { private TimerTask startTask; private AccessControlContext noDestinationDumpOnExitAccessControlContext; private boolean shuoldWriteActiveRecordingEvent = true; + private Duration flushInterval = Duration.ofSeconds(1); PlatformRecording(PlatformRecorder recorder, long id) { // Typically the access control context is taken @@ -98,9 +99,10 @@ public final class PlatformRecording implements AutoCloseable { this.name = String.valueOf(id); } - public void start() { + public long start() { RecordingState oldState; RecordingState newState; + long startNanos = -1; synchronized (recorder) { oldState = getState(); if (!Utils.isBefore(state, RecordingState.RUNNING)) { @@ -111,7 +113,7 @@ public final class PlatformRecording implements AutoCloseable { startTask = null; startTime = null; } - recorder.start(this); + startNanos = recorder.start(this); Logger.log(LogTag.JFR, LogLevel.INFO, () -> { // Only print non-default values so it easy to see // which options were added @@ -143,6 +145,8 @@ public final class PlatformRecording implements AutoCloseable { newState = getState(); } notifyIfStateChanged(oldState, newState); + + return startNanos; } public boolean stop(String reason) { @@ -779,4 +783,28 @@ public final class PlatformRecording implements AutoCloseable { public boolean isRecorderEnabled(String 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; + } + } } diff --git a/src/share/classes/jdk/jfr/internal/Repository.java b/src/share/classes/jdk/jfr/internal/Repository.java index f48f8c603..a7ac12dfe 100644 --- a/src/share/classes/jdk/jfr/internal/Repository.java +++ b/src/share/classes/jdk/jfr/internal/Repository.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -43,6 +43,7 @@ public final class Repository { public final static DateTimeFormatter REPO_DATE_FORMAT = DateTimeFormatter .ofPattern("yyyy_MM_dd_HH_mm_ss"); + private static final String JFR_REPOSITORY_LOCATION_PROPERTY = "jdk.jfr.repository"; private final Set cleanupDirectories = new HashSet<>(); private SafePath baseLocation; @@ -55,7 +56,7 @@ public final class Repository { 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 // during JVM startup or JFR.configure this.repository = createRepository(baseLocation); @@ -69,7 +70,7 @@ public final class Repository { this.baseLocation = baseLocation; } - synchronized void ensureRepository() throws Exception { + public synchronized void ensureRepository() throws IOException { if (baseLocation == null) { setBasePath(SecuritySupport.JAVA_IO_TMPDIR); } @@ -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 f = null; @@ -108,13 +109,14 @@ public final class Repository { } 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); + SecuritySupport.setProperty(JFR_REPOSITORY_LOCATION_PROPERTY, canonicalRepositoryPath.toString()); return canonicalRepositoryPath; } - private static SafePath createRealBasePath(SafePath safePath) throws Exception { + private static SafePath createRealBasePath(SafePath safePath) throws IOException { if (SecuritySupport.exists(safePath)) { if (!SecuritySupport.isWritable(safePath)) { throw new IOException("JFR repository directory (" + safePath.toString() + ") exists, but isn't writable"); @@ -154,7 +156,7 @@ public final class Repository { SecuritySupport.clearDirectory(p); Logger.log(LogTag.JFR, LogLevel.INFO, "Removed repository " + p); } 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()); } } } diff --git a/src/share/classes/jdk/jfr/internal/RepositoryChunk.java b/src/share/classes/jdk/jfr/internal/RepositoryChunk.java index 373f17205..f51800519 100644 --- a/src/share/classes/jdk/jfr/internal/RepositoryChunk.java +++ b/src/share/classes/jdk/jfr/internal/RepositoryChunk.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -63,10 +63,10 @@ final class RepositoryChunk { LocalDateTime.ofInstant(startTime, z.getZone())); this.startTime = startTime; this.repositoryPath = path; - this.unFinishedFile = findFileName(repositoryPath, fileName, ".part"); + this.unFinishedFile = findFileName(repositoryPath, fileName, ".jfr"); this.file = findFileName(repositoryPath, fileName, ".jfr"); this.unFinishedRAF = SecuritySupport.createRandomAccessFile(unFinishedFile); - SecuritySupport.touch(file); + // SecuritySupport.touch(file); } private static SafePath findFileName(SafePath directory, String name, String extension) throws Exception { @@ -105,8 +105,6 @@ final class RepositoryChunk { private static long finish(SafePath unFinishedFile, SafePath file) throws IOException { Objects.requireNonNull(unFinishedFile); Objects.requireNonNull(file); - SecuritySupport.delete(file); - SecuritySupport.moveReplace(unFinishedFile, file); return SecuritySupport.getFileSize(file); } @@ -123,9 +121,11 @@ final class RepositoryChunk { SecuritySupport.delete(f); Logger.log(LogTag.JFR, LogLevel.DEBUG, () -> "Repository chunk " + f + " deleted"); } 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) { - SecuritySupport.deleteOnExit(f); + FilePurger.add(f); } } } diff --git a/src/share/classes/jdk/jfr/internal/RequestEngine.java b/src/share/classes/jdk/jfr/internal/RequestEngine.java index 3455f1f99..fa5374ba4 100644 --- a/src/share/classes/jdk/jfr/internal/RequestEngine.java +++ b/src/share/classes/jdk/jfr/internal/RequestEngine.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -30,6 +30,7 @@ import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; @@ -96,6 +97,8 @@ public final class RequestEngine { private final static List entries = new CopyOnWriteArrayList<>(); 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) { Objects.requireNonNull(acc); @@ -209,7 +212,9 @@ public final class RequestEngine { lastTimeMillis = now; return 0; } - for (RequestHook he : entries) { + Iterator hookIterator = entries.iterator(); + while(hookIterator.hasNext()) { + RequestHook he = hookIterator.next(); long left = 0; PlatformEventType es = he.type; // Not enabled, skip. @@ -228,7 +233,6 @@ public final class RequestEngine { // for wait > period r_delta = 0; he.execute(); - ; } // calculate time left @@ -250,7 +254,39 @@ public final class RequestEngine { 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; 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(); + } + } + } } diff --git a/src/share/classes/jdk/jfr/internal/SecuritySupport.java b/src/share/classes/jdk/jfr/internal/SecuritySupport.java index 769231409..a944ba9df 100644 --- a/src/share/classes/jdk/jfr/internal/SecuritySupport.java +++ b/src/share/classes/jdk/jfr/internal/SecuritySupport.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -37,6 +37,7 @@ import java.lang.reflect.Method; import java.lang.reflect.ReflectPermission; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; +import java.nio.file.DirectoryStream; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -63,6 +64,7 @@ import jdk.jfr.FlightRecorder; import jdk.jfr.FlightRecorderListener; import jdk.jfr.FlightRecorderPermission; import jdk.jfr.Recording; +import jdk.jfr.internal.consumer.FileAccess; /** * Contains JFR code that does @@ -71,7 +73,7 @@ import jdk.jfr.Recording; public final class SecuritySupport { private final static Unsafe unsafe = Unsafe.getUnsafe(); 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 JAVA_IO_TMPDIR = getPathInProperty("java.io.tmpdir", null); @@ -138,7 +140,7 @@ public final class SecuritySupport { * a malicious provider. * */ - public static final class SafePath { + public static final class SafePath implements Comparable { private final Path path; private final String text; @@ -156,9 +158,18 @@ public final class SecuritySupport { return path; } + public File toFile() { + return path.toFile(); + } + public String toString() { return text; } + + @Override + public int compareTo(SafePath that) { + return that.text.compareTo(this.text); + } } private interface RunnableWithCheckedException { @@ -256,6 +267,10 @@ public final class SecuritySupport { 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) { return doPrivilegedWithReturn(() -> Boolean.getBoolean(propertyName), new PropertyPermission(propertyName, "read")); } @@ -296,7 +311,7 @@ public final class SecuritySupport { 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())); } @@ -322,7 +337,8 @@ public final class SecuritySupport { } 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 { @@ -377,7 +393,7 @@ public final class SecuritySupport { 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]); } @@ -388,4 +404,32 @@ public final class SecuritySupport { public static SafePath getAbsolutePath(SafePath path) throws IOException { 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 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)); + } + } + + } diff --git a/src/share/classes/jdk/jfr/internal/SettingsManager.java b/src/share/classes/jdk/jfr/internal/SettingsManager.java index 592f216c1..b722cabf9 100644 --- a/src/share/classes/jdk/jfr/internal/SettingsManager.java +++ b/src/share/classes/jdk/jfr/internal/SettingsManager.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -33,11 +33,11 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.StringJoiner; import jdk.jfr.Event; +import jdk.jfr.internal.EventControl.NamedControl; import jdk.jfr.internal.handlers.EventHandler; final class SettingsManager { @@ -214,18 +214,21 @@ final class SettingsManager { void setEventControl(EventControl ec) { InternalSetting is = getInternalSetting(ec); - Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "Applied settings for " + ec.getEventType().getLogName() + " {"); - for (Entry entry : ec.getEntries()) { + boolean shouldLog = Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO); + if (shouldLog) { + Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "Applied settings for " + ec.getEventType().getLogName() + " {"); + } + for (NamedControl nc: ec.getNamedControls()) { Set values = null; - String settingName = entry.getKey(); + String settingName = nc.name; if (is != null) { values = is.getValues(settingName); } - Control control = entry.getValue(); + Control control = nc.control; if (values != null) { control.apply(values); String after = control.getLastValue(); - if (Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO)) { + if (shouldLog) { if (Utils.isSettingVisible(control, ec.getEventType().hasEventHook())) { if (values.size() > 1) { StringJoiner sj = new StringJoiner(", ", "{", "}"); @@ -242,14 +245,16 @@ final class SettingsManager { } } else { control.setDefault(); - if (Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO)) { + if (shouldLog) { String message = " " + settingName + "=\"" + control.getLastValue() + "\""; Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, message); } } } ec.writeActiveSettingEvent(); - Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "}"); + if (shouldLog) { + Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "}"); + } } private InternalSetting getInternalSetting(EventControl ec) { diff --git a/src/share/classes/jdk/jfr/internal/TypeLibrary.java b/src/share/classes/jdk/jfr/internal/TypeLibrary.java index 40544e944..b88da56ff 100644 --- a/src/share/classes/jdk/jfr/internal/TypeLibrary.java +++ b/src/share/classes/jdk/jfr/internal/TypeLibrary.java @@ -408,6 +408,7 @@ public final class TypeLibrary { // Purpose of this method is to mark types that are reachable // from registered event types. Those types that are not reachable can // safely be removed + // Returns true if type was removed public boolean clearUnregistered() { Logger.log(LogTag.JFR_METADATA, LogLevel.TRACE, "Cleaning out obsolete metadata"); List registered = new ArrayList<>(); diff --git a/src/share/classes/jdk/jfr/internal/Utils.java b/src/share/classes/jdk/jfr/internal/Utils.java index 9c2a858fb..21029323f 100644 --- a/src/share/classes/jdk/jfr/internal/Utils.java +++ b/src/share/classes/jdk/jfr/internal/Utils.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -43,6 +43,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.file.Path; import java.time.Duration; +import java.time.Instant; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; @@ -65,18 +66,18 @@ import jdk.jfr.internal.settings.ThresholdSetting; public final class Utils { + private static final Object flushObject = new Object(); 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 INSTRUMENT_PACKAGE_NAME = "jdk.jfr.internal.instrument"; public static final String HANDLERS_PACKAGE_NAME = "jdk.jfr.internal.handlers"; public static final String REGISTER_EVENT = "registerEvent"; public static final String ACCESS_FLIGHT_RECORDER = "accessFlightRecorder"; - private final static String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk."; + private static Boolean SAVE_GENERATED; + + public static void checkAccessFlightRecorder() throws SecurityException { SecurityManager sm = System.getSecurityManager(); if (sm != null) { @@ -551,4 +552,33 @@ public final class Utils { String idText = recording == null ? "" : "-id-" + Long.toString(recording.getId()); 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(); + } } diff --git a/src/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java b/src/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java new file mode 100644 index 000000000..97c5a5c2a --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java @@ -0,0 +1,273 @@ +/* + * 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 action) { + Objects.requireNonNull(action); + configuration.addEventAction(action); + } + + @Override + public final void onEvent(String eventName, Consumer 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 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() { + @Override + public Void run() { + execute(); + return null; + } + }, accessControlContext); + } + + private String nextThreadName() { + counter.incrementAndGet(); + return "JFR Event Stream " + counter; + } +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java b/src/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java index fcdf14adf..3fc4c657f 100644 --- a/src/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java +++ b/src/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -25,48 +25,60 @@ package jdk.jfr.internal.consumer; -import java.io.DataInput; import java.io.IOException; import jdk.jfr.internal.LogLevel; import jdk.jfr.internal.LogTag; import jdk.jfr.internal.Logger; import jdk.jfr.internal.MetadataDescriptor; +import jdk.jfr.internal.Utils; 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 byte[] FILE_MAGIC = { 'F', 'L', 'R', '\0' }; private final short major; private final short minor; - private final long chunkSize; private final long chunkStartTicks; private final long ticksPerSecond; 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 boolean lastChunk; private final RecordingInput input; - private final long durationNanos; 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 { this(input, 0, 0); } 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); if (input.position() >= input.size()) { - throw new IOException("Chunk contains no data"); + throw new IOException("Chunk contains no data"); } verifyMagic(input); this.input = input; 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); major = input.readRawShort(); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: major=" + major); @@ -75,11 +87,11 @@ public final class ChunkHeader { 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."); } - chunkSize = input.readRawLong(); + input.readRawLong(); // chunk size 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); - metadataPosition = input.readRawLong(); + input.readRawLong(); // metadata position Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: metadataPosition=" + metadataPosition); chunkStartNanos = input.readRawLong(); // nanos since epoch Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startNanos=" + chunkStartNanos); @@ -91,21 +103,98 @@ public final class ChunkHeader { Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: ticksPerSecond=" + ticksPerSecond); input.readRawInt(); // features, not used - // set up boundaries - this.absoluteChunkStart = absoluteChunkStart; - absoluteChunkEnd = absoluteChunkStart + chunkSize; - lastChunk = input.size() == absoluteChunkEnd; - absoluteEventStart = input.position(); - - // read metadata + refresh(); 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 { return new ChunkHeader(input, absoluteChunkEnd, id + 1); } - public MetadataDescriptor readMetadata() throws IOException { + return readMetadata(null); + } + + public MetadataDescriptor readMetadata(MetadataDescriptor previous) throws IOException { input.position(absoluteChunkStart + metadataPosition); input.readInt(); // size long id = input.readLong(); // event type id @@ -115,15 +204,15 @@ public final class ChunkHeader { input.readLong(); // start time input.readLong(); // duration long metadataId = input.readLong(); - Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "Metadata id=" + metadataId); - // No need to read if metadataId == lastMetadataId, but we - // do it for verification purposes. - return MetadataDescriptor.read(input); + if (previous != null && metadataId == previous.metadataId) { + return previous; + } + 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() { return major; @@ -137,13 +226,22 @@ public final class ChunkHeader { return absoluteChunkStart; } + public long getAbsoluteEventStart() { + return absoluteEventStart; + } public long getConstantPoolPosition() { return constantPoolPosition; } + public long getMetataPosition() { + return metadataPosition; + } public long getStartTicks() { return chunkStartTicks; } + public long getChunkSize() { + return chunkSize; + } public double getTicksPerSecond() { return ticksPerSecond; @@ -169,7 +267,7 @@ public final class ChunkHeader { return input; } - private static void verifyMagic(DataInput input) throws IOException { + private static void verifyMagic(RecordingInput input) throws IOException { for (byte c : FILE_MAGIC) { if (input.readByte() != c) { throw new IOException("Not a Flight Recorder file"); @@ -181,4 +279,7 @@ public final class ChunkHeader { return absoluteEventStart; } + static long headerSize() { + return HEADER_SIZE; + } } diff --git a/src/share/classes/jdk/jfr/internal/consumer/ChunkParser.java b/src/share/classes/jdk/jfr/internal/consumer/ChunkParser.java new file mode 100644 index 000000000..6bc20361a --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/ChunkParser.java @@ -0,0 +1,451 @@ +/* + * 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 constantLookups; + + private LongMap typeMap; + private LongMap 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 getTypes() { + return metadata.getTypes(); + } + + public List 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(); + } + +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/ConstantLookup.java b/src/share/classes/jdk/jfr/internal/consumer/ConstantLookup.java new file mode 100644 index 000000000..a88b935e7 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/ConstantLookup.java @@ -0,0 +1,62 @@ +/* + * 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); + } +} diff --git a/src/share/classes/jdk/jfr/consumer/ConstantMap.java b/src/share/classes/jdk/jfr/internal/consumer/ConstantMap.java similarity index 57% rename from src/share/classes/jdk/jfr/consumer/ConstantMap.java rename to src/share/classes/jdk/jfr/internal/consumer/ConstantMap.java index 4779e03d6..c45b80574 100644 --- a/src/share/classes/jdk/jfr/consumer/ConstantMap.java +++ b/src/share/classes/jdk/jfr/internal/consumer/ConstantMap.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -23,10 +23,9 @@ * questions. */ -package jdk.jfr.consumer; +package jdk.jfr.internal.consumer; -import java.util.ArrayList; -import java.util.List; +import jdk.jfr.internal.LongMap; /** * Holds mapping between a set of keys and their corresponding object. @@ -35,6 +34,15 @@ import java.util.List; * {@link ObjectFactory} can be supplied which will instantiate a typed object. */ final class ConstantMap { + + private static final int RESOLUTION_FINISHED = 0; + private static final int RESOLUTION_STARTED = 1; + public static final ConstantMap EMPTY = new ConstantMap(); + + // A temporary placeholder, so objects can + // reference themselves (directly, or indirectly), + // when making a transition from numeric id references + // to normal Java references. private final static class Reference { private final long key; private final ConstantMap pool; @@ -47,18 +55,28 @@ final class ConstantMap { Object resolve() { 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 objects; - private LongMap isResolving; + private boolean resolving; private boolean allResolved; - private String name; + + private ConstantMap() { + this(null, ""); + allResolved = true; + } ConstantMap(ObjectFactory factory, String name) { this.name = name; - this.objects = new LongMap<>(); + this.objects = new LongMap<>(2); this.factory = factory; } @@ -68,26 +86,42 @@ final class ConstantMap { return objects.get(id); } // referenced from a pool, deal with this later - if (isResolving == null) { + if (!resolving) { 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) - if (Boolean.FALSE.equals(beingResolved)) { - return objects.get(id); + // id is resolved (but not the whole pool) + if (objects.isSetId(id, RESOLUTION_FINISHED)) { + return value; } // resolving ourself, abort to avoid infinite recursion - if (Boolean.TRUE.equals(beingResolved)) { + if (objects.isSetId(id, RESOLUTION_STARTED)) { return null; } - // resolve me! - isResolving.put(id, Boolean.TRUE); - Object resolved = resolve(objects.get(id)); - isResolving.put(id, Boolean.FALSE); + // mark id as STARTED if we should + // come back during object resolution + objects.setId(id, RESOLUTION_STARTED); + + // 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) { Object factorized = factory.createObject(id, resolved); objects.put(id, factorized); @@ -105,7 +139,8 @@ final class ConstantMap { if (o != null && o.getClass().isArray()) { final Object[] array = (Object[]) o; for (int i = 0; i < array.length; i++) { - array[i] = resolve(array[i]); + Object element = array[i]; + array[i] = resolve(element); } return array; } @@ -113,27 +148,37 @@ final class ConstantMap { } public void resolve() { - List keyList = new ArrayList<>(); - objects.keys().forEachRemaining(keyList::add); - for (Long l : keyList) { - get(l); - } + objects.forEachKey(k -> get(k)); } public void put(long key, Object value) { objects.put(key, value); } - public void setIsResolving() { - isResolving = new LongMap<>(); + public void setResolving() { + resolving = true; + allResolved = false; } public void setResolved() { allResolved = true; - isResolving = null; // pool finished, release memory + resolving = false; } public String getName() { 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; + } } diff --git a/src/share/classes/jdk/jfr/internal/consumer/Dispatcher.java b/src/share/classes/jdk/jfr/internal/consumer/Dispatcher.java new file mode 100644 index 000000000..4dab2a5b9 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/Dispatcher.java @@ -0,0 +1,188 @@ +/* + * 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 action; + + public EventDispatcher(String eventName, Consumer 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 getAction() { + return action; + } + } + + private final Consumer[] errorActions; + private final Runnable[] flushActions; + private final Runnable[] closeActions; + private final EventDispatcher[] dispatchers; + private final LongMap 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 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 consumer = (Consumer) consumers[i]; + consumer.accept(e); + } + } + + private void defaultErrorHandler(Throwable e) { + e.printStackTrace(); + } +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java b/src/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java new file mode 100644 index 000000000..13cad0636 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java @@ -0,0 +1,209 @@ +/* + * 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 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); + } + } + } +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/EventFileStream.java b/src/share/classes/jdk/jfr/internal/consumer/EventFileStream.java new file mode 100644 index 000000000..130af0f98 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/EventFileStream.java @@ -0,0 +1,147 @@ +/* + * 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.util.Arrays; +import java.util.Comparator; +import java.util.Objects; + +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.internal.consumer.Dispatcher; +import jdk.jfr.internal.consumer.FileAccess; +import jdk.jfr.internal.consumer.RecordingInput; + +/** + * Implementation of an event stream that operates against a recording file. + * + */ +public final class EventFileStream extends AbstractEventStream { + private final static Comparator EVENT_COMPARATOR = JdkJfrConsumer.instance().eventComparator(); + + private final RecordingInput input; + + private ChunkParser currentParser; + private RecordedEvent[] cacheSorted; + + public EventFileStream(AccessControlContext acc, Path path) throws IOException { + super(acc, false); + Objects.requireNonNull(path); + this.input = new RecordingInput(path.toFile(), FileAccess.UNPRIVILIGED); + } + + @Override + public void start() { + start(0); + } + + @Override + public void startAsync() { + startAsync(0); + } + + @Override + public void close() { + setClosed(true); + dispatcher().runCloseActions(); + try { + input.close(); + } catch (IOException e) { + // ignore + } + } + + @Override + protected void process() throws IOException { + Dispatcher disp = dispatcher(); + long start = 0; + long end = Long.MAX_VALUE; + if (disp.startTime != null) { + start = disp.startNanos; + } + if (disp.endTime != null) { + end = disp.endNanos; + } + + currentParser = new ChunkParser(input, disp.parserConfiguration); + while (!isClosed()) { + if (currentParser.getStartNanos() > end) { + close(); + return; + } + disp = dispatcher(); + disp.parserConfiguration.filterStart = start; + disp.parserConfiguration.filterEnd = end; + currentParser.updateConfiguration(disp.parserConfiguration, true); + currentParser.setFlushOperation(getFlushOperation()); + if (disp.parserConfiguration.isOrdered()) { + processOrdered(disp); + } else { + processUnordered(disp); + } + if (isClosed() || currentParser.isLastChunk()) { + return; + } + currentParser = currentParser.nextChunkParser(); + } + } + + private void processOrdered(Dispatcher c) throws IOException { + if (cacheSorted == null) { + cacheSorted = new RecordedEvent[10_000]; + } + RecordedEvent event; + int index = 0; + while (true) { + event = currentParser.readEvent(); + if (event == null) { + Arrays.sort(cacheSorted, 0, index, EVENT_COMPARATOR); + for (int i = 0; i < index; i++) { + c.dispatch(cacheSorted[i]); + } + return; + } + if (index == cacheSorted.length) { + RecordedEvent[] tmp = cacheSorted; + cacheSorted = new RecordedEvent[2 * tmp.length]; + System.arraycopy(tmp, 0, cacheSorted, 0, tmp.length); + } + cacheSorted[index++] = event; + } + } + + private void processUnordered(Dispatcher c) throws IOException { + while (!isClosed()) { + RecordedEvent event = currentParser.readEvent(); + if (event == null) { + return; + } + c.dispatch(event); + } + } +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/EventParser.java b/src/share/classes/jdk/jfr/internal/consumer/EventParser.java new file mode 100644 index 000000000..4d6661c30 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/EventParser.java @@ -0,0 +1,198 @@ +/* + * 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 static jdk.jfr.internal.EventInstrumentation.FIELD_DURATION; + +import java.io.IOException; +import java.util.List; + +import jdk.jfr.EventType; +import jdk.jfr.ValueDescriptor; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.internal.consumer.Parser; +import jdk.jfr.internal.consumer.RecordingInput; + +/** + * Parses an event and returns a {@link RecordedEvent}. + * + */ +final class EventParser extends Parser { + + private static final JdkJfrConsumer PRIVATE_ACCESS = JdkJfrConsumer.instance(); + + private final Parser[] parsers; + private final EventType eventType; + private final TimeConverter timeConverter; + private final boolean hasDuration; + private final List valueDescriptors; + private final int startIndex; + private final int length; + private final RecordedEvent unorderedEvent; + private final ObjectContext objectContext; + + private RecordedEvent[] cached; + private int cacheIndex; + + private boolean enabled = true; + private boolean ordered; + private long filterStart; + private long filterEnd = Long.MAX_VALUE; + private long thresholdNanos = -1; + + EventParser(TimeConverter timeConverter, EventType type, Parser[] parsers) { + this.timeConverter = timeConverter; + this.parsers = parsers; + this.eventType = type; + this.hasDuration = type.getField(FIELD_DURATION) != null; + this.startIndex = hasDuration ? 2 : 1; + this.length = parsers.length - startIndex; + this.valueDescriptors = type.getFields(); + this.objectContext = new ObjectContext(type, valueDescriptors, timeConverter); + this.unorderedEvent = PRIVATE_ACCESS.newRecordedEvent(objectContext, new Object[length], 0L, 0L); + } + + private RecordedEvent cachedEvent() { + if (ordered) { + if (cacheIndex == cached.length) { + RecordedEvent[] old = cached; + cached = new RecordedEvent[cached.length * 2]; + System.arraycopy(old, 0, cached, 0, old.length); + } + RecordedEvent event = cached[cacheIndex]; + if (event == null) { + event = PRIVATE_ACCESS.newRecordedEvent(objectContext, new Object[length], 0L, 0L); + cached[cacheIndex] = event; + } + cacheIndex++; + return event; + } else { + return unorderedEvent; + } + } + + public EventType getEventType() { + return eventType; + } + + public void setThresholdNanos(long thresholdNanos) { + this.thresholdNanos = thresholdNanos; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public RecordedEvent parse(RecordingInput input) throws IOException { + if (!enabled) { + return null; + } + + long startTicks = input.readLong(); + long endTicks = startTicks; + if (hasDuration) { + long durationTicks = input.readLong(); + if (thresholdNanos > 0L) { + if (timeConverter.convertTimespan(durationTicks) < thresholdNanos) { + return null; + } + } + endTicks += durationTicks; + } + if (filterStart != 0L || filterEnd != Long.MAX_VALUE) { + long eventEnd = timeConverter.convertTimestamp(endTicks); + if (eventEnd < filterStart) { + return null; + } + if (eventEnd > filterEnd) { + return null; + } + } + + if (cached != null) { + RecordedEvent event = cachedEvent(); + JdkJfrConsumer access = PRIVATE_ACCESS; + access.setStartTicks(event, startTicks); + access.setEndTicks(event, endTicks); + Object[] values = access.eventValues(event); + for (int i = 0; i < values.length; i++) { + values[i] = parsers[startIndex + i].parse(input); + } + return event; + } + + Object[] values = new Object[length]; + for (int i = 0; i < values.length; i++) { + values[i] = parsers[startIndex + i].parse(input); + } + return PRIVATE_ACCESS.newRecordedEvent(objectContext, values, startTicks, endTicks); + } + + @Override + public void skip(RecordingInput input) throws IOException { + throw new InternalError("Should not call this method. More efficent to read event size and skip ahead"); + } + + public void resetCache() { + cacheIndex = 0; + } + + private boolean hasReuse() { + return cached != null; + } + + public void setReuse(boolean reuse) { + if (reuse == hasReuse()) { + return; + } + if (reuse) { + cached = new RecordedEvent[2]; + cacheIndex = 0; + } else { + cached = null; + } + } + + public void setFilterStart(long filterStart) { + this.filterStart = filterStart; + } + + public void setFilterEnd(long filterEnd) { + this.filterEnd = filterEnd; + } + + public void setOrdered(boolean ordered) { + if (this.ordered == ordered) { + return; + } + this.ordered = ordered; + this.cacheIndex = 0; + } +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/FileAccess.java b/src/share/classes/jdk/jfr/internal/consumer/FileAccess.java new file mode 100644 index 000000000..5ffb0636a --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/FileAccess.java @@ -0,0 +1,73 @@ +/* + * 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 java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; + +// Protected by modular boundaries. +public abstract class FileAccess { + public final static FileAccess UNPRIVILIGED = new UnPriviliged(); + + public abstract RandomAccessFile openRAF(File f, String mode) throws IOException; + + public abstract DirectoryStream newDirectoryStream(Path repository) throws IOException; + + public abstract String getAbsolutePath(File f) throws IOException; + + public abstract long length(File f) throws IOException; + + public abstract long fileSize(Path p) throws IOException; + + private static class UnPriviliged extends FileAccess { + @Override + public RandomAccessFile openRAF(File f, String mode) throws IOException { + return new RandomAccessFile(f, mode); + } + + @Override + public DirectoryStream newDirectoryStream(Path dir) throws IOException { + return Files.newDirectoryStream(dir); + } + + @Override + public String getAbsolutePath(File f) throws IOException { + return f.getAbsolutePath(); + } + + @Override + public long length(File f) throws IOException { + return f.length(); + } + + @Override + public long fileSize(Path p) throws IOException { + return Files.size(p); + } + } +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/JdkJfrConsumer.java b/src/share/classes/jdk/jfr/internal/consumer/JdkJfrConsumer.java new file mode 100644 index 000000000..64f7885e1 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/JdkJfrConsumer.java @@ -0,0 +1,101 @@ +/* + * 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.util.Comparator; +import java.util.List; + +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedClassLoader; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.consumer.RecordedObject; +import jdk.jfr.consumer.RecordedStackTrace; +import jdk.jfr.consumer.RecordedThread; +import jdk.jfr.consumer.RecordedThreadGroup; +import jdk.jfr.consumer.RecordingFile; +import jdk.jfr.internal.Type; +/* + * Purpose of this class is to give package private access to + * the jdk.jfr.consumer package + */ +public abstract class JdkJfrConsumer { + + private static JdkJfrConsumer instance; + + // Initialization will trigger setAccess being called + private static void forceInitializetion() { + try { + Class c = RecordedObject.class; + Class.forName(c.getName(), true, c.getClassLoader()); + } catch (ClassNotFoundException e) { + throw new InternalError("Should not happen"); + } + } + + public static void setAccess(JdkJfrConsumer access) { + instance = access; + } + + public static JdkJfrConsumer instance() { + if (instance == null) { + forceInitializetion(); + } + return instance; + } + + public abstract List readTypes(RecordingFile file) throws IOException; + + public abstract boolean isLastEventInChunk(RecordingFile file); + + public abstract Object getOffsetDataTime(RecordedObject event, String name); + + public abstract RecordedClass newRecordedClass(ObjectContext objectContext, long id, Object[] values); + + public abstract RecordedClassLoader newRecordedClassLoader(ObjectContext objectContext, long id, Object[] values); + + public abstract RecordedStackTrace newRecordedStackTrace(ObjectContext objectContext, Object[] values); + + public abstract RecordedThreadGroup newRecordedThreadGroup(ObjectContext objectContext, Object[] values); + + public abstract RecordedFrame newRecordedFrame(ObjectContext objectContext, Object[] values); + + public abstract RecordedThread newRecordedThread(ObjectContext objectContext, long id, Object[] values); + + public abstract RecordedMethod newRecordedMethod(ObjectContext objectContext, Object[] values); + + public abstract RecordedEvent newRecordedEvent(ObjectContext objectContext, Object[] objects, long l, long m); + + public abstract Comparator eventComparator(); + + public abstract void setStartTicks(RecordedEvent event, long startTicks); + + public abstract void setEndTicks(RecordedEvent event, long endTicks); + + public abstract Object[] eventValues(RecordedEvent event); +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/ObjectContext.java b/src/share/classes/jdk/jfr/internal/consumer/ObjectContext.java new file mode 100644 index 000000000..3c946ba20 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/ObjectContext.java @@ -0,0 +1,77 @@ +/* + * 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.ZoneId; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jdk.jfr.EventType; +import jdk.jfr.ValueDescriptor; + +public final class ObjectContext { + private final Map contextLookup; + private final TimeConverter timeConverter; + + public final EventType eventType; + public final List fields; + + ObjectContext(EventType eventType, List fields, TimeConverter timeConverter) { + this.contextLookup = new HashMap<>(); + this.eventType = eventType; + this.fields = fields; + this.timeConverter = timeConverter; + } + + private ObjectContext(ObjectContext parent, ValueDescriptor descriptor) { + this.eventType = parent.eventType; + this.contextLookup = parent.contextLookup; + this.timeConverter = parent.timeConverter; + this.fields = descriptor.getFields(); + } + + public ObjectContext getInstance(ValueDescriptor descriptor) { + ObjectContext context = contextLookup.get(descriptor); + if (context == null) { + context = new ObjectContext(this, descriptor); + contextLookup.put(descriptor, context); + } + return context; + } + + public long convertTimestamp(long ticks) { + return timeConverter.convertTimestamp(ticks); + } + + public long convertTimespan(long ticks) { + return timeConverter.convertTimespan(ticks); + } + + public ZoneId getZoneOffset() { + return timeConverter.getZoneOffset(); + } +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/ObjectFactory.java b/src/share/classes/jdk/jfr/internal/consumer/ObjectFactory.java new file mode 100644 index 000000000..7c9983893 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/ObjectFactory.java @@ -0,0 +1,153 @@ +/* + * 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 jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedClassLoader; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.consumer.RecordedStackTrace; +import jdk.jfr.consumer.RecordedThread; +import jdk.jfr.consumer.RecordedThreadGroup; +import jdk.jfr.internal.Type; + +/** + * Abstract factory for creating specialized types + */ +public abstract class ObjectFactory { + private static final JdkJfrConsumer PRIVATE_ACCESS = JdkJfrConsumer.instance(); + + private final static String TYPE_PREFIX_VERSION_1 = "com.oracle.jfr.types."; + private final static String TYPE_PREFIX_VERSION_2 = Type.TYPES_PREFIX; + public final static String STACK_FRAME_VERSION_1 = TYPE_PREFIX_VERSION_1 + "StackFrame"; + public final static String STACK_FRAME_VERSION_2 = TYPE_PREFIX_VERSION_2 + "StackFrame"; + + static ObjectFactory create(Type type, TimeConverter timeConverter) { + switch (type.getName()) { + case "java.lang.Thread": + return createThreadFactory(type, timeConverter); + case TYPE_PREFIX_VERSION_1 + "StackFrame": + case TYPE_PREFIX_VERSION_2 + "StackFrame": + return createFrameFactory(type, timeConverter); + case TYPE_PREFIX_VERSION_1 + "Method": + case TYPE_PREFIX_VERSION_2 + "Method": + return createMethodFactory(type, timeConverter); + case TYPE_PREFIX_VERSION_1 + "ThreadGroup": + case TYPE_PREFIX_VERSION_2 + "ThreadGroup": + return createdThreadGroupFactory(type, timeConverter); + case TYPE_PREFIX_VERSION_1 + "StackTrace": + case TYPE_PREFIX_VERSION_2 + "StackTrace": + return createStackTraceFactory(type, timeConverter); + case TYPE_PREFIX_VERSION_1 + "ClassLoader": + case TYPE_PREFIX_VERSION_2 + "ClassLoader": + return createClassLoaderFactory(type, timeConverter); + case "java.lang.Class": + return createClassFactory(type, timeConverter); + } + return null; + } + + private static ObjectFactory createClassFactory(Type type, TimeConverter timeConverter) { + return new ObjectFactory(type, timeConverter) { + @Override + RecordedClass createTyped(ObjectContext objectContext, long id, Object[] values) { + return PRIVATE_ACCESS.newRecordedClass(objectContext, id, values); + } + }; + } + + private static ObjectFactory createClassLoaderFactory(Type type, TimeConverter timeConverter) { + return new ObjectFactory(type, timeConverter) { + @Override + RecordedClassLoader createTyped(ObjectContext objectContext, long id, Object[] values) { + return PRIVATE_ACCESS.newRecordedClassLoader(objectContext, id, values); + } + }; + } + + private static ObjectFactory createStackTraceFactory(Type type, TimeConverter timeConverter) { + return new ObjectFactory(type, timeConverter) { + @Override + RecordedStackTrace createTyped(ObjectContext objectContext, long id, Object[] values) { + return PRIVATE_ACCESS.newRecordedStackTrace(objectContext, values); + } + }; + } + + private static ObjectFactory createdThreadGroupFactory(Type type, TimeConverter timeConverter) { + return new ObjectFactory(type, timeConverter) { + @Override + RecordedThreadGroup createTyped(ObjectContext objectContext, long id, Object[] values) { + return PRIVATE_ACCESS.newRecordedThreadGroup(objectContext, values); + } + }; + } + + private static ObjectFactory createMethodFactory(Type type, TimeConverter timeConverter) { + return new ObjectFactory(type, timeConverter) { + @Override + RecordedMethod createTyped(ObjectContext objectContext, long id, Object[] values) { + return PRIVATE_ACCESS.newRecordedMethod(objectContext, values); + } + }; + } + + private static ObjectFactory createFrameFactory(Type type, TimeConverter timeConverter) { + return new ObjectFactory(type, timeConverter) { + @Override + RecordedFrame createTyped(ObjectContext objectContext, long id, Object[] values) { + return PRIVATE_ACCESS.newRecordedFrame(objectContext, values); + } + }; + } + + private static ObjectFactory createThreadFactory(Type type, TimeConverter timeConverter) { + return new ObjectFactory(type, timeConverter) { + @Override + RecordedThread createTyped(ObjectContext objectContext, long id, Object[] values) { + return PRIVATE_ACCESS.newRecordedThread(objectContext, id, values); + } + }; + } + + private final ObjectContext objectContext; + + private ObjectFactory(Type type, TimeConverter timeConverter) { + this.objectContext = new ObjectContext(null, type.getFields(), timeConverter); + } + + T createObject(long id, Object value) { + if (value == null) { + return null; + } + if (value instanceof Object[]) { + return createTyped(objectContext, id, (Object[]) value); + } + throw new InternalError("Object factory must have struct type. Type was " + value.getClass().getName()); + } + + abstract T createTyped(ObjectContext objectContextm, long id, Object[] values); +} diff --git a/src/share/classes/jdk/jfr/consumer/Parser.java b/src/share/classes/jdk/jfr/internal/consumer/Parser.java similarity index 75% rename from src/share/classes/jdk/jfr/consumer/Parser.java rename to src/share/classes/jdk/jfr/internal/consumer/Parser.java index 572e37d3a..54edc910b 100644 --- a/src/share/classes/jdk/jfr/consumer/Parser.java +++ b/src/share/classes/jdk/jfr/internal/consumer/Parser.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -23,12 +23,10 @@ * questions. */ -package jdk.jfr.consumer; +package jdk.jfr.internal.consumer; import java.io.IOException; -import jdk.jfr.internal.consumer.RecordingInput; - /** * Base class for parsing data from a {@link RecordingInput}. */ @@ -41,5 +39,14 @@ abstract class Parser { * @throws IOException if operation couldn't be completed due to I/O * problems */ - abstract Object parse(RecordingInput input) throws IOException; + public abstract Object parse(RecordingInput input) throws IOException; + + /** + * Skips data that would usually be by parsed the {@code #parse(RecordingInput)} method. + * + * @param input input to read from + * @throws IOException if operation couldn't be completed due to I/O + * problems + */ + public abstract void skip(RecordingInput input) throws IOException; } diff --git a/src/share/classes/jdk/jfr/consumer/ParserFactory.java b/src/share/classes/jdk/jfr/internal/consumer/ParserFactory.java similarity index 61% rename from src/share/classes/jdk/jfr/consumer/ParserFactory.java rename to src/share/classes/jdk/jfr/internal/consumer/ParserFactory.java index 4064e889d..3a3c58864 100644 --- a/src/share/classes/jdk/jfr/consumer/ParserFactory.java +++ b/src/share/classes/jdk/jfr/internal/consumer/ParserFactory.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -23,18 +23,19 @@ * questions. */ -package jdk.jfr.consumer; +package jdk.jfr.internal.consumer; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import jdk.jfr.EventType; import jdk.jfr.ValueDescriptor; +import jdk.jfr.internal.LongMap; import jdk.jfr.internal.MetadataDescriptor; import jdk.jfr.internal.PrivateAccess; import jdk.jfr.internal.Type; +import jdk.jfr.internal.consumer.Parser; import jdk.jfr.internal.consumer.RecordingInput; /** @@ -44,21 +45,25 @@ final class ParserFactory { private final LongMap parsers = new LongMap<>(); private final TimeConverter timeConverter; private final LongMap types = new LongMap<>(); - private final LongMap constantPools; + private final LongMap constantLookups; - public ParserFactory(MetadataDescriptor metadata, TimeConverter timeConverter) throws IOException { - this.constantPools = new LongMap<>(); + public ParserFactory(MetadataDescriptor metadata, LongMap constantLookups, TimeConverter timeConverter) throws IOException { + this.constantLookups = constantLookups; this.timeConverter = timeConverter; for (Type t : metadata.getTypes()) { types.put(t.getId(), t); } - for (Type t : types) { + // Add to separate list + // so createCompositeParser can throw + // IOException outside lambda + List typeList = new ArrayList<>(); + types.forEach(typeList::add); + for (Type t : typeList) { if (!t.getFields().isEmpty()) { // Avoid primitives - CompositeParser cp = createCompositeParser(t); + CompositeParser cp = createCompositeParser(t, false); if (t.isSimpleType()) { // Reduce to nested parser - parsers.put(t.getId(), cp.parsers[0]); + parsers.put(t.getId(), cp.parsers[0]); } - } } // Override event types with event parsers @@ -71,10 +76,6 @@ final class ParserFactory { return parsers; } - public LongMap getConstantPools() { - return constantPools; - } - public LongMap getTypeMap() { return types; } @@ -82,17 +83,17 @@ final class ParserFactory { private EventParser createEventParser(EventType eventType) throws IOException { List parsers = new ArrayList(); for (ValueDescriptor f : eventType.getFields()) { - parsers.add(createParser(f)); + parsers.add(createParser(f, true)); } return new EventParser(timeConverter, eventType, parsers.toArray(new Parser[0])); } - private Parser createParser(ValueDescriptor v) throws IOException { + private Parser createParser(ValueDescriptor v, boolean event) throws IOException { boolean constantPool = PrivateAccess.getInstance().isConstantPool(v); if (v.isArray()) { Type valueType = PrivateAccess.getInstance().getType(v); ValueDescriptor element = PrivateAccess.getInstance().newValueDescriptor(v.getName(), valueType, v.getAnnotationElements(), 0, constantPool, null); - return new ArrayParser(createParser(element)); + return new ArrayParser(createParser(element, event)); } long id = v.getTypeId(); Type type = types.get(id); @@ -100,25 +101,29 @@ final class ParserFactory { throw new IOException("Type '" + v.getTypeName() + "' is not defined"); } if (constantPool) { - ConstantMap pool = constantPools.get(id); - if (pool == null) { - pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName()); - constantPools.put(id, pool); + ConstantLookup lookup = constantLookups.get(id); + if (lookup == null) { + ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName()); + lookup = new ConstantLookup(pool, type); + constantLookups.put(id, lookup); + } + if (event) { + return new EventValueConstantParser(lookup); } - return new ConstantMapValueParser(pool); + return new ConstantValueParser(lookup); } Parser parser = parsers.get(id); if (parser == null) { if (!v.getFields().isEmpty()) { - return createCompositeParser(type); + return createCompositeParser(type, event); } else { - return registerParserType(type, createPrimitiveParser(type)); + return registerParserType(type, createPrimitiveParser(type, constantPool)); } } return parser; } - private Parser createPrimitiveParser(Type type) throws IOException { + private Parser createPrimitiveParser(Type type, boolean event) throws IOException { switch (type.getName()) { case "int": return new IntegerParser(); @@ -138,8 +143,9 @@ final class ParserFactory { return new ByteParser(); case "java.lang.String": ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName()); - constantPools.put(type.getId(), pool); - return new StringParser(pool); + ConstantLookup lookup = new ConstantLookup(pool, type); + constantLookups.put(type.getId(), lookup); + return new StringParser(lookup, event); default: throw new IOException("Unknown primitive type " + type.getName()); } @@ -155,7 +161,7 @@ final class ParserFactory { return parser; } - private CompositeParser createCompositeParser(Type type) throws IOException { + private CompositeParser createCompositeParser(Type type, boolean event) throws IOException { List vds = type.getFields(); Parser[] parsers = new Parser[vds.size()]; CompositeParser composite = new CompositeParser(parsers); @@ -164,7 +170,7 @@ final class ParserFactory { int index = 0; for (ValueDescriptor vd : vds) { - parsers[index++] = createParser(vd); + parsers[index++] = createParser(vd, event); } return composite; } @@ -174,6 +180,11 @@ final class ParserFactory { public Object parse(RecordingInput input) throws IOException { return input.readBoolean() ? Boolean.TRUE : Boolean.FALSE; } + + @Override + public void skip(RecordingInput input) throws IOException { + input.skipBytes(1); + } } private static final class ByteParser extends Parser { @@ -181,19 +192,51 @@ final class ParserFactory { public Object parse(RecordingInput input) throws IOException { return Byte.valueOf(input.readByte()); } + + @Override + public void skip(RecordingInput input) throws IOException { + input.skipBytes(1); + } } private static final class LongParser extends Parser { + private Object lastLongObject = Long.valueOf(0); + private long last = 0; + @Override public Object parse(RecordingInput input) throws IOException { - return Long.valueOf(input.readLong()); + long l = input.readLong(); + if (l == last) { + return lastLongObject; + } + last = l; + lastLongObject = Long.valueOf(l); + return lastLongObject; + } + + @Override + public void skip(RecordingInput input) throws IOException { + input.readLong(); } } private static final class IntegerParser extends Parser { + private Integer lastIntegergObject = Integer.valueOf(0); + private int last = 0; + @Override public Object parse(RecordingInput input) throws IOException { - return Integer.valueOf(input.readInt()); + int i = input.readInt(); + if (i != last) { + last = i; + lastIntegergObject = Integer.valueOf(i); + } + return lastIntegergObject; + } + + @Override + public void skip(RecordingInput input) throws IOException { + input.readInt(); } } @@ -202,6 +245,11 @@ final class ParserFactory { public Object parse(RecordingInput input) throws IOException { return Short.valueOf(input.readShort()); } + + @Override + public void skip(RecordingInput input) throws IOException { + input.readShort(); + } } private static final class CharacterParser extends Parser { @@ -209,6 +257,11 @@ final class ParserFactory { public Object parse(RecordingInput input) throws IOException { return Character.valueOf(input.readChar()); } + + @Override + public void skip(RecordingInput input) throws IOException { + input.readChar(); + } } private static final class FloatParser extends Parser { @@ -216,40 +269,22 @@ final class ParserFactory { public Object parse(RecordingInput input) throws IOException { return Float.valueOf(input.readFloat()); } - } - private static final class DoubleParser extends Parser { @Override - public Object parse(RecordingInput input) throws IOException { - return Double.valueOf(input.readDouble()); + public void skip(RecordingInput input) throws IOException { + input.skipBytes(Float.SIZE); } } - private static final class StringParser extends Parser { - private final ConstantMap stringConstantMap; - private String last; - - StringParser(ConstantMap stringConstantMap) { - this.stringConstantMap = stringConstantMap; - } - + private static final class DoubleParser extends Parser { @Override public Object parse(RecordingInput input) throws IOException { - String s = parseEncodedString(input); - if (!Objects.equals(s, last)) { - last = s; - } - return last; + return Double.valueOf(input.readDouble()); } - private String parseEncodedString(RecordingInput input) throws IOException { - byte encoding = input.readByte(); - if (encoding == RecordingInput.STRING_ENCODING_CONSTANT_POOL) { - long id = input.readLong(); - return (String) stringConstantMap.get(id); - } else { - return input.readEncodedString(encoding); - } + @Override + public void skip(RecordingInput input) throws IOException { + input.skipBytes(Double.SIZE); } } @@ -269,6 +304,14 @@ final class ParserFactory { } return array; } + + @Override + public void skip(RecordingInput input) throws IOException { + final int size = input.readInt(); + for (int i = 0; i < size; i++) { + elementParser.skip(input); + } + } } private final static class CompositeParser extends Parser { @@ -286,18 +329,54 @@ final class ParserFactory { } return values; } + + @Override + public void skip(RecordingInput input) throws IOException { + for (int i = 0; i < parsers.length; i++) { + parsers[i].skip(input); + } + } } - private static final class ConstantMapValueParser extends Parser { - private final ConstantMap pool; + private static final class EventValueConstantParser extends Parser { + private final ConstantLookup lookup; + private Object lastValue = 0; + private long lastKey = -1; + EventValueConstantParser(ConstantLookup lookup) { + this.lookup = lookup; + } - ConstantMapValueParser(ConstantMap pool) { - this.pool = pool; + @Override + public Object parse(RecordingInput input) throws IOException { + long key = input.readLong(); + if (key == lastKey) { + return lastValue; + } + lastKey = key; + lastValue = lookup.getCurrentResolved(key); + return lastValue; + } + + @Override + public void skip(RecordingInput input) throws IOException { + input.readLong(); + } + } + + private static final class ConstantValueParser extends Parser { + private final ConstantLookup lookup; + ConstantValueParser(ConstantLookup lookup) { + this.lookup = lookup; } @Override public Object parse(RecordingInput input) throws IOException { - return pool.get(input.readLong()); + return lookup.getCurrent(input.readLong()); + } + + @Override + public void skip(RecordingInput input) throws IOException { + input.readLong(); } } } diff --git a/src/share/classes/jdk/jfr/internal/consumer/ParserFilter.java b/src/share/classes/jdk/jfr/internal/consumer/ParserFilter.java new file mode 100644 index 000000000..7d47dc620 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/ParserFilter.java @@ -0,0 +1,81 @@ +/* + * 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.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; + +final class ParserFilter { + public static final ParserFilter ACCEPT_ALL = new ParserFilter(true, Collections.emptyMap()); + + private final Map thresholds; + private final boolean acceptAll; + + public ParserFilter() { + this(false, new HashMap<>()); + } + + private ParserFilter(boolean acceptAll, Map thresholds) { + this.acceptAll = acceptAll; + this.thresholds = thresholds; + } + + public void setThreshold(String eventName, long nanos) { + Long l = thresholds.get(eventName); + if (l != null) { + l = Math.min(l, nanos); + } else { + l = nanos; + } + thresholds.put(eventName, l); + } + + public long getThreshold(String eventName) { + if (acceptAll) { + return 0L; + } + Long l = thresholds.get(eventName); + if (l != null) { + return l; + } + return -1; + } + + public String toString() { + if (acceptAll) { + return "ACCEPT ALL"; + } + + StringJoiner sb = new StringJoiner(", "); + for (String key : thresholds.keySet().toArray(new String[0])) { + Long value = thresholds.get(key); + sb.add(key + " = " + value); + } + return sb.toString(); + } +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/RecordingInput.java b/src/share/classes/jdk/jfr/internal/consumer/RecordingInput.java index b766894f3..23700fd8e 100644 --- a/src/share/classes/jdk/jfr/internal/consumer/RecordingInput.java +++ b/src/share/classes/jdk/jfr/internal/consumer/RecordingInput.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -30,61 +30,82 @@ import java.io.EOFException; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; -import java.nio.charset.Charset; +import java.nio.file.Path; public final class RecordingInput implements DataInput, AutoCloseable { - public static final byte STRING_ENCODING_NULL = 0; - public static final byte STRING_ENCODING_EMPTY_STRING = 1; - public static final byte STRING_ENCODING_CONSTANT_POOL = 2; - public static final byte STRING_ENCODING_UTF8_BYTE_ARRAY = 3; - public static final byte STRING_ENCODING_CHAR_ARRAY = 4; - public static final byte STRING_ENCODING_LATIN1_BYTE_ARRAY = 5; - - private final static int DEFAULT_BLOCK_SIZE = 16 * 1024 * 1024; - private final static Charset UTF8 = Charset.forName("UTF-8"); - private final static Charset LATIN1 = Charset.forName("ISO-8859-1"); + private final static int DEFAULT_BLOCK_SIZE = 64_000; private static final class Block { private byte[] bytes = new byte[0]; private long blockPosition; + private long blockPositionEnd; boolean contains(long position) { - return position >= blockPosition && position < blockPosition + bytes.length; + return position >= blockPosition && position < blockPositionEnd; } public void read(RandomAccessFile file, int amount) throws IOException { blockPosition = file.getFilePointer(); // reuse byte array, if possible - if (amount != bytes.length) { + if (amount > bytes.length) { bytes = new byte[amount]; } - file.readFully(bytes); + this.blockPositionEnd = blockPosition + amount; + file.readFully(bytes, 0, amount); } public byte get(long position) { return bytes[(int) (position - blockPosition)]; } - } - private final RandomAccessFile file; - private final long size; + public void reset() { + blockPosition = 0; + blockPositionEnd = 0; + } + } + private final int blockSize; + private final FileAccess fileAccess; + private RandomAccessFile file; + private String filename; private Block currentBlock = new Block(); private Block previousBlock = new Block(); private long position; - private final int blockSize; + private long size = -1; // Fail fast if setSize(...) has not been called + // before parsing - private RecordingInput(File f, int blockSize) throws IOException { - this.size = f.length(); + RecordingInput(File f, FileAccess fileAccess, int blockSize) throws IOException { this.blockSize = blockSize; - this.file = new RandomAccessFile(f, "r"); - if (size < 8) { - throw new IOException("Not a valid Flight Recorder file. File length is only " + size + " bytes."); + this.fileAccess = fileAccess; + initialize(f); + } + + private void initialize(File f) throws IOException { + this.filename = fileAccess.getAbsolutePath(f); + this.file = fileAccess.openRAF(f, "r"); + this.position = 0; + this.size = -1; + this.currentBlock.reset(); + previousBlock.reset(); + if (fileAccess.length(f) < 8) { + throw new IOException("Not a valid Flight Recorder file. File length is only " + fileAccess.length(f) + " bytes."); } } - public RecordingInput(File f) throws IOException { - this(f, DEFAULT_BLOCK_SIZE); + public RecordingInput(File f, FileAccess fileAccess) throws IOException { + this(f, fileAccess, DEFAULT_BLOCK_SIZE); + } + + void positionPhysical(long position) throws IOException { + file.seek(position); + } + + byte readPhysicalByte() throws IOException { + return file.readByte(); + } + + long readPhysicalLong() throws IOException { + return file.readLong(); } @Override @@ -109,7 +130,7 @@ public final class RecordingInput implements DataInput, AutoCloseable { readFully(dst, 0, dst.length); } - public final short readRawShort() throws IOException { + short readRawShort() throws IOException { // copied from java.io.Bits byte b0 = readByte(); byte b1 = readByte(); @@ -117,18 +138,18 @@ public final class RecordingInput implements DataInput, AutoCloseable { } @Override - public final double readDouble() throws IOException { + public double readDouble() throws IOException { // copied from java.io.Bits return Double.longBitsToDouble(readRawLong()); } @Override - public final float readFloat() throws IOException { + public float readFloat() throws IOException { // copied from java.io.Bits return Float.intBitsToFloat(readRawInt()); } - public final int readRawInt() throws IOException { + int readRawInt() throws IOException { // copied from java.io.Bits byte b0 = readByte(); byte b1 = readByte(); @@ -137,7 +158,7 @@ public final class RecordingInput implements DataInput, AutoCloseable { return ((b3 & 0xFF)) + ((b2 & 0xFF) << 8) + ((b1 & 0xFF) << 16) + ((b0) << 24); } - public final long readRawLong() throws IOException { + long readRawLong() throws IOException { // copied from java.io.Bits byte b0 = readByte(); byte b1 = readByte(); @@ -150,20 +171,20 @@ public final class RecordingInput implements DataInput, AutoCloseable { return ((b7 & 0xFFL)) + ((b6 & 0xFFL) << 8) + ((b5 & 0xFFL) << 16) + ((b4 & 0xFFL) << 24) + ((b3 & 0xFFL) << 32) + ((b2 & 0xFFL) << 40) + ((b1 & 0xFFL) << 48) + (((long) b0) << 56); } - public final long position() throws IOException { + public final long position() { return position; } public final void position(long newPosition) throws IOException { if (!currentBlock.contains(newPosition)) { if (!previousBlock.contains(newPosition)) { - if (newPosition > size()) { - throw new EOFException("Trying to read at " + newPosition + ", but file is only " + size() + " bytes."); + if (newPosition > size) { + throw new EOFException("Trying to read at " + newPosition + ", but file is only " + size + " bytes."); } long blockStart = trimToFileSize(calculateBlockStart(newPosition)); file.seek(blockStart); // trim amount to file size - long amount = Math.min(size() - blockStart, blockSize); + long amount = Math.min(size - blockStart, blockSize); previousBlock.read(file, (int) amount); } // swap previous and current @@ -191,11 +212,12 @@ public final class RecordingInput implements DataInput, AutoCloseable { return newPosition - blockSize / 2; } - public final long size() throws IOException { + long size() { return size; } - public final void close() throws IOException { + @Override + public void close() throws IOException { file.close(); } @@ -245,34 +267,7 @@ public final class RecordingInput implements DataInput, AutoCloseable { // 4, means "" @Override public String readUTF() throws IOException { - return readEncodedString(readByte()); - } - - public String readEncodedString(byte encoding) throws IOException { - if (encoding == STRING_ENCODING_NULL) { - return null; - } - if (encoding == STRING_ENCODING_EMPTY_STRING) { - return ""; - } - int size = readInt(); - if (encoding == STRING_ENCODING_CHAR_ARRAY) { - char[] c = new char[size]; - for (int i = 0; i < size; i++) { - c[i] = readChar(); - } - return new String(c); - } - byte[] bytes = new byte[size]; - readFully(bytes); // TODO: optimize, check size, and copy only if needed - if (encoding == STRING_ENCODING_UTF8_BYTE_ARRAY) { - return new String(bytes, UTF8); - } - - if (encoding == STRING_ENCODING_LATIN1_BYTE_ARRAY) { - return new String(bytes, LATIN1); - } - throw new IOException("Unknown string encoding " + encoding); + throw new UnsupportedOperationException("Use StringParser"); } @Override @@ -292,48 +287,152 @@ public final class RecordingInput implements DataInput, AutoCloseable { @Override public long readLong() throws IOException { - // can be optimized by branching checks, but will do for now + final byte[] bytes = currentBlock.bytes; + final int index = (int) (position - currentBlock.blockPosition); + + if (index + 8 < bytes.length && index >= 0) { + byte b0 = bytes[index]; + long ret = (b0 & 0x7FL); + if (b0 >= 0) { + position += 1; + return ret; + } + int b1 = bytes[index + 1]; + ret += (b1 & 0x7FL) << 7; + if (b1 >= 0) { + position += 2; + return ret; + } + int b2 = bytes[index + 2]; + ret += (b2 & 0x7FL) << 14; + if (b2 >= 0) { + position += 3; + return ret; + } + int b3 = bytes[index + 3]; + ret += (b3 & 0x7FL) << 21; + if (b3 >= 0) { + position += 4; + return ret; + } + int b4 = bytes[index + 4]; + ret += (b4 & 0x7FL) << 28; + if (b4 >= 0) { + position += 5; + return ret; + } + int b5 = bytes[index + 5]; + ret += (b5 & 0x7FL) << 35; + if (b5 >= 0) { + position += 6; + return ret; + } + int b6 = bytes[index + 6]; + ret += (b6 & 0x7FL) << 42; + if (b6 >= 0) { + position += 7; + return ret; + } + int b7 = bytes[index + 7]; + ret += (b7 & 0x7FL) << 49; + if (b7 >= 0) { + position += 8; + return ret; + } + int b8 = bytes[index + 8];// read last byte raw + position += 9; + return ret + (((long) (b8 & 0XFF)) << 56); + } else { + return readLongSlow(); + } + } + + private long readLongSlow() throws IOException { byte b0 = readByte(); long ret = (b0 & 0x7FL); if (b0 >= 0) { return ret; } + int b1 = readByte(); ret += (b1 & 0x7FL) << 7; if (b1 >= 0) { return ret; } + int b2 = readByte(); ret += (b2 & 0x7FL) << 14; if (b2 >= 0) { return ret; } + int b3 = readByte(); ret += (b3 & 0x7FL) << 21; if (b3 >= 0) { return ret; } + int b4 = readByte(); ret += (b4 & 0x7FL) << 28; if (b4 >= 0) { return ret; } + int b5 = readByte(); ret += (b5 & 0x7FL) << 35; if (b5 >= 0) { return ret; } + int b6 = readByte(); ret += (b6 & 0x7FL) << 42; if (b6 >= 0) { return ret; } + int b7 = readByte(); ret += (b7 & 0x7FL) << 49; if (b7 >= 0) { return ret; + } + int b8 = readByte(); // read last byte raw return ret + (((long) (b8 & 0XFF)) << 56); } + + public void setValidSize(long size) { + if (size > this.size) { + this.size = size; + } + } + + public long getFileSize() throws IOException { + return file.length(); + } + + public String getFilename() { + return filename; + } + + // Purpose of this method is to reuse block cache from a + // previous RecordingInput + public void setFile(Path path) throws IOException { + try { + file.close(); + } catch (IOException e) { + // perhaps deleted + } + file = null; + initialize(path.toFile()); + } +/* + + + + + + * + * + */ } diff --git a/src/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java b/src/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java new file mode 100644 index 000000000..8be6d3e8a --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java @@ -0,0 +1,230 @@ +/* + * 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.DirectoryStream; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import jdk.jfr.internal.LogLevel; +import jdk.jfr.internal.LogTag; +import jdk.jfr.internal.Logger; +import jdk.jfr.internal.Repository; +import jdk.jfr.internal.SecuritySupport.SafePath; + +public final class RepositoryFiles { + private static final Object WAIT_OBJECT = new Object(); + public static void notifyNewFile() { + synchronized (WAIT_OBJECT) { + WAIT_OBJECT.notifyAll(); + } + } + + private final FileAccess fileAccess; + private final NavigableMap pathSet = new TreeMap<>(); + private final Map pathLookup = new HashMap<>(); + private final Path repository; + private final Object waitObject; + + private volatile boolean closed; + + RepositoryFiles(FileAccess fileAccess, Path repository) { + this.repository = repository; + this.fileAccess = fileAccess; + this.waitObject = repository == null ? WAIT_OBJECT : new Object(); + } + + long getTimestamp(Path p) { + return pathLookup.get(p); + } + + Path lastPath() { + if (waitForPaths()) { + return pathSet.lastEntry().getValue(); + } + return null; // closed + } + + Path firstPath(long startTimeNanos) { + if (waitForPaths()) { + // Pick closest chunk before timestamp + Long time = pathSet.floorKey(startTimeNanos); + if (time != null) { + startTimeNanos = time; + } + return path(startTimeNanos); + } + return null; // closed + } + + private boolean waitForPaths() { + while (!closed) { + try { + if (updatePaths()) { + break; + } + } catch (IOException e) { + Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "IOException during repository file scan " + e.getMessage()); + // This can happen if a chunk is being removed + // between the file was discovered and an instance + // was accessed, or if new file has been written yet + // Just ignore, and retry later. + } + nap(); + } + return !closed; + } + + Path nextPath(long startTimeNanos) { + if (closed) { + return null; + } + // Try to get the 'exact' path first + // to avoid skipping files if repository + // is updated while DirectoryStream + // is traversing it + Path path = pathSet.get(startTimeNanos); + if (path != null) { + return path; + } + // Update paths + try { + updatePaths(); + } catch (IOException e) { + // ignore + } + // try to get the next file + return path(startTimeNanos); + } + + private Path path(long timestamp) { + if (closed) { + return null; + } + while (true) { + SortedMap after = pathSet.tailMap(timestamp); + if (!after.isEmpty()) { + Path path = after.get(after.firstKey()); + if (Logger.shouldLog(LogTag.JFR_SYSTEM_STREAMING, LogLevel.TRACE)) { + Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.TRACE, "Return path " + path + " for start time nanos " + timestamp); + } + return path; + } + if (!waitForPaths()) { + return null; // closed + } + } + } + + private void nap() { + try { + synchronized (waitObject) { + waitObject.wait(1000); + } + } catch (InterruptedException e) { + // ignore + } + } + + private boolean updatePaths() throws IOException { + boolean foundNew = false; + Path repoPath = repository; + if (repoPath == null) { + // Always get the latest repository if 'jcmd JFR.configure + // repositorypath=...' has been executed + SafePath sf = Repository.getRepository().getRepositoryPath(); + if (sf == null) { + return false; // not initialized + } + repoPath = sf.toPath(); + } + + try (DirectoryStream dirStream = fileAccess.newDirectoryStream(repoPath)) { + List added = new ArrayList<>(); + Set current = new HashSet<>(); + for (Path p : dirStream) { + if (!pathLookup.containsKey(p)) { + String s = p.toString(); + if (s.endsWith(".jfr")) { + added.add(p); + Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "New file found: " + p.toAbsolutePath()); + } + current.add(p); + } + } + List removed = new ArrayList<>(); + for (Path p : pathLookup.keySet()) { + if (!current.contains(p)) { + removed.add(p); + } + } + + for (Path remove : removed) { + Long time = pathLookup.get(remove); + pathSet.remove(time); + pathLookup.remove(remove); + } + Collections.sort(added, (p1, p2) -> p1.compareTo(p2)); + for (Path p : added) { + // Only add files that have a complete header + // as the JVM may be in progress writing the file + long size = fileAccess.fileSize(p); + if (size >= ChunkHeader.headerSize()) { + long startNanos = readStartTime(p); + pathSet.put(startNanos, p); + pathLookup.put(p, startNanos); + foundNew = true; + } + } + return foundNew; + } + } + + private long readStartTime(Path p) throws IOException { + try (RecordingInput in = new RecordingInput(p.toFile(), fileAccess, 100)) { + Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Parsing header for chunk start time"); + ChunkHeader c = new ChunkHeader(in); + return c.getStartNanos(); + } + } + + void close() { + synchronized (waitObject) { + this.closed = true; + waitObject.notify(); + } + } +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/StreamConfiguration.java b/src/share/classes/jdk/jfr/internal/consumer/StreamConfiguration.java new file mode 100644 index 000000000..3fd937cb2 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/StreamConfiguration.java @@ -0,0 +1,124 @@ +/* + * 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.consumer.RecordedEvent; +import jdk.jfr.internal.Utils; +import jdk.jfr.internal.consumer.Dispatcher.EventDispatcher; + +final class StreamConfiguration { + final List closeActions = new ArrayList<>(); + final List flushActions = new ArrayList<>(); + final List eventActions = new ArrayList<>(); + final List> errorActions = new ArrayList<>(); + + boolean reuse = true; + boolean ordered = true; + Instant startTime = null; + Instant endTime = null; + boolean started = false; + long startNanos = 0; + long endNanos = Long.MAX_VALUE; + + private volatile boolean changed = true; + + public synchronized boolean remove(Object action) { + boolean removed = false; + removed |= flushActions.removeIf(e -> e == action); + removed |= closeActions.removeIf(e -> e == action); + removed |= errorActions.removeIf(e -> e == action); + removed |= eventActions.removeIf(e -> e.getAction() == action); + if (removed) { + changed = true; + } + return removed; + } + + public synchronized void addEventAction(String name, Consumer consumer) { + eventActions.add(new EventDispatcher(name, consumer)); + changed = true; + } + + public void addEventAction(Consumer action) { + addEventAction(null, action); + } + + public synchronized void addFlushAction(Runnable action) { + flushActions.add(action); + changed = true; + } + + public synchronized void addCloseAction(Runnable action) { + closeActions.add(action); + changed = true; + } + + public synchronized void addErrorAction(Consumer action) { + errorActions.add(action); + changed = true; + } + + public synchronized void setReuse(boolean reuse) { + this.reuse = reuse; + changed = true; + } + + public synchronized void setOrdered(boolean ordered) { + this.ordered = ordered; + changed = true; + } + + public synchronized void setEndTime(Instant endTime) { + this.endTime = endTime; + this.endNanos = Utils.timeToNanos(endTime); + changed = true; + } + + public synchronized void setStartTime(Instant startTime) { + this.startTime = startTime; + this.startNanos = Utils.timeToNanos(startTime); + changed = true; + } + + public synchronized void setStartNanos(long startNanos) { + this.startNanos = startNanos; + changed = true; + } + + public synchronized void setStarted(boolean started) { + this.started = started; + changed = true; + } + + public boolean hasChanged() { + return changed; + } +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/StringParser.java b/src/share/classes/jdk/jfr/internal/consumer/StringParser.java new file mode 100644 index 000000000..f63016a65 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/consumer/StringParser.java @@ -0,0 +1,222 @@ +/* + * 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.charset.Charset; + +public final class StringParser extends Parser { + + public enum Encoding { + NULL(0), + EMPTY_STRING(1), + CONSTANT_POOL(2), + UT8_BYTE_ARRAY(3), + CHAR_ARRAY(4), + LATIN1_BYTE_ARRAY(5); + + private byte byteValue; + + private Encoding(int byteValue) { + this.byteValue = (byte) byteValue; + } + + public byte byteValue() { + return byteValue; + } + + public boolean is(byte value) { + return value == byteValue; + } + + } + private final static Charset UTF8 = Charset.forName("UTF-8"); + private final static Charset LATIN1 = Charset.forName("ISO-8859-1"); + + private final static class CharsetParser extends Parser { + private final Charset charset; + private int lastSize; + private byte[] buffer = new byte[16]; + private String lastString; + + CharsetParser(Charset charset) { + this.charset = charset; + } + + @Override + public Object parse(RecordingInput input) throws IOException { + int size = input.readInt(); + ensureSize(size); + if (lastSize == size) { + boolean equalsLastString = true; + for (int i = 0; i < size; i++) { + // TODO: No need to read byte per byte + byte b = input.readByte(); + if (buffer[i] != b) { + equalsLastString = false; + buffer[i] = b; + } + } + if (equalsLastString) { + return lastString; + } + } else { + for (int i = 0; i < size; i++) { + buffer[i] = input.readByte(); + } + } + lastString = new String(buffer, 0, size, charset); + lastSize = size; + return lastString; + } + + @Override + public void skip(RecordingInput input) throws IOException { + int size = input.readInt(); + input.skipBytes(size); + } + + private void ensureSize(int size) { + if (buffer.length < size) { + buffer = new byte[size]; + } + } + } + + private final static class CharArrayParser extends Parser { + private char[] buffer = new char[16]; + private int lastSize = -1; + private String lastString = null; + + @Override + public Object parse(RecordingInput input) throws IOException { + int size = input.readInt(); + ensureSize(size); + if (lastSize == size) { + boolean equalsLastString = true; + for (int i = 0; i < size; i++) { + char c = input.readChar(); + if (buffer[i] != c) { + equalsLastString = false; + buffer[i] = c; + } + } + if (equalsLastString) { + return lastString; + } + } else { + for (int i = 0; i < size; i++) { + buffer[i] = input.readChar(); + } + } + lastString = new String(buffer, 0, size); + lastSize = size; + return lastString; + } + + @Override + public void skip(RecordingInput input) throws IOException { + int size = input.readInt(); + for (int i = 0; i < size; i++) { + input.readChar(); + } + } + + private void ensureSize(int size) { + if (buffer.length < size) { + buffer = new char[size]; + } + } + } + + private final ConstantLookup stringLookup; + private final CharArrayParser charArrayParser = new CharArrayParser(); + private final CharsetParser utf8parser = new CharsetParser(UTF8); + private final CharsetParser latin1parser = new CharsetParser(LATIN1); + private final boolean event; + + public StringParser(ConstantLookup stringLookup, boolean event) { + this.stringLookup = stringLookup; + this.event = event; + } + + @Override + public Object parse(RecordingInput input) throws IOException { + byte encoding = input.readByte(); + if (Encoding.CONSTANT_POOL.is(encoding)) { + long key = input.readLong(); + if (event) { + return stringLookup.getCurrentResolved(key); + } else { + return stringLookup.getCurrent(key); + } + } + if (Encoding.NULL.is(encoding)) { + return null; + } + if (Encoding.EMPTY_STRING.is(encoding)) { + return ""; + } + if (Encoding.CHAR_ARRAY.is(encoding)) { + return charArrayParser.parse(input); + } + if (Encoding.UT8_BYTE_ARRAY.is(encoding)) { + return utf8parser.parse(input); + } + if (Encoding.LATIN1_BYTE_ARRAY.is(encoding)) { + return latin1parser.parse(input); + } + throw new IOException("Unknown string encoding " + encoding); + } + + @Override + public void skip(RecordingInput input) throws IOException { + byte encoding = input.readByte(); + if (Encoding.CONSTANT_POOL.is(encoding)) { + input.readLong(); + return; + } + if (Encoding.EMPTY_STRING.is(encoding)) { + return; + } + if (Encoding.NULL.is(encoding)) { + return; + } + if (Encoding.CHAR_ARRAY.is(encoding)) { + charArrayParser.skip(input); + return; + } + if (Encoding.UT8_BYTE_ARRAY.is(encoding)) { + utf8parser.skip(input); + return; + } + if (Encoding.LATIN1_BYTE_ARRAY.is(encoding)) { + latin1parser.skip(input); + return; + } + throw new IOException("Unknown string encoding " + encoding); + } +} diff --git a/src/share/classes/jdk/jfr/consumer/TimeConverter.java b/src/share/classes/jdk/jfr/internal/consumer/TimeConverter.java similarity index 96% rename from src/share/classes/jdk/jfr/consumer/TimeConverter.java rename to src/share/classes/jdk/jfr/internal/consumer/TimeConverter.java index 0397f8a68..44f962c5b 100644 --- a/src/share/classes/jdk/jfr/consumer/TimeConverter.java +++ b/src/share/classes/jdk/jfr/internal/consumer/TimeConverter.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ * questions. */ -package jdk.jfr.consumer; +package jdk.jfr.internal.consumer; import java.time.DateTimeException; import java.time.ZoneOffset; @@ -49,15 +49,6 @@ final class TimeConverter { this.zoneOffet = zoneOfSet(rawOffset); } - private ZoneOffset zoneOfSet(int rawOffset) { - try { - return ZoneOffset.ofTotalSeconds(rawOffset / 1000); - } catch (DateTimeException dte) { - Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Could not create ZoneOffset from raw offset " + rawOffset); - } - return ZoneOffset.UTC; - } - public long convertTimestamp(long ticks) { return startNanos + (long) ((ticks - startTicks) / divisor); } @@ -69,4 +60,13 @@ final class TimeConverter { public ZoneOffset getZoneOffset() { return zoneOffet; } + + private ZoneOffset zoneOfSet(int rawOffset) { + try { + return ZoneOffset.ofTotalSeconds(rawOffset / 1000); + } catch (DateTimeException dte) { + Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Could not create ZoneOffset from raw offset " + rawOffset); + } + return ZoneOffset.UTC; + } } diff --git a/src/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java b/src/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java index a7bd31df3..2261f4dac 100644 --- a/src/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java +++ b/src/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -27,10 +27,12 @@ package jdk.jfr.internal.dcmd; +import jdk.jfr.FlightRecorder; import jdk.jfr.internal.LogLevel; import jdk.jfr.internal.LogTag; import jdk.jfr.internal.Logger; import jdk.jfr.internal.Options; +import jdk.jfr.internal.PrivateAccess; import jdk.jfr.internal.Repository; import jdk.jfr.internal.SecuritySupport.SafePath; @@ -101,6 +103,9 @@ final class DCmdConfigure extends AbstractDCmd { SafePath s = new SafePath(repositoryPath); Repository.getRepository().setBasePath(s); Logger.log(LogTag.JFR, LogLevel.INFO, "Base repository path set to " + repositoryPath); + if (FlightRecorder.isInitialized()) { + PrivateAccess.getInstance().getPlatformRecorder().rotateIfRecordingToDisk();; + } } catch (Exception e) { throw new DCmdException("Could not use " + repositoryPath + " as repository. " + e.getMessage(), e); } diff --git a/src/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java b/src/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java index 09bd717c1..adc73fddf 100644 --- a/src/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java +++ b/src/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -83,7 +83,7 @@ final class DCmdStart extends AbstractDCmd { * @throws DCmdException if recording could not be started */ @SuppressWarnings("resource") - public String execute(String name, String[] settings, Long delay, Long duration, Boolean disk, String path, Long maxAge, Long maxSize, Boolean dumpOnExit, Boolean pathToGcRoots) throws DCmdException { + public String execute(String name, String[] settings, Long delay, Long duration, Boolean disk, String path, Long maxAge, Long maxSize, Long flush, Boolean dumpOnExit, Boolean pathToGcRoots) throws DCmdException { if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) { Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdStart: name=" + name + ", settings=" + (settings != null ? Arrays.asList(settings) : "(none)") + @@ -92,6 +92,7 @@ final class DCmdStart extends AbstractDCmd { ", disk=" + disk+ ", filename=" + path + ", maxage=" + maxAge + + ", flush=" + flush + ", maxsize=" + maxSize + ", dumponexit =" + dumpOnExit + ", path-to-gc-roots=" + pathToGcRoots); @@ -138,6 +139,12 @@ final class DCmdStart extends AbstractDCmd { } } + if (flush != null) { + if (Boolean.FALSE.equals(disk)) { + throw new DCmdException("Flush can only be set for recordings that are to disk."); + } + } + if (!FlightRecorder.isInitialized() && delay == null) { initializeWithForcedInstrumentation(s); } @@ -150,6 +157,7 @@ final class DCmdStart extends AbstractDCmd { if (disk != null) { recording.setToDisk(disk.booleanValue()); } + recording.setSettings(s); SafePath safePath = null; @@ -187,6 +195,10 @@ final class DCmdStart extends AbstractDCmd { recording.setMaxAge(Duration.ofNanos(maxAge)); } + if (flush != null) { + recording.setFlushInterval(Duration.ofNanos(flush)); + } + if (maxSize != null) { recording.setMaxSize(maxSize); } @@ -232,6 +244,7 @@ final class DCmdStart extends AbstractDCmd { print("Use jcmd " + getPid() + " JFR." + cmd + " " + recordingspecifier + " " + fileOption + "to copy recording data to file."); println(); } + return getResult(); } diff --git a/src/share/classes/jdk/jfr/internal/tool/Disassemble.java b/src/share/classes/jdk/jfr/internal/tool/Disassemble.java index 17ad5d176..0ec55ba92 100644 --- a/src/share/classes/jdk/jfr/internal/tool/Disassemble.java +++ b/src/share/classes/jdk/jfr/internal/tool/Disassemble.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,7 @@ import java.util.Deque; import java.util.List; import jdk.jfr.internal.consumer.ChunkHeader; +import jdk.jfr.internal.consumer.FileAccess; import jdk.jfr.internal.consumer.RecordingInput; final class Disassemble extends Command { @@ -163,7 +164,7 @@ final class Disassemble extends Command { } private List findChunkSizes(Path p) throws IOException { - try (RecordingInput input = new RecordingInput(p.toFile())) { + try (RecordingInput input = new RecordingInput(p.toFile(), FileAccess.UNPRIVILIGED)) { List sizes = new ArrayList<>(); ChunkHeader ch = new ChunkHeader(input); sizes.add(ch.getSize()); diff --git a/src/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java b/src/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java index 546f48117..49b6e9bb7 100644 --- a/src/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java +++ b/src/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,7 +43,7 @@ import jdk.jfr.ValueDescriptor; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedObject; import jdk.jfr.consumer.RecordingFile; -import jdk.jfr.internal.consumer.RecordingInternals; +import jdk.jfr.internal.consumer.JdkJfrConsumer; abstract class EventPrintWriter extends StructuredWriter { @@ -52,6 +53,7 @@ abstract class EventPrintWriter extends StructuredWriter { protected static final String STACK_TRACE_FIELD = "stackTrace"; protected static final String EVENT_THREAD_FIELD = "eventThread"; + private static final JdkJfrConsumer PRIVATE_ACCESS = JdkJfrConsumer.instance(); private Predicate eventFilter = x -> true; private int stackDepth; @@ -74,8 +76,8 @@ abstract class EventPrintWriter extends StructuredWriter { if (acceptEvent(event)) { events.add(event); } - if (RecordingInternals.INSTANCE.isLastEventInChunk(file)) { - RecordingInternals.INSTANCE.sort(events); + if (PRIVATE_ACCESS.isLastEventInChunk(file)) { + Collections.sort(events, PRIVATE_ACCESS.eventComparator()); print(events); events.clear(); } @@ -121,7 +123,7 @@ abstract class EventPrintWriter extends StructuredWriter { case TIMESPAN: return object.getDuration(v.getName()); case TIMESTAMP: - return RecordingInternals.INSTANCE.getOffsetDataTime(object, v.getName()); + return PRIVATE_ACCESS.getOffsetDataTime(object, v.getName()); default: return object.getValue(v.getName()); } diff --git a/src/share/classes/jdk/jfr/internal/tool/Metadata.java b/src/share/classes/jdk/jfr/internal/tool/Metadata.java index 44c989c6e..320cc5634 100644 --- a/src/share/classes/jdk/jfr/internal/tool/Metadata.java +++ b/src/share/classes/jdk/jfr/internal/tool/Metadata.java @@ -35,10 +35,12 @@ import java.util.List; import jdk.jfr.consumer.RecordingFile; import jdk.jfr.internal.Type; -import jdk.jfr.internal.consumer.RecordingInternals; +import jdk.jfr.internal.consumer.JdkJfrConsumer; final class Metadata extends Command { + private final static JdkJfrConsumer PRIVATE_ACCESS = JdkJfrConsumer.instance(); + private static class TypeComparator implements Comparator { @Override @@ -89,6 +91,7 @@ final class Metadata extends Command { } } + @Override public String getName() { return "metadata"; @@ -125,7 +128,7 @@ final class Metadata extends Command { PrettyWriter prettyWriter = new PrettyWriter(pw); prettyWriter.setShowIds(showIds); try (RecordingFile rf = new RecordingFile(file)) { - List types = RecordingInternals.INSTANCE.readTypes(rf); + List types = PRIVATE_ACCESS.readTypes(rf); Collections.sort(types, new TypeComparator()); for (Type type : types) { prettyWriter.printType(type); diff --git a/src/share/classes/jdk/jfr/internal/tool/Summary.java b/src/share/classes/jdk/jfr/internal/tool/Summary.java index e282d6b7e..45fb39f85 100644 --- a/src/share/classes/jdk/jfr/internal/tool/Summary.java +++ b/src/share/classes/jdk/jfr/internal/tool/Summary.java @@ -1,5 +1,5 @@ /* - * 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. * * This code is free software; you can redistribute it and/or modify it @@ -42,6 +42,7 @@ import jdk.jfr.EventType; import jdk.jfr.internal.MetadataDescriptor; import jdk.jfr.internal.Type; import jdk.jfr.internal.consumer.ChunkHeader; +import jdk.jfr.internal.consumer.FileAccess; import jdk.jfr.internal.consumer.RecordingInput; final class Summary extends Command { @@ -91,7 +92,7 @@ final class Summary extends Command { long totalDuration = 0; long chunks = 0; - try (RecordingInput input = new RecordingInput(p.toFile())) { + try (RecordingInput input = new RecordingInput(p.toFile(), FileAccess.UNPRIVILIGED)) { ChunkHeader first = new ChunkHeader(input); ChunkHeader ch = first; String eventPrefix = Type.EVENT_NAME_PREFIX; diff --git a/test/jdk/jfr/api/consumer/TestReadTwice.java b/test/jdk/jfr/api/consumer/TestReadTwice.java index aa07d854b..bca0bc844 100644 --- a/test/jdk/jfr/api/consumer/TestReadTwice.java +++ b/test/jdk/jfr/api/consumer/TestReadTwice.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -26,7 +26,6 @@ package jdk.jfr.api.consumer; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.LinkedList; import java.util.List; diff --git a/test/jdk/jfr/api/consumer/TestRecordingFile.java b/test/jdk/jfr/api/consumer/TestRecordingFile.java index 831292641..08b28a3f2 100644 --- a/test/jdk/jfr/api/consumer/TestRecordingFile.java +++ b/test/jdk/jfr/api/consumer/TestRecordingFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -210,12 +210,12 @@ public class TestRecordingFile { assertHasEventType(types, "Event2"); assertMissingEventType(types, "Event3"); } - try (RecordingFile f = new RecordingFile(twoEventTypes)) { + try (RecordingFile f = new RecordingFile(threeEventTypes)) { List types = f.readEventTypes(); assertUniqueEventTypes(types); assertHasEventType(types, "Event1"); assertHasEventType(types, "Event2"); - assertMissingEventType(types, "Event3"); + assertHasEventType(types, "Event3"); } } diff --git a/test/jdk/jfr/api/consumer/TestRecordingInternals.java b/test/jdk/jfr/api/consumer/TestRecordingInternals.java index 232078dee..caa8bff1e 100644 --- a/test/jdk/jfr/api/consumer/TestRecordingInternals.java +++ b/test/jdk/jfr/api/consumer/TestRecordingInternals.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -60,6 +60,7 @@ public class TestRecordingInternals { Asserts.assertEquals(id.toString(), rt.getThreadGroup().getName(), "Thread group name should match id"); Asserts.assertEquals(id, Integer.valueOf(i), "Chunks out of order"); i++; + System.out.println(i + " OK "); } } } diff --git a/test/jdk/jfr/api/consumer/filestream/TestMultipleChunk.java b/test/jdk/jfr/api/consumer/filestream/TestMultipleChunk.java new file mode 100644 index 000000000..a7afa7020 --- /dev/null +++ b/test/jdk/jfr/api/consumer/filestream/TestMultipleChunk.java @@ -0,0 +1,85 @@ +/* + * 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.api.consumer.filestream; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.atomic.AtomicLong; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.EventStream; + +/** + * @test + * @summary Verifies that it is possible to stream contents from a multichunked file + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.filestream.TestMultipleChunk + */ +public class TestMultipleChunk { + + static class SnakeEvent extends Event { + int id; + } + + public static void main(String... args) throws Exception { + Path path = Paths.get("./using-file.jfr"); + try (Recording r1 = new Recording()) { + r1.start(); + emitSnakeEvent(1); + emitSnakeEvent(2); + emitSnakeEvent(3); + // Force a chunk rotation + try (Recording r2 = new Recording()) { + r2.start(); + emitSnakeEvent(4); + emitSnakeEvent(5); + emitSnakeEvent(6); + r2.stop(); + } + r1.stop(); + r1.dump(path); + AtomicLong counter = new AtomicLong(); + try (EventStream es = EventStream.openFile(path)) { + es.onEvent(e -> { + counter.incrementAndGet(); + }); + es.start(); + if (counter.get() != 6) { + throw new Exception("Expected 6 event, but got " + counter.get()); + } + } + } + } + + static void emitSnakeEvent(int id) { + SnakeEvent e = new SnakeEvent(); + e.id = id; + e.commit(); + } + +} diff --git a/test/jdk/jfr/api/consumer/filestream/TestOrdered.java b/test/jdk/jfr/api/consumer/filestream/TestOrdered.java new file mode 100644 index 000000000..69212cb84 --- /dev/null +++ b/test/jdk/jfr/api/consumer/filestream/TestOrdered.java @@ -0,0 +1,151 @@ +/* + * 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.api.consumer.filestream; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.EventStream; + +/** + * @test + * @summary Test EventStream::setOrdered(...) + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.filestream.TestOrdered + */ +public class TestOrdered { + + static class OrderedEvent extends Event { + } + + static class Emitter extends Thread { + private final CyclicBarrier barrier; + + public Emitter(CyclicBarrier barrier) { + this.barrier = barrier; + } + + @Override + public void run() { + OrderedEvent e1 = new OrderedEvent(); + e1.commit(); + try { + barrier.await(); + } catch (Exception e) { + e.printStackTrace(); + throw new Error("Unexpected exception in barrier"); + } + OrderedEvent e2 = new OrderedEvent(); + e2.commit(); + } + } + + private static final int THREAD_COUNT = 4; + private static final boolean[] BOOLEAN_STATES = { false, true }; + + public static void main(String... args) throws Exception { + Path p = makeUnorderedRecording(); + + testSetOrderedTrue(p); + testSetOrderedFalse(p); + } + + private static void testSetOrderedTrue(Path p) throws Exception { + for (boolean reuse : BOOLEAN_STATES) { + AtomicReference timestamp = new AtomicReference<>(Instant.MIN); + try (EventStream es = EventStream.openFile(p)) { + es.setReuse(reuse); + es.setOrdered(true); + es.onEvent(e -> { + Instant endTime = e.getEndTime(); + if (endTime.isBefore(timestamp.get())) { + throw new Error("Events are not ordered! Reuse = " + reuse); + } + timestamp.set(endTime); + }); + es.start(); + } + } + } + + private static void testSetOrderedFalse(Path p) throws Exception { + for (boolean reuse : BOOLEAN_STATES) { + AtomicReference timestamp = new AtomicReference<>(Instant.MIN); + AtomicBoolean unoreded = new AtomicBoolean(false); + try (EventStream es = EventStream.openFile(p)) { + es.setReuse(reuse); + es.setOrdered(false); + es.onEvent(e -> { + Instant endTime = e.getEndTime(); + if (endTime.isBefore(timestamp.get())) { + unoreded.set(true); + es.close(); + } + timestamp.set(endTime); + }); + es.start(); + if (!unoreded.get()) { + throw new Exception("Expected at least some events to be out of order! Reuse = " + reuse); + } + } + } + } + + private static Path makeUnorderedRecording() throws Exception { + CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT + 1); + + try (Recording r = new Recording()) { + r.start(); + List emitters = new ArrayList<>(); + for (int i = 0; i < THREAD_COUNT; i++) { + Emitter e = new Emitter(barrier); + e.start(); + emitters.add(e); + } + // Thread buffers should now have one event each + barrier.await(); + // Add another event to each thread buffer, so + // events are bound to come out of order when they + // are flushed + for (Emitter e : emitters) { + e.join(); + } + r.stop(); + Path p = Files.createTempFile("recording", ".jfr"); + r.dump(p); + return p; + } + } +} diff --git a/test/jdk/jfr/api/consumer/filestream/TestReuse.java b/test/jdk/jfr/api/consumer/filestream/TestReuse.java new file mode 100644 index 000000000..d8b5bb7a0 --- /dev/null +++ b/test/jdk/jfr/api/consumer/filestream/TestReuse.java @@ -0,0 +1,127 @@ +/* + * 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.api.consumer.filestream; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.EventStream; +import jdk.jfr.consumer.RecordedEvent; + +/** + * @test + * @summary Test EventStream::setReuse(...) + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.filestream.TestReuse + */ +public class TestReuse { + + static class ReuseEvent extends Event { + } + + private static final boolean[] BOOLEAN_STATES = { false, true }; + + public static void main(String... args) throws Exception { + Path p = makeRecording(); + + testSetReuseTrue(p); + testSetReuseFalse(p); + } + + private static void testSetReuseFalse(Path p) throws Exception { + for (boolean ordered : BOOLEAN_STATES) { + AtomicBoolean fail = new AtomicBoolean(false); + Map identity = new IdentityHashMap<>(); + try (EventStream es = EventStream.openFile(p)) { + es.setOrdered(ordered); + es.setReuse(false); + es.onEvent(e -> { + if (identity.containsKey(e)) { + fail.set(true); + es.close(); + } + identity.put(e, e); + }); + es.start(); + } + if (fail.get()) { + throw new Exception("Unexpected reuse! Ordered = " + ordered); + } + + } + } + + private static void testSetReuseTrue(Path p) throws Exception { + for (boolean ordered : BOOLEAN_STATES) { + AtomicBoolean success = new AtomicBoolean(false); + Map events = new IdentityHashMap<>(); + try (EventStream es = EventStream.openFile(p)) { + es.setOrdered(ordered); + es.setReuse(true); + es.onEvent(e -> { + if(events.containsKey(e)) { + success.set(true);; + es.close(); + } + events.put(e,e); + }); + es.start(); + } + if (!success.get()) { + throw new Exception("No reuse! Ordered = " + ordered); + } + } + + } + + private static Path makeRecording() throws IOException { + try (Recording r = new Recording()) { + r.start(); + for (int i = 0; i < 5; i++) { + ReuseEvent e = new ReuseEvent(); + e.commit(); + } + Recording rotation = new Recording(); + rotation.start(); + for (int i = 0; i < 5; i++) { + ReuseEvent e = new ReuseEvent(); + e.commit(); + } + r.stop(); + rotation.close(); + Path p = Files.createTempFile("recording", ".jfr"); + r.dump(p); + return p; + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/EventProducer.java b/test/jdk/jfr/api/consumer/recordingstream/EventProducer.java new file mode 100644 index 000000000..5621edf0f --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/EventProducer.java @@ -0,0 +1,58 @@ +/* + * 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.api.consumer.recordingstream; + +import jdk.jfr.api.consumer.recordingstream.TestStart.StartEvent; + +class EventProducer extends Thread { + private final Object lock = new Object(); + private boolean killed = false; + public void run() { + while (true) { + StartEvent s = new StartEvent(); + s.commit(); + synchronized (lock) { + try { + lock.wait(10); + if (killed) { + return; // end thread + } + } catch (InterruptedException e) { + // ignore + } + } + } + } + public void kill() { + synchronized (lock) { + this.killed = true; + lock.notifyAll(); + } + try { + join(); + } catch (InterruptedException e) { + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestAwaitTermination.java b/test/jdk/jfr/api/consumer/recordingstream/TestAwaitTermination.java new file mode 100644 index 000000000..a8ac5d5ca --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestAwaitTermination.java @@ -0,0 +1,77 @@ +/* + * 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.api.consumer.recordingstream; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Test RecordingStream::awaitTermination(...) + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestAwaitTermination + */ +public class TestAwaitTermination { + + public static void main(String... args) throws Exception { + testAwaitClose(); + testAwaitTimeOut(); + } + + private static void testAwaitClose() throws InterruptedException, ExecutionException { + try (RecordingStream r = new RecordingStream()) { + r.startAsync(); + CompletableFuture c = CompletableFuture.runAsync(() -> { + try { + r.awaitTermination(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + r.close(); + c.get(); + } + } + + private static void testAwaitTimeOut() throws InterruptedException, ExecutionException { + try (RecordingStream r = new RecordingStream()) { + r.startAsync(); + CompletableFuture c = CompletableFuture.runAsync(() -> { + try { + r.awaitTermination(Duration.ofMillis(10)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + c.get(); + r.close(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestClose.java b/test/jdk/jfr/api/consumer/recordingstream/TestClose.java new file mode 100644 index 000000000..f31a5f2d5 --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestClose.java @@ -0,0 +1,127 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicLong; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::close() + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestClose + */ +public class TestClose { + + private static class CloseEvent extends Event { + } + + public static void main(String... args) throws Exception { + testCloseUnstarted(); + testCloseStarted(); + testCloseTwice(); + testCloseStreaming(); + testCloseMySelf(); + } + + private static void testCloseMySelf() throws Exception { + log("Entering testCloseMySelf()"); + CountDownLatch l1 = new CountDownLatch(1); + CountDownLatch l2 = new CountDownLatch(1); + RecordingStream r = new RecordingStream(); + r.onEvent(e -> { + try { + l1.await(); + r.close(); + l2.countDown(); + } catch (InterruptedException ie) { + throw new Error(ie); + } + }); + r.startAsync(); + CloseEvent c = new CloseEvent(); + c.commit(); + l1.countDown(); + l2.await(); + log("Leaving testCloseMySelf()"); + } + + private static void testCloseStreaming() throws Exception { + log("Entering testCloseStreaming()"); + CountDownLatch streaming = new CountDownLatch(1); + RecordingStream r = new RecordingStream(); + AtomicLong count = new AtomicLong(); + r.onEvent(e -> { + if (count.incrementAndGet() > 100) { + streaming.countDown(); + } + }); + r.startAsync(); + CompletableFuture streamingLoop = CompletableFuture.runAsync(() -> { + while (true) { + CloseEvent c = new CloseEvent(); + c.commit(); + } + }); + streaming.await(); + r.close(); + streamingLoop.cancel(true); + log("Leaving testCloseStreaming()"); + } + + private static void testCloseStarted() { + log("Entering testCloseStarted()"); + RecordingStream r = new RecordingStream(); + r.startAsync(); + r.close(); + log("Leaving testCloseStarted()"); + } + + private static void testCloseUnstarted() { + log("Entering testCloseUnstarted()"); + RecordingStream r = new RecordingStream(); + r.close(); + log("Leaving testCloseUnstarted()"); + } + + private static void testCloseTwice() { + log("Entering testCloseTwice()"); + RecordingStream r = new RecordingStream(); + r.startAsync(); + r.close(); + r.close(); + log("Leaving testCloseTwice()"); + } + + private static void log(String msg) { + System.out.println(msg); + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestConstructor.java b/test/jdk/jfr/api/consumer/recordingstream/TestConstructor.java new file mode 100644 index 000000000..3d0d765db --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestConstructor.java @@ -0,0 +1,68 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Configuration; +import jdk.jfr.Enabled; +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; + +/** + * @test + * @summary Tests RecordingStream::RecordingStream() + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestConstructor + */ +public class TestConstructor { + + public static void main(String... args) throws Exception { + testEmpty(); + testConfiguration(); + } + + private static void testConfiguration() throws Exception { + CountDownLatch jvmInformation = new CountDownLatch(1); + Configuration c = Configuration.getConfiguration("default"); + if (!c.getSettings().containsKey(EventNames.JVMInformation + "#" + Enabled.NAME)) { + throw new Exception("Expected default configuration to contain enabled " + EventNames.JVMInformation); + } + RecordingStream r = new RecordingStream(c); + r.onEvent("jdk.JVMInformation", e -> { + jvmInformation.countDown(); + }); + r.startAsync(); + jvmInformation.await(); + r.close(); + } + + private static void testEmpty() { + RecordingStream r = new RecordingStream(); + r.close(); + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestDisable.java b/test/jdk/jfr/api/consumer/recordingstream/TestDisable.java new file mode 100644 index 000000000..640d383cb --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestDisable.java @@ -0,0 +1,91 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::disable(...) + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestDisable + */ +public class TestDisable { + + private static class DisabledEvent extends Event { + } + + private static class EnabledEvent extends Event { + } + + public static void main(String... args) throws Exception { + testDisableWithClass(); + testDisableWithEventName(); + } + + private static void testDisableWithEventName() { + test(r -> r.disable(DisabledEvent.class.getName())); + } + + private static void testDisableWithClass() { + test(r -> r.disable(DisabledEvent.class)); + } + + private static void test(Consumer disablement) { + CountDownLatch twoEvent = new CountDownLatch(2); + AtomicBoolean fail = new AtomicBoolean(false); + try(RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + if (e.getEventType().getName().equals(DisabledEvent.class.getName())) { + fail.set(true); + } + twoEvent.countDown(); + }); + disablement.accept(r); + r.startAsync(); + EnabledEvent e1 = new EnabledEvent(); + e1.commit(); + DisabledEvent d1 = new DisabledEvent(); + d1.commit(); + EnabledEvent e2 = new EnabledEvent(); + e2.commit(); + try { + twoEvent.await(); + } catch (InterruptedException ie) { + throw new RuntimeException("Unexpexpected interruption of thread", ie); + } + if (fail.get()) { + throw new RuntimeException("Should not receive a disabled event"); + } + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestEnable.java b/test/jdk/jfr/api/consumer/recordingstream/TestEnable.java new file mode 100644 index 000000000..30a2608b6 --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestEnable.java @@ -0,0 +1,78 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +import jdk.jfr.Enabled; +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::enable(...) + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestEnable + */ +public class TestEnable { + + @Enabled(false) + private static class EnabledEvent extends Event { + } + + public static void main(String... args) throws Exception { + testEnableWithClass(); + testEnableWithEventName(); + } + + private static void testEnableWithEventName() { + test(r -> r.enable(EnabledEvent.class.getName())); + } + + private static void testEnableWithClass() { + test(r -> r.enable(EnabledEvent.class)); + } + + private static void test(Consumer enablement) { + CountDownLatch event = new CountDownLatch(1); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + event.countDown(); + }); + enablement.accept(r); + r.startAsync(); + EnabledEvent e = new EnabledEvent(); + e.commit(); + try { + event.await(); + } catch (InterruptedException ie) { + throw new RuntimeException("Unexpected interruption of latch", ie); + } + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestMaxAge.java b/test/jdk/jfr/api/consumer/recordingstream/TestMaxAge.java new file mode 100644 index 000000000..7d757332a --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestMaxAge.java @@ -0,0 +1,60 @@ +/* + * 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.api.consumer.recordingstream; + +import java.time.Duration; + +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; + +/** + * @test + * @summary Tests RecordingStream::setMaxAge(...) + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestMaxAge + */ +public class TestMaxAge { + + public static void main(String... args) throws Exception { + Duration testDuration = Duration.ofMillis(1234567); + try (RecordingStream r = new RecordingStream()) { + r.setMaxAge(testDuration); + r.enable(EventNames.ActiveRecording); + r.onEvent(e -> { + System.out.println(e); + Duration d = e.getDuration("maxAge"); + System.out.println(d.toMillis()); + if (testDuration.equals(d)) { + r.close(); + return; + } + System.out.println("Max age not set, was " + d.toMillis() + " ms , but expected " + testDuration.toMillis() + " ms"); + }); + r.start(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestOnClose.java b/test/jdk/jfr/api/consumer/recordingstream/TestOnClose.java new file mode 100644 index 000000000..b07670ce5 --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestOnClose.java @@ -0,0 +1,94 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::onClose(...) + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestOnClose + */ +public class TestOnClose { + + private static class CloseEvent extends Event { + } + + public static void main(String... args) throws Exception { + testOnCloseNull(); + testOnClosedUnstarted(); + testOnClosedStarted(); + } + + private static void testOnCloseNull() { + try (RecordingStream rs = new RecordingStream()) { + try { + rs.onClose(null); + throw new AssertionError("Expected NullPointerException from onClose(null"); + } catch (NullPointerException npe) { + // OK; as expected + } + } + } + + private static void testOnClosedStarted() throws InterruptedException { + AtomicBoolean onClose = new AtomicBoolean(false); + CountDownLatch event = new CountDownLatch(1); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + event.countDown(); + }); + r.onClose(() -> { + onClose.set(true); + }); + r.startAsync(); + CloseEvent c = new CloseEvent(); + c.commit(); + event.await(); + } + if (!onClose.get()) { + throw new AssertionError("OnClose was not called"); + } + } + + private static void testOnClosedUnstarted() { + AtomicBoolean onClose = new AtomicBoolean(false); + try (RecordingStream r = new RecordingStream()) { + r.onClose(() -> { + onClose.set(true); + }); + } + if (!onClose.get()) { + throw new AssertionError("OnClose was not called"); + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestOnErrorAsync.java b/test/jdk/jfr/api/consumer/recordingstream/TestOnErrorAsync.java new file mode 100644 index 000000000..fd1d9cc6f --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestOnErrorAsync.java @@ -0,0 +1,205 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.jfr.api.consumer.recordingstream.TestUtils.TestError; +import jdk.jfr.api.consumer.recordingstream.TestUtils.TestException; +import jdk.jfr.api.consumer.security.TestStreamingRemote.TestEvent; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::onError(...) when using + * RecordingStream:startAsync + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestOnErrorAsync + */ +public class TestOnErrorAsync { + public static void main(String... args) throws Exception { + testDefaultError(); + testCustomError(); + testDefaultException(); + testCustomException(); + testOnFlushSanity(); + testOnCloseSanity(); + } + + private static void testDefaultError() throws Exception { + AtomicBoolean closed = new AtomicBoolean(); + CountDownLatch receivedError = new CountDownLatch(1); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + TestError error = new TestError(); + TestUtils.installUncaughtException(receivedError, error); + throw error; // closes stream + }); + r.onClose(() -> { + closed.set(true); + }); + r.startAsync(); + TestEvent e = new TestEvent(); + e.commit(); + r.awaitTermination(); + receivedError.await(); + if (!closed.get()) { + throw new Exception("Expected stream to be closed"); + } + } + } + + private static void testCustomError() throws Exception { + AtomicBoolean onError = new AtomicBoolean(); + AtomicBoolean closed = new AtomicBoolean(); + CountDownLatch receivedError = new CountDownLatch(1); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + TestError error = new TestError(); + TestUtils.installUncaughtException(receivedError, error); + throw error; // closes stream + }); + r.onError(e -> { + onError.set(true); + }); + r.onClose(() -> { + closed.set(true); + }); + r.startAsync(); + TestEvent e = new TestEvent(); + e.commit(); + r.awaitTermination(); + receivedError.await(); + if (onError.get()) { + throw new Exception("onError handler should not be invoked on java.lang.Error."); + } + if (!closed.get()) { + throw new Exception("Expected stream to be closed"); + } + } + } + + private static void testDefaultException() throws Exception { + TestException exception = new TestException(); + AtomicBoolean closed = new AtomicBoolean(); + AtomicInteger counter = new AtomicInteger(); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + if (counter.incrementAndGet() == 2) { + r.close(); + return; + } + TestUtils.throwUnchecked(exception); + }); + r.onClose(() -> { + closed.set(true); + }); + r.startAsync(); + TestEvent e1 = new TestEvent(); + e1.commit(); + TestEvent e2 = new TestEvent(); + e2.commit(); + r.awaitTermination(); + if (!exception.isPrinted()) { + throw new Exception("Expected stack trace from Exception to be printed"); + } + if (!closed.get()) { + throw new Exception("Expected stream to be closed"); + } + } + } + + private static void testCustomException() throws Exception { + TestException exception = new TestException(); + AtomicBoolean closed = new AtomicBoolean(); + AtomicBoolean received = new AtomicBoolean(); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + TestUtils.throwUnchecked(exception); + }); + r.onError(t -> { + received.set(t == exception); + r.close(); + }); + r.onClose(() -> { + closed.set(true); + }); + r.startAsync(); + TestEvent event = new TestEvent(); + event.commit(); + r.awaitTermination(); + if (!received.get()) { + throw new Exception("Did not receive expected exception in onError(...)"); + } + if (exception.isPrinted()) { + throw new Exception("Expected stack trace from Exception NOT to be printed"); + } + if (!closed.get()) { + throw new Exception("Expected stream to be closed"); + } + } + } + + private static void testOnFlushSanity() throws Exception { + TestException exception = new TestException(); + CountDownLatch received = new CountDownLatch(1); + try (RecordingStream r = new RecordingStream()) { + r.onFlush(() -> { + TestUtils.throwUnchecked(exception); + }); + r.onError(t -> { + if (t == exception) { + received.countDown(); + } + }); + r.startAsync(); + received.await(); + } + } + + private static void testOnCloseSanity() throws Exception { + TestException exception = new TestException(); + CountDownLatch received = new CountDownLatch(1); + try (RecordingStream r = new RecordingStream()) { + r.onFlush(() -> { + r.close(); // will trigger onClose + }); + r.onClose(() -> { + TestUtils.throwUnchecked(exception); // will trigger onError + }); + r.onError(t -> { + if (t == exception) { + received.countDown(); + } + }); + r.startAsync(); + received.await(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestOnErrorSync.java b/test/jdk/jfr/api/consumer/recordingstream/TestOnErrorSync.java new file mode 100644 index 000000000..54540b6f8 --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestOnErrorSync.java @@ -0,0 +1,240 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.jfr.api.consumer.recordingstream.TestUtils.TestError; +import jdk.jfr.api.consumer.recordingstream.TestUtils.TestException; +import jdk.jfr.api.consumer.security.TestStreamingRemote.TestEvent; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::onError(...) when using RecordingStream:start + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestOnErrorSync + */ +public class TestOnErrorSync { + public static void main(String... args) throws Exception { + testDefaultError(); + testCustomError(); + testDefaultException(); + testCustomException(); + testOnFlushSanity(); + testOnCloseSanity(); + } + + private static void testDefaultError() throws Exception { + TestError error = new TestError(); + AtomicBoolean closed = new AtomicBoolean(); + Timer t = newEventEmitter(); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + throw error; // closes stream + }); + r.onClose(() -> { + closed.set(true); + }); + try { + r.start(); + throw new Exception("Expected TestError to be thrown"); + } catch (TestError te) { + // as expected + } + if (!closed.get()) { + throw new Exception("Expected stream to be closed"); + } + } finally { + t.cancel(); + } + } + + private static void testCustomError() throws Exception { + TestError error = new TestError(); + AtomicBoolean onError = new AtomicBoolean(); + AtomicBoolean closed = new AtomicBoolean(); + Timer t = newEventEmitter(); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + throw error; // closes stream + }); + r.onError(e -> { + onError.set(true); + }); + r.onClose(() -> { + closed.set(true); + }); + try { + r.start(); + throw new Exception("Expected TestError to be thrown"); + } catch (TestError terror) { + // as expected + } + if (onError.get()) { + throw new Exception("Expected onError(...) NOT to be invoked"); + } + if (!closed.get()) { + throw new Exception("Expected stream to be closed"); + } + } finally { + t.cancel(); + } + } + + private static void testDefaultException() throws Exception { + TestException exception = new TestException(); + AtomicInteger counter = new AtomicInteger(); + AtomicBoolean closed = new AtomicBoolean(); + Timer t = newEventEmitter(); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + if (counter.incrementAndGet() == 2) { + // Only close if we get a second event after an exception + r.close(); + return; + } + TestUtils.throwUnchecked(exception); + }); + r.onClose(() -> { + closed.set(true); + }); + try { + r.start(); + } catch (Exception e) { + throw new Exception("Unexpected exception thrown from start()", e); + } + if (!exception.isPrinted()) { + throw new Exception("Expected stack trace from Exception to be printed"); + } + if (!closed.get()) { + throw new Exception("Expected stream to be closed"); + } + } finally { + t.cancel(); + } + } + + private static void testCustomException() throws Exception { + TestException exception = new TestException(); + AtomicInteger counter = new AtomicInteger(); + AtomicBoolean onError = new AtomicBoolean(); + AtomicBoolean closed = new AtomicBoolean(); + AtomicBoolean received = new AtomicBoolean(); + Timer t = newEventEmitter(); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + if (counter.incrementAndGet() == 2) { + // Only close if we get a second event after an exception + r.close(); + return; + } + TestUtils.throwUnchecked(exception); + }); + r.onError(e -> { + received.set(e == exception); + onError.set(true); + }); + r.onClose(() -> { + closed.set(true); + }); + try { + r.start(); + } catch (Exception e) { + throw new Exception("Unexpected exception thrown from start()", e); + } + if (!received.get()) { + throw new Exception("Did not receive expected exception in onError(...)"); + } + if (exception.isPrinted()) { + throw new Exception("Expected stack trace from Exception NOT to be printed"); + } + if (!onError.get()) { + throw new Exception("Expected OnError(...) to be invoked"); + } + if (!closed.get()) { + throw new Exception("Expected stream to be closed"); + } + } finally { + t.cancel(); + } + } + + private static void testOnFlushSanity() throws Exception { + TestException exception = new TestException(); + AtomicBoolean received = new AtomicBoolean(); + try (RecordingStream r = new RecordingStream()) { + r.onFlush(() -> { + TestUtils.throwUnchecked(exception); + }); + r.onError(t -> { + received.set(t == exception); + r.close(); + }); + r.start(); + if (!received.get()) { + throw new Exception("Expected exception in OnFlush to propagate to onError"); + } + } + } + + private static void testOnCloseSanity() throws Exception { + TestException exception = new TestException(); + AtomicBoolean received = new AtomicBoolean(); + try (RecordingStream r = new RecordingStream()) { + r.onFlush(() -> { + r.close(); // will trigger onClose + }); + r.onClose(() -> { + TestUtils.throwUnchecked(exception); // will trigger onError + }); + r.onError(t -> { + received.set(t == exception); + }); + r.start(); + if (!received.get()) { + throw new Exception("Expected exception in OnFlush to propagate to onError"); + } + } + } + + private static Timer newEventEmitter() { + Timer timer = new Timer(); + TimerTask task = new TimerTask() { + @Override + public void run() { + TestEvent event = new TestEvent(); + event.commit(); + } + }; + timer.schedule(task, 0, 100); + return timer; + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestOnEvent.java b/test/jdk/jfr/api/consumer/recordingstream/TestOnEvent.java new file mode 100644 index 000000000..8d4114ffe --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestOnEvent.java @@ -0,0 +1,154 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.Name; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::onEvent(...) + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestOnEvent + */ +public class TestOnEvent { + + @Name("A") + static class EventA extends Event { + } + + @Name("A") + static class EventAlsoA extends Event { + } + + @Name("C") + static class EventC extends Event { + } + + public static void main(String... args) throws Exception { + testOnEventNull(); + testOnEvent(); + testNamedEvent(); + testTwoEventWithSameName(); + } + + private static void testOnEventNull() { + log("Entering testOnEventNull()"); + try (RecordingStream rs = new RecordingStream()) { + try { + rs.onEvent(null); + throw new AssertionError("Expected NullPointerException from onEvent(null)"); + } catch (NullPointerException npe) { + // OK; as expected + } + try { + rs.onEvent("A", null); + throw new AssertionError("Expected NullPointerException from onEvent(\"A\", null)"); + + } catch (NullPointerException npe) { + // OK; as expected + } + try { + String s = null; + rs.onEvent(s, null); + throw new AssertionError("Expected NullPointerException from onEvent(null, null)"); + } catch (NullPointerException npe) { + // OK; as expected + } + } + log("Leaving testOnEventNull()"); + } + + private static void testTwoEventWithSameName() throws Exception { + log("Entering testTwoEventWithSameName()"); + CountDownLatch eventA = new CountDownLatch(2); + try (RecordingStream r = new RecordingStream()) { + r.onEvent("A", e -> { + System.out.println("testTwoEventWithSameName" + e); + eventA.countDown(); + }); + r.startAsync(); + EventA a1 = new EventA(); + a1.commit(); + EventAlsoA a2 = new EventAlsoA(); + a2.commit(); + eventA.await(); + } + log("Leaving testTwoEventWithSameName()"); + } + + private static void testNamedEvent() throws Exception { + log("Entering testNamedEvent()"); + try (RecordingStream r = new RecordingStream()) { + CountDownLatch eventA = new CountDownLatch(1); + CountDownLatch eventC = new CountDownLatch(1); + r.onEvent("A", e -> { + System.out.println("TestNamedEvent:" + e); + if (e.getEventType().getName().equals("A")) { + eventA.countDown(); + } + }); + r.onEvent("C", e -> { + System.out.println("TestNamedEvent:" + e); + if (e.getEventType().getName().equals("C")) { + eventC.countDown(); + } + }); + + r.startAsync(); + EventA a = new EventA(); + a.commit(); + EventC c = new EventC(); + c.commit(); + eventA.await(); + eventC.await(); + } + log("Leaving testNamedEvent()"); + } + + private static void testOnEvent() throws Exception { + log("Entering testOnEvent()"); + try (RecordingStream r = new RecordingStream()) { + CountDownLatch event = new CountDownLatch(1); + r.onEvent(e -> { + event.countDown(); + }); + r.startAsync(); + EventA a = new EventA(); + a.commit(); + event.await(); + } + log("Leaving testOnEvent()"); + } + + private static void log(String msg) { + System.out.println(msg); + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestOnFlush.java b/test/jdk/jfr/api/consumer/recordingstream/TestOnFlush.java new file mode 100644 index 000000000..e26547b80 --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestOnFlush.java @@ -0,0 +1,98 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::onFlush(...) + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestOnFlush + */ +public class TestOnFlush { + + static class OneEvent extends Event { + } + + public static void main(String... args) throws Exception { + testOnFlushNull(); + testOneEvent(); + testNoEvent(); + } + + private static void testOnFlushNull() { + log("Entering testOnFlushNull()"); + try (RecordingStream rs = new RecordingStream()) { + try { + rs.onFlush(null); + throw new AssertionError("Expected NullPointerException from onFlush(null"); + } catch (NullPointerException npe) { + // OK; as expected + } + } + log("Leaving testOnFlushNull()"); + } + + private static void testNoEvent() throws Exception { + log("Entering testNoEvent()"); + CountDownLatch flush = new CountDownLatch(1); + try (RecordingStream r = new RecordingStream()) { + r.onFlush(() -> { + flush.countDown(); + }); + r.startAsync(); + flush.await(); + } + log("Leaving testNoEvent()"); + } + + private static void testOneEvent() throws InterruptedException { + log("Entering testOneEvent()"); + CountDownLatch flush = new CountDownLatch(1); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + // ignore event + }); + r.onFlush(() -> { + flush.countDown(); + }); + r.startAsync(); + OneEvent e = new OneEvent(); + e.commit(); + flush.await(); + } + log("Leaving testOneEvent()"); + } + + private static void log(String msg) { + System.out.println(msg); + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestRecursive.java b/test/jdk/jfr/api/consumer/recordingstream/TestRecursive.java new file mode 100644 index 000000000..47e9eafe4 --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestRecursive.java @@ -0,0 +1,196 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @summary Tests that events are not emitted in handlers + * @key jfr + * @library /lib / + * @build jdk.jfr.api.consumer.recordingstream.EventProducer + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestRecursive + */ +public class TestRecursive { + + public static class NotRecorded extends Event { + } + + public static class Recorded extends Event { + } + + public static class Provoker extends Event { + } + + public static void main(String... args) throws Exception { + testSync(); + testAsync(); + testStreamInStream(); + } + + private static void testStreamInStream() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + try (Recording r = new Recording()) { + r.start(); + Recorded r1 = new Recorded(); // 1 + r1.commit(); + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(e1 -> { + streamInStream(); + latch.countDown(); + }); + rs.startAsync(); + Recorded r2 = new Recorded(); // 2 + r2.commit(); + latch.await(); + } + Recorded r3 = new Recorded(); // 2 + r3.commit(); + r.stop(); + List events = Events.fromRecording(r); + if (count(events, NotRecorded.class) != 0) { + throw new Exception("Expected 0 NotRecorded events"); + } + if (count(events, Recorded.class) != 3) { + throw new Exception("Expected 3 Recorded events"); + } + } + } + + // No events should be recorded in this method + private static void streamInStream() { + NotRecorded nr1 = new NotRecorded(); + nr1.commit(); + CountDownLatch latch = new CountDownLatch(1); + try (RecordingStream rs2 = new RecordingStream()) { + rs2.onEvent(e2 -> { + NotRecorded nr2 = new NotRecorded(); + nr2.commit(); + latch.countDown(); + }); + NotRecorded nr3 = new NotRecorded(); + nr3.commit(); + rs2.startAsync(); + // run event in separate thread + CompletableFuture.runAsync(() -> { + Provoker p = new Provoker(); + p.commit(); + }); + try { + latch.await(); + } catch (InterruptedException e) { + throw new Error("Unexpected interruption", e); + } + } + NotRecorded nr2 = new NotRecorded(); + nr2.commit(); + } + + private static void testSync() throws Exception { + try (Recording r = new Recording()) { + r.start(); + EventProducer p = new EventProducer(); + try (RecordingStream rs = new RecordingStream()) { + Recorded e1 = new Recorded(); + e1.commit(); + rs.onEvent(e -> { + System.out.println("Emitting NotRecorded event"); + NotRecorded event = new NotRecorded(); + event.commit(); + System.out.println("Stopping event provoker"); + p.kill(); + System.out.println("Closing recording stream"); + rs.close(); + return; + }); + p.start(); + rs.start(); + Recorded e2 = new Recorded(); + e2.commit(); + } + r.stop(); + List events = Events.fromRecording(r); + System.out.println(events); + if (count(events, NotRecorded.class) != 0) { + throw new Exception("Expected 0 NotRecorded events"); + } + if (count(events, Recorded.class) != 2) { + throw new Exception("Expected 2 Recorded events"); + } + } + } + + private static int count(List events, Class eventClass) { + int count = 0; + for (RecordedEvent e : events) { + if (e.getEventType().getName().equals(eventClass.getName())) { + count++; + } + } + System.out.println(count); + return count; + } + + private static void testAsync() throws InterruptedException, Exception { + CountDownLatch latchOne = new CountDownLatch(1); + CountDownLatch latchTwo = new CountDownLatch(2); + AtomicBoolean fail = new AtomicBoolean(); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + System.out.println(e); + NotRecorded event = new NotRecorded(); + event.commit(); + if (e.getEventType().getName().equals(Recorded.class.getName())) { + latchOne.countDown(); + latchTwo.countDown(); + } + if (e.getEventType().getName().equals(NotRecorded.class.getName())) { + fail.set(true); + } + }); + r.startAsync(); + Recorded e1 = new Recorded(); + e1.commit(); + latchOne.await(); + Recorded e2 = new Recorded(); + e2.commit(); + latchTwo.await(); + if (fail.get()) { + throw new Exception("Unexpected event found"); + } + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestRemove.java b/test/jdk/jfr/api/consumer/recordingstream/TestRemove.java new file mode 100644 index 000000000..55274bf67 --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestRemove.java @@ -0,0 +1,148 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStrream::remove(...) + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestRemove + */ +public class TestRemove { + + static class RemoveEvent extends Event { + + } + + public static void main(String... args) throws Exception { + testRemoveNull(); + testRemoveOnFlush(); + testRemoveOnClose(); + testRemoveOnEvent(); + } + + private static void testRemoveNull() { + log("Entering testRemoveNull()"); + try (RecordingStream rs = new RecordingStream()) { + try { + rs.remove(null); + throw new AssertionError("Expected NullPointerException from remove(null"); + } catch (NullPointerException npe) { + // OK; as expected + } + } + log("Leaving testRemoveNull()"); + } + + private static void testRemoveOnEvent() throws Exception { + log("Entering testRemoveOnEvent()"); + try (RecordingStream rs = new RecordingStream()) { + AtomicInteger counter = new AtomicInteger(0); + CountDownLatch events = new CountDownLatch(2); + Consumer c1 = e -> { + counter.incrementAndGet(); + }; + + Consumer c2 = e -> { + events.countDown(); + }; + rs.onEvent(c1); + rs.onEvent(c2); + + rs.remove(c1); + rs.startAsync(); + RemoveEvent r1 = new RemoveEvent(); + r1.commit(); + RemoveEvent r2 = new RemoveEvent(); + r2.commit(); + events.await(); + if (counter.get() > 0) { + throw new AssertionError("OnEvent handler not removed!"); + } + } + log("Leaving testRemoveOnEvent()"); + } + + private static void testRemoveOnClose() { + log("Entering testRemoveOnClose()"); + try (RecordingStream rs = new RecordingStream()) { + AtomicBoolean onClose = new AtomicBoolean(false); + Runnable r = () -> { + onClose.set(true); + }; + rs.onClose(r); + rs.remove(r); + rs.close(); + if (onClose.get()) { + throw new AssertionError("onClose handler not removed!"); + } + } + log("Leaving testRemoveOnClose()"); + } + + private static void testRemoveOnFlush() throws Exception { + log("Entering testRemoveOnFlush()"); + try (RecordingStream rs = new RecordingStream()) { + AtomicInteger flushCount = new AtomicInteger(2); + AtomicBoolean removeExecuted = new AtomicBoolean(false); + Runnable onFlush1 = () -> { + removeExecuted.set(true); + }; + Runnable onFlush2 = () -> { + flushCount.incrementAndGet(); + }; + + rs.onFlush(onFlush1); + rs.onFlush(onFlush2); + rs.remove(onFlush1); + rs.startAsync(); + while (flushCount.get() < 2) { + RemoveEvent r = new RemoveEvent(); + r.commit(); + Thread.sleep(100); + } + + if (removeExecuted.get()) { + throw new AssertionError("onFlush handler not removed!"); + } + } + log("Leaving testRemoveOnFlush()"); + } + + private static void log(String msg) { + System.out.println(msg); + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestSetEndTime.java b/test/jdk/jfr/api/consumer/recordingstream/TestSetEndTime.java new file mode 100644 index 000000000..0f8992d3f --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestSetEndTime.java @@ -0,0 +1,174 @@ +/* + * 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.api.consumer.recordingstream; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.jfr.Event; +import jdk.jfr.Name; +import jdk.jfr.Recording; +import jdk.jfr.StackTrace; +import jdk.jfr.consumer.EventStream; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingFile; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests EventStream::setEndTime + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestSetEndTime + */ +public final class TestSetEndTime { + + @Name("Mark") + @StackTrace(false) + public final static class Mark extends Event { + public boolean include; + public int id; + } + + public static void main(String... args) throws Exception { + testEventStream(); + testRecordingStream(); + } + + private static void testRecordingStream() throws Exception { + while (true) { + CountDownLatch closed = new CountDownLatch(1); + AtomicInteger count = new AtomicInteger(); + try (RecordingStream rs = new RecordingStream()) { + rs.setFlushInterval(Duration.ofSeconds(1)); + rs.onEvent(e -> { + count.incrementAndGet(); + }); + // when end is reached stream is closed + rs.onClose(() -> { + closed.countDown(); + }); + Instant endTime = Instant.now().plus(Duration.ofMillis(100)); + System.out.println("Setting end time: " + endTime); + rs.setEndTime(endTime); + rs.startAsync(); + for (int i = 0; i < 50; i++) { + Mark m = new Mark(); + m.commit(); + Thread.sleep(10); + } + closed.await(); + System.out.println("Found events: " + count.get()); + if (count.get() < 50) { + return; + } + System.out.println("Found 50 events. Retrying"); + System.out.println(); + } + } + } + + static void testEventStream() throws InterruptedException, IOException, Exception { + while (true) { + try (Recording r = new Recording()) { + r.start(); + + Mark event1 = new Mark(); + event1.id = 1; + event1.include = false; + event1.commit(); // start time + + nap(); + + Mark event2 = new Mark(); + event2.id = 2; + event2.include = true; + event2.commit(); + + nap(); + + Mark event3 = new Mark(); + event3.id = 3; + event3.include = false; + event3.commit(); // end time + + Path p = Paths.get("recording.jfr"); + r.dump(p); + Instant start = null; + Instant end = null; + System.out.println("Find start and end time as instants:"); + for (RecordedEvent e : RecordingFile.readAllEvents(p)) { + if (e.getInt("id") == 1) { + start = e.getEndTime(); + System.out.println("Start : " + start); + } + if (e.getInt("id") == 2) { + Instant middle = e.getEndTime(); + System.out.println("Middle : " + middle); + } + if (e.getInt("id") == 3) { + end = e.getEndTime(); + System.out.println("End : " + end); + } + } + System.out.println(); + System.out.println("Opening stream between " + start + " and " + end); + AtomicBoolean success = new AtomicBoolean(false); + AtomicInteger eventsCount = new AtomicInteger(); + try (EventStream d = EventStream.openRepository()) { + d.setStartTime(start.plusNanos(1)); + // Stream should close when end is reached + d.setEndTime(end.minusNanos(1)); + d.onEvent(e -> { + eventsCount.incrementAndGet(); + boolean include = e.getBoolean("include"); + System.out.println("Event " + e.getEndTime() + " include=" + include); + if (include) { + success.set(true); + } + }); + d.start(); + if (eventsCount.get() == 1 && success.get()) { + return; + } + } + } + } + + } + + private static void nap() throws InterruptedException { + // Ensure we advance at least 1 ns with fast time + Thread.sleep(1); + } + +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestSetFlushInterval.java b/test/jdk/jfr/api/consumer/recordingstream/TestSetFlushInterval.java new file mode 100644 index 000000000..657139354 --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestSetFlushInterval.java @@ -0,0 +1,61 @@ +/* + * 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.api.consumer.recordingstream; + +import java.time.Duration; + +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; + +/** + * @test + * @summary Tests RecordingStream::setFlushInterval + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestSetFlushInterval + */ +public class TestSetFlushInterval { + + public static void main(String... args) throws Exception { + Duration expectedDuration = Duration.ofMillis(1001); + try (RecordingStream r = new RecordingStream()) { + r.setFlushInterval(expectedDuration); + r.enable(EventNames.ActiveRecording); + r.onEvent(e -> { + System.out.println(e); + Duration duration = e.getDuration("flushInterval"); + if (expectedDuration.equals(duration)) { + System.out.println("Closing recording"); + r.close(); + return; + } + System.out.println("Flush interval not set, was " + duration + + ", but expected " + expectedDuration); + }); + r.start(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestSetMaxAge.java b/test/jdk/jfr/api/consumer/recordingstream/TestSetMaxAge.java new file mode 100644 index 000000000..54c070a05 --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestSetMaxAge.java @@ -0,0 +1,59 @@ +/* + * 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.api.consumer.recordingstream; + +import java.time.Duration; + +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; + +/** + * @test + * @summary Tests RecordingStrream::setMaxAge + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestSetMaxAge + */ +public class TestSetMaxAge { + + public static void main(String... args) throws Exception { + Duration expecteddAge = Duration.ofMillis(123456789); + try (RecordingStream r = new RecordingStream()) { + r.setMaxAge(expecteddAge); + r.enable(EventNames.ActiveRecording); + r.onEvent(e -> { + System.out.println(e); + Duration age = e.getDuration("maxAge"); + if (expecteddAge.equals(age)) { + r.close(); + return; + } + System.out.println("Max age not set, was " + age + ", but expected " + expecteddAge); + }); + r.start(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestSetMaxSize.java b/test/jdk/jfr/api/consumer/recordingstream/TestSetMaxSize.java new file mode 100644 index 000000000..12012729d --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestSetMaxSize.java @@ -0,0 +1,57 @@ +/* + * 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.api.consumer.recordingstream; + +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; + +/** +* @test +* @summary Tests RecordingStrream::setMaxSize +* @key jfr +* @library /lib / +* @run main/othervm jdk.jfr.api.consumer.recordingstream.TestSetMaxSize +*/ +public class TestSetMaxSize { + + public static void main(String... args) throws Exception { + long testSize = 123456789; + try (RecordingStream r = new RecordingStream()) { + r.setMaxSize(123456789); + r.enable(EventNames.ActiveRecording); + r.onEvent(e -> { + System.out.println(e); + long size= e.getLong("maxSize"); + if (size == testSize) { + r.close(); + return; + } + System.out.println("Max size not set, was " + size + ", but expected " + testSize); + }); + r.start(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestSetSettings.java b/test/jdk/jfr/api/consumer/recordingstream/TestSetSettings.java new file mode 100644 index 000000000..902d47391 --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestSetSettings.java @@ -0,0 +1,67 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Enabled; +import jdk.jfr.Event; +import jdk.jfr.Name; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::setSettings + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestSetSettings + */ +public final class TestSetSettings { + + @Name("LateBloomer") + @Enabled(false) + private final static class LateBloomer extends Event { + } + + private static CountDownLatch lateBloomer = new CountDownLatch(1); + + public static void main(String... args) throws Exception { + try (RecordingStream r = new RecordingStream()) { + r.startAsync(); + Map settings = new HashMap(); + settings.put("LateBloomer#enabled", "true"); + r.setSettings(settings); + r.onEvent("LateBloomer", e -> { + lateBloomer.countDown(); + }); + LateBloomer event = new LateBloomer(); + event.commit(); + lateBloomer.await(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestSetStartTime.java b/test/jdk/jfr/api/consumer/recordingstream/TestSetStartTime.java new file mode 100644 index 000000000..7fc5e59fa --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestSetStartTime.java @@ -0,0 +1,137 @@ +/* + * 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.api.consumer.recordingstream; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.atomic.AtomicBoolean; + +import jdk.jfr.Event; +import jdk.jfr.Name; +import jdk.jfr.Recording; +import jdk.jfr.StackTrace; +import jdk.jfr.consumer.EventStream; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests EventStream::setStartTime + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestSetStartTime + */ +public final class TestSetStartTime { + + private final static int SLEEP_TIME_MS = 100; + + @Name("Mark") + @StackTrace(false) + public final static class Mark extends Event { + public boolean before; + } + + public static void main(String... args) throws Exception { + testEventStream(); + testRecordingStream(); + } + + private static void testRecordingStream() throws InterruptedException { + AtomicBoolean exit = new AtomicBoolean(); + int attempt = 1; + while (!exit.get()) { + System.out.println("Testing RecordingStream:setStartTime(...). Attempt: " + attempt++); + AtomicBoolean firstEvent = new AtomicBoolean(true); + try (RecordingStream r2 = new RecordingStream()) { + Instant t = Instant.now().plus(Duration.ofMillis(SLEEP_TIME_MS / 2)); + System.out.println("Setting start time: " + t); + r2.setStartTime(t); + r2.onEvent(e -> { + if (firstEvent.get()) { + firstEvent.set(false); + if (!e.getBoolean("before")) { + // Skipped first event, let's exit + exit.set(true); + } + r2.close(); + } + }); + r2.startAsync(); + Mark m1 = new Mark(); + m1.before = true; + m1.commit(); + System.out.println("First event emitted: " + Instant.now()); + Thread.sleep(SLEEP_TIME_MS); + Mark m2 = new Mark(); + m2.before = false; + m2.commit(); + System.out.println("Second event emitted: " + Instant.now()); + r2.awaitTermination(); + } + System.out.println(); + } + } + + private static void testEventStream() throws InterruptedException, Exception, IOException { + AtomicBoolean exit = new AtomicBoolean(); + int attempt = 1; + while (!exit.get()) { + System.out.println("Testing EventStream:setStartTime(...). Attempt: " + attempt++); + AtomicBoolean firstEvent = new AtomicBoolean(true); + try (Recording r = new Recording()) { + r.start(); + Mark event1 = new Mark(); + event1.before = true; + event1.commit(); + Instant t = Instant.now(); + System.out.println("First event emitted: " + t); + Thread.sleep(SLEEP_TIME_MS); + Mark event2 = new Mark(); + event2.before = false; + event2.commit(); + System.out.println("Second event emitted: " + Instant.now()); + try (EventStream es = EventStream.openRepository()) { + Instant startTime = t.plus(Duration.ofMillis(SLEEP_TIME_MS / 2)); + es.setStartTime(startTime); + System.out.println("Setting start time: " + startTime); + es.onEvent(e -> { + if (firstEvent.get()) { + firstEvent.set(false); + if (!e.getBoolean("before")) { + // Skipped first event, let's exit + exit.set(true); + } + es.close(); + } + }); + es.startAsync(); + es.awaitTermination(); + } + } + System.out.println(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestStart.java b/test/jdk/jfr/api/consumer/recordingstream/TestStart.java new file mode 100644 index 000000000..92c5e834c --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestStart.java @@ -0,0 +1,150 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::start() + * @key jfr + * @library /lib / + * @build jdk.jfr.api.consumer.recordingstream.EventProducer + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestStart + */ +public class TestStart { + static class StartEvent extends Event { + } + public static void main(String... args) throws Exception { + testStart(); + testStartOnEvent(); + testStartTwice(); + testStartClosed(); + } + + private static void testStartTwice() throws Exception { + log("Entering testStartTwice()"); + CountDownLatch started = new CountDownLatch(1); + try (RecordingStream rs = new RecordingStream()) { + EventProducer t = new EventProducer(); + t.start(); + Thread thread = new Thread() { + public void run() { + rs.start(); + } + }; + thread.start(); + rs.onEvent(e -> { + if (started.getCount() > 0) { + started.countDown(); + } + }); + started.await(); + t.kill(); + try { + rs.start(); + throw new AssertionError("Expected IllegalStateException if started twice"); + } catch (IllegalStateException ise) { + // OK, as expected + } + } + log("Leaving testStartTwice()"); + } + + static void testStart() throws Exception { + log("Entering testStart()"); + CountDownLatch started = new CountDownLatch(1); + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(e -> { + started.countDown(); + }); + EventProducer t = new EventProducer(); + t.start(); + Thread thread = new Thread() { + public void run() { + rs.start(); + } + }; + thread.start(); + started.await(); + t.kill(); + } + log("Leaving testStart()"); + } + + static void testStartOnEvent() throws Exception { + log("Entering testStartOnEvent()"); + AtomicBoolean ISE = new AtomicBoolean(false); + CountDownLatch startedTwice = new CountDownLatch(1); + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(e -> { + try { + rs.start(); // must not deadlock + } catch (IllegalStateException ise) { + if (!ISE.get()) { + ISE.set(true); + startedTwice.countDown(); + } + } + }); + EventProducer t = new EventProducer(); + t.start(); + Thread thread = new Thread() { + public void run() { + rs.start(); + } + }; + thread.start(); + startedTwice.await(); + t.kill(); + if (!ISE.get()) { + throw new AssertionError("Expected IllegalStateException"); + } + } + log("Leaving testStartOnEvent()"); + } + + static void testStartClosed() { + log("Entering testStartClosed()"); + RecordingStream rs = new RecordingStream(); + rs.close(); + try { + rs.start(); + throw new AssertionError("Expected IllegalStateException"); + } catch (IllegalStateException ise) { + // OK, as expected. + } + log("Leaving testStartClosed()"); + } + + private static void log(String msg) { + System.out.println(msg); + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestStartAsync.java b/test/jdk/jfr/api/consumer/recordingstream/TestStartAsync.java new file mode 100644 index 000000000..80ecaa373 --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestStartAsync.java @@ -0,0 +1,85 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::startAsync() + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestStartAsync + */ +public class TestStartAsync { + static class StartEvent extends Event { + } + + public static void main(String... args) throws Exception { + testStart(); + testStartTwice(); + testStartClosed(); + } + + private static void testStartTwice() throws Exception { + try (RecordingStream rs = new RecordingStream()) { + rs.startAsync(); + try { + rs.startAsync(); + throw new AssertionError("Expected IllegalStateException if started twice"); + } catch (IllegalStateException ise) { + // OK, as expected + } + } + } + + static void testStart() throws Exception { + CountDownLatch started = new CountDownLatch(1); + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(e -> { + started.countDown(); + }); + rs.startAsync(); + StartEvent e = new StartEvent(); + e.commit(); + started.await(); + } + } + + static void testStartClosed() { + RecordingStream rs = new RecordingStream(); + rs.close(); + try { + rs.startAsync(); + throw new AssertionError("Expected IllegalStateException"); + } catch (IllegalStateException ise) { + // OK, as expected. + } + } +} diff --git a/test/jdk/jfr/api/consumer/recordingstream/TestUtils.java b/test/jdk/jfr/api/consumer/recordingstream/TestUtils.java new file mode 100644 index 000000000..c5a17b1bc --- /dev/null +++ b/test/jdk/jfr/api/consumer/recordingstream/TestUtils.java @@ -0,0 +1,65 @@ +/* + * 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.api.consumer.recordingstream; + +import java.util.concurrent.CountDownLatch; + +public class TestUtils { + + public static final class TestError extends Error { + private static final long serialVersionUID = 1L; + } + + public static final class TestException extends Exception { + private static final long serialVersionUID = 1L; + private volatile boolean printed; + + @Override + public void printStackTrace() { + super.printStackTrace(); + printed = true; + } + + public boolean isPrinted() { + return printed; + } + } + + // Can throw checked exception as unchecked. + @SuppressWarnings("unchecked") + public static void throwUnchecked(Throwable e) throws T { + throw (T) e; + } + + public static void installUncaughtException(CountDownLatch receivedError, Throwable expected) { + Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) -> { + if (throwable == expected) { + System.out.println("Received uncaught exception " + expected.getClass()); + receivedError.countDown(); + } + }); + } +} diff --git a/src/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java b/test/jdk/jfr/api/consumer/security/DriverRecordingDumper.java similarity index 62% rename from src/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java rename to test/jdk/jfr/api/consumer/security/DriverRecordingDumper.java index 34be20f8a..ecaffc1b9 100644 --- a/src/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java +++ b/test/jdk/jfr/api/consumer/security/DriverRecordingDumper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -22,26 +22,28 @@ * 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.List; +package jdk.jfr.api.consumer.security; -import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedObject; -import jdk.jfr.consumer.RecordingFile; -import jdk.jfr.internal.Type; +import java.nio.file.Paths; -public abstract class RecordingInternals { - - public static RecordingInternals INSTANCE; - - public abstract boolean isLastEventInChunk(RecordingFile file); - - public abstract Object getOffsetDataTime(RecordedObject event, String name); - - public abstract List readTypes(RecordingFile file) throws IOException; - - public abstract void sort(List events); +import jdk.jfr.Recording; +import jdk.test.lib.jfr.EventNames; +/** + * Driver that dumps a recording with a JVMInformation event + * + * Usage: + * + * @run driver jdk.jfr.api.consumer.security.RecordingDumper + */ +public class DriverRecordingDumper { + public static void main(String... args) throws Exception { + try (Recording r = new Recording()) { + r.enable(EventNames.JVMInformation); + r.start(); + r.stop(); + r.dump(Paths.get(args[0])); + } + } } diff --git a/test/jdk/jfr/api/consumer/security/TestMissingPermission.java b/test/jdk/jfr/api/consumer/security/TestMissingPermission.java new file mode 100644 index 000000000..f08c4c138 --- /dev/null +++ b/test/jdk/jfr/api/consumer/security/TestMissingPermission.java @@ -0,0 +1,69 @@ +/* + * 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.api.consumer.security; + +import java.io.IOException; + +import jdk.jfr.consumer.EventStream; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests that streaming doesn't work if + * FlightRecordingPermission("accessFlightRecorder") is missing + * @key jfr + * @library /lib / + * + * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=no-permission.policy + * jdk.jfr.api.consumer.security.TestMissingPermission + */ +public class TestMissingPermission { + + public static void main(String... args) throws Exception { + testOpenRepository(); + testRecordingStream(); + } + + private static void testRecordingStream() throws IOException { + try { + try (EventStream es = EventStream.openRepository()) { + throw new AssertionError("Should not be able to create EventStream without FlightRecorderPermission"); + } + } catch (SecurityException se) { + // OK, as expected + } + } + + private static void testOpenRepository() throws IOException { + try { + try (RecordingStream es = new RecordingStream()) { + throw new AssertionError("Should not be able to create RecordingStream without FlightRecorderPermission"); + } + } catch (SecurityException se) { + // OK, as expected + } + } +} diff --git a/test/jdk/jfr/api/consumer/security/TestRecordingFile.java b/test/jdk/jfr/api/consumer/security/TestRecordingFile.java new file mode 100644 index 000000000..f39dfa06a --- /dev/null +++ b/test/jdk/jfr/api/consumer/security/TestRecordingFile.java @@ -0,0 +1,54 @@ +/* + * 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.api.consumer.security; + +import java.nio.file.Paths; + +import jdk.jfr.consumer.RecordingFile; + +/** + * @test + * @summary Test that a recording file can't be opened without permissions + * @key jfr + * @library /lib / + * + * @run driver jdk.jfr.api.consumer.security.DriverRecordingDumper + * test-recording-file.jfr + * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=no-permission.policy + * jdk.jfr.api.consumer.security.TestRecordingFile + * test-recording-file.jfr + */ +public class TestRecordingFile { + public static void main(String... args) throws Exception { + try { + RecordingFile.readAllEvents(Paths.get(args[0])); + throw new AssertionError("Expected SecurityException"); + } catch (SecurityException se) { + // OK, as expected + return; + } + } +} diff --git a/test/jdk/jfr/api/consumer/security/TestRecordingStream.java b/test/jdk/jfr/api/consumer/security/TestRecordingStream.java new file mode 100644 index 000000000..d0b621103 --- /dev/null +++ b/test/jdk/jfr/api/consumer/security/TestRecordingStream.java @@ -0,0 +1,59 @@ +/* + * 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.api.consumer.security; + +import java.time.Instant; +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; + +/** + * @test + * @summary Tests that a RecordingStream works using only + * FlightRecordingPermission("accessFlightRecorder") + * @key jfr + * @library /lib / + * + * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=local-streaming.policy + * jdk.jfr.api.consumer.security.TestStreamingLocal + */ +public class TestRecordingStream { + + public static void main(String... args) throws Exception { + CountDownLatch latch = new CountDownLatch(1); + try (RecordingStream r = new RecordingStream()) { + // Enable JVM event, no write permission needed + r.enable(EventNames.JVMInformation); + r.setStartTime(Instant.EPOCH); + r.onEvent(EventNames.JVMInformation, e -> { + latch.countDown(); + }); + r.startAsync(); + latch.await(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/security/TestStreamingFile.java b/test/jdk/jfr/api/consumer/security/TestStreamingFile.java new file mode 100644 index 000000000..4d386b228 --- /dev/null +++ b/test/jdk/jfr/api/consumer/security/TestStreamingFile.java @@ -0,0 +1,54 @@ +/* + * 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.api.consumer.security; + +import java.nio.file.Paths; + +import jdk.jfr.consumer.EventStream; + +/** + * @test + * @summary Test that an event file stream can't be opened without permissions + * @key jfr + * @library /lib / + * + * @run driver jdk.jfr.api.consumer.security.DriverRecordingDumper + * test-streaming-file.jfr + * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=no-permission.policy + * jdk.jfr.api.consumer.security.TestStreamingFile + * test-streaming-file.jfr + */ +public class TestStreamingFile { + + public static void main(String... args) throws Exception { + try (EventStream es = EventStream.openFile(Paths.get(args[0]))) { + throw new AssertionError("Expected SecurityException"); + } catch (SecurityException se) { + // OK, as expected + return; + } + } +} diff --git a/test/jdk/jfr/api/consumer/security/TestStreamingLocal.java b/test/jdk/jfr/api/consumer/security/TestStreamingLocal.java new file mode 100644 index 000000000..bc314d5f8 --- /dev/null +++ b/test/jdk/jfr/api/consumer/security/TestStreamingLocal.java @@ -0,0 +1,69 @@ +/* + * 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.api.consumer.security; + +import java.time.Instant; +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.EventStream; +import jdk.test.lib.jfr.EventNames; + +/** + * @test + * @summary Tests that local streaming works using only + * FlightRecordingPermission("accessFlightRecorder") + * @key jfr + * @library /lib / + * + * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=local-streaming.policy + * jdk.jfr.api.consumer.security.TestStreamingLocal + */ +public class TestStreamingLocal { + + public static void main(String... args) throws Exception { + CountDownLatch latch = new CountDownLatch(1); + try (Recording r = new Recording()) { + // Enable JVM event, no write permission needed + r.enable(EventNames.JVMInformation); + r.start(); + try (Recording r2 = new Recording()){ + r2.start(); + r2.stop(); + } + r.stop(); + try (EventStream es = EventStream.openRepository()) { + es.setStartTime(Instant.EPOCH); + es.onEvent("jdk.JVMInformation", e -> { + latch.countDown(); + }); + es.startAsync(); + latch.await(); + } + } + + } +} diff --git a/test/jdk/jfr/api/consumer/security/TestStreamingRemote.java b/test/jdk/jfr/api/consumer/security/TestStreamingRemote.java new file mode 100644 index 000000000..12f3c3e1d --- /dev/null +++ b/test/jdk/jfr/api/consumer/security/TestStreamingRemote.java @@ -0,0 +1,116 @@ +/* + * 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.api.consumer.security; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.EventStream; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +/** + * @test + * @summary Test that a stream can be opened against a remote repository using + * only file permission + * @key jfr + * @library /lib / + * + * @run main/othervm jdk.jfr.api.consumer.security.TestStreamingRemote + */ +public class TestStreamingRemote { + + private static final String SUCCESS = "Success!"; + + public static class TestEvent extends Event { + } + + public static class Test { + public static void main(String... args) throws Exception { + Path repo = Paths.get(args[0]); + System.out.println("Repository: " + repo); + try (EventStream es = EventStream.openRepository(repo)) { + es.setStartTime(Instant.EPOCH); + es.onEvent(e -> { + System.out.println(SUCCESS); + es.close(); + }); + es.start(); + } + } + } + + public static void main(String... args) throws Exception { + try (Recording r = new Recording()) { + r.setFlushInterval(Duration.ofSeconds(1)); + r.start(); + String repository = System.getProperty("jdk.jfr.repository"); + Path policy = createPolicyFile(repository); + TestEvent e = new TestEvent(); + e.commit(); + String[] c = new String[4]; + c[0] = "-Djava.security.manager"; + c[1] = "-Djava.security.policy=" + escapeBackslashes(policy.toString()); + c[2] = Test.class.getName(); + c[3] = repository; + OutputAnalyzer oa = ProcessTools.executeTestJvm(c); + oa.shouldContain(SUCCESS); + } + } + + private static Path createPolicyFile(String repository) throws IOException { + Path policy = Paths.get("permission.policy").toAbsolutePath(); + try (PrintWriter pw = new PrintWriter(policy.toFile())) { + pw.println("grant {"); + // All the files and directories the contained in path + String dir = escapeBackslashes(repository); + String contents = escapeBackslashes(repository + File.separatorChar + "-"); + pw.println(" permission java.io.FilePermission \"" + dir + "\", \"read\";"); + pw.println(" permission java.io.FilePermission \"" + contents + "\", \"read\";"); + pw.println("};"); + pw.println(); + } + System.out.println("Permission file: " + policy); + for (String line : Files.readAllLines(policy)) { + System.out.println(line); + } + System.out.println(); + return policy; + } + + // Needed for Windows + private static String escapeBackslashes(String text) { + return text.replace("\\", "\\\\"); + } +} diff --git a/test/jdk/jfr/api/consumer/security/local-streaming.policy b/test/jdk/jfr/api/consumer/security/local-streaming.policy new file mode 100644 index 000000000..66b04ece4 --- /dev/null +++ b/test/jdk/jfr/api/consumer/security/local-streaming.policy @@ -0,0 +1,4 @@ +// Minimum policy to stream locally +grant { +permission jdk.jfr.FlightRecorderPermission "accessFlightRecorder"; +}; diff --git a/test/jdk/jfr/api/consumer/security/no-permission.policy b/test/jdk/jfr/api/consumer/security/no-permission.policy new file mode 100644 index 000000000..9b4355bdd --- /dev/null +++ b/test/jdk/jfr/api/consumer/security/no-permission.policy @@ -0,0 +1,3 @@ +// No permission +grant { +}; diff --git a/test/jdk/jfr/api/consumer/streaming/TestChunkGap.java b/test/jdk/jfr/api/consumer/streaming/TestChunkGap.java new file mode 100644 index 000000000..4ab514c86 --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestChunkGap.java @@ -0,0 +1,110 @@ +/* + * 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.api.consumer.streaming; + +import java.time.Instant; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.EventStream; + +/** + * @test + * @summary Tests that a stream can gracefully handle chunk being removed in the + * middle + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestFilledChunks + */ +public class TestChunkGap { + + static class StartEvent extends Event { + } + + static class TestGapEvent extends Event { + } + + static class EndEvent extends Event { + } + + private final static AtomicInteger count = new AtomicInteger(0); + + public static void main(String... args) throws Exception { + + CountDownLatch gap = new CountDownLatch(1); + CountDownLatch receivedEvent = new CountDownLatch(1); + + try (EventStream s = EventStream.openRepository()) { + try (Recording r1 = new Recording()) { + s.setStartTime(Instant.EPOCH); + s.onEvent(e -> { + System.out.println(e); + receivedEvent.countDown(); + try { + gap.await(); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + count.incrementAndGet(); + if (e.getEventType().getName().equals(EndEvent.class.getName())) { + s.close(); + } + }); + s.startAsync(); + r1.start(); + StartEvent event1 = new StartEvent(); + event1.commit(); + receivedEvent.await(); + r1.stop(); + + // create chunk that is removed + try (Recording r2 = new Recording()) { + r2.enable(TestGapEvent.class); + r2.start(); + TestGapEvent event2 = new TestGapEvent(); + event2.commit(); + r2.stop(); + } + gap.countDown(); + try (Recording r3 = new Recording()) { + r3.enable(EndEvent.class); + r3.start(); + EndEvent event3 = new EndEvent(); + event3.commit(); + r3.stop(); + + s.awaitTermination(); + if (count.get() != 2) { + throw new AssertionError("Expected 2 event, but got " + count); + } + } + } + } + } + +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestEmptyChunks.java b/test/jdk/jfr/api/consumer/streaming/TestEmptyChunks.java new file mode 100644 index 000000000..aa962ff85 --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestEmptyChunks.java @@ -0,0 +1,83 @@ +/* + * 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.api.consumer.streaming; + +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Test that it is possible to iterate over chunk without normal events + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestEmptyChunks + */ +public class TestEmptyChunks { + static class EndEvent extends Event { + } + + public static void main(String... args) throws Exception { + CountDownLatch end = new CountDownLatch(1); + CountDownLatch firstFlush = new CountDownLatch(1); + try (RecordingStream es = new RecordingStream()) { + es.onEvent(EndEvent.class.getName(), e -> { + end.countDown(); + }); + es.onFlush(() -> { + firstFlush.countDown(); + }); + es.startAsync(); + System.out.println("Invoked startAsync()"); + // Wait for stream thread to start + firstFlush.await(); + System.out.println("Stream thread active"); + Recording r1 = new Recording(); + r1.start(); + System.out.println("Chunk 1 started"); + Recording r2 = new Recording(); + r2.start(); + System.out.println("Chunk 2 started"); + Recording r3 = new Recording(); + r3.start(); + System.out.println("Chunk 3 started"); + r2.stop(); + System.out.println("Chunk 4 started"); + r3.stop(); + System.out.println("Chunk 5 started"); + EndEvent e = new EndEvent(); + e.commit(); + end.await(); + r1.stop(); + System.out.println("Chunk 5 ended"); + r1.close(); + r2.close(); + r3.close(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestEnableEvents.java b/test/jdk/jfr/api/consumer/streaming/TestEnableEvents.java new file mode 100644 index 000000000..dd28df61f --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestEnableEvents.java @@ -0,0 +1,98 @@ +/* + * 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.api.consumer.streaming; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Enabled; +import jdk.jfr.Event; +import jdk.jfr.FlightRecorder; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Verifies that it is possible to stream contents from specified event + * settings + * @key jfr + * @library /lib / + * + * @run main/othervm jdk.jfr.api.consumer.streaming.TestEnableEvents + */ +public class TestEnableEvents { + + @Enabled(false) + static class HorseEvent extends Event { + } + + @Enabled(false) + static class ElephantEvent extends Event { + } + + @Enabled(false) + static class TigerEvent extends Event { + } + + public static void main(String... args) throws Exception { + CountDownLatch elephantLatch = new CountDownLatch(1); + CountDownLatch tigerLatch = new CountDownLatch(1); + CountDownLatch horseLatch = new CountDownLatch(1); + + FlightRecorder.addPeriodicEvent(ElephantEvent.class, () -> { + HorseEvent ze = new HorseEvent(); + ze.commit(); + }); + + try (RecordingStream s = new RecordingStream()) { + s.enable(HorseEvent.class.getName()).withPeriod(Duration.ofMillis(50)); + s.enable(TigerEvent.class.getName()); + s.enable(ElephantEvent.class.getName()); + s.onEvent(TigerEvent.class.getName(), e -> { + System.out.println("Event: " + e.getEventType().getName()); + System.out.println("Found tiger!"); + tigerLatch.countDown(); + }); + s.onEvent(HorseEvent.class.getName(), e -> { + System.out.println("Found horse!"); + horseLatch.countDown(); + }); + s.onEvent(ElephantEvent.class.getName(), e -> { + System.out.println("Found elelphant!"); + elephantLatch.countDown(); + }); + s.startAsync(); + TigerEvent te = new TigerEvent(); + te.commit(); + ElephantEvent ee = new ElephantEvent(); + ee.commit(); + elephantLatch.await(); + horseLatch.await(); + tigerLatch.await(); + } + + } + +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestEventRegistration.java b/test/jdk/jfr/api/consumer/streaming/TestEventRegistration.java new file mode 100644 index 000000000..4cb720ba4 --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestEventRegistration.java @@ -0,0 +1,80 @@ +/* + * 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.api.consumer.streaming; + +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.FlightRecorder; +import jdk.jfr.Registered; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Test that it is possible to register new metadata in a chunk + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestEventRegistration + */ +public class TestEventRegistration { + @Registered(false) + static class StreamEvent1 extends Event { + } + + @Registered(false) + static class StreamEvent2 extends Event { + } + + public static void main(String... args) throws Exception { + + CountDownLatch s1Latch = new CountDownLatch(1); + CountDownLatch s2Latch = new CountDownLatch(1); + try (RecordingStream es = new RecordingStream()) { + es.onEvent(StreamEvent1.class.getName(), e -> { + s1Latch.countDown(); + }); + es.onEvent(StreamEvent2.class.getName(), e -> { + s2Latch.countDown(); + }); + es.startAsync(); + System.out.println("Registering " + StreamEvent1.class.getName()); + FlightRecorder.register(StreamEvent1.class); + StreamEvent1 s1 = new StreamEvent1(); + s1.commit(); + System.out.println(StreamEvent1.class.getName() + " commited"); + System.out.println("Awaiting latch for " + StreamEvent1.class.getName()); + s1Latch.await(); + System.out.println(); + System.out.println("Registering " + StreamEvent2.class.getName()); + FlightRecorder.register(StreamEvent2.class); + StreamEvent2 s2 = new StreamEvent2(); + s2.commit(); + System.out.println(StreamEvent2.class.getName() + " commited"); + System.out.println("Awaiting latch for " + StreamEvent2.class.getName()); + s2Latch.await(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestFilledChunks.java b/test/jdk/jfr/api/consumer/streaming/TestFilledChunks.java new file mode 100644 index 000000000..d35ba49d4 --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestFilledChunks.java @@ -0,0 +1,81 @@ +/* + * 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.api.consumer.streaming; + +import java.util.Random; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Test that it is possible to iterate over chunk with normal events + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestFilledChunks + */ +public class TestFilledChunks { + + static class FillEvent extends Event { + String message; + int value; + int id; + } + + static class EndEvent extends Event { + } + + // Will generate about 100 MB of data, or 8-9 chunks + private static final int EVENT_COUNT = 5_000_000; + + public static void main(String... args) throws Exception { + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(FillEvent.class.getName(), e -> { + int id = e.getInt("id"); + // Some events may get lost due to + // buffer being full. + if (id > EVENT_COUNT / 2) { + rs.close(); + } + }); + rs.startAsync(); + long seed = System.currentTimeMillis(); + System.out.println("Random seed: " + seed); + Random r = new Random(seed); + for (int i = 1; i < EVENT_COUNT; i++) { + FillEvent f = new FillEvent(); + f.message = i % 2 == 0 ? "hello, hello, hello, hello, hello!" : "hi!"; + f.value = r.nextInt(10000); + f.id = i; + f.commit(); + if (i % 1_000_000 == 0) { + System.out.println("Emitted " + i + " events"); + } + } + rs.awaitTermination(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestFiltering.java b/test/jdk/jfr/api/consumer/streaming/TestFiltering.java new file mode 100644 index 000000000..d8f75b4cb --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestFiltering.java @@ -0,0 +1,80 @@ +/* + * 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.api.consumer.streaming; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Verifies that it is possible to filter a stream for an event + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestFiltering + */ +public class TestFiltering { + + static class SnakeEvent extends Event { + int id; + } + + static class EelEvent extends Event { + int id; + } + + public static void main(String... args) throws Exception { + CountDownLatch l = new CountDownLatch(1); + String eventName = SnakeEvent.class.getName(); + AtomicInteger idCounter = new AtomicInteger(-1); + try (RecordingStream e = new RecordingStream()) { + e.onEvent(eventName, event -> { + if (!event.getEventType().getName().equals(eventName)) { + throw new InternalError("Unexpected event " + e); + } + if (event.getInt("id") != idCounter.incrementAndGet()) { + throw new InternalError("Incorrect id"); + } + if (idCounter.get() == 99) { + l.countDown(); + } + }); + e.startAsync(); + for (int i = 0; i < 100; i++) { + SnakeEvent se = new SnakeEvent(); + se.id = i; + se.commit(); + + EelEvent ee = new EelEvent(); + ee.id = i; + ee.commit(); + } + l.await(); + } + } +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestLatestEvent.java b/test/jdk/jfr/api/consumer/streaming/TestLatestEvent.java new file mode 100644 index 000000000..b7e8f6e15 --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestLatestEvent.java @@ -0,0 +1,134 @@ +/* + * 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.api.consumer.streaming; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import jdk.jfr.Event; +import jdk.jfr.FlightRecorder; +import jdk.jfr.Name; +import jdk.jfr.Recording; +import jdk.jfr.consumer.EventStream; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Verifies that EventStream::openRepository() read from the latest flush + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestLatestEvent + */ +public class TestLatestEvent { + + @Name("NotLatest") + static class NotLatestEvent extends Event { + + public int id; + } + + @Name("Latest") + static class LatestEvent extends Event { + } + + @Name("MakeChunks") + static class MakeChunks extends Event { + } + + public static void main(String... args) throws Exception { + CountDownLatch notLatestEvent = new CountDownLatch(6); + CountDownLatch beginChunks = new CountDownLatch(1); + + try (RecordingStream r = new RecordingStream()) { + r.onEvent("MakeChunks", event-> { + System.out.println(event); + beginChunks.countDown(); + }); + r.onEvent("NotLatest", event -> { + System.out.println(event); + notLatestEvent.countDown(); + }); + r.startAsync(); + MakeChunks e = new MakeChunks(); + e.commit(); + + System.out.println("Waiting for first chunk"); + beginChunks.await(); + // Create 5 chunks with events in the repository + for (int i = 0; i < 5; i++) { + System.out.println("Creating empty chunk"); + try (Recording r1 = new Recording()) { + r1.start(); + NotLatestEvent notLatest = new NotLatestEvent(); + notLatest.id = i; + notLatest.commit(); + r1.stop(); + } + } + System.out.println("All empty chunks created"); + + // Create event in new chunk + NotLatestEvent notLatest = new NotLatestEvent(); + notLatest.id = 5; + notLatest.commit(); + + // This latch ensures thatNotLatest has been + // flushed and a new valid position has been written + // to the chunk header + notLatestEvent.await(80, TimeUnit.SECONDS); + if (notLatestEvent.getCount() != 0) { + Recording rec = FlightRecorder.getFlightRecorder().takeSnapshot(); + Path p = Paths.get("error-not-latest.jfr").toAbsolutePath(); + rec.dump(p); + System.out.println("Dumping repository as a file for inspection at " + p); + throw new Exception("Timeout 80 s. Expected 6 event, but got " + notLatestEvent.getCount()); + } + + try (EventStream s = EventStream.openRepository()) { + System.out.println("EventStream opened"); + AtomicBoolean foundLatest = new AtomicBoolean(); + s.onEvent(event -> { + String name = event.getEventType().getName(); + System.out.println("Found event " + name); + foundLatest.set(name.equals("Latest")); + }); + s.startAsync(); + // Must loop here as there is no guarantee + // that the parser thread starts before event + // is flushed + while (!foundLatest.get()) { + LatestEvent latest = new LatestEvent(); + latest.commit(); + System.out.println("Latest event emitted. Waiting 1 s ..."); + Thread.sleep(1000); + } + } + } + } +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestRecordingBefore.java b/test/jdk/jfr/api/consumer/streaming/TestRecordingBefore.java new file mode 100644 index 000000000..c9db74545 --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestRecordingBefore.java @@ -0,0 +1,97 @@ +/* + * 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.api.consumer.streaming; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Verifies that it is possible to start a stream when there are + * already chunk in the repository + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestRecordingBefore + */ +public class TestRecordingBefore { + + static class SnakeEvent extends Event { + int id; + } + + public static void main(String... args) throws Exception { + + try (Recording r1 = new Recording()) { + r1.start(); + emitSnakeEvent(1); + emitSnakeEvent(2); + emitSnakeEvent(3); + // Force a chunk rotation + try (Recording r2 = new Recording()) { + r2.start(); + emitSnakeEvent(4); + emitSnakeEvent(5); + emitSnakeEvent(6); + r2.stop(); + } + r1.stop(); + // Two chunks should now exist in the repository + AtomicBoolean fail = new AtomicBoolean(false); + CountDownLatch lastEvent = new CountDownLatch(1); + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(e -> { + long id = e.getLong("id"); + if (id < 7) { + System.out.println("Found unexpected id " + id); + fail.set(true); + } + if (id == 9) { + lastEvent.countDown(); + } + }); + rs.startAsync(); + emitSnakeEvent(7); + emitSnakeEvent(8); + emitSnakeEvent(9); + lastEvent.await(); + if (fail.get()) { + throw new Exception("Found events from a previous recording"); + } + } + } + } + + static void emitSnakeEvent(int id) { + SnakeEvent e = new SnakeEvent(); + e.id = id; + e.commit(); + } + +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestRemovedChunks.java b/test/jdk/jfr/api/consumer/streaming/TestRemovedChunks.java new file mode 100644 index 000000000..e2b662908 --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestRemovedChunks.java @@ -0,0 +1,125 @@ +/* + * 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.api.consumer.streaming; + +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests that a stream can gracefully handle chunk being removed + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestRemovedChunks + */ +public class TestRemovedChunks { + private final static CountDownLatch parkLatch = new CountDownLatch(1); + private final static CountDownLatch removalLatch = new CountDownLatch(1); + private final static CountDownLatch IFeelFineLatch = new CountDownLatch(1); + + static class DataEvent extends Event { + double double1; + double double2; + double double3; + double double4; + double double5; + } + + static class ParkStream extends Event { + } + + static class IFeelFine extends Event { + } + + public static void main(String... args) throws Exception { + + try (RecordingStream s = new RecordingStream()) { + s.setMaxSize(5_000_000); + s.onEvent(ParkStream.class.getName(), e -> { + parkLatch.countDown(); + await(removalLatch); + + }); + s.onEvent(IFeelFine.class.getName(), e -> { + IFeelFineLatch.countDown(); + }); + s.startAsync(); + // Fill first chunk with data + emitData(1_000_000); + // Park stream + ParkStream ps = new ParkStream(); + ps.commit(); + await(parkLatch); + // Rotate and emit data that exceeds maxSize + for (int i = 0; i< 10;i++) { + try (Recording r = new Recording()) { + r.start(); + emitData(1_000_000); + } + } + // Emit final event + IFeelFine i = new IFeelFine(); + i.commit(); + // Wake up parked stream + removalLatch.countDown(); + // Await event things gone bad + await(IFeelFineLatch); + } + } + + private static void await(CountDownLatch latch) throws Error { + try { + latch.await(); + } catch (InterruptedException e1) { + throw new Error("Latch interupted"); + } + } + + private static void emitData(int amount) throws InterruptedException { + int count = 0; + while (amount > 0) { + DataEvent de = new DataEvent(); + // 5 doubles are 40 bytes bytes + // and event size, event type, thread, + // start time, duration and stack trace about 15 bytes + de.double1 = 0.0; + de.double2 = 1.0; + de.double3 = 2.0; + de.double4 = 3.0; + de.double5 = 4.0; + de.commit(); + amount -= 55; + count++; + // + if (count % 100_000 == 0) { + Thread.sleep(10); + } + } + } +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestRepositoryMigration.java b/test/jdk/jfr/api/consumer/streaming/TestRepositoryMigration.java new file mode 100644 index 000000000..86cddefb9 --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestRepositoryMigration.java @@ -0,0 +1,107 @@ +/* + * 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.api.consumer.streaming; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.EventStream; +import jdk.jfr.jcmd.JcmdHelper; + +/** + * @test + * @summary Verifies that is possible to stream from a repository that is being + * moved. + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestRepositoryMigration + */ +public class TestRepositoryMigration { + static class MigrationEvent extends Event { + int id; + } + + public static void main(String... args) throws Exception { + Path newRepository = Paths.get("new-repository"); + CountDownLatch event1 = new CountDownLatch(1); + CountDownLatch event2 = new CountDownLatch(1); + + try (EventStream es = EventStream.openRepository()) { + es.setStartTime(Instant.EPOCH); + es.onEvent(e -> { + System.out.println(e); + if (e.getInt("id") == 1) { + event1.countDown(); + } + if (e.getInt("id") == 2) { + event2.countDown(); + } + }); + es.startAsync(); + System.out.println("Started es.startAsync()"); + + try (Recording r = new Recording()) { + r.setFlushInterval(Duration.ofSeconds(1)); + r.start(); + // Chunk in default repository + MigrationEvent e1 = new MigrationEvent(); + e1.id = 1; + e1.commit(); + event1.await(); + System.out.println("Passed the event1.await()"); + JcmdHelper.jcmd("JFR.configure", "repositorypath=" + newRepository.toAbsolutePath()); + // Chunk in new repository + MigrationEvent e2 = new MigrationEvent(); + e2.id = 2; + e2.commit(); + r.stop(); + event2.await(); + System.out.println("Passed the event2.await()"); + // Verify that it happened in new repository + if (!Files.exists(newRepository)) { + throw new AssertionError("Could not find repository " + newRepository); + } + System.out.println("Listing contents in new repository:"); + boolean empty = true; + for (Path p : Files.newDirectoryStream(newRepository)) { + System.out.println(p.toAbsolutePath()); + empty = false; + } + System.out.println(); + if (empty) { + throw new AssertionError("Could not find contents in new repository location " + newRepository); + } + } + } + } + +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestRepositoryProperty.java b/test/jdk/jfr/api/consumer/streaming/TestRepositoryProperty.java new file mode 100644 index 000000000..ff906c7cd --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestRepositoryProperty.java @@ -0,0 +1,111 @@ +/* + * 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.api.consumer.streaming; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.Properties; + +import com.sun.tools.attach.AttachNotSupportedException; +import com.sun.tools.attach.VirtualMachine; + +import jdk.jfr.Recording; +import jdk.test.lib.dcmd.CommandExecutor; +import jdk.test.lib.dcmd.PidJcmdExecutor; +import jdk.test.lib.process.ProcessTools; + +/** + * @test + * @summary Verifies that it is possible to access JFR repository from a system + * property + * @key jfr + * @library /lib / + * @modules jdk.attach + * jdk.jfr + * @run main/othervm -Djdk.attach.allowAttachSelf=true jdk.jfr.api.consumer.streaming.TestRepositoryProperty + */ +public class TestRepositoryProperty { + + private final static String JFR_REPOSITORY_LOCATION_PROPERTY = "jdk.jfr.repository"; + + public static void main(String... args) throws Exception { + testBeforeInitialization(); + testAfterInitialization(); + testFromAgent(); + testAfterChange(); + } + + private static void testFromAgent() throws Exception { + String pidText = String.valueOf(ProcessTools.getProcessId()); + VirtualMachine vm = VirtualMachine.attach(pidText); + Properties p = vm.getSystemProperties(); + String location = (String) p.get(JFR_REPOSITORY_LOCATION_PROPERTY); + if (location == null) { + throw new AssertionError("Could not find repository path in agent properties"); + } + Path path = FileSystems.getDefault().getPath(location); + if (!Files.isDirectory(path)) { + throw new AssertionError("Repository path doesn't point to directory"); + } + } + + private static void testAfterChange() { + Path newRepository = FileSystems.getDefault().getPath(".").toAbsolutePath(); + + String cmd = "JFR.configure repository=" + newRepository.toString(); + CommandExecutor executor = new PidJcmdExecutor(); + executor.execute(cmd); + String location = System.getProperty(JFR_REPOSITORY_LOCATION_PROPERTY); + if (newRepository.toString().equals(location)) { + throw new AssertionError("Repository path not updated after it has been changed"); + } + } + + private static void testAfterInitialization() { + try (Recording r = new Recording()) { + r.start(); + String location = System.getProperty(JFR_REPOSITORY_LOCATION_PROPERTY); + if (location == null) { + throw new AssertionError("Repository path should exist after JFR is initialized"); + } + System.out.println("repository=" + location); + Path p = FileSystems.getDefault().getPath(location); + if (!Files.isDirectory(p)) { + throw new AssertionError("Repository path doesn't point to directory"); + } + } + + } + + private static void testBeforeInitialization() { + String location = System.getProperty(JFR_REPOSITORY_LOCATION_PROPERTY); + if (location != null) { + throw new AssertionError("Repository path should not exist before JFR is initialized"); + } + } +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java b/test/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java new file mode 100644 index 000000000..ec48d2992 --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java @@ -0,0 +1,120 @@ +/* + * 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.api.consumer.streaming; + +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.FlightRecorder; +import jdk.jfr.Name; +import jdk.jfr.Period; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Verifies that it is possible to stream contents of ongoing + * recordings + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestStartMultiChunk + */ +public class TestStartMultiChunk { + + @Period("10 s") + @Name("Zebra") + static class ZebraEvent extends Event { + } + + @Name("Cat") + static class CatEvent extends Event { + } + + @Name("Dog") + static class DogEvent extends Event { + } + + @Name("Mouse") + static class MouseEvent extends Event { + } + + public static void main(String... args) throws Exception { + CountDownLatch dogLatch = new CountDownLatch(1); + CountDownLatch catLatch = new CountDownLatch(1); + CountDownLatch mouseLatch = new CountDownLatch(1); + CountDownLatch zebraLatch = new CountDownLatch(3); + + FlightRecorder.addPeriodicEvent(ZebraEvent.class, () -> { + ZebraEvent ze = new ZebraEvent(); + ze.commit(); + System.out.println("Zebra emitted"); + }); + + try (RecordingStream s = new RecordingStream()) { + s.onEvent("Cat", e -> { + System.out.println("Found cat!"); + catLatch.countDown(); + }); + s.onEvent("Dog", e -> { + System.out.println("Found dog!"); + dogLatch.countDown(); + }); + s.onEvent("Zebra", e -> { + System.out.println("Found zebra!"); + zebraLatch.countDown(); + }); + s.onEvent("Mouse", e -> { + System.out.println("Found mouse!"); + mouseLatch.countDown(); + }); + s.startAsync(); + System.out.println("Stream recoding started"); + + try (Recording r1 = new Recording()) { + r1.start(); + System.out.println("r1.start()"); + MouseEvent me = new MouseEvent(); + me.commit(); + System.out.println("Mouse emitted"); + mouseLatch.await(); + try (Recording r2 = new Recording()) { // force chunk rotation + // in stream + r2.start(); + System.out.println("r2.start()"); + DogEvent de = new DogEvent(); + de.commit(); + System.out.println("Dog emitted"); + dogLatch.await(); + CatEvent ce = new CatEvent(); + ce.commit(); + System.out.println("Cat emitted"); + catLatch.await(); + zebraLatch.await(); + } + } + } + } +} diff --git a/test/jdk/jfr/api/consumer/streaming/TestStartSingleChunk.java b/test/jdk/jfr/api/consumer/streaming/TestStartSingleChunk.java new file mode 100644 index 000000000..88e96bdd4 --- /dev/null +++ b/test/jdk/jfr/api/consumer/streaming/TestStartSingleChunk.java @@ -0,0 +1,89 @@ +/* + * 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.api.consumer.streaming; + +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.FlightRecorder; +import jdk.jfr.Period; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Verifies that it is possible to stream contents of ongoing + * recordings + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestStartSingleChunk + */ +public class TestStartSingleChunk { + + @Period("500 ms") + static class ElkEvent extends Event { + } + + static class FrogEvent extends Event { + } + + static class LionEvent extends Event { + } + + public static void main(String... args) throws Exception { + CountDownLatch frogLatch = new CountDownLatch(1); + CountDownLatch lionLatch = new CountDownLatch(1); + CountDownLatch elkLatch = new CountDownLatch(3); + + FlightRecorder.addPeriodicEvent(ElkEvent.class, () -> { + ElkEvent ee = new ElkEvent(); + ee.commit(); + }); + try (RecordingStream s = new RecordingStream()) { + s.onEvent(ElkEvent.class.getName(), e -> { + System.out.println("Found elk!"); + elkLatch.countDown(); + }); + s.onEvent(LionEvent.class.getName(), e -> { + System.out.println("Found lion!"); + lionLatch.countDown(); + }); + s.onEvent(FrogEvent.class.getName(), e -> { + System.out.println("Found frog!"); + frogLatch.countDown(); + }); + s.startAsync(); + FrogEvent fe = new FrogEvent(); + fe.commit(); + + LionEvent le = new LionEvent(); + le.commit(); + + frogLatch.await(); + lionLatch.await(); + elkLatch.await(); + } + } +} diff --git a/src/share/classes/jdk/jfr/consumer/LongMap.java b/test/jdk/jfr/api/consumer/streaming/TestUnstarted.java similarity index 59% rename from src/share/classes/jdk/jfr/consumer/LongMap.java rename to test/jdk/jfr/api/consumer/streaming/TestUnstarted.java index 65706e7bb..c2cf4ed2e 100644 --- a/src/share/classes/jdk/jfr/consumer/LongMap.java +++ b/test/jdk/jfr/api/consumer/streaming/TestUnstarted.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -23,39 +23,25 @@ * questions. */ -package jdk.jfr.consumer; +package jdk.jfr.api.consumer.streaming; -import java.util.HashMap; -import java.util.Iterator; +import jdk.jfr.consumer.EventStream; /** - * Commonly used data structure for looking up objects given an id (long value) - * - * TODO: Implement without using Map and Long objects, to minimize allocation - * - * @param + * @test + * @summary Verifies that it is possible to open a stream when a repository doesn't + * exists + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.consumer.streaming.TestUnstarted */ -final class LongMap implements Iterable { - private final HashMap map; - - LongMap() { - map = new HashMap<>(101); - } - - void put(long id, T object) { - map.put(id, object); - } - - T get(long id) { - return map.get(id); - } - - @Override - public Iterator iterator() { - return map.values().iterator(); - } - - Iterator keys() { - return map.keySet().iterator(); +public class TestUnstarted { + public static void main(String... args) throws Exception { + try (EventStream es = EventStream.openRepository()) { + es.onEvent(e -> { + // ignore + }); + es.startAsync(); + } } } diff --git a/test/jdk/jfr/api/event/TestEventDuration.java b/test/jdk/jfr/api/event/TestEventDuration.java new file mode 100644 index 000000000..9c5cfb919 --- /dev/null +++ b/test/jdk/jfr/api/event/TestEventDuration.java @@ -0,0 +1,66 @@ +/* + * 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.api.event; + +import java.util.List; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.test.lib.jfr.Events; +import jdk.test.lib.jfr.SimpleEvent; + +/** + * @test + * @summary Tests that a duration is recorded. + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.api.event.TestEventDuration + */ +public class TestEventDuration { + + public static int counter; + + public static void main(String[] args) throws Exception { + + try(Recording r = new Recording()) { + r.start(); + SimpleEvent e = new SimpleEvent(); + e.begin(); + for (int i = 0; i < 10_000;i++) { + counter+=i; + } + e.end(); + e.commit(); + + r.stop(); + List events = Events.fromRecording(r); + if (events.get(0).getDuration().toNanos() < 1) { + throw new AssertionError("Expected a duration"); + } + } + + } + +} diff --git a/test/jdk/jfr/api/recording/time/TestSetFlushInterval.java b/test/jdk/jfr/api/recording/time/TestSetFlushInterval.java new file mode 100644 index 000000000..179363861 --- /dev/null +++ b/test/jdk/jfr/api/recording/time/TestSetFlushInterval.java @@ -0,0 +1,85 @@ +/* + * 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.api.recording.time; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.EventStream; +import jdk.test.lib.Asserts; + +/** + * @test + * @key jfr + * @summary Test Recording::SetFlushInterval(...) and + * Recording::getFlushInterval() + * @library /lib / + * @run main/othervm jdk.jfr.api.recording.time.TestSetFlushInterval + */ + +public class TestSetFlushInterval { + + public static void main(String[] args) throws Throwable { + testSetGet(); + testSetNull(); + testFlush(); + } + + static void testFlush() throws Exception { + CountDownLatch flush = new CountDownLatch(1); + try (EventStream es = EventStream.openRepository()) { + es.onFlush(() -> { + flush.countDown(); + }); + es.startAsync(); + try (Recording r = new Recording()) { + r.setFlushInterval(Duration.ofSeconds(1)); + r.start(); + flush.await(); + } + } + } + + static void testSetNull() { + try (Recording r = new Recording()) { + r.setFlushInterval(null); + Asserts.fail("Expected NullPointerException"); + } catch (NullPointerException npe) { + // as expected + } + } + + static void testSetGet() { + try (Recording r = new Recording()) { + Duration a = Duration.ofNanos(21378461289374646L); + r.setFlushInterval(a); + Duration b = r.getFlushInterval(); + Asserts.assertEQ(a, b); + } + } + +} diff --git a/test/jdk/jfr/event/metadata/TestLookForUntestedEvents.java b/test/jdk/jfr/event/metadata/TestLookForUntestedEvents.java index 93e4e4aae..96dbda7f0 100644 --- a/test/jdk/jfr/event/metadata/TestLookForUntestedEvents.java +++ b/test/jdk/jfr/event/metadata/TestLookForUntestedEvents.java @@ -87,6 +87,12 @@ public class TestLookForUntestedEvents { ) ); + // Experimental events + private static final Set experimentalEvents = new HashSet<>( + Arrays.asList( + "Flush", "FlushStorage", "FlushStacktrace", + "FlushStringPool", "FlushMetadata", "FlushTypeSet") + ); public static void main(String[] args) throws Exception { for (EventType type : FlightRecorder.getFlightRecorder().getEventTypes()) { @@ -145,6 +151,10 @@ public class TestLookForUntestedEvents { } } + // remove experimental events from eventsFromEventNamesClass since jfrEventTypes + // excludes experimental events + eventsFromEventNamesClass.removeAll(experimentalEvents); + if (!jfrEventTypes.equals(eventsFromEventNamesClass)) { String exceptionMsg = "Events declared in jdk.test.lib.jfr.EventNames differ " + "from events returned by FlightRecorder.getEventTypes()"; diff --git a/test/jdk/jfr/event/objectsprofiling/TestOptoObjectAllocationsSampling.java b/test/jdk/jfr/event/objectsprofiling/TestOptoObjectAllocationsSampling.java index 2dbee122d..22e238834 100644 --- a/test/jdk/jfr/event/objectsprofiling/TestOptoObjectAllocationsSampling.java +++ b/test/jdk/jfr/event/objectsprofiling/TestOptoObjectAllocationsSampling.java @@ -157,8 +157,11 @@ public class TestOptoObjectAllocationsSampling { final String arrayObjectClassName = InstanceObject[].class.getName(); for (RecordedEvent event : events) { + System.out.println(event); RecordedStackTrace stackTrace = event.getStackTrace(); - Asserts.assertTrue(stackTrace != null); + if (stackTrace == null) { + continue; + } List frames = stackTrace.getFrames(); Asserts.assertTrue(frames != null && frames.size() > 0); RecordedFrame topFrame = frames.get(0); diff --git a/test/jdk/jfr/event/oldobject/TestLargeRootSet.java b/test/jdk/jfr/event/oldobject/TestLargeRootSet.java index f2f29d45a..1f85f0cd8 100644 --- a/test/jdk/jfr/event/oldobject/TestLargeRootSet.java +++ b/test/jdk/jfr/event/oldobject/TestLargeRootSet.java @@ -26,15 +26,19 @@ package jdk.jfr.event.oldobject; import java.util.ArrayList; import java.util.List; +import java.util.Random; +import java.util.Vector; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedMethod; import jdk.jfr.consumer.RecordedObject; +import jdk.jfr.consumer.RecordedStackTrace; import jdk.jfr.internal.test.WhiteBox; -import jdk.test.lib.Asserts; import jdk.test.lib.jfr.EventNames; import jdk.test.lib.jfr.Events; @@ -50,13 +54,13 @@ import jdk.test.lib.jfr.Events; public class TestLargeRootSet { private static final int THREAD_COUNT = 50; + private static final Random RANDOM = new Random(4711); + public static Vector temporaries = new Vector<>(OldObjects.MIN_SIZE); private static class RootThread extends Thread { private final CyclicBarrier barrier; private int maxDepth = OldObjects.MIN_SIZE / THREAD_COUNT; - public List temporaries = new ArrayList<>(maxDepth); - RootThread(CyclicBarrier cb) { this.barrier = cb; } @@ -67,8 +71,9 @@ public class TestLargeRootSet { private void buildRootObjects() { if (maxDepth-- > 0) { - // Allocate array to trigger sampling code path for interpreter / c1 - StackObject[] stackObject = new StackObject[0]; + // Allocate array to trigger sampling code path for interpreter + // / c1 + StackObject[] stackObject = new StackObject[RANDOM.nextInt(7)]; temporaries.add(stackObject); // make sure object escapes buildRootObjects(); } else { @@ -91,37 +96,73 @@ public class TestLargeRootSet { public static void main(String[] args) throws Exception { WhiteBox.setWriteAllObjectSamples(true); - - List threads = new ArrayList<>(); - try (Recording r = new Recording()) { - r.enable(EventNames.OldObjectSample).withStackTrace().with("cutoff", "infinity"); - r.start(); - CyclicBarrier cb = new CyclicBarrier(THREAD_COUNT + 1); - for (int i = 0; i < THREAD_COUNT; i++) { - RootThread t = new RootThread(cb); - t.start(); - if (i % 10 == 0) { - // Give threads some breathing room before starting next batch - Thread.sleep(100); + int attempt = 1; + while (true) { + System.out.println(); + System.out.println(); + System.out.println("ATTEMPT: " + attempt); + System.out.println("===================================="); + List threads = new ArrayList<>(); + try (Recording r = new Recording()) { + r.enable(EventNames.OldObjectSample).withStackTrace().with("cutoff", "infinity"); + r.start(); + CyclicBarrier cb = new CyclicBarrier(THREAD_COUNT + 1); + for (int i = 0; i < THREAD_COUNT; i++) { + RootThread t = new RootThread(cb); + t.start(); + if (i % 10 == 0) { + // Give threads some breathing room before starting next + // batch + Thread.sleep(100); + } + threads.add(t); } - threads.add(t); - } - cb.await(); - System.gc(); - r.stop(); - cb.await(); - List events = Events.fromRecording(r); - Events.hasEvents(events); - for (RecordedEvent e : events) { - RecordedObject ro = e.getValue("object"); - RecordedClass rc = ro.getValue("type"); - System.out.println(rc.getName()); - if (rc.getName().equals(StackObject[].class.getName())) { - return; // ok + cb.await(); + System.gc(); + r.stop(); + cb.await(); + List events = Events.fromRecording(r); + Events.hasEvents(events); + int sample = 0; + for (RecordedEvent e : events) { + RecordedObject ro = e.getValue("object"); + RecordedClass rc = ro.getValue("type"); + System.out.println("Sample: " + sample); + System.out.println(" - allocationTime: " + e.getInstant("allocationTime")); + System.out.println(" - type: " + rc.getName()); + RecordedObject root = e.getValue("root"); + if (root != null) { + System.out.println(" - root:"); + System.out.println(" - description: " + root.getValue("description")); + System.out.println(" - system: " + root.getValue("system")); + System.out.println(" - type: " + root.getValue("type")); + } else { + System.out.println(" - root: N/A"); + } + RecordedStackTrace stack = e.getStackTrace(); + if (stack != null) { + System.out.println(" - stack:"); + int frameCount = 0; + for (RecordedFrame frame : stack.getFrames()) { + RecordedMethod m = frame.getMethod(); + System.out.println(" " + m.getType().getName() + "." + m.getName() + "(...)"); + frameCount++; + if (frameCount == 10) { + break; + } + } + } else { + System.out.println(" - stack: N/A"); + } + System.out.println(); + if (rc.getName().equals(StackObject[].class.getName())) { + return; // ok + } + sample++; } } - Asserts.fail("Could not find root object"); + attempt++; } } -} +} diff --git a/test/jdk/jfr/event/runtime/TestFlush.java b/test/jdk/jfr/event/runtime/TestFlush.java new file mode 100644 index 000000000..d5b6d5d14 --- /dev/null +++ b/test/jdk/jfr/event/runtime/TestFlush.java @@ -0,0 +1,165 @@ +/* + * 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.event.runtime; + +import java.util.concurrent.CountDownLatch; + +import java.util.HashMap; +import java.util.Map; + +import jdk.jfr.Event; +import jdk.jfr.FlightRecorder; +import jdk.jfr.Period; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordingStream; +import jdk.jfr.consumer.RecordedEvent; + +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; + +/** + * @test + * @summary Verifies at the metalevel that stream contents are written to ongoing recordings + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.event.runtime.TestFlush + */ +public class TestFlush { + private static boolean flushEventAck = false; + + @Period("2 s") + static class ZebraEvent extends Event { + } + static class CatEvent extends Event { + } + static class DogEvent extends Event { + } + static class MouseEvent extends Event { + } + + public static void main(String... args) throws InterruptedException { + CountDownLatch dogLatch = new CountDownLatch(1); + CountDownLatch catLatch = new CountDownLatch(1); + CountDownLatch mouseLatch = new CountDownLatch(1); + CountDownLatch zebraLatch = new CountDownLatch(3); + + FlightRecorder.addPeriodicEvent(ZebraEvent.class, () -> { + ZebraEvent ze = new ZebraEvent(); + ze.commit(); + }); + + try (RecordingStream rs = new RecordingStream()) { + rs.enable(EventNames.Flush); + rs.enable(EventNames.FlushStorage); + rs.enable(EventNames.FlushStacktrace); + rs.enable(EventNames.FlushStringPool); + rs.enable(EventNames.FlushMetadata); + rs.enable(EventNames.FlushTypeSet); + rs.onEvent(e -> { + switch (e.getEventType().getName()) { + case EventNames.Flush: + flushEventAck = true; + case EventNames.FlushStorage: + case EventNames.FlushStacktrace: + case EventNames.FlushStringPool: + case EventNames.FlushMetadata: + case EventNames.FlushTypeSet: + validateFlushEvent(e); + return; + } + if (e.getEventType().getName().equals(CatEvent.class.getName())) { + System.out.println("Found cat!"); + catLatch.countDown(); + return; + } + if (e.getEventType().getName().equals(DogEvent.class.getName())) { + System.out.println("Found dog!"); + dogLatch.countDown(); + return; + } + if (e.getEventType().getName().equals(ZebraEvent.class.getName())) { + System.out.println("Found zebra!"); + zebraLatch.countDown(); + return; + } + if (e.getEventType().getName().equals(MouseEvent.class.getName())) { + System.out.println("Found mouse!"); + mouseLatch.countDown(); + return; + } + System.out.println("Unexpected event: " + e.getEventType().getName()); + }); + + rs.startAsync(); + + try (Recording r1 = new Recording()) { + r1.start(); + MouseEvent me = new MouseEvent(); + me.commit(); + System.out.println("Mouse emitted"); + mouseLatch.await(); + try (Recording r2 = new Recording()) { // force chunk rotation in stream + r2.start(); + DogEvent de = new DogEvent(); + de.commit(); + System.out.println("Dog emitted"); + dogLatch.await(); + CatEvent ce = new CatEvent(); + ce.commit(); + System.out.println("Cat emitted"); + catLatch.await(); + zebraLatch.await(); + acknowledgeFlushEvent(); + } + } + } + } + + private static void printEvent(RecordedEvent re) { + System.out.println(re.getEventType().getName()); + System.out.println(re.getStartTime().toEpochMilli()); + System.out.println(re.getEndTime().toEpochMilli()); + } + + private static void printFlushEvent(RecordedEvent re) { + printEvent(re); + System.out.println("flushID: " + (long) re.getValue("flushId")); + System.out.println("elements: " + (long) re.getValue("elements")); + System.out.println("size: " + (long) re.getValue("size")); + } + + private static void validateFlushEvent(RecordedEvent re) { + printFlushEvent(re); + Asserts.assertTrue(re.getEventType().getName().contains("Flush"), "invalid Event type"); + Asserts.assertGT((long) re.getValue("flushId"), 0L, "Invalid flush ID"); + Asserts.assertGT((long) re.getValue("elements"), 0L, "No elements"); + Asserts.assertGT((long) re.getValue("size"), 0L, "Empty size"); + } + + private static void acknowledgeFlushEvent() { + Asserts.assertTrue(flushEventAck, "No Flush event"); + } +} diff --git a/test/jdk/jfr/jcmd/TestJcmdStartFlushInterval.java b/test/jdk/jfr/jcmd/TestJcmdStartFlushInterval.java new file mode 100644 index 000000000..841a63c02 --- /dev/null +++ b/test/jdk/jfr/jcmd/TestJcmdStartFlushInterval.java @@ -0,0 +1,55 @@ +/* + * 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.jcmd; + +import java.time.Duration; + +import jdk.jfr.FlightRecorder; +import jdk.jfr.Recording; + +/** + * @test + * @summary Start a recording with a flush interval + * @key jfr + * @library /lib / + * @run main/othervm jdk.jfr.jcmd.TestJcmdStartReadOnlyFile + */ +public class TestJcmdStartFlushInterval { + + public static void main(String[] args) throws Exception { + JcmdHelper.jcmd("JFR.start","flush-interval=1s"); + for (Recording r : FlightRecorder.getFlightRecorder().getRecordings()) { + Duration d = r.getFlushInterval(); + if (d.equals(Duration.ofSeconds(1))) { + return; //OK + } else { + throw new Exception("Unexpected flush-interval=" + d); + } + } + throw new Exception("No recording found"); + } + +} diff --git a/test/jdk/jfr/jvm/TestThreadExclusion.java b/test/jdk/jfr/jvm/TestThreadExclusion.java new file mode 100644 index 000000000..963cda076 --- /dev/null +++ b/test/jdk/jfr/jvm/TestThreadExclusion.java @@ -0,0 +1,150 @@ +/* + * 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.jvm; + +import java.time.Duration; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.internal.JVM; +import jdk.jfr.Recording; + +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; + +import static jdk.test.lib.Asserts.assertTrue; + +/** + * @test + * @key jfr + * @library /lib / + * @modules jdk.jfr/jdk.jfr.internal + * @run main/othervm jdk.jfr.jvm.TestThreadExclusion + */ + +/** + * Starts and stops a number of threads in order. + * Verifies that events are in the same order. + */ +public class TestThreadExclusion { + private final static String EVENT_NAME_THREAD_START = EventNames.ThreadStart; + private final static String EVENT_NAME_THREAD_END = EventNames.ThreadEnd; + private static final String THREAD_NAME_PREFIX = "TestThread-"; + private static JVM jvm; + + public static void main(String[] args) throws Throwable { + // Test Java Thread Start event + Recording recording = new Recording(); + recording.enable(EVENT_NAME_THREAD_START).withThreshold(Duration.ofMillis(0)); + recording.enable(EVENT_NAME_THREAD_END).withThreshold(Duration.ofMillis(0)); + recording.start(); + LatchedThread[] threads = startThreads(); + long[] javaThreadIds = getJavaThreadIds(threads); + stopThreads(threads); + recording.stop(); + List events = Events.fromRecording(recording); + verifyThreadExclusion(events, javaThreadIds); + } + + private static void verifyThreadExclusion(List events, long[] javaThreadIds) throws Exception { + for (RecordedEvent event : events) { + System.out.println("Event:" + event); + final long eventJavaThreadId = event.getThread().getJavaThreadId(); + for (int i = 0; i < javaThreadIds.length; ++i) { + if (eventJavaThreadId == javaThreadIds[i]) { + throw new Exception("Event " + event.getEventType().getName() + " has a thread id " + eventJavaThreadId + " that should have been excluded"); + } + } + } + } + + private static LatchedThread[] startThreads() { + LatchedThread threads[] = new LatchedThread[10]; + ThreadGroup threadGroup = new ThreadGroup("TestThreadGroup"); + jvm = JVM.getJVM(); + for (int i = 0; i < threads.length; i++) { + threads[i] = new LatchedThread(threadGroup, THREAD_NAME_PREFIX + i); + jvm.exclude(threads[i]); + threads[i].startThread(); + System.out.println("Started thread id=" + threads[i].getId()); + } + return threads; + } + + private static long[] getJavaThreadIds(LatchedThread[] threads) { + long[] javaThreadIds = new long[threads.length]; + for (int i = 0; i < threads.length; ++i) { + javaThreadIds[i] = threads[i].getId(); + } + return javaThreadIds; + } + + private static void stopThreads(LatchedThread[] threads) { + for (LatchedThread thread : threads) { + assertTrue(jvm.isExcluded(thread), "Thread " + thread + "should be excluded"); + thread.stopThread(); + while (thread.isAlive()) { + try { + Thread.sleep(5); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + private static class LatchedThread extends Thread { + private final CountDownLatch start = new CountDownLatch(1); + private final CountDownLatch stop = new CountDownLatch(1); + + public LatchedThread(ThreadGroup threadGroup, String name) { + super(threadGroup, name); + } + + public void run() { + start.countDown(); + try { + stop.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void startThread() { + this.start(); + try { + start.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void stopThread() { + stop.countDown(); + } + } +} diff --git a/test/jdk/jfr/jvm/TestUnsupportedVM.java b/test/jdk/jfr/jvm/TestUnsupportedVM.java index c2485d746..3ce26bc86 100644 --- a/test/jdk/jfr/jvm/TestUnsupportedVM.java +++ b/test/jdk/jfr/jvm/TestUnsupportedVM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -30,8 +30,10 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Duration; import java.util.ArrayList; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; import jdk.jfr.AnnotationElement; import jdk.jfr.Configuration; @@ -48,6 +50,7 @@ import jdk.jfr.Recording; import jdk.jfr.RecordingState; import jdk.jfr.SettingControl; import jdk.jfr.ValueDescriptor; +import jdk.jfr.consumer.EventStream; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedFrame; @@ -57,6 +60,7 @@ import jdk.jfr.consumer.RecordedStackTrace; import jdk.jfr.consumer.RecordedThread; import jdk.jfr.consumer.RecordedThreadGroup; import jdk.jfr.consumer.RecordingFile; +import jdk.jfr.consumer.RecordingStream; import jdk.management.jfr.ConfigurationInfo; import jdk.management.jfr.EventTypeInfo; import jdk.management.jfr.FlightRecorderMXBean; @@ -105,9 +109,11 @@ public class TestUnsupportedVM { RecordingState.class, SettingControl.class, SettingDescriptorInfo.class, - ValueDescriptor.class + ValueDescriptor.class, + EventStream.class, + RecordingStream.class }; - // * @run main/othervm -Dprepare-recording=true jdk.jfr.jvm.TestUnsupportedVM + @Label("My Event") @Description("My fine event") static class MyEvent extends Event { @@ -124,7 +130,7 @@ public class TestUnsupportedVM { return; } - System.out.println("jdk.jfr.unsupportedvm=" + System.getProperty("jdk.jfr.unsupportedvm")); + System.out.println("jfr.unsupported.vm=" + System.getProperty("jfr.unsupported.vm")); // Class FlightRecorder if (FlightRecorder.isAvailable()) { throw new AssertionError("JFR should not be available on an unsupported VM"); @@ -135,6 +141,7 @@ public class TestUnsupportedVM { } assertIllegalStateException(() -> FlightRecorder.getFlightRecorder()); + assertIllegalStateException(() -> new RecordingStream()); assertSwallow(() -> FlightRecorder.addListener(new FlightRecorderListener() {})); assertSwallow(() -> FlightRecorder.removeListener(new FlightRecorderListener() {})); assertSwallow(() -> FlightRecorder.register(MyEvent.class)); @@ -173,8 +180,39 @@ public class TestUnsupportedVM { // Only run this part of tests if we are on VM // that can produce a recording file if (Files.exists(RECORDING_FILE)) { + boolean firstFileEvent = true; for(RecordedEvent re : RecordingFile.readAllEvents(RECORDING_FILE)) { - System.out.println(re); + // Print one event + if (firstFileEvent) { + System.out.println(re); + firstFileEvent = false; + } + } + AtomicBoolean firstStreamEvent = new AtomicBoolean(true); + try (EventStream es = EventStream.openFile(RECORDING_FILE)) { + es.onEvent(e -> { + // Print one event + if (firstStreamEvent.get()) { + try { + System.out.println(e); + firstStreamEvent.set(false); + } catch (Throwable t) { + t.printStackTrace(); + } + } + }); + es.start(); + if (firstStreamEvent.get()) { + throw new AssertionError("Didn't print streaming event"); + } + } + + try (EventStream es = EventStream.openRepository()) { + es.onEvent(e -> { + System.out.println(e); + }); + es.startAsync(); + es.awaitTermination(Duration.ofMillis(10)); } } } diff --git a/test/jdk/jfr/startupargs/TestFlushInterval.java b/test/jdk/jfr/startupargs/TestFlushInterval.java new file mode 100644 index 000000000..310a75bb7 --- /dev/null +++ b/test/jdk/jfr/startupargs/TestFlushInterval.java @@ -0,0 +1,54 @@ +/* + * 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.startupargs; + +import java.time.Duration; + +import jdk.jfr.FlightRecorder; +import jdk.jfr.Recording; + +/** + * @test + * @summary Start a recording with a flush interval + * @key jfr + * @library /lib / + * @run main/othervm -XX:StartFlightRecording=flush-interval=1s jdk.jfr.startupargs.TestFlushInterval + */ +public class TestFlushInterval { + + public static void main(String[] args) throws Exception { + for (Recording r : FlightRecorder.getFlightRecorder().getRecordings()) { + Duration d = r.getFlushInterval(); + if (d.equals(Duration.ofSeconds(1))) { + return; //OK + } else { + throw new Exception("Unexpected flush-interval " + d); + } + } + throw new Exception("No recording found"); + } + +} diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index ba5b4cc83..1a1a5fc3a 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -175,6 +175,12 @@ public class EventNames { public final static String CPUTimeStampCounter = PREFIX + "CPUTimeStampCounter"; public final static String ActiveRecording = PREFIX + "ActiveRecording"; public final static String ActiveSetting = PREFIX + "ActiveSetting"; + public static final String Flush = PREFIX + "Flush"; + public static final String FlushStringPool = PREFIX + "FlushStringPool"; + public static final String FlushStacktrace = PREFIX + "FlushStacktrace"; + public static final String FlushStorage = PREFIX + "FlushStorage"; + public static final String FlushMetadata = PREFIX + "FlushMetadata"; + public static final String FlushTypeSet = PREFIX + "FlushTypeSet"; public final static String OptoInstanceObjectAllocation = PREFIX + "OptoInstanceObjectAllocation"; public final static String OptoArrayObjectAllocation = PREFIX + "OptoArrayObjectAllocation"; -- GitLab