Merge "Add anr tracing to AnrTimerService" into main
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 0e19347..210301e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -133,6 +133,7 @@
import com.android.server.am.nano.VMInfo;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.utils.AnrTimer;
import com.android.server.utils.Slogf;
import dalvik.annotation.optimization.NeverCompile;
@@ -285,6 +286,8 @@
return -1;
case "trace-ipc":
return runTraceIpc(pw);
+ case "trace-timer":
+ return runTraceTimer(pw);
case "profile":
return runProfile(pw);
case "dumpheap":
@@ -1062,6 +1065,23 @@
return 0;
}
+ // Update AnrTimer tracing.
+ private int runTraceTimer(PrintWriter pw) throws RemoteException {
+ if (!AnrTimer.traceFeatureEnabled()) return -1;
+
+ // Delegate all argument parsing to the AnrTimer method.
+ try {
+ final String result = AnrTimer.traceTimers(peekRemainingArgs());
+ if (result != null) {
+ pw.println(result);
+ }
+ return 0;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println("Error: bad trace-timer command: " + e);
+ return -1;
+ }
+ }
+
// NOTE: current profiles can only be started on default display (even on automotive builds with
// passenger displays), so there's no need to pass a display-id
private int runProfile(PrintWriter pw) throws RemoteException {
@@ -4352,6 +4372,7 @@
pw.println(" start: start tracing IPC transactions.");
pw.println(" stop: stop tracing IPC transactions and dump the results to file.");
pw.println(" --dump-file <FILE>: Specify the file the trace should be dumped to.");
+ anrTimerHelp(pw);
pw.println(" profile start [--user <USER_ID> current]");
pw.println(" [--clock-type <TYPE>]");
pw.println(" [" + PROFILER_OUTPUT_VERSION_FLAG + " VERSION]");
@@ -4605,4 +4626,19 @@
Intent.printIntentArgsHelp(pw, "");
}
}
+
+ static void anrTimerHelp(PrintWriter pw) {
+ // Return silently if tracing is not feature-enabled.
+ if (!AnrTimer.traceFeatureEnabled()) return;
+
+ String h = AnrTimer.traceTimers(new String[]{"help"});
+ if (h == null) {
+ return;
+ }
+
+ pw.println(" trace-timer <cmd>");
+ for (String s : h.split("\n")) {
+ pw.println(" " + s);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 153bb91..1ba2487 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -130,22 +130,35 @@
}
/**
- * Return true if freezing is enabled. This has no effect if the service is not enabled.
+ * Return true if freezing is feature-enabled. Freezing must still be enabled on a
+ * per-service basis.
*/
- private static boolean anrTimerFreezerEnabled() {
+ private static boolean freezerFeatureEnabled() {
return Flags.anrTimerFreezer();
}
/**
+ * Return true if tracing is feature-enabled. This has no effect unless tracing is configured.
+ * Note that this does not represent any per-process overrides via an Injector.
+ */
+ public static boolean traceFeatureEnabled() {
+ return anrTimerServiceEnabled() && Flags.anrTimerTrace();
+ }
+
+ /**
* This class allows test code to provide instance-specific overrides.
*/
static class Injector {
- boolean anrTimerServiceEnabled() {
+ boolean serviceEnabled() {
return AnrTimer.anrTimerServiceEnabled();
}
- boolean anrTimerFreezerEnabled() {
- return AnrTimer.anrTimerFreezerEnabled();
+ boolean freezerEnabled() {
+ return AnrTimer.freezerFeatureEnabled();
+ }
+
+ boolean traceEnabled() {
+ return AnrTimer.traceFeatureEnabled();
}
}
@@ -349,7 +362,7 @@
mWhat = what;
mLabel = label;
mArgs = args;
- boolean enabled = args.mInjector.anrTimerServiceEnabled() && nativeTimersSupported();
+ boolean enabled = args.mInjector.serviceEnabled() && nativeTimersSupported();
mFeature = createFeatureSwitch(enabled);
}
@@ -448,7 +461,7 @@
/**
* The FeatureDisabled class bypasses almost all AnrTimer logic. It is used when the AnrTimer
- * service is disabled via Flags.anrTimerServiceEnabled.
+ * service is disabled via Flags.anrTimerService().
*/
private class FeatureDisabled extends FeatureSwitch {
/** Start a timer by sending a message to the client's handler. */
@@ -515,7 +528,7 @@
/**
* The FeatureEnabled class enables the AnrTimer logic. It is used when the AnrTimer service
- * is enabled via Flags.anrTimerServiceEnabled.
+ * is enabled via Flags.anrTimerService().
*/
private class FeatureEnabled extends FeatureSwitch {
@@ -533,7 +546,7 @@
FeatureEnabled() {
mNative = nativeAnrTimerCreate(mLabel,
mArgs.mExtend,
- mArgs.mFreeze && mArgs.mInjector.anrTimerFreezerEnabled());
+ mArgs.mFreeze && mArgs.mInjector.freezerEnabled());
if (mNative == 0) throw new IllegalArgumentException("unable to create native timer");
synchronized (sAnrTimerList) {
sAnrTimerList.put(mNative, new WeakReference(AnrTimer.this));
@@ -550,7 +563,7 @@
// exist.
if (cancel(arg)) mTotalRestarted++;
- int timerId = nativeAnrTimerStart(mNative, pid, uid, timeoutMs);
+ final int timerId = nativeAnrTimerStart(mNative, pid, uid, timeoutMs);
if (timerId > 0) {
mTimerIdMap.put(arg, timerId);
mTimerArgMap.put(timerId, arg);
@@ -895,7 +908,7 @@
/** Dumpsys output, allowing for overrides. */
@VisibleForTesting
static void dump(@NonNull PrintWriter pw, boolean verbose, @NonNull Injector injector) {
- if (!injector.anrTimerServiceEnabled()) return;
+ if (!injector.serviceEnabled()) return;
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
ipw.println("AnrTimer statistics");
@@ -926,6 +939,18 @@
}
/**
+ * Set a trace specification. The input is a set of strings. On success, the function pushes
+ * the trace specification to all timers, and then returns a response message. On failure,
+ * the function throws IllegalArgumentException and tracing is disabled.
+ *
+ * An empty specification has no effect other than returning the current trace specification.
+ */
+ @Nullable
+ public static String traceTimers(@Nullable String[] spec) {
+ return nativeAnrTimerTrace(spec);
+ }
+
+ /**
* Return true if the native timers are supported. Native timers are supported if the method
* nativeAnrTimerSupported() can be executed and it returns true.
*/
@@ -981,6 +1006,15 @@
*/
private static native boolean nativeAnrTimerRelease(long service, int timerId);
+ /**
+ * Configure tracing. The input array is a set of words pulled from the command line. All
+ * parsing happens inside the native layer. The function returns a string which is either an
+ * error message (so nothing happened) or the current configuration after applying the config.
+ * Passing an null array or an empty array simply returns the current configuration.
+ * The function returns null if the native layer is not implemented.
+ */
+ private static native @Nullable String nativeAnrTimerTrace(@Nullable String[] config);
+
/** Retrieve runtime dump information from the native layer. */
private static native String[] nativeAnrTimerDump(long service);
}
diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig
index 00ebb66..333287f 100644
--- a/services/core/java/com/android/server/utils/flags.aconfig
+++ b/services/core/java/com/android/server/utils/flags.aconfig
@@ -17,3 +17,10 @@
bug: "325594551"
}
+flag {
+ name: "anr_timer_trace"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "When true, start a trace if an ANR timer reaches 50%"
+ bug: "352085328"
+}
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index cf96114..2836d46 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -19,6 +19,8 @@
#include <sys/timerfd.h>
#include <inttypes.h>
#include <sys/stat.h>
+#include <unistd.h>
+#include <regex.h>
#include <algorithm>
#include <list>
@@ -26,6 +28,7 @@
#include <set>
#include <string>
#include <vector>
+#include <map>
#define LOG_TAG "AnrTimerService"
#define ATRACE_TAG ATRACE_TAG_ACTIVITY_MANAGER
@@ -33,8 +36,8 @@
#include <jni.h>
#include <nativehelper/JNIHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-#include "core_jni_helpers.h"
+#include <android_runtime/AndroidRuntime.h>
+#include <core_jni_helpers.h>
#include <processgroup/processgroup.h>
#include <utils/Log.h>
@@ -109,32 +112,336 @@
// Return the name of the process whose pid is the input. If the process does not exist, the
// name will "notfound".
std::string getProcessName(pid_t pid) {
- char buffer[PATH_MAX];
- snprintf(buffer, sizeof(buffer), "/proc/%d/cmdline", pid);
- int fd = ::open(buffer, O_RDONLY);
- if (fd >= 0) {
- size_t pos = 0;
- ssize_t result;
- while (pos < sizeof(buffer)-1) {
- result = ::read(fd, buffer + pos, (sizeof(buffer) - pos) - 1);
- if (result <= 0) {
- break;
- }
- }
- ::close(fd);
-
- if (result >= 0) {
- buffer[pos] = 0;
+ char path[PATH_MAX];
+ snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
+ FILE* cmdline = fopen(path, "r");
+ if (cmdline != nullptr) {
+ char name[PATH_MAX];
+ char const *retval = fgets(name, sizeof(name), cmdline);
+ fclose(cmdline);
+ if (retval == nullptr) {
+ return std::string("unknown");
} else {
- snprintf(buffer, sizeof(buffer), "err: %s", strerror(errno));
+ return std::string(name);
}
} else {
- snprintf(buffer, sizeof(buffer), "notfound");
+ return std::string("notfound");
}
- return std::string(buffer);
}
/**
+ * Three wrappers of the trace utilities, which hard-code the timer track.
+ */
+void traceBegin(const char* msg, int cookie) {
+ ATRACE_ASYNC_FOR_TRACK_BEGIN(ANR_TIMER_TRACK, msg, cookie);
+}
+
+void traceEnd(int cookie) {
+ ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie);
+}
+
+void traceEvent(const char* msg) {
+ ATRACE_INSTANT_FOR_TRACK(ANR_TIMER_TRACK, msg);
+}
+
+/**
+ * This class captures tracing information for processes tracked by an AnrTimer. A user can
+ * configure tracing to have the AnrTimerService emit extra information for watched processes.
+ * singleton.
+ *
+ * The tracing configuration has two components: process selection and an optional early action.
+ *
+ * Processes are selected in one of three ways:
+ * 1. A list of numeric linux process IDs.
+ * 2. A regular expression, matched against process names.
+ * 3. The keyword "all", to trace every process that uses an AnrTimer.
+ * Perfetto trace events are always emitted for every operation on a traced process.
+ *
+ * An early action occurs before the scheduled timeout. The early timeout is specified as a
+ * percentage (integer value in the range 0:100) of the programmed timeout. The AnrTimer will
+ * execute the early action at the early timeout. The early action may terminate the timer.
+ *
+ * There is one early action:
+ * 1. Expire - consider the AnrTimer expired and report it to the upper layers.
+ */
+class AnrTimerTracer {
+ public:
+ // Actions that can be taken when an early timer expires.
+ enum EarlyAction {
+ // Take no action. This is the value used when tracing is disabled.
+ None,
+ // Trace the timer but take no other action.
+ Trace,
+ // Report timer expiration to the upper layers. This is terminal, in that
+ Expire,
+ };
+
+ // The trace information for a single timer.
+ struct TraceConfig {
+ bool enabled = false;
+ EarlyAction action = None;
+ int earlyTimeout = 0;
+ };
+
+ AnrTimerTracer() {
+ AutoMutex _l(lock_);
+ resetLocked();
+ }
+
+ // Return the TraceConfig for a process.
+ TraceConfig getConfig(int pid) {
+ AutoMutex _l(lock_);
+ // The most likely situation: no tracing is configured.
+ if (!config_.enabled) return {};
+ if (matchAllPids_) return config_;
+ if (watched_.contains(pid)) return config_;
+ if (!matchNames_) return {};
+ if (matchedPids_.contains(pid)) return config_;
+ if (unmatchedPids_.contains(pid)) return {};
+ std::string proc_name = getProcessName(pid);
+ bool matched = regexec(®ex_, proc_name.c_str(), 0, 0, 0) == 0;
+ if (matched) {
+ matchedPids_.insert(pid);
+ return config_;
+ } else {
+ unmatchedPids_.insert(pid);
+ return {};
+ }
+ }
+
+ // Set the trace configuration. The input is a string that contains key/value pairs of the
+ // form "key=value". Pairs are separated by spaces. The function returns a string status.
+ // On success, the normalized config is returned. On failure, the configuration reset the
+ // result contains an error message. As a special case, an empty set of configs, or a
+ // config that contains only the keyword "show", will do nothing except return the current
+ // configuration. On any error, all tracing is disabled.
+ std::pair<bool, std::string> setConfig(const std::vector<std::string>& config) {
+ AutoMutex _l(lock_);
+ if (config.size() == 0) {
+ // Implicit "show"
+ return { true, currentConfigLocked() };
+ } else if (config.size() == 1) {
+ // Process the one-word commands
+ const char* s = config[0].c_str();
+ if (strcmp(s, "show") == 0) {
+ return { true, currentConfigLocked() };
+ } else if (strcmp(s, "off") == 0) {
+ resetLocked();
+ return { true, currentConfigLocked() };
+ } else if (strcmp(s, "help") == 0) {
+ return { true, help() };
+ }
+ } else if (config.size() > 2) {
+ return { false, "unexpected values in config" };
+ }
+
+ // Barring an error in the remaining specification list, tracing will be enabled.
+ resetLocked();
+ // Fetch the process specification. This must be the first configuration entry.
+ {
+ auto result = setTracedProcess(config[0]);
+ if (!result.first) return result;
+ }
+
+ // Process optional actions.
+ if (config.size() > 1) {
+ auto result = setTracedAction(config[1]);
+ if (!result.first) return result;
+ }
+
+ // Accept the result.
+ config_.enabled = true;
+ return { true, currentConfigLocked() };
+ }
+
+ private:
+ // Identify the processes to be traced.
+ std::pair<bool, std::string> setTracedProcess(std::string config) {
+ const char* s = config.c_str();
+ const char* word = nullptr;
+
+ if (strcmp(s, "pid=all") == 0) {
+ matchAllPids_ = true;
+ } else if ((word = startsWith(s, "pid=")) != nullptr) {
+ int p;
+ int n;
+ while (sscanf(word, "%d%n", &p, &n) == 1) {
+ watched_.insert(p);
+ word += n;
+ if (*word == ',') word++;
+ }
+ if (*word != 0) {
+ return { false, "invalid pid list" };
+ }
+ config_.action = Trace;
+ } else if ((word = startsWith(s, "name=")) != nullptr) {
+ if (matchNames_) {
+ regfree(®ex_);
+ matchNames_ = false;
+ }
+ if (regcomp(®ex_, word, REG_EXTENDED) != 0) {
+ return { false, "invalid regex" };
+ }
+ matchNames_ = true;
+ namePattern_ = word;
+ config_.action = Trace;
+ } else {
+ return { false, "no process specified" };
+ }
+ return { true, "" };
+ }
+
+ // Set the action to be taken on a traced process. The incoming default action is Trace;
+ // this method may overwrite that action.
+ std::pair<bool, std::string> setTracedAction(std::string config) {
+ const char* s = config.c_str();
+ const char* word = nullptr;
+ if (sscanf(s, "expire=%d", &config_.earlyTimeout) == 1) {
+ if (config_.earlyTimeout < 0) {
+ return { false, "invalid expire timeout" };
+ }
+ config_.action = Expire;
+ } else {
+ return { false, std::string("cannot parse action ") + s };
+ }
+ return { true, "" };
+ }
+
+ // Return the string value of an action.
+ static const char* toString(EarlyAction action) {
+ switch (action) {
+ case None: return "none";
+ case Trace: return "trace";
+ case Expire: return "expire";
+ }
+ return "unknown";
+ }
+
+ // Return the action represented by the string.
+ static EarlyAction fromString(const char* action) {
+ if (strcmp(action, "expire") == 0) return Expire;
+ return None;
+ }
+
+ // Return the help message. This has everything except the invocation command.
+ static std::string help() {
+ static const char* msg =
+ "help show this message\n"
+ "show report the current configuration\n"
+ "off clear the current configuration, turning off all tracing\n"
+ "spec... configure tracing according to the specification list\n"
+ " action=<action> what to do when a split timer expires\n"
+ " expire expire the timer to the upper levels\n"
+ " event generate extra trace events\n"
+ " pid=<pid>[,<pid>] watch the processes in the pid list\n"
+ " pid=all watch every process in the system\n"
+ " name=<regex> watch the processes whose name matches the regex\n";
+ return msg;
+ }
+
+ // A small convenience function for parsing. If the haystack starts with the needle and the
+ // haystack has at least one more character following, return a pointer to the following
+ // character. Otherwise return null.
+ static const char* startsWith(const char* haystack, const char* needle) {
+ if (strncmp(haystack, needle, strlen(needle)) == 0 && strlen(haystack) + strlen(needle)) {
+ return haystack + strlen(needle);
+ }
+ return nullptr;
+ }
+
+ // Return the currently watched pids. The lock must be held.
+ std::string watchedPidsLocked() const {
+ if (watched_.size() == 0) return "none";
+ bool first = true;
+ std::string result = "";
+ for (auto i = watched_.cbegin(); i != watched_.cend(); i++) {
+ if (first) {
+ result += StringPrintf("%d", *i);
+ } else {
+ result += StringPrintf(",%d", *i);
+ }
+ }
+ return result;
+ }
+
+ // Return the current configuration, in a form that can be consumed by setConfig().
+ std::string currentConfigLocked() const {
+ if (!config_.enabled) return "off";
+ std::string result;
+ if (matchAllPids_) {
+ result = "pid=all";
+ } else if (matchNames_) {
+ result = StringPrintf("name=\"%s\"", namePattern_.c_str());
+ } else {
+ result = std::string("pid=") + watchedPidsLocked();
+ }
+ switch (config_.action) {
+ case None:
+ break;
+ case Trace:
+ // The default action is Trace
+ break;
+ case Expire:
+ result += StringPrintf(" %s=%d", toString(config_.action), config_.earlyTimeout);
+ break;
+ }
+ return result;
+ }
+
+ // Reset the current configuration.
+ void resetLocked() {
+ if (!config_.enabled) return;
+
+ config_.enabled = false;
+ config_.earlyTimeout = 0;
+ config_.action = {};
+ matchAllPids_ = false;
+ watched_.clear();
+ if (matchNames_) regfree(®ex_);
+ matchNames_ = false;
+ namePattern_ = "";
+ matchedPids_.clear();
+ unmatchedPids_.clear();
+ }
+
+ // The lock for all operations
+ mutable Mutex lock_;
+
+ // The current tracing information, when a process matches.
+ TraceConfig config_;
+
+ // A short-hand flag that causes all processes to be tracing without the overhead of
+ // searching any of the maps.
+ bool matchAllPids_;
+
+ // A set of process IDs that should be traced. This is updated directly in setConfig()
+ // and only includes pids that were explicitly called out in the configuration.
+ std::set<pid_t> watched_;
+
+ // Name mapping is a relatively expensive operation, since the process name must be fetched
+ // from the /proc file system and then a regex must be evaluated. However, name mapping is
+ // useful to ensure processes are traced at the moment they start. To make this faster, a
+ // process's name is matched only once, and the result is stored in the matchedPids_ or
+ // unmatchedPids_ set, as appropriate. This can lead to confusion if a process changes its
+ // name after it starts.
+
+ // The global flag that enables name matching. If this is disabled then all name matching
+ // is disabled.
+ bool matchNames_;
+
+ // The regular expression that matches processes to be traced. This is saved for logging.
+ std::string namePattern_;
+
+ // The compiled regular expression.
+ regex_t regex_;
+
+ // The set of all pids that whose process names match (or do not match) the name regex.
+ // There is one set for pids that match and one set for pids that do not match.
+ std::set<pid_t> matchedPids_;
+ std::set<pid_t> unmatchedPids_;
+};
+
+/**
* This class encapsulates the anr timer service. The service manages a list of individual
* timers. A timer is either Running or Expired. Once started, a timer may be canceled or
* accepted. Both actions collect statistics about the timer and then delete it. An expired
@@ -177,7 +484,7 @@
* traditional void* and Java object pointer. The remaining parameters are
* configuration options.
*/
- AnrTimerService(char const* label, notifier_t notifier, void* cookie, jweak jtimer, Ticker*,
+ AnrTimerService(const char* label, notifier_t notifier, void* cookie, jweak jtimer, Ticker*,
bool extend, bool freeze);
// Delete the service and clean up memory.
@@ -211,6 +518,11 @@
// Release a timer. The timer must be in the expired list.
bool release(timer_id_t);
+ // Configure a trace specification to trace selected timers. See AnrTimerTracer for details.
+ static std::pair<bool, std::string> trace(const std::vector<std::string>& spec) {
+ return tracer_.setConfig(spec);
+ }
+
// Return the Java object associated with this instance.
jweak jtimer() const {
return notifierObject_;
@@ -221,7 +533,7 @@
private:
// The service cannot be copied.
- AnrTimerService(AnrTimerService const&) = delete;
+ AnrTimerService(const AnrTimerService&) = delete;
// Insert a timer into the running list. The lock must be held by the caller.
void insertLocked(const Timer&);
@@ -230,7 +542,7 @@
Timer removeLocked(timer_id_t timerId);
// Add a timer to the expired list.
- void addExpiredLocked(Timer const&);
+ void addExpiredLocked(const Timer&);
// Scrub the expired list by removing all entries for non-existent processes. The expired
// lock must be held by the caller.
@@ -240,10 +552,10 @@
static const char* statusString(Status);
// The name of this service, for logging.
- std::string const label_;
+ const std::string label_;
// The callback that is invoked when a timer expires.
- notifier_t const notifier_;
+ const notifier_t notifier_;
// The two cookies passed to the notifier.
void* notifierCookie_;
@@ -289,8 +601,13 @@
// The clock used by this AnrTimerService.
Ticker *ticker_;
+
+ // The global tracing specification.
+ static AnrTimerTracer tracer_;
};
+AnrTimerTracer AnrTimerService::tracer_;
+
class AnrTimerService::ProcessStats {
public:
nsecs_t cpu_time;
@@ -337,14 +654,23 @@
class AnrTimerService::Timer {
public:
// A unique ID assigned when the Timer is created.
- timer_id_t const id;
+ const timer_id_t id;
// The creation parameters. The timeout is the original, relative timeout.
- int const pid;
- int const uid;
- nsecs_t const timeout;
- bool const extend;
- bool const freeze;
+ const int pid;
+ const int uid;
+ const nsecs_t timeout;
+ // True if the timer may be extended.
+ const bool extend;
+ // True if process should be frozen when its timer expires.
+ const bool freeze;
+ // This is a percentage between 0 and 100. If it is non-zero then timer will fire at
+ // timeout*split/100, and the EarlyAction will be invoked. The timer may continue running
+ // or may expire, depending on the action. Thus, this value "splits" the timeout into two
+ // pieces.
+ const int split;
+ // The action to take if split (above) is non-zero, when the timer reaches the split point.
+ const AnrTimerTracer::EarlyAction action;
// The state of this timer.
Status status;
@@ -355,6 +681,9 @@
// The scheduled timeout. This is an absolute time. It may be extended.
nsecs_t scheduled;
+ // True if this timer is split and in its second half
+ bool splitting;
+
// True if this timer has been extended.
bool extended;
@@ -367,22 +696,10 @@
// The default constructor is used to create timers that are Invalid, representing the "not
// found" condition when a collection is searched.
- Timer() :
- id(NOTIMER),
- pid(0),
- uid(0),
- timeout(0),
- extend(false),
- freeze(false),
- status(Invalid),
- started(0),
- scheduled(0),
- extended(false),
- frozen(false) {
- }
+ Timer() : Timer(NOTIMER) { }
- // This constructor creates a timer with the specified id. This can be used as the argument
- // to find().
+ // This constructor creates a timer with the specified id and everything else set to
+ // "empty". This can be used as the argument to find().
Timer(timer_id_t id) :
id(id),
pid(0),
@@ -390,29 +707,37 @@
timeout(0),
extend(false),
freeze(false),
+ split(0),
+ action(AnrTimerTracer::None),
status(Invalid),
started(0),
scheduled(0),
+ splitting(false),
extended(false),
frozen(false) {
}
// Create a new timer. This starts the timer.
- Timer(int pid, int uid, nsecs_t timeout, bool extend, bool freeze) :
+ Timer(int pid, int uid, nsecs_t timeout, bool extend, bool freeze,
+ AnrTimerTracer::TraceConfig trace) :
id(nextId()),
pid(pid),
uid(uid),
timeout(timeout),
extend(extend),
freeze(pid != 0 && freeze),
+ split(trace.earlyTimeout),
+ action(trace.action),
status(Running),
started(now()),
- scheduled(started + timeout),
+ scheduled(started + (split > 0 ? (timeout*split)/100 : timeout)),
+ splitting(false),
extended(false),
frozen(false) {
if (extend && pid != 0) {
initial.fill(pid);
}
+
// A zero-pid is odd but it means the upper layers will never ANR the process. Freezing
// is always disabled. (It won't work anyway, but disabling it avoids error messages.)
ALOGI_IF(DEBUG_ERROR && pid == 0, "error: zero-pid %s", toString().c_str());
@@ -434,6 +759,23 @@
// returns false if the timer is eligible for extension. If the function returns false, the
// scheduled time is updated.
bool expire() {
+ if (split > 0 && !splitting) {
+ scheduled = started + timeout;
+ splitting = true;
+ event("split");
+ switch (action) {
+ case AnrTimerTracer::None:
+ case AnrTimerTracer::Trace:
+ break;
+ case AnrTimerTracer::Expire:
+ status = Expired;
+ maybeFreezeProcess();
+ event("expire");
+ break;
+ }
+ return status == Expired;
+ }
+
nsecs_t extension = 0;
if (extend && !extended) {
// Only one extension is permitted.
@@ -525,15 +867,15 @@
char tag[PATH_MAX];
snprintf(tag, sizeof(tag), "freeze(pid=%d,uid=%d)", pid, uid);
- ATRACE_ASYNC_FOR_TRACK_BEGIN(ANR_TIMER_TRACK, tag, cookie);
+ traceBegin(tag, cookie);
if (SetProcessProfiles(uid, pid, {"Frozen"})) {
ALOGI("freeze %s name=%s", toString().c_str(), getName().c_str());
frozen = true;
- ATRACE_ASYNC_FOR_TRACK_BEGIN(ANR_TIMER_TRACK, "frozen", cookie+1);
+ traceBegin("frozen", cookie+1);
} else {
ALOGE("error: freezing %s name=%s error=%s",
toString().c_str(), getName().c_str(), strerror(errno));
- ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie);
+ traceEnd(cookie);
}
}
@@ -543,7 +885,7 @@
// See maybeFreezeProcess for an explanation of the cookie.
const uint32_t cookie = id << 1;
- ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie+1);
+ traceEnd(cookie+1);
if (SetProcessProfiles(uid, pid, {"Unfrozen"})) {
ALOGI("unfreeze %s name=%s", toString().c_str(), getName().c_str());
frozen = false;
@@ -551,7 +893,7 @@
ALOGE("error: unfreezing %s name=%s error=%s",
toString().c_str(), getName().c_str(), strerror(errno));
}
- ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie);
+ traceEnd(cookie);
}
// Get the next free ID. NOTIMER is never returned.
@@ -564,12 +906,17 @@
}
// Log an event, non-verbose.
- void event(char const* tag) {
+ void event(const char* tag) {
event(tag, false);
}
// Log an event, guarded by the debug flag.
- void event(char const* tag, bool verbose) {
+ void event(const char* tag, bool verbose) {
+ if (action != AnrTimerTracer::None) {
+ char msg[PATH_MAX];
+ snprintf(msg, sizeof(msg), "%s(pid=%d)", tag, pid);
+ traceEvent(msg);
+ }
if (verbose) {
char name[PATH_MAX];
ALOGI_IF(DEBUG_TIMER, "event %s %s name=%s",
@@ -594,12 +941,12 @@
struct Entry {
const nsecs_t scheduled;
const timer_id_t id;
- AnrTimerService* const service;
+ AnrTimerService* service;
Entry(nsecs_t scheduled, timer_id_t id, AnrTimerService* service) :
scheduled(scheduled), id(id), service(service) {};
- bool operator<(const Entry &r) const {
+ bool operator<(const Entry& r) const {
return scheduled == r.scheduled ? id < r.id : scheduled < r.scheduled;
}
};
@@ -664,7 +1011,7 @@
}
// Remove every timer associated with the service.
- void remove(AnrTimerService const* service) {
+ void remove(const AnrTimerService* service) {
AutoMutex _l(lock_);
timer_id_t front = headTimerId();
for (auto i = running_.begin(); i != running_.end(); ) {
@@ -746,7 +1093,7 @@
// scheduled expiration time of the first entry.
void restartLocked() {
if (!running_.empty()) {
- Entry const x = *(running_.cbegin());
+ const Entry x = *(running_.cbegin());
nsecs_t delay = x.scheduled - now();
// Force a minimum timeout of 10ns.
if (delay < 10) delay = 10;
@@ -807,7 +1154,7 @@
std::atomic<size_t> AnrTimerService::Ticker::idGen_;
-AnrTimerService::AnrTimerService(char const* label, notifier_t notifier, void* cookie,
+AnrTimerService::AnrTimerService(const char* label, notifier_t notifier, void* cookie,
jweak jtimer, Ticker* ticker, bool extend, bool freeze) :
label_(label),
notifier_(notifier),
@@ -841,7 +1188,7 @@
AnrTimerService::timer_id_t AnrTimerService::start(int pid, int uid, nsecs_t timeout) {
AutoMutex _l(lock_);
- Timer t(pid, uid, timeout, extend_, freeze_);
+ Timer t(pid, uid, timeout, extend_, freeze_, tracer_.getConfig(pid));
insertLocked(t);
t.start();
counters_.started++;
@@ -918,7 +1265,7 @@
return okay;
}
-void AnrTimerService::addExpiredLocked(Timer const& timer) {
+void AnrTimerService::addExpiredLocked(const Timer& timer) {
scrubExpiredLocked();
expired_.insert(timer);
}
@@ -1077,7 +1424,7 @@
ScopedUtfChars name(env, jname);
jobject timer = env->NewWeakGlobalRef(jtimer);
AnrTimerService* service = new AnrTimerService(name.c_str(),
- anrNotify, &gAnrArgs, timer, gAnrArgs.ticker, extend, freeze);
+ anrNotify, &gAnrArgs, timer, gAnrArgs.ticker, extend, freeze);
return reinterpret_cast<jlong>(service);
}
@@ -1122,6 +1469,19 @@
return toService(ptr)->release(timerId);
}
+jstring anrTimerTrace(JNIEnv* env, jclass, jobjectArray jconfig) {
+ if (!nativeSupportEnabled) return nullptr;
+ std::vector<std::string> config;
+ const jsize jlen = jconfig == nullptr ? 0 : env->GetArrayLength(jconfig);
+ for (size_t i = 0; i < jlen; i++) {
+ jstring je = static_cast<jstring>(env->GetObjectArrayElement(jconfig, i));
+ ScopedUtfChars e(env, je);
+ config.push_back(e.c_str());
+ }
+ auto r = AnrTimerService::trace(config);
+ return env->NewStringUTF(r.second.c_str());
+}
+
jobjectArray anrTimerDump(JNIEnv *env, jclass, jlong ptr) {
if (!nativeSupportEnabled) return nullptr;
std::vector<std::string> stats = toService(ptr)->getDump();
@@ -1134,22 +1494,23 @@
}
static const JNINativeMethod methods[] = {
- {"nativeAnrTimerSupported", "()Z", (void*) anrTimerSupported},
- {"nativeAnrTimerCreate", "(Ljava/lang/String;ZZ)J", (void*) anrTimerCreate},
- {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose},
- {"nativeAnrTimerStart", "(JIIJ)I", (void*) anrTimerStart},
- {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel},
- {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept},
- {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard},
- {"nativeAnrTimerRelease", "(JI)Z", (void*) anrTimerRelease},
- {"nativeAnrTimerDump", "(J)[Ljava/lang/String;", (void*) anrTimerDump},
+ {"nativeAnrTimerSupported", "()Z", (void*) anrTimerSupported},
+ {"nativeAnrTimerCreate", "(Ljava/lang/String;ZZ)J", (void*) anrTimerCreate},
+ {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose},
+ {"nativeAnrTimerStart", "(JIIJ)I", (void*) anrTimerStart},
+ {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel},
+ {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept},
+ {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard},
+ {"nativeAnrTimerRelease", "(JI)Z", (void*) anrTimerRelease},
+ {"nativeAnrTimerTrace", "([Ljava/lang/String;)Ljava/lang/String;", (void*) anrTimerTrace},
+ {"nativeAnrTimerDump", "(J)[Ljava/lang/String;", (void*) anrTimerDump},
};
} // anonymous namespace
int register_android_server_utils_AnrTimer(JNIEnv* env)
{
- static const char *className = "com/android/server/utils/AnrTimer";
+ static const char* className = "com/android/server/utils/AnrTimer";
jniRegisterNativeMethods(env, className, methods, NELEM(methods));
nativeSupportEnabled = NATIVE_SUPPORT;
diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
index b09e9b1..54282ff 100644
--- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -119,7 +119,7 @@
*/
private class TestInjector extends AnrTimer.Injector {
@Override
- boolean anrTimerServiceEnabled() {
+ boolean serviceEnabled() {
return mEnabled;
}
}