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