1 /*
2  * Copyright (C) 2016 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 
17 package com.android.loganalysis.parser;
18 
19 import com.android.loganalysis.item.DmesgActionInfoItem;
20 import com.android.loganalysis.item.DmesgItem;
21 import com.android.loganalysis.item.DmesgServiceInfoItem;
22 import com.android.loganalysis.item.DmesgStageInfoItem;
23 
24 import com.google.common.annotations.VisibleForTesting;
25 
26 import java.io.BufferedReader;
27 import java.io.IOException;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 
33 
34 /**
35  * Parse the dmesg logs. </p>
36  */
37 public class DmesgParser implements IParser {
38 
39     private static final String SERVICENAME = "SERVICENAME";
40     private static final String TIMESTAMP = "TIMESTAMP";
41     private static final String STAGE = "STAGE";
42     private static final String ACTION = "ACTION";
43     private static final String SOURCE = "SOURCE";
44     private static final String DURATION = "DURATION";
45     private static final String UEVENTD = "ueventd";
46     private static final String INIT = "init";
47 
48     // Matches: [ 14.822691] init:
49     private static final String SERVICE_PREFIX = String.format("^\\[\\s+(?<%s>.*)\\] init:\\s+",
50             TIMESTAMP);
51     // Matches: [    3.791635] ueventd:
52     private static final String UEVENTD_PREFIX = String.format("^\\[\\s+(?<%s>.*)\\] ueventd:\\s+",
53             TIMESTAMP);
54 
55     // Matches: starting service 'ueventd'...
56     private static final String START_SERVICE_SUFFIX = String.format("starting service "
57             + "\\'(?<%s>.*)\\'...", SERVICENAME);
58     // Matches: Service 'ueventd' (pid 439) exited with status 0
59     private static final String EXIT_SERVICE_SUFFIX = String.format("Service \\'(?<%s>.*)\\'\\s+"
60             + "\\((?<PID>.*)\\) exited with status 0.*", SERVICENAME);
61 
62     private static final Pattern START_SERVICE = Pattern.compile(
63             String.format("%s%s", SERVICE_PREFIX, START_SERVICE_SUFFIX));
64     private static final Pattern EXIT_SERVICE = Pattern.compile(
65             String.format("%s%s", SERVICE_PREFIX, EXIT_SERVICE_SUFFIX));
66 
67     // Matches: init first stage started!
68     // Matches: init second stage started!
69     private static final String START_STAGE_PREFIX = String.format("init (?<%s>.*) stage started!",
70             STAGE);
71 
72     // Matches: [   13.647997] init: init first stage started!
73     private static final Pattern START_STAGE = Pattern.compile(
74             String.format("%s%s", SERVICE_PREFIX, START_STAGE_PREFIX));
75 
76     // Matches: init: processing action (early-init) from (/init.rc:14)
77     private static final String START_PROCESSING_ACTION_PREFIX =
78             String.format("processing action \\((?<%s>[^)]*)\\)( from \\((?<%s>.*)\\)|.*)$",
79                     ACTION, SOURCE);
80 
81     // Matches: init: processing action (early-init) from (/init.rc:14)
82     private static final Pattern START_PROCESSING_ACTION =
83             Pattern.compile(String.format("%s%s", SERVICE_PREFIX, START_PROCESSING_ACTION_PREFIX));
84 
85     // Matches: [    3.791635] ueventd: Coldboot took 0.695055 seconds
86     private static final String STAGE_SUFFIX= String.format(
87             "(?<%s>.*)\\s+took\\s+(?<%s>.*)\\s+seconds$", STAGE, DURATION);
88     private static final Pattern UEVENTD_STAGE_INFO = Pattern.compile(
89             String.format("%s%s", UEVENTD_PREFIX, STAGE_SUFFIX));
90 
91     private static final String PROPERTY_SUFFIX= String.format(
92             "(?<%s>.*)\\s+took\\s+(?<%s>.*)ms$", STAGE, DURATION);
93     // Matches [    7.270487] init: Wait for property 'apexd.status=ready' took 230ms
94     private static final Pattern WAIT_FOR_PROPERTY_INFO = Pattern.compile(
95             String.format("%s%s", SERVICE_PREFIX, PROPERTY_SUFFIX));
96 
97     private DmesgItem mDmesgItem = new DmesgItem();
98 
99     /**
100      * {@inheritDoc}
101      */
102     @Override
parse(List<String> lines)103     public DmesgItem parse(List<String> lines) {
104         for (String line : lines) {
105             parse(line);
106         }
107 
108         return mDmesgItem;
109     }
110 
111     /**
112      * Parse the kernel log till EOF to retrieve the duration of the service calls, start times of
113      * different boot stages and actions taken. Besides, while parsing these informations are stored
114      * in the intermediate {@link DmesgServiceInfoItem}, {@link DmesgStageInfoItem} and
115      * {@link DmesgActionInfoItem} objects
116      *
117      * @param input dmesg log
118      * @throws IOException
119      */
parseInfo(BufferedReader bufferedLog)120     public DmesgItem parseInfo(BufferedReader bufferedLog) throws IOException {
121         String line;
122         while ((line = bufferedLog.readLine()) != null) {
123             parse(line);
124         }
125 
126         return mDmesgItem;
127     }
128 
129     /**
130      * <p>
131      * Parse single line of the dmesg log to retrieve the duration of the service calls or start
132      * times of different boot stages or start times of actions taken based on the info contained in
133      * the line.
134      * </p>
135      * <p>
136      * Besides, while parsing these informations are stored in the intermediate
137      * {@link DmesgServiceInfoItem}, {@link DmesgStageInfoItem} and {@link DmesgActionInfoItem}
138      * objects
139      * </p>
140      *
141      * @param line individual line of the dmesg log
142      */
parse(String line)143     private void parse(String line) {
144 
145         if (parseServiceInfo(line)) {
146             return;
147         }
148         if (parseStageInfo(line)) {
149             return;
150         }
151         if (parseActionInfo(line)) {
152             return;
153         }
154     }
155 
156     /**
157      * Parse init services {@code start time} and {@code end time} from each {@code line} of dmesg
158      * log and store the {@code duration} it took to complete the service if the end time stamp is
159      * available in {@link DmesgServiceInfoItem}.
160      *
161      * @param individual line of the dmesg log
162      * @return {@code true}, if the {@code line} indicates start or end of a service,
163      *         {@code false}, otherwise
164      */
165     @VisibleForTesting
parseServiceInfo(String line)166     boolean parseServiceInfo(String line) {
167         Matcher match = null;
168         if ((match = matches(START_SERVICE, line)) != null) {
169             DmesgServiceInfoItem serviceItem = new DmesgServiceInfoItem();
170             serviceItem.setServiceName(match.group(SERVICENAME));
171             serviceItem.setStartTime((long) (Double.parseDouble(
172                     match.group(TIMESTAMP)) * 1000));
173             getServiceInfoItems().put(match.group(SERVICENAME), serviceItem);
174             return true;
175         } else if ((match = matches(EXIT_SERVICE, line)) != null) {
176             if (getServiceInfoItems().containsKey(match.group(SERVICENAME))) {
177                 DmesgServiceInfoItem serviceItem = getServiceInfoItems().get(
178                         match.group(SERVICENAME));
179                 serviceItem.setEndTime((long) (Double.parseDouble(
180                         match.group(TIMESTAMP)) * 1000));
181             }
182             return true;
183         }
184         return false;
185     }
186 
187     /**
188      * Parse init stages log from each {@code line} of dmesg log and
189      * store the stage name, start time and duration if available in a
190      * {@link DmesgStageInfoItem} object
191      *
192      * @param individual line of the dmesg log
193      * @return {@code true}, if the {@code line} indicates start of a boot stage,
194      *         {@code false}, otherwise
195      */
196     @VisibleForTesting
parseStageInfo(String line)197     boolean parseStageInfo(String line) {
198         Matcher match = null;
199         if ((match = matches(START_STAGE, line)) != null) {
200             DmesgStageInfoItem stageInfoItem = new DmesgStageInfoItem();
201             stageInfoItem.setStageName(match.group(STAGE));
202             stageInfoItem.setStartTime((long) (Double.parseDouble(
203                     match.group(TIMESTAMP)) * 1000));
204             mDmesgItem.addStageInfoItem(stageInfoItem);
205             return true;
206         }
207         if((match = matches(UEVENTD_STAGE_INFO, line)) != null) {
208             DmesgStageInfoItem stageInfoItem = new DmesgStageInfoItem();
209             stageInfoItem.setStageName(String.format("%s_%s", UEVENTD, match.group(STAGE)));
210             stageInfoItem.setDuration((long) (Double.parseDouble(
211                     match.group(DURATION)) * 1000));
212             mDmesgItem.addStageInfoItem(stageInfoItem);
213             return true;
214         }
215         if((match = matches(WAIT_FOR_PROPERTY_INFO, line)) != null) {
216             DmesgStageInfoItem stageInfoItem = new DmesgStageInfoItem();
217             stageInfoItem.setStageName(String.format("%s_%s", INIT, match.group(STAGE)));
218             stageInfoItem.setDuration((long) Double.parseDouble(match.group(DURATION)));
219             mDmesgItem.addStageInfoItem(stageInfoItem);
220             return true;
221         }
222 
223         return false;
224     }
225 
226     /**
227      * Parse action from each {@code line} of dmesg log and store the action name and start time
228      * in {@link DmesgActionInfoItem} object
229      *
230      * @param individual {@code line} of the dmesg log
231      * @return {@code true}, if {@code line} indicates starting of processing of action
232      *         {@code false}, otherwise
233      */
234     @VisibleForTesting
parseActionInfo(String line)235     boolean parseActionInfo(String line) {
236         Matcher match = null;
237         if ((match = matches(START_PROCESSING_ACTION, line)) != null) {
238             DmesgActionInfoItem actionInfoItem = new DmesgActionInfoItem();
239             if (match.group(SOURCE) != null) {
240                 actionInfoItem.setSourceName(match.group(SOURCE));
241             }
242             actionInfoItem.setActionName(match.group(ACTION));
243             actionInfoItem.setStartTime((long) (Double.parseDouble(
244                     match.group(TIMESTAMP)) * 1000));
245             mDmesgItem.addActionInfoItem(actionInfoItem);
246             return true;
247         }
248         return false;
249     }
250 
251     /**
252      * Checks whether {@code line} matches the given {@link Pattern}.
253      *
254      * @return The resulting {@link Matcher} obtained by matching the {@code line} against
255      *         {@code pattern}, or null if the {@code line} does not match.
256      */
matches(Pattern pattern, String line)257     private static Matcher matches(Pattern pattern, String line) {
258         Matcher ret = pattern.matcher(line);
259         return ret.matches() ? ret : null;
260     }
261 
getServiceInfoItems()262     public Map<String, DmesgServiceInfoItem> getServiceInfoItems() {
263         return mDmesgItem.getServiceInfoItems();
264     }
265 
setServiceInfoItems(Map<String, DmesgServiceInfoItem> serviceInfoItems)266     public void setServiceInfoItems(Map<String, DmesgServiceInfoItem> serviceInfoItems) {
267         for(String key : serviceInfoItems.keySet()) {
268             mDmesgItem.addServiceInfoItem(key, serviceInfoItems.get(key));
269         }
270     }
271 
getStageInfoItems()272     public List<DmesgStageInfoItem> getStageInfoItems() {
273         return mDmesgItem.getStageInfoItems();
274     }
275 
getActionInfoItems()276     public List<DmesgActionInfoItem> getActionInfoItems() {
277         return mDmesgItem.getActionInfoItems();
278     }
279 
280 }