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