From 0d8c5cb7fad5f28435dbc8df6195f9c485cb1667 Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Fri, 3 Mar 2023 11:05:00 +0800 Subject: [PATCH 01/12] feat: hprof parser --- common/build.gradle | 2 + .../Entity/AndroidHprofMemoryInfo.java | 19 + .../performance/Entity/AndroidMemoryInfo.java | 3 + .../PerformanceTestManagementService.java | 11 +- .../performance/hprof/BitmapInfo.java | 25 + .../hprof/BitmapInfoExtractor.java | 19 + .../hydralab/performance/hprof/Extractor.java | 176 +++++++ .../performance/hprof/HeapProfProcessor.java | 449 ++++++++++++++++++ .../performance/hprof/ObjectInfo.java | 67 +++ .../hprof/TopObjectInfoExtractor.java | 41 ++ .../AndroidMemoryHprofInspector.java | 49 ++ .../AndroidMemoryInfoInspector.java | 3 + .../AndroidMemoryHprofResultParser.java | 70 +++ .../AndroidMemoryInfoResultParser.java | 3 + sdk/build.gradle | 1 + .../PerformanceInspectionResult.java | 5 +- .../performance/PerformanceResultParser.java | 5 +- 17 files changed, 943 insertions(+), 5 deletions(-) create mode 100644 common/src/main/java/com/microsoft/hydralab/performance/Entity/AndroidHprofMemoryInfo.java create mode 100644 common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfo.java create mode 100644 common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfoExtractor.java create mode 100644 common/src/main/java/com/microsoft/hydralab/performance/hprof/Extractor.java create mode 100644 common/src/main/java/com/microsoft/hydralab/performance/hprof/HeapProfProcessor.java create mode 100644 common/src/main/java/com/microsoft/hydralab/performance/hprof/ObjectInfo.java create mode 100644 common/src/main/java/com/microsoft/hydralab/performance/hprof/TopObjectInfoExtractor.java create mode 100644 common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java create mode 100644 common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java diff --git a/common/build.gradle b/common/build.gradle index 218af4917..c49e9dea6 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -73,6 +73,8 @@ dependencies { compile group: 'net.dongliu', name: 'apk-parser', version: '2.5.3' //Ipa Parse compile group: 'com.googlecode.plist', name: 'dd-plist', version: '1.3' + + compile 'com.squareup.haha:haha:2.1' } repositories { diff --git a/common/src/main/java/com/microsoft/hydralab/performance/Entity/AndroidHprofMemoryInfo.java b/common/src/main/java/com/microsoft/hydralab/performance/Entity/AndroidHprofMemoryInfo.java new file mode 100644 index 000000000..d00093876 --- /dev/null +++ b/common/src/main/java/com/microsoft/hydralab/performance/Entity/AndroidHprofMemoryInfo.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.performance.Entity; + +import com.microsoft.hydralab.performance.hprof.ObjectInfo; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +public class AndroidHprofMemoryInfo implements Serializable { + private List bitmapInfoList; + private List topObjectList; + private String appPackageName; + private long timeStamp; + private String description; +} diff --git a/common/src/main/java/com/microsoft/hydralab/performance/Entity/AndroidMemoryInfo.java b/common/src/main/java/com/microsoft/hydralab/performance/Entity/AndroidMemoryInfo.java index d8d250d52..064254a39 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/Entity/AndroidMemoryInfo.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/Entity/AndroidMemoryInfo.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + package com.microsoft.hydralab.performance.Entity; import lombok.Data; diff --git a/common/src/main/java/com/microsoft/hydralab/performance/PerformanceTestManagementService.java b/common/src/main/java/com/microsoft/hydralab/performance/PerformanceTestManagementService.java index ef6fd7fe3..9ea9d2473 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/PerformanceTestManagementService.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/PerformanceTestManagementService.java @@ -10,10 +10,12 @@ import com.microsoft.hydralab.common.util.FileUtil; import com.microsoft.hydralab.common.util.ThreadPoolUtil; import com.microsoft.hydralab.performance.inspectors.AndroidBatteryInfoInspector; +import com.microsoft.hydralab.performance.inspectors.AndroidMemoryHprofInspector; import com.microsoft.hydralab.performance.inspectors.AndroidMemoryInfoInspector; import com.microsoft.hydralab.performance.inspectors.WindowsBatteryInspector; import com.microsoft.hydralab.performance.inspectors.WindowsMemoryInspector; import com.microsoft.hydralab.performance.parsers.AndroidBatteryInfoResultParser; +import com.microsoft.hydralab.performance.parsers.AndroidMemoryHprofResultParser; import com.microsoft.hydralab.performance.parsers.AndroidMemoryInfoResultParser; import com.microsoft.hydralab.performance.parsers.WindowsBatteryResultParser; import com.microsoft.hydralab.performance.parsers.WindowsMemoryResultParser; @@ -34,19 +36,22 @@ public class PerformanceTestManagementService implements IPerformanceInspectionS INSPECTOR_ANDROID_BATTERY_INFO, PARSER_ANDROID_BATTERY_INFO, INSPECTOR_WIN_MEMORY, PARSER_WIN_MEMORY, INSPECTOR_WIN_BATTERY, PARSER_WIN_BATTERY, - INSPECTOR_ANDROID_MEMORY_INFO, PARSER_ANDROID_MEMORY_INFO + INSPECTOR_ANDROID_MEMORY_INFO, PARSER_ANDROID_MEMORY_INFO, + INSPECTOR_ANDROID_MEMORY_DUMP, PARSER_ANDROID_MEMORY_DUMP ); private final Map performanceInspectorMap = Map.of( INSPECTOR_ANDROID_BATTERY_INFO, new AndroidBatteryInfoInspector(), INSPECTOR_WIN_MEMORY, new WindowsMemoryInspector(), INSPECTOR_WIN_BATTERY, new WindowsBatteryInspector(), - INSPECTOR_ANDROID_MEMORY_INFO, new AndroidMemoryInfoInspector() + INSPECTOR_ANDROID_MEMORY_INFO, new AndroidMemoryInfoInspector(), + INSPECTOR_ANDROID_MEMORY_DUMP, new AndroidMemoryHprofInspector() ); private final Map performanceResultParserMap = Map.of( PARSER_ANDROID_BATTERY_INFO, new AndroidBatteryInfoResultParser(), PARSER_WIN_MEMORY, new WindowsMemoryResultParser(), PARSER_WIN_BATTERY, new WindowsBatteryResultParser(), - PARSER_ANDROID_MEMORY_INFO, new AndroidMemoryInfoResultParser() + PARSER_ANDROID_MEMORY_INFO, new AndroidMemoryInfoResultParser(), + PARSER_ANDROID_MEMORY_DUMP, new AndroidMemoryHprofResultParser() ); private final Map>> inspectPerformanceTimerMap = new ConcurrentHashMap<>(); diff --git a/common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfo.java b/common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfo.java new file mode 100644 index 000000000..62f61fbb3 --- /dev/null +++ b/common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfo.java @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.performance.hprof; + +public class BitmapInfo extends ObjectInfo implements Serializable { + + public int width; + public int height; + public int density; + public boolean recycled; + public int pixelsCount; + public long nativePtr; + public float perPixelSize; + + + public void computePerPixelSize() { + perPixelSize = nativeSize * 1f / height / width; + } + + @Override + public String getSizeInfo() { + return super.getSizeInfo() + ",  BitmapSize: " + width + " × " + height; + } +} diff --git a/common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfoExtractor.java b/common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfoExtractor.java new file mode 100644 index 000000000..9cd6a2e2d --- /dev/null +++ b/common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfoExtractor.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.performance.hprof; + +import com.squareup.haha.perflib.Instance; + +public class BitmapInfoExtractor extends Extractor { + + @Override + public ObjectInfo extractInstanceInfo(int retainedSizeRanking, Instance instance) { + return extractBitmapInfo(retainedSizeRanking, instance); + } + + @Override + public String getType() { + return "bitmap"; + } +} diff --git a/common/src/main/java/com/microsoft/hydralab/performance/hprof/Extractor.java b/common/src/main/java/com/microsoft/hydralab/performance/hprof/Extractor.java new file mode 100644 index 000000000..c9570de39 --- /dev/null +++ b/common/src/main/java/com/microsoft/hydralab/performance/hprof/Extractor.java @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.performance.hprof; + +import com.squareup.haha.perflib.ClassInstance; +import com.squareup.haha.perflib.ClassObj; +import com.squareup.haha.perflib.Field; +import com.squareup.haha.perflib.Instance; +import com.squareup.haha.perflib.Type; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public abstract class Extractor { + /** + * min size in byte + */ + private int minNativeSize = 1000; + + protected List resultList = new ArrayList<>(); + + public void setMinNativeSize(int minNativeSize) { + this.minNativeSize = minNativeSize; + } + + public String getName() { + return this.getClass().getSimpleName(); + } + + void onExtractInfo(int retainedSizeRanking, Instance instance) { + ObjectInfo objectInfo = extractInstanceInfo(retainedSizeRanking, instance); + if (objectInfo == null) { + return; + } + resultList.add(objectInfo); + } + + abstract ObjectInfo extractInstanceInfo(int retainedSizeRanking, Instance instance); + + public List getResultList() { + return resultList; + } + + protected void mapSetBaseAttr(Instance instance, ObjectInfo bitmapInfo) { + List founds = new ArrayList<>(); + ClassObj classObj = instance.getClassObj(); + String className = classObj.getClassName(); + String[] names = new String[1]; + findRelatedInstances(instance, null, founds, names); + bitmapInfo.instance = instance; + bitmapInfo.firstLevelLauncherRef = founds.size() > 0 ? founds.get(0) : null; + bitmapInfo.className = className; + if (bitmapInfo.firstLevelLauncherRef instanceof ClassObj) { + bitmapInfo.isStaticMember = true; + } + bitmapInfo.fieldName = names[0]; + bitmapInfo.nativeSize = instance.getNativeSize(); + bitmapInfo.distanceToRoot = instance.getDistanceToGcRoot(); + bitmapInfo.retainedSize = instance.getTotalRetainedSize(); + bitmapInfo.size = instance.getSize(); + bitmapInfo.id = instance.getId(); + bitmapInfo.uniqueId = instance.getUniqueId(); + } + + protected void findRelatedInstances(Instance instance, Instance visited, List founds, String[] names) { + if (instance == null) { + return; + } + ClassObj classObj = instance.getClassObj(); + if (classObj == null) { + if (!(instance instanceof ClassObj)) { + return; + } + classObj = (ClassObj) instance; + } + String className = classObj.getClassName(); + if (className.contains(".launcher")) { + founds.add(instance); + if (instance instanceof ClassInstance) { + ClassInstance classInstance = (ClassInstance) instance; + List values = classInstance.getValues(); + for (ClassInstance.FieldValue value : values) { + if (Objects.equals(value.getField().getType(), Type.OBJECT)) { + if (Objects.equals(value.getValue(), visited)) { + if (visited != null) { + founds.add(visited); + names[0] = value.getField().getName(); + } + } + } + } + // static case + } else if (instance instanceof ClassObj) { + if (instance.getNextInstanceToGcRoot() == null) { + founds.add(instance); + Map staticFieldValues = ((ClassObj) instance).getStaticFieldValues(); + for (Map.Entry fieldObjectEntry : staticFieldValues.entrySet()) { + Field key = fieldObjectEntry.getKey(); + if (Objects.equals(key.getType(), Type.OBJECT)) { + if (Objects.equals(staticFieldValues.get(key), visited)) { + if (visited != null) { + founds.add(visited); + names[0] = key.getName(); + } + } + } + } + } + } + return; + } + findRelatedInstances(instance.getNextInstanceToGcRoot(), instance, founds, names); + } + + public ObjectInfo extractBitmapInfo(int retainedSizeRanking, Instance instance) { + ClassObj classObj = instance.getClassObj(); + String className = classObj.getClassName(); + if (className.equals("android.graphics.Bitmap")) { + if (instance instanceof ClassInstance) { + ClassInstance classInstance = (ClassInstance) instance; + List values = classInstance.getValues(); + BitmapInfo bitmapInfo = new BitmapInfo(); + if (instance.getNativeSize() < minNativeSize) { + return null; + } + mapSetBaseAttr(instance, bitmapInfo); + for (ClassInstance.FieldValue value : values) { + Field field = value.getField(); + String name = field.getName(); + switch (name) { + case "mWidth": + bitmapInfo.width = (int) value.getValue(); + break; + case "mHeight": + bitmapInfo.height = (int) value.getValue(); + break; + case "mDensity": + bitmapInfo.density = (int) value.getValue(); + break; + case "mNativePtr": + bitmapInfo.nativePtr = (long) value.getValue(); + break; + case "mRecycled": + bitmapInfo.recycled = (boolean) value.getValue(); + break; + } + } + bitmapInfo.computePerPixelSize(); + return bitmapInfo; + + } + } + return null; + } + + public abstract String getType(); + + public void onExtractComplete() { + resultList.sort((o1, o2) -> Long.compare(o2.retainedSize, o1.retainedSize)); + Iterator iterator = resultList.iterator(); + int i = 0; + while (iterator.hasNext()) { + ObjectInfo next = iterator.next(); + if (next.distanceToRoot == 0 || next.getFieldChainString() == null) { + iterator.remove(); + continue; + } + next.index = i + 1; + i++; + } + } +} diff --git a/common/src/main/java/com/microsoft/hydralab/performance/hprof/HeapProfProcessor.java b/common/src/main/java/com/microsoft/hydralab/performance/hprof/HeapProfProcessor.java new file mode 100644 index 000000000..50c93da38 --- /dev/null +++ b/common/src/main/java/com/microsoft/hydralab/performance/hprof/HeapProfProcessor.java @@ -0,0 +1,449 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.performance.hprof; + +import com.android.tools.perflib.captures.DataBuffer; +import com.android.tools.perflib.captures.MemoryMappedFileBuffer; +import com.squareup.haha.perflib.*; +import gnu.trove.THashMap; +import gnu.trove.TObjectProcedure; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import static com.microsoft.hydralab.common.util.FileUtil.getSizeStringWithTagIfLarge; + +public class HeapProfProcessor { + public static final int MAX_FIELD_CHAIN_DEPTH = 10; + //The max length that channel can display + public static final int HTML_MAX_LENGTH = 18000; + + private final File heapDumpFile; + private static Logger logger = LoggerFactory.getLogger(HeapProfProcessor.class.getSimpleName()); + private HashMap extractorMap = new HashMap<>(); + + public HeapProfProcessor(File heapFile) { + heapDumpFile = heapFile; + } + + private static String generateRootKey(RootObj root) { + return String.format("%s@0x%08x", root.getRootType().getName(), root.getId()); + } + + /** + * TODO + * + * @param instance + * @return + */ + public static String getObjectRefTrace(Instance instance) { + return "todo"; + } + + public void registerExtractor(Extractor extractor) { + extractorMap.put(extractor.getName(), extractor); + } + + + public void loadAndExtract() throws IOException { + if (!heapDumpFile.exists()) { + return; + } + + DataBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile); + + Snapshot snapshot = Snapshot.createSnapshot(buffer); + logger.info("start createSnapshot: " + heapDumpFile.getName()); + + deduplicateGcRoots(snapshot); + logger.info("deduplicateGcRoots: " + heapDumpFile.getName()); + + snapshot.computeDominators(); + logger.info("compute dominators finished: " + heapDumpFile.getName()); + + List dominatorList = snapshot.getReachableInstances(); + if (dominatorList.isEmpty()) { + logger.warn("no gReachable Instances: in file: {}", heapDumpFile.getName()); + return; + } + dominatorList.sort((instance1, instance2) -> -Long.compare(instance1.getTotalRetainedSize(), instance2.getTotalRetainedSize())); + logger.info("sortByRetainedSize finish start listing dominatorList: {}, in file: {}", dominatorList.size(), heapDumpFile.getName()); + + Iterator instanceIterator = dominatorList.iterator(); + + int index = 0; + while (instanceIterator.hasNext()) { + Instance instance = instanceIterator.next(); + if (instance.getClassObj() == null) { + continue; + } + processInstance(index++, instance); + } + + // sort all extracted object info by retained size + for (Map.Entry entry : extractorMap.entrySet()) { + entry.getValue().onExtractComplete(); + } + } + + /** + * Pruning duplicates reduces memory pressure from hprof bloat added in Marshmallow. + */ + private void deduplicateGcRoots(Snapshot snapshot) { + // THashMap has a smaller memory footprint than HashMap. + final THashMap uniqueRootMap = new THashMap<>(); + + final Collection gcRoots = snapshot.getGCRoots(); + for (RootObj root : gcRoots) { + String key = generateRootKey(root); + if (!uniqueRootMap.containsKey(key)) { + uniqueRootMap.put(key, root); + } + } + + // Repopulate snapshot with unique GC roots. + gcRoots.clear(); + uniqueRootMap.forEach(new TObjectProcedure() { + @Override + public boolean execute(String key) { + return gcRoots.add(uniqueRootMap.get(key)); + } + }); + } + +// public File generateReport(File csvFile) { +// if (extractorResultsMap == null || extractorResultsMap.isEmpty()) { +// return null; +// } +// try { +// CsvWriter csvWriter = new CsvWriter(csvFile.getAbsolutePath(), ',', Charset.forName("utf-8")); +// +// for (Map.Entry> entry : extractorResultsMap.entrySet()) { +// String key = entry.getKey(); +// List value = entry.getValue(); +// if (value == null) { +// continue; +// } +// if (value.isEmpty()) { +// continue; +// } +// csvWriter.writeRecord(new String[]{key}); +// ObjectInfo objectInfo = value.get(0); +// csvWriter.writeRecord(("index," + objectInfo.getColumnsTitle()).split(",")); +// int i = 1; +// for (ObjectInfo info : value) { +// csvWriter.writeRecord((i++ + "," + info.toDataString()).split(",")); +// } +// } +// csvWriter.close(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// return csvFile; +// } + +// public File generateMarkdownReport(File mdFile, int maxRowCount) { +// if (extractorResultsMap == null || extractorResultsMap.isEmpty()) { +// return null; +// } +// StringBuilder builder = new StringBuilder(); +// String endLine = "\n\n"; +// FileWriter fileWriter = null; +// try { +// fileWriter = new FileWriter(mdFile); +// for (Map.Entry> entry : extractorResultsMap.entrySet()) { +// String title = entry.getKey(); +// if (extractorTitleMap.containsKey(entry.getKey())) { +// title = extractorTitleMap.get(entry.getKey()); +// } +// builder.append(endLine).append("# ").append(title).append(endLine); +// builder.append(getHtmlReport(entry.getKey(), maxRowCount, false)); +// } +// fileWriter.write(builder.toString()); +// } catch (IOException e) { +// e.printStackTrace(); +// } finally { +// try { +// if (fileWriter != null) { +// fileWriter.flush(); +// fileWriter.close(); +// } +// } catch (IOException ex) { +// ex.printStackTrace(); +// } +// } +// +// return mdFile; +// } + +// public String getHtmlReport(String extractorName, int maxRowCount, boolean isLimit) { +// if (extractorResultsMap == null || extractorResultsMap.isEmpty()) { +// return null; +// } +// if (extractorName == null) { +// return null; +// } +// StringBuilder stringBuilder = new StringBuilder(); +// String endLine = "
"; +// List value = extractorResultsMap.get(extractorName); +// if (value == null || value.isEmpty()) { +// return null; +// } +// int i = 0; +// for (ObjectInfo info : value) { +// if (i >= maxRowCount) { +// break; +// } +// StringBuilder nextLineBuilder = new StringBuilder(); +// nextLineBuilder.append("
  • ").append("").append("# ").append(i + 1).append(": ").append("").append(endLine).append(info.toHtmlDataStringOCV()).append("
  • "); +// if (isLimit && (nextLineBuilder.length() + stringBuilder.length() > HTML_MAX_LENGTH)) { +// break; +// } +// stringBuilder.append(nextLineBuilder); +// i++; +// } +// stringBuilder.append(""); +// return stringBuilder.toString(); +// } + +// public String getHtmlReport(String extractorName, int maxRowCount) { +// return getHtmlReport(extractorName, maxRowCount, true); +// } + + private void processInstance(int index, Instance instance) { + for (Map.Entry entry : extractorMap.entrySet()) { + entry.getValue().onExtractInfo(index, instance); + } + } + + private static void findRelatedInstances(Instance instance, List founds, Instance visited, List fieldNames) { + if (instance == null) { + return; + } + if (visited != null) { + if (instance instanceof ClassInstance) { + ClassInstance classInstance = (ClassInstance) instance; + List values = classInstance.getValues(); + for (ClassInstance.FieldValue value : values) { + if (Objects.equals(value.getField().getType(), Type.OBJECT)) { + if (Objects.equals(value.getValue(), visited)) { + founds.add(instance); + fieldNames.add(value.getField().getName()); + break; + } + } + } + } else if (instance instanceof ClassObj) { + Map staticFieldValues = ((ClassObj) instance).getStaticFieldValues(); + for (Map.Entry fieldObjectEntry : staticFieldValues.entrySet()) { + Field key = fieldObjectEntry.getKey(); + if (Objects.equals(key.getType(), Type.OBJECT)) { + if (Objects.equals(staticFieldValues.get(key), visited)) { + founds.add(instance); + fieldNames.add(key.getName()); + break; + } + } + } + } else if (instance instanceof ArrayInstance) { + final ArrayInstance arrayInstance = (ArrayInstance) instance; + int i = 0; + for (Object object : arrayInstance.getValues()) { + if (Objects.equals(object, visited)) { + founds.add(instance); + fieldNames.add(i + ""); + break; + } + i++; + } + } + } + findRelatedInstances(instance.getNextInstanceToGcRoot(), founds, instance, fieldNames); + } + + private static List getPathToGCRoot(Instance instance) { + List gcRoots = new ArrayList<>(); + List founds = new ArrayList<>(); + List fieldNames = new ArrayList<>(); + findRelatedInstances(instance, founds, null, fieldNames); + for (int i = founds.size() - 1; i >= 0; i--) { + Instance foundInstance = founds.get(i); + String fieldName = fieldNames.get(i); + ClassObj classObj = foundInstance.getClassObj(); + if (classObj == null) { + if (!(foundInstance instanceof ClassObj)) { + continue; + } + classObj = (ClassObj) foundInstance; + } + String message = ""; + message += String.format("%s %s.%s", + getSizeStringWithTagIfLarge(foundInstance.getTotalRetainedSize(), ObjectInfo.SIZE_THRESHOLD), + classObj.getClassName(), fieldName); + gcRoots.add(new FieldChain(message, foundInstance)); + + } + return gcRoots; + } + + public static String generateFieldChainString(Instance instance, String lineEnding) { + if (instance == null) { + return ""; + } + + StringBuilder builder = new StringBuilder(); + List gcRoots = getPathToGCRoot(instance); + for (FieldChain fieldChain : gcRoots) { + String className; + if (fieldChain.instance.getClassObj() == null) { + className = ((ClassObj) fieldChain.instance).getClassName(); + } else { + className = fieldChain.instance.getClassObj().getClassName(); + } + if (className != null && (className.startsWith("android.graphics.Bitmap") || className.startsWith("java.util.HashMap$Node"))) { + continue; + } + if (className != null && className.contains(".launcher")) { + builder.append("").append("↱ ").append(fieldChain.message).append("").append(lineEnding); + } else { + builder.append("↱ ").append(fieldChain.message).append(lineEnding); + } + } + + + if (instance.getClassObj() != null) { + builder.append(instance.getClassObj().getClassName()).append(" "); + } + + List fieldChains = generateDominatedFieldChain(instance, 0, new HashSet()); + if (!fieldChains.isEmpty()) { + builder.append(lineEnding); + + for (FieldChain fieldChain : fieldChains) { + String className = fieldChain.instance.getClassObj().getClassName(); + if (className != null && (className.startsWith("android.graphics.Bitmap") || className.startsWith("java.util.HashMap$Node"))) { + continue; + } + if (className != null && className.contains(".launcher")) { + builder.append("").append("↳ ").append(fieldChain.message).append("").append(lineEnding); + } else { + builder.append("↳ ").append(fieldChain.message).append(lineEnding); + } + } + } + builder.append(lineEnding); + return builder.toString(); + } + + private static List generateDominatedFieldChain(Instance instance, int indent, Set chainInstanceSet) { + final List fieldChains = new ArrayList<>(); + if (indent > MAX_FIELD_CHAIN_DEPTH) { + // Limit recursion depth + return fieldChains; + } + + if (instance instanceof ArrayInstance) { + Instance maxRetainedSize = null; + int maxIndex = -1; + final ArrayInstance arrayInstance = (ArrayInstance) instance; + int i = 0; + for (Object object : arrayInstance.getValues()) { + if (object == null) { + continue; + } + + if (object instanceof Instance) { + Instance instance1 = (Instance) object; + + if (chainInstanceSet.contains(instance1.getId())) { + continue; + } + + if (maxRetainedSize == null + || instance1.getTotalRetainedSize() > maxRetainedSize.getTotalRetainedSize()) { + maxRetainedSize = instance1; + maxIndex = i; + } + } + ++i; + } + + if (maxRetainedSize != null) { + String message = ""; + message += String.format("%s %s%d", + getSizeStringWithTagIfLarge(instance.getTotalRetainedSize(), ObjectInfo.SIZE_THRESHOLD), + instance.getClassObj().getClassName(), + maxIndex); + logger.debug(message); + fieldChains.add(new FieldChain(message, instance)); + chainInstanceSet.add(maxRetainedSize.getId()); + fieldChains.addAll(generateDominatedFieldChain(maxRetainedSize, indent + 1, chainInstanceSet)); + } + } else if (instance instanceof ClassInstance) { + ClassInstance classInstance = (ClassInstance) instance; + List fieldValues = classInstance.getValues(); + Instance maxRetainedSize = null; + ClassInstance.FieldValue maxFieldValue = null; + for (ClassInstance.FieldValue fieldValue : fieldValues) { + Object value = fieldValue.getValue(); + if (value == null) { + continue; + } + + if (value instanceof Instance) { + Instance instance1 = (Instance) value; + + if (chainInstanceSet.contains(instance1.getId())) { + continue; + } + + if (maxRetainedSize == null + || instance1.getTotalRetainedSize() > maxRetainedSize.getTotalRetainedSize()) { + maxRetainedSize = instance1; + maxFieldValue = fieldValue; + } + } else { + logger.debug("ignore unknown field value type " + value.getClass().getName()); + } + } + + if (maxRetainedSize != null) { + String message = ""; + message += String.format("%s %s.%s", + getSizeStringWithTagIfLarge(instance.getTotalRetainedSize(), ObjectInfo.SIZE_THRESHOLD), + instance.getClassObj().getClassName(), + maxFieldValue.getField().getName()); + logger.debug(message); + fieldChains.add(new FieldChain(message, instance)); + chainInstanceSet.add(maxRetainedSize.getId()); + fieldChains.addAll(generateDominatedFieldChain(maxRetainedSize, indent + 1, chainInstanceSet)); + } + } else { + logger.debug("ignore unknown instance type " + instance.getClass().getName()); + } + + return fieldChains; + } + + private static class FieldChain { + public final String message; + public final Instance instance; + + FieldChain(String message, Instance instance) { + this.message = message; + this.instance = instance; + } + } +} diff --git a/common/src/main/java/com/microsoft/hydralab/performance/hprof/ObjectInfo.java b/common/src/main/java/com/microsoft/hydralab/performance/hprof/ObjectInfo.java new file mode 100644 index 000000000..296993b59 --- /dev/null +++ b/common/src/main/java/com/microsoft/hydralab/performance/hprof/ObjectInfo.java @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.performance.hprof; + +import com.squareup.haha.perflib.ClassObj; +import com.squareup.haha.perflib.Instance; + +import java.io.Serializable; + +import static com.microsoft.hydralab.common.util.FileUtil.getSizeStringWithTagIfLarge; + +public class ObjectInfo implements Serializable { + private static final int SIZE_THRESHOLD = 500 * 1024; + + public int index; + public int distanceToRoot; + public String fieldName; + public Instance instance; + public Instance firstLevelLauncherRef; + public long nativeSize; + public long retainedSize; + public long size; + public boolean isStaticMember; + public long id; + public long uniqueId; + public String className; + + public String getFieldChain(String lineEnding) { + if (className != null && className.startsWith("dalvik.system.PathClassLoader")) { + return null; + } else { + return HeapProfProcessor.generateFieldChainString(instance, lineEnding); + } + } + + protected String getRefClassName() { + if (firstLevelLauncherRef == null) { + return "null"; + } + ClassObj classObj = firstLevelLauncherRef.getClassObj(); + if (classObj == null) { + if (firstLevelLauncherRef instanceof ClassObj) { + return ((ClassObj) firstLevelLauncherRef).getClassName(); + } + return "null"; + } + return classObj.getClassName(); + } + + protected String getSizeInfo() { + String retainedSizeString = "RetainedSize: " + getSizeStringWithTagIfLarge(retainedSize, SIZE_THRESHOLD); + String nativeSizeString = ""; + if (nativeSize != 0) { + nativeSizeString = ",  NativeSize: " + getSizeStringWithTagIfLarge(nativeSize, SIZE_THRESHOLD); + } + return retainedSizeString + nativeSizeString; + } + + public String getFieldChainString() { + String fieldChain = getFieldChain("
    "); + if (fieldChain == null) { + return null; + } + return "" + fieldChain + ""; + } +} diff --git a/common/src/main/java/com/microsoft/hydralab/performance/hprof/TopObjectInfoExtractor.java b/common/src/main/java/com/microsoft/hydralab/performance/hprof/TopObjectInfoExtractor.java new file mode 100644 index 000000000..ca247681b --- /dev/null +++ b/common/src/main/java/com/microsoft/hydralab/performance/hprof/TopObjectInfoExtractor.java @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.performance.hprof; + +import com.squareup.haha.perflib.ClassInstance; +import com.squareup.haha.perflib.ClassObj; +import com.squareup.haha.perflib.Instance; + +public class TopObjectInfoExtractor extends Extractor { + private int count; + + public TopObjectInfoExtractor(int count) { + this.count = count; + } + + @Override + public ObjectInfo extractInstanceInfo(int retainedSizeRanking, Instance instance) { + if (retainedSizeRanking > count) { + return null; + } + + ClassObj classObj = instance.getClassObj(); + String className = classObj.getClassName(); + if (className.equals("android.graphics.Bitmap")) { + return extractBitmapInfo(retainedSizeRanking, instance); + } + + if (instance instanceof ClassInstance) { + ObjectInfo objectInfo = new ObjectInfo(); + mapSetBaseAttr(instance, objectInfo); + return objectInfo; + } + return null; + } + + @Override + public String getType() { + return "top" + count; + } +} diff --git a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java new file mode 100644 index 000000000..1430f3384 --- /dev/null +++ b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.performance.inspectors; + +import com.microsoft.hydralab.common.util.ShellUtils; +import com.microsoft.hydralab.common.util.TimeUtils; +import com.microsoft.hydralab.performance.PerformanceInspection; +import com.microsoft.hydralab.performance.PerformanceInspectionResult; +import com.microsoft.hydralab.performance.PerformanceInspector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +import java.io.File; + +public class AndroidMemoryHprofInspector implements PerformanceInspector { + + private static final String RAW_RESULT_FILE_NAME_FORMAT = "%s_%s_%s_memory.hprof"; + private static final String HPROF_FILE_PREFIX = "/data/local/tmp/"; + private final Logger classLogger = LoggerFactory.getLogger(getClass()); + + + @Override + public PerformanceInspectionResult inspect(PerformanceInspection performanceInspection) { + + File rawResultFolder = new File(performanceInspection.resultFolder, performanceInspection.appId); + Assert.isTrue(rawResultFolder.exists() || rawResultFolder.mkdir(), "rawResultFolder.mkdirs() failed in" + rawResultFolder.getAbsolutePath()); + String tmpTime = TimeUtils.getTimestampForFilename(); + String hprofFileName = String.format(RAW_RESULT_FILE_NAME_FORMAT, getClass().getSimpleName(), performanceInspection.appId, tmpTime); + File rawResultFile = new File(rawResultFolder, + hprofFileName); + String sdHprofFilePath = HPROF_FILE_PREFIX + hprofFileName; + String dumpCommand = String.format(getMemHprofCommand(), performanceInspection.deviceIdentifier, performanceInspection.appId, sdHprofFilePath); + + Process process = ShellUtils.execLocalCommand(dumpCommand, classLogger); + if (process != null && process.exitValue() == 0) { + Process pullProcess = ShellUtils.execLocalCommand("adb pull " + sdHprofFilePath + " " + rawResultFile.getAbsolutePath(), classLogger); + if (pullProcess != null && pullProcess.exitValue() == 0) { + return new PerformanceInspectionResult(rawResultFile, performanceInspection); + } + } + return new PerformanceInspectionResult(null, performanceInspection); + } + + private String getMemHprofCommand() { + return "adb -s %s shell am dumpheap %s %s"; + } +} diff --git a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryInfoInspector.java b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryInfoInspector.java index ec02d0990..4baec77b7 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryInfoInspector.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryInfoInspector.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + package com.microsoft.hydralab.performance.inspectors; import com.microsoft.hydralab.common.util.ShellUtils; diff --git a/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java b/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java new file mode 100644 index 000000000..3b337a9b3 --- /dev/null +++ b/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.performance.parsers; + +import com.microsoft.hydralab.performance.Entity.AndroidHprofMemoryInfo; +import com.microsoft.hydralab.performance.Entity.AndroidMemoryInfo; +import com.microsoft.hydralab.performance.PerformanceInspectionResult; +import com.microsoft.hydralab.performance.PerformanceResultParser; +import com.microsoft.hydralab.performance.PerformanceTestResult; +import com.microsoft.hydralab.performance.hprof.BitmapInfoExtractor; +import com.microsoft.hydralab.performance.hprof.HeapProfProcessor; +import com.microsoft.hydralab.performance.hprof.TopObjectInfoExtractor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class AndroidMemoryHprofResultParser implements PerformanceResultParser { + private final Logger logger = LoggerFactory.getLogger(getClass()); + private static final int MD_OBJECT_REPORT_COUNT = 50; + + @Override + public PerformanceTestResult parse(PerformanceTestResult performanceTestResult) { + if (performanceTestResult == null || performanceTestResult.performanceInspectionResults == null + || performanceTestResult.performanceInspectionResults.isEmpty()) { + return null; + } + List inspectionResults = performanceTestResult.performanceInspectionResults; + for (PerformanceInspectionResult inspectionResult : inspectionResults) { + File hprofFile = inspectionResult.rawResultFile; + if (hprofFile == null) { + return performanceTestResult; + } + inspectionResult.parsedData = buildAndroidHprofMemoryInfo(hprofFile, inspectionResult.inspection.appId, inspectionResult.inspection.description, + inspectionResult.timestamp); + } + + return performanceTestResult; + } + + + + private AndroidHprofMemoryInfo buildAndroidHprofMemoryInfo(File dumpFile, String packageName, String description, long timeStamp) { + + HeapProfProcessor profProcessor = new HeapProfProcessor(dumpFile); + + BitmapInfoExtractor bitmapInfoExtractor = new BitmapInfoExtractor(); + profProcessor.registerExtractor(bitmapInfoExtractor); + + TopObjectInfoExtractor topObjectInfoExtractor = new TopObjectInfoExtractor(MD_OBJECT_REPORT_COUNT); + profProcessor.registerExtractor(topObjectInfoExtractor); + try { + profProcessor.loadAndExtract(); + } catch (IOException e) { + logger.error("parseHprofFile", e); + return null; + } + AndroidHprofMemoryInfo info = new AndroidHprofMemoryInfo(); + info.setAppPackageName(packageName); + info.setDescription(description); + info.setTimeStamp(timeStamp); + info.setBitmapInfoList(bitmapInfoExtractor.getResultList()); + info.setTopObjectList(topObjectInfoExtractor.getResultList()); + return info; + + } +} diff --git a/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryInfoResultParser.java b/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryInfoResultParser.java index 048a39bf2..1afa37f61 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryInfoResultParser.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryInfoResultParser.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + package com.microsoft.hydralab.performance.parsers; import com.google.common.base.Strings; diff --git a/sdk/build.gradle b/sdk/build.gradle index 73a3f4fe0..b2a5c0e00 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -11,6 +11,7 @@ repositories { } dependencies { + implementation 'org.jetbrains:annotations:21.0.1' compile 'ch.qos.logback:logback-classic:1.2.3' compile 'org.slf4j:slf4j-api:1.7.30' } diff --git a/sdk/src/main/java/com/microsoft/hydralab/performance/PerformanceInspectionResult.java b/sdk/src/main/java/com/microsoft/hydralab/performance/PerformanceInspectionResult.java index 671d3e00e..6788e02c6 100644 --- a/sdk/src/main/java/com/microsoft/hydralab/performance/PerformanceInspectionResult.java +++ b/sdk/src/main/java/com/microsoft/hydralab/performance/PerformanceInspectionResult.java @@ -3,6 +3,8 @@ package com.microsoft.hydralab.performance; +import org.jetbrains.annotations.Nullable; + import java.io.File; public class PerformanceInspectionResult { @@ -12,12 +14,13 @@ public class PerformanceInspectionResult { @SuppressWarnings("visibilitymodifier") public PerformanceInspection inspection; @SuppressWarnings("visibilitymodifier") + @Nullable public File rawResultFile; @SuppressWarnings("visibilitymodifier") // TODO: restrict the size of it. public Object parsedData; - public PerformanceInspectionResult(File rawResultFile, PerformanceInspection inspection) { + public PerformanceInspectionResult(@Nullable File rawResultFile, PerformanceInspection inspection) { this.timestamp = System.currentTimeMillis(); this.rawResultFile = rawResultFile; this.inspection = inspection; diff --git a/sdk/src/main/java/com/microsoft/hydralab/performance/PerformanceResultParser.java b/sdk/src/main/java/com/microsoft/hydralab/performance/PerformanceResultParser.java index c8c4c1038..d8f5de411 100644 --- a/sdk/src/main/java/com/microsoft/hydralab/performance/PerformanceResultParser.java +++ b/sdk/src/main/java/com/microsoft/hydralab/performance/PerformanceResultParser.java @@ -3,8 +3,11 @@ package com.microsoft.hydralab.performance; +import org.jetbrains.annotations.Nullable; + public interface PerformanceResultParser { - PerformanceTestResult parse(PerformanceTestResult performanceTestResult); + @Nullable + PerformanceTestResult parse(@Nullable PerformanceTestResult performanceTestResult); enum PerformanceResultParserType { PARSER_ANDROID_MEMORY_DUMP, From c66a1a65462e307c417d7db89fcac1f26ecaf1ea Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Fri, 3 Mar 2023 11:18:29 +0800 Subject: [PATCH 02/12] fix: fix build: --- .../com/microsoft/hydralab/performance/hprof/BitmapInfo.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfo.java b/common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfo.java index 62f61fbb3..df3cb83cd 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfo.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/hprof/BitmapInfo.java @@ -3,6 +3,8 @@ package com.microsoft.hydralab.performance.hprof; +import java.io.Serializable; + public class BitmapInfo extends ObjectInfo implements Serializable { public int width; From 585ee9b14f2e5882ca187d871117b044700425ff Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Fri, 3 Mar 2023 11:25:44 +0800 Subject: [PATCH 03/12] fix: fix build break --- .../com/microsoft/hydralab/performance/hprof/ObjectInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/com/microsoft/hydralab/performance/hprof/ObjectInfo.java b/common/src/main/java/com/microsoft/hydralab/performance/hprof/ObjectInfo.java index 296993b59..fe6bfe974 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/hprof/ObjectInfo.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/hprof/ObjectInfo.java @@ -11,7 +11,7 @@ import static com.microsoft.hydralab.common.util.FileUtil.getSizeStringWithTagIfLarge; public class ObjectInfo implements Serializable { - private static final int SIZE_THRESHOLD = 500 * 1024; + public static final int SIZE_THRESHOLD = 500 * 1024; public int index; public int distanceToRoot; From b867be25ec139e62998911e3b86fe4e7982df507 Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Fri, 3 Mar 2023 16:10:21 +0800 Subject: [PATCH 04/12] feat: check dumpheap return value --- .../AndroidMemoryHprofInspector.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java index 1430f3384..6aefee13b 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java @@ -33,12 +33,24 @@ public PerformanceInspectionResult inspect(PerformanceInspection performanceInsp String sdHprofFilePath = HPROF_FILE_PREFIX + hprofFileName; String dumpCommand = String.format(getMemHprofCommand(), performanceInspection.deviceIdentifier, performanceInspection.appId, sdHprofFilePath); - Process process = ShellUtils.execLocalCommand(dumpCommand, classLogger); - if (process != null && process.exitValue() == 0) { - Process pullProcess = ShellUtils.execLocalCommand("adb pull " + sdHprofFilePath + " " + rawResultFile.getAbsolutePath(), classLogger); - if (pullProcess != null && pullProcess.exitValue() == 0) { - return new PerformanceInspectionResult(rawResultFile, performanceInspection); + Process process = ShellUtils.execLocalCommand(dumpCommand, false, classLogger); + if (process != null) { + try { + int ret = process.waitFor(); + if (ret == 0) { + Process pullProcess = ShellUtils.execLocalCommand("adb pull " + sdHprofFilePath + " " + rawResultFile.getAbsolutePath(), false, classLogger); + if (pullProcess != null) { + ret = pullProcess.waitFor(); + if (ret == 0) { + return new PerformanceInspectionResult(rawResultFile, performanceInspection); + } + } + } + } catch (InterruptedException e) { + classLogger.error(e.getMessage(), e); + return new PerformanceInspectionResult(null, performanceInspection); } + } return new PerformanceInspectionResult(null, performanceInspection); } From a32809690c06265b92bda5eb223eaf8c7f8ac8bb Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Wed, 10 May 2023 16:26:15 +0800 Subject: [PATCH 05/12] feat: check only if app is debuggable --- .../AndroidMemoryHprofInspector.java | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java index 6aefee13b..183ddcc77 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java @@ -32,24 +32,28 @@ public PerformanceInspectionResult inspect(PerformanceInspection performanceInsp hprofFileName); String sdHprofFilePath = HPROF_FILE_PREFIX + hprofFileName; String dumpCommand = String.format(getMemHprofCommand(), performanceInspection.deviceIdentifier, performanceInspection.appId, sdHprofFilePath); - - Process process = ShellUtils.execLocalCommand(dumpCommand, false, classLogger); - if (process != null) { - try { - int ret = process.waitFor(); - if (ret == 0) { - Process pullProcess = ShellUtils.execLocalCommand("adb pull " + sdHprofFilePath + " " + rawResultFile.getAbsolutePath(), false, classLogger); - if (pullProcess != null) { - ret = pullProcess.waitFor(); - if (ret == 0) { - return new PerformanceInspectionResult(rawResultFile, performanceInspection); + if (isDebuggable(performanceInspection.deviceIdentifier, performanceInspection.appId)) { + return new PerformanceInspectionResult(null, performanceInspection); + } else { + Process process = ShellUtils.execLocalCommand(dumpCommand, false, classLogger); + if (process != null) { + try { + int ret = process.waitFor(); + if (ret == 0) { + Process pullProcess = ShellUtils.execLocalCommand("adb pull " + sdHprofFilePath + " " + rawResultFile.getAbsolutePath(), false, classLogger); + if (pullProcess != null) { + ret = pullProcess.waitFor(); + if (ret == 0) { + return new PerformanceInspectionResult(rawResultFile, performanceInspection); + } } } + } catch (InterruptedException e) { + classLogger.error(e.getMessage(), e); + return new PerformanceInspectionResult(null, performanceInspection); } - } catch (InterruptedException e) { - classLogger.error(e.getMessage(), e); - return new PerformanceInspectionResult(null, performanceInspection); - } + } + } return new PerformanceInspectionResult(null, performanceInspection); @@ -58,4 +62,11 @@ public PerformanceInspectionResult inspect(PerformanceInspection performanceInsp private String getMemHprofCommand() { return "adb -s %s shell am dumpheap %s %s"; } + + + private boolean isDebuggable(String deviceId, String packageName) { + String cmd = String.format("adb -s %s shell dumpsys package %s | findstr flags", deviceId, packageName); + String ret = ShellUtils.execLocalCommandWithResult(cmd, classLogger); + return ret != null && ret.contains("DEBUGGABLE"); + } } From b1adec6df340d009f908905d20cb3b131f0d1b4f Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Wed, 10 May 2023 16:40:34 +0800 Subject: [PATCH 06/12] fix: fix merge issues --- .../entity/AndroidHprofMemoryInfo.java | 19 +++++++++++++++++++ .../AndroidMemoryHprofResultParser.java | 3 +-- 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 common/src/main/java/com/microsoft/hydralab/performance/entity/AndroidHprofMemoryInfo.java diff --git a/common/src/main/java/com/microsoft/hydralab/performance/entity/AndroidHprofMemoryInfo.java b/common/src/main/java/com/microsoft/hydralab/performance/entity/AndroidHprofMemoryInfo.java new file mode 100644 index 000000000..cfca30f0a --- /dev/null +++ b/common/src/main/java/com/microsoft/hydralab/performance/entity/AndroidHprofMemoryInfo.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.performance.entity; + +import com.microsoft.hydralab.performance.hprof.ObjectInfo; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +public class AndroidHprofMemoryInfo implements Serializable { + private List bitmapInfoList; + private List topObjectList; + private String appPackageName; + private long timeStamp; + private String description; +} diff --git a/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java b/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java index 3b337a9b3..300109a75 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java @@ -3,11 +3,10 @@ package com.microsoft.hydralab.performance.parsers; -import com.microsoft.hydralab.performance.Entity.AndroidHprofMemoryInfo; -import com.microsoft.hydralab.performance.Entity.AndroidMemoryInfo; import com.microsoft.hydralab.performance.PerformanceInspectionResult; import com.microsoft.hydralab.performance.PerformanceResultParser; import com.microsoft.hydralab.performance.PerformanceTestResult; +import com.microsoft.hydralab.performance.entity.AndroidHprofMemoryInfo; import com.microsoft.hydralab.performance.hprof.BitmapInfoExtractor; import com.microsoft.hydralab.performance.hprof.HeapProfProcessor; import com.microsoft.hydralab.performance.hprof.TopObjectInfoExtractor; From 5df550b0fbfb894ef43972071f5dcf87b8c6733a Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Thu, 25 May 2023 14:42:26 +0800 Subject: [PATCH 07/12] feat: only dump hprof with debugable build --- .../performance/inspectors/AndroidMemoryHprofInspector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java index 183ddcc77..8231a8d6e 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java @@ -32,7 +32,7 @@ public PerformanceInspectionResult inspect(PerformanceInspection performanceInsp hprofFileName); String sdHprofFilePath = HPROF_FILE_PREFIX + hprofFileName; String dumpCommand = String.format(getMemHprofCommand(), performanceInspection.deviceIdentifier, performanceInspection.appId, sdHprofFilePath); - if (isDebuggable(performanceInspection.deviceIdentifier, performanceInspection.appId)) { + if (!isDebuggable(performanceInspection.deviceIdentifier, performanceInspection.appId)) { return new PerformanceInspectionResult(null, performanceInspection); } else { Process process = ShellUtils.execLocalCommand(dumpCommand, false, classLogger); From f55f1892284085310a2b3a3b252f8c2eb21ae132 Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Wed, 7 Jun 2023 10:06:17 +0800 Subject: [PATCH 08/12] fix: fix build --- .../inspectors/AndroidMemoryHprofInspector.java | 16 +++++++--------- .../parsers/AndroidMemoryHprofResultParser.java | 7 +++---- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java index 8231a8d6e..86d607a6b 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java @@ -9,7 +9,6 @@ import com.microsoft.hydralab.performance.PerformanceInspectionResult; import com.microsoft.hydralab.performance.PerformanceInspector; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import java.io.File; @@ -18,11 +17,10 @@ public class AndroidMemoryHprofInspector implements PerformanceInspector { private static final String RAW_RESULT_FILE_NAME_FORMAT = "%s_%s_%s_memory.hprof"; private static final String HPROF_FILE_PREFIX = "/data/local/tmp/"; - private final Logger classLogger = LoggerFactory.getLogger(getClass()); @Override - public PerformanceInspectionResult inspect(PerformanceInspection performanceInspection) { + public PerformanceInspectionResult inspect(PerformanceInspection performanceInspection, Logger logger) { File rawResultFolder = new File(performanceInspection.resultFolder, performanceInspection.appId); Assert.isTrue(rawResultFolder.exists() || rawResultFolder.mkdir(), "rawResultFolder.mkdirs() failed in" + rawResultFolder.getAbsolutePath()); @@ -32,15 +30,15 @@ public PerformanceInspectionResult inspect(PerformanceInspection performanceInsp hprofFileName); String sdHprofFilePath = HPROF_FILE_PREFIX + hprofFileName; String dumpCommand = String.format(getMemHprofCommand(), performanceInspection.deviceIdentifier, performanceInspection.appId, sdHprofFilePath); - if (!isDebuggable(performanceInspection.deviceIdentifier, performanceInspection.appId)) { + if (!isDebuggable(performanceInspection.deviceIdentifier, performanceInspection.appId, logger)) { return new PerformanceInspectionResult(null, performanceInspection); } else { - Process process = ShellUtils.execLocalCommand(dumpCommand, false, classLogger); + Process process = ShellUtils.execLocalCommand(dumpCommand, false, logger); if (process != null) { try { int ret = process.waitFor(); if (ret == 0) { - Process pullProcess = ShellUtils.execLocalCommand("adb pull " + sdHprofFilePath + " " + rawResultFile.getAbsolutePath(), false, classLogger); + Process pullProcess = ShellUtils.execLocalCommand("adb pull " + sdHprofFilePath + " " + rawResultFile.getAbsolutePath(), false, logger); if (pullProcess != null) { ret = pullProcess.waitFor(); if (ret == 0) { @@ -49,7 +47,7 @@ public PerformanceInspectionResult inspect(PerformanceInspection performanceInsp } } } catch (InterruptedException e) { - classLogger.error(e.getMessage(), e); + logger.error(e.getMessage(), e); return new PerformanceInspectionResult(null, performanceInspection); } } @@ -64,9 +62,9 @@ private String getMemHprofCommand() { } - private boolean isDebuggable(String deviceId, String packageName) { + private boolean isDebuggable(String deviceId, String packageName, Logger logger) { String cmd = String.format("adb -s %s shell dumpsys package %s | findstr flags", deviceId, packageName); - String ret = ShellUtils.execLocalCommandWithResult(cmd, classLogger); + String ret = ShellUtils.execLocalCommandWithResult(cmd, logger); return ret != null && ret.contains("DEBUGGABLE"); } } diff --git a/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java b/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java index 300109a75..edfdbc457 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/parsers/AndroidMemoryHprofResultParser.java @@ -18,11 +18,10 @@ import java.util.List; public class AndroidMemoryHprofResultParser implements PerformanceResultParser { - private final Logger logger = LoggerFactory.getLogger(getClass()); private static final int MD_OBJECT_REPORT_COUNT = 50; @Override - public PerformanceTestResult parse(PerformanceTestResult performanceTestResult) { + public PerformanceTestResult parse(PerformanceTestResult performanceTestResult, Logger logger) { if (performanceTestResult == null || performanceTestResult.performanceInspectionResults == null || performanceTestResult.performanceInspectionResults.isEmpty()) { return null; @@ -34,7 +33,7 @@ public PerformanceTestResult parse(PerformanceTestResult performanceTestResult) return performanceTestResult; } inspectionResult.parsedData = buildAndroidHprofMemoryInfo(hprofFile, inspectionResult.inspection.appId, inspectionResult.inspection.description, - inspectionResult.timestamp); + inspectionResult.timestamp, logger); } return performanceTestResult; @@ -42,7 +41,7 @@ public PerformanceTestResult parse(PerformanceTestResult performanceTestResult) - private AndroidHprofMemoryInfo buildAndroidHprofMemoryInfo(File dumpFile, String packageName, String description, long timeStamp) { + private AndroidHprofMemoryInfo buildAndroidHprofMemoryInfo(File dumpFile, String packageName, String description, long timeStamp, Logger logger) { HeapProfProcessor profProcessor = new HeapProfProcessor(dumpFile); From aa73efa5e35fd71e482f7d464f32bb643c18de37 Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Wed, 7 Jun 2023 17:06:11 +0800 Subject: [PATCH 09/12] feat: shorten hprof file name --- .../performance/inspectors/AndroidMemoryHprofInspector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java index 86d607a6b..e3099ae65 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java @@ -15,7 +15,7 @@ public class AndroidMemoryHprofInspector implements PerformanceInspector { - private static final String RAW_RESULT_FILE_NAME_FORMAT = "%s_%s_%s_memory.hprof"; + private static final String RAW_RESULT_FILE_NAME_FORMAT = "%s_%s_memory.hprof"; private static final String HPROF_FILE_PREFIX = "/data/local/tmp/"; @@ -25,7 +25,7 @@ public PerformanceInspectionResult inspect(PerformanceInspection performanceInsp File rawResultFolder = new File(performanceInspection.resultFolder, performanceInspection.appId); Assert.isTrue(rawResultFolder.exists() || rawResultFolder.mkdir(), "rawResultFolder.mkdirs() failed in" + rawResultFolder.getAbsolutePath()); String tmpTime = TimeUtils.getTimestampForFilename(); - String hprofFileName = String.format(RAW_RESULT_FILE_NAME_FORMAT, getClass().getSimpleName(), performanceInspection.appId, tmpTime); + String hprofFileName = String.format(RAW_RESULT_FILE_NAME_FORMAT, performanceInspection.appId, tmpTime); File rawResultFile = new File(rawResultFolder, hprofFileName); String sdHprofFilePath = HPROF_FILE_PREFIX + hprofFileName; From 20b123dcc646b2594e03962127ac8e7d10349e6c Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Wed, 7 Jun 2023 17:16:58 +0800 Subject: [PATCH 10/12] feat: remove appid in file name --- .../performance/inspectors/AndroidMemoryHprofInspector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java index e3099ae65..cef7fe91b 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/inspectors/AndroidMemoryHprofInspector.java @@ -15,7 +15,7 @@ public class AndroidMemoryHprofInspector implements PerformanceInspector { - private static final String RAW_RESULT_FILE_NAME_FORMAT = "%s_%s_memory.hprof"; + private static final String RAW_RESULT_FILE_NAME_FORMAT = "memory_%s.hprof"; private static final String HPROF_FILE_PREFIX = "/data/local/tmp/"; @@ -25,7 +25,7 @@ public PerformanceInspectionResult inspect(PerformanceInspection performanceInsp File rawResultFolder = new File(performanceInspection.resultFolder, performanceInspection.appId); Assert.isTrue(rawResultFolder.exists() || rawResultFolder.mkdir(), "rawResultFolder.mkdirs() failed in" + rawResultFolder.getAbsolutePath()); String tmpTime = TimeUtils.getTimestampForFilename(); - String hprofFileName = String.format(RAW_RESULT_FILE_NAME_FORMAT, performanceInspection.appId, tmpTime); + String hprofFileName = String.format(RAW_RESULT_FILE_NAME_FORMAT, tmpTime); File rawResultFile = new File(rawResultFolder, hprofFileName); String sdHprofFilePath = HPROF_FILE_PREFIX + hprofFileName; From 18310d77fbedcf54ab99a85e86f9898bf29c0297 Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Fri, 9 Jun 2023 16:32:37 +0800 Subject: [PATCH 11/12] feat: add final --- .../hydralab/performance/hprof/TopObjectInfoExtractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/com/microsoft/hydralab/performance/hprof/TopObjectInfoExtractor.java b/common/src/main/java/com/microsoft/hydralab/performance/hprof/TopObjectInfoExtractor.java index ca247681b..eebb5911f 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/hprof/TopObjectInfoExtractor.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/hprof/TopObjectInfoExtractor.java @@ -8,7 +8,7 @@ import com.squareup.haha.perflib.Instance; public class TopObjectInfoExtractor extends Extractor { - private int count; + private final int count; public TopObjectInfoExtractor(int count) { this.count = count; From 2d961ab9a496efe3e032c47f502cf962fad05c36 Mon Sep 17 00:00:00 2001 From: TedaLIEz Date: Wed, 21 Jun 2023 15:34:56 +0800 Subject: [PATCH 12/12] feat: remove unused code --- .../performance/hprof/HeapProfProcessor.java | 99 ------------------- 1 file changed, 99 deletions(-) diff --git a/common/src/main/java/com/microsoft/hydralab/performance/hprof/HeapProfProcessor.java b/common/src/main/java/com/microsoft/hydralab/performance/hprof/HeapProfProcessor.java index 50c93da38..6b8929e41 100644 --- a/common/src/main/java/com/microsoft/hydralab/performance/hprof/HeapProfProcessor.java +++ b/common/src/main/java/com/microsoft/hydralab/performance/hprof/HeapProfProcessor.java @@ -123,105 +123,6 @@ public boolean execute(String key) { }); } -// public File generateReport(File csvFile) { -// if (extractorResultsMap == null || extractorResultsMap.isEmpty()) { -// return null; -// } -// try { -// CsvWriter csvWriter = new CsvWriter(csvFile.getAbsolutePath(), ',', Charset.forName("utf-8")); -// -// for (Map.Entry> entry : extractorResultsMap.entrySet()) { -// String key = entry.getKey(); -// List value = entry.getValue(); -// if (value == null) { -// continue; -// } -// if (value.isEmpty()) { -// continue; -// } -// csvWriter.writeRecord(new String[]{key}); -// ObjectInfo objectInfo = value.get(0); -// csvWriter.writeRecord(("index," + objectInfo.getColumnsTitle()).split(",")); -// int i = 1; -// for (ObjectInfo info : value) { -// csvWriter.writeRecord((i++ + "," + info.toDataString()).split(",")); -// } -// } -// csvWriter.close(); -// } catch (IOException e) { -// e.printStackTrace(); -// } -// return csvFile; -// } - -// public File generateMarkdownReport(File mdFile, int maxRowCount) { -// if (extractorResultsMap == null || extractorResultsMap.isEmpty()) { -// return null; -// } -// StringBuilder builder = new StringBuilder(); -// String endLine = "\n\n"; -// FileWriter fileWriter = null; -// try { -// fileWriter = new FileWriter(mdFile); -// for (Map.Entry> entry : extractorResultsMap.entrySet()) { -// String title = entry.getKey(); -// if (extractorTitleMap.containsKey(entry.getKey())) { -// title = extractorTitleMap.get(entry.getKey()); -// } -// builder.append(endLine).append("# ").append(title).append(endLine); -// builder.append(getHtmlReport(entry.getKey(), maxRowCount, false)); -// } -// fileWriter.write(builder.toString()); -// } catch (IOException e) { -// e.printStackTrace(); -// } finally { -// try { -// if (fileWriter != null) { -// fileWriter.flush(); -// fileWriter.close(); -// } -// } catch (IOException ex) { -// ex.printStackTrace(); -// } -// } -// -// return mdFile; -// } - -// public String getHtmlReport(String extractorName, int maxRowCount, boolean isLimit) { -// if (extractorResultsMap == null || extractorResultsMap.isEmpty()) { -// return null; -// } -// if (extractorName == null) { -// return null; -// } -// StringBuilder stringBuilder = new StringBuilder(); -// String endLine = "
    "; -// List value = extractorResultsMap.get(extractorName); -// if (value == null || value.isEmpty()) { -// return null; -// } -// int i = 0; -// for (ObjectInfo info : value) { -// if (i >= maxRowCount) { -// break; -// } -// StringBuilder nextLineBuilder = new StringBuilder(); -// nextLineBuilder.append("
  • ").append("").append("# ").append(i + 1).append(": ").append("").append(endLine).append(info.toHtmlDataStringOCV()).append("
  • "); -// if (isLimit && (nextLineBuilder.length() + stringBuilder.length() > HTML_MAX_LENGTH)) { -// break; -// } -// stringBuilder.append(nextLineBuilder); -// i++; -// } -// stringBuilder.append(""); -// return stringBuilder.toString(); -// } - -// public String getHtmlReport(String extractorName, int maxRowCount) { -// return getHtmlReport(extractorName, maxRowCount, true); -// } - private void processInstance(int index, Instance instance) { for (Map.Entry entry : extractorMap.entrySet()) { entry.getValue().onExtractInfo(index, instance);