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.impl; 18 19 import io.perfmark.Impl; 20 import io.perfmark.Link; 21 import io.perfmark.StringFunction; 22 import io.perfmark.Tag; 23 import java.util.concurrent.atomic.AtomicLong; 24 import java.util.logging.Level; 25 import java.util.logging.LogRecord; 26 import java.util.logging.Logger; 27 import javax.annotation.Nullable; 28 29 final class SecretPerfMarkImpl { 30 31 public static final class PerfMarkImpl extends Impl { 32 private static final int ENABLED_BIT_SPACE = 2; 33 private static final int GEN_TIMESTAMP_SPACE = 54; 34 private static final long MAX_MIBROS = (1L << GEN_TIMESTAMP_SPACE) - 1; 35 private static final Tag NO_TAG = packTag(Mark.NO_TAG_NAME, Mark.NO_TAG_ID); 36 private static final Link NO_LINK = packLink(Mark.NO_LINK_ID); 37 private static final long INCREMENT = 1L << Generator.GEN_OFFSET; 38 39 private static final AtomicLong linkIdAlloc = new AtomicLong(1); 40 private static final Generator generator; 41 42 // May be null if debugging is disabled. 43 private static final Object logger; 44 45 /** 46 * This is the generation of the recorded tasks. The bottom 8 bits [0-7] are reserved for opcode packing. 47 * Bit 9 [8] is used for detecting if PerfMark is enabled or not. Bit 10 [9] is unused. Bits 11-64 [10-647] 48 * are used for storing the time since Perfmark Was last / enabled or disabled. The units are in nanoseconds/1024, 49 * or (inaccurately) called mibros (like micros, but power of 2 based). 50 */ 51 private static long actualGeneration; 52 53 static { 54 assert ENABLED_BIT_SPACE + Generator.GEN_OFFSET + GEN_TIMESTAMP_SPACE <= 64; 55 Generator gen = null; 56 Throwable[] problems = new Throwable[4]; 57 // Avoid using a for-loop for this code, as it makes it easier for tools like Proguard to rewrite. 58 try { 59 Class<?> clz = Class.forName("io.perfmark.java7.SecretMethodHandleGenerator$MethodHandleGenerator"); 60 gen = clz.asSubclass(Generator.class).getConstructor().newInstance(); 61 } catch (Throwable t) { 62 problems[0] = t; 63 } 64 if (gen == null) { 65 try { 66 Class<?> clz = Class.forName("io.perfmark.java9.SecretVarHandleGenerator$VarHandleGenerator"); 67 gen = clz.asSubclass(Generator.class).getConstructor().newInstance(); 68 } catch (Throwable t) { 69 problems[1] = t; 70 } 71 } 72 if (gen == null) { 73 try { 74 Class<?> clz = Class.forName("io.perfmark.java6.SecretVolatileGenerator$VolatileGenerator"); 75 gen = clz.asSubclass(Generator.class).getConstructor().newInstance(); 76 } catch (Throwable t) { 77 problems[2] = t; 78 } 79 } 80 if (gen == null) { 81 generator = new NoopGenerator(); 82 } else { 83 generator = gen; 84 } 85 86 boolean startEnabled = false; 87 boolean startEnabledSuccess = false; 88 try { 89 if ((startEnabled = Boolean.getBoolean("io.perfmark.PerfMark.startEnabled"))) { 90 startEnabledSuccess = setEnabledQuiet(startEnabled, Generator.INIT_NANO_TIME); 91 } 92 } catch (Throwable t) { 93 problems[3] = t; 94 } 95 96 Object log = null; 97 try { 98 if (Boolean.getBoolean("io.perfmark.PerfMark.debug")) { 99 Logger localLogger = Logger.getLogger(PerfMarkImpl.class.getName()); 100 log = localLogger; 101 for (Throwable problem : problems) { 102 if (problem == null) { 103 continue; 104 } localLogger.log(Level.FINE, "Error loading Generator", problem)105 localLogger.log(Level.FINE, "Error loading Generator", problem); 106 } localLogger.log(Level.FINE, "Using {0}", new Object[] {generator.getClass().getName()})107 localLogger.log(Level.FINE, "Using {0}", new Object[] {generator.getClass().getName()}); logEnabledChange(startEnabled, startEnabledSuccess)108 logEnabledChange(startEnabled, startEnabledSuccess); 109 } 110 } catch (Throwable t) { 111 // ignore 112 } 113 logger = log; 114 } 115 PerfMarkImpl(Tag key)116 public PerfMarkImpl(Tag key) { 117 super(key); 118 } 119 120 @Override setEnabled(boolean value)121 protected synchronized void setEnabled(boolean value) { 122 boolean changed = setEnabledQuiet(value, System.nanoTime()); 123 logEnabledChange(value, changed); 124 } 125 126 @Override setEnabled(boolean value, boolean overload)127 protected synchronized boolean setEnabled(boolean value, boolean overload) { 128 boolean changed = setEnabledQuiet(value, System.nanoTime()); 129 logEnabledChange(value, changed); 130 return changed; 131 } 132 logEnabledChange(boolean value, boolean success)133 private static synchronized void logEnabledChange(boolean value, boolean success) { 134 if (success && logger != null) { 135 Logger localLogger = (Logger) logger; 136 if (localLogger.isLoggable(Level.FINE)) { 137 localLogger.fine((value ? "Enabling" : "Disabling") + " PerfMark recorder"); 138 } 139 } 140 } 141 142 /** Returns true if successfully changed. */ setEnabledQuiet(boolean value, long now)143 private static synchronized boolean setEnabledQuiet(boolean value, long now) { 144 if (isEnabled(actualGeneration) == value) { 145 return false; 146 } 147 if (actualGeneration == Generator.FAILURE) { 148 return false; 149 } 150 long nanoDiff = now - Generator.INIT_NANO_TIME; 151 generator.setGeneration(actualGeneration = nextGeneration(actualGeneration, nanoDiff)); 152 return true; 153 } 154 155 // VisibleForTesting nextGeneration(final long currentGeneration, final long nanosSinceInit)156 static long nextGeneration(final long currentGeneration, final long nanosSinceInit) { 157 assert currentGeneration != Generator.FAILURE; 158 long currentMibros = mibrosFromGeneration(currentGeneration); 159 long mibrosSinceInit = Math.min(mibrosFromNanos(nanosSinceInit), MAX_MIBROS); // 54bits 160 boolean nextEnabled = !isEnabled(currentGeneration); 161 long nextMibros; 162 if (mibrosSinceInit > currentMibros) { 163 nextMibros = mibrosSinceInit; 164 } else { 165 nextMibros = currentMibros + (nextEnabled ? 1 : 0); 166 } 167 if (nextMibros > MAX_MIBROS || nextMibros < 0) { 168 return Generator.FAILURE; 169 } 170 long enabledMask = nextEnabled ? INCREMENT : 0; 171 long mibroMask = (nextMibros << (Generator.GEN_OFFSET + ENABLED_BIT_SPACE)); 172 assert (enabledMask & mibroMask) == 0; 173 return mibroMask | enabledMask; 174 } 175 mibrosFromGeneration(long currentGeneration)176 private static long mibrosFromGeneration(long currentGeneration) { 177 if (currentGeneration == Generator.FAILURE) { 178 throw new IllegalArgumentException(); 179 } 180 return currentGeneration >>> (Generator.GEN_OFFSET + ENABLED_BIT_SPACE); 181 } 182 mibrosFromNanos(long nanos)183 private static long mibrosFromNanos(long nanos) { 184 long remainder = ((1L<<(64 - GEN_TIMESTAMP_SPACE)) - 1) & nanos; 185 return (nanos >>> (64 - GEN_TIMESTAMP_SPACE)) 186 + (remainder >= (1L<<(64 - GEN_TIMESTAMP_SPACE - 1)) ? 1 : 0); 187 } 188 189 @Override startTask(String taskName, Tag tag)190 protected void startTask(String taskName, Tag tag) { 191 final long gen = getGen(); 192 if (!isEnabled(gen)) { 193 return; 194 } 195 Storage.startAnyway(gen, taskName, unpackTagName(tag), unpackTagId(tag)); 196 } 197 198 @Override startTask(String taskName)199 protected void startTask(String taskName) { 200 final long gen = getGen(); 201 if (!isEnabled(gen)) { 202 return; 203 } 204 Storage.startAnyway(gen, taskName); 205 } 206 207 @Override startTask(String taskName, String subTaskName)208 protected void startTask(String taskName, String subTaskName) { 209 final long gen = getGen(); 210 if (!isEnabled(gen)) { 211 return; 212 } 213 Storage.startAnyway(gen, taskName, subTaskName); 214 } 215 216 @Override startTask(T taskNameObject, StringFunction<? super T> stringFunction)217 protected <T> void startTask(T taskNameObject, StringFunction<? super T> stringFunction) { 218 final long gen = getGen(); 219 if (!isEnabled(gen)) { 220 return; 221 } 222 String taskName = deriveTaskValue(taskNameObject, stringFunction); 223 Storage.startAnyway(gen, taskName); 224 } 225 226 @Override stopTask()227 protected void stopTask() { 228 final long gen = getGen(); 229 if (!isEnabled(gen)) { 230 return; 231 } 232 Storage.stopAnyway(gen); 233 } 234 235 @Override stopTask(String taskName, Tag tag)236 protected void stopTask(String taskName, Tag tag) { 237 final long gen = getGen(); 238 if (!isEnabled(gen)) { 239 return; 240 } 241 Storage.stopAnyway(gen, taskName, unpackTagName(tag), unpackTagId(tag)); 242 } 243 244 @Override stopTask(String taskName)245 protected void stopTask(String taskName) { 246 final long gen = getGen(); 247 if (!isEnabled(gen)) { 248 return; 249 } 250 Storage.stopAnyway(gen, taskName); 251 } 252 253 @Override stopTask(String taskName, String subTaskName)254 protected void stopTask(String taskName, String subTaskName) { 255 final long gen = getGen(); 256 if (!isEnabled(gen)) { 257 return; 258 } 259 Storage.stopAnyway(gen, taskName, subTaskName); 260 } 261 262 @Override event(String eventName, Tag tag)263 protected void event(String eventName, Tag tag) { 264 final long gen = getGen(); 265 if (!isEnabled(gen)) { 266 return; 267 } 268 Storage.eventAnyway(gen, eventName, unpackTagName(tag), unpackTagId(tag)); 269 } 270 271 @Override event(String eventName)272 protected void event(String eventName) { 273 final long gen = getGen(); 274 if (!isEnabled(gen)) { 275 return; 276 } 277 Storage.eventAnyway(gen, eventName); 278 } 279 280 @Override event(String eventName, String subEventName)281 protected void event(String eventName, String subEventName) { 282 final long gen = getGen(); 283 if (!isEnabled(gen)) { 284 return; 285 } 286 Storage.eventAnyway(gen, eventName, subEventName); 287 } 288 289 @Override attachTag(Tag tag)290 protected void attachTag(Tag tag) { 291 final long gen = getGen(); 292 if (!isEnabled(gen)) { 293 return; 294 } 295 Storage.attachTagAnyway(gen, unpackTagName(tag), unpackTagId(tag)); 296 } 297 298 @Override attachTag(String tagName, String tagValue)299 protected void attachTag(String tagName, String tagValue) { 300 final long gen = getGen(); 301 if (!isEnabled(gen)) { 302 return; 303 } 304 Storage.attachKeyedTagAnyway(gen, tagName, tagValue); 305 } 306 307 @Override attachTag( String tagName, T tagObject, StringFunction<? super T> stringFunction)308 protected <T> void attachTag( 309 String tagName, T tagObject, StringFunction<? super T> stringFunction) { 310 final long gen = getGen(); 311 if (!isEnabled(gen)) { 312 return; 313 } 314 String tagValue = deriveTagValue(tagName, tagObject, stringFunction); 315 Storage.attachKeyedTagAnyway(gen, tagName, tagValue); 316 } 317 deriveTagValue( String tagName, T tagObject, StringFunction<? super T> stringFunction)318 static <T> String deriveTagValue( 319 String tagName, T tagObject, StringFunction<? super T> stringFunction) { 320 try { 321 return stringFunction.apply(tagObject); 322 } catch (Throwable t) { 323 handleTagValueFailure(tagName, tagObject, stringFunction, t); 324 return "PerfMarkTagError:" + t.getClass().getName(); 325 } 326 } 327 deriveTaskValue(T taskNameObject, StringFunction<? super T> stringFunction)328 static <T> String deriveTaskValue(T taskNameObject, StringFunction<? super T> stringFunction) { 329 try { 330 return stringFunction.apply(taskNameObject); 331 } catch (Throwable t) { 332 handleTaskNameFailure(taskNameObject, stringFunction, t); 333 return "PerfMarkTaskError:" + t.getClass().getName(); 334 } 335 } 336 handleTagValueFailure( String tagName, T tagObject, StringFunction<? super T> stringFunction, Throwable cause)337 static <T> void handleTagValueFailure( 338 String tagName, T tagObject, StringFunction<? super T> stringFunction, Throwable cause) { 339 if (logger == null) { 340 return; 341 } 342 Logger localLogger = (Logger) logger; 343 try { 344 if (localLogger.isLoggable(Level.FINE)) { 345 LogRecord lr = 346 new LogRecord( 347 Level.FINE, 348 "PerfMark.attachTag failed: tagName={0}, tagObject={1}, stringFunction={2}"); 349 lr.setParameters(new Object[] {tagName, tagObject, stringFunction}); 350 lr.setThrown(cause); 351 localLogger.log(lr); 352 } 353 } catch (Throwable t) { 354 // Need to be careful here. It's possible that the Exception thrown may itself throw 355 // while trying to convert it to a String. Instead, only pass the class name, which is 356 // safer than passing the whole object. 357 localLogger.log( 358 Level.FINE, 359 "PerfMark.attachTag failed for {0}: {1}", 360 new Object[] {tagName, t.getClass()}); 361 } 362 } 363 handleTaskNameFailure( T taskNameObject, StringFunction<? super T> stringFunction, Throwable cause)364 static <T> void handleTaskNameFailure( 365 T taskNameObject, StringFunction<? super T> stringFunction, Throwable cause) { 366 if (logger == null) { 367 return; 368 } 369 Logger localLogger = (Logger) logger; 370 try { 371 if (localLogger.isLoggable(Level.FINE)) { 372 LogRecord lr = 373 new LogRecord( 374 Level.FINE, "PerfMark.startTask failed: taskObject={0}, stringFunction={1}"); 375 lr.setParameters(new Object[] {taskNameObject, stringFunction}); 376 lr.setThrown(cause); 377 localLogger.log(lr); 378 } 379 } catch (Throwable t) { 380 // Need to be careful here. It's possible that the Exception thrown may itself throw 381 // while trying to convert it to a String. Instead, only pass the class name, which is 382 // safer than passing the whole object. 383 localLogger.log(Level.FINE, "PerfMark.startTask failed for {0}", new Object[] {t.getClass()}); 384 } 385 } 386 387 @Override attachTag(String tagName, long tagValue)388 protected void attachTag(String tagName, long tagValue) { 389 final long gen = getGen(); 390 if (!isEnabled(gen)) { 391 return; 392 } 393 Storage.attachKeyedTagAnyway(gen, tagName, tagValue); 394 } 395 396 @Override attachTag(String tagName, long tagValue0, long tagValue1)397 protected void attachTag(String tagName, long tagValue0, long tagValue1) { 398 final long gen = getGen(); 399 if (!isEnabled(gen)) { 400 return; 401 } 402 Storage.attachKeyedTagAnyway(gen, tagName, tagValue0, tagValue1); 403 } 404 405 @Override createTag(@ullable String tagName, long tagId)406 protected Tag createTag(@Nullable String tagName, long tagId) { 407 if (!isEnabled(getGen())) { 408 return NO_TAG; 409 } 410 return packTag(tagName, tagId); 411 } 412 413 @Override linkOut()414 protected Link linkOut() { 415 final long gen = getGen(); 416 if (!isEnabled(gen)) { 417 return NO_LINK; 418 } 419 long linkId = linkIdAlloc.getAndIncrement(); 420 Storage.linkAnyway(gen, linkId); 421 return packLink(linkId); 422 } 423 424 @Override linkIn(Link link)425 protected void linkIn(Link link) { 426 final long gen = getGen(); 427 if (!isEnabled(gen)) { 428 return; 429 } 430 Storage.linkAnyway(gen, -unpackLinkId(link)); 431 } 432 getGen()433 private static long getGen() { 434 return generator.getGeneration(); 435 } 436 isEnabled(long gen)437 private static boolean isEnabled(long gen) { 438 return ((gen >>> Generator.GEN_OFFSET) & 0x1L) != 0L; 439 } 440 } 441 SecretPerfMarkImpl()442 private SecretPerfMarkImpl() {} 443 } 444