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