Home | History | Annotate | Download | only in parser
      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
     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. */
    155     private static String createNamedRegexGroup(String base, String name) {
    156         return String.format("(?<%s>%s)", name, base);
    157     }
    158 }
    159