diff --git a/OWNERS b/OWNERS index e4533e067f6d0052177066cccef4bc8765f9dddc..90c5d36b20f400ef6ea4a4dfe470191d5b17351a 100644 --- a/OWNERS +++ b/OWNERS @@ -1,4 +1,7 @@ erowe@google.com +gelanchezhian@google.com guangzhu@google.com +jdesprez@google.com +mrosenfeld@google.com siniavine@google.com tobiast@google.com diff --git a/src/com/android/loganalysis/item/DumpsysProcessMeminfoItem.java b/src/com/android/loganalysis/item/DumpsysProcessMeminfoItem.java new file mode 100644 index 0000000000000000000000000000000000000000..3abfd40f4aacd741d1ae6bca09068cbbf83ea801 --- /dev/null +++ b/src/com/android/loganalysis/item/DumpsysProcessMeminfoItem.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2018 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; + +import java.util.HashMap; +import java.util.Map; + +/** + * An {@link IItem} used to store output from `dumpsys meminfo --checkin PROCESS` where PROCESS is + * from the output of `dumpsys meminfo`. Data is stored as a map of categories to a map of + * measurement types to values. + */ +public class DumpsysProcessMeminfoItem extends GenericMapItem<Map<String, Long>> { + // Should match value from ActivityThread + public static final int ACTIVITY_THREAD_CHECKIN_VERSION = 4; + + // Default Categories + public static final String NATIVE = "NATIVE"; + public static final String DALVIK = "DALVIK"; + public static final String OTHER = "OTHER"; + public static final String TOTAL = "TOTAL"; + + // Memory Measurement Types + public static final String PSS = "PSS"; + public static final String SWAPPABLE_PSS = "SWAPPABLE_PSS"; + public static final String SHARED_DIRTY = "SHARED_DIRTY"; + public static final String SHARED_CLEAN = "SHARED_CLEAN"; + public static final String PRIVATE_DIRTY = "PRIVATE_DIRTY"; + public static final String PRIVATE_CLEAN = "PRIVATE_CLEAN"; + public static final String SWAPPED_OUT = "SWAPPED_OUT"; + public static final String SWAPPED_OUT_PSS = "SWAPPED_OUT_PSS"; + // NATIVE, DALVIK, TOTAL only + public static final String MAX = "MAX"; + public static final String ALLOCATED = "ALLOCATED"; + public static final String FREE = "FREE"; + + public static final String[] MAIN_OUTPUT_ORDER = { + MAX, + ALLOCATED, + FREE, + PSS, + SWAPPABLE_PSS, + SHARED_DIRTY, + SHARED_CLEAN, + PRIVATE_DIRTY, + PRIVATE_CLEAN, + SWAPPED_OUT, + SWAPPED_OUT_PSS + }; + public static final String[] OTHER_OUTPUT_ORDER = { + PSS, + SWAPPABLE_PSS, + SHARED_DIRTY, + SHARED_CLEAN, + PRIVATE_DIRTY, + PRIVATE_CLEAN, + SWAPPED_OUT, + SWAPPED_OUT_PSS + }; + + private int mPid; + private String mProcessName; + + public DumpsysProcessMeminfoItem() { + this.put(NATIVE, new HashMap<>()); + this.put(DALVIK, new HashMap<>()); + this.put(OTHER, new HashMap<>()); + this.put(TOTAL, new HashMap<>()); + } + + /** Get the pid */ + public int getPid() { + return mPid; + } + + /** Set the pid */ + public void setPid(int pid) { + mPid = pid; + } + + /** Get the process name */ + public String getProcessName() { + return mProcessName; + } + + /** Set the process name */ + public void setProcessName(String processName) { + mProcessName = processName; + } + + /** {@inheritDoc} */ + @Override + public JSONObject toJson() { + JSONObject result = super.toJson(); + try { + result.put("pid", mPid); + result.put("process_name", mProcessName); + } catch (JSONException e) { + //ignore + } + return result; + } +} diff --git a/src/com/android/loganalysis/item/GfxInfoItem.java b/src/com/android/loganalysis/item/GfxInfoItem.java index 482cea3d1e094b3d411b8bd72a1a9d8362d0e0f3..21ff245e65c41f2971f172e3434301935edbfe59 100644 --- a/src/com/android/loganalysis/item/GfxInfoItem.java +++ b/src/com/android/loganalysis/item/GfxInfoItem.java @@ -38,6 +38,12 @@ public class GfxInfoItem implements IItem { public static final String TOTAL_FRAMES_KEY = "total_frames"; /** Constant for JSON output */ public static final String JANKY_FRAMES_KEY = "janky_frames"; + /** Constant for JSON output */ + public static final String PERCENTILE_90_KEY = "percentile_90"; + /** Constant for JSON output */ + public static final String PERCENTILE_95_KEY = "percentile_95"; + /** Constant for JSON output */ + public static final String PERCENTILE_99_KEY = "percentile_99"; private Map<Integer, Row> mRows = new HashMap<Integer, Row>(); @@ -45,6 +51,9 @@ public class GfxInfoItem implements IItem { public String name; public long totalFrames; public long jankyFrames; + public int percentile90; + public int percentile95; + public int percentile99; } /** @@ -76,7 +85,9 @@ public class GfxInfoItem implements IItem { proc.put(PID_KEY, pid); proc.put(NAME_KEY, getName(pid)); proc.put(TOTAL_FRAMES_KEY, getTotalFrames(pid)); - proc.put(JANKY_FRAMES_KEY, getJankyFrames(pid)); + proc.put(PERCENTILE_90_KEY, getPrecentile90(pid)); + proc.put(PERCENTILE_95_KEY, getPrecentile95(pid)); + proc.put(PERCENTILE_99_KEY, getPrecentile99(pid)); processes.put(proc); } catch (JSONException e) { // ignore @@ -106,11 +117,21 @@ public class GfxInfoItem implements IItem { * @param totalFrames The number of total frames rendered by the process * @param jankyFrames The number of janky frames rendered by the process */ - public void addRow(int pid, String name, long totalFrames, long jankyFrames) { + public void addRow( + int pid, + String name, + long totalFrames, + long jankyFrames, + int percentile90, + int percentile95, + int percentile99) { Row row = new Row(); row.name = name; row.totalFrames = totalFrames; row.jankyFrames = jankyFrames; + row.percentile90 = percentile90; + row.percentile95 = percentile95; + row.percentile99 = percentile99; mRows.put(pid, row); } @@ -134,4 +155,19 @@ public class GfxInfoItem implements IItem { public long getJankyFrames(int pid) { return mRows.get(pid).jankyFrames; } + + /** Get the 90th percentile value of frame times (ms) */ + public int getPrecentile90(int pid) { + return mRows.get(pid).percentile90; + } + + /** Get the 95th percentile value of frame times (ms) */ + public int getPrecentile95(int pid) { + return mRows.get(pid).percentile95; + } + + /** Get the 99th percentile value of frame times (ms) */ + public int getPrecentile99(int pid) { + return mRows.get(pid).percentile99; + } } diff --git a/src/com/android/loganalysis/item/TraceFormatItem.java b/src/com/android/loganalysis/item/TraceFormatItem.java new file mode 100644 index 0000000000000000000000000000000000000000..82944d53d6e437f55f635efb47c9d2985a1b7fed --- /dev/null +++ b/src/com/android/loganalysis/item/TraceFormatItem.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017 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.List; +import java.util.Set; +import java.util.regex.Pattern; + +/** A {@link GenericItem} of trace format. */ +public class TraceFormatItem extends GenericItem { + private static final String REGEX = "regex"; + private static final String PARAMS = "params"; + private static final String NUM_PARAMS = "num_params"; + private static final String HEX_PARAMS = "hex_params"; + private static final String STR_PARAMS = "str_params"; + private static final Set<String> ATTRIBUTES = + new HashSet<>(Arrays.asList(REGEX, PARAMS, NUM_PARAMS, HEX_PARAMS, STR_PARAMS)); + + /** Create a new {@link TraceFormatItem} */ + public TraceFormatItem() { + super(ATTRIBUTES); + } + + @Override + /** TraceFormatItem doesn't support merge */ + public IItem merge(IItem other) throws ConflictingItemException { + throw new ConflictingItemException("Trace format items cannot be merged"); + } + + /** Get a compiled regex that matches output of this trace format */ + public Pattern getRegex() { + return (Pattern) getAttribute(REGEX); + } + + /** Set a compiled regex that matches output of this trace format */ + public void setRegex(Pattern regex) { + setAttribute(REGEX, regex); + } + + /** + * Get all parameters found in this trace format. The parameters were converted to camel case + * and match the group names in the regex. + */ + public List<String> getParameters() { + return (List<String>) getAttribute(PARAMS); + } + + /** + * Set all parameters found in this trace format. The parameters were converted to camel case + * and match the group names in the regex. + */ + public void setParameters(List<String> parameters) { + setAttribute(PARAMS, parameters); + } + + /** + * Get numeric parameters found in this trace format. The parameters were converted to camel + * case and match the group names in the regex. + */ + public List<String> getNumericParameters() { + return (List<String>) getAttribute(NUM_PARAMS); + } + + /** + * Set numeric parameters found in this trace format. The parameters were converted to camel + * case and match the group names in the regex. + */ + public void setNumericParameters(List<String> numericParameters) { + setAttribute(NUM_PARAMS, numericParameters); + } + + /** + * Get hexadecimal parameters found in this trace format. The parameters were converted to camel + * case and match the group names in the regex. + */ + public List<String> getHexParameters() { + return (List<String>) getAttribute(HEX_PARAMS); + } + + /** + * Set hexadecimal parameters found in this trace format. The parameters were converted to camel + * case and match the group names in the regex. + */ + public void setHexParameters(List<String> hexParameters) { + setAttribute(HEX_PARAMS, hexParameters); + } + + /** + * Get string parameters found in this trace format. The parameters were converted to camel case + * and match the group names in the regex. + */ + public List<String> getStringParameters() { + return (List<String>) getAttribute(STR_PARAMS); + } + + /** + * Set string parameters found in this trace format. The parameters were converted to camel case + * and match the group names in the regex. + */ + public void setStringParameters(List<String> stringParameters) { + setAttribute(STR_PARAMS, stringParameters); + } +} diff --git a/src/com/android/loganalysis/item/TransitionDelayItem.java b/src/com/android/loganalysis/item/TransitionDelayItem.java index 33a1b9331915273d7169ade9d65b051f652253e1..6e09d4b221c9ea51d86fbd0800a10edae7198c7f 100644 --- a/src/com/android/loganalysis/item/TransitionDelayItem.java +++ b/src/com/android/loganalysis/item/TransitionDelayItem.java @@ -30,9 +30,19 @@ public class TransitionDelayItem extends GenericItem { public static final String START_WINDOW_DELAY = "START_WINDOW_DELAY"; /** Constant for JSON output */ public static final String TRANSITION_DELAY = "TRANSITION_DELAY"; + /** Constant for JSON output */ + public static final String DATE_TIME = "DATE_TIME"; + /** Constant for JSON output */ + public static final String WINDOW_DRAWN_DELAY = "WINDOW_DRAWN_DELAY"; - private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList( - COMPONENT_NAME, START_WINDOW_DELAY, TRANSITION_DELAY)); + private static final Set<String> ATTRIBUTES = + new HashSet<String>( + Arrays.asList( + COMPONENT_NAME, + START_WINDOW_DELAY, + TRANSITION_DELAY, + DATE_TIME, + WINDOW_DRAWN_DELAY)); /** * The constructor for {@link TransitionDelayItem}. @@ -49,20 +59,42 @@ public class TransitionDelayItem extends GenericItem { setAttribute(COMPONENT_NAME, componentName); } - public long getStartingWindowDelay() { - return (long) getAttribute(START_WINDOW_DELAY); + public Long getStartingWindowDelay() { + return getAttribute(START_WINDOW_DELAY) != null ? (Long) getAttribute(START_WINDOW_DELAY) + : null; } public void setStartingWindowDelay(long startingWindowDelay) { setAttribute(START_WINDOW_DELAY, startingWindowDelay); } - public long getTransitionDelay() { - return (long) getAttribute(TRANSITION_DELAY); + public Long getTransitionDelay() { + return (Long) getAttribute(TRANSITION_DELAY); } public void setTransitionDelay(long transitionDelay) { setAttribute(TRANSITION_DELAY, transitionDelay); } + /** + * @return date and time (MM-DD HH:MM:SS.mmm) in string format parsed from events log + * and used in AUPT test. + */ + public String getDateTime() { + return (String) getAttribute(DATE_TIME); + } + + public void setDateTime(String dateTime) { + setAttribute(DATE_TIME, dateTime); + } + + public Long getWindowDrawnDelay() { + return getAttribute(WINDOW_DRAWN_DELAY) != null + ? (Long) getAttribute(WINDOW_DRAWN_DELAY) + : null; + } + + public void setWindowDrawnDelay(long windowDrawnDelay) { + setAttribute(WINDOW_DRAWN_DELAY, windowDrawnDelay); + } } diff --git a/src/com/android/loganalysis/parser/DumpsysProcessMeminfoParser.java b/src/com/android/loganalysis/parser/DumpsysProcessMeminfoParser.java new file mode 100644 index 0000000000000000000000000000000000000000..119bab6141f17d96c6f2eb07c6e1fea67c558f25 --- /dev/null +++ b/src/com/android/loganalysis/parser/DumpsysProcessMeminfoParser.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2018 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.DumpsysProcessMeminfoItem; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An {@link IParser} used to parse output from `dumpsys meminfo --checkin PROCESS` where PROCESS is + * from the output of `dumpsys meminfo`. Data is stored as a map of categories to a map of + * measurement types to values. Format is from {@link android.app.ActivityThread#dumpMemInfoTable}. + */ +public class DumpsysProcessMeminfoParser implements IParser { + + // Order is VERSION,PID,NAME,[native,dalvik,other,total]{11},[name,val{8}]* + private static final Pattern MEMINFO_OUTPUT = + Pattern.compile("(\\d+),(\\d+),([^,]+),((?:(?:N/A|\\d+),){44})(.*)"); + // Matches the ending [name,val{8}] + private static final Pattern MEMINFO_ADDITIONAL_OUTPUT = + Pattern.compile("([^,]+),((?:(?:N/A|\\d+),){8})"); + // Matches a value with comma + private static final Pattern MEMINFO_VALUE = Pattern.compile("(N/A|\\d+),"); + + @Override + public DumpsysProcessMeminfoItem parse(List<String> lines) { + DumpsysProcessMeminfoItem item = new DumpsysProcessMeminfoItem(); + for (String line : lines) { + Matcher m = MEMINFO_OUTPUT.matcher(line); + if (!m.matches()) continue; + try { + item.setPid(Integer.parseInt(m.group(2))); + } catch (NumberFormatException e) { + // skip + } + item.setProcessName(m.group(3)); + // parse memory info main areas + String mainValues = m.group(4); + Matcher mainMatcher = MEMINFO_VALUE.matcher(mainValues); + Map<String, Long> nativeData = item.get(DumpsysProcessMeminfoItem.NATIVE); + Map<String, Long> dalvikData = item.get(DumpsysProcessMeminfoItem.DALVIK); + Map<String, Long> otherData = item.get(DumpsysProcessMeminfoItem.OTHER); + Map<String, Long> totalData = item.get(DumpsysProcessMeminfoItem.TOTAL); + for (int i = 0; i < DumpsysProcessMeminfoItem.MAIN_OUTPUT_ORDER.length; i++) { + String curMeasurement = DumpsysProcessMeminfoItem.MAIN_OUTPUT_ORDER[i]; + parseNextValue(mainMatcher, nativeData, curMeasurement); + parseNextValue(mainMatcher, dalvikData, curMeasurement); + parseNextValue(mainMatcher, otherData, curMeasurement); + parseNextValue(mainMatcher, totalData, curMeasurement); + } + String additionalData = m.group(5); + Matcher additionalMatcher = MEMINFO_ADDITIONAL_OUTPUT.matcher(additionalData); + // parse memory info other areas + while (additionalMatcher.find()) { + try { + String curLabel = additionalMatcher.group(1); + Matcher additionalValueMatcher = + MEMINFO_VALUE.matcher(additionalMatcher.group(2)); + Map<String, Long> curData = new HashMap<>(); + for (int i = 0; i < DumpsysProcessMeminfoItem.OTHER_OUTPUT_ORDER.length; i++) { + String curMeasurement = DumpsysProcessMeminfoItem.OTHER_OUTPUT_ORDER[i]; + parseNextValue(additionalValueMatcher, curData, curMeasurement); + } + item.put(curLabel, curData); + } catch (ArrayIndexOutOfBoundsException e) { + break; + } + } + } + return item; + } + + private void parseNextValue(Matcher m, Map<String, Long> output, String key) { + if (!m.find()) return; + String value = m.group(1); + if ("N/A".equals(value)) return; + try { + output.put(key, Long.parseLong(value)); + } catch (NumberFormatException e) { + // skip + } + } +} diff --git a/src/com/android/loganalysis/parser/EventsLogParser.java b/src/com/android/loganalysis/parser/EventsLogParser.java index 33afb2fd6a5dd6c595d2a5c6b780f79eb29e9d8a..1e1803c389e55faf3b89ef1f862e694acf2d5d21 100644 --- a/src/com/android/loganalysis/parser/EventsLogParser.java +++ b/src/com/android/loganalysis/parser/EventsLogParser.java @@ -23,7 +23,9 @@ import com.android.loganalysis.item.TransitionDelayItem; import java.io.BufferedReader; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,28 +34,27 @@ import java.util.regex.Pattern; */ public class EventsLogParser implements IParser { - // 08-21 17:53:53.876 1053 2135 - private static final String EVENTS_PREFIX = "^\\d{2}-\\d{2} \\d{2}:\\d{2}" - + ":\\d{2}.\\d{3}\\s+\\d+\\s+\\d+ "; - - // 01-01 01:38:44.863 1037 1111 I sysui_multi_action: - // [319,64,321,64,322,99,325,5951,757,761,758,9,759,4,806,com.google.android.gm,871, - // com.google.android.gm.welcome.WelcomeTourActivity,905,0] - private static final Pattern TRANSITION_STARTING_DELAY = Pattern.compile( - String.format("%s%s", EVENTS_PREFIX, "I sysui_multi_action: \\[319,(.*),321,(.*)" - + ",322,(.*),806,(.*),871,(.*),905.*\\]$")); - - // 01-01 01:38:44.863 1037 1111 I sysui_multi_action: - // [319,64,322,99,325,5951,757,761,758,9,759,4,806,com.google.android.gm,871, - // com.google.android.gm.welcome.WelcomeTourActivity,905,0] - private static final Pattern TRANSITION_DELAY = Pattern.compile( - String.format("%s%s", EVENTS_PREFIX, "I sysui_multi_action: \\[319,(.*),322,(.*)" - + ",806,(.*),871,(.*),905.*\\]$")); + // 09-18 23:56:19.376 1140 1221 I sysui_multi_action: + // [319,51,321,50,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871, + // com.android.calculator2.Calculator,905,0,945,41] + private static final Pattern SYSUI_TRANSITION_INFO_PATTERN = Pattern.compile( + "^(?<date>[0-9-]*)\\s+(?<time>[0-9:.]*)\\s+\\d+\\s+\\d+ I sysui_multi_action:" + + " \\[(?<transitioninfo>.*)\\]$"); // 08-21 17:53:53.876 1053 2135 I sysui_latency: [1,50] - private static final Pattern ACTION_LATENCY = Pattern.compile( - String.format("%s%s", EVENTS_PREFIX, "I sysui_latency: \\[(?<action>.*)," - + "(?<delay>.*)\\]$")); + private static final Pattern ACTION_LATENCY = Pattern.compile("^(?<date>[0-9-]*)\\s+" + + "(?<time>[0-9:.]*)\\s+\\d+\\s+\\d+ I sysui_latency: \\[(?<action>.*)," + + "(?<delay>.*)\\]$"); + + private static final String DATE = "date"; + private static final String TIME = "time"; + private static final String TRANSITION_INFO = "transitioninfo"; + private static final String PACKAGE_KEY = "806"; + private static final String ACTIVITY_KEY = "871"; + private static final String TRANSITION_DELAY_KEY = "319"; + private static final String STARTING_WINDOW_DELAY_KEY = "321"; + private static final String COLD_LAUNCH_KEY = "945"; + private static final String WINDOWS_DRAWN_DELAY_KEY = "322"; @Override public IItem parse(List<String> lines) { @@ -62,37 +63,66 @@ public class EventsLogParser implements IParser { } /** - * Method to parse the transition delay information from the events log - * + * Parse the transition delay information from the events log. * @param input - * @return + * @return list of transition delay items. * @throws IOException */ public List<TransitionDelayItem> parseTransitionDelayInfo(BufferedReader input) throws IOException { List<TransitionDelayItem> transitionDelayItems = new ArrayList<TransitionDelayItem>(); String line; + Matcher match = null; while ((line = input.readLine()) != null) { - Matcher match = null; - if (((match = matches(TRANSITION_STARTING_DELAY, line)) != null)) { - TransitionDelayItem delayItem = new TransitionDelayItem(); - delayItem.setComponentName(match.group(4) + "/" + match.group(5)); - delayItem.setTransitionDelay(Long.parseLong(match.group(1))); - delayItem.setStartingWindowDelay(Long.parseLong(match.group(2))); - transitionDelayItems.add(delayItem); - } else if (((match = matches(TRANSITION_DELAY, line)) != null)) { - TransitionDelayItem delayItem = new TransitionDelayItem(); - delayItem.setComponentName(match.group(3) + "/" + match.group(4)); - delayItem.setTransitionDelay(Long.parseLong(match.group(1))); - transitionDelayItems.add(delayItem); + if ((match = matches(SYSUI_TRANSITION_INFO_PATTERN, line)) != null) { + Map<String, String> transitionInfoMap = getTransitionInfoMap( + match.group(TRANSITION_INFO)); + if (transitionInfoMap.containsKey(TRANSITION_DELAY_KEY)) { + TransitionDelayItem delayItem = new TransitionDelayItem(); + if (null != transitionInfoMap.get(PACKAGE_KEY) + && null != transitionInfoMap.get(ACTIVITY_KEY) + && null != transitionInfoMap.get(TRANSITION_DELAY_KEY) + && null != transitionInfoMap.get(WINDOWS_DRAWN_DELAY_KEY)) { + delayItem.setComponentName(transitionInfoMap.get(PACKAGE_KEY) + "/" + + transitionInfoMap.get(ACTIVITY_KEY)); + delayItem.setTransitionDelay(Long.parseLong(transitionInfoMap + .get(TRANSITION_DELAY_KEY))); + delayItem.setDateTime(String.format("%s %s", match.group(DATE), + match.group(TIME))); + delayItem.setWindowDrawnDelay( + Long.parseLong(transitionInfoMap.get(WINDOWS_DRAWN_DELAY_KEY))); + } + if (transitionInfoMap.containsKey(COLD_LAUNCH_KEY)) { + if (null != transitionInfoMap.get(STARTING_WINDOW_DELAY_KEY)) { + delayItem.setStartingWindowDelay(Long.parseLong(transitionInfoMap + .get(STARTING_WINDOW_DELAY_KEY))); + } + } + transitionDelayItems.add(delayItem); + } } } return transitionDelayItems; } + /** + * Split the transition info string in to key, values and return a map. + * @param transitionInfo transition info map in hey value format. + * @return + */ + public Map<String, String> getTransitionInfoMap(String transitionInfo) { + String[] transitionSplit = transitionInfo.split(","); + Map<String, String> transitionInfoMap = new HashMap<>(); + if (transitionSplit.length % 2 == 0) { + for (int i = 0; i < transitionSplit.length; i = i + 2) { + transitionInfoMap.put(transitionSplit[i], transitionSplit[i + 1]); + } + } + return transitionInfoMap; + } + /** * Method to parse the latency information from the events log - * * @param input * @return * @throws IOException diff --git a/src/com/android/loganalysis/parser/GfxInfoParser.java b/src/com/android/loganalysis/parser/GfxInfoParser.java index ef549d73a86608eb6f4924ede1f84c4b4b15f440..8f0dce7f89eaf17f4095abda29aaa74b09cb7a2b 100644 --- a/src/com/android/loganalysis/parser/GfxInfoParser.java +++ b/src/com/android/loganalysis/parser/GfxInfoParser.java @@ -38,6 +38,18 @@ public class GfxInfoParser implements IParser { private static final Pattern JANKY_FRAMES_PREFIX = Pattern.compile( "Janky frames: (\\d+) \\(.+\\%\\)"); + // Example: "90th percentile: 9ms" + private static final Pattern PERCENTILE_90_PREFIX = + Pattern.compile("90th percentile: (\\d+)ms"); + + // Example: "90th percentile: 14ms" + private static final Pattern PERCENTILE_95_PREFIX = + Pattern.compile("95th percentile: (\\d+)ms"); + + // Example: "90th percentile: 32ms" + private static final Pattern PERCENTILE_99_PREFIX = + Pattern.compile("99th percentile: (\\d+)ms"); + /** * Parses the log of "dumpsys gfxinfo". * Currently it only parses total frame number and total jank number per process. @@ -51,6 +63,9 @@ public class GfxInfoParser implements IParser { Integer pid = null; Long totalFrames = null; Long jankyFrames = null; + Integer percentile90 = null; + Integer percentile95 = null; + Integer percentile99 = null; // gfxinfo also offers stats for specific views, but this parser // only records per process data. See example in GfxInfoParserTest.java. @@ -64,6 +79,9 @@ public class GfxInfoParser implements IParser { totalFrames = null; jankyFrames = null; + percentile90 = null; + percentile95 = null; + percentile99 = null; } m = TOTAL_FRAMES_PREFIX.matcher(line); @@ -76,14 +94,45 @@ public class GfxInfoParser implements IParser { jankyFrames = Long.parseLong(m.group(1)); } - if (name != null && pid != null && totalFrames != null && jankyFrames != null) { + m = PERCENTILE_90_PREFIX.matcher(line); + if (percentile90 == null && m.matches()) { + percentile90 = Integer.parseInt(m.group(1)); + } + + m = PERCENTILE_95_PREFIX.matcher(line); + if (percentile95 == null && m.matches()) { + percentile95 = Integer.parseInt(m.group(1)); + } + + m = PERCENTILE_99_PREFIX.matcher(line); + if (percentile99 == null && m.matches()) { + percentile99 = Integer.parseInt(m.group(1)); + } + + if (name != null + && pid != null + && totalFrames != null + && jankyFrames != null + && percentile90 != null + && percentile95 != null + && percentile99 != null) { // All the data for the process is recorded, add as a row. - item.addRow(pid, name, totalFrames, jankyFrames); + item.addRow( + pid, + name, + totalFrames, + jankyFrames, + percentile90, + percentile95, + percentile99); name = null; pid = null; totalFrames = null; jankyFrames = null; + percentile90 = null; + percentile95 = null; + percentile99 = null; } } diff --git a/src/com/android/loganalysis/parser/TraceFormatParser.java b/src/com/android/loganalysis/parser/TraceFormatParser.java new file mode 100644 index 0000000000000000000000000000000000000000..1c444f4f29aeb66931ef4dfe4bf08299b390186a --- /dev/null +++ b/src/com/android/loganalysis/parser/TraceFormatParser.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2017 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.TraceFormatItem; + +import com.google.common.base.CaseFormat; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Read trace format and generate a regex that matches output of such format. + * + * <p>Traces under /d/tracing specify the output format with a printf string. This parser reads such + * string, finds all parameters, and generates a regex that matches output of such format. Each + * parameter corresponds to a named-capturing group in the regex. The parameter names are converted + * to camel case because Java regex group name must contain only letters and numbers. + * + * <p>An end-to-end example: + * + * <pre>{@code + * List<String> formatLine = Arrays.asList("print fmt: \"foo=%llu, bar:%s\", REC->foo, REC->bar"); + * TraceFormatItem parsedFormat = new TraceFormatParser.parse(formatLine); + * parsedFormat.getParameters(); // "foo", "bar" + * parsedFormat.getNumericParameters(); // "foo" + * Matcher matcher = parsedFormat.getRegex.matcher("foo=123, bar:enabled"); + * matcher.matches(); + * matcher.group("foo") // 123 + * matcher.group("bar") // "enabled" + * }</pre> + * + * <p>The initial implementation supports some commonly used specifiers: signed and unsigned integer + * (with or without long or long long modifier), floating point number (with or without precision), + * hexadecimal number (with or without 0's padding), and string (contains only [a-zA-Z_0-9]). It is + * assumed no characters found in the format line need to be escaped. + * + * <p>Some examples of trace format line: + * + * <p>(thermal/tsens_read) + * + * <p>print fmt: "temp=%lu sensor=tsens_tz_sensor%u", REC->temp, REC->sensor + * + * <p>(sched/sched_cpu_hotplug) + * + * <p>print fmt: "cpu %d %s error=%d", REC->affected_cpu, REC->status ? "online" : "offline", + * REC->error + * + * <p>(mmc/mmc_blk_erase_start) + * + * <p>print fmt: "cmd=%u,addr=0x%08x,size=0x%08x", REC->cmd, REC->addr, REC->size + */ +public class TraceFormatParser implements IParser { + // split the raw format line + private static final Pattern SPLIT_FORMAT_LINE = + Pattern.compile(".*?\"(?<printf>.*?)\"(?<params>.*)"); + // match parameter names + private static final Pattern SPLIT_PARAMS = Pattern.compile("->(?<param>\\w+)"); + // match and categorize common printf specifiers + // use ?: to flag all non-capturing group so any group captured correspond to a specifier + private static final Pattern PRINTF_SPECIFIERS = + Pattern.compile( + "(?<num>%(?:llu|lu|u|lld|ld|d|(?:.\\d*)?f))|(?<hex>%\\d*(?:x|X))|(?<str>%s)"); + + // regex building blocks to match simple numeric/hex/string parameters, exposed for unit testing + static final String MATCH_NUM = "-?\\\\d+(?:\\\\.\\\\d+)?"; + static final String MATCH_HEX = "[\\\\da-fA-F]+"; + static final String MATCH_STR = "[\\\\w]*"; + + /** Parse a trace format line and return an {@link TraceFormatItem} */ + @Override + public TraceFormatItem parse(List<String> lines) { + // sanity check + if (lines == null || lines.size() != 1) { + throw new RuntimeException("Cannot parse format line: expect one-line trace format"); + } + + // split the raw format line + Matcher formatLineMatcher = SPLIT_FORMAT_LINE.matcher(lines.get(0)); + if (!formatLineMatcher.matches()) { + throw new RuntimeException("Cannot parse format line: unexpected format"); + } + String printfString = formatLineMatcher.group("printf"); + String paramsString = formatLineMatcher.group("params"); + + // list of parameters, to be populated soon + List<String> allParams = new ArrayList<>(); + List<String> numParams = new ArrayList<>(); + List<String> hexParams = new ArrayList<>(); + List<String> strParams = new ArrayList<>(); + + // find all parameters and convert them to camel case + Matcher paramsMatcher = SPLIT_PARAMS.matcher(paramsString); + while (paramsMatcher.find()) { + String camelCasedParam = + CaseFormat.LOWER_UNDERSCORE.to( + CaseFormat.LOWER_CAMEL, paramsMatcher.group("param")); + allParams.add(camelCasedParam); + } + + // scan the printf string, categorizing parameters and generating a matching regex + StringBuffer regexBuilder = new StringBuffer(); + int paramIndex = 0; + String currentParam; + + Matcher printfMatcher = PRINTF_SPECIFIERS.matcher(printfString); + while (printfMatcher.find()) { + // parameter corresponds to the found specifier + currentParam = allParams.get(paramIndex++); + if (printfMatcher.group("num") != null) { + printfMatcher.appendReplacement( + regexBuilder, createNamedRegexGroup(MATCH_NUM, currentParam)); + numParams.add(currentParam); + } else if (printfMatcher.group("hex") != null) { + printfMatcher.appendReplacement( + regexBuilder, createNamedRegexGroup(MATCH_HEX, currentParam)); + hexParams.add(currentParam); + } else if (printfMatcher.group("str") != null) { + printfMatcher.appendReplacement( + regexBuilder, createNamedRegexGroup(MATCH_STR, currentParam)); + strParams.add(currentParam); + } else { + throw new RuntimeException("Unrecognized specifier: " + printfMatcher.group()); + } + } + printfMatcher.appendTail(regexBuilder); + Pattern generatedRegex = Pattern.compile(regexBuilder.toString()); + + // assemble and return a TraceFormatItem + TraceFormatItem item = new TraceFormatItem(); + item.setRegex(generatedRegex); + item.setParameters(allParams); + item.setNumericParameters(numParams); + item.setHexParameters(hexParams); + item.setStringParameters(strParams); + return item; + } + + /** Helper function to create a regex group with given name. */ + private static String createNamedRegexGroup(String base, String name) { + return String.format("(?<%s>%s)", name, base); + } +} diff --git a/tests/.classpath b/tests/.classpath index c7726fef8fc5cbad88e165529b7fc4207dd26d6c..55aa5dda28f5568f9f6f7c1250a7fb96d0f4cedf 100644 --- a/tests/.classpath +++ b/tests/.classpath @@ -3,7 +3,7 @@ <classpathentry kind="src" path="src"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry combineaccessrules="false" kind="src" path="/loganalysis"/> - <classpathentry kind="var" path="TRADEFED_ROOT/out/host/common/obj/JAVA_LIBRARIES/junit-host_intermediates/javalib.jar"/> <classpathentry kind="var" path="TRADEFED_ROOT/external/easymock/src"/> + <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/tests/src/com/android/loganalysis/UnitTests.java b/tests/src/com/android/loganalysis/UnitTests.java index 70c4bd3b00b40ce63443c61448393c5a20e39fa5..68d5bf6152c068996b13c9bc985796606df11774 100644 --- a/tests/src/com/android/loganalysis/UnitTests.java +++ b/tests/src/com/android/loganalysis/UnitTests.java @@ -17,9 +17,12 @@ package com.android.loganalysis; import com.android.loganalysis.item.BatteryDischargeItemTest; +import com.android.loganalysis.item.BatteryUsageItemTest; +import com.android.loganalysis.item.DumpsysPackageStatsItemTest; import com.android.loganalysis.item.DvmLockSampleItemTest; import com.android.loganalysis.item.GenericItemTest; import com.android.loganalysis.item.InterruptItemTest; +import com.android.loganalysis.item.LocationDumpsItemTest; import com.android.loganalysis.item.MemInfoItemTest; import com.android.loganalysis.item.MonkeyLogItemTest; import com.android.loganalysis.item.ProcrankItemTest; @@ -28,25 +31,48 @@ import com.android.loganalysis.item.SystemPropsItemTest; import com.android.loganalysis.item.TopItemTest; import com.android.loganalysis.item.WakelockItemTest; import com.android.loganalysis.parser.AbstractSectionParserTest; +import com.android.loganalysis.parser.ActivityServiceParserTest; import com.android.loganalysis.parser.AnrParserTest; +import com.android.loganalysis.parser.BatteryDischargeStatsInfoParserTest; +import com.android.loganalysis.parser.BatteryStatsDetailedInfoParserTest; +import com.android.loganalysis.parser.BatteryStatsSummaryInfoParserTest; +import com.android.loganalysis.parser.BatteryUsageParserTest; import com.android.loganalysis.parser.BugreportParserTest; import com.android.loganalysis.parser.CompactMemInfoParserTest; +import com.android.loganalysis.parser.CpuInfoParserTest; import com.android.loganalysis.parser.DmesgParserTest; +import com.android.loganalysis.parser.DumpsysBatteryStatsParserTest; +import com.android.loganalysis.parser.DumpsysPackageStatsParserTest; +import com.android.loganalysis.parser.DumpsysParserTest; +import com.android.loganalysis.parser.DumpsysProcStatsParserTest; +import com.android.loganalysis.parser.DumpsysProcessMeminfoParserTest; +import com.android.loganalysis.parser.DumpsysWifiStatsParserTest; import com.android.loganalysis.parser.DvmLockSampleParserTest; import com.android.loganalysis.parser.EventsLogParserTest; +import com.android.loganalysis.parser.GfxInfoParserTest; import com.android.loganalysis.parser.InterruptParserTest; import com.android.loganalysis.parser.JavaCrashParserTest; import com.android.loganalysis.parser.KernelLogParserTest; +import com.android.loganalysis.parser.LocationServiceParserTest; import com.android.loganalysis.parser.LogcatParserTest; +import com.android.loganalysis.parser.MemHealthParserTest; import com.android.loganalysis.parser.MemInfoParserTest; import com.android.loganalysis.parser.MonkeyLogParserTest; import com.android.loganalysis.parser.NativeCrashParserTest; import com.android.loganalysis.parser.ProcessUsageParserTest; import com.android.loganalysis.parser.ProcrankParserTest; +import com.android.loganalysis.parser.QtaguidParserTest; +import com.android.loganalysis.parser.SmartMonkeyLogParserTest; import com.android.loganalysis.parser.SystemPropsParserTest; import com.android.loganalysis.parser.TopParserTest; +import com.android.loganalysis.parser.TraceFormatParserTest; import com.android.loganalysis.parser.TracesParserTest; import com.android.loganalysis.parser.WakelockParserTest; +import com.android.loganalysis.rule.InterruptRuleTest; +import com.android.loganalysis.rule.LocationUsageRuleTest; +import com.android.loganalysis.rule.ProcessUsageRuleTest; +import com.android.loganalysis.rule.WakelockRuleTest; +import com.android.loganalysis.rule.WifiStatsRuleTest; import com.android.loganalysis.util.ArrayUtilTest; import com.android.loganalysis.util.LogPatternUtilTest; import com.android.loganalysis.util.LogTailUtilTest; @@ -60,17 +86,21 @@ import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** - * A test suite for all Trade Federation unit tests. - * <p/> - * All tests listed here should be self-contained, and should not require any external dependencies. + * A test suite for all log analysis unit tests. + * + * <p>All tests listed here should be self-contained, and should not require any external + * dependencies. */ @RunWith(Suite.class) @SuiteClasses({ // item BatteryDischargeItemTest.class, + BatteryUsageItemTest.class, + DumpsysPackageStatsItemTest.class, DvmLockSampleItemTest.class, GenericItemTest.class, InterruptItemTest.class, + LocationDumpsItemTest.class, MemInfoItemTest.class, MonkeyLogItemTest.class, ProcrankItemTest.class, @@ -79,25 +109,50 @@ import org.junit.runners.Suite.SuiteClasses; TopItemTest.class, WakelockItemTest.class, + // rule + InterruptRuleTest.class, + LocationUsageRuleTest.class, + ProcessUsageRuleTest.class, + WakelockRuleTest.class, + WifiStatsRuleTest.class, + // parser AbstractSectionParserTest.class, + ActivityServiceParserTest.class, AnrParserTest.class, + BatteryDischargeStatsInfoParserTest.class, + BatteryStatsDetailedInfoParserTest.class, + BatteryStatsSummaryInfoParserTest.class, + BatteryUsageParserTest.class, BugreportParserTest.class, CompactMemInfoParserTest.class, + CpuInfoParserTest.class, DmesgParserTest.class, - EventsLogParserTest.class, + DumpsysBatteryStatsParserTest.class, + DumpsysPackageStatsParserTest.class, + DumpsysParserTest.class, + DumpsysProcessMeminfoParserTest.class, + DumpsysProcStatsParserTest.class, + DumpsysWifiStatsParserTest.class, DvmLockSampleParserTest.class, + EventsLogParserTest.class, + GfxInfoParserTest.class, InterruptParserTest.class, JavaCrashParserTest.class, KernelLogParserTest.class, + LocationServiceParserTest.class, LogcatParserTest.class, + MemHealthParserTest.class, MemInfoParserTest.class, MonkeyLogParserTest.class, NativeCrashParserTest.class, ProcessUsageParserTest.class, ProcrankParserTest.class, + QtaguidParserTest.class, + SmartMonkeyLogParserTest.class, SystemPropsParserTest.class, TopParserTest.class, + TraceFormatParserTest.class, TracesParserTest.class, WakelockParserTest.class, @@ -114,4 +169,4 @@ import org.junit.runners.Suite.SuiteClasses; }) public class UnitTests { // empty of purpose -} \ No newline at end of file +} diff --git a/tests/src/com/android/loganalysis/parser/BatteryStatsSummaryInfoParserTest.java b/tests/src/com/android/loganalysis/parser/BatteryStatsSummaryInfoParserTest.java index ad0c999e9d1c08623e07f17dba44130bac789b5a..c6ba3811cbca266b2bd741ab5399fbe8eab9cd05 100644 --- a/tests/src/com/android/loganalysis/parser/BatteryStatsSummaryInfoParserTest.java +++ b/tests/src/com/android/loganalysis/parser/BatteryStatsSummaryInfoParserTest.java @@ -20,7 +20,9 @@ import com.android.loganalysis.item.BatteryStatsSummaryInfoItem; import junit.framework.TestCase; import java.util.Arrays; +import java.util.GregorianCalendar; import java.util.List; +import java.util.TimeZone; /** * Unit tests for {@link BatteryStatsSummaryInfoParser} @@ -83,8 +85,14 @@ public class BatteryStatsSummaryInfoParserTest extends TestCase { assertEquals("The battery dropped a level 24 mins on average", summary.getBatteryDischargeRate()); - assertEquals("The peak discharge time was during Tue Dec 09 16:31:07 PST 2014 to " - + "Tue Dec 09 19:35:52 PST 2014 where battery dropped from 89 to 80", + // Get the current timezone short name (PST, GMT) to properly output the time as expected. + String timezone = + new GregorianCalendar().getTimeZone().getDisplayName(false, TimeZone.SHORT); + assertEquals( + String.format( + "The peak discharge time was during Tue Dec 09 16:31:07 %s 2014 to " + + "Tue Dec 09 19:35:52 %s 2014 where battery dropped from 89 to 80", + timezone, timezone), summary.getPeakDischargeTime()); } diff --git a/tests/src/com/android/loganalysis/parser/DumpsysProcessMeminfoParserTest.java b/tests/src/com/android/loganalysis/parser/DumpsysProcessMeminfoParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b7042d3585e405936e4b761460c19829790aafb2 --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/DumpsysProcessMeminfoParserTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2018 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.android.loganalysis.item.DumpsysProcessMeminfoItem; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +/** Unit tests for {@link DumpsysProcessMeminfoParser} */ +public class DumpsysProcessMeminfoParserTest { + private static final String TEST_INPUT = + "time,28506638,177086152\n" + + "4,938,system_server,11,22,N/A,44,0,0,N/A,0,0,0,N/A,0,27613,14013,176602," + + "218228,0,0,122860,122860,1512,1412,5740,8664,0,0,154924,154924,27568," + + "13972,11916,53456,0,0,123008,123008,0,0,0,0,0,0,0,0,Dalvik Other,3662,0," + + "104,0,3660,0,0,0,Stack,1576,0,8,0,1576,0,0,0,Cursor,0,0,0,0,0,0,0,0," + + "Ashmem,156,0,20,0,148,0,0,0,Gfx dev,100,0,48,0,76,0,0,0,Other dev,116,0," + + "164,0,0,96,0,0,.so mmap,7500,2680,3984,21864,904,2680,0,0,.jar mmap,0,0,0," + + "0,0,0,0,0,.apk mmap,72398,71448,0,11736,0,71448,0,0,.ttf mmap,0,0,0,0,0,0," + + "0,0,.dex mmap,76874,46000,0,83644,40,46000,0,0,.oat mmap,8127,2684,64," + + "26652,0,2684,0,0,.art mmap,1991,48,972,10004,1544,48,0,0,Other mmap,137,0," + + "44,1024,4,52,0,0,EGL mtrack,0,0,0,0,0,0,0,0,GL mtrack,111,222,333,444,555," + + "666,777,888,"; + + private static final String INVALID_TEST_INPUT = "RANDOM,TEST,DATA\n234235345345"; + + // Test that normal input is parsed + @Test + public void testDumpsysProcessMeminfoParser() { + List<String> inputBlock = Arrays.asList(TEST_INPUT.split("\n")); + DumpsysProcessMeminfoItem dump = new DumpsysProcessMeminfoParser().parse(inputBlock); + assertEquals(938, dump.getPid()); + assertEquals("system_server", dump.getProcessName()); + assertEquals( + Long.valueOf(11L), + dump.get(DumpsysProcessMeminfoItem.NATIVE).get(DumpsysProcessMeminfoItem.MAX)); + assertEquals( + Long.valueOf(22L), + dump.get(DumpsysProcessMeminfoItem.DALVIK).get(DumpsysProcessMeminfoItem.MAX)); + assertFalse( + dump.get(DumpsysProcessMeminfoItem.OTHER) + .containsKey(DumpsysProcessMeminfoItem.MAX)); + assertEquals( + Long.valueOf(44L), + dump.get(DumpsysProcessMeminfoItem.TOTAL).get(DumpsysProcessMeminfoItem.MAX)); + assertEquals( + Long.valueOf(218228L), + dump.get(DumpsysProcessMeminfoItem.TOTAL).get(DumpsysProcessMeminfoItem.PSS)); + assertEquals( + Long.valueOf(3662L), dump.get("Dalvik Other").get(DumpsysProcessMeminfoItem.PSS)); + assertEquals(Long.valueOf(111L), dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.PSS)); + assertEquals( + Long.valueOf(222L), + dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.SWAPPABLE_PSS)); + assertEquals( + Long.valueOf(333L), + dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.SHARED_DIRTY)); + assertEquals( + Long.valueOf(444L), + dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.SHARED_CLEAN)); + assertEquals( + Long.valueOf(555L), + dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.PRIVATE_DIRTY)); + assertEquals( + Long.valueOf(666L), + dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.PRIVATE_CLEAN)); + assertEquals( + Long.valueOf(777L), + dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.SWAPPED_OUT)); + assertEquals( + Long.valueOf(888L), + dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.SWAPPED_OUT_PSS)); + } + + // Test that the parser does not crash on invalid input and returns empty data + @Test + public void testDumpsysProcessMeminfoParserInvalid() { + List<String> inputBlock = Arrays.asList(INVALID_TEST_INPUT.split("\n")); + DumpsysProcessMeminfoItem dump = new DumpsysProcessMeminfoParser().parse(inputBlock); + assertNull(dump.getProcessName()); + assertTrue(dump.get(DumpsysProcessMeminfoItem.TOTAL).isEmpty()); + } +} diff --git a/tests/src/com/android/loganalysis/parser/EventsLogParserTest.java b/tests/src/com/android/loganalysis/parser/EventsLogParserTest.java index f8115805ce3d262990251eea0233db67788fa4ca..6e7be2ac5bcd0329763acfa5aa6ab678c892c221 100644 --- a/tests/src/com/android/loganalysis/parser/EventsLogParserTest.java +++ b/tests/src/com/android/loganalysis/parser/EventsLogParserTest.java @@ -73,43 +73,45 @@ public class EventsLogParserTest extends TestCase { /** * Test for Cold launch transition delay and starting window delay info */ - public void testValidTransitionDelay() throws IOException { + public void testValidColdTransitionDelay() throws IOException { List<String> lines = Arrays - .asList("01-02 08:12:10.849 934 986 I sysui_multi_action: [319,42,321,59,322,208,325,84100,757,761,758,9,759,4,806,com.google.android.apps.maps,871,com.google.android.maps.MapsActivity,905,0]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,803,799,overview_trigger_nav_btn,802,1]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,803,799,overview_source_app,802,1]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,804,799,overview_source_app_index,801,8,802,1]"); + .asList("09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,50,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,904,com.google.android.apps.nexuslauncher,905,0,945,41]", + "09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,50,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,905,0,945,41]"); List<TransitionDelayItem> transitionItems = (new EventsLogParser()). parseTransitionDelayInfo(readInputBuffer(getTempFile(lines))); - assertEquals("Transition Delay items list should have one item", 1, + assertEquals("Startinng Window Delay items list should have two item", 2, transitionItems.size()); assertEquals("Component name not parsed correctly", - "com.google.android.apps.maps/com.google.android.maps.MapsActivity", + "com.google.android.calculator/com.android.calculator2.Calculator", transitionItems.get(0).getComponentName()); - assertEquals("Transition delay is not parsed correctly", 42, + assertEquals("Transition delay is not parsed correctly", Long.valueOf(51), transitionItems.get(0).getTransitionDelay()); - assertEquals("Starting window delay is not parsed correctly", 59, + assertEquals("Starting window delay is not parsed correctly", Long.valueOf(50), transitionItems.get(0).getStartingWindowDelay()); + assertEquals("Date and time is not parsed correctly", "09-18 23:56:19.376", + transitionItems.get(0).getDateTime()); } /** - * Test for only transition delay in hot launch + * Test for Hot launch transition delay and starting window delay info */ - public void testOnlyTransitionDelay() throws IOException { + public void testValidHotTransitionDelay() throws IOException { List<String> lines = Arrays - .asList("01-02 08:12:10.849 934 986 I sysui_multi_action: [319,42,322,208,325,84100,757,761,758,9,759,4,806,com.google.android.apps.maps,871,com.google.android.maps.MapsActivity,905,0]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,803,799,overview_trigger_nav_btn,802,1]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,803,799,overview_source_app,802,1]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,804,799,overview_source_app_index,801,8,802,1]"); + .asList("09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,50,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,904,com.google.android.apps.nexuslauncher,905,0]", + "09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,50,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,905,0]", + "09-19 02:26:30.182 1143 1196 I sysui_multi_action: [319,87,322,75,325,212,757,761,758,9,759,2,806,com.google.android.apps.nexuslauncher,871,com.google.android.apps.nexuslauncher.NexusLauncherActivity,904,com.google.android.apps.nexuslauncher,905,0]", + "09-19 02:26:30.182 1143 1196 I sysui_multi_action: [319,87,322,75,325,212,757,761,758,9,759,2,806,com.google.android.apps.nexuslauncher,871,com.google.android.apps.nexuslauncher.NexusLauncherActivity,905,0]"); List<TransitionDelayItem> transitionItems = (new EventsLogParser()). parseTransitionDelayInfo(readInputBuffer(getTempFile(lines))); - assertEquals("Transition Delay items list should have one item", 1, + assertEquals("Transition Delay items list should have four item", 4, transitionItems.size()); assertEquals("Component name not parsed correctly", - "com.google.android.apps.maps/com.google.android.maps.MapsActivity", + "com.google.android.calculator/com.android.calculator2.Calculator", transitionItems.get(0).getComponentName()); - assertEquals("Transition delay is not parsed correctly", 42, + assertEquals("Transition delay is not parsed correctly", Long.valueOf(51), transitionItems.get(0).getTransitionDelay()); + assertEquals("Date is not parsed correctly", "09-18 23:56:19.376", + transitionItems.get(0).getDateTime()); } /** @@ -117,20 +119,16 @@ public class EventsLogParserTest extends TestCase { */ public void testTransitionDelayOrder() throws IOException { List<String> lines = Arrays - .asList("01-02 08:12:10.849 934 986 I sysui_multi_action: [319,42,321,59,322,208,325,84100,757,761,758,9,759,4,806,com.google.android.apps.maps,871,com.google.android.maps.MapsActivity,905,0]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,803,799,overview_trigger_nav_btn,802,1]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,803,799,overview_source_app,802,1]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,804,799,overview_source_app_index,801,8,802,1]", - "01-02 08:12:42.187 934 986 I sysui_multi_action: [319,61,321,46,322,159,325,84131,757,761,758,9,759,4,806,com.google.android.apps.maps,871,com.google.android.maps.MapsActivity,905,0]", - "01-02 08:12:42.450 1446 1446 I sysui_multi_action: [757,224,758,2]"); + .asList("09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,59,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,904,com.google.android.apps.nexuslauncher,905,0,945,41]", + "09-18 23:59:18.380 1140 1221 I sysui_multi_action: [319,55,321,65,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,905,0,945,41]"); List<TransitionDelayItem> transitionItems = (new EventsLogParser()). parseTransitionDelayInfo(readInputBuffer(getTempFile(lines))); assertEquals("Transition Delay items list should have two items", 2, transitionItems.size()); - assertEquals("Transition delay for the first item is not set correct", 42, - transitionItems.get(0).getTransitionDelay()); - assertEquals("Transition delay for the second item is not set correct", 61, - transitionItems.get(1).getTransitionDelay()); + assertEquals("Transition delay for the first item is not set correct", Long.valueOf(59), + transitionItems.get(0).getStartingWindowDelay()); + assertEquals("Transition delay for the second item is not set correct", Long.valueOf(65), + transitionItems.get(1).getStartingWindowDelay()); } /** @@ -138,12 +136,8 @@ public class EventsLogParserTest extends TestCase { */ public void testDifferentAppTransitionDelay() throws IOException { List<String> lines = Arrays - .asList("01-02 08:11:58.691 934 986 I sysui_multi_action: [319,48,321,37,322,82,325,84088,757,761,758,9,759,4,806,com.google.android.calculator,871,com.android.calculator2.Calculator,905,0]", - "01-02 08:12:03.639 934 970 I sysui_multi_action: [757,803,799,window_time_0,802,5]", - "01-02 08:12:10.849 934 986 I sysui_multi_action: [319,42,321,59,322,208,325,84100,757,761,758,9,759,4,806,com.google.android.apps.maps,871,com.google.android.maps.MapsActivity,905,0]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,803,799,overview_trigger_nav_btn,802,1]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,803,799,overview_source_app,802,1]", - "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,804,799,overview_source_app_index,801,8,802,1]"); + .asList("09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,50,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,904,com.google.android.apps.nexuslauncher,905,0]", + "09-19 02:26:30.182 1143 1196 I sysui_multi_action: [319,87,322,75,325,212,757,761,758,9,759,2,806,com.google.android.apps.nexuslauncher,871,com.google.android.apps.nexuslauncher.NexusLauncherActivity,904,com.google.android.apps.nexuslauncher,905,0]"); List<TransitionDelayItem> transitionItems = (new EventsLogParser()). parseTransitionDelayInfo(readInputBuffer(getTempFile(lines))); assertEquals("Transition Delay items list should have two items", 2, @@ -152,7 +146,8 @@ public class EventsLogParserTest extends TestCase { "com.google.android.calculator/com.android.calculator2.Calculator", transitionItems.get(0).getComponentName()); assertEquals("Maps is not the second transition delay item", - "com.google.android.apps.maps/com.google.android.maps.MapsActivity", + "com.google.android.apps.nexuslauncher/" + + "com.google.android.apps.nexuslauncher.NexusLauncherActivity", transitionItems.get(1).getComponentName()); } @@ -161,7 +156,7 @@ public class EventsLogParserTest extends TestCase { */ public void testInvalidTransitionPattern() throws IOException { List<String> lines = Arrays - .asList("01-02 08:11:58.691 934 986 I sysui_multi_action: [319,48,322,82,325,84088,757,761,758,9,759,4,807,com.google.android.calculator,871,com.android.calculator2.Calculator,905,0]", + .asList("01-02 08:11:58.691 934 986 I sysui_multi_action: a[319,48,322,82,325,84088,757,761,758,9,759,4,807,com.google.android.calculator,871,com.android.calculator2.Calculator,905,0]", "01-02 08:12:03.639 934 970 I sysui_multi_action: [757,803,799,window_time_0,802,5]", "01-02 08:12:10.849 934 986 I sysui_multi_action: 319,42,321,59,322,208,325,84100,757,761,758,9,759,4,806,com.google.android.apps.maps,871,com.google.android.maps.MapsActivity,905,0]", "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,803,799,overview_trigger_nav_btn,802,1]", diff --git a/tests/src/com/android/loganalysis/parser/GfxInfoParserTest.java b/tests/src/com/android/loganalysis/parser/GfxInfoParserTest.java index b424472657a9f3e2dd53abb6050d57c443c8a848..fcd053b9c8f60625b10cbb4227729e8c08f61d12 100644 --- a/tests/src/com/android/loganalysis/parser/GfxInfoParserTest.java +++ b/tests/src/com/android/loganalysis/parser/GfxInfoParserTest.java @@ -93,6 +93,9 @@ public class GfxInfoParserTest extends TestCase { assertEquals("com.google.android.leanbacklauncher", item.getName(853)); assertEquals(20391, item.getTotalFrames(853)); assertEquals(785, item.getJankyFrames(853)); + assertEquals(9, item.getPrecentile90(853)); + assertEquals(14, item.getPrecentile95(853)); + assertEquals(32, item.getPrecentile99(853)); } /** @@ -285,12 +288,21 @@ public class GfxInfoParserTest extends TestCase { assertEquals("com.google.android.leanbacklauncher", item.getName(844)); assertEquals(1690, item.getTotalFrames(844)); assertEquals(125, item.getJankyFrames(844)); + assertEquals(13, item.getPrecentile90(844)); + assertEquals(19, item.getPrecentile95(844)); + assertEquals(48, item.getPrecentile99(844)); assertEquals("com.android.vending", item.getName(1881)); assertEquals(693, item.getTotalFrames(1881)); assertEquals(62, item.getJankyFrames(1881)); + assertEquals(16, item.getPrecentile90(1881)); + assertEquals(26, item.getPrecentile95(1881)); + assertEquals(81, item.getPrecentile99(1881)); assertEquals("com.google.android.videos", item.getName(2931)); assertEquals(107, item.getTotalFrames(2931)); assertEquals(42, item.getJankyFrames(2931)); + assertEquals(48, item.getPrecentile90(2931)); + assertEquals(65, item.getPrecentile95(2931)); + assertEquals(113, item.getPrecentile99(2931)); } /** diff --git a/tests/src/com/android/loganalysis/parser/TraceFormatParserTest.java b/tests/src/com/android/loganalysis/parser/TraceFormatParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..06ce9ec36489aac28ea8e3881fd7fafaa26c5bd9 --- /dev/null +++ b/tests/src/com/android/loganalysis/parser/TraceFormatParserTest.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2017 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 static org.junit.Assert.fail; + +import com.android.loganalysis.item.TraceFormatItem; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; + +/** Test for {@link TraceFormatParser}. */ +@RunWith(JUnit4.class) +public class TraceFormatParserTest { + private TraceFormatParser mParser; + + // "unwrap" the regex strings so that we can compare with the generated regex + private static final String MATCH_NUM_UNESCAPED = + TraceFormatParser.MATCH_NUM.replaceAll("\\\\\\\\", "\\\\"); + private static final String MATCH_HEX_UNESCAPED = + TraceFormatParser.MATCH_HEX.replaceAll("\\\\\\\\", "\\\\"); + private static final String MATCH_STR_UNESCAPED = + TraceFormatParser.MATCH_STR.replaceAll("\\\\\\\\", "\\\\"); + + @Before + public void setUp() { + mParser = new TraceFormatParser(); + } + + @Test + public void testParseFormatLine() { + List<String> formatLine = + Arrays.asList("print fmt: \"foo=%llu, bar=%s\", REC->foo, REC->bar"); + String expectedRegex = + String.format( + "foo=(?<foo>%s), bar=(?<bar>%s)", MATCH_NUM_UNESCAPED, MATCH_STR_UNESCAPED); + List<String> expectedParameters = Arrays.asList("foo", "bar"); + List<String> expectedNumericParameters = Arrays.asList("foo"); + List<String> expectedHexParameters = Arrays.asList(); + List<String> expectedStringParameters = Arrays.asList("bar"); + String shouldMatch = "foo=123, bar=enabled"; + + TraceFormatItem parsedItem = mParser.parse(formatLine); + Assert.assertEquals(expectedParameters, parsedItem.getParameters()); + Assert.assertEquals(expectedNumericParameters, parsedItem.getNumericParameters()); + Assert.assertEquals(expectedHexParameters, parsedItem.getHexParameters()); + Assert.assertEquals(expectedStringParameters, parsedItem.getStringParameters()); + Assert.assertEquals(expectedRegex, parsedItem.getRegex().toString()); + Matcher m = parsedItem.getRegex().matcher(shouldMatch); + Assert.assertTrue(m.matches()); + Assert.assertEquals(m.group("foo"), "123"); + Assert.assertEquals(m.group("bar"), "enabled"); + } + + @Test + public void testNoParameters() { + List<String> formatLine = Arrays.asList("print fmt: \"foo\""); + String expectedRegex = "foo"; + List<String> expectedParameters = Arrays.asList(); + String shouldMatch = "foo"; + + TraceFormatItem parsedItem = mParser.parse(formatLine); + Assert.assertEquals(expectedParameters, parsedItem.getParameters()); + Assert.assertEquals(expectedRegex, parsedItem.getRegex().toString()); + Matcher m = parsedItem.getRegex().matcher(shouldMatch); + Assert.assertTrue(m.matches()); + } + + @Test + public void testNullInput() { + try { + mParser.parse(null); + fail("Expected an exception thrown by TraceFormatParser"); + } catch (RuntimeException e) { + // expected + } + } + + @Test + public void testEmptyInput() { + List<String> formatLine = Arrays.asList(""); + try { + mParser.parse(formatLine); + fail("Expected an exception thrown by TraceFormatParser"); + } catch (RuntimeException e) { + // expected + } + } + + @Test + public void testMultiLineInput() { + List<String> formatLine = Arrays.asList("foo", "bar"); + try { + mParser.parse(formatLine); + fail("Expected an exception thrown by TraceFormatParser"); + } catch (RuntimeException e) { + // expected + } + } + + @Test + public void testOneLineInvalidInput() { + List<String> formatLine = Arrays.asList("foo bar"); + try { + mParser.parse(formatLine); + fail("Expected an exception thrown by TraceFormatParser"); + } catch (RuntimeException e) { + // expected + } + } + + @Test + public void testQuoteInParams() { + List<String> formatLine = + Arrays.asList("print fmt: \"foo %s\", REC->foo ? \"online\" : \"offline\""); + String expectedRegex = String.format("foo (?<foo>%s)", MATCH_STR_UNESCAPED); + String shouldMatch = "foo online"; + + TraceFormatItem parsedItem = mParser.parse(formatLine); + Assert.assertEquals(expectedRegex, parsedItem.getRegex().toString()); + Matcher m = parsedItem.getRegex().matcher(shouldMatch); + Assert.assertTrue(m.matches()); + Assert.assertEquals(m.group("foo"), "online"); + } + + @Test + public void testCategorizeParameters() { + List<String> formatLine = + Arrays.asList( + "print fmt: \"num1=%lu, num2=%f, hex=%08x, str=%s\", REC->num1, REC->num2, REC->hex, REC->str"); + List<String> expectedNumericParameters = Arrays.asList("num1", "num2"); + List<String> expectedHexParameters = Arrays.asList("hex"); + List<String> expectedStringParameters = Arrays.asList("str"); + + TraceFormatItem parsedItem = mParser.parse(formatLine); + Assert.assertEquals(expectedNumericParameters, parsedItem.getNumericParameters()); + Assert.assertEquals(expectedHexParameters, parsedItem.getHexParameters()); + Assert.assertEquals(expectedStringParameters, parsedItem.getStringParameters()); + } + + @Test + public void testCaseConvertParameterName() { + List<String> formatLine = Arrays.asList("print fmt: \"foo_bar=%llu\", REC->foo_bar"); + List<String> expectedParameters = Arrays.asList("fooBar"); + String shouldMatch = "foo_bar=123"; + + TraceFormatItem parsedItem = mParser.parse(formatLine); + Assert.assertEquals(expectedParameters, parsedItem.getParameters()); + Matcher m = parsedItem.getRegex().matcher(shouldMatch); + Assert.assertTrue(m.matches()); + Assert.assertEquals(m.group("fooBar"), "123"); + } + + @Test + public void testMatchInt() { + List<String> formatLine = + Arrays.asList("print fmt: \"foo=%d, bar=%lu\", REC->foo, REC->bar"); + String shouldMatch = "foo=-123, bar=456"; + + TraceFormatItem parsedItem = mParser.parse(formatLine); + Matcher m = parsedItem.getRegex().matcher(shouldMatch); + Assert.assertTrue(m.matches()); + Assert.assertEquals(m.group("foo"), "-123"); + Assert.assertEquals(m.group("bar"), "456"); + } + + @Test + public void testMatchFloat() { + List<String> formatLine = + Arrays.asList("print fmt: \"foo=%f, bar=%.2f\", REC->foo, REC->bar"); + String shouldMatch = "foo=123.4567, bar=456.78"; + + TraceFormatItem parsedItem = mParser.parse(formatLine); + Matcher m = parsedItem.getRegex().matcher(shouldMatch); + Assert.assertTrue(m.matches()); + Assert.assertEquals(m.group("foo"), "123.4567"); + Assert.assertEquals(m.group("bar"), "456.78"); + } + + @Test + public void testMatchHex() { + List<String> formatLine = + Arrays.asList( + "print fmt: \"foo=0x%04x, bar=0x%08X, baz=%x\", REC->foo, REC->bar, REC->baz"); + String shouldMatch = "foo=0x007b, bar=0x000001C8, baz=7b"; + + TraceFormatItem parsedItem = mParser.parse(formatLine); + Matcher m = parsedItem.getRegex().matcher(shouldMatch); + Assert.assertTrue(m.matches()); + Assert.assertEquals(m.group("foo"), "007b"); + Assert.assertEquals(m.group("bar"), "000001C8"); + Assert.assertEquals(m.group("baz"), "7b"); + } + + @Test + public void testMatchString() { + List<String> formatLine = + Arrays.asList("print fmt: \"foo=%s, bar=%s\", REC->foo, REC->bar"); + String shouldMatch = "foo=oof, bar=123"; + + TraceFormatItem parsedItem = mParser.parse(formatLine); + Matcher m = parsedItem.getRegex().matcher(shouldMatch); + Assert.assertTrue(m.matches()); + Assert.assertEquals(m.group("foo"), "oof"); + Assert.assertEquals(m.group("bar"), "123"); + } +}