From 4e708e58dcd2cfa54807e8e412b7c6b472467efe Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Thu, 25 Mar 2021 20:43:16 +0000 Subject: [PATCH] 8260862: JFR: New configure command for the jfr tool Reviewed-by: mgronlun --- .../classes/jdk/jfr/internal/jfc/jfc.xsd | 18 +- .../internal/jfc/model/AbortException.java | 29 ++ .../jfr/internal/jfc/model/Constraint.java | 36 ++ .../internal/jfc/model/ControlElement.java | 31 ++ .../jdk/jfr/internal/jfc/model/JFCModel.java | 206 ++++++++++ .../jdk/jfr/internal/jfc/model/Parser.java | 106 +++++ .../jfr/internal/jfc/model/PrettyPrinter.java | 95 +++++ .../jdk/jfr/internal/jfc/model/Result.java | 53 +++ .../jfr/internal/jfc/model/SettingsLog.java | 55 +++ .../jfr/internal/jfc/model/UserInterface.java | 54 +++ .../jdk/jfr/internal/jfc/model/Utilities.java | 126 ++++++ .../jdk/jfr/internal/jfc/model/XmlAnd.java | 49 +++ .../jfr/internal/jfc/model/XmlCondition.java | 74 ++++ .../internal/jfc/model/XmlConfiguration.java | 102 +++++ .../jfr/internal/jfc/model/XmlControl.java | 63 +++ .../jfr/internal/jfc/model/XmlElement.java | 227 +++++++++++ .../jdk/jfr/internal/jfc/model/XmlEvent.java | 66 ++++ .../jfr/internal/jfc/model/XmlExpression.java | 53 +++ .../jdk/jfr/internal/jfc/model/XmlFlag.java | 73 ++++ .../jdk/jfr/internal/jfc/model/XmlInput.java | 57 +++ .../jdk/jfr/internal/jfc/model/XmlNot.java | 56 +++ .../jdk/jfr/internal/jfc/model/XmlOption.java | 49 +++ .../jdk/jfr/internal/jfc/model/XmlOr.java | 50 +++ .../jfr/internal/jfc/model/XmlSelection.java | 118 ++++++ .../jfr/internal/jfc/model/XmlSetting.java | 84 ++++ .../jdk/jfr/internal/jfc/model/XmlTest.java | 84 ++++ .../jdk/jfr/internal/jfc/model/XmlText.java | 85 ++++ .../jdk/jfr/internal/tool/Assemble.java | 2 +- .../jdk/jfr/internal/tool/Command.java | 21 +- .../jdk/jfr/internal/tool/Configure.java | 301 +++++++++++++++ src/jdk.jfr/share/conf/jfr/default.jfc | 250 ++++++------ src/jdk.jfr/share/conf/jfr/profile.jfc | 249 ++++++------ test/jdk/jdk/jfr/tool/TestConfigure.java | 362 ++++++++++++++++++ test/jdk/jdk/jfr/tool/configure/and.jfc | 24 ++ test/jdk/jdk/jfr/tool/configure/condition.jfc | 33 ++ test/jdk/jdk/jfr/tool/configure/flag.jfc | 21 + test/jdk/jdk/jfr/tool/configure/missing.jfc | 19 + test/jdk/jdk/jfr/tool/configure/or.jfc | 20 + test/jdk/jdk/jfr/tool/configure/plain.jfc | 15 + test/jdk/jdk/jfr/tool/configure/selection.jfc | 27 ++ .../jdk/jfr/tool/configure/superfluous.jfc | 18 + test/jdk/jdk/jfr/tool/configure/text.jfc | 22 ++ test/jdk/jdk/jfr/tool/configure/timespan.jfc | 18 + 43 files changed, 3227 insertions(+), 274 deletions(-) create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/AbortException.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Constraint.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/ControlElement.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/JFCModel.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Parser.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/PrettyPrinter.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Result.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/SettingsLog.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/UserInterface.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Utilities.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlAnd.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlCondition.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlConfiguration.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlControl.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlElement.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlEvent.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlExpression.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlFlag.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlInput.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlNot.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlOption.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlOr.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlSelection.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlSetting.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlTest.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlText.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Configure.java create mode 100644 test/jdk/jdk/jfr/tool/TestConfigure.java create mode 100644 test/jdk/jdk/jfr/tool/configure/and.jfc create mode 100644 test/jdk/jdk/jfr/tool/configure/condition.jfc create mode 100644 test/jdk/jdk/jfr/tool/configure/flag.jfc create mode 100644 test/jdk/jdk/jfr/tool/configure/missing.jfc create mode 100644 test/jdk/jdk/jfr/tool/configure/or.jfc create mode 100644 test/jdk/jdk/jfr/tool/configure/plain.jfc create mode 100644 test/jdk/jdk/jfr/tool/configure/selection.jfc create mode 100644 test/jdk/jdk/jfr/tool/configure/superfluous.jfc create mode 100644 test/jdk/jdk/jfr/tool/configure/text.jfc create mode 100644 test/jdk/jdk/jfr/tool/configure/timespan.jfc diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/jfc.xsd b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/jfc.xsd index b09a5769c6f..205877cc67e 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/jfc.xsd +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/jfc.xsd @@ -6,29 +6,19 @@ - - - - - - - - - - - - - + + + - + diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/AbortException.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/AbortException.java new file mode 100644 index 00000000000..26685dcacb7 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/AbortException.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +public final class AbortException extends Exception { + private static final long serialVersionUID = -2501519883611363246L; +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Constraint.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Constraint.java new file mode 100644 index 00000000000..342b31d43ea --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Constraint.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +record Constraint(Class type, int min, int max) { + + public static Constraint any(Class type) { + return new Constraint(type, 0, Integer.MAX_VALUE); + } + + public static Constraint atLeast(Class type, int min) { + return new Constraint(type, min, Integer.MAX_VALUE); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/ControlElement.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/ControlElement.java new file mode 100644 index 00000000000..a91ddb025a9 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/ControlElement.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +// Interface for elements that can control a setting, +// , , and +interface ControlElement { + String getName(); +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/JFCModel.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/JFCModel.java new file mode 100644 index 00000000000..a2ee14ad343 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/JFCModel.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.Charset; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import jdk.jfr.internal.SecuritySupport.SafePath; + +// Holds the structure of a .jfc file similar to an XML DOM. +public final class JFCModel { + private final Map> controls = new LinkedHashMap<>(); + private final XmlConfiguration configuration; + + public JFCModel(SafePath file) throws ParseException, IOException { + this.configuration = createConfiguration(file); + this.configuration.validate(); + addControls(); + wireConditions(); + wireSettings(); + } + + public JFCModel(List files) throws IOException, ParseException { + this.configuration = new XmlConfiguration(); + this.configuration.setAttribute("version", "2.0"); + for (SafePath file : files) { + JFCModel model = new JFCModel(file); + for (var entry : model.controls.entrySet()) { + String name = entry.getKey(); + // Fail-fast checks that prevents an ambiguous file to be written later + if (controls.containsKey(name)) { + throw new ParseException("Control with '" + name + "' is declared in multiple files", 0); + } + controls.put(name, entry.getValue()); + } + for (XmlElement child : model.configuration.getChildren()) { + this.configuration.addChild(child); + } + } + } + + public void setLabel(String label) { + configuration.setAttribute("label", label); + } + + public void configure(String name, String value) { + for (XmlInput i : getInputs()) { + if (i.getName().equals(name)) { + i.configure(value); + return; + } + } + boolean add = name.startsWith("+"); + if (add) { + name = name.substring(1); + } + int index = name.indexOf("#"); + if (index < 1 || index == name.length() - 1) { + throw new IllegalArgumentException("Option '" + name + "' doesn't exist in configuration"); + } + XmlEvent event = configuration.getEvent(name.substring(0, index), add); + String settingName = name.substring(index + 1); + XmlSetting setting = event.getSetting(settingName, add); + + if (settingName.equals("period") || settingName.equals("threshold")) { + try { + value = Utilities.parseTimespan(value); + } catch (IllegalArgumentException iae) { + // OK, no validation to allow forward compatibility. + } + } + + setting.setContent(value); + } + + public void configure(UserInterface ui) throws AbortException { + for (XmlInput input : getInputs()) { + input.configure(ui); + } + } + + public List getInputs() { + List inputs = new ArrayList<>(); + for (XmlControl control : configuration.getControls()) { + inputs.addAll(control.getInputs()); + } + return inputs; + } + + public XmlConfiguration getConfiguration() { + return configuration; + } + + public void saveToFile(SafePath path) throws IOException { + try (PrintWriter p = new PrintWriter(path.toFile(), Charset.forName("UTF-8"))) { + PrettyPrinter pp = new PrettyPrinter(p); + pp.print(configuration); + if (p.checkError()) { + throw new IOException("Error writing " + path); + } + } + } + + private List getControlElements(String name) { + return controls.getOrDefault(name, Collections.emptyList()); + } + + private void addControls() { + for (var controls : configuration.getControls()) { + for (var control : controls.getControlElements()) { + add(control); + } + } + } + + private void wireConditions() { + for (XmlControl control : configuration.getControls()) { + for (XmlCondition condition : control.getConditions()) { + for (XmlElement element : condition.getChildren()) { + wireExpression(condition, element); + } + } + } + } + + private void wireExpression(XmlElement parent, XmlElement element) { + element.addListener(parent); + if (element instanceof XmlTest test) { + wireTest(test); + } + if (element instanceof XmlExpression expression) { + for (XmlExpression child : expression.getExpressions()) { + wireExpression(expression, child); + } + } + } + + private void wireTest(XmlTest test) { + String name = test.getName(); + for (ControlElement ce : getControlElements(name)) { + XmlElement control = (XmlElement) ce; + control.addListener(test); + } + } + + private void wireSettings() { + for (XmlEvent event : configuration.getEvents()) { + for (XmlSetting setting : event.getSettings()) { + var controlName = setting.getControl(); + if (controlName.isPresent()) { + List controls = getControlElements(controlName.get()); + if (controls.isEmpty()) { + System.out.println("Warning! Setting '" + setting.getFullName() + "' refers to missing control '" + controlName.get() + "'"); + } + for (ControlElement ce : controls) { + XmlElement control = (XmlElement) ce; + control.addListener(setting); + } + } + } + } + } + + private void add(ControlElement control) { + controls.computeIfAbsent(control.getName(), x -> new ArrayList<>()).add(control); + } + + private XmlConfiguration createConfiguration(SafePath file) throws ParseException, IOException { + if (file.toString().equals("none")) { + XmlConfiguration configuration = new XmlConfiguration(); + configuration.setAttribute("version", "2.0"); + configuration.setAttribute("label", "None"); + return configuration; + } + return Parser.parse(file.toPath()); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Parser.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Parser.java new file mode 100644 index 00000000000..ad5dae8f896 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Parser.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.text.ParseException; +import java.util.ArrayDeque; +import java.util.Deque; + +import jdk.internal.org.xml.sax.Attributes; +import jdk.internal.org.xml.sax.InputSource; +import jdk.internal.org.xml.sax.SAXException; +import jdk.internal.org.xml.sax.helpers.DefaultHandler; +import jdk.internal.util.xml.SAXParser; +import jdk.internal.util.xml.impl.SAXParserImpl; + +final class Parser { + + static XmlConfiguration parse(Path path) throws ParseException, IOException { + try (FileReader r = new FileReader(path.toFile(), Charset.forName("UTF-8"))) { + SAXParser saxParser = new SAXParserImpl(); + ConfigurationHandler handler = new ConfigurationHandler(); + saxParser.parse(new InputSource(r), handler); + return handler.configuration; + } catch (SAXException sp) { + ParseException pe = new ParseException(sp.getMessage(), -1); + pe.initCause(sp); + throw pe; + } + } + + private static final class ConfigurationHandler extends DefaultHandler { + private final Deque stack = new ArrayDeque<>(); + private final StringBuilder buffer = new StringBuilder(); + private XmlConfiguration configuration; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (configuration == null) { + if (!qName.equalsIgnoreCase("configuration")) { + throw new SAXException("Expected root element to be named 'configuration'"); + } + configuration = new XmlConfiguration(); + addAttributes(configuration, attributes); + stack.push(configuration); + return; + } + XmlElement current = stack.peek(); + XmlElement child = current.createChild(qName); + addAttributes(child, attributes); + stack.push(child); + } + + @Override + public void characters(char ch[], int start, int length) throws SAXException { + buffer.append(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) { + String content = buffer.toString().strip(); + if (!content.isEmpty()) { + stack.peek().setContent(content); + buffer.setLength(0); + } + + XmlElement current = stack.peek(); + if (current.getElementName().equalsIgnoreCase(qName)) { + stack.pop(); + } else { + throw new IllegalStateException("Unexpected <" + qName + "/>"); + } + } + + private void addAttributes(XmlElement element, Attributes attributes) { + for (int i = 0; i < attributes.getLength(); i++) { + element.setAttribute(attributes.getQName(i), attributes.getValue(i)); + } + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/PrettyPrinter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/PrettyPrinter.java new file mode 100644 index 00000000000..087337bd219 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/PrettyPrinter.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.io.PrintWriter; +import java.util.Map; + +final class PrettyPrinter { + private final PrintWriter out; + + PrettyPrinter(PrintWriter out) { + this.out = out; + } + + void print(XmlConfiguration configuration) { + printHeader(); + prettyPrint("", configuration); + } + + private void printHeader() { + out.println(""); + } + + private void prettyPrint(String indent, XmlElement element) { + printComment(indent, element); + String elementName = element.getElementName(); + out.print(indent + '<' + elementName); + printAttributes(element.getAttributes()); + if (element.getChildren().isEmpty() && !element.hasContent()) { + out.println("/>"); + return; + } + out.print('>'); + out.print(Utilities.escapeAll(element.getContent().trim())); + if (element.getChildren().isEmpty()) { + out.println("'); + return; + } + out.println(); + boolean first = true; + for (XmlElement child : element.getChildren()) { + if (first && child.isEntity()) { + out.println(); + } + prettyPrint(indent + " ", child); + if (child.isEntity()) { + out.println(); + } + first = false; + } + out.println(indent + "'); + } + + private void printComment(String indent, XmlElement element) { + String comment = element.comment(); + if (!comment.isEmpty()) { + String text = comment.indent(indent.length()); + out.println(indent + ""); + } + } + + private void printAttributes(Map attributes) { + for (var entry : attributes.entrySet()) { + out.print(' '); + out.print(entry.getKey()); + out.print("=\""); + out.print(Utilities.escapeAll(entry.getValue())); + out.print('\"'); + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Result.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Result.java new file mode 100644 index 00000000000..ae4e4b6cd2a --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Result.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +record Result(String value) { + public static final Result FALSE = new Result("false"); + public static final Result TRUE = new Result("true"); + public static final Result NULL = new Result(null); + + public boolean isTrue() { + return "true".equalsIgnoreCase(value); + } + + public boolean isNull() { + return value == null; + } + + public boolean isFalse() { + return "false".equalsIgnoreCase(value); + } + + public static Result of(String value) { + if ("true".equalsIgnoreCase(value)) { + return TRUE; + } + if ("false".equalsIgnoreCase(value)) { + return FALSE; + } + return new Result(value); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/SettingsLog.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/SettingsLog.java new file mode 100644 index 00000000000..11568664984 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/SettingsLog.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.util.Map; +import java.util.TreeMap; + +public final class SettingsLog { + private static final Map settings = new TreeMap<>(); + private static boolean enabled; + + public static void enable() { + enabled = true; + } + + public static void flush() { + if (!settings.isEmpty()) { + System.out.println(); + System.out.println("Setting:"); + for (var s : settings.entrySet()) { + System.out.println("\"" + s.getKey() + "=" + s.getValue() + "\""); + } + settings.clear(); + } + } + + static void log(XmlSetting setting, String value) { + if (enabled) { + settings.put(setting.getFullName(), value); + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/UserInterface.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/UserInterface.java new file mode 100644 index 00000000000..7d9fc2478ba --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/UserInterface.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public final class UserInterface { + + public void println() { + System.out.println(); + } + + public void println(String text) { + System.out.println(text); + } + + public String readLine() throws AbortException { + try { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + String line = br.readLine(); + if (line == null || line.equalsIgnoreCase("Q")) { + println(); + throw new AbortException(); + } + return line; + } catch (IOException e) { + throw new Error("Unable to read input", e); + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Utilities.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Utilities.java new file mode 100644 index 00000000000..76e834fe6eb --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Utilities.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.util.StringJoiner; + +public final class Utilities { + private static final String[] UNITS = new String[] { + "ns", "us", "ns", "ms", "s", "m", "h", "d" // order matters + }; + + static XmlElement instantiate(Class type) { + try { + return type.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new InternalError("Unable to instantiate " + type, e); + } + } + + static String elementName(Class type) { + String name = type.getSimpleName(); + if (name.startsWith("Xml") && name.length() > 3) { + return name.substring(3).toLowerCase(); + } + throw new InternalError("Unexpected class " + type); + } + + static String escapeAll(String text) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < text.length(); i++) { + addCharacter(s, text.charAt(i)); + } + return s.toString(); + } + + private static void addCharacter(StringBuilder s, char c) { + if (c == 34) { + s.append("""); + return; + } + if (c == 38) { + s.append("&"); + return; + } + if (c == 39) { + s.append("'"); + return; + } + if (c == 60) { + s.append("<"); + return; + } + if (c == 62) { + s.append(">"); + return; + } + if (c > 0x7F) { + s.append("&#"); + s.append((int) c); + s.append(';'); + return; + } + s.append(c); + } + + static void checkValid(String value, Object... valid) { + StringJoiner sj = new StringJoiner(", "); + for (Object v : valid) { + if (v.equals(value)) { + return; + } + sj.add("'" + v + "'"); + } + String msg = "Incorrect value '" + value + "'. Valid values are " + sj.toString() + "."; + int index = msg.lastIndexOf(","); + if (index != -1) { + msg = msg.substring(0, index) + " and" + msg.substring(index + 1); + } + throw new IllegalArgumentException(msg); + } + + static String parseTimespan(String s) { + StringBuilder sb = new StringBuilder(); + try { + for (String unit : UNITS) { + if (s.endsWith(unit)) { + return parseForUnit(s, unit); + } + } + Long.parseLong(s); + sb.append("Timespan '" + s + "' is missing unit."); + } catch (NumberFormatException nfe) { + sb.append("'" + s + "' is not a valid timespan." + System.lineSeparator()); + sb.append("Should be numeric value followed by a unit, i.e. 20 ms."); + } + sb.append(" Valid units are ns, us, ms, s, m, h and d."); + throw new IllegalArgumentException(sb.toString()); + } + + private static String parseForUnit(String s, String unit) { + String number = s.trim().substring(0, s.length() - unit.length()); + return Long.parseLong(number.trim()) + " " + unit; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlAnd.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlAnd.java new file mode 100644 index 00000000000..e7c7d89d294 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlAnd.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +// Corresponds to +final class XmlAnd extends XmlExpression { + + @Override + boolean isEntity() { + return false; + } + + @Override + protected Result evaluate() { + Result result = Result.NULL; + for (XmlElement e : getProducers()) { + Result r = e.evaluate(); + if (r.isFalse()) { + return Result.FALSE; + } + if (r.isTrue()) { + result = Result.TRUE; + } + } + return result; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlCondition.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlCondition.java new file mode 100644 index 00000000000..21cd990f0c8 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlCondition.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.text.ParseException; +import java.util.List; +import java.util.Optional; + +// Corresponds +final class XmlCondition extends XmlExpression implements ControlElement { + + @Override + public String getName() { + return attribute("name"); + } + + public Result getTrueValue() { + return Result.of(attribute("true")); + } + + public Result getFalseValue() { + return Result.of(attribute("false")); + } + + @Override + protected void validateChildConstraints() throws ParseException { + if (getExpressions().size() > 1) { + throw new ParseException("Expected to not have more than one child", -1); + } + } + + @Override + protected List attributes() { + return List.of("name"); + } + + @Override + protected Result evaluate() { + Optional trueValue = optional("true"); + Optional falseValue = optional("false"); + for (XmlElement producer : getProducers()) { + Result r = producer.evaluate(); + if (trueValue.isPresent() && r.isTrue()) { + return getTrueValue(); + } + if (falseValue.isPresent() && r.isFalse()) { + return getFalseValue(); + } + } + return Result.NULL; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlConfiguration.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlConfiguration.java new file mode 100644 index 00000000000..6a6c96dc597 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlConfiguration.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.text.ParseException; +import java.util.List; +import java.util.Optional; + +// Corresponds to +final class XmlConfiguration extends XmlElement { + + public List getEvents() { + return elements(XmlEvent.class); + } + + public Optional getDescription() { + return optional("description"); + } + + public Optional getLabel() { + return optional("label"); + } + + public Optional getProvider() { + return optional("provider"); + } + + public Optional getVersion() { + return optional("version"); + } + + public List getControls() { + return elements(XmlControl.class); + } + + @Override + String comment() { + return """ + Recommended way to edit .jfc files is to use the configure command of + the 'jfr' tool, i.e. jfr configure, or JDK Mission Control + see Window -> Flight Recorder Template Manager + """; + } + + XmlEvent getEvent(String eventName, boolean add) { + for (XmlEvent event : getEvents()) { + if (eventName.equals(event.getName())) { + return event; + } + } + if (!add) { + throw new IllegalArgumentException("Could not find event '" + eventName + "'"); + } + XmlEvent event = new XmlEvent(); + event.setAttribute("name", eventName); + addChild(event); + return event; + } + + @Override + protected List attributes() { + return List.of("version", "label"); + } + + @Override + protected void validateAttributes() throws ParseException { + super.validateAttributes(); + if (!attribute("version").equals("2.0")) { + throw new ParseException("Only .jfc files of version 2.0 is supported", -1); + } + } + + @Override + protected List constraints() { + return List.of( + Constraint.any(XmlEvent.class), + Constraint.any(XmlControl.class) + ); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlControl.java new file mode 100644 index 00000000000..f2574de35e5 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlControl.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.util.List; + +// Corresponds to +final class XmlControl extends XmlElement { + + public List getInputs() { + return elements(XmlInput.class); + } + + public List getConditions() { + return elements(XmlCondition.class); + } + + // Returns list of all , , and + public List getControlElements() { + return elements(ControlElement.class); + } + + @Override + String comment() { + return """ + Contents of the control element is not read by the JVM, it's used + by JDK Mission Control and the 'jfr'-tool to change settings that + carry the control attribute. + """; + } + + @Override + protected List constraints() { + return List.of( + Constraint.any(XmlCondition.class), + Constraint.any(XmlText.class), + Constraint.any(XmlSelection.class), + Constraint.any(XmlFlag.class) + ); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlElement.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlElement.java new file mode 100644 index 00000000000..a128a645d24 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlElement.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +// Base class for XML-elements +class XmlElement { + private final Map attributes = new HashMap<>(); + private final List elements = new ArrayList<>(); + private final List listeners = new ArrayList<>(2); + private final List producers = new ArrayList<>(2); + private final String elementName; + + private XmlElement parent; + private String content = ""; + + XmlElement() { + this.elementName = Utilities.elementName(this.getClass()); + } + + private XmlElement(String elementName) { + this.elementName = elementName; + } + + final String getElementName() { + return elementName; + } + + final boolean hasContent() { + return content != null && !content.isEmpty(); + } + + final Map getAttributes() { + return attributes; + } + + final void validate() throws ParseException { + validateAttributes(); + validateChildConstraints(); + validateChildren(); + } + + final void setAttribute(String key, String value) { + Objects.requireNonNull(key); + Objects.requireNonNull(key); + attributes.put(key, value); + } + + final XmlElement createChild(String name) { + XmlElement child = instantiate(name); + child.parent = this; + addChild(child); + return child; + } + + private XmlElement instantiate(String name) { + for (var e : constraints()) { + String elementName = Utilities.elementName(e.type()); + if (elementName.equalsIgnoreCase(name)) { + return Utilities.instantiate(e.type()); + } + } + return new XmlElement(name); + } + + final void addChild(XmlElement element) { + elements.add(element); + } + + final List getChildren() { + return elements; + } + + void setContent(String content) { + this.content = content; + } + + final String getContent() { + return content; + } + + final void addListener(XmlElement listener) { + listeners.add(listener); + listener.addProducer(this); + } + + // If the element should be surrounded with an empty + // line when printed to file, for example, but not + boolean isEntity() { + return true; + } + + String comment() { + return ""; + } + + protected final void notifyListeners() { + onChange(); + SettingsLog.flush(); + } + + protected void onChange() { + for (XmlElement listener : listeners) { + listener.onChange(); + } + } + + protected final XmlElement getParent() { + return parent; + } + + protected List constraints() { + return List.of(); + } + + protected List attributes() { + return List.of(); + } + + protected final List getProducers() { + return producers; + } + + protected final Optional optional(String name) { + return Optional.ofNullable(attributes.get(name)); + } + + protected final String attribute(String name) { + return attributes.get(name); + } + + @SuppressWarnings("unchecked") + protected final List elements(Class type) { + List result = new ArrayList<>(); + for (XmlElement e : elements) { + if (type.isAssignableFrom(e.getClass())) { + result.add((T) e); + } + } + return result; + } + + protected Result evaluate() { + if (producers.isEmpty()) { + throw new Error("No producer evaluate for " + getClass()); + } + if (producers.size() != 1) { + throw new Error("Unsure how to evaluate multiple producers " + getClass()); + } + return producers.get(0).evaluate(); + } + + protected void validateAttributes() throws ParseException { + for (String key : attributes()) { + if (!attributes.containsKey(key)) { + throw new ParseException("Missing mandatory attribute '" + key + "'", 0); + } + } + } + + private void validateChildren() throws ParseException { + for (XmlElement child : elements) { + child.validate(); + } + } + + protected void validateChildConstraints() throws ParseException { + for (Constraint c : constraints()) { + validateConstraint(c); + } + } + + private final void validateConstraint(Constraint c) throws ParseException { + int count = count(c.type()); + if (count < c.min()) { + String elementName = Utilities.elementName(c.type()); + throw new ParseException("Missing mandatory element <" + elementName + ">", 0); + } + if (count > c.max()) { + String elementName = Utilities.elementName(c.type()); + throw new ParseException("Too many elements of type <" + elementName + ">", 0); + } + } + + private void addProducer(XmlElement producer) { + producers.add(producer); + } + + private int count(Class type) { + int count = 0; + for (XmlElement element : getChildren()) { + if (type.isAssignableFrom(element.getClass())) { + count++; + } + } + return count; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlEvent.java new file mode 100644 index 00000000000..8a6b63147db --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlEvent.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.util.List; + +// Corresponds to +final class XmlEvent extends XmlElement { + + public String getName() { + return attribute("name"); + } + + public List getSettings() { + return elements(XmlSetting.class); + } + + XmlSetting getSetting(String settingName, boolean add) { + for (XmlSetting setting : getSettings()) { + if (settingName.equals(setting.getName())) { + return setting; + } + } + if (!add) { + String msg = "Could not find setting '" + settingName; + msg += "' for event '" + getName() + "'"; + throw new IllegalArgumentException(msg); + } + XmlSetting setting = new XmlSetting(); + setting.setAttribute("name", settingName); + addChild(setting); + return setting; + } + + @Override + protected List attributes() { + return List.of("name"); + } + + @Override + protected List constraints() { + return List.of(Constraint.any(XmlSetting.class)); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlExpression.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlExpression.java new file mode 100644 index 00000000000..9162ed22e75 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlExpression.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.text.ParseException; +import java.util.List; + +// Base class for , , , and +abstract class XmlExpression extends XmlElement { + + public final List getExpressions() { + return elements(XmlExpression.class); + } + + @Override + protected List constraints() { + return List.of( + Constraint.any(XmlOr.class), + Constraint.any(XmlAnd.class), + Constraint.any(XmlTest.class), + Constraint.any(XmlNot.class) + ); + } + + @Override + protected void validateChildConstraints() throws ParseException { + if (getExpressions().size() < 2) { + throw new ParseException("Expected + <" + getElementName() + "> to have at least two children", 0); + } + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlFlag.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlFlag.java new file mode 100644 index 00000000000..d494d3eb57d --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlFlag.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +// Corresponds to +final class XmlFlag extends XmlInput { + + @Override + public String getOptionSyntax() { + return getName() + "="; + } + + @Override + public void configure(String value) { + Utilities.checkValid(value, "true", "false"); + setContent(value); + notifyListeners(); + } + + @Override + public void configure(UserInterface ui) throws AbortException { + Result defaultValue = Result.of(getContent()); + ui.println(); + ui.println(getLabel() + " [Y/N]: " + (defaultValue.isTrue() ? "Yes" : "No") + " (default)"); + + while (!read(ui, defaultValue)) { + ; + } + } + + @Override + protected Result evaluate() { + return Result.of(getContent()); + } + + private boolean read(UserInterface ui, Result defaultValue) throws AbortException { + String line = ui.readLine(); + if (line.isBlank()) { + ui.println("Using default: " + (defaultValue.isTrue() ? "Yes" : "No")); + return true; + } + if (line.equalsIgnoreCase("Y") || line.equalsIgnoreCase("N")) { + boolean value = line.equalsIgnoreCase("Y"); + ui.println("Using: " + (value ? "Yes" : "No")); + configure(String.valueOf(value)); + return true; + } + ui.println("Not a valid choice."); + return false; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlInput.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlInput.java new file mode 100644 index 00000000000..2754c7ca16c --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlInput.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.util.List; +import java.util.Optional; + +// Base class for elements that the user can interact with, +// , and +public abstract class XmlInput extends XmlElement implements ControlElement { + + public abstract String getOptionSyntax(); + + abstract void configure(UserInterface ui) throws AbortException; + + abstract void configure(String value); + + public final Optional getContentType() { + return optional("contentType"); + } + + @Override + public final String getName() { + return attribute("name"); + } + + public final String getLabel() { + return attribute("label"); + } + + @Override + protected List attributes() { + return List.of("name", "label"); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlNot.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlNot.java new file mode 100644 index 00000000000..057382a6601 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlNot.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.text.ParseException; +import java.util.List; + +// Corresponds to +final class XmlNot extends XmlExpression { + + @Override + boolean isEntity() { + return false; + } + + @Override + protected void validateChildConstraints() throws ParseException { + if (getExpressions().size() != 1) { + throw new ParseException("Expected to have a single child", 0); + } + } + + @Override + protected Result evaluate() { + List producers = getProducers(); + if (!producers.isEmpty()) { + Result r = producers.get(0).evaluate(); + if (!r.isNull()) { + return r.isTrue() ? Result.FALSE : Result.TRUE; + } + } + return Result.NULL; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlOption.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlOption.java new file mode 100644 index 00000000000..fca10115167 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlOption.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021, 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.jfc.model; + +import java.util.List; + +// Corresponds to - true + true - false + false @@ -207,7 +207,7 @@ true - everyChunk + everyChunk @@ -274,7 +274,7 @@ - false + false everyChunk @@ -382,22 +382,22 @@ - false + false 0 ms - false + false 0 ms - true + true 0 ms - true + true 0 ms @@ -443,11 +443,11 @@ - false + false - false + false @@ -455,7 +455,7 @@ - false + false true @@ -464,27 +464,27 @@ - false + false everyChunk - false + false - false + false everyChunk - false + false - true - false - 0 ns + true + false + 0 ns @@ -605,18 +605,18 @@ - false + false true - false + false true - true - 150/s + true + 150/s true @@ -638,31 +638,31 @@ true true - 20 ms + 20 ms true true - 20 ms + 20 ms true true - 20 ms + 20 ms true true - 20 ms + 20 ms true true - 20 ms + 20 ms @@ -819,67 +819,63 @@ - - + - + - - + + + + - - + + + + + + + + + + + + + + + + + + + - + - - - - - + + + + + + - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - + @@ -887,96 +883,96 @@ - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - + + + - + - + - - - - + + + + - + - + @@ -984,44 +980,44 @@ - - + + - + - + - - - + + + - - + + - + - - + + - - + + - 20 ms + 20 ms - 20 ms + 20 ms - 20 ms + 20 ms - false + false diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index 1ded1577adc..d8c7419264a 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -1,8 +1,8 @@ - @@ -64,31 +64,31 @@ true true - 10 ms + 10 ms true true - 10 ms + 10 ms true true - 10 ms + 10 ms true true - 10 ms + 10 ms true true - 10 ms + 10 ms @@ -120,13 +120,13 @@ - false + false true 0 ms - false + false true @@ -143,11 +143,11 @@ - true + true - false + false @@ -207,7 +207,7 @@ true - 60 s + 60 s @@ -274,7 +274,7 @@ - false + false everyChunk @@ -382,22 +382,22 @@ - false + false 0 ms - false + false 0 ms - true + true 0 ms - true + true 0 ms @@ -443,11 +443,11 @@ - true + true - true + true @@ -455,7 +455,7 @@ - false + false true @@ -464,27 +464,27 @@ - false + false everyChunk - false + false - false + false everyChunk - false + false - true - true - 0 ns + true + true + 0 ns @@ -605,18 +605,18 @@ - false + false true - false + false true - true - 300/s + true + 300/s true @@ -638,31 +638,31 @@ true true - 10 ms + 10 ms true true - 10 ms + 10 ms true true - 10 ms + 10 ms true true - 10 ms + 10 ms true true - 10 ms + 10 ms @@ -819,66 +819,63 @@ - + - - + - - + + + + - - + + + + + + + + + + + + + + + + + + + - + - - - - - + + + + + + - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - + @@ -886,96 +883,96 @@ - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - + + + - + - + - - - - + + + + - + - + @@ -983,44 +980,44 @@ - - + + - + - + - - - + + + - - + + - + - - + + - - + + - 10 ms + 10 ms - 10 ms + 10 ms - 10 ms + 10 ms - false + false diff --git a/test/jdk/jdk/jfr/tool/TestConfigure.java b/test/jdk/jdk/jfr/tool/TestConfigure.java new file mode 100644 index 00000000000..ae39b0935c5 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/TestConfigure.java @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2021, 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.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.ParseException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import jdk.jfr.Configuration; +import jdk.test.lib.process.OutputAnalyzer; +/** + * @test + * @summary Test jfr configure + * @key jfr + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @run main/othervm jdk.jfr.tool.TestConfigure + */ +public class TestConfigure { + + private static final String DIR = System.getProperty("test.src", "."); + + public static void main(String... args) throws Throwable { + testSelection(); + testText(); + testFlag(); + testCondition(); + testAnd(); + testOr(); + testNone(); + testMissingControl(); + testDefault(); + testCopyPredefined(); + testSuperflouos(); + testModify(); + testAdding(); + testUnification(); + testTimespan(); + testVerbose(); + } + + private static void testVerbose() throws Throwable { + var input = newInputFile("flag.jfc"); + + var output = newOutputFile("verbose-1.jfc"); + var result = jfrConfigure("--input", input, "--verbose", "mammal=true", "--output", output); + result.shouldContain("com.example.Lion#enabled=true"); + result.shouldContain("com.example.Tiger#enabled=true"); + + output = newOutputFile("verbose-2.jfc"); + result = jfrConfigure("--input", input, "--verbose", "+com.example.Albatross#enabled=true", "--output", output); + result.shouldContain("com.example.Albatross#enabled=true"); + } + + private static void testTimespan() throws Throwable { + var input = newInputFile("timespan.jfc"); + + var output = newOutputFile("quoted-timespan.jfc"); + jfrConfigure("--input", input, "value=20 s","--output", output); + var outputSetting = readSettings(output); + var expected = readSettings(input); + expected.put("com.example.Tiger#threshold", "20 s"); + expected.put("com.example.Lion#period", "20 s"); + + aseertEqual(outputSetting, expected); + + output = newOutputFile("compact-timespan.jfc"); + jfrConfigure("--input", input, "value=13s","--output", output); + outputSetting = readSettings(output); + expected = readSettings(input); + expected.put("com.example.Tiger#threshold", "13 s"); + expected.put("com.example.Lion#period", "13 s"); + + aseertEqual(outputSetting, expected); + + output = newOutputFile("threshold-period-timespan.jfc"); + jfrConfigure("--input", input, + "com.example.Tiger#threshold=2s", + "com.example.Lion#period=3s", + "--output", output); + + outputSetting = readSettings(output); + expected = readSettings(input); + expected.put("com.example.Tiger#threshold", "2 s"); + expected.put("com.example.Lion#period", "3 s"); + + aseertEqual(outputSetting, expected); + } + + private static void testUnification() throws Throwable { + var input1 = newInputFile("or.jfc"); + var input2 = newInputFile("and.jfc"); + var output = newOutputFile("combined.jfc"); + + jfrConfigure("--input", input1 + "," + input2, "--output", output); + + var input1Setting = readSettings(input1); + var input2Setting = readSettings(input2); + var outputSetting = readSettings(output); + + Map expected = new HashMap<>(); + expected.putAll(input1Setting); + expected.putAll(input2Setting); + + aseertEqual(outputSetting, expected); + } + + private static void testAdding() throws Throwable { + var input = newInputFile("plain.jfc"); + + var output = newOutputFile("test-adding-succeed-1.jfc"); + var result = jfrConfigure("--input", input, "+com.example.Tiger#legs=4", "--output", output); + result.shouldNotContain("Could not find"); + var outputSetting = readSettings(output); + var expected = readSettings(input); + expected.put("com.example.Tiger#legs", "4"); + + aseertEqual(outputSetting, expected); + + output = newOutputFile("test-adding-succeed-2.jfc"); + result = jfrConfigure("--input", input, "+com.example.Foo#bar=baz", "--output", output); + result.shouldNotContain("Could not find"); + + outputSetting = readSettings(output); + expected = readSettings(input); + expected.put("com.example.Foo#bar", "baz"); + + aseertEqual(outputSetting, expected); + } + + private static void testModify() throws Throwable { + var input = newInputFile("plain.jfc"); + + var output = newOutputFile("test-modify-fail-1.jfc"); + var result = jfrConfigure("--input", input, "com.example.Zebra#stackTrace=true", "--output", output); + result.shouldContain("Could not find event 'com.example.Zebra'"); + + output = newOutputFile("test-modify-fail-2.jfc"); + result = jfrConfigure("--input", input, "com.example.Tiger#foo=true", "--output", output); + result.shouldContain("Could not find setting 'foo' for event 'com.example.Tiger'"); + + output = newOutputFile("test-modify-succeed.jfc"); + result = jfrConfigure("--input", input, "com.example.Tiger#enabled=true", "--output", output); + result.shouldNotContain("Could not find"); + + var outputSetting = readSettings(output); + var expected = readSettings(input); + expected.put("com.example.Tiger#enabled", "true"); + + aseertEqual(outputSetting, expected); + } + + // JMC may add attributes or elements, make sure some random elements/attributes survive + private static void testSuperflouos() throws Throwable { + var output = newOutputFile("test-superfluous.jfc"); + var input = newInputFile("superfluous.jfc"); + jfrConfigure("--input", input, "--output", output); + String content = Files.readString(Path.of(output)); + for (String t : List.of("legs=\"4\"", "radio", "red", "", "option")) { + if (!content.contains(t)) { + throw new Exception("Expected superfluous element '" + t + "' or attribute to survive"); + } + } + } + + private static void testMissingControl() throws Throwable { + var output = newOutputFile("missed.jfc"); + var input = newInputFile("missing.jfc"); + var result = jfrConfigure("--input", input, "--output", output); + result.shouldContain("Warning! Setting 'com.example.Tiger#enabled' refers to missing control 'tigre'"); + } + + private static void testDefault() throws Throwable { + var output = newOutputFile("fresh.jfc"); + var result = jfrConfigure("--output", output); + result.shouldNotContain("Warning"); // checks dangling control reference in default.jfc + var outputSetting = readSettings(output); + aseertEqual(outputSetting, Configuration.getConfiguration("default").getSettings()); + } + + private static void testCopyPredefined() throws Throwable { + var output = newOutputFile("new.jfc"); + var result = jfrConfigure("--input", "profile", "--output", output); + result.shouldNotContain("Warning"); // checks missing control reference in profile.jfc + + var outputSetting = readSettings(output); + aseertEqual(outputSetting, Configuration.getConfiguration("profile").getSettings()); + } + + private static void testNone() throws Throwable { + var output = newOutputFile("new.jfc"); + jfrConfigure("--input", "none", "--output", output); + var outputSetting = readSettings(output); + aseertEqual(outputSetting, Map.of()); + } + + private static void testOr() throws Throwable { + var output = newOutputFile("test-or-true.jfc"); + var input = newInputFile("or.jfc"); + jfrConfigure("--input", input, "month=May", "--output", output); + var outputSetting = readSettings(output); + var expected = readSettings(input); + expected.put("season.Spring#enabled", "true"); + + aseertEqual(outputSetting, expected); + + output = newOutputFile("test-or-false.jfc"); + jfrConfigure("--input", input, "month=September", "--output", output); + outputSetting = readSettings(output); + expected = readSettings(input); + expected.put("season.Spring#enabled", "false"); + + aseertEqual(outputSetting, expected); + } + + private static void testAnd() throws Throwable { + var output = newOutputFile("test-and-true.jfc"); + var input = newInputFile("and.jfc"); + jfrConfigure("--input", input, + "closure=true", + "identity=true", + "associativity=true", + "inverse=true", + "--output", output); + var outputSetting = readSettings(output); + var expected = readSettings(input); + expected.put("algebra.Group#enabled", "true"); + + aseertEqual(outputSetting, expected); + + output = newOutputFile("test-and-false.jfc"); + jfrConfigure("--input", input, + "closure=true", + "identity=true", + "associativity=true", + "inverse=false", + "--output", output); + outputSetting = readSettings(output); + expected = readSettings(input); + expected.put("algebra.Group#enabled", "false"); + + aseertEqual(outputSetting, expected); + } + + + private static void testCondition() throws Throwable { + var output = newOutputFile("test-condition-1.jfc"); + var input = newInputFile("condition.jfc"); + jfrConfigure("--input", input, "variable=activate", "--output", output); + var outputSetting = readSettings(output); + var expected = readSettings(input); + expected.put("com.example.Tiger#period", "1 s"); + expected.put("com.example.Lion#period", "3 s"); + + aseertEqual(outputSetting, expected); + + output = newOutputFile("test-condition-2.jfc"); + jfrConfigure("--input", input, "variable=whatever", "--output", output); + outputSetting = readSettings(output); + expected = readSettings(input); + expected.put("com.example.Lion#period", "5 s"); + expected.put("com.example.Zebra#period", "7 s"); + + aseertEqual(outputSetting, expected); + } + + private static void testFlag() throws Throwable { + var output = newOutputFile("test-flag.jfc"); + var input = newInputFile("flag.jfc"); + jfrConfigure("--input", input, "mammal=true", "--output", output); + var outputSetting = readSettings(output); + var expected = readSettings(input); + expected.put("com.example.Tiger#enabled", "true"); + expected.put("com.example.Lion#enabled", "true"); + + aseertEqual(outputSetting, expected); + } + + private static void testText() throws Throwable { + var output = newOutputFile("test-text.jfc"); + var input = newInputFile("text.jfc"); + jfrConfigure("--input", input, "animal-threshold=3s", "--output", output); + var outputSetting = readSettings(output); + var expected = readSettings(input); + expected.put("com.example.Tiger#threshold", "3 s"); + expected.put("com.example.Lion#threshold", "3 s"); + + aseertEqual(outputSetting, expected); + } + + private static void testSelection() throws Throwable { + var output = newOutputFile("test-selection.jfc"); + var input = newInputFile("selection.jfc"); + jfrConfigure("--input", input, "animal=medium", "--output", output); + var outputSetting = readSettings(output); + var expected = readSettings(input); + expected.put("com.example.Tiger#threshold", "10 s"); + expected.put("com.example.Lion#threshold", "10 s"); + + aseertEqual(outputSetting, expected); + } + + private static String newInputFile(String filename) { + return Path.of(DIR, "configure", filename).toAbsolutePath().toString(); + } + + private static Map readSettings(String text) throws IOException, ParseException { + return Configuration.create(Path.of(text)).getSettings(); + } + + private static OutputAnalyzer jfrConfigure(String... args) throws Throwable { + String[] all = new String[args.length + 1]; + all[0] = "configure"; + for (int i = 0; i < args.length; i++) { + all[i + 1] = args[i]; + } + OutputAnalyzer o = ExecuteHelper.jfr(all); + System.out.println(o.getOutput()); + return o; + } + + private static String newOutputFile(String filename) { + return Path.of(DIR, System.currentTimeMillis() + filename).toAbsolutePath().toString(); + } + + private static void aseertEqual(Map output, Map expected) throws Exception { + if (!output.equals(expected)) { + System.out.println("Output:"); + for (var e : output.entrySet()) { + System.out.println("\"" + e.getKey() + "=" + e.getValue() + "\""); + } + System.out.println("Expected:"); + for (var e : expected.entrySet()) { + System.out.println("\"" + e.getKey() + "=" + e.getValue() + "\""); + } + throw new Exception("Mismatch between output and expected"); + } + } +} diff --git a/test/jdk/jdk/jfr/tool/configure/and.jfc b/test/jdk/jdk/jfr/tool/configure/and.jfc new file mode 100644 index 00000000000..ea591347d94 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/configure/and.jfc @@ -0,0 +1,24 @@ + + + + + unknown + + + + false + false + false + false + + + + + + + + + + + + \ No newline at end of file diff --git a/test/jdk/jdk/jfr/tool/configure/condition.jfc b/test/jdk/jdk/jfr/tool/configure/condition.jfc new file mode 100644 index 00000000000..579b4881ecd --- /dev/null +++ b/test/jdk/jdk/jfr/tool/configure/condition.jfc @@ -0,0 +1,33 @@ + + + + + 0 s + + + + 0 s + + + + 0 s + + + + disabled + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/jdk/jdk/jfr/tool/configure/flag.jfc b/test/jdk/jdk/jfr/tool/configure/flag.jfc new file mode 100644 index 00000000000..30f6f597c18 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/configure/flag.jfc @@ -0,0 +1,21 @@ + + + + + false + false + + + + false + + + + false + + + + false + + + \ No newline at end of file diff --git a/test/jdk/jdk/jfr/tool/configure/missing.jfc b/test/jdk/jdk/jfr/tool/configure/missing.jfc new file mode 100644 index 00000000000..0c85a3b2a0b --- /dev/null +++ b/test/jdk/jdk/jfr/tool/configure/missing.jfc @@ -0,0 +1,19 @@ + + + + + false + + + + 0 s + + + + + + + + + + \ No newline at end of file diff --git a/test/jdk/jdk/jfr/tool/configure/or.jfc b/test/jdk/jdk/jfr/tool/configure/or.jfc new file mode 100644 index 00000000000..740f36d19dd --- /dev/null +++ b/test/jdk/jdk/jfr/tool/configure/or.jfc @@ -0,0 +1,20 @@ + + + + + unknown + + + + January + + + + + + + + + + + \ No newline at end of file diff --git a/test/jdk/jdk/jfr/tool/configure/plain.jfc b/test/jdk/jdk/jfr/tool/configure/plain.jfc new file mode 100644 index 00000000000..aff2fd77568 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/configure/plain.jfc @@ -0,0 +1,15 @@ + + + + + false + false + + + + false + true + + + + \ No newline at end of file diff --git a/test/jdk/jdk/jfr/tool/configure/selection.jfc b/test/jdk/jdk/jfr/tool/configure/selection.jfc new file mode 100644 index 00000000000..e9939c93cce --- /dev/null +++ b/test/jdk/jdk/jfr/tool/configure/selection.jfc @@ -0,0 +1,27 @@ + + + + + false + 0 s + + + + true + 0 s + + + + 0 s + + + + + + + + + + + + \ No newline at end of file diff --git a/test/jdk/jdk/jfr/tool/configure/superfluous.jfc b/test/jdk/jdk/jfr/tool/configure/superfluous.jfc new file mode 100644 index 00000000000..54646b47bda --- /dev/null +++ b/test/jdk/jdk/jfr/tool/configure/superfluous.jfc @@ -0,0 +1,18 @@ + + + + + false + + + + true + + + + + + + + + \ No newline at end of file diff --git a/test/jdk/jdk/jfr/tool/configure/text.jfc b/test/jdk/jdk/jfr/tool/configure/text.jfc new file mode 100644 index 00000000000..879097511c6 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/configure/text.jfc @@ -0,0 +1,22 @@ + + + + + false + 0 s + + + + true + 0 s + + + + 0 s + + + + 0 s + + + \ No newline at end of file diff --git a/test/jdk/jdk/jfr/tool/configure/timespan.jfc b/test/jdk/jdk/jfr/tool/configure/timespan.jfc new file mode 100644 index 00000000000..74c743eed13 --- /dev/null +++ b/test/jdk/jdk/jfr/tool/configure/timespan.jfc @@ -0,0 +1,18 @@ + + + + + false + 0 s + + + + true + 0 s + + + + 0 s + + + \ No newline at end of file