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 STREAMING_DUAL_CLOCK_VERSION = 0xF3; 28 public static final String START_SECTION_ID = "*"; 29 public static final String METHODS_SECTION_ID = "*methods"; 30 public static final String THREADS_SECTION_ID = "*threads"; 31 public static final String END_SECTION_ID = "*end"; 32 InitializeParser(File file)33 public void InitializeParser(File file) throws IOException { 34 dataStream = new DataInputStream(new FileInputStream(file)); 35 methodIdMap = new HashMap<Integer, String>(); 36 threadIdMap = new HashMap<Integer, String>(); 37 nestingLevelMap = new HashMap<Integer, Integer>(); 38 threadEventsMap = new HashMap<String, String>(); 39 } 40 closeFile()41 public void closeFile() throws IOException { 42 dataStream.close(); 43 } 44 readString(int numBytes)45 public String readString(int numBytes) throws IOException { 46 byte[] buffer = new byte[numBytes]; 47 dataStream.readFully(buffer); 48 return new String(buffer, StandardCharsets.UTF_8); 49 } 50 readLine()51 public String readLine() throws IOException { 52 StringBuilder sb = new StringBuilder(); 53 char lineSeparator = '\n'; 54 char c = (char)dataStream.readUnsignedByte(); 55 while ( c != lineSeparator) { 56 sb.append(c); 57 c = (char)dataStream.readUnsignedByte(); 58 } 59 return sb.toString(); 60 } 61 readNumber(int numBytes)62 public int readNumber(int numBytes) throws IOException { 63 int number = 0; 64 for (int i = 0; i < numBytes; i++) { 65 number += dataStream.readUnsignedByte() << (i * 8); 66 } 67 return number; 68 } 69 validateTraceHeader(int expectedVersion)70 public void validateTraceHeader(int expectedVersion) throws Exception { 71 // Read 4-byte magicNumber. 72 int magicNumber = readNumber(4); 73 if (magicNumber != MAGIC_NUMBER) { 74 throw new Exception("Magic number doesn't match. Expected " 75 + Integer.toHexString(MAGIC_NUMBER) + " Got " 76 + Integer.toHexString(magicNumber)); 77 } 78 // Read 2-byte version. 79 int version = readNumber(2); 80 if (version != expectedVersion) { 81 throw new Exception( 82 "Unexpected version. Expected " + expectedVersion + " Got " + version); 83 } 84 traceFormatVersion = version & 0xF; 85 // Read 2-byte headerLength length. 86 int headerLength = readNumber(2); 87 // Read 8-byte starting time - Ignore timestamps since they are not deterministic. 88 dataStream.skipBytes(8); 89 // 4 byte magicNumber + 2 byte version + 2 byte offset + 8 byte timestamp. 90 int numBytesRead = 16; 91 if (version >= DUAL_CLOCK_VERSION) { 92 // Read 2-byte record size. 93 // TODO(mythria): Check why this is needed. We can derive recordSize from version. Not 94 // sure why this is needed. 95 recordSize = readNumber(2); 96 numBytesRead += 2; 97 } 98 // Skip any padding. 99 if (headerLength > numBytesRead) { 100 dataStream.skipBytes(headerLength - numBytesRead); 101 } 102 } 103 GetEntryHeader()104 public int GetEntryHeader() throws IOException { 105 // Read 2-byte thread-id. On host thread-ids can be greater than 16-bit. 106 int threadId = readNumber(2); 107 if (threadId != 0) { 108 return threadId; 109 } 110 // Read 1-byte header type 111 return readNumber(1); 112 } 113 ProcessMethodInfoEntry()114 public void ProcessMethodInfoEntry() throws IOException { 115 // Read 2-byte method info size 116 int headerLength = readNumber(2); 117 // Read header size data. 118 String methodInfo = readString(headerLength); 119 String[] tokens = methodInfo.split("\t", 2); 120 // Get methodId and record methodId -> methodName map. 121 int methodId = Integer.decode(tokens[0]); 122 String methodLine = tokens[1].replace('\t', ' '); 123 methodLine = methodLine.substring(0, methodLine.length() - 1); 124 methodIdMap.put(methodId, methodLine); 125 } 126 ProcessThreadInfoEntry()127 public void ProcessThreadInfoEntry() throws IOException { 128 // Read 2-byte thread id 129 int threadId = readNumber(2); 130 // Read 2-byte thread info size 131 int headerLength = readNumber(2); 132 // Read header size data. 133 String threadInfo = readString(headerLength); 134 threadIdMap.put(threadId, threadInfo); 135 } 136 ShouldIgnoreThread(int threadId)137 public boolean ShouldIgnoreThread(int threadId) throws Exception { 138 if (threadIdMap.get(threadId).contains("Daemon")) { 139 return true; 140 } 141 return false; 142 } 143 eventTypeToString(int eventType, int threadId)144 public String eventTypeToString(int eventType, int threadId) { 145 if (!nestingLevelMap.containsKey(threadId)) { 146 nestingLevelMap.put(threadId, 0); 147 } 148 149 int nestingLevel = nestingLevelMap.get(threadId); 150 String str = ""; 151 for (int i = 0; i < nestingLevel; i++) { 152 str += "."; 153 } 154 switch (eventType) { 155 case 0: 156 nestingLevel++; 157 str += ".>>"; 158 break; 159 case 1: 160 nestingLevel--; 161 str += "<<"; 162 break; 163 case 2: 164 nestingLevel--; 165 str += "<<E"; 166 break; 167 default: 168 str += "??"; 169 } 170 nestingLevelMap.put(threadId, nestingLevel); 171 return str; 172 } 173 ProcessEventEntry(int threadId)174 public String ProcessEventEntry(int threadId) throws IOException { 175 // Read 4-byte method value 176 int methodAndEvent = readNumber(4); 177 int methodId = methodAndEvent & ~0x3; 178 int eventType = methodAndEvent & 0x3; 179 180 String str = eventTypeToString(eventType, threadId) + " " + threadIdMap.get(threadId) 181 + " " + methodIdMap.get(methodId); 182 // Depending on the version skip either one or two timestamps. 183 // TODO(mythria): Probably add a check that time stamps are always greater than initial 184 // timestamp. 185 int numBytesTimestamp = (traceFormatVersion == 2) ? 4 : 8; 186 dataStream.skipBytes(numBytesTimestamp); 187 return str; 188 } 189 UpdateThreadEvents(int threadId, String entry)190 public void UpdateThreadEvents(int threadId, String entry) { 191 String threadName = threadIdMap.get(threadId); 192 if (!threadEventsMap.containsKey(threadName)) { 193 threadEventsMap.put(threadName, entry); 194 return; 195 } 196 threadEventsMap.put(threadName, threadEventsMap.get(threadName) + "\n" + entry); 197 } 198 CheckTraceFileFormat(File traceFile, int expectedVersion)199 public abstract void CheckTraceFileFormat(File traceFile, int expectedVersion) 200 throws Exception; 201 202 DataInputStream dataStream; 203 HashMap<Integer, String> methodIdMap; 204 HashMap<Integer, String> threadIdMap; 205 HashMap<Integer, Integer> nestingLevelMap; 206 HashMap<String, String> threadEventsMap; 207 int recordSize = 0; 208 int traceFormatVersion = 0; 209 } 210