# HG changeset patch # User mgronlun # Date 1558108994 -7200 # Node ID 53dccc90a5be3a890cb2ecd505d37e9ac80f9297 # Parent 5d043a159d5c05964982d037e31ca797216e2f54 Preview-addendum diff -r 5d043a159d5c -r 53dccc90a5be src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,192 @@ +/* + * 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 + * 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. + * + */ + +#include "precompiled.hpp" +#include "jfr/dcmd/jfrDcmds.hpp" +#include "jfr/recorder/jfrRecorder.hpp" +#include "jfr/recorder/repository/jfrChunk.hpp" +#include "jfr/recorder/repository/jfrChunkWriter.hpp" +#include "jfr/utilities/jfrTimeConverter.hpp" +#include "logging/log.hpp" +#include "runtime/os.inline.hpp" +#include "runtime/thread.inline.hpp" + +static const u1 GUARD = 0xff; + +static jlong nanos_now() { + return os::javaTimeMillis() * JfrTimeConverter::NANOS_PER_MILLISEC; +} + +static jlong ticks_now() { + return JfrTicks::now(); +} + +JfrChunk::JfrChunk() : + _path(NULL), + _start_ticks(0), + _previous_start_ticks(invalid_time), + _start_nanos(0), + _previous_start_nanos(invalid_time), + _last_update_nanos(0), + _last_checkpoint_offset(0), + _last_metadata_offset(0), + _generation(1) {} + +JfrChunk::~JfrChunk() { + reset(); +} + +void JfrChunk::reset() { + if (_path != NULL) { + JfrCHeapObj::free(_path, strlen(_path) + 1); + _path = NULL; + } + _last_checkpoint_offset = _last_metadata_offset = 0; + _generation = 1; +} + +void JfrChunk::set_last_checkpoint_offset(int64_t offset) { + _last_checkpoint_offset = offset; +} + +int64_t JfrChunk::last_checkpoint_offset() const { + return _last_checkpoint_offset; +} + +int64_t JfrChunk::start_ticks() const { + assert(_start_ticks != 0, "invariant"); + return _start_ticks; +} + +int64_t JfrChunk::start_nanos() const { + assert(_start_nanos != 0, "invariant"); + return _start_nanos; +} + +int64_t JfrChunk::previous_start_ticks() const { + assert(_previous_start_ticks != invalid_time, "invariant"); + return _previous_start_ticks; +} + +int64_t JfrChunk::previous_start_nanos() const { + assert(_previous_start_nanos != invalid_time, "invariant"); + return _previous_start_nanos; +} + +void JfrChunk::update_start_ticks() { + _start_ticks = ticks_now(); +} + +void JfrChunk::update_start_nanos() { + _start_nanos = _last_update_nanos = nanos_now(); +} + +void JfrChunk::update() { + _last_update_nanos = nanos_now(); +} + +void JfrChunk::save_current_and_update_start_ticks() { + _previous_start_ticks = _start_ticks; + update_start_ticks(); +} + +void JfrChunk::save_current_and_update_start_nanos() { + _previous_start_nanos = _start_nanos; + update_start_nanos(); +} + +void JfrChunk::update_time_to_now() { + save_current_and_update_start_nanos(); + save_current_and_update_start_ticks(); +} + +int64_t JfrChunk::last_chunk_duration() const { + assert(_previous_start_nanos != invalid_time, "invariant"); + return _start_nanos - _previous_start_nanos; +} + +static char* copy_path(const char* path) { + assert(path != NULL, "invariant"); + const size_t path_len = strlen(path); + char* new_path = JfrCHeapObj::new_array(path_len + 1); + strncpy(new_path, path, path_len + 1); + return new_path; +} + +void JfrChunk::set_path(const char* path) { + if (_path != NULL) { + JfrCHeapObj::free(_path, strlen(_path) + 1); + _path = NULL; + } + if (path != NULL) { + _path = copy_path(path); + } +} + +const char* JfrChunk::path() const { + return _path; +} + +bool JfrChunk::is_started() const { + return _start_nanos != 0; +} + +bool JfrChunk::is_finished() const { + return 0 == _generation; +} + +bool JfrChunk::is_initial_flush() const { + return 0 == _last_metadata_offset; +} + +int64_t JfrChunk::duration() const { + assert(_last_update_nanos >= _start_nanos, "invariant"); + return _last_update_nanos - _start_nanos; +} + +int64_t JfrChunk::last_metadata_offset() const { + return _last_metadata_offset; +} + +void JfrChunk::set_last_metadata_offset(int64_t offset) { + if (0 == offset) { + return; + } + assert(offset > _last_metadata_offset, "invariant"); + _last_metadata_offset = offset; +} + +bool JfrChunk::has_metadata() const { + return 0 != _last_metadata_offset; +} + +u1 JfrChunk::generation() const { + assert(_generation > 0, "invariant"); + const u1 this_generation = _generation++; + if (GUARD == _generation) { + _generation = 1; + } + return this_generation; +} + diff -r 5d043a159d5c -r 53dccc90a5be src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,83 @@ +/* + * 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 + * 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. + * + */ + +#ifndef SHARE_VM_JFR_RECORDER_REPOSITORY_JFRRCHUNK_HPP +#define SHARE_VM_JFR_RECORDER_REPOSITORY_JFRRCHUNK_HPP + +#include "jni.h" +#include "jfr/utilities/jfrAllocation.hpp" +#include "jfr/utilities/jfrTypes.hpp" + +class JfrChunk : public JfrCHeapObj { + friend class JfrChunkWriter; + friend class JfrChunkHeadWriter; + private: + char* _path; + int64_t _start_ticks; + int64_t _previous_start_ticks; + int64_t _start_nanos; + int64_t _previous_start_nanos; + int64_t _last_update_nanos; + int64_t _last_checkpoint_offset; + int64_t _last_metadata_offset; + mutable u1 _generation; + + JfrChunk(); + ~JfrChunk(); + + void update_start_ticks(); + void update_start_nanos(); + void save_current_and_update_start_ticks(); + void save_current_and_update_start_nanos(); + + void reset(); + int64_t last_checkpoint_offset() const; + void set_last_checkpoint_offset(int64_t offset); + + int64_t last_metadata_offset() const; + void set_last_metadata_offset(int64_t offset); + bool has_metadata() const; + + int64_t start_ticks() const; + int64_t start_nanos() const; + + int64_t previous_start_ticks() const; + int64_t previous_start_nanos() const; + int64_t last_chunk_duration() const; + + void update_time_to_now(); + void set_path(const char* path); + const char* path() const; + + void update(); + + bool is_started() const; + bool is_finished() const; + bool is_initial_flush() const; + + int64_t duration() const; + u1 generation() const; +}; + +#endif // SHARE_VM_JFR_RECORDER_REPOSITORY_JFRRCHUNK_HPP diff -r 5d043a159d5c -r 53dccc90a5be src/hotspot/share/jfr/utilities/jfrThreadIterator.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/jfr/utilities/jfrThreadIterator.cpp Fri May 17 18:03:14 2019 +0200 @@ -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. + * + * 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. + * + */ + +#include "precompiled.hpp" +#include "jfr/support/jfrThreadLocal.hpp" +#include "jfr/utilities/jfrThreadIterator.hpp" +#include "runtime/thread.inline.hpp" + +static bool thread_inclusion_predicate(Thread* t) { + assert(t != NULL, "invariant"); + return !(t->jfr_thread_local()->is_excluded() || t->jfr_thread_local()->is_dead()); +} + +static bool java_thread_inclusion_predicate(JavaThread* jt) { + assert(jt != NULL, "invariant"); + return thread_inclusion_predicate(jt) && jt->thread_state() != _thread_new; +} + +static JavaThread* next_java_thread(JavaThreadIteratorWithHandle& iter) { + JavaThread* next = iter.next(); + while (next != NULL && !java_thread_inclusion_predicate(next)) { + next = iter.next(); + } + return next; +} + +static NonJavaThread* next_non_java_thread(NonJavaThread::Iterator& iter) { + NonJavaThread* next = NULL; + while (!iter.end()) { + next = iter.current(); + iter.step(); + assert(next != NULL, "invariant"); + if (!thread_inclusion_predicate(next)) { + continue; + } + } + return next; +} + +JfrJavaThreadIteratorAdapter::JfrJavaThreadIteratorAdapter() : _iter(), _next(next_java_thread(_iter)) {} + +JavaThread* JfrJavaThreadIteratorAdapter::next() { + assert(has_next(), "invariant"); + Type* const temp = _next; + assert(java_thread_inclusion_predicate(temp), "invariant"); + _next = next_java_thread(_iter); + assert(temp != _next, "invariant"); + return temp; +} + +JfrNonJavaThreadIteratorAdapter::JfrNonJavaThreadIteratorAdapter() : _iter(), _next(next_non_java_thread(_iter)) {} + +bool JfrNonJavaThreadIteratorAdapter::has_next() const { + return _next != NULL; +} + +NonJavaThread* JfrNonJavaThreadIteratorAdapter::next() { + assert(has_next(), "invariant"); + Type* const temp = _next; + assert(thread_inclusion_predicate(temp), "invariant"); + _next = next_non_java_thread(_iter); + assert(temp != _next, "invariant"); + return temp; +} + +// explicit instantiations +template class JfrThreadIterator; +template class JfrThreadIterator; diff -r 5d043a159d5c -r 53dccc90a5be src/hotspot/share/jfr/utilities/jfrThreadIterator.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/jfr/utilities/jfrThreadIterator.hpp Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,74 @@ +/* + * 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. + * + */ + +#ifndef SHARE_VM_JFR_UTILITIES_JFRTHREADITERATOR_HPP +#define SHARE_VM_JFR_UTILITIES_JFRTHREADITERATOR_HPP + +#include "memory/allocation.hpp" +#include "runtime/thread.hpp" +#include "runtime/threadSMR.hpp" + +template +class JfrThreadIterator : public AP { + private: + Adapter _adapter; + public: + JfrThreadIterator() : _adapter() {} + typename Adapter::Type* next() { + assert(has_next(), "invariant"); + return _adapter.next(); + } + bool has_next() const { + return _adapter.has_next(); + } +}; + +class JfrJavaThreadIteratorAdapter { + private: + JavaThreadIteratorWithHandle _iter; + JavaThread* _next; + public: + typedef JavaThread Type; + JfrJavaThreadIteratorAdapter(); + bool has_next() const { + return _next != NULL; + } + Type* next(); +}; + +class JfrNonJavaThreadIteratorAdapter { + private: + NonJavaThread::Iterator _iter; + NonJavaThread* _next; + public: + typedef NonJavaThread Type; + JfrNonJavaThreadIteratorAdapter(); + bool has_next() const; + Type* next(); +}; + +typedef JfrThreadIterator JfrJavaThreadIterator; +typedef JfrThreadIterator JfrNonJavaThreadIterator; + +#endif // SHARE_VM_JFR_UTILITIES_JFRTHREADITERATOR_HPP diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/ConstantLookup.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/ConstantLookup.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,42 @@ +package jdk.jfr.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); + // previous = new ConstantMap(); // disable cache + } + + 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 -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/EventDirectoryStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventDirectoryStream.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,172 @@ +/* + * 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.time.Duration; +import java.time.Instant; +import java.util.Objects; +import java.util.function.Consumer; + +import jdk.jfr.EventType; + +final class EventDirectoryStream implements EventStream { + + public final static class EventConsumer { + final private String eventName; + final Consumer action; + + EventConsumer(String eventName, Consumer eventConsumer) { + this.eventName = eventName; + this.action = eventConsumer; + } + + public void offer(RecordedEvent event) { + action.accept(event); + } + + public boolean accepts(EventType eventType) { + return (eventName == null || eventType.getName().equals(eventName)); + } + } + + private final EventRunner eventRunner; + private Thread thread; + private boolean started; + + public EventDirectoryStream(AccessControlContext acc) throws IOException { + eventRunner = new EventRunner(acc); + } + + public void close() { + synchronized (eventRunner) { + eventRunner.close(); + } + } + + public synchronized void onFlush(Runnable action) { + Objects.requireNonNull(action); + synchronized (eventRunner) { + this.eventRunner.addFlush(action); + } + } + + void start(long startNanos) { + synchronized (eventRunner) { + if (started) { + throw new IllegalStateException("Event stream can only be started once"); + } + started = true; + eventRunner.setStartNanos(startNanos); + } + eventRunner.run(); + } + + @Override + public void start() { + start(Instant.now().toEpochMilli() * 1000*1000L); + } + + @Override + public void startAsync() { + startAsync(Instant.now().toEpochMilli() * 1000*1000L); + } + + void startAsync(long startNanos) { + synchronized (eventRunner) { + eventRunner.setStartNanos(startNanos); + thread = new Thread(eventRunner); + thread.setDaemon(true); + thread.start(); + } + } + + public void addEventConsumer(EventConsumer action) { + Objects.requireNonNull(action); + synchronized (eventRunner) { + eventRunner.add(action); + } + } + + + + @Override + public void onEvent(Consumer action) { + Objects.requireNonNull(action); + synchronized (eventRunner) { + eventRunner.add(new EventConsumer(null, action)); + } + } + + @Override + public void onEvent(String eventName, Consumer action) { + Objects.requireNonNull(eventName); + Objects.requireNonNull(action); + synchronized (eventRunner) { + eventRunner.add(new EventConsumer(eventName, action)); + } + } + + @Override + public void onClose(Runnable action) { + Objects.requireNonNull(action); + synchronized (eventRunner) { + eventRunner.addCloseAction(action); + } + } + + @Override + public boolean remove(Object action) { + Objects.requireNonNull(action); + synchronized (eventRunner) { + return eventRunner.remove(action); + } + } + + @Override + public void awaitTermination(Duration timeout) { + Objects.requireNonNull(timeout); + Thread t = null; + synchronized (eventRunner) { + t = thread; + } + if (t != null && t != Thread.currentThread()) { + try { + t.join(timeout.toMillis()); + } catch (InterruptedException e) { + // ignore + } + } + } + + @Override + public void awaitTermination() { + awaitTermination(Duration.ofMillis(0)); + } + + +} diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFileStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFileStream.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,109 @@ +/* + * 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.nio.file.Path; +import java.time.Duration; +import java.util.Objects; +import java.util.function.Consumer; + +/** + * Implementation of an event stream that operates against a recording file. + * + */ +final class EventFileStream implements EventStream { + + public EventFileStream(Path path) { + Objects.requireNonNull(path); + } + + @Override + public void onEvent(Consumer action) { + Objects.requireNonNull(action); + notImplemented(); + } + + public void onEvent(EventFilter filter, Consumer action) { + Objects.requireNonNull(filter); + Objects.requireNonNull(action); + notImplemented(); + } + + @Override + public void onEvent(String eventName, Consumer action) { + Objects.requireNonNull(eventName); + Objects.requireNonNull(action); + notImplemented(); + } + + @Override + public void onFlush(Runnable action) { + Objects.requireNonNull(action); + notImplemented(); + } + + @Override + public void onClose(Runnable action) { + Objects.requireNonNull(action); + notImplemented(); + } + + @Override + public void close() { + notImplemented(); + } + + @Override + public boolean remove(Object action) { + Objects.requireNonNull(action); + notImplemented(); + return false; + } + + @Override + public void start() { + notImplemented(); + } + + @Override + public void startAsync() { + notImplemented(); + } + + @Override + public void awaitTermination(Duration timeout) { + Objects.requireNonNull(timeout); + } + + @Override + public void awaitTermination() { + notImplemented(); + } + + private static void notImplemented() { + throw new UnsupportedOperationException("Streaming for files not yet implemenetd"); + } +} diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFilter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFilter.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,92 @@ +/* + * 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.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; + +final class EventFilter { + private final String[] eventNames; + private final Duration threshold; + private final String[] fields; + + private EventFilter(String[] eventNames, Duration threshold, String[] fields) { + this.eventNames = eventNames; + this.threshold = threshold; + this.fields = fields; + } + + public static EventFilter eventTypes(String... eventNames) { + return new EventFilter(eventNames.clone(), null, new String[0]); + } + + public EventFilter threshold(Duration threshold) { + return new EventFilter(eventNames, threshold, fields); + } + + public EventFilter fields(String... fieldNames) { + return new EventFilter(eventNames, threshold, fieldNames); + } + + public EventFilter start(Instant instant) { + return this; + } + + public EventFilter end(Instant instant) { + return this; + } + + public EventFilter threads(Thread... t) { + return this; + } + + public EventFilter threadIds(long... threadId) { + return this; + } + + public EventFilter threadNames(String... threadName) { + return this; + } + + public EventFilter threadFilters(String... filter) { + return this; + } + + List getFields() { + return Arrays.asList(fields); + } + + List getEventNames() { + return Arrays.asList(fields); + } + + Duration getThreshold() { + return threshold; + } + +} diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/EventRunner.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventRunner.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,266 @@ +/* + * 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.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import jdk.jfr.consumer.EventDirectoryStream.EventConsumer; +import jdk.jfr.internal.JVM; +import jdk.jfr.internal.LogLevel; +import jdk.jfr.internal.LogTag; +import jdk.jfr.internal.Logger; + +class EventRunner implements Runnable { + private final static VarHandle closedHandle; + private final static VarHandle consumersHandle; + private final static VarHandle dispatcherHandle; + private final static VarHandle flushActionsHandle; + private final static VarHandle closeActionsHandle; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + closedHandle = l.findVarHandle(EventRunner.class, "closed", boolean.class); + consumersHandle = l.findVarHandle(EventRunner.class, "consumers", EventConsumer[].class); + dispatcherHandle = l.findVarHandle(EventRunner.class, "dispatcher", LongMap.class); + flushActionsHandle = l.findVarHandle(EventRunner.class, "flushActions", Runnable[].class); + closeActionsHandle = l.findVarHandle(EventRunner.class, "closeActions", Runnable[].class); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + } + // set by VarHandle + private boolean closed; + // set by VarHandle + private EventConsumer[] consumers = new EventConsumer[0]; + // set by VarHandle + private LongMap dispatcher = new LongMap<>(); + // set by VarHandle + private Runnable[] flushActions = new Runnable[0]; + // set by VarHandle + private Runnable[] closeActions = new Runnable[0]; + + private final static JVM jvm = JVM.getJVM(); + private final static EventConsumer[] NO_CONSUMERS = new EventConsumer[0]; + private final AccessControlContext accessControlContext; + private EventSetLocation location; + private EventSet eventSet; + private InternalEventFilter eventFilter = InternalEventFilter.ACCEPT_ALL; + private int eventSetIndex; + private int eventArrayIndex; + private RecordedEvent[] currentEventArray = new RecordedEvent[0]; + private volatile long startNanos; + + public EventRunner(AccessControlContext acc) throws IOException { + this.accessControlContext = acc; + } + + public void run() { + doPriviliged(() -> execute()); + } + + void doPriviliged(Runnable r) { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + r.run(); + return null; + } + }, accessControlContext); + } + + private void execute() { + jvm.exclude(Thread.currentThread()); + try { + process(); + } catch (Throwable e) { + e.printStackTrace(); + Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "Unexpectedexception iterating consumer."); + } finally { + Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "Execution of stream ended."); + } + } + + private void process() throws Exception, IOException { + this.location = EventSetLocation.current(); + this.eventSet = location.acquire(startNanos, null); // use timestamp from + if (eventSet == null) { + return; + } + while (!closed) { + processSegment(); + Runnable[] fas = this.flushActions; + for (int i = 0; i < fas.length; i++) { + fas[i].run(); + } + do { + if (closed) { + return; + } + currentEventArray = eventSet.readEvents(eventSetIndex); + if (currentEventArray == EventSet.END_OF_SET) { + eventSet = eventSet.next(eventFilter); + if (eventSet == null || closed) { + return; + } + eventSetIndex = 0; + continue; + } + if (currentEventArray == null) { + return; // no more events + } + eventSetIndex++; + } while (currentEventArray.length == 0); + eventArrayIndex = 0; + } + } + + private void processSegment() { + while (eventArrayIndex < currentEventArray.length) { + RecordedEvent e = currentEventArray[eventArrayIndex++]; + if (e == null) { + return; + } + EventConsumer[] consumerDispatch = dispatcher.get(e.getEventType().getId()); + if (consumerDispatch == null) { + consumerDispatch = NO_CONSUMERS; + for (EventConsumer ec : consumers.clone()) { + if (ec.accepts(e.getEventType())) { + consumerDispatch = merge(consumerDispatch, ec); + } + } + dispatcher.put(e.getEventType().getId(), consumerDispatch); + } + for (int i = 0; i < consumerDispatch.length; i++) { + consumerDispatch[i].offer(e); + } + } + } + + static EventConsumer[] merge(EventConsumer[] current, EventConsumer add) { + EventConsumer[] array = new EventConsumer[current.length + 1]; + System.arraycopy(current, 0, array, 0, current.length); + array[current.length] = add; + return array; + } + + public void add(EventConsumer e) { + consumersHandle.setVolatile(this, merge(consumers, e)); + dispatcherHandle.setVolatile(this, new LongMap<>()); // will reset + } + + private static Runnable[] removeAction(Runnable[] array, Object action) { + if (array.length == 0) { + return null; + } + boolean remove = false; + List list = new ArrayList<>(); + for (int i = 0; i < array.length; i++) { + if (array[i] != action) { + list.add(array[i]); + } else { + remove = true; + } + } + if (remove) { + return list.toArray(new Runnable[list.size()]); + } + return null; + } + + private static Runnable[] addAction(Runnable[] array, Runnable action) { + ArrayList a = new ArrayList<>(); + a.addAll(Arrays.asList(array)); + a.add(action); + return a.toArray(new Runnable[0]); + } + + public boolean remove(Object action) { + boolean remove = false; + Runnable[] updatedFlushActions = removeAction(flushActions, action); + if (updatedFlushActions != null) { + flushActionsHandle.setVolatile(this, updatedFlushActions); + remove = true; + } + Runnable[] updatedCloseActions = removeAction(closeActions, action); + if (updatedCloseActions != null) { + closeActionsHandle.setVolatile(this, updatedCloseActions); + remove = true; + } + + boolean removeConsumer = false; + List list = new ArrayList<>(); + for (int i = 0; i < consumers.length; i++) { + if (consumers[i].action != action) { + list.add(consumers[i]); + } else { + removeConsumer = true; + remove = true; + } + } + if (removeConsumer) { + EventConsumer[] array = list.toArray(new EventConsumer[list.size()]); + consumersHandle.setVolatile(this, array); + dispatcherHandle.setVolatile(this, new LongMap<>()); // will reset dispatch + } + return remove; + } + + public void addFlush(Runnable action) { + flushActionsHandle.setVolatile(this, addAction(flushActions, action)); + } + + public void close() { + closedHandle.setVolatile(this, true); + // TODO: Data races here, must fix + if (eventSet != null) { + eventSet.release(null); + } + if (location != null) { + location.release(); + } + + Runnable[] cas = this.closeActions; + for (int i = 0; i < cas.length; i++) { + cas[i].run(); + } + } + + public void addCloseAction(Runnable action) { + closeActionsHandle.setVolatile(this, addAction(closeActions, action)); + } + + public void setStartNanos(long startNanos) { + this.startNanos = startNanos; + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/EventSet.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventSet.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,232 @@ +/* + * 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.time.Instant; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; + +import jdk.jfr.internal.consumer.ChunkHeader; +import jdk.jfr.internal.consumer.RecordingInput; + +/** + * Cache that represents all discovered events in a chunk. + * + */ +final class EventSet { + + public static final RecordedEvent[] END_OF_SET = new RecordedEvent[0]; + private static final AtomicInteger idCounter = new AtomicInteger(-1); + + private volatile Object[][] segments = new Object[1000][]; + private volatile boolean closed; + private final long startTimeNanos; + private final EventSetLocation location; + private final Path path; + private final int id; + + // Guarded by lock + private boolean awaitNewEvents; + private RecordingInput input; + private ChunkParser chunkParser; + private int referenceCount; + private final ReentrantLock lock = new ReentrantLock(); + private final Set filters = new HashSet<>(); + private InternalEventFilter globalFilter = InternalEventFilter.ACCEPT_ALL; + private boolean dirtyFilter = true; + + public void release(InternalEventFilter eventFilter) { + try { + lock.lock(); + filters.remove(eventFilter); + updateGlobalFilter(); + referenceCount--; + if (referenceCount == 0) { + closed = true; + if (input != null) { + try { + input.close(); + } catch (IOException e) { + // TODO: Flie locked by other process? + } + chunkParser = null; + input = null; + } + } + } finally { + lock.unlock(); + } + } + + public EventSet(EventSetLocation location, EventSet previousEventSet, Path p) throws IOException { + this.location = location; + this.path = p; + this.startTimeNanos = readStartTime(p); + this.id = idCounter.incrementAndGet(); + } + + private long readStartTime(Path p) throws IOException { + try (RecordingInput in = new RecordingInput(p.toFile(), 100)) { + ChunkHeader c = new ChunkHeader(in); + return c.getStartNanos(); + } + } + + Path getPath() { + return path; + } + + // TODO: Use binary search, must use lock + public int findIndex(Instant timestamp) { + int index = 0; + for (int i = 0; i < segments.length; i++) { + RecordedEvent[] events = (RecordedEvent[]) segments[i]; + if (events == null || events.length == 0) { + return Math.max(index - 1, 0); + } + RecordedEvent e = events[0]; // May not be sorted. + if (timestamp.isAfter(e.getEndTime())) { + return Math.max(index - 1, 0); + } + } + return segments.length; + } + + public void addFilter(InternalEventFilter filter) { + try { + lock.lock(); + filters.add(filter); + updateGlobalFilter(); + } finally { + lock.unlock(); + } + } + + // held with lock + private void updateGlobalFilter() { + globalFilter = InternalEventFilter.merge(filters); + dirtyFilter = true; + } + + public RecordedEvent[] readEvents(int index) throws Exception { + while (!closed) { + + RecordedEvent[] events = (RecordedEvent[]) segments[index]; + if (events != null) { + return events; + } + if (lock.tryLock(250, TimeUnit.MILLISECONDS)) { + try { + addSegment(index); + } finally { + lock.unlock(); + } + } + } + return null; + } + + // held with lock + private void addSegment(int index) throws IOException { + if (chunkParser == null) { + chunkParser = new ChunkParser(new RecordingInput(path.toFile())); + } + if (dirtyFilter) { + chunkParser.setParserFilter(globalFilter); + } + if (segments[index] != null) { + return; + } + if (index == segments.length - 2) { + segments = Arrays.copyOf(segments, segments.length * 2); + } + RecordedEvent[] segment = new RecordedEvent[10]; + int i = 0; + while (true) { + RecordedEvent e = chunkParser.readStreamingEvent(awaitNewEvents); + if (e == null) { + // wait for new event with next call to readStreamingEvent() + awaitNewEvents = true; + break; + } + awaitNewEvents = false; + if (i == segment.length) { + segment = Arrays.copyOf(segment, segment.length * 2); + } + segment[i++] = e; + } + + // no events found + if (i == 0) { + if (chunkParser.isChunkFinished()) { + segments[index] = END_OF_SET; + return; + } + } + // at least 2 events, sort them + if (i > 1) { + Arrays.sort(segment, 0, i, (e1, e2) -> Long.compare(e1.endTime, e2.endTime)); + } + segments[index] = segment; + if (chunkParser.isChunkFinished()) { + segments[index + 1] = END_OF_SET; + } + } + + public long getStartTimeNanos() { + return startTimeNanos; + } + + public EventSet next(InternalEventFilter filter) throws IOException { + EventSet next = location.acquire(startTimeNanos + 1, this); + if (next == null) { + // closed + return null; + } + next.addFilter(filter); + release(filter); + return next; + } + + public void acquire() { + try { + lock.lock(); + referenceCount++; + } finally { + lock.unlock(); + } + } + + public String toString() { + return "Chunk:" + id + " (" + path + ")"; + } +} diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/EventSetLocation.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventSetLocation.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,175 @@ +/* + * 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.DirectoryStream; +import java.nio.file.Files; +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.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.consumer.ChunkHeader; + +/** + * This class corresponds to a disk repository. + *

+ * Main purpose is to act as a cache if multiple {@code EventStream} want to + * access the same repository. An {@code EventSetLocation} should be released + * when it is no longer being used. + * + */ +final class EventSetLocation { + private static Map locations = new HashMap<>(); + + private final SortedMap eventSets = new TreeMap<>(); + private final Map lastPaths = new HashMap<>(); + + final Path path; + private int count = 0; + private volatile boolean closed; + + private EventSetLocation(Path path) { + this.path = path; + } + + public static EventSetLocation get(Path absolutPath) { + synchronized (locations) { + EventSetLocation esl = locations.get(absolutPath); + if (esl == null) { + esl = new EventSetLocation(absolutPath); + locations.put(absolutPath, esl); + } + esl.count++; + return esl; + } + } + + public static EventSetLocation current() throws IOException { + Repository.getRepository().ensureRepository(); + return get(Repository.getRepository().getRepositoryPath().toPath()); + } + + public void release() { + synchronized (locations) { + count--; + if (count == 0) { + locations.remove(path); + closed = true; + } + } + } + + public synchronized EventSet acquire(long startTimeNanos, EventSet previousEventSet) { + synchronized (eventSets) { + while (!closed) { + SortedMap after = eventSets.tailMap(startTimeNanos); + if (!after.isEmpty()) { + EventSet es = after.get(after.firstKey()); + Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.TRACE, "Acquired " + startTimeNanos + ", got " + es); + es.acquire(); + return es; + } + try { + updateEventSets(previousEventSet); + } catch (IOException e) { + e.printStackTrace(); + // This can happen if a chunk is being removed + // between the file was discovered and an instance + // of an EventSet was constructed. Just ignore, + // and retry later. + } + try { + eventSets.wait(1000); + } catch (InterruptedException e) { + // ignore + } + } + } + return null; + } + + private void updateEventSets(EventSet previousEventSet) throws IOException { + List added = new ArrayList<>(); + Set current = new HashSet<>(); + try (DirectoryStream dirStream = Files.newDirectoryStream(path, "*.jfr")) { + for (Path p : dirStream) { + if (!lastPaths.containsKey(p)) { + 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 : lastPaths.keySet()) { + if (!current.contains(p)) { + removed.add(p); + } + } + + for (Path remove : removed) { + Long time = lastPaths.get(remove); + eventSets.remove(time); + lastPaths.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 = Files.size(p); + if (size >= ChunkHeader.HEADER_SIZE) { + EventSet es = new EventSet(this, previousEventSet, p); + long startTime = es.getStartTimeNanos(); + if (startTime == 0) { + String errorMsg = "Chunk header should always contain a valid start time"; + System.err.println(errorMsg); + throw new InternalError(errorMsg); + } + EventSet previous = eventSets.get(startTime); + if (previous != null) { + String errorMsg = "Found chunk " + p + " with the same start time " + startTime + " as previous chunk " + previous.getPath(); + System.err.println(errorMsg); + throw new InternalError(errorMsg); + } + eventSets.put(startTime, es); + lastPaths.put(p, startTime); + previousEventSet = es; + } + } + } +} diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,155 @@ +/* + * 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.time.Duration; +import java.util.function.Consumer; + +/** + * Represents a stream of event that actions can be performed up on. + */ +public interface EventStream extends AutoCloseable { + + /** + * Creates a stream starting from the next written event in a disk + * repository. + * + * @param directory location of the disk repository, not {@code null} + * @return an event stream, not {@code null} + */ + public static EventStream openRepository(Path directory) throws IOException { + throw new UnsupportedOperationException("Not yet implemented"); +// AccessControlContext acc = AccessController.getContext(); +// return new EventDirectoryStream(acc); + } + + /** + * Creates an event stream starting from the first event in a file. + * + * @param file location of the file, not {@code null} + * @return an event stream, not {@code null} + */ + public static EventStream openFile(Path file) throws IOException { + throw new UnsupportedOperationException("Not yet implemented"); +// return new EventFileStream(file); + } + + /** + * Performs an action on all events in the stream. + * + * @param action an action to be performed on each {@code RecordedEvent}, + * not {@code null} + */ + void onEvent(Consumer action); + + /** + * Performs an action on all events in the stream with a specified name. + * + * @param eventName the name of the event, not {@code null} + * @param action an action to be performed on each {@code RecordedEvent} + * that matches the event name, not {@code null} + */ + void onEvent(String eventName, Consumer action); + + /** + * Performs an action when the event stream has been flushed. + * + * @param action an action to be performed after stream has been flushed, + * not {@code null} + */ + void onFlush(Runnable action); + + /** + * Performs an action when the event stream is closed. + * + * @param action an action to be performed after the stream has been closed, + * not {@code null} + */ + void onClose(Runnable action); + + /** + * Releases all resources associated with this event stream. + */ + void close(); + + /** + * Removes an action from the stream. + *

+ * If the action has been added multiple times, all instance of it will be + * removed. + * + * @param action the action to remove, not {@code null} + * @return {@code true} if the action was removed, {@code false} otherwise + * + * @see #onClose(Runnable) + * @see #onFlush(Runnable) + * @see #onEvent(Consumer) + * @see #onEvent(String, Consumer) + */ + boolean remove(Object action); + + /** + * Starts processing events in the stream. + *

+ * All actions will performed on this stream will happen in the current + * thread. + * + * @throws IllegalStateException if the stream is already started or if it + * has been closed + */ + void start(); + + /** + * Starts processing events in the stream asynchronously. + *

+ * All actions on this stream will be performed in a separate thread. + * + * @throws IllegalStateException if the stream is already started or if it + * has been closed + */ + void startAsync(); + + /** + * Blocks the current thread until the stream is finished, closed, or it + * times out. + * + * @param timeout the maximum time to wait, not {@code null} + * + * @see #start() + * @see #startAsync() + */ + void awaitTermination(Duration timeout); + + /** + * Blocks the current thread until the stream is finished or closed. + * + * @see #start() + * @see #startAsync() + */ + void awaitTermination(); +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/InternalEventFilter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/InternalEventFilter.java Fri May 17 18:03:14 2019 +0200 @@ -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.consumer; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public final class InternalEventFilter { + static final InternalEventFilter ACCEPT_ALL = new InternalEventFilter(); + private final Map thresholds = new HashMap<>(); + private boolean acceptAll; + + public static InternalEventFilter merge(Collection filters) { + for (InternalEventFilter ef : filters) { + if (ef.getAcceptAll()) { + return ACCEPT_ALL; + } + } + if (filters.size() == 1) { + return filters.iterator().next(); + } + + Set eventNames = new HashSet<>(); + for (InternalEventFilter ef : filters) { + eventNames.addAll(ef.thresholds.keySet()); + } + InternalEventFilter result = new InternalEventFilter(); + for (String eventName : eventNames) { + for (InternalEventFilter ef : filters) { + Long l = ef.thresholds.get(eventName); + if (l != null) { + result.setThreshold(eventName, l.longValue()); + } + } + } + return result; + } + + private boolean getAcceptAll() { + return acceptAll; + } + + public void setAcceptAll() { + acceptAll = true; + } + + 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 0; + } + Long l = thresholds.get(eventName); + if (l != null) { + return l; + } + return -1; + } + public String toString() { + if (acceptAll) { + return "ACCEPT ALL"; + } + StringBuilder sb = new StringBuilder(); + for (String key : thresholds.keySet().toArray(new String[0])) { + Long value = thresholds.get(key); + sb.append(key); + sb.append(" = "); + sb.append(value.longValue() / 1_000_000); + sb.append(" ms"); + } + return sb.toString(); + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/Parser.java --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/Parser.java Fri May 17 16:02:27 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +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 jdk.jfr.internal.consumer.RecordingInput; - -/** - * Base class for parsing data from a {@link RecordingInput}. - */ -abstract class Parser { - /** - * Parses data from a {@link RecordingInput} and return an object. - * - * @param input input to read from - * @return an object - * @throws IOException if operation couldn't be completed due to I/O - * problems - */ - abstract Object parse(RecordingInput input) throws IOException; -} diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,299 @@ +/* + * 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.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.Utils; + +/** + * An event stream produces events from a file, directory or a running JVM (Java + * Virtual Machine). + */ +public class RecordingStream implements AutoCloseable, EventStream { + + private final Recording recording; + private final EventDirectoryStream stream; + + /** + * Creates an event stream for this JVM (Java Virtual Machine). + *

+ * The following example shows how to create a recording stream that prints + * CPU usage and information about garbage collections. + * + *

+     * 
+     * try (RecordingStream  r = new RecordingStream()) {
+     *   r.enable("jdk.GarbageCollection");
+     *   r.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
+     *   r.onEvent(System.out::println);
+     *   r.start();
+     * }
+     * 
+     * 
+ * + * @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.stream = new EventDirectoryStream(acc); + } catch (IOException ioe) { + 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. + * + *

+     * 
+     * Configuration c = Configuration.getConfiguration("default");
+     * try (RecordingStream  r = new RecordingStream(c)) {
+     *   r.onEvent(System.out::println);
+     *   r.start();
+     * }
+     * 
+     * 
+ * + * @param configuration configuration that contains the settings to be 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); + } + + /** + * 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 is + * 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 if the stream can't + * keep up. + *

+ * 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 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 in the disk repository if the stream + * can't keep up. + *

+ * 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. + * + * @param maxSize the amount of data to retain, {@code 0} if infinite + * + * @throws IllegalArgumentException if maxSize is negative + * + * @throws IllegalStateException if the recording is in {@code CLOSED} state + */ + public void setMaxSize(long maxSize) { + recording.setMaxSize(maxSize); + } + + @Override + public void onEvent(String eventName, Consumer action) { + stream.onEvent(eventName, action); + } + + @Override + public void onEvent(Consumer action) { + stream.onEvent(action); + } + + @Override + public void onFlush(Runnable action) { + stream.onFlush(action); + } + + @Override + public void onClose(Runnable action) { + stream.onClose(action); + } + + @Override + public void close() { + recording.close(); + stream.close(); + } + + @Override + public boolean remove(Object action) { + return stream.remove(action); + } + + @Override + public void start() { + PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording); + long startNanos = pr.start(); + stream.start(startNanos); + } + + @Override + public void startAsync() { + PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording); + long startNanos = pr.start(); + stream.startAsync(startNanos); + } + + @Override + public void awaitTermination(Duration timeout) { + stream.awaitTermination(timeout); + } + + /** + * Determines how often events are made available for streaming. + * + * @param interval the interval at which events are made available to the + * stream + * + * @throws IllegalArgumentException if interval is negative + * + * @throws IllegalStateException if the stream is closed + */ + public void setInterval(Duration duration) { + recording.setFlushInterval(duration); + } + + @Override + public void awaitTermination() { + stream.awaitTermination(); + } +} diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/StringParser.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/StringParser.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,203 @@ +/* + * 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.charset.Charset; + +import jdk.jfr.internal.consumer.Parser; +import jdk.jfr.internal.consumer.RecordingInput; +import jdk.jfr.internal.consumer.StringEncoding; + +final class StringParser extends Parser { + private final static Charset UTF8 = Charset.forName("UTF-8"); + private final static Charset LATIN1 = Charset.forName("ISO-8859-1"); + + + 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]; + } + } + } + + 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 == StringEncoding.STRING_ENCODING_CONSTANT_POOL) { + long key = input.readLong(); + if (event) { + return stringLookup.getCurrentResolved(key); + } else { + return stringLookup.getCurrent(key); + } + } + if (encoding == StringEncoding.STRING_ENCODING_NULL) { + return null; + } + if (encoding == StringEncoding.STRING_ENCODING_EMPTY_STRING) { + return ""; + } + if (encoding == StringEncoding.STRING_ENCODING_CHAR_ARRAY) { + return charArrayParser.parse(input); + } + if (encoding == StringEncoding.STRING_ENCODING_UTF8_BYTE_ARRAY) { + return utf8parser.parse(input); + } + if (encoding == StringEncoding.STRING_ENCODING_LATIN1_BYTE_ARRAY) { + 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 == StringEncoding.STRING_ENCODING_CONSTANT_POOL) { + input.readLong(); + return; + } + if (encoding == StringEncoding.STRING_ENCODING_EMPTY_STRING) { + return; + } + if (encoding == StringEncoding.STRING_ENCODING_NULL) { + return; + } + if (encoding == StringEncoding.STRING_ENCODING_CHAR_ARRAY) { + charArrayParser.skip(input); + return; + } + if (encoding == StringEncoding.STRING_ENCODING_UTF8_BYTE_ARRAY) { + utf8parser.skip(input); + return; + } + if (encoding == StringEncoding.STRING_ENCODING_LATIN1_BYTE_ARRAY) { + latin1parser.skip(input); + return; + } + throw new IOException("Unknown string encoding " + encoding); + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/consumer/UseCasesStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/UseCasesStream.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,193 @@ +/* + * 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.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.time.Duration; +import java.util.ArrayDeque; + +import jdk.jfr.Configuration; +import jdk.jfr.EventType; +import jdk.jfr.ValueDescriptor; + +class UseCasesStream { + + // + // Use case: Out-of-the-Box Experience + // + // - Simple things should be simple + // - Pique interest, i.e. a one-liner on Stack Overflow + // - Few lines of code as possible + // - Should be easier than alternative technologies, like JMX and JVM TI + // + // - Non-goals: Corner-cases, advanced configuration, releasing resources + // + public static void outOfTheBox() throws Exception { + try (RecordingStream rs = new RecordingStream()) { + rs.enable("jdk.ExceptionThrown"); + rs.onEvent(e -> System.out.println(e.getString("message"))); + rs.start(); + } + + // EventStream.start("jdk.JavaMonitorEnter", "threshold", "20 ms", + // "stackTrace", "false") + // .addConsumer(System.out::println); + // + // EventStream.start("jdk.CPULoad", "period", "1 s") + // .addConsumer(e -> System.out.println(100 * + // e.getDouble("totalMachine") + " %")); + // + // EventStream.start("jdk.GarbageCollection") + // .addConsumer(e -> System.out.println("GC: " + e.getStartTime() + " + // maxPauseTime=" + e.getDuration("maxPauseTime").toMillis() + " ms")); + + Thread.sleep(100_000); + } + + // Use case: Event Forwarding + // + // - Forward arbitrary event to frameworks such as RxJava, JSON/XML and + // Kafka + // - Handle flooding + // - Performant + // - Graceful shutdown + // - Non-goals: Filter events + // + public static void eventForwarding() throws InterruptedException, IOException, ParseException { + // KafkaProducer producer = new KafkaProducer(); + try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("default"))) { + rs.setMaxAge(Duration.ofMinutes(5)); + rs.setMaxSize(1000_000_000L); + // es.setParallel(true); + // es.setReuse(true); + // es.consume(e -> producer.send(new ProducerRecord("topic", + // e.getString("key"), e.getString("value")))); + rs.start(); + } + // Write primitive values to XML + try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("deafult"))) { + try (PrintWriter p = new PrintWriter(new FileWriter("recording.xml"))) { + // es.setParallel(false); + // es.setReuse(true); + p.println(""); + p.println(""); + rs.onEvent(e -> { + EventType type = e.getEventType(); + p.println(" "); + for (ValueDescriptor field : e.getEventType().getFields()) { + Object value = e.getValue(field.getName()); + if (value instanceof Number || field.getTypeName().equals("java.lang.String")) { + p.println(" " + value + ""); + } + } + }); + rs.start(); + p.println(""); + } + } + } + + // Use case: Repository Access + // + // - Read the disk repository from another process, for example a side car + // in + // Docker container + // - Be able to configure flush interval from command line or jcmd. + // - Graceful shutdown + // + public static void repositoryAccess() throws IOException, InterruptedException { + Path repository = Paths.get("c:\\repository").toAbsolutePath(); + String command = new String(); + command += "java -XX:StartFlightRecording:flush=2s"; + command += "-XX:FlightRecorderOption:repository=" + repository + " Application"; + Process myProcess = Runtime.getRuntime().exec(command); + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(System.out::println); + rs.startAsync(); + Thread.sleep(10_000); + myProcess.destroy(); + Thread.sleep(10_000); + } + } + + // Use: Tooling + // + // - Monitor a stream of data for a very long time + // - Predictable interval, i.e. once every second + // - Notification with minimal delay + // - Events with the same period should arrive together + // - Consume events in chronological order + // - Low overhead + // + public static void tooling() throws IOException, ParseException { + ArrayDeque measurements = new ArrayDeque<>(); + try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("profile"))) { + rs.setInterval(Duration.ofSeconds(1)); + rs.setMaxAge(Duration.ofMinutes(1)); + // rs.setOrdered(true); + // rs.setReuse(false); + // rs.setParallel(true); + rs.onEvent("jdk.CPULoad", e -> { + double d = e.getDouble("totalMachine"); + measurements.addFirst(d); + if (measurements.size() > 60) { + measurements.removeLast(); + } + // repaint(); + }); + rs.start(); + } + } + + // Use case: Low Impact + // + // - Support event subscriptions in a low latency environment (minimal GC + // pauses) + // - Filter out relevant events to minimize disk overhead and allocation + // pressure + // - Avoid impact from other recordings + // - Avoid Heisenberg effects, in particular self-recursion + // + // Non-goals: one-liner + // + public static void lowImpact() throws InterruptedException, IOException, ParseException { + try (RecordingStream rs = new RecordingStream()) { + rs.enable("jdk.JavaMonitorEnter#threshold").withThreshold(Duration.ofMillis(10)); + rs.enable("jdk.ExceptionThrow#enabled"); + // ep.setReuse(true); + rs.onEvent("jdk.JavaMonitorEnter", System.out::println); + rs.onEvent("jdk.ExceptionThrow", System.out::println); + rs.start(); + ; + } + } +} diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/internal/FilePurger.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/FilePurger.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,47 @@ +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 -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Parser.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Parser.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,52 @@ +/* + * 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.internal.consumer; + +import java.io.IOException; + +/** + * Base class for parsing data from a {@link RecordingInput}. + */ +public abstract class Parser { + /** + * Parses data from a {@link RecordingInput} and return an object. + * + * @param input input to read from + * @return an object + * @throws IOException if operation couldn't be completed due to I/O + * problems + */ + 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 -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StringEncoding.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StringEncoding.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,11 @@ +package jdk.jfr.internal.consumer; + +public final class StringEncoding { + public final static byte STRING_ENCODING_NULL = (byte) 0; + public final static byte STRING_ENCODING_EMPTY_STRING = (byte) 1; + public final static byte STRING_ENCODING_CONSTANT_POOL = (byte) 2; + public final static byte STRING_ENCODING_UTF8_BYTE_ARRAY = (byte) 3; + public final static byte STRING_ENCODING_CHAR_ARRAY = (byte) 4; + public final static byte STRING_ENCODING_LATIN1_BYTE_ARRAY = (byte) 5; + +} diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/internal/util/PerfectHashMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/PerfectHashMap.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,340 @@ +package jdk.jfr.internal.util; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public class PerfectHashMap { + private static final long COLLISION_SHIFT = 63; + private static final long COLLISION_BIT = 1L << COLLISION_SHIFT; + private static final long COLLISION_MASK = COLLISION_BIT - 1; + private static final int MAX_REMAP_ATTEMPTS = 100000; + private static final int MAX_ATTEMPS_BEFORE_RESIZE = 100; + + static final long W = 64L; + static class LinkedValue { + final V value; + long next; + + LinkedValue(V value) { + this.value = value; + this.next = 0; + } + } + + private UniversalHashFamily hashFamily = new UniversalHashFamily(); + private PrimitiveHashMap> loadMap; + private Object[] valueTable; + private long[] routeTable; + private long shift; + private long shiftMask; + private int tableLengthMask; + private long primaryHashFunction = 0; + private int collisions = 0; + private int retries = 0; + private int sizeFactor = 1; + private boolean minimal; + + public V get(long key) { + LinkedValue v = loadMap.get(key); + return v != null ? v.value : null; + } + + public V put(long key, V value) { + LinkedValue existing = loadMap.put(key, new LinkedValue(value)); + return existing != null ? existing.value : null; + } + + public void forEach(BiConsumer action) { + //loadMap.forEach(PerfectHashMap::callback); + } + + public final void forEach(Consumer action) { + //loadMap.forEach(action); + } + + public final long[] keys() { + return loadMap.keys(); + } + + static class Log2 { + private static final int MAX_SIZE_EXPONENT = 32; + + static long log2base10(long exponent) { + return 1L << exponent; + } + + static int log2(int value) { + int i = 0; + int lastMultiple = 0; + while (i < MAX_SIZE_EXPONENT) { + int multiple = (int)log2base10(i); + if ((value & multiple) != 0) { + lastMultiple = i; + } + ++i; + } + return ((int)log2base10(lastMultiple) ^ value) != 0 ? lastMultiple + 1 : lastMultiple; + } + } + + static final int tableExponent(int cap) { + return Log2.log2(cap); + } + + PerfectHashMap() { + this(false, 101); + } + + PerfectHashMap(int size) { + this(false, size); + } + + PerfectHashMap(boolean minimal, int size) { + this.minimal = minimal; + this.loadMap = new PrimitiveHashMap<>(size); + this.primaryHashFunction = hashFamily.getRandomHashFunction(); + } + + @SuppressWarnings("unchecked") + public V getPerfect(long key) { + int routeIdx = getIndex(key, primaryHashFunction); + assert(routeIdx >= 0); + assert(routeIdx < routeTable.length); + long element = routeTable[routeIdx]; + int valueIdx = element < 0 ? getIndex(key, -element - 1) : (int)element; + assert(valueIdx >= 0); + assert(valueIdx < valueTable.length); + return (V)valueTable[valueIdx]; + } + + private long getRandomHashFunction() { + return hashFamily.getRandomHashFunction(); + } + private int getIndex(long key, long hashFunction) { + final int idx = UniversalHashFamily.getIndex(key, hashFunction, shift, shiftMask); + assert(idx >= 0); + assert(idx < routeTable.length); + return idx; + } + private static boolean isColliding(long entry) { + return entry < 0; + } + private boolean isNonColliding(long entry) { + return entry > 0; + } + private static long setColliding(long entry) { + return entry | COLLISION_BIT; + } + private static long read(long entry) { + return entry & COLLISION_MASK; + } + + private int nextValueTableSlot(int lastIdx) { + assert(lastIdx < valueTable.length); + int i = lastIdx; + for (; i < valueTable.length; ++i) { + if (valueTable[i] == null) { + break; + } + } + return i; + } + + private int valueTableStore(V value, int lastIdx) { + if (lastIdx > valueTable.length) { + lastIdx = 0; + } + assert(lastIdx < valueTable.length); + final int idx = nextValueTableSlot(lastIdx); + assert(idx < valueTable.length); + assert(valueTable[idx] == null); + valueTable[idx] = value; + return idx; + } + + + private void routeNonCollisions() { + int lastIdx = 0; + for (int i = 0; i < routeTable.length; ++i) { + if (isNonColliding(routeTable[i])) { + lastIdx = valueTableStore(loadMap.get(routeTable[i]).value, lastIdx); + routeTable[i] = lastIdx++; + } + } + } + + private void rollback(int idx, int length, long hashFunction) { + assert(isColliding(routeTable[idx])); + long key = read(routeTable[idx]); + LinkedValue v = loadMap.get(key); // boxing + for (int i = 0; i < length; ++i) { + final int valueIdx = getIndex(key, hashFunction); + assert(valueIdx >= 0); + assert(valueIdx < valueTable.length); + assert(valueTable[valueIdx] != null); + valueTable[valueIdx] = null; + key = v.next; + v = loadMap.get(v.next); // no boxing + } + } + + private boolean remap(int idx, long hashFunction) { + assert(isColliding(routeTable[idx])); + int completed = 0; + long key = read(routeTable[idx]); + LinkedValue v = loadMap.get(key); + while (key != 0) { + final int valueIdx = getIndex(key, hashFunction); + assert(valueIdx >= 0); + assert(valueIdx < valueTable.length); + if (valueTable[valueIdx] == null) { + valueTable[valueIdx] = v.value; + ++completed; + key = v.next; + v = loadMap.get(v.next); + continue; + } + rollback(idx, completed, hashFunction); + return false; + } + return true; + } + + private boolean routeCollisions(int idx) { + assert(isColliding(routeTable[idx])); + boolean success = false; + int attempts = 0; + long randomHashFunction = 0; + do { + randomHashFunction = getRandomHashFunction(); + success = remap(idx, randomHashFunction); + if (++attempts == MAX_REMAP_ATTEMPTS) { + System.out.println("Failed number of attempts - restart: " + attempts); + return false; + } + } while (!success); + System.out.println("Number of remap attempts: " + attempts); + routeTable[idx] = -1 - randomHashFunction; + assert(-routeTable[idx] - 1 == randomHashFunction); + return true; + } + + + private boolean routeCollisions() { + for (int i = 0; i < routeTable.length; ++i) { + if (isColliding(routeTable[i])) { + if (!routeCollisions(i)) { + return false; + } + } + } + return true; + } + + private static void clearLongTable(long[] table) { + Arrays.fill(table, 0); + for (int i = 0; i < table.length; ++i) { + assert(table[i] == 0); + } + } + + private static void clearReferenceTable(T[] table) { + Arrays.fill(table, null); + for (int i = 0; i < table.length; ++i) { + assert(table[i] == null); + } + } + + private void unlinkChains() { + for (long key : loadMap.keys()) { + loadMap.get(key).next = 0; + } + } + + private void routeTableStore(long key, LinkedValue value, int idx) { + assert(idx >= 0); + assert(idx < routeTable.length); + long existing = read(routeTable[idx]); + if (existing == 0) { + routeTable[idx] = key; + return; + } + ++collisions; + routeTable[idx] = setColliding(existing); + LinkedValue existingValue = loadMap.get(existing); + value.next = existingValue.next; + existingValue.next = key; + } + + private void mapKeys() { + for (long key : loadMap.keys()) { + routeTableStore(key, loadMap.get(key), getIndex(key, primaryHashFunction)); + } + } + + private void validate() { + for (long key : loadMap.keys()) { + long element = routeTable[getIndex(key, primaryHashFunction)]; + int valueIdx = element < 0 ? getIndex(key, -element - 1) : (int)element; + assert(valueIdx >= 0); + assert(loadMap.get(key) == valueTable[valueIdx]); + } + } + + private void reset() { + collisions = 0; + clearLongTable(routeTable); + clearReferenceTable(valueTable); + } + + private int dimensionTableSize() { + int size = loadMap.size() * sizeFactor; + return (int)Log2.log2base10(Log2.log2(size)); + } + + @SuppressWarnings({"rawtypes","unchecked"}) + private void allocateTables() { + int size = dimensionTableSize(); + this.tableLengthMask = size - 1; + this.shift = W - tableExponent(size); + this.shiftMask = Log2.log2base10(shift) - 1; + routeTable = new long[size]; + valueTable = (V[])new Object[size]; + collisions = 0; + retries = 0; + } + + public void build() { + start: + while (true) { + allocateTables(); + System.out.println("Table size " + routeTable.length); + mapKeys(); + if (collisions > 0) { + if (!routeCollisions()) { + unlinkChains(); + if (++retries <= MAX_ATTEMPS_BEFORE_RESIZE) { + reset(); + } else { + sizeFactor *= 2; + } + continue start; + } + } + routeNonCollisions(); + return; + } + } + + public void rebuild() { + sizeFactor = 1; + build(); + } + public int size() { + return loadMap.size(); + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/internal/util/PrimitiveHashMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/PrimitiveHashMap.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,341 @@ +package jdk.jfr.internal.util; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.Map; +import java.util.Iterator; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.ConcurrentModificationException; +import java.util.Random; + +public class PrimitiveHashMap { + + static final long W = 64L; + static final long A = 4633630788178346939L; + final Random rand = new Random(); + + private static int getIndex(long key, long hashFunction, long shift, long mask) { + return (int)(((A * key) + (hashFunction & mask)) >>> shift); + } + private long getRandomHashFunction() { + return rand.nextLong(); + } + /** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + */ + static final int MAX_SIZE_EXPONENT = 30; + static final int MAXIMUM_CAPACITY = 1 << MAX_SIZE_EXPONENT; + + static final int DEFAULT_SIZE_EXPONENT = 4; + static final int DEFAULT_INITIAL_CAPACITY = 1 << DEFAULT_SIZE_EXPONENT; // aka 16 + /** + * The load factor used when none specified in constructor. + */ + static final float DEFAULT_LOAD_FACTOR = 0.75f; + static class Log2 { + + static long log2base10(long exponent) { + return 1L << exponent; + } + static int log2(int value) { + int i = 0; + int lastMultiple = 0; + while (i < MAX_SIZE_EXPONENT) { + int multiple = (int)log2base10(i); + if ((value & multiple) != 0) { + lastMultiple = i; + } + ++i; + } + return ((int)log2base10(lastMultiple) ^ value) != 0 ? lastMultiple + 1 : lastMultiple; + } + } + /** + * Returns a power of two size for the given target capacity. + */ + static final int tableSizeFor(int cap) { + int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1); + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } + static final int tableExponent(int cap) { + return Log2.log2(cap); + } + + static class Node { + final long key; + V value; + + Node(long key, V value) { + this.key = key; + this.value = value; + } + + public final long getKey() { return key; } + public final V getValue() { return value; } + public final String toString() { return key + "=" + value; } + } + + private Node[] table; + private int size; + private int threshold; + private long shift; + private long shiftMask; + private int tableLengthMask; + int modCount; + private final float loadFactor; + long h1 = 0; + long h2 = 0; + + public PrimitiveHashMap(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) { + throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); + } + if (initialCapacity > MAXIMUM_CAPACITY) { + initialCapacity = MAXIMUM_CAPACITY; + } + if (loadFactor <= 0 || Float.isNaN(loadFactor)) { + throw new IllegalArgumentException("Illegal load factor: " + loadFactor); + } + this.loadFactor = loadFactor; + this.threshold = tableSizeFor(initialCapacity); + h1 = getRandomHashFunction(); + h2 = getRandomHashFunction(); + resize(); + } + + public PrimitiveHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + public PrimitiveHashMap() { + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + public final void forEach(BiConsumer action) { + Node[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (int i = 0; i < table.length; ++i) { + if (table[i] == null) continue; + action.accept(table[i].getKey(), table[i].getValue()); + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + + public final void forEach(Consumer action) { + Node[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (int i = 0; i < table.length; ++i) { + if (table[i] == null) continue; + action.accept(table[i].getValue()); + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + + public final long[] keys() { + long[] keys = new long[size]; + int j = 0; + for (int i = 0; i < table.length; ++i) { + if (table[i] == null) continue; + keys[j++] = table[i].getKey(); + } + assert(j == size); + assert(keys.length == size); + return keys; + } + + public Collection values () { + final PrimitiveHashMap thisMap = this; + return new AbstractCollection() { + private PrimitiveHashMap map = thisMap; + public Iterator iterator() { + return new Iterator() { + private int i = 0; + private long [] k = keys(); + public boolean hasNext() { + return i < k.length; + } + public V next() { + assert(i < k.length); + return map.get(k[i++]); + } + }; + } + public int size() { + return map.size(); + } + public boolean isEmpty() { + return size() != 0; + } + public void clear() { + throw new UnsupportedOperationException(); + } + public boolean contains(Object v) { + for (V value : map.values()) { + if (v == value) { + return true; + } + } + return false; + } + }; + } + private int doubleHash(long key, int i) { + int h1_idx = getIndex(key, h1, shift, shiftMask); + assert(h1_idx < table.length); + int h2_idx = 0; + if (i != 0) { + h2_idx = getIndex(key, h2, shift, shiftMask); + h2_idx |= 1; + assert((h2_idx & 1) == 1); + } + assert(h2_idx < table.length); + final int idx = (h1_idx + (i * h2_idx)) & tableLengthMask; + assert(idx >= 0); + assert(idx < table.length); + return idx; + } + + /** + * Initializes or doubles table size. If null, allocates in + * accord with initial capacity target held in field threshold. + * Otherwise, because we are using power-of-two expansion, the + * elements from each bin must either stay at same index, or move + * with a power of two offset in the new table. + * + * @return the table + */ + final Node[] resize() { + Node[] oldTab = table; + int oldCap = (oldTab == null) ? 0 : oldTab.length; + int oldThr = threshold; + int newCap, newThr = 0; + if (oldCap > 0) { + if (oldCap >= MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return oldTab; + } + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + else { // zero initial threshold signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; + tableLengthMask = newCap - 1; + this.shift = W - tableExponent(newCap); + this.shiftMask = Log2.log2base10(shift) - 1; + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) { + oldTab[j] = null; + reinsert(e); + } + } + } + return newTab; + } + + // used by table resize + private void reinsert(Node e) { + assert(size < table.length); + for (int i = 0; i < table.length; ++i) { + int idx = doubleHash(e.getKey(), i); + assert(idx >= 0); + assert(idx < table.length); + if (table[idx] == null) { + table[idx] = e; + return; + } + assert(table[idx].key != e.getKey()); + } + } + + public V put(long key, V value) { + Node existing = insert(key, value); + return existing != null ? existing.value : null; + } + + private Node insert(long key, V value) { + return insert(new Node(key, value), key); + } + + private Node insert(Node e, final long key) { + assert(size < table.length); + assert(e.getKey() == key); + Node existing = null; + for (int i = 0; i < table.length; ++i) { + int idx = doubleHash(key, i); + assert(idx >= 0); + assert(idx < table.length); + if (table[idx] == null) { + table[idx] = e; + ++size; + break; + } else { + if (table[idx].key == key) { + existing = table[idx]; + table[idx] = e; + break; + } + } + } + if (size > threshold) { + resize(); + } + return existing; + } + + private Node find(long key) { + Node result = null; + for (int i = 0; i < table.length; ++i) { + int idx = doubleHash(key, i); + assert(idx >= 0); + assert(idx < table.length); + result = table[idx]; + if (result == null || result.key == key) { + break; + } + } + return result; + } + + + public V get(long key) { + Node existing = find(key); + return existing != null ? existing.value : null; + } + + public boolean containsKey(long key) { + return find(key) != null; + } + public int size() { + return this.size; + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be src/jdk.jfr/share/classes/jdk/jfr/internal/util/UniversalHashFamily.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/UniversalHashFamily.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,27 @@ +package jdk.jfr.internal.util; + +import java.util.Random; + +public class UniversalHashFamily { + final Random rand = new Random(); + + private static long getA(long hashFunction) { + return hashFunction | 1; + } + + private static long getB(long hashFunction, long mask) { + return hashFunction & mask; + } + + private static long getHash(long key, long hashFunction, long mask) { + return (getA(hashFunction) * key) + (hashFunction & mask); + } + + public static int getIndex(long key, long hashFunction, long shift, long mask) { + return (int)(getHash(key, hashFunction, mask) >>> shift); + } + + public long getRandomHashFunction() { + return rand.nextLong() & Long.MAX_VALUE; + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestAwaitTermination.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestAwaitTermination.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,70 @@ +/* + * 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 + * @requires vm.hasJFR + * @library /test/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(); + var c = CompletableFuture.runAsync(() -> { + r.awaitTermination(); + }); + r.close(); + c.get(); + } + } + + private static void testAwaitTimeOut() throws InterruptedException, ExecutionException { + try (RecordingStream r = new RecordingStream()) { + r.startAsync(); + var c = CompletableFuture.runAsync(() -> { + r.awaitTermination(Duration.ofMillis(10)); + }); + c.get(); + r.close(); + } + } +} diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestClose.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestClose.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,115 @@ +/* + * 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 + * @requires vm.hasJFR + * @library /test/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 { + 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(); + } + + private static void testCloseStreaming() throws Exception { + CountDownLatch streaming = new CountDownLatch(1); + RecordingStream r = new RecordingStream(); + AtomicLong count = new AtomicLong(); + r.onEvent(e -> { + if (count.incrementAndGet() == 100) { + streaming.countDown(); + } + }); + r.startAsync(); + var streamingLoop = CompletableFuture.runAsync(() -> { + while (true) { + CloseEvent c = new CloseEvent(); + c.commit(); + } + }); + streaming.await(); + r.close(); + streamingLoop.cancel(true); + } + + private static void testCloseStarted() { + RecordingStream r = new RecordingStream(); + r.startAsync(); + r.close(); + } + + private static void testCloseUnstarted() { + RecordingStream r = new RecordingStream(); + r.close(); + } + + private static void testCloseTwice() { + RecordingStream r = new RecordingStream(); + r.startAsync(); + r.close(); + r.close(); + } + +} diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestConstructor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestConstructor.java Fri May 17 18:03:14 2019 +0200 @@ -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 + * @requires vm.hasJFR + * @library /test/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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestDisable.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestDisable.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,74 @@ +/* + * 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::disable(...) + * @key jfr + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestConstructor + */ +public class TestDisable { + + private static class DisabledEvent extends Event { + } + + private static class EnabledEvent extends Event { + } + + public static void main(String... args) throws Exception { + 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(); + }); + r.disable(DisabledEvent.class.getName()); + r.startAsync(); + EnabledEvent e1 = new EnabledEvent(); + e1.commit(); + DisabledEvent d1 = new DisabledEvent(); + d1.commit(); + EnabledEvent e2 = new EnabledEvent(); + e2.commit(); + twoEvent.await(); + if (fail.get()) { + throw new Exception("Should not receive a disabled event"); + } + } + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestEnable.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestEnable.java Fri May 17 18:03:14 2019 +0200 @@ -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.util.concurrent.CountDownLatch; + +import jdk.jfr.Enabled; +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::enable(...) + * @key jfr + * @requires vm.hasJFR + * @library /test/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 { + CountDownLatch event = new CountDownLatch(1); + try (RecordingStream r = new RecordingStream()) { + r.onEvent(e -> { + event.countDown(); + }); + r.enable(EnabledEvent.class.getName()); + r.startAsync(); + EnabledEvent e = new EnabledEvent(); + e.commit(); + event.await(); + } + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestMaxAge.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestMaxAge.java Fri May 17 18:03:14 2019 +0200 @@ -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::setMaxAge(...) + * @key jfr + * @requires vm.hasJFR + * @library /test/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(); + } + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnClose.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnClose.java Fri May 17 18:03:14 2019 +0200 @@ -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 + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestMaxAge + */ +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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnEvent.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnEvent.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,142 @@ +/* + * 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 + * @requires vm.hasJFR + * @library /test/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() { + 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 + } + } + } + + private static void testTwoEventWithSameName() throws Exception { + 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(); + } + } + + private static void testNamedEvent() throws Exception { + 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(); + } + } + + private static void testOnEvent() throws Exception { + 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(); + } + } +} diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnFlush.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnFlush.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,88 @@ +/* + * 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 + * @requires vm.hasJFR + * @library /test/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() { + try (RecordingStream rs = new RecordingStream()) { + try { + rs.onFlush(null); + throw new AssertionError("Expected NullPointerException from onFlush(null"); + } catch (NullPointerException npe) { + // OK; as expected + } + } + } + + private static void testNoEvent() throws Exception { + CountDownLatch flush = new CountDownLatch(1); + try (RecordingStream r = new RecordingStream()) { + r.onFlush(() -> { + flush.countDown(); + }); + r.startAsync(); + flush.await(); + } + } + + private static void testOneEvent() throws InterruptedException { + 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(); + } + } +} diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestRemove.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestRemove.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,136 @@ +/* + * 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 + * @requires vm.hasJFR + * @library /test/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() { + try (RecordingStream rs = new RecordingStream()) { + try { + rs.remove(null); + throw new AssertionError("Expected NullPointerException from remove(null"); + } catch (NullPointerException npe) { + // OK; as expected + } + } + } + + private static void testRemoveOnEvent() throws Exception { + 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!"); + } + } + } + + private static void 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!"); + } + } + } + + private static void testRemoveOnFlush() throws Exception { + 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!"); + } + } + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetFlushInterval.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetFlushInterval.java Fri May 17 18:03:14 2019 +0200 @@ -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 RecordingStrream::setFlushInterval + * @key jfr + * @requires vm.hasJFR + * @library /test/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.setInterval(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(); + } + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetMaxAge.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetMaxAge.java Fri May 17 18:03:14 2019 +0200 @@ -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 + * @requires vm.hasJFR + * @library /test/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(); + } + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetMaxSize.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetMaxSize.java Fri May 17 18:03:14 2019 +0200 @@ -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 +* @requires vm.hasJFR +* @library /test/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(); + } + } +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestStart.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStart.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,157 @@ +/* + * 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.AtomicBoolean; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests RecordingStream::start() + * @key jfr + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestStart + */ +public class TestStart { + static class StartEvent extends Event { + } + static class EventProducer extends Thread { + private boolean killed = false; + public void run() { + while (true) { + StartEvent s = new StartEvent(); + s.commit(); + synchronized (this) { + try { + wait(10); + if (killed) { + return; // end thread + } + } catch (InterruptedException e) { + // ignore + } + } + } + } + public void kill() { + synchronized (this) { + this.killed = true; + this.notifyAll(); + } + } + } + + public static void main(String... args) throws Exception { + testStart(); + testStartOnEvent(); + testStartTwice(); + testStartClosed(); + } + + private static void testStartTwice() throws Exception { + CountDownLatch started = new CountDownLatch(1); + try (RecordingStream rs = new RecordingStream()) { + EventProducer t = new EventProducer(); + t.start(); + CompletableFuture.runAsync(() -> { + rs.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 + } + } + } + + static void testStart() throws Exception { + CountDownLatch started = new CountDownLatch(1); + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(e -> { + started.countDown(); + }); + EventProducer t = new EventProducer(); + t.start(); + CompletableFuture.runAsync(() -> { + rs.start(); + }); + started.await(); + t.kill(); + } + } + + static void testStartOnEvent() throws Exception { + 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()) { + startedTwice.countDown(); + ISE.set(true); + } + } + }); + EventProducer t = new EventProducer(); + t.start(); + CompletableFuture.runAsync(() -> { + rs.start(); + }); + startedTwice.await(); + t.kill(); + if (!ISE.get()) { + throw new AssertionError("Expected IllegalStateException"); + } + } + } + + static void testStartClosed() { + RecordingStream rs = new RecordingStream(); + rs.close(); + try { + rs.start(); + throw new AssertionError("Expected IllegalStateException"); + } catch (IllegalStateException ise) { + // OK, as expected. + } + } + +} \ No newline at end of file diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/recordingstream/TestStartAsync.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStartAsync.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,86 @@ +/* + * 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 + * @requires vm.hasJFR + * @library /test/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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/TestEmptyChunks.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestEmptyChunks.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,76 @@ +/* + * 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 + * @requires vm.hasJFR + * @library /test/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); + try (RecordingStream es = new RecordingStream()) { + es.onEvent(EndEvent.class.getName(), e -> { + end.countDown(); + }); + es.startAsync(); + 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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/TestEnableEvents.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestEnableEvents.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,99 @@ +/* + * 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 + * @requires vm.hasJFR + * @library /test/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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/TestEventRegistration.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestEventRegistration.java Fri May 17 18:03:14 2019 +0200 @@ -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.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 + * @requires vm.hasJFR + * @library /test/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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/TestFilledChunks.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestFilledChunks.java Fri May 17 18:03:14 2019 +0200 @@ -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.streaming; + +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +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 + * @requires vm.hasJFR + * @library /test/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 { + CountDownLatch end = new CountDownLatch(1); + AtomicInteger idCounter = new AtomicInteger(); + try (RecordingStream es = new RecordingStream()) { + es.onEvent(EndEvent.class.getName(), e -> end.countDown()); + es.onEvent(FillEvent.class.getName(), e -> { + idCounter.incrementAndGet(); +// if (id != expected) { +// throw new Error("Expected id " + expected + ", but got " + id); +// } + }); + es.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 ? "ko" : "kak"; + f.value = r.nextInt(10000); + f.id = i; + f.commit(); + if (i % 1_000_000 == 0) { + System.out.println("Emitted " + i + " events"); + } + } + System.out.println("Awaiting end event"); + Thread.sleep(1_000); + for (int i = 1; i < EVENT_COUNT; i++) { + EndEvent e = new EndEvent(); + e.commit(); + } + end.await(); + + } + } +} diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/TestFiltering.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestFiltering.java Fri May 17 18:03:14 2019 +0200 @@ -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.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 + * @requires vm.hasJFR + * @library /test/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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/TestFromFile.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestFromFile.java Fri May 17 18:03:14 2019 +0200 @@ -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.streaming; + +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicLong; + +import jdk.jfr.Event; +import jdk.jfr.consumer.EventStream; + +/** + * @test + * @summary Verifies that it is possible to stream contents from a file + * @key jfr + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.consumer.streaming.TestFromFile + */ +public class TestFromFile { + + 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); +// +// testIterator(path); +// testConsumer(path); +// } + } + + static void testConsumer(Path path) throws Exception { + AtomicLong counter = new AtomicLong(); + try (EventStream es = EventStream.openFile(path)) { + es.onEvent(e -> { + counter.incrementAndGet(); + }); + es.startAsync(); + if (counter.get() != 6) { + throw new Exception("Expected 6 event, but got " + counter.get()); + } + es.awaitTermination(); + } + } + + static void emitSnakeEvent(int id) { + SnakeEvent e = new SnakeEvent(); + e.id = id; + e.commit(); + } + +} diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/TestRecordingBefore.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestRecordingBefore.java Fri May 17 18:03:14 2019 +0200 @@ -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.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 + * @requires vm.hasJFR + * @library /test/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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/TestRemovedChunks.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestRemovedChunks.java Fri May 17 18:03:14 2019 +0200 @@ -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.streaming; + +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Tests that a stream can gracefully handle chunk being removed + * @key jfr + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.consumer.streaming.TestFilledChunks + */ +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(20_000_000); + s.onEvent(ParkStream.class.getName(), e -> { + parkLatch.countDown(); + await(removalLatch); + + }); + s.onEvent(IFeelFine.class.getName(), e -> { + IfeelFineLatch.countDown(); + }); + s.startAsync(); + emitData(15_000_000); + ParkStream ps = new ParkStream(); + ps.commit(); + await(parkLatch); + // Try to force removal of chunk that is being streamed + emitData(50_000_000); + removalLatch.countDown(); + IFeelFine i = new IFeelFine(); + i.commit(); + 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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryProperty.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryProperty.java Fri May 17 18:03:14 2019 +0200 @@ -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.io.IOException; +import java.nio.file.Files; +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; + +/** + * @test + * @summary Verifies that it is possible to access JFR repository from a system + * property + * @key jfr + * @requires vm.hasJFR + * @library /test/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 AttachNotSupportedException, IOException { + String pidText = String.valueOf(ProcessHandle.current().pid()); + 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 = Path.of(location); + if (!Files.isDirectory(path)) { + throw new AssertionError("Repository path doesn't point to directory"); + } + } + + private static void testAfterChange() { + Path newRepository = Path.of(".").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 exit before JFR is initialized"); + } + System.out.println("repository=" + location); + Path p = Path.of(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 exit before JFR is initialized"); + } + } +} diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,121 @@ +/* + * 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 + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm -Xlog:jfr+system+streaming=trace + * 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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/TestStartSingleChunk.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestStartSingleChunk.java Fri May 17 18:03:14 2019 +0200 @@ -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.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 + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm -Xlog:jfr+system+streaming=trace + * 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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/api/consumer/streaming/UseCasesStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/api/consumer/streaming/UseCasesStream.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,194 @@ +/* + * 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.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.time.Duration; +import java.util.ArrayDeque; + +import jdk.jfr.Configuration; +import jdk.jfr.EventType; +import jdk.jfr.ValueDescriptor; +import jdk.jfr.consumer.RecordingStream; + +class UseCasesStream { + + // + // Use case: Out-of-the-Box Experience + // + // - Simple things should be simple + // - Pique interest, i.e. a one-liner on Stack Overflow + // - Few lines of code as possible + // - Should be easier than alternative technologies, like JMX and JVM TI + // + // - Non-goals: Corner-cases, advanced configuration, releasing resources + // + public static void outOfTheBox() throws InterruptedException { + try (RecordingStream rs = new RecordingStream()) { + rs.enable("jdk.ExceptionThrown"); + rs.onEvent(e -> System.out.println(e.getString("message"))); + rs.start(); + } + + // EventStream.start("jdk.JavaMonitorEnter", "threshold", "20 ms", + // "stackTrace", "false") + // .addConsumer(System.out::println); + // + // EventStream.start("jdk.CPULoad", "period", "1 s") + // .addConsumer(e -> System.out.println(100 * + // e.getDouble("totalMachine") + " %")); + // + // EventStream.start("jdk.GarbageCollection") + // .addConsumer(e -> System.out.println("GC: " + e.getStartTime() + " + // maxPauseTime=" + e.getDuration("maxPauseTime").toMillis() + " ms")); + + Thread.sleep(100_000); + } + + // Use case: Event Forwarding + // + // - Forward arbitrary event to frameworks such as RxJava, JSON/XML and + // Kafka + // - Handle flooding + // - Performant + // - Graceful shutdown + // - Non-goals: Filter events + // + public static void eventForwarding() throws InterruptedException, IOException, ParseException { + // KafkaProducer producer = new KafkaProducer(); + try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("default"))) { + rs.setMaxAge(Duration.ofMinutes(5)); + rs.setMaxSize(1000_000_000L); + // es.setParallel(true); + // es.setReuse(true); + // es.consume(e -> producer.send(new ProducerRecord("topic", + // e.getString("key"), e.getString("value")))); + rs.start(); + } + // Write primitive values to XML + try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("deafult"))) { + try (PrintWriter p = new PrintWriter(new FileWriter("recording.xml"))) { + // es.setParallel(false); + // es.setReuse(true); + p.println(""); + p.println(""); + rs.onEvent(e -> { + EventType type = e.getEventType(); + p.println(" "); + for (ValueDescriptor field : e.getEventType().getFields()) { + Object value = e.getValue(field.getName()); + if (value instanceof Number || field.getTypeName().equals("java.lang.String")) { + p.println(" " + value + ""); + } + } + }); + rs.start(); + p.println(""); + } + } + } + + // Use case: Repository Access + // + // - Read the disk repository from another process, for example a side car + // in + // Docker container + // - Be able to configure flush interval from command line or jcmd. + // - Graceful shutdown + // + public static void repositoryAccess() throws IOException, InterruptedException { + Path repository = Paths.get("c:\\repository").toAbsolutePath(); + String command = new String(); + command += "java -XX:StartFlightRecording:flush=2s"; + command += "-XX:FlightRecorderOption:repository=" + repository + " Application"; + Process myProcess = Runtime.getRuntime().exec(command); + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(System.out::println); + rs.startAsync(); + Thread.sleep(10_000); + myProcess.destroy(); + Thread.sleep(10_000); + } + } + + // Use: Tooling + // + // - Monitor a stream of data for a very long time + // - Predictable interval, i.e. once every second + // - Notification with minimal delay + // - Events with the same period should arrive together + // - Consume events in chronological order + // - Low overhead + // + public static void tooling() throws IOException, ParseException { + ArrayDeque measurements = new ArrayDeque<>(); + try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("profile"))) { + rs.setInterval(Duration.ofSeconds(1)); + rs.setMaxAge(Duration.ofMinutes(1)); + // rs.setOrdered(true); + // rs.setReuse(false); + // rs.setParallel(true); + rs.onEvent("jdk.CPULoad", e -> { + double d = e.getDouble("totalMachine"); + measurements.addFirst(d); + if (measurements.size() > 60) { + measurements.removeLast(); + } + // repaint(); + }); + rs.start(); + } + } + + // Use case: Low Impact + // + // - Support event subscriptions in a low latency environment (minimal GC + // pauses) + // - Filter out relevant events to minimize disk overhead and allocation + // pressure + // - Avoid impact from other recordings + // - Avoid Heisenberg effects, in particular self-recursion + // + // Non-goals: one-liner + // + public static void lowImpact() throws InterruptedException, IOException, ParseException { + try (RecordingStream rs = new RecordingStream()) { + rs.enable("jdk.JavaMonitorEnter#threshold").withThreshold(Duration.ofMillis(10)); + rs.enable("jdk.ExceptionThrow#enabled"); + // ep.setReuse(true); + rs.onEvent("jdk.JavaMonitorEnter", System.out::println); + rs.onEvent("jdk.ExceptionThrow", System.out::println); + rs.start(); + ; + } + } +} diff -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/event/runtime/TestFlush.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/event/runtime/TestFlush.java Fri May 17 18:03:14 2019 +0200 @@ -0,0 +1,166 @@ +/* + * 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 + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm -Xlog:jfr+system+streaming=trace 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 -r 5d043a159d5c -r 53dccc90a5be test/jdk/jdk/jfr/jvm/TestThreadExclusion.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/jvm/TestThreadExclusion.java Fri May 17 18:03:14 2019 +0200 @@ -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.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 + * @requires vm.hasJFR + * @library /test/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(); + } + } +}