summaryrefslogtreecommitdiff
path: root/src/com/android/loganalysis/parser/TraceFormatParser.java
blob: 1c444f4f29aeb66931ef4dfe4bf08299b390186a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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);
    }
}