diff options
author | Brad Fitzpatrick <bradfitz@android.com> | 2010-11-11 11:33:18 -0800 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2010-11-11 11:33:18 -0800 |
commit | 54019ee8c582bc0871d4d3395d690dfd35a8e08b (patch) | |
tree | cc93dffba1de38c8ff0ad9fe29ca26e294e1a7e0 | |
parent | 53f0f36352919d83b7a063da2d45ca0003ff5b36 (diff) | |
parent | e7520d89fe2c5dc9dd833ecd9769c981df855b61 (diff) | |
download | frameworks_base-54019ee8c582bc0871d4d3395d690dfd35a8e08b.zip frameworks_base-54019ee8c582bc0871d4d3395d690dfd35a8e08b.tar.gz frameworks_base-54019ee8c582bc0871d4d3395d690dfd35a8e08b.tar.bz2 |
Merge "StrictMode.Span support"
-rw-r--r-- | core/java/android/os/StrictMode.java | 168 | ||||
-rw-r--r-- | services/java/com/android/server/am/ActivityManagerService.java | 7 |
2 files changed, 174 insertions, 1 deletions
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 25d868f..22876c0 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -112,6 +112,9 @@ public final class StrictMode { // Only show an annoying dialog at most every 30 seconds private static final long MIN_DIALOG_INTERVAL_MS = 30000; + // How many Span tags (e.g. animations) to report. + private static final int MAX_SPAN_TAGS = 20; + // How many offending stacks to keep track of (and time) per loop // of the Looper. private static final int MAX_OFFENSES_PER_LOOP = 10; @@ -1217,6 +1220,140 @@ public final class StrictMode { } /** + * A tracked, critical time span. (e.g. during an animation.) + * + * The object itself is a linked list node, to avoid any allocations + * during rapid span entries and exits. + * + * @hide + */ + public static class Span { + private String mName; + private long mCreateMillis; + private Span mNext; + private Span mPrev; // not used when in freeList, only active + private final ThreadSpanState mContainerState; + + Span(ThreadSpanState threadState) { + mContainerState = threadState; + } + + /** + * To be called when the critical span is complete (i.e. the + * animation is done animating). This can be called on any + * thread (even a different one from where the animation was + * taking place), but that's only a defensive implementation + * measure. It really makes no sense for you to call this on + * thread other than that where you created it. + * + * @hide + */ + public void finish() { + ThreadSpanState state = mContainerState; + synchronized (state) { + if (mName == null) { + // Duplicate finish call. Ignore. + return; + } + + // Remove ourselves from the active list. + if (mPrev != null) { + mPrev.mNext = mNext; + } + if (mNext != null) { + mNext.mPrev = mPrev; + } + if (state.mActiveHead == this) { + state.mActiveHead = mNext; + } + + this.mCreateMillis = -1; + this.mName = null; + this.mPrev = null; + this.mNext = null; + state.mActiveSize--; + + // Add ourselves to the freeList, if it's not already + // too big. + if (state.mFreeListSize < 5) { + this.mNext = state.mFreeListHead; + state.mFreeListHead = this; + state.mFreeListSize++; + } + } + } + } + + /** + * Linked lists of active spans and a freelist. + * + * Locking notes: there's one of these structures per thread and + * all members of this structure (as well as the Span nodes under + * it) are guarded by the ThreadSpanState object instance. While + * in theory there'd be no locking required because it's all local + * per-thread, the finish() method above is defensive against + * people calling it on a different thread from where they created + * the Span, hence the locking. + */ + private static class ThreadSpanState { + public Span mActiveHead; // doubly-linked list. + public int mActiveSize; + public Span mFreeListHead; // singly-linked list. only changes at head. + public int mFreeListSize; + } + + private static final ThreadLocal<ThreadSpanState> sThisThreadSpanState = + new ThreadLocal<ThreadSpanState>() { + @Override protected ThreadSpanState initialValue() { + return new ThreadSpanState(); + } + }; + + /** + * Enter a named critical span (e.g. an animation) + * + * <p>The name is an arbitary label (or tag) that will be applied + * to any strictmode violation that happens while this span is + * active. You must call finish() on the span when done. + * + * <p>This will never return null, but on devices without debugging + * enabled, this may return a dummy object on which the finish() + * method is a no-op. + * + * <p>TODO: add CloseGuard to this, verifying callers call finish. + * + * @hide + */ + public static Span enterCriticalSpan(String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("name must be non-null and non-empty"); + } + ThreadSpanState state = sThisThreadSpanState.get(); + Span span = null; + synchronized (state) { + if (state.mFreeListHead != null) { + span = state.mFreeListHead; + state.mFreeListHead = span.mNext; + state.mFreeListSize--; + } else { + // Shouldn't have to do this often. + span = new Span(state); + } + span.mName = name; + span.mCreateMillis = SystemClock.uptimeMillis(); + span.mNext = state.mActiveHead; + span.mPrev = null; + state.mActiveHead = span; + state.mActiveSize++; + if (span.mNext != null) { + span.mNext.mPrev = span; + } + } + return span; + } + + + /** * Parcelable that gets sent in Binder call headers back to callers * to report violations that happened during a cross-process call. * @@ -1245,6 +1382,12 @@ public final class StrictMode { public int numAnimationsRunning = 0; /** + * List of tags from active Span instances during this + * violation, or null for none. + */ + public String[] tags; + + /** * Which violation number this was (1-based) since the last Looper loop, * from the perspective of the root caller (if it crossed any processes * via Binder calls). The value is 0 if the root caller wasn't on a Looper @@ -1284,6 +1427,23 @@ public final class StrictMode { if (broadcastIntent != null) { broadcastIntentAction = broadcastIntent.getAction(); } + ThreadSpanState state = sThisThreadSpanState.get(); + synchronized (state) { + int spanActiveCount = state.mActiveSize; + if (spanActiveCount > MAX_SPAN_TAGS) { + spanActiveCount = MAX_SPAN_TAGS; + } + if (spanActiveCount != 0) { + this.tags = new String[spanActiveCount]; + Span iter = state.mActiveHead; + int index = 0; + while (iter != null && index < spanActiveCount) { + this.tags[index] = iter.mName; + index++; + iter = iter.mNext; + } + } + } } /** @@ -1312,6 +1472,7 @@ public final class StrictMode { numAnimationsRunning = in.readInt(); violationUptimeMillis = in.readLong(); broadcastIntentAction = in.readString(); + tags = in.readStringArray(); } /** @@ -1325,6 +1486,7 @@ public final class StrictMode { dest.writeInt(numAnimationsRunning); dest.writeLong(violationUptimeMillis); dest.writeString(broadcastIntentAction); + dest.writeStringArray(tags); } @@ -1347,6 +1509,12 @@ public final class StrictMode { if (broadcastIntentAction != null) { pw.println(prefix + "broadcastIntentAction: " + broadcastIntentAction); } + if (tags != null) { + int index = 0; + for (String tag : tags) { + pw.println(prefix + "tag[" + (index++) + "]: " + tag); + } + } } } diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index ba8d7d2..1ec8a22 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -6702,9 +6702,14 @@ public final class ActivityManagerService extends ActivityManagerNative if (info.broadcastIntentAction != null) { sb.append("Broadcast-Intent-Action: ").append(info.broadcastIntentAction).append("\n"); } - if (info != null && info.durationMillis != -1) { + if (info.durationMillis != -1) { sb.append("Duration-Millis: ").append(info.durationMillis).append("\n"); } + if (info.tags != null) { + for (String tag : info.tags) { + sb.append("Span-Tag: ").append(tag).append("\n"); + } + } sb.append("\n"); if (info.crashInfo != null && info.crashInfo.stackTrace != null) { sb.append(info.crashInfo.stackTrace); |