# HG changeset patch # User egahlin # Date 1548767475 -3600 # Tue Jan 29 14:11:15 2019 +0100 # Node ID 621bff70d6640e0a92b558f6d6d66e8de97608ca # Parent d7f789845dc1a66ba744899681ecca62f7a11824 JFR Tool diff --git a/make/launcher/Launcher-jdk.jfr.gmk b/make/launcher/Launcher-jdk.jfr.gmk new file mode 100644 --- /dev/null +++ b/make/launcher/Launcher-jdk.jfr.gmk @@ -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, \ +)) diff --git a/src/hotspot/share/jfr/leakprofiler/emitEventOperation.cpp b/src/hotspot/share/jfr/leakprofiler/emitEventOperation.cpp --- a/src/hotspot/share/jfr/leakprofiler/emitEventOperation.cpp +++ b/src/hotspot/share/jfr/leakprofiler/emitEventOperation.cpp @@ -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 diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp --- a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp +++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp @@ -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(); } diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.hpp b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.hpp --- a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.hpp +++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.hpp @@ -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); diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.cpp b/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.cpp --- a/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.cpp +++ b/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.cpp @@ -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; } diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.hpp b/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.hpp --- a/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.hpp +++ b/src/hotspot/share/jfr/leakprofiler/sampling/sampleList.hpp @@ -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); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/ValueDescriptor.java b/src/jdk.jfr/share/classes/jdk/jfr/ValueDescriptor.java --- a/src/jdk.jfr/share/classes/jdk/jfr/ValueDescriptor.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/ValueDescriptor.java @@ -291,7 +291,7 @@ if (type.isSimpleType()) { return Collections.emptyList(); } - return Collections.unmodifiableList(type.getFields()); + return type.getFields(); } // package private diff --git a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ChunkParser.java b/src/jdk.jfr/share/classes/jdk/jfr/consumer/ChunkParser.java --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ChunkParser.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/ChunkParser.java @@ -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 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 getTypes() { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java @@ -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 vds, Object[] values, long startTime, long endTime, TimeConverter timeConverter) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java @@ -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. @@ -878,18 +878,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) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java @@ -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 readTypes(RecordingFile file) throws IOException { + return file.readTypes(); + } + public boolean isLastEventInChunk(RecordingFile file) { + return file.isLastEventInChunk; + } + + @Override + public Object getOffsetDataTime(RecordedObject event, String name) { + return event.getOffsetDateTime(name); + } + + @Override + public void sort(List events) { + Collections.sort(events, (e1, e2) -> Long.compare(e1.endTime, e2.endTime)); + } + }; + } + + private boolean isLastEventInChunk; private final File file; 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 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 readTypes() throws IOException { + ensureOpen(); + List types = new ArrayList<>(); + HashSet 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 types, HashSet foundIds) throws IOException { + private void aggregateTypeForChunk(ChunkHeader ch, List types, HashSet 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 types, HashSet foundIds) throws IOException { MetadataDescriptor m = ch.readMetadata(); for (EventType t : m.getEventTypes()) { if (!foundIds.contains(t.getId())) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/consumer/TimeConverter.java b/src/jdk.jfr/share/classes/jdk/jfr/consumer/TimeConverter.java --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/TimeConverter.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/TimeConverter.java @@ -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; + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java @@ -24,7 +24,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)); @@ -34,7 +34,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 recordings, Boolean pathToGcRoots) { boolean enabled = false; long cutoffNanos = Boolean.TRUE.equals(pathToGcRoots) ? Long.MAX_VALUE : 0L; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java @@ -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; @@ -72,7 +73,7 @@ private final String superType; private final boolean constantPool; private final long id; - private final ArrayList fields = new ArrayList<>(); + private List fields = new ArrayList<>(); private Boolean simpleType; // calculated lazy private boolean remove = true; /** @@ -182,6 +183,10 @@ } public List getFields() { + if (fields instanceof ArrayList) { + ((ArrayList) fields).trimToSize(); + fields = Collections.unmodifiableList(fields); + } return fields; } @@ -215,7 +220,7 @@ } void trimFields() { - fields.trimToSize(); + getFields(); } void setAnnotations(List annotations) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/Command.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/Command.java deleted file mode 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/Command.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.internal.cmd; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.List; - -abstract class Command { - - private final static Command HELP = new HelpCommand(); - private final static List COMMANDS = createCommands(); - - static void displayHelp() { - System.out.println("Usage: java " + Execute.class.getName() + " []"); - System.out.println(); - displayAvailableCommands(); - } - - static void displayAvailableCommands() { - System.out.println("Available commands are:"); - System.out.println(); - boolean first = true; - for (Command c : Command.COMMANDS) { - if (!first) { - System.out.println(); - } - System.out.println(" " + c.getName() + " " + c.getOptionSyntax()); - System.out.println(" " + c.getDescription()); - first = false; - } - } - - public static List getCommands() { - return COMMANDS; - } - - public static Command valueOf(String commandName) { - for (Command command : COMMANDS) { - if (command.getName().equals(commandName)) { - return command; - } - } - return null; - } - - abstract public String getOptionSyntax(); - - abstract public String getName(); - - abstract public String getDescription(); - - abstract public void displayOptionUsage(); - - abstract public void execute(Deque options); - - final protected void userFailed(String message) { - println(); - println(message); - displayUsage(); - throw new IllegalArgumentException(message); - } - - final protected void ensureMaxArgumentCount(Deque options, int maxCount) { - if (options.size() > maxCount) { - userFailed("Too many arguments"); - } - } - - final protected void ensureMinArgumentCount(Deque options, int minCount) { - if (options.size() < minCount) { - userFailed("Too few arguments"); - } - } - - final protected void ensureFileExist(Path file) { - if (!Files.exists(file)) { - userFailed("Could not find file " + file); - } - } - - final protected Path ensureFileDoesNotExist(Path file) { - if (Files.exists(file)) { - userFailed("File " + file + " already exists"); - } - return file; - } - - final protected void ensureJFRFile(Path path) { - if (!path.toString().endsWith(".jfr")) { - userFailed("Filename must end with .jfr"); - } - } - - final protected void displayUsage() { - String javaText = "java " + Execute.class.getName(); - println(); - println("Usage: " + javaText + " " + getName() + " " + getOptionSyntax()); - println(); - displayOptionUsage(); - } - - 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); - } - - private static List createCommands() { - List commands = new ArrayList<>(); - commands.add(new PrintCommand()); - commands.add(new SummaryCommand()); - commands.add(new ReconstructCommand()); - commands.add(new SplitCommand()); - commands.add(HELP); - return Collections.unmodifiableList(commands); - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/Execute.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/Execute.java deleted file mode 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/Execute.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.internal.cmd; - -import java.util.Arrays; -import java.util.Deque; -import java.util.LinkedList; - -/** - * Launcher class for JFR tools - * - */ -public final class Execute { - - public static void main(String... args) { - Deque argList = new LinkedList<>(Arrays.asList(args)); - if (argList.isEmpty()) { - System.out.println(); - Command.displayHelp(); - return; - } - String command = argList.remove(); - for (Command c : Command.getCommands()) { - if (c.getName().equals(command)) { - try { - c.execute(argList); - } catch (IllegalArgumentException iae) { - return; // already handled by command - } catch (Throwable e) { - System.out.println(); - System.out.println(e.getMessage()); - System.out.println(); - } - return; - } - } - System.out.println(); - System.out.println("Unknown command " + command + "."); - System.out.println(); - Command.displayHelp(); - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/HelpCommand.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/HelpCommand.java deleted file mode 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/HelpCommand.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.internal.cmd; - -import java.util.Deque; - -final class HelpCommand extends Command { - - @Override - public String getOptionSyntax() { - return "[]"; - } - - @Override - public void displayOptionUsage() { - println(" The name of the command to get help for"); - println(); - Command.displayAvailableCommands(); - } - - @Override - public String getName() { - return "help"; - } - - @Override - public String getDescription() { - return "Display help about a command"; - } - - @Override - public void execute(Deque options) { - if (options.isEmpty()) { - displayUsage(); - } else { - ensureMaxArgumentCount(options, 1); - String commandName = options.remove(); - Command c = Command.valueOf(commandName); - if (c == null) { - userFailed("Unknown command " + commandName); - } - c.displayUsage(); - } - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/JSONWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/JSONWriter.java deleted file mode 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/JSONWriter.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.internal.cmd; - -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Path; - -import jdk.jfr.EventType; -import jdk.jfr.ValueDescriptor; -import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedObject; -import jdk.jfr.consumer.RecordingFile; - -final class JSONWriter extends StructuredWriter { - - public JSONWriter(PrintWriter writer) { - super(writer); - } - - public void print(Path source) throws IOException { - try (RecordingFile es = new RecordingFile(source)) { - printObjectBegin(); - printRecording(es); - printObjectEnd(); - flush(); - } - } - - private void printRecording(RecordingFile es) throws IOException { - printDataStructureName("recording"); - printObjectBegin(); - printEvents(es); - printObjectEnd(); - } - - private void printEvents(RecordingFile es) throws IOException { - printDataStructureName("events"); - printArrayBegin(); - boolean first = true; - while (es.hasMoreEvents()) { - RecordedEvent e = es.readEvent(); - printNewDataStructure(first, true, null); - printEvent(e); - flush(); - first = false; - } - printArrayEnd(); - } - - private void printEvent(RecordedEvent e) { - printObjectBegin(); - EventType type = e.getEventType(); - printValue(true, false, "name", type.getName()); - printValue(false, false, "typeId", type.getId()); - printValue(false, false, "startTime", e.getStartTime()); - printValue(false, false, "duration", e.getDuration()); - printNewDataStructure(false, false, "values"); - printObject(e); - 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, object.getValue(v.getName())); - first = false; - } - printObjectEnd(); - } - - private void printArray(ValueDescriptor v, Object[] array) { - printArrayBegin(); - boolean first = true; - for (Object arrayElement : array) { - printValueDescriptor(first, true, v, arrayElement); - 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); - } - -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/PrettyWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/PrettyWriter.java deleted file mode 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/PrettyWriter.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.internal.cmd; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.StringJoiner; - -import jdk.jfr.AnnotationElement; -import jdk.jfr.ValueDescriptor; -import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedObject; -import jdk.jfr.consumer.RecordingFile; -import jdk.jfr.internal.PrivateAccess; -import jdk.jfr.internal.Type; -import jdk.jfr.internal.consumer.ChunkHeader; -import jdk.jfr.internal.consumer.RecordingInput; - -public final class PrettyWriter extends StructuredWriter { - - public PrettyWriter(PrintWriter destination) { - super(destination); - } - - void print(Path source) throws FileNotFoundException, IOException { - try (RecordingInput input = new RecordingInput(source.toFile())) { - HashSet typeSet = new HashSet<>(); - for (ChunkHeader ch = new ChunkHeader(input); !ch.isLastChunk(); ch = ch.nextHeader()) { - typeSet.addAll(ch.readMetadata().getTypes()); - } - List types = new ArrayList<>(typeSet); - Collections.sort(types, (c1, c2) -> Long.compare(c1.getId(), c2.getId())); - for (Type t : types) { - printType(t); - } - flush(); - } - - try (RecordingFile es = new RecordingFile(source)) { - while (es.hasMoreEvents()) { - print(es.readEvent()); - flush(); - } - } - flush(); - } - - public void printType(Type t) throws IOException { - 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("package " + typeName.substring(0, index) + ";"); - } - printAnnotations(commentIndex, t.getAnnotationElements()); - print("class " + typeName.substring(index + 1)); - String superType = t.getSuperType(); - if (superType != null) { - print(" extends " + superType); - } - println(" {"); - indent(); - for (ValueDescriptor v : t.getFields()) { - printField(commentIndex, v); - } - retract(); - println("}"); - println(); - } - - private void printField(int commentIndex, ValueDescriptor v) throws IOException { - 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) throws IOException { - int column = getColumn(); - if (column > commentIndex) { - print(" "); - } else { - while (column < commentIndex) { - print(" "); - column++; - } - } - println(" // id=" + typeId); - } - - private void printAnnotations(int commentIndex, List annotations) throws IOException { - for (AnnotationElement a : annotations) { - printIndent(); - print("@"); - print(makeSimpleType(a.getTypeName())); - List vs = a.getValueDescriptors(); - if (!vs.isEmpty()) { - printAnnotation(a); - printCommentRef(commentIndex, a.getTypeId()); - } else { - println(); - } - } - } - - private void printAnnotation(AnnotationElement a) throws IOException { - StringJoiner sj = new StringJoiner(", ", "(", ")"); - List 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) throws IOException { - print(makeSimpleType(event.getEventType().getName()), " "); - print((RecordedObject) event, ""); - } - - public void print(RecordedObject struct, String postFix) throws IOException { - println("{"); - indent(); - for (ValueDescriptor v : struct.getFields()) { - printIndent(); - print(v.getName(), " = "); - printValue(struct.getValue(v.getName()), ""); - } - retract(); - printIndent(); - println("}" + postFix); - } - - private void printArray(Object[] array) throws IOException { - println("["); - indent(); - for (int i = 0; i < array.length; i++) { - printIndent(); - printValue(array[i], i + 1 < array.length ? ", " : ""); - } - retract(); - printIndent(); - println("]"); - } - - private void printValue(Object value, String postFix) throws IOException { - if (value == null) { - println("null" + postFix); - } else if (value instanceof RecordedObject) { - print((RecordedObject) value, postFix); - } else if (value.getClass().isArray()) { - printArray((Object[]) value); - } else { - String text = String.valueOf(value); - if (value instanceof String) { - text = "\"" + text + "\""; - } - println(text); - } - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/PrintCommand.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/PrintCommand.java deleted file mode 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/PrintCommand.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.internal.cmd; - -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Deque; - -final class PrintCommand extends Command { - @Override - public String getName() { - return "print"; - } - - @Override - public String getOptionSyntax() { - return "[--xml|--json] "; - } - - @Override - public String getDescription() { - return "Print contents of a recording file (.jfr)"; - } - - @Override - public void displayOptionUsage() { - println(" --xml Print a recording in XML format"); - println(); - println(" --json Print a recording in JSON format"); - println(); - println(" Location of the recording file (.jfr) to print"); - } - - @Override - public void execute(Deque options) { - if (options.isEmpty()) { - userFailed("Missing file"); - } - ensureMaxArgumentCount(options, 2); - - Path file = Paths.get(options.removeLast()); - - ensureFileExist(file); - ensureJFRFile(file); - ensureMaxArgumentCount(options, 1); - - String format = "--pretty"; - if (!options.isEmpty()) { - format = options.remove(); - } - try (PrintWriter pw = new PrintWriter(System.out)) { - try { - switch (format) { - case "--pretty": - PrettyWriter prettyWriter = new PrettyWriter(pw); - prettyWriter.print(file); - break; - case "--xml": - XMLWriter xmlPrinter = new XMLWriter(pw); - xmlPrinter.print(file); - break; - case "--json": - JSONWriter jsonWriter = new JSONWriter(pw); - jsonWriter.print(file); - break; - default: - userFailed("Unknown option " + format); - break; - } - } catch (IOException ioe) { - userFailed("Could not read recording at " + file.toAbsolutePath() + ". " + ioe.getMessage()); - } - } - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/ReconstructCommand.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/ReconstructCommand.java deleted file mode 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/ReconstructCommand.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.internal.cmd; - -import java.io.FileOutputStream; -import java.io.IOException; -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.Deque; -import java.util.List; - -final class ReconstructCommand extends Command { - - @Override - public String getOptionSyntax() { - return " "; - } - - @Override - public String getName() { - return "reconstruct"; - } - - @Override - public String getDescription() { - return "Assemble leftover chunks, from a disk repository, into a recording file (.jfr)"; - } - - @Override - public void displayOptionUsage() { - println(" Directory where the repository is located"); - println(); - println(" Name of the recording file (.jfr) to create"); - } - - @Override - public void execute(Deque options) { - ensureMinArgumentCount(options, 2); - ensureMaxArgumentCount(options, 2); - - Path repository = Paths.get(options.pop()).toAbsolutePath(); - if (!Files.exists(repository)) { - userFailed("Could not find disk repository at " + repository); - } - if (!Files.isDirectory(repository)) { - userFailed("Must specify a directory as disk repository"); - } - Path output = Paths.get(options.pop()); - ensureFileDoesNotExist(output); - ensureJFRFile(output); - - try (FileOutputStream fos = new FileOutputStream(output.toFile())) { - List files = listJFRFiles(repository); - if (files.isEmpty()) { - throw new IllegalStateException("No *.jfr files found at " + repository); - } - println(); - println("Combining files... "); - println(); - transferTo(files, output, fos.getChannel()); - println(); - println("Reconstruction complete."); - } catch (IOException e) { - userFailed("Could not open destination file " + output + ". " + e.getMessage()); - } - } - - private List listJFRFiles(Path path) throws IOException { - try { - List files = new ArrayList<>(); - if (Files.isDirectory(path)) { - try (DirectoryStream 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 IllegalStateException("Could not list *.jfr for directory " + path + ". " + ioe.getMessage()); - } - } - - private void transferTo(List sourceFiles, Path output, FileChannel out) { - 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 IllegalStateException("Could not copy recording chunk " + p + " to new file. " + ioe.getMessage()); - } - } - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/SplitCommand.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/SplitCommand.java deleted file mode 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/SplitCommand.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.internal.cmd; - -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.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -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 SplitCommand extends Command { - - @Override - public String getOptionSyntax() { - return "[--maxchunks ] "; - } - - @Override - public void displayOptionUsage() { - println(" --maxchunks Maximum number of chunks per splitted file (default 5)."); - println(" The chunk size varies, but is typically around 15 MB."); - println(); - println(" Location of recording file (.jfr) to split"); - } - - @Override - public String getName() { - return "split"; - } - - @Override - public String getDescription() { - return "Splits a recording file into smaller files"; - } - - @Override - public void execute(Deque options) { - if (options.isEmpty()) { - userFailed("Missing file"); - } - ensureMaxArgumentCount(options, 3); - Path file = Paths.get(options.removeLast()); - ensureFileExist(file); - ensureJFRFile(file); - int maxchunks = 5; - if (!options.isEmpty()) { - String option = options.pop(); - if (!"--maxchunks".equals(option)) { - userFailed("Unknown option " + option); - } - if (options.isEmpty()) { - userFailed("Missing value for --maxChunks"); - } - String value = options.pop(); - try { - maxchunks = Integer.parseInt(value); - if (maxchunks < 1) { - userFailed("Must be at least one chunk per file."); - } - } catch (NumberFormatException nfe) { - userFailed("Not a valid value for --maxchunks."); - } - } - ensureMaxArgumentCount(options, 0); - println(); - println("Examining recording " + file + " ..."); - List sizes; - - try { - sizes = findChunkSizes(file); - } catch (IOException e) { - throw new IllegalStateException("Unexpected error. " + e.getMessage()); - } - if (sizes.size() <= maxchunks) { - throw new IllegalStateException("Number of chunks in recording (" + sizes.size() + ") doesn't exceed max chunks (" + maxchunks + ")"); - } - println(); - - println(); - if (sizes.size() > 0) { - print("File consists of " + sizes.size() + " chunks. The recording will be split into "); - sizes = combineChunkSizes(sizes, maxchunks); - println(sizes.size() + " files with at most " + maxchunks + " chunks per file."); - println(); - - try { - splitFile(file, sizes); - } catch (IOException e) { - throw new IllegalStateException("Unexpected error. " + e.getMessage()); - } - } else { - println("No JFR chunks found in file. "); - } - } - - private List findChunkSizes(Path p) throws IOException { - try (RecordingInput input = new RecordingInput(p.toFile())) { - List 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 combineChunkSizes(List sizes, int chunksPerFile) { - List reduced = new ArrayList(); - long size = sizes.get(0); - for (int n = 1; n < sizes.size(); n++) { - if (n % chunksPerFile == 0) { - reduced.add(size); - size = 0; - } - size += sizes.get(n); - } - reduced.add(size); - return reduced; - } - - private void splitFile(Path file, List splitPositions) throws IOException { - - int padAmountZeros = String.valueOf(splitPositions.size() - 1).length(); - String fileName = file.toString(); - String fileFormatter = fileName.subSequence(0, fileName.length() - 4) + "_%0" + padAmountZeros + "d.jfr"; - for (int i = 0; i < splitPositions.size(); i++) { - Path p = Paths.get(String.format(fileFormatter, i)); - if (Files.exists(p)) { - throw new IllegalStateException("Can't create split file " + p + ", a file with that name already exist"); - } - } - 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()); - Path p = Paths.get(String.format(fileFormatter, i)); - File splittedFile = p.toFile(); - println("Writing " + splittedFile + " ..."); - FileOutputStream fos = new FileOutputStream(splittedFile); - fos.write(bytes); - fos.close(); - } - stream.close(); - } - - private byte[] readBytes(InputStream stream, int count) throws 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 IOException("Unexpected end of data."); - } - totalRead += read; - } - return data; - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/StructuredWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/StructuredWriter.java deleted file mode 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/StructuredWriter.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.internal.cmd; - -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; - - StructuredWriter(PrintWriter p) { - out = p; - } - - final protected int getColumn() { - return column; - } - - // Flush to print writer - public final void flush() { - out.print(builder.toString()); - builder.setLength(0); - } - - 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] = ' '; - } - } - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/SummaryCommand.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/SummaryCommand.java deleted file mode 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/SummaryCommand.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.internal.cmd; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -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; - -final class SummaryCommand extends Command { - - private static class Statistics { - Statistics(String name) { - this.name = name; - } - - String name; - long count; - long size; - } - - @Override - public String getOptionSyntax() { - return ""; - } - - @Override - public void displayOptionUsage() { - println(" Location of the recording file (.jfr) to display information about"); - } - - @Override - public String getName() { - return "summary"; - } - - @Override - public String getDescription() { - return "Display general information about a recording file (.jfr)"; - } - - @Override - public void execute(Deque options) { - if (options.isEmpty()) { - userFailed("Missing file"); - } - ensureMaxArgumentCount(options, 1); - Path p = Paths.get(options.remove()); - ensureFileExist(p); - ensureJFRFile(p); - try { - printInformation(p); - } catch (IOException e) { - throw new IllegalStateException("Unexpected error. " + e.getMessage()); - } - } - - private void printInformation(Path p) throws IOException { - long totalSize = 0; - 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 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()); - } - - totalSize += ch.getSize(); - totalDuration += ch.getDuration(); - 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(" Size: " + totalSize + " bytes"); - println(" Start: " + Instant.ofEpochSecond(epochSeconds, adjustNanos)); - println(" Duration: " + Duration.ofNanos(totalDuration)); - println(); - println(" Start Ticks: " + first.getStartTicks()); - println(" Ticks / Second: " + first.getTicksPerSecond()); - - List 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(); - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/XMLWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/XMLWriter.java deleted file mode 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/cmd/XMLWriter.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.internal.cmd; - -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Path; - -import jdk.jfr.EventType; -import jdk.jfr.ValueDescriptor; -import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedObject; -import jdk.jfr.consumer.RecordingFile; - -final class XMLWriter extends StructuredWriter { - - public XMLWriter(PrintWriter destination) { - super(destination); - } - - public void print(Path source) throws IOException { - try (RecordingFile es = new RecordingFile(source)) { - println(""); - println(""); - indent(); - printIndent(); - println(""); - indent(); - while (es.hasMoreEvents()) { - printEvent(es.readEvent()); - flush(); - } - retract(); - printIndent(); - println(""); - retract(); - println(""); - flush(); - } - } - - private void printEvent(RecordedEvent e) throws IOException { - EventType type = e.getEventType(); - printIndent(); - print(""); - printObject(e); - printIndent(); - println(""); - 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, struct.getValue(v.getName()), -1); - } - retract(); - } - - private void printArray(ValueDescriptor v, Object[] array) { - println(); - indent(); - for (int index = 0; index < array.length; index++) { - printValueDescriptor(v, array[index], index); - } - 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) { - print(">"); - 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(""); - } - - 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("""); - return; - } - if (c == 38) { - print("&"); - return; - } - if (c == 39) { - print("'"); - return; - } - if (c == 60) { - print("<"); - return; - } - if (c == 62) { - print(">"); - return; - } - if (c > 0x7F) { - print("&#"); - print((int) c); - print(';'); - return; - } - print(c); - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java @@ -161,7 +161,7 @@ return chunkSize; } - public long getDuration() { + public long getDurationNanos() { return durationNanos; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java @@ -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 readTypes(RecordingFile file) throws IOException; + + public abstract void sort(List events); + +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Assemble.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Assemble.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Assemble.java @@ -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 getOptionSyntax() { + return Collections.singletonList(" "); + } + + @Override + public String getDescription() { + return "Assemble leftover chunks from a disk repository into a recording file"; + } + + @Override + public void displayOptionUsage(PrintStream stream) { + stream.println(" Directory where the repository is located"); + stream.println(); + stream.println(" Name of the recording file (.jfr) to create"); + } + + @Override + public void execute(Deque 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 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 listJFRFiles(Path path) throws UserDataException { + try { + List files = new ArrayList<>(); + if (Files.isDirectory(path)) { + try (DirectoryStream 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 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()); + } + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Command.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Command.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Command.java @@ -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 COMMANDS = createCommands(); + + private static List createCommands() { + List 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 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 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 getCommands() { + return COMMANDS; + } + + public static Command valueOf(String commandName) { + for (Command command : COMMANDS) { + if (command.getName().equals(commandName)) { + return command; + } + } + return null; + } + + public List getOptionSyntax() { + return Collections.singletonList(""); + } + + public void displayOptionUsage(PrintStream stream) { + } + + protected boolean acceptOption(Deque 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 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 options, int maxCount) throws UserSyntaxException { + if (options.size() > maxCount) { + throw new UserSyntaxException("too many arguments"); + } + } + + final protected void ensureMinArgumentCount(Deque 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 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 getAliases() { + return Collections.emptyList(); + } + + public List getNames() { + List names = new ArrayList<>(); + names.add(getName()); + names.addAll(getAliases()); + return names; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Disassemble.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Disassemble.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Disassemble.java @@ -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 getOptionSyntax() { + List list = new ArrayList<>(); + list.add("[--output ]"); + list.add("[--max-chunks ]"); + list.add("[--max-size ]"); + list.add(""); + return list; + } + + @Override + public void displayOptionUsage(PrintStream stream) { + stream.println(" --output The location to write the disassembled file,"); + stream.println(" by default the current directory"); + stream.println(""); + stream.println(" --max-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 Maximum number of bytes per file."); + stream.println(""); + stream.println(" Location of the recording file (.jfr)"); + } + + @Override + public String getDescription() { + return "Disassamble a recording file into smaller files/chunks"; + } + + @Override + public void execute(Deque 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 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 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 findChunkSizes(Path p) throws IOException { + try (RecordingInput input = new RecordingInput(p.toFile())) { + List 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 combineChunkSizes(List sizes, int maxChunks, long maxSize) { + List reduced = new ArrayList(); + 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 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; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java @@ -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 eventFilter = x -> true; + private int stackDepth; + + // cach that will speed up annotation lookup + private Map typeOfValues = new HashMap<>(); + + EventPrintWriter(PrintWriter p) { + super(p); + } + + abstract protected void print(List events); + + void print(Path source) throws FileNotFoundException, IOException { + List 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 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; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Help.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Help.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Help.java @@ -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 getOptionSyntax() { + return Collections.singletonList("[]"); + } + + protected List getAliases() { + return List.of("--help", "-h", "-?"); + } + + @Override + public void displayOptionUsage(PrintStream stream) { + println(" 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 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); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/JSONWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/JSONWriter.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/JSONWriter.java @@ -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 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); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Main.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Main.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Main.java @@ -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 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 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 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 recording.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); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Metadata.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Metadata.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Metadata.java @@ -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 { + + @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 getOptionSyntax() { + return Collections.singletonList(""); + } + + @Override + public String getDescription() { + return "Display event metadata, such as labels, descriptions and field layout"; + } + + @Override + public void execute(Deque 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 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); + } + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java @@ -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 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 annotations) { + for (AnnotationElement a : annotations) { + printIndent(); + print("@"); + print(makeSimpleType(a.getTypeName())); + List vs = a.getValueDescriptors(); + if (!vs.isEmpty()) { + printAnnotation(a); + printCommentRef(commentIndex, a.getTypeId()); + } else { + println(); + } + } + } + + private void printAnnotation(AnnotationElement a) { + StringJoiner sj = new StringJoiner(", ", "(", ")"); + List 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 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 decodeDescriptors(String descriptor) { + List 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 = ""; + } + 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; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Print.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Print.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Print.java @@ -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 getOptionSyntax() { + List list = new ArrayList<>(); + list.add("[--xml|--json]"); + list.add("[--categories ]"); + list.add("[--events ]"); + list.add("[--stack-depth ]"); + list.add(""); + 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 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 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 Number of frames in stack traces, by default 5"); + stream.println(); + stream.println(" 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 options) throws UserSyntaxException, UserDataException { + Path file = getJFRInputFile(options); + PrintWriter pw = new PrintWriter(System.out, false, Charset.forName("UTF-8")); + Predicate 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 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 Predicate addCache(final Predicate filter, Function cacheFunction) { + Map cache = new HashMap<>(); + return t -> cache.computeIfAbsent(cacheFunction.apply(t), x -> filter.test(t)); + } + + private static Predicate recurseIfPossible(Predicate filter) { + return x -> filter != null && filter.test(x); + } + + private static Predicate addCategoryFilter(String filterText, Predicate eventFilter) throws UserSyntaxException { + List 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 addEventFilter(String filterText, final Predicate eventFilter) throws UserSyntaxException { + List 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 explodeFilter(String filter) throws UserSyntaxException { + List list = new ArrayList<>(); + for (String s : filter.split(",")) { + s = s.trim(); + if (!s.isEmpty()) { + list.add(s); + } + } + return list; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/StructuredWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/StructuredWriter.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/StructuredWriter.java @@ -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] = ' '; + } + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java @@ -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 getOptionSyntax() { + return Collections.singletonList(""); + } + + @Override + public void displayOptionUsage(PrintStream stream) { + stream.println(" 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 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 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 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(); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserDataException.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserDataException.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserDataException.java @@ -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. + *

