1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.loganalysis.parser;
17 
18 import com.android.loganalysis.item.BatteryDischargeItem;
19 import com.android.loganalysis.item.BatteryDischargeItem.BatteryDischargeInfoItem;
20 import com.android.loganalysis.item.BatteryStatsSummaryInfoItem;
21 import com.android.loganalysis.util.NumberFormattingUtil;
22 
23 import java.util.Calendar;
24 import java.util.GregorianCalendar;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Queue;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 
31 /**
32  * A {@link IParser} to parse batterystats summary
33  */
34 public class BatteryStatsSummaryInfoParser implements IParser{
35 
36     /**
37      * Matches: 0 (15) RESET:TIME: 2015-01-18-12-56-57
38      */
39     private static final Pattern RESET_TIME_PATTERN = Pattern.compile("^\\s*"
40             + "\\d\\s*\\(\\d+\\)\\s*RESET:TIME:\\s*(\\d+)-(\\d+)-(\\d+)-(\\d+)-(\\d+)-(\\d+)$");
41 
42     /**
43      * Matches: +1d01h03m37s246ms (1) 028 c10400010 -running -wake_lock
44      */
45     private static final Pattern BATTERY_DISCHARGE_PATTERN = Pattern.compile(
46             "^\\s*\\+(?:(\\d+)d)?(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?(?:(\\d+)ms)? \\(\\d+\\) "
47             + "(\\d+) \\w+ .*");
48 
49     private BatteryDischargeItem mBatteryDischarge = new BatteryDischargeItem();
50     private BatteryStatsSummaryInfoItem mItem = new BatteryStatsSummaryInfoItem();
51     private long mBatteryDischargeRateAvg = 0;
52     private int mBatteryDischargeSamples = 0;
53     private Calendar mResetTime;
54     private static final int BATTERY_GROUP_LIMIT = 10;
55 
56     /**
57      * {@inheritDoc}
58      *
59      * @return The {@link BatteryStatsSummaryInfoItem}.
60      */
61     @Override
parse(List<String> lines)62     public BatteryStatsSummaryInfoItem parse(List<String> lines) {
63         Matcher resetTimeMatcher = null;
64         Matcher dischargeMatcher = null;
65 
66         long previousDischargeElapsedTime= 0;
67         int previousBatteryLevel = 0;
68         boolean batteryDischargedFully = false;
69         for (String line : lines) {
70             resetTimeMatcher = RESET_TIME_PATTERN.matcher(line);
71             dischargeMatcher = BATTERY_DISCHARGE_PATTERN.matcher(line);
72             if (resetTimeMatcher.matches()) {
73                 mResetTime = new GregorianCalendar();
74                 final int year = Integer.parseInt(resetTimeMatcher.group(1));
75                 final int month = Integer.parseInt(resetTimeMatcher.group(2));
76                 final int day = Integer.parseInt(resetTimeMatcher.group(3));
77                 final int hour = Integer.parseInt(resetTimeMatcher.group(4));
78                 final int minute = Integer.parseInt(resetTimeMatcher.group(5));
79                 final int second = Integer.parseInt(resetTimeMatcher.group(6));
80                 // Calendar month is zero indexed but the parsed date is 1-12
81                 mResetTime.set(year, (month - 1), day, hour, minute, second);
82             } else if (dischargeMatcher.matches()) {
83                 final int days = NumberFormattingUtil.parseIntOrZero(dischargeMatcher.group(1));
84                 final int hours = NumberFormattingUtil.parseIntOrZero(dischargeMatcher.group(2));
85                 final int mins = NumberFormattingUtil.parseIntOrZero(dischargeMatcher.group(3));
86                 final int secs = NumberFormattingUtil.parseIntOrZero(dischargeMatcher.group(4));
87                 final int msecs = NumberFormattingUtil.parseIntOrZero(dischargeMatcher.group(5));
88                 final int batteryLevel = Integer.parseInt(dischargeMatcher.group(6));
89                 if (batteryLevel == 0) {
90                     // Ignore the subsequent battery drop readings
91                     batteryDischargedFully = true;
92                     continue;
93                 } else if (previousBatteryLevel == 0) {
94                     // Ignore the first drop
95                     previousBatteryLevel = batteryLevel;
96                     continue;
97                 } else if (!batteryDischargedFully && previousBatteryLevel != batteryLevel) {
98                     long elapsedTime = NumberFormattingUtil.getMs(days, hours, mins, secs, msecs);
99                     mBatteryDischargeRateAvg += (elapsedTime  - previousDischargeElapsedTime);
100                     mBatteryDischargeSamples++;
101                     mBatteryDischarge.addBatteryDischargeInfo(
102                             getDischargeClockTime(days, hours, mins, secs),
103                             (elapsedTime - previousDischargeElapsedTime), batteryLevel);
104                     previousDischargeElapsedTime = elapsedTime;
105                     previousBatteryLevel = batteryLevel;
106                 }
107             }
108         }
109         mItem.setBatteryDischargeRate(getAverageDischargeRate());
110         mItem.setPeakDischargeTime(getPeakDischargeTime());
111         return mItem;
112     }
113 
getDischargeClockTime(int days, int hours, int mins, int secs)114     private Calendar getDischargeClockTime(int days, int hours, int mins, int secs) {
115         Calendar dischargeClockTime = new GregorianCalendar();
116 
117         dischargeClockTime.setTime(mResetTime.getTime());
118         dischargeClockTime.add(Calendar.DATE, days);
119         dischargeClockTime.add(Calendar.HOUR, hours);
120         dischargeClockTime.add(Calendar.MINUTE, mins);
121         dischargeClockTime.add(Calendar.SECOND, secs);
122         return dischargeClockTime;
123     }
124 
getAverageDischargeRate()125     private String getAverageDischargeRate() {
126         if (mBatteryDischargeSamples == 0) {
127             return "The battery did not discharge";
128         }
129 
130         final long minsPerLevel = mBatteryDischargeRateAvg / (mBatteryDischargeSamples * 60 * 1000);
131         return String.format("The battery dropped a level %d mins on average", minsPerLevel);
132     }
133 
getPeakDischargeTime()134     private String getPeakDischargeTime() {
135 
136         int peakDischargeStartBatteryLevel = 0, peakDischargeStopBatteryLevel = 0;
137         long minDischargeDuration = 0;
138         Calendar peakDischargeStartTime= null, peakDischargeStopTime = null;
139         Queue <BatteryDischargeInfoItem> batteryDischargeWindow =
140                 new LinkedList <BatteryDischargeInfoItem>();
141         long sumDischargeDuration = 0;
142         for (BatteryDischargeInfoItem dischargeSteps : mBatteryDischarge.getDischargeStepsInfo()) {
143             batteryDischargeWindow.add(dischargeSteps);
144             sumDischargeDuration += dischargeSteps.getElapsedTime();
145             if (batteryDischargeWindow.size() >= BATTERY_GROUP_LIMIT) {
146                 final long averageDischargeDuration = sumDischargeDuration/BATTERY_GROUP_LIMIT;
147                 final BatteryDischargeInfoItem startNode = batteryDischargeWindow.remove();
148                 sumDischargeDuration -= startNode.getElapsedTime();
149 
150                 if (minDischargeDuration == 0 || averageDischargeDuration < minDischargeDuration) {
151                     minDischargeDuration = averageDischargeDuration;
152                     peakDischargeStartBatteryLevel = startNode.getBatteryLevel();
153                     peakDischargeStopBatteryLevel = dischargeSteps.getBatteryLevel();
154                     peakDischargeStartTime = startNode.getClockTime();
155                     peakDischargeStopTime = dischargeSteps.getClockTime();
156                 }
157             }
158         }
159         if (peakDischargeStartTime != null && peakDischargeStopTime != null &&
160                 peakDischargeStartBatteryLevel > 0 && peakDischargeStopBatteryLevel > 0) {
161             return String.format(
162                     "The peak discharge time was during %s to %s where battery dropped from %d to "
163                     + "%d", peakDischargeStartTime.getTime().toString(),
164                     peakDischargeStopTime.getTime().toString(), peakDischargeStartBatteryLevel,
165                     peakDischargeStopBatteryLevel);
166 
167         } else {
168             return "The battery did not discharge";
169         }
170     }
171 
172     /**
173      * Get the {@link BatteryStatsSummaryInfoItem}.
174      * <p>
175      * Exposed for unit testing.
176      * </p>
177      */
getItem()178     BatteryStatsSummaryInfoItem getItem() {
179         return mItem;
180     }
181 }
182