1 /*
   2  * Copyright (c) 2013, 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 package jdk.test.lib.jfr;
  26 
  27 import static jdk.test.lib.Asserts.assertEquals;
  28 import static jdk.test.lib.Asserts.assertFalse;
  29 import static jdk.test.lib.Asserts.assertNotNull;
  30 import static jdk.test.lib.Asserts.assertTrue;
  31 import static jdk.test.lib.Asserts.fail;
  32 
  33 import java.io.File;
  34 import java.io.IOException;
  35 import java.nio.file.Path;
  36 import java.util.List;
  37 import java.lang.management.ManagementFactory;
  38 
  39 import jdk.jfr.AnnotationElement;
  40 import jdk.jfr.EventType;
  41 import jdk.jfr.Recording;
  42 import jdk.jfr.SettingDescriptor;
  43 import jdk.jfr.ValueDescriptor;
  44 import jdk.jfr.consumer.RecordingFile;
  45 import jdk.test.lib.Asserts;
  46 import jdk.jfr.consumer.RecordedClass;
  47 import jdk.jfr.consumer.RecordedEvent;
  48 import jdk.jfr.consumer.RecordedObject;
  49 import jdk.jfr.consumer.RecordedThread;
  50 import jdk.jfr.consumer.RecordedThreadGroup;
  51 
  52 
  53 /**
  54  * Helper class to verify RecordedEvent content
  55  */
  56 public class Events {
  57 
  58     public static EventField assertField(RecordedEvent event, String name)  {
  59         String[] partNames = name.split("\\.");
  60         RecordedObject struct = event;
  61         try {
  62             for (int i=0; i<partNames.length; ++i) {
  63                 final String partName =  partNames[i];
  64                 final boolean isLastPart = i == partNames.length - 1;
  65                 ValueDescriptor d = getValueDescriptor(struct, partName);
  66                 if (isLastPart) {
  67                     return new EventField(struct, d);
  68                 } else {
  69                     assertTrue(struct.getValue(partName) instanceof RecordedObject, "Expected '" + partName + "' to be a struct");
  70                     struct = struct.getValue(partName);
  71                 }
  72             }
  73         } catch (Exception e) {
  74             e.printStackTrace();
  75         }
  76         System.out.printf("Failed event:%n%s%n", event.toString());
  77         fail(String.format("Field %s not in event", name));
  78         return null;
  79     }
  80 
  81     private static RecordedObject getRecordedPackage(final RecordedClass rc) {
  82         if (rc == null) {
  83             throw new RuntimeException("RecordedClass must not be null!");
  84         }
  85         return rc.getValue("package");
  86     }
  87 
  88     private static RecordedObject getRecordedModule(final RecordedObject pkg) {
  89         if (pkg == null) {
  90             // null package is an unnamed module (null)
  91             return null;
  92         }
  93 
  94         return pkg.getValue("module");
  95     }
  96     /**
  97      * Validates the recored name field
  98      *
  99      * @param ro should be a Package or a Module
 100      * @param targetName name to match
 101      */
 102     private static boolean isMatchingTargetName(final RecordedObject ro, final String targetName) {
 103         if (ro == null) {
 104             return targetName == null;
 105         }
 106 
 107         final String recordedName = ro.getValue("name");
 108 
 109         if (recordedName == null) {
 110             return targetName == null;
 111         }
 112 
 113         return recordedName.equals(targetName);
 114     }
 115 
 116     public static void assertClassPackage(final RecordedClass rc, final String packageNameTarget) {
 117         final RecordedObject recordedPackage = getRecordedPackage(rc);
 118 
 119         if (recordedPackage == null) {
 120             if (packageNameTarget != null) {
 121                 throw new RuntimeException("RecordedClass package is null!");
 122             }
 123             return;
 124         }
 125         assertTrue(isMatchingTargetName(recordedPackage, packageNameTarget), "mismatched package name! Target is " + packageNameTarget);
 126     }
 127 
 128     public static void assertClassModule(final RecordedClass rc, final String moduleNameTarget) {
 129         final RecordedObject recordedPackage = getRecordedPackage(rc);
 130         final RecordedObject recordedModule = getRecordedModule(recordedPackage);
 131 
 132         if (recordedModule == null) {
 133             if (moduleNameTarget != null) {
 134                 throw new RuntimeException("RecordedClass module is null!");
 135             }
 136             return;
 137         }
 138 
 139        assertTrue(isMatchingTargetName(recordedModule, moduleNameTarget), "mismatched module name! Target is " + moduleNameTarget);
 140     }
 141 
 142     private static ValueDescriptor getValueDescriptor(RecordedObject struct, String name) throws Exception {
 143         List<ValueDescriptor> valueDescriptors = struct.getFields();
 144         for (ValueDescriptor d : valueDescriptors) {
 145             if (name.equals(d.getName())) {
 146                 return d;
 147             }
 148         }
 149         System.out.printf("Failed struct:%s", struct.toString());
 150         fail(String.format("Field %s not in struct", name));
 151         return null;
 152     }
 153 
 154     public static void hasEvents(List<RecordedEvent> events) {
 155         assertFalse(events.isEmpty(), "No events");
 156     }
 157 
 158     public static void hasEvents(RecordingFile file) {
 159         assertTrue(file.hasMoreEvents(), "No events");
 160     }
 161 
 162     public static void assertEventThread(RecordedEvent event) {
 163         RecordedThread eventThread = event.getThread();
 164         if (eventThread == null) {
 165             System.out.printf("Failed event:%n%s%n", event.toString());
 166             fail("No thread in event");
 167         }
 168     }
 169 
 170     public static void assertJavaMethod(RecordedEvent event) {
 171         assertField(event, "method.name").notEmpty();
 172         assertField(event, "method.descriptor").notEmpty();
 173         assertField(event, "method.modifiers").atLeast(0);
 174         assertField(event, "method.hidden");
 175         assertField(event, "method.type.name").notEmpty();
 176         assertField(event, "method.type.modifiers").atLeast(0);
 177     }
 178 
 179     public static void assertEventThread(RecordedEvent event, Thread thread) {
 180         assertThread(event.getThread(), thread);
 181     }
 182 
 183     public static void assertEventThread(RecordedEvent event, String structName, Thread thread) {
 184         assertThread(assertField(event, structName).notNull().getValue(), thread);
 185     }
 186 
 187     private static void assertThread(RecordedThread eventThread, Thread thread) {
 188         assertNotNull(eventThread, "Thread in event was null");
 189         assertEquals(eventThread.getJavaThreadId(), thread.getId(), "Wrong thread id");
 190         assertEquals(eventThread.getJavaName(), thread.getName(), "Wrong thread name");
 191 
 192         ThreadGroup threadGroup = thread.getThreadGroup();
 193         RecordedThreadGroup eventThreadGroup = eventThread.getThreadGroup();
 194         assertNotNull(eventThreadGroup, "eventThreadGroup was null");
 195 
 196         // Iterate and check all threadGroups
 197         while (eventThreadGroup != null) {
 198             final String groupName = eventThreadGroup.getName();
 199             if (threadGroup != null) {
 200                 assertEquals(groupName, threadGroup.getName(), "Wrong threadGroup name");
 201                 threadGroup = threadGroup.getParent();
 202             } else {
 203                 assertNotNull(groupName, "threadGroup name was null");
 204                 assertFalse(groupName.isEmpty(), "threadGroup name was empty");
 205             }
 206             eventThreadGroup = eventThreadGroup.getParent();
 207         }
 208     }
 209 
 210     public static boolean hasField(RecordedEvent event, String name) {
 211         return event.getFields().stream().map(vd -> vd.getName()).anyMatch(s -> s.equals(name));
 212     }
 213 
 214     public static boolean isEventType(RecordedEvent event, String typeName) {
 215         return typeName.equals(event.getEventType().getName());
 216     }
 217 
 218 
 219     /**
 220      * Creates a list of events from a recording.
 221      *
 222      * @param recording recording, not {@code null}
 223      * @return an a list, not null
 224      * @throws IOException if an event set could not be created due to I/O
 225      *         errors.
 226      */
 227     public static List<RecordedEvent> fromRecording(Recording recording) throws IOException {
 228         return RecordingFile.readAllEvents(makeCopy(recording));
 229     }
 230 
 231     public static RecordingFile copyTo(Recording r) throws IOException {
 232         return new RecordingFile(makeCopy(r));
 233     }
 234    
 235     private static String getProcessId(final String fallback) {
 236         // Note: may fail in some JVM implementations
 237         // therefore fallback has to be provided
 238 
 239         // something like '<pid>@<hostname>', at least in SUN / Oracle JVMs
 240        final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
 241 
 242        final int index = jvmName.indexOf('@');
 243 
 244         if (index < 1) {
 245             // part before '@' empty (index = 0) / '@' not found (index = -1)
 246             return fallback;
 247         }
 248 
 249         try {
 250             return Long.toString(Long.parseLong(jvmName.substring(0, index)));
 251         } catch (NumberFormatException e) {
 252             // ignore
 253         }
 254         return fallback;
 255     }
 256         
 257     private static Path makeCopy(Recording recording) throws IOException {
 258         Path p = recording.getDestination();
 259         if (p == null) {
 260             File directory = new File(".");
 261             // FIXME: Must come up with a way to give human-readable name
 262             // this will at least not clash when running parallel.
 263             //ProcessHandle h = ProcessHandle.current();
 264             //p = new File(directory.getAbsolutePath(), "recording-" + recording.getId() + "-pid" + h.pid() + ".jfr").toPath();
 265             p = new File(directory.getAbsolutePath(), "recording-" + recording.getId() + "-pid" + getProcessId("666") + ".jfr").toPath();
 266             recording.dump(p);
 267         }
 268         return p;
 269     }
 270 
 271    public static void hasAnnotation(ValueDescriptor field, Class<? extends java.lang.annotation.Annotation> annotationClass) throws Exception {
 272        AnnotationElement a = getAnnotation(field, annotationClass);
 273        if (a == null) {
 274            throw new Exception("Expected " + annotationClass.getSimpleName() + " on field " + field.getName());
 275        }
 276    }
 277 
 278    public static void assertAnnotation(ValueDescriptor field, Class<? extends java.lang.annotation.Annotation> annotationClass, String value) throws Exception {
 279        AnnotationElement a = getAnnotation(field, annotationClass);
 280        Object v = a.getValue("value");
 281        if (!v.equals(value)) {
 282            throw new Exception("Expected " + annotationClass.getSimpleName() + " on field " + field.getName() + " to have value " + value + ", but got " + v);
 283        }
 284    }
 285 
 286    // candidate for moving into API
 287    public static AnnotationElement getAnnotation(ValueDescriptor v, Class<?> clazz) throws Exception {
 288       for (AnnotationElement a : v.getAnnotationElements()) {
 289           if (a.getTypeName().equals(clazz.getName())) {
 290               return a;
 291           }
 292       }
 293 
 294       throw new Exception("Could not find annotation " + clazz.getName());
 295   }
 296 
 297    // candidate for moving into API
 298    public static AnnotationElement getAnnotationByName(EventType t, String name) throws Exception {
 299        for (AnnotationElement a : t.getAnnotationElements()) {
 300            if (a.getTypeName().equals(name)) {
 301                return a;
 302            }
 303        }
 304        throw new Exception("Could not find annotation '" + name + " in type " + t.getName());
 305    }
 306 
 307     // candidate for moving into API
 308     public static SettingDescriptor getSetting(EventType type, String name) {
 309         for (SettingDescriptor s : type.getSettingDescriptors()) {
 310             if (s.getName().equals(name)) {
 311                 return s;
 312             }
 313         }
 314         throw new IllegalArgumentException("Could not setting with name " + name);
 315     }
 316 
 317     public static void hasEvent(Recording r, String name) throws IOException {
 318         List<RecordedEvent> events = fromRecording(r);
 319         Events.hasEvents(events);
 320         Events.hasEvent(events, name);
 321     }
 322 
 323     public static void hasEvent(List<RecordedEvent> events, String name) throws IOException {
 324         if (!containsEvent(events, name)) {
 325             Asserts.fail("Missing event " + name  + " in recording " + events.toString());
 326         }
 327     }
 328 
 329     public static void hasNotEvent(List<RecordedEvent> events, String name) throws IOException {
 330         if (containsEvent(events, name)) {
 331             Asserts.fail("Rercording should not contain event " + name  + " " + events.toString());
 332         }
 333     }
 334 
 335     private static boolean containsEvent(List<RecordedEvent> events, String name) {
 336         for (RecordedEvent event : events) {
 337             if (event.getEventType().getName().equals(name)) {
 338                 return true;
 339             }
 340         }
 341         return false;
 342     }
 343 }