| /* |
| * Copyright (C) 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import java.io.DataInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.HashMap; |
| |
| abstract class BaseTraceParser { |
| public static final int MAGIC_NUMBER = 0x574f4c53; |
| public static final int DUAL_CLOCK_VERSION = 3; |
| public static final int STREAMING_DUAL_CLOCK_VERSION = 0xF3; |
| public static final String START_SECTION_ID = "*"; |
| public static final String METHODS_SECTION_ID = "*methods"; |
| public static final String THREADS_SECTION_ID = "*threads"; |
| public static final String END_SECTION_ID = "*end"; |
| |
| public void InitializeParser(File file) throws IOException { |
| dataStream = new DataInputStream(new FileInputStream(file)); |
| methodIdMap = new HashMap<Integer, String>(); |
| threadIdMap = new HashMap<Integer, String>(); |
| nestingLevelMap = new HashMap<Integer, Integer>(); |
| threadEventsMap = new HashMap<String, String>(); |
| } |
| |
| public void closeFile() throws IOException { |
| dataStream.close(); |
| } |
| |
| public String readString(int numBytes) throws IOException { |
| byte[] buffer = new byte[numBytes]; |
| dataStream.readFully(buffer); |
| return new String(buffer, StandardCharsets.UTF_8); |
| } |
| |
| public String readLine() throws IOException { |
| StringBuilder sb = new StringBuilder(); |
| char lineSeparator = '\n'; |
| char c = (char)dataStream.readUnsignedByte(); |
| while ( c != lineSeparator) { |
| sb.append(c); |
| c = (char)dataStream.readUnsignedByte(); |
| } |
| return sb.toString(); |
| } |
| |
| public int readNumber(int numBytes) throws IOException { |
| int number = 0; |
| for (int i = 0; i < numBytes; i++) { |
| number += dataStream.readUnsignedByte() << (i * 8); |
| } |
| return number; |
| } |
| |
| public void validateTraceHeader(int expectedVersion) throws Exception { |
| // Read 4-byte magicNumber. |
| int magicNumber = readNumber(4); |
| if (magicNumber != MAGIC_NUMBER) { |
| throw new Exception("Magic number doesn't match. Expected " |
| + Integer.toHexString(MAGIC_NUMBER) + " Got " |
| + Integer.toHexString(magicNumber)); |
| } |
| // Read 2-byte version. |
| int version = readNumber(2); |
| if (version != expectedVersion) { |
| throw new Exception( |
| "Unexpected version. Expected " + expectedVersion + " Got " + version); |
| } |
| traceFormatVersion = version & 0xF; |
| // Read 2-byte headerLength length. |
| int headerLength = readNumber(2); |
| // Read 8-byte starting time - Ignore timestamps since they are not deterministic. |
| dataStream.skipBytes(8); |
| // 4 byte magicNumber + 2 byte version + 2 byte offset + 8 byte timestamp. |
| int numBytesRead = 16; |
| if (version >= DUAL_CLOCK_VERSION) { |
| // Read 2-byte record size. |
| // TODO(mythria): Check why this is needed. We can derive recordSize from version. Not |
| // sure why this is needed. |
| recordSize = readNumber(2); |
| numBytesRead += 2; |
| } |
| // Skip any padding. |
| if (headerLength > numBytesRead) { |
| dataStream.skipBytes(headerLength - numBytesRead); |
| } |
| } |
| |
| public int GetEntryHeader() throws IOException { |
| // Read 2-byte thread-id. On host thread-ids can be greater than 16-bit. |
| int threadId = readNumber(2); |
| if (threadId != 0) { |
| return threadId; |
| } |
| // Read 1-byte header type |
| return readNumber(1); |
| } |
| |
| public void ProcessMethodInfoEntry() throws IOException { |
| // Read 2-byte method info size |
| int headerLength = readNumber(2); |
| // Read header size data. |
| String methodInfo = readString(headerLength); |
| String[] tokens = methodInfo.split("\t", 2); |
| // Get methodId and record methodId -> methodName map. |
| int methodId = Integer.decode(tokens[0]); |
| String methodLine = tokens[1].replace('\t', ' '); |
| methodLine = methodLine.substring(0, methodLine.length() - 1); |
| methodIdMap.put(methodId, methodLine); |
| } |
| |
| public void ProcessThreadInfoEntry() throws IOException { |
| // Read 2-byte thread id |
| int threadId = readNumber(2); |
| // Read 2-byte thread info size |
| int headerLength = readNumber(2); |
| // Read header size data. |
| String threadInfo = readString(headerLength); |
| threadIdMap.put(threadId, threadInfo); |
| } |
| |
| public boolean ShouldIgnoreThread(int threadId) throws Exception { |
| if (threadIdMap.get(threadId).contains("Daemon")) { |
| return true; |
| } |
| return false; |
| } |
| |
| public String eventTypeToString(int eventType, int threadId) { |
| if (!nestingLevelMap.containsKey(threadId)) { |
| nestingLevelMap.put(threadId, 0); |
| } |
| |
| int nestingLevel = nestingLevelMap.get(threadId); |
| String str = ""; |
| for (int i = 0; i < nestingLevel; i++) { |
| str += "."; |
| } |
| switch (eventType) { |
| case 0: |
| nestingLevel++; |
| str += ".>>"; |
| break; |
| case 1: |
| nestingLevel--; |
| str += "<<"; |
| break; |
| case 2: |
| nestingLevel--; |
| str += "<<E"; |
| break; |
| default: |
| str += "??"; |
| } |
| nestingLevelMap.put(threadId, nestingLevel); |
| return str; |
| } |
| |
| public String ProcessEventEntry(int threadId) throws IOException { |
| // Read 4-byte method value |
| int methodAndEvent = readNumber(4); |
| int methodId = methodAndEvent & ~0x3; |
| int eventType = methodAndEvent & 0x3; |
| |
| String str = eventTypeToString(eventType, threadId) + " " + threadIdMap.get(threadId) |
| + " " + methodIdMap.get(methodId); |
| // Depending on the version skip either one or two timestamps. |
| // TODO(mythria): Probably add a check that time stamps are always greater than initial |
| // timestamp. |
| int numBytesTimestamp = (traceFormatVersion == 2) ? 4 : 8; |
| dataStream.skipBytes(numBytesTimestamp); |
| return str; |
| } |
| |
| public void UpdateThreadEvents(int threadId, String entry) { |
| String threadName = threadIdMap.get(threadId); |
| if (!threadEventsMap.containsKey(threadName)) { |
| threadEventsMap.put(threadName, entry); |
| return; |
| } |
| threadEventsMap.put(threadName, threadEventsMap.get(threadName) + "\n" + entry); |
| } |
| |
| public abstract void CheckTraceFileFormat(File traceFile, int expectedVersion) |
| throws Exception; |
| |
| DataInputStream dataStream; |
| HashMap<Integer, String> methodIdMap; |
| HashMap<Integer, String> threadIdMap; |
| HashMap<Integer, Integer> nestingLevelMap; |
| HashMap<String, String> threadEventsMap; |
| int recordSize = 0; |
| int traceFormatVersion = 0; |
| } |