diff options
Diffstat (limited to 'src/com')
25 files changed, 2270 insertions, 251 deletions
diff --git a/src/com/android/loganalysis/LogAnalyzer.java b/src/com/android/loganalysis/LogAnalyzer.java index 787e58a..a0a49a1 100644 --- a/src/com/android/loganalysis/LogAnalyzer.java +++ b/src/com/android/loganalysis/LogAnalyzer.java @@ -26,10 +26,14 @@ import com.android.loganalysis.parser.KernelLogParser; import com.android.loganalysis.parser.LogcatParser; import com.android.loganalysis.parser.MemoryHealthParser; import com.android.loganalysis.parser.MonkeyLogParser; +import com.android.loganalysis.rule.RuleEngine; +import com.android.loganalysis.rule.RuleEngine.RuleType; import com.android.loganalysis.util.config.ArgsOptionParser; import com.android.loganalysis.util.config.ConfigurationException; import com.android.loganalysis.util.config.Option; +import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; @@ -39,6 +43,9 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + /** * A command line tool to parse a bugreport, logcat, or kernel log file and return the output. */ @@ -49,6 +56,10 @@ public class LogAnalyzer { JSON; } + private enum ResultType { + RAW, ANALYSIS; + } + @Option(name="bugreport", description="The path to the bugreport") private String mBugreportPath = null; @@ -67,6 +78,17 @@ public class LogAnalyzer { @Option(name="output", description="The output format, currently only JSON") private OutputFormat mOutputFormat = OutputFormat.JSON; + @Option(name="rule-type", description="The type of rules to be applied") + private RuleType mRuleType = RuleType.ALL; + + @Option(name="print", description="Print the result type") + private List<ResultType> mResultType = new ArrayList<ResultType>(); + + /** Constant for JSON output */ + private static final String RAW_DATA = "RAW"; + /** Constant for JSON output */ + private static final String ANALYSIS_DATA = "ANALYSIS"; + /** * Run the command line tool */ @@ -140,9 +162,61 @@ public class LogAnalyzer { */ private void printBugreport(BugreportItem bugreport) { if (OutputFormat.JSON.equals(mOutputFormat)) { - printJson(bugreport); + if (mResultType.size() == 0) { + printJson(bugreport); + } else if (mResultType.size() == 1) { + switch (mResultType.get(0)) { + case RAW: + printJson(bugreport); + break; + case ANALYSIS: + printBugreportAnalysis(getBugreportAnalysis(bugreport)); + break; + default: + // should not get here + return; + } + } else { + JSONObject result = new JSONObject(); + try { + for (ResultType resultType : mResultType) { + switch (resultType) { + case RAW: + result.put(RAW_DATA, bugreport.toJson()); + break; + case ANALYSIS: + result.put(ANALYSIS_DATA, getBugreportAnalysis(bugreport)); + break; + default: + // should not get here + break; + } + } + } catch (JSONException e) { + // Ignore + } + printJson(result); + } + } + } + + private JSONArray getBugreportAnalysis(BugreportItem bugreport) { + RuleEngine ruleEngine = new RuleEngine(bugreport); + ruleEngine.registerRules(mRuleType); + ruleEngine.executeRules(); + if (ruleEngine.getAnalysis() != null) { + return ruleEngine.getAnalysis(); + } else { + return new JSONArray(); + } + } + + private void printBugreportAnalysis(JSONArray analysis) { + if (analysis != null && analysis.length() > 0) { + System.out.println(analysis.toString()); + } else { + System.out.println(new JSONObject().toString()); } - // TODO: Print bugreport in human readable form. } /** @@ -180,7 +254,18 @@ public class LogAnalyzer { */ private void printJson(IItem item) { if (item != null && item.toJson() != null) { - System.out.println(item.toJson().toString()); + printJson(item.toJson()); + } else { + printJson(new JSONObject()); + } + } + + /** + * Print an {@link JSONObject} to stdout + */ + private void printJson(JSONObject json) { + if (json != null) { + System.out.println(json.toString()); } else { System.out.println(new JSONObject().toString()); } diff --git a/src/com/android/loganalysis/item/BatteryDischargeItem.java b/src/com/android/loganalysis/item/BatteryDischargeItem.java new file mode 100644 index 0000000..cdfcbcc --- /dev/null +++ b/src/com/android/loganalysis/item/BatteryDischargeItem.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2015 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.item; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +/** + * An {@link IItem} used to store information related to Battery discharge + */ +public class BatteryDischargeItem implements IItem { + + /** Constant for JSON output */ + public static final String BATTERY_DISCHARGE = "BATTERY_DISCHARGE"; + + private Collection<BatteryDischargeInfoItem> mBatteryDischargeInfo = + new LinkedList<BatteryDischargeInfoItem>(); + + public static class BatteryDischargeInfoItem extends GenericItem { + /** Constant for JSON output */ + public static final String CLOCK_TIME_OF_DISCHARGE = "CLOCK_TIME_OF_DISCHARGE"; + /** Constant for JSON output */ + public static final String DISCHARGE_ELAPSED_TIME = "DISCHARGE_ELAPSED_TIME"; + /** Constant for JSON output */ + public static final String BATTERY_LEVEL = "BATTERY_LEVEL"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + CLOCK_TIME_OF_DISCHARGE, DISCHARGE_ELAPSED_TIME, BATTERY_LEVEL)); + + /** + * The constructor for {@link BatteryDischargeInfoItem} + * + * @param clockTime Clock time when the battery discharge happened + * @param elapsedTime Time it took to discharge to the current battery level + * @param batteryLevel Current battery level + */ + public BatteryDischargeInfoItem(Calendar clockTime, long elapsedTime, int batteryLevel) { + super(ATTRIBUTES); + + setAttribute(CLOCK_TIME_OF_DISCHARGE, clockTime); + setAttribute(DISCHARGE_ELAPSED_TIME, elapsedTime); + setAttribute(BATTERY_LEVEL, batteryLevel); + } + + /** + * Get the clock time when the battery level dropped + */ + public Calendar getClockTime() { + return (Calendar) getAttribute(CLOCK_TIME_OF_DISCHARGE); + } + + /** + * Get the time elapsed to discharge to the current battery level + */ + public long getElapsedTime() { + return (long) getAttribute(DISCHARGE_ELAPSED_TIME); + } + + /** + * Get the current battery level + */ + public int getBatteryLevel() { + return (int) getAttribute(BATTERY_LEVEL); + } + } + + /** + * Add a battery discharge step from battery stats + * + * @param clockTime Clock time when the battery discharge happened + * @param elapsedTime Time it took to discharge to the current battery level + * @param batteryLevel Current battery level + */ + public void addBatteryDischargeInfo(Calendar clockTime, long elapsedTime, int batteryLevel) { + mBatteryDischargeInfo.add(new BatteryDischargeInfoItem(clockTime, + elapsedTime, batteryLevel)); + } + + public Collection<BatteryDischargeInfoItem> getDischargeStepsInfo() { + return mBatteryDischargeInfo; + } + + /** + * {@inheritDoc} + */ + @Override + public IItem merge(IItem other) throws ConflictingItemException { + throw new ConflictingItemException("Wakelock items cannot be merged"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isConsistent(IItem other) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public JSONObject toJson() { + JSONObject object = new JSONObject(); + try { + JSONArray batteryDischargeSteps = new JSONArray(); + for (BatteryDischargeInfoItem batteryDischargeStep : mBatteryDischargeInfo) { + batteryDischargeSteps.put(batteryDischargeStep.toJson()); + } + object.put(BATTERY_DISCHARGE, batteryDischargeSteps); + } catch (JSONException e) { + // Ignore + } + return object; + } +} diff --git a/src/com/android/loganalysis/item/BatteryStatsDetailedInfoItem.java b/src/com/android/loganalysis/item/BatteryStatsDetailedInfoItem.java new file mode 100644 index 0000000..a1ac0b0 --- /dev/null +++ b/src/com/android/loganalysis/item/BatteryStatsDetailedInfoItem.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2015 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.item; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * An {@link IItem} used to store BatteryStats Info + */ +public class BatteryStatsDetailedInfoItem implements IItem { + + /** Constant for JSON output */ + public static final String TIME_ON_BATTERY = "TIME_ON_BATTERY"; + /** Constant for JSON output */ + public static final String SCREEN_ON_TIME = "SCREEN_ON_TIME"; + /** Constant for JSON output */ + public static final String WAKELOCKS = "WAKELOCKS"; + /** Constant for JSON output */ + public static final String INTERRUPTS = "INTERRUPTS"; + /** Constant for JSON output */ + public static final String PROCESS_USAGE = "PROCESS_USAGE"; + + private long mTimeOnBattery = 0; + private long mScreenOnTime = 0; + private WakelockItem mWakelockItem = null; + private InterruptItem mInterruptItem = null; + private ProcessUsageItem mprocessUsageItem = null; + + /** + * Set the time on battery + */ + public void setTimeOnBattery(long timeOnBattery) { + mTimeOnBattery = timeOnBattery; + } + + /** + * Set the time on battery + */ + public void setScreenOnTime(long screenOnTime) { + mScreenOnTime = screenOnTime; + } + + /** + * Set the wakelock summary {@link WakelockItem} + */ + public void setWakelockItem(WakelockItem wakelockItem) { + mWakelockItem = wakelockItem; + } + + /** + * Set the interrupt summary {@link InterruptItem} + */ + public void setInterruptItem(InterruptItem interruptItem) { + mInterruptItem = interruptItem; + } + + /** + * Set the process usage {@link ProcessUsageItem} + */ + public void setProcessUsageItem(ProcessUsageItem processUsageItem) { + mprocessUsageItem = processUsageItem; + } + + /** + * Get the time on battery + */ + public long getTimeOnBattery() { + return mTimeOnBattery; + } + + /** + * Get the screen on time + */ + public long getScreenOnTime() { + return mScreenOnTime; + } + + /** + * Get the wakelock summary {@link WakelockItem} + */ + public WakelockItem getWakelockItem() { + return mWakelockItem; + } + + /** + * Get the interrupt summary {@link InterruptItem} + */ + public InterruptItem getInterruptItem() { + return mInterruptItem; + } + + /** + * Get the process usage summary {@link ProcessUsageItem} + */ + public ProcessUsageItem getProcessUsageItem() { + return mprocessUsageItem; + } + + /** + * {@inheritDoc} + */ + @Override + public IItem merge(IItem other) throws ConflictingItemException { + throw new ConflictingItemException("Dumpsys battery info items cannot be merged"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isConsistent(IItem other) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public JSONObject toJson() { + JSONObject batteryStatsComponent = new JSONObject(); + try { + if (mTimeOnBattery > 0) { + batteryStatsComponent.put(TIME_ON_BATTERY, getTimeOnBattery()); + } + if (mScreenOnTime > 0) { + batteryStatsComponent.put(SCREEN_ON_TIME, getScreenOnTime()); + } + if (mWakelockItem != null) { + batteryStatsComponent.put(WAKELOCKS, mWakelockItem.toJson()); + } + if (mInterruptItem != null) { + batteryStatsComponent.put(INTERRUPTS, mInterruptItem.toJson()); + } + if (mprocessUsageItem != null) { + batteryStatsComponent.put(PROCESS_USAGE, mprocessUsageItem.toJson()); + } + } catch (JSONException e) { + // ignore + } + return batteryStatsComponent; + } +} diff --git a/src/com/android/loganalysis/item/BatteryStatsSummaryInfoItem.java b/src/com/android/loganalysis/item/BatteryStatsSummaryInfoItem.java new file mode 100644 index 0000000..6b07905 --- /dev/null +++ b/src/com/android/loganalysis/item/BatteryStatsSummaryInfoItem.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 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.item; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * An {@link GenericItem} used to store the power analysis summary + */ +public class BatteryStatsSummaryInfoItem extends GenericItem { + + /** Constant for JSON output */ + public static final String DISCHARGE_RATE = "DISCHARGE_RATE"; + /** Constant for JSON output */ + public static final String PEAK_DISCHARGE_TIME = "PEAK_DISCHARGE_TIME"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + DISCHARGE_RATE, PEAK_DISCHARGE_TIME)); + + /** + * The constructor for {@link BatteryStatsSummaryInfoItem}. + */ + public BatteryStatsSummaryInfoItem() { + super(ATTRIBUTES); + } + + /** + * Get the battery discharge rate + */ + public String getBatteryDischargeRate() { + return (String) getAttribute(DISCHARGE_RATE); + } + + /** + * Set the battery discharge rate + */ + public void setBatteryDischargeRate(String dischargeRate) { + setAttribute(DISCHARGE_RATE, dischargeRate); + } + + /** + * Get the peak discharge time + */ + public String getPeakDischargeTime() { + return (String) getAttribute(PEAK_DISCHARGE_TIME); + } + + /** + * Set the peak discharge time + */ + public void setPeakDischargeTime(String peakDischargeTime) { + setAttribute(PEAK_DISCHARGE_TIME, peakDischargeTime); + } +} diff --git a/src/com/android/loganalysis/item/DumpsysBatteryStatsItem.java b/src/com/android/loganalysis/item/DumpsysBatteryStatsItem.java new file mode 100644 index 0000000..1039324 --- /dev/null +++ b/src/com/android/loganalysis/item/DumpsysBatteryStatsItem.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015 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.item; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * An {@link IItem} used to store BatteryStats Info + */ +public class DumpsysBatteryStatsItem implements IItem { + + /** Constant for JSON output */ + public static final String SUMMARY = "SUMMARY"; + /** Constant for JSON output */ + public static final String DETAILED_STATS = "DETAILED_STATS"; + + private BatteryStatsSummaryInfoItem mBatteryStatsSummaryItem; + private BatteryStatsDetailedInfoItem mDetailedBatteryStatsItem; + + /** + * Set the battery stats summary {@link BatteryStatsSummaryInfoItem} + */ + public void setBatteryStatsSummarytem(BatteryStatsSummaryInfoItem summaryItem) { + mBatteryStatsSummaryItem = summaryItem; + } + + /** + * Set the detailed battery stats item {@link BatteryStatsDetailedInfoItem} + */ + public void setDetailedBatteryStatsItem(BatteryStatsDetailedInfoItem detailedItem) { + mDetailedBatteryStatsItem = detailedItem; + } + + /** + * Get the battery stats summary {@link BatteryStatsSummaryInfoItem} + */ + public BatteryStatsSummaryInfoItem getBatteryStatsSummaryItem() { + return mBatteryStatsSummaryItem; + } + + /** + * Get the detailed battery stats item {@link BatteryStatsDetailedInfoItem} + */ + public BatteryStatsDetailedInfoItem getDetailedBatteryStatsItem() { + return mDetailedBatteryStatsItem; + } + + + /** + * {@inheritDoc} + */ + @Override + public IItem merge(IItem other) throws ConflictingItemException { + throw new ConflictingItemException("Dumpsys battery info items cannot be merged"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isConsistent(IItem other) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public JSONObject toJson() { + JSONObject batteryStatsComponent = new JSONObject(); + try { + if (mBatteryStatsSummaryItem != null) { + batteryStatsComponent.put(SUMMARY, mBatteryStatsSummaryItem.toJson()); + } + if (mDetailedBatteryStatsItem != null) { + batteryStatsComponent.put(DETAILED_STATS, mDetailedBatteryStatsItem.toJson()); + } + } catch (JSONException e) { + // ignore + } + return batteryStatsComponent; + } +} diff --git a/src/com/android/loganalysis/item/DumpsysItem.java b/src/com/android/loganalysis/item/DumpsysItem.java index 866c56f..5bd82e2 100644 --- a/src/com/android/loganalysis/item/DumpsysItem.java +++ b/src/com/android/loganalysis/item/DumpsysItem.java @@ -25,28 +25,45 @@ import java.util.Set; public class DumpsysItem extends GenericItem { /** Constant for JSON output */ - private static final String BATTERY_INFO = "BATTERY_INFO"; + private static final String BATTERY_STATS = "BATTERY_STATS"; + /** Constant for JSON output */ + private static final String PROC_STATS = "PROC_STATS"; - private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(BATTERY_INFO)); + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + BATTERY_STATS, PROC_STATS)); /** - * The constructor for {@link BugreportItem}. + * The constructor for {@link DumpsysItem}. */ public DumpsysItem() { super(ATTRIBUTES); } /** - * Get the battery info section of the dumpsys. + * Set the {@link DumpsysBatteryStatsItem} of the bugreport. + */ + public void setBatteryInfo(DumpsysBatteryStatsItem batteryStats) { + setAttribute(BATTERY_STATS, batteryStats); + } + + /** + * Set the {@link DumpsysProcStatsItem} of the bugreport. + */ + public void setProcStats(DumpsysProcStatsItem procStats) { + setAttribute(PROC_STATS, procStats); + } + + /** + * Get the {@link DumpsysBatteryStatsItem} of the bugreport. */ - public DumpsysBatteryInfoItem getBatteryInfo() { - return (DumpsysBatteryInfoItem) getAttribute(BATTERY_INFO); + public DumpsysBatteryStatsItem getBatteryStats() { + return (DumpsysBatteryStatsItem) getAttribute(BATTERY_STATS); } /** - * Set the battery info section of the dumpsys. + * Get the {@link DumpsysProcStatsItem} of the bugreport. */ - public void setBatteryInfo(DumpsysBatteryInfoItem batteryInfo) { - setAttribute(BATTERY_INFO, batteryInfo); + public DumpsysProcStatsItem getProcStats() { + return (DumpsysProcStatsItem) getAttribute(PROC_STATS); } } diff --git a/src/com/android/loganalysis/item/DumpsysProcStatsItem.java b/src/com/android/loganalysis/item/DumpsysProcStatsItem.java new file mode 100644 index 0000000..846f70a --- /dev/null +++ b/src/com/android/loganalysis/item/DumpsysProcStatsItem.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015 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.item; + +/** + * An {@link IItem} used to store uid and processname map. It is going to be + * used a helper item and not going to be marshalled to JSON + */ +public class DumpsysProcStatsItem extends GenericMapItem<String> { + +} diff --git a/src/com/android/loganalysis/item/InterruptItem.java b/src/com/android/loganalysis/item/InterruptItem.java new file mode 100644 index 0000000..1169004 --- /dev/null +++ b/src/com/android/loganalysis/item/InterruptItem.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015 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.item; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * An {@link IItem} used to store information related to interrupts + */ +public class InterruptItem implements IItem { + /** Constant for JSON output */ + public static final String INTERRUPTS = "INTERRUPT_INFO"; + + private Collection<InterruptInfoItem> mInterrupts = new LinkedList<InterruptInfoItem>(); + + /** + * Enum for describing the type of interrupt + */ + public enum InterruptCategory { + WIFI_INTERRUPT, + MODEM_INTERRUPT, + ALARM_INTERRUPT, + ADSP_INTERRUPT, + UNKNOWN_INTERRUPT, + } + + public static class InterruptInfoItem extends GenericItem { + /** Constant for JSON output */ + public static final String NAME = "NAME"; + /** Constant for JSON output */ + public static final String CATEGORY = "CATEGORY"; + /** Constant for JSON output */ + public static final String INTERRUPT_COUNT = "INTERRUPT_COUNT"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + NAME, INTERRUPT_COUNT, CATEGORY)); + + /** + * The constructor for {@link InterruptItem} + * + * @param name The name of the wake lock + * @param interruptCount The number of times the interrupt woke up the AP + * @param category The {@link InterruptCategory} of the interrupt + */ + public InterruptInfoItem(String name, int interruptCount, + InterruptCategory category) { + super(ATTRIBUTES); + + setAttribute(NAME, name); + setAttribute(INTERRUPT_COUNT, interruptCount); + setAttribute(CATEGORY, category); + } + + /** + * Get the name of the interrupt + */ + public String getName() { + return (String) getAttribute(NAME); + } + + /** + * Get the interrupt count. + */ + public int getInterruptCount() { + return (Integer) getAttribute(INTERRUPT_COUNT); + } + + /** + * Get the {@link InterruptCategory} of the wake lock. + */ + public InterruptCategory getCategory() { + return (InterruptCategory) getAttribute(CATEGORY); + } + } + + /** + * Add an interrupt from the battery info section. + * + * @param name The name of the interrupt + * @param interruptCount Number of interrupts + * @param category The {@link InterruptCategory} of the interrupt. + */ + public void addInterrupt(String name, int interruptCount, + InterruptCategory category) { + mInterrupts.add(new InterruptInfoItem(name, interruptCount, category)); + } + + /** + * Get a list of {@link InterruptInfoItem} objects matching a given {@link InterruptCategory}. + */ + public List<InterruptInfoItem> getInterrupts(InterruptCategory category) { + LinkedList<InterruptInfoItem> interrupts = new LinkedList<InterruptInfoItem>(); + if (category == null) { + return interrupts; + } + + for (InterruptInfoItem interrupt : mInterrupts) { + if (category.equals(interrupt.getCategory())) { + interrupts.add(interrupt); + } + } + return interrupts; + } + + /** + * {@inheritDoc} + */ + @Override + public IItem merge(IItem other) throws ConflictingItemException { + throw new ConflictingItemException("Wakelock items cannot be merged"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isConsistent(IItem other) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public JSONObject toJson() { + JSONObject object = new JSONObject(); + if (mInterrupts != null) { + try { + JSONArray interrupts = new JSONArray(); + for (InterruptInfoItem interrupt : mInterrupts) { + interrupts.put(interrupt.toJson()); + } + object.put(INTERRUPTS, interrupts); + } catch (JSONException e) { + // Ignore + } + } + return object; + } +} diff --git a/src/com/android/loganalysis/item/ProcessUsageItem.java b/src/com/android/loganalysis/item/ProcessUsageItem.java new file mode 100644 index 0000000..e2feddb --- /dev/null +++ b/src/com/android/loganalysis/item/ProcessUsageItem.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2015 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.item; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +/** + * An {@link IItem} used to store information related to network bandwidth, sensor usage, + * alarm usage by each processes + */ +public class ProcessUsageItem implements IItem { + + /** Constant for JSON output */ + public static final String PROCESS_USAGE = "PROCESS_USAGE"; + + private Collection<ProcessUsageInfoItem> mProcessUsage = + new LinkedList<ProcessUsageInfoItem>(); + + public static class SensorInfoItem extends GenericItem { + /** Constant for JSON output */ + public static final String SENSOR_NAME = "SENSOR_NAME"; + /** Constant for JSON output */ + public static final String USAGE_DURATION = "USAGE_DURATION"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + SENSOR_NAME, USAGE_DURATION)); + + /** + * The constructor for {@link SensorInfoItem} + * + * @param name The name of the sensor + * @param usageDuration Duration of the usage + */ + public SensorInfoItem(String name, long usageDuration) { + super(ATTRIBUTES); + + setAttribute(SENSOR_NAME, name); + setAttribute(USAGE_DURATION, usageDuration); + } + + /** + * Get the sensor name + */ + public String getSensorName() { + return (String) getAttribute(SENSOR_NAME); + } + + /** + * Get the sensor usage duration in milliseconds + */ + public long getUsageDurationMs() { + return (long) getAttribute(USAGE_DURATION); + } + } + + public static class ProcessUsageInfoItem extends GenericItem { + /** Constant for JSON output */ + public static final String ALARM_WAKEUPS = "ALARM_WAKEUPS"; + /** Constant for JSON output */ + public static final String SENSOR_USAGE = "SENSOR_USAGE"; + /** Constant for JSON output */ + public static final String PROCESS_UID = "PROCESS_UID"; + + private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( + ALARM_WAKEUPS, SENSOR_USAGE, PROCESS_UID)); + + /** + * The constructor for {@link ProcessUsageItem} + * + * @param uid The name of the process + * @param alarmWakeups Number of alarm wakeups + * @param sensorUsage Different sensors used by the process + */ + public ProcessUsageInfoItem(String uid, int alarmWakeups, + LinkedList<SensorInfoItem> sensorUsage) { + super(ATTRIBUTES); + + setAttribute(PROCESS_UID, uid); + setAttribute(ALARM_WAKEUPS, alarmWakeups); + setAttribute(SENSOR_USAGE, sensorUsage); + } + + /** + * Get the number of Alarm wakeups + */ + public int getAlarmWakeups() { + return (int) getAttribute(ALARM_WAKEUPS); + } + + /** + * Get the Sensor usage of the process + */ + @SuppressWarnings("unchecked") + public LinkedList<SensorInfoItem> getSensorUsage() { + return (LinkedList<SensorInfoItem>) getAttribute(SENSOR_USAGE); + } + + /** + * Get the process name + */ + public String getProcessUID() { + return (String) getAttribute(PROCESS_UID); + } + + /** + * {@inheritDoc} + */ + @Override + public JSONObject toJson() { + JSONObject object = new JSONObject(); + try { + object.put(PROCESS_UID, getProcessUID()); + JSONArray sensorUsage = new JSONArray(); + for (SensorInfoItem usage : getSensorUsage()) { + sensorUsage.put(usage.toJson()); + } + object.put(SENSOR_USAGE, sensorUsage); + object.put(ALARM_WAKEUPS, getAlarmWakeups()); + + } catch (JSONException e) { + // Ignore + } + return object; + } + } + + /** + * Add individual process usage from the battery stats section. + * + * @param processUID The name of the process + * @param alarmWakeups The number of alarm wakeups + * @param sensorUsage Sensor usage of the process + */ + public void addProcessUsage(String processUID, int alarmWakeups, + LinkedList<SensorInfoItem> sensorUsage) { + mProcessUsage.add(new ProcessUsageInfoItem(processUID, alarmWakeups, sensorUsage)); + } + + /** + * {@inheritDoc} + */ + @Override + public IItem merge(IItem other) throws ConflictingItemException { + throw new ConflictingItemException("Wakelock items cannot be merged"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isConsistent(IItem other) { + return false; + } + + public Collection<ProcessUsageInfoItem> getProcessUsage() { + return mProcessUsage; + } + + /** + * {@inheritDoc} + */ + @Override + public JSONObject toJson() { + JSONObject object = new JSONObject(); + if (mProcessUsage != null) { + try { + JSONArray processUsage = new JSONArray(); + for (ProcessUsageInfoItem usage : mProcessUsage) { + processUsage.put(usage.toJson()); + } + object.put(PROCESS_USAGE, processUsage); + } catch (JSONException e) { + // Ignore + } + } + return object; + } +} diff --git a/src/com/android/loganalysis/item/DumpsysBatteryInfoItem.java b/src/com/android/loganalysis/item/WakelockItem.java index 8525f94..7bfab4b 100644 --- a/src/com/android/loganalysis/item/DumpsysBatteryInfoItem.java +++ b/src/com/android/loganalysis/item/WakelockItem.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2015 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. @@ -27,32 +27,30 @@ import java.util.List; import java.util.Set; /** - * An {@link IItem} used to store the battery info part of the dumpsys output. + * An {@link IItem} used to store information related to wake locks and kernel wake locks */ -public class DumpsysBatteryInfoItem implements IItem { +public class WakelockItem implements IItem { /** Constant for JSON output */ - public static final String WAKELOCKS = "WAKELOCKS"; + public static final String WAKELOCKS = "WAKELOCKS_INFO"; + + private Collection<WakelockInfoItem> mWakeLocks = new LinkedList<WakelockInfoItem>(); /** * Enum for describing the type of wakelock */ public enum WakeLockCategory { - LAST_CHARGE_WAKELOCK, - LAST_CHARGE_KERNEL_WAKELOCK, - LAST_UNPLUGGED_WAKELOCK, - LAST_UNPLUGGED_KERNEL_WAKELOCK; + KERNEL_WAKELOCK, + PARTIAL_WAKELOCK, } - /** - * A class designed to store information related to wake locks and kernel wake locks. - */ - public static class WakeLock extends GenericItem { - + public static class WakelockInfoItem extends GenericItem { /** Constant for JSON output */ public static final String NAME = "NAME"; /** Constant for JSON output */ - public static final String NUMBER = "NUMBER"; + public static final String PROCESS_UID = "PROCESS_UID"; + /** Constant for JSON output */ + public static final String PROCESS_NAME = "PROCESS_NAME"; /** Constant for JSON output */ public static final String HELD_TIME = "HELD_TIME"; /** Constant for JSON output */ @@ -61,35 +59,35 @@ public class DumpsysBatteryInfoItem implements IItem { public static final String CATEGORY = "CATEGORY"; private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( - NAME, NUMBER, HELD_TIME, LOCKED_COUNT, CATEGORY)); + NAME, PROCESS_UID, PROCESS_NAME, HELD_TIME, LOCKED_COUNT, CATEGORY)); /** - * The constructor for {@link WakeLock} + * The constructor for {@link WakelockItem} * * @param name The name of the wake lock * @param heldTime The amount of time held in milliseconds * @param lockedCount The number of times the wake lock was locked * @param category The {@link WakeLockCategory} of the wake lock */ - public WakeLock(String name, long heldTime, int lockedCount, WakeLockCategory category) { + public WakelockInfoItem(String name, long heldTime, int lockedCount, WakeLockCategory category) { this(name, null, heldTime, lockedCount, category); } /** - * The constructor for {@link WakeLock} + * The constructor for {@link WakelockItem} * * @param name The name of the wake lock - * @param number The number of the wake lock + * @param processUID The number of the wake lock * @param heldTime The amount of time held in milliseconds * @param lockedCount The number of times the wake lock was locked * @param category The {@link WakeLockCategory} of the wake lock */ - public WakeLock(String name, Integer number, long heldTime, int lockedCount, + public WakelockInfoItem(String name, String processUID, long heldTime, int lockedCount, WakeLockCategory category) { super(ATTRIBUTES); setAttribute(NAME, name); - setAttribute(NUMBER, number); + setAttribute(PROCESS_UID, processUID); setAttribute(HELD_TIME, heldTime); setAttribute(LOCKED_COUNT, lockedCount); setAttribute(CATEGORY, category); @@ -103,10 +101,10 @@ public class DumpsysBatteryInfoItem implements IItem { } /** - * Get the number of the wake lock. + * Get the process UID holding the wake lock. */ - public Integer getNumber() { - return (Integer) getAttribute(NUMBER); + public String getProcessUID() { + return (String) getAttribute(PROCESS_UID); } /** @@ -129,26 +127,31 @@ public class DumpsysBatteryInfoItem implements IItem { public WakeLockCategory getCategory() { return (WakeLockCategory) getAttribute(CATEGORY); } - } - private Collection<WakeLock> mWakeLocks = new LinkedList<WakeLock>(); + /** + * Set the process name holding the wake lock + */ + public void setWakelockProcessName(String processName) { + setAttribute(PROCESS_NAME, processName); + } + } /** - * Add a wakelock from the battery info section. + * Add a wakelock from the battery stats section. * * @param name The name of the wake lock. - * @param number The number of the wake lock. + * @param processUID The number of the wake lock. * @param heldTime The held time of the wake lock. * @param timesCalled The number of times the wake lock has been called. * @param category The {@link WakeLockCategory} of the wake lock. */ - public void addWakeLock(String name, Integer number, long heldTime, int timesCalled, + public void addWakeLock(String name, String processUID, long heldTime, int timesCalled, WakeLockCategory category) { - mWakeLocks.add(new WakeLock(name, number, heldTime, timesCalled, category)); + mWakeLocks.add(new WakelockInfoItem(name, processUID, heldTime, timesCalled, category)); } /** - * Add a wakelock from the battery info section. + * Add a wakelock from the battery stats section. * * @param name The name of the wake lock. * @param heldTime The held time of the wake lock. @@ -161,15 +164,15 @@ public class DumpsysBatteryInfoItem implements IItem { } /** - * Get a list of {@link WakeLock} objects matching a given {@link WakeLockCategory}. + * Get a list of {@link WakelockInfoItem} objects matching a given {@link WakeLockCategory}. */ - public List<WakeLock> getWakeLocks(WakeLockCategory category) { - LinkedList<WakeLock> wakeLocks = new LinkedList<WakeLock>(); + public List<WakelockInfoItem> getWakeLocks(WakeLockCategory category) { + LinkedList<WakelockInfoItem> wakeLocks = new LinkedList<WakelockInfoItem>(); if (category == null) { return wakeLocks; } - for (WakeLock wakeLock : mWakeLocks) { + for (WakelockInfoItem wakeLock : mWakeLocks) { if (category.equals(wakeLock.getCategory())) { wakeLocks.add(wakeLock); } @@ -178,11 +181,23 @@ public class DumpsysBatteryInfoItem implements IItem { } /** + * Get a list of {@link WakelockInfoItem} . + */ + public List<WakelockInfoItem> getWakeLocks() { + LinkedList<WakelockInfoItem> wakeLocks = new LinkedList<WakelockInfoItem>(); + + for (WakelockInfoItem wakeLock : mWakeLocks) { + wakeLocks.add(wakeLock); + } + return wakeLocks; + } + + /** * {@inheritDoc} */ @Override public IItem merge(IItem other) throws ConflictingItemException { - throw new ConflictingItemException("Dumpsys battery info items cannot be merged"); + throw new ConflictingItemException("Wakelock items cannot be merged"); } /** @@ -199,14 +214,16 @@ public class DumpsysBatteryInfoItem implements IItem { @Override public JSONObject toJson() { JSONObject object = new JSONObject(); - try { - JSONArray wakeLocks = new JSONArray(); - for (WakeLock wakeLock : mWakeLocks) { - wakeLocks.put(wakeLock.toJson()); + if (mWakeLocks != null) { + try { + JSONArray wakeLocks = new JSONArray(); + for (WakelockInfoItem wakeLock : mWakeLocks) { + wakeLocks.put(wakeLock.toJson()); + } + object.put(WAKELOCKS, wakeLocks); + } catch (JSONException e) { + // Ignore } - object.put(WAKELOCKS, wakeLocks); - } catch (JSONException e) { - // Ignore } return object; } diff --git a/src/com/android/loganalysis/parser/BatteryStatsDetailedInfoParser.java b/src/com/android/loganalysis/parser/BatteryStatsDetailedInfoParser.java new file mode 100644 index 0000000..f1bbed0 --- /dev/null +++ b/src/com/android/loganalysis/parser/BatteryStatsDetailedInfoParser.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2015 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.parser; + +import com.android.loganalysis.item.BatteryStatsDetailedInfoItem; +import com.android.loganalysis.item.InterruptItem; +import com.android.loganalysis.item.ProcessUsageItem; +import com.android.loganalysis.item.WakelockItem; +import com.android.loganalysis.util.NumberFormattingUtil; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * A {@link IParser} to parse the battery stats section of the bugreport + */ +public class BatteryStatsDetailedInfoParser extends AbstractSectionParser { + + private static final String WAKELOCK_SECTION_REGEX = "^\\s*All kernel wake locks:$"; + private static final String INTERRUPT_SECTION_REGEX = "^\\s*All wakeup reasons:$"; + private static final String PROCESS_USAGE_SECTION_REGEX = "^\\s*0:$"; + + /** + * Matches: Time on battery: 7h 45m 54s 332ms (98.3%) realtime, 4h 40m 51s 315ms (59.3%) uptime + */ + private static final Pattern TIME_ON_BATTERY_PATTERN = Pattern.compile( + "^\\s*Time on battery: (?:(\\d+)d)?\\s?(?:(\\d+)h)?\\s?(?:(\\d+)m)?\\s?(?:(\\d+)s)?" + + "\\s?(?:(\\d+)ms)?.*"); + /** + * Matches:Time on battery screen off: 1d 4h 6m 16s 46ms (99.1%) realtime, 6h 37m 49s 201ms + */ + private static final Pattern SCREEN_OFF_TIME_PATTERN = Pattern.compile("^\\s*Time on battery " + + "screen off: (?:(\\d+)d)?\\s?(?:(\\d+)h)?\\s?(?:(\\d+)m)?\\s?(?:(\\d+)s)?\\s?" + + "(?:(\\d+)ms).*"); + + private WakelockParser mWakelockParser = new WakelockParser(); + private InterruptParser mInterruptParser = new InterruptParser(); + private ProcessUsageParser mProcessUsageParser = new ProcessUsageParser(); + + private IParser mBatteryTimeParser = new IParser() { + @Override + public BatteryStatsDetailedInfoItem parse(List<String> lines) { + BatteryStatsDetailedInfoItem detailedInfo = null; + long timeOnBattery = 0, screenOffTime = 0; + Matcher m = null; + for (String line : lines) { + if (detailedInfo == null && !"".equals(line.trim())) { + detailedInfo = new BatteryStatsDetailedInfoItem(); + } + m = TIME_ON_BATTERY_PATTERN.matcher(line); + if (m.matches()) { + timeOnBattery = NumberFormattingUtil.getMs( + NumberFormattingUtil.parseIntOrZero(m.group(1)), + NumberFormattingUtil.parseIntOrZero(m.group(2)), + NumberFormattingUtil.parseIntOrZero(m.group(3)), + NumberFormattingUtil.parseIntOrZero(m.group(4)), + NumberFormattingUtil.parseIntOrZero(m.group(5))); + detailedInfo.setTimeOnBattery(timeOnBattery); + } else { + m = SCREEN_OFF_TIME_PATTERN.matcher(line); + if (m.matches()) { + screenOffTime = NumberFormattingUtil.getMs( + NumberFormattingUtil.parseIntOrZero(m.group(1)), + NumberFormattingUtil.parseIntOrZero(m.group(2)), + NumberFormattingUtil.parseIntOrZero(m.group(3)), + NumberFormattingUtil.parseIntOrZero(m.group(4)), + NumberFormattingUtil.parseIntOrZero(m.group(5))); + detailedInfo.setScreenOnTime(getScreenOnTime(timeOnBattery, screenOffTime)); + return detailedInfo; + } + } + } + return detailedInfo; + } + + private long getScreenOnTime(long timeOnBattery, long screenOffTime) { + if (timeOnBattery > screenOffTime) { + return (timeOnBattery - screenOffTime); + } + return 0; + } + }; + + private BatteryStatsDetailedInfoItem mBatteryStatsDetailedInfoItem = null; + private boolean mParsedInput = false; + + /** + * {@inheritDoc} + * + * @return The {@link BatteryStatsDetailedInfoItem} + */ + @Override + public BatteryStatsDetailedInfoItem parse(List<String> lines) { + setup(); + for (String line : lines) { + if (!mParsedInput && !"".equals(line.trim())) { + mParsedInput = true; + } + parseLine(line); + } + commit(); + return mBatteryStatsDetailedInfoItem; + } + + /** + * Sets up the parser by adding the section parsers. + */ + protected void setup() { + setParser(mBatteryTimeParser); + addSectionParser(mWakelockParser, WAKELOCK_SECTION_REGEX); + addSectionParser(mInterruptParser, INTERRUPT_SECTION_REGEX); + addSectionParser(mProcessUsageParser, PROCESS_USAGE_SECTION_REGEX); + } + + /** + * Set the {@link BatteryStatsDetailedInfoItem} + * + */ + @Override + protected void onSwitchParser() { + if (mBatteryStatsDetailedInfoItem == null) { + mBatteryStatsDetailedInfoItem = (BatteryStatsDetailedInfoItem) + getSection(mBatteryTimeParser); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void commit() { + // signal EOF + super.commit(); + if (mParsedInput) { + if (mBatteryStatsDetailedInfoItem == null) { + mBatteryStatsDetailedInfoItem = new BatteryStatsDetailedInfoItem(); + } + } + + if (mBatteryStatsDetailedInfoItem != null) { + mBatteryStatsDetailedInfoItem.setWakelockItem( + (WakelockItem) getSection(mWakelockParser)); + mBatteryStatsDetailedInfoItem.setInterruptItem( + (InterruptItem) getSection(mInterruptParser)); + mBatteryStatsDetailedInfoItem.setProcessUsageItem( + (ProcessUsageItem) getSection(mProcessUsageParser)); + } + } +} diff --git a/src/com/android/loganalysis/parser/BatteryStatsSummaryInfoParser.java b/src/com/android/loganalysis/parser/BatteryStatsSummaryInfoParser.java new file mode 100644 index 0000000..d5177e3 --- /dev/null +++ b/src/com/android/loganalysis/parser/BatteryStatsSummaryInfoParser.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2015 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.parser; + +import com.android.loganalysis.item.BatteryDischargeItem; +import com.android.loganalysis.item.BatteryDischargeItem.BatteryDischargeInfoItem; +import com.android.loganalysis.item.BatteryStatsSummaryInfoItem; +import com.android.loganalysis.util.NumberFormattingUtil; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link IParser} to parse batterystats summary + */ +public class BatteryStatsSummaryInfoParser implements IParser{ + + /** + * Matches: 0 (15) RESET:TIME: 2015-01-18-12-56-57 + */ + private static final Pattern RESET_TIME_PATTERN = Pattern.compile("^\\s*" + + "\\d\\s*\\(\\d+\\)\\s*RESET:TIME:\\s*(\\d+)-(\\d+)-(\\d+)-(\\d+)-(\\d+)-(\\d+)$"); + + /** + * Matches: +1d01h03m37s246ms (1) 028 c10400010 -running -wake_lock + */ + private static final Pattern BATTERY_DISCHARGE_PATTERN = Pattern.compile( + "^\\s*\\+(?:(\\d+)d)?(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?(?:(\\d+)ms)? \\(\\d+\\) " + + "(\\d+) \\w+ .*"); + + private BatteryDischargeItem mBatteryDischarge = new BatteryDischargeItem(); + private BatteryStatsSummaryInfoItem mItem = new BatteryStatsSummaryInfoItem(); + private long mBatteryDischargeRateAvg = 0; + private int mBatteryDischargeSamples = 0; + private Calendar mResetTime; + private static final int BATTERY_GROUP_LIMIT = 10; + + /** + * {@inheritDoc} + * + * @return The {@link BatteryStatsSummaryInfoItem}. + */ + @Override + public BatteryStatsSummaryInfoItem parse(List<String> lines) { + Matcher resetTimeMatcher = null; + Matcher dischargeMatcher = null; + + long previousDischargeElapsedTime= 0; + int previousBatteryLevel = 0; + boolean batteryDischargedFully = false; + for (String line : lines) { + resetTimeMatcher = RESET_TIME_PATTERN.matcher(line); + dischargeMatcher = BATTERY_DISCHARGE_PATTERN.matcher(line); + if (resetTimeMatcher.matches()) { + mResetTime = new GregorianCalendar(); + final int year = Integer.parseInt(resetTimeMatcher.group(1)); + final int month = Integer.parseInt(resetTimeMatcher.group(2)); + final int day = Integer.parseInt(resetTimeMatcher.group(3)); + final int hour = Integer.parseInt(resetTimeMatcher.group(4)); + final int minute = Integer.parseInt(resetTimeMatcher.group(5)); + final int second = Integer.parseInt(resetTimeMatcher.group(6)); + // Calendar month is zero indexed but the parsed date is 1-12 + mResetTime.set(year, (month - 1), day, hour, minute, second); + } else if (dischargeMatcher.matches()) { + final int days = NumberFormattingUtil.parseIntOrZero(dischargeMatcher.group(1)); + final int hours = NumberFormattingUtil.parseIntOrZero(dischargeMatcher.group(2)); + final int mins = NumberFormattingUtil.parseIntOrZero(dischargeMatcher.group(3)); + final int secs = NumberFormattingUtil.parseIntOrZero(dischargeMatcher.group(4)); + final int msecs = NumberFormattingUtil.parseIntOrZero(dischargeMatcher.group(5)); + final int batteryLevel = Integer.parseInt(dischargeMatcher.group(6)); + if (batteryLevel == 0) { + // Ignore the subsequent battery drop readings + batteryDischargedFully = true; + continue; + } else if (previousBatteryLevel == 0) { + // Ignore the first drop + previousBatteryLevel = batteryLevel; + continue; + } else if (!batteryDischargedFully && previousBatteryLevel != batteryLevel) { + long elapsedTime = NumberFormattingUtil.getMs(days, hours, mins, secs, msecs); + mBatteryDischargeRateAvg += (elapsedTime - previousDischargeElapsedTime); + mBatteryDischargeSamples++; + mBatteryDischarge.addBatteryDischargeInfo( + getDischargeClockTime(days, hours, mins, secs), + (elapsedTime - previousDischargeElapsedTime), batteryLevel); + previousDischargeElapsedTime = elapsedTime; + previousBatteryLevel = batteryLevel; + } + } + } + mItem.setBatteryDischargeRate(getAverageDischargeRate()); + mItem.setPeakDischargeTime(getPeakDischargeTime()); + return mItem; + } + + private Calendar getDischargeClockTime(int days, int hours, int mins, int secs) { + Calendar dischargeClockTime = new GregorianCalendar(); + + dischargeClockTime.setTime(mResetTime.getTime()); + dischargeClockTime.add(Calendar.DATE, days); + dischargeClockTime.add(Calendar.HOUR, hours); + dischargeClockTime.add(Calendar.MINUTE, mins); + dischargeClockTime.add(Calendar.SECOND, secs); + return dischargeClockTime; + } + + private String getAverageDischargeRate() { + if (mBatteryDischargeSamples == 0) { + return "The battery did not discharge"; + } + + final long minsPerLevel = mBatteryDischargeRateAvg / (mBatteryDischargeSamples * 60 * 1000); + return String.format("The battery dropped a level %d mins on average", minsPerLevel); + } + + private String getPeakDischargeTime() { + + int peakDischargeStartBatteryLevel = 0, peakDischargeStopBatteryLevel = 0; + long minDischargeDuration = 0; + Calendar peakDischargeStartTime= null, peakDischargeStopTime = null; + Queue <BatteryDischargeInfoItem> batteryDischargeWindow = + new LinkedList <BatteryDischargeInfoItem>(); + long sumDischargeDuration = 0; + for (BatteryDischargeInfoItem dischargeSteps : mBatteryDischarge.getDischargeStepsInfo()) { + batteryDischargeWindow.add(dischargeSteps); + sumDischargeDuration += dischargeSteps.getElapsedTime(); + if (batteryDischargeWindow.size() >= BATTERY_GROUP_LIMIT) { + final long averageDischargeDuration = sumDischargeDuration/BATTERY_GROUP_LIMIT; + final BatteryDischargeInfoItem startNode = batteryDischargeWindow.remove(); + sumDischargeDuration -= startNode.getElapsedTime(); + + if (minDischargeDuration == 0 || averageDischargeDuration < minDischargeDuration) { + minDischargeDuration = averageDischargeDuration; + peakDischargeStartBatteryLevel = startNode.getBatteryLevel(); + peakDischargeStopBatteryLevel = dischargeSteps.getBatteryLevel(); + peakDischargeStartTime = startNode.getClockTime(); + peakDischargeStopTime = dischargeSteps.getClockTime(); + } + } + } + if (peakDischargeStartTime != null && peakDischargeStopTime != null && + peakDischargeStartBatteryLevel > 0 && peakDischargeStopBatteryLevel > 0) { + return String.format( + "The peak discharge time was during %s to %s where battery dropped from %d to " + + "%d", peakDischargeStartTime.getTime().toString(), + peakDischargeStopTime.getTime().toString(), peakDischargeStartBatteryLevel, + peakDischargeStopBatteryLevel); + + } else { + return "The battery did not discharge"; + } + } + + /** + * Get the {@link BatteryStatsSummaryInfoItem}. + * <p> + * Exposed for unit testing. + * </p> + */ + BatteryStatsSummaryInfoItem getItem() { + return mItem; + } +} diff --git a/src/com/android/loganalysis/parser/DumpsysBatteryInfoParser.java b/src/com/android/loganalysis/parser/DumpsysBatteryInfoParser.java deleted file mode 100644 index afd4359..0000000 --- a/src/com/android/loganalysis/parser/DumpsysBatteryInfoParser.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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.parser; - -import com.android.loganalysis.item.DumpsysBatteryInfoItem; -import com.android.loganalysis.item.DumpsysBatteryInfoItem.WakeLockCategory; - -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A {@link IParser} to handle the "dumpsys batteryinfo" command output. - */ -public class DumpsysBatteryInfoParser implements IParser { - private static final Pattern LAST_CHARGED_START_PAT = Pattern.compile( - "^Statistics since last charge:$"); - private static final Pattern LAST_UNPLUGGED_START_PAT = Pattern.compile( - "^Statistics since last unplugged:$"); - private static final Pattern WAKE_LOCK_START_PAT = Pattern.compile( - "^ All partial wake locks:$"); - - private static final String WAKE_LOCK_PAT_SUFFIX = - "((\\d+)d )?((\\d+)h )?((\\d+)m )?((\\d+)s )?((\\d+)ms )?\\((\\d+) times\\) realtime"; - - /** - * Match a valid line such as: - * " Kernel Wake lock \"Process\": 1d 2h 3m 4s 5ms (6 times) realtime"; - */ - private static final Pattern KERNEL_WAKE_LOCK_PAT = Pattern.compile( - "^ Kernel Wake lock \"([^\"]+)\": " + WAKE_LOCK_PAT_SUFFIX); - /** - * Match a valid line such as: - * " Wake lock #1234 Process: 1d 2h 3m 4s 5ms (6 times) realtime"; - */ - private static final Pattern WAKE_LOCK_PAT = Pattern.compile( - "^ Wake lock #(\\d+) (.+): " + WAKE_LOCK_PAT_SUFFIX); - - private DumpsysBatteryInfoItem mItem = new DumpsysBatteryInfoItem(); - - /** - * {@inheritDoc} - */ - @Override - public DumpsysBatteryInfoItem parse(List<String> lines) { - WakeLockCategory kernelWakeLockCategory = null; - WakeLockCategory wakeLockCategory = null; - boolean inKernelWakeLock = false; - boolean inWakeLock = false; - - // Look for the section for last unplugged statistics. Kernel wakelocks are in the lines - // immediately following, until a blank line. Partial wake locks are in their own block, - // until a blank line. Return immediately after since there is nothing left to parse. - for (String line : lines) { - if (kernelWakeLockCategory == null || wakeLockCategory == null) { - Matcher m = LAST_CHARGED_START_PAT.matcher(line); - if (m.matches()) { - kernelWakeLockCategory = WakeLockCategory.LAST_CHARGE_KERNEL_WAKELOCK; - wakeLockCategory = WakeLockCategory.LAST_CHARGE_WAKELOCK; - inKernelWakeLock = true; - } - m = LAST_UNPLUGGED_START_PAT.matcher(line); - if (m.matches()) { - kernelWakeLockCategory = WakeLockCategory.LAST_UNPLUGGED_KERNEL_WAKELOCK; - wakeLockCategory = WakeLockCategory.LAST_UNPLUGGED_WAKELOCK; - inKernelWakeLock = true; - } - } else { - if (inKernelWakeLock) { - if ("".equals(line.trim())) { - inKernelWakeLock = false; - } else { - parseKernelWakeLock(line, kernelWakeLockCategory); - } - } else if (inWakeLock) { - if ("".equals(line.trim())) { - inWakeLock = false; - kernelWakeLockCategory = null; - wakeLockCategory = null; - } else { - parseWakeLock(line, wakeLockCategory); - } - } else { - Matcher m = WAKE_LOCK_START_PAT.matcher(line); - if (m.matches()) { - inWakeLock = true; - } - } - } - } - return mItem; - } - - /** - * Parse a line of output and add it to the last unplugged kernel wake lock section. - * <p> - * Exposed for unit testing. - * </p> - */ - void parseKernelWakeLock(String line, WakeLockCategory category) { - Matcher m = KERNEL_WAKE_LOCK_PAT.matcher(line); - if (!m.matches()) { - return; - } - - final String name = m.group(1); - final long days = parseLongOrZero(m.group(3)); - final long hours = parseLongOrZero(m.group(5)); - final long mins = parseLongOrZero(m.group(7)); - final long secs = parseLongOrZero(m.group(9)); - final long msecs = parseLongOrZero(m.group(11)); - final int timesCalled = Integer.parseInt(m.group(12)); - - mItem.addWakeLock(name, getMs(days, hours, mins, secs, msecs), timesCalled, category); - } - - /** - * Parse a line of output and add it to the last unplugged wake lock section. - * <p> - * Exposed for unit testing. - * </p> - */ - void parseWakeLock(String line, WakeLockCategory category) { - Matcher m = WAKE_LOCK_PAT.matcher(line); - if (!m.matches()) { - return; - } - - final int number = Integer.parseInt(m.group(1)); - final String name = m.group(2); - final long days = parseLongOrZero(m.group(4)); - final long hours = parseLongOrZero(m.group(6)); - final long mins = parseLongOrZero(m.group(8)); - final long secs = parseLongOrZero(m.group(10)); - final long msecs = parseLongOrZero(m.group(12)); - final int timesCalled = Integer.parseInt(m.group(13)); - - mItem.addWakeLock(name, number, getMs(days, hours, mins, secs, msecs), timesCalled, - category); - } - - /** - * Get the {@link DumpsysBatteryInfoItem}. - * <p> - * Exposed for unit testing. - * </p> - */ - DumpsysBatteryInfoItem getItem() { - return mItem; - } - - /** - * Convert days/hours/mins/secs/msecs into milliseconds. - * <p> - * Exposed for unit testing. - * </p> - */ - static long getMs(long days, long hours, long mins, long secs, long msecs) { - return (((24 * days + hours) * 60 + mins) * 60 + secs) * 1000 + msecs; - } - - /** - * Parses a string into a long, or returns 0 if the string is null. - * - * @param s a {@link String} containing the long representation to be parsed - * @return the long represented by the argument in decimal, or 0 if the string is {@code null}. - * @throws NumberFormatException if the string is not {@code null} or does not contain a - * parsable long. - */ - private long parseLongOrZero(String s) { - if (s == null) { - return 0; - } - return Long.parseLong(s); - } -} diff --git a/src/com/android/loganalysis/parser/DumpsysBatteryStatsParser.java b/src/com/android/loganalysis/parser/DumpsysBatteryStatsParser.java new file mode 100644 index 0000000..3e8be2c --- /dev/null +++ b/src/com/android/loganalysis/parser/DumpsysBatteryStatsParser.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 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.parser; + +import com.android.loganalysis.item.BatteryStatsDetailedInfoItem; +import com.android.loganalysis.item.DumpsysBatteryStatsItem; +import com.android.loganalysis.item.BatteryStatsSummaryInfoItem; + +import java.util.List; + + +/** + * A {@link IParser} to parse the battery stats section of the bugreport + */ +public class DumpsysBatteryStatsParser extends AbstractSectionParser { + + private static final String SUMMARY_INFO_SECTION_REGEX = + "Battery History \\(\\d+% used, \\d+KB used of \\d+KB, \\d+ strings using \\d+KB\\):$"; + private static final String DETAILED_INFO_SECTION_REGEX = "^Statistics since last charge:$"; + private static final String NOOP_SECTION_REGEX = "^Statistics since last unplugged:$"; + + private BatteryStatsSummaryInfoParser mSummaryParser = new BatteryStatsSummaryInfoParser(); + private BatteryStatsDetailedInfoParser mDetailedParser = new BatteryStatsDetailedInfoParser(); + + private DumpsysBatteryStatsItem mDumpsysBatteryStatsItem = null; + private boolean mParsedInput = false; + + /** + * {@inheritDoc} + * + * @return The {@link DumpsysBatteryStatsItem} + */ + @Override + public DumpsysBatteryStatsItem parse(List<String> lines) { + setup(); + for (String line : lines) { + if (!mParsedInput && !"".equals(line.trim())) { + mParsedInput = true; + } + parseLine(line); + } + commit(); + return mDumpsysBatteryStatsItem; + } + + /** + * Sets up the parser by adding the section parsers. + */ + protected void setup() { + addSectionParser(mSummaryParser, SUMMARY_INFO_SECTION_REGEX); + addSectionParser(mDetailedParser, DETAILED_INFO_SECTION_REGEX); + addSectionParser(new NoopParser(), NOOP_SECTION_REGEX); + } + + /** + * {@inheritDoc} + */ + @Override + protected void commit() { + // signal EOF + super.commit(); + if (mParsedInput) { + if (mDumpsysBatteryStatsItem == null) { + mDumpsysBatteryStatsItem = new DumpsysBatteryStatsItem(); + } + } + + if (mDumpsysBatteryStatsItem != null) { + mDumpsysBatteryStatsItem.setBatteryStatsSummarytem( + (BatteryStatsSummaryInfoItem) getSection(mSummaryParser)); + mDumpsysBatteryStatsItem.setDetailedBatteryStatsItem( + (BatteryStatsDetailedInfoItem) getSection(mDetailedParser)); + } + } +} diff --git a/src/com/android/loganalysis/parser/DumpsysParser.java b/src/com/android/loganalysis/parser/DumpsysParser.java index e950341..d8941e0 100644 --- a/src/com/android/loganalysis/parser/DumpsysParser.java +++ b/src/com/android/loganalysis/parser/DumpsysParser.java @@ -15,8 +15,10 @@ */ package com.android.loganalysis.parser; -import com.android.loganalysis.item.DumpsysBatteryInfoItem; + +import com.android.loganalysis.item.DumpsysBatteryStatsItem; import com.android.loganalysis.item.DumpsysItem; +import com.android.loganalysis.item.DumpsysProcStatsItem; import java.util.List; @@ -24,10 +26,14 @@ import java.util.List; * A {@link IParser} to handle the output of the dumpsys section of the bugreport. */ public class DumpsysParser extends AbstractSectionParser { - private static final String BATTERY_INFO_SECTION_REGEX = "DUMP OF SERVICE batteryinfo:"; + + private static final String BATTERY_STATS_SECTION_REGEX = "^DUMP OF SERVICE batterystats:$"; + private static final String PROC_STATS_SECTION_REGEX = "^DUMP OF SERVICE procstats:"; private static final String NOOP_SECTION_REGEX = "DUMP OF SERVICE .*"; - private DumpsysBatteryInfoParser mBatteryInfoParser = new DumpsysBatteryInfoParser(); + private DumpsysBatteryStatsParser mBatteryStatsParser = new DumpsysBatteryStatsParser(); + private DumpsysProcStatsParser mProcStatsParser = new DumpsysProcStatsParser(); + private DumpsysItem mDumpsys = null; /** @@ -53,7 +59,8 @@ public class DumpsysParser extends AbstractSectionParser { * Sets up the parser by adding the section parsers. */ protected void setup() { - addSectionParser(mBatteryInfoParser, BATTERY_INFO_SECTION_REGEX); + addSectionParser(mBatteryStatsParser, BATTERY_STATS_SECTION_REGEX); + addSectionParser(mProcStatsParser, PROC_STATS_SECTION_REGEX); addSectionParser(new NoopParser(), NOOP_SECTION_REGEX); } @@ -64,10 +71,12 @@ public class DumpsysParser extends AbstractSectionParser { protected void commit() { // signal EOF super.commit(); - + if (mDumpsys == null) { + mDumpsys = new DumpsysItem(); + } if (mDumpsys != null) { - mDumpsys.setBatteryInfo( - (DumpsysBatteryInfoItem) getSection(mBatteryInfoParser)); + mDumpsys.setBatteryInfo((DumpsysBatteryStatsItem) getSection(mBatteryStatsParser)); + mDumpsys.setProcStats((DumpsysProcStatsItem) getSection(mProcStatsParser)); } } } diff --git a/src/com/android/loganalysis/parser/DumpsysProcStatsParser.java b/src/com/android/loganalysis/parser/DumpsysProcStatsParser.java new file mode 100644 index 0000000..010036b --- /dev/null +++ b/src/com/android/loganalysis/parser/DumpsysProcStatsParser.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 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.parser; + +import com.android.loganalysis.item.DumpsysProcStatsItem; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link IParser} to parse procstats and create a mapping table of process names and UIDs + */ +public class DumpsysProcStatsParser implements IParser { + + /** + * Matches: * com.google.android.googlequicksearchbox:search / u0a19 / v300401240: ----- + */ + private static final Pattern UID = Pattern.compile("^\\s*\\* (.*):?.*/ (.*)/.*"); + + /** + * {@inheritDoc} + * + * @return The {@link DumpsysProcStatsItem}. + */ + @Override + public DumpsysProcStatsItem parse(List<String> lines) { + DumpsysProcStatsItem item = new DumpsysProcStatsItem(); + for (String line : lines) { + Matcher m = UID.matcher(line); + if(m.matches()) { + item.put(m.group(2).trim(), m.group(1).trim()); + } + } + return item; + } + +} diff --git a/src/com/android/loganalysis/parser/InterruptParser.java b/src/com/android/loganalysis/parser/InterruptParser.java new file mode 100644 index 0000000..26417c4 --- /dev/null +++ b/src/com/android/loganalysis/parser/InterruptParser.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 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.parser; + +import com.android.loganalysis.item.InterruptItem; +import com.android.loganalysis.item.InterruptItem.InterruptCategory; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link IParser} to parse wake up interrupts + */ +public class InterruptParser implements IParser { + + /** + * Matches: Wakeup reason 289:bcmsdh_sdmmc:200:qcom,smd-rpm:240:msmgpio: + * 20m 5s 194ms (1485 times) realtime + */ + private static final Pattern Interrupt = Pattern.compile( + "^\\s*Wakeup reason (.*): (?:\\d+h )?(?:\\d+m )?(?:\\d+s )(?:\\d+ms )" + + "\\((\\d+) times\\) realtime"); + + private InterruptItem mItem = new InterruptItem(); + + /** + * {@inheritDoc} + * + * @return The {@link InterruptItem}. + */ + @Override + public InterruptItem parse(List<String> lines) { + for (String line : lines) { + Matcher m = Interrupt.matcher(line); + if(m.matches()) { + final String interruptName = m.group(1); + final int interruptCount = Integer.parseInt(m.group(2)); + mItem.addInterrupt(interruptName, interruptCount, + getInterruptCategory(interruptName)); + } else { + // Done with interrupts + break; + } + } + return mItem; + } + + /** + * Get the {@link InterruptItem}. + * <p> + * Exposed for unit testing. + * </p> + */ + InterruptItem getItem() { + return mItem; + } + + private InterruptCategory getInterruptCategory(String interruptName) { + if (interruptName.contains("bcmsdh_sdmmc")) { + return InterruptCategory.WIFI_INTERRUPT; + } else if (interruptName.contains("smd-modem") || + interruptName.contains("smsm-modem")) { + return InterruptCategory.MODEM_INTERRUPT; + } else if (interruptName.contains("smd-adsp")) { + return InterruptCategory.ADSP_INTERRUPT; + } else if (interruptName.contains("max77686-irq") || + interruptName.contains("cpcap-irq") || + interruptName.contains("TWL6030-PIH")) { + return InterruptCategory.ALARM_INTERRUPT; + } + + return InterruptCategory.UNKNOWN_INTERRUPT; + } + +} diff --git a/src/com/android/loganalysis/parser/ProcessUsageParser.java b/src/com/android/loganalysis/parser/ProcessUsageParser.java new file mode 100644 index 0000000..9609cd2 --- /dev/null +++ b/src/com/android/loganalysis/parser/ProcessUsageParser.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 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.parser; + +import com.android.loganalysis.item.ProcessUsageItem; +import com.android.loganalysis.item.ProcessUsageItem.SensorInfoItem; +import com.android.loganalysis.util.NumberFormattingUtil; + +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link IParser} to handle the parsing of process usage information + */ +public class ProcessUsageParser implements IParser { + + private ProcessUsageItem mItem = new ProcessUsageItem(); + private LinkedList<SensorInfoItem> mSensorUsage = new LinkedList<SensorInfoItem>(); + + /** + * Matches: 1000: + */ + private static final Pattern UID_PATTERN = Pattern.compile("^\\s*(\\w+):$"); + + /** + * Matches: Sensor 1: 12m 52s 311ms realtime (29 times) + */ + private static final Pattern SENSOR_PATTERN = Pattern.compile( + "^\\s*Sensor (\\d+): (?:(\\d+)d\\s)?" + + "(?:(\\d+)h\\s)?(?:(\\d+)m\\s)?(?:(\\d+)s\\s)?(\\d+)ms " + + "realtime \\((\\d+) times\\)$"); + + /** + * Matches: 507 wakeup alarms + */ + private static final Pattern ALARM_PATTERN = Pattern.compile("^\\s*(\\d+) wakeup alarms$"); + + /** + * {@inheritDoc} + */ + @Override + public ProcessUsageItem parse(List<String> lines) { + String processUid = null; + int alarmWakeups = 0; + for (String line : lines) { + Matcher m = UID_PATTERN.matcher(line); + if (m.matches()) { + if (processUid != null) { + // Save the process usage info for the previous process + mItem.addProcessUsage(processUid, alarmWakeups, mSensorUsage); + } + processUid = m.group(1); + mSensorUsage = new LinkedList<SensorInfoItem>(); + continue; + } + m = SENSOR_PATTERN.matcher(line); + if (m.matches()) { + final long duration = NumberFormattingUtil.getMs( + NumberFormattingUtil.parseIntOrZero(m.group(2)), + NumberFormattingUtil.parseIntOrZero(m.group(3)), + NumberFormattingUtil.parseIntOrZero(m.group(4)), + NumberFormattingUtil.parseIntOrZero(m.group(5)), + NumberFormattingUtil.parseIntOrZero(m.group(6))); + mSensorUsage.add(new SensorInfoItem(m.group(1), duration)); + continue; + } + m = ALARM_PATTERN.matcher(line); + if (m.matches()) { + alarmWakeups = Integer.parseInt(m.group(1)); + } + } + return mItem; + } + + /** + * Get the {@link ProcessUsageItem}. + * <p> + * Exposed for unit testing. + * </p> + */ + ProcessUsageItem getItem() { + return mItem; + } +} diff --git a/src/com/android/loganalysis/parser/WakelockParser.java b/src/com/android/loganalysis/parser/WakelockParser.java new file mode 100644 index 0000000..91652f7 --- /dev/null +++ b/src/com/android/loganalysis/parser/WakelockParser.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 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.parser; + +import com.android.loganalysis.item.WakelockItem; +import com.android.loganalysis.item.WakelockItem.WakeLockCategory; +import com.android.loganalysis.util.NumberFormattingUtil; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link IParser} to handle the parsing of wakelock information + */ +public class WakelockParser implements IParser { + + private static final Pattern PARTIAL_WAKE_LOCK_START_PAT = Pattern.compile( + "^\\s*All partial wake locks:$"); + + private static final String WAKE_LOCK_PAT_SUFFIX = + "(?:(\\d+)d)?\\s?(?:(\\d+)h)?\\s?(?:(\\d+)m)?\\s?(?:(\\d+)s)?\\s?(?:(\\d+)ms)?" + + "\\s?\\((\\d+) times\\) realtime"; + + /** + * Match a valid line such as: + * " Kernel Wake lock PowerManagerService.WakeLocks: 1h 13m 50s 950ms (2858 times) realtime" + */ + private static final Pattern KERNEL_WAKE_LOCK_PAT = Pattern.compile( + "^\\s*Kernel Wake lock (.+): " + WAKE_LOCK_PAT_SUFFIX); + /** + * Match a valid line such as: + * " Wake lock u0a7 NlpWakeLock: 8m 13s 203ms (1479 times) realtime"; + */ + private static final Pattern PARTIAL_WAKE_LOCK_PAT = Pattern.compile( + "^\\s*Wake lock (.*)\\s(.+): " + WAKE_LOCK_PAT_SUFFIX); + + private WakelockItem mItem = new WakelockItem(); + + public static final int TOP_WAKELOCK_COUNT = 5; + + /** + * {@inheritDoc} + */ + @Override + public WakelockItem parse(List<String> lines) { + boolean inPartialWakeLock = false; + Matcher m = null; + int wakelockCounter = 0; + for (String line : lines) { + if ("".equals(line.trim())) { + if (inPartialWakeLock) { + // Done with parsing wakelock sections + break; + } else { + // Done with parsing kernel wakelocks and continue with + // partial wakelock + wakelockCounter = 0; + continue; + } + } + m = KERNEL_WAKE_LOCK_PAT.matcher(line); + if (m.matches()) { + if (wakelockCounter < TOP_WAKELOCK_COUNT && + !line.contains("PowerManagerService.WakeLocks")) { + parseKernelWakeLock(line, WakeLockCategory.KERNEL_WAKELOCK); + wakelockCounter++; + } + continue; + } + m = PARTIAL_WAKE_LOCK_START_PAT.matcher(line); + if (m.matches()) { + inPartialWakeLock = true; + continue; + } + m = PARTIAL_WAKE_LOCK_PAT.matcher(line); + if (m.matches() && wakelockCounter < TOP_WAKELOCK_COUNT) { + parsePartialWakeLock(line, WakeLockCategory.PARTIAL_WAKELOCK); + wakelockCounter++; + } + } + return mItem; + } + + /** + * Parse a line of output and add it to wakelock section + * <p> + * Exposed for unit testing. + * </p> + */ + void parseKernelWakeLock(String line, WakeLockCategory category) { + Matcher m = KERNEL_WAKE_LOCK_PAT.matcher(line); + if (!m.matches()) { + return; + } + final String name = m.group(1); + final long wakelockTime = NumberFormattingUtil.getMs( + NumberFormattingUtil.parseIntOrZero(m.group(2)), + NumberFormattingUtil.parseIntOrZero(m.group(3)), + NumberFormattingUtil.parseIntOrZero(m.group(4)), + NumberFormattingUtil.parseIntOrZero(m.group(5)), + NumberFormattingUtil.parseIntOrZero(m.group(6))); + + final int timesCalled = Integer.parseInt(m.group(7)); + + mItem.addWakeLock(name, wakelockTime, timesCalled, category); + } + + /** + * Parse a line of output and add it to wake lock section. + * <p> + * Exposed for unit testing. + * </p> + */ + void parsePartialWakeLock(String line, WakeLockCategory category) { + Matcher m = PARTIAL_WAKE_LOCK_PAT.matcher(line); + if (!m.matches()) { + return; + } + final String processUID = m.group(1); + final String name = m.group(2); + final long wakelockTime = NumberFormattingUtil.getMs( + NumberFormattingUtil.parseIntOrZero(m.group(3)), + NumberFormattingUtil.parseIntOrZero(m.group(4)), + NumberFormattingUtil.parseIntOrZero(m.group(5)), + NumberFormattingUtil.parseIntOrZero(m.group(6)), + NumberFormattingUtil.parseIntOrZero(m.group(7))); + final int timesCalled = Integer.parseInt(m.group(8)); + + mItem.addWakeLock(name, processUID, wakelockTime, timesCalled, category); + } + + /** + * Get the {@link WakelockItem}. + * <p> + * Exposed for unit testing. + * </p> + */ + WakelockItem getItem() { + return mItem; + } +} diff --git a/src/com/android/loganalysis/rule/AbstractPowerRule.java b/src/com/android/loganalysis/rule/AbstractPowerRule.java new file mode 100644 index 0000000..cab46a3 --- /dev/null +++ b/src/com/android/loganalysis/rule/AbstractPowerRule.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 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.rule; + +import com.android.loganalysis.item.BatteryStatsDetailedInfoItem; +import com.android.loganalysis.item.BugreportItem; +import com.android.loganalysis.item.BatteryStatsSummaryInfoItem; +import com.android.loganalysis.item.DumpsysProcStatsItem; + +import org.json.JSONObject; + +/** + * Base class for all power rules + */ +public abstract class AbstractPowerRule implements IRule { + + private BugreportItem mBugreportItem; + private BatteryStatsSummaryInfoItem mPowerSummaryAnalysisItem; + private BatteryStatsDetailedInfoItem mPowerDetailedAnalysisItem; + private DumpsysProcStatsItem mProcStatsItem; + + public AbstractPowerRule(BugreportItem bugreportItem) { + mBugreportItem = bugreportItem; + mPowerSummaryAnalysisItem = mBugreportItem.getDumpsys().getBatteryStats(). + getBatteryStatsSummaryItem(); + mPowerDetailedAnalysisItem = mBugreportItem.getDumpsys().getBatteryStats(). + getDetailedBatteryStatsItem(); + mProcStatsItem = mBugreportItem.getDumpsys().getProcStats(); + } + + protected long getTimeOnBattery() { + return mPowerDetailedAnalysisItem.getTimeOnBattery(); + } + + protected BatteryStatsSummaryInfoItem getSummaryItem() { + return mPowerSummaryAnalysisItem; + } + + protected BatteryStatsDetailedInfoItem getDetailedAnalysisItem() { + return mPowerDetailedAnalysisItem; + } + + protected DumpsysProcStatsItem getProcStatsItem() { + return mProcStatsItem; + } + + @Override + public abstract void applyRule(); + + @Override + public abstract JSONObject getAnalysis(); + +} diff --git a/src/com/android/loganalysis/rule/IRule.java b/src/com/android/loganalysis/rule/IRule.java new file mode 100644 index 0000000..416d7f6 --- /dev/null +++ b/src/com/android/loganalysis/rule/IRule.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015 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.rule; + +import org.json.JSONObject; + +/** + * An interface which defines the rules. Individual rules will apply the filter on the parsed data + * and return the high level analysis in JSON Format + */ +public interface IRule { + // Apply the rules + public void applyRule(); + + public JSONObject getAnalysis(); +} diff --git a/src/com/android/loganalysis/rule/ProcessUsageRule.java b/src/com/android/loganalysis/rule/ProcessUsageRule.java new file mode 100644 index 0000000..4e15aee --- /dev/null +++ b/src/com/android/loganalysis/rule/ProcessUsageRule.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015 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.rule; + +import com.android.loganalysis.item.BugreportItem; +import com.android.loganalysis.item.DumpsysProcStatsItem; +import com.android.loganalysis.item.ProcessUsageItem; +import com.android.loganalysis.item.ProcessUsageItem.ProcessUsageInfoItem; + +import org.json.JSONException; +import org.json.JSONObject; + + +/** + * Rules definition for Process usage + */ +public class ProcessUsageRule extends AbstractPowerRule { + + private static final String PROCESS_USAGE_ANALYSIS = "PROCESS_USAGE_ANALYSIS"; + private static final long ALARM_THRESHOLD = 60000; + + private StringBuffer mAnalysisBuffer; + + public ProcessUsageRule (BugreportItem bugreportItem) { + super(bugreportItem); + } + + + @Override + public void applyRule() { + mAnalysisBuffer = new StringBuffer(); + ProcessUsageItem processUsageItem = getDetailedAnalysisItem().getProcessUsageItem(); + DumpsysProcStatsItem procStatsItem = getProcStatsItem(); + if (processUsageItem != null && procStatsItem!= null) { + for (ProcessUsageInfoItem usage : processUsageItem.getProcessUsage()) { + if (usage.getAlarmWakeups() > 0) { + final long alarmsPerMs = getTimeOnBattery()/usage.getAlarmWakeups(); + if (alarmsPerMs < ALARM_THRESHOLD) { + final String processName = procStatsItem.get(usage.getProcessUID()); + if (processName != null) { + mAnalysisBuffer.append(processName); + } else { + mAnalysisBuffer.append(usage.getProcessUID()); + } + mAnalysisBuffer.append(" has requested frequent repeating alarms"); + } + } + } + } + } + + @Override + public JSONObject getAnalysis() { + JSONObject usageAnalysis = new JSONObject(); + try { + usageAnalysis.put(PROCESS_USAGE_ANALYSIS, mAnalysisBuffer.toString()); + } catch (JSONException e) { + // do nothing + } + return usageAnalysis; + } +} diff --git a/src/com/android/loganalysis/rule/RuleEngine.java b/src/com/android/loganalysis/rule/RuleEngine.java new file mode 100644 index 0000000..add61e6 --- /dev/null +++ b/src/com/android/loganalysis/rule/RuleEngine.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2015 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.rule; + +import com.android.loganalysis.item.BugreportItem; + +import org.json.JSONArray; + +import java.util.Collection; +import java.util.LinkedList; + + +/** + * Applies rules to the parsed bugreport + */ +public class RuleEngine { + + public enum RuleType{ + ALL, POWER; + } + + BugreportItem mBugreportItem; + private Collection<IRule> mRulesList; + + public RuleEngine(BugreportItem bugreportItem) { + mBugreportItem = bugreportItem; + mRulesList = new LinkedList<IRule>(); + } + + public void registerRules(RuleType ruleType) { + if (ruleType == RuleType.ALL) { + // add all rules + addPowerRules(); + } else if (ruleType == RuleType.POWER) { + addPowerRules(); + } + } + + public void executeRules() { + for (IRule rule : mRulesList) { + rule.applyRule(); + } + } + + public JSONArray getAnalysis() { + JSONArray result = new JSONArray(); + for (IRule rule : mRulesList) { + result.put(rule.getAnalysis()); + } + return result; + } + + private void addPowerRules() { + mRulesList.add(new WakelockRule(mBugreportItem)); + mRulesList.add(new ProcessUsageRule(mBugreportItem)); + } +} diff --git a/src/com/android/loganalysis/rule/WakelockRule.java b/src/com/android/loganalysis/rule/WakelockRule.java new file mode 100644 index 0000000..b2db2cd --- /dev/null +++ b/src/com/android/loganalysis/rule/WakelockRule.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 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.rule; + +import com.android.loganalysis.item.BugreportItem; +import com.android.loganalysis.item.WakelockItem; +import com.android.loganalysis.item.WakelockItem.WakelockInfoItem; +import com.android.loganalysis.util.NumberFormattingUtil; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Rules definition for wakelock + */ +public class WakelockRule extends AbstractPowerRule { + + private static final String WAKELOCK_ANALYSIS = "WAKELOCK_ANALYSIS"; + private static final float WAKELOCK_HELD_TIME_THRESHOLD_PERCENTAGE = 0.1f; // 10% + + private String mAnalysis = null; + + public WakelockRule (BugreportItem bugreportItem) { + super(bugreportItem); + } + + @SuppressWarnings("cast") + @Override + public void applyRule() { + WakelockItem wakelockItem = getDetailedAnalysisItem().getWakelockItem(); + if (wakelockItem != null) { + long wakelockThreshold = (long)(getTimeOnBattery() + * WAKELOCK_HELD_TIME_THRESHOLD_PERCENTAGE); + + for (WakelockInfoItem wakelocks : wakelockItem.getWakeLocks()) { + if (wakelocks.getHeldTime() > wakelockThreshold) { + mAnalysis = String.format("%s %s is held for %s", wakelocks.getName(), + wakelocks.getCategory(), + NumberFormattingUtil.getDuration(wakelocks.getHeldTime())); + } + } + } + } + + @Override + public JSONObject getAnalysis() { + JSONObject wakelockAnalysis = new JSONObject(); + try { + if (mAnalysis != null) { + wakelockAnalysis.put(WAKELOCK_ANALYSIS, mAnalysis); + } + } catch (JSONException e) { + // do nothing + } + return wakelockAnalysis; + } +} diff --git a/src/com/android/loganalysis/util/NumberFormattingUtil.java b/src/com/android/loganalysis/util/NumberFormattingUtil.java new file mode 100644 index 0000000..8652c2e --- /dev/null +++ b/src/com/android/loganalysis/util/NumberFormattingUtil.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2015 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.util; + +import java.util.concurrent.TimeUnit; + + + +/** + * Utility methods for number formatting + */ +public class NumberFormattingUtil { + + private NumberFormattingUtil() { + } + + /** + * Convert days/hours/mins/secs/msecs into milliseconds. + */ + public static long getMs(long days, long hours, long mins, long secs, long msecs) { + return (((24 * days + hours) * 60 + mins) * 60 + secs) * 1000 + msecs; + } + + /** + * Convert hours/mins/secs/msecs into milliseconds. + */ + public static long getMs(long hours, long mins, long secs, long msecs) { + return getMs(0, hours, mins, secs, msecs); + } + + /** + * Parses a string into a long, or returns 0 if the string is null. + * + * @param s a {@link String} containing the long representation to be parsed + * @return the long represented by the argument in decimal, or 0 if the string is {@code null}. + * @throws NumberFormatException if the string is not {@code null} or does not contain a + * parsable long. + */ + public static long parseLongOrZero(String s) throws NumberFormatException { + if (s == null) { + return 0; + } + return Long.parseLong(s); + } + + /** + * Parses a string into a int, or returns 0 if the string is null. + * + * @param s a {@link String} containing the int representation to be parsed + * @return the int represented by the argument in decimal, or 0 if the string is {@code null}. + * @throws NumberFormatException if the string is not {@code null} or does not contain a + * parsable long. + */ + public static int parseIntOrZero(String s) throws NumberFormatException { + if (s == null) { + return 0; + } + return Integer.parseInt(s); + } + + /** + * Converts milliseconds to days/hours/mins/secs + * + * @param ms elapsed time in ms + * @return the duration in days/hours/mins/secs + */ + public static String getDuration(long ms) { + if (ms <= 0) { + return "Not a valid time"; + } + final long days = TimeUnit.MILLISECONDS.toDays(ms); + final long hrs = TimeUnit.MILLISECONDS.toHours(ms - TimeUnit.DAYS.toMillis(days)); + final long mins = TimeUnit.MILLISECONDS.toMinutes(ms - TimeUnit.DAYS.toMillis(days) + - TimeUnit.HOURS.toMillis(hrs)); + final long secs = TimeUnit.MILLISECONDS.toSeconds(ms - TimeUnit.DAYS.toMillis(days) + - TimeUnit.HOURS.toMillis(hrs) - TimeUnit.MINUTES.toMillis(mins)); + + return String.format("%dd %dh %dm %ds", days, hrs, mins, secs); + } +} + |