1 /*
2  * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 @file:Suppress("UNUSED", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
6 
7 package kotlinx.coroutines.debug
8 
9 import kotlinx.coroutines.*
10 import kotlinx.coroutines.debug.internal.*
11 import java.io.*
12 import java.lang.management.*
13 import kotlin.coroutines.*
14 
15 /**
16  * Debug probes support.
17  *
18  * Debug probes is a dynamic attach mechanism which installs multiple hooks into coroutines machinery.
19  * It slows down all coroutine-related code, but in return provides a lot of diagnostic information, including
20  * asynchronous stack-traces and coroutine dumps (similar to [ThreadMXBean.dumpAllThreads] and `jstack` via [DebugProbes.dumpCoroutines].
21  * All introspecting methods throw [IllegalStateException] if debug probes were not installed.
22  *
23  * Installed hooks:
24  *
25  * * `probeCoroutineResumed` is invoked on every [Continuation.resume].
26  * * `probeCoroutineSuspended` is invoked on every continuation suspension.
27  * * `probeCoroutineCreated` is invoked on every coroutine creation using stdlib intrinsics.
28  *
29  * Overhead:
30  *  * Every created coroutine is stored in a concurrent hash map and hash map is looked up and
31  *    updated on each suspension and resumption.
32  *  * If [DebugProbes.enableCreationStackTraces] is enabled, stack trace of the current thread is captured on
33  *    each created coroutine that is a rough equivalent of throwing an exception per each created coroutine.
34  */
35 @ExperimentalCoroutinesApi
36 public object DebugProbes {
37 
38     /**
39      * Whether coroutine creation stack traces should be sanitized.
40      * Sanitization removes all frames from `kotlinx.coroutines` package except
41      * the first one and the last one to simplify diagnostic.
42      */
43     public var sanitizeStackTraces: Boolean
44         get() = DebugProbesImpl.sanitizeStackTraces
45         set(value) {
46             DebugProbesImpl.sanitizeStackTraces = value
47         }
48 
49     /**
50      * Whether coroutine creation stack traces should be captured.
51      * When enabled, for each created coroutine a stack trace of the current
52      * thread is captured and attached to the coroutine.
53      * This option can be useful during local debug sessions, but is recommended
54      * to be disabled in production environments to avoid stack trace dumping overhead.
55      */
56     public var enableCreationStackTraces: Boolean
57         get() = DebugProbesImpl.enableCreationStackTraces
58         set(value) {
59             DebugProbesImpl.enableCreationStackTraces = value
60         }
61 
62     /**
63      * Determines whether debug probes were [installed][DebugProbes.install].
64      */
65     public val isInstalled: Boolean get() = DebugProbesImpl.isInstalled
66 
67     /**
68      * Installs a [DebugProbes] instead of no-op stdlib probes by redefining
69      * debug probes class using the same class loader as one loaded [DebugProbes] class.
70      */
installnull71     public fun install() {
72         DebugProbesImpl.install()
73     }
74 
75     /**
76      * Uninstall debug probes.
77      */
uninstallnull78     public fun uninstall() {
79         DebugProbesImpl.uninstall()
80     }
81 
82     /**
83      * Invokes given block of code with installed debug probes and uninstall probes in the end.
84      */
withDebugProbesnull85     public inline fun withDebugProbes(block: () -> Unit) {
86         install()
87         try {
88             block()
89         } finally {
90             uninstall()
91         }
92     }
93 
94     /**
95      * Returns string representation of the coroutines [job] hierarchy with additional debug information.
96      * Hierarchy is printed from the [job] as a root transitively to all children.
97      */
jobToStringnull98     public fun jobToString(job: Job): String = DebugProbesImpl.hierarchyToString(job)
99 
100     /**
101      * Returns string representation of all coroutines launched within the given [scope].
102      * Throws [IllegalStateException] if the scope has no a job in it.
103      */
104     public fun scopeToString(scope: CoroutineScope): String =
105         jobToString(scope.coroutineContext[Job] ?: error("Job is not present in the scope"))
106 
107     /**
108      * Prints [job] hierarchy representation from [jobToString] to the given [out].
109      */
110     public fun printJob(job: Job, out: PrintStream = System.out): Unit =
111         out.println(DebugProbesImpl.hierarchyToString(job))
112 
113     /**
114      * Prints all coroutines launched within the given [scope].
115      * Throws [IllegalStateException] if the scope has no a job in it.
116      */
117     public fun printScope(scope: CoroutineScope, out: PrintStream = System.out): Unit =
118        printJob(scope.coroutineContext[Job] ?: error("Job is not present in the scope"), out)
119 
120     /**
121      * Returns all existing coroutines info.
122      * The resulting collection represents a consistent snapshot of all existing coroutines at the moment of invocation.
123      */
124     public fun dumpCoroutinesInfo(): List<CoroutineInfo> = DebugProbesImpl.dumpCoroutinesInfo().map { CoroutineInfo(it) }
125 
126     /**
127      * Dumps all active coroutines into the given output stream, providing a consistent snapshot of all existing coroutines at the moment of invocation.
128      * The output of this method is similar to `jstack` or a full thread dump. It can be used as the replacement to
129      * "Dump threads" action.
130      *
131      * Example of the output:
132      * ```
133      * Coroutines dump 2018/11/12 19:45:14
134      *
135      * Coroutine "coroutine#42":StandaloneCoroutine{Active}@58fdd99, state: SUSPENDED
136      *     at MyClass$awaitData.invokeSuspend(MyClass.kt:37)
137      * (Coroutine creation stacktrace)
138      *     at MyClass.createIoRequest(MyClass.kt:142)
139      *     at MyClass.fetchData(MyClass.kt:154)
140      *     at MyClass.showData(MyClass.kt:31)
141      * ...
142      * ```
143      */
dumpCoroutinesnull144     public fun dumpCoroutines(out: PrintStream = System.out): Unit = DebugProbesImpl.dumpCoroutines(out)
145 }
146 
147 // Stubs which are injected as coroutine probes. Require direct match of signatures
148 internal fun probeCoroutineResumed(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineResumed(frame)
149 
150 internal fun probeCoroutineSuspended(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineSuspended(frame)
151 internal fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> =
152     DebugProbesImpl.probeCoroutineCreated(completion)
153