diff --git a/app/dist_files/Info.plist b/app/dist_files/Info.plist
index 6a38897..81dc1a1 100644
--- a/app/dist_files/Info.plist
+++ b/app/dist_files/Info.plist
@@ -48,7 +48,7 @@
CFBundleVersion
100
NSHumanReadableCopyright
- Copyright (C) 2020
+ Copyright (C) 2024
NSHighResolutionCapable
true
diff --git a/app/src/main/java/com/github/grishberg/profiler/common/settings/JsonSettings.kt b/app/src/main/java/com/github/grishberg/profiler/common/settings/JsonSettings.kt
index 682d727..b52e294 100644
--- a/app/src/main/java/com/github/grishberg/profiler/common/settings/JsonSettings.kt
+++ b/app/src/main/java/com/github/grishberg/profiler/common/settings/JsonSettings.kt
@@ -342,7 +342,6 @@ class JsonSettings(
override fun filesDir() = filesDirName
override var shouldShowToolbar: Boolean = false
- override var lastVersion: String = ""
private fun initWithDefaults() {
initWithDefaultStringValue(SETTINGS_FONT_NAME, "Arial")
diff --git a/core/build.gradle b/core/build.gradle
index 98311cc..d9dba86 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -12,6 +12,7 @@ repositories {
}
dependencies {
+ implementation 'com.github.Grishberg:simpleperf-parser:1.0.6'
implementation 'com.github.Grigory-Rylov:adb-facade-core:0.1.8'
implementation 'com.github.Grigory-Rylov:andoid_method_trace_recorder:2.1.0'
implementation 'com.github.Grigory-Rylov:proguard-deobfuscator:0.4.0'
diff --git a/core/src/main/java/com/github/grishberg/profiler/analyzer/AnalyzerResultImpl.kt b/core/src/main/java/com/github/grishberg/profiler/analyzer/AnalyzerResultImpl.kt
index a91b77c..1d4fee3 100644
--- a/core/src/main/java/com/github/grishberg/profiler/analyzer/AnalyzerResultImpl.kt
+++ b/core/src/main/java/com/github/grishberg/profiler/analyzer/AnalyzerResultImpl.kt
@@ -8,11 +8,14 @@ data class AnalyzerResultImpl(
override val threadTimeBounds: Map,
override val globalTimeBounds: Map,
override val maxLevel: Int,
- internal val mutableData: MutableMap>,
+ internal val mutableData: Map>,
override val threads: List,
override val mainThreadId: Int,
override val startTimeUs: Long = -1,
+ override val minThreadTime: Double = 0.0,
+ override val minGlobalTime: Double = 0.0,
) : AnalyzerResult {
+
override val data: Map>
get() = mutableData
}
diff --git a/core/src/main/java/com/github/grishberg/profiler/analyzer/ProfileDataImpl.kt b/core/src/main/java/com/github/grishberg/profiler/analyzer/ProfileDataImpl.kt
index 6a744e9..480fa17 100644
--- a/core/src/main/java/com/github/grishberg/profiler/analyzer/ProfileDataImpl.kt
+++ b/core/src/main/java/com/github/grishberg/profiler/analyzer/ProfileDataImpl.kt
@@ -1,5 +1,6 @@
package com.github.grishberg.profiler.analyzer
+import com.github.grishberg.profiler.core.ExtendedData
import com.github.grishberg.profiler.core.ProfileData
data class ProfileDataImpl(
@@ -11,7 +12,8 @@ data class ProfileDataImpl(
override var globalEndTimeInMillisecond: Double = -1.0,
override var threadSelfTime: Double = 0.0,
override var globalSelfTime: Double = 0.0,
- override var parent: ProfileDataImpl? = null
+ override var parent: ProfileDataImpl? = null,
+ override var extendedData: ExtendedData? = null,
) : ProfileData {
private val _children = mutableListOf()
override val children: List = _children
diff --git a/core/src/main/java/com/github/grishberg/profiler/analyzer/SimplePerfAnalyzer.kt b/core/src/main/java/com/github/grishberg/profiler/analyzer/SimplePerfAnalyzer.kt
new file mode 100644
index 0000000..16fd162
--- /dev/null
+++ b/core/src/main/java/com/github/grishberg/profiler/analyzer/SimplePerfAnalyzer.kt
@@ -0,0 +1,159 @@
+package com.github.grishberg.profiler.analyzer
+
+import com.android.tools.profilers.cpu.CaptureNode
+import com.android.tools.profilers.cpu.CpuCapture
+import com.android.tools.profilers.cpu.CpuThreadInfo
+import com.android.tools.profilers.cpu.nodemodel.CppFunctionModel
+import com.android.tools.profilers.cpu.nodemodel.JavaMethodModel
+import com.android.tools.profilers.cpu.nodemodel.NoSymbolModel
+import com.android.tools.profilers.cpu.nodemodel.SyscallModel
+import com.android.tools.profilers.cpu.simpleperf.SimpleperfTraceParser
+import com.github.grishberg.profiler.analyzer.converter.NameConverter
+import com.github.grishberg.profiler.common.AppLogger
+import com.github.grishberg.profiler.core.AnalyzerResult
+import com.github.grishberg.profiler.core.ExtendedData
+import java.io.File
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.random.Random
+
+class SimplePerfAnalyzer(
+ private val log: AppLogger,
+ private val nameConverter: NameConverter,
+) {
+
+ fun analyze(traceFile: File): AnalyzerResult {
+ val parser = SimpleperfTraceParser()
+ val traceId = Random.nextLong()
+ try {
+ return captureToAnalyzerResult(parser.parse(traceFile, traceId))
+ } catch (e: Exception) {
+ throw WrongFormatException()
+ }
+ }
+
+ private fun captureToAnalyzerResult(capture: CpuCapture): AnalyzerResult {
+ val threads = mapThreads(capture.threads)
+
+ val data = mutableMapOf>()
+ var maxLevel = 0
+ val threadTimeBounds = mutableMapOf()
+ val globalTimeBounds = mutableMapOf()
+
+ var minThreadTime = Long.MAX_VALUE
+ var minGlobalTime = Long.MAX_VALUE
+
+ for (thread in threads) {
+ val currentThreadRoot = capture.getCaptureNode(thread.threadId) ?: continue
+ val profileDataList = mutableListOf()
+ for (threadChild in currentThreadRoot.children) {
+ val newRoot = mapCpuNode(threadChild)
+ val level = processChildren(profileDataList, threadChild, newRoot)
+ maxLevel = max(maxLevel, level)
+ }
+ threadTimeBounds[thread.threadId] = ThreadTimeBoundsImpl(
+ minTime = convertTime(currentThreadRoot.startThread), maxTime = convertTime(currentThreadRoot.endThread)
+ )
+ globalTimeBounds[thread.threadId] = ThreadTimeBoundsImpl(
+ minTime = convertTime(currentThreadRoot.startGlobal), maxTime = convertTime(currentThreadRoot.endGlobal)
+ )
+
+ minThreadTime = min(minThreadTime, currentThreadRoot.startThread)
+ minGlobalTime = min(minGlobalTime, currentThreadRoot.startGlobal)
+ data[thread.threadId] = profileDataList
+ }
+
+ return AnalyzerResultImpl(
+ threadTimeBounds = threadTimeBounds,
+ globalTimeBounds = globalTimeBounds,
+ maxLevel = maxLevel,
+ mutableData = data,
+ threads = threads,
+ mainThreadId = capture.mainThreadId,
+ startTimeUs = -1,
+ convertTime(minThreadTime),
+ convertTime(minGlobalTime),
+ )
+ }
+
+ private fun processChildren(allNodes: MutableList, node: CaptureNode, newRoot: ProfileDataImpl): Int {
+ var maxLevel = node.depth - 1
+ node.children.forEachIndexed { index, captureNode ->
+ val profileDataChild = mapCpuNode(captureNode)
+ newRoot.addChild(profileDataChild)
+ allNodes.add(newRoot)
+
+ val level = processChildren(allNodes, captureNode, profileDataChild)
+ maxLevel = max(maxLevel, level)
+
+ }
+ return maxLevel
+ }
+
+ private fun mapThreads(threads: Set): List {
+ return threads.map { ThreadItemImpl(it.name, it.id) }.sortedBy { it.threadId }
+ }
+
+ private fun mapCpuNode(cpuNode: CaptureNode): ProfileDataImpl {
+ val data = cpuNode.data
+ val profileData = ProfileDataImpl(
+ name = data.fullName,
+ level = cpuNode.depth - THREAD_DEPTH_OFFSET,
+ threadStartTimeInMillisecond = convertTime(cpuNode.startThread),
+ threadEndTimeInMillisecond = convertTime(cpuNode.endThread),
+ globalStartTimeInMillisecond = convertTime(cpuNode.startGlobal),
+ globalEndTimeInMillisecond = convertTime(cpuNode.endGlobal),
+ )
+ when (data) {
+ is CppFunctionModel -> {
+ profileData.extendedData = ExtendedData.CppFunctionData(
+ tag = data.tag,
+ id = data.id,
+ fullName = data.fullName,
+ classOrNamespace = data.classOrNamespace,
+ parameters = data.parameters,
+ isUserCode = data.isUserCode,
+ fileName = data.fileName,
+ vAddress = data.vAddress,
+ )
+ }
+
+ is JavaMethodModel -> {
+ profileData.extendedData = ExtendedData.JavaMethodData(
+ tag = data.tag,
+ id = data.id,
+ fullName = data.fullName,
+ className = data.className,
+ signature = data.signature,
+ )
+ }
+
+ is NoSymbolModel -> {
+ profileData.extendedData = ExtendedData.NoSymbolData(
+ tag = data.tag,
+ id = data.id,
+ fullName = data.fullName,
+ isKernel = data.isKernel,
+ )
+ }
+
+ is SyscallModel -> {
+ profileData.extendedData = ExtendedData.SyscallData(
+ tag = data.tag,
+ id = data.id,
+ fullName = data.fullName,
+ )
+ }
+ }
+
+ return profileData
+ }
+
+ private fun convertTime(time: Long): Double {
+ return time.toDouble() / 1000.0
+ }
+
+ private companion object {
+ private const val THREAD_DEPTH_OFFSET = 1
+ }
+}
diff --git a/core/src/main/java/com/github/grishberg/profiler/analyzer/TraceAnalyzer.kt b/core/src/main/java/com/github/grishberg/profiler/analyzer/TraceAnalyzer.kt
index 1283e4e..7724350 100644
--- a/core/src/main/java/com/github/grishberg/profiler/analyzer/TraceAnalyzer.kt
+++ b/core/src/main/java/com/github/grishberg/profiler/analyzer/TraceAnalyzer.kt
@@ -1,255 +1,22 @@
package com.github.grishberg.profiler.analyzer
-import com.android.tools.perflib.vmtrace.MethodInfo
-import com.android.tools.perflib.vmtrace.TraceAction
-import com.android.tools.perflib.vmtrace.VmTraceHandler
-import com.android.tools.perflib.vmtrace.VmTraceParser
+import com.github.grishberg.profiler.analyzer.converter.LegacyTraceAnalyzerTraceAnalyzer
import com.github.grishberg.profiler.analyzer.converter.NameConverter
import com.github.grishberg.profiler.analyzer.converter.NoOpNameConverter
import com.github.grishberg.profiler.common.AppLogger
+import com.github.grishberg.profiler.core.AnalyzerResult
import java.io.File
-import java.util.*
-import kotlin.collections.ArrayList
-import kotlin.collections.HashMap
class TraceAnalyzer(
private val log: AppLogger
) {
var nameConverter: NameConverter = NoOpNameConverter
- fun analyze(traceFile: File): AnalyzerResultImpl {
- val vmTraceHandler = OnVmTraceHandler(log, nameConverter)
- VmTraceParser(traceFile, vmTraceHandler).parse()
-
- for (threadId in vmTraceHandler.threads) {
- val traceDataForThread = vmTraceHandler.traceData.getOrDefault(threadId.key, mutableListOf())
-
- for (duration in traceDataForThread) {
- if (duration.threadEndTimeInMillisecond == -1.0) {
- duration.threadEndTimeInMillisecond =
- vmTraceHandler.threadTimeBounds.getOrDefault(threadId.key, ThreadTimeBoundsImpl()).maxTime
- }
- if (duration.globalEndTimeInMillisecond == -1.0) {
- duration.globalEndTimeInMillisecond =
- vmTraceHandler.globalTimeBounds.getOrDefault(threadId.key, ThreadTimeBoundsImpl()).maxTime
- }
- }
-
- for (duration in traceDataForThread) {
- duration.updateSelfTime()
- }
- }
-
- val sortedThreads = ArrayList()
-
- var threadIndex = 0
- for (threadEntity in vmTraceHandler.globalTimeBounds) {
- val id = threadEntity.key
- if (id == vmTraceHandler.mainThreadId) {
- continue
- }
-
- val thread = vmTraceHandler.threads[id]
- val threadName = thread ?: "Thread-$threadIndex"
-
- sortedThreads.add(ThreadItemImpl(threadName, id))
- threadIndex++
- }
-
- sortedThreads.sortBy { it.name }
-
- sortedThreads.add(
- 0,
- ThreadItemImpl(
- vmTraceHandler.threads.getOrDefault(vmTraceHandler.mainThreadId, "main")!!,
- vmTraceHandler.mainThreadId
- )
- )
-
- return AnalyzerResultImpl(
- vmTraceHandler.threadTimeBounds,
- vmTraceHandler.globalTimeBounds,
- vmTraceHandler.maxLevel,
- vmTraceHandler.traceData,
- sortedThreads,
- vmTraceHandler.mainThreadId,
- vmTraceHandler.startTime
- )
- }
-
- private fun updateSelfTime(current: ProfileDataImpl) {
- if (current.threadSelfTime > 0.0 && current.globalSelfTime > 0.0) {
- return
- }
- current.apply {
- threadSelfTime = threadEndTimeInMillisecond - threadStartTimeInMillisecond
- globalSelfTime = globalEndTimeInMillisecond - globalStartTimeInMillisecond
- for (child in children) {
- threadSelfTime -= child.threadEndTimeInMillisecond - child.threadStartTimeInMillisecond
- globalSelfTime -= child.globalEndTimeInMillisecond - child.globalStartTimeInMillisecond
- try {
- updateSelfTime(child)
- } catch (e: StackOverflowError) {
- e.printStackTrace()
- }
- }
- }
- }
-
- internal class OnVmTraceHandler(
- private val log: AppLogger,
- private val nameConverter: NameConverter
- ) : VmTraceHandler {
- private var version: Int = -1
-
- var mainThreadIndex = 0
- val threads = mutableMapOf()
-
- val properties = mutableMapOf()
-
- val methodsAndClasses = MethodsAndClasses()
- val methods = mutableMapOf()
-
- val threadTimeBounds = mutableMapOf()
- val globalTimeBounds = mutableMapOf()
- var mainThreadId = -1
- var maxLevel = 0
-
- val traceData = mutableMapOf>()
- val methodsStacksForThread = mutableMapOf>>()
- val level = mutableMapOf()
- val parents = mutableMapOf>()
- var startTime: Long = -1
-
- override fun setVersion(version: Int) {
- this.version = version
- }
-
- override fun setProperty(key: String?, value: String?) {
- properties[key] = value
- }
-
- override fun addThread(id: Int, name: String?) {
- threads[id] = name
- if (name == "main") {
- mainThreadId = id
- mainThreadIndex = threads.size - 1
- }
- }
-
- override fun addMethod(id: Long, info: MethodInfo?) {
- methods[id] = info
- info?.let {
- methodsAndClasses.put(id, MethodData(it.fullName, it.className, it.methodName))
- }
- }
-
- override fun addMethodAction(
- threadId: Int,
- methodId: Long,
- methodAction: TraceAction,
- threadTime: Int,
- globalTime: Int
- ) {
- if (methods[methodId] == null) {
- return
- }
- val methodInfo = methods[methodId]!!
- val thread = threads[threadId]
-
- val threadTimeBoundsForThread = threadTimeBounds.getOrPut(threadId) { ThreadTimeBoundsImpl() }
- val globalTimeBoundsForThread = globalTimeBounds.getOrPut(threadId) { ThreadTimeBoundsImpl() }
-
- if (level[threadId] == null) {
- level[threadId] = 0
- }
- if (methodAction != TraceAction.METHOD_ENTER) {
- val parentsStackForThread = parents.getOrDefault(threadId, Stack())
- val stacksForThread = methodsStacksForThread.getOrDefault(threadId, HashMap())
-
- val stack = stacksForThread[methodId]
- if (stack == null) {
- log.d("There are no any stack for methodId=${methodId}, thread=$thread, startTime=$globalTime")
- } else {
- if (parentsStackForThread.isNotEmpty()) {
- parentsStackForThread.pop()
- }
- if (stack.isNotEmpty()) {
- val data = stack.pop()
- data.apply {
- threadEndTimeInMillisecond = threadTime / 1000.0
- globalEndTimeInMillisecond = globalTime / 1000.0
- }
- if (threadTimeBoundsForThread.maxTime < threadTime / 1000.0) {
- threadTimeBoundsForThread.maxTime = threadTime / 1000.0
- }
- if (globalTimeBoundsForThread.maxTime < globalTime / 1000.0) {
- globalTimeBoundsForThread.maxTime = globalTime / 1000.0
- }
- level[threadId] = level[threadId]!! - 1
- } else {
- log.w("Action $methodAction but stack is empty for thread=$threadId, startTime=$globalTime\"")
- }
- }
- }
-
- if (methodAction == TraceAction.METHOD_ENTER) {
- var parentsStackForThread = parents[threadId]
- if (parentsStackForThread == null) {
- parentsStackForThread = Stack()
- parents[threadId] = parentsStackForThread
- }
-
- var stacksForThread: MutableMap>? = methodsStacksForThread[threadId]
- if (stacksForThread == null) {
- stacksForThread = HashMap()
- methodsStacksForThread[threadId] = stacksForThread
- }
-
- var stack: Stack? = stacksForThread[methodId]
- if (stack == null) {
- stack = Stack()
- stacksForThread[methodId] = stack
- }
- val parent: ProfileDataImpl? =
- if (parentsStackForThread.isEmpty()) null else parentsStackForThread.peek()
-
- val convertedClassName = nameConverter.convertClassName(methodInfo.className)
- val convertedMethodName =
- nameConverter.convertMethodName(convertedClassName, methodInfo.methodName, methodInfo.signature)
- val duration = ProfileDataImpl(
- "${convertedClassName}.${convertedMethodName}",
- level.getOrDefault(threadId, -1),
- threadStartTimeInMillisecond = threadTime / 1000.0,
- globalStartTimeInMillisecond = globalTime / 1000.0,
- parent = parent
- )
- parent?.addChild(duration)
-
- var traceDataForThread = traceData[threadId]
- if (traceDataForThread == null) {
- traceDataForThread = ArrayList()
- traceData[threadId] = traceDataForThread
- }
- traceDataForThread.add(duration)
- stack.push(duration)
- parentsStackForThread.push(duration)
- if (threadTimeBoundsForThread.minTime > threadTime / 1000.0) {
- threadTimeBoundsForThread.minTime = threadTime / 1000.0
- }
- if (globalTimeBoundsForThread.minTime > threadTime / 1000.0) {
- globalTimeBoundsForThread.minTime = threadTime / 1000.0
- }
-
- if (maxLevel < level[threadId]!!) {
- maxLevel = level[threadId]!!
- }
- level[threadId] = level[threadId]!! + 1
- }
- }
-
- override fun setStartTimeUs(startTimeUs: Long) {
- this.startTime = startTimeUs
+ fun analyze(traceFile: File): AnalyzerResult {
+ return try {
+ SimplePerfAnalyzer(log, nameConverter).analyze(traceFile)
+ } catch (e: WrongFormatException){
+ LegacyTraceAnalyzerTraceAnalyzer(log, nameConverter).analyze(traceFile)
}
}
}
diff --git a/core/src/main/java/com/github/grishberg/profiler/analyzer/WrongFormatException.kt b/core/src/main/java/com/github/grishberg/profiler/analyzer/WrongFormatException.kt
new file mode 100644
index 0000000..9f0e119
--- /dev/null
+++ b/core/src/main/java/com/github/grishberg/profiler/analyzer/WrongFormatException.kt
@@ -0,0 +1,3 @@
+package com.github.grishberg.profiler.analyzer
+
+class WrongFormatException:Exception()
diff --git a/core/src/main/java/com/github/grishberg/profiler/analyzer/converter/LegacyTraceAnalyzer.kt b/core/src/main/java/com/github/grishberg/profiler/analyzer/converter/LegacyTraceAnalyzer.kt
new file mode 100644
index 0000000..edfffde
--- /dev/null
+++ b/core/src/main/java/com/github/grishberg/profiler/analyzer/converter/LegacyTraceAnalyzer.kt
@@ -0,0 +1,258 @@
+package com.github.grishberg.profiler.analyzer.converter
+
+import com.android.tools.perflib.vmtrace.MethodInfo
+import com.android.tools.perflib.vmtrace.TraceAction
+import com.android.tools.perflib.vmtrace.VmTraceHandler
+import com.android.tools.perflib.vmtrace.VmTraceParser
+import com.github.grishberg.profiler.analyzer.AnalyzerResultImpl
+import com.github.grishberg.profiler.analyzer.MethodData
+import com.github.grishberg.profiler.analyzer.MethodsAndClasses
+import com.github.grishberg.profiler.analyzer.ProfileDataImpl
+import com.github.grishberg.profiler.analyzer.ThreadItemImpl
+import com.github.grishberg.profiler.analyzer.ThreadTimeBoundsImpl
+import com.github.grishberg.profiler.common.AppLogger
+import java.io.File
+import java.util.Stack
+
+class LegacyTraceAnalyzerTraceAnalyzer(
+ private val log: AppLogger,
+ private val nameConverter: NameConverter = NoOpNameConverter
+) {
+
+ fun analyze(traceFile: File): AnalyzerResultImpl {
+ val vmTraceHandler = OnVmTraceHandler(log, nameConverter)
+ VmTraceParser(traceFile, vmTraceHandler).parse()
+
+ for (threadId in vmTraceHandler.threads) {
+ val traceDataForThread = vmTraceHandler.traceData.getOrDefault(threadId.key, mutableListOf())
+
+ for (duration in traceDataForThread) {
+ if (duration.threadEndTimeInMillisecond == -1.0) {
+ duration.threadEndTimeInMillisecond =
+ vmTraceHandler.threadTimeBounds.getOrDefault(threadId.key, ThreadTimeBoundsImpl()).maxTime
+ }
+ if (duration.globalEndTimeInMillisecond == -1.0) {
+ duration.globalEndTimeInMillisecond =
+ vmTraceHandler.globalTimeBounds.getOrDefault(threadId.key, ThreadTimeBoundsImpl()).maxTime
+ }
+ }
+
+ for (duration in traceDataForThread) {
+ duration.updateSelfTime()
+ }
+ }
+
+ val sortedThreads = ArrayList()
+
+ var threadIndex = 0
+ for (threadEntity in vmTraceHandler.globalTimeBounds) {
+ val id = threadEntity.key
+ if (id == vmTraceHandler.mainThreadId) {
+ continue
+ }
+
+ val thread = vmTraceHandler.threads[id]
+ val threadName = thread ?: "Thread-$threadIndex"
+
+ sortedThreads.add(ThreadItemImpl(threadName, id))
+ threadIndex++
+ }
+
+ sortedThreads.sortBy { it.name }
+
+ sortedThreads.add(
+ 0,
+ ThreadItemImpl(
+ vmTraceHandler.threads.getOrDefault(vmTraceHandler.mainThreadId, "main")!!,
+ vmTraceHandler.mainThreadId
+ )
+ )
+
+ return AnalyzerResultImpl(
+ vmTraceHandler.threadTimeBounds,
+ vmTraceHandler.globalTimeBounds,
+ vmTraceHandler.maxLevel,
+ vmTraceHandler.traceData,
+ sortedThreads,
+ vmTraceHandler.mainThreadId,
+ vmTraceHandler.startTime
+ )
+ }
+
+ private fun updateSelfTime(current: ProfileDataImpl) {
+ if (current.threadSelfTime > 0.0 && current.globalSelfTime > 0.0) {
+ return
+ }
+ current.apply {
+ threadSelfTime = threadEndTimeInMillisecond - threadStartTimeInMillisecond
+ globalSelfTime = globalEndTimeInMillisecond - globalStartTimeInMillisecond
+ for (child in children) {
+ threadSelfTime -= child.threadEndTimeInMillisecond - child.threadStartTimeInMillisecond
+ globalSelfTime -= child.globalEndTimeInMillisecond - child.globalStartTimeInMillisecond
+ try {
+ updateSelfTime(child)
+ } catch (e: StackOverflowError) {
+ e.printStackTrace()
+ }
+ }
+ }
+ }
+
+ internal class OnVmTraceHandler(
+ private val log: AppLogger,
+ private val nameConverter: NameConverter
+ ) : VmTraceHandler {
+ private var version: Int = -1
+
+ var mainThreadIndex = 0
+ val threads = mutableMapOf()
+
+ val properties = mutableMapOf()
+
+ val methodsAndClasses = MethodsAndClasses()
+ val methods = mutableMapOf()
+
+ val threadTimeBounds = mutableMapOf()
+ val globalTimeBounds = mutableMapOf()
+ var mainThreadId = -1
+ var maxLevel = 0
+
+ val traceData = mutableMapOf>()
+ val methodsStacksForThread = mutableMapOf>>()
+ val level = mutableMapOf()
+ val parents = mutableMapOf>()
+ var startTime: Long = -1
+
+ override fun setVersion(version: Int) {
+ this.version = version
+ }
+
+ override fun setProperty(key: String?, value: String?) {
+ properties[key] = value
+ }
+
+ override fun addThread(id: Int, name: String?) {
+ threads[id] = name
+ if (name == "main") {
+ mainThreadId = id
+ mainThreadIndex = threads.size - 1
+ }
+ }
+
+ override fun addMethod(id: Long, info: MethodInfo?) {
+ methods[id] = info
+ info?.let {
+ methodsAndClasses.put(id, MethodData(it.fullName, it.className, it.methodName))
+ }
+ }
+
+ override fun addMethodAction(
+ threadId: Int,
+ methodId: Long,
+ methodAction: TraceAction,
+ threadTime: Int,
+ globalTime: Int
+ ) {
+ if (methods[methodId] == null) {
+ return
+ }
+ val methodInfo = methods[methodId]!!
+ val thread = threads[threadId]
+
+ val threadTimeBoundsForThread = threadTimeBounds.getOrPut(threadId) { ThreadTimeBoundsImpl() }
+ val globalTimeBoundsForThread = globalTimeBounds.getOrPut(threadId) { ThreadTimeBoundsImpl() }
+
+ if (level[threadId] == null) {
+ level[threadId] = 0
+ }
+ if (methodAction != TraceAction.METHOD_ENTER) {
+ val parentsStackForThread = parents.getOrDefault(threadId, Stack())
+ val stacksForThread = methodsStacksForThread.getOrDefault(threadId, HashMap())
+
+ val stack = stacksForThread[methodId]
+ if (stack == null) {
+ log.d("There are no any stack for methodId=${methodId}, thread=$thread, startTime=$globalTime")
+ } else {
+ if (parentsStackForThread.isNotEmpty()) {
+ parentsStackForThread.pop()
+ }
+ if (stack.isNotEmpty()) {
+ val data = stack.pop()
+ data.apply {
+ threadEndTimeInMillisecond = threadTime / 1000.0
+ globalEndTimeInMillisecond = globalTime / 1000.0
+ }
+ if (threadTimeBoundsForThread.maxTime < threadTime / 1000.0) {
+ threadTimeBoundsForThread.maxTime = threadTime / 1000.0
+ }
+ if (globalTimeBoundsForThread.maxTime < globalTime / 1000.0) {
+ globalTimeBoundsForThread.maxTime = globalTime / 1000.0
+ }
+ level[threadId] = level[threadId]!! - 1
+ } else {
+ log.w("Action $methodAction but stack is empty for thread=$threadId, startTime=$globalTime\"")
+ }
+ }
+ }
+
+ if (methodAction == TraceAction.METHOD_ENTER) {
+ var parentsStackForThread = parents[threadId]
+ if (parentsStackForThread == null) {
+ parentsStackForThread = Stack()
+ parents[threadId] = parentsStackForThread
+ }
+
+ var stacksForThread: MutableMap>? = methodsStacksForThread[threadId]
+ if (stacksForThread == null) {
+ stacksForThread = HashMap()
+ methodsStacksForThread[threadId] = stacksForThread
+ }
+
+ var stack: Stack? = stacksForThread[methodId]
+ if (stack == null) {
+ stack = Stack()
+ stacksForThread[methodId] = stack
+ }
+ val parent: ProfileDataImpl? =
+ if (parentsStackForThread.isEmpty()) null else parentsStackForThread.peek()
+
+ val convertedClassName = nameConverter.convertClassName(methodInfo.className)
+ val convertedMethodName =
+ nameConverter.convertMethodName(convertedClassName, methodInfo.methodName, methodInfo.signature)
+ val duration = ProfileDataImpl(
+ "${convertedClassName}.${convertedMethodName}",
+ level.getOrDefault(threadId, -1),
+ threadStartTimeInMillisecond = threadTime / 1000.0,
+ globalStartTimeInMillisecond = globalTime / 1000.0,
+ parent = parent
+ )
+ parent?.addChild(duration)
+
+ var traceDataForThread = traceData[threadId]
+ if (traceDataForThread == null) {
+ traceDataForThread = ArrayList()
+ traceData[threadId] = traceDataForThread
+ }
+ traceDataForThread.add(duration)
+ stack.push(duration)
+ parentsStackForThread.push(duration)
+ if (threadTimeBoundsForThread.minTime > threadTime / 1000.0) {
+ threadTimeBoundsForThread.minTime = threadTime / 1000.0
+ }
+ if (globalTimeBoundsForThread.minTime > threadTime / 1000.0) {
+ globalTimeBoundsForThread.minTime = threadTime / 1000.0
+ }
+
+ if (maxLevel < level[threadId]!!) {
+ maxLevel = level[threadId]!!
+ }
+ level[threadId] = level[threadId]!! + 1
+ }
+ }
+
+ override fun setStartTimeUs(startTimeUs: Long) {
+ this.startTime = startTimeUs
+ }
+ }
+}
+
diff --git a/core/src/main/java/com/github/grishberg/profiler/chart/CallTracePanel.java b/core/src/main/java/com/github/grishberg/profiler/chart/CallTracePanel.java
index 9fd5865..941c59e 100644
--- a/core/src/main/java/com/github/grishberg/profiler/chart/CallTracePanel.java
+++ b/core/src/main/java/com/github/grishberg/profiler/chart/CallTracePanel.java
@@ -14,25 +14,13 @@
import com.github.grishberg.profiler.comparator.model.ComparableProfileData;
import com.github.grishberg.profiler.core.AnalyzerResult;
import com.github.grishberg.profiler.core.ProfileData;
+import com.github.grishberg.profiler.core.ThreadTimeBounds;
import com.github.grishberg.profiler.ui.BookMarkInfo;
import com.github.grishberg.profiler.ui.SimpleComponentListener;
import com.github.grishberg.profiler.ui.TimeFormatter;
import com.github.grishberg.profiler.ui.ZoomAndPanDelegate;
import com.github.grishberg.profiler.ui.theme.Palette;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.JPanel;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.RenderingHints;
-import java.awt.Shape;
+import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
@@ -46,15 +34,30 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import javax.swing.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class CallTracePanel extends JPanel
+ implements ProfileDataDimensionDelegate, ChartPaintDelegate, RepaintDelegate {
-public class CallTracePanel extends JPanel implements ProfileDataDimensionDelegate, ChartPaintDelegate, RepaintDelegate {
public static final int TOP_OFFSET = 20;
public static final int MARKER_LABEL_TEXT_MIN_WIDTH = 20;
private static final int FIT_PADDING = 80;
private static final int SCALE_FONT_SIZE = 13;
private static final double NOT_FOUND_ITEM_DARKEN_FACTOR = 0.5;
private static final double MINIMUM_WIDTH_IN_PX = 1;
- private static final AnalyzerResultImpl RESULT_STUB = new AnalyzerResultImpl(Collections.emptyMap(), Collections.emptyMap(), 0, Collections.emptyMap(), Collections.emptyList(), 0, -1);
+ private static final AnalyzerResultImpl RESULT_STUB =
+ new AnalyzerResultImpl(Collections.emptyMap(),
+ Collections.emptyMap(),
+ 0,
+ Collections.emptyMap(),
+ Collections.emptyList(),
+ 0,
+ -1,
+ 0.0,
+ 0.0
+ );
private final FoundNavigationListener foundNavigationListener;
private boolean init = true;
@@ -68,8 +71,8 @@ public class CallTracePanel extends JPanel implements ProfileDataDimensionDelega
private int leftSymbolOffset = 4;
private int fontTopOffset = 4;
private double maxRightOffset;
+ private double minLeftOffset;
private double maxBottomOffset;
- private double minTime;
private int minLevel;
private Dimension screenSize;
@@ -108,18 +111,20 @@ public class CallTracePanel extends JPanel implements ProfileDataDimensionDelega
private final MethodsNameDrawer cellPaintDelegate = new MethodsNameDrawer(leftSymbolOffset);
private final ElementColor colorBuffer = new ElementColor();
- public CallTracePanel(TimeFormatter timeFormatter,
- MethodsColorImpl methodsColor,
- FoundNavigationListener foundInfoListener,
- SettingsFacade settings,
- AppLogger logger,
- DependenciesFoundAction dependenciesFoundAction,
- StagesFacade stagesFacade,
- SystraceStagesFacade systraceStagesFacade,
- Bookmarks bookmarks,
- PreviewImageRepository previewImageRepository,
- CallTracePreviewPanel previewPanel,
- Palette palette) {
+ public CallTracePanel(
+ TimeFormatter timeFormatter,
+ MethodsColorImpl methodsColor,
+ FoundNavigationListener foundInfoListener,
+ SettingsFacade settings,
+ AppLogger logger,
+ DependenciesFoundAction dependenciesFoundAction,
+ StagesFacade stagesFacade,
+ SystraceStagesFacade systraceStagesFacade,
+ Bookmarks bookmarks,
+ PreviewImageRepository previewImageRepository,
+ CallTracePreviewPanel previewPanel,
+ Palette palette
+ ) {
this.timeFormatter = timeFormatter;
this.methodsColor = methodsColor;
this.foundNavigationListener = foundInfoListener;
@@ -130,7 +135,8 @@ public CallTracePanel(TimeFormatter timeFormatter,
this.previewImageRepository = previewImageRepository;
this.previewPanel = previewPanel;
this.palette = palette;
- this.zoomAndPanDelegate = new ZoomAndPanDelegate(this, TOP_OFFSET, new ZoomAndPanDelegate.LeftTopBounds());
+ this.zoomAndPanDelegate =
+ new ZoomAndPanDelegate(this, TOP_OFFSET, new ZoomAndPanDelegate.LeftTopBounds());
this.bookmarks = bookmarks;
stagesFacade.setRepaintDelegate(this);
stagesFacade.setLabelPaintDelegate(this);
@@ -185,9 +191,11 @@ public void onPreviewClicked(double offsetInPercent) {
double offset = 0;
if (isThreadTime) {
- offset = result.getThreadTimeBounds().get(currentThreadId).getMaxTime() * offsetInPercent;
+ offset = result.getThreadTimeBounds().get(currentThreadId).getMaxTime() *
+ offsetInPercent;
} else {
- offset = result.getGlobalTimeBounds().get(currentThreadId).getMaxTime() * offsetInPercent;
+ offset = result.getGlobalTimeBounds().get(currentThreadId).getMaxTime() *
+ offsetInPercent;
}
zoomAndPanDelegate.scrollTo(offset);
}
@@ -198,10 +206,14 @@ public void updatePreviewImage() {
if (result == RESULT_STUB) {
return;
}
- PreviewType previewType = isThreadTime ? PreviewType.PREVIEW_THREAD : PreviewType.PREVIEW_GLOBAL;
- BufferedImage cachedImage = previewImageRepository.preparePreview(currentThreadId, previewType, (image, threadId) -> {
- previewPanel.setImage(image);
- });
+ PreviewType previewType =
+ isThreadTime ? PreviewType.PREVIEW_THREAD : PreviewType.PREVIEW_GLOBAL;
+ BufferedImage cachedImage = previewImageRepository.preparePreview(currentThreadId,
+ previewType,
+ (image, threadId) -> {
+ previewPanel.setImage(image);
+ }
+ );
if (cachedImage != null) {
previewPanel.setImage(cachedImage);
}
@@ -215,10 +227,17 @@ private boolean checkBookmarkHeaderClicked(Point point) {
Rectangle rect = transformedShape.getBounds();
int cx = (rect.x + rect.width / 2);
- int labelTextWidth = Math.max(labelFontMetrics.stringWidth(bookmark.getName()), MARKER_LABEL_TEXT_MIN_WIDTH);
+ int labelTextWidth = Math.max(
+ labelFontMetrics.stringWidth(bookmark.getName()),
+ MARKER_LABEL_TEXT_MIN_WIDTH
+ );
// header background
- Rectangle labelRect = new Rectangle(cx - labelTextWidth / 2 - leftSymbolOffset, 0, labelTextWidth + 2 * leftSymbolOffset, TOP_OFFSET);
+ Rectangle labelRect = new Rectangle(cx - labelTextWidth / 2 - leftSymbolOffset,
+ 0,
+ labelTextWidth + 2 * leftSymbolOffset,
+ TOP_OFFSET
+ );
if (labelRect.contains(point)) {
if (rightClickListener != null) {
rightClickListener.onBookmarkRightClicked(point.x, point.y, bookmark);
@@ -230,34 +249,35 @@ private boolean checkBookmarkHeaderClicked(Point point) {
}
public void setMouseEventListener(ZoomAndPanDelegate.MouseEventsListener l) {
- ZoomAndPanDelegate.MouseEventsListener delegate = new ZoomAndPanDelegate.MouseEventsListener() {
- @Override
- public void onMouseClicked(Point point, double x, double y) {
- l.onMouseClicked(point, x, y);
- }
+ ZoomAndPanDelegate.MouseEventsListener delegate =
+ new ZoomAndPanDelegate.MouseEventsListener() {
+ @Override
+ public void onMouseClicked(Point point, double x, double y) {
+ l.onMouseClicked(point, x, y);
+ }
- @Override
- public void onMouseMove(Point point, double x, double y) {
- l.onMouseMove(point, x, y);
- }
+ @Override
+ public void onMouseMove(Point point, double x, double y) {
+ l.onMouseMove(point, x, y);
+ }
- @Override
- public void onMouseExited() {
- l.onMouseExited();
- }
+ @Override
+ public void onMouseExited() {
+ l.onMouseExited();
+ }
- @Override
- public void onControlMouseClicked(Point point, double x, double y) {
- l.onControlMouseClicked(point, x, y);
- findCreatedByDagger(x, y);
- }
+ @Override
+ public void onControlMouseClicked(Point point, double x, double y) {
+ l.onControlMouseClicked(point, x, y);
+ findCreatedByDagger(x, y);
+ }
- @Override
- public void onControlShiftMouseClicked(Point point, double x, double y) {
- l.onControlShiftMouseClicked(point, x, y);
- findDaggerCaller(x, y);
- }
- };
+ @Override
+ public void onControlShiftMouseClicked(Point point, double x, double y) {
+ l.onControlShiftMouseClicked(point, x, y);
+ findDaggerCaller(x, y);
+ }
+ };
zoomAndPanDelegate.setMouseEventsListener(delegate);
}
@@ -303,14 +323,17 @@ public void openTraceResult(TraceContainer trace) {
switchThread(result.getMainThreadId());
this.minLevel = 0;
- this.minTime = 0;
maxBottomOffset = calculateTopForLevel(result.getMaxLevel()) + levelHeight;
bookmarks.set(trace.getBookmarks());
bookmarks.setup(maxBottomOffset, isThreadTime);
zoomAndPanDelegate.setTransform(new AffineTransform());
- zoomAndPanDelegate.fitZoom(new Rectangle.Double(0, 0, maxRightOffset, maxBottomOffset), 0, ZoomAndPanDelegate.VerticalAlign.NONE);
+ zoomAndPanDelegate.fitZoom(
+ new Rectangle.Double(minLeftOffset, 0, maxRightOffset - minLeftOffset, maxBottomOffset),
+ 0,
+ ZoomAndPanDelegate.VerticalAlign.NONE
+ );
removeSelection();
previewImageRepository.setAnalyzerResult(result);
updatePreviewImage();
@@ -362,7 +385,9 @@ public void highlightCompare(ComparableProfileData rootCompareData, int threadId
updateStages(threadId, objectsForThread);
- if (rootCompareData.getProfileData().getLevel() != -1) throw new IllegalStateException("Root has level -1");
+ if (rootCompareData.getProfileData().getLevel() != -1) {
+ throw new IllegalStateException("Root has level -1");
+ }
rebuildDataWithCompare(rootCompareData, objectsForThread);
repaint();
}
@@ -378,7 +403,10 @@ public void updateCompare(ComparableProfileData root, int threadId) {
repaint();
}
- private void updateCompare(ComparableProfileData root, Map objectsForThread) {
+ private void updateCompare(
+ ComparableProfileData root,
+ Map objectsForThread
+ ) {
ProfileRectangle rect = createProfileRectangle(root.getProfileData());
ProfileRectangle currentRect = objectsForThread.get(rect.toString());
currentRect.setColor(methodsColor.getColorForCompare(root.getMark(), root.getName()));
@@ -387,7 +415,10 @@ private void updateCompare(ComparableProfileData root, Map objectsForThread) {
+ private void rebuildDataWithCompare(
+ ComparableProfileData root,
+ List objectsForThread
+ ) {
if (root.getProfileData().getLevel() != -1) {
ProfileRectangle rect = createProfileRectangle(root.getProfileData());
rect.setColor(methodsColor.getColorForCompare(root.getMark(), root.getName()));
@@ -399,23 +430,33 @@ private void rebuildDataWithCompare(ComparableProfileData root, List objectsForThread) {
- stagesFacade.onThreadSwitched(objectsForThread,
- threadId == result.getMainThreadId(),
- isThreadTime,
- TOP_OFFSET);
- systraceStagesFacade.onThreadSwitched(objectsForThread,
- threadId == result.getMainThreadId(),
- isThreadTime,
- TOP_OFFSET);
+ stagesFacade.onThreadSwitched(
+ objectsForThread,
+ threadId == result.getMainThreadId(),
+ isThreadTime,
+ TOP_OFFSET
+ );
+ systraceStagesFacade.onThreadSwitched(
+ objectsForThread,
+ threadId == result.getMainThreadId(),
+ isThreadTime,
+ TOP_OFFSET
+ );
}
private void updateBounds(int threadId) {
if (isThreadTime) {
- maxRightOffset = result.getThreadTimeBounds().getOrDefault(threadId, new ThreadTimeBoundsImpl()).getMaxTime();
+ ThreadTimeBounds threadTimeBounds = result.getThreadTimeBounds()
+ .getOrDefault(threadId, new ThreadTimeBoundsImpl());
+ maxRightOffset = threadTimeBounds.getMaxTime();
+ minLeftOffset = threadTimeBounds.getMinTime();
} else {
- maxRightOffset = result.getGlobalTimeBounds().getOrDefault(threadId, new ThreadTimeBoundsImpl()).getMaxTime();
+ ThreadTimeBounds threadTimeBounds = result.getGlobalTimeBounds()
+ .getOrDefault(threadId, new ThreadTimeBoundsImpl());
+ maxRightOffset = threadTimeBounds.getMaxTime();
+ minLeftOffset = threadTimeBounds.getMinTime();
}
- zoomAndPanDelegate.updateRightBottomCorner(maxBottomOffset, maxBottomOffset);
+ zoomAndPanDelegate.updateBounds(minLeftOffset, maxRightOffset, maxBottomOffset);
}
private void rebuildData(List objectsForThread) {
@@ -438,11 +479,12 @@ private ProfileRectangle createProfileRectangle(ProfileData record) {
double width = right - left;
return new ProfileRectangle(
- left,
- top,
- width,
- levelHeight,
- record);
+ left,
+ top,
+ width,
+ levelHeight,
+ record
+ );
}
private void updateData() {
@@ -517,8 +559,9 @@ protected void paintComponent(Graphics graphics) {
private void draw(Graphics2D g) {
g.setRenderingHint(
- RenderingHints.KEY_TEXT_ANTIALIASING,
- RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
+ RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_GASP
+ );
int fontSize = settings.getFontSize();
fontName = "Arial";
@@ -536,12 +579,25 @@ private void paintObjects(Graphics2D g, AffineTransform at) {
double screenTop = 0;
double screenRight = 0;
double screenBottom = 0;
+ if (result != null) {
+ Map bounds;
+ if (isThreadTime) {
+ bounds = result.getThreadTimeBounds();
+ } else {
+ bounds = result.getGlobalTimeBounds();
+ }
+ ThreadTimeBounds currentBound = bounds.get(currentThreadId);
+ if (currentBound != null) {
+ screenLeft = currentBound.getMinTime();
+ }
+ }
g.setColor(palette.getTraceBackgroundColor());
g.fillRect(0, 0, getWidth(), getHeight());
try {
Point2D.Double leftTop = zoomAndPanDelegate.transformPoint(new Point(0, 0));
- Point2D.Double rightBottom = zoomAndPanDelegate.transformPoint(new Point(screenSize.width, screenSize.height));
+ Point2D.Double rightBottom =
+ zoomAndPanDelegate.transformPoint(new Point(screenSize.width, screenSize.height));
screenLeft = leftTop.x;
screenTop = leftTop.y;
@@ -558,7 +614,8 @@ private void paintObjects(Graphics2D g, AffineTransform at) {
FontMetrics fm = getFontMetrics(g.getFont());
- List objectsForThread = objects.getOrDefault(currentThreadId, Collections.emptyList());
+ List objectsForThread =
+ objects.getOrDefault(currentThreadId, Collections.emptyList());
// draw rectangles
for (int i = 0; i < objectsForThread.size(); i++) {
@@ -599,13 +656,26 @@ private void paintObjects(Graphics2D g, AffineTransform at) {
double left = Math.max(0, bounds.x);
double right = Math.min(screenSize.width, bounds.x + bounds.width);
cellPaintDelegate.drawLabel(g, fm, element.profileData.getName(),
- left, right, bounds.y + bounds.height - fontTopOffset);
+ left, right, bounds.y + bounds.height - fontTopOffset
+ );
}
// draw selections
@Nullable
- ProfileRectangle selected = currentSelectedElement >= 0 ? objectsForThread.get(currentSelectedElement) : null;
- calledStacktrace.draw(g, at, fm, currentThreadId, selected, minimumSizeInMs, screenLeft, screenTop, screenRight, screenBottom);
+ ProfileRectangle selected =
+ currentSelectedElement >= 0 ? objectsForThread.get(currentSelectedElement) : null;
+ calledStacktrace.draw(
+ g,
+ at,
+ fm,
+ currentThreadId,
+ selected,
+ minimumSizeInMs,
+ screenLeft,
+ screenTop,
+ screenRight,
+ screenBottom
+ );
// toolbar background.
g.setColor(toolbarColor);
@@ -658,25 +728,37 @@ private void drawToolbar(Graphics2D g, AffineTransform at, FontMetrics fm) {
Rectangle rect = transformedShape.getBounds();
int cx = (rect.x + rect.width / 2);
- int labelTextWidth = Math.max(fm.stringWidth(bookmark.getName()), MARKER_LABEL_TEXT_MIN_WIDTH);
+ int labelTextWidth =
+ Math.max(fm.stringWidth(bookmark.getName()), MARKER_LABEL_TEXT_MIN_WIDTH);
// header background
g.setColor(bookmark.getHeaderColor());
- g.fillRect(cx - labelTextWidth / 2 - leftSymbolOffset, 0, labelTextWidth + 2 * leftSymbolOffset, TOP_OFFSET);
+ g.fillRect(
+ cx - labelTextWidth / 2 - leftSymbolOffset,
+ 0,
+ labelTextWidth + 2 * leftSymbolOffset,
+ TOP_OFFSET
+ );
g.setColor(bookmark.getHeaderTitleColor());
if (bookmark.getName().length() > 0) {
- g.drawString(bookmark.getName(), cx - labelTextWidth / 2, labelFontMetrics.getHeight());
+ g.drawString(
+ bookmark.getName(),
+ cx - labelTextWidth / 2,
+ labelFontMetrics.getHeight()
+ );
}
}
}
@Override
- public void drawLabel(Graphics2D g,
- FontMetrics fm,
- String name,
- Rectangle horizontalBounds,
- int topPosition) {
+ public void drawLabel(
+ Graphics2D g,
+ FontMetrics fm,
+ String name,
+ Rectangle horizontalBounds,
+ int topPosition
+ ) {
int leftPosition = horizontalBounds.x + leftSymbolOffset;
if (leftPosition < 0) {
leftPosition = 0;
@@ -696,13 +778,16 @@ public void drawLabel(Graphics2D g,
}
private void calculateColorProfileData(
- ProfileRectangle element,
- List objects) {
- boolean isSelectedElement = currentSelectedElement >= 0 && element == objects.get(currentSelectedElement);
+ ProfileRectangle element,
+ List objects
+ ) {
+ boolean isSelectedElement =
+ currentSelectedElement >= 0 && element == objects.get(currentSelectedElement);
if (isSearchingInProgress) {
if (element.isFoundElement) {
- boolean isFocusedElement = currentFocusedFoundElement >= 0 && element == foundItems.get(currentFocusedFoundElement);
+ boolean isFocusedElement = currentFocusedFoundElement >= 0 &&
+ element == foundItems.get(currentFocusedFoundElement);
Color color = isSelectedElement ? selectedFoundColor : foundColor;
if (isFocusedElement && !isSelectedElement) {
color = focusedFoundColor;
@@ -731,10 +816,12 @@ private void calculateColorProfileData(
}
public static Color darker(Color color, Double darkenFactor) {
- return new Color(Math.max((int) (color.getRed() * darkenFactor), 0),
- Math.max((int) (color.getGreen() * darkenFactor), 0),
- Math.max((int) (color.getBlue() * darkenFactor), 0),
- color.getAlpha());
+ return new Color(
+ Math.max((int) (color.getRed() * darkenFactor), 0),
+ Math.max((int) (color.getGreen() * darkenFactor), 0),
+ Math.max((int) (color.getBlue() * darkenFactor), 0),
+ color.getAlpha()
+ );
}
@NotNull
@@ -824,10 +911,11 @@ public ProfileData findDataByPosition(double x, double y) {
}
private int findElementIndexByXY(double x, double y) {
- if (x < 0 || x > maxRightOffset || y < 0) {
+ if (x < minLeftOffset || x > maxRightOffset || y < 0) {
return -1;
}
- List objectsForThread = objects.getOrDefault(currentThreadId, Collections.emptyList());
+ List objectsForThread =
+ objects.getOrDefault(currentThreadId, Collections.emptyList());
for (int i = 0; i < objectsForThread.size(); i++) {
ProfileRectangle currentElement = objectsForThread.get(i);
@@ -842,7 +930,8 @@ public void renderFoundItems(Finder.ThreadFindResult threadFindResult) {
isSearchingInProgress = true;
foundItems.clear();
- List objectsForThread = objects.getOrDefault(currentThreadId, Collections.emptyList());
+ List objectsForThread =
+ objects.getOrDefault(currentThreadId, Collections.emptyList());
for (int i = 0; i < objectsForThread.size(); i++) {
ProfileRectangle element = objectsForThread.get(i);
@@ -856,14 +945,21 @@ public void renderFoundItems(Finder.ThreadFindResult threadFindResult) {
currentFocusedFoundElement = 0;
ProfileRectangle element = foundItems.get(currentFocusedFoundElement);
- foundNavigationListener.onSelected(foundItems.size(), currentFocusedFoundElement, element.profileData);
+ foundNavigationListener.onSelected(
+ foundItems.size(),
+ currentFocusedFoundElement,
+ element.profileData
+ );
zoomAndPanDelegate.fitZoom(element, FIT_PADDING, ZoomAndPanDelegate.VerticalAlign.ENABLED);
requestFocus();
}
private void navigateToElement(Shape element) {
- zoomAndPanDelegate.navigateToRectangle(element.getBounds2D(), ZoomAndPanDelegate.VerticalAlign.ENABLED);
+ zoomAndPanDelegate.navigateToRectangle(
+ element.getBounds2D(),
+ ZoomAndPanDelegate.VerticalAlign.ENABLED
+ );
}
private void navigateToElement(Shape element, ZoomAndPanDelegate.VerticalAlign verticalAlign) {
@@ -876,18 +972,18 @@ public boolean isSearchingInProgress() {
private void addBookmark(ProfileRectangle foundElement, Color color, String title) {
bookmarks.add(
- new BookmarksRectangle(
- title,
- color,
- foundElement.profileData.getThreadStartTimeInMillisecond(),
- foundElement.profileData.getThreadEndTimeInMillisecond(),
- foundElement.profileData.getGlobalStartTimeInMillisecond(),
- foundElement.profileData.getGlobalEndTimeInMillisecond(),
- foundElement.profileData.getLevel(),
- currentThreadId,
- maxBottomOffset,
- isThreadTime
- )
+ new BookmarksRectangle(
+ title,
+ color,
+ foundElement.profileData.getThreadStartTimeInMillisecond(),
+ foundElement.profileData.getThreadEndTimeInMillisecond(),
+ foundElement.profileData.getGlobalStartTimeInMillisecond(),
+ foundElement.profileData.getGlobalEndTimeInMillisecond(),
+ foundElement.profileData.getLevel(),
+ currentThreadId,
+ maxBottomOffset,
+ isThreadTime
+ )
);
previewImageRepository.clear();
updatePreviewImage();
@@ -898,7 +994,8 @@ public void addBookmarkAtSelectedElement(BookMarkInfo bookMarkInfo) {
return;
}
- List objectsForThread = objects.getOrDefault(currentThreadId, Collections.emptyList());
+ List objectsForThread =
+ objects.getOrDefault(currentThreadId, Collections.emptyList());
ProfileRectangle selected = objectsForThread.get(currentSelectedElement);
addBookmark(selected, bookMarkInfo.getColor(), bookMarkInfo.getTitle());
repaint();
@@ -945,7 +1042,8 @@ public ProfileData getSelected() {
return null;
}
- List objectsForThread = objects.getOrDefault(currentThreadId, Collections.emptyList());
+ List objectsForThread =
+ objects.getOrDefault(currentThreadId, Collections.emptyList());
return objectsForThread.get(currentSelectedElement).profileData;
}
@@ -969,7 +1067,11 @@ public void resetFoundItemToEnd() {
private void focusFoundItem(int currentFocusedFoundElement) {
ProfileRectangle found = foundItems.get(currentFocusedFoundElement);
zoomAndPanDelegate.fitZoom(found, FIT_PADDING, ZoomAndPanDelegate.VerticalAlign.ENABLED);
- foundNavigationListener.onSelected(foundItems.size(), currentFocusedFoundElement, found.profileData);
+ foundNavigationListener.onSelected(
+ foundItems.size(),
+ currentFocusedFoundElement,
+ found.profileData
+ );
}
public void focusNextFoundItem() {
@@ -1029,7 +1131,8 @@ public void centerSelectedElement() {
if (currentSelectedElement < 0) {
return;
}
- List objectsForThread = objects.getOrDefault(currentThreadId, Collections.emptyList());
+ List objectsForThread =
+ objects.getOrDefault(currentThreadId, Collections.emptyList());
navigateToElement(objectsForThread.get(currentSelectedElement));
}
@@ -1037,7 +1140,8 @@ public String copySelectedStacktrace() {
if (currentSelectedElement < 0) {
return null;
}
- List objectsForThread = objects.getOrDefault(currentThreadId, Collections.emptyList());
+ List objectsForThread =
+ objects.getOrDefault(currentThreadId, Collections.emptyList());
ProfileRectangle selected = objectsForThread.get(currentSelectedElement);
return createStackTrace(selected);
}
@@ -1056,7 +1160,11 @@ private String createStackTrace(ProfileRectangle selected) {
public void resetZoom() {
zoomAndPanDelegate.setTransform(new AffineTransform());
- zoomAndPanDelegate.fitZoom(new Rectangle.Double(0, 0, maxRightOffset, maxBottomOffset), 0, ZoomAndPanDelegate.VerticalAlign.NONE);
+ zoomAndPanDelegate.fitZoom(
+ new Rectangle.Double(minLeftOffset, 0, maxRightOffset - minLeftOffset, maxBottomOffset),
+ 0,
+ ZoomAndPanDelegate.VerticalAlign.NONE
+ );
repaint();
}
@@ -1065,7 +1173,8 @@ public void fitSelectedElement() {
if (foundItems.size() > 0) {
rectangle = foundItems.get(currentFocusedFoundElement);
} else if (currentSelectedElement >= 0) {
- List objectsForThread = objects.getOrDefault(currentThreadId, Collections.emptyList());
+ List objectsForThread =
+ objects.getOrDefault(currentThreadId, Collections.emptyList());
rectangle = objectsForThread.get(currentSelectedElement);
}
@@ -1073,7 +1182,11 @@ public void fitSelectedElement() {
return;
}
- zoomAndPanDelegate.fitZoom(rectangle, FIT_PADDING, ZoomAndPanDelegate.VerticalAlign.ENABLED);
+ zoomAndPanDelegate.fitZoom(
+ rectangle,
+ FIT_PADDING,
+ ZoomAndPanDelegate.VerticalAlign.ENABLED
+ );
}
public void scaleScreenToRange(double start, double end) {
@@ -1129,7 +1242,8 @@ private int changeFontSize(int value) {
*/
public void selectProfileData(ProfileData selectedElement) {
ProfileRectangle selectedRectangle = createProfileRectangle(selectedElement);
- List objectsForThread = objects.getOrDefault(currentThreadId, Collections.emptyList());
+ List objectsForThread =
+ objects.getOrDefault(currentThreadId, Collections.emptyList());
currentSelectedElement = objectsForThread.indexOf(selectedRectangle);
if (currentSelectedElement < 0) {
return;
@@ -1154,7 +1268,8 @@ private ProfileRectangle getSelectedElement() {
if (currentSelectedElement < 0) {
return null;
}
- List objectsForThread = objects.getOrDefault(currentThreadId, Collections.emptyList());
+ List objectsForThread =
+ objects.getOrDefault(currentThreadId, Collections.emptyList());
return objectsForThread.get(currentSelectedElement);
}
@@ -1194,16 +1309,21 @@ public void invalidateHighlighting() {
}
public class ProfilerPanelData {
+
public final List profileData;
public final List markersData;
- public ProfilerPanelData(List profileData, List markersData) {
+ public ProfilerPanelData(
+ List profileData,
+ List markersData
+ ) {
this.profileData = profileData;
this.markersData = markersData;
}
}
public interface OnRightClickListener {
+
/**
* Is called when right-clicked on bookmark.
*/
diff --git a/core/src/main/java/com/github/grishberg/profiler/chart/CalledStacktrace.kt b/core/src/main/java/com/github/grishberg/profiler/chart/CalledStacktrace.kt
index 030cc13..041654b 100644
--- a/core/src/main/java/com/github/grishberg/profiler/chart/CalledStacktrace.kt
+++ b/core/src/main/java/com/github/grishberg/profiler/chart/CalledStacktrace.kt
@@ -39,7 +39,7 @@ class CalledStacktrace(
addChildrenStrategy = findChildrenStrategy
clearAllData()
lastSelectedElement = profileData
- renderer.currentThreadId = threadId
+ renderer.setCurrentThreadId(threadId)
if (foundProfileData.isNotEmpty()) {
dependenciesFoundAction?.onDependenciesFound(foundProfileData)
@@ -68,7 +68,7 @@ class CalledStacktrace(
if (!found.name.endsWith(".")) {
return
}
- renderer.currentThreadId = threadId
+ renderer.setCurrentThreadId(threadId)
lastSelectedElement = found
//TODO: use coroutine
@@ -111,7 +111,7 @@ class CalledStacktrace(
) {
addChildrenStrategy = findDaggerCallerMethodStrategy
clearAllData()
- renderer.currentThreadId = threadId
+ renderer.setCurrentThreadId(threadId)
lastSelectedElement = found
lastDaggerFactory = null
@@ -180,7 +180,7 @@ class CalledStacktrace(
fun removeElements() {
lastSelectedElement = null
- renderer.currentThreadId = -1
+ renderer.setCurrentThreadId(-1)
clearAllData()
}
diff --git a/core/src/main/java/com/github/grishberg/profiler/chart/ElementsSelectionRenderer.kt b/core/src/main/java/com/github/grishberg/profiler/chart/ElementsSelectionRenderer.kt
index 2500f60..7c0c075 100644
--- a/core/src/main/java/com/github/grishberg/profiler/chart/ElementsSelectionRenderer.kt
+++ b/core/src/main/java/com/github/grishberg/profiler/chart/ElementsSelectionRenderer.kt
@@ -7,7 +7,10 @@ import java.awt.Graphics2D
import java.awt.geom.AffineTransform
interface SelectionRenderer {
- var currentThreadId: Int
+ val currentThreadId: Int
+
+ fun setCurrentThreadId(id: Int)
+
fun draw(
g: Graphics2D, at: AffineTransform, fm: FontMetrics, threadId: Int,
selected: ProfileRectangle?,
@@ -40,7 +43,13 @@ class ElementsSelectionRenderer(
private val callTraceItems = mutableListOf()
private var callerRectangles = mutableListOf()
- override var currentThreadId = -1
+ private var _currentThreadId = -1
+ override val currentThreadId
+ get() = _currentThreadId
+
+ override fun setCurrentThreadId(id: Int) {
+ _currentThreadId = id
+ }
override fun draw(
g: Graphics2D, at: AffineTransform, fm: FontMetrics, threadId: Int,
@@ -131,7 +140,7 @@ class ElementsSelectionRenderer(
}
override fun clear() {
- currentThreadId = -1
+ _currentThreadId = -1
callTraceItems.clear()
callerRectangles.clear()
}
diff --git a/core/src/main/java/com/github/grishberg/profiler/chart/Grid.kt b/core/src/main/java/com/github/grishberg/profiler/chart/Grid.kt
index 9809964..3e5ed0c 100644
--- a/core/src/main/java/com/github/grishberg/profiler/chart/Grid.kt
+++ b/core/src/main/java/com/github/grishberg/profiler/chart/Grid.kt
@@ -7,7 +7,6 @@ import java.awt.FontMetrics
import java.awt.Graphics2D
import java.awt.geom.AffineTransform
import java.awt.geom.Line2D
-import java.util.ArrayList
private const val AXISES_COUNT = 10
private const val MINIMUM_DURATION = 1.0 / 1000000000.0
@@ -21,6 +20,7 @@ class Grid(
private val labelFont: Font,
private val labelFontMetrics: FontMetrics
) {
+
private val labelColor = Color(246, 255, 241)
private val lineColor = Color(191, 198, 187, 120)
@@ -60,6 +60,8 @@ class Grid(
return
}
val transformedScreenWidth = screenRight - screenLeft
+ //adjustScale(screenWidth.toDouble())
+
while (transformedScreenWidth < minScreenRange) {
if (distance / k < MINIMUM_DURATION) {
return
@@ -84,6 +86,7 @@ class Grid(
while (distance > 0 && firstLineX < screenLeft && firstLineX - distance < screenLeft) {
firstLineX += distance
}
+
var x: Double = firstLineX
var n = 0
diff --git a/core/src/main/java/com/github/grishberg/profiler/chart/preview/PreviewImageFactoryImpl.kt b/core/src/main/java/com/github/grishberg/profiler/chart/preview/PreviewImageFactoryImpl.kt
index 1869d6f..5044ebd 100644
--- a/core/src/main/java/com/github/grishberg/profiler/chart/preview/PreviewImageFactoryImpl.kt
+++ b/core/src/main/java/com/github/grishberg/profiler/chart/preview/PreviewImageFactoryImpl.kt
@@ -18,16 +18,16 @@ class PreviewImageFactoryImpl(
private val methodHeight = 2
override fun createPreview(
- width: Int,
- height: Int,
+ panelWidthPx: Int,
+ panelHeightPx: Int,
result: AnalyzerResult,
threadId: Int,
previewType: PreviewType
): BufferedImage {
- val image = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
+ val image = BufferedImage(panelWidthPx, panelHeightPx, BufferedImage.TYPE_INT_RGB)
val g = image.createGraphics()
- drawMethods(result, threadId, previewType, g, width, height)
+ drawMethods(result, threadId, previewType, g, panelWidthPx, panelHeightPx)
return image
}
@@ -41,13 +41,18 @@ class PreviewImageFactoryImpl(
g.fillRect(0, 0, width, height)
g.scale(1.0, 0.5)
+ val minLeft = when (previewType) {
+ PreviewType.PREVIEW_GLOBAL -> result.globalTimeBounds.getValue(threadId).minTime
+ PreviewType.PREVIEW_THREAD -> result.threadTimeBounds.getValue(threadId).minTime
+ PreviewType.THREAD_SWITCHER -> result.globalTimeBounds.getValue(result.mainThreadId).minTime
+ }
val maxRight = when (previewType) {
PreviewType.PREVIEW_GLOBAL -> result.globalTimeBounds.getValue(threadId).maxTime
PreviewType.PREVIEW_THREAD -> result.threadTimeBounds.getValue(threadId).maxTime
PreviewType.THREAD_SWITCHER -> result.globalTimeBounds.getValue(result.mainThreadId).maxTime
}
- val widthFactor: Double = maxRight / width
+ val widthFactor: Double = (maxRight - minLeft) / width
val methods = result.data.getValue(threadId)
@@ -71,7 +76,7 @@ class PreviewImageFactoryImpl(
val w = (right - left) / widthFactor
val h = bottom - top
- val l = left / widthFactor
+ val l = (left - minLeft) / widthFactor
g.fillRect(l.toInt(), top, w.toInt(), h)
}
@@ -93,7 +98,7 @@ class PreviewImageFactoryImpl(
val w = (right - left) / widthFactor
val h = result.maxLevel * methodHeight
- val l = left / widthFactor
+ val l = (left - minLeft) / widthFactor
g.color = bookmark.color
g.fillRect(l.toInt(), 0, ceil(w).toInt(), h)
}
diff --git a/core/src/main/java/com/github/grishberg/profiler/comparator/aggregator/FlameChartAggregator.kt b/core/src/main/java/com/github/grishberg/profiler/comparator/aggregator/FlameChartAggregator.kt
index 0112b9c..4a3efe3 100644
--- a/core/src/main/java/com/github/grishberg/profiler/comparator/aggregator/FlameChartAggregator.kt
+++ b/core/src/main/java/com/github/grishberg/profiler/comparator/aggregator/FlameChartAggregator.kt
@@ -6,6 +6,7 @@ import com.github.grishberg.profiler.comparator.findAllOf
import com.github.grishberg.profiler.comparator.aggregator.model.AggregatedFlameProfileDataImpl
import com.github.grishberg.profiler.comparator.aggregator.model.ComparableFlameChildHolder
import com.github.grishberg.profiler.comparator.aggregator.model.FlameProfileData
+import com.github.grishberg.profiler.core.ExtendedData
import kotlin.math.min
private const val INCLUDE_METHOD_THRESHOLD = 0.4
@@ -135,6 +136,7 @@ class FlameChartAggregator {
override val threadEndTimeInMillisecond = Double.MAX_VALUE
override val threadSelfTime = Double.MAX_VALUE
override val threadStartTimeInMillisecond = Double.MIN_VALUE
+ override val extendedData: ExtendedData? = null
}
processChildrenToAggregate(listOf(fakeParent), fakeRoot)
diff --git a/core/src/main/java/com/github/grishberg/profiler/core/AnalyzerResult.kt b/core/src/main/java/com/github/grishberg/profiler/core/AnalyzerResult.kt
index 092ee08..f28fe62 100644
--- a/core/src/main/java/com/github/grishberg/profiler/core/AnalyzerResult.kt
+++ b/core/src/main/java/com/github/grishberg/profiler/core/AnalyzerResult.kt
@@ -8,4 +8,6 @@ interface AnalyzerResult {
val threads: List
val mainThreadId: Int
val startTimeUs: Long // start recording time in System.upTimeInMs()
+ val minThreadTime: Double
+ val minGlobalTime: Double
}
diff --git a/core/src/main/java/com/github/grishberg/profiler/core/ExtendedData.kt b/core/src/main/java/com/github/grishberg/profiler/core/ExtendedData.kt
new file mode 100644
index 0000000..8042ed1
--- /dev/null
+++ b/core/src/main/java/com/github/grishberg/profiler/core/ExtendedData.kt
@@ -0,0 +1,35 @@
+package com.github.grishberg.profiler.core
+
+sealed interface ExtendedData {
+ val tag: String?
+ val id:String
+ val fullName: String
+ data class CppFunctionData(
+ override val tag: String?,
+ override val id: String,
+ override val fullName: String,
+ val classOrNamespace: String,
+ val parameters:List,
+ val isUserCode: Boolean,
+ val fileName: String,
+ val vAddress: Long,
+ ):ExtendedData
+
+ data class JavaMethodData(override val tag: String?,
+ override val id: String,
+ override val fullName: String,
+ val className: String,
+ val signature: String,
+ ):ExtendedData
+
+ data class NoSymbolData(override val tag: String?,
+ override val id: String,
+ override val fullName: String,
+ val isKernel: Boolean,
+ ):ExtendedData
+
+ data class SyscallData(override val tag: String?,
+ override val id: String,
+ override val fullName: String,
+ ):ExtendedData
+}
diff --git a/core/src/main/java/com/github/grishberg/profiler/core/ProfileData.kt b/core/src/main/java/com/github/grishberg/profiler/core/ProfileData.kt
index 5a0b057..4043781 100644
--- a/core/src/main/java/com/github/grishberg/profiler/core/ProfileData.kt
+++ b/core/src/main/java/com/github/grishberg/profiler/core/ProfileData.kt
@@ -11,4 +11,5 @@ interface ProfileData {
val globalSelfTime: Double
val parent: ProfileData?
val children: List
+ val extendedData: ExtendedData?
}
diff --git a/core/src/main/java/com/github/grishberg/profiler/ui/Main.java b/core/src/main/java/com/github/grishberg/profiler/ui/Main.java
index 4507d2c..06f774f 100644
--- a/core/src/main/java/com/github/grishberg/profiler/ui/Main.java
+++ b/core/src/main/java/com/github/grishberg/profiler/ui/Main.java
@@ -1264,7 +1264,16 @@ protected void done() {
systraceRecords);
}
pluginsFacade.setCurrentTraceProfiler(traceContainerResult);
- ThreadItem firstThread = resultContainer.getResult().getThreads().get(0);
+ int mainThreadId = resultContainer.getResult().getMainThreadId();
+ List threads = resultContainer.getResult().getThreads();
+ ThreadItem firstThread = threads.get(0);
+
+ for (int i = 0; i < threads.size(); i++) {
+ if (mainThreadId == threads.get(i).getThreadId()){
+ firstThread = threads.get(i);
+ break;
+ }
+ }
switchThreadsButton.switchThread(firstThread);
pluginsFacade.setCurrentThread(firstThread);
} catch (Exception e) {
diff --git a/core/src/main/java/com/github/grishberg/profiler/ui/ZoomAndPanDelegate.java b/core/src/main/java/com/github/grishberg/profiler/ui/ZoomAndPanDelegate.java
index 733d994..2abd628 100644
--- a/core/src/main/java/com/github/grishberg/profiler/ui/ZoomAndPanDelegate.java
+++ b/core/src/main/java/com/github/grishberg/profiler/ui/ZoomAndPanDelegate.java
@@ -39,6 +39,7 @@ public enum VerticalAlign {
private final Rectangle visibleScreenBounds = new Rectangle();
private ScrollBoundsStrategy boundsStrategy;
+ private Point2D dataLeftTopCorner = new Point2D.Double();
private Point2D dataRightBottomCorner = new Point2D.Double();
public ZoomAndPanDelegate(Component targetComponent, int topOffset, ScrollBoundsStrategy boundsStrategy) {
@@ -50,10 +51,12 @@ public ZoomAndPanDelegate(Component targetComponent, int topOffset, ScrollBounds
targetComponent.addMouseWheelListener(this);
}
- public void updateRightBottomCorner(double maxX, double maxY) {
+ public void updateBounds(double minX, double maxX, double maxY) {
dataRightBottomCorner.setLocation(maxX, maxY);
+ dataLeftTopCorner.setLocation(minX, 0.0);
}
+
public void setMouseEventsListener(MouseEventsListener l) {
mouseEventsListener = l;
}
diff --git a/core/src/test/kotlin/com/github/grishberg/profiler/analyzer/SimplePerfAnalyzerTest.kt b/core/src/test/kotlin/com/github/grishberg/profiler/analyzer/SimplePerfAnalyzerTest.kt
new file mode 100644
index 0000000..e0a812e
--- /dev/null
+++ b/core/src/test/kotlin/com/github/grishberg/profiler/analyzer/SimplePerfAnalyzerTest.kt
@@ -0,0 +1,36 @@
+package com.github.grishberg.profiler.analyzer
+
+import com.github.grishberg.profiler.analyzer.converter.NoOpNameConverter
+import com.github.grishberg.profiler.common.AppLogger
+import com.github.grishberg.profiler.common.TestLogger
+import java.io.File
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertNotNull
+import org.junit.Test
+
+internal class SimplePerfAnalyzerTest {
+
+ private val logger: AppLogger = TestLogger()
+ private val underTest = SimplePerfAnalyzer(logger, NoOpNameConverter)
+
+ @Test
+ fun `test simpleperf analyzer`() {
+ val testFile = getSimplePerfFile()
+ val result = underTest.analyze(testFile)
+
+ assertEquals(24, result.threads.size)
+
+ val mainThreadData = result.data[result.mainThreadId]
+
+ assertNotNull(mainThreadData)
+
+ assertEquals(38319, mainThreadData!!.size)
+
+ }
+
+ private fun getSimplePerfFile(): File {
+ val classLoader = javaClass.classLoader
+ val filePath = classLoader.getResource("simpleperf.trace")?.file ?: throw IllegalStateException()
+ return File(filePath)
+ }
+}
diff --git a/core/src/test/kotlin/com/github/grishberg/profiler/analyzer/TraceAnalyzerTest.kt b/core/src/test/kotlin/com/github/grishberg/profiler/analyzer/TraceAnalyzerTest.kt
new file mode 100644
index 0000000..1fb5fe8
--- /dev/null
+++ b/core/src/test/kotlin/com/github/grishberg/profiler/analyzer/TraceAnalyzerTest.kt
@@ -0,0 +1,21 @@
+package com.github.grishberg.profiler.analyzer
+
+import com.github.grishberg.profiler.common.TestLogger
+import java.io.File
+import org.junit.Test
+
+internal class TraceAnalyzerTest {
+ private val underTest = TraceAnalyzer(TestLogger())
+
+ @Test
+ fun `test simplePerf trace parsing`(){
+ underTest.analyze(getSimplePerfFile())
+ }
+
+ private fun getSimplePerfFile(): File {
+ val classLoader = javaClass.classLoader
+ val filePath = classLoader.getResource("simpleperf.trace")?.file ?: throw IllegalStateException()
+ return File(filePath)
+ }
+
+}
diff --git a/core/src/test/kotlin/com/github/grishberg/profiler/common/TestLogger.kt b/core/src/test/kotlin/com/github/grishberg/profiler/common/TestLogger.kt
new file mode 100644
index 0000000..cc8a0bc
--- /dev/null
+++ b/core/src/test/kotlin/com/github/grishberg/profiler/common/TestLogger.kt
@@ -0,0 +1,24 @@
+package com.github.grishberg.profiler.common
+
+class TestLogger: AppLogger {
+
+ override fun d(msg: String) {
+ println(msg)
+ }
+
+ override fun e(msg: String) {
+ println(msg)
+ }
+
+ override fun e(msg: String, t: Throwable) {
+ println(msg)
+ }
+
+ override fun w(msg: String) {
+ println(msg)
+ }
+
+ override fun i(msg: String) {
+ println(msg)
+ }
+}
diff --git a/core/src/test/resources/simpleperf.trace b/core/src/test/resources/simpleperf.trace
new file mode 100644
index 0000000..ce0901d
Binary files /dev/null and b/core/src/test/resources/simpleperf.trace differ
diff --git a/gradle.properties b/gradle.properties
index 5aec92b..926d556 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,7 +8,7 @@ studioCompilePath=/Applications/Android Studio.app/Contents
pluginGroup = com.github.grishberg
pluginName = android-methods-profiler
-yampVersion = 24.07.31
+yampVersion = 24.08.22
# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
# for insight into build numbers and IntelliJ Platform versions.