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