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 java.lang.ref.Reference; 20 import java.lang.ref.ReferenceQueue; 21 import java.lang.ref.SoftReference; 22 import java.lang.ref.WeakReference; 23 import java.lang.reflect.Method; 24 import java.util.ArrayList; 25 import java.util.Collections; 26 import java.util.Iterator; 27 import java.util.List; 28 import java.util.concurrent.ConcurrentHashMap; 29 import java.util.concurrent.ConcurrentMap; 30 import java.util.concurrent.atomic.AtomicLong; 31 import java.util.concurrent.atomic.AtomicReference; 32 import javax.annotation.Nullable; 33 34 /** 35 * Storage is responsible for storing and returning recorded marks. This is a low level class and 36 * not intended for use by users. Instead, the {@code TraceEventWriter} and {@code TraceEventViewer} 37 * classes provide easier to use APIs for accessing PerfMark data. 38 * 39 * <p>This code is <strong>NOT</strong> API stable, and may be removed in the future, or changed 40 * without notice. 41 */ 42 public final class Storage { 43 static final AtomicLong markHolderIdAllocator = new AtomicLong(1); 44 // The order of initialization here matters. If a logger invokes PerfMark, it will be re-entrant 45 // and need to use these static variables. 46 static final ConcurrentMap<Reference<Thread>, MarkHolderHandle> allMarkHolders = 47 new ConcurrentHashMap<Reference<Thread>, MarkHolderHandle>(); 48 private static final LocalMarkHolder localMarkHolder = new MostlyThreadLocalMarkHolder(); 49 private static final MarkHolderProvider markHolderProvider; 50 private static final ReferenceQueue<Thread> threadReferenceQueue = new ReferenceQueue<Thread>(); 51 private static volatile long lastGlobalIndexClear = Generator.INIT_NANO_TIME - 1; 52 53 static { 54 MarkHolderProvider provider = null; 55 Throwable[] problems = new Throwable[3]; 56 try { 57 String markHolderOverride = System.getProperty("io.perfmark.PerfMark.markHolderProvider"); 58 if (markHolderOverride != null && !markHolderOverride.isEmpty()) { 59 Class<?> clz = Class.forName(markHolderOverride); 60 provider = clz.asSubclass(MarkHolderProvider.class).getConstructor().newInstance(); 61 } 62 } catch (Throwable t) { 63 problems[0] = t; 64 } 65 if (provider == null) { 66 try { 67 Class<?> clz = 68 Class.forName( 69 "io.perfmark.java9.SecretVarHandleMarkHolderProvider$VarHandleMarkHolderProvider"); 70 provider = clz.asSubclass(MarkHolderProvider.class).getConstructor().newInstance(); 71 } catch (Throwable t) { 72 problems[1] = t; 73 } 74 } 75 if (provider == null) { 76 try { 77 Class<?> clz = 78 Class.forName( 79 "io.perfmark.java6.SecretSynchronizedMarkHolderProvider$SynchronizedMarkHolderProvider"); 80 provider = clz.asSubclass(MarkHolderProvider.class).getConstructor().newInstance(); 81 } catch (Throwable t) { 82 problems[2] = t; 83 } 84 } 85 if (provider == null) { 86 markHolderProvider = new NoopMarkHolderProvider(); 87 } else { 88 markHolderProvider = provider; 89 } 90 try { 91 if (Boolean.getBoolean("io.perfmark.PerfMark.debug")) { 92 // See the comment in io.perfmark.PerfMark for why this is invoked reflectively. 93 Class<?> logClass = Class.forName("java.util.logging.Logger"); 94 Object logger = logClass.getMethod("getLogger", String.class).invoke(null, Storage.class.getName()); 95 Class<?> levelClass = Class.forName("java.util.logging.Level"); 96 Object level = levelClass.getField("FINE").get(null); 97 Method logProblemMethod = logClass.getMethod("log", levelClass, String.class, Throwable.class); 98 99 for (Throwable problem : problems) { 100 if (problem == null) { 101 continue; 102 } logProblemMethod.invoke(logger, level, "Error loading MarkHolderProvider", problem)103 logProblemMethod.invoke(logger, level, "Error loading MarkHolderProvider", problem); 104 } 105 Method logSuccessMethod = logClass.getMethod("log", levelClass, String.class, Object[].class); logSuccessMethod.invoke(logger, level, "Using {0}", new Object[] {markHolderProvider.getClass().getName()})106 logSuccessMethod.invoke(logger, level, "Using {0}", new Object[] {markHolderProvider.getClass().getName()}); 107 } 108 } catch (Throwable t) { 109 // ignore 110 } 111 } 112 getInitNanoTime()113 public static long getInitNanoTime() { 114 return Generator.INIT_NANO_TIME; 115 } 116 117 /** 118 * Returns a list of {@link MarkList}s across all reachable threads. 119 * 120 * @return all reachable MarkLists. 121 */ read()122 public static List<MarkList> read() { 123 long lastReset = lastGlobalIndexClear; 124 drainThreadQueue(); 125 List<MarkList> markLists = new ArrayList<MarkList>(allMarkHolders.size()); 126 for (Iterator<MarkHolderHandle> it = allMarkHolders.values().iterator(); it.hasNext();) { 127 MarkHolderHandle handle = it.next(); 128 Thread writer = handle.threadRef().get(); 129 if (writer == null) { 130 handle.softenMarkHolderReference(); 131 } 132 MarkHolder markHolder = handle.markHolder(); 133 if (markHolder == null) { 134 it.remove(); 135 handle.clearSoftReference(); 136 continue; 137 } 138 String threadName = handle.getAndUpdateThreadName(); 139 long threadId = handle.getAndUpdateThreadId(); 140 boolean concurrentWrites = !(Thread.currentThread() == writer || writer == null); 141 markLists.add( 142 MarkList.newBuilder() 143 .setMarks(markHolder.read(concurrentWrites)) 144 .setThreadName(threadName) 145 .setThreadId(threadId) 146 .setMarkListId(handle.markHolderId) 147 .build()); 148 } 149 return Collections.unmodifiableList(markLists); 150 } 151 startAnyway(long gen, String taskName, @Nullable String tagName, long tagId)152 static void startAnyway(long gen, String taskName, @Nullable String tagName, long tagId) { 153 MarkHolder mh = localMarkHolder.acquire(); 154 mh.start(gen, taskName, tagName, tagId, System.nanoTime()); 155 localMarkHolder.release(mh); 156 } 157 startAnyway(long gen, String taskName)158 static void startAnyway(long gen, String taskName) { 159 MarkHolder mh = localMarkHolder.acquire(); 160 mh.start(gen, taskName, System.nanoTime()); 161 localMarkHolder.release(mh); 162 } 163 startAnyway(long gen, String taskName, String subTaskName)164 static void startAnyway(long gen, String taskName, String subTaskName) { 165 MarkHolder mh = localMarkHolder.acquire(); 166 mh.start(gen, taskName, subTaskName, System.nanoTime()); 167 localMarkHolder.release(mh); 168 } 169 stopAnyway(long gen)170 static void stopAnyway(long gen) { 171 long nanoTime = System.nanoTime(); 172 MarkHolder mh = localMarkHolder.acquire(); 173 mh.stop(gen, nanoTime); 174 localMarkHolder.release(mh); 175 } 176 stopAnyway(long gen, String taskName, @Nullable String tagName, long tagId)177 static void stopAnyway(long gen, String taskName, @Nullable String tagName, long tagId) { 178 long nanoTime = System.nanoTime(); 179 MarkHolder mh = localMarkHolder.acquire(); 180 mh.stop(gen, taskName, tagName, tagId, nanoTime); 181 localMarkHolder.release(mh); 182 } 183 stopAnyway(long gen, String taskName)184 static void stopAnyway(long gen, String taskName) { 185 long nanoTime = System.nanoTime(); 186 MarkHolder mh = localMarkHolder.acquire(); 187 mh.stop(gen, taskName, nanoTime); 188 localMarkHolder.release(mh); 189 } 190 stopAnyway(long gen, String taskName, String subTaskName)191 static void stopAnyway(long gen, String taskName, String subTaskName) { 192 long nanoTime = System.nanoTime(); 193 MarkHolder mh = localMarkHolder.acquire(); 194 mh.stop(gen, taskName, subTaskName, nanoTime); 195 localMarkHolder.release(mh); 196 } 197 eventAnyway(long gen, String eventName, @Nullable String tagName, long tagId)198 static void eventAnyway(long gen, String eventName, @Nullable String tagName, long tagId) { 199 long nanoTime = System.nanoTime(); 200 MarkHolder mh = localMarkHolder.acquire(); 201 mh.event(gen, eventName, tagName, tagId, nanoTime); 202 localMarkHolder.release(mh); 203 } 204 eventAnyway(long gen, String eventName)205 static void eventAnyway(long gen, String eventName) { 206 long nanoTime = System.nanoTime(); 207 MarkHolder mh = localMarkHolder.acquire(); 208 mh.event(gen, eventName, nanoTime); 209 localMarkHolder.release(mh); 210 } 211 eventAnyway(long gen, String eventName, String subEventName)212 static void eventAnyway(long gen, String eventName, String subEventName) { 213 long nanoTime = System.nanoTime(); 214 MarkHolder mh = localMarkHolder.acquire(); 215 mh.event(gen, eventName, subEventName, nanoTime); 216 localMarkHolder.release(mh); 217 } 218 linkAnyway(long gen, long linkId)219 static void linkAnyway(long gen, long linkId) { 220 MarkHolder mh = localMarkHolder.acquire(); 221 mh.link(gen, linkId); 222 localMarkHolder.release(mh); 223 } 224 attachTagAnyway(long gen, @Nullable String tagName, long tagId)225 static void attachTagAnyway(long gen, @Nullable String tagName, long tagId) { 226 MarkHolder mh = localMarkHolder.acquire(); 227 mh.attachTag(gen, tagName, tagId); 228 localMarkHolder.release(mh); 229 } 230 attachKeyedTagAnyway(long gen, @Nullable String tagName, String tagValue)231 static void attachKeyedTagAnyway(long gen, @Nullable String tagName, String tagValue) { 232 MarkHolder mh = localMarkHolder.acquire(); 233 mh.attachKeyedTag(gen, tagName, tagValue); 234 localMarkHolder.release(mh); 235 } 236 attachKeyedTagAnyway(long gen, @Nullable String tagName, long tagValue)237 static void attachKeyedTagAnyway(long gen, @Nullable String tagName, long tagValue) { 238 MarkHolder mh = localMarkHolder.acquire(); 239 mh.attachKeyedTag(gen, tagName, tagValue); 240 localMarkHolder.release(mh); 241 } 242 attachKeyedTagAnyway( long gen, @Nullable String tagName, long tagValue0, long tagValue1)243 static void attachKeyedTagAnyway( 244 long gen, @Nullable String tagName, long tagValue0, long tagValue1) { 245 MarkHolder mh = localMarkHolder.acquire(); 246 mh.attachKeyedTag(gen, tagName, tagValue0, tagValue1); 247 localMarkHolder.release(mh); 248 } 249 250 /** 251 * Removes all data for the calling Thread. Other threads may Still have stored data. 252 */ clearLocalStorage()253 public static void clearLocalStorage() { 254 for (Iterator<MarkHolderHandle> it = allMarkHolders.values().iterator(); it.hasNext();) { 255 MarkHolderHandle handle = it.next(); 256 if (handle.threadRef.get() == Thread.currentThread()) { 257 it.remove(); 258 handle.threadRef.clearInternal(); 259 handle.softenMarkHolderReference(); 260 handle.clearSoftReference(); 261 } 262 } 263 localMarkHolder.clear(); 264 } 265 266 /** 267 * Removes the global Read index on all storage, but leaves local storage in place. Because writer threads may still 268 * be writing to the same buffer (which they have a strong ref to), this function only removed data that is truly 269 * unwritable anymore. In addition, it captures a timestamp to which marks to include when reading. Thus, the data 270 * isn't fully removed. To fully remove all data, each tracing thread must call {@link #clearLocalStorage}. 271 */ clearGlobalIndex()272 public static void clearGlobalIndex() { 273 lastGlobalIndexClear = System.nanoTime() - 1; 274 for (Iterator<MarkHolderHandle> it = allMarkHolders.values().iterator(); it.hasNext();) { 275 MarkHolderHandle handle = it.next(); 276 handle.softenMarkHolderReference(); 277 Thread writer = handle.threadRef().get(); 278 if (writer == null) { 279 it.remove(); 280 handle.clearSoftReference(); 281 } 282 } 283 } 284 drainThreadQueue()285 private static void drainThreadQueue() { 286 while (true) { 287 Reference<?> ref = threadReferenceQueue.poll(); 288 if (ref == null) { 289 return; 290 } 291 MarkHolderHandle handle = allMarkHolders.get(ref); 292 if (handle != null) { 293 handle.softenMarkHolderReference(); 294 if (handle.markHolder() == null) { 295 allMarkHolders.remove(ref); 296 handle.clearSoftReference(); 297 } 298 } 299 } 300 } 301 302 @Nullable readForTest()303 public static MarkList readForTest() { 304 List<MarkList> lists = read(); 305 for (MarkList list : lists) { 306 // This is slightly wrong as the thread ID could be reused. 307 if (list.getThreadId() == Thread.currentThread().getId()) { 308 return list; 309 } 310 } 311 return null; 312 } 313 314 public static final class MarkHolderHandle { 315 private static final SoftReference<MarkHolder> EMPTY = new SoftReference<MarkHolder>(null); 316 317 private final UnmodifiableWeakReference<Thread> threadRef; 318 private final AtomicReference<MarkHolder> markHolderRef; 319 private volatile SoftReference<MarkHolder> softMarkHolderRef; 320 321 private volatile String threadName; 322 private volatile long threadId; 323 private final long markHolderId; 324 MarkHolderHandle(Thread thread, MarkHolder markHolder, long markHolderId)325 MarkHolderHandle(Thread thread, MarkHolder markHolder, long markHolderId) { 326 this.threadRef = new UnmodifiableWeakReference<Thread>(thread, threadReferenceQueue); 327 this.markHolderRef = new AtomicReference<MarkHolder>(markHolder); 328 this.threadName = thread.getName(); 329 this.threadId = thread.getId(); 330 this.markHolderId = markHolderId; 331 } 332 333 /** 334 * Returns the MarkHolder. May return {@code null} if the Thread is gone. If {@code null} is returned, 335 * then {@code getThreadRef().get() == null}. If a non-{@code null} value is returned, the thread may be dead or 336 * alive. Additionally, since the {@link #threadRef} may be externally cleared, it is not certain that the Thread 337 * is dead. 338 */ markHolder()339 public MarkHolder markHolder() { 340 MarkHolder markHolder = markHolderRef.get(); 341 if (markHolder == null) { 342 markHolder = softMarkHolderRef.get(); 343 assert markHolder != null || threadRef.get() == null; 344 } 345 return markHolder; 346 } 347 348 /** 349 * Returns a weak reference to the Thread that created the MarkHolder. 350 */ threadRef()351 public WeakReference<? extends Thread> threadRef() { 352 return threadRef; 353 } 354 softenMarkHolderReference()355 void softenMarkHolderReference() { 356 synchronized (markHolderRef) { 357 MarkHolder markHolder = markHolderRef.get(); 358 if (markHolder != null) { 359 softMarkHolderRef = new SoftReference<MarkHolder>(markHolder); 360 markHolderRef.set(null); 361 } 362 } 363 } 364 clearSoftReference()365 void clearSoftReference() { 366 Thread thread = threadRef.get(); 367 if (thread != null) { 368 throw new IllegalStateException("Thread still alive " + thread); 369 } 370 synchronized (markHolderRef) { 371 MarkHolder markHolder = markHolderRef.get(); 372 if (markHolder != null) { 373 throw new IllegalStateException("Handle not yet softened"); 374 } 375 softMarkHolderRef.clear(); 376 softMarkHolderRef = EMPTY; 377 threadName = null; 378 threadId = -255; 379 } 380 } 381 getAndUpdateThreadName()382 String getAndUpdateThreadName() { 383 Thread t = threadRef.get(); 384 String name; 385 if (t != null) { 386 threadName = (name = t.getName()); 387 } else { 388 name = threadName; 389 } 390 return name; 391 } 392 393 /** 394 * Some threads change their id over time, so we need to sync it if available. 395 */ getAndUpdateThreadId()396 long getAndUpdateThreadId() { 397 Thread t = threadRef.get(); 398 long id; 399 if (t != null) { 400 threadId = (id = t.getId()); 401 } else { 402 id = threadId; 403 } 404 return id; 405 } 406 } 407 408 /** 409 * This class is needed to work around a race condition where a newly created MarkHolder could be GC'd before 410 * the caller of {@link #allocateMarkHolder} can consume the results. The provided MarkHolder is strongly reachable. 411 */ 412 public static final class MarkHolderAndHandle { 413 private final MarkHolder markHolder; 414 private final MarkHolderHandle handle; 415 MarkHolderAndHandle(MarkHolder markHolder, MarkHolderHandle handle)416 MarkHolderAndHandle(MarkHolder markHolder, MarkHolderHandle handle) { 417 this.markHolder = markHolder; 418 this.handle = handle; 419 420 MarkHolder tmp = handle.markHolder(); 421 if (markHolder != tmp && tmp != null) { 422 throw new IllegalArgumentException("Holder Handle mismatch"); 423 } 424 } 425 markHolder()426 public MarkHolder markHolder() { 427 return markHolder; 428 } 429 handle()430 public MarkHolderHandle handle() { 431 return handle; 432 } 433 } 434 allocateMarkHolder()435 public static MarkHolderAndHandle allocateMarkHolder() { 436 drainThreadQueue(); 437 long markHolderId = markHolderIdAllocator.getAndIncrement(); 438 MarkHolder holder = markHolderProvider.create(markHolderId); 439 MarkHolderHandle handle = new MarkHolderHandle(Thread.currentThread(), holder, markHolderId); 440 allMarkHolders.put(handle.threadRef, handle); 441 return new MarkHolderAndHandle(holder, handle); 442 } 443 444 private static final class UnmodifiableWeakReference<T> extends WeakReference<T> { 445 UnmodifiableWeakReference(T referent, ReferenceQueue<T> q)446 UnmodifiableWeakReference(T referent, ReferenceQueue<T> q) { 447 super(referent, q); 448 } 449 450 @Override 451 @Deprecated 452 @SuppressWarnings("InlineMeSuggester") clear()453 public void clear() {} 454 455 @Override 456 @Deprecated 457 @SuppressWarnings("InlineMeSuggester") enqueue()458 public boolean enqueue() { 459 return false; 460 } 461 clearInternal()462 void clearInternal() { 463 super.clear(); 464 } 465 } 466 Storage()467 private Storage() {} 468 } 469