xref: /aosp_15_r20/external/leakcanary2/shark/src/main/java/shark/LeakTraceObject.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1 package shark
2 
3 import shark.LeakTrace.Companion.ZERO_WIDTH_SPACE
4 import shark.LeakTraceObject.LeakingStatus
5 import shark.LeakTraceObject.LeakingStatus.LEAKING
6 import shark.LeakTraceObject.LeakingStatus.NOT_LEAKING
7 import shark.LeakTraceObject.LeakingStatus.UNKNOWN
8 import shark.internal.lastSegment
9 import java.io.Serializable
10 import java.util.Locale
11 import kotlin.math.ln
12 import kotlin.math.pow
13 
14 data class LeakTraceObject(
15   val type: ObjectType,
16   /**
17    * Class name of the object.
18    * The class name format is the same as what would be returned by [Class.getName].
19    */
20   val className: String,
21 
22   /**
23    * Labels that were computed during analysis. A label provides extra information that helps
24    * understand the state of the leak trace object.
25    */
26   val labels: Set<String>,
27   val leakingStatus: LeakingStatus,
28   val leakingStatusReason: String,
29   /**
30    * The minimum number of bytes which would be freed if all references to this object were
31    * released. Not null only if the retained heap size was computed AND [leakingStatus] is
32    * equal to [LeakingStatus.UNKNOWN] or [LeakingStatus.LEAKING].
33    */
34   val retainedHeapByteSize: Int?,
35   /**
36    * The minimum number of objects which would be unreachable if all references to this object were
37    * released. Not null only if the retained heap size was computed AND [leakingStatus] is
38    * equal to [LeakingStatus.UNKNOWN] or [LeakingStatus.LEAKING].
39    */
40   val retainedObjectCount: Int?
41 ) : Serializable {
42 
43   /**
44    * Returns {@link #className} without the package, ie stripped of any string content before the
45    * last period (included).
46    */
47   val classSimpleName: String get() = className.lastSegment('.')
48 
49   val typeName
50     get() = type.name.toLowerCase(Locale.US)
51 
toStringnull52   override fun toString(): String {
53     val firstLinePrefix = ""
54     val additionalLinesPrefix = "$ZERO_WIDTH_SPACE  "
55     return toString(firstLinePrefix, additionalLinesPrefix, true)
56   }
57 
toStringnull58   internal fun toString(
59     firstLinePrefix: String,
60     additionalLinesPrefix: String,
61     showLeakingStatus: Boolean,
62     typeName: String = this.typeName
63   ): String {
64     val leakStatus = when (leakingStatus) {
65       UNKNOWN -> "UNKNOWN"
66       NOT_LEAKING -> "NO ($leakingStatusReason)"
67       LEAKING -> "YES ($leakingStatusReason)"
68     }
69 
70     var result = ""
71     result += "$firstLinePrefix$className $typeName"
72     if (showLeakingStatus) {
73       result += "\n${additionalLinesPrefix}Leaking: $leakStatus"
74     }
75 
76     if (retainedHeapByteSize != null) {
77       val humanReadableRetainedHeapSize =
78         humanReadableByteCount(retainedHeapByteSize.toLong())
79       result += "\n${additionalLinesPrefix}Retaining $humanReadableRetainedHeapSize in $retainedObjectCount objects"
80     }
81     for (label in labels) {
82       result += "\n${additionalLinesPrefix}$label"
83     }
84     return result
85   }
86 
87   enum class ObjectType {
88     CLASS,
89     ARRAY,
90     INSTANCE
91   }
92 
93   enum class LeakingStatus {
94     /** The object was needed and therefore expected to be reachable. */
95     NOT_LEAKING,
96 
97     /** The object was no longer needed and therefore expected to be unreachable. */
98     LEAKING,
99 
100     /** No decision can be made about the provided object. */
101     UNKNOWN;
102   }
103 
104   companion object {
105     private const val serialVersionUID = -3616216391305196341L
106 
107     // https://stackoverflow.com/a/3758880
humanReadableByteCountnull108     private fun humanReadableByteCount(bytes: Long): String {
109       val unit = 1000
110       if (bytes < unit) return "$bytes B"
111       val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
112       val pre = "kMGTPE"[exp - 1]
113       return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
114     }
115   }
116 }