8306703: JFR: Summary views

Reviewed-by: mgronlun
This commit is contained in:
Erik Gahlin 2023-05-25 15:39:45 +00:00
parent 534de6d8ae
commit 98acce13d5
64 changed files with 7651 additions and 71 deletions

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2020, 2023, 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
@ -24,5 +24,5 @@
#
DISABLED_WARNINGS_java += exports
COPY := .xsd .xml .dtd
COPY := .xsd .xml .dtd .ini
JAVAC_FLAGS := -XDstringConcat=inline

View File

@ -52,6 +52,9 @@ bool register_jfr_dcmds() {
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrDumpFlightRecordingDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrStartFlightRecordingDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrStopFlightRecordingDCmd>(full_export, true, false));
// JFR.query Uncomment when developing new queries for the JFR.view command
// DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrQueryFlightRecordingDCmd>(full_export, true, true));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrViewFlightRecordingDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrConfigureFlightRecorderDCmd>(full_export, true, false));
return true;
}
@ -318,7 +321,7 @@ static DCmdArgumentInfo* create_info(oop argument, TRAPS) {
read_string_field(argument, "type", THREAD),
read_string_field(argument, "defaultValue", THREAD),
read_boolean_field(argument, "mandatory", THREAD),
true, // a DcmdFramework "option"
read_boolean_field(argument, "option", THREAD),
read_boolean_field(argument, "allowMultiple", THREAD));
}

View File

