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.
|
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
#
|
#
|
||||||
# This code is free software; you can redistribute it and/or modify it
|
# This code is free software; you can redistribute it and/or modify it
|
||||||
@ -24,5 +24,5 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
DISABLED_WARNINGS_java += exports
|
DISABLED_WARNINGS_java += exports
|
||||||
COPY := .xsd .xml .dtd
|
COPY := .xsd .xml .dtd .ini
|
||||||
JAVAC_FLAGS := -XDstringConcat=inline
|
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<JfrDumpFlightRecordingDCmd>(full_export, true, false));
|
||||||
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrStartFlightRecordingDCmd>(full_export, true, false));
|
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrStartFlightRecordingDCmd>(full_export, true, false));
|
||||||
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrStopFlightRecordingDCmd>(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));
|
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JfrConfigureFlightRecorderDCmd>(full_export, true, false));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -318,7 +321,7 @@ static DCmdArgumentInfo* create_info(oop argument, TRAPS) {
|
|||||||
read_string_field(argument, "type", THREAD),
|
read_string_field(argument, "type", THREAD),
|
||||||
read_string_field(argument, "defaultValue", THREAD),
|
read_string_field(argument, "defaultValue", THREAD),
|
||||||
read_boolean_field(argument, "mandatory", THREAD),
|
read_boolean_field(argument, "mandatory", THREAD),
|
||||||
true, // a DcmdFramework "option"
|
read_boolean_field(argument, "option", THREAD),
|
||||||
read_boolean_field(argument, "allowMultiple", 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 {
|
class JfrConfigureFlightRecorderDCmd : public DCmdWithParser {
|
||||||
friend class JfrOptionSet;
|
friend class JfrOptionSet;
|
||||||
protected:
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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 emitAll = WhiteBox.getWriteAllObjectSamples();
|
||||||
boolean skipBFS = WhiteBox.getSkipBFS();
|
boolean skipBFS = WhiteBox.getSkipBFS();
|
||||||
JVM.getJVM().emitOldObjectSamples(ticks, emitAll, skipBFS);
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -420,7 +420,7 @@ public final class PlatformRecorder {
|
|||||||
return runningRecordings;
|
return runningRecordings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<RepositoryChunk> makeChunkList(Instant startTime, Instant endTime) {
|
public List<RepositoryChunk> makeChunkList(Instant startTime, Instant endTime) {
|
||||||
Set<RepositoryChunk> chunkSet = new HashSet<>();
|
Set<RepositoryChunk> chunkSet = new HashSet<>();
|
||||||
for (PlatformRecording r : getRecordings()) {
|
for (PlatformRecording r : getRecordings()) {
|
||||||
chunkSet.addAll(r.getChunks());
|
chunkSet.addAll(r.getChunks());
|
||||||
@ -438,7 +438,7 @@ public final class PlatformRecorder {
|
|||||||
return chunks;
|
return chunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.emptyList();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startDiskMonitor() {
|
private void startDiskMonitor() {
|
||||||
@ -454,6 +454,8 @@ public final class PlatformRecorder {
|
|||||||
r.appendChunk(chunk);
|
r.appendChunk(chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Decrease initial reference count
|
||||||
|
chunk.release();
|
||||||
FilePurger.purge();
|
FilePurger.purge();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,4 +661,8 @@ public final class PlatformRecorder {
|
|||||||
rotateDisk();
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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;
|
import jdk.jfr.internal.SecuritySupport.SafePath;
|
||||||
|
|
||||||
final class RepositoryChunk {
|
public final class RepositoryChunk {
|
||||||
|
|
||||||
static final Comparator<RepositoryChunk> END_TIME_COMPARATOR = new Comparator<RepositoryChunk>() {
|
static final Comparator<RepositoryChunk> END_TIME_COMPARATOR = new Comparator<RepositoryChunk>() {
|
||||||
@Override
|
@Override
|
||||||
@ -47,7 +47,7 @@ final class RepositoryChunk {
|
|||||||
|
|
||||||
private Instant endTime = null; // unfinished
|
private Instant endTime = null; // unfinished
|
||||||
private Instant startTime;
|
private Instant startTime;
|
||||||
private int refCount = 0;
|
private int refCount = 1;
|
||||||
private long size;
|
private long size;
|
||||||
|
|
||||||
RepositoryChunk(SafePath path) throws Exception {
|
RepositoryChunk(SafePath path) throws Exception {
|
||||||
@ -166,4 +166,12 @@ final class RepositoryChunk {
|
|||||||
public SafePath getFile() {
|
public SafePath getFile() {
|
||||||
return chunkFile;
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.internal.module.Checks;
|
||||||
import jdk.jfr.Event;
|
import jdk.jfr.Event;
|
||||||
|
import jdk.jfr.EventType;
|
||||||
import jdk.jfr.FlightRecorderPermission;
|
import jdk.jfr.FlightRecorderPermission;
|
||||||
import jdk.jfr.Recording;
|
import jdk.jfr.Recording;
|
||||||
import jdk.jfr.RecordingState;
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.FlightRecorder;
|
||||||
import jdk.jfr.Recording;
|
import jdk.jfr.Recording;
|
||||||
import jdk.jfr.internal.JVM;
|
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.LogLevel;
|
||||||
import jdk.jfr.internal.LogTag;
|
import jdk.jfr.internal.LogTag;
|
||||||
import jdk.jfr.internal.Logger;
|
import jdk.jfr.internal.Logger;
|
||||||
@ -50,9 +52,7 @@ import jdk.jfr.internal.Utils;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
abstract class AbstractDCmd {
|
abstract class AbstractDCmd {
|
||||||
|
private final LinePrinter output = new LinePrinter();
|
||||||
private final StringBuilder currentLine = new StringBuilder(80);
|
|
||||||
private final List<String> lines = new ArrayList<>();
|
|
||||||
private String source;
|
private String source;
|
||||||
|
|
||||||
// Called by native
|
// Called by native
|
||||||
@ -94,13 +94,16 @@ abstract class AbstractDCmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected final Output getOutput() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
protected final FlightRecorder getFlightRecorder() {
|
protected final FlightRecorder getFlightRecorder() {
|
||||||
return FlightRecorder.getFlightRecorder();
|
return FlightRecorder.getFlightRecorder();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final String[] getResult() {
|
protected final String[] getResult() {
|
||||||
return lines.toArray(new String[lines.size()]);
|
return output.getLines().toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void logWarning(String message) {
|
protected void logWarning(String message) {
|
||||||
@ -181,21 +184,19 @@ abstract class AbstractDCmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected final void println() {
|
protected final void println() {
|
||||||
lines.add(currentLine.toString());
|
output.println();
|
||||||
currentLine.setLength(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void print(String s) {
|
protected final void print(String s) {
|
||||||
currentLine.append(s);
|
output.print(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void print(String s, Object... args) {
|
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) {
|
protected final void println(String s, Object... args) {
|
||||||
print(s, args);
|
output.println(s, args);
|
||||||
println();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void printBytes(long bytes) {
|
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) {
|
protected final void printPath(Path path) {
|
||||||
try {
|
try {
|
||||||
println(path.toAbsolutePath().toString());
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -29,6 +29,7 @@ record Argument(
|
|||||||
String description,
|
String description,
|
||||||
String type,
|
String type,
|
||||||
boolean mandatory,
|
boolean mandatory,
|
||||||
|
boolean option,
|
||||||
String defaultValue,
|
String defaultValue,
|
||||||
boolean allowMultiple
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -35,6 +35,7 @@ import java.util.StringJoiner;
|
|||||||
final class ArgumentParser {
|
final class ArgumentParser {
|
||||||
private final Map<String, Object> options = new HashMap<>();
|
private final Map<String, Object> options = new HashMap<>();
|
||||||
private final Map<String, Object> extendedOptions = 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 StringBuilder builder = new StringBuilder();
|
||||||
private final String text;
|
private final String text;
|
||||||
private final char delimiter;
|
private final char delimiter;
|
||||||
@ -42,8 +43,7 @@ final class ArgumentParser {
|
|||||||
private final String valueDelimiter;
|
private final String valueDelimiter;
|
||||||
private final Argument[] arguments;
|
private final Argument[] arguments;
|
||||||
private int position;
|
private int position;
|
||||||
|
private int argumentIndex;
|
||||||
private final List<String> conflictedOptions = new ArrayList<>();
|
|
||||||
|
|
||||||
ArgumentParser(Argument[] arguments, String text, char delimiter) {
|
ArgumentParser(Argument[] arguments, String text, char delimiter) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
@ -60,6 +60,11 @@ final class ArgumentParser {
|
|||||||
String value = null;
|
String value = null;
|
||||||
if (accept('=')) {
|
if (accept('=')) {
|
||||||
value = readText(valueDelimiter);
|
value = readText(valueDelimiter);
|
||||||
|
} else {
|
||||||
|
if (hasArgumentsLeft()) {
|
||||||
|
value = key;
|
||||||
|
key = nextArgument().name();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!atEnd() && !accept(delimiter)) { // must be followed by delimiter
|
if (!atEnd() && !accept(delimiter)) { // must be followed by delimiter
|
||||||
throw new IllegalArgumentException("Expected delimiter, but found " + currentChar());
|
throw new IllegalArgumentException("Expected delimiter, but found " + currentChar());
|
||||||
@ -72,6 +77,25 @@ final class ArgumentParser {
|
|||||||
return options;
|
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() {
|
protected void checkConflict() {
|
||||||
if (conflictedOptions.isEmpty()) {
|
if (conflictedOptions.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@ -97,14 +121,15 @@ final class ArgumentParser {
|
|||||||
throw new IllegalArgumentException(sb.toString());
|
throw new IllegalArgumentException(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkMandatory() {
|
public boolean checkMandatory() {
|
||||||
for (Argument arg : arguments) {
|
for (Argument arg : arguments) {
|
||||||
if (!options.containsKey(arg.name())) {
|
if (!options.containsKey(arg.name())) {
|
||||||
if (arg.mandatory()) {
|
if (arg.mandatory()) {
|
||||||
throw new IllegalArgumentException("The argument '" + arg.name() + "' is mandatory");
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
@ -194,6 +219,7 @@ final class ArgumentParser {
|
|||||||
|
|
||||||
private Object value(String name, String type, String text) {
|
private Object value(String name, String type, String text) {
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
|
case "JULONG" -> parseLong(name, text);
|
||||||
case "STRING", "STRING SET" -> text == null ? "" : text;
|
case "STRING", "STRING SET" -> text == null ? "" : text;
|
||||||
case "BOOLEAN" -> parseBoolean(name, text);
|
case "BOOLEAN" -> parseBoolean(name, text);
|
||||||
case "NANOTIME" -> parseNanotime(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) {
|
private Boolean parseBoolean(String name, String text) {
|
||||||
if ("true".equals(text)) {
|
if ("true".equals(text)) {
|
||||||
return Boolean.TRUE;
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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[] {
|
return new Argument[] {
|
||||||
new Argument("name",
|
new Argument("name",
|
||||||
"Recording name, e.g. \\\"My Recording\\\" or omit to see all recordings",
|
"Recording name, e.g. \\\"My Recording\\\" or omit to see all recordings",
|
||||||
"STRING", false, null, false),
|
"STRING", false, true, null, false),
|
||||||
new Argument("verbose",
|
new Argument("verbose",
|
||||||
"Print event settings for the recording(s)","BOOLEAN",
|
"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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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[] {
|
return new Argument[] {
|
||||||
new Argument("name",
|
new Argument("name",
|
||||||
"Recording name, e.g. \\\"My Recording\\\"",
|
"Recording name, e.g. \\\"My Recording\\\"",
|
||||||
"STRING", false, null, false),
|
"STRING", false, true, null, false),
|
||||||
new Argument("filename",
|
new Argument("filename",
|
||||||
"Copy recording data to file, e.g. \\\"" + exampleFilename() + "\\\"",
|
"Copy recording data to file, e.g. \\\"" + exampleFilename() + "\\\"",
|
||||||
"STRING", false, null, false),
|
"STRING", false, true, null, false),
|
||||||
new Argument("maxage",
|
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",
|
"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",
|
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",
|
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",
|
"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",
|
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",
|
"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",
|
new Argument("path-to-gc-roots",
|
||||||
"Collect 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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[] {
|
return new Argument[] {
|
||||||
new Argument("name",
|
new Argument("name",
|
||||||
"Name that can be used to identify recording, e.g. \\\"My Recording\\\"",
|
"Name that can be used to identify recording, e.g. \\\"My Recording\\\"",
|
||||||
"STRING", false, null, false),
|
"STRING", false, true, null, false),
|
||||||
new Argument("settings",
|
new Argument("settings",
|
||||||
"Settings file(s), e.g. profile or default. See JAVA_HOME/lib/jfr",
|
"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",
|
new Argument("delay",
|
||||||
"Delay recording start with (s)econds, (m)inutes), (h)ours), or (d)ays, e.g. 5h.",
|
"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",
|
new Argument("duration",
|
||||||
"Duration of recording in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 300s.",
|
"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",
|
new Argument("disk",
|
||||||
"Recording should be persisted to disk",
|
"Recording should be persisted to disk",
|
||||||
"BOOLEAN", false, "true", false),
|
"BOOLEAN", false, true, "true", false),
|
||||||
new Argument("filename",
|
new Argument("filename",
|
||||||
"Resulting recording filename, e.g. \\\"" + exampleFilename() + "\\\"",
|
"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",
|
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",
|
"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",
|
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",
|
"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",
|
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",
|
"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",
|
new Argument("dumponexit",
|
||||||
"Dump running recording when JVM shuts down",
|
"Dump running recording when JVM shuts down",
|
||||||
"BOOLEAN", false, "false", false),
|
"BOOLEAN", false, true, "false", false),
|
||||||
new Argument("path-to-gc-roots",
|
new Argument("path-to-gc-roots",
|
||||||
"Collect 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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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[] {
|
return new Argument[] {
|
||||||
new Argument("name",
|
new Argument("name",
|
||||||
"Recording name, e.g. \\\"My Recording\\\"",
|
"Recording name, e.g. \\\"My Recording\\\"",
|
||||||
"STRING", true, null, false),
|
"STRING", true, true, null, false),
|
||||||
new Argument("filename",
|
new Argument("filename",
|
||||||
"Copy recording data to file, e.g. \\\"" + exampleFilename() + "\\\"",
|
"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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.Deque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import jdk.jfr.internal.util.UserDataException;
|
||||||
|
import jdk.jfr.internal.util.UserSyntaxException;
|
||||||
|
|
||||||
final class Assemble extends Command {
|
final class Assemble extends Command {
|
||||||
|
|
||||||
@Override
|
@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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.Deque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import jdk.jfr.internal.util.UserDataException;
|
||||||
|
import jdk.jfr.internal.util.UserSyntaxException;
|
||||||
|
|
||||||
abstract class Command {
|
abstract class Command {
|
||||||
public static final String title = "Tool for working with Flight Recorder files";
|
public static final String title = "Tool for working with Flight Recorder files";
|
||||||
private static final Command HELP = new Help();
|
private static final Command HELP = new Help();
|
||||||
@ -48,6 +51,9 @@ abstract class Command {
|
|||||||
private static List<Command> createCommands() {
|
private static List<Command> createCommands() {
|
||||||
List<Command> commands = new ArrayList<>();
|
List<Command> commands = new ArrayList<>();
|
||||||
commands.add(new Print());
|
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 Configure());
|
||||||
commands.add(new Metadata());
|
commands.add(new Metadata());
|
||||||
commands.add(new Scrub());
|
commands.add(new Scrub());
|
||||||
@ -170,6 +176,18 @@ abstract class Command {
|
|||||||
return false;
|
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 {
|
protected void warnForWildcardExpansion(String option, String filter) throws UserDataException {
|
||||||
// Users should quote their wildcards to avoid expansion by the shell
|
// Users should quote their wildcards to avoid expansion by the shell
|
||||||
try {
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.SettingsLog;
|
||||||
import jdk.jfr.internal.jfc.model.UserInterface;
|
import jdk.jfr.internal.jfc.model.UserInterface;
|
||||||
import jdk.jfr.internal.jfc.model.XmlInput;
|
import jdk.jfr.internal.jfc.model.XmlInput;
|
||||||
|
import jdk.jfr.internal.util.UserDataException;
|
||||||
|
import jdk.jfr.internal.util.UserSyntaxException;
|
||||||
|
|
||||||
final class Configure extends Command {
|
final class Configure extends Command {
|
||||||
private final List<String> inputFiles = new ArrayList<>();
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.ChunkHeader;
|
||||||
import jdk.jfr.internal.consumer.FileAccess;
|
import jdk.jfr.internal.consumer.FileAccess;
|
||||||
import jdk.jfr.internal.consumer.RecordingInput;
|
import jdk.jfr.internal.consumer.RecordingInput;
|
||||||
|
import jdk.jfr.internal.util.UserDataException;
|
||||||
|
import jdk.jfr.internal.util.UserSyntaxException;
|
||||||
|
|
||||||
final class Disassemble extends Command {
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.EventType;
|
||||||
import jdk.jfr.consumer.RecordedThread;
|
import jdk.jfr.consumer.RecordedThread;
|
||||||
|
import jdk.jfr.internal.util.UserSyntaxException;
|
||||||
import jdk.jfr.consumer.RecordedEvent;
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.Deque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import jdk.jfr.internal.util.UserDataException;
|
||||||
|
import jdk.jfr.internal.util.UserSyntaxException;
|
||||||
|
|
||||||
final class Help extends Command {
|
final class Help extends Command {
|
||||||
|
|
||||||
@Override
|
@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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.Arrays;
|
||||||
import java.util.Deque;
|
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
|
* Launcher class for the JDK_HOME\bin\jfr tool
|
||||||
*
|
*
|
||||||
@ -49,7 +52,7 @@ public final class Main {
|
|||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.println(" java -XX:StartFlightRecording:filename=recording.jfr,duration=30s ... ");
|
System.out.println(" java -XX:StartFlightRecording:filename=recording.jfr,duration=30s ... ");
|
||||||
System.out.println();
|
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();
|
||||||
System.out.println(" jcmd (to list available pids)");
|
System.out.println(" jcmd (to list available pids)");
|
||||||
System.out.println(" jcmd <pid> JFR.start");
|
System.out.println(" jcmd <pid> JFR.start");
|
||||||
@ -71,11 +74,13 @@ public final class Main {
|
|||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.println(" jfr print --events " + q + "jdk.*" + q + " --stack-depth 64 recording.jfr");
|
System.out.println(" jfr print --events " + q + "jdk.*" + q + " --stack-depth 64 recording.jfr");
|
||||||
System.out.println();
|
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(" jfr summary recording.jfr");
|
||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.println(" jfr metadata recording.jfr");
|
System.out.println(" jfr metadata");
|
||||||
System.out.println();
|
|
||||||
System.out.println(" jfr metadata --categories GC,Detailed");
|
|
||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.println("For more information about available commands, use 'jfr help'");
|
System.out.println("For more information about available commands, use 'jfr help'");
|
||||||
System.exit(EXIT_OK);
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.Type;
|
||||||
import jdk.jfr.internal.MetadataRepository;
|
import jdk.jfr.internal.MetadataRepository;
|
||||||
import jdk.jfr.internal.consumer.JdkJfrConsumer;
|
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;
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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 java.util.function.Predicate;
|
||||||
|
|
||||||
import jdk.jfr.EventType;
|
import jdk.jfr.EventType;
|
||||||
|
import jdk.jfr.internal.util.UserDataException;
|
||||||
|
import jdk.jfr.internal.util.UserSyntaxException;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.EventType;
|
||||||
import jdk.jfr.consumer.RecordedEvent;
|
import jdk.jfr.consumer.RecordedEvent;
|
||||||
import jdk.jfr.consumer.RecordingFile;
|
import jdk.jfr.consumer.RecordingFile;
|
||||||
|
import jdk.jfr.internal.util.UserDataException;
|
||||||
|
import jdk.jfr.internal.util.UserSyntaxException;
|
||||||
|
|
||||||
final class Scrub extends Command {
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.ChunkHeader;
|
||||||
import jdk.jfr.internal.consumer.FileAccess;
|
import jdk.jfr.internal.consumer.FileAccess;
|
||||||
import jdk.jfr.internal.consumer.RecordingInput;
|
import jdk.jfr.internal.consumer.RecordingInput;
|
||||||
|
import jdk.jfr.internal.util.UserDataException;
|
||||||
|
import jdk.jfr.internal.util.UserSyntaxException;
|
||||||
|
|
||||||
final class Summary extends Command {
|
final class Summary extends Command {
|
||||||
private final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withLocale(Locale.UK).withZone(ZoneOffset.UTC);
|
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