# HG changeset patch # User egahlin # Date 1574439643 -3600 # Node ID a0f39cc47387eba8eaaa27b14fb2377553e46d8f # Parent 80e1201f6c9a649d95591b2c786fff4f2e7aae04 8233700: EventStream not closed Reviewed-by: mgronlun, mseledtsov diff -r 80e1201f6c9a -r a0f39cc47387 src/hotspot/share/jfr/jni/jfrJniMethod.cpp --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp Fri Nov 22 09:06:35 2019 -0500 +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp Fri Nov 22 17:20:43 2019 +0100 @@ -210,6 +210,10 @@ JfrRecorder::start_recording(); JVM_END +JVM_ENTRY_NO_ENV(jboolean, jfr_is_recording(JNIEnv * env, jobject jvm)) + return JfrRecorder::is_recording() ? JNI_TRUE : JNI_FALSE; +JVM_END + JVM_ENTRY_NO_ENV(void, jfr_end_recording(JNIEnv* env, jobject jvm)) if (!JfrRecorder::is_recording()) { return; @@ -217,6 +221,9 @@ JfrRecorder::stop_recording(); JVM_END +JVM_ENTRY_NO_ENV(void, jfr_mark_chunk_final(JNIEnv * env, jobject jvm)) + JfrRepository::mark_chunk_final(); +JVM_END JVM_ENTRY_NO_ENV(jboolean, jfr_emit_event(JNIEnv* env, jobject jvm, jlong eventTypeId, jlong timeStamp, jlong when)) JfrPeriodicEventSet::requestEvent((JfrEventId)eventTypeId); diff -r 80e1201f6c9a -r a0f39cc47387 src/hotspot/share/jfr/jni/jfrJniMethod.hpp --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp Fri Nov 22 09:06:35 2019 -0500 +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp Fri Nov 22 17:20:43 2019 +0100 @@ -49,8 +49,12 @@ void JNICALL jfr_begin_recording(JNIEnv* env, jobject jvm); +jboolean JNICALL jfr_is_recording(JNIEnv* env, jobject jvm); + void JNICALL jfr_end_recording(JNIEnv* env, jobject jvm); +void JNICALL jfr_mark_chunk_final(JNIEnv* env, jobject jvm); + jboolean JNICALL jfr_emit_event(JNIEnv* env, jobject jvm, jlong eventTypeId, jlong timeStamp, jlong when); jobject JNICALL jfr_get_all_event_classes(JNIEnv* env, jobject jvm); diff -r 80e1201f6c9a -r a0f39cc47387 src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp --- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp Fri Nov 22 09:06:35 2019 -0500 +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp Fri Nov 22 17:20:43 2019 +0100 @@ -36,7 +36,9 @@ if (jfr_clz != NULL) { JNINativeMethod method[] = { (char*)"beginRecording", (char*)"()V", (void*)jfr_begin_recording, + (char*)"isRecording", (char*)"()Z", (void*)jfr_is_recording, (char*)"endRecording", (char*)"()V", (void*)jfr_end_recording, + (char*)"markChunkFinal", (char*)"()V", (void*)jfr_mark_chunk_final, (char*)"counterTime", (char*)"()J", (void*)jfr_elapsed_counter, (char*)"createJFR", (char*)"(Z)Z", (void*)jfr_create_jfr, (char*)"destroyJFR", (char*)"()Z", (void*)jfr_destroy_jfr, diff -r 80e1201f6c9a -r a0f39cc47387 src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp --- a/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp Fri Nov 22 09:06:35 2019 -0500 +++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp Fri Nov 22 17:20:43 2019 +0100 @@ -59,7 +59,8 @@ _last_update_nanos(0), _last_checkpoint_offset(0), _last_metadata_offset(0), - _generation(1) {} + _generation(1), + _final(false) {} JfrChunk::~JfrChunk() { reset(); @@ -86,10 +87,20 @@ return JFR_VERSION_MINOR; } -u2 JfrChunk::capabilities() const { +void JfrChunk::mark_final() { + _final = true; +} + +u2 JfrChunk::flags() const { // chunk capabilities, CompressedIntegers etc - static bool compressed_integers = JfrOptionSet::compressed_integers(); - return compressed_integers; + u2 flags = 0; + if (JfrOptionSet::compressed_integers()) { + flags |= 1 << 0; + } + if (_final) { + flags |= 1 << 1; + } + return flags; } int64_t JfrChunk::cpu_frequency() const { diff -r 80e1201f6c9a -r a0f39cc47387 src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp --- a/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp Fri Nov 22 09:06:35 2019 -0500 +++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp Fri Nov 22 17:20:43 2019 +0100 @@ -44,6 +44,7 @@ int64_t _last_checkpoint_offset; int64_t _last_metadata_offset; mutable u1 _generation; + bool _final; JfrChunk(); ~JfrChunk(); @@ -53,7 +54,9 @@ u2 major_version() const; u2 minor_version() const; int64_t cpu_frequency() const; - u2 capabilities() const; + u2 flags() const; + + void mark_final(); void update_start_ticks(); void update_start_nanos(); diff -r 80e1201f6c9a -r a0f39cc47387 src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.cpp --- a/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.cpp Fri Nov 22 09:06:35 2019 -0500 +++ b/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.cpp Fri Nov 22 17:20:43 2019 +0100 @@ -41,8 +41,8 @@ static const int64_t START_TICKS_OFFSET = DURATION_NANOS_OFFSET + SLOT_SIZE; static const int64_t CPU_FREQUENCY_OFFSET = START_TICKS_OFFSET + SLOT_SIZE; static const int64_t GENERATION_OFFSET = CPU_FREQUENCY_OFFSET + SLOT_SIZE; -static const int64_t CAPABILITY_OFFSET = GENERATION_OFFSET + 2; -static const int64_t HEADER_SIZE = CAPABILITY_OFFSET + 2; +static const int64_t FLAG_OFFSET = GENERATION_OFFSET + 2; +static const int64_t HEADER_SIZE = FLAG_OFFSET + 2; static fio_fd open_chunk(const char* path) { return path != NULL ? os::open(path, O_CREAT | O_RDWR, S_IREAD | S_IWRITE) : invalid_fd; @@ -117,8 +117,8 @@ _writer->flush(); } - void write_capabilities() { - _writer->be_write(_chunk->capabilities()); + void write_flags() { + _writer->be_write(_chunk->flags()); } void write_size_to_generation(int64_t size, bool finalize) { @@ -135,7 +135,7 @@ assert(_chunk != NULL, "invariant"); DEBUG_ONLY(assert_writer_position(_writer, SIZE_OFFSET);) write_size_to_generation(size, finalize); - // no need to write capabilities + write_flags(); _writer->seek(size); // implicit flush } @@ -146,7 +146,7 @@ write_magic(); write_version(); write_size_to_generation(HEADER_SIZE, false); - write_capabilities(); + write_flags(); DEBUG_ONLY(assert_writer_position(_writer, HEADER_SIZE);) _writer->flush(); } @@ -201,7 +201,7 @@ head.write_time(false); head.write_cpu_frequency(); head.write_next_generation(); - head.write_capabilities(); + head.write_flags(); assert(current_offset() - header_content_pos == HEADER_SIZE, "invariant"); const u4 checkpoint_size = current_offset() - event_size_offset; write_padded_at_offset(checkpoint_size, event_size_offset); @@ -211,6 +211,11 @@ return sz_written; } +void JfrChunkWriter::mark_chunk_final() { + assert(_chunk != NULL, "invariant"); + _chunk->mark_final(); +} + int64_t JfrChunkWriter::flush_chunk(bool flushpoint) { assert(_chunk != NULL, "invariant"); const int64_t sz_written = write_chunk_header_checkpoint(flushpoint); diff -r 80e1201f6c9a -r a0f39cc47387 src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.hpp --- a/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.hpp Fri Nov 22 09:06:35 2019 -0500 +++ b/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.hpp Fri Nov 22 17:20:43 2019 +0100 @@ -59,6 +59,7 @@ bool has_metadata() const; void set_time_stamp(); + void mark_chunk_final(); }; #endif // SHARE_JFR_RECORDER_REPOSITORY_JFRCHUNKWRITER_HPP diff -r 80e1201f6c9a -r a0f39cc47387 src/hotspot/share/jfr/recorder/repository/jfrRepository.cpp --- a/src/hotspot/share/jfr/recorder/repository/jfrRepository.cpp Fri Nov 22 09:06:35 2019 -0500 +++ b/src/hotspot/share/jfr/recorder/repository/jfrRepository.cpp Fri Nov 22 17:20:43 2019 +0100 @@ -115,6 +115,10 @@ chunkwriter().set_path(path); } +void JfrRepository::mark_chunk_final() { + chunkwriter().mark_chunk_final(); +} + jlong JfrRepository::current_chunk_start_nanos() { return chunkwriter().current_chunk_start_nanos(); } diff -r 80e1201f6c9a -r a0f39cc47387 src/hotspot/share/jfr/recorder/repository/jfrRepository.hpp --- a/src/hotspot/share/jfr/recorder/repository/jfrRepository.hpp Fri Nov 22 09:06:35 2019 -0500 +++ b/src/hotspot/share/jfr/recorder/repository/jfrRepository.hpp Fri Nov 22 17:20:43 2019 +0100 @@ -70,6 +70,7 @@ public: static void set_path(jstring location, JavaThread* jt); static void set_chunk_path(jstring path, JavaThread* jt); + static void mark_chunk_final(); static void flush(JavaThread* jt); static jlong current_chunk_start_nanos(); }; diff -r 80e1201f6c9a -r a0f39cc47387 src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp --- a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp Fri Nov 22 09:06:35 2019 -0500 +++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp Fri Nov 22 17:20:43 2019 +0100 @@ -432,6 +432,7 @@ if (_chunkwriter.is_valid()) { Thread* const t = Thread::current(); _storage.flush_regular_buffer(t->jfr_thread_local()->native_buffer(), t); + _chunkwriter.mark_chunk_final(); invoke_flush(); _chunkwriter.set_time_stamp(); _repository.close_chunk(); diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java Fri Nov 22 17:20:43 2019 +0100 @@ -139,7 +139,7 @@ */ public static EventStream openRepository() throws IOException { Utils.checkAccessFlightRecorder(); - return new EventDirectoryStream(AccessController.getContext(), null, SecuritySupport.PRIVILIGED, false); + return new EventDirectoryStream(AccessController.getContext(), null, SecuritySupport.PRIVILIGED, null); } /** @@ -162,7 +162,7 @@ public static EventStream openRepository(Path directory) throws IOException { Objects.nonNull(directory); AccessControlContext acc = AccessController.getContext(); - return new EventDirectoryStream(acc, directory, FileAccess.UNPRIVILIGED, false); + return new EventDirectoryStream(acc, directory, FileAccess.UNPRIVILIGED, null); } /** diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java Fri Nov 22 17:20:43 2019 +0100 @@ -88,7 +88,8 @@ this.recording = new Recording(); this.recording.setFlushInterval(Duration.ofMillis(1000)); try { - this.directoryStream = new EventDirectoryStream(acc, null, SecuritySupport.PRIVILIGED, true); + PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording); + this.directoryStream = new EventDirectoryStream(acc, null, SecuritySupport.PRIVILIGED, pr); } catch (IOException ioe) { this.recording.close(); throw new IllegalStateException(ioe.getMessage()); diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java Fri Nov 22 17:20:43 2019 +0100 @@ -43,7 +43,6 @@ static final long RESERVED_CLASS_ID_LIMIT = 400; - private volatile boolean recording; private volatile boolean nativeOK; private static native void registerNatives(); @@ -69,6 +68,15 @@ } /** + * Marks current chunk as final + *

