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