diff options
Diffstat (limited to 'src/com/android/loganalysis/heuristic')
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; + } +} |