aboutsummaryrefslogtreecommitdiff
path: root/impl/src/main/java/io/perfmark/impl/Storage.java
diff options
context:
space:
mode:
Diffstat (limited to 'impl/src/main/java/io/perfmark/impl/Storage.java')
-rw-r--r--impl/src/main/java/io/perfmark/impl/Storage.java468
1 files changed, 468 insertions, 0 deletions
diff --git a/impl/src/main/java/io/perfmark/impl/Storage.java b/impl/src/main/java/io/perfmark/impl/Storage.java
new file mode 100644
index 0000000..9cbda73
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/Storage.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
+
+/**
+ * Storage is responsible for storing and returning recorded marks. This is a low level class and
+ * not intended for use by users. Instead, the {@code TraceEventWriter} and {@code TraceEventViewer}
+ * classes provide easier to use APIs for accessing PerfMark data.
+ *
+ * <p>This code is <strong>NOT</strong> API stable, and may be removed in the future, or changed
+ * without notice.
+ */
+public final class Storage {
+ static final AtomicLong markHolderIdAllocator = new AtomicLong(1);
+ // The order of initialization here matters. If a logger invokes PerfMark, it will be re-entrant
+ // and need to use these static variables.
+ static final ConcurrentMap<Reference<Thread>, MarkHolderHandle> allMarkHolders =
+ new ConcurrentHashMap<Reference<Thread>, MarkHolderHandle>();
+ private static final LocalMarkHolder localMarkHolder = new MostlyThreadLocalMarkHolder();
+ private static final MarkHolderProvider markHolderProvider;
+ private static final ReferenceQueue<Thread> threadReferenceQueue = new ReferenceQueue<Thread>();
+ private static volatile long lastGlobalIndexClear = Generator.INIT_NANO_TIME - 1;
+
+ static {
+ MarkHolderProvider provider = null;
+ Throwable[] problems = new Throwable[3];
+ try {
+ String markHolderOverride = System.getProperty("io.perfmark.PerfMark.markHolderProvider");
+ if (markHolderOverride != null && !markHolderOverride.isEmpty()) {
+ Class<?> clz = Class.forName(markHolderOverride);
+ provider = clz.asSubclass(MarkHolderProvider.class).getConstructor().newInstance();
+ }
+ } catch (Throwable t) {
+ problems[0] = t;
+ }
+ if (provider == null) {
+ try {
+ Class<?> clz =
+ Class.forName(
+ "io.perfmark.java9.SecretVarHandleMarkHolderProvider$VarHandleMarkHolderProvider");
+ provider = clz.asSubclass(MarkHolderProvider.class).getConstructor().newInstance();
+ } catch (Throwable t) {
+ problems[1] = t;
+ }
+ }
+ if (provider == null) {
+ try {
+ Class<?> clz =
+ Class.forName(
+ "io.perfmark.java6.SecretSynchronizedMarkHolderProvider$SynchronizedMarkHolderProvider");
+ provider = clz.asSubclass(MarkHolderProvider.class).getConstructor().newInstance();
+ } catch (Throwable t) {
+ problems[2] = t;
+ }
+ }
+ if (provider == null) {
+ markHolderProvider = new NoopMarkHolderProvider();
+ } else {
+ markHolderProvider = provider;
+ }
+ try {
+ if (Boolean.getBoolean("io.perfmark.PerfMark.debug")) {
+ // See the comment in io.perfmark.PerfMark for why this is invoked reflectively.
+ Class<?> logClass = Class.forName("java.util.logging.Logger");
+ Object logger = logClass.getMethod("getLogger", String.class).invoke(null, Storage.class.getName());
+ Class<?> levelClass = Class.forName("java.util.logging.Level");
+ Object level = levelClass.getField("FINE").get(null);
+ Method logProblemMethod = logClass.getMethod("log", levelClass, String.class, Throwable.class);
+
+ for (Throwable problem : problems) {
+ if (problem == null) {
+ continue;
+ }
+ logProblemMethod.invoke(logger, level, "Error loading MarkHolderProvider", problem);
+ }
+ Method logSuccessMethod = logClass.getMethod("log", levelClass, String.class, Object[].class);
+ logSuccessMethod.invoke(logger, level, "Using {0}", new Object[] {markHolderProvider.getClass().getName()});
+ }
+ } catch (Throwable t) {
+ // ignore
+ }
+ }
+
+ public static long getInitNanoTime() {
+ return Generator.INIT_NANO_TIME;
+ }
+
+ /**
+ * Returns a list of {@link MarkList}s across all reachable threads.
+ *
+ * @return all reachable MarkLists.
+ */
+ public static List<MarkList> read() {
+ long lastReset = lastGlobalIndexClear;
+ drainThreadQueue();
+ List<MarkList> markLists = new ArrayList<MarkList>(allMarkHolders.size());
+ for (Iterator<MarkHolderHandle> it = allMarkHolders.values().iterator(); it.hasNext();) {
+ MarkHolderHandle handle = it.next();
+ Thread writer = handle.threadRef().get();
+ if (writer == null) {
+ handle.softenMarkHolderReference();
+ }
+ MarkHolder markHolder = handle.markHolder();
+ if (markHolder == null) {
+ it.remove();
+ handle.clearSoftReference();
+ continue;
+ }
+ String threadName = handle.getAndUpdateThreadName();
+ long threadId = handle.getAndUpdateThreadId();
+ boolean concurrentWrites = !(Thread.currentThread() == writer || writer == null);
+ markLists.add(
+ MarkList.newBuilder()
+ .setMarks(markHolder.read(concurrentWrites))
+ .setThreadName(threadName)
+ .setThreadId(threadId)
+ .setMarkListId(handle.markHolderId)
+ .build());
+ }
+ return Collections.unmodifiableList(markLists);
+ }
+
+ static void startAnyway(long gen, String taskName, @Nullable String tagName, long tagId) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.start(gen, taskName, tagName, tagId, System.nanoTime());
+ localMarkHolder.release(mh);
+ }
+
+ static void startAnyway(long gen, String taskName) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.start(gen, taskName, System.nanoTime());
+ localMarkHolder.release(mh);
+ }
+
+ static void startAnyway(long gen, String taskName, String subTaskName) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.start(gen, taskName, subTaskName, System.nanoTime());
+ localMarkHolder.release(mh);
+ }
+
+ static void stopAnyway(long gen) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.stop(gen, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void stopAnyway(long gen, String taskName, @Nullable String tagName, long tagId) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.stop(gen, taskName, tagName, tagId, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void stopAnyway(long gen, String taskName) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.stop(gen, taskName, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void stopAnyway(long gen, String taskName, String subTaskName) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.stop(gen, taskName, subTaskName, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void eventAnyway(long gen, String eventName, @Nullable String tagName, long tagId) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.event(gen, eventName, tagName, tagId, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void eventAnyway(long gen, String eventName) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.event(gen, eventName, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void eventAnyway(long gen, String eventName, String subEventName) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.event(gen, eventName, subEventName, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void linkAnyway(long gen, long linkId) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.link(gen, linkId);
+ localMarkHolder.release(mh);
+ }
+
+ static void attachTagAnyway(long gen, @Nullable String tagName, long tagId) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.attachTag(gen, tagName, tagId);
+ localMarkHolder.release(mh);
+ }
+
+ static void attachKeyedTagAnyway(long gen, @Nullable String tagName, String tagValue) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.attachKeyedTag(gen, tagName, tagValue);
+ localMarkHolder.release(mh);
+ }
+
+ static void attachKeyedTagAnyway(long gen, @Nullable String tagName, long tagValue) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.attachKeyedTag(gen, tagName, tagValue);
+ localMarkHolder.release(mh);
+ }
+
+ static void attachKeyedTagAnyway(
+ long gen, @Nullable String tagName, long tagValue0, long tagValue1) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.attachKeyedTag(gen, tagName, tagValue0, tagValue1);
+ localMarkHolder.release(mh);
+ }
+
+ /**
+ * Removes all data for the calling Thread. Other threads may Still have stored data.
+ */
+ public static void clearLocalStorage() {
+ for (Iterator<MarkHolderHandle> it = allMarkHolders.values().iterator(); it.hasNext();) {
+ MarkHolderHandle handle = it.next();
+ if (handle.threadRef.get() == Thread.currentThread()) {
+ it.remove();
+ handle.threadRef.clearInternal();
+ handle.softenMarkHolderReference();
+ handle.clearSoftReference();
+ }
+ }
+ localMarkHolder.clear();
+ }
+
+ /**
+ * Removes the global Read index on all storage, but leaves local storage in place. Because writer threads may still
+ * be writing to the same buffer (which they have a strong ref to), this function only removed data that is truly
+ * unwritable anymore. In addition, it captures a timestamp to which marks to include when reading. Thus, the data
+ * isn't fully removed. To fully remove all data, each tracing thread must call {@link #clearLocalStorage}.
+ */
+ public static void clearGlobalIndex() {
+ lastGlobalIndexClear = System.nanoTime() - 1;
+ for (Iterator<MarkHolderHandle> it = allMarkHolders.values().iterator(); it.hasNext();) {
+ MarkHolderHandle handle = it.next();
+ handle.softenMarkHolderReference();
+ Thread writer = handle.threadRef().get();
+ if (writer == null) {
+ it.remove();
+ handle.clearSoftReference();
+ }
+ }
+ }
+
+ private static void drainThreadQueue() {
+ while (true) {
+ Reference<?> ref = threadReferenceQueue.poll();
+ if (ref == null) {
+ return;
+ }
+ MarkHolderHandle handle = allMarkHolders.get(ref);
+ if (handle != null) {
+ handle.softenMarkHolderReference();
+ if (handle.markHolder() == null) {
+ allMarkHolders.remove(ref);
+ handle.clearSoftReference();
+ }
+ }
+ }
+ }
+
+ @Nullable
+ public static MarkList readForTest() {
+ List<MarkList> lists = read();
+ for (MarkList list : lists) {
+ // This is slightly wrong as the thread ID could be reused.
+ if (list.getThreadId() == Thread.currentThread().getId()) {
+ return list;
+ }
+ }
+ return null;
+ }
+
+ public static final class MarkHolderHandle {
+ private static final SoftReference<MarkHolder> EMPTY = new SoftReference<MarkHolder>(null);
+
+ private final UnmodifiableWeakReference<Thread> threadRef;
+ private final AtomicReference<MarkHolder> markHolderRef;
+ private volatile SoftReference<MarkHolder> softMarkHolderRef;
+
+ private volatile String threadName;
+ private volatile long threadId;
+ private final long markHolderId;
+
+ MarkHolderHandle(Thread thread, MarkHolder markHolder, long markHolderId) {
+ this.threadRef = new UnmodifiableWeakReference<Thread>(thread, threadReferenceQueue);
+ this.markHolderRef = new AtomicReference<MarkHolder>(markHolder);
+ this.threadName = thread.getName();
+ this.threadId = thread.getId();
+ this.markHolderId = markHolderId;
+ }
+
+ /**
+ * Returns the MarkHolder. May return {@code null} if the Thread is gone. If {@code null} is returned,
+ * then {@code getThreadRef().get() == null}. If a non-{@code null} value is returned, the thread may be dead or
+ * alive. Additionally, since the {@link #threadRef} may be externally cleared, it is not certain that the Thread
+ * is dead.
+ */
+ public MarkHolder markHolder() {
+ MarkHolder markHolder = markHolderRef.get();
+ if (markHolder == null) {
+ markHolder = softMarkHolderRef.get();
+ assert markHolder != null || threadRef.get() == null;
+ }
+ return markHolder;
+ }
+
+ /**
+ * Returns a weak reference to the Thread that created the MarkHolder.
+ */
+ public WeakReference<? extends Thread> threadRef() {
+ return threadRef;
+ }
+
+ void softenMarkHolderReference() {
+ synchronized (markHolderRef) {
+ MarkHolder markHolder = markHolderRef.get();
+ if (markHolder != null) {
+ softMarkHolderRef = new SoftReference<MarkHolder>(markHolder);
+ markHolderRef.set(null);
+ }
+ }
+ }
+
+ void clearSoftReference() {
+ Thread thread = threadRef.get();
+ if (thread != null) {
+ throw new IllegalStateException("Thread still alive " + thread);
+ }
+ synchronized (markHolderRef) {
+ MarkHolder markHolder = markHolderRef.get();
+ if (markHolder != null) {
+ throw new IllegalStateException("Handle not yet softened");
+ }
+ softMarkHolderRef.clear();
+ softMarkHolderRef = EMPTY;
+ threadName = null;
+ threadId = -255;
+ }
+ }
+
+ String getAndUpdateThreadName() {
+ Thread t = threadRef.get();
+ String name;
+ if (t != null) {
+ threadName = (name = t.getName());
+ } else {
+ name = threadName;
+ }
+ return name;
+ }
+
+ /**
+ * Some threads change their id over time, so we need to sync it if available.
+ */
+ long getAndUpdateThreadId() {
+ Thread t = threadRef.get();
+ long id;
+ if (t != null) {
+ threadId = (id = t.getId());
+ } else {
+ id = threadId;
+ }
+ return id;
+ }
+ }
+
+ /**
+ * This class is needed to work around a race condition where a newly created MarkHolder could be GC'd before
+ * the caller of {@link #allocateMarkHolder} can consume the results. The provided MarkHolder is strongly reachable.
+ */
+ public static final class MarkHolderAndHandle {
+ private final MarkHolder markHolder;
+ private final MarkHolderHandle handle;
+
+ MarkHolderAndHandle(MarkHolder markHolder, MarkHolderHandle handle) {
+ this.markHolder = markHolder;
+ this.handle = handle;
+
+ MarkHolder tmp = handle.markHolder();
+ if (markHolder != tmp && tmp != null) {
+ throw new IllegalArgumentException("Holder Handle mismatch");
+ }
+ }
+
+ public MarkHolder markHolder() {
+ return markHolder;
+ }
+
+ public MarkHolderHandle handle() {
+ return handle;
+ }
+ }
+
+ public static MarkHolderAndHandle allocateMarkHolder() {
+ drainThreadQueue();
+ long markHolderId = markHolderIdAllocator.getAndIncrement();
+ MarkHolder holder = markHolderProvider.create(markHolderId);
+ MarkHolderHandle handle = new MarkHolderHandle(Thread.currentThread(), holder, markHolderId);
+ allMarkHolders.put(handle.threadRef, handle);
+ return new MarkHolderAndHandle(holder, handle);
+ }
+
+ private static final class UnmodifiableWeakReference<T> extends WeakReference<T> {
+
+ UnmodifiableWeakReference(T referent, ReferenceQueue<T> q) {
+ super(referent, q);
+ }
+
+ @Override
+ @Deprecated
+ @SuppressWarnings("InlineMeSuggester")
+ public void clear() {}
+
+ @Override
+ @Deprecated
+ @SuppressWarnings("InlineMeSuggester")
+ public boolean enqueue() {
+ return false;
+ }
+
+ void clearInternal() {
+ super.clear();
+ }
+ }
+
+ private Storage() {}
+}