1 /*
   2  * Copyright (c) 2013, 2019, 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.testlibrary.jfr;
  26 
  27 import static jdk.testlibrary.Asserts.assertEquals;
  28 import static jdk.testlibrary.Asserts.assertFalse;
  29 import static jdk.testlibrary.Asserts.assertNotNull;
  30 import static jdk.testlibrary.Asserts.assertTrue;
  31 import static jdk.testlibrary.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 
  38 import jdk.jfr.AnnotationElement;
  39 import jdk.jfr.EventType;
  40 import jdk.jfr.Recording;
  41 import jdk.jfr.SettingDescriptor;
  42 import jdk.jfr.ValueDescriptor;
  43 import jdk.jfr.consumer.RecordingFile;
  44 import jdk.testlibrary.Asserts;
  45 import jdk.testlibrary.ProcessTools;
  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 Path makeCopy(Recording recording) throws IOException {
 236         Path p = recording.getDestination();
 237         int pid = 0;
 238         try {
 239             pid = ProcessTools.getProcessId();
 240         } catch (Exception e) {
 241             //do nothing, let's use 0
 242         }
 243 
 244         if (p == null) {
 245             File directory = new File(".");
 246             // FIXME: Must come up with a way to give human-readable name
 247             // this will at least not clash when running parallel.
 248             p = new File(directory.getAbsolutePath(), "recording-" + recording.getId() + "-pid" + pid + ".jfr").toPath();
 249             recording.dump(p);
 250         }
 251         return p;
 252     }
 253 
 254    public static void hasAnnotation(ValueDescriptor field, Class<? extends java.lang.annotation.Annotation> annotationClass) throws Exception {
 255        AnnotationElement a = getAnnotation(field, annotationClass);
 256        if (a == null) {
 257            throw new Exception("Expected " + annotationClass.getSimpleName() + " on field " + field.getName());
 258        }
 259    }
 260 
 261    public static void assertAnnotation(ValueDescriptor field, Class<? extends java.lang.annotation.Annotation> annotationClass, String value) throws Exception {
 262        AnnotationElement a = getAnnotation(field, annotationClass);
 263        Object v = a.getValue("value");
 264        if (!v.equals(value)) {
 265            throw new Exception("Expected " + annotationClass.getSimpleName() + " on field " + field.getName() + " to have value " + value + ", but got " + v);
 266        }
 267    }
 268 
 269    // candidate for moving into API
 270    public static AnnotationElement getAnnotation(ValueDescriptor v, Class<?> clazz) throws Exception {
 271       for (AnnotationElement a : v.getAnnotationElements()) {
 272           if (a.getTypeName().equals(clazz.getName())) {
 273               return a;
 274           }
 275       }
 276 
 277       throw new Exception("Could not find annotation " + clazz.getName());
 278   }
 279 
 280    // candidate for moving into API
 281    public static AnnotationElement getAnnotationByName(EventType t, String name) throws Exception {
 282        for (AnnotationElement a : t.getAnnotationElements()) {
 283            if (a.getTypeName().equals(name)) {
 284                return a;
 285            }
 286        }
 287        throw new Exception("Could not find annotation '" + name + " in type " + t.getName());
 288    }
 289 
 290     // candidate for moving into API
 291     public static SettingDescriptor getSetting(EventType type, String name) {
 292         for (SettingDescriptor s : type.getSettingDescriptors()) {
 293             if (s.getName().equals(name)) {
 294                 return s;
 295             }
 296         }
 297         throw new IllegalArgumentException("Could not setting with name " + name);
 298     }
 299 
 300     public static void hasEvent(Recording r, String name) throws IOException {
 301         List<RecordedEvent> events = fromRecording(r);
 302         Events.hasEvents(events);
 303         Events.hasEvent(events, name);
 304     }
 305 
 306     public static void hasEvent(List<RecordedEvent> events, String name) throws IOException {
 307         if (!containsEvent(events, name)) {
 308             Asserts.fail("Missing event " + name  + " in recording " + events.toString());
 309         }
 310     }
 311 
 312     public static void hasNotEvent(List<RecordedEvent> events, String name) throws IOException {
 313         if (containsEvent(events, name)) {
 314             Asserts.fail("Rercording should not contain event " + name  + " " + events.toString());
 315         }
 316     }
 317 
 318     private static boolean containsEvent(List<RecordedEvent> events, String name) {
 319         for (RecordedEvent event : events) {
 320             if (event.getEventType().getName().equals(name)) {
 321                 return true;
 322             }
 323         }
 324         return false;
 325     }
 326 }