summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@android.com>2010-11-11 11:33:18 -0800
committerAndroid (Google) Code Review <android-gerrit@google.com>2010-11-11 11:33:18 -0800
commit54019ee8c582bc0871d4d3395d690dfd35a8e08b (patch)
treecc93dffba1de38c8ff0ad9fe29ca26e294e1a7e0
parent53f0f36352919d83b7a063da2d45ca0003ff5b36 (diff)
parente7520d89fe2c5dc9dd833ecd9769c981df855b61 (diff)
downloadframeworks_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.java168
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java7
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);