diff options
Diffstat (limited to 'src/com')
8 files changed, 688 insertions, 46 deletions
diff --git a/src/com/android/loganalysis/item/DumpsysProcessMeminfoItem.java b/src/com/android/loganalysis/item/DumpsysProcessMeminfoItem.java new file mode 100644 index 0000000..3abfd40 --- /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 482cea3..21ff245 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 0000000..82944d5 --- /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 33a1b93..6e09d4b 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 0000000..119bab6 --- /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 33afb2f..1e1803c 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 ef549d7..8f0dce7 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 0000000..1c444f4 --- /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); + } +} |