+ * This allows streaming clients to read the chunk header and + * close the stream when no more data will be written into + * the current repository. + */ + public native void markChunkFinal(); + + /** * Begin recording events * * Requires that JFR has been started with {@link #createNativeJFR()} @@ -76,6 +84,19 @@ public native void beginRecording(); /** + * Return true if the JVM is recording + */ + public native boolean isRecording(); + + /** + * End recording events, which includes flushing data in thread buffers + * + * Requires that JFR has been started with {@link #createNativeJFR()} + * + */ + public native void endRecording(); + + /** * Return ticks * * @return the time, in ticks @@ -97,13 +118,7 @@ */ public native boolean emitEvent(long eventTypeId, long timestamp, long when); - /** - * End recording events, which includes flushing data in thread buffers - * - * Requires that JFR has been started with {@link #createNativeJFR()} - * - */ - public native void endRecording(); + /** * Return a list of all classes deriving from {@link jdk.internal.event.Event} @@ -354,20 +369,6 @@ */ public native void storeMetadataDescriptor(byte[] bytes); - public void endRecording_() { - endRecording(); - recording = false; - } - - public void beginRecording_() { - beginRecording(); - recording = true; - } - - public boolean isRecording() { - return recording; - } - /** * If the JVM supports JVM TI and retransformation has not been disabled this * method will return true. This flag can not change during the lifetime of @@ -558,4 +559,5 @@ *@return start time of the recording in nanos, -1 in case of in-memory */ public native long getChunkStartNanos(); + } diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java Fri Nov 22 17:20:43 2019 +0100 @@ -31,6 +31,7 @@ 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; @@ -53,6 +54,7 @@ import jdk.jfr.RecordingState; import jdk.jfr.events.ActiveRecordingEvent; import jdk.jfr.events.ActiveSettingEvent; +import jdk.jfr.internal.SecuritySupport.SafePath; import jdk.jfr.internal.SecuritySupport.SecureRecorderListener; import jdk.jfr.internal.instrument.JDKEvents; @@ -70,6 +72,7 @@ private long recordingCounter = 0; private RepositoryChunk currentChunk; + private boolean inShutdown; public PlatformRecorder() throws Exception { repository = Repository.getRepository(); @@ -176,6 +179,10 @@ } } + synchronized void setInShutDown() { + this.inShutdown = true; + } + // called by shutdown hook synchronized void destroy() { try { @@ -198,7 +205,7 @@ if (jvm.hasNativeJFR()) { if (jvm.isRecording()) { - jvm.endRecording_(); + jvm.endRecording(); } jvm.destroyNativeJFR(); } @@ -236,7 +243,7 @@ MetadataRepository.getInstance().setOutput(null); } currentChunk = newChunk; - jvm.beginRecording_(); + jvm.beginRecording(); startNanos = jvm.getChunkStartNanos(); recording.setState(RecordingState.RUNNING); updateSettings(); @@ -289,11 +296,15 @@ } } OldObjectSample.emit(recording); + recording.setFinalStartnanos(jvm.getChunkStartNanos()); if (endPhysical) { RequestEngine.doChunkEnd(); if (recording.isToDisk()) { if (currentChunk != null) { + if (inShutdown) { + jvm.markChunkFinal(); + } MetadataRepository.getInstance().setOutput(null); finishChunk(currentChunk, now, null); currentChunk = null; @@ -302,7 +313,7 @@ // last memory dumpMemoryToDestination(recording); } - jvm.endRecording_(); + jvm.endRecording(); disableEvents(); } else { RepositoryChunk newChunk = null; @@ -327,7 +338,6 @@ } else { RequestEngine.setFlushInterval(Long.MAX_VALUE); } - recording.setState(RecordingState.STOPPED); } @@ -357,17 +367,7 @@ 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(); @@ -584,6 +584,19 @@ target.setInternalDuration(Duration.between(startTime, endTime)); } - - + public synchronized void migrate(SafePath repo) throws IOException { + // Must set repository while holding recorder lock so + // the final chunk in repository gets marked correctly + Repository.getRepository().setBasePath(repo); + boolean disk = false; + for (PlatformRecording s : getRecordings()) { + if (RecordingState.RUNNING == s.getState() && s.isToDisk()) { + disk = true; + } + } + if (disk) { + jvm.markChunkFinal(); + rotateDisk(); + } + } } diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java Fri Nov 22 17:20:43 2019 +0100 @@ -85,6 +85,7 @@ private AccessControlContext noDestinationDumpOnExitAccessControlContext; private boolean shuoldWriteActiveRecordingEvent = true; private Duration flushInterval = Duration.ofSeconds(1); + private long finalStartChunkNanos = Long.MIN_VALUE; PlatformRecording(PlatformRecorder recorder, long id) { // Typically the access control context is taken @@ -811,4 +812,12 @@ return Long.MAX_VALUE; } } + + public long getFinalChunkStartNanos() { + return finalStartChunkNanos; + } + + public void setFinalStartnanos(long chunkStartNanos) { + this.finalStartChunkNanos = chunkStartNanos; + } } diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java Fri Nov 22 17:20:43 2019 +0100 @@ -85,6 +85,7 @@ if (!SecuritySupport.existDirectory(repository)) { this.repository = createRepository(baseLocation); jvm.setRepositoryLocation(repository.toString()); + SecuritySupport.setProperty(JFR_REPOSITORY_LOCATION_PROPERTY, repository.toString()); cleanupDirectories.add(repository); } return new RepositoryChunk(repository, timestamp); @@ -115,9 +116,7 @@ if (i == MAX_REPO_CREATION_RETRIES) { 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; + return SecuritySupport.toRealPath(f); } private static SafePath createRealBasePath(SafePath safePath) throws IOException { diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java Fri Nov 22 17:20:43 2019 +0100 @@ -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 @@ -51,7 +51,7 @@ // starting any "real" operations. In low memory situations, // we would like to take an OOM as early as possible. tlabDummyObject = new Object(); - + recorder.setInShutDown(); for (PlatformRecording recording : recorder.getRecordings()) { if (recording.getDumpOnExit() && recording.getState() == RecordingState.RUNNING) { dump(recording); diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java Fri Nov 22 17:20:43 2019 +0100 @@ -40,6 +40,7 @@ import jdk.jfr.internal.LogLevel; import jdk.jfr.internal.LogTag; import jdk.jfr.internal.Logger; +import jdk.jfr.internal.PlatformRecording; import jdk.jfr.internal.SecuritySupport; /* @@ -50,19 +51,19 @@ 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 final PlatformRecording recording; private volatile Thread thread; private Dispatcher dispatcher; private volatile boolean closed; - AbstractEventStream(AccessControlContext acc, boolean active) throws IOException { + AbstractEventStream(AccessControlContext acc, PlatformRecording recording) throws IOException { this.accessControllerContext = Objects.requireNonNull(acc); - this.active = active; + this.recording = recording; } @Override @@ -229,7 +230,7 @@ if (configuration.started) { throw new IllegalStateException("Event stream can only be started once"); } - if (active && configuration.startTime == null) { + if (recording != null && configuration.startTime == null) { configuration.setStartNanos(startNanos); } configuration.setStarted(true); diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java Fri Nov 22 17:20:43 2019 +0100 @@ -39,8 +39,10 @@ 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 FLAG_BYTE_POSITION = 67; private static final long METADATA_TYPE_ID = 0; private static final byte[] FILE_MAGIC = { 'F', 'L', 'R', '\0' }; + private static final int MASK_FINAL_CHUNK = 1 << 1; private final short major; private final short minor; @@ -58,6 +60,7 @@ private long absoluteChunkEnd; private boolean isFinished; private boolean finished; + private boolean finalChunk; public ChunkHeader(RecordingInput input) throws IOException { this(input, 0, 0); @@ -101,8 +104,7 @@ Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startTicks=" + chunkStartTicks); ticksPerSecond = input.readRawLong(); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: ticksPerSecond=" + ticksPerSecond); - input.readRawInt(); // features, not used - + input.readRawInt(); // ignore file state and flag bits refresh(); input.position(absoluteEventStart); } @@ -123,6 +125,8 @@ long durationNanos = input.readPhysicalLong(); input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION); byte fileState2 = input.readPhysicalByte(); + input.positionPhysical(absoluteChunkStart + FLAG_BYTE_POSITION); + int flagByte = input.readPhysicalByte(); if (fileState1 == fileState2) { // valid header finished = fileState1 == 0; if (metadataPosition != 0) { @@ -150,6 +154,8 @@ 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()); + this.finalChunk = (flagByte & MASK_FINAL_CHUNK) != 0; + Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: finalChunk=" + finalChunk); absoluteChunkEnd = absoluteChunkStart + chunkSize; return; } @@ -183,6 +189,10 @@ return input.getFileSize() == absoluteChunkEnd; } + public boolean isFinalChunk() { + return finalChunk; + } + public boolean isFinished() throws IOException { return isFinished; } diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkParser.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkParser.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkParser.java Fri Nov 22 17:20:43 2019 +0100 @@ -448,4 +448,8 @@ return chunkHeader.getStartNanos(); } + public boolean isFinalChunk() { + return chunkHeader.isFinalChunk(); + } + } diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java Fri Nov 22 17:20:43 2019 +0100 @@ -35,6 +35,7 @@ import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.internal.JVM; +import jdk.jfr.internal.PlatformRecording; import jdk.jfr.internal.Utils; import jdk.jfr.internal.consumer.ChunkParser.ParserConfiguration; @@ -43,12 +44,12 @@ * with chunk files. * */ -public final class EventDirectoryStream extends AbstractEventStream { +public class EventDirectoryStream extends AbstractEventStream { private final static Comparator EVENT_COMPARATOR = JdkJfrConsumer.instance().eventComparator(); private final RepositoryFiles repositoryFiles; - private final boolean active; + private final PlatformRecording recording; private final FileAccess fileAccess; private ChunkParser currentParser; @@ -56,10 +57,10 @@ private RecordedEvent[] sortedCache; private int threadExclusionLevel = 0; - public EventDirectoryStream(AccessControlContext acc, Path p, FileAccess fileAccess, boolean active) throws IOException { - super(acc, active); + public EventDirectoryStream(AccessControlContext acc, Path p, FileAccess fileAccess, PlatformRecording recording) throws IOException { + super(acc, recording); this.fileAccess = Objects.requireNonNull(fileAccess); - this.active = active; + this.recording = recording; this.repositoryFiles = new RepositoryFiles(fileAccess, p); } @@ -104,7 +105,7 @@ Dispatcher disp = dispatcher(); Path path; - boolean validStartTime = active || disp.startTime != null; + boolean validStartTime = recording != null || disp.startTime != null; if (validStartTime) { path = repositoryFiles.firstPath(disp.startNanos); } else { @@ -139,8 +140,17 @@ return; } } + if (isLastChunk()) { + // Recording was stopped/closed externally, and no more data to process. + return; + } + if (repositoryFiles.hasFixedPath() && currentParser.isFinalChunk()) { + // JVM process exited/crashed, or repository migrated to an unknown location + return; + } if (isClosed()) { + // Stream was closed return; } long durationNanos = currentParser.getChunkDuration(); @@ -162,6 +172,13 @@ } } + private boolean isLastChunk() { + if (recording == null) { + return false; + } + return recording.getFinalChunkStartNanos() >= currentParser.getStartNanos(); + } + private boolean processOrdered(Dispatcher c, boolean awaitNewEvents) throws IOException { if (sortedCache == null) { sortedCache = new RecordedEvent[100_000]; @@ -206,4 +223,5 @@ } } } + } diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java Fri Nov 22 17:20:43 2019 +0100 @@ -50,7 +50,7 @@ private RecordedEvent[] cacheSorted; public EventFileStream(AccessControlContext acc, Path path) throws IOException { - super(acc, false); + super(acc, null); Objects.requireNonNull(path); this.input = new RecordingInput(path.toFile(), FileAccess.UNPRIVILIGED); } diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java Fri Nov 22 17:20:43 2019 +0100 @@ -227,4 +227,8 @@ waitObject.notify(); } } + + public boolean hasFixedPath() { + return repository != null; + } } diff -r 80e1201f6c9a -r a0f39cc47387 src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java Fri Nov 22 09:06:35 2019 -0500 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java Fri Nov 22 17:20:43 2019 +0100 @@ -32,6 +32,7 @@ import jdk.jfr.internal.LogTag; import jdk.jfr.internal.Logger; import jdk.jfr.internal.Options; +import jdk.jfr.internal.PlatformRecorder; import jdk.jfr.internal.PrivateAccess; import jdk.jfr.internal.Repository; import jdk.jfr.internal.SecuritySupport.SafePath; @@ -89,11 +90,12 @@ if (repositoryPath != null) { try { SafePath s = new SafePath(repositoryPath); - Repository.getRepository().setBasePath(s); + if (FlightRecorder.isInitialized()) { + PrivateAccess.getInstance().getPlatformRecorder().migrate(s); + } else { + 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 -r 80e1201f6c9a -r a0f39cc47387 test/jdk/jdk/jfr/api/consumer/recordingstream/TestStoppedRecording.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStoppedRecording.java Fri Nov 22 17:20:43 2019 +0100 @@ -0,0 +1,63 @@ +/* + * 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.FlightRecorder; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests that a RecordingStream is closed if the underlying Recording + * is stopped. + * @key jfr + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestStoppedRecording + */ +public class TestStoppedRecording { + + private static final class StopEvent extends Event { + } + + public static void main(String... args) throws Exception { + CountDownLatch latch = new CountDownLatch(1); + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(e -> { + FlightRecorder.getFlightRecorder().getRecordings().get(0).stop(); + }); + rs.onClose(() -> { + latch.countDown(); + }); + rs.startAsync(); + StopEvent stop = new StopEvent(); + stop.commit(); + latch.await(); + } + } +} \ No newline at end of file diff -r 80e1201f6c9a -r a0f39cc47387 test/jdk/jdk/jfr/api/consumer/streaming/TestInProcessMigration.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestInProcessMigration.java Fri Nov 22 17:20:43 2019 +0100 @@ -0,0 +1,108 @@ +/* + * 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 an in-process repository + * that is being moved. + * @key jfr + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @run main/othervm jdk.jfr.api.consumer.streaming.TestInProcessMigration + */ +public class TestInProcessMigration { + 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 -r 80e1201f6c9a -r a0f39cc47387 test/jdk/jdk/jfr/api/consumer/streaming/TestJVMCrash.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestJVMCrash.java Fri Nov 22 17:20:43 2019 +0100 @@ -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.streaming; + +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.jfr.consumer.EventStream; + +/** + * @test + * @summary Test that a stream ends/closes when an application crashes. + * @key jfr + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @modules jdk.jfr jdk.attach java.base/jdk.internal.misc + * + * @run main/othervm jdk.jfr.api.consumer.streaming.TestJVMCrash + */ +public class TestJVMCrash { + + public static void main(String... args) throws Exception { + int id = 1; + while (true) { + TestProcess process = new TestProcess("crash-application-" + id++); + AtomicInteger eventCounter = new AtomicInteger(); + try (EventStream es = EventStream.openRepository(process.getRepository())) { + // Start from first event in repository + es.setStartTime(Instant.EPOCH); + es.onEvent(e -> { + if (eventCounter.incrementAndGet() == TestProcess.NUMBER_OF_EVENTS) { + process.crash(); + } + }); + es.startAsync(); + // If crash corrupts chunk in repository, retry in 30 seconds + es.awaitTermination(Duration.ofSeconds(30)); + if (eventCounter.get() == TestProcess.NUMBER_OF_EVENTS) { + return; + } + System.out.println("Incorrect event count. Retrying..."); + } + } + } +} diff -r 80e1201f6c9a -r a0f39cc47387 test/jdk/jdk/jfr/api/consumer/streaming/TestJVMExit.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestJVMExit.java Fri Nov 22 17:20:43 2019 +0100 @@ -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.streaming; + +import java.time.Instant; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.jfr.consumer.EventStream; + +/** + * @test + * @summary Test that a stream ends/closes when an application exists. + * @key jfr + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @modules jdk.jfr jdk.attach java.base/jdk.internal.misc + * + * @run main/othervm jdk.jfr.api.consumer.streaming.TestJVMExit + */ +public class TestJVMExit { + + public static void main(String... args) throws Exception { + TestProcess process = new TestProcess("exit-application"); + AtomicInteger eventCounter = new AtomicInteger(); + try (EventStream es = EventStream.openRepository(process.getRepository())) { + // Start from first event in repository + es.setStartTime(Instant.EPOCH); + es.onEvent(e -> { + if (eventCounter.incrementAndGet() == TestProcess.NUMBER_OF_EVENTS) { + process.exit(); + } + }); + es.start(); + } + } +} diff -r 80e1201f6c9a -r a0f39cc47387 test/jdk/jdk/jfr/api/consumer/streaming/TestOutOfProcessMigration.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestOutOfProcessMigration.java Fri Nov 22 17:20:43 2019 +0100 @@ -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.streaming; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.jfr.consumer.EventStream; +import jdk.test.lib.dcmd.CommandExecutor; +import jdk.test.lib.dcmd.PidJcmdExecutor; +import jdk.test.lib.process.OutputAnalyzer; + +/** + * @test + * @summary Verifies that a out-of-process stream is closed when the repository + * is changed. + * @key jfr + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @modules jdk.jfr jdk.attach java.base/jdk.internal.misc + * @run main/othervm jdk.jfr.api.consumer.streaming.TestOutOfProcessMigration + */ +public class TestOutOfProcessMigration { + public static void main(String... args) throws Exception { + Path newRepo = Paths.get("new-repository").toAbsolutePath(); + + TestProcess process = new TestProcess("application"); + AtomicInteger eventCounter = new AtomicInteger(); + try (EventStream es = EventStream.openRepository(process.getRepository())) { + // Start from first event in repository + es.setStartTime(Instant.EPOCH); + es.onEvent(e -> { + if (eventCounter.incrementAndGet() == TestProcess.NUMBER_OF_EVENTS) { + System.out.println("Changing repository to " + newRepo + " ..."); + CommandExecutor executor = new PidJcmdExecutor(String.valueOf(process.pid())); + // This should close stream + OutputAnalyzer oa = executor.execute("JFR.configure repositorypath=" + newRepo); + System.out.println(oa); + } + }); + es.start(); + } + } +} diff -r 80e1201f6c9a -r a0f39cc47387 test/jdk/jdk/jfr/api/consumer/streaming/TestProcess.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestProcess.java Fri Nov 22 17:20:43 2019 +0100 @@ -0,0 +1,138 @@ +/* + * 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.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; + +import jdk.internal.misc.Unsafe; +import jdk.jfr.Event; +import jdk.test.lib.process.ProcessTools; + +import com.sun.tools.attach.VirtualMachine; + +/** + * Class that emits a NUMBER_OF_EVENTS and then awaits crash or exit + * + * Requires jdk.attach module. + * + */ +public final class TestProcess { + + private static class TestEvent extends Event { + } + + public final static int NUMBER_OF_EVENTS = 10; + + private final Process process; + private final Path path; + + public TestProcess(String name) throws IOException { + this.path = Paths.get("action-" + System.currentTimeMillis()).toAbsolutePath(); + String[] args = { + "--add-exports", + "java.base/jdk.internal.misc=ALL-UNNAMED", + "-XX:StartFlightRecording:settings=none", + TestProcess.class.getName(), path.toString() + }; + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(false, args); + process = ProcessTools.startProcess(name, pb); + } + + public static void main(String... args) throws Exception { + for (int i = 0; i < NUMBER_OF_EVENTS; i++) { + TestEvent e = new TestEvent(); + e.commit(); + } + + Path path = Paths.get(args[0]); + while (true) { + try { + String action = Files.readString(path); + if ("crash".equals(action)) { + System.out.println("About to crash..."); + Unsafe.getUnsafe().putInt(0L, 0); + } + if ("exit".equals(action)) { + System.out.println("About to exit..."); + System.exit(0); + } + } catch (Exception ioe) { + // Ignore + } + takeNap(); + } + } + + public Path getRepository() { + while (true) { + try { + VirtualMachine vm = VirtualMachine.attach(String.valueOf(process.pid())); + Properties p = vm.getSystemProperties(); + vm.detach(); + String repo = (String) p.get("jdk.jfr.repository"); + if (repo != null) { + return Paths.get(repo); + } + } catch (Exception e) { + System.out.println("Attach failed: " + e.getMessage()); + System.out.println("Retrying..."); + } + takeNap(); + } + } + + private static void takeNap() { + try { + Thread.sleep(10); + } catch (InterruptedException ie) { + // ignore + } + } + + public void crash() { + try { + Files.writeString(path, "crash"); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + + public void exit() { + try { + Files.writeString(path, "exit"); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + + public long pid() { + return process.pid(); + } +} diff -r 80e1201f6c9a -r a0f39cc47387 test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryMigration.java --- a/test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryMigration.java Fri Nov 22 09:06:35 2019 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -/* - * 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 - * @requires vm.hasJFR - * @library /test/lib /test/jdk - * @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); - } - } - } - } - -}