8205516: JFR tool
authoregahlin
Wed, 05 Dec 2018 16:40:12 +0100
changeset 52850 f527b24990d7
parent 52849 eef755718cb2
child 52851 f0c62b8f73c0
8205516: JFR tool Reviewed-by: mgronlun
make/launcher/Launcher-jdk.jfr.gmk
src/hotspot/share/jfr/leakprofiler/emitEventOperation.cpp
src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp
src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.hpp
src/hotspot/share/jfr/leakprofiler/sampling/sampleList.cpp
src/hotspot/share/jfr/leakprofiler/sampling/sampleList.hpp
src/jdk.jfr/share/classes/jdk/jfr/ValueDescriptor.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/ChunkParser.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/TimeConverter.java
src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java
src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java
src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Assemble.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Command.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Disassemble.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Help.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/JSONWriter.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Main.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Metadata.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Print.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/StructuredWriter.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserDataException.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserSyntaxException.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Version.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/XMLWriter.java
src/jdk.jfr/share/classes/module-info.java
test/jdk/jdk/jfr/tool/ExecuteHelper.java
test/jdk/jdk/jfr/tool/TestAssemble.java
test/jdk/jdk/jfr/tool/TestDisassemble.java
test/jdk/jdk/jfr/tool/TestHelp.java
test/jdk/jdk/jfr/tool/TestMetadata.java
test/jdk/jdk/jfr/tool/TestPrint.java
test/jdk/jdk/jfr/tool/TestPrintDefault.java
test/jdk/jdk/jfr/tool/TestPrintJSON.java
test/jdk/jdk/jfr/tool/TestPrintXML.java
test/jdk/jdk/jfr/tool/TestSummary.java
test/jdk/jdk/jfr/tool/jfr.xsd
test/jdk/tools/launcher/HelpFlagsTest.java
test/jdk/tools/launcher/VersionCheck.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/launcher/Launcher-jdk.jfr.gmk	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 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.
+#
+
+include LauncherCommon.gmk
+
+$(eval $(call SetupBuildLauncher, jfr, \
+    MAIN_CLASS := jdk.jfr.internal.tool.Main, \
+    CFLAGS := -DEXPAND_CLASSPATH_WILDCARDS, \
+))
--- a/src/hotspot/share/jfr/leakprofiler/emitEventOperation.cpp	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/hotspot/share/jfr/leakprofiler/emitEventOperation.cpp	Wed Dec 05 16:40:12 2018 +0100
@@ -158,12 +158,14 @@
   const jlong last_sweep = _emit_all ? max_jlong : _object_sampler->last_sweep().value();
   int count = 0;
 
-  for (int i = 0; i < _object_sampler->item_count(); ++i) {
-    const ObjectSample* sample = _object_sampler->item_at(i);
-    if (sample->is_alive_and_older_than(last_sweep)) {
-      write_event(sample, edge_store);
+  const ObjectSample* current = _object_sampler->first();
+  while (current != NULL) {
+    ObjectSample* prev = current->prev();
+    if (current->is_alive_and_older_than(last_sweep)) {
+      write_event(current, edge_store);
       ++count;
     }
+    current = prev;
   }
 
   // restore thread local stack trace and thread id
--- a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp	Wed Dec 05 16:40:12 2018 +0100
@@ -122,6 +122,10 @@
   return _list->last();
 }
 
+const ObjectSample* ObjectSampler::first() const {
+  return _list->first();
+}
+
 const ObjectSample* ObjectSampler::last_resolved() const {
   return _list->last_resolved();
 }
--- a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.hpp	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.hpp	Wed Dec 05 16:40:12 2018 +0100
@@ -69,6 +69,7 @@
   const ObjectSample* item_at(int index) const;
   ObjectSample* item_at(int index);
   int item_count() const;
+  const ObjectSample* first() const;
   const ObjectSample* last() const;
   const ObjectSample* last_resolved() const;
   void set_last_resolved(const ObjectSample* sample);
--- a/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.cpp	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.cpp	Wed Dec 05 16:40:12 2018 +0100
@@ -45,6 +45,10 @@
   return _in_use_list.head();
 }
 
+ObjectSample* SampleList::first() const {
+  return _in_use_list.tail();
+}
+
 const ObjectSample* SampleList::last_resolved() const {
   return _last_resolved;
 }
--- a/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.hpp	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.hpp	Wed Dec 05 16:40:12 2018 +0100
@@ -53,6 +53,7 @@
   void set_last_resolved(const ObjectSample* sample);
   ObjectSample* get();
   ObjectSample* last() const;
+  ObjectSample* first() const;
   void release(ObjectSample* sample);
   const ObjectSample* last_resolved() const;
   ObjectSample* reuse(ObjectSample* sample);
--- a/src/jdk.jfr/share/classes/jdk/jfr/ValueDescriptor.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/ValueDescriptor.java	Wed Dec 05 16:40:12 2018 +0100
@@ -291,7 +291,7 @@
         if (type.isSimpleType()) {
             return Collections.emptyList();
         }
