summaryrefslogtreecommitdiff
path: root/src/com/android/loganalysis/heuristic
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/loganalysis/heuristic')
-rw-r--r--src/com/android/loganalysis/heuristic/AbstractHeuristic.java106
-rw-r--r--src/com/android/loganalysis/heuristic/AnrHeuristic.java125
-rw-r--r--src/com/android/loganalysis/heuristic/CpuUsageHeuristic.java153
-rw-r--r--src/com/android/loganalysis/heuristic/IHeuristic.java135
-rw-r--r--src/com/android/loganalysis/heuristic/JavaCrashHeuristic.java121
-rw-r--r--src/com/android/loganalysis/heuristic/KernelResetHeuristic.java118
-rw-r--r--src/com/android/loganalysis/heuristic/MemoryUsageHeuristic.java154
-rw-r--r--src/com/android/loganalysis/heuristic/NativeCrashHeuristic.java121
-rw-r--r--src/com/android/loganalysis/heuristic/PowerUsageHeuristic.java233
-rw-r--r--src/com/android/loganalysis/heuristic/RuntimeRestartHeuristic.java241
10 files changed, 1507 insertions, 0 deletions
diff --git a/src/com/android/loganalysis/heuristic/AbstractHeuristic.java b/src/com/android/loganalysis/heuristic/AbstractHeuristic.java
new file mode 100644
index 0000000..d74ab4a
--- /dev/null
+++ b/src/com/android/loganalysis/heuristic/AbstractHeuristic.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.loganalysis.heuristic;
+
+import com.android.loganalysis.item.BugreportItem;
+import com.android.loganalysis.item.DumpsysItem;
+import com.android.loganalysis.item.KernelLogItem;
+import com.android.loganalysis.item.LogcatItem;
+import com.android.loganalysis.item.MemInfoItem;
+import com.android.loganalysis.item.ProcrankItem;
+import com.android.loganalysis.item.TopItem;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * An abstract {@link IHeuristic} which implements empty methods for all the add methods.
+ */
+public abstract class AbstractHeuristic implements IHeuristic {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addBugreport(Date timestamp, BugreportItem bugreport) {
+ // Ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addLogcat(Date timestamp, LogcatItem logcat) {
+ // Ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addKernelLog(Date timestamp, KernelLogItem kernelLog) {
+ // Ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addMemInfo(Date timestamp, MemInfoItem meminfo) {
+ // Ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addProcrank(Date timestamp, ProcrankItem procrank) {
+ // Ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addTop(Date timestamp, TopItem top) {
+ // Ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addDumpsys(Date timestamp, DumpsysItem dumpsys) {
+ // Ignore
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public JSONObject toJson() {
+ JSONObject object = new JSONObject();
+ try {
+ object.put(TYPE, getType());
+ object.put(NAME, getName());
+ object.put(STATUS, failed() ? FAILED : PASSED);
+ } catch (JSONException e) {
+ // Ignore
+ }
+ return object;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/loganalysis/heuristic/AnrHeuristic.java b/src/com/android/loganalysis/heuristic/AnrHeuristic.java
new file mode 100644
index 0000000..3ad6ec3
--- /dev/null
+++ b/src/com/android/loganalysis/heuristic/AnrHeuristic.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.loganalysis.heuristic;
+
+import com.android.loganalysis.item.AnrItem;
+import com.android.loganalysis.item.LogcatItem;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * A {@link IHeuristic} to detect if there are any ARNs.
+ */
+public class AnrHeuristic extends AbstractHeuristic {
+
+ /** Constant for JSON output */
+ public static final String ANRS = "ANRS";
+
+ private static final String HEURISTIC_NAME = "ANR";
+ private static final String HEURISTIC_TYPE = "ANR_HEURISTIC";
+
+ private LogcatItem mLogcat = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addLogcat(Date timestamp, LogcatItem logcat) {
+ mLogcat = logcat;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean failed() {
+ if (mLogcat == null) {
+ return false;
+ }
+
+ return (mLogcat.getAnrs().size() > 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getType() {
+ return HEURISTIC_TYPE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getName() {
+ return HEURISTIC_NAME;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getSummary() {
+ if (!failed()) {
+ return null;
+ }
+
+ return String.format("Found %d ANR%s", mLogcat.getAnrs().size(),
+ mLogcat.getAnrs().size() == 1 ? "" : "s");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDetails() {
+ if (!failed()) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (AnrItem anr : mLogcat.getAnrs()) {
+ sb.append(anr.getStack());
+ sb.append("\n\nLast lines of logcat\n");
+ sb.append(anr.getLastPreamble());
+ sb.append(String.format("\n\nLast lines of logcat for PID %d\n", anr.getPid()));
+ sb.append(anr.getProcessPreamble());
+ sb.append("\n\n");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public JSONObject toJson() {
+ JSONObject output = super.toJson();
+ try {
+ JSONArray anrs = new JSONArray();
+ for (AnrItem anr : mLogcat.getAnrs()) {
+ anrs.put(anr.toJson());
+ }
+ output.put(ANRS, anrs);
+ } catch (JSONException e) {
+ // Ignore
+ }
+ return output;
+ }
+}
diff --git a/src/com/android/loganalysis/heuristic/CpuUsageHeuristic.java b/src/com/android/loganalysis/heuristic/CpuUsageHeuristic.java
new file mode 100644
index 0000000..da2f161
--- /dev/null
+++ b/src/com/android/loganalysis/heuristic/CpuUsageHeuristic.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.loganalysis.heuristic;
+
+import com.android.loganalysis.item.LogcatItem;
+import com.android.loganalysis.item.TopItem;
+import com.android.loganalysis.parser.LogcatParser;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * A {@link IHeuristic} to detect if the CPU usage is abnormally high.
+ */
+public class CpuUsageHeuristic extends AbstractHeuristic {
+
+ /** Constant for JSON output */
+ public static final String CUTOFF = "CUTOFF";
+ /** Constant for JSON output */
+ public static final String USAGE = "USAGE";
+ /** Constant for JSON output */
+ public static final String TOP = "TOP";
+
+ private static final String HEURISTIC_NAME = "CPU usage";
+ private static final String HEURISTIC_TYPE = "CPU_USAGE_HEURISTIC";
+
+ // TODO: Make this value configurable.
+ private static final double TOP_USAGE_CUTOFF = 0.8;
+
+ private LogcatItem mLogcat = null;
+ private TopItem mTop = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addLogcat(Date timestamp, LogcatItem logcat) {
+ mLogcat = logcat;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addTop(Date timestamp, TopItem top) {
+ mTop = top;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean failed() {
+ if (mTop != null && getUsage() > TOP_USAGE_CUTOFF) {
+ return true;
+ }
+
+ if (mLogcat != null && mLogcat.getMiscEvents(LogcatParser.HIGH_CPU_USAGE).size() > 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getType() {
+ return HEURISTIC_TYPE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getName() {
+ return HEURISTIC_NAME;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getSummary() {
+ if (!failed()) {
+ return null;
+ }
+
+ return String.format("CPU usage at %.0f%% (over %.0f%%)", 100.0 * getUsage(),
+ 100.0 * TOP_USAGE_CUTOFF);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDetails() {
+ // TODO: List the top cpu using processes
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public JSONObject toJson() {
+ JSONObject output = super.toJson();
+ try {
+ output.put(CUTOFF, TOP_USAGE_CUTOFF);
+ if (mTop != null) {
+ output.put(USAGE, getUsage());
+ output.put(TOP, mTop.toJson());
+ }
+ } catch (JSONException e) {
+ // Ignore
+ }
+ return output;
+ }
+
+ /**
+ * Get the usage as a fraction between 0 and 1.
+ */
+ private double getUsage() {
+ if (mTop == null) {
+ return 0.0;
+ }
+
+ return ((double) (mTop.getTotal() - mTop.getIdle())) / mTop.getTotal();
+ }
+
+ /**
+ * Get the CPU usage threshold.
+ * <p>
+ * Exposed for unit testing.
+ * </p>
+ */
+ double getCutoff() {
+ return TOP_USAGE_CUTOFF;
+ }
+}
diff --git a/src/com/android/loganalysis/heuristic/IHeuristic.java b/src/com/android/loganalysis/heuristic/IHeuristic.java
new file mode 100644
index 0000000..4bdfab5
--- /dev/null
+++ b/src/com/android/loganalysis/heuristic/IHeuristic.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.loganalysis.heuristic;
+
+import com.android.loganalysis.item.BugreportItem;
+import com.android.loganalysis.item.DumpsysItem;
+import com.android.loganalysis.item.IItem;
+import com.android.loganalysis.item.KernelLogItem;
+import com.android.loganalysis.item.LogcatItem;
+import com.android.loganalysis.item.MemInfoItem;
+import com.android.loganalysis.item.ProcrankItem;
+import com.android.loganalysis.item.TopItem;
+
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * Interface for all heuristics used to determine if there are any problems in the logs.
+ * <p>
+ * Certain heuristics will care about different {@link IItem}s. For example, a heuristic looking
+ * for runtime restarts will look at the logcat and procrank but ignore the rest. It is recommended
+ * to use {@link AbstractHeuristic} and implement the methods to only add the {@link IItem}s that
+ * the heuristic cares about.
+ * </p><p>
+ * The {@code add()} methods are used to add {@link IItem}s which the heuristic cares about. When
+ * {@link #failed()} is called, it will only evaluate the {@link IItem}s which have been
+ * added. The same is true for {{@link #getSummary()} and {{@link #getDetails()}.
+ * </p>
+ */
+public interface IHeuristic {
+
+ /** Constant for JSON output */
+ public static final String TYPE = "TYPE";
+ /** Constant for JSON output */
+ public static final String NAME = "NAME";
+ /** Constant for JSON output */
+ public static final String STATUS = "STATUS";
+ /** Constant for JSON output */
+ public static final String PASSED = "PASSED";
+ /** Constant for JSON output */
+ public static final String FAILED = "FAILED";
+
+ /**
+ * Add a bugreport item to be checked.
+ */
+ public void addBugreport(Date timestamp, BugreportItem bugreport);
+
+ /**
+ * Add a logcat item to be checked.
+ */
+ public void addLogcat(Date timestamp, LogcatItem logcat);
+
+ /**
+ * Add a kernel log item to be checked.
+ */
+ public void addKernelLog(Date timestamp, KernelLogItem kernelLog);
+
+ /**
+ * Add a memory info item to be checked.
+ */
+ public void addMemInfo(Date timestamp, MemInfoItem meminfo);
+
+ /**
+ * Add a procrank item to be checked.
+ */
+ public void addProcrank(Date timestamp, ProcrankItem procrank);
+
+ /**
+ * Add a top item to be checked.
+ */
+ public void addTop(Date timestamp, TopItem top);
+
+ /**
+ * Add a dumpsys item to be checked.
+ */
+ public void addDumpsys(Date timestamp, DumpsysItem dumpsys);
+
+ /**
+ * Checks to see if there are any problems.
+ *
+ * @return {@code true} if there is a problem, {@code false} if there is not.
+ */
+ public boolean failed();
+
+ /**
+ * Get the type of the heuristic.
+ *
+ * @return The type.
+ */
+ public String getType();
+
+ /**
+ * Get the name of the heuristic.
+ *
+ * @return The name of the heuristic, to be used for the output.
+ */
+ public String getName();
+
+ /**
+ * Get the summary of the problem.
+ *
+ * @return The summary as a string or {@code null} if there is no problem.
+ */
+ public String getSummary();
+
+ /**
+ * Get the problem details.
+ *
+ * @return The details as a string or {@code null} if there is no problem.
+ */
+ public String getDetails();
+
+ /**
+ * Get the JSON representation of the problem.
+ *
+ * @return The {@link JSONObject} representing the heuristic. JSON object must contain at least
+ * the key {@link #STATUS} with either the value {@link #PASSED} or {@link #FAILED}, as well as
+ * any additional information about the status.
+ */
+ public JSONObject toJson();
+}
diff --git a/src/com/android/loganalysis/heuristic/JavaCrashHeuristic.java b/src/com/android/loganalysis/heuristic/JavaCrashHeuristic.java
new file mode 100644
index 0000000..4c32c4d
--- /dev/null
+++ b/src/com/android/loganalysis/heuristic/JavaCrashHeuristic.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.loganalysis.heuristic;
+
+import com.android.loganalysis.item.JavaCrashItem;
+import com.android.loganalysis.item.LogcatItem;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * A {@link IHeuristic} to detect if there are any Java crashes.
+ */
+public class JavaCrashHeuristic extends AbstractHeuristic {
+
+ /** Constant for JSON output */
+ public static final String JAVA_CRASHES = "JAVA_CRASHES";
+
+ private static final String HEURISTIC_NAME = "Java crash";
+ private static final String HEURISTIC_TYPE = "JAVA_CRASH_HEURISTIC";
+
+ private LogcatItem mLogcat = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addLogcat(Date timestamp, LogcatItem logcat) {
+ mLogcat = logcat;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean failed() {
+ if (mLogcat == null) {
+ return false;
+ }
+
+ return (mLogcat.getJavaCrashes().size() > 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getType() {
+ return HEURISTIC_TYPE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getName() {
+ return HEURISTIC_NAME;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getSummary() {
+ if (!failed()) {
+ return null;
+ }
+
+ return String.format("Found %d Java crash%s", mLogcat.getJavaCrashes().size(),
+ mLogcat.getJavaCrashes().size() == 1 ? "" : "es");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDetails() {
+ if (!failed()) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (JavaCrashItem crash : mLogcat.getJavaCrashes()) {
+ sb.append(crash.getStack());
+ sb.append("\n\n");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public JSONObject toJson() {
+ JSONObject output = super.toJson();
+ try {
+ JSONArray javaCrashes = new JSONArray();
+ for (JavaCrashItem jc : mLogcat.getJavaCrashes()) {
+ javaCrashes.put(jc.toJson());
+ }
+ output.put(JAVA_CRASHES, javaCrashes);
+ } catch (JSONException e) {
+ // Ignore
+ }
+ return output;
+ }
+}
diff --git a/src/com/android/loganalysis/heuristic/KernelResetHeuristic.java b/src/com/android/loganalysis/heuristic/KernelResetHeuristic.java
new file mode 100644
index 0000000..ba6a08c
--- /dev/null
+++ b/src/com/android/loganalysis/heuristic/KernelResetHeuristic.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.loganalysis.heuristic;
+
+import com.android.loganalysis.item.KernelLogItem;
+import com.android.loganalysis.item.MiscKernelLogItem;
+import com.android.loganalysis.parser.KernelLogParser;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * A {@link IHeuristic} to detect if there are any kernel resets.
+ */
+public class KernelResetHeuristic extends AbstractHeuristic {
+
+ /** Constant for JSON output */
+ public static final String KERNEL_RESETS = "KERNEL_RESETS";
+
+ private static final String HEURISTIC_NAME = "Kernel reset";
+ private static final String HEURISTIC_TYPE = "KERNEL_RESET_HEURISTIC";
+
+ private KernelLogItem mKernelLog = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addKernelLog(Date timestamp, KernelLogItem kernelLog) {
+ mKernelLog = kernelLog;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean failed() {
+ return (mKernelLog != null &&
+ mKernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).size() > 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getType() {
+ return HEURISTIC_TYPE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getName() {
+ return HEURISTIC_NAME;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getSummary() {
+ if (!failed()) {
+ return null;
+ }
+
+ return "Found a kernel reset";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDetails() {
+ if (!failed()) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (MiscKernelLogItem item : mKernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET)) {
+ sb.append(String.format("Reason: %s, Time: %.6f\nPreamble:\n%s\n\n", item.getMessage(),
+ item.getEventTime(), item.getPreamble()));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public JSONObject toJson() {
+ JSONObject output = super.toJson();
+ try {
+ JSONArray kernelResets = new JSONArray();
+ for (MiscKernelLogItem item : mKernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET)) {
+ kernelResets.put(item.toJson());
+ }
+ output.put(KERNEL_RESETS, kernelResets);
+ } catch (JSONException e) {
+ // Ignore
+ }
+ return output;
+ }
+}
diff --git a/src/com/android/loganalysis/heuristic/MemoryUsageHeuristic.java b/src/com/android/loganalysis/heuristic/MemoryUsageHeuristic.java
new file mode 100644
index 0000000..1355390
--- /dev/null
+++ b/src/com/android/loganalysis/heuristic/MemoryUsageHeuristic.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.loganalysis.heuristic;
+
+import com.android.loganalysis.item.LogcatItem;
+import com.android.loganalysis.item.MemInfoItem;
+import com.android.loganalysis.parser.LogcatParser;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * A {@link IHeuristic} to detect if the memory usage is abnormally high.
+ */
+public class MemoryUsageHeuristic extends AbstractHeuristic {
+
+ /** Constant for JSON output */
+ public static final String CUTOFF = "CUTOFF";
+ /** Constant for JSON output */
+ public static final String USAGE = "USAGE";
+ /** Constant for JSON output */
+ public static final String MEM_INFO = "MEM_INFO";
+
+ private static final String HEURISTIC_NAME = "Memory usage";
+ private static final String HEURISTIC_TYPE = "MEMORY_USAGE_HEURISTIC";
+
+ // TODO: Make this value configurable.
+ private static final double MEMORY_USAGE_CUTOFF = 0.99;
+
+ private LogcatItem mLogcat = null;
+ private MemInfoItem mMemInfo = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addLogcat(Date timestamp, LogcatItem logcat) {
+ mLogcat = logcat;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addMemInfo(Date timestamp, MemInfoItem top) {
+ mMemInfo = top;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean failed() {
+ if (mMemInfo != null && getUsage() > MEMORY_USAGE_CUTOFF) {
+ return true;
+ }
+
+ if (mLogcat != null && mLogcat.getMiscEvents(LogcatParser.HIGH_MEMORY_USAGE).size() > 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getType() {
+ return HEURISTIC_TYPE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getName() {
+ return HEURISTIC_NAME;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getSummary() {
+ if (!failed()) {
+ return null;
+ }
+
+ return String.format("Memory usage at %.0f%% (over %.0f%%)", 100.0 * getUsage(),
+ 100.0 * MEMORY_USAGE_CUTOFF);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDetails() {
+ // TODO: List the top memory using processes.
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public JSONObject toJson() {
+ JSONObject output = super.toJson();
+ try {
+ output.put(CUTOFF, MEMORY_USAGE_CUTOFF);
+ if (mMemInfo != null) {
+ output.put(USAGE, getUsage());
+ output.put(MEM_INFO, mMemInfo.toJson());
+ }
+ } catch (JSONException e) {
+ // Ignore
+ }
+ return output;
+ }
+
+ /**
+ * Get the memory usage as a fraction between 0 and 1.
+ */
+ private double getUsage() {
+ if (mMemInfo == null) {
+ return 0.0;
+ }
+
+ return ((double) (mMemInfo.get("MemTotal") - mMemInfo.get("MemFree"))) /
+ mMemInfo.get("MemTotal");
+ }
+
+ /**
+ * Get the memory usage threshold.
+ * <p>
+ * Exposed for unit testing.
+ * </p>
+ */
+ double getCutoff() {
+ return MEMORY_USAGE_CUTOFF;
+ }
+}
diff --git a/src/com/android/loganalysis/heuristic/NativeCrashHeuristic.java b/src/com/android/loganalysis/heuristic/NativeCrashHeuristic.java
new file mode 100644
index 0000000..a10982c
--- /dev/null
+++ b/src/com/android/loganalysis/heuristic/NativeCrashHeuristic.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.loganalysis.heuristic;
+
+import com.android.loganalysis.item.LogcatItem;
+import com.android.loganalysis.item.NativeCrashItem;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * A {@link IHeuristic} to detect if there are any native crashes.
+ */
+public class NativeCrashHeuristic extends AbstractHeuristic {
+
+ /** Constant for JSON output */
+ public static final String NATIVE_CRASHES = "NATIVE_CRASHES";
+
+ private static final String HEURISTIC_NAME = "Native crash";
+ private static final String HEURISTIC_TYPE = "NATIVE_CRASH_HEURISTIC";
+
+ private LogcatItem mLogcat = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addLogcat(Date timestamp, LogcatItem logcat) {
+ mLogcat = logcat;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean failed() {
+ if (mLogcat == null) {
+ return false;
+ }
+
+ return (mLogcat.getNativeCrashes().size() > 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getType() {
+ return HEURISTIC_TYPE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getName() {
+ return HEURISTIC_NAME;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getSummary() {
+ if (!failed()) {
+ return null;
+ }
+
+ return String.format("Found %d native crash%s", mLogcat.getNativeCrashes().size(),
+ mLogcat.getNativeCrashes().size() == 1 ? "" : "es");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDetails() {
+ if (!failed()) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (NativeCrashItem crash : mLogcat.getNativeCrashes()) {
+ sb.append(crash.getStack());
+ sb.append("\n\n");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public JSONObject toJson() {
+ JSONObject output = super.toJson();
+ try {
+ JSONArray nativeCrashes = new JSONArray();
+ for (NativeCrashItem nc : mLogcat.getNativeCrashes()) {
+ nativeCrashes.put(nc.toJson());
+ }
+ output.put(NATIVE_CRASHES, nativeCrashes);
+ } catch (JSONException e) {
+ // Ignore
+ }
+ return output;
+ }
+}
diff --git a/src/com/android/loganalysis/heuristic/PowerUsageHeuristic.java b/src/com/android/loganalysis/heuristic/PowerUsageHeuristic.java
new file mode 100644
index 0000000..a9b27b3
--- /dev/null
+++ b/src/com/android/loganalysis/heuristic/PowerUsageHeuristic.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.loganalysis.heuristic;
+
+import com.android.loganalysis.item.DumpsysBatteryInfoItem;
+import com.android.loganalysis.item.DumpsysBatteryInfoItem.WakeLock;
+import com.android.loganalysis.item.DumpsysItem;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * A {@link IHeuristic} to detect if there are any power issues such as held wake locks.
+ */
+public class PowerUsageHeuristic extends AbstractHeuristic {
+
+ /** Constant for JSON output */
+ public static final String CUTOFF = "CUTOFF";
+ /** Constant for JSON output */
+ public static final String KERNEL_WAKE_LOCKS = "KERNEL_WAKE_LOCKS";
+ /** Constant for JSON output */
+ public static final String WAKE_LOCKS = "WAKE_LOCKS";
+
+ private static final String HEURISTIC_NAME = "Power usage";
+ private static final String HEURISTIC_TYPE = "POWER_USAGE_HEURISTIC";
+
+ // TODO: Make this value configurable.
+ private static final long WAKE_LOCK_TIME_CUTOFF = 30 * 60 * 1000; // 30 minutes
+
+ private DumpsysBatteryInfoItem mBatteryInfo = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addDumpsys(Date timestamp, DumpsysItem dumpsys) {
+ mBatteryInfo = dumpsys.getBatteryInfo();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean failed() {
+ if (mBatteryInfo == null) {
+ return false;
+ }
+
+ for (WakeLock wakeLock : mBatteryInfo.getLastUnpluggedWakeLocks()) {
+ if (wakeLock.getHeldTime() > WAKE_LOCK_TIME_CUTOFF) {
+ return true;
+ }
+ }
+
+ for (WakeLock wakeLock :
+ mBatteryInfo.getLastUnpluggedKernelWakeLocks()) {
+ if (wakeLock.getHeldTime() > WAKE_LOCK_TIME_CUTOFF) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getType() {
+ return HEURISTIC_TYPE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getName() {
+ return HEURISTIC_NAME;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getSummary() {
+ if (!failed()) {
+ return null;
+ }
+
+ int wakeLockCounter = 0;
+ for (WakeLock wakeLock : mBatteryInfo.getLastUnpluggedWakeLocks()) {
+ if (wakeLock.getHeldTime() > WAKE_LOCK_TIME_CUTOFF) {
+ wakeLockCounter++;
+ }
+ }
+ int kernelWakeLockCounter = 0;
+ for (WakeLock wakeLock : mBatteryInfo.getLastUnpluggedKernelWakeLocks()) {
+ if (wakeLock.getHeldTime() > WAKE_LOCK_TIME_CUTOFF) {
+ kernelWakeLockCounter++;
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ if (wakeLockCounter > 0) {
+ sb.append(String.format("%d wake lock%s helder longer than %s", wakeLockCounter,
+ wakeLockCounter == 1 ? "" : "s", formatTime(WAKE_LOCK_TIME_CUTOFF)));
+ }
+ if (wakeLockCounter >= 0 && kernelWakeLockCounter >= 0) {
+ sb.append(", ");
+ }
+ if (kernelWakeLockCounter > 0) {
+ sb.append(String.format("%d kernel wake lock%s helder longer than %s",
+ kernelWakeLockCounter, kernelWakeLockCounter == 1 ? "" : "s",
+ formatTime(WAKE_LOCK_TIME_CUTOFF)));
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDetails() {
+ if (!failed()) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (WakeLock wakeLock : mBatteryInfo.getLastUnpluggedWakeLocks()) {
+ if (wakeLock.getHeldTime() > WAKE_LOCK_TIME_CUTOFF) {
+ sb.append(String.format("Wake lock \"%s\" #%d held for %s (%s times)\n",
+ wakeLock.getName(), wakeLock.getNumber(),
+ formatTime(wakeLock.getHeldTime()), wakeLock.getLockedCount()));
+ }
+ }
+ for (WakeLock wakeLock : mBatteryInfo.getLastUnpluggedKernelWakeLocks()) {
+ if (wakeLock.getHeldTime() > WAKE_LOCK_TIME_CUTOFF) {
+ sb.append(String.format("Kernel wake lock \"%s\" held for %s (%s times)\n",
+ wakeLock.getName(), formatTime(wakeLock.getHeldTime()),
+ wakeLock.getLockedCount()));
+ }
+ }
+ sb.append("\n");
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public JSONObject toJson() {
+ JSONObject output = super.toJson();
+ try {
+ output.put(CUTOFF, WAKE_LOCK_TIME_CUTOFF);
+
+ JSONArray kernelWakeLocks = new JSONArray();
+ for (WakeLock wakeLock : mBatteryInfo.getLastUnpluggedKernelWakeLocks()) {
+ kernelWakeLocks.put(wakeLock.toJson());
+ }
+ output.put(KERNEL_WAKE_LOCKS, kernelWakeLocks);
+
+ JSONArray wakeLocks = new JSONArray();
+ for (WakeLock wakeLock : mBatteryInfo.getLastUnpluggedWakeLocks()) {
+ wakeLocks.put(wakeLock.toJson());
+ }
+ output.put(WAKE_LOCKS, wakeLocks);
+ } catch (JSONException e) {
+ // Ignore
+ }
+ return output;
+ }
+
+ /**
+ * Convert the time in milliseconds into a readable string broken into d/h/m/s/ms.
+ */
+ private static String formatTime(long time) {
+ long msecs = time % 1000;
+ time /= 1000; // Convert to secs
+ long secs = time % 60;
+ time /= 60; // Convert to mins
+ long mins = time % 60;
+ time /= 60; // Convert to hours
+ long hours = time % 24;
+ time /= 24; // Convert to days
+
+ StringBuilder sb = new StringBuilder();
+ if (time != 0) {
+ sb.append(time);
+ sb.append("d ");
+ }
+ if (hours != 0) {
+ sb.append(hours);
+ sb.append("h ");
+ }
+ if (mins != 0) {
+ sb.append(mins);
+ sb.append("m ");
+ }
+ if (secs != 0) {
+ sb.append(secs);
+ sb.append("s ");
+ }
+ if (msecs != 0) {
+ sb.append(msecs);
+ sb.append("ms ");
+ }
+ return sb.toString().trim();
+ }
+
+ /**
+ * Get the wake lock held time threshold.
+ * <p>
+ * Exposed for unit testing.
+ * </p>
+ */
+ long getCutoff() {
+ return WAKE_LOCK_TIME_CUTOFF;
+ }
+}
diff --git a/src/com/android/loganalysis/heuristic/RuntimeRestartHeuristic.java b/src/com/android/loganalysis/heuristic/RuntimeRestartHeuristic.java
new file mode 100644
index 0000000..3d77267
--- /dev/null
+++ b/src/com/android/loganalysis/heuristic/RuntimeRestartHeuristic.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.loganalysis.heuristic;
+
+import com.android.loganalysis.item.LogcatItem;
+import com.android.loganalysis.item.MiscLogcatItem;
+import com.android.loganalysis.item.ProcrankItem;
+import com.android.loganalysis.parser.LogcatParser;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * A {@link IHeuristic} to detect if there are any runtime restarts.
+ */
+public class RuntimeRestartHeuristic extends AbstractHeuristic {
+
+ /** Constant for JSON output */
+ public static final String RUNTIME_RESTARTS = "RUNTIME_RESTARTS";
+ /** Constant for JSON output */
+ public static final String PROCRANK = "PROCRANK";
+ /** Constant for JSON output */
+ public static final String PROCRANK_SUMMARY = "PROCRANK_SUMMARY";
+
+ private static final String HEURISTIC_NAME = "Runtime restart";
+ private static final String HEURISTIC_TYPE = "RUNTIME_RESTART_HEURISTIC";
+
+ private static final int MAX_SYSTEM_SERVER_PID = 1000;
+ private static final String SYSTEM_SERVER_PROCESS_NAME = "system_server";
+ private static final String BOOT_ANIMATION_PROCESS_NAME = "/system/bin/bootanimation";
+
+ private LogcatItem mLogcat = null;
+ private ProcrankItem mProcrank = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addLogcat(Date timestamp, LogcatItem logcat) {
+ mLogcat = logcat;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addProcrank(Date timestamp, ProcrankItem procrank) {
+ mProcrank = procrank;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean failed() {
+ if (mLogcat != null && mLogcat.getMiscEvents(LogcatParser.RUNTIME_RESTART).size() > 0) {
+ return true;
+ }
+
+ if (mProcrank != null && mProcrank.getPids().size() > 0) {
+ Integer systemServerPid = getPid(SYSTEM_SERVER_PROCESS_NAME);
+ Integer bootAnimationPid = getPid(BOOT_ANIMATION_PROCESS_NAME);
+
+ if (systemServerPid == null || systemServerPid > MAX_SYSTEM_SERVER_PID) {
+ return true;
+ }
+
+ if (bootAnimationPid != null && bootAnimationPid > MAX_SYSTEM_SERVER_PID) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getType() {
+ return HEURISTIC_TYPE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getName() {
+ return HEURISTIC_NAME;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getSummary() {
+ if (!failed()) {
+ return null;
+ }
+
+ return "Found a runtime restart";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDetails() {
+ if (!failed()) {
+ return null;
+ }
+
+ Integer systemServerPid = getPid(SYSTEM_SERVER_PROCESS_NAME);
+ Integer bootAnimtationPid = getPid(BOOT_ANIMATION_PROCESS_NAME);
+
+ StringBuilder sb = new StringBuilder();
+ if (mLogcat != null) {
+ for (MiscLogcatItem item : mLogcat.getMiscEvents(LogcatParser.RUNTIME_RESTART)) {
+ sb.append(String.format("Message: %s, Time: %s\n\nLast lines of logcat:\n%s\n\n" +
+ "Process lines for pid %d:\n%s\n\n", item.getMessage(), item.getEventTime(),
+ item.getLastPreamble(), item.getPid(), item.getProcessPreamble()));
+ }
+ }
+
+ if (systemServerPid == null) {
+ sb.append("Suspected runtime restart detected because system_server is missing from " +
+ "procrank");
+ }
+ if (systemServerPid != null && systemServerPid > MAX_SYSTEM_SERVER_PID) {
+ sb.append(String.format("Suspected runtime restart detected because system_server " +
+ "has a PID of %d (greater than %d)\n", systemServerPid, MAX_SYSTEM_SERVER_PID));
+ }
+ if (bootAnimtationPid != null && bootAnimtationPid > MAX_SYSTEM_SERVER_PID) {
+ sb.append(String.format("Suspected runtime restart detected because " +
+ "/system/bin/bootanimation is present in procrank with a PID of %d (greater " +
+ "than %d)\n", bootAnimtationPid, MAX_SYSTEM_SERVER_PID));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public JSONObject toJson() {
+ JSONObject output = super.toJson();
+ try {
+ Integer systemServerPid = getPid(SYSTEM_SERVER_PROCESS_NAME);
+ Integer bootAnimtationPid = getPid(BOOT_ANIMATION_PROCESS_NAME);
+
+ if (mLogcat != null) {
+ JSONArray runtimeRestarts = new JSONArray();
+ for (MiscLogcatItem item : mLogcat.getMiscEvents(LogcatParser.RUNTIME_RESTART)) {
+ runtimeRestarts.put(item.toJson());
+ }
+ output.put(RUNTIME_RESTARTS, runtimeRestarts);
+ }
+
+ if (mProcrank != null) {
+ output.put(PROCRANK, mProcrank.toJson());
+ JSONArray summary = new JSONArray();
+ if (systemServerPid == null) {
+ summary.put(String.format("%s is absent from the procrank",
+ SYSTEM_SERVER_PROCESS_NAME));
+ }
+ if (systemServerPid != null && systemServerPid > MAX_SYSTEM_SERVER_PID) {
+ summary.put(String.format("%s is present in the procrank with a PID greater " +
+ "than %d", SYSTEM_SERVER_PROCESS_NAME, MAX_SYSTEM_SERVER_PID));
+ }
+ if (bootAnimtationPid != null && bootAnimtationPid > MAX_SYSTEM_SERVER_PID) {
+ summary.put(String.format("%s is present in the procrank with a PID greater " +
+ "than %d", BOOT_ANIMATION_PROCESS_NAME, MAX_SYSTEM_SERVER_PID));
+ }
+ output.put(PROCRANK, mProcrank.toJson());
+ output.put(PROCRANK_SUMMARY, summary);
+ }
+ } catch (JSONException e) {
+ // Ignore
+ }
+ return output;
+ }
+
+ /**
+ * Get the PID from a process name
+ */
+ private Integer getPid(String processName) {
+ if (mProcrank == null || processName == null) {
+ return null;
+ }
+ for (Integer pid : mProcrank.getPids()) {
+ if (processName.equals(mProcrank.getProcessName(pid))) {
+ return pid;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the system server PID threshold.
+ * <p>
+ * Exposed for unit testing.
+ * </p>
+ */
+ int getCutoff() {
+ return MAX_SYSTEM_SERVER_PID;
+ }
+
+ /**
+ * Get the system server name.
+ * <p>
+ * Exposed for unit testing.
+ * </p>
+ */
+ String getSystemServerName() {
+ return SYSTEM_SERVER_PROCESS_NAME;
+ }
+
+ /**
+ * Get the bootanimation name.
+ * <p>
+ * Exposed for unit testing.
+ * </p>
+ */
+ String getBootAnimationName() {
+ return BOOT_ANIMATION_PROCESS_NAME;
+ }
+}