1 /*
2  * Copyright (C) 2023 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 
17 import java.io.DataInputStream;
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.IOException;
21 import java.nio.charset.StandardCharsets;
22 import java.util.HashMap;
23 
24 abstract class BaseTraceParser {
25     public static final int MAGIC_NUMBER = 0x574f4c53;
26     public static final int DUAL_CLOCK_VERSION = 3;
27     public static final int WALL_CLOCK_VERSION = 2;
28     public static final int STREAMING_DUAL_CLOCK_VERSION = 0xF3;
29     public static final int STREAMING_WALL_CLOCK_VERSION = 0xF2;
30     public static final String START_SECTION_ID = "*";
31     public static final String METHODS_SECTION_ID = "*methods";
32     public static final String THREADS_SECTION_ID = "*threads";
33     public static final String END_SECTION_ID = "*end";
34 
InitializeParser(File file)35     public void InitializeParser(File file) throws IOException {
36         dataStream = new DataInputStream(new FileInputStream(file));
37         methodIdMap = new HashMap<Integer, String>();
38         threadIdMap = new HashMap<Integer, String>();
39         nestingLevelMap = new HashMap<Integer, Integer>();
40         threadEventsMap = new HashMap<String, String>();
41         threadTimestamp1Map = new HashMap<Integer, Integer>();
42         threadTimestamp2Map = new HashMap<Integer, Integer>();
43     }
44 
closeFile()45     public void closeFile() throws IOException {
46         dataStream.close();
47     }
48 
readString(int numBytes)49     public String readString(int numBytes) throws IOException {
50         byte[] buffer = new byte[numBytes];
51         dataStream.readFully(buffer);
52         return new String(buffer, StandardCharsets.UTF_8);
53     }
54 
readLine()55     public String readLine() throws IOException {
56         StringBuilder sb = new StringBuilder();
57         char lineSeparator = '\n';
58         char c = (char)dataStream.readUnsignedByte();
59         while ( c != lineSeparator) {
60             sb.append(c);
61             c = (char)dataStream.readUnsignedByte();
62         }
63         return sb.toString();
64     }
65 
readNumber(int numBytes)66     public int readNumber(int numBytes) throws IOException {
67         int number = 0;
68         for (int i = 0; i < numBytes; i++) {
69             number += dataStream.readUnsignedByte() << (i * 8);
70         }
71         return number;
72     }
73 
validateTraceHeader(int expectedVersion)74     public void validateTraceHeader(int expectedVersion) throws Exception {
75         // Read 4-byte magicNumber.
76         int magicNumber = readNumber(4);
77         if (magicNumber != MAGIC_NUMBER) {
78             throw new Exception("Magic number doesn't match. Expected "
79                     + Integer.toHexString(MAGIC_NUMBER) + " Got "
80                     + Integer.toHexString(magicNumber));
81         }
82         // Read 2-byte version.
83         int version = readNumber(2);
84         if (version != expectedVersion) {
85             throw new Exception(
86                     "Unexpected version. Expected " + expectedVersion + " Got " + version);
87         }
88         traceFormatVersion = version & 0xF;
89         // Read 2-byte headerLength length.
90         int headerLength = readNumber(2);
91         // Read 8-byte starting time - Ignore timestamps since they are not deterministic.
92         dataStream.skipBytes(8);
93         // 4 byte magicNumber + 2 byte version + 2 byte offset + 8 byte timestamp.
94         int numBytesRead = 16;
95         if (version >= DUAL_CLOCK_VERSION) {
96             // Read 2-byte record size.
97             // TODO(mythria): Check why this is needed. We can derive recordSize from version. Not
98             // sure why this is needed.
99             recordSize = readNumber(2);
100             numBytesRead += 2;
101         }
102         // Skip any padding.
103         if (headerLength > numBytesRead) {
104             dataStream.skipBytes(headerLength - numBytesRead);
105         }
106     }
107 
GetThreadID()108     public int GetThreadID() throws IOException {
109         // Read 2-byte thread-id. On host thread-ids can be greater than 16-bit but it is truncated
110         // to 16-bits in the trace.
111         int threadId = readNumber(2);
112         return threadId;
113     }
114 
GetEntryHeader()115     public int GetEntryHeader() throws IOException {
116         // Read 1-byte header type
117         return readNumber(1);
118     }
119 
ProcessMethodInfoEntry()120     public void ProcessMethodInfoEntry() throws IOException {
121         // Read 2-byte method info size
122         int headerLength = readNumber(2);
123         // Read header size data.
124         String methodInfo = readString(headerLength);
125         String[] tokens = methodInfo.split("\t", 2);
126         // Get methodId and record methodId -> methodName map.
127         int methodId = Integer.decode(tokens[0]);
128         String methodLine = tokens[1].replace('\t', ' ');
129         methodLine = methodLine.substring(0, methodLine.length() - 1);
130         methodIdMap.put(methodId, methodLine);
131     }
132 
ProcessThreadInfoEntry()133     public void ProcessThreadInfoEntry() throws IOException {
134         // Read 2-byte thread id
135         int threadId = readNumber(2);
136         // Read 2-byte thread info size
137         int headerLength = readNumber(2);
138         // Read header size data.
139         String threadInfo = readString(headerLength);
140         threadIdMap.put(threadId, threadInfo);
141     }
142 
ShouldCheckThread(int threadId, String threadName)143     public boolean ShouldCheckThread(int threadId, String threadName) throws Exception {
144         if (!threadIdMap.containsKey(threadId)) {
145           System.out.println("no threadId -> name  mapping for thread " + threadId);
146           // TODO(b/279547877): Ideally we should throw here, since it isn't expected. Just
147           // continuing to get more logs from the bots to see what's happening here. The
148           // test will fail anyway because the expected output will be different.
149           return true;
150         }
151 
152         return threadIdMap.get(threadId).equals(threadName);
153     }
154 
eventTypeToString(int eventType, int threadId)155     public String eventTypeToString(int eventType, int threadId) {
156         if (!nestingLevelMap.containsKey(threadId)) {
157             nestingLevelMap.put(threadId, 0);
158         }
159 
160         int nestingLevel = nestingLevelMap.get(threadId);
161         String str = "";
162         for (int i = 0; i < nestingLevel; i++) {
163             str += ".";
164         }
165         switch (eventType) {
166             case 0:
167                 nestingLevel++;
168                 str += ".>>";
169                 break;
170             case 1:
171                 nestingLevel--;
172                 str += "<<";
173                 break;
174             case 2:
175                 nestingLevel--;
176                 str += "<<E";
177                 break;
178             default:
179                 str += "??";
180         }
181         nestingLevelMap.put(threadId, nestingLevel);
182         return str;
183     }
184 
CheckTimestamp(int timestamp, int threadId, HashMap<Integer, Integer> threadTimestampMap)185     public void CheckTimestamp(int timestamp, int threadId,
186             HashMap<Integer, Integer> threadTimestampMap) throws Exception {
187         if (threadTimestampMap.containsKey(threadId)) {
188             int oldTimestamp = threadTimestampMap.get(threadId);
189             if (timestamp < oldTimestamp) {
190                 throw new Exception("timestamps are not increasing current: " + timestamp
191                         + "  earlier: " + oldTimestamp);
192             }
193         }
194         threadTimestampMap.put(threadId, timestamp);
195     }
196 
ProcessEventEntry(int threadId)197     public String ProcessEventEntry(int threadId) throws IOException, Exception {
198         // Read 4-byte method value
199         int methodAndEvent = readNumber(4);
200         int methodId = methodAndEvent & ~0x3;
201         int eventType = methodAndEvent & 0x3;
202 
203         String str = eventTypeToString(eventType, threadId) + " " + threadIdMap.get(threadId)
204                 + " " + methodIdMap.get(methodId);
205         // Depending on the version skip either one or two timestamps.
206         int timestamp1 = readNumber(4);
207         CheckTimestamp(timestamp1, threadId, threadTimestamp1Map);
208         if (traceFormatVersion != 2) {
209             // Read second timestamp
210             int timestamp2 = readNumber(4);
211             CheckTimestamp(timestamp2, threadId, threadTimestamp2Map);
212         }
213         return str;
214     }
215 
UpdateThreadEvents(int threadId, String entry)216     public void UpdateThreadEvents(int threadId, String entry) {
217         String threadName = threadIdMap.get(threadId);
218         if (!threadEventsMap.containsKey(threadName)) {
219             threadEventsMap.put(threadName, entry);
220             return;
221         }
222         threadEventsMap.put(threadName, threadEventsMap.get(threadName) + "\n" + entry);
223     }
224 
CheckTraceFileFormat(File traceFile, int expectedVersion, String threadName)225     public abstract void CheckTraceFileFormat(File traceFile,
226         int expectedVersion, String threadName) throws Exception;
227 
228     DataInputStream dataStream;
229     HashMap<Integer, String> methodIdMap;
230     HashMap<Integer, String> threadIdMap;
231     HashMap<Integer, Integer> nestingLevelMap;
232     HashMap<String, String> threadEventsMap;
233     HashMap<Integer, Integer> threadTimestamp1Map;
234     HashMap<Integer, Integer> threadTimestamp2Map;
235     int recordSize = 0;
236     int traceFormatVersion = 0;
237 }
238