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