diff options
Diffstat (limited to 'src/com/android/loganalysis/parser/TraceFormatParser.java')
-rw-r--r-- | src/com/android/loganalysis/parser/TraceFormatParser.java | 158 |
1 files changed, 158 insertions, 0 deletions
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); + } +} |