8306703: JFR: Summary views
Reviewed-by: mgronlun
This commit is contained in:
parent
534de6d8ae
commit
98acce13d5
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -91,16 +91,19 @@ abstract class AbstractDCmd {
|
||||
DCmdException e = new DCmdException(iae.getMessage());
|
||||
e.addSuppressed(iae);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
@ -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
|
||||
) { }
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
171
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdQuery.java
Normal file
171
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdQuery.java
Normal 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), };
|
||||
}
|
||||
}
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
168
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdView.java
Normal file
168
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdView.java
Normal 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)
|
||||
};
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
109
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Aggregator.java
Normal file
109
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Aggregator.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
147
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Field.java
Normal file
147
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Field.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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';
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
662
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Function.java
Normal file
662
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Function.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
214
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Histogram.java
Normal file
214
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Histogram.java
Normal 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;
|
||||
}
|
||||
}
|
167
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Query.java
Normal file
167
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Query.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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>"'.""";
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
118
src/jdk.jfr/share/classes/jdk/jfr/internal/query/QueryRun.java
Normal file
118
src/jdk.jfr/share/classes/jdk/jfr/internal/query/QueryRun.java
Normal 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;
|
||||
}
|
||||
}
|
58
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Row.java
Normal file
58
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Row.java
Normal 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();
|
||||
}
|
||||
}
|
72
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Table.java
Normal file
72
src/jdk.jfr/share/classes/jdk/jfr/internal/query/Table.java
Normal 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);
|
||||
}
|
||||
}
|
149
src/jdk.jfr/share/classes/jdk/jfr/internal/query/TableCell.java
Normal file
149
src/jdk.jfr/share/classes/jdk/jfr/internal/query/TableCell.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
129
src/jdk.jfr/share/classes/jdk/jfr/internal/query/ViewFile.java
Normal file
129
src/jdk.jfr/share/classes/jdk/jfr/internal/query/ViewFile.java
Normal 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<>());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
576
src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini
Normal file
576
src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini
Normal 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"
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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<>();
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
149
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Query.java
Normal file
149
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Query.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
||||
|
@ -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);
|
||||
|
164
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/View.java
Normal file
164
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/View.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
55
src/jdk.jfr/share/classes/jdk/jfr/internal/util/Matcher.java
Normal file
55
src/jdk.jfr/share/classes/jdk/jfr/internal/util/Matcher.java
Normal 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;
|
||||
}
|
||||
}
|
123
src/jdk.jfr/share/classes/jdk/jfr/internal/util/Output.java
Normal file
123
src/jdk.jfr/share/classes/jdk/jfr/internal/util/Output.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
236
src/jdk.jfr/share/classes/jdk/jfr/internal/util/Tokenizer.java
Normal file
236
src/jdk.jfr/share/classes/jdk/jfr/internal/util/Tokenizer.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
148
test/jdk/jdk/jfr/jcmd/TestJcmdView.java
Normal file
148
test/jdk/jdk/jfr/jcmd/TestJcmdView.java
Normal 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");
|
||||
}
|
||||
}
|
106
test/jdk/jdk/jfr/tool/TestView.java
Normal file
106
test/jdk/jdk/jfr/tool/TestView.java
Normal 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");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user