| /* |
| * 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; |
| } |
| } |