+ * 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); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserSyntaxException.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserSyntaxException.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserSyntaxException.java @@ -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); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Version.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Version.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Version.java @@ -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 options) { + System.out.println("1.0"); + } + + protected List getAliases() { + return List.of("--version"); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/XMLWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/XMLWriter.java new file mode 100644 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/XMLWriter.java @@ -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(""); + println(""); + indent(); + printIndent(); + println(""); + indent(); + } + + @Override + protected void printEnd() { + retract(); + printIndent(); + println(""); + retract(); + println(""); + } + + @Override + protected void print(List events) { + for (RecordedEvent event : events) { + printEvent(event); + } + } + + private void printEvent(RecordedEvent event) { + EventType type = event.getEventType(); + printIndent(); + print(""); + println(); + indent(); + for (ValueDescriptor v : event.getFields()) { + printValueDescriptor(v, getValue(event, v), -1); + } + retract(); + printIndent(); + println(""); + 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(""); + } + + 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("""); + return; + } + if (c == 38) { + print("&"); + return; + } + if (c == 39) { + print("'"); + return; + } + if (c == 60) { + print("<"); + return; + } + if (c == 62) { + print(">"); + return; + } + if (c > 0x7F) { + print("&#"); + print((int) c); + print(';'); + return; + } + print(c); + } +} diff --git a/src/jdk.jfr/share/classes/module-info.java b/src/jdk.jfr/share/classes/module-info.java --- a/src/jdk.jfr/share/classes/module-info.java +++ b/src/jdk.jfr/share/classes/module-info.java @@ -25,6 +25,12 @@ /** * Defines the API for JDK Flight Recorder. + *

+ * + *

+ *
Tool Guides: + *
{@extLink jfr_tool_reference jfr} + *
* * @moduleGraph * @since 9 diff --git a/test/jdk/jdk/jfr/cmd/ExecuteHelper.java b/test/jdk/jdk/jfr/cmd/ExecuteHelper.java deleted file mode 100644 --- a/test/jdk/jdk/jfr/cmd/ExecuteHelper.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.cmd; - -import java.nio.file.Path; -import java.util.Arrays; -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; -import jdk.test.lib.process.OutputAnalyzer; -import jdk.test.lib.process.ProcessTools;; - -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 OutputAnalyzer run(String... args) { - String[] array = new String[args.length + 1]; - System.arraycopy(args, 0, array, 1, args.length); - array[0] = "jdk.jfr.internal.cmd.Execute"; - try { - return ProcessTools.executeTestJava(array); - } catch (Exception e) { - String message = String.format("Caught exception while executing '%s'", Arrays.asList(array)); - throw new RuntimeException(message, e); - } - } - - 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(); - } -} diff --git a/test/jdk/jdk/jfr/cmd/TestHelp.java b/test/jdk/jdk/jfr/cmd/TestHelp.java deleted file mode 100644 --- a/test/jdk/jdk/jfr/cmd/TestHelp.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.cmd; - -import jdk.test.lib.process.OutputAnalyzer; - -/** - * @test - * @summary Test help - * @key jfr - * @requires vm.hasJFR - * @library /test/lib /test/jdk - * @run main/othervm jdk.jfr.cmd.TestHelp - */ -public class TestHelp { - - public static void main(String[] args) throws Exception { - OutputAnalyzer output = ExecuteHelper.run("help"); - output.shouldContain("Available commands are:"); - output.shouldContain("print"); - output.shouldContain("reconstruct"); - output.shouldContain("summary"); - output.shouldContain("help"); - - output = ExecuteHelper.run("help", "help"); - output.shouldContain("Available commands are:"); - - output = ExecuteHelper.run("help", "wrongcommand"); - output.shouldContain("Unknown command"); - - output = ExecuteHelper.run("help", "wrongcommand", "wrongarguments"); - output.shouldContain("Too many arguments"); - } -} diff --git a/test/jdk/jdk/jfr/cmd/TestPrint.java b/test/jdk/jdk/jfr/cmd/TestPrint.java deleted file mode 100644 --- a/test/jdk/jdk/jfr/cmd/TestPrint.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.cmd; - -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; - -/** - * @test - * @summary Test jfr print - * @key jfr - * @requires vm.hasJFR - * @library /test/lib /test/jdk - * @run main/othervm jdk.jfr.cmd.TestPrint - */ -public class TestPrint { - - public static void main(String[] args) throws Exception { - - OutputAnalyzer output = ExecuteHelper.run("print"); - output.shouldContain("Missing file"); - - output = ExecuteHelper.run("print", "missing.jfr"); - output.shouldContain("Could not find file "); - - output = ExecuteHelper.run("print", "missing.jfr", "option1", "option2"); - output.shouldContain("Too many arguments"); - - Path file = Utils.createTempFile("faked-print-file", ".jfr"); - FileWriter fw = new FileWriter(file.toFile()); - fw.write('d'); - fw.close(); - output = ExecuteHelper.run("print", "--wrongOption", file.toAbsolutePath().toString()); - output.shouldContain("Unknown option"); - Files.delete(file); - - // Also see TestPrintJSON, TestPrintXML and TestPrintDefault. - } -} diff --git a/test/jdk/jdk/jfr/cmd/TestPrintDefault.java b/test/jdk/jdk/jfr/cmd/TestPrintDefault.java deleted file mode 100644 --- a/test/jdk/jdk/jfr/cmd/TestPrintDefault.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.cmd; - -import java.nio.file.Path; - -import jdk.test.lib.process.OutputAnalyzer; - -/** - * @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.cmd.TestPrintDefault - */ -public class TestPrintDefault { - - public static void main(String... args) throws Exception { - - Path recordingFile = ExecuteHelper.createProfilingRecording().toAbsolutePath(); - - OutputAnalyzer output = ExecuteHelper.run("print", recordingFile.toString()); - output.shouldContain("JVMInformation"); - } -} diff --git a/test/jdk/jdk/jfr/cmd/TestPrintJSON.java b/test/jdk/jdk/jfr/cmd/TestPrintJSON.java deleted file mode 100644 --- a/test/jdk/jdk/jfr/cmd/TestPrintJSON.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.cmd; - -import java.nio.file.Path; - -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; - -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; - -/** - * @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.cmd.TestPrintJSON - */ -public class TestPrintJSON { - - public static void main(String... args) throws Exception { - - Path recordingFile = ExecuteHelper.createProfilingRecording().toAbsolutePath(); - - OutputAnalyzer output = ExecuteHelper.run("print", "--json", 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 events = (JSObject) recording.getMember("events"); - - // Verify events are equal - try (RecordingFile rf = new RecordingFile(recordingFile)) { - for (Object jsonEvent : events.values()) { - RecordedEvent recordedEvent = rf.readEvent(); - double typeId = recordedEvent.getEventType().getId(); - String startTime = recordedEvent.getStartTime().toString(); - String duration = recordedEvent.getDuration().toString(); - Asserts.assertEquals(typeId, ((Number) ((JSObject) jsonEvent).getMember("typeId")).doubleValue()); - Asserts.assertEquals(startTime, ((JSObject) jsonEvent).getMember("startTime")); - Asserts.assertEquals(duration, ((JSObject) jsonEvent).getMember("duration")); - assertEquals(jsonEvent, recordedEvent); - } - Asserts.assertFalse(rf.hasMoreEvents(), "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(); - assertEquals(values.getMember(name), recObject.getValue(name)); - 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); - } -} diff --git a/test/jdk/jdk/jfr/cmd/TestPrintXML.java b/test/jdk/jdk/jfr/cmd/TestPrintXML.java deleted file mode 100644 --- a/test/jdk/jdk/jfr/cmd/TestPrintXML.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.cmd; - -import java.io.StringReader; -import java.nio.file.Path; -import java.time.Duration; -import java.time.Instant; -import java.util.AbstractMap.SimpleEntry; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Stack; - -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -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 org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.xml.sax.helpers.DefaultHandler; - -/** - * @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.cmd.TestPrintXML - */ -public class TestPrintXML { - - public static void main(String... args) throws Exception { - - Path recordingFile = ExecuteHelper.createProfilingRecording().toAbsolutePath(); - - OutputAnalyzer output = ExecuteHelper.run("print", "--xml", recordingFile.toString()); - String xml = output.getStdout(); - System.out.println(xml); - // Parse XML string - SAXParserFactory factory = SAXParserFactory.newInstance(); - SAXParser sp = factory.newSAXParser(); - XMLReader xr = sp.getXMLReader(); - RecordingHandler handler = new RecordingHandler(); - xr.setContentHandler(handler); - xr.parse(new InputSource(new StringReader(xml))); - - // Verify that all data was written correctly - Iterator it = RecordingFile.readAllEvents(recordingFile).iterator(); - for (XMLEvent xmlEvent : handler.events) { - RecordedEvent re = it.next(); - if (!compare(re, xmlEvent.values)) { - System.out.println(re); - System.out.println(xmlEvent.values.toString()); - 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 xmlMap = (Map) xmlObject; - List fields = re.getFields(); - if (fields.size() != xmlMap.size()) { - return false; - } - for (ValueDescriptor v : fields) { - String name = v.getName(); - if (!compare(re.getValue(name), xmlMap.get(name))) { - 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; - Instant startTime; - Duration duration; - Map values = new HashMap<>(); - - XMLEvent(String name, Instant startTime, Duration duration) { - this.name = name; - this.startTime = startTime; - this.duration = duration; - } - } - - public static final class RecordingHandler extends DefaultHandler { - - private Stack objects = new Stack<>(); - private Stack> elements = new Stack<>(); - private List 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"))); - switch (qName) { - case "null": - objects.pop(); - objects.push(null); - break; - case "event": - Instant startTime = Instant.parse(attrs.getValue("startTime")); - Duration duration = Duration.parse(attrs.getValue("duration")); - objects.push(new XMLEvent(attrs.getValue("name"), startTime, duration)); - break; - case "struct": - objects.push(new HashMap()); - 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 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) parent).put(name, value); - } - if (parent != null && parent.getClass().isArray()) { - int index = Integer.parseInt(element.getValue()); - ((Object[]) parent)[index] = value; - } - } - } - } -} diff --git a/test/jdk/jdk/jfr/cmd/TestReconstruct.java b/test/jdk/jdk/jfr/cmd/TestReconstruct.java deleted file mode 100644 --- a/test/jdk/jdk/jfr/cmd/TestReconstruct.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.cmd; - -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; - -/** - * @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.cmd.TestReconstruct - */ -public class TestReconstruct { - - @Name("Correlation") - static class CorrelationEvent extends Event { - int id; - } - private static int RECORDING_COUNT = 5; - - @SuppressWarnings("resource") - public static void main(String[] args) throws Exception { - // 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 = ExecuteHelper.run("reconstruct"); - - output.shouldContain("Too few arguments"); - - output = ExecuteHelper.run("reconstruct", directory); - output.shouldContain("Too few arguments"); - - output = ExecuteHelper.run("reconstruct", "not-a-directory", destination); - output.shouldContain("Could not find disk repository at"); - - output = ExecuteHelper.run("reconstruct", directory, "not-a-destination"); - output.shouldContain("Filename must end with .jfr"); - - output = ExecuteHelper.run("reconstruct", "--wrongOption", directory, destination); - output.shouldContain("Too many arguments"); - - FileWriter fw = new FileWriter(destination); - fw.write('d'); - fw.close(); - output = ExecuteHelper.run("reconstruct", directory, destination); - output.shouldContain("already exists"); - Files.delete(destinationPath); - - // test success - output = ExecuteHelper.run("reconstruct", directory, destination); - System.out.println(output.getOutput()); - output.shouldContain("Reconstruction complete"); - - 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; - } - } -} diff --git a/test/jdk/jdk/jfr/cmd/TestSplit.java b/test/jdk/jdk/jfr/cmd/TestSplit.java deleted file mode 100644 --- a/test/jdk/jdk/jfr/cmd/TestSplit.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.cmd; - -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; - -/** - * @test - * @summary Test jfr split - * @key jfr - * @requires vm.hasJFR - * @library /test/lib /test/jdk - * @run main/othervm jdk.jfr.cmd.TestSplit - */ -public class TestSplit { - - public static void main(String[] args) throws Exception { - 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"); - makeRecordingWithChunks(6, recordingFileA); - Files.copy(recordingFileA, recordingFileB); - - String fileAText = recordingFileA.toAbsolutePath().toString(); - String fileBText = recordingFileB.toAbsolutePath().toString(); - - OutputAnalyzer output = ExecuteHelper.run("split"); - output.shouldContain("Missing file"); - - output = ExecuteHelper.run("split", "--wrongOption1", "..wrongOption2", "..wrongOption3", fileAText); - output.shouldContain("Too many arguments"); - - output = ExecuteHelper.run("split", "--wrongOption", fileAText); - output.shouldContain("Unknown option"); - - output = ExecuteHelper.run("split", "--wrongOption", "1", fileAText); - output.shouldContain("Unknown option"); - - output = ExecuteHelper.run("split", "--maxchunks", "-3", fileAText); - output.shouldContain("Must be at least one chunk per file"); - - output = ExecuteHelper.run("split", "--maxchunks", "1000", fileAText); - output.shouldContain("Number of chunks in recording"); - output.shouldContain("doesn't exceed max chunks"); - output = ExecuteHelper.run("split", 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 = ExecuteHelper.run("split", "--maxchunks", "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 = ExecuteHelper.run("split", "--maxchunks", "2", fileBText); - output.shouldContain("file with that name already exist"); - } - - private static void verifyRecording(String name) throws IOException { - System.out.println("split name " + 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(); - } -} diff --git a/test/jdk/jdk/jfr/cmd/TestSummary.java b/test/jdk/jdk/jfr/cmd/TestSummary.java deleted file mode 100644 --- a/test/jdk/jdk/jfr/cmd/TestSummary.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jfr.cmd; - -import java.nio.file.Path; - -import jdk.jfr.EventType; -import jdk.jfr.consumer.RecordingFile; -import jdk.test.lib.process.OutputAnalyzer; - -/** - * @test - * @summary Test jfr info - * @key jfr - * @requires vm.hasJFR - * @library /test/lib /test/jdk - * @run main/othervm jdk.jfr.cmd.TestSummary - */ -public class TestSummary { - - public static void main(String[] args) throws Exception { - Path f = ExecuteHelper.createProfilingRecording().toAbsolutePath(); - String file = f.toAbsolutePath().toString(); - - OutputAnalyzer output = ExecuteHelper.run("summary"); - output.shouldContain("Missing file"); - - output = ExecuteHelper.run("summary", "--wrongOption", file); - output.shouldContain("Too many arguments"); - - output = ExecuteHelper.run("summary", file); - try (RecordingFile rf = new RecordingFile(f)) { - for (EventType t : rf.readEventTypes()) { - output.shouldContain(t.getName()); - } - } - output.shouldContain("Version"); - } -} diff --git a/test/jdk/jdk/jfr/tool/ExecuteHelper.java b/test/jdk/jdk/jfr/tool/ExecuteHelper.java new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/ExecuteHelper.java @@ -0,0 +1,136 @@ +/* + * 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.JDKToolLauncher; +import jdk.test.lib.Utils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools;; + +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(); + } + + public static OutputAnalyzer jfr(String... args) throws Throwable { + JDKToolLauncher l = JDKToolLauncher.createUsingTestJDK("jfr"); + for (String arg : args) { + l.addToolArg(arg); + } + return ProcessTools.executeCommand(l.getCommand()); + } +} diff --git a/test/jdk/jdk/jfr/tool/TestAssemble.java b/test/jdk/jdk/jfr/tool/TestAssemble.java new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/TestAssemble.java @@ -0,0 +1,145 @@ +/* + * 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; + +/** + * @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 = ExecuteHelper.jfr("assemble"); + output.shouldContain("too few arguments"); + + output = ExecuteHelper.jfr("assemble", directory); + output.shouldContain("too few arguments"); + + output = ExecuteHelper.jfr("assemble", "not-a-directory", destination); + output.shouldContain("directory does not exist, not-a-directory"); + + output = ExecuteHelper.jfr("assemble", directory, "not-a-destination"); + output.shouldContain("filename must end with '.jfr'"); + + output = ExecuteHelper.jfr("assemble", "--wrongOption", directory, destination); + output.shouldContain("too many arguments"); + + FileWriter fw = new FileWriter(destination); + fw.write('d'); + fw.close(); + output = ExecuteHelper.jfr("assemble", directory, destination); + output.shouldContain("already exists"); + Files.delete(destinationPath); + + // test success + output = ExecuteHelper.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; + } + } +} diff --git a/test/jdk/jdk/jfr/tool/TestDisassemble.java b/test/jdk/jdk/jfr/tool/TestDisassemble.java new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/TestDisassemble.java @@ -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.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; + +/** + * @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 = ExecuteHelper.jfr("disassemble"); + output.shouldContain("missing file"); + + output = ExecuteHelper.jfr("disassemble", "--wrongOption", fileAText); + output.shouldContain("unknown option"); + + output = ExecuteHelper.jfr("disassemble", "--wrongOption", "1", fileAText); + output.shouldContain("unknown option"); + + output = ExecuteHelper.jfr("disassemble", "--max-chunks", "-3", fileAText); + output.shouldContain("max chunks must be at least 1"); + + output = ExecuteHelper.jfr("disassemble", "--max-chunks", "1000", fileAText); + output.shouldContain("number of chunks in recording"); + output.shouldContain("doesn't exceed max chunks"); + output = ExecuteHelper.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 = ExecuteHelper.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 = ExecuteHelper.jfr("disassemble", "--max-chunks", "2", fileBText); + output.shouldContain("file with that name already exist"); + + // sanity check + output = ExecuteHelper.jfr("disassemble", "--max-size", "10000", fileCText); + verifyRecording(fileCText.substring(0, fileCText.length() - 4) + "_01.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(); + } +} diff --git a/test/jdk/jdk/jfr/tool/TestHelp.java b/test/jdk/jdk/jfr/tool/TestHelp.java new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/TestHelp.java @@ -0,0 +1,56 @@ +/* + * 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; + +/** + * @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 = ExecuteHelper.jfr("help"); + output.shouldContain("print"); + output.shouldContain("assemble"); + output.shouldContain("disassemble"); + output.shouldContain("metadata"); + output.shouldContain("summary"); + output.shouldContain("help"); + + output = ExecuteHelper.jfr("help", "version"); + output.shouldContain("Display version of the jfr tool"); + output.shouldContain("jfr version"); + + output = ExecuteHelper.jfr("help", "wrongcommand"); + output.shouldContain("unknown command 'wrongcommand'"); + } +} diff --git a/test/jdk/jdk/jfr/tool/TestMetadata.java b/test/jdk/jdk/jfr/tool/TestMetadata.java new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/TestMetadata.java @@ -0,0 +1,63 @@ +/* + * 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; + +/** + * @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 = ExecuteHelper.jfr("metadata"); + output.shouldContain("missing file"); + + output = ExecuteHelper.jfr("metadata", "--wrongOption", file); + output.shouldContain("unknown option --wrongOption"); + + output = ExecuteHelper.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); + } + } + } +} diff --git a/test/jdk/jdk/jfr/tool/TestPrint.java b/test/jdk/jdk/jfr/tool/TestPrint.java new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/TestPrint.java @@ -0,0 +1,61 @@ +/* + * 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; + +/** + * @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 = ExecuteHelper.jfr("print"); + output.shouldContain("missing file"); + + output = ExecuteHelper.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 = ExecuteHelper.jfr("print", "--wrongOption", file.toAbsolutePath().toString()); + output.shouldContain("unknown option"); + Files.delete(file); + } +} diff --git a/test/jdk/jdk/jfr/tool/TestPrintDefault.java b/test/jdk/jdk/jfr/tool/TestPrintDefault.java new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/TestPrintDefault.java @@ -0,0 +1,53 @@ +/* + * 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; + +/** + * @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 = ExecuteHelper.jfr("print", recordingFile.toString()); + output.shouldContain("JVMInformation"); + } +} diff --git a/test/jdk/jdk/jfr/tool/TestPrintJSON.java b/test/jdk/jdk/jfr/tool/TestPrintJSON.java new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/TestPrintJSON.java @@ -0,0 +1,163 @@ +/* + * 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; + +/** + * @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 = ExecuteHelper.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 events = RecordingFile.readAllEvents(recordingFile); + Collections.sort(events, (e1, e2) -> e1.getEndTime().compareTo(e2.getEndTime())); + // Verify events are equal + Iterator 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); + } +} diff --git a/test/jdk/jdk/jfr/tool/TestPrintXML.java b/test/jdk/jdk/jfr/tool/TestPrintXML.java new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/TestPrintXML.java @@ -0,0 +1,260 @@ +/* + * 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; + +/** + * @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 = ExecuteHelper.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 events = RecordingFile.readAllEvents(recordingFile); + Collections.sort(events, (e1, e2) -> e1.getEndTime().compareTo(e2.getEndTime())); + Iterator 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 xmlMap = (Map) xmlObject; + List 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 values = new HashMap<>(); + + XMLEvent(String name) { + this.name = name; + } + } + + public static final class RecordingHandler extends DefaultHandler { + + private Stack objects = new Stack<>(); + private Stack> elements = new Stack<>(); + private List 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()); + 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 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) 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); + } + } +} diff --git a/test/jdk/jdk/jfr/tool/TestSummary.java b/test/jdk/jdk/jfr/tool/TestSummary.java new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/TestSummary.java @@ -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.nio.file.Path; + +import jdk.jfr.EventType; +import jdk.jfr.consumer.RecordingFile; +import jdk.test.lib.process.OutputAnalyzer; + +/** + * @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 = ExecuteHelper.jfr("summary"); + output.shouldContain("missing file"); + + output = ExecuteHelper.jfr("summary", "--wrongOption", file); + output.shouldContain("too many arguments"); + + output = ExecuteHelper.jfr("summary", file); + try (RecordingFile rf = new RecordingFile(f)) { + for (EventType t : rf.readEventTypes()) { + output.shouldContain(t.getName()); + } + } + output.shouldContain("Version"); + } +} diff --git a/test/jdk/jdk/jfr/tool/jfr.xsd b/test/jdk/jdk/jfr/tool/jfr.xsd new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/jfr.xsd @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/jdk/tools/launcher/HelpFlagsTest.java b/test/jdk/tools/launcher/HelpFlagsTest.java --- a/test/jdk/tools/launcher/HelpFlagsTest.java +++ b/test/jdk/tools/launcher/HelpFlagsTest.java @@ -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 diff --git a/test/jdk/tools/launcher/VersionCheck.java b/test/jdk/tools/launcher/VersionCheck.java --- a/test/jdk/tools/launcher/VersionCheck.java +++ b/test/jdk/tools/launcher/VersionCheck.java @@ -88,6 +88,7 @@ "jcontrol", "jdeprscan", "jdeps", + "jfr", "jimage", "jinfo", "jlink",