1 /* 2 * Copyright 2019 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.perfmark.agent; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.junit.Assert.assertEquals; 21 22 import com.google.common.truth.Truth; 23 import io.perfmark.PerfMark; 24 import io.perfmark.TaskCloseable; 25 import io.perfmark.impl.Mark; 26 import io.perfmark.impl.Storage; 27 import java.io.Closeable; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.lang.annotation.ElementType; 31 import java.lang.annotation.Target; 32 import java.lang.reflect.Constructor; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map; 36 import org.junit.Ignore; 37 import org.junit.Test; 38 import org.junit.runner.RunWith; 39 import org.junit.runners.JUnit4; 40 41 @RunWith(JUnit4.class) 42 public class PerfMarkTransformerTest { 43 44 @Test deriveFileName()45 public void deriveFileName() { 46 String file = PerfMarkTransformer.deriveFileName("io/perfmark/Clz"); 47 48 assertEquals("Clz.java", file); 49 } 50 51 @Test deriveFileName_innerClass()52 public void deriveFileName_innerClass() { 53 String file = PerfMarkTransformer.deriveFileName("io/perfmark/Clz$Inner"); 54 55 assertEquals("Clz.java", file); 56 } 57 58 @Test 59 @Ignore transform_autoAnnotate()60 public void transform_autoAnnotate() throws Exception { 61 // This test currently depends on the transformer treating this test class specially. 62 PerfMark.setEnabled(true); 63 Storage.clearLocalStorage(); 64 65 Class<?> clz = transformAndLoad(TransformerTestClasses.ClzAutoRecord.class); 66 Constructor<?> ctor = clz.getConstructor(); 67 ctor.setAccessible(true); 68 ctor.newInstance(); 69 List<Mark> marks = Storage.readForTest(); 70 assertThat(marks).hasSize(2); 71 } 72 73 @Test transform_record()74 public void transform_record() throws Exception { 75 PerfMark.setEnabled(true); 76 Storage.clearLocalStorage(); 77 78 Class<?> clz = transformAndLoad(TransformerTestClasses.SomeRecord.class); 79 Constructor<?> ctor = clz.getConstructor(int.class); 80 ctor.setAccessible(true); 81 ctor.newInstance(2); 82 List<Mark> marks = Storage.readForTest(); 83 assertThat(marks).hasSize(4); 84 assertEquals(marks.get(0).withTaskName("task"), marks.get(0)); 85 86 assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite"); 87 Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$SomeRecord"); 88 Truth.assertThat(marks.get(1).getTagStringValue()).contains("<init>"); 89 90 assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite"); 91 Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$SomeRecord"); 92 Truth.assertThat(marks.get(2).getTagStringValue()).contains("<init>"); 93 94 assertEquals(marks.get(3).withTaskName("task"), marks.get(3)); 95 } 96 97 @Test transform_lambda()98 public void transform_lambda() throws Exception { 99 PerfMark.setEnabled(true); 100 Storage.clearLocalStorage(); 101 102 Class<?> clz = transformAndLoad(TransformerTestClasses.ClzCtorLambda.class); 103 Constructor<?> ctor = clz.getConstructor(); 104 ctor.setAccessible(true); 105 ctor.newInstance(); 106 List<Mark> marks = Storage.readForTest(); 107 assertThat(marks).hasSize(4); 108 assertEquals(marks.get(0).withTaskName("task"), marks.get(0)); 109 110 assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite"); 111 Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzCtorLambda"); 112 Truth.assertThat(marks.get(1).getTagStringValue()).contains("lambda"); 113 114 assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite"); 115 Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$ClzCtorLambda"); 116 Truth.assertThat(marks.get(2).getTagStringValue()).contains("lambda"); 117 118 assertEquals(marks.get(3).withTaskName("task"), marks.get(3)); 119 } 120 121 @Test transform_methodRef()122 public void transform_methodRef() throws Exception { 123 PerfMark.setEnabled(true); 124 Storage.clearLocalStorage(); 125 126 Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithMethodRefs.class); 127 Constructor<?> ctor = clz.getConstructor(); 128 ctor.setAccessible(true); 129 ctor.newInstance(); 130 List<Mark> marks = Storage.readForTest(); 131 assertThat(marks).hasSize(2); 132 // I'm not sure what to do with methodrefs, so just leave it alone for now. 133 } 134 135 @Test transform_interface()136 public void transform_interface() throws Exception { 137 PerfMark.setEnabled(true); 138 Storage.clearLocalStorage(); 139 140 Class<?> clz = 141 transformAndLoad(TransformerTestClasses.Bar.class, TransformerTestClasses.InterfaceWithDefaults.class); 142 Constructor<?> ctor = clz.getConstructor(); 143 ctor.setAccessible(true); 144 ctor.newInstance(); 145 146 List<Mark> marks = Storage.readForTest(); 147 assertThat(marks).hasSize(10); 148 149 assertEquals(marks.get(0).withTaskName("task"), marks.get(0)); 150 151 assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite"); 152 Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$InterfaceWithDefaults"); 153 Truth.assertThat(marks.get(1).getTagStringValue()).contains("record"); 154 155 assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite"); 156 Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$InterfaceWithDefaults"); 157 Truth.assertThat(marks.get(2).getTagStringValue()).contains("record"); 158 159 assertEquals(marks.get(3).withTaskName("task"), marks.get(3)); 160 161 assertEquals(marks.get(4).withTaskName("task"), marks.get(4)); 162 163 // Ignore the regular tag at 5 164 165 assertEquals(marks.get(6).getTagKey(), "PerfMark.startCallSite"); 166 Truth.assertThat(marks.get(6).getTagStringValue()).contains("TransformerTestClasses$InterfaceWithDefaults"); 167 Truth.assertThat(marks.get(6).getTagStringValue()).contains("record"); 168 169 assertEquals(marks.get(7).getTagKey(), "PerfMark.stopCallSite"); 170 Truth.assertThat(marks.get(7).getTagStringValue()).contains("TransformerTestClasses$InterfaceWithDefaults"); 171 Truth.assertThat(marks.get(7).getTagStringValue()).contains("record"); 172 173 // Ignore the regular tag at 8 174 175 assertEquals(marks.get(9).withTaskName("task"), marks.get(9)); 176 } 177 178 @Test transform_link()179 public void transform_link() throws Exception { 180 PerfMark.setEnabled(true); 181 Storage.clearLocalStorage(); 182 183 Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithLinks.class); 184 Constructor<?> ctor = clz.getConstructor(); 185 ctor.setAccessible(true); 186 ctor.newInstance(); 187 List<Mark> marks = Storage.readForTest(); 188 assertThat(marks).hasSize(6); 189 190 assertEquals(marks.get(0).withTaskName("task"), marks.get(0)); 191 192 assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite"); 193 Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithLinks"); 194 Truth.assertThat(marks.get(1).getTagStringValue()).contains("init"); 195 196 // assume links have not been modified 197 198 assertEquals(marks.get(4).getTagKey(), "PerfMark.stopCallSite"); 199 Truth.assertThat(marks.get(4).getTagStringValue()).contains("TransformerTestClasses$ClzWithLinks"); 200 Truth.assertThat(marks.get(4).getTagStringValue()).contains("init"); 201 202 assertEquals(marks.get(5).withTaskName("task"), marks.get(5)); 203 } 204 205 @Test transform_closeable()206 public void transform_closeable() throws Exception { 207 PerfMark.setEnabled(true); 208 Storage.clearLocalStorage(); 209 210 Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithCloseable.class); 211 Constructor<?> ctor = clz.getConstructor(); 212 ctor.setAccessible(true); 213 ctor.newInstance(); 214 List<Mark> marks = Storage.readForTest(); 215 assertThat(marks).hasSize(4); 216 217 assertEquals(marks.get(0).withTaskName("task"), marks.get(0)); 218 219 assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite"); 220 Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithCloseable"); 221 Truth.assertThat(marks.get(1).getTagStringValue()).contains("init"); 222 223 assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite"); 224 Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$ClzWithCloseable"); 225 Truth.assertThat(marks.get(2).getTagStringValue()).contains("init"); 226 227 assertEquals(Mark.Operation.TASK_END_N1S0, marks.get(3).getOperation()); 228 } 229 230 @Test transform_wrongCloseable()231 public void transform_wrongCloseable() throws Exception { 232 // If the wrong static type is used, the agent won't be able to instrument it. Add a test to document this 233 // behavior. 234 PerfMark.setEnabled(true); 235 Storage.clearLocalStorage(); 236 237 Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithWrongCloseable.class); 238 Constructor<?> ctor = clz.getConstructor(); 239 ctor.setAccessible(true); 240 ctor.newInstance(); 241 List<Mark> marks = Storage.readForTest(); 242 assertThat(marks).hasSize(3); 243 244 assertEquals(marks.get(0).withTaskName("task"), marks.get(0)); 245 246 assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite"); 247 Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithWrongCloseable"); 248 Truth.assertThat(marks.get(1).getTagStringValue()).contains("init"); 249 250 assertEquals(Mark.Operation.TASK_END_N1S0, marks.get(2).getOperation()); 251 } 252 253 @Test transform_wrongCloseable_autoCloseable()254 public void transform_wrongCloseable_autoCloseable() throws Exception { 255 // If the wrong static type is used, the agent won't be able to instrument it. Add a test to document this 256 // behavior. 257 PerfMark.setEnabled(true); 258 Storage.clearLocalStorage(); 259 260 Class<? extends Closeable> clz = transformAndLoad(TaskCloseable.class).asSubclass(Closeable.class); 261 Constructor<? extends Closeable> ctor = clz.getDeclaredConstructor(); 262 ctor.setAccessible(true); 263 Closeable closeable = ctor.newInstance(); 264 closeable.close(); 265 List<Mark> marks = Storage.readForTest(); 266 assertThat(marks).hasSize(1); 267 268 assertEquals(Mark.Operation.TASK_END_N1S0, marks.get(0).getOperation()); 269 } 270 271 @Test transform_ctor()272 public void transform_ctor() throws Exception { 273 PerfMark.setEnabled(true); 274 Storage.clearLocalStorage(); 275 276 Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithCtor.class); 277 Constructor<?> ctor = clz.getConstructor(); 278 ctor.setAccessible(true); 279 ctor.newInstance(); 280 List<Mark> marks = Storage.readForTest(); 281 assertThat(marks).hasSize(10); 282 283 assertEquals(marks.get(0).withTaskName("task"), marks.get(0)); 284 285 assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite"); 286 Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithCtor"); 287 Truth.assertThat(marks.get(1).getTagStringValue()).contains("init"); 288 289 assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite"); 290 Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$ClzWithCtor"); 291 Truth.assertThat(marks.get(2).getTagStringValue()).contains("init"); 292 293 assertEquals(marks.get(3).withTaskName("task"), marks.get(3)); 294 295 assertEquals(marks.get(4).withTaskName("task"), marks.get(4)); 296 297 // Ignore the regular tag at 5 298 299 assertEquals(marks.get(6).getTagKey(), "PerfMark.startCallSite"); 300 Truth.assertThat(marks.get(6).getTagStringValue()).contains("TransformerTestClasses$ClzWithCtor"); 301 Truth.assertThat(marks.get(6).getTagStringValue()).contains("init"); 302 303 assertEquals(marks.get(7).getTagKey(), "PerfMark.stopCallSite"); 304 Truth.assertThat(marks.get(7).getTagStringValue()).contains("TransformerTestClasses$ClzWithCtor"); 305 Truth.assertThat(marks.get(7).getTagStringValue()).contains("init"); 306 307 // Ignore the regular tag at 8 308 309 assertEquals(marks.get(9).withTaskName("task"), marks.get(9)); 310 } 311 312 @Test transform_init()313 public void transform_init() throws Exception { 314 PerfMark.setEnabled(true); 315 Storage.clearLocalStorage(); 316 317 Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithInit.class); 318 Constructor<?> ctor = clz.getDeclaredConstructor(); 319 ctor.setAccessible(true); 320 ctor.newInstance(); 321 List<Mark> marks = Storage.readForTest(); 322 assertThat(marks).hasSize(10); 323 324 assertEquals(marks.get(0).withTaskName("task"), marks.get(0)); 325 326 assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite"); 327 Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithInit"); 328 Truth.assertThat(marks.get(1).getTagStringValue()).contains("init"); 329 330 assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite"); 331 Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$ClzWithInit"); 332 Truth.assertThat(marks.get(2).getTagStringValue()).contains("init"); 333 334 assertEquals(marks.get(3).withTaskName("task"), marks.get(3)); 335 336 assertEquals(marks.get(4).withTaskName("task"), marks.get(4)); 337 338 // Ignore the regular tag at 5 339 340 assertEquals(marks.get(6).getTagKey(), "PerfMark.startCallSite"); 341 Truth.assertThat(marks.get(6).getTagStringValue()).contains("TransformerTestClasses$ClzWithInit"); 342 Truth.assertThat(marks.get(6).getTagStringValue()).contains("init"); 343 344 assertEquals(marks.get(7).getTagKey(), "PerfMark.stopCallSite"); 345 Truth.assertThat(marks.get(7).getTagStringValue()).contains("TransformerTestClasses$ClzWithInit"); 346 Truth.assertThat(marks.get(7).getTagStringValue()).contains("init"); 347 348 // Ignore the regular tag at 8 349 350 assertEquals(marks.get(9).withTaskName("task"), marks.get(9)); 351 } 352 353 @Test transform_clinit()354 public void transform_clinit() throws Exception { 355 PerfMark.setEnabled(true); 356 Storage.clearLocalStorage(); 357 358 Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithClinit.class); 359 Constructor<?> ctor = clz.getDeclaredConstructor(); 360 ctor.setAccessible(true); 361 ctor.newInstance(); 362 List<Mark> marks = Storage.readForTest(); 363 assertThat(marks).hasSize(10); 364 365 assertEquals(marks.get(0).withTaskName("task"), marks.get(0)); 366 367 assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite"); 368 Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithClinit"); 369 Truth.assertThat(marks.get(1).getTagStringValue()).contains("clinit"); 370 371 assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite"); 372 Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$ClzWithClinit"); 373 Truth.assertThat(marks.get(2).getTagStringValue()).contains("clinit"); 374 375 assertEquals(marks.get(3).withTaskName("task"), marks.get(3)); 376 377 assertEquals(marks.get(4).withTaskName("task"), marks.get(4)); 378 379 // Ignore the regular tag at 5 380 381 assertEquals(marks.get(6).getTagKey(), "PerfMark.startCallSite"); 382 Truth.assertThat(marks.get(6).getTagStringValue()).contains("TransformerTestClasses$ClzWithClinit"); 383 Truth.assertThat(marks.get(6).getTagStringValue()).contains("clinit"); 384 385 assertEquals(marks.get(7).getTagKey(), "PerfMark.stopCallSite"); 386 Truth.assertThat(marks.get(7).getTagStringValue()).contains("TransformerTestClasses$ClzWithClinit"); 387 Truth.assertThat(marks.get(7).getTagStringValue()).contains("clinit"); 388 389 // Ignore the regular tag at 8 390 391 assertEquals(marks.get(9).withTaskName("task"), marks.get(9)); 392 } 393 394 @Test transform_toplevel()395 public void transform_toplevel() throws Exception { 396 PerfMark.setEnabled(true); 397 Storage.clearLocalStorage(); 398 399 Class<?> clz = transformAndLoad(ClzFooter.class); 400 Constructor<?> ctor = clz.getDeclaredConstructor(); 401 ctor.setAccessible(true); 402 ctor.newInstance(); 403 List<Mark> marks = Storage.readForTest(); 404 assertThat(marks).hasSize(10); 405 406 assertEquals(marks.get(0).withTaskName("task"), marks.get(0)); 407 408 assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite"); 409 Truth.assertThat(marks.get(1).getTagStringValue()).contains("ClzFooter"); 410 Truth.assertThat(marks.get(1).getTagStringValue()).contains("init"); 411 412 assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite"); 413 Truth.assertThat(marks.get(2).getTagStringValue()).contains("ClzFooter"); 414 Truth.assertThat(marks.get(2).getTagStringValue()).contains("init"); 415 416 assertEquals(marks.get(3).withTaskName("task"), marks.get(3)); 417 418 assertEquals(marks.get(4).withTaskName("task"), marks.get(4)); 419 420 // Ignore the regular tag at 5 421 422 assertEquals(marks.get(6).getTagKey(), "PerfMark.startCallSite"); 423 Truth.assertThat(marks.get(6).getTagStringValue()).contains("ClzFooter"); 424 Truth.assertThat(marks.get(6).getTagStringValue()).contains("init"); 425 426 assertEquals(marks.get(7).getTagKey(), "PerfMark.stopCallSite"); 427 Truth.assertThat(marks.get(7).getTagStringValue()).contains("ClzFooter"); 428 Truth.assertThat(marks.get(7).getTagStringValue()).contains("init"); 429 430 // Ignore the regular tag at 8 431 432 assertEquals(marks.get(9).withTaskName("task"), marks.get(9)); 433 } 434 435 @Test transform_anonymousClass()436 public void transform_anonymousClass() throws Exception { 437 PerfMark.setEnabled(true); 438 Storage.clearLocalStorage(); 439 440 Class<?> clz = transformAndLoad(new Runnable() { 441 // avoid IntelliJ thinking this should be a lambda. 442 public volatile int a; 443 @Override 444 public void run() { 445 PerfMark.startTask("task"); 446 PerfMark.stopTask("task"); 447 } 448 }.getClass()); 449 Constructor<?> ctor = clz.getDeclaredConstructor(PerfMarkTransformerTest.class); 450 ctor.setAccessible(true); 451 Runnable instance = (Runnable) ctor.newInstance(this); 452 instance.run(); 453 List<Mark> marks = Storage.readForTest(); 454 assertThat(marks).hasSize(4); 455 456 assertEquals(marks.get(0).withTaskName("task"), marks.get(0)); 457 458 assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite"); 459 Truth.assertThat(marks.get(1).getTagStringValue()).contains("PerfMarkTransformerTest$"); 460 Truth.assertThat(marks.get(1).getTagStringValue()).contains("run"); 461 462 assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite"); 463 Truth.assertThat(marks.get(2).getTagStringValue()).contains("PerfMarkTransformerTest$"); 464 Truth.assertThat(marks.get(2).getTagStringValue()).contains("run"); 465 466 assertEquals(marks.get(3).withTaskName("task"), marks.get(3)); 467 } 468 transformAndLoad(Class<?> toLoad, Class<?> ...extra)469 private static Class<?> transformAndLoad(Class<?> toLoad, Class<?> ...extra) throws IOException { 470 Map<String, Class<?>> toTransform = new HashMap<>(); 471 for (Class<?> clz : extra) { 472 toTransform.put(clz.getName(), clz); 473 } 474 toTransform.put(toLoad.getName(), toLoad); 475 try { 476 return new ClassLoader(toLoad.getClassLoader()) { 477 478 @Override 479 protected Class<?> loadClass(String binaryClassName, boolean resolve) throws ClassNotFoundException { 480 Class<?> existing = toTransform.get(binaryClassName); 481 if (existing == null) { 482 return super.loadClass(binaryClassName, resolve); 483 } 484 String internalFullyQualifiedClassName = binaryClassName.replace('.', '/'); 485 String resourceName = internalFullyQualifiedClassName + ".class"; 486 byte[] data; 487 try (InputStream stream = getResourceAsStream(resourceName)) { 488 data = stream.readAllBytes(); 489 } catch (IOException e) { 490 throw new RuntimeException(e); 491 } 492 byte[] newClassBytes = 493 new PerfMarkTransformer(null).transformInternal( 494 this, internalFullyQualifiedClassName, existing, null, data); 495 if (newClassBytes == null) { 496 newClassBytes = data; 497 } 498 Class<?> newClass = defineClass(binaryClassName, newClassBytes, 0, newClassBytes.length); 499 if (resolve) { 500 resolveClass(newClass); 501 } 502 return newClass; 503 } 504 }.loadClass(toLoad.getName()); 505 } catch (ClassNotFoundException e) { 506 throw new RuntimeException(e); 507 } 508 } 509 } 510