1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.loganalysis.parser; 17 18 import com.android.loganalysis.item.TraceFormatItem; 19 20 import com.google.common.base.CaseFormat; 21 22 import java.util.ArrayList; 23 import java.util.List; 24 import java.util.regex.Matcher; 25 import java.util.regex.Pattern; 26 27 /** 28 * Read trace format and generate a regex that matches output of such format. 29 * 30 * <p>Traces under /d/tracing specify the output format with a printf string. This parser reads such 31 * string, finds all parameters, and generates a regex that matches output of such format. Each 32 * parameter corresponds to a named-capturing group in the regex. The parameter names are converted 33 * to camel case because Java regex group name must contain only letters and numbers. 34 * 35 * <p>An end-to-end example: 36 * 37 * <pre>{@code 38 * List<String> formatLine = Arrays.asList("print fmt: \"foo=%llu, bar:%s\", REC->foo, REC->bar"); 39 * TraceFormatItem parsedFormat = new TraceFormatParser.parse(formatLine); 40 * parsedFormat.getParameters(); // "foo", "bar" 41 * parsedFormat.getNumericParameters(); // "foo" 42 * Matcher matcher = parsedFormat.getRegex.matcher("foo=123, bar:enabled"); 43 * matcher.matches(); 44 * matcher.group("foo") // 123 45 * matcher.group("bar") // "enabled" 46 * }</pre> 47 * 48 * <p>The initial implementation supports some commonly used specifiers: signed and unsigned integer 49 * (with or without long or long long modifier), floating point number (with or without precision), 50 * hexadecimal number (with or without 0's padding), and string (contains only [a-zA-Z_0-9]). It is 51 * assumed no characters found in the format line need to be escaped. 52 * 53 * <p>Some examples of trace format line: 54 * 55 * <p>(thermal/tsens_read) 56 * 57 * <p>print fmt: "temp=%lu sensor=tsens_tz_sensor%u", REC->temp, REC->sensor 58 * 59 * <p>(sched/sched_cpu_hotplug) 60 * 61 * <p>print fmt: "cpu %d %s error=%d", REC->affected_cpu, REC->status ? "online" : "offline", 62 * REC->error 63 * 64 * <p>(mmc/mmc_blk_erase_start) 65 * 66 * <p>print fmt: "cmd=%u,addr=0x%08x,size=0x%08x", REC->cmd, REC->addr, REC->size 67 */ 68 public class TraceFormatParser implements IParser { 69 // split the raw format line 70 private static final Pattern SPLIT_FORMAT_LINE = 71 Pattern.compile(".*?\"(?<printf>.*?)\"(?<params>.*)"); 72 // match parameter names 73 private static final Pattern SPLIT_PARAMS = Pattern.compile("->(?<param>\\w+)"); 74 // match and categorize common printf specifiers 75 // use ?: to flag all non-capturing group so any group captured correspond to a specifier 76 private static final Pattern PRINTF_SPECIFIERS = 77 Pattern.compile( 78 "(?<num>%(?:llu|lu|u|lld|ld|d|(?:.\\d*)?f))|(?<hex>%\\d*(?:x|X))|(?<str>%s)"); 79 80 // regex building blocks to match simple numeric/hex/string parameters, exposed for unit testing 81 static final String MATCH_NUM = "-?\\\\d+(?:\\\\.\\\\d+)?"; 82 static final String MATCH_HEX = "[\\\\da-fA-F]+"; 83 static final String MATCH_STR = "[\\\\w]*"; 84 85 /** Parse a trace format line and return an {@link TraceFormatItem} */ 86 @Override parse(List<String> lines)87 public TraceFormatItem parse(List<String> lines) { 88 // sanity check 89 if (lines == null || lines.size() != 1) { 90 throw new RuntimeException("Cannot parse format line: expect one-line trace format"); 91 } 92 93 // split the raw format line 94 Matcher formatLineMatcher = SPLIT_FORMAT_LINE.matcher(lines.get(0)); 95 if (!formatLineMatcher.matches()) { 96 throw new RuntimeException("Cannot parse format line: unexpected format"); 97 } 98 String printfString = formatLineMatcher.group("printf"); 99 String paramsString = formatLineMatcher.group("params"); 100 101 // list of parameters, to be populated soon 102 List<String> allParams = new ArrayList<>(); 103 List<String> numParams = new ArrayList<>(); 104 List<String> hexParams = new ArrayList<>(); 105 List<String> strParams = new ArrayList<>(); 106 107 // find all parameters and convert them to camel case 108 Matcher paramsMatcher = SPLIT_PARAMS.matcher(paramsString); 109 while (paramsMatcher.find()) { 110 String camelCasedParam = 111 CaseFormat.LOWER_UNDERSCORE.to( 112 CaseFormat.LOWER_CAMEL, paramsMatcher.group("param")); 113 allParams.add(camelCasedParam); 114 } 115 116 // scan the printf string, categorizing parameters and generating a matching regex 117 StringBuffer regexBuilder = new StringBuffer(); 118 int paramIndex = 0; 119 String currentParam; 120 121 Matcher printfMatcher = PRINTF_SPECIFIERS.matcher(printfString); 122 while (printfMatcher.find()) { 123 // parameter corresponds to the found specifier 124 currentParam = allParams.get(paramIndex++); 125 if (printfMatcher.group("num") != null) { 126 printfMatcher.appendReplacement( 127 regexBuilder, createNamedRegexGroup(MATCH_NUM, currentParam)); 128 numParams.add(currentParam); 129 } else if (printfMatcher.group("hex") != null) { 130 printfMatcher.appendReplacement( 131 regexBuilder, createNamedRegexGroup(MATCH_HEX, currentParam)); 132 hexParams.add(currentParam); 133 } else if (printfMatcher.group("str") != null) { 134 printfMatcher.appendReplacement( 135 regexBuilder, createNamedRegexGroup(MATCH_STR, currentParam)); 136 strParams.add(currentParam); 137 } else { 138 throw new RuntimeException("Unrecognized specifier: " + printfMatcher.group()); 139 } 140 } 141 printfMatcher.appendTail(regexBuilder); 142 Pattern generatedRegex = Pattern.compile(regexBuilder.toString()); 143 144 // assemble and return a TraceFormatItem 145 TraceFormatItem item = new TraceFormatItem(); 146 item.setRegex(generatedRegex); 147 item.setParameters(allParams); 148 item.setNumericParameters(numParams); 149 item.setHexParameters(hexParams); 150 item.setStringParameters(strParams); 151 return item; 152 } 153 154 /** Helper function to create a regex group with given name. */ createNamedRegexGroup(String base, String name)155 private static String createNamedRegexGroup(String base, String name) { 156 return String.format("(?<%s>%s)", name, base); 157 } 158 } 159