1 /*
   2  * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.jfr.tool;
  27 
  28 import java.io.File;
  29 import java.io.StringReader;
  30 import java.nio.file.Path;
  31 import java.time.OffsetDateTime;
  32 import java.util.AbstractMap.SimpleEntry;
  33 import java.util.ArrayList;
  34 import java.util.Collections;
  35 import java.util.HashMap;
  36 import java.util.Iterator;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.Stack;
  40 
  41 import javax.xml.XMLConstants;
  42 import javax.xml.parsers.SAXParser;
  43 import javax.xml.parsers.SAXParserFactory;
  44 import javax.xml.validation.Schema;
  45 import javax.xml.validation.SchemaFactory;
  46 
  47 import org.xml.sax.Attributes;
  48 import org.xml.sax.InputSource;
  49 import org.xml.sax.SAXException;
  50 import org.xml.sax.SAXParseException;
  51 import org.xml.sax.XMLReader;
  52 import org.xml.sax.helpers.DefaultHandler;
  53 
  54 import jdk.jfr.Timespan;
  55 import jdk.jfr.Timestamp;
  56 import jdk.jfr.ValueDescriptor;
  57 import jdk.jfr.consumer.RecordedEvent;
  58 import jdk.jfr.consumer.RecordedObject;
  59 import jdk.jfr.consumer.RecordingFile;
  60 import jdk.test.lib.process.OutputAnalyzer;
  61 
  62 /**
  63  * @test
  64  * @key jfr
  65  * @summary Tests print --xml
  66  *
  67  * @library /lib /
  68  * @modules java.scripting java.xml jdk.jfr
  69  *
  70  * @run main/othervm jdk.jfr.tool.TestPrintXML
  71  */
  72 public class TestPrintXML {
  73 
  74     public static void main(String... args) throws Throwable {
  75 
  76         Path recordingFile = ExecuteHelper.createProfilingRecording().toAbsolutePath();
  77 
  78         OutputAnalyzer output = ExecuteHelper.jfr("print", "--xml", "--stack-depth", "9999", recordingFile.toString());
  79         System.out.println(recordingFile);
  80         String xml = output.getStdout();
  81 
  82         SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
  83         Schema schema = schemaFactory.newSchema(new File(System.getProperty("test.src"), "jfr.xsd"));
  84 
  85         SAXParserFactory factory = SAXParserFactory.newInstance();
  86         factory.setSchema(schema);
  87         factory.setNamespaceAware(true);
  88 
  89         SAXParser sp = factory.newSAXParser();
  90         XMLReader xr = sp.getXMLReader();
  91         RecordingHandler handler = new RecordingHandler();
  92         xr.setContentHandler(handler);
  93         xr.setErrorHandler(handler);
  94         xr.parse(new InputSource(new StringReader(xml)));
  95 
  96         // Verify that all data was written correctly
  97         List<RecordedEvent> events = RecordingFile.readAllEvents(recordingFile);
  98         Collections.sort(events, (e1, e2) -> e1.getEndTime().compareTo(e2.getEndTime()));
  99         Iterator<RecordedEvent> it = events.iterator();
 100         for (XMLEvent xmlEvent : handler.events) {
 101             RecordedEvent re = it.next();
 102             if (!compare(re, xmlEvent.values)) {
 103                 System.out.println("Expected:");
 104                 System.out.println("----------------------");
 105                 System.out.println(re);
 106                 System.out.println();
 107                 System.out.println("Was (XML)");
 108                 System.out.println("----------------------");
 109                 System.out.println(xmlEvent);
 110                 System.out.println();
 111                 throw new Exception("Event doesn't match");
 112             }
 113         }
 114 
 115     }
 116 
 117     @SuppressWarnings("unchecked")
 118     static boolean compare(Object eventObject, Object xmlObject) {
 119         if (eventObject == null) {
 120             return xmlObject == null;
 121         }
 122         if (eventObject instanceof RecordedObject) {
 123             RecordedObject re = (RecordedObject) eventObject;
 124             Map<String, Object> xmlMap = (Map<String, Object>) xmlObject;
 125             List<ValueDescriptor> fields = re.getFields();
 126             if (fields.size() != xmlMap.size()) {
 127                 return false;
 128             }
 129             for (ValueDescriptor v : fields) {
 130                 String name = v.getName();
 131                 Object xmlValue = xmlMap.get(name);
 132                 Object expectedValue = re.getValue(name);
 133                 if (v.getAnnotation(Timestamp.class) != null) {
 134                     // Make instant of OffsetDateTime
 135                     xmlValue = OffsetDateTime.parse("" + xmlValue).toInstant().toString();
 136                     expectedValue = re.getInstant(name);
 137                 }
 138                 if (v.getAnnotation(Timespan.class) != null) {
 139                     expectedValue = re.getDuration(name);
 140                 }
 141                 if (!compare(expectedValue, xmlValue)) {
 142                     return false;
 143                 }
 144             }
 145             return true;
 146         }
 147         if (eventObject.getClass().isArray()) {
 148             Object[] array = (Object[]) eventObject;
 149             Object[] xmlArray = (Object[]) xmlObject;
 150             if (array.length != xmlArray.length) {
 151                 return false;
 152             }
 153             for (int i = 0; i < array.length; i++) {
 154                 if (!compare(array[i], xmlArray[i])) {
 155                     return false;
 156                 }
 157             }
 158             return true;
 159         }
 160         String s1 = String.valueOf(eventObject);
 161         String s2 = (String) xmlObject;
 162         return s1.equals(s2);
 163     }
 164 
 165     static class XMLEvent {
 166         String name;
 167         private Map<String, Object> values = new HashMap<>();
 168 
 169         XMLEvent(String name) {
 170             this.name = name;
 171         }
 172     }
 173 
 174     public static final class RecordingHandler extends DefaultHandler {
 175 
 176         private Stack<Object> objects = new Stack<>();
 177         private Stack<SimpleEntry<String, String>> elements = new Stack<>();
 178         private List<XMLEvent> events = new ArrayList<>();
 179 
 180         @Override
 181         public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
 182             elements.push(new SimpleEntry<>(attrs.getValue("name"), attrs.getValue("index")));
 183             String nil = attrs.getValue("xsi:nil");
 184             if ("true".equals(nil)) {
 185                 objects.push(null);
 186                 return;
 187             }
 188 
 189             switch (qName) {
 190             case "event":
 191                 objects.push(new XMLEvent(attrs.getValue("type")));
 192                 break;
 193             case "struct":
 194                 objects.push(new HashMap<String, Object>());
 195                 break;
 196             case "array":
 197                 objects.push(new Object[Integer.parseInt(attrs.getValue("size"))]);
 198                 break;
 199             case "value":
 200                 objects.push(new StringBuilder());
 201                 break;
 202             }
 203         }
 204 
 205         @Override
 206         public void characters(char[] ch, int start, int length) throws SAXException {
 207             if (!objects.isEmpty()) {
 208                 Object o = objects.peek();
 209                 if (o instanceof StringBuilder) {
 210                     ((StringBuilder) o).append(ch, start, length);
 211                 }
 212             }
 213         }
 214 
 215         @SuppressWarnings("unchecked")
 216         @Override
 217         public void endElement(String uri, String localName, String qName) {
 218             SimpleEntry<String, String> element = elements.pop();
 219             switch (qName) {
 220             case "event":
 221             case "struct":
 222             case "array":
 223             case "value":
 224                 String name = element.getKey();
 225                 Object value = objects.pop();
 226                 if (objects.isEmpty()) {
 227                     events.add((XMLEvent) value);
 228                     return;
 229                 }
 230                 if (value instanceof StringBuilder) {
 231                     value = ((StringBuilder) value).toString();
 232                 }
 233                 Object parent = objects.peek();
 234                 if (parent instanceof XMLEvent) {
 235                     ((XMLEvent) parent).values.put(name, value);
 236                 }
 237                 if (parent instanceof Map) {
 238                     ((Map<String, Object>) parent).put(name, value);
 239                 }
 240                 if (parent != null && parent.getClass().isArray()) {
 241                     int index = Integer.parseInt(element.getValue());
 242                     ((Object[]) parent)[index] = value;
 243                 }
 244             }
 245         }
 246 
 247         public void warning(SAXParseException spe) throws SAXException {
 248             throw new SAXException(spe);
 249         }
 250 
 251         public void error(SAXParseException spe) throws SAXException {
 252             throw new SAXException(spe);
 253         }
 254 
 255         public void fatalError(SAXParseException spe) throws SAXException {
 256             throw new SAXException(spe);
 257         }
 258     }
 259 }