@ -145,6 +145,56 @@ class JfrStopFlightRecordingDCmd : public JfrDCmd {
}
};
class JfrViewFlightRecordingDCmd : public JfrDCmd {
public:
JfrViewFlightRecordingDCmd(outputStream* output, bool heap) : JfrDCmd(output, heap, num_arguments()) {}
static const char* name() {
return "JFR.view";
}
static const char* description() {
return "Display event data in predefined views";
}
static const char* impact() {
return "Medium";
}
static const JavaPermission permission() {
JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL};
return p;
}
virtual const char* javaClass() const {
return "jdk/jfr/internal/dcmd/DCmdView";
}
static int num_arguments() {
return 7;
}
};
class JfrQueryFlightRecordingDCmd : public JfrDCmd {
public:
JfrQueryFlightRecordingDCmd(outputStream* output, bool heap) : JfrDCmd(output, heap, num_arguments()) {}
static const char* name() {
return "JFR.query";
}
static const char* description() {
return "Query and display event data in a tabular form";
}
static const char* impact() {
return "Medium";
}
static const JavaPermission permission() {
JavaPermission p = {"java.lang.management.ManagementPermission", "monitor", NULL};
return p;
}
virtual const char* javaClass() const {
return "jdk/jfr/internal/dcmd/DCmdQuery";
}
static int num_arguments() {
return 5;
}
};
class JfrConfigureFlightRecorderDCmd : public DCmdWithParser {
friend class JfrOptionSet;
protected:

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2023, 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
@ -79,7 +79,7 @@ public final class OldObjectSample {
}
}
private static void emit(long ticks) {
public static void emit(long ticks) {
boolean emitAll = WhiteBox.getWriteAllObjectSamples();
boolean skipBFS = WhiteBox.getSkipBFS();
JVM.getJVM().emitOldObjectSamples(ticks, emitAll, skipBFS);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2023, 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
@ -420,7 +420,7 @@ public final class PlatformRecorder {
return runningRecordings;
}
private List<RepositoryChunk> makeChunkList(Instant startTime, Instant endTime) {
public List<RepositoryChunk> makeChunkList(Instant startTime, Instant endTime) {
Set<RepositoryChunk> chunkSet = new HashSet<>();
for (PlatformRecording r : getRecordings()) {
chunkSet.addAll(r.getChunks());
@ -438,7 +438,7 @@ public final class PlatformRecorder {
return chunks;
}
return Collections.emptyList();
return new ArrayList<>();
}
private void startDiskMonitor() {
@ -454,6 +454,8 @@ public final class PlatformRecorder {
r.appendChunk(chunk);
}
}
// Decrease initial reference count
chunk.release();
FilePurger.purge();
}
@ -659,4 +661,8 @@ public final class PlatformRecorder {
rotateDisk();
}
}
public RepositoryChunk getCurrentChunk() {
return currentChunk;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -33,7 +33,7 @@ import java.util.Comparator;
import jdk.jfr.internal.SecuritySupport.SafePath;
final class RepositoryChunk {
public final class RepositoryChunk {
static final Comparator<RepositoryChunk> END_TIME_COMPARATOR = new Comparator<RepositoryChunk>() {
@Override
@ -47,7 +47,7 @@ final class RepositoryChunk {
private Instant endTime = null; // unfinished
private Instant startTime;
private int refCount = 0;
private int refCount = 1;
private long size;
RepositoryChunk(SafePath path) throws Exception {
@ -166,4 +166,12 @@ final class RepositoryChunk {
public SafePath getFile() {
return chunkFile;
}
public long getCurrentFileSize() {
try {
return SecuritySupport.getFileSize(chunkFile);
} catch (IOException e) {
return 0L;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2023, 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
@ -55,6 +55,7 @@ import java.util.Objects;
import jdk.internal.module.Checks;
import jdk.jfr.Event;
import jdk.jfr.EventType;
import jdk.jfr.FlightRecorderPermission;
import jdk.jfr.Recording;
import jdk.jfr.RecordingState;
@ -859,4 +860,12 @@ public final class Utils {
}
}
}
public static String makeSimpleName(EventType type) {
return makeSimpleName(type.getName());
}
public static String makeSimpleName(String qualified) {
return qualified.substring(qualified.lastIndexOf(".") + 1);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2023, 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
@ -38,6 +38,8 @@ import java.util.List;
import jdk.jfr.FlightRecorder;
import jdk.jfr.Recording;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.util.Output.LinePrinter;
import jdk.jfr.internal.util.Output;
import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger;
@ -50,9 +52,7 @@ import jdk.jfr.internal.Utils;
*
*/
abstract class AbstractDCmd {
private final StringBuilder currentLine = new StringBuilder(80);
private final List<String> lines = new ArrayList<>();
private final LinePrinter output = new LinePrinter();
private String source;
// Called by native
@ -94,13 +94,16 @@ abstract class AbstractDCmd {
}
}
protected final Output getOutput() {
return output;
}
protected final FlightRecorder getFlightRecorder() {
return FlightRecorder.getFlightRecorder();
}
protected final String[] getResult() {
return lines.toArray(new String[lines.size()]);
return output.getLines().toArray(new String[0]);
}
protected void logWarning(String message) {
@ -181,21 +184,19 @@ abstract class AbstractDCmd {
}
protected final void println() {
lines.add(currentLine.toString());
currentLine.setLength(0);
output.println();
}
protected final void print(String s) {
currentLine.append(s);
output.print(s);
}
protected final void print(String s, Object... args) {
currentLine.append(args.length > 0 ? String.format(s, args) : s);
output.print(s, args);
}
protected final void println(String s, Object... args) {
print(s, args);
println();
output.println(s, args);
}
protected final void printBytes(long bytes) {
@ -218,6 +219,12 @@ abstract class AbstractDCmd {
}
}
protected final void printHelpText() {
for (String line : printHelp()) {
println(line);
}
}
protected final void printPath(Path path) {
try {
println(path.toAbsolutePath().toString());

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2023, 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
@ -29,6 +29,7 @@ record Argument(
String description,
String type,
boolean mandatory,
boolean option,
String defaultValue,
boolean allowMultiple
) { }

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2023, 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
@ -35,6 +35,7 @@ import java.util.StringJoiner;
final class ArgumentParser {
private final Map<String, Object> options = new HashMap<>();
private final Map<String, Object> extendedOptions = new HashMap<>();
private final List<String> conflictedOptions = new ArrayList<>();
private final StringBuilder builder = new StringBuilder();
private final String text;
private final char delimiter;
@ -42,8 +43,7 @@ final class ArgumentParser {
private final String valueDelimiter;
private final Argument[] arguments;
private int position;
private final List<String> conflictedOptions = new ArrayList<>();
private int argumentIndex;
ArgumentParser(Argument[] arguments, String text, char delimiter) {
this.text = text;
@ -60,6 +60,11 @@ final class ArgumentParser {
String value = null;
if (accept('=')) {
value = readText(valueDelimiter);
} else {
if (hasArgumentsLeft()) {
value = key;
key = nextArgument().name();
}
}
if (!atEnd() && !accept(delimiter)) { // must be followed by delimiter
throw new IllegalArgumentException("Expected delimiter, but found " + currentChar());
@ -72,6 +77,25 @@ final class ArgumentParser {
return options;
}
private boolean hasArgumentsLeft() {
for (int index = argumentIndex; index < arguments.length; index++) {
if (!arguments[index].option()) {
return true;
}
}
return false;
}
private Argument nextArgument() {
while (argumentIndex < arguments.length) {
Argument argument = arguments[argumentIndex++];
if (!argument.option()) {
return argument;
}
}
return null;
}
protected void checkConflict() {
if (conflictedOptions.isEmpty()) {
return;
@ -97,14 +121,15 @@ final class ArgumentParser {
throw new IllegalArgumentException(sb.toString());
}
private void checkMandatory() {
public boolean checkMandatory() {
for (Argument arg : arguments) {
if (!options.containsKey(arg.name())) {
if (arg.mandatory()) {
throw new IllegalArgumentException("The argument '" + arg.name() + "' is mandatory");
return false;
}
}
}
return true;
}
@SuppressWarnings({"unchecked", "rawtypes"})
@ -194,6 +219,7 @@ final class ArgumentParser {
private Object value(String name, String type, String text) {
return switch (type) {
case "JULONG" -> parseLong(name, text);
case "STRING", "STRING SET" -> text == null ? "" : text;
case "BOOLEAN" -> parseBoolean(name, text);
case "NANOTIME" -> parseNanotime(name, text);
@ -202,6 +228,22 @@ final class ArgumentParser {
};
}
private Long parseLong(String name, String text) {
if (text == null) {
throw new IllegalArgumentException("Parsing error long value: syntax error, value is null");
}
try {
long value = Long.parseLong(text);
if (value >= 0) {
return value;
}
} catch (NumberFormatException nfe) {
// fall through
}
String msg = "Integer parsing error in command argument '" + name + "'. Could not parse: " + text + ".";
throw new IllegalArgumentException(msg);
}
private Boolean parseBoolean(String name, String text) {
if ("true".equals(text)) {
return Boolean.TRUE;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2023, 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
@ -169,10 +169,10 @@ final class DCmdCheck extends AbstractDCmd {
return new Argument[] {
new Argument("name",
"Recording name, e.g. \\\"My Recording\\\" or omit to see all recordings",
"STRING", false, null, false),
"STRING", false, true, null, false),
new Argument("verbose",
"Print event settings for the recording(s)","BOOLEAN",
false, "false", false)
false, true, "false", false)
};
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2023, 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
@ -281,24 +281,24 @@ final class DCmdDump extends AbstractDCmd {
return new Argument[] {
new Argument("name",
"Recording name, e.g. \\\"My Recording\\\"",
"STRING", false, null, false),
"STRING", false, true, null, false),
new Argument("filename",
"Copy recording data to file, e.g. \\\"" + exampleFilename() + "\\\"",
"STRING", false, null, false),
"STRING", false, true, null, false),
new Argument("maxage",
"Maximum duration to dump, in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit",
"NANOTIME", false, null, false),
"NANOTIME", false, true, null, false),
new Argument("maxsize", "Maximum amount of bytes to dump, in (M)B or (G)B, e.g. 500M, or 0 for no limit",
"MEMORY SIZE", false, "hotspot-pid-xxxxx-id-y-YYYY_MM_dd_HH_mm_ss.jfr", false),
"MEMORY SIZE", false, true, "hotspot-pid-xxxxx-id-y-YYYY_MM_dd_HH_mm_ss.jfr", false),
new Argument("begin",
"Point in time to dump data from, e.g. 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d",
"STRING", false, null, false),
"STRING", false, true, null, false),
new Argument("end",
"Point in time to dump data to, e.g. 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d",
"STRING", false, null, false),
"STRING", false, true, null, false),
new Argument("path-to-gc-roots",
"Collect path to GC roots",
"BOOLEAN", false, "false", false)
"BOOLEAN", false, true, "false", false)
};
}
}

View File

@ -0,0 +1,171 @@
/*
* Copyright (c) 2023, 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.dcmd;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import jdk.jfr.internal.query.Configuration;
import jdk.jfr.internal.query.QueryPrinter;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
/**
* JFR.query
*/
// Instantiated by native
public final class DCmdQuery extends AbstractDCmd {
protected void execute(ArgumentParser parser) throws DCmdException {
parser.checkUnknownArguments();
if (!parser.checkMandatory()) {
println("The argument 'query' is mandatory");
println();
printHelpText();
return;
}
Configuration configuration = new Configuration();
configuration.output = getOutput();
configuration.endTime = Instant.now().minusSeconds(1);
Boolean verbose = parser.getOption("verbose");
if (verbose != null) {
configuration.verboseHeaders = verbose;
}
try (QueryRecording recording = new QueryRecording(configuration, parser)) {
QueryPrinter printer = new QueryPrinter(configuration, recording.getStream());
String query = parser.getOption("query");
printer.execute(stripQuotes(query));
} catch (UserDataException e) {
throw new DCmdException(e.getMessage());
} catch (UserSyntaxException e) {
throw new DCmdException(e.getMessage());
} catch (IOException e) {
throw new DCmdException("Could not open repository. " + e.getMessage());
} catch (IllegalArgumentException e) {
throw new DCmdException(e.getMessage() + ". See help JFR.query");
}
}
private String stripQuotes(String text) {
if (text.startsWith("\"")) {
text = text.substring(1);
}
if (text.endsWith("\"")) {
text = text.substring(0, text.length() - 1);
}
return text;
}
@Override
public String[] printHelp() {
List<String> lines = new ArrayList<>();
lines.addAll(getOptions().lines().toList());
lines.add("");
lines.addAll(QueryPrinter.getGrammarText().lines().toList());
lines.add("");
lines.addAll(getExamples().lines().toList());
return lines.toArray(String[]::new);
}
private String getExamples() {
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
return """
Example usage:
$ jcmd <pid> JFR.query '"SHOW EVENTS"'
$ jcmd <pid> JFR.query '"SHOW FIELDS ObjectAllocationSample"'
$ jcmd <pid> JFR.query '"SELECT * FROM ObjectAllocationSample"'
verbose=true maxsize=10M
$ jcmd <pid> JFR.query '"SELECT pid, path FROM SystemProcess"'
width=100
$ jcmd <pid> JFR.query '"SELECT stackTrace.topFrame AS T, SUM(weight)
FROM ObjectAllocationSample GROUP BY T"'
maxage=100s
$ jcmd <pid> JFR.query '"CAPTION 'Method', 'Percentage'
FORMAT default, normalized;width:10
SELECT stackTrace.topFrame AS T, COUNT(*) AS C
GROUP BY T
FROM ExecutionSample ORDER BY C DESC"'
$ jcmd <pid> JFR.query '"CAPTION 'Start', 'GC ID', 'Heap Before GC',
'Heap After GC', 'Longest Pause'
SELECT G.startTime, G.gcId, B.heapUsed,
A.heapUsed, longestPause
FROM GarbageCollection AS G,
GCHeapSummary AS B,
GCHeapSummary AS A
WHERE B.when = 'Before GC' AND A.when = 'After GC'
GROUP BY gcId
ORDER BY G.startTime"'""";
}
private String getOptions() {
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
return """
Syntax : JFR.query [options]
Options:
maxage (Optional) Length of time for the query to span. (INTEGER followed by
's' for seconds 'm' for minutes or 'h' for hours, no default value)
maxsize (Optional) Maximum size for the query to span in bytes if one of
the following suffixes is not used: 'm' or 'M' for megabytes OR
'g' or 'G' for gigabytes. (STRING, no default value)
<query> (Mandatory) Query, for example '"SELECT * FROM GarbageCollection"'
See below for grammar. (STRING, no default value)
verbose (Optional) Display additional information about the query execution.
(BOOLEAN, false)
width (Optional) Maximum number of horizontal characters. (BOOLEAN, false)""";
}
@Override
public Argument[] getArgumentInfos() {
return new Argument[] {
new Argument("maxage",
"Length of time for the query to span, in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit",
"NANOTIME", false, true, "10m", false),
new Argument("maxsize",
"Maximum size for the query to span, in (M)B or (G)B, e.g. 500M, or 0 for no limit",
"MEMORY SIZE", false, true, "100M", false),
new Argument("query", "Query, for example 'SELECT * FROM GarbageCollection'", "STRING", true, false,
null, false),
new Argument("verbose", "Display additional information about the query execution", "BOOLEAN", false,
true, "false", false),
new Argument("width", "Maximum number of horizontal characters", "JULONG", false, true, "100",
false), };
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2023, 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
@ -456,37 +456,37 @@ final class DCmdStart extends AbstractDCmd {
return new Argument[] {
new Argument("name",
"Name that can be used to identify recording, e.g. \\\"My Recording\\\"",
"STRING", false, null, false),
"STRING", false, true, null, false),
new Argument("settings",
"Settings file(s), e.g. profile or default. See JAVA_HOME/lib/jfr",
"STRING SET", false, "deafult.jfc", true),
"STRING SET", false, true, "default.jfc", true),
new Argument("delay",
"Delay recording start with (s)econds, (m)inutes), (h)ours), or (d)ays, e.g. 5h.",
"NANOTIME", false, "0s", false),
"NANOTIME", false, true, "0s", false),
new Argument("duration",
"Duration of recording in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 300s.",
"NANOTIME", false, null, false),
"NANOTIME", false, true, null, false),
new Argument("disk",
"Recording should be persisted to disk",
"BOOLEAN", false, "true", false),
"BOOLEAN", false, true, "true", false),
new Argument("filename",
"Resulting recording filename, e.g. \\\"" + exampleFilename() + "\\\"",
"STRING", false, "hotspot-pid-xxxxx-id-y-YYYY_MM_dd_HH_mm_ss.jfr", false),
"STRING", false, true, "hotspot-pid-xxxxx-id-y-YYYY_MM_dd_HH_mm_ss.jfr", false),
new Argument("maxage",
"Maximum time to keep recorded data (on disk) in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit",
"NANOTIME", false, "0", false),
"NANOTIME", false, true, "0", false),
new Argument("maxsize",
"Maximum amount of bytes to keep (on disk) in (k)B, (M)B or (G)B, e.g. 500M, or 0 for no limit",
"MEMORY SIZE", false, "250M", false),
"MEMORY SIZE", false, true, "250M", false),
new Argument("flush-interval",
"Minimum time before flushing buffers, measured in (s)econds, e.g. 4 s, or 0 for flushing when a recording ends",
"NANOTIME", false, "1s", false),
"NANOTIME", false, true, "1s", false),
new Argument("dumponexit",
"Dump running recording when JVM shuts down",
"BOOLEAN", false, "false", false),
"BOOLEAN", false, true, "false", false),
new Argument("path-to-gc-roots",
"Collect path to GC roots",
"BOOLEAN", false, "false", false)
"BOOLEAN", false, true, "false", false)
};
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2023, 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
@ -104,10 +104,10 @@ final class DCmdStop extends AbstractDCmd {
return new Argument[] {
new Argument("name",
"Recording name, e.g. \\\"My Recording\\\"",
"STRING", true, null, false),
"STRING", true, true, null, false),
new Argument("filename",
"Copy recording data to file, e.g. \\\"" + exampleFilename() + "\\\"",
"STRING", false, null, false)
"STRING", false, true, null, false)
};
}
}

View File

@ -0,0 +1,168 @@
/*
* Copyright (c) 2023, 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.dcmd;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import jdk.jfr.internal.OldObjectSample;
import jdk.jfr.internal.Utils;
import jdk.jfr.internal.query.Configuration;
import jdk.jfr.internal.query.QueryPrinter;
import jdk.jfr.internal.query.ViewPrinter;
import jdk.jfr.internal.util.Columnizer;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
/**
* JFR.view
* <p>
* The implementation is also used by DCmdQuery since there
* is little difference between JFR.query and JFR.view.
*/
// Instantiated by native
public class DCmdView extends AbstractDCmd {
protected void execute(ArgumentParser parser) throws DCmdException {
parser.checkUnknownArguments();
if (!parser.checkMandatory()) {
println("The argument 'view' is mandatory");
println();
printHelpText();
return;
}
Configuration configuration = new Configuration();
configuration.output = getOutput();
configuration.endTime = Instant.now().minusSeconds(1);
String view = parser.getOption("view");
if (view.startsWith("memory-leaks")) {
// Make sure old object sample event is part of data.
OldObjectSample.emit(0);
Utils.waitFlush(10_000);
configuration.endTime = Instant.now();
}
try (QueryRecording recording = new QueryRecording(configuration, parser)) {
ViewPrinter printer = new ViewPrinter(configuration, recording.getStream());
printer.execute(view);
} catch (UserDataException e) {
throw new DCmdException(e.getMessage());
} catch (UserSyntaxException e) {
throw new DCmdException(e.getMessage());
} catch (IOException e) {
throw new DCmdException("Could not open repository. " + e.getMessage());
} catch (IllegalArgumentException e) {
throw new DCmdException(e.getMessage() + ". See help JFR.view");
}
}
@Override
public String[] printHelp() {
List<String> lines = new ArrayList<>();
lines.addAll(getOptions().lines().toList());
lines.add("");
lines.addAll(ViewPrinter.getAvailableViews());
lines.add("");
lines.add(" The <view> parameter can be an event type name. Use the 'JFR.view types'");
lines.add(" to see a list. To display all views, use 'JFR.view all-views'. To display");
lines.add(" all events, use 'JFR.view all-events'.");
lines.add("");
lines.addAll(getExamples().lines().toList());
return lines.toArray(String[]::new);
}
public String getOptions() {
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
return """
Options:
cell-height (Optional) Maximum number of rows in a table cell. (INTEGER, no default value)
maxage (Optional) Length of time for the view to span. (INTEGER followed by
's' for seconds 'm' for minutes or 'h' for hours, default value is 10m)
maxsize (Optional) Maximum size for the view to span in bytes if one of
the following suffixes is not used: 'm' or 'M' for megabytes OR
'g' or 'G' for gigabytes. (STRING, default value is 32MB)
truncate (Optional) How to truncate content that exceeds space in a table cell.
Mode can be 'beginning' or 'end'. (STRING, default value 'end')
verbose (Optional) Displays the query that makes up the view.
(BOOLEAN, default value false)
<view> (Mandatory) Name of the view or event type to display.
See list below for available views. (STRING, no default value)
width (Optional) The width of the view in characters
(INTEGER, no default value)""";
}
public String getExamples() {
return """
Example usage:
$ jcmd <pid> JFR.view gc
$ jcmd <pid< JFR.view width=160 hot-methods
$ jcmd <pid> JFR.view verbose=true allocation-by-class
$ jcmd <pid> JFR.view contention-by-site
$ jcmd <pid> JFR.view jdk.GarbageCollection
$ jcmd <pid> JFR.view cell-height=5 ThreadStart
$ jcmd <pid> JFR.view truncate=beginning SystemProcess""";
}
@Override
public Argument[] getArgumentInfos() {
return new Argument[] {
new Argument("cell-height",
"Maximum heigth of a table cell",
"JULONG", false, true, "1", false),
new Argument("maxage",
"Maximum duration of data to view, in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit",
"NANOTIME", false, true, "10m", false),
new Argument("maxsize",
"Maximum amount of bytes to view, in (M)B or (G)B, e.g. 500M, or 0 for no limit",
"MEMORY SIZE", false, true, "100M", false),
new Argument("truncate",
"Truncation mode if value doesn't fit in a table cell, valid values are 'beginning' and 'end'",
"STRING", false, true, "end", false),
new Argument("verbose",
"Display additional information about the view, such as the underlying query",
"BOOLEAN", false, true, "false", false),
new Argument("view",
"Name of the view, for example hot-methods",
"STRING", true, false, null, false),
new Argument("width",
"Maximum number of horizontal characters",
"JULONG", false, true, "100", false)
};
}
}

View File

@ -0,0 +1,166 @@
/*
* Copyright (c) 2023, 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.dcmd;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import jdk.jfr.FlightRecorder;
import jdk.jfr.consumer.EventStream;
import jdk.jfr.internal.PlatformRecorder;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.RepositoryChunk;
import jdk.jfr.internal.query.Configuration;
import jdk.jfr.internal.query.QueryPrinter;
import jdk.jfr.internal.query.ViewPrinter;
import jdk.jfr.internal.query.Configuration.Truncate;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
import jdk.jfr.internal.util.Output;
/**
* Helper class that holds recording chunks alive during a query. It also helps
* out with configuration shared by DCmdView and DCmdQuery
*/
final class QueryRecording implements AutoCloseable {
private final long DEFAULT_MAX_SIZE = 32 * 1024 * 1024L;
private final long DEFAULT_MAX_AGE = 60 * 10;
private final PlatformRecorder recorder;
private final List<RepositoryChunk> chunks;
private final EventStream eventStream;
private final Instant endTime;
public QueryRecording(Configuration configuration, ArgumentParser parser) throws IOException, DCmdException {
if (!FlightRecorder.isInitialized()) {
throw new DCmdException("No recording data available. Start a recording with JFR.start");
}
recorder = PrivateAccess.getInstance().getPlatformRecorder();
Boolean verbose = parser.getOption("verbose");
if (verbose != null) {
configuration.verbose = verbose;
}
configuration.truncate = valueOf(parser.getOption("truncate"));
Long width = parser.getOption("width");
if (width != null) {
configuration.width = (int) Math.min(Integer.MAX_VALUE, width.longValue());
}
Long height = parser.getOption("cell-height");
if (height != null) {
if (height < 1) {
throw new DCmdException("Height must be at least 1");
}
configuration.cellHeight = (int) Math.min(Integer.MAX_VALUE, height.longValue());
}
Long maxAge = parser.getOption("maxage");
Long maxSize = parser.getOption("maxsize");
if (maxSize == null) {
maxSize = DEFAULT_MAX_SIZE;;
}
Instant startTime = Instant.EPOCH;
endTime = configuration.endTime;
if (maxAge != null) {
startTime = endTime.minus(Duration.ofNanos(maxAge));
} else {
startTime = endTime.minus(Duration.ofSeconds(DEFAULT_MAX_AGE));
}
chunks = acquireChunks(startTime);
Instant streamStart = determineStreamStart(maxSize, startTime);
configuration.startTime = streamStart;
eventStream = makeStream(streamStart);
}
private List<RepositoryChunk> acquireChunks(Instant startTime) {
synchronized (recorder) {
List<RepositoryChunk> list = recorder.makeChunkList(startTime, endTime);
list.add(currentChunk());
for (RepositoryChunk r : list) {
r.use();
}
return list;
}
}
private RepositoryChunk currentChunk() {
return PrivateAccess.getInstance().getPlatformRecorder().getCurrentChunk();
}
private void releaseChunks() {
synchronized (recorder) {
for (RepositoryChunk r : chunks) {
r.release();
}
}
}
private EventStream makeStream(Instant startTime) throws IOException {
EventStream es = EventStream.openRepository();
es.setStartTime(startTime);
es.setEndTime(endTime);
return es;
}
private Instant determineStreamStart(Long maxSize, Instant startTime) {
ListIterator<RepositoryChunk> iterator = chunks.listIterator(chunks.size());
long size = 0;
while (iterator.hasPrevious()) {
RepositoryChunk r = iterator.previous();
if (r.isFinished()) {
size += r.getSize();
if (size > maxSize) {
return r.getStartTime().isAfter(startTime) ? r.getStartTime() : startTime;
}
} else {
size += r.getCurrentFileSize();
}
}
return startTime;
}
private Truncate valueOf(String truncate) throws DCmdException {
if (truncate == null || truncate.equals("end")) {
return Truncate.END;
}
if (truncate.equals("beginning")) {
return Truncate.BEGINNING;
}
throw new DCmdException("Truncate must be 'end' or 'beginning");
}
public EventStream getStream() {
return eventStream;
}
@Override
public void close() {
eventStream.close();
releaseChunks();
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2023, 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.query;
/**
* Enum describing the different ways values can be aggregated.
*/
enum Aggregator {
/**
* Dummy to indicate no aggregation is being used.
*/
MISSING(" "),
/**
* Calculate the average value of all finite numeric values.
*/
AVERAGE("AVG"),
/**
* Calculate the number of elements, including {@code null}.
*/
COUNT("COUNT"),
/**
* Calculate the difference between the last and first finite numeric value.
*/
DIFFERENCE("DIFF"),
/**
* The first value, including {@code null}.
*/
FIRST("FIRST"),
/**
* The last value, including {@code null}.
*/
LAST("LAST"),
/**
* Aggregate values into a comma-separated list, including {@code null}.
*/
LIST("LIST"),
/**
* The highest numeric value.
*/
MAXIMUM("MAX"),
/**
* The median of all finite numeric values.
*/
MEDIAN("MEDIAN"),
/**
* The lowest numeric value.
*/
MINIMUM("MIN"),
/**
* Calculate the 90th percentile of all finite numeric values.
*/
P90("P90"),
/**
* Calculate the 95th percentile of all finite numeric values.
*/
P95("P95"),
/**
* Calculate the 99th percentile of all finite numeric values.
*/
P99("P99"),
/**
* Calculate the 99.9th percentile of all finite numeric values.
*/
P999("P999"),
/**
* Calculate the standard deviation of all finite numeric values.
*/
STANDARD_DEVIATION("STDEV"),
/**
* Calculate the sum of all finite numeric values.
*/
SUM("SUM"),
/**
* Calculates the number of distinct values determined by invoking Object.equals.
*/
UNIQUE("UNIQUE"),
/**
* The last elements, for an event type, that all share the same end timestamp.
*/
LAST_BATCH("LAST_BATCH");
public final String name;
private Aggregator(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2023, 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.query;
import java.time.Instant;
import jdk.jfr.internal.util.Output;
/**
* Holds information on how a query should be rendered.
*/
public final class Configuration {
public static final int MAX_PREFERRED_WIDTH = 120;
public static final int MIN_PREFERRED_WIDTH = 40;
public static final int PREFERRED_WIDTH = 80;
public enum Truncate {
BEGINNING, END
}
/**
* Where the rendered result should be printed.
*/
public Output output;
/**
* The title of the table or form.
* <p>
* {@code null) means no title.
*/
public String title;
/**
* Truncation mode if text overflows.
* <p>
* If truncate is not set, it will be determined by heuristics.
*/
public Truncate truncate;
/**
* Height of table cells.
* <p>
* If cellHeight is not set, it will be determined by heuristics.
*/
public int cellHeight;
/**
* Width of a table or form.
* <p>
* If width is not set, it will be determined by heuristics.
*/
public int width;
/**
* If additional information should be printed.
*/
public boolean verbose;
/**
* If symbolic names should be printed for table headers.
*/
public boolean verboseHeaders;
/**
* If the title of the table or form should be printed.
*/
public boolean verboseTitle;
/**
* The start time for the query.
* <p>
* {@code null) means no start time.
*/
public Instant startTime;
/**
* The end time for the query.
* <p>
* {@code null) means no end time.
*/
public Instant endTime;
}

View File

@ -0,0 +1,147 @@
/*
* Copyright (c) 2023, 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.query;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.internal.query.Configuration.Truncate;
import jdk.jfr.internal.query.Query.Grouper;
import jdk.jfr.internal.query.Query.OrderElement;
/**
* Field is the core class of the package.
* <p>
* It contains all the information related to how a field in a query should be
* grouped, sorted, formatted and aggregated.
* <p>
* Defaults are derived from the metadata available in the event type, for
* example, numeric fields are right-aligned in the output, but can also
* be set using query clauses COLUMN and FORMAT.
* <p>
* Settings in {@Configuration} overrides any field setting.
*/
final class Field {
// The fields to use as data sources, for example, when a
// field references multiple event types. First field
// is always the same as this field.
// (It's confusing, but hard to come up with an another
// abstraction that doesn't complicate the implementation.)
final List<Field> sourceFields = new ArrayList<>();
// Source type
final FilteredType type;
// Symbolic name
final String name;
// Index in the fields list
int index;
// Human readable name
String label;
// Function to extract a value from an event object
Function<RecordedEvent, Object> valueGetter;
// Set if the field is part of GROUP BY clause
Grouper grouper;
// Set if the field is part of ORDER BY clause
OrderElement orderer;
// Set if the field is part of an aggregation
Aggregator aggregator = Aggregator.MISSING;
// Height of a table cell
int cellHeight = 1;
// Truncation mode (beginning or end)
Truncate truncate;
// If the value visible
boolean visible;
// Should value be aligned left
boolean alignLeft;
// Should value be normalized between 0.0 and 1.0
boolean normalized;
// Should column be sorted textually
boolean lexicalSort;
// A percentage value
boolean percentage;
// A frequency value
boolean frequency;
// A memory address
boolean memoryAddress;
// A byte value
boolean bytes;
// A bits value
boolean bits;
// A fractional type (double or float)
boolean fractionalType;
// An integral type (byte, short, int, long)
boolean integralType;
// A java.time.Duration
boolean timespan;
// A java.time.Instant
boolean timestamp;
// Used by LAST_BATCH aggregator
Instant last = Instant.EPOCH;
// The data type, for example, jdk.types.Frame or java.lang.String
String dataType;
// Should not be given additional whitespace if available
public boolean fixedWidth;
// Text to render if value is missing, typically used when value is null
public String missingText = "N/A";
public Field(FilteredType type, String name) {
this.type = type;
this.name = name;
}
@Override
public String toString() {
return type.getName() + "#" + name;
}
}

View File

@ -0,0 +1,422 @@
/*
* Copyright (c) 2023, 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.query;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import jdk.jfr.DataAmount;
import jdk.jfr.EventType;
import jdk.jfr.Frequency;
import jdk.jfr.MemoryAddress;
import jdk.jfr.Percentage;
import jdk.jfr.Timespan;
import jdk.jfr.Timestamp;
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.RecordedStackTrace;
/**
* This is a helper class to QueryResolver. It handles the creation of fields
* and their default configuration.
* <p>
* The class applies heuristics to decide how values should be formatted,
* and labeled.
*/
final class FieldBuilder {
private static final Set<String> KNOWN_TYPES = createKnownTypes();
private final List<EventType> eventTypes;
private final ValueDescriptor descriptor;
private final Field field;
private final String fieldName;
public FieldBuilder(List<EventType> eventTypes, FilteredType type, String fieldName) {
this.eventTypes = eventTypes;
this.descriptor = type.getField(fieldName);
this.field = new Field(type, fieldName);
this.fieldName = fieldName;
}
public List<Field> build() {
if (configureSyntheticFields()) {
field.fixedWidth = false;
return List.of(field);
}
if (descriptor != null) {
field.fixedWidth = !descriptor.getTypeName().equals("java.lang.String");
field.dataType = descriptor.getTypeName();
field.label = makeLabel(descriptor, hasDuration());
field.alignLeft = true;
field.valueGetter = valueGetter(field.name);
configureNumericTypes();
configureTime();
configurePercentage();
configureDataAmount();
configureFrequency();
configureMemoryAddress();
configureKnownType();
return List.of(field);
}
return List.of();
}
private static Function<RecordedEvent, Object> valueGetter(String name) {
return event -> {
try {
return event.getValue(name);
} catch (NullPointerException e) {
// This can happen when accessing a nested structure
// that is null, for example root.referrer
return null;
}
};
}
private boolean hasDuration() {
return field.type.getField("duration") != null;
}
private boolean configureSyntheticFields() {
if (fieldName.equals("stackTrace.topApplicationFrame")) {
configureTopApplicationFrameField();
return true;
}
if (fieldName.equals("stackTrace.notInit")) {
configureNotInitFrameField();
return true;
}
if (fieldName.equals("stackTrace.topFrame")) {
configureTopFrameField();
return true;
}
if (fieldName.equals("id") && field.type.getName().equals("jdk.ActiveSetting")) {
configureEventTypeIdField();
return true;
}
if (fieldName.equals("eventType.label")) {
configureEventType(e -> e.getEventType().getLabel());
return true;
}
if (fieldName.equals("eventType.name")) {
configureEventType(e -> e.getEventType().getName());
return true;
}
return false;
}
private void configureEventTypeIdField() {
Map<Long, String> eventTypes = createEventTypeLookup();
field.alignLeft = true;
field.label = "Event Type";
field.dataType = String.class.getName();
field.valueGetter = event -> eventTypes.get(event.getLong("id"));
field.lexicalSort = true;
field.integralType = false;
}
private Map<Long, String> createEventTypeLookup() {
Map<Long, String> map = new HashMap<>();
for (EventType eventType : eventTypes) {
String label = eventType.getLabel();
if (label == null) {
label = eventType.getName();
}
map.put(eventType.getId(), label);
}
return map;
}
private void configureTopFrameField() {
field.alignLeft = true;
field.label = "Method";
field.dataType = "jdk.types.Method";
field.valueGetter = e -> {
RecordedStackTrace t = e.getStackTrace();
return t != null ? t.getFrames().getFirst() : null;
};
field.lexicalSort = true;
}
private void configureCustomFrame(Predicate<RecordedFrame> condition) {
field.alignLeft = true;
field.dataType = "jdk.types.Frame";
field.label = "Method";
field.lexicalSort = true;
field.valueGetter = e -> {
RecordedStackTrace t = e.getStackTrace();
if (t != null) {
for (RecordedFrame f : t.getFrames()) {
if (f.isJavaFrame()) {
if (condition.test(f)) {
return f;
}
}
}
}
return null;
};
}
private void configureNotInitFrameField() {
configureCustomFrame(frame -> {
return !frame.getMethod().getName().equals("<init>");
});
}
private void configureTopApplicationFrameField() {
configureCustomFrame(frame -> {
RecordedClass cl = frame.getMethod().getType();
RecordedClassLoader classLoader = cl.getClassLoader();
return classLoader != null && !"bootstrap".equals(classLoader.getName());
});
}
private void configureEventType(Function<RecordedEvent, Object> retriever) {
field.alignLeft = true;
field.dataType = String.class.getName();
field.label = "Event Type";
field.valueGetter = retriever;
field.lexicalSort = true;
}
private static String makeLabel(ValueDescriptor v, boolean hasDuration) {
String label = v.getLabel();
if (label == null) {
return v.getName();
}
String name = v.getName();
if (name.equals("gcId")) {
return "GC ID";
}
if (name.equals("compilerId")) {
return "Compiler ID";
}
if (name.equals("startTime") && !hasDuration) {
return "Time";
}
return label;
}
private void configureTime() {
Timestamp timestamp = descriptor.getAnnotation(Timestamp.class);
if (timestamp != null) {
field.alignLeft = true;
field.dataType = Instant.class.getName();
field.timestamp = true;
field.valueGetter = e -> e.getInstant(fieldName);
}
Timespan timespan = descriptor.getAnnotation(Timespan.class);
if (timespan != null) {
field.alignLeft = false;
field.dataType = Duration.class.getName();
field.timespan = true;
field.valueGetter = e -> e.getDuration(fieldName);
}
}
private void configureNumericTypes() {
switch (descriptor.getTypeName()) {
case "int":
case "long":
case "short":
case "byte":
field.integralType = true;
field.alignLeft = false;
break;
case "float":
case "double":
field.fractionalType = true;
field.alignLeft = false;
break;
case "boolean":
field.alignLeft = false;
break;
}
}
private void configureKnownType() {
String type = descriptor.getTypeName();
if (KNOWN_TYPES.contains(type)) {
field.lexicalSort = true;
field.fixedWidth = false;
}
}
private void configureMemoryAddress() {
MemoryAddress memoryAddress = descriptor.getAnnotation(MemoryAddress.class);
if (memoryAddress != null) {
field.memoryAddress = true;
field.alignLeft = true;
}
}
private void configureFrequency() {
if (descriptor.getAnnotation(Frequency.class) != null) {
field.frequency = true;
}
}
private void configureDataAmount() {
DataAmount dataAmount = descriptor.getAnnotation(DataAmount.class);
if (dataAmount != null) {
if (DataAmount.BITS.equals(dataAmount.value())) {
field.bits = true;
}
if (DataAmount.BYTES.equals(dataAmount.value())) {
field.bytes = true;
}
}
}
private void configurePercentage() {
Percentage percentage = descriptor.getAnnotation(Percentage.class);
if (percentage != null) {
field.percentage = true;
}
}
// Fields created with "SELECT * FROM ..." queries
public static List<Field> createWildcardFields(List<EventType> eventTypes, List<FilteredType> types) {
List<Field> result = new ArrayList<>();
for (FilteredType type : types) {
result.addAll(createWildcardFields(eventTypes, type));
}
return result;
}
private static List<Field> createWildcardFields(List<EventType> eventTypes, FilteredType type) {
record WildcardElement(String name, String label, ValueDescriptor field) {
}
var visited = new HashSet<ValueDescriptor>();
var stack = new ArrayDeque<WildcardElement>();
var wildcardElements = new ArrayList<WildcardElement>();
for (ValueDescriptor field : type.getFields().reversed()) {
stack.push(new WildcardElement(field.getName(), makeLabel(field, hasDuration(type)), field));
}
while (!stack.isEmpty()) {
var we = stack.pop();
if (!visited.contains(we.field)) {
visited.add(we.field);
var subFields = we.field().getFields().reversed();
if (!subFields.isEmpty() && !KNOWN_TYPES.contains(we.field().getTypeName())) {
for (ValueDescriptor subField : subFields) {
String n = we.name + "." + subField.getName();
String l = we.label + " : " + makeLabel(subField, false);
if (stack.size() < 2) { // Limit depth to 2
stack.push(new WildcardElement(n, l, subField));
}
}
} else {
wildcardElements.add(we);
}
}
}
List<Field> result = new ArrayList<>();
for (WildcardElement we : wildcardElements) {
FieldBuilder fb = new FieldBuilder(eventTypes, type, we.name());
Field field = fb.build().getFirst();
field.label = we.label;
field.index = result.size();
field.visible = true;
field.sourceFields.add(field);
result.add(field);
}
return result;
}
private static boolean hasDuration(FilteredType type) {
return type.getField("duration") != null;
}
public static void configureAggregator(Field field) {
Aggregator aggregator = field.aggregator;
if (aggregator == Aggregator.COUNT || aggregator == Aggregator.UNIQUE) {
field.integralType = true;
field.timestamp = false;
field.timespan = false;
field.fractionalType = false;
field.bytes = false;
field.bits = false;
field.frequency = false;
field.memoryAddress = false;
field.percentage = false;
field.alignLeft = false;
field.lexicalSort = false;
}
if (aggregator == Aggregator.LIST) {
field.alignLeft = true;
field.lexicalSort = true;
}
field.label = switch (aggregator) {
case COUNT -> "Count";
case AVERAGE -> "Avg. " + field.label;
case FIRST, LAST, LAST_BATCH -> field.label;
case MAXIMUM -> "Max. " + field.label;
case MINIMUM -> "Min. " + field.label;
case SUM -> "Total " + field.label;
case UNIQUE -> "Unique Count " + field.label;
case LIST -> field.label + "s";
case MISSING -> field.label;
case DIFFERENCE -> "Difference " + field.label;
case MEDIAN -> "Median " + field.label;
case P90 -> "P90 " + field.label;
case P95 -> "P95 " + field.label;
case P99 -> "P99 " + field.label;
case P999 -> "P99.9 " + field.label;
case STANDARD_DEVIATION -> "Std. Dev. " + field.label;
};
}
private static Set<String> createKnownTypes() {
Set<String> set = new HashSet<>();
set.add(String.class.getName());
set.add(Thread.class.getName());
set.add(Class.class.getName());
set.add("jdk.types.ThreadGroup");
set.add("jdk.types.ClassLoader");
set.add("jdk.types.Method");
set.add("jdk.types.StackFrame");
set.add("jdk.types.StackTrace");
return set;
}
}

View File

@ -0,0 +1,185 @@
/*
* Copyright (c) 2023, 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.query;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedClassLoader;
import jdk.jfr.consumer.RecordedFrame;
import jdk.jfr.consumer.RecordedMethod;
import jdk.jfr.consumer.RecordedStackTrace;
import jdk.jfr.consumer.RecordedThread;
import jdk.jfr.consumer.RecordedThreadGroup;
import jdk.jfr.internal.util.ValueFormatter;
public class FieldFormatter {
public static String formatCompact(Field field, Object object) {
return format(field, object, true);
}
public static String format(Field field, Object object) {
return format(field, object, false);
}
private static String format(Field field, Object object, boolean compact) {
if (object == null) {
return field.missingText;
}
if (object instanceof String s) {
return stripFormatting(s);
}
if (object instanceof Double d) {
if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY) {
return field.missingText;
}
}
if (object instanceof Float f) {
if (Float.isNaN(f) || f == Float.NEGATIVE_INFINITY) {
return field.missingText;
}
}
if (object instanceof Long l && l == Long.MIN_VALUE) {
return field.missingText;
}
if (object instanceof Integer i && i == Integer.MIN_VALUE) {
return field.missingText;
}
if (object instanceof RecordedThread t) {
if (t.getJavaThreadId() > 0) {
return t.getJavaName();
} else {
return t.getOSName();
}
}
if (object instanceof RecordedClassLoader cl) {
return format(field, cl.getType(), compact);
}
if (object instanceof RecordedStackTrace st) {
return format(field, st.getFrames().getFirst(), compact);
}
if (object instanceof RecordedThreadGroup tg) {
return tg.getName();
}
if (object instanceof RecordedMethod m) {
return ValueFormatter.formatMethod(m, compact);
}
if (object instanceof RecordedClass clazz) {
String text = ValueFormatter.formatClass(clazz);
if (compact) {
return text.substring(text.lastIndexOf(".") + 1);
}
return text;
}
if (object instanceof RecordedFrame f) {
if (f.isJavaFrame()) {
return format(field, f.getMethod(), compact);
}
return "<unknown>";
}
if (object instanceof Duration d) {
if (d.getSeconds() == Long.MIN_VALUE && d.getNano() == 0) {
return field.missingText;
}
if (d.equals(ChronoUnit.FOREVER.getDuration())) {
return "Indefinite";
}
return ValueFormatter.formatDuration(d);
}
if (object instanceof Instant instant) {
return ValueFormatter.formatTimestamp(instant);
}
if (field.percentage) {
if (object instanceof Number n) {
double d = n.doubleValue();
return String.format("%.2f", d * 100) + "%";
}
}
if (field.bits || field.bytes) {
if (object instanceof Number n) {
long amount = n.longValue();
if (field.frequency) {
if (field.bytes) {
return ValueFormatter.formatBytesPerSecond(amount);
}
if (field.bits) {
return ValueFormatter.formatBitsPerSecond(amount);
}
} else {
if (field.bytes) {
return ValueFormatter.formatBytes(amount);
}
if (field.bits) {
return ValueFormatter.formatBits(amount);
}
}
}
}
if (field.memoryAddress) {
if (object instanceof Number n) {
long d = n.longValue();
return String.format("0x%08X", d);
}
}
if (field.frequency) {
if (object instanceof Number) {
return object + " Hz";
}
}
if (object instanceof Number number) {
return ValueFormatter.formatNumber(number);
}
return object.toString();
}
private static String stripFormatting(String text) {
if (!hasFormatting(text)) {
return text; // Fast path to avoid allocation
}
StringBuilder sb = new StringBuilder(text.length());
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
sb.append(isFormatting(c) ? ' ' : c);
}
return sb.toString();
}
private static boolean hasFormatting(String s) {
for (int i = 0; i < s.length(); i++) {
if (isFormatting(s.charAt(i))) {
return true;
}
}
return false;
}
private static boolean isFormatting(char c) {
return c == '\n' || c == '\r' || c == '\t';
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright (c) 2023, 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.query;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import jdk.jfr.EventType;
import jdk.jfr.Experimental;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.Utils;
/**
* Type referenced in a FROM-clause.
* <p>
* If the query has a WHEN clause, the available events for the event type
* is restricted by a list of filter conditions.
*/
final class FilteredType {
public record Filter (Field field, String value) {
@Override
public int hashCode() {
return field.name.hashCode() + value.hashCode();
}
@Override
public boolean equals(Object object) {
if (object instanceof Filter that) {
return this.field.name.equals(that.field.name) && Objects.equals(this.value, that.value);
}
return false;
}
}
private final List<Filter> filters = new ArrayList<>();
private final EventType eventType;
private final String simpleName;
public FilteredType(EventType type) {
this.eventType = type;
this.simpleName = Utils.makeSimpleName(type);
}
public boolean isExperimental() {
return eventType.getAnnotation(Experimental.class) != null;
}
public String getName() {
return eventType.getName();
}
public String getLabel() {
return eventType.getLabel();
}
public String getSimpleName() {
return simpleName;
}
public void addFilter(Filter filter) {
filters.add(filter);
}
public List<Filter> getFilters() {
return filters;
}
public ValueDescriptor getField(String name) {
return eventType.getField(name);
}
public List<ValueDescriptor> getFields() {
return eventType.getFields();
}
@Override
public int hashCode() {
return Long.hashCode(eventType.getId()) + filters.hashCode();
}
@Override
public boolean equals(Object object) {
if (object instanceof FilteredType that) {
return that.eventType.getId() == this.eventType.getId()
&& that.filters.equals(this.filters);
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getName());
sb.append(" ");
for (Filter condition : filters) {
sb.append(condition.field());
sb.append(" = ");
sb.append(condition.value());
sb.append(" ");
}
return sb.toString();
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) 2023, 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.query;
/**
* Class responsible for printing and formatting the contents of the first row in a table,
* as a form.
*/
import jdk.jfr.internal.util.Output;
final class FormRenderer {
private static final String LABEL_SUFFIX = ": ";
private final Table table;
private final Output out;
private final Configuration configuration;
private final int width;
public FormRenderer(Configuration configuration, Table table) {
this.table = table;
this.out = configuration.output;
this.configuration = configuration;
this.width = determineWidth(configuration);
}
private static int determineWidth(Configuration configuration) {
if (configuration.width == 0) {
return Configuration.PREFERRED_WIDTH;
} else {
return configuration.width;
}
}
public void render() {
if (table.isEmpty()) {
if (configuration.title != null) {
out.println();
out.println("No events found for '" + configuration.title +"'.");
}
return;
}
int maxWidth = 0;
for (Field field : table.getFields()) {
String label = field.label + LABEL_SUFFIX;
maxWidth = Math.max(label.length() + 1, maxWidth);
}
out.println();
if (maxWidth + 2 > width) {
out.println("Columns are too wide to fit width " + configuration.width + ".");
return;
}
if (configuration.title != null) {
out.println(configuration.title);
out.println("-".repeat(configuration.title.length()));
}
if (table.isEmpty()) {
return;
}
for (Field field : table.getFields()) {
if (field.visible) {
out.println();
renderField(field);
}
}
}
private void renderField(Field field) {
Row row = table.getRows().getFirst();
String label = field.label + LABEL_SUFFIX;
Object value = row.getValue(field.index);
String text = FieldFormatter.format(field, value);
boolean newLine = false;
out.print(label);
long p = width - label.length() - 1;
for (int i = 0; i < text.length(); i++) {
if (newLine) {
out.print(" ".repeat(label.length()));
newLine = false;
}
out.print(text.charAt(i));
if (i % p == p - 1) {
newLine = true;
out.println();
}
}
out.println();
}
public int getWidth() {
return width;
}
}

View File

@ -0,0 +1,662 @@
/*
* Copyright (c) 2023, 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.query;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;
abstract class Function {
public abstract void add(Object value);
public abstract Object result();
public static Function create(Field field) {
Aggregator aggregator = field.aggregator;
if (field.grouper != null || aggregator == Aggregator.MISSING) {
return new FirstNonNull();
}
if (aggregator == Aggregator.LIST) {
return new List();
}
if (aggregator == Aggregator.DIFFERENCE) {
if (field.timestamp) {
return new TimeDifference();
} else {
return new Difference();
}
}
if (aggregator == Aggregator.STANDARD_DEVIATION) {
if (field.timespan) {
return new TimespanFunction(new StandardDeviation());
} else {
return new StandardDeviation();
}
}
if (aggregator == Aggregator.MEDIAN) {
if (field.timespan) {
return new TimespanFunction(new Median());
} else {
return new Median();
}
}
if (aggregator == Aggregator.P90) {
return createPercentile(field, 0.95);
}
if (aggregator == Aggregator.P95) {
return createPercentile(field, 0.95);
}
if (aggregator == Aggregator.P99) {
return createPercentile(field, 0.99);
}
if (aggregator == Aggregator.P999) {
return createPercentile(field, 0.9999);
}
if (aggregator == Aggregator.MAXIMUM) {
return new Maximum();
}
if (aggregator == Aggregator.MINIMUM) {
return new Minimum();
}
if (aggregator == Aggregator.SUM) {
if (field.timespan) {
return new SumDuration();
}
if (field.fractionalType) {
return new SumDouble();
}
if (field.integralType) {
return new SumLong();
}
}
if (aggregator == Aggregator.FIRST) {
return new First();
}
if (aggregator == Aggregator.LAST_BATCH) {
return new LastBatch(field);
}
if (aggregator == Aggregator.LAST) {
return new Last();
}
if (aggregator == Aggregator.AVERAGE) {
if (field.timespan) {
return new AverageDuration();
} else {
return new Average();
}
}
if (aggregator == Aggregator.COUNT) {
return new Count();
}
if (aggregator == Aggregator.UNIQUE) {
return new Unique();
}
return new Null();
}
// **** AVERAGE ****
private static final class Average extends Function {
private double total;
private long count;
@Override
public void add(Object value) {
if (value instanceof Number n && Double.isFinite(n.doubleValue())) {
total += n.doubleValue();
count++;
}
}
@Override
public Object result() {
if (count != 0) {
return total / count;
} else {
return null;
}
}
}
private static final class AverageDuration extends Function {
private long seconds;
private long nanos;
private int count;
@Override
public void add(Object value) {
if (value instanceof Duration duration) {
seconds += duration.getSeconds();
nanos += duration.getNano();
count++;
}
}
@Override
public Object result() {
if (count != 0) {
long s = seconds / count;
long n = nanos / count;
return Duration.ofSeconds(s, n);
} else {
return null;
}
}
}
// **** COUNT ****
private static final class Count extends Function {
private long count = 0;
@Override
public void add(Object value) {
count++;
}
@Override
public Object result() {
return count;
}
}
// **** FIRST ****
private static final class First extends Function {
private static Object firstObject = new Object();
private Object first = firstObject;
@Override
public void add(Object value) {
if (first == firstObject) {
first = value;
}
}
@Override
public Object result() {
return first == firstObject ? null : first;
}
}
// **** LAST ****
private static final class Last extends Function {
private static Object lastObject = new Object();
private Object last = lastObject;
@Override
public void add(Object value) {
last = value;
}
@Override
public Object result() {
return last == lastObject ? null : last;
}
}
private static final class FirstNonNull extends Function {
private Object first;
@Override
public void add(Object value) {
if (value == null) {
return;
}
first = value;
}
@Override
public Object result() {
return first;
}
}
// **** MAXIMUM ****
@SuppressWarnings("rawtypes")
private static final class Maximum extends Function {
private Comparable<Comparable> maximum;
@SuppressWarnings("unchecked")
@Override
public void add(Object value) {
if (value instanceof Comparable comparable) {
if (maximum == null) {
maximum = comparable;
return;
}
if (comparable.compareTo(maximum) > 0) {
maximum = comparable;
}
}
}
@Override
public Object result() {
if (maximum == null) {
System.out.println("Why");
}
return maximum;
}
}
// **** MINIMUM ****
@SuppressWarnings("rawtypes")
private static final class Minimum extends Function {
private Comparable<Comparable> minimum;
@SuppressWarnings("unchecked")
@Override
public void add(Object value) {
if (value instanceof Comparable comparable) {
if (minimum == null) {
minimum = comparable;
return;
}
if (comparable.compareTo(minimum) < 0) {
minimum = comparable;
}
}
}
@Override
public Object result() {
return minimum;
}
}
// *** NULL ****
private static final class Null extends Function {
@Override
public void add(Object value) {
}
@Override
public Object result() {
return null;
}
}
// **** SUM ****
private static final class SumDouble extends Function {
private boolean hasValue = false;
private double sum = 0;
@Override
public void add(Object value) {
if (value instanceof Number n && Double.isFinite(n.doubleValue())) {
sum += n.doubleValue();
hasValue = true;
}
}
@Override
public Object result() {
return hasValue ? sum : null;
}
}
private static final class SumDuration extends Function {
private long seconds;
private long nanos;
private boolean hasValue;
@Override
public void add(Object value) {
if (value instanceof Duration n) {
seconds += n.getSeconds();
nanos += n.getNano();
hasValue = true;
}
}
@Override
public Object result() {
return hasValue ? Duration.ofSeconds(seconds, nanos) : null;
}
}
private static final class SumLong extends Function {
private boolean hasValue = false;
private long sum = 0;
@Override
public void add(Object value) {
if (value instanceof Number n) {
sum += n.longValue();
hasValue = true;
}
}
@Override
public Object result() {
return hasValue ? sum : null;
}
}
// **** UNIQUE ****
private static final class Unique extends Function {
private final Set<Object> unique = new HashSet<>();
@Override
public void add(Object value) {
unique.add(value);
}
@Override
public Object result() {
return unique.size();
}
}
// **** LIST ****
private static final class List extends Function {
private final ArrayList<Object> list = new ArrayList<>();
@Override
public void add(Object value) {
list.add(value);
}
@Override
public Object result() {
StringJoiner sj = new StringJoiner(", ");
for (Object object : list) {
sj.add(String.valueOf(object));
}
return sj.toString();
}
}
// **** DIFF ****
private static final class Difference extends Function {
private Number first;
private Number last;
@Override
public void add(Object value) {
if (value instanceof Number number && Double.isFinite(number.doubleValue())) {
if (first == null) {
first = number;
}
last = number;
}
}
@Override
public Object result() {
if (last == null) {
return null;
}
if (isIntegral(first) && isIntegral(last)) {
return last.longValue() - first.longValue();
}
if (first instanceof Float f && last instanceof Float l) {
return l - f;
}
return last.doubleValue() - first.doubleValue();
}
private boolean isIntegral(Number number) {
if ((number instanceof Long) || (number instanceof Integer) || (number instanceof Short)
|| (number instanceof Byte)) {
return true;
}
return false;
}
}
private static final class TimeDifference extends Function {
private Instant first;
private Instant last;
@Override
public void add(Object value) {
if (value instanceof Instant instant) {
if (first == null) {
first = instant;
return;
}
last = instant;
}
}
@Override
public Object result() {
if (first == null) {
return null;
}
if (last == null) {
return ChronoUnit.FOREVER.getDuration();
}
return Duration.between(first, last);
}
}
@SuppressWarnings("rawtypes")
private static final class Median extends Function {
private final java.util.List<Comparable> comparables = new ArrayList<>();
@Override
public void add(Object value) {
if (value instanceof Number && value instanceof Comparable c) {
comparables.add(c);
}
}
@Override
@SuppressWarnings("unchecked")
public Object result() {
if (comparables.isEmpty()) {
return null;
}
if (comparables.size() == 1) {
return comparables.getFirst();
}
comparables.sort(Comparator.naturalOrder());
if (comparables.size() % 2 == 1) {
return comparables.get(comparables.size() / 2);
}
Number a = (Number) comparables.get(comparables.size() / 2 - 1);
Number b = (Number) comparables.get(comparables.size() / 2);
return (a.doubleValue() + b.doubleValue()) / 2;
}
}
// **** PERCENTILE ****
private static Function createPercentile(Field field, double percentile) {
Percentile p = new Percentile(percentile);
if (field.timespan) {
return new TimespanFunction(p);
} else {
return p;
}
}
private static final class TimespanFunction extends Function {
private final Function function;
TimespanFunction(Function function) {
this.function = function;
}
@Override
public void add(Object value) {
if (value instanceof Duration duration) {
long nanos = 1_000_000_000L * duration.getSeconds() + duration.getNano();
function.add(nanos);
}
}
@Override
public Object result() {
Object object = function.result();
if (object instanceof Number nanos) {
return Duration.ofNanos(nanos.longValue());
}
return null;
}
}
private static final class Percentile extends Function {
private final double percentile;
private final java.util.List<Number> numbers = new ArrayList<>();
Percentile(double percentile) {
this.percentile = percentile;
}
@Override
public void add(Object value) {
if (value instanceof Number number) {
if (Double.isFinite(number.doubleValue())) {
numbers.add(number);
}
}
}
@Override
public Object result() {
if (numbers.isEmpty()) {
return null;
}
if (numbers.size() == 1) {
return numbers.getFirst();
}
numbers.sort((n1, n2) -> Double.compare(n1.doubleValue(), n2.doubleValue()));
int size = numbers.size();
// Use size + 1 so range is stretched out for interpolation
// For example with percentile 50%
// size | valueIndex | valueNextindex | fraction
// 2 0 1 0.50
// 3 1 2 0.0
// 4 1 2 0.50
// 5 2 3 0.0
// 6 2 3 0.50
double doubleIndex = (size + 1) * percentile;
int valueIndex = (int) doubleIndex - 1;
int valueNextIndex = (int) doubleIndex;
double fraction = doubleIndex - valueIndex;
if (valueIndex < 0) {
return numbers.getFirst();
}
if (valueNextIndex >= size) {
return numbers.getLast();
}
double a = numbers.get(valueIndex).doubleValue();
double b = numbers.get(valueNextIndex).doubleValue();
return a + fraction * (b - a);
}
}
// **** STANDARD DEVIATION ****
private static final class StandardDeviation extends Function {
private final java.util.List<Number> values = new ArrayList<>();
@Override
public void add(Object value) {
if (value instanceof Number n && Double.isFinite(n.doubleValue())) {
values.add(n);
}
}
@Override
public Object result() {
if (values.size() > 0) {
long N = values.size();
double average = sum() / N;
double sum = 0;
for (Number number : values) {
double diff = number.doubleValue() - average;
sum = sum + (diff * diff);
}
return Math.sqrt(sum / N);
}
return null;
}
private double sum() {
double sum = 0;
for (Number number : values) {
sum += number.doubleValue();
}
return sum ;
}
}
public static final class LastBatch extends Function {
private final Field field;
private final Last last = new Last();
private Instant timestamp;
public LastBatch(Field field) {
this.field = field;
}
@Override
public void add(Object value) {
last.add(value);
}
@Override
public Object result() {
return last.result();
}
public void setTime(Instant timestamp) {
this.timestamp = timestamp;
field.last = timestamp;
}
public boolean valid() {
if (timestamp != null) {
return timestamp.equals(field.last);
}
return true;
}
}
}

View File

@ -0,0 +1,214 @@
/*
* Copyright (c) 2023, 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.query;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedClassLoader;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedFrame;
import jdk.jfr.consumer.RecordedMethod;
import jdk.jfr.consumer.RecordedObject;
import jdk.jfr.consumer.RecordedStackTrace;
import jdk.jfr.consumer.RecordedThread;
import jdk.jfr.consumer.RecordedThreadGroup;
import jdk.jfr.internal.query.Function.LastBatch;
/**
* Class responsible for aggregating values
*/
final class Histogram {
private static final class LookupKey {
private Object keys;
@SuppressWarnings({ "unchecked", "rawtypes" })
public void add(Object o) {
// One key, fast path
if (keys == null) {
keys = o;
return;
}
// Three or more keys
if (keys instanceof Set set) {
set.add(o);
return;
}
// Two keys
Set<Object> set = HashSet.newHashSet(2);
set.add(keys);
set.add(o);
keys = set;
}
@Override
public int hashCode() {
return Objects.hashCode(keys);
}
@Override
public boolean equals(Object object) {
if (object instanceof LookupKey that) {
return Objects.deepEquals(that.keys, this.keys);
}
return false;
}
}
private static final class MethodEquality {
private final String methodName;
private final String descriptor;
private final long classId;
public MethodEquality(RecordedMethod method) {
methodName = method.getName();
descriptor = method.getDescriptor();
classId = method.getType().getId();
}
@Override
public int hashCode() {
int hash1 = Long.hashCode(classId);
int hash2 = methodName.hashCode();
int hash3 = descriptor.hashCode();
int result = 31 + hash1;
result += 31 * result + hash2;
result += 31 * result + hash3;
return result;
}
@Override
public boolean equals(Object object) {
if (object instanceof MethodEquality that) {
if ((this.classId != that.classId) || !Objects.equals(this.methodName, that.methodName)) {
return false;
}
return Objects.equals(this.descriptor, that.descriptor);
}
return false;
}
}
private final Map<LookupKey, Function[]> keyFunctionsMap = new HashMap<>();
private final List<Field> fields = new ArrayList<>();
public void addFields(List<Field> fields) {
this.fields.addAll(fields);
}
public void add(RecordedEvent e, FilteredType type, List<Field> sourceFields) {
LookupKey lk = new LookupKey();
final Object[] values = new Object[sourceFields.size()];
for (int i = 0; i < values.length; i++) {
Field field = sourceFields.get(i);
Object value = field.valueGetter.apply(e);
values[i] = value;
if (field.grouper != null) {
lk.add(makeKey(value));
}
}
Function[] fs = keyFunctionsMap.computeIfAbsent(lk, k -> createFunctions());
for (int i = 0; i < values.length; i++) {
Function function = fs[sourceFields.get(i).index];
function.add(values[i]);
if (function instanceof LastBatch l) {
l.setTime(e.getEndTime());
}
}
}
public List<Row> toRows() {
List<Row> rows = new ArrayList<>(keyFunctionsMap.size());
for (Function[] functions : keyFunctionsMap.values()) {
Row row = new Row(fields.size());
boolean valid = true;
int index = 0;
for (Function f : functions) {
if (f instanceof LastBatch last && !last.valid()) {
valid = false;
}
row.putValue(index++, f.result());
}
if (valid) {
rows.add(row);
}
}
return rows;
}
private Function[] createFunctions() {
Function[] functions = new Function[fields.size()];
for (int i = 0; i < functions.length; i++) {
functions[i] = Function.create(fields.get(i));
}
return functions;
}
private static Object makeKey(Object object) {
if (!(object instanceof RecordedObject)) {
return object;
}
if (object instanceof RecordedMethod method) {
return new MethodEquality(method);
}
if (object instanceof RecordedThread thread) {
return thread.getId();
}
if (object instanceof RecordedClass clazz) {
return clazz.getId();
}
if (object instanceof RecordedFrame frame) {
if (frame.isJavaFrame()) {
return makeKey(frame.getMethod());
}
return null;
}
if (object instanceof RecordedStackTrace stackTrace) {
List<RecordedFrame> recordedFrames = stackTrace.getFrames();
List<Object> frames = new ArrayList<>(recordedFrames.size());
for (RecordedFrame frame : recordedFrames) {
frames.add(makeKey(frame));
}
return frames;
}
if (object instanceof RecordedClassLoader classLoader) {
return classLoader.getId();
}
if (object instanceof RecordedThreadGroup group) {
String name = group.getName();
String parentName = group.getParent() != null ? group.getParent().getName() : null;
return name + ":" + parentName;
}
return object;
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright (c) 2023, 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.query;
import java.text.ParseException;
import java.util.List;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.function.Consumer;
final class Query {
enum SortOrder {
NONE, ASCENDING, DESCENDING
}
record Source(String name, Optional<String> alias) {
}
record Condition(String field, String value) {
}
record Where(List<Condition> conditions) {
}
record Property(String name, Consumer<Field> style) {
}
record Formatter(List<Property> properties) {
}
record Expression(String name, Optional<String> alias, Aggregator aggregator) {
}
record Grouper(String field) {
}
record OrderElement(String name, SortOrder order) {
}
final List<String> column;
final List<Formatter> format;
final List<Expression> select;
final List<Source> from;
final List<Condition> where;
final List<Grouper> groupBy;
final List<OrderElement> orderBy;
final int limit;
public Query(String text) throws ParseException {
try (QueryParser qp = new QueryParser(text)) {
column = qp.column();
format = qp.format();
select = qp.select();
from = qp.from();
where = qp.where();
groupBy = qp.groupBy();
orderBy = qp.orderBy();
limit = qp.limit();
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (!column.isEmpty()) {
StringJoiner sj = new StringJoiner(", ");
for (String c : column) {
sj.add("'" + c + "'");
}
sb.append("COLUMN ").append(sj).append(" ");
}
if (!format.isEmpty()) {
StringJoiner sj = new StringJoiner(", ");
for (Formatter f : format) {
StringJoiner t = new StringJoiner(";");
for (Property p : f.properties()) {
t.add(p.name());
}
sj.add(t.toString());
}
sb.append("FORMAT ").append(sj).append(" ");
}
StringJoiner t = new StringJoiner(", ");
for (Expression e : select) {
StringBuilder w = new StringBuilder();
if (e.aggregator() != Aggregator.MISSING) {
w.append(e.aggregator().name());
w.append("(");
}
w.append(e.name());
if (e.aggregator() != Aggregator.MISSING) {
w.append(")");
}
if (e.alias().isPresent()) {
w.append(" AS ");
w.append(e.alias().get());
}
t.add(w.toString());
}
sb.append("SELECT ")
.append(select.isEmpty() ? "*" : t.toString());
StringJoiner u = new StringJoiner(", ");
for (Source e : from) {
String s = e.name();
if (e.alias().isPresent()) {
s += " AS " + e.alias().get();
}
u.add(s);
}
sb.append(" FROM ").append(u);
if (!where.isEmpty()) {
StringJoiner sj = new StringJoiner(" AND");
for (Condition c : where) {
sj.add(" " + c.field() + " = '" + c.value() + "'");
}
sb.append(" WHERE").append(sj);
}
if (!groupBy.isEmpty()) {
StringJoiner sj = new StringJoiner(", ");
for (Grouper g : groupBy) {
sj.add(g.field());
}
sb.append(" GROUP BY ").append(sj);
}
if (!orderBy.isEmpty()) {
StringJoiner sj = new StringJoiner(", ");
for (OrderElement e : orderBy) {
String name = e.name();
if (e.order() == SortOrder.ASCENDING) {
name += " ASC";
}
if (e.order() == SortOrder.DESCENDING) {
name += " DESC";
}
sj.add(name);
}
sb.append(" ORDER BY ").append(sj);
}
if (limit != Integer.MAX_VALUE) {
sb.append(" LIMIT " + limit);
}
return sb.toString();
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) 2023, 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.query;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import jdk.jfr.EventType;
import jdk.jfr.consumer.EventStream;
import jdk.jfr.consumer.MetadataEvent;
final class QueryExecutor {
private final List<QueryRun> queryRuns = new ArrayList<>();
private final List<EventType> eventTypes = new ArrayList<>();
private final EventStream stream;
public QueryExecutor(EventStream stream) {
this(stream, List.of());
}
public QueryExecutor(EventStream stream, Query query) {
this(stream, List.of(query));
}
public QueryExecutor(EventStream stream, List<Query> queries) {
this.stream = stream;
for (Query query : queries) {
queryRuns.add(new QueryRun(stream, query));
}
stream.setReuse(false);
stream.setOrdered(true);
stream.onMetadata(this::onMetadata);
}
public List<QueryRun> run() {
stream.start();
for (QueryRun run : queryRuns) {
run.complete();
}
return queryRuns;
}
private void onMetadata(MetadataEvent e) {
if (eventTypes.isEmpty()) {
eventTypes.addAll(e.getEventTypes());
}
if (queryRuns.isEmpty()) {
addQueryRuns();
}
for (QueryRun run : queryRuns) {
run.onMetadata(e);
}
}
private void addQueryRuns() {
for (EventType type : eventTypes) {
try {
Query query = new Query("SELECT * FROM " + type.getName());
QueryRun run = new QueryRun(stream, query);
queryRuns.add(run);
} catch (ParseException pe) {
// The event name contained whitespace or similar, ignore.
}
}
}
public List<EventType> getEventTypes() {
return eventTypes;
}
}

View File

@ -0,0 +1,336 @@
/*
* Copyright (c) 2023, 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.query;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import jdk.jfr.internal.query.Configuration.Truncate;
import jdk.jfr.internal.query.Query.Condition;
import jdk.jfr.internal.query.Query.Expression;
import jdk.jfr.internal.query.Query.Formatter;
import jdk.jfr.internal.query.Query.Grouper;
import jdk.jfr.internal.query.Query.OrderElement;
import jdk.jfr.internal.query.Query.Property;
import jdk.jfr.internal.query.Query.SortOrder;
import jdk.jfr.internal.query.Query.Source;
import jdk.jfr.internal.util.Tokenizer;
final class QueryParser implements AutoCloseable {
static final char[] SEPARATORS = {'=', ',', ';', '(', ')'};
private final Tokenizer tokenizer;
public QueryParser(String text) {
tokenizer = new Tokenizer(text, SEPARATORS);
}
public List<String> column() throws ParseException {
if (!tokenizer.accept("COLUMN")) {
return List.of();
}
List<String> texts = new ArrayList<>();
texts.add(text());
while (tokenizer.accept(",")) {
texts.add(text());
}
return texts;
}
public List<Formatter> format() throws ParseException {
if (tokenizer.accept("FORMAT")) {
List<Formatter> formatters = new ArrayList<>();
formatters.add(formatter());
while (tokenizer.accept(",")) {
formatters.add(formatter());
}
return formatters;
}
return List.of();
}
private Formatter formatter() throws ParseException {
List<Property> properties = new ArrayList<>();
properties.add(property());
while (tokenizer.accept(";")) {
properties.add(property());
}
return new Formatter(properties);
}
public List<Expression> select() throws ParseException {
tokenizer.expect("SELECT");
if (tokenizer.accept("*")) {
return List.of();
}
List<Expression> expressions = new ArrayList<>();
if (tokenizer.accept("FROM")) {
throw new ParseException("Missing fields in SELECT statement", position());
}
expressions.add(expression());
while (tokenizer.accept(",")) {
Expression exp = expression();
if (exp.name().equalsIgnoreCase("FROM")) {
throw new ParseException("Missing field name in SELECT statement, or qualify field with event type if name is called '" + exp.name() + "'", position());
}
expressions.add(exp);
}
return expressions;
}
private Expression expression() throws ParseException {
Expression aggregator = aggregator();
if (aggregator != null) {
return aggregator;
} else {
return new Expression(eventField(), alias(), Aggregator.MISSING);
}
}
private Expression aggregator() throws ParseException {
for (Aggregator function : Aggregator.values()) {
if (tokenizer.accept(function.name, "(")) {
String eventField = eventField();
tokenizer.expect(")");
return new Expression(eventField, alias(), function);
}
}
return null;
}
private Optional<String> alias() throws ParseException {
Optional<String> alias = Optional.empty();
if (tokenizer.accept("AS")) {
alias = Optional.of(symbol());
}
return alias;
}
public List<Source> from() throws ParseException {
tokenizer.expect("FROM");
List<Source> sources = new ArrayList<>();
sources.add(source());
while (tokenizer.accept(",")) {
sources.add(source());
}
return sources;
}
private Source source() throws ParseException {
String type = type();
if (tokenizer.accept("SELECT")) {
throw new ParseException("Subquery is not allowed", position());
}
if (tokenizer.accept("INNER", "JOIN", "LEFT", "RIGHT", "FULL")) {
throw new ParseException("JOIN is not allowed", position());
}
return new Source(type, alias());
}
private String type() throws ParseException {
return tokenizer.next();
}
public List<Condition> where() throws ParseException {
if (tokenizer.accept("WHERE")) {
List<Condition> conditions = new ArrayList<>();
conditions.add(condition());
while (tokenizer.accept("AND")) {
conditions.add(condition());
}
return conditions;
}
return List.of();
}
private Condition condition() throws ParseException {
String field = eventField();
if (tokenizer.acceptAny("<", ">", "<>", ">=", "<=", "==", "BETWEEN", "LIKE", "IN")) {
throw new ParseException("The only operator allowed in WHERE clause is '='", position());
}
tokenizer.expect("=");
String value = text();
return new Condition(field, value);
}
public List<Grouper> groupBy() throws ParseException {
if (tokenizer.accept("HAVING")) {
throw new ParseException("HAVING is not allowed", position());
}
if (tokenizer.accept("GROUP")) {
tokenizer.expect("BY");
List<Grouper> groupers = new ArrayList<>();
groupers.add(grouper());
while (tokenizer.accept(",")) {
groupers.add(grouper());
}
return groupers;
}
return new ArrayList<>(); // Need to return mutable collection
}
private Grouper grouper() throws ParseException {
return new Grouper(eventField());
}
public List<OrderElement> orderBy() throws ParseException {
if (tokenizer.accept("ORDER")) {
tokenizer.expect("BY");
List<OrderElement> fields = new ArrayList<>();
fields.add(orderer());
while (tokenizer.accept(",")) {
fields.add(orderer());
}
return fields;
}
return List.of();
}
private OrderElement orderer() throws ParseException {
return new OrderElement(eventField(), sortOrder());
}
private SortOrder sortOrder() throws ParseException {
if (tokenizer.accept("ASC")) {
return SortOrder.ASCENDING;
}
if (tokenizer.accept("DESC")) {
return SortOrder.DESCENDING;
}
return SortOrder.NONE;
}
private String text() throws ParseException {
if (tokenizer.peekChar() != '\'') {
throw new ParseException("Expected text to start with a single quote character", position());
}
return tokenizer.next();
}
private String symbol() throws ParseException {
String s = tokenizer.next();
for (int index = 0; index < s.length(); index++) {
int cp = s.codePointAt(index);
if (!Character.isLetter(cp)) {
throw new ParseException("Symbol must consist of letters, found '" + s.charAt(index) + "' in '" + s + "'",
position());
}
}
return s;
}
private String eventField() throws ParseException {
if (!tokenizer.hasNext()) {
throw new ParseException("Unexpected end when looking for event field", position());
}
if (tokenizer.peekChar() == '\'') {
throw new ParseException("Expected unquoted symbolic name (not label)", position());
}
String name = tokenizer.next();
if (name.equals("*")) {
return name;
}
for (int index = 0; index < name.length(); index++) {
char c = name.charAt(index);
boolean valid = index == 0 ? Character.isJavaIdentifierStart(c) : Character.isJavaIdentifierPart(c);
if (c != '.' && c != '[' && c!= ']' && c != '|' && !valid) {
throw new ParseException("Not a valid field name: " + name, position());
}
}
return name;
}
private Property property() throws ParseException {
String text = tokenizer.next();
Consumer<Field> style = switch (text.toLowerCase()) {
case "none" -> field -> {};
case "missing:" -> field -> {};
case "normalized" -> field -> field.normalized = field.percentage = true;
case "truncate-beginning" -> field -> field.truncate = Truncate.BEGINNING;
case "truncate-end" -> field -> field.truncate = Truncate.END;
default -> {
if (text.startsWith("missing:")) {
yield missing(text.substring("missing:".length()));
}
if (text.startsWith("cell-height:")) {
yield cellHeight(text.substring("cell-height:".length()));
}
throw new ParseException("Unknown formatter '" + text + "'", position());
}
};
return new Property(text, style);
}
private Consumer<Field> missing(String missing) {
if ("whitespace".equals(missing)) {
return field -> field.missingText = "";
} else {
return field -> field.missingText = missing;
}
}
private Consumer<Field> cellHeight(String height) throws ParseException {
try {
int h = Integer.parseInt(height);
if (h < 1) {
throw new ParseException("Expected 'cell-height:' to be at least 1' ", position());
}
return field -> field.cellHeight = h;
} catch (NumberFormatException nfe) {
throw new ParseException("Not valid number for 'cell-height:' " + height, position());
}
}
public int position() {
return tokenizer.getPosition();
}
public int limit() throws ParseException {
if (tokenizer.accept("LIMIT")) {
try {
if (tokenizer.hasNext()) {
String number = tokenizer.next();
int limit= Integer.parseInt(number);
if (limit < 0) {
throw new ParseException("Expected a positive integer after LIMIT", position());
}
return limit;
}
} catch (NumberFormatException nfe) {
// Fall through
}
throw new ParseException("Expected an integer after LIMIT", position());
}
return Integer.MAX_VALUE;
}
@Override
public void close() throws ParseException {
tokenizer.close();
}
}

View File

@ -0,0 +1,303 @@
/*
* Copyright (c) 2023, 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.query;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jdk.jfr.AnnotationElement;
import jdk.jfr.EventType;
import jdk.jfr.Experimental;
import jdk.jfr.Relational;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.consumer.EventStream;
import jdk.jfr.internal.Utils;
import jdk.jfr.internal.util.Columnizer;
import jdk.jfr.internal.util.Output;
import jdk.jfr.internal.util.StopWatch;
import jdk.jfr.internal.util.Tokenizer;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
import jdk.jfr.internal.util.ValueFormatter;
/**
* Class responsible for executing and displaying the contents of a query.
* <p>
* Used by 'jcmd JFR.query' and 'jfr query'.
*/
public final class QueryPrinter {
private final EventStream stream;
private final Configuration configuration;
private final Output out;
private final StopWatch stopWatch;
/**
* Constructs a query printer.
*
* @param configuration display configuration
* @param stream a non-started stream from where data should be fetched.
*/
public QueryPrinter(Configuration configuration, EventStream stream) {
this.configuration = configuration;
this.out = configuration.output;
this.stopWatch = new StopWatch();
this.stream = stream;
}
/**
* Prints the query.
*
* @see getGrammarText().
*
* @param query the query text
*
* @throws UserDataException if the stream associated with the printer lacks
* event or event metadata
* @throws UserSyntaxException if the syntax of the query is incorrect.
*/
public void execute(String query) throws UserDataException, UserSyntaxException {
if (showEvents(query) || showFields(query)) {
return;
}
showQuery(query);
}
private void showQuery(String query) throws UserDataException, UserSyntaxException {
try {
stopWatch.beginQueryValidation();
Query q = new Query(query);
QueryExecutor executor = new QueryExecutor(stream, q);
stopWatch.beginAggregation();
QueryRun task = executor.run().getFirst();
if (!task.getSyntaxErrors().isEmpty()) {
throw new UserSyntaxException(task.getSyntaxErrors().getFirst());
}
if (!task.getMetadataErrors().isEmpty()) {
throw new UserDataException(task.getMetadataErrors().getFirst());
}
Table table = task.getTable();
if (configuration.verboseTitle) {
FilteredType type = table.getFields().getFirst().type;
configuration.title = type.getLabel();
if (type.isExperimental()) {
configuration.title += " (Experimental)";
}
}
stopWatch.beginFormatting();
TableRenderer renderer = new TableRenderer(configuration, table, q);
renderer.render();
stopWatch.finish();
if (configuration.verbose) {
out.println();
out.println("Execution: " + stopWatch.toString());
}
if (configuration.startTime != null) {
String s = ValueFormatter.formatTimestamp(configuration.startTime);
String e = ValueFormatter.formatTimestamp(configuration.endTime);
out.println();
out.println("Timespan: " + s + " - " + e);
}
} catch (ParseException pe) {
throw new UserSyntaxException(pe.getMessage());
}
}
private boolean showFields(String text) {
String eventType = null;
try (Tokenizer t = new Tokenizer(text)) {
t.expect("SHOW");
t.expect("FIELDS");
eventType = t.next();
} catch (ParseException pe) {
return false;
}
Map<Long, EventType> eventTypes = new HashMap<>();
stream.onMetadata(e -> {
for (EventType t : e.getAddedEventTypes()) {
eventTypes.put(t.getId(), t);
}
});
stream.start();
List<EventType> types = new ArrayList<>(eventTypes.values());
Collections.sort(types, Comparator.comparing(EventType::getName));
for (EventType type : types) {
String qualifiedName = type.getName();
String name = Utils.makeSimpleName(qualifiedName);
if (qualifiedName.equals(eventType) || name.equals(eventType)) {
printFields(type, types);
return true;
}
}
return false;
}
private void printFields(EventType type, List<EventType> allTypes) {
out.println();
out.println("" + type.getName() + ":");
out.println();
for (ValueDescriptor f : type.getFields()) {
String typeName = Utils.makeSimpleName(f.getTypeName());
out.println(" " + typeName + " " + f.getName());
}
List<String> related = new ArrayList<>();
for (String s : relations(type)) {
out.println();
String simpleName = Utils.makeSimpleName(s);
out.println("Event types with a " + simpleName + " relation:");
out.println();
for (EventType et : allTypes) {
if (et != type) {
List<String> r = relations(et);
if (r.contains(s)) {
related.add(et.getName());
}
}
}
out.println(new Columnizer(related, 2).toString());
}
out.println();
}
private List<String> relations(EventType type) {
List<String> relations = new ArrayList<>();
for (ValueDescriptor field : type.getFields()) {
for (AnnotationElement annotation : field.getAnnotationElements()) {
Relational relation = annotation.getAnnotation(Relational.class);
if (relation != null) {
relations.add(annotation.getTypeName());
}
}
}
return relations;
}
private boolean showEvents(String queryText) {
try (Tokenizer t = new Tokenizer(queryText)) {
t.expect("SHOW");
t.expect("EVENTS");
} catch (ParseException pe) {
return false;
// Ignore
}
out.println("Event Types (number of events):");
out.println();
Map<Long, Long> eventCount = new HashMap<>();
Map<Long, EventType> eventTypes = new HashMap<>();
stream.onMetadata(e -> {
for (EventType t : e.getAddedEventTypes()) {
eventTypes.put(t.getId(), t);
}
});
stream.onEvent(event -> {
eventCount.merge(event.getEventType().getId(), 1L, Long::sum);
});
stream.start();
List<String> types = new ArrayList<>();
for (EventType type : eventTypes.values()) {
if (!isExperimental(type)) {
String name = Utils.makeSimpleName(type);
Long count = eventCount.get(type.getId());
String countText = count == null ? "" : " (" + count + ")";
types.add(name + countText);
}
}
out.println(new Columnizer(types, 2).toString());
return true;
}
private boolean isExperimental(EventType t) {
return t.getAnnotation(Experimental.class) != null;
}
public static String getGrammarText() {
return """
Grammar:
query ::= [column] [format] select from [where] [groupBy] [orderBy] [limit]
column ::= "COLUMN" text ("," text)*
format ::= "FORMAT" formatter ("," formatter)*
formatter ::= property (";" property)*
select ::= "SELECT" "*" | expression ("," expression)*
expression ::= (aggregator | field) [alias]
aggregator ::= function "(" (field | "*") ")"
alias ::= "AS" symbol
from ::= "FROM" source ("," source)*
source ::= type [alias]
where ::= condition ("AND" condition)*
condition ::= field "=" text
groupBy ::= "GROUP BY" field ("," field)*
orderBy ::= "ORDER BY" orderField ("," orderField)*
orderField ::= field [sortOrder]
sortOrder ::= "ASC" | "DESC"
limit ::= "LIMIT" <integer>
- text, characters surrounded by single quotes
- symbol, alphabetic characters
- type, the event type name, for example SystemGC. To avoid ambiguity,
the name may be qualified, for example jdk.SystemGC
- field, the event field name, for example stackTrace.
To avoid ambiguity, the name may be qualified, for example
jdk.SystemGC.stackTrace. A type alias declared in a FROM clause
can be used instead of the type, for example S.eventThread
- function, determines how fields are aggregated when using GROUP BY.
Aggregate functions are:
AVG: The numeric average
COUNT: The number of values
DIFF: The numeric difference between the last and first value
FIRST: The first value
LAST: The last value
LAST_BATCH: The last set of values with the same end timestamp
LIST: All values in a comma-separated list
MAX: The numeric maximum
MEDIAN: The numeric median
MIN: The numeric minimum
P90, P95, P99, P999: The numeric percentile, 90%, 95%, 99% or 99.9%
STDEV: The numeric standard deviation
SUM: The numeric sum
UNIQUE: The unique number of occurrences of a value
Null values are included, but ignored for numeric functions. If no
aggregator function is specified, the first non-null value is used.
- property, any of the following:
cell-height:<integer> Maximum height of a table cell
missing:whitespace Replace missing values (N/A) with blank space
normalized Normalize values between 0 and 1.0 for the column
truncate-beginning if value can't fit a table cell, remove the first characters
truncate-end if value can't fit a table cell, remove the last characters
If no value exist, or a numeric value can't be aggregated, the result is 'N/A',
unless missing:whitespace is used. The top frame of a stack trace can be referred'
to as stackTrace.topFrame. When multiple event types are specified in a FROM clause,
the union of the event types are used (not the cartesian product)
To see all available events, use the query '"SHOW EVENTS"'. To see all fields for
a particular event type, use the query '"SHOW FIELDS <type>"'.""";
}
}

View File

@ -0,0 +1,372 @@
/*
* Copyright (c) 2023, 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.query;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.regex.Pattern;
import jdk.jfr.EventType;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.internal.Utils;
import jdk.jfr.internal.query.FilteredType.Filter;
import jdk.jfr.internal.query.Query.Condition;
import jdk.jfr.internal.query.Query.Expression;
import jdk.jfr.internal.query.Query.Formatter;
import jdk.jfr.internal.query.Query.Grouper;
import jdk.jfr.internal.query.Query.OrderElement;
import jdk.jfr.internal.query.Query.Source;
import jdk.jfr.internal.util.Matcher;
import jdk.jfr.internal.util.SpellChecker;
/**
* Purpose of this class is to take a query and all available event types and
* check that the query is valid, for example, to see if all fields and types
* referenced in the query exists. The end result is a list of fields
* suitable for grouping, sorting and rendering operations later.
*/
final class QueryResolver {
@SuppressWarnings("serial")
static class QueryException extends Exception {
public QueryException(String message) {
super(message);
}
}
@SuppressWarnings("serial")
static final class QuerySyntaxException extends QueryException {
public QuerySyntaxException(String message) {
super(message);
}
}
private final List<EventType> eventTypes;
private final List<FilteredType> fromTypes = new ArrayList<>();
private final Map<String, FilteredType> typeAliases = new LinkedHashMap<>();
private final Map<String, Field> fieldAliases = new LinkedHashMap<>();
private final List<Field> resultFields = new ArrayList<>();
// For readability take query apart
private final List<String> column;
private final List<Formatter> format;
private final List<Expression> select;
private final List<Source> from;
private final List<Condition> where;
private final List<OrderElement> orderBy;
private final List<Grouper> groupBy;
public QueryResolver(Query query, List<EventType> eventTypes) {
this.eventTypes = eventTypes;
this.column = query.column;
this.format = query.format;
this.select = query.select;
this.from = query.from;
this.where = query.where;
this.orderBy = query.orderBy;
this.groupBy = query.groupBy;
}
public List<Field> resolve() throws QueryException {
resolveFrom();
resolveSelect();
resolveGroupBy();
resolveOrderBy();
resolveWhere();
applyColumn();
applyFormat();
return resultFields;
}
private void resolveWhere() throws QuerySyntaxException {
for (Condition condition : where) {
List<Field> fields = new ArrayList<>();
String fieldName = condition.field();
Field aliasedField = fieldAliases.get(fieldName);
if (aliasedField != null) {
fields.add(aliasedField);
} else {
fields.addAll(resolveFields(fieldName, fromTypes));
}
for (Field field : fields) {
field.type.addFilter(new Filter(field, condition.value()));
}
}
}
private void resolveFrom() throws QueryException {
for (Source source : from) {
List<EventType> eventTypes = resolveEventType(source.name());
if (!source.alias().isEmpty() && eventTypes.size() > 1) {
throw new QueryException("Alias can only refer to a single event type");
}
for (EventType eventType : eventTypes) {
FilteredType type = new FilteredType(eventType);
fromTypes.add(type);
source.alias().ifPresent(alias -> typeAliases.put(alias, type));
}
}
}
private void resolveSelect() throws QueryException {
if (select.isEmpty()) { // SELECT *
resultFields.addAll(FieldBuilder.createWildcardFields(eventTypes, fromTypes));
return;
}
for (Expression expression : select) {
Field field = addField(expression.name(), fromTypes);
field.visible = true;
field.aggregator = expression.aggregator();
FieldBuilder.configureAggregator(field);
expression.alias().ifPresent(alias -> fieldAliases.put(alias, field));
if (field.name.equals("*") && field.aggregator != Aggregator.COUNT) {
throw new QuerySyntaxException("Wildcard ('*') can only be used with aggregator function COUNT");
}
}
}
private void resolveGroupBy() throws QueryException {
if (groupBy.isEmpty()) {
// Queries on the form "SELECT SUM(a), b, c FROM D" should group all rows implicitly
var f = select.stream().filter(e -> e.aggregator() != Aggregator.MISSING).findFirst();
if (f.isPresent()) {
Grouper grouper = new Grouper("startTime");
for (var fr : fromTypes) {
Field implicit = addField("startTime", List.of(fr));
implicit.valueGetter = e -> 1;
implicit.grouper = grouper;
}
groupBy.add(grouper);
return;
}
}
for (Grouper grouper : groupBy) {
for (FilteredType type : fromTypes) {
String fieldName = grouper.field();
// Check if alias exists, e.g. "SELECT field AS K FROM ... GROUP BY K"
Field field= fieldAliases.get(fieldName);
if (field != null) {
fieldName = field.name;
if (field.aggregator != Aggregator.MISSING) {
throw new QueryException("Aggregate funtion can't be used together with an alias");
}
}
field = addField(fieldName, List.of(type));
field.grouper = grouper;
}
}
}
private void resolveOrderBy() throws QueryException {
for (OrderElement orderer : orderBy) {
Field field = fieldAliases.get(orderer.name());
if (field == null) {
field = addField(orderer.name(), fromTypes);
}
field.orderer = orderer;
}
}
private void applyColumn() throws QueryException {
if (column.isEmpty()) {
return;
}
if (column.size() != select.size()) {
throw new QuerySyntaxException("Number of fields in COLUMN clause doesn't match SELECT");
}
for (Field field : resultFields) {
if (field.visible) {
field.label = column.get(field.index);
}
}
}
private void applyFormat() throws QueryException {
if (format.isEmpty()) {
return;
}
if (format.size() != select.size()) {
throw new QueryException("Number of fields in FORMAT doesn't match SELECT");
}
for (Field field : resultFields) {
if (field.visible) {
for (var formatter : format.get(field.index).properties()) {
formatter.style().accept(field);
}
}
}
}
private Field addField(String name, List<FilteredType> types) throws QueryException {
List<Field> fields = resolveFields(name, types);
if (fields.isEmpty()) {
throw new QueryException(unknownField(name, types));
}
Field primary = fields.getFirst();
boolean mixedTypes = false;
for (Field f : fields) {
if (!f.dataType.equals(primary.dataType)) {
mixedTypes = true;
}
}
for (Field field: fields) {
field.index = resultFields.size();
primary.sourceFields.add(field);
// Convert to String if field data types mismatch
if (mixedTypes) {
final Function<RecordedEvent, Object> valueGetter = field.valueGetter;
field.valueGetter = event -> {
return FieldFormatter.format(field, valueGetter.apply(event));
};
field.lexicalSort = true;
field.dataType = String.class.getName();
field.alignLeft = true;
}
}
resultFields.add(primary);
return primary;
}
private List<Field> resolveFields(String name, List<FilteredType> types) {
List<Field> fields = new ArrayList<>();
if (name.equals("*")) {
// Used with COUNT(*) and UNIQUE(*)
// All events should have a start time
name = "startTime";
}
if (name.startsWith("[")) {
int index = name.indexOf("]");
if (index != -1) {
String typeNames = name.substring(1, index);
String suffix = name.substring(index + 1);
for (String typeName : typeNames.split(Pattern.quote("|"))) {
fields.addAll(resolveFields(typeName + suffix, types));
}
return fields;
}
}
// Match "namespace.Event.field" and "Event.field"
if (name.contains(".")) {
for (FilteredType et : types) {
String fullEventType = et.getName() + ".";
if (name.startsWith(fullEventType)) {
String fieldName = name.substring(fullEventType.length());
FieldBuilder fb = new FieldBuilder(eventTypes, et, fieldName);
fields.addAll(fb.build());
}
String eventType = et.getSimpleName() + ".";
if (name.startsWith(eventType)) {
String fieldName = name.substring(eventType.length());
FieldBuilder fb = new FieldBuilder(eventTypes, et, fieldName);
fields.addAll(fb.build());
}
}
}
// Match "ALIAS.field" where ALIAS can be "namespace.Event" or "Event"
for (var entry : typeAliases.entrySet()) {
String alias = entry.getKey() + ".";
FilteredType s = entry.getValue();
if (name.startsWith(alias)) {
int index = name.indexOf(".");
String unaliased = s.getName() + "." + name.substring(index + 1);
fields.addAll(resolveFields(unaliased, List.of(s)));
}
}
// Match without namespace
for (FilteredType eventType : types) {
FieldBuilder fb = new FieldBuilder(eventTypes, eventType, name);
fields.addAll(fb.build());
}
return fields;
}
private List<EventType> resolveEventType(String name) throws QueryException {
List<EventType> types = new ArrayList<>();
// Match fully qualified name first
for (EventType eventType : eventTypes) {
if (Matcher.match(eventType.getName(),name)) {
types.add(eventType);
}
}
// Match less qualified name
for (EventType eventType : eventTypes) {
if (eventType.getName().endsWith("." + name)) {
types.add(eventType);
break;
}
}
if (types.isEmpty()) {
throw new QueryException(unknownEventType(eventTypes, name));
}
return types;
}
private static String unknownField(String name, List<FilteredType> types) {
List<String> alternatives = new ArrayList<>();
StringJoiner sj = new StringJoiner(", ");
for (FilteredType t : types) {
for (var v : t.getFields()) {
alternatives.add(v.getName());
alternatives.add(t.getName() + "." + v.getName());
alternatives.add(t.getSimpleName() + "." + v.getName());
}
sj.add(t.getName());
}
String message = "Can't find field named '" + name + "' in " + sj;
String alternative = SpellChecker.check(name, alternatives);
if (alternative != null) {
return message + ".\nDid you mean '" + alternative + "'?";
} else {
return message + ".\nUse 'SHOW FIELDS " + types.getFirst().getSimpleName() + "' to list available fields.";
}
}
private static String unknownEventType(List<EventType> eventTypes, String name) {
List<String> alternatives = new ArrayList<>();
for (EventType type : eventTypes) {
alternatives.add(Utils.makeSimpleName(type));
}
String alternative = SpellChecker.check(name, alternatives);
String message = "Can't find event type named '" + name + "'.";
if (alternative != null) {
return message + " Did you mean '" + alternative + "'?";
} else {
return message + " 'SHOW EVENTS' will list available event types.";
}
}
public List<FilteredType> getFromTypes() {
return fromTypes;
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright (c) 2023, 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.query;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import jdk.jfr.consumer.EventStream;
import jdk.jfr.consumer.MetadataEvent;
import jdk.jfr.internal.query.QueryResolver.QueryException;
import jdk.jfr.internal.query.QueryResolver.QuerySyntaxException;
final class QueryRun {
private final Histogram histogram = new Histogram();
private final Table table = new Table();
private final List<String> syntaxErrors = new ArrayList<>();
private final List<String> metadataErrors = new ArrayList<>();
private final Query query;
private final EventStream stream;
public QueryRun(EventStream stream, Query query) {
this.stream = stream;
this.query = query;
}
void onMetadata(MetadataEvent e) {
if (table.getFields().isEmpty()) {
// Only use first metadata event for now
try {
QueryResolver resolver = new QueryResolver(query, e.getEventTypes());
List<Field> fields = resolver.resolve();
table.addFields(fields);
histogram.addFields(fields);
addEventListeners();
} catch (QuerySyntaxException qe) {
syntaxErrors.add(qe.getMessage());
} catch (QueryException qe) {
metadataErrors.add(qe.getMessage());
}
}
}
public void complete() {
if (!query.groupBy.isEmpty()) {
table.addRows(histogram.toRows());
}
}
private void addEventListeners() {
for (var entry : groupByTypeDescriptor().entrySet()) {
FilteredType type = entry.getKey();
List<Field> sourceFields = entry.getValue();
stream.onEvent(type.getName(), e -> {
for (var filter : type.getFilters()) {
Object object = filter.field().valueGetter.apply(e);
String text = FieldFormatter.format(filter.field(), object);
if (!text.equals(filter.value())) {
return;
}
}
if (query.groupBy.isEmpty()) {
table.add(e, sourceFields);
} else {
histogram.add(e, type, sourceFields);
}
});
}
}
private LinkedHashMap<FilteredType, List<Field>> groupByTypeDescriptor() {
var multiMap = new LinkedHashMap<FilteredType, List<Field>>();
for (Field field : table.getFields()) {
for (Field sourceFields : field.sourceFields) {
multiMap.computeIfAbsent(sourceFields.type, k -> new ArrayList<>()).add(field);
}
}
return multiMap;
}
public List<String> getSyntaxErrors() {
return syntaxErrors;
}
public List<String> getMetadataErrors() {
return metadataErrors;
}
public Query getQuery() {
return query;
}
public Table getTable() {
return table;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2023, 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.query;
import java.util.Arrays;
final class Row {
private final Object[] values;
private final String[] texts;
public Row(int size) {
values = new Object[size];
texts = new String[size];
}
public Object getValue(int index) {
return values[index];
}
public void putValue(int index, Object o) {
values[index] = o;
}
public String getText(int index) {
return texts[index];
}
public void putText(int index, String text) {
texts[index] = text;
}
@Override
public String toString() {
return Arrays.asList(values).toString();
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2023, 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.query;
import java.util.ArrayList;
import java.util.List;
import jdk.jfr.consumer.RecordedEvent;
/**
* Class responsible for holding rows, their values and textual
* representation.
*/
final class Table {
private final List<Row> rows = new ArrayList<>();
private final List<Field> fields = new ArrayList<>();
boolean isEmpty() {
return rows.isEmpty();
}
void addRows(List<Row> rows) {
this.rows.addAll(rows);
}
List<Row> getRows() {
return rows;
}
void addFields(List<Field> fields) {
for (int index = 0; index <fields.size(); index++) {
if (fields.get(index).index != index) {
throw new InternalError("Field index not in sync. with array position");
}
}
this.fields.addAll(fields);
}
List<Field> getFields() {
return fields;
}
public void add(RecordedEvent event, List<Field> sourceFields) {
Row row = new Row(fields.size());
for (Field field : sourceFields) {
row.putValue(field.index, field.valueGetter.apply(event));
}
rows.add(row);
}
}

View File

@ -0,0 +1,149 @@
/*
* Copyright (c) 2023, 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.query;
import java.util.ArrayList;
import java.util.List;
import jdk.jfr.internal.query.Configuration.Truncate;
final class TableCell {
static final String ELLIPSIS = "...";
static final String COLUMN_SEPARATOR = " ";
static final int MINIMAL_CELL_WIDTH = 1 + COLUMN_SEPARATOR.length();
static final int ELLIPSIS_LENGTH = ELLIPSIS.length();
private final List<String> lines = new ArrayList<>();
private final Truncate truncate;
private int preferredWidth;
final int cellHeight;
final Field field;
int width;
public TableCell(Field field, int cellHeight, Truncate truncate) {
this.field = field;
this.cellHeight = cellHeight;
this.truncate = truncate;
}
public int getContentWidth() {
return width - COLUMN_SEPARATOR.length();
}
public int getHeight() {
return lines.size();
}
public String getText(int rowIndex) {
return lines.get(rowIndex);
}
public void setPreferredWidth(int width) {
preferredWidth = width;
}
public int getPreferredWidth() {
return preferredWidth;
}
public void addLine(String text) {
int contentWidth = getContentWidth();
if (text.length() >= contentWidth) {
add(truncate(text, contentWidth));
} else {
addAligned(text);
}
}
public void setContent(String text) {
clear();
int contentSize = getContentSize();
// Bail out early to prevent ellipsis when size is the same
if (text.length() == contentSize) {
add(text);
return;
}
// Text is larger than size of the cell, truncate
if (text.length() >= contentSize) {
add(truncate(text, contentSize));
return;
}
// Text fits on one line, pad left or right depending on alignment
int contentWidth = getContentWidth();
if (text.length() < contentWidth) {
addAligned(text);
return;
}
// Multiple lines and text fits cell
add(text);
}
private void addAligned(String text) {
String padding = " ".repeat(getContentWidth() - text.length());
if (field.alignLeft) {
add(text + padding);
} else {
add(padding + text);
}
}
public int getContentSize() {
return cellHeight * getContentWidth();
}
public List<String> getLines() {
return lines;
}
private String truncate(String text, int size) {
if (size < ELLIPSIS_LENGTH) {
return ELLIPSIS.substring(0, ELLIPSIS_LENGTH - size);
}
int textSize = size - ELLIPSIS_LENGTH;
if (truncate == Truncate.BEGINNING) {
return ELLIPSIS + text.substring(text.length() - textSize);
} else {
return text.substring(0, textSize) + ELLIPSIS;
}
}
private void add(String text) {
int contentWidth = getContentWidth();
int contentSize = getContentSize();
for (int index = 0; index < contentSize; index += contentWidth) {
int end = index + contentWidth;
if (end >= text.length()) {
String content = text.substring(index);
content += " ".repeat(contentWidth - content.length());
lines.add(content);
return;
}
lines.add(text.substring(index, end));
}
}
public void clear() {
lines.clear();
}
}

View File

@ -0,0 +1,357 @@
/*
* Copyright (c) 2023, 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.query;
import static jdk.jfr.internal.query.Configuration.MAX_PREFERRED_WIDTH;
import static jdk.jfr.internal.query.Configuration.MIN_PREFERRED_WIDTH;
import static jdk.jfr.internal.query.Configuration.PREFERRED_WIDTH;
import java.util.List;
import java.util.function.Predicate;
import jdk.jfr.consumer.RecordedFrame;
import jdk.jfr.consumer.RecordedMethod;
import jdk.jfr.consumer.RecordedStackTrace;
import jdk.jfr.internal.query.Configuration.Truncate;
import jdk.jfr.internal.util.Output;
/**
* Class responsible for printing and formatting the contents of a table.
*/
final class TableRenderer {
private final Configuration configuration;
private final List<TableCell> tableCells;
private final Table table;
private final Query query;
private final Output out;
private int width;
private int preferredWidth;
public TableRenderer(Configuration configuration, Table table, Query query) {
this.configuration = configuration;
this.tableCells = createTableCells(table);
this.table = table;
this.query = query;
this.out = configuration.output;
}
private List<TableCell> createTableCells(Table table) {
return table.getFields().stream().filter(f -> f.visible).map(f -> createTableCell(f)).toList();
}
private TableCell createTableCell(Field field) {
Truncate truncate = configuration.truncate;
if (truncate == null) {
truncate = field.truncate;
}
if (configuration.cellHeight != 0) {
return new TableCell(field, configuration.cellHeight, truncate);
} else {
return new TableCell(field, field.cellHeight, truncate);
}
}
public void render() {
if (isEmpty()) {
if (configuration.title != null) {
out.println();
out.println("No events found for '" + configuration.title +"'.");
}
return;
}
if (tooManyColumns()) {
out.println();
out.println("Too many columns to fit width.");
return;
}
formatRow();
sortRows();
setColumnWidths();
printTitle();
printHeaderRow();
printHeaderRowSeparators();
printRows();
}
private boolean isEmpty() {
return tableCells.isEmpty() || table.getRows().isEmpty();
}
private boolean tooManyColumns() {
int minWidth = tableCells.size() * TableCell.MINIMAL_CELL_WIDTH;
if (configuration.width != 0) {
return minWidth > configuration.width;
}
return minWidth > MAX_PREFERRED_WIDTH;
}
private void formatRow() {
double[] max = calculateNormalization();
for (Row row : table.getRows()) {
for (Field field : table.getFields()) {
int index = field.index;
Object object = row.getValue(index);
if (field.normalized && object instanceof Number number) {
object = number.doubleValue() / max[index];
}
String text = FieldFormatter.format(field, object);
row.putText(index, text);
if (index < tableCells.size()) {
TableCell cell = tableCells.get(index);
int width = text.length() + TableCell.COLUMN_SEPARATOR.length();
if (width > cell.getPreferredWidth()) {
cell.setPreferredWidth(width);
}
}
}
}
}
private double[] calculateNormalization() {
double[] max = new double[tableCells.size()];
int index = 0;
for (TableCell cell : tableCells) {
if (cell.field.normalized) {
for (Row row : table.getRows()) {
if (row.getValue(index) instanceof Number number) {
max[index] += number.doubleValue();
}
}
}
index++;
}
return max;
}
private void sortRows() {
TableSorter sorter = new TableSorter(table, query);
sorter.sort();
}
private void setColumnWidths() {
setRowWidths();
setPreferredHeaderWidths();
if (configuration.width == 0) {
preferredWidth= determineTableWidth();
} else {
preferredWidth = configuration.width;
}
// Set minimum table cell width
distribute(cell -> cell.width < TableCell.MINIMAL_CELL_WIDTH);
// Fill with preferred width
distribute(cell -> cell.width < cell.getPreferredWidth());
// Distribute additional width to table cells with a non-fixed size
distribute(cell -> !cell.field.fixedWidth);
// If all table cells are fixed size, distribute to any of them
distribute(cell -> true);
}
private void setRowWidths() {
int rowCount = 0;
for (Row row : table.getRows()) {
if (rowCount == query.limit) {
return;
}
int columnIndex = 0;
for (TableCell cell : tableCells) {
String text = row.getText(columnIndex);
int width = text.length() + TableCell.COLUMN_SEPARATOR.length();
if (width > cell.getPreferredWidth()) {
cell.setPreferredWidth(width);
}
columnIndex++;
}
rowCount++;
}
}
private void setPreferredHeaderWidths() {
for (TableCell cell : tableCells) {
int headerWidth = cell.field.label.length();
if (configuration.verboseHeaders) {
headerWidth = Math.max(fieldName(cell.field).length(), headerWidth);
}
headerWidth += TableCell.COLUMN_SEPARATOR.length();
if (headerWidth > cell.getPreferredWidth()) {
cell.setPreferredWidth(headerWidth);
}
}
}
private int determineTableWidth() {
int preferred = 0;
for (TableCell cell : tableCells) {
preferred += cell.getPreferredWidth();
}
// Avoid a very large table.
if (preferred > MAX_PREFERRED_WIDTH) {
return MAX_PREFERRED_WIDTH;
}
// Avoid a very small width, but not preferred width if there a few columns
if (preferred < MIN_PREFERRED_WIDTH && tableCells.size() < 3) {
return MIN_PREFERRED_WIDTH;
}
// Expand to preferred width
if (preferred < PREFERRED_WIDTH) {
return PREFERRED_WIDTH;
}
return preferred;
}
private void distribute(Predicate<TableCell> condition) {
long amountLeft = preferredWidth - width;
long last = -1;
while (amountLeft > 0 && amountLeft != last) {
last = amountLeft;
for (TableCell cell : tableCells) {
if (condition.test(cell)) {
cell.width++;
width++;
amountLeft--;
}
}
}
}
private void printTitle() {
String title = configuration.title;
if (title != null) {
if (isExperimental()) {
title += " (Experimental)";
}
int pos = width - title.length();
pos = Math.max(0, pos);
pos = pos / 2;
out.println();
out.println(" ".repeat(pos) + title);
out.println();
}
}
private boolean isExperimental() {
return tableCells.stream().flatMap(c -> c.field.sourceFields.stream()).anyMatch(f -> f.type.isExperimental());
}
private void printHeaderRow() {
printRow(cell -> cell.field.label);
if (configuration.verboseHeaders) {
printRow(cell -> fieldName(cell.field));
}
}
private void printHeaderRowSeparators() {
printRow(cell -> "-".repeat(cell.getContentWidth()));
}
private void printRow(java.util.function.Function<TableCell, String> action) {
for (TableCell cell : tableCells) {
cell.setContent(action.apply(cell));
}
printRow();
}
private void printRows() {
int rowCount = 0;
for (Row row : table.getRows()) {
if (rowCount == query.limit) {
return;
}
int columnIndex = 0;
for (TableCell cell : tableCells) {
setCellContent(cell, row, columnIndex++);
}
printRow();
rowCount++;
}
}
private void setCellContent(TableCell cell, Row row, int columnIndex) {
String text = row.getText(columnIndex);
if (cell.cellHeight > 1) {
Object o = row.getValue(columnIndex);
if (o instanceof RecordedStackTrace s) {
setStackTrace(cell, s);
return;
}
}
if (text.length() > cell.getContentSize()) {
Object o = row.getValue(columnIndex);
cell.setContent(FieldFormatter.formatCompact(cell.field, o));
return;
}
cell.setContent(text);
}
private void setStackTrace(TableCell cell, RecordedStackTrace s) {
int row = 0;
cell.clear();
for(RecordedFrame f : s.getFrames()) {
if (row == cell.cellHeight) {
return;
}
if (f.isJavaFrame()) {
RecordedMethod method = f.getMethod();
String text = FieldFormatter.format(cell.field, method);
if (text.length() > cell.getContentWidth()) {
text = FieldFormatter.formatCompact(cell.field, method);
}
cell.addLine(text);
}
row++;
}
}
private void printRow() {
long maxHeight = 0;
for (TableCell cell : tableCells) {
maxHeight = Math.max(cell.getHeight(), maxHeight);
}
TableCell lastCell = tableCells.get(tableCells.size() - 1);
for (int rowIndex = 0; rowIndex < maxHeight; rowIndex++) {
for (TableCell cell : tableCells) {
if (rowIndex < cell.getHeight()) {
out.print(cell.getText(rowIndex));
} else {
out.print(" ".repeat(cell.getContentWidth()));
}
if (cell != lastCell) {
out.print(TableCell.COLUMN_SEPARATOR);
}
}
out.println();
}
}
private String fieldName(Field field) {
return "(" + field.name + ")";
}
public long getWidth() {
return width;
}
}

View File

@ -0,0 +1,152 @@
/*
* Copyright (c) 2023, 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.query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
import jdk.jfr.internal.query.Query.OrderElement;
import jdk.jfr.internal.query.Query.SortOrder;
/**
* Class responsible for sorting a table according to an ORDER BY statement or
* a heuristics.
*/
final class TableSorter {
@SuppressWarnings({ "rawtypes", "unchecked" })
private static class ColumnComparator implements Comparator<Row> {
private final int factor;
private final int index;
private final boolean lexical;
public ColumnComparator(Field field, SortOrder order) {
this.factor = sortOrderToFactor(determineSortOrder(field, order));
this.index = field.index;
this.lexical = field.lexicalSort;
}
private SortOrder determineSortOrder(Field field, SortOrder order) {
if (order != SortOrder.NONE) {
return order;
}
if (field.timespan || field.percentage) {
return SortOrder.DESCENDING;
}
return SortOrder.ASCENDING;
}
int sortOrderToFactor(SortOrder order) {
return order == SortOrder.DESCENDING ? -1 : 1;
}
@Override
public int compare(Row rowA, Row rowB) {
if (lexical) {
return compareObjects(rowA.getText(index), rowB.getText(index));
} else {
return compareObjects(rowA.getValue(index), rowB.getValue(index));
}
}
private int compareObjects(Object a, Object b) {
if (a instanceof Comparable c1 && b instanceof Comparable c2) {
return factor * c1.compareTo(c2);
}
return factor;
}
}
private final Table table;
private final Query query;
public TableSorter(Table table, Query query) {
this.table = table;
this.query = query;
}
public void sort() {
if (table.getFields().isEmpty()) {
return;
}
if (query.orderBy.isEmpty()) {
sortDefault();
return;
}
sortOrderBy();
}
private void sortDefault() {
if (sortAggregators()) {
return;
}
if (sortGroupBy()) {
return;
}
sortLeftMost();
}
private boolean sortAggregators() {
return sortPredicate(field -> field.aggregator != Aggregator.MISSING);
}
private boolean sortGroupBy() {
return sortPredicate(field -> query.groupBy.contains(field.grouper));
}
private void sortOrderBy() {
for (OrderElement orderer : query.orderBy.reversed()) {
sortPredicate(field -> field.orderer == orderer);
}
}
private boolean sortPredicate(Predicate<Field> predicate) {
boolean sorted = false;
for (Field field : table.getFields()) {
if (predicate.test(field)) {
sort(field, determineSortOrder(field));
sorted = true;
}
}
return sorted;
}
private SortOrder determineSortOrder(Field field) {
if (field.orderer == null) {
return SortOrder.NONE;
} else {
return field.orderer.order();
}
}
private void sortLeftMost() {
sort(table.getFields().getFirst(), SortOrder.NONE);
}
private void sort(Field field, SortOrder order) {
table.getRows().sort(new ColumnComparator(field, order));
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright (c) 2023, 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.query;
import java.io.IOException;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jdk.jfr.internal.util.Tokenizer;
/**
* Represents a configuration file that holds a set up queries and their
* associated metadata, such as labels and descriptions.
*/
final class ViewFile {
record ViewConfiguration(String name, String category, Map<String, String> properties) {
public String query() {
String form = get("form");
if (form != null) {
return form;
}
String table = get("table");
if (table != null) {
return table;
}
throw new IllegalStateException("Expected section to have form or table attribute");
}
public String getLabel() {
return get("label");
}
public String getForm() {
return get("form");
}
public String getTable() {
return get("table");
}
private String get(String key) {
return properties.get(key);
}
}
private final List<ViewConfiguration> configurations;
public ViewFile(String text) throws ParseException {
this.configurations = parse(text);
}
public static ViewFile getDefault() {
try {
var is = ViewFile.class.getResourceAsStream("/jdk/jfr/internal/query/view.ini");
byte[] bytes = is.readAllBytes();
String query = new String(bytes, Charset.forName("UTF-8"));
return new ViewFile(query);
} catch (ParseException e) {
throw new InternalError("Internal error, invalid view.ini", e);
} catch (IOException e) {
throw new InternalError("Internal error, could not read view.ini", e);
}
}
public List<ViewConfiguration> getViewConfigurations() {
return configurations;
}
private List<ViewConfiguration> parse(String text) throws ParseException {
List<ViewConfiguration> views = new ArrayList<>();
try (Tokenizer tokenizer = new Tokenizer(text, '[', ']', ';')) {
while (tokenizer.hasNext()) {
while (tokenizer.accept(";")) {
tokenizer.skipLine();
}
if (tokenizer.accept("[")) {
String fullName = tokenizer.next();
tokenizer.expect("]");
views.add(createView(fullName));
}
if (views.isEmpty()) {
throw new ParseException("Expected view file to begin with a section", tokenizer.getPosition());
}
String key = tokenizer.next();
tokenizer.expect("=");
String value = tokenizer.next();
ViewConfiguration view = views.get(views.size() - 1);
view.properties().put(key, value);
}
}
return views;
}
private ViewConfiguration createView(String fullName) {
int index = fullName.lastIndexOf(".");
if (index == -1) {
throw new InternalError("Missing name space for " + fullName);
}
String category = fullName.substring(0, index);
String name = fullName.substring(index + 1);
return new ViewConfiguration(name, category, new LinkedHashMap<>());
}
}

View File

@ -0,0 +1,350 @@
/*
* Copyright (c) 2023, 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.query;
import java.io.Closeable;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import jdk.jfr.EventType;
import jdk.jfr.consumer.EventStream;
import jdk.jfr.internal.query.QueryResolver.QueryException;
import jdk.jfr.internal.query.ViewFile.ViewConfiguration;
import jdk.jfr.internal.util.Columnizer;
import jdk.jfr.internal.util.Output;
import jdk.jfr.internal.util.StopWatch;
import jdk.jfr.internal.util.Tokenizer;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
import jdk.jfr.internal.util.ValueFormatter;
/**
* Class responsible for executing and displaying the contents of a query.
* <p>
* Used by 'jcmd JFR.view' and 'jfr view'.
* <p>
* Views are defined in jdk/jfr/internal/query/view.ini
*/
public final class ViewPrinter {
private final Configuration configuration;
private final EventStream stream;
private final Output out;
private final StopWatch stopWatch;
/**
* Constructs a view printer object.
*
* @param configuration display configuration
* @param stream a non-started stream from where data should be fetched.
*/
public ViewPrinter(Configuration configuration, EventStream stream) {
this.configuration = configuration;
this.out = configuration.output;
this.stopWatch = new StopWatch();
this.stream = stream;
}
/**
* Prints the view.
*
* @param text the view or event type to display
*
* @throws UserDataException if the stream associated with the printer lacks
* event or event metadata
* @throws UserSyntaxException if the syntax of the query is incorrect.
*/
public void execute(String text) throws UserDataException, UserSyntaxException {
try {
if (showViews(text) || showEventType(text)) {
return;
}
} catch (ParseException pe) {
throw new InternalError("Internal error, view.ini file is invalid", pe);
}
throw new UserDataException("Could not find a view or an event type named " + text);
}
private boolean showEventType(String eventType) {
try {
QueryPrinter printer = new QueryPrinter(configuration, stream);
configuration.verboseTitle = true;
printer.execute("SELECT * FROM " + eventType);
return true;
} catch (UserDataException | UserSyntaxException e) {
return false;
}
}
private boolean showViews(String text) throws UserDataException, ParseException, UserSyntaxException {
if (configuration.verbose) {
configuration.verboseHeaders = true;
}
if (text.equalsIgnoreCase("all-events")) {
QueryExecutor executor = new QueryExecutor(stream);
stopWatch.beginAggregation();
List<QueryRun> runs = executor.run();
stopWatch.beginFormatting();
for (QueryRun task : runs) {
Table table = task.getTable();
FilteredType type = table.getFields().getFirst().type;
configuration.title = type.getLabel();
TableRenderer renderer = new TableRenderer(configuration, table , task.getQuery());
renderer.render();
out.println();
}
stopWatch.finish();
if (configuration.verbose) {
out.println();
out.println("Execution: " + stopWatch.toString());
}
printTimespan();
return true;
}
if (text.equals("types")) {
QueryPrinter qp = new QueryPrinter(configuration, stream);
qp.execute("SHOW EVENTS");
return true;
}
List<ViewConfiguration> views = ViewFile.getDefault().getViewConfigurations();
if (text.equalsIgnoreCase("all-views")) {
stopWatch.beginQueryValidation();
List<Query> queries = new ArrayList<>();
for (ViewConfiguration view : views) {
queries.add(new Query(view.query()));
}
QueryExecutor executor = new QueryExecutor(stream, queries);
int index = 0;
stopWatch.beginAggregation();
List<QueryRun> runs = executor.run();
stopWatch.beginFormatting();
for (QueryRun run : runs) {
printView(views.get(index++), run);
}
stopWatch.finish();
if (configuration.verbose) {
out.println();
out.println("Execution: " + stopWatch.toString());
}
printTimespan();
printViewTypeRelation(views, executor.getEventTypes());
return true;
}
for (ViewConfiguration view : views) {
if (view.name().equalsIgnoreCase(text)) {
stopWatch.beginQueryValidation();
Query q = new Query(view.query());
QueryExecutor executor = new QueryExecutor(stream, q);
stopWatch.beginAggregation();
QueryRun run = executor.run().getFirst();
stopWatch.beginFormatting();
printView(view, run);
stopWatch.finish();
if (configuration.verbose) {
out.println();
out.println("Execution: " + stopWatch.toString());
out.println();
}
printTimespan();
return true;
}
}
return false;
}
void printViewTypeRelation(List<ViewConfiguration> views, List<EventType> eventTypes) throws ParseException {
if (!configuration.verbose) {
return;
}
out.println();
out.println("Event types and views");
out.println();
Map<String, Set<String>> viewMap = new HashMap<>();
for (EventType type : eventTypes) {
viewMap.put(type.getName(), new LinkedHashSet<>());
}
for (ViewConfiguration view : views) {
Query query = new Query(view.query());
if (query.from.getFirst().name().equals("*")) {
continue;
}
QueryResolver resolver = new QueryResolver(query, eventTypes);
try {
resolver.resolve();
} catch (QueryException e) {
throw new InternalError(e);
}
for (FilteredType ft: resolver.getFromTypes()) {
Set<String> list = viewMap.get(ft.getName());
list.add(view.name());
}
}
List<String> names = new ArrayList<>(viewMap.keySet());
Collections.sort(names);
for (String name : names) {
Set<String> vs = viewMap.get(name);
StringJoiner sj = new StringJoiner(", ");
vs.stream().forEach(sj::add);
out.println(String.format("%-35s %s", name, sj.toString()));
}
}
private void printTimespan() {
if (configuration.startTime != null) {
String start = ValueFormatter.formatTimestamp(configuration.startTime);
String end = ValueFormatter.formatTimestamp(configuration.endTime);
out.println();
out.println("Timespan: " + start + " - " + end);
}
}
private void printView(ViewConfiguration section, QueryRun queryRun)
throws UserDataException, ParseException, UserSyntaxException {
if (!queryRun.getSyntaxErrors().isEmpty()) {
throw new UserSyntaxException(queryRun.getSyntaxErrors().getFirst());
}
if (!queryRun.getMetadataErrors().isEmpty()) {
// Recording doesn't have the event,
out.println(queryRun.getMetadataErrors().getFirst());
out.println("Missing event found for " + section.name());
return;
}
Table table = queryRun.getTable();
configuration.title = section.getLabel();
long width = 0;
if (section.getForm() != null) {
FormRenderer renderer = new FormRenderer(configuration, table);
renderer.render();
width = renderer.getWidth();
}
if (section.getTable() != null) {
Query query = queryRun.getQuery();
TableRenderer renderer = new TableRenderer(configuration, table, query);
renderer.render();
width = renderer.getWidth();
}
if (width != 0 && configuration.verbose && !queryRun.getTable().isEmpty()) {
out.println();
Query query = queryRun.getQuery();
printQuery(new LineBuilder(out, width), query.toString());
}
}
private void printQuery(LineBuilder lb, String query) {
char[] separators = {'=', ','};
try (Tokenizer tokenizer = new Tokenizer(query, separators)) {
while (tokenizer.hasNext()) {
lb.append(nextText(tokenizer));
}
lb.out.println();
} catch (ParseException pe) {
throw new InternalError("Could not format already parsed query", pe);
}
}
private String nextText(Tokenizer tokenizer) throws ParseException {
if (tokenizer.peekChar() == '\'') {
return "'" + tokenizer.next() + "'";
} else {
return tokenizer.next();
}
}
// Helper class for line breaking
private static class LineBuilder implements Closeable {
private final Output out;
private final long width;
private int position;
LineBuilder(Output out, long width) {
this.out = out;
this.width = width;
}
public void append(String text) {
String original = text;
if (!text.equals(",") && !text.equals(";") && position != 0) {
text = " " + text;
}
if (text.length() > width) {
print(text);
return;
}
if (text.length() + position > width) {
out.println();
position = 0;
text = original;
}
out.print(text);
position += text.length();
}
private void print(String s) {
for (int i = 0; i < s.length(); i++) {
if (position % width == 0 && position != 0) {
out.println();
}
out.print(s.charAt(i));
position++;
}
}
@Override
public void close() throws IOException {
out.println();
}
}
public static List<String> getAvailableViews() {
List<String> list = new ArrayList<>();
list.add("Java virtual machine views:");
list.add(new Columnizer(getViewList("jvm"), 3).toString());
list.add("");
list.add("Environment views:");
list.add(new Columnizer(getViewList("environment"), 3).toString());
list.add("");
list.add("Application views:");
list.add(new Columnizer(getViewList("application"), 3).toString());
list.add("");
return list;
}
private static List<String> getViewList(String selection) {
List<String> names = new ArrayList<>();
for (var view : ViewFile.getDefault().getViewConfigurations()) {
String category = view.category();
if (selection.equals(category)) {
names.add(view.name());
}
}
return names;
}
}

View File

@ -0,0 +1,576 @@
;
; Copyright (c) 2023, 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.
;
[environment.active-recordings]
label = "Active Recordings"
table = "COLUMN 'Start', 'Duration', 'Name',
'Destination', 'Max Age', 'Max Size'
FORMAT none, none, none,
cell-height:5, none, none
SELECT LAST(recordingStart), LAST(recordingDuration), LAST(name),
LAST(destination), LAST(maxAge), LAST(maxSize)
FROM ActiveRecording
GROUP BY id"
[environment.active-settings]
label = "Active Settings"
table = "COLUMN 'Event Type', 'Enabled', 'Threshold',
'Stack Trace','Period','Cutoff', 'Throttle'
FORMAT none, missing:whitespace, missing:whitespace, missing:whitespace,
missing:whitespace, missing:whitespace, missing:whitespace
SELECT E.id, LAST_BATCH(E.value), LAST_BATCH(T.value),
LAST_BATCH(S.value), LAST_BATCH(P.value),
LAST_BATCH(C.value), LAST_BATCH(U.value)
FROM
ActiveSetting AS E,
ActiveSetting AS T,
ActiveSetting AS S,
ActiveSetting AS P,
ActiveSetting AS C,
ActiveSetting AS U
WHERE
E.name = 'enabled' AND
T.name = 'threshold' AND
S.name = 'stackTrace' AND
P.name = 'period' AND
C.name = 'cutoff' AND
U.name = 'throttle'
GROUP BY
id
ORDER BY
E.id"
[application.allocation-by-class]
label = "Allocation by Class"
table = "COLUMN 'Object Type', 'Allocation Pressure'
FORMAT none, normalized
SELECT objectClass AS O, SUM(weight) AS W
FROM ObjectAllocationSample GROUP BY O ORDER BY W DESC LIMIT 25"
[application.allocation-by-thread]
label = "Allocation by Thread"
table = "COLUMN 'Thread', 'Allocation Pressure'
FORMAT none, normalized
SELECT eventThread AS T, SUM(weight) AS W
FROM ObjectAllocationSample GROUP BY T ORDER BY W DESC LIMIT 25"
[application.allocation-by-site]
label = "Allocation by Site"
table = "COLUMN 'Method', 'Allocation Pressure'
FORMAT none, normalized
SELECT stackTrace.topFrame AS S, SUM(weight) AS W
FROM ObjectAllocationSample
GROUP BY S
ORDER BY W DESC LIMIT 25"
[application.class-loaders]
label = "Class Loaders"
table = "FORMAT missing:null-bootstrap, none, none
SELECT classLoader, LAST(hiddenClassCount),
LAST(classCount) AS C
FROM ClassLoaderStatistics
GROUP BY classLoader ORDER BY C DESC";
[jvm.class-modifications]
label = "Class Modifications"
table = "COLUMN 'Time', 'Requested By', 'Operation', 'Classes'
SELECT duration, stackTrace.topApplicationFrame, eventType.label, classCount
FROM RetransformClasses, RedefineClasses
GROUP BY redefinitionId
ORDER BY duration DESC"
[jvm.compiler-configuration]
label = "Compiler Configuration"
form = "SELECT LAST(threadCount), LAST(dynamicCompilerThreadCount), LAST(tieredCompilation)
FROM CompilerConfiguration"
[jvm.compiler-statistics]
label = "Compiler Statistics"
form = "SELECT LAST(compileCount), LAST(peakTimeSpent), LAST(totalTimeSpent),
LAST(bailoutCount), LAST(osrCompileCount),
LAST(standardCompileCount), LAST(osrBytesCompiled),
LAST(standardBytesCompiled), LAST(nmethodsSize),
LAST(nmethodCodeSize) FROM CompilerStatistics"
[jvm.compiler-phases]
label = "Concurrent Compiler Phases"
table = "COLUMN 'Level', 'Phase', 'Average',
'P95', 'Longest', 'Count',
'Total'
SELECT phaseLevel AS L, phase AS P, AVG(duration),
P95(duration), MAX(duration), COUNT(*),
SUM(duration) AS S
FROM CompilerPhase
GROUP BY P ORDER BY L ASC, S DESC"
[environment.container-configuration]
label = "Container Configuration"
form = "SELECT LAST(containerType), LAST(cpuSlicePeriod), LAST(cpuQuota), LAST(cpuShares),
LAST(effectiveCpuCount), LAST(memorySoftLimit), LAST(memoryLimit),
LAST(swapMemoryLimit), LAST(hostTotalMemory)
FROM ContainerConfiguration"
[environment.container-cpu-usage]
label = "Container CPU Usage"
form = "SELECT LAST(cpuTime), LAST(cpuUserTime), LAST(cpuSystemTime) FROM ContainerCPUUsage"
[environment.container-memory-usage]
label = "Container Memory Usage"
form = "SELECT LAST(memoryFailCount), LAST(memoryUsage), LAST(swapMemoryUsage) FROM ContainerMemoryUsage"
[environment.container-io-usage]
label = "Container I/O Usage"
form = "SELECT LAST(serviceRequests), LAST(dataTransferred) FROM ContainerIOUsage"
[environment.container-cpu-throttling]
label = "Container CPU Throttling"
form = "SELECT LAST(cpuElapsedSlices), LAST(cpuThrottledSlices), LAST(cpuThrottledTime) FROM ContainerCPUThrottling"
[application.contention-by-thread]
label = "Contention by Thread"
table = "COLUMN 'Thread', 'Count', 'Avg', 'P90', 'Max.'
SELECT eventThread, COUNT(*), AVG(duration), P90(duration), MAX(duration) AS M
FROM JavaMonitorEnter GROUP BY eventThread ORDER BY M"
[application.contention-by-class]
label = "Contention by Lock Class"
table = "COLUMN 'Lock Class', 'Count', 'Avg.', 'P90', 'Max.'
SELECT monitorClass, COUNT(*), AVG(duration), P90(duration), MAX(duration) AS M
FROM JavaMonitorEnter GROUP BY monitorClass ORDER BY M"
[application.contention-by-site]
label = "Contention by Site"
table = "COLUMN 'StackTrace', 'Count', 'Avg.', 'Max.'
SELECT stackTrace AS S, COUNT(*), AVG(duration), MAX(duration) AS M
FROM JavaMonitorEnter GROUP BY S ORDER BY M"
[application.contention-by-address]
label = "Contention by Monitor Address"
table = "COLUMN 'Monitor Address', 'Class', 'Threads', 'Max Duration'
SELECT address, FIRST(monitorClass), UNIQUE(*), MAX(duration) AS M
FROM JavaMonitorEnter
GROUP BY monitorClass ORDER BY M"
[environment.cpu-information]
label ="CPU Information"
form = "SELECT cpu, sockets, cores, hwThreads, description FROM CPUInformation"
[environment.cpu-load]
label = "CPU Load Statistics"
form = "COLUMN
'JVM User (Minimum)',
'JVM User (Average)',
'JVM User (Maximum)',
'JVM System (Minimum)',
'JVM System (Average)',
'JVM System (Maximum)',
'Machine Total (Minimum)',
'Machine Total (Average)',
'Machine Total (Maximum)'
SELECT MIN(jvmUser), AVG(jvmUser), MAX(jvmUser),
MIN(jvmSystem), AVG(jvmSystem), MAX(jvmSystem),
MIN(machineTotal), AVG(machineTotal), MAX(machineTotal)
FROM CPULoad"
[environment.cpu-load-samples]
label = "CPU Load"
table = "SELECT startTime, jvmUser, jvmSystem, machineTotal FROM CPULoad"
[environment.cpu-tsc]
label ="CPU Time Stamp Counter"
form = "SELECT LAST(fastTimeAutoEnabled), LAST(fastTimeEnabled),
LAST(fastTimeFrequency), LAST(osFrequency)
FROM CPUTimeStampCounter"
[jvm.deoptimizations-by-reason]
label = "Deoptimization by Reason"
table = "SELECT reason, COUNT(reason) AS C
FROM Deoptimization GROUP BY reason ORDER BY C DESC"
[jvm.deoptimizations-by-site]
label = "Deoptimization by Site"
table = "SELECT method, lineNumber, bci, COUNT(reason) AS C
FROM Deoptimization GROUP BY method ORDER BY C DESC"
[environment.events-by-count]
label = "Event Types by Count"
table = "SELECT eventType.label AS E, COUNT(*) AS C FROM * GROUP BY E ORDER BY C DESC"
[environment.events-by-name]
label = "Event Types by Name"
table = "SELECT eventType.label AS E, COUNT(*) AS C FROM * GROUP BY E ORDER BY E ASC"
[environment.environment-variables]
label = "Environment Variables"
table = "FORMAT none, cell-height:20
SELECT LAST(key) AS K, LAST(value)
FROM InitialEnvironmentVariable GROUP BY key ORDER BY K"
[application.exception-count]
label ="Exception Statistics"
form = "COLUMN 'Exceptions Thrown' SELECT DIFF(throwables) FROM ExceptionStatistics"
[application.exception-by-type]
label ="Exceptions by Type"
table = "COLUMN 'Class', 'Count'
SELECT thrownClass AS T, COUNT(thrownClass) AS C
FROM JavaErrorThrow, JavaExceptionThrow GROUP BY T ORDER BY C DESC"
[application.exception-by-message]
label ="Exceptions by Message"
table = "COLUMN 'Message', 'Count'
SELECT message AS M, COUNT(message) AS C
FROM JavaErrorThrow, JavaExceptionThrow GROUP BY M ORDER BY C DESC"
[application.exception-by-site]
label ="Exceptions by Site"
table = "COLUMN 'Method', 'Count'
SELECT stackTrace.notInit AS S, COUNT(startTime) AS C
FROM JavaErrorThrow, JavaExceptionThrow GROUP BY S ORDER BY C DESC"
[application.file-reads-by-path]
label = "File Reads by Path"
table = "COLUMN 'Path', 'Reads', 'Total Read'
FORMAT cell-height:5, none, none
SELECT path, COUNT(*), SUM(bytesRead) AS S FROM FileRead
GROUP BY path ORDER BY S DESC"
[application.file-writes-by-path]
label = "File Writes by Path"
table = "COLUMN 'Path', 'Writes', 'Total Written'
FORMAT cell-height:5, none, none
SELECT path, COUNT(bytesWritten), SUM(bytesWritten) AS S FROM FileWrite
GROUP BY path ORDER BY S DESC"
[application.finalizers]
label = "Finalizers"
table = "SELECT finalizableClass, LAST_BATCH(objects) AS O, LAST_BATCH(totalFinalizersRun)
FROM FinalizerStatistics GROUP BY finalizableClass ORDER BY O DESC"
[jvm.gc]
label = "Garbage Collections"
table = "COLUMN 'Start', 'GC ID', 'Type', 'Heap Before GC', 'Heap After GC', 'Longest Pause'
FORMAT none, none, missing:Unknown, none, none, none
SELECT G.startTime, gcId, [Y|O].eventType.label,
B.heapUsed, A.heapUsed, longestPause
FROM
GarbageCollection AS G,
GCHeapSummary AS B,
GCHeapSummary AS A,
OldGarbageCollection AS O,
YoungGarbageCollection AS Y
WHERE B.when = 'Before GC' AND A.when = 'After GC'
GROUP BY gcId ORDER BY G.startTime"
[jvm.gc-concurrent-phases]
label = "Concurrent GC Phases"
table = "COLUMN 'Name', 'Average', 'P95',
'Longest', 'Count', 'Total'
SELECT name, AVG(duration), P95(duration),
MAX(duration), COUNT(*), SUM(duration) AS S
FROM GCPhaseConcurrent, GCPhaseConcurrentLevel1
GROUP BY name ORDER BY S"
[jvm.gc-configuration]
label = 'GC Configuration'
form = "COLUMN 'Young GC', 'Old GC',
'Parallel GC Threads','Concurrent GC Threads',
'Dynamic GC Threads', 'Concurrent Explicit GC',
'Disable Explicit GC', 'Pause Target',
'GC Time Ratio'
SELECT LAST(youngCollector), LAST(oldCollector),
LAST(parallelGCThreads), LAST(concurrentGCThreads),
LAST(usesDynamicGCThreads), LAST(isExplicitGCConcurrent),
LAST(isExplicitGCDisabled), LAST(pauseTarget),
LAST(gcTimeRatio)
FROM GCConfiguration"
[jvm.gc-references]
label = "GC References"
table = "COLUMN 'Time', 'GC ID', 'Soft Ref.', 'Weak Ref.', 'Phantom Ref.', 'Final Ref.', 'Total Count'
SELECT G.startTime, G.gcId, S.count, W.count, P.count, F.count, SUM(G.count)
FROM GCReferenceStatistics AS S,
GCReferenceStatistics AS W,
GCReferenceStatistics AS P,
GCReferenceStatistics AS F,
GCReferenceStatistics AS G
WHERE S.type = 'Soft reference' AND
W.type = 'Weak reference' AND
P.type = 'Phantom reference' AND
F.type = 'Final reference'
GROUP BY gcId ORDER By G.gcId ASC"
[jvm.gc-pause-phases]
label = "GC Pause Phases"
table = "COLUMN 'Type', 'Name', 'Average',
'P95', 'Longest', 'Count', 'Total'
SELECT eventType.label AS T, name, AVG(duration),
P95(duration), MAX(duration), COUNT(*), SUM(duration) AS S
FROM GCPhasePause, GCPhasePauseLevel1, GCPhasePauseLevel2,
GCPhasePauseLevel3, GCPhasePauseLevel4 GROUP BY name
ORDER BY T ASC, S"
[jvm.gc-pauses]
label = "GC Pauses"
form = "COLUMN 'Total Pause Time','Number of Pauses', 'Minimum Pause Time',
'Median Pause Time', 'Average Pause Time', 'P90 Pause Time',
'P95 Pause Time', 'P99 Pause Time', 'P99.9% Pause Time',
'Maximum Pause Time'
SELECT SUM(duration), COUNT(duration), MIN(duration),
MEDIAN(duration), AVG(duration), P90(duration),
P95(duration), P99(duration), P999(duration),
MAX(duration)
FROM GCPhasePause"
[jvm.gc-allocation-trigger]
label = "GC Allocation Trigger"
table = "COLUMN 'Trigger Method (Non-JDK)', 'Count', 'Total Requested'
SELECT stackTrace.topApplicationFrame AS S, COUNT(*), SUM(size)
FROM AllocationRequiringGC GROUP BY S"
[jvm.gc-cpu-time]
label = "GC CPU Time"
form = "COLUMN 'GC User Time', 'GC System Time',
'GC Wall Clock Time', 'Total Time',
'GC Count'
SELECT SUM(userTime), SUM(systemTime),
SUM(realTime), DIFF(startTime), COUNT(*)
FROM GCCPUTime"
[jvm.heap-configuration]
label = "Heap Configuration"
form = "SELECT LAST(initialSize), LAST(minSize), LAST(maxSize),
LAST(usesCompressedOops), LAST(compressedOopsMode)
FROM GCHeapConfiguration"
[application.hot-methods]
label = "Java Methods that Executes the Most"
table = "COLUMN 'Method', 'Samples', 'Percent'
FORMAT none, none, normalized
SELECT stackTrace.topFrame AS T, COUNT(*), COUNT(*)
FROM ExecutionSample GROUP BY T LIMIT 25"
[environment.jvm-flags]
label = "Command Line Flags"
table = "SELECT name AS N, LAST(value)
FROM IntFlag, UnsignedIntFlag, BooleanFlag,
LongFlag, UnsignedLongFlag,
DoubleFlag, StringFlag,
IntFlagChanged, UnsignedIntFlagChanged, BooleanFlagChanged,
LongFlagChanged, UnsignedLongFlagChanged,
DoubleFlagChanged, StringFlagChanged
GROUP BY name ORDER BY name ASC"
[jvm.jvm-information]
label = "JVM Information"
form = "COLUMN
'PID', 'VM Start', 'Name', 'Version',
'VM Arguments', 'Program Arguments'
SELECT LAST(pid), LAST(jvmStartTime), LAST(jvmName), LAST(jvmVersion),
LAST(jvmArguments), LAST(javaArguments) FROM JVMInformation"
[application.latencies-by-type]
label = "Latencies by Type"
table = "COLUMN 'Event Type', 'Count', 'Average', 'P 99', 'Longest', 'Total'
SELECT eventType.label AS T, COUNT(*), AVG(duration), P99(duration), MAX(duration), SUM(duration)
FROM JavaMonitorWait, JavaMonitorEnter, ThreadPark, ThreadSleep,
SocketRead, SocketWrite, FileWrite, FileRead GROUP BY T"
[application.memory-leaks-by-class]
label = "Memory Leak Candidates by Class"
table = "COLUMN 'Alloc. Time', 'Object Class', 'Object Age', 'Heap Usage'
SELECT LAST_BATCH(allocationTime), LAST_BATCH(object.type), LAST_BATCH(objectAge),
LAST_BATCH(lastKnownHeapUsage) FROM OldObjectSample GROUP BY object.type ORDER BY allocationTime"
[application.memory-leaks-by-site]
label = "Memory Leak Candidates by Site"
table = "COLUMN 'Alloc. Time', 'Application Method', 'Object Age', 'Heap Usage'
SELECT LAST_BATCH(allocationTime), LAST_BATCH(stackTrace.topApplicationFrame), LAST_BATCH(objectAge),
LAST_BATCH(lastKnownHeapUsage) FROM OldObjectSample GROUP BY stackTrace.topApplicationFrame ORDER BY allocationTime"
[application.modules]
label = "Modules"
table = "SELECT LAST(source.name) AS S FROM ModuleRequire GROUP BY source.name ORDER BY S"
[application.monitor-inflation]
label = "Monitor Inflation"
table = "SELECT stackTrace, monitorClass, COUNT(*), SUM(duration) AS S
FROM jdk.JavaMonitorInflate GROUP BY stackTrace, monitorClass ORDER BY S"
[environment.native-libraries]
label = "Native Libraries"
table = "FORMAT cell-height:2, none, none
SELECT name AS N, baseAddress, topAddress FROM NativeLibrary GROUP BY name ORDER BY N"
[jvm.native-memory-committed]
label = "Native Memory Committed"
table = "COLUMN 'Memory Type', 'First Observed', 'Average', 'Last Observed', 'Maximum'
SELECT type, FIRST(committed), AVG(committed), LAST(committed), MAX(committed) AS M
FROM NativeMemoryUsage GROUP BY type ORDER BY M DESC"
[jvm.native-memory-reserved]
label = "Native Memory Reserved"
table = "COLUMN 'Memory Type', 'First Observed', 'Average', 'Last Observed', 'Maximum'
SELECT type, FIRST(reserved), AVG(reserved), LAST(reserved), MAX(reserved) AS M
FROM NativeMemoryUsage GROUP BY type ORDER BY M DESC"
[application.native-methods]
label = "Waiting or Executing Native Methods"
table = "COLUMN 'Method', 'Samples', 'Percent'
FORMAT none, none, normalized
SELECT stackTrace.topFrame AS T, COUNT(*), COUNT(*)
FROM NativeMethodSample GROUP BY T"
[environment.network-utilization]
label = "Network Utilization"
table = "SELECT networkInterface, AVG(readRate), MAX(readRate), AVG(writeRate), MAX(writeRate)
FROM NetworkUtilization GROUP BY networkInterface"
[application.object-statistics]
label = "Objects Occupying More than 1%"
table = "COLUMN 'Class', 'Count', 'Heap Space', 'Increase'
SELECT
LAST_BATCH(objectClass), LAST_BATCH(count),
LAST_BATCH(totalSize), DIFF(totalSize)
FROM ObjectCountAfterGC, ObjectCount
GROUP BY objectClass
ORDER BY totalSize DESC"
[application.pinned-threads]
label = "Pinned Virtual Threads"
table = "COLUMN 'Method', 'Pinned Count', 'Longest Pinning', 'Total Time Pinned'
SELECT stackTrace.topApplicationFrame AS S, COUNT(*),
MAX(duration), SUM(duration) AS T FROM VirtualThreadPinned
GROUP BY S
ORDER BY T DESC"
[application.thread-count]
label ="Java Thread Statistics"
table = "SELECT * FROM JavaThreadStatistics"
[environment.recording]
label = "Recording Information"
form = "COLUMN 'Event Count', 'First Recorded Event', 'Last Recorded Event',
'Length of Recorded Events', 'Dump Reason'
SELECT COUNT(startTime), FIRST(startTime), LAST(startTime),
DIFF(startTime), LAST(jdk.Shutdown.reason)
FROM *"
[jvm.safepoints]
label = "Safepoints"
table = "COLUMN 'Start Time', 'Duration',
'State Syncronization', 'Cleanup',
'JNI Critical Threads', 'Total Threads'
SELECT B.startTime, DIFF([B|E].startTime),
S.duration, C.duration,
jniCriticalThreadCount, totalThreadCount
FROM SafepointBegin AS B, SafepointEnd AS E,
SafepointCleanup AS C, SafepointStateSynchronization AS S
GROUP BY safepointId ORDER BY B.startTime"
[jvm.longest-compilations]
label = "Longest Compilations"
table = "SELECT startTime, duration AS D, method, compileLevel, succeded
FROM Compilation ORDER BY D LIMIT 25"
[application.longest-class-loading]
label = "Longest Class Loading"
table = "COLUMN 'Time', 'Loaded Class', 'Load Time'
SELECT startTime,loadedClass, duration AS D
FROM ClassLoad ORDER BY D DESC LIMIT 25"
[environment.system-properties]
label = "System Properties at Startup"
table = "FORMAT none, cell-height:25
SELECT key AS K, value FROM InitialSystemProperty GROUP BY key ORDER by K"
[application.socket-writes-by-host]
label = "Socket Writes by Host"
table = "COLUMN 'Host', 'Writes', 'Total Written'
FORMAT cell-height:2, none, none
SELECT host, COUNT(*), SUM(bytesWritten) AS S FROM SocketWrite
GROUP BY host ORDER BY S DESC"
[application.socket-reads-by-host]
label = "Socket Reads by Host"
table = "COLUMN 'Host', 'Reads', 'Total Read'
FORMAT cell-height:2, none, none
SELECT host, COUNT(*), SUM(bytesRead) AS S FROM SocketRead
GROUP BY host ORDER BY S DESC"
[environment.system-information]
label = "System Information"
form = "COLUMN 'Total Physical Memory Size', 'OS Version', 'CPU Type',
'Number of Cores', 'Number of Hardware Threads',
'Number of Sockets', 'CPU Description'
SELECT LAST(totalSize), LAST(osVersion), LAST(cpu),
LAST(cores), LAST(hwThreads),
LAST(sockets), LAST(description)
FROM CPUInformation, PhysicalMemory, OSInformation"
[environment.system-processes]
label = "System Processes"
table = "COLUMN 'First Observed', 'Last Observed', 'PID', 'Command Line'
SELECT FIRST(startTime), LAST(startTime),
FIRST(pid), FIRST(commandLine)
FROM SystemProcess GROUP BY pid"
[jvm.tlabs]
label = "Thread Local Allocation Buffers"
form = "COLUMN 'Inside TLAB Count', 'Inside TLAB Minimum Size', 'Inside TLAB Average Size',
'Inside TLAB Maximum Size', 'Inside TLAB Total Allocation',
'Outside TLAB Count', 'OutSide TLAB Minimum Size', 'Outside TLAB Average Size',
'Outside TLAB Maximum Size', 'Outside TLAB Total Allocation'
SELECT COUNT(I.tlabSize), MIN(I.tlabSize), AVG(I.tlabSize),
MAX(I.tlabSize), SUM(I.tlabSize),
COUNT(O.allocationSize), MIN(O.allocationSize), AVG(O.allocationSize),
MAX(O.allocationSize), SUM(O.allocationSize)
FROM ObjectAllocationInNewTLAB AS I, ObjectAllocationOutsideTLAB AS O"
[application.thread-allocation]
label = "Thread Allocation Statistics"
table = "COLUMN 'Thread', 'Allocated', 'Percentage'
FORMAT none, none, normalized
SELECT thread, LAST(allocated), LAST(allocated) AS A FROM ThreadAllocationStatistics
GROUP BY thread ORDER BY A DESC"
[application.thread-cpu-load]
label = "Thread CPU Load"
table = "COLUMN 'Thread', 'System', 'User'
SELECT eventThread AS E, LAST(system), LAST(user) AS U
FROM ThreadCPULoad GROUP BY E ORDER BY U DESC"
[application.thread-start]
label = "Platform Thread Start by Method"
table = "COLUMN 'Start Time','Stack Trace', 'Thread', 'Duration'
SELECT S.startTime, S.stackTrace, eventThread, DIFF(startTime) AS D
FROM ThreadStart AS S, ThreadEnd AS E GROUP
by eventThread ORDER BY D DESC"
[jvm.vm-operations]
label = "VM Operations"
table = "COLUMN 'VM Operation', 'Average Duration', 'Longest Duration', 'Count' , 'Total Duration'
SELECT operation, AVG(duration), MAX(duration), COUNT(*), SUM(duration)
FROM jdk.ExecuteVMOperation GROUP BY operation"

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2023, 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
@ -38,6 +38,9 @@ import java.util.Collections;
import java.util.Deque;
import java.util.List;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
final class Assemble extends Command {
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -40,6 +40,9 @@ import java.util.Collections;
import java.util.Deque;
import java.util.List;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
abstract class Command {
public static final String title = "Tool for working with Flight Recorder files";
private static final Command HELP = new Help();
@ -48,6 +51,9 @@ abstract class Command {
private static List<Command> createCommands() {
List<Command> commands = new ArrayList<>();
commands.add(new Print());
// Uncomment when developing new queries for the view command
// commands.add(new Query());
commands.add(new View());
commands.add(new Configure());
commands.add(new Metadata());
commands.add(new Scrub());
@ -170,6 +176,18 @@ abstract class Command {
return false;
}
protected int acceptInt(Deque<String> options, String text) throws UserSyntaxException {
if (options.size() < 1) {
throw new UserSyntaxException("missing integer value");
}
String t = options.remove();
try {
return Integer.parseInt(t);
} catch (NumberFormatException nfe) {
throw new UserSyntaxException("could not parse integer value " + t);
}
}
protected void warnForWildcardExpansion(String option, String filter) throws UserDataException {
// Users should quote their wildcards to avoid expansion by the shell
try {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2023, 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
@ -46,6 +46,8 @@ import jdk.jfr.internal.jfc.model.JFCModelException;
import jdk.jfr.internal.jfc.model.SettingsLog;
import jdk.jfr.internal.jfc.model.UserInterface;
import jdk.jfr.internal.jfc.model.XmlInput;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
final class Configure extends Command {
private final List<String> inputFiles = new ArrayList<>();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -43,6 +43,8 @@ import java.util.List;
import jdk.jfr.internal.consumer.ChunkHeader;
import jdk.jfr.internal.consumer.FileAccess;
import jdk.jfr.internal.consumer.RecordingInput;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
final class Disassemble extends Command {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -33,6 +33,7 @@ import java.util.function.Predicate;
import jdk.jfr.EventType;
import jdk.jfr.consumer.RecordedThread;
import jdk.jfr.internal.util.UserSyntaxException;
import jdk.jfr.consumer.RecordedEvent;
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2023, 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
@ -29,6 +29,9 @@ import java.io.PrintStream;
import java.util.Deque;
import java.util.List;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
final class Help extends Command {
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2023, 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
@ -29,6 +29,9 @@ import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
/**
* Launcher class for the JDK_HOME\bin\jfr tool
*
@ -49,7 +52,7 @@ public final class Main {
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("A recording can also be started on an already running Java Virtual Machine:");
System.out.println();
System.out.println(" jcmd (to list available pids)");
System.out.println(" jcmd <pid> JFR.start");
@ -71,11 +74,13 @@ public final class Main {
System.out.println();
System.out.println(" jfr print --events " + q + "jdk.*" + q + " --stack-depth 64 recording.jfr");
System.out.println();
System.out.println(" jfr view gc recording.jfr");
System.out.println();
System.out.println(" jfr view allocation-by-site 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(" jfr metadata --categories GC,Detailed");
System.out.println(" jfr metadata");
System.out.println();
System.out.println("For more information about available commands, use 'jfr help'");
System.exit(EXIT_OK);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2023, 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
@ -44,6 +44,8 @@ import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.MetadataRepository;
import jdk.jfr.internal.consumer.JdkJfrConsumer;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
import static java.nio.charset.StandardCharsets.UTF_8;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2023, 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
@ -35,6 +35,8 @@ import java.util.List;
import java.util.function.Predicate;
import jdk.jfr.EventType;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
import static java.nio.charset.StandardCharsets.UTF_8;

View File

@ -0,0 +1,149 @@
/*
* Copyright (c) 2023, 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.util.ArrayList;
import java.util.Deque;
import java.util.List;
import jdk.jfr.consumer.EventStream;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.Output.BufferedPrinter;
import jdk.jfr.internal.util.UserSyntaxException;
import jdk.jfr.internal.query.QueryPrinter;
import jdk.jfr.internal.query.Configuration.Truncate;
import jdk.jfr.internal.query.Configuration;
final class Query extends Command {
@Override
public String getName() {
return "query";
}
@Override
public String getDescription() {
return "Display event values in a recording file (.jfr) in a tabular format";
}
@Override
public void displayOptionUsage(PrintStream p) {
// 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890
p.println(" --verbose Displays the symbolic column names");
p.println();
p.println(" --width <integer> The width of the table. Default value depends on the query");
p.println();
p.println(" <query> Query, for example \"SELECT * FROM GarbageCollection\"");
p.println(" See below for grammar.");
p.println();
p.println(" <file> Location of the recording file (.jfr)");
p.println();
p.println(QueryPrinter.getGrammarText());
p.println();
p.println("Example usage:");
p.println();
p.println(" $ jfr query \"SHOW EVENTS\" recording.jfr");
p.println();
p.println(" $ jfr query \"SHOW FIELDS ObjectAllocationSample\" recording.jfr");
p.println();
p.println(" $ jfr query --verbose \"SELECT * FROM ObjectAllocationSample\" recording.jfr");
p.println();
p.println(" $ jfr query --width 160 \"SELECT pid, path FROM SystemProcess\" recording.jfr");
p.println();
p.println(" $ jfr query \"SELECT stackTrace.topFrame AS T, SUM(weight)");
p.println(" FROM ObjectAllocationSample GROUP BY T\" recording.jfr");
p.println();
p.println("$ jfr JFR.query \"COLUMN 'Method', 'Percentage'");
p.println(" FORMAT default, normalized;width:10");
p.println(" SELECT stackTrace.topFrame AS T, COUNT(*) AS C");
p.println(" GROUP BY T");
p.println(" FROM ExecutionSample ORDER BY C DESC\" recording.jfr");
p.println();
p.println("$ jcmd <pid> JFR.query \"COLUMN 'Start', 'GC ID', 'Heap Before GC',");
p.println(" 'Heap After GC', 'Longest Pause'");
p.println(" SELECT G.startTime, G.gcId, B.heapUsed,");
p.println(" A.heapUsed, longestPause");
p.println(" FROM GarbageCollection AS G,");
p.println(" GCHeapSummary AS B,");
p.println(" GCHeapSummary AS A");
p.println(" WHERE B.when = 'Before GC' AND A.when = 'After GC'");
p.println(" GROUP BY gcId");
p.println(" ORDER BY G.startTime\" recording.jfr");
}
@Override
public List<String> getOptionSyntax() {
List<String> list = new ArrayList<>();
list.add("[--verbose] [--width <integer>] <query> <file>");
return list;
}
@Override
public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
Path file = getJFRInputFile(options);
int optionCount = options.size();
var configuration = new Configuration();
BufferedPrinter printer = new BufferedPrinter(System.out);
configuration.output = printer;
while (optionCount > 0) {
if (acceptSwitch(options, "--verbose")) {
configuration.verbose = true;
configuration.verboseHeaders = true;
}
if (acceptOption(options, "--truncate")) {
String mode = options.remove();
try {
configuration.truncate = Truncate.valueOf(mode.toUpperCase());
} catch (IllegalArgumentException iae) {
throw new UserSyntaxException("truncate must be 'beginning' or 'end'");
}
}
if (acceptOption(options, "--cell-height")) {
configuration.cellHeight = acceptInt(options, "cell-height");
}
if (acceptOption(options, "--width")) {
configuration.width = acceptInt(options, "width");
}
if (optionCount == 1) {
String query = options.pop();
try (EventStream stream = EventStream.openFile(file)) {
QueryPrinter qp = new QueryPrinter(configuration, stream);
qp.execute(query);
printer.flush();
} catch (IOException ioe) {
couldNotReadError(file, ioe);
}
return;
}
if (optionCount == options.size()) {
throw new UserSyntaxException("unknown option " + options.peek());
}
optionCount = options.size();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -37,6 +37,8 @@ import java.util.function.Predicate;
import jdk.jfr.EventType;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordingFile;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
final class Scrub extends Command {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -43,6 +43,8 @@ import jdk.jfr.internal.Type;
import jdk.jfr.internal.consumer.ChunkHeader;
import jdk.jfr.internal.consumer.FileAccess;
import jdk.jfr.internal.consumer.RecordingInput;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
final class Summary extends Command {
private final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withLocale(Locale.UK).withZone(ZoneOffset.UTC);

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) 2023, 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.util.ArrayList;
import java.util.Deque;
import java.util.List;
import jdk.jfr.consumer.EventStream;
import jdk.jfr.internal.util.Columnizer;
import jdk.jfr.internal.query.ViewPrinter;
import jdk.jfr.internal.query.Configuration;
import jdk.jfr.internal.query.Configuration.Truncate;
import jdk.jfr.internal.util.UserDataException;
import jdk.jfr.internal.util.UserSyntaxException;
import jdk.jfr.internal.util.Output.BufferedPrinter;
public final class View extends Command {
@Override
public String getName() {
return "view";
}
@Override
protected String getTitle() {
return "Display event values in a recording file (.jfr) in predefined views";
}
@Override
public String getDescription() {
return "Display events in a tabular format. See 'jfr help view' for details.";
}
@Override
public void displayOptionUsage(PrintStream stream) {
stream.println(" --verbose Displays the query that makes up the view");
stream.println("");
stream.println(" --width <integer> The width of the view in characters. Default value depends on the view");
stream.println("");
stream.println(" --truncate <mode> How to truncate content that exceeds space in a table cell.");
stream.println(" Mode can be 'beginning' or 'end'. Default value is 'end'");
stream.println("");
stream.println(" --cell-height <integer> Maximum number of rows in a table cell. Default value depends on the view");
stream.println("");
stream.println(" <view> Name of the view or event type to display. See list below for");
stream.println(" available views");
stream.println("");
stream.println(" <file> Location of the recording file (.jfr)");
stream.println();
for (String line : ViewPrinter.getAvailableViews()) {
stream.println(line);
}
stream.println(" The <view> parameter can be an event type name. Use the 'jfr view types <file>'");
stream.println(" to see a list. To display all views, use 'jfr view all-views <file>'. To display");
stream.println(" all events, use 'jfr view all-events <file>'.");
stream.println();
stream.println("Example usage:");
stream.println();
stream.println(" jfr view gc recording.jfr");
stream.println();
stream.println(" jfr view --width 160 hot-methods recording.jfr");
stream.println();
stream.println(" jfr view --verbose allocation-by-class recording.jfr");
stream.println();
stream.println(" jfr view contention-by-site recording.jfr");
stream.println();
stream.println(" jfr view jdk.GarbageCollection recording.jfr");
stream.println();
stream.println(" jfr view --cell-height 10 ThreadStart recording.jfr");
stream.println();
stream.println(" jfr view --truncate beginning SystemProcess recording.jfr");
stream.println();
}
@Override
public List<String> getOptionSyntax() {
List<String> list = new ArrayList<>();
list.add("[--verbose]");
list.add("[--width <integer>");
list.add("[--truncate <mode>]");
list.add("[--cell-height <integer>]");
list.add("<view>");
list.add("<file>");
return list;
}
@Override
public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
Path file = getJFRInputFile(options);
int optionCount = options.size();
if (optionCount < 1) {
throw new UserSyntaxException("must specify a view or event type");
}
Configuration configuration = new Configuration();
BufferedPrinter printer = new BufferedPrinter(System.out);
configuration.output = printer;
while (true) {
if (acceptSwitch(options, "--verbose")) {
configuration.verbose = true;
}
if (acceptOption(options, "--truncate")) {
String mode = options.remove();
try {
configuration.truncate = Truncate.valueOf(mode.toUpperCase());
} catch (IllegalArgumentException iae) {
throw new UserSyntaxException("truncate must be 'beginning' or 'end'");
}
}
if (acceptOption(options, "--cell-height")) {
configuration.cellHeight = acceptInt(options, "cell-height");
}
if (acceptOption(options, "--width")) {
configuration.width = acceptInt(options, "width");
}
if (options.size() == 1) {
String view = options.pop();
try (EventStream stream = EventStream.openFile(file)) {
ViewPrinter vp = new ViewPrinter(configuration, stream);
vp.execute(view);
printer.flush();
return;
} catch (IOException ioe) {
couldNotReadError(file, ioe);
}
}
System.out.println("count:" + optionCount);
System.out.println("size:" + options.size());
if (optionCount == options.size()) {
String peek = options.peek();
if (peek == null) {
throw new UserSyntaxException("must specify option <view>");
}
throw new UserSyntaxException("unknown option " + peek);
}
optionCount = options.size();
}
}
}

View File

@ -0,0 +1,90 @@
/*
* Copyright (c) 2023, 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.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Class that creates a column-sorted list.
* <p>
* For example, the list: "Bison", "Dog", "Frog", Goldfish", "Kangaroo", "Ant",
* "Jaguar", "Cat", "Elephant", "Ibex" becomes:
* <pre>
* Ant Elephant Jaguar
* Bison Frog Kangaroo
* Cat Goldfish
* Dog Ibex"
* </pre>
*/
public final class Columnizer {
private static final class Column {
int maxWidth;
List<String> entries = new ArrayList<>();
public void add(String text) {
entries.add(text);
maxWidth = Math.max(maxWidth, text.length());
}
}
private final List<Column> columns = new ArrayList<>();
public Columnizer(List<String> texts, int columnCount) {
List<String> list = new ArrayList<>(texts);
Collections.sort(list);
int columnHeight = (list.size() + columnCount - 1) / columnCount;
int index = 0;
Column column = null;
for (String text : list) {
if (index % columnHeight == 0) {
column = new Column();
columns.add(column);
}
column.add(text);
index++;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
int index = 0;
while (true) {
for (Column column : columns) {
if (index == column.entries.size()) {
return sb.toString();
}
if (index != 0 && columns.getFirst() == column) {
sb.append(System.lineSeparator());
}
String text = column.entries.get(index);
sb.append(" ");
sb.append(text);
sb.append(" ".repeat(column.maxWidth - text.length()));
}
index++;
}
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2023, 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.util;
public final class Matcher {
/**
* Returns true if text matches pattern of characters, '*' and '?'
*/
public static boolean match(String text, String pattern) {
if (pattern.length() == 0) {
// empty filter string matches if string is empty
return text.length() == 0;
}
if (pattern.charAt(0) == '*') { // recursive check
pattern = pattern.substring(1);
for (int n = 0; n <= text.length(); n++) {
if (match(text.substring(n), pattern))
return true;
}
} else if (text.length() == 0) {
// empty string and non-empty filter does not match
return false;
} else if (pattern.charAt(0) == '?') {
// eat any char and move on
return match(text.substring(1), pattern.substring(1));
} else if (pattern.charAt(0) == text.charAt(0)) {
// eat chars and move on
return match(text.substring(1), pattern.substring(1));
}
return false;
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) 2023, 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.util;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
public interface Output {
public void println();
public void print(String s);
public void print(String s, Object... args);
default public void println(String s, Object... args) {
print(s, args);
println();
}
public void print(char c);
public static final class LinePrinter implements Output {
private final StringBuilder currentLine = new StringBuilder(80);
private final List<String> lines = new ArrayList<>();
@Override
public void println() {
lines.add(currentLine.toString());
currentLine.setLength(0);
}
@Override
public void print(String s) {
currentLine.append(s);
}
@Override
public void print(String s, Object... args) {
currentLine.append(args.length > 0 ? String.format(s, args) : s);
}
@Override
public void print(char c) {
currentLine.append(c);
}
public List<String> getLines() {
return lines;
}
}
public static final class BufferedPrinter implements Output {
private final StringBuilder buffer = new StringBuilder(100_000);
private final PrintStream out;
public BufferedPrinter(PrintStream out) {
this.out = out;
}
@Override
public void println() {
buffer.append(System.lineSeparator());
flushCheck();
}
@Override
public void print(String s) {
buffer.append(s);
flushCheck();
}
@Override
public void print(String s, Object... args) {
if (args.length > 0) {
buffer.append(String.format(s, args));
} else {
buffer.append(s);
}
flushCheck();
}
@Override
public void print(char c) {
buffer.append(c);
flush();
}
public void flush() {
out.print(buffer.toString());
buffer.setLength(0);
}
private void flushCheck() {
if (buffer.length() > 99_000) {
flush();
}
}
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) 2023, 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.util;
import java.util.List;
public final class SpellChecker {
public static String check(String name, List<String> alternatives) {
for (String expected : alternatives) {
String s = name.toLowerCase();
int lengthDifference = expected.length() - s.length();
boolean spellingError = false;
if (lengthDifference == 0) {
if (expected.equals(s)) {
spellingError = true; // incorrect case, or we wouldn't be here
} else {
if (s.length() < 6) {
spellingError = diff(expected, s) < 2; // one incorrect letter
} else {
spellingError = diff(expected, s) < 3; // two incorrect letter
}
}
}
if (lengthDifference == 1) {
spellingError = inSequence(expected, s); // missing letter
}
if (lengthDifference == -1) {
spellingError = inSequence(s, expected); // additional letter
}
if (spellingError) {
return expected;
}
}
return null;
}
private static int diff(String a, String b) {
int count = a.length();
for (int i = 0; i < a.length(); i++) {
if (a.charAt(i) == b.charAt(i)) {
count--;
}
}
return count;
}
private static boolean inSequence(String longer, String shorter) {
int l = 0;
int s = 0;
while (l < longer.length() && s < shorter.length()) {
if (longer.charAt(l) == shorter.charAt(s)) {
s++;
}
l++;
}
return shorter.length() == s; // if 0, all letters in longer found in shorter
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2023, 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.util;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
public final class StopWatch {
private record Timing(String name, Instant start) {
}
private final List<Timing> timings = new ArrayList<>();
public void beginQueryValidation() {
beginTask("query-validation");
}
public void beginAggregation() {
beginTask("aggregation");
}
public void beginFormatting() {
beginTask("formatting");
}
public void beginTask(String name) {
timings.add(new Timing(name, Instant.now()));
}
public void finish() {
beginTask("end");
}
@Override
public String toString() {
StringJoiner sb = new StringJoiner(", ");
for (int i = 0; i < timings.size() - 1; i++) {
Timing current = timings.get(i);
Timing next = timings.get(i + 1);
Duration d = Duration.between(current.start(), next.start());
sb.add(current.name() + "=" + ValueFormatter.formatDuration(d));
}
return sb.toString();
}
}

View File

@ -0,0 +1,236 @@
/*
* Copyright (c) 2023, 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.util;
import java.text.ParseException;
public final class Tokenizer implements AutoCloseable {
private final String text;
private final char[] separators;
private int index;
/**
* Constructs a Tokenizer.
*
* @param text text to tokenize
* @param separators separator, for example ',' or ';'
*/
public Tokenizer(String text, char... separators) {
this.text = text;
this.separators = separators;
}
/**
* If the next token matches a string, it is consumed and {@code true} returned,
* {@code false} otherwise.
*/
public boolean accept(String match) {
skipWhitespace();
int t = 0;
while (index + t < text.length() && t < match.length()) {
char c = Character.toLowerCase(text.charAt(index + t));
char d = Character.toLowerCase(match.charAt(t));
if (d != c) {
return false;
}
t++;
if (isSeparator(c)) {
break;
}
}
if (t == match.length()) {
index += match.length();
return true;
}
return false;
}
/**
* Similar to accept(String), but requires several tokens to match.
*/
public boolean accept(String... matches) {
int position = index;
for (String s : matches) {
if (!accept(s)) {
index = position;
return false;
}
}
return true;
}
/**
* Similar to accept(String...), but sufficient if one token matches.
*
* @param matches
* @return
*/
public boolean acceptAny(String... matches) {
for (String match : matches) {
if (accept(match)) {
return true;
}
}
return false;
}
/**
* Return {@code true} if there are more tokens.
*/
public boolean hasNext() {
int k = index;
while (k < text.length()) {
char c = text.charAt(k);
if (!Character.isWhitespace(c)) {
return true;
}
k++;
}
return false;
}
/**
* Throws exception if the next token doesn't match.
*/
public void expect(String expected) throws ParseException {
if (!accept(expected)) {
throw new ParseException("Expected " + expected, index);
}
}
/**
* Return the next character without consuming it, or throw exception if
* {@code EOF} is reached.
*/
public char peekChar() throws ParseException {
skipWhitespace();
if (index < text.length()) {
return text.charAt(index);
}
throw new ParseException("Unexpected EOF reached", index);
}
/**
* Return the next token, or throw exception if {@code EOF} is reached.
*/
public String next() throws ParseException {
skipWhitespace();
StringBuilder sb = new StringBuilder();
while (index < text.length()) {
char c = text.charAt(index);
if (isQuoteCharacter(c)) {
int p = findNext(c);
String t = text.substring(index + 1, p);
sb.append(t);
index = p;
} else {
if (isSeparator(c)) {
if (sb.isEmpty()) {
index++;
return String.valueOf(c); // Inte helt optimalt
} else {
return sb.toString();
}
}
if (Character.isWhitespace(c)) {
return sb.toString();
}
sb.append(c);
}
index++;
}
if (sb.isEmpty()) {
throw new ParseException("Unexpected EOF reached", index);
}
return sb.toString();
}
/**
* Skips all character until {@code '\n'}
*/
public void skipLine() {
while (index < text.length()) {
char c = text.charAt(index++);
if (c == '\n') {
return;
}
}
}
/**
* Returns the current position in the text.
*/
public int getPosition() {
return index;
}
private void skipWhitespace() {
while (index < text.length()) {
char c = text.charAt(index);
if (!Character.isWhitespace(c)) {
return;
}
index++;
}
}
private boolean isSeparator(char c) {
for (char separator : separators) {
if (c == separator) {
return true;
}
}
return false;
}
private int findNext(char c) throws ParseException {
for (int p = index + 1; p < text.length(); p++) {
if (c == text.charAt(p)) {
return p;
}
}
throw new ParseException("Could not find match " + c, index);
}
private boolean isQuoteCharacter(char c) {
return c == '\'' || c == '"';
}
/**
* Closes the Tokenizer.
* <p>
* Requires that all the tokens have been consumed.
*
* @throws ParseException if there are tokens left
*/
@Override
public void close() throws ParseException {
skipWhitespace();
if (index != text.length()) {
throw new ParseException("Unexpected token '" + next() + "' found", getPosition());
}
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2018, 2023, 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.util;
/**
* Exception that is thrown if there is something wrong with the input, for instance
* a file that can't be read or a numerical value that is out of range.
* <p>
* When this exception is thrown, a user will typically not want to see the
* command line syntax, but instead information about what was wrong with the
* input.
*/
public 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);
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2018, 2023, 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.util;
/**
* Exception that is thrown if options don't follow the syntax of the command.
*/
public 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 message
*/
public UserSyntaxException(String errorMessage) {
super(errorMessage);
}
}

View File

@ -0,0 +1,275 @@
/*
* Copyright (c) 2023, 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.util;
import java.text.NumberFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import jdk.jfr.consumer.RecordedClass;
import jdk.jfr.consumer.RecordedMethod;
public final class ValueFormatter {
private static final NumberFormat NUMBER_FORMAT = NumberFormat.getNumberInstance();
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss");
private static final Duration MICRO_SECOND = Duration.ofNanos(1_000);
private static final Duration SECOND = Duration.ofSeconds(1);
private static final Duration MINUTE = Duration.ofMinutes(1);
private static final Duration HOUR = Duration.ofHours(1);
private static final Duration DAY = Duration.ofDays(1);
private static final int NANO_SIGNIFICANT_FIGURES = 9;
private static final int MILL_SIGNIFICANT_FIGURES = 3;
private static final int DISPLAY_NANO_DIGIT = 3;
private static final int BASE = 10;
public static String formatNumber(Number n) {
return NUMBER_FORMAT.format(n);
}
public static String formatDuration(Duration d) {
Duration roundedDuration = roundDuration(d);
if (roundedDuration.equals(Duration.ZERO)) {
return "0 s";
} else if (roundedDuration.isNegative()) {
return "-" + formatPositiveDuration(roundedDuration.abs());
} else {
return formatPositiveDuration(roundedDuration);
}
}
private static String formatPositiveDuration(Duration d){
if (d.compareTo(MICRO_SECOND) < 0) {
// 0.000001 ms - 0.000999 ms
double outputMs = (double) d.toNanosPart() / 1_000_000;
return String.format("%.6f ms", outputMs);
} else if (d.compareTo(SECOND) < 0) {
// 0.001 ms - 999 ms
int valueLength = countLength(d.toNanosPart());
int outputDigit = NANO_SIGNIFICANT_FIGURES - valueLength;
double outputMs = (double) d.toNanosPart() / 1_000_000;
return String.format("%." + outputDigit + "f ms", outputMs);
} else if (d.compareTo(MINUTE) < 0) {
// 1.00 s - 59.9 s
int valueLength = countLength(d.toSecondsPart());
int outputDigit = MILL_SIGNIFICANT_FIGURES - valueLength;
double outputSecond = d.toSecondsPart() + (double) d.toMillisPart() / 1_000;
return String.format("%." + outputDigit + "f s", outputSecond);
} else if (d.compareTo(HOUR) < 0) {
// 1 m 0 s - 59 m 59 s
return String.format("%d m %d s", d.toMinutesPart(), d.toSecondsPart());
} else if (d.compareTo(DAY) < 0) {
// 1 h 0 m - 23 h 59 m
return String.format("%d h %d m", d.toHoursPart(), d.toMinutesPart());
} else {
// 1 d 0 h -
return String.format("%d d %d h", d.toDaysPart(), d.toHoursPart());
}
}
private static int countLength(long value){
return (int) Math.log10(value) + 1;
}
private static Duration roundDuration(Duration d) {
if (d.equals(Duration.ZERO)) {
return d;
} else if(d.isNegative()) {
Duration roundedPositiveDuration = roundPositiveDuration(d.abs());
return roundedPositiveDuration.negated();
} else {
return roundPositiveDuration(d);
}
}
private static Duration roundPositiveDuration(Duration d){
if (d.compareTo(MICRO_SECOND) < 0) {
// No round
return d;
} else if (d.compareTo(SECOND) < 0) {
// Round significant figures to three digits
int valueLength = countLength(d.toNanosPart());
int roundValue = (int) Math.pow(BASE, valueLength - DISPLAY_NANO_DIGIT);
long roundedNanos = Math.round((double) d.toNanosPart() / roundValue) * roundValue;
return d.truncatedTo(ChronoUnit.SECONDS).plusNanos(roundedNanos);
} else if (d.compareTo(MINUTE) < 0) {
// Round significant figures to three digits
int valueLength = countLength(d.toSecondsPart());
int roundValue = (int) Math.pow(BASE, valueLength);
long roundedMills = Math.round((double) d.toMillisPart() / roundValue) * roundValue;
return d.truncatedTo(ChronoUnit.SECONDS).plusMillis(roundedMills);
} else if (d.compareTo(HOUR) < 0) {
// Round for more than 500 ms or less
return d.plusMillis(SECOND.dividedBy(2).toMillisPart()).truncatedTo(ChronoUnit.SECONDS);
} else if (d.compareTo(DAY) < 0) {
// Round for more than 30 seconds or less
return d.plusSeconds(MINUTE.dividedBy(2).toSecondsPart()).truncatedTo(ChronoUnit.MINUTES);
} else {
// Round for more than 30 minutes or less
return d.plusMinutes(HOUR.dividedBy(2).toMinutesPart()).truncatedTo(ChronoUnit.HOURS);
}
}
public static String formatClass(RecordedClass clazz) {
String name = clazz.getName();
if (name.startsWith("[")) {
return decodeDescriptors(name, "").getFirst();
}
return name;
}
// Tjis method can't handle Long.MIN_VALUE because absolute value is negative
private static String formatDataAmount(String formatter, long amount) {
int exp = (int) (Math.log(Math.abs(amount)) / Math.log(1024));
char unitPrefix = "kMGTPE".charAt(exp - 1);
return String.format(formatter, amount / Math.pow(1024, exp), unitPrefix);
}
public static String formatBytesCompact(long bytes) {
if (bytes < 1024) {
return String.valueOf(bytes);
}
return formatDataAmount("%.1f%cB", bytes);
}
public static String formatBits(long bits) {
if (bits == 1 || bits == -1) {
return bits + " bit";
}
if (bits < 1024 && bits > -1024) {
return bits + " bits";
}
return formatDataAmount("%.1f %cbit", bits);
}
public static String formatBytes(long bytes) {
if (bytes == 1 || bytes == -1) {
return bytes + " byte";
}
if (bytes < 1024 && bytes > -1024) {
return bytes + " bytes";
}
return formatDataAmount("%.1f %cB", bytes);
}
public static String formatBytesPerSecond(long bytes) {
if (bytes < 1024 && bytes > -1024) {
return bytes + " byte/s";
}
return formatDataAmount("%.1f %cB/s", bytes);
}
public static String formatBitsPerSecond(long bits) {
if (bits < 1024 && bits > -1024) {
return bits + " bps";
}
return formatDataAmount("%.1f %cbps", bits);
}
public static String formatMethod(RecordedMethod m, boolean compact) {
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(")"));
List<String> parameters = decodeDescriptors(parameter, "");
if (!compact) {
for (String qualifiedName :parameters) {
String typeName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
sj.add(typeName);
}
sb.append(sj.toString());
} else {
if (!parameters.isEmpty()) {
sb.append("...");
}
}
sb.append(")");
return sb.toString();
}
private static List<String> decodeDescriptors(String descriptor, String arraySize) {
List<String> descriptors = new ArrayList<>();
for (int index = 0; index < descriptor.length(); index++) {
String arrayBrackets = "";
while (descriptor.charAt(index) == '[') {
arrayBrackets = arrayBrackets + "[" + arraySize + "]";
arraySize = "";
index++;
}
char c = descriptor.charAt(index);
String type;
switch (c) {
case 'L':
int endIndex = descriptor.indexOf(';', index);
type = descriptor.substring(index + 1, endIndex);
index = endIndex;
break;
case 'I':
type = "int";
break;
case 'J':
type = "long";
break;
case 'Z':
type = "boolean";
break;
case 'D':
type = "double";
break;
case 'F':
type = "float";
break;
case 'S':
type = "short";
break;
case 'C':
type = "char";
break;
case 'B':
type = "byte";
break;
default:
type = "<unknown-descriptor-type>";
}
descriptors.add(type + arrayBrackets);
}
return descriptors;
}
public static String formatTimestamp(Instant instant) {
return LocalTime.ofInstant(instant, ZoneId.systemDefault()).format(DATE_FORMAT);
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jfr.jcmd;
import java.util.concurrent.CountDownLatch;
import jdk.jfr.Recording;
import jdk.jfr.consumer.RecordingStream;
import jdk.test.lib.process.OutputAnalyzer;
/**
* @test
* @summary The test verifies JFR.view command
* @key jfr
* @requires vm.hasJFR
* @requires (vm.gc == "G1" | vm.gc == null)
* & vm.opt.ExplicitGCInvokesConcurrent != false
* @library /test/lib /test/jdk
* @run main/othervm -XX:-ExplicitGCInvokesConcurrent -XX:-DisableExplicitGC
* -XX:+UseG1GC jdk.jfr.jcmd.TestJcmdView
*/
public class TestJcmdView {
public static void main(String... args) throws Throwable {
CountDownLatch jvmInformation = new CountDownLatch(1);
CountDownLatch systemGC = new CountDownLatch(1);
CountDownLatch gcHeapSummary = new CountDownLatch(1);
CountDownLatch oldCollection = new CountDownLatch(1);
CountDownLatch garbageCollection = new CountDownLatch(1);
try (RecordingStream rs = new RecordingStream()) {
// Make sure chunks are not released after consumption
rs.setMaxSize(Long.MAX_VALUE);
rs.enable("jdk.JVMInformation").with("period", "beginChunk");
rs.enable("jdk.SystemGC");
rs.enable("jdk.GCHeapSummary");
rs.enable("jdk.GarbageCollection");
rs.enable("jdk.OldGarbageCollection");
rs.enable("jdk.YoungGarbageCollection");
rs.onEvent("jdk.JVMInformation", e -> {
jvmInformation.countDown();
System.out.println(e);
});
rs.onEvent("jdk.SystemGC", e -> {
systemGC.countDown();
System.out.println(e);
});
rs.onEvent("jdk.GCHeapSummary", e -> {
gcHeapSummary.countDown();
System.out.println(e);
});
rs.onEvent("jdk.OldGarbageCollection", e -> {
oldCollection.countDown();
System.out.println(e);
});
rs.onEvent("jdk.GarbageCollection", e-> {
garbageCollection.countDown();
System.out.println(e);
});
rs.startAsync();
// Emit some GC events
System.gc();
System.gc();
System.gc();
// Wait for them being in the repository
jvmInformation.await();
systemGC.await();
gcHeapSummary.await();
oldCollection.countDown();
// Test events that are in the current chunk
testEventType();
testFormView();
testTableView();
rs.disable("jdk.JVMInformation");
// Force chunk rotation
rotate();
// Test events that are NOT in current chunk
testEventType();
testFormView();
testTableView();
}
}
private static void rotate() {
try (Recording r = new Recording()) {
r.start();
}
}
private static void testFormView() throws Throwable {
OutputAnalyzer output = JcmdHelper.jcmd("JFR.view", "jvm-information");
// Verify title
output.shouldContain("JVM Information");
// Verify field label
output.shouldContain("VM Arguments:");
// Verify field value
long pid = ProcessHandle.current().pid();
String lastThreeDigits = String.valueOf(pid % 1000);
output.shouldContain(lastThreeDigits);
}
private static void testTableView() throws Throwable {
OutputAnalyzer output = JcmdHelper.jcmd("JFR.view", "verbose=true", "gc");
// Verify heading
output.shouldContain("Longest Pause");
// Verify verbose heading
output.shouldContain("(longestPause)");
// Verify row contents
output.shouldContain("Old Garbage Collection");
// Verify verbose query
output.shouldContain("SELECT");
}
private static void testEventType() throws Throwable {
OutputAnalyzer output = JcmdHelper.jcmd(
"JFR.view", "verbose=true", "width=300", "cell-height=100", "SystemGC");
// Verify title
output.shouldContain("System GC");
// Verify headings
output.shouldContain("Invoked Concurrent");
// Verify verbose headings
output.shouldContain("invokedConcurrent");
// Verify thread value
output.shouldContain(Thread.currentThread().getName());
// Verify stack frame
output.shouldContain("TestJcmdView.main");
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jfr.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 view
* @key jfr
* @requires vm.hasJFR
* @requires (vm.gc == "G1" | vm.gc == null)
* & vm.opt.ExplicitGCInvokesConcurrent != false
* @library /test/lib /test/jdk
* @run main/othervm -XX:-ExplicitGCInvokesConcurrent -XX:-DisableExplicitGC
* -XX:+UseG1GC jdk.jfr.jcmd.TestJcmdView
*/
public class TestView {
public static void main(String... args) throws Throwable {
testIncorrectUsage();
String recordingFile = ExecuteHelper.createProfilingRecording().toAbsolutePath().toString();
testEventType(recordingFile);
testFormView(recordingFile);
testTableView(recordingFile);
}
private static void testIncorrectUsage() throws Throwable {
OutputAnalyzer output = ExecuteHelper.jfr("view");
output.shouldContain("missing file");
output = ExecuteHelper.jfr("view", "missing.jfr");
output.shouldContain("could not open file ");
Path file = Utils.createTempFile("faked-file", ".jfr");
FileWriter fw = new FileWriter(file.toFile());
fw.write('d');
fw.close();
output = ExecuteHelper.jfr("view", "--wrongOption", file.toAbsolutePath().toString());
output.shouldContain("unknown option");
Files.delete(file);
}
private static void testFormView(String recording) throws Throwable {
OutputAnalyzer output = ExecuteHelper.jfr("view", "jvm-information", recording);
// Verify title
output.shouldContain("JVM Information");
// Verify field label
output.shouldContain("VM Arguments:");
// Verify field value
long pid = ProcessHandle.current().pid();
String lastThreeDigits = String.valueOf(pid % 1000);
output.shouldContain(lastThreeDigits);
}
private static void testTableView(String recording) throws Throwable {
OutputAnalyzer output = ExecuteHelper.jfr("view", "--verbose", "gc", recording);
// Verify heading
output.shouldContain("Longest Pause");
// Verify verbose heading
output.shouldContain("(longestPause)");
// Verify row contents
output.shouldContain("Old Garbage Collection");
// Verify verbose query
output.shouldContain("SELECT");
}
private static void testEventType(String recording) throws Throwable {
OutputAnalyzer output = ExecuteHelper.jfr(
"view", "--verbose", "--width", "300", "--cell-height", "100", "SystemGC", recording);
// Verify title
output.shouldContain("System GC");
// Verify headings
output.shouldContain("Invoked Concurrent");
// Verify verbose headings
output.shouldContain("invokedConcurrent");
// Verify thread value
output.shouldContain(Thread.currentThread().getName());
// Verify stack frame
output.shouldContain("ExecuteHelper.createProfilingRecording");
}
}