-        return Collections.unmodifiableList(type.getFields());
+        return type.getFields();
     }
 
     // package private
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ChunkParser.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/ChunkParser.java	Wed Dec 05 16:40:12 2018 +0100
@@ -53,7 +53,7 @@
     private final TimeConverter timeConverter;
 
     public ChunkParser(RecordingInput input) throws IOException {
-      this(new ChunkHeader(input));
+        this(new ChunkHeader(input));
     }
 
     private ChunkParser(ChunkHeader header) throws IOException {
@@ -61,7 +61,7 @@
         this.chunkHeader = header;
         this.metadata = header.readMetadata();
         this.absoluteChunkEnd = header.getEnd();
-        this.timeConverter =  new TimeConverter(chunkHeader);
+        this.timeConverter = new TimeConverter(chunkHeader, metadata.getGMTOffset());
 
         ParserFactory factory = new ParserFactory(metadata, timeConverter);
         LongMap<ConstantMap> constantPools = factory.getConstantPools();
@@ -114,9 +114,7 @@
             boolean flush = input.readBoolean();
             int poolCount = input.readInt();
             Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, () -> {
-                return "New constant pool: startPosition=" + position +
-                        ", size=" + size + ", deltaToNext=" + delta +
-                        ", flush=" + flush + ", poolCount=" + poolCount;
+                return "New constant pool: startPosition=" + position + ", size=" + size + ", deltaToNext=" + delta + ", flush=" + flush + ", poolCount=" + poolCount;
             });
 
             for (int i = 0; i < poolCount; i++) {
@@ -155,7 +153,7 @@
 
     private String getName(long id) {
         Type type = typeMap.get(id);
-        return type == null ? ("unknown(" + id +")") : type.getName();
+        return type == null ? ("unknown(" + id + ")") : type.getName();
     }
 
     public Collection<Type> getTypes() {
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java	Wed Dec 05 16:40:12 2018 +0100
@@ -41,7 +41,8 @@
 public final class RecordedEvent extends RecordedObject {
     private final EventType eventType;
     private final long startTime;
-    private final long endTime;
+    // package private needed for efficient sorting
+    final long endTime;
 
     // package private
     RecordedEvent(EventType type, List<ValueDescriptor> vds, Object[] values, long startTime, long endTime, TimeConverter timeConverter) {
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java	Wed Dec 05 16:40:12 2018 +0100
@@ -25,11 +25,11 @@
 
 package jdk.jfr.consumer;
 
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.time.Duration;
 import java.time.Instant;
+import java.time.OffsetDateTime;
 import java.util.List;
 import java.util.Objects;
 
@@ -37,7 +37,7 @@
 import jdk.jfr.Timestamp;
 import jdk.jfr.ValueDescriptor;
 import jdk.jfr.internal.PrivateAccess;
-import jdk.jfr.internal.cmd.PrettyWriter;
+import jdk.jfr.internal.tool.PrettyWriter;
 
 /**
  * A complex data type that consists of one or more fields.
@@ -872,18 +872,19 @@
     final public String toString() {
         StringWriter s = new StringWriter();
         PrettyWriter p = new PrettyWriter(new PrintWriter(s));
-        try {
-            if (this instanceof RecordedEvent) {
-                p.print((RecordedEvent) this);
-            } else {
-                p.print(this, "");
-            }
+        p.setStackDepth(5);
+        if (this instanceof RecordedEvent) {
+            p.print((RecordedEvent) this);
+        } else {
+            p.print(this, "");
+        }
+        p.flush(true);
+        return s.toString();
+    }
 
-        } catch (IOException e) {
-            // Ignore, should not happen with StringWriter
-        }
-        p.flush();
-        return s.toString();
+    // package private for now. Used by EventWriter
+    OffsetDateTime getOffsetDateTime(String name) {
+        return OffsetDateTime.ofInstant(getInstant(name), timeConverter.getZoneOffset());
     }
 
     private static IllegalArgumentException newIllegalArgumentException(String name, String typeName) {
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java	Wed Dec 05 16:40:12 2018 +0100
@@ -32,13 +32,16 @@
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 
 import jdk.jfr.EventType;
 import jdk.jfr.internal.MetadataDescriptor;
+import jdk.jfr.internal.Type;
 import jdk.jfr.internal.consumer.ChunkHeader;
 import jdk.jfr.internal.consumer.RecordingInput;
+import jdk.jfr.internal.consumer.RecordingInternals;
 
 /**
  * A recording file.
@@ -59,7 +62,29 @@
  * @since 9
  */
 public final class RecordingFile implements Closeable {
+    static{
+        RecordingInternals.INSTANCE = new RecordingInternals() {
+            public List<Type> readTypes(RecordingFile file) throws IOException {
+                return file.readTypes();
+            }
 
+            public boolean isLastEventInChunk(RecordingFile file) {
+                return file.isLastEventInChunk;
+            }
+
+            @Override
+            public Object getOffsetDataTime(RecordedObject event, String name) {
+                return event.getOffsetDateTime(name);
+            }
+
+            @Override
+            public void sort(List<RecordedEvent> events) {
+               Collections.sort(events, (e1, e2) -> Long.compare(e1.endTime, e2.endTime));
+            }
+        };
+    }
+
+    private boolean isLastEventInChunk;
     private final File file;
     private RecordingInput input;
     private ChunkParser chunkParser;
@@ -98,9 +123,11 @@
             ensureOpen();
             throw new EOFException();
         }
+        isLastEventInChunk = false;
         RecordedEvent event = nextEvent;
         nextEvent = chunkParser.readEvent();
         if (nextEvent == null) {
+            isLastEventInChunk = true;
             findNext();
         }
         return event;
@@ -131,6 +158,21 @@
         HashSet<Long> foundIds = new HashSet<>();
         try (RecordingInput ri = new RecordingInput(file)) {
             ChunkHeader ch = new ChunkHeader(ri);
+            aggregateEventTypeForChunk(ch, types, foundIds);
+            while (!ch.isLastChunk()) {
+                ch = ch.nextHeader();
+                aggregateEventTypeForChunk(ch, types, foundIds);
+            }
+        }
+        return types;
+    }
+
+    List<Type> readTypes() throws IOException  {
+        ensureOpen();
+        List<Type> types = new ArrayList<>();
+        HashSet<Long> foundIds = new HashSet<>();
+        try (RecordingInput ri = new RecordingInput(file)) {
+            ChunkHeader ch = new ChunkHeader(ri);
             aggregateTypeForChunk(ch, types, foundIds);
             while (!ch.isLastChunk()) {
                 ch = ch.nextHeader();
@@ -140,7 +182,17 @@
         return types;
     }
 
-    private static void aggregateTypeForChunk(ChunkHeader ch, List<EventType> types, HashSet<Long> foundIds) throws IOException {
+    private void aggregateTypeForChunk(ChunkHeader ch, List<Type> types, HashSet<Long> foundIds) throws IOException {
+        MetadataDescriptor m = ch.readMetadata();
+        for (Type t : m.getTypes()) {
+            if (!foundIds.contains(t.getId())) {
+                types.add(t);
+                foundIds.add(t.getId());
+            }
+        }
+    }
+
+    private static void aggregateEventTypeForChunk(ChunkHeader ch, List<EventType> types, HashSet<Long> foundIds) throws IOException {
         MetadataDescriptor m = ch.readMetadata();
         for (EventType t : m.getEventTypes()) {
             if (!foundIds.contains(t.getId())) {
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/TimeConverter.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/TimeConverter.java	Wed Dec 05 16:40:12 2018 +0100
@@ -25,6 +25,12 @@
 
 package jdk.jfr.consumer;
 
+import java.time.DateTimeException;
+import java.time.ZoneOffset;
+
+import jdk.jfr.internal.LogLevel;
+import jdk.jfr.internal.LogTag;
+import jdk.jfr.internal.Logger;
 import jdk.jfr.internal.consumer.ChunkHeader;
 
 /**
@@ -34,11 +40,22 @@
     private final long startTicks;
     private final long startNanos;
     private final double divisor;
+    private final ZoneOffset zoneOffet;
 
-    TimeConverter(ChunkHeader chunkHeader) {
+    TimeConverter(ChunkHeader chunkHeader, int rawOffset) {
         this.startTicks = chunkHeader.getStartTicks();
         this.startNanos = chunkHeader.getStartNanos();
         this.divisor = chunkHeader.getTicksPerSecond() / 1000_000_000L;
+        this.zoneOffet = zoneOfSet(rawOffset);
+    }
+
+    private ZoneOffset zoneOfSet(int rawOffset) {
+        try {
+            return ZoneOffset.ofTotalSeconds(rawOffset / 1000);
+        } catch (DateTimeException dte) {
+            Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Could not create ZoneOffset from raw offset " + rawOffset);
+        }
+        return ZoneOffset.UTC;
     }
 
     public long convertTimestamp(long ticks) {
@@ -48,4 +65,8 @@
     public long convertTimespan(long ticks) {
         return (long) (ticks / divisor);
     }
+
+    public ZoneOffset getZoneOffset() {
+        return zoneOffet;
+    }
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java	Wed Dec 05 16:40:12 2018 +0100
@@ -49,7 +49,7 @@
     private static final String OLD_OBJECT_CUTOFF = EVENT_NAME + "#" + Cutoff.NAME;
     private static final String OLD_OBJECT_ENABLED = EVENT_NAME + "#" + Enabled.NAME;
 
-    // Emit if old object is enabled in recoding with cutoff for that recording
+    // Emit if old object is enabled in recording with cutoff for that recording
     public static void emit(PlatformRecording recording) {
         if (isEnabled(recording)) {
             long nanos = CutoffSetting.parseValueSafe(recording.getSettings().get(OLD_OBJECT_CUTOFF));
@@ -59,7 +59,7 @@
     }
 
     // Emit if old object is enabled for at least one recording, and use the largest
-    // cutoff for an enabled recoding
+    // cutoff for an enabled recording
     public static void emit(List<PlatformRecording> recordings, Boolean pathToGcRoots) {
         boolean enabled = false;
         long cutoffNanos = Boolean.TRUE.equals(pathToGcRoots) ? Long.MAX_VALUE : 0L;
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java	Wed Dec 05 16:40:12 2018 +0100
@@ -27,6 +27,7 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -71,7 +72,7 @@
     private final String name;
     private final String superType;
     private final boolean constantPool;
-    private final ArrayList<ValueDescriptor> fields = new ArrayList<>();
+    private List<ValueDescriptor> fields = new ArrayList<>();
     private Boolean simpleType; // calculated lazy
     private boolean remove = true;
     private long id;
@@ -183,6 +184,10 @@
     }
 
     public List<ValueDescriptor> getFields() {
+        if (fields instanceof ArrayList) {
+            ((ArrayList<ValueDescriptor>) fields).trimToSize();
+            fields = Collections.unmodifiableList(fields);
+        }
         return fields;
     }
 
@@ -216,7 +221,7 @@
     }
 
     void trimFields() {
-        fields.trimToSize();
+        getFields();
     }
 
     void setAnnotations(List<AnnotationElement> annotations) {
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java	Wed Dec 05 16:40:12 2018 +0100
@@ -102,6 +102,9 @@
     }
 
     public static String formatBytes(long bytes, String separation) {
+        if (bytes == 1) {
+            return "1 byte";
+        }
         if (bytes < 1024) {
             return bytes + " bytes";
         }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java	Wed Dec 05 16:40:12 2018 +0100
@@ -161,7 +161,7 @@
         return chunkSize;
     }
 
-    public long getDuration() {
+    public long getDurationNanos() {
         return durationNanos;
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 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;
+import java.util.List;
+
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.jfr.internal.Type;
+
+public abstract class RecordingInternals {
+
+    public static RecordingInternals INSTANCE;
+
+    public abstract boolean isLastEventInChunk(RecordingFile file);
+
+    public abstract Object getOffsetDataTime(RecordedObject event, String name);
+
+    public abstract List<Type> readTypes(RecordingFile file) throws IOException;
+
+    public abstract void sort(List<RecordedEvent> events);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Assemble.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,127 @@
+/*
+ * 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.tool;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.channels.FileChannel;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+
+final class Assemble extends Command {
+
+    @Override
+    public String getName() {
+        return "assemble";
+    }
+
+    @Override
+    public List<String> getOptionSyntax() {
+        return Collections.singletonList("<repository> <file>");
+    }
+
+    @Override
+    public String getDescription() {
+        return "Assemble leftover chunks from a disk repository into a recording file";
+    }
+
+    @Override
+    public void displayOptionUsage(PrintStream stream) {
+        stream.println("  <repository>   Directory where the repository is located");
+        stream.println();
+        stream.println("  <file>         Name of the recording file (.jfr) to create");
+    }
+
+    @Override
+    public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
+        ensureMinArgumentCount(options, 2);
+        ensureMaxArgumentCount(options, 2);
+        Path repository = getDirectory(options.pop());
+
+        Path file = Paths.get(options.pop());
+        ensureFileDoesNotExist(file);
+        ensureJFRFile(file);
+
+        try (FileOutputStream fos = new FileOutputStream(file.toFile())) {
+            List<Path> files = listJFRFiles(repository);
+            if (files.isEmpty()) {
+                throw new UserDataException("no *.jfr files found at " + repository);
+            }
+            println();
+            println("Assembling files... ");
+            println();
+            transferTo(files, file, fos.getChannel());
+            println();
+            println("Finished.");
+        } catch (IOException e) {
+            throw new UserDataException("could not open destination file " + file + ". " + e.getMessage());
+        }
+    }
+
+    private List<Path> listJFRFiles(Path path) throws UserDataException {
+        try {
+            List<Path> files = new ArrayList<>();
+            if (Files.isDirectory(path)) {
+                try (DirectoryStream<Path> stream = Files.newDirectoryStream(path, "*.jfr")) {
+                    for (Path p : stream) {
+                        if (!Files.isDirectory(p) && Files.isReadable(p)) {
+                            files.add(p);
+                        }
+                    }
+                }
+            }
+            files.sort((u, v) -> u.getFileName().compareTo(v.getFileName()));
+            return files;
+        } catch (IOException ioe) {
+            throw new UserDataException("could not list *.jfr for directory " + path + ". " + ioe.getMessage());
+        }
+    }
+
+    private void transferTo(List<Path> sourceFiles, Path output, FileChannel out) throws UserDataException {
+        long pos = 0;
+        for (Path p : sourceFiles) {
+            println(" " + p.toString());
+            try (FileChannel sourceChannel = FileChannel.open(p)) {
+                long rem = Files.size(p);
+                while (rem > 0) {
+                    long n = Math.min(rem, 1024 * 1024);
+                    long w = out.transferFrom(sourceChannel, pos, n);
+                    pos += w;
+                    rem -= w;
+                }
+            } catch (IOException ioe) {
+                throw new UserDataException("could not copy recording chunk " + p + " to new file. " + ioe.getMessage());
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Command.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,306 @@
+/*
+ * 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.tool;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOError;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+
+abstract class Command {
+    public final static String title = "Tool for working with Flight Recorder files (.jfr)";
+    private final static Command HELP = new Help();
+    private final static List<Command> COMMANDS = createCommands();
+
+    private static List<Command> createCommands() {
+        List<Command> commands = new ArrayList<>();
+        commands.add(new Print());
+        commands.add(new Metadata());
+        commands.add(new Summary());
+        commands.add(new Assemble());
+        commands.add(new Disassemble());
+        commands.add(new Version());
+        commands.add(HELP);
+        return Collections.unmodifiableList(commands);
+    }
+
+    static void displayHelp() {
+        System.out.println(title);
+        System.out.println();
+        displayAvailableCommands(System.out);
+    }
+
+    abstract public String getName();
+
+    abstract public String getDescription();
+
+    abstract public void execute(Deque<String> argList) throws UserSyntaxException, UserDataException;
+
+    protected String getTitle() {
+        return getDescription();
+    }
+
+    static void displayAvailableCommands(PrintStream stream) {
+        boolean first = true;
+        for (Command c : Command.COMMANDS) {
+            if (!first) {
+                System.out.println();
+            }
+            displayCommand(stream, c);
+            stream.println("     " + c.getDescription());
+            first = false;
+        }
+    }
+
+    protected static void displayCommand(PrintStream stream, Command c) {
+        boolean firstSyntax = true;
+        String alias = buildAlias(c);
+        String initial = " jfr " + c.getName();
+        for (String syntaxLine : c.getOptionSyntax()) {
+            if (firstSyntax) {
+                if (syntaxLine.length() != 0) {
+                   stream.println(initial + " " + syntaxLine + alias);
+                } else {
+                   stream.println(initial + alias);
+                }
+            } else {
+                for (int i = 0; i < initial.length(); i++) {
+                    stream.print(" ");
+                }
+                stream.println(" " + syntaxLine);
+            }
+            firstSyntax = false;
+        }
+    }
+
+    private static String buildAlias(Command c) {
+        List<String> aliases = c.getAliases();
+        if (aliases.isEmpty()) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        if (aliases.size() == 1) {
+            sb.append(" (alias ");
+            sb.append(aliases.get(0));
+            sb.append(")");
+            return sb.toString();
+         }
+         sb.append(" (aliases ");
+         for (int i = 0; i< aliases.size(); i ++ ) {
+             sb.append(aliases.get(i));
+             if (i < aliases.size() -1) {
+                 sb.append(", ");
+             }
+         }
+         sb.append(")");
+         return sb.toString();
+    }
+
+    public static List<Command> getCommands() {
+        return COMMANDS;
+    }
+
+    public static Command valueOf(String commandName) {
+        for (Command command : COMMANDS) {
+            if (command.getName().equals(commandName)) {
+                return command;
+            }
+        }
+        return null;
+    }
+
+    public List<String> getOptionSyntax() {
+        return Collections.singletonList("");
+    }
+
+    public void displayOptionUsage(PrintStream stream) {
+    }
+
+    protected boolean acceptOption(Deque<String> options, String expected) throws UserSyntaxException {
+        if (expected.equals(options.peek())) {
+            if (options.size() < 2) {
+                throw new UserSyntaxException("missing value for " + options.peek());
+            }
+            options.remove();
+            return true;
+        }
+        return false;
+    }
+
+    protected void warnForWildcardExpansion(String option, String filter) throws UserDataException {
+        // Users should quote their wildcards to avoid expansion by the shell
+        try {
+            if (!filter.contains(File.pathSeparator)) {
+                Path p = Path.of(".", filter);
+                if (!Files.exists(p)) {
+                    return;
+                }
+            }
+            throw new UserDataException("wildcards should be quoted, for example " + option + " \"Foo*\"");
+        } catch (InvalidPathException ipe) {
+            // ignore
+        }
+    }
+
+    protected boolean acceptFilterOption(Deque<String> options, String expected) throws UserSyntaxException {
+        if (!acceptOption(options, expected)) {
+            return false;
+        }
+        if (options.isEmpty()) {
+            throw new UserSyntaxException("missing filter after " + expected);
+        }
+        String filter = options.peek();
+        if (filter.startsWith("--")) {
+            throw new UserSyntaxException("missing filter after " + expected);
+        }
+        return true;
+    }
+
+    final protected void ensureMaxArgumentCount(Deque<String> options, int maxCount) throws UserSyntaxException {
+        if (options.size() > maxCount) {
+            throw new UserSyntaxException("too many arguments");
+        }
+    }
+
+    final protected void ensureMinArgumentCount(Deque<String> options, int minCount) throws UserSyntaxException {
+        if (options.size() < minCount) {
+            throw new UserSyntaxException("too few arguments");
+        }
+    }
+
+    final protected Path getDirectory(String pathText) throws UserDataException {
+        try {
+            Path path = Paths.get(pathText).toAbsolutePath();
+            if (!Files.exists((path))) {
+                throw new UserDataException("directory does not exist, " + pathText);
+            }
+            if (!Files.isDirectory(path)) {
+                throw new UserDataException("path must be directory, " + pathText);
+            }
+            return path;
+        } catch (InvalidPathException ipe) {
+            throw new UserDataException("invalid path '" + pathText + "'");
+        }
+    }
+
+    final protected Path getJFRInputFile(Deque<String> options) throws UserSyntaxException, UserDataException {
+        if (options.isEmpty()) {
+            throw new UserSyntaxException("missing file");
+        }
+        String file = options.removeLast();
+        if (file.startsWith("--")) {
+            throw new UserSyntaxException("missing file");
+        }
+        try {
+            Path path = Paths.get(file).toAbsolutePath();
+            ensureAccess(path);
+            ensureJFRFile(path);
+            return path;
+        } catch (IOError ioe) {
+            throw new UserDataException("i/o error reading file '" + file + "', " + ioe.getMessage());
+        } catch (InvalidPathException ipe) {
+            throw new UserDataException("invalid path '" + file + "'");
+        }
+    }
+
+    private void ensureAccess(Path path) throws UserDataException {
+        try (RandomAccessFile rad = new RandomAccessFile(path.toFile(), "r")) {
+            if (rad.length() == 0) {
+                throw new UserDataException("file is empty '" + path + "'");
+            }
+            rad.read(); // try to read 1 byte
+        } catch (FileNotFoundException e) {
+            throw new UserDataException("could not find file '" + path + "'");
+        } catch (IOException e) {
+            throw new UserDataException("i/o error reading file '" + path + "', " + e.getMessage());
+        }
+    }
+
+    final protected void couldNotReadError(Path p, IOException e) throws UserDataException {
+        throw new UserDataException("could not read recording at " + p.toAbsolutePath() + ". " + e.getMessage());
+    }
+
+    final protected Path ensureFileDoesNotExist(Path file) throws UserDataException {
+        if (Files.exists(file)) {
+            throw new UserDataException("file '" + file + "' already exists");
+        }
+        return file;
+    }
+
+    final protected void ensureJFRFile(Path path) throws UserDataException {
+        if (!path.toString().endsWith(".jfr")) {
+            throw new UserDataException("filename must end with '.jfr'");
+        }
+    }
+
+    protected void displayUsage(PrintStream stream) {
+        displayCommand(stream, this);
+        stream.println();
+        displayOptionUsage(stream);
+    }
+
+    final protected void println() {
+        System.out.println();
+    }
+
+    final protected void print(String text) {
+        System.out.print(text);
+    }
+
+    final protected void println(String text) {
+        System.out.println(text);
+    }
+
+    final protected boolean matches(String command) {
+        for (String s : getNames()) {
+            if (s.equals(command)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected List<String> getAliases() {
+        return Collections.emptyList();
+    }
+
+    public List<String> getNames() {
+        List<String> names = new ArrayList<>();
+        names.add(getName());
+        names.addAll(getAliases());
+        return names;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Disassemble.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,250 @@
+/*
+ * 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.tool;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+
+import jdk.jfr.internal.consumer.ChunkHeader;
+import jdk.jfr.internal.consumer.RecordingInput;
+
+final class Disassemble extends Command {
+
+    @Override
+    public String getName() {
+        return "disassemble";
+    }
+
+    @Override
+    public List<String> getOptionSyntax() {
+        List<String> list = new ArrayList<>();
+        list.add("[--output <directory>]");
+        list.add("[--max-chunks <chunks>]");
+        list.add("[--max-size <size>]");
+        list.add("<file>");
+        return list;
+    }
+
+    @Override
+    public void displayOptionUsage(PrintStream stream) {
+        stream.println(" --output <directory>    The location to write the disassembled file,");
+        stream.println("                         by default the current directory");
+        stream.println("");
+        stream.println(" --max-chunks <chunks>   Maximum number of chunks per disassembled file,");
+        stream.println("                         by default 5. The chunk size varies, but is ");
+        stream.println("                         typically around 15 MB.");
+        stream.println("");
+        stream.println(" --max-size <size>       Maximum number of bytes per file.");
+        stream.println("");
+        stream.println("  <file>                 Location of the recording file (.jfr)");
+    }
+
+    @Override
+    public String getDescription() {
+        return "Disassamble a recording file into smaller files/chunks";
+    }
+
+    @Override
+    public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
+        if (options.isEmpty()) {
+            throw new UserSyntaxException("missing file");
+        }
+        Path file = getJFRInputFile(options);
+        int maxChunks = Integer.MAX_VALUE;
+        int maxsize = Integer.MAX_VALUE;
+        String output = System.getProperty("user.dir");
+        int optionCount = options.size();
+        while (optionCount > 0) {
+            if (acceptOption(options, "--output")) {
+                output = options.pop();
+            }
+            if (acceptOption(options, "--max-size")) {
+                String value = options.pop();
+                try {
+                    maxsize = Integer.parseInt(value);
+                    if (maxsize < 1) {
+                        throw new UserDataException("max size must be at least 1");
+                    }
+                } catch (NumberFormatException nfe) {
+                    throw new UserDataException("not a valid value for --max-size.");
+                }
+            }
+            if (acceptOption(options, "--max-chunks")) {
+                String value = options.pop();
+                try {
+                    maxChunks = Integer.parseInt(value);
+                    if (maxChunks < 1) {
+                        throw new UserDataException("max chunks must be at least 1.");
+                    }
+                } catch (NumberFormatException nfe) {
+                    throw new UserDataException("not a valid value for --max-size.");
+                }
+            }
+            if (optionCount == options.size()) {
+                // No progress made
+                throw new UserSyntaxException("unknown option " + options.peek());
+            }
+            optionCount = options.size();
+        }
+        Path outputPath = getDirectory(output);
+
+        println();
+        println("Examining recording " + file + " ...");
+        List<Long> sizes;
+        if (maxsize != Integer.MAX_VALUE && maxChunks == Integer.MAX_VALUE) {
+            try {
+                long fileSize = Files.size(file);
+                if (maxsize >=fileSize) {
+                    println();
+                    println("File size (" + fileSize +") does not exceed max size (" + maxsize + ")");
+                    return;
+                }
+            } catch (IOException e) {
+                throw new UserDataException("unexpected i/o error when determining file size" + e.getMessage());
+            }
+        }
+        if (maxsize == Integer.MAX_VALUE && maxChunks == Integer.MAX_VALUE) {
+            maxChunks = 5;
+        }
+
+        try {
+            sizes = findChunkSizes(file);
+        } catch (IOException e) {
+            throw new UserDataException("unexpected i/o error. " + e.getMessage());
+        }
+        if (maxsize == Integer.MAX_VALUE == sizes.size() <= maxChunks) {
+            throw new UserDataException("number of chunks in recording (" + sizes.size() + ") doesn't exceed max chunks (" + maxChunks + ")");
+        }
+        println();
+        if (sizes.size() > 0) {
+            List<Long> combinedSizes = combineChunkSizes(sizes, maxChunks, maxsize);
+            print("File consists of " + sizes.size() + " chunks. The recording will be split into ");
+            println(combinedSizes.size() + " files");
+            println();
+            splitFile(outputPath, file, combinedSizes);
+        } else {
+            throw new UserDataException("no JFR chunks found in file.");
+        }
+    }
+
+    private List<Long> findChunkSizes(Path p) throws IOException {
+        try (RecordingInput input = new RecordingInput(p.toFile())) {
+            List<Long> sizes = new ArrayList<>();
+            ChunkHeader ch = new ChunkHeader(input);
+            sizes.add(ch.getSize());
+            while (!ch.isLastChunk()) {
+                ch = ch.nextHeader();
+                sizes.add(ch.getSize());
+            }
+            return sizes;
+        }
+    }
+
+    private List<Long> combineChunkSizes(List<Long> sizes, int maxChunks, long maxSize) {
+        List<Long> reduced = new ArrayList<Long>();
+        int chunks = 1;
+        long fileSize = sizes.get(0);
+        for (int i = 1; i < sizes.size(); i++) {
+            long size = sizes.get(i);
+            if (fileSize + size > maxSize) {
+                reduced.add(fileSize);
+                chunks = 1;
+                fileSize = size;
+                continue;
+            }
+            fileSize += size;
+            if (chunks == maxChunks) {
+                reduced.add(fileSize);
+                fileSize = 0;
+                chunks = 1;
+                continue;
+            }
+            chunks++;
+        }
+        if (fileSize != 0) {
+            reduced.add(fileSize);
+        }
+        return reduced;
+    }
+
+    private void splitFile(Path directory, Path file, List<Long> splitPositions) throws UserDataException {
+        int padAmountZeros = String.valueOf(splitPositions.size() - 1).length();
+        String fileName = file.getFileName().toString();
+        String fileFormatter = fileName.subSequence(0, fileName.length() - 4) + "_%0" + padAmountZeros + "d.jfr";
+        for (int i = 0; i < splitPositions.size(); i++) {
+            String formattedFilename = String.format(fileFormatter, i);
+            try {
+                Path p = directory.resolve(formattedFilename);
+                if (Files.exists(p)) {
+                    throw new UserDataException("can't create disassembled file " + p + ", a file with that name already exist");
+                }
+            } catch (InvalidPathException ipe) {
+                throw new UserDataException("can't construct path with filename" + formattedFilename);
+            }
+        }
+
+        try (DataInputStream stream = new DataInputStream(new BufferedInputStream(new FileInputStream(file.toFile())))) {
+            for (int i = 0; i < splitPositions.size(); i++) {
+                Long l = splitPositions.get(i);
+                byte[] bytes = readBytes(stream, l.intValue());
+                String formattedFilename = String.format(fileFormatter, i);
+                Path p = directory.resolve(formattedFilename);
+                File splittedFile = p.toFile();
+                println("Writing " + splittedFile + " ... " + bytes.length);
+                FileOutputStream fos = new FileOutputStream(splittedFile);
+                fos.write(bytes);
+                fos.close();
+            }
+        } catch (IOException ioe) {
+            throw new UserDataException("i/o error writing file " + file);
+        }
+    }
+
+    private byte[] readBytes(InputStream stream, int count) throws UserDataException, IOException {
+        byte[] data = new byte[count];
+        int totalRead = 0;
+        while (totalRead < data.length) {
+            int read = stream.read(data, totalRead, data.length - totalRead);
+            if (read == -1) {
+                throw new UserDataException("unexpected end of data");
+            }
+            totalRead += read;
+        }
+        return data;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 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.tool;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import jdk.jfr.EventType;
+import jdk.jfr.Timespan;
+import jdk.jfr.Timestamp;
+import jdk.jfr.ValueDescriptor;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.jfr.internal.consumer.RecordingInternals;
+
+abstract class EventPrintWriter extends StructuredWriter {
+
+    enum ValueType {
+        TIMESPAN, TIMESTAMP, OTHER
+    }
+
+    protected static final String STACK_TRACE_FIELD = "stackTrace";
+    protected static final String EVENT_THREAD_FIELD = "eventThread";
+
+    private Predicate<EventType> eventFilter = x -> true;
+    private int stackDepth;
+
+    // cach that will speed up annotation lookup
+    private Map<ValueDescriptor, ValueType> typeOfValues = new HashMap<>();
+
+    EventPrintWriter(PrintWriter p) {
+        super(p);
+    }
+
+    abstract protected void print(List<RecordedEvent> events);
+
+    void print(Path source) throws FileNotFoundException, IOException {
+        List<RecordedEvent> events = new ArrayList<>(500_000);
+        printBegin();
+        try (RecordingFile file = new RecordingFile(source)) {
+            while (file.hasMoreEvents()) {
+                RecordedEvent event = file.readEvent();
+                if (acceptEvent(event)) {
+                    events.add(event);
+                }
+                if (RecordingInternals.INSTANCE.isLastEventInChunk(file)) {
+                    RecordingInternals.INSTANCE.sort(events);
+                    print(events);
+                    events.clear();
+                }
+            }
+        }
+        printEnd();
+        flush(true);
+    }
+
+    protected void printEnd() {
+    }
+
+    protected void printBegin() {
+    }
+
+    public final void setEventFilter(Predicate<EventType> eventFilter) {
+        this.eventFilter = eventFilter;
+    }
+
+    protected final boolean acceptEvent(RecordedEvent event) {
+        return eventFilter.test(event.getEventType());
+    }
+
+    protected final int getStackDepth() {
+        return stackDepth;
+    }
+
+    protected final boolean isLateField(String name) {
+        return name.equals(EVENT_THREAD_FIELD) || name.equals(STACK_TRACE_FIELD);
+    }
+
+    public void setStackDepth(int stackDepth) {
+        this.stackDepth = stackDepth;
+    }
+
+    protected Object getValue(RecordedObject object, ValueDescriptor v) {
+        ValueType valueType = typeOfValues.get(v);
+        if (valueType == null) {
+            valueType = determineValueType(v);
+            typeOfValues.put(v, valueType);
+        }
+        switch (valueType) {
+        case TIMESPAN:
+            return object.getDuration(v.getName());
+        case TIMESTAMP:
+            return RecordingInternals.INSTANCE.getOffsetDataTime(object, v.getName());
+        default:
+            return object.getValue(v.getName());
+        }
+    }
+    // It's expensive t check
+    private ValueType determineValueType(ValueDescriptor v) {
+        if (v.getAnnotation(Timespan.class) != null) {
+            return ValueType.TIMESPAN;
+        }
+        if (v.getAnnotation(Timestamp.class) != null) {
+            return ValueType.TIMESTAMP;
+        }
+        return ValueType.OTHER;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Help.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,75 @@
+/*
+ * 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.tool;
+
+import java.io.PrintStream;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+
+final class Help extends Command {
+
+    @Override
+    public String getName() {
+        return "help";
+    }
+
+    @Override
+    public List<String> getOptionSyntax() {
+        return Collections.singletonList("[<command>]");
+    }
+
+    protected List<String> getAliases() {
+        return List.of("--help", "-h", "-?");
+    }
+
+    @Override
+    public void displayOptionUsage(PrintStream stream) {
+        println("  <command>   The name of the command to get help for");
+    }
+
+    @Override
+    public String getDescription() {
+        return "Display all available commands, or help about a specific command";
+    }
+
+    @Override
+    public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
+        if (options.isEmpty()) {
+            Command.displayHelp();
+            return;
+        }
+        ensureMaxArgumentCount(options, 1);
+        String commandName = options.remove();
+        Command c = Command.valueOf(commandName);
+        if (c == null) {
+            throw new UserDataException("unknown command '" + commandName + "'");
+        }
+        println(c.getTitle());
+        println();
+        c.displayUsage(System.out);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/JSONWriter.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,261 @@
+/*
+ * 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.tool;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+import jdk.jfr.EventType;
+import jdk.jfr.ValueDescriptor;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedFrame;
+import jdk.jfr.consumer.RecordedObject;
+
+final class JSONWriter extends EventPrintWriter {
+
+    private boolean first = true;
+
+    public JSONWriter(PrintWriter writer) {
+        super(writer);
+    }
+
+    @Override
+    protected void printBegin() {
+        printObjectBegin();
+        printDataStructureName("recording");
+        printObjectBegin();
+        printDataStructureName("events");
+        printArrayBegin();
+    }
+
+    @Override
+    protected void print(List<RecordedEvent> events) {
+        for (RecordedEvent event : events) {
+            printNewDataStructure(first, true, null);
+            printEvent(event);
+            flush(false);
+            first = false;
+        }
+    }
+
+    @Override
+    protected void printEnd() {
+        printArrayEnd();;
+        printObjectEnd();
+        printObjectEnd();
+    }
+
+    private void printEvent(RecordedEvent event) {
+        printObjectBegin();
+        EventType type = event.getEventType();
+        printValue(true, false, "type", type.getName());
+        printNewDataStructure(false, false, "values");
+        printObjectBegin();
+        boolean first = true;
+        for (ValueDescriptor v : event.getFields()) {
+            printValueDescriptor(first, false, v, getValue(event, v));
+            first = false;
+        }
+        printObjectEnd();
+        printObjectEnd();
+    }
+
+    void printValue(boolean first, boolean arrayElement, String name, Object value) {
+        printNewDataStructure(first, arrayElement, name);
+        if (!printIfNull(value)) {
+            if (value instanceof Boolean) {
+                printAsString(value);
+                return;
+            }
+            if (value instanceof Double) {
+                Double dValue = (Double) value;
+                if (Double.isNaN(dValue) || Double.isInfinite(dValue)) {
+                    printNull();
+                    return;
+                }
+                printAsString(value);
+                return;
+            }
+            if (value instanceof Float) {
+                Float fValue = (Float) value;
+                if (Float.isNaN(fValue) || Float.isInfinite(fValue)) {
+                    printNull();
+                    return;
+                }
+                printAsString(value);
+                return;
+            }
+            if (value instanceof Number) {
+                printAsString(value);
+                return;
+            }
+            print("\"");
+            printEscaped(String.valueOf(value));
+            print("\"");
+        }
+    }
+
+    public void printObject(RecordedObject object) {
+        printObjectBegin();
+        boolean first = true;
+        for (ValueDescriptor v : object.getFields()) {
+            printValueDescriptor(first, false, v, getValue(object, v));
+            first = false;
+        }
+        printObjectEnd();
+    }
+
+    private void printArray(ValueDescriptor v, Object[] array) {
+        printArrayBegin();
+        boolean first = true;
+        int depth = 0;
+        for (Object arrayElement : array) {
+            if (!(arrayElement instanceof RecordedFrame) || depth < getStackDepth()) {
+                printValueDescriptor(first, true, v, arrayElement);
+            }
+            depth++;
+            first = false;
+        }
+        printArrayEnd();
+    }
+
+    private void printValueDescriptor(boolean first, boolean arrayElement, ValueDescriptor vd, Object value) {
+        if (vd.isArray() && !arrayElement) {
+            printNewDataStructure(first, arrayElement, vd.getName());
+            if (!printIfNull(value)) {
+                printArray(vd, (Object[]) value);
+            }
+            return;
+        }
+        if (!vd.getFields().isEmpty()) {
+            printNewDataStructure(first, arrayElement, vd.getName());
+            if (!printIfNull(value)) {
+                printObject((RecordedObject) value);
+            }
+            return;
+        }
+        printValue(first, arrayElement, vd.getName(), value);
+    }
+
+    private void printNewDataStructure(boolean first, boolean arrayElement, String name) {
+        if (!first) {
+            print(", ");
+            if (!arrayElement) {
+                println();
+            }
+        }
+        if (!arrayElement) {
+            printDataStructureName(name);
+        }
+    }
+
+    private boolean printIfNull(Object value) {
+        if (value == null) {
+            printNull();
+            return true;
+        }
+        return false;
+    }
+
+    private void printNull() {
+        print("null");
+    }
+
+    private void printDataStructureName(String text) {
+        printIndent();
+        print("\"");
+        print(text);
+        print("\": ");
+    }
+
+    private void printObjectEnd() {
+        retract();
+        println();
+        printIndent();
+        print("}");
+    }
+
+    private void printObjectBegin() {
+        println("{");
+        indent();
+    }
+
+    private void printArrayEnd() {
+        print("]");
+    }
+
+    private void printArrayBegin() {
+        print("[");
+    }
+
+    private void printEscaped(String text) {
+        for (int i = 0; i < text.length(); i++) {
+            printEscaped(text.charAt(i));
+        }
+    }
+
+    private void printEscaped(char c) {
+        if (c == '\b') {
+            print("\\b");
+            return;
+        }
+        if (c == '\n') {
+            print("\\n");
+            return;
+        }
+        if (c == '\t') {
+            print("\\t");
+            return;
+        }
+        if (c == '\f') {
+            print("\\f");
+            return;
+        }
+        if (c == '\r') {
+            print("\\r");
+            return;
+        }
+        if (c == '\"') {
+            print("\\\"");
+            return;
+        }
+        if (c == '\\') {
+            print("\\\\");
+            return;
+        }
+        if (c == '/') {
+            print("\\/");
+            return;
+        }
+        if (c > 0x7F || c < 32) {
+            print("\\u");
+            // 0x10000 will pad with zeros.
+            print(Integer.toHexString(0x10000 + (int) c).substring(1));
+            return;
+        }
+        print(c);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Main.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,110 @@
+/*
+ * 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.tool;
+
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.LinkedList;
+
+/**
+ * Launcher class for the JDK_HOME\bin\jfr tool
+ *
+ */
+public final class Main {
+
+    private static final int EXIT_OK = 0;
+    private static final int EXIT_FAILED = 1;
+    private static final int EXIT_WRONG_ARGUMENTS = 2;
+
+    public static void main(String... args) {
+        Deque<String> argList = new LinkedList<>(Arrays.asList(args));
+        if (argList.isEmpty()) {
+            System.out.println(Command.title);
+            System.out.println();
+            System.out.println("Before using this tool, you must have a recording file.");
+            System.out.println("A file can be created by starting a recording from command line:");
+            System.out.println();
+            System.out.println(" java -XX:StartFlightRecording:filename=recording.jfr,duration=30s ... ");
+            System.out.println();
+            System.out.println("A recording can also be started on already running Java Virtual Machine:");
+            System.out.println();
+            System.out.println(" jcmd (to list available pids)");
+            System.out.println(" jcmd <pid> JFR.start");
+            System.out.println();
+            System.out.println("Recording data can be dumped to file using the JFR.dump command:");
+            System.out.println();
+            System.out.println(" jcmd <pid> JFR.dump filename=recording.jfr");
+            System.out.println();
+            System.out.println("The contents of the recording can then be printed, for example:");
+            System.out.println();
+            System.out.println(" jfr print recording.jfr");
+            System.out.println();
+            System.out.println(" jfr print --events CPULoad,GarbageCollection recording.jfr");
+            System.out.println();
+            System.out.println(" jfr print --json --events CPULoad recording.jfr");
+            System.out.println();
+            System.out.println(" jfr print --categories \"GC,JVM,Java*\" recording.jfr");
+            System.out.println();
+            System.out.println(" jfr print --events \"jdk.*\" --stack-depth 64 recording.jfr");
+            System.out.println();
+            System.out.println(" jfr summary recording.jfr");
+            System.out.println();
+            System.out.println(" jfr metadata recoding.jfr");
+            System.out.println();
+            System.out.println("For more information about available commands, use 'jfr help'");
+            System.exit(EXIT_OK);
+        }
+        String command = argList.remove();
+        for (Command c : Command.getCommands()) {
+            if (c.matches(command)) {
+                try {
+                    c.execute(argList);
+                    System.exit(EXIT_OK);
+                } catch (UserDataException ude) {
+                    System.err.println("jfr " + c.getName() + ": " + ude.getMessage());
+                    System.exit(EXIT_FAILED);
+                } catch (UserSyntaxException use) {
+                    System.err.println("jfr " + c.getName() + ": " + use.getMessage());
+                    System.err.println();
+                    System.err.println("Usage:");
+                    System.err.println();
+                    c.displayUsage(System.err);
+                    System.exit(EXIT_WRONG_ARGUMENTS);
+                } catch (Throwable e) {
+                    System.err.println("jfr " + c.getName() + ": unexpected internal error, " + e.getMessage());
+                    e.printStackTrace();
+                    System.exit(EXIT_FAILED);
+                }
+            }
+        }
+        System.err.println("jfr: unknown command '" + command + "'");
+        System.err.println();
+        System.err.println("List of available commands:");
+        System.err.println();
+        Command.displayAvailableCommands(System.err);
+        System.exit(EXIT_WRONG_ARGUMENTS);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Metadata.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 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.tool;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Deque;
+import java.util.List;
+
+import jdk.jfr.consumer.RecordingFile;
+import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.RecordingInternals;
+
+final class Metadata extends Command {
+
+    private static class TypeComparator implements Comparator<Type> {
+
+        @Override
+        public int compare(Type t1, Type t2) {
+            int g1 = groupValue(t1);
+            int g2 = groupValue(t2);
+            if (g1 == g2) {
+                String n1 = t1.getName();
+                String n2 = t2.getName();
+                String package1 = n1.substring(0, n1.lastIndexOf('.') + 1);
+                String package2 = n2.substring(0, n2.lastIndexOf('.') + 1);
+
+                if (package1.equals(package2)) {
+                    return n1.compareTo(n2);
+                } else {
+                    // Ensure that jdk.* are printed first
+                    // This makes it easier to find user defined events at the end.
+                    if (Type.SUPER_TYPE_EVENT.equals(t1.getSuperType()) && !package1.equals(package2)) {
+                        if (package1.equals("jdk.jfr")) {
+                            return -1;
+                        }
+                        if (package2.equals("jdk.jfr")) {
+                            return 1;
+                        }
+                    }
+                    return package1.compareTo(package2);
+                }
+            } else {
+                return Integer.compare(groupValue(t1), groupValue(t2));
+            }
+        }
+
+        int groupValue(Type t) {
+            String superType = t.getSuperType();
+            if (superType == null) {
+                return 1;
+            }
+            if (Type.SUPER_TYPE_ANNOTATION.equals(superType)) {
+                return 3;
+            }
+            if (Type.SUPER_TYPE_SETTING.equals(superType)) {
+                return 4;
+            }
+            if (Type.SUPER_TYPE_EVENT.equals(superType)) {
+                return 5;
+            }
+            return 2; // reserved for enums in the future
+        }
+    }
+
+    @Override
+    public String getName() {
+        return "metadata";
+    }
+
+    @Override
+    public List<String> getOptionSyntax() {
+        return Collections.singletonList("<file>");
+    }
+
+    @Override
+    public String getDescription() {
+        return "Display event metadata, such as labels, descriptions and field layout";
+    }
+
+    @Override
+    public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
+        Path file = getJFRInputFile(options);
+
+        boolean showIds = false;
+        int optionCount = options.size();
+        while (optionCount > 0) {
+            if (acceptOption(options, "--ids")) {
+                showIds = true;
+            }
+            if (optionCount == options.size()) {
+                // No progress made
+                throw new UserSyntaxException("unknown option " + options.peek());
+            }
+            optionCount = options.size();
+        }
+
+        try (PrintWriter pw = new PrintWriter(System.out)) {
+            PrettyWriter prettyWriter = new PrettyWriter(pw);
+            prettyWriter.setShowIds(showIds);
+            try (RecordingFile rf = new RecordingFile(file)) {
+                List<Type> types = RecordingInternals.INSTANCE.readTypes(rf);
+                Collections.sort(types, new TypeComparator());
+                for (Type type : types) {
+                    prettyWriter.printType(type);
+                }
+                prettyWriter.flush(true);
+            } catch (IOException ioe) {
+                couldNotReadError(file, ioe);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,501 @@
+/*
+ * 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.tool;
+
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+import jdk.jfr.AnnotationElement;
+import jdk.jfr.DataAmount;
+import jdk.jfr.Frequency;
+import jdk.jfr.MemoryAddress;
+import jdk.jfr.Percentage;
+import jdk.jfr.ValueDescriptor;
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedClassLoader;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedFrame;
+import jdk.jfr.consumer.RecordedMethod;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordedStackTrace;
+import jdk.jfr.consumer.RecordedThread;
+import jdk.jfr.internal.PrivateAccess;
+import jdk.jfr.internal.Type;
+import jdk.jfr.internal.Utils;
+
+/**
+ * Print events in a human-readable format.
+ *
+ * This class is also used by {@link RecordedObject#toString()}
+ */
+public final class PrettyWriter extends EventPrintWriter {
+    private final static DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
+    private final static Long ZERO = 0L;
+    private boolean showIds;
+
+    public PrettyWriter(PrintWriter destination) {
+        super(destination);
+    }
+
+    @Override
+    protected void print(List<RecordedEvent> events) {
+        for (RecordedEvent e : events) {
+            print(e);
+            flush(false);
+        }
+    }
+
+    public void printType(Type t) {
+        if (showIds) {
+            print("// id: ");
+            println(String.valueOf(t.getId()));
+        }
+        int commentIndex = t.getName().length() + 10;
+        String typeName = t.getName();
+        int index = typeName.lastIndexOf(".");
+        if (index != -1) {
+            println("@Name(\"" + typeName + "\")");
+        }
+        printAnnotations(commentIndex, t.getAnnotationElements());
+        print("class " + typeName.substring(index + 1));
+        String superType = t.getSuperType();
+        if (superType != null) {
+            print(" extends " + superType);
+        }
+        println(" {");
+        indent();
+        boolean first = true;
+        for (ValueDescriptor v : t.getFields()) {
+            printField(commentIndex, v, first);
+            first = false;
+        }
+        retract();
+        println("}");
+        println();
+    }
+
+    private void printField(int commentIndex, ValueDescriptor v, boolean first) {
+        if (!first) {
+            println();
+        }
+        printAnnotations(commentIndex, v.getAnnotationElements());
+        printIndent();
+        Type vType = PrivateAccess.getInstance().getType(v);
+        if (Type.SUPER_TYPE_SETTING.equals(vType.getSuperType())) {
+            print("static ");
+        }
+        print(makeSimpleType(v.getTypeName()));
+        if (v.isArray()) {
+            print("[]");
+        }
+        print(" ");
+        print(v.getName());
+        print(";");
+        printCommentRef(commentIndex, v.getTypeId());
+    }
+
+    private void printCommentRef(int commentIndex, long typeId) {
+        if (showIds) {
+            int column = getColumn();
+            if (column > commentIndex) {
+                print("  ");
+            } else {
+                while (column < commentIndex) {
+                    print(" ");
+                    column++;
+                }
+            }
+            println(" // id=" + typeId);
+        } else {
+            println();
+        }
+    }
+
+    private void printAnnotations(int commentIndex, List<AnnotationElement> annotations) {
+        for (AnnotationElement a : annotations) {
+            printIndent();
+            print("@");
+            print(makeSimpleType(a.getTypeName()));
+            List<ValueDescriptor> vs = a.getValueDescriptors();
+            if (!vs.isEmpty()) {
+                printAnnotation(a);
+                printCommentRef(commentIndex, a.getTypeId());
+            } else {
+                println();
+            }
+        }
+    }
+
+    private void printAnnotation(AnnotationElement a) {
+        StringJoiner sj = new StringJoiner(", ", "(", ")");
+        List<ValueDescriptor> vs = a.getValueDescriptors();
+        for (ValueDescriptor v : vs) {
+            Object o = a.getValue(v.getName());
+            if (vs.size() == 1 && v.getName().equals("value")) {
+                sj.add(textify(o));
+            } else {
+                sj.add(v.getName() + "=" + textify(o));
+            }
+        }
+        print(sj.toString());
+    }
+
+    private String textify(Object o) {
+        if (o.getClass().isArray()) {
+            Object[] array = (Object[]) o;
+            if (array.length == 1) {
+                return quoteIfNeeded(array[0]);
+            }
+            StringJoiner s = new StringJoiner(", ", "{", "}");
+            for (Object ob : array) {
+                s.add(quoteIfNeeded(ob));
+            }
+            return s.toString();
+        } else {
+            return quoteIfNeeded(o);
+        }
+    }
+
+    private String quoteIfNeeded(Object o) {
+        if (o instanceof String) {
+            return "\"" + o + "\"";
+        } else {
+            return String.valueOf(o);
+        }
+    }
+
+    private String makeSimpleType(String typeName) {
+        int index = typeName.lastIndexOf(".");
+        return typeName.substring(index + 1);
+    }
+
+    public void print(RecordedEvent event) {
+        print(event.getEventType().getName(), " ");
+        println("{");
+        indent();
+        for (ValueDescriptor v : event.getFields()) {
+            String name = v.getName();
+            if (!isZeroDuration(event, name) && !isLateField(name)) {
+                printFieldValue(event, v);
+            }
+        }
+        if (event.getThread() != null) {
+            printIndent();
+            print(EVENT_THREAD_FIELD + " = ");
+            printThread(event.getThread(), "");
+        }
+        if (event.getStackTrace() != null) {
+            printIndent();
+            print(STACK_TRACE_FIELD + " = ");
+            printStackTrace(event.getStackTrace());
+        }
+        retract();
+        printIndent();
+        println("}");
+        println();
+    }
+
+    private boolean isZeroDuration(RecordedEvent event, String name) {
+        return name.equals("duration") && ZERO.equals(event.getValue("duration"));
+    }
+
+    private void printStackTrace(RecordedStackTrace stackTrace) {
+        println("[");
+        List<RecordedFrame> frames = stackTrace.getFrames();
+        indent();
+        int i = 0;
+        while (i < frames.size() && i < getStackDepth()) {
+            RecordedFrame frame = frames.get(i);
+            if (frame.isJavaFrame()) {
+                printIndent();
+                printValue(frame, null, "");
+                println();
+                i++;
+            }
+        }
+        if (stackTrace.isTruncated() || i == getStackDepth()) {
+            printIndent();
+            println("...");
+        }
+        retract();
+        printIndent();
+        println("]");
+    }
+
+    public void print(RecordedObject struct, String postFix) {
+        println("{");
+        indent();
+        for (ValueDescriptor v : struct.getFields()) {
+            printFieldValue(struct, v);
+        }
+        retract();
+        printIndent();
+        println("}" + postFix);
+    }
+
+    private void printFieldValue(RecordedObject struct, ValueDescriptor v) {
+        printIndent();
+        print(v.getName(), " = ");
+        printValue(getValue(struct, v), v, "");
+    }
+
+    private void printArray(Object[] array) {
+        println("[");
+        indent();
+        for (int i = 0; i < array.length; i++) {
+            printIndent();
+            printValue(array[i], null, i + 1 < array.length ? ", " : "");
+        }
+        retract();
+        printIndent();
+        println("]");
+    }
+
+    private void printValue(Object value, ValueDescriptor field, String postFix) {
+        if (value == null) {
+            println("null" + postFix);
+            return;
+        }
+        if (value instanceof RecordedObject) {
+            if (value instanceof RecordedThread) {
+                printThread((RecordedThread) value, postFix);
+                return;
+            }
+            if (value instanceof RecordedClass) {
+                printClass((RecordedClass) value, postFix);
+                return;
+            }
+            if (value instanceof RecordedClassLoader) {
+                printClassLoader((RecordedClassLoader) value, postFix);
+                return;
+            }
+            if (value instanceof RecordedFrame) {
+                RecordedFrame frame = (RecordedFrame) value;
+                if (frame.isJavaFrame()) {
+                    printJavaFrame((RecordedFrame) value, postFix);
+                    return;
+                }
+            }
+            if (value instanceof RecordedMethod) {
+                println(formatMethod((RecordedMethod) value));
+                return;
+            }
+            print((RecordedObject) value, postFix);
+            return;
+        }
+        if (value.getClass().isArray()) {
+            printArray((Object[]) value);
+            return;
+        }
+        if (field.getContentType() != null) {
+            if (printFormatted(field, value)) {
+                return;
+            }
+        }
+        String text = String.valueOf(value);
+        if (value instanceof String) {
+            text = "\"" + text + "\"";
+        }
+        println(text);
+    }
+
+    private void printClassLoader(RecordedClassLoader cl, String postFix) {
+        // Purposely not printing class loader name to avoid cluttered output
+        RecordedClass clazz = cl.getType();
+        print(clazz == null ? "null" : clazz.getName());
+        if (clazz != null) {
+            print(" (");
+            print("id = ");
+            print(String.valueOf(cl.getId()));
+            println(")");
+        }
+    }
+
+    private void printJavaFrame(RecordedFrame f, String postFix) {
+        print(formatMethod(f.getMethod()));
+        int line = f.getLineNumber();
+        if (line >= 0) {
+            print(" line: " + line);
+        }
+        print(postFix);
+    }
+
+    private String formatMethod(RecordedMethod m) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(m.getType().getName());
+        sb.append(".");
+        sb.append(m.getName());
+        sb.append("(");
+        StringJoiner sj = new StringJoiner(", ");
+        String md = m.getDescriptor().replace("/", ".");
+        String parameter = md.substring(1, md.lastIndexOf(")"));
+        for (String qualifiedName : decodeDescriptors(parameter)) {
+            String typeName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
+            sj.add(typeName);
+        }
+        sb.append(sj);
+        sb.append(")");
+        return sb.toString();
+    }
+
+    private void printClass(RecordedClass clazz, String postFix) {
+        RecordedClassLoader classLoader = clazz.getClassLoader();
+        String classLoaderName = "null";
+        if (classLoader != null) {
+            if (classLoader.getName() != null) {
+                classLoaderName = classLoader.getName();
+            } else {
+                classLoaderName = classLoader.getType().getName();
+            }
+        }
+        String className = clazz.getName();
+        if (className.startsWith("[")) {
+            className = decodeDescriptors(className).get(0);
+        }
+        println(className + " (classLoader = " + classLoaderName + ")" + postFix);
+    }
+
+    List<String> decodeDescriptors(String descriptor) {
+        List<String> descriptors = new ArrayList<>();
+        for (int index = 0; index < descriptor.length(); index++) {
+            String arrayBrackets = "";
+            while (descriptor.charAt(index) == '[') {
+                arrayBrackets += "[]";
+                index++;
+            }
+            char c = descriptor.charAt(index);
+            String type;
+            switch (c) {
+            case 'L':
+                int endIndex = descriptor.indexOf(';', index);
+                type = descriptor.substring(index + 1, endIndex);
+                index = endIndex;
+                break;
+            case 'I':
+                type = "int";
+                break;
+            case 'J':
+                type = "long";
+                break;
+            case 'Z':
+                type = "boolean";
+                break;
+            case 'D':
+                type = "double";
+                break;
+            case 'F':
+                type = "float";
+                break;
+            case 'S':
+                type = "short";
+                break;
+            case 'C':
+                type = "char";
+                break;
+            case 'B':
+                type = "byte";
+                break;
+            default:
+                type = "<unknown-descriptor-type>";
+            }
+            descriptors.add(type + arrayBrackets);
+        }
+        return descriptors;
+    }
+
+    private void printThread(RecordedThread thread, String postFix) {
+        long javaThreadId = thread.getJavaThreadId();
+        if (javaThreadId > 0) {
+            println("\"" + thread.getJavaName() + "\" (javaThreadId = " + thread.getJavaThreadId() + ")" + postFix);
+        } else {
+            println("\"" + thread.getOSName() + "\" (osThreadId = " + thread.getOSThreadId() + ")" + postFix);
+        }
+    }
+
+    private boolean printFormatted(ValueDescriptor field, Object value) {
+        if (value instanceof Duration) {
+            Duration d = (Duration) value;
+            double s = d.toNanosPart() / 1000_000_000.0 + d.toSecondsPart();
+            if (s < 1.0) {
+                if (s < 0.001) {
+                    println(String.format("%.3f", s * 1_000_000) + " us");
+                } else {
+                    println(String.format("%.3f", s * 1_000) + " ms");
+                }
+            } else {
+                if (s < 1000.0) {
+                    println(String.format("%.3f", s) + " s");
+                } else {
+                    println(String.format("%.0f", s) + " s");
+                }
+            }
+            return true;
+        }
+        if (value instanceof OffsetDateTime) {
+            OffsetDateTime zdt = (OffsetDateTime) value;
+            println(TIME_FORMAT.format(zdt));
+            return true;
+        }
+        Percentage percentage = field.getAnnotation(Percentage.class);
+        if (percentage != null) {
+            if (value instanceof Number) {
+                double d = ((Number) value).doubleValue();
+                println(String.format("%.2f", d * 100) + "%");
+                return true;
+            }
+        }
+        DataAmount dataAmount = field.getAnnotation(DataAmount.class);
+        if (dataAmount != null) {
+            if (value instanceof Number) {
+                Number n = (Number) value;
+                String bytes = Utils.formatBytes(n.longValue(), " ");
+                if (field.getAnnotation(Frequency.class) != null) {
+                    bytes += "/s";
+                }
+                println(bytes);
+                return true;
+            }
+        }
+        MemoryAddress memoryAddress = field.getAnnotation(MemoryAddress.class);
+        if (memoryAddress != null) {
+            if (value instanceof Number) {
+                long d = ((Number) value).longValue();
+                println(String.format("0x%08X", d));
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void setShowIds(boolean showIds) {
+        this.showIds = showIds;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Print.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,262 @@
+/*
+ * 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.tool;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import jdk.jfr.EventType;
+
+final class Print extends Command {
+    @Override
+    public String getName() {
+        return "print";
+    }
+
+    @Override
+    public List<String> getOptionSyntax() {
+        List<String> list = new ArrayList<>();
+        list.add("[--xml|--json]");
+        list.add("[--categories <filter>]");
+        list.add("[--events <filter>]");
+        list.add("[--stack-depth <depth>]");
+        list.add("<file>");
+        return list;
+    }
+
+    @Override
+    protected String getTitle() {
+        return "Print contents of a recording file";
+    }
+
+    @Override
+    public String getDescription() {
+        return getTitle() + ". See 'jfr help print' for details.";
+    }
+
+    @Override
+    public void displayOptionUsage(PrintStream stream) {
+        stream.println("  --xml                   Print recording in XML format");
+        stream.println();
+        stream.println("  --json                  Print recording in JSON format");
+        stream.println();
+        stream.println("  --categories <filter>   Select events matching a category name.");
+        stream.println("                          The filter is a comma-separated list of names,");
+        stream.println("                          simple and/or qualified, and/or quoted glob patterns");
+        stream.println();
+        stream.println("  --events <filter>       Select events matching an event name.");
+        stream.println("                          The filter is a comma-separated list of names,");
+        stream.println("                          simple and/or qualified, and/or quoted glob patterns");
+        stream.println();
+        stream.println("  --stack-depth <depth>   Number of frames in stack traces, by default 5");
+        stream.println();
+        stream.println("  <file>                  Location of the recording file (.jfr)");
+        stream.println();
+        stream.println();
+        stream.println("Example usage:");
+        stream.println();
+        stream.println(" jfr print --events OldObjectSample recording.jfr");
+        stream.println();
+        stream.println(" jfr print --events CPULoad,GarbageCollection recording.jfr");
+        stream.println();
+        stream.println(" jfr print --categories \"GC,JVM,Java*\" recording.jfr");
+        stream.println();
+        stream.println(" jfr print --events \"jdk.*\" --stack-depth 64 recording.jfr");
+        stream.println();
+        stream.println(" jfr print --json --events CPULoad recording.jfr");
+    }
+
+    @Override
+    public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
+        Path file = getJFRInputFile(options);
+        PrintWriter pw = new PrintWriter(System.out, false, Charset.forName("UTF-8"));
+        Predicate<EventType> eventFilter = null;
+        int stackDepth = 5;
+        EventPrintWriter eventWriter = null;
+        int optionCount = options.size();
+        while (optionCount > 0) {
+            if (acceptFilterOption(options, "--events")) {
+                String filter = options.remove();
+                warnForWildcardExpansion("--events", filter);
+                eventFilter = addEventFilter(filter, eventFilter);
+            }
+            if (acceptFilterOption(options, "--categories")) {
+                String filter = options.remove();
+                warnForWildcardExpansion("--categories", filter);
+                eventFilter = addCategoryFilter(filter, eventFilter);
+            }
+            if (acceptOption(options, "--stack-depth")) {
+                String value = options.pop();
+                try {
+                    stackDepth = Integer.parseInt(value);
+                    if (stackDepth < 0) {
+                        throw new UserSyntaxException("stack depth must be zero or a positive integer.");
+                    }
+                } catch (NumberFormatException nfe) {
+                    throw new UserSyntaxException("not a valid value for --stack-depth");
+                }
+            }
+            if (acceptFormatterOption(options, eventWriter, "--json")) {
+                eventWriter = new JSONWriter(pw);
+            }
+            if (acceptFormatterOption(options, eventWriter, "--xml")) {
+                eventWriter = new XMLWriter(pw);
+            }
+            if (optionCount == options.size()) {
+                // No progress made
+                throw new UserSyntaxException("unknown option " + options.peek());
+            }
+            optionCount = options.size();
+        }
+        if (eventWriter == null) {
+            eventWriter = new PrettyWriter(pw); // default to pretty printer
+        }
+        eventWriter.setStackDepth(stackDepth);
+        if (eventFilter != null) {
+            eventFilter = addCache(eventFilter, eventType -> eventType.getId());
+            eventWriter.setEventFilter(eventFilter);
+        }
+        try {
+            eventWriter.print(file);
+        } catch (IOException ioe) {
+            couldNotReadError(file, ioe);
+        }
+        pw.flush();
+    }
+
+    private static boolean acceptFormatterOption(Deque<String> options, EventPrintWriter eventWriter, String expected) throws UserSyntaxException {
+        if (expected.equals(options.peek())) {
+            if (eventWriter != null) {
+                throw new UserSyntaxException("only one format can be specified at a time");
+            }
+            options.remove();
+            return true;
+        }
+        return false;
+    }
+
+    private static <T, X> Predicate<T> addCache(final Predicate<T> filter, Function<T, X> cacheFunction) {
+        Map<X, Boolean> cache = new HashMap<>();
+        return t -> cache.computeIfAbsent(cacheFunction.apply(t), x -> filter.test(t));
+    }
+
+    private static <T> Predicate<T> recurseIfPossible(Predicate<T> filter) {
+        return x -> filter != null && filter.test(x);
+    }
+
+    private static Predicate<EventType> addCategoryFilter(String filterText, Predicate<EventType> eventFilter) throws UserSyntaxException {
+        List<String> filters = explodeFilter(filterText);
+        return recurseIfPossible(eventType -> {
+            for (String category : eventType.getCategoryNames()) {
+                for (String filter : filters) {
+                    if (match(category, filter)) {
+                        return true;
+                    }
+                    if (category.contains(" ") && acronomify(category).equals(filter)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        });
+    }
+
+    private static String acronomify(String multipleWords) {
+        boolean newWord = true;
+        String acronym = "";
+        for (char c : multipleWords.toCharArray()) {
+            if (newWord) {
+                if (Character.isAlphabetic(c) && Character.isUpperCase(c)) {
+                    acronym += c;
+                }
+            }
+            newWord = Character.isWhitespace(c);
+        }
+        return acronym;
+    }
+
+    private static Predicate<EventType> addEventFilter(String filterText, final Predicate<EventType> eventFilter) throws UserSyntaxException {
+        List<String> filters = explodeFilter(filterText);
+        return recurseIfPossible(eventType -> {
+            for (String filter : filters) {
+                String fullEventName = eventType.getName();
+                if (match(fullEventName, filter)) {
+                    return true;
+                }
+                String eventName = fullEventName.substring(fullEventName.lastIndexOf(".") + 1);
+                if (match(eventName, filter)) {
+                    return true;
+                }
+            }
+            return false;
+        });
+    }
+
+    private static boolean match(String text, String filter) {
+        if (filter.length() == 0) {
+            // empty filter string matches if string is empty
+            return text.length() == 0;
+        }
+        if (filter.charAt(0) == '*') { // recursive check
+            filter = filter.substring(1);
+            for (int n = 0; n <= text.length(); n++) {
+                if (match(text.substring(n), filter))
+                    return true;
+            }
+        } else if (text.length() == 0) {
+            // empty string and non-empty filter does not match
+            return false;
+        } else if (filter.charAt(0) == '?') {
+            // eat any char and move on
+            return match(text.substring(1), filter.substring(1));
+        } else if (filter.charAt(0) == text.charAt(0)) {
+            // eat chars and move on
+            return match(text.substring(1), filter.substring(1));
+        }
+        return false;
+    }
+
+    private static List<String> explodeFilter(String filter) throws UserSyntaxException {
+        List<String> list = new ArrayList<>();
+        for (String s : filter.split(",")) {
+            s = s.trim();
+            if (!s.isEmpty()) {
+                list.add(s);
+            }
+        }
+        return list;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/StructuredWriter.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,121 @@
+/*
+ * 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.tool;
+
+import java.io.PrintWriter;
+
+abstract class StructuredWriter {
+    private final static String LINE_SEPARATOR = String.format("%n");
+
+    private final PrintWriter out;
+    private final StringBuilder builder = new StringBuilder(4000);
+
+    private char[] indentionArray = new char[0];
+    private int indent = 0;
+    private int column;
+    // print first event immediately so tool feels responsive
+    private boolean first = true;
+
+    StructuredWriter(PrintWriter p) {
+        out = p;
+    }
+
+    final protected int getColumn() {
+        return column;
+    }
+
+    // Flush to print writer
+    public final void flush(boolean hard) {
+        if (hard) {
+            out.print(builder.toString());
+            builder.setLength(0);
+            return;
+        }
+        if (first || builder.length() > 100_000) {
+            out.print(builder.toString());
+            builder.setLength(0);
+            first = false;
+        }
+    }
+
+    final public void printIndent() {
+        builder.append(indentionArray, 0, indent);
+        column += indent;
+    }
+
+    final public void println() {
+        builder.append(LINE_SEPARATOR);
+        column = 0;
+    }
+
+    final public void print(String... texts) {
+        for (String text : texts) {
+            print(text);
+        }
+    }
+
+    final public void printAsString(Object o) {
+        print(String.valueOf(o));
+    }
+
+    final public void print(String text) {
+        builder.append(text);
+        column += text.length();
+    }
+
+    final public void print(char c) {
+        builder.append(c);
+        column++;
+    }
+
+    final public void print(int value) {
+        print(String.valueOf(value));
+    }
+
+    final public void indent() {
+        indent += 2;
+        updateIndent();
+    }
+
+    final public void retract() {
+        indent -= 2;
+        updateIndent();
+    }
+
+    final public void println(String text) {
+        print(text);
+        println();
+    }
+
+    private void updateIndent() {
+        if (indent > indentionArray.length) {
+            indentionArray = new char[indent];
+            for (int i = 0; i < indentionArray.length; i++) {
+                indentionArray[i] = ' ';
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,162 @@
+/*
+ * 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.tool;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import jdk.jfr.EventType;
+import jdk.jfr.internal.MetadataDescriptor;
+import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ChunkHeader;
+import jdk.jfr.internal.consumer.RecordingInput;
+
+final class Summary extends Command {
+    private final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withLocale(Locale.UK).withZone(ZoneOffset.UTC);
+
+    @Override
+    public String getName() {
+        return "summary";
+    }
+
+    private static class Statistics {
+        Statistics(String name) {
+            this.name = name;
+        }
+        String name;
+        long count;
+        long size;
+    }
+
+    @Override
+    public List<String> getOptionSyntax() {
+        return Collections.singletonList("<file>");
+    }
+
+    @Override
+    public void displayOptionUsage(PrintStream stream) {
+        stream.println("  <file>   Location of the recording file (.jfr) to display information about");
+    }
+
+    @Override
+    public String getDescription() {
+        return "Display general information about a recording file (.jfr)";
+    }
+
+    @Override
+    public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
+        ensureMaxArgumentCount(options, 1);
+        Path p = getJFRInputFile(options);
+        try {
+            printInformation(p);
+        } catch (IOException e) {
+            couldNotReadError(p, e);
+        }
+    }
+
+    private void printInformation(Path p) throws IOException {
+        long totalDuration = 0;
+        long chunks = 0;
+
+        try (RecordingInput input = new RecordingInput(p.toFile())) {
+            ChunkHeader first = new ChunkHeader(input);
+            ChunkHeader ch = first;
+            String eventPrefix = Type.EVENT_NAME_PREFIX;
+            if (first.getMajor() == 1) {
+                eventPrefix = "com.oracle.jdk.";
+            }
+            HashMap<Long, Statistics> stats = new HashMap<>();
+            stats.put(0L, new Statistics(eventPrefix + "Metadata"));
+            stats.put(1L, new Statistics(eventPrefix + "CheckPoint"));
+            int minWidth = 0;
+            while (true) {
+                long chunkEnd = ch.getEnd();
+                MetadataDescriptor md = ch.readMetadata();
+
+                for (EventType eventType : md.getEventTypes()) {
+                    stats.computeIfAbsent(eventType.getId(), (e) -> new Statistics(eventType.getName()));
+                    minWidth = Math.max(minWidth, eventType.getName().length());
+                }
+
+                totalDuration += ch.getDurationNanos();
+                chunks++;
+                input.position(ch.getEventStart());
+                while (input.position() < chunkEnd) {
+                    long pos = input.position();
+                    int size = input.readInt();
+                    long eventTypeId = input.readLong();
+                    Statistics s = stats.get(eventTypeId);
+                    if (s != null) {
+                        s.count++;
+                        s.size += size;
+                    }
+                    input.position(pos + size);
+                }
+                if (ch.isLastChunk()) {
+                    break;
+                }
+                ch = ch.nextHeader();
+            }
+            println();
+            long epochSeconds = first.getStartNanos() / 1_000_000_000L;
+            long adjustNanos = first.getStartNanos() - epochSeconds * 1_000_000_000L;
+            println(" Version: " + first.getMajor() + "." + first.getMinor());
+            println(" Chunks: " + chunks);
+            println(" Start: " + DATE_FORMAT.format(Instant.ofEpochSecond(epochSeconds, adjustNanos)) + " (UTC)");
+            println(" Duration: " + (totalDuration + 500_000_000) / 1_000_000_000 + " s");
+
+            List<Statistics> statsList = new ArrayList<>(stats.values());
+            Collections.sort(statsList, (u, v) -> Long.compare(v.count, u.count));
+            println();
+            String header = "      Count  Size (bytes) ";
+            String typeHeader = " Event Type";
+            minWidth = Math.max(minWidth, typeHeader.length());
+            println(typeHeader + pad(minWidth - typeHeader.length(), ' ') + header);
+            println(pad(minWidth + header.length(), '='));
+            for (Statistics s : statsList) {
+                System.out.printf(" %-" + minWidth + "s%10d  %12d\n", s.name, s.count, s.size);
+            }
+        }
+    }
+
+    private String pad(int count, char c) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < count; i++) {
+            sb.append(c);
+        }
+        return sb.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserDataException.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 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.tool;
+
+/**
+ * Exception that is thrown if there is something wrong with the input, for instance
+ * a file that can't be read or a numerical value that is out of range.
+ * <p>
+ * When this exception is thrown, a user will typically not want to see the
+ * command line syntax, but instead information about what was wrong with the
+ * input.
+ */
+final class UserDataException extends Exception {
+    private static final long serialVersionUID = 6656457380115167810L;
+    /**
+     * The error message.
+     *
+     * The first letter should not be capitalized, so a context can be printed prior
+     * to the error message.
+     *
+     * @param errorMessage
+     */
+    public UserDataException(String errorMessage) {
+        super(errorMessage);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserSyntaxException.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 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.tool;
+
+/**
+ * Exception that is thrown if options don't follow the syntax of the command.
+ */
+final class UserSyntaxException extends Exception {
+    private static final long serialVersionUID = 3437009454344160933L;
+
+    /**
+     * The error message.
+     *
+     * The first letter should not be capitalized, so a context can be printed prior
+     * to the error message.
+     *
+     * @param errorMessage
+     */
+    public UserSyntaxException(String message) {
+        super(message);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Version.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 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.tool;
+
+import java.util.Deque;
+import java.util.List;
+
+final class Version extends Command {
+    @Override
+    public String getName() {
+        return "version";
+    }
+
+    @Override
+    public String getDescription() {
+        return "Display version of the jfr tool";
+    }
+
+    @Override
+    public void execute(Deque<String> options) {
+        System.out.println("1.0");
+    }
+
+    protected List<String> getAliases() {
+        return List.of("--version");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/XMLWriter.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,200 @@
+/*
+ * 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.tool;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+import jdk.jfr.EventType;
+import jdk.jfr.ValueDescriptor;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedFrame;
+import jdk.jfr.consumer.RecordedObject;
+
+final class XMLWriter extends EventPrintWriter {
+    public XMLWriter(PrintWriter destination) {
+        super(destination);
+    }
+
+    @Override
+    protected void printBegin() {
+        println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        println("<recording xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">");
+        indent();
+        printIndent();
+        println("<events>");
+        indent();
+    }
+
+    @Override
+    protected void printEnd() {
+        retract();
+        printIndent();
+        println("</events>");
+        retract();
+        println("</recording>");
+    }
+
+    @Override
+    protected void print(List<RecordedEvent> events) {
+        for (RecordedEvent event : events) {
+            printEvent(event);
+        }
+    }
+
+    private void printEvent(RecordedEvent event) {
+        EventType type = event.getEventType();
+        printIndent();
+        print("<event");
+        printAttribute("type", type.getName());
+        print(">");
+        println();
+        indent();
+        for (ValueDescriptor v : event.getFields()) {
+            printValueDescriptor(v, getValue(event, v), -1);
+        }
+        retract();
+        printIndent();
+        println("</event>");
+        println();
+    }
+
+    private void printAttribute(String name, String value) {
+        print(" ", name, "=\"", value, "\"");
+    }
+
+    public void printObject(RecordedObject struct) {
+        println();
+        indent();
+        for (ValueDescriptor v : struct.getFields()) {
+            printValueDescriptor(v, getValue(struct, v), -1);
+        }
+        retract();
+    }
+
+    private void printArray(ValueDescriptor v, Object[] array) {
+        println();
+        indent();
+        int depth = 0;
+        for (int index = 0; index < array.length; index++) {
+            Object arrayElement = array[index];
+            if (!(arrayElement instanceof RecordedFrame) || depth < getStackDepth()) {
+                printValueDescriptor(v, array[index], index);
+            }
+            depth++;
+        }
+        retract();
+    }
+
+    private void printValueDescriptor(ValueDescriptor vd, Object value, int index) {
+        boolean arrayElement = index != -1;
+        String name = arrayElement ? null : vd.getName();
+        if (vd.isArray() && !arrayElement) {
+            if (printBeginElement("array", name, value, index)) {
+                printArray(vd, (Object[]) value);
+                printIndent();
+                printEndElement("array");
+            }
+            return;
+        }
+        if (!vd.getFields().isEmpty()) {
+            if (printBeginElement("struct", name, value, index)) {
+                printObject((RecordedObject) value);
+                printIndent();
+                printEndElement("struct");
+            }
+            return;
+        }
+        if (printBeginElement("value", name, value, index)) {
+            printEscaped(String.valueOf(value));
+            printEndElement("value");
+        }
+    }
+
+    private boolean printBeginElement(String elementName, String name, Object value, int index) {
+        printIndent();
+        print("<", elementName);
+        if (name != null) {
+            printAttribute("name", name);
+        }
+        if (index != -1) {
+            printAttribute("index", Integer.toString(index));
+        }
+        if (value == null) {
+            printAttribute("xsi:nil", "true");
+            println("/>");
+            return false;
+        }
+        if (value.getClass().isArray()) {
+            Object[] array = (Object[]) value;
+            printAttribute("size", Integer.toString(array.length));
+        }
+        print(">");
+        return true;
+    }
+
+    private void printEndElement(String elementName) {
+        print("</");
+        print(elementName);
+        println(">");
+    }
+
+    private void printEscaped(String text) {
+        for (int i = 0; i < text.length(); i++) {
+            printEscaped(text.charAt(i));
+        }
+    }
+
+    private void printEscaped(char c) {
+        if (c == 34) {
+            print("&quot;");
+            return;
+        }
+        if (c == 38) {
+            print("&amp;");
+            return;
+        }
+        if (c == 39) {
+            print("&apos;");
+            return;
+        }
+        if (c == 60) {
+            print("&lt;");
+            return;
+        }
+        if (c == 62) {
+            print("&gt;");
+            return;
+        }
+        if (c > 0x7F) {
+            print("&#");
+            print((int) c);
+            print(';');
+            return;
+        }
+        print(c);
+    }
+}
--- a/src/jdk.jfr/share/classes/module-info.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/src/jdk.jfr/share/classes/module-info.java	Wed Dec 05 16:40:12 2018 +0100
@@ -25,6 +25,12 @@
 
 /**
  * Defines the API for JDK Flight Recorder.
+ * <p>
+ *
+ * <dl style="font-family:'DejaVu Sans', Arial, Helvetica, sans serif">
+ * <dt class="simpleTagLabel">Tool Guides:
+ * <dd>{@extLink jfr_tool_reference jfr}
+ * </dl>
  *
  * @moduleGraph
  * @since 9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/tool/ExecuteHelper.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,125 @@
+/*
+ * 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.tool;
+
+import java.nio.file.Path;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jdk.jfr.Configuration;
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.test.lib.Utils;;
+
+final class ExecuteHelper {
+
+    public static Object[] array;
+
+    static class CustomEvent extends Event {
+        int intValue;
+        long longValue;
+        double doubliValue;
+        float floatValue;
+        String stringValue;
+        Short shortValue;
+        boolean booleanValue;
+        char charValue;
+        double trickyDouble;
+    }
+
+    public static void emitCustomEvents() {
+        // Custom events with potentially tricky values
+        CustomEvent event1 = new CustomEvent();
+        event1.trickyDouble = Double.NaN;
+        event1.intValue = Integer.MIN_VALUE;
+        event1.longValue = Long.MIN_VALUE;
+        event1.doubliValue = Double.MIN_VALUE;
+        event1.floatValue = Float.MIN_VALUE;
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < 512; i++) {
+            sb.append((char) i);
+        }
+        sb.append("\u2324");
+        event1.stringValue = sb.toString();
+        event1.shortValue = Short.MIN_VALUE;
+        event1.booleanValue = true;
+        event1.booleanValue = false;
+        event1.charValue = '\b';
+        event1.commit();
+
+        CustomEvent event2 = new CustomEvent();
+        event2.trickyDouble = Double.NEGATIVE_INFINITY;
+        event2.intValue = Integer.MAX_VALUE;
+        event2.longValue = Long.MAX_VALUE;
+        event2.doubliValue = Double.MAX_VALUE;
+        event2.floatValue = Float.MAX_VALUE;
+        event2.stringValue = null;
+        event2.shortValue = Short.MAX_VALUE;
+        event2.booleanValue = false;
+        event2.charValue = 0;
+        event2.commit();
+    }
+
+    public static Path createProfilingRecording() throws Exception {
+        Path file = Utils.createTempFile("profiling-recording", ".jfr");
+        // Create a recording with some data
+        try (Recording r = new Recording(Configuration.getConfiguration("profile"))) {
+            r.start();
+
+            // Allocation event
+            array = new Object[1000000];
+            array = null;
+
+            // Class loading event etc
+            provokeClassLoading();
+
+            // GC events
+            System.gc();
+
+            // ExecutionSample
+            long t = System.currentTimeMillis();
+            while (System.currentTimeMillis() - t < 50) {
+                // do nothing
+            }
+
+            // Other periodic events, i.e CPU load
+            Thread.sleep(1000);
+
+            r.stop();
+            r.dump(file);
+        }
+
+        return file;
+    }
+
+    private static void provokeClassLoading() {
+       // Matching a string with regexp
+       // is expected to load some classes and generate some VM events
+       Pattern p = Pattern.compile("a*b");
+       Matcher m = p.matcher("aaaaab");
+       m.matches();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/tool/TestAssemble.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,146 @@
+/*
+ * 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.tool;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import jdk.jfr.Event;
+import jdk.jfr.Name;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.jfr.internal.Repository;
+import jdk.jfr.internal.SecuritySupport.SafePath;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+/**
+ * @test
+ * @summary Test jfr reconstruct
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @modules jdk.jfr/jdk.jfr.internal
+ * @run main/othervm jdk.jfr.tool.TestAssemble
+ */
+public class TestAssemble {
+
+    @Name("Correlation")
+    static class CorrelationEvent extends Event {
+        int id;
+    }
+    private static int RECORDING_COUNT = 5;
+
+    @SuppressWarnings("resource")
+    public static void main(String[] args) throws Throwable {
+        // Create some disk recordings
+        Recording[] recordings = new Recording[5];
+        for (int i = 0; i < RECORDING_COUNT; i++) {
+            Recording r = new Recording();
+            r.setToDisk(true);
+            r.start();
+            CorrelationEvent ce = new CorrelationEvent();
+            ce.id = i;
+            ce.commit();
+            r.stop();
+            recordings[i] = r;
+        }
+        Path dir = Paths.get("reconstruction-parts");
+        Files.createDirectories(dir);
+
+        long expectedCount = 0;
+        for (int i = 0; i < RECORDING_COUNT; i++) {
+            Path tmp = dir.resolve("chunk-part-" + i + ".jfr");
+            recordings[i].dump(tmp);
+            expectedCount += countEventInRecording(tmp);
+        }
+
+        SafePath repository = Repository.getRepository().getRepositoryPath();
+        Path destinationPath = Paths.get("reconstructed.jfr");
+
+        String directory = repository.toString();
+        String destination = destinationPath.toAbsolutePath().toString();
+
+        // Test failure
+        OutputAnalyzer output = ProcessTools.executeProcess("jfr", "assemble");
+        output.shouldContain("too few arguments");
+
+        output = ProcessTools.executeProcess("jfr", "assemble", directory);
+        output.shouldContain("too few arguments");
+
+        output = ProcessTools.executeProcess("jfr", "assemble", "not-a-directory", destination);
+        output.shouldContain("directory does not exist, not-a-directory");
+
+        output = ProcessTools.executeProcess("jfr", "assemble", directory, "not-a-destination");
+        output.shouldContain("filename must end with '.jfr'");
+
+        output = ProcessTools.executeProcess("jfr","assemble", "--wrongOption", directory, destination);
+        output.shouldContain("too many arguments");
+
+        FileWriter fw = new FileWriter(destination);
+        fw.write('d');
+        fw.close();
+        output = ProcessTools.executeProcess("jfr", "assemble", directory, destination);
+        output.shouldContain("already exists");
+        Files.delete(destinationPath);
+
+        // test success
+        output = ProcessTools.executeProcess("jfr", "assemble", directory, destination);
+        System.out.println(output.getOutput());
+        output.shouldContain("Finished.");
+
+        long reconstructedCount = countEventInRecording(destinationPath);
+        Asserts.assertEquals(expectedCount, reconstructedCount);
+        // Cleanup
+        for (int i = 0; i < RECORDING_COUNT; i++) {
+            recordings[i].close();
+        }
+    }
+
+    private static long countEventInRecording(Path file) throws IOException {
+        Integer lastId = -1;
+        try (RecordingFile rf = new RecordingFile(file)) {
+            long count = 0;
+            while (rf.hasMoreEvents()) {
+                RecordedEvent re = rf.readEvent();
+                if (re.getEventType().getName().equals("Correlation")) {
+                    Integer id = re.getValue("id");
+                    if (id < lastId) {
+                        Asserts.fail("Expected chunk number to increase");
+                    }
+                    lastId = id;
+                }
+                count++;
+            }
+            return count;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/tool/TestDisassemble.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,126 @@
+/*
+ * 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.tool;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import jdk.jfr.Configuration;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+/**
+ * @test
+ * @summary Test jfr split
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.tool.TestDisassemble
+ */
+public class TestDisassemble {
+
+    public static void main(String[] args) throws Throwable {
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
+        String dateText = formatter.format(new Date());
+
+        Path recordingFileA = Paths.get("many-chunks-A-" + dateText + ".jfr");
+        Path recordingFileB = Paths.get("many-chunks-B-" + dateText + ".jfr");
+        Path recordingFileC = Paths.get("many-chunks-C-" + dateText + ".jfr");
+        makeRecordingWithChunks(6, recordingFileA);
+        Files.copy(recordingFileA, recordingFileB);
+        Files.copy(recordingFileA, recordingFileC);
+
+        String fileAText = recordingFileA.toAbsolutePath().toString();
+        String fileBText = recordingFileB.toAbsolutePath().toString();
+        String fileCText = recordingFileC.toAbsolutePath().toString();
+
+        OutputAnalyzer output = ProcessTools.executeProcess("jfr", "disassemble");
+        output.shouldContain("missing file");
+
+        output =  ProcessTools.executeProcess("jfr","disassemble", "--wrongOption", fileAText);
+        output.shouldContain("unknown option");
+
+        output = ProcessTools.executeProcess("jfr","disassemble", "--wrongOption", "1", fileAText);
+        output.shouldContain("unknown option");
+
+        output =  ProcessTools.executeProcess("jfr","disassemble", "--max-chunks", "-3", fileAText);
+        output.shouldContain("max chunks must be at least 1");
+
+        output =  ProcessTools.executeProcess("jfr","disassemble", "--max-chunks", "1000", fileAText);
+        output.shouldContain("number of chunks in recording");
+        output.shouldContain("doesn't exceed max chunks");
+        output =  ProcessTools.executeProcess("jfr", "disassemble", fileAText); // maxchunks is 5 by
+                                                        // default
+        System.out.println(output.getOutput());
+        System.out.println(fileAText);
+        verifyRecording(fileAText.substring(0, fileAText.length() - 4) + "_1.jfr");
+        verifyRecording(fileAText.substring(0, fileAText.length() - 4) + "_2.jfr");
+
+        output =  ProcessTools.executeProcess("jfr","disassemble", "--max-chunks", "2", fileBText);
+
+        verifyRecording(fileBText.substring(0, fileBText.length() - 4) + "_1.jfr");
+        verifyRecording(fileBText.substring(0, fileBText.length() - 4) + "_2.jfr");
+        verifyRecording(fileBText.substring(0, fileBText.length() - 4) + "_3.jfr");
+
+        output =  ProcessTools.executeProcess("jfr","disassemble", "--max-chunks", "2", fileBText);
+        output.shouldContain("file with that name already exist");
+
+        // sanity check
+        output =  ProcessTools.executeProcess("jfr","disassemble", "--max-size", "500000", fileCText);
+        verifyRecording(fileCText.substring(0, fileCText.length() - 4) + "_1.jfr");
+    }
+
+    private static void verifyRecording(String name) throws IOException {
+        System.out.println("Disassembling: " + name);
+        try (RecordingFile rf = new RecordingFile(Paths.get(name))) {
+            rf.readEvent();
+        }
+    }
+
+    // Will create at least 2 * count + 1 chunks.
+    private static void makeRecordingWithChunks(int count, Path file) throws IOException, ParseException {
+        Recording main = new Recording(Configuration.getConfiguration("default"));
+        main.setToDisk(true);
+        main.start();
+        for (int i = 0; i < count; i++) {
+            Recording r = new Recording();
+            r.setToDisk(true);
+            r.start();
+            r.stop();
+            r.close();
+        }
+        main.stop();
+        main.dump(file);
+        main.close();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/tool/TestHelp.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,57 @@
+/*
+ * 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.tool;
+
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+/**
+ * @test
+ * @summary Test help
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.tool.TestHelp
+ */
+public class TestHelp {
+
+    public static void main(String[] args) throws Throwable {
+        OutputAnalyzer output = ProcessTools.executeProcess("jfr", "help");
+        output.shouldContain("print");
+        output.shouldContain("assemble");
+        output.shouldContain("disassemble");
+        output.shouldContain("metadata");
+        output.shouldContain("summary");
+        output.shouldContain("help");
+
+        output = ProcessTools.executeProcess("jfr", "help", "version");
+        output.shouldContain("Display version of the jfr tool");
+        output.shouldContain("jfr version");
+
+        output = ProcessTools.executeProcess("jfr", "help", "wrongcommand");
+        output.shouldContain("unknown command 'wrongcommand'");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/tool/TestMetadata.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 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.tool;
+
+import java.nio.file.Path;
+
+import jdk.jfr.EventType;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+/**
+ * @test
+ * @summary Test jfr info
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.tool.TestMetadata
+ */
+public class TestMetadata {
+
+    public static void main(String[] args) throws Throwable {
+        Path f = ExecuteHelper.createProfilingRecording().toAbsolutePath();
+        String file = f.toAbsolutePath().toString();
+
+        OutputAnalyzer output = ProcessTools.executeProcess("jfr", "metadata");
+        output.shouldContain("missing file");
+
+        output = ProcessTools.executeProcess("jfr", "metadata", "--wrongOption", file);
+        output.shouldContain("unknown option --wrongOption");
+
+        output = ProcessTools.executeProcess("jfr", "metadata", file);
+        try (RecordingFile rf = new RecordingFile(f)) {
+            for (EventType t : rf.readEventTypes()) {
+                String name = t.getName();
+                name = name.substring(name.lastIndexOf(".") + 1);
+                output.shouldContain(name);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/tool/TestPrint.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,62 @@
+/*
+ * 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.tool;
+
+import java.io.FileWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import jdk.test.lib.Utils;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+/**
+ * @test
+ * @summary Test jfr print
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.tool.TestPrint
+ */
+public class TestPrint {
+
+    public static void main(String[] args) throws Throwable {
+
+        OutputAnalyzer output = ProcessTools.executeProcess("jfr", "print");
+        output.shouldContain("missing file");
+
+        output = ProcessTools.executeProcess("jfr", "print", "missing.jfr");
+        output.shouldContain("could not find file ");
+
+        Path file = Utils.createTempFile("faked-print-file",  ".jfr");
+        FileWriter fw = new FileWriter(file.toFile());
+        fw.write('d');
+        fw.close();
+        output = ProcessTools.executeProcess("jfr", "print", "--wrongOption", file.toAbsolutePath().toString());
+        output.shouldContain("unknown option");
+        Files.delete(file);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/tool/TestPrintDefault.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,54 @@
+/*
+ * 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.tool;
+
+import java.nio.file.Path;
+
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+/**
+ * @test
+ * @key jfr
+ * @summary Tests print --json
+ * @requires vm.hasJFR
+ *
+ * @library /test/lib /test/jdk
+ * @modules java.scripting
+ *          jdk.jfr
+ *
+ * @run main/othervm jdk.jfr.tool.TestPrintDefault
+ */
+public class TestPrintDefault {
+
+    public static void main(String... args) throws Throwable {
+
+        Path recordingFile = ExecuteHelper.createProfilingRecording().toAbsolutePath();
+
+        OutputAnalyzer output = ProcessTools.executeProcess("jfr", "print", recordingFile.toString());
+        output.shouldContain("JVMInformation");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/tool/TestPrintJSON.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,164 @@
+/*
+ * 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.tool;
+
+import java.nio.file.Path;
+import java.time.OffsetDateTime;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+
+import jdk.jfr.Timespan;
+import jdk.jfr.Timestamp;
+import jdk.jfr.ValueDescriptor;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.nashorn.api.scripting.JSObject;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+/**
+ * @test
+ * @key jfr
+ * @summary Tests print --json
+ * @requires vm.hasJFR
+ *
+ * @library /test/lib /test/jdk
+ * @modules jdk.scripting.nashorn
+ *          jdk.jfr
+ *
+ * @run main/othervm jdk.jfr.tool.TestPrintJSON
+ */
+public class TestPrintJSON {
+
+    public static void main(String... args) throws Throwable {
+
+        Path recordingFile = ExecuteHelper.createProfilingRecording().toAbsolutePath();
+
+        OutputAnalyzer output = ProcessTools.executeProcess("jfr", "print", "--json", "--stack-depth", "999", recordingFile.toString());
+        String json = output.getStdout();
+
+        // Parse JSON using Nashorn
+        String statement = "var jsonObject = " + json;
+        ScriptEngineManager factory = new ScriptEngineManager();
+        ScriptEngine engine = factory.getEngineByName("nashorn");
+        engine.eval(statement);
+        JSObject o = (JSObject) engine.get("jsonObject");
+        JSObject recording = (JSObject) o.getMember("recording");
+        JSObject jsonEvents = (JSObject) recording.getMember("events");
+
+        List<RecordedEvent> events = RecordingFile.readAllEvents(recordingFile);
+        Collections.sort(events, (e1, e2) -> e1.getEndTime().compareTo(e2.getEndTime()));
+        // Verify events are equal
+        Iterator<RecordedEvent> it = events.iterator();
+
+        for (Object jsonEvent : jsonEvents.values()) {
+            RecordedEvent recordedEvent = it.next();
+            String typeName = recordedEvent.getEventType().getName();
+            Asserts.assertEquals(typeName, ((JSObject) jsonEvent).getMember("type").toString());
+            assertEquals(jsonEvent, recordedEvent);
+        }
+        Asserts.assertFalse(events.size() != jsonEvents.values().size(), "Incorrect number of events");
+    }
+
+    private static void assertEquals(Object jsonObject, Object jfrObject) throws Exception {
+        // Check object
+        if (jfrObject instanceof RecordedObject) {
+            JSObject values = (JSObject) ((JSObject) jsonObject).getMember("values");
+            RecordedObject recObject = (RecordedObject) jfrObject;
+            Asserts.assertEquals(values.values().size(), recObject.getFields().size());
+            for (ValueDescriptor v : recObject.getFields()) {
+                String name = v.getName();
+                Object jsonValue = values.getMember(name);
+                Object expectedValue = recObject.getValue(name);
+                if (v.getAnnotation(Timestamp.class) != null) {
+                    // Make instant of OffsetDateTime
+                    jsonValue = OffsetDateTime.parse("" + jsonValue).toInstant().toString();
+                    expectedValue = recObject.getInstant(name);
+                }
+                if (v.getAnnotation(Timespan.class) != null) {
+                    expectedValue = recObject.getDuration(name);
+                }
+                assertEquals(jsonValue, expectedValue);
+                return;
+            }
+        }
+        // Check array
+        if (jfrObject != null && jfrObject.getClass().isArray()) {
+            Object[] jfrArray = (Object[]) jfrObject;
+            JSObject jsArray = (JSObject) jsonObject;
+            for (int i = 0; i < jfrArray.length; i++) {
+                assertEquals(jsArray.getSlot(i), jfrArray[i]);
+            }
+            return;
+        }
+        String jsonText = String.valueOf(jsonObject);
+        // Double.NaN / Double.Inifinity is not supported by JSON format,
+        // use null
+        if (jfrObject instanceof Double) {
+            double expected = ((Double) jfrObject);
+            if (Double.isInfinite(expected) || Double.isNaN(expected)) {
+                Asserts.assertEquals("null", jsonText);
+                return;
+            }
+            double value = Double.parseDouble(jsonText);
+            Asserts.assertEquals(expected, value);
+            return;
+        }
+        // Float.NaN / Float.Inifinity is not supported by JSON format,
+        // use null
+        if (jfrObject instanceof Float) {
+            float expected = ((Float) jfrObject);
+            if (Float.isInfinite(expected) || Float.isNaN(expected)) {
+                Asserts.assertEquals("null", jsonText);
+                return;
+            }
+            float value = Float.parseFloat(jsonText);
+            Asserts.assertEquals(expected, value);
+            return;
+        }
+        if (jfrObject instanceof Integer) {
+            Integer expected = ((Integer) jfrObject);
+            double value = Double.parseDouble(jsonText);
+            Asserts.assertEquals(expected.doubleValue(), value);
+            return;
+        }
+        if (jfrObject instanceof Long) {
+            Long expected = ((Long) jfrObject);
+            double value = Double.parseDouble(jsonText);
+            Asserts.assertEquals(expected.doubleValue(), value);
+            return;
+        }
+
+        String jfrText = String.valueOf(jfrObject);
+        Asserts.assertEquals(jfrText, jsonText, "Primitive values don't match. JSON = " + jsonText);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/tool/TestPrintXML.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,261 @@
+/*
+ * 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.tool;
+
+import java.io.File;
+import java.io.StringReader;
+import java.nio.file.Path;
+import java.time.OffsetDateTime;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import jdk.jfr.Timespan;
+import jdk.jfr.Timestamp;
+import jdk.jfr.ValueDescriptor;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+/**
+ * @test
+ * @key jfr
+ * @summary Tests print --xml
+ * @requires vm.hasJFR
+ *
+ * @library /test/lib /test/jdk
+ * @modules java.scripting java.xml jdk.jfr
+ *
+ * @run main/othervm jdk.jfr.tool.TestPrintXML
+ */
+public class TestPrintXML {
+
+    public static void main(String... args) throws Throwable {
+
+        Path recordingFile = ExecuteHelper.createProfilingRecording().toAbsolutePath();
+
+        OutputAnalyzer output = ProcessTools.executeProcess("jfr", "print", "--xml", "--stack-depth", "9999", recordingFile.toString());
+        System.out.println(recordingFile);
+        String xml = output.getStdout();
+
+        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        Schema schema = schemaFactory.newSchema(new File(System.getProperty("test.src"), "jfr.xsd"));
+
+        SAXParserFactory factory = SAXParserFactory.newInstance();
+        factory.setSchema(schema);
+        factory.setNamespaceAware(true);
+
+        SAXParser sp = factory.newSAXParser();
+        XMLReader xr = sp.getXMLReader();
+        RecordingHandler handler = new RecordingHandler();
+        xr.setContentHandler(handler);
+        xr.setErrorHandler(handler);
+        xr.parse(new InputSource(new StringReader(xml)));
+
+        // Verify that all data was written correctly
+        List<RecordedEvent> events = RecordingFile.readAllEvents(recordingFile);
+        Collections.sort(events, (e1, e2) -> e1.getEndTime().compareTo(e2.getEndTime()));
+        Iterator<RecordedEvent> it = events.iterator();
+        for (XMLEvent xmlEvent : handler.events) {
+            RecordedEvent re = it.next();
+            if (!compare(re, xmlEvent.values)) {
+                System.out.println("Expected:");
+                System.out.println("----------------------");
+                System.out.println(re);
+                System.out.println();
+                System.out.println("Was (XML)");
+                System.out.println("----------------------");
+                System.out.println(xmlEvent);
+                System.out.println();
+                throw new Exception("Event doesn't match");
+            }
+        }
+
+    }
+
+    @SuppressWarnings("unchecked")
+    static boolean compare(Object eventObject, Object xmlObject) {
+        if (eventObject == null) {
+            return xmlObject == null;
+        }
+        if (eventObject instanceof RecordedObject) {
+            RecordedObject re = (RecordedObject) eventObject;
+            Map<String, Object> xmlMap = (Map<String, Object>) xmlObject;
+            List<ValueDescriptor> fields = re.getFields();
+            if (fields.size() != xmlMap.size()) {
+                return false;
+            }
+            for (ValueDescriptor v : fields) {
+                String name = v.getName();
+                Object xmlValue = xmlMap.get(name);
+                Object expectedValue = re.getValue(name);
+                if (v.getAnnotation(Timestamp.class) != null) {
+                    // Make instant of OffsetDateTime
+                    xmlValue = OffsetDateTime.parse("" + xmlValue).toInstant().toString();
+                    expectedValue = re.getInstant(name);
+                }
+                if (v.getAnnotation(Timespan.class) != null) {
+                    expectedValue = re.getDuration(name);
+                }
+                if (!compare(expectedValue, xmlValue)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        if (eventObject.getClass().isArray()) {
+            Object[] array = (Object[]) eventObject;
+            Object[] xmlArray = (Object[]) xmlObject;
+            if (array.length != xmlArray.length) {
+                return false;
+            }
+            for (int i = 0; i < array.length; i++) {
+                if (!compare(array[i], xmlArray[i])) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        String s1 = String.valueOf(eventObject);
+        String s2 = (String) xmlObject;
+        return s1.equals(s2);
+    }
+
+    static class XMLEvent {
+        String name;
+        private Map<String, Object> values = new HashMap<>();
+
+        XMLEvent(String name) {
+            this.name = name;
+        }
+    }
+
+    public static final class RecordingHandler extends DefaultHandler {
+
+        private Stack<Object> objects = new Stack<>();
+        private Stack<SimpleEntry<String, String>> elements = new Stack<>();
+        private List<XMLEvent> events = new ArrayList<>();
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
+            elements.push(new SimpleEntry<>(attrs.getValue("name"), attrs.getValue("index")));
+            String nil = attrs.getValue("xsi:nil");
+            if ("true".equals(nil)) {
+                objects.push(null);
+                return;
+            }
+
+            switch (qName) {
+            case "event":
+                objects.push(new XMLEvent(attrs.getValue("type")));
+                break;
+            case "struct":
+                objects.push(new HashMap<String, Object>());
+                break;
+            case "array":
+                objects.push(new Object[Integer.parseInt(attrs.getValue("size"))]);
+                break;
+            case "value":
+                objects.push(new StringBuilder());
+                break;
+            }
+        }
+
+        @Override
+        public void characters(char[] ch, int start, int length) throws SAXException {
+            if (!objects.isEmpty()) {
+                Object o = objects.peek();
+                if (o instanceof StringBuilder) {
+                    ((StringBuilder) o).append(ch, start, length);
+                }
+            }
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public void endElement(String uri, String localName, String qName) {
+            SimpleEntry<String, String> element = elements.pop();
+            switch (qName) {
+            case "event":
+            case "struct":
+            case "array":
+            case "value":
+                String name = element.getKey();
+                Object value = objects.pop();
+                if (objects.isEmpty()) {
+                    events.add((XMLEvent) value);
+                    return;
+                }
+                if (value instanceof StringBuilder) {
+                    value = ((StringBuilder) value).toString();
+                }
+                Object parent = objects.peek();
+                if (parent instanceof XMLEvent) {
+                    ((XMLEvent) parent).values.put(name, value);
+                }
+                if (parent instanceof Map) {
+                    ((Map<String, Object>) parent).put(name, value);
+                }
+                if (parent != null && parent.getClass().isArray()) {
+                    int index = Integer.parseInt(element.getValue());
+                    ((Object[]) parent)[index] = value;
+                }
+            }
+        }
+
+        public void warning(SAXParseException spe) throws SAXException {
+            throw new SAXException(spe);
+        }
+
+        public void error(SAXParseException spe) throws SAXException {
+            throw new SAXException(spe);
+        }
+
+        public void fatalError(SAXParseException spe) throws SAXException {
+            throw new SAXException(spe);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/tool/TestSummary.java	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,63 @@
+/*
+ * 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.tool;
+
+import java.nio.file.Path;
+
+import jdk.jfr.EventType;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+/**
+ * @test
+ * @summary Test jfr info
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.tool.TestSummary
+ */
+public class TestSummary {
+
+    public static void main(String[] args) throws Throwable {
+        Path f = ExecuteHelper.createProfilingRecording().toAbsolutePath();
+        String file = f.toAbsolutePath().toString();
+
+        OutputAnalyzer output = ProcessTools.executeProcess("jfr", "summary");
+        output.shouldContain("missing file");
+
+        output = ProcessTools.executeProcess("jfr", "summary", "--wrongOption", file);
+        output.shouldContain("too many arguments");
+
+        output = ProcessTools.executeProcess("jfr", "summary", file);
+        try (RecordingFile rf = new RecordingFile(f)) {
+            for (EventType t : rf.readEventTypes()) {
+                output.shouldContain(t.getName());
+            }
+        }
+        output.shouldContain("Version");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/tool/jfr.xsd	Wed Dec 05 16:40:12 2018 +0100
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" elementFormDefault="unqualified" attributeFormDefault="unqualified" version="1.0">
+    <xs:element name="recording">
+        <xs:complexType>
+            <xs:sequence minOccurs="0" maxOccurs="1">
+                <xs:element name="events">
+                    <xs:complexType>
+                        <xs:sequence minOccurs="0" maxOccurs="unbounded">
+                            <xs:element name="event" type="eventType" />
+                        </xs:sequence>
+                    </xs:complexType>
+                </xs:element>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+    <xs:complexType name="eventType">
+        <xs:sequence minOccurs="0" maxOccurs="unbounded">
+            <xs:choice>
+                <xs:element name="struct" nillable="true" type="structType" />
+                <xs:element name="array" nillable="true" type="arrayType" />
+                <xs:element name="value" nillable="true" type="valueType" />
+            </xs:choice>
+        </xs:sequence>
+        <xs:attribute use="required" name="type" type="xs:string" />
+    </xs:complexType>
+    <xs:complexType name="structType">
+        <xs:sequence minOccurs="0" maxOccurs="unbounded">
+            <xs:choice>
+                <xs:element name="struct" nillable="true" type="structType" />
+                <xs:element name="array" nillable="true" type="arrayType" />
+                <xs:element name="value" nillable="true" type="valueType" />
+            </xs:choice>
+        </xs:sequence>
+        <xs:attribute use="required" name="name" type="xs:string" />
+    </xs:complexType>
+    <xs:complexType name="arrayType">
+        <xs:sequence minOccurs="0" maxOccurs="unbounded">
+            <xs:choice>
+                <xs:element name="struct" nillable="true" type="structElement" />
+                <xs:element name="array" nillable="true" type="arrayElement" />
+                <xs:element name="value" nillable="true" type="valueElement" />
+            </xs:choice>
+        </xs:sequence>
+        <xs:attribute use="required" name="size" type="xs:int" />
+        <xs:attribute use="required" name="name" type="xs:string" />
+    </xs:complexType>
+    <xs:complexType name="valueType">
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute use="required" name="name" type="xs:string" />
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+    <xs:complexType name="structElement">
+        <xs:sequence minOccurs="0" maxOccurs="unbounded">
+            <xs:choice>
+                <xs:element name="struct" nillable="true" type="structType" />
+                <xs:element name="array" nillable="true" type="arrayType" />
+                <xs:element name="value" nillable="true" type="valueType" />
+            </xs:choice>
+        </xs:sequence>
+        <xs:attribute use="required" name="index" type="xs:int" />
+    </xs:complexType>
+    <xs:complexType name="arrayElement">
+        <xs:sequence minOccurs="0" maxOccurs="unbounded">
+            <xs:choice>
+                <xs:element name="value" nillable="true" type="valueType" />
+                <xs:element name="array" nillable="true" type="arrayType" />
+                <xs:element name="struct" nillable="true" type="structType" />
+            </xs:choice>
+        </xs:sequence>
+        <xs:attribute use="required" name="index" type="xs:int" />
+    </xs:complexType>
+    <xs:complexType name="valueElement">
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute use="required" name="index" type="xs:int" />
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+</xs:schema>
\ No newline at end of file
--- a/test/jdk/tools/launcher/HelpFlagsTest.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/test/jdk/tools/launcher/HelpFlagsTest.java	Wed Dec 05 16:40:12 2018 +0100
@@ -140,6 +140,7 @@
         new ToolHelpSpec("jdb",         1,   1,   1,   0,         1,    1,     0),     // -?, -h, --help -help, Documents -help
         new ToolHelpSpec("jdeprscan",   1,   1,   1,   0,         0,    0,     1),     // -?, -h, --help
         new ToolHelpSpec("jdeps",       1,   1,   1,   0,         1,    0,     2),     // -?, -h, --help, -help accepted but not documented.
+        new ToolHelpSpec("jfr",         1,   1,   1,   0,         0,    0,     2),     // -?, -h, --help
         new ToolHelpSpec("jhsdb",       0,   0,   0,   0,         0,    0,     0),     // none, prints help message anyways.
         new ToolHelpSpec("jimage",      1,   1,   1,   0,         0,    0,     2),     // -?, -h, --help
         new ToolHelpSpec("jinfo",       1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
@@ -224,6 +225,7 @@
                     line.charAt(posAfter) != ',' &&
                     line.charAt(posAfter) != '[' && // jar
                     line.charAt(posAfter) != ']' && // jarsigner
+                    line.charAt(posAfter) != ')' && // jfr
                     line.charAt(posAfter) != '|' && // jstatd
                     line.charAt(posAfter) != ':' && // jps
                     line.charAt(posAfter) != '"') { // keytool
--- a/test/jdk/tools/launcher/VersionCheck.java	Wed Dec 05 09:34:01 2018 -0500
+++ b/test/jdk/tools/launcher/VersionCheck.java	Wed Dec 05 16:40:12 2018 +0100
@@ -88,6 +88,7 @@
         "jcontrol",
         "jdeprscan",
         "jdeps",
+        "jfr",
         "jimage",
         "jinfo",
         "jlink",