xref: /aosp_15_r20/external/perfmark/doc/fix-stop-task.md (revision 27e8546d0ef5f99cf83d5252272c7dd38d18d29a)
1*27e8546dSMatt Gilbride# Unbalancing PerfMark Calls
2*27e8546dSMatt Gilbride
3*27e8546dSMatt GilbrideTl;Dr: `PerfMark.stopTask()` will no longer use task name or tags.
4*27e8546dSMatt Gilbride
5*27e8546dSMatt Gilbride# Background
6*27e8546dSMatt Gilbride
7*27e8546dSMatt GilbridePerfMark was designed as a replacement for an existing tracing library called
8*27e8546dSMatt GilbrideJEndoscope.  JEndoscope used annotations to indicate which methods to trace.
9*27e8546dSMatt GilbrideWhen the classes were loaded, a Java agent would rewrite the methods to include
10*27e8546dSMatt Gilbridethe tracing calls if JEndoscope was enabled.   One of the benefits of this API
11*27e8546dSMatt Gilbrideis that it isn't possible to forget to add the closing trace call, which
12*27e8546dSMatt Gilbrideindicates a span is complete.  Thus, the danger of mismatched start and stop
13*27e8546dSMatt Gilbridetrace calls was not a problem.
14*27e8546dSMatt Gilbride
15*27e8546dSMatt GilbridePerfMark was designed to not rely on an agent for tracing, and thus could not
16*27e8546dSMatt Gilbrideuse annotations to indicate which calls to trace.   To avoid the risk for API
17*27e8546dSMatt Gilbrideusers, PerfMark offered matching stop trace calls that included the relevant
18*27e8546dSMatt Gilbridetask name and tag.  An example usage:
19*27e8546dSMatt Gilbride
20*27e8546dSMatt Gilbride```java
21*27e8546dSMatt GilbridePerfMark.startTask("makeRPC");
22*27e8546dSMatt GilbrideinvokeSomeRPC();
23*27e8546dSMatt GilbridePerfMark.stopTask("makeRPC");
24*27e8546dSMatt Gilbride```
25*27e8546dSMatt Gilbride
26*27e8546dSMatt GilbrideWhen PerfMark returned the traces collected, it was expected that the
27*27e8546dSMatt Gilbridestart task name matched the stop task name.  It could verify these to ensure
28*27e8546dSMatt Gilbridethe trace calls were properly balanced.  It was expected that a warning could
29*27e8546dSMatt Gilbridebe emitted to aid the user in fixing the mismatch.
30*27e8546dSMatt Gilbride
31*27e8546dSMatt GilbrideThere is an additional benefit to this API that was not needed in the previous
32*27e8546dSMatt GilbrideJEndoscope model.   PerfMark allows tracing to be dynamically enabled and
33*27e8546dSMatt Gilbridedisable at runtime.   This means that tracing may be enabled in the middle of a
34*27e8546dSMatt Gilbridestart-stop pair.   This would mean that only the stop would be recorded,
35*27e8546dSMatt Gilbridewithout knowing the starting task name.   Even if the calls were properly
36*27e8546dSMatt Gilbridebalanced, if only the stop edge is present, there isn't a way to reconstruct the
37*27e8546dSMatt Gilbridename of the task.   Having the task name in both the start and the stop solves
38*27e8546dSMatt Gilbridethis problem.   (The same problem exists on the opposite side too, if the
39*27e8546dSMatt Gilbridetraces are read before the task completes.)
40*27e8546dSMatt Gilbride
41*27e8546dSMatt GilbrideFinally, having the task name in both the start and stop has a symmetry to it
42*27e8546dSMatt Gilbridethat made it look correct.  It was clear which task was being stopped, even if
43*27e8546dSMatt Gilbridethe start trace was many lines above.
44*27e8546dSMatt Gilbride
45*27e8546dSMatt Gilbride# Problems with stopTask().
46*27e8546dSMatt Gilbride
47*27e8546dSMatt GilbrideAt the time of the design, the aforementioned choices made sense.   However,
48*27e8546dSMatt Gilbrideseveral problems arose from this pattern that can't easily be worked around.
49*27e8546dSMatt Gilbride
50*27e8546dSMatt Gilbride## Stuttering Arguments Makes Code Verbose
51*27e8546dSMatt Gilbride
52*27e8546dSMatt GilbrideThe PerfMark API is very fast, but it accomplishes this by trusting the
53*27e8546dSMatt Gilbrideprogrammer to be careful.   Because of this, a safe invocation of the trace
54*27e8546dSMatt Gilbrideinvolves Wrapping the code in a try-finally block:
55*27e8546dSMatt Gilbride
56*27e8546dSMatt Gilbride```java
57*27e8546dSMatt GilbridePerfMark.startTask("makeRPC");
58*27e8546dSMatt Gilbridetry {
59*27e8546dSMatt Gilbride  invokeSomeRPC();
60*27e8546dSMatt Gilbride} finally {
61*27e8546dSMatt Gilbride  PerfMark.stopTask("makeRPC");
62*27e8546dSMatt Gilbride}
63*27e8546dSMatt Gilbride```
64*27e8546dSMatt Gilbride
65*27e8546dSMatt GilbrideThis costs an indent block, as well as 3 extra lines per trace call.   This
66*27e8546dSMatt Gilbrideputs us into verbosity debt.  Having multiple such calls makes the code pyramid
67*27e8546dSMatt Gilbrideoff the page, decreasing the readability of the code.
68*27e8546dSMatt Gilbride
69*27e8546dSMatt GilbrideIn order to repay this debt, dropping the redundant task name (and tag) makes
70*27e8546dSMatt Gilbridethe code less unpleasant to look at.
71*27e8546dSMatt Gilbride
72*27e8546dSMatt GilbrideWhile the duplicated names do have technical reasons (mentioned above), users
73*27e8546dSMatt Gilbridefeedback indicated the verbosity was more of a problem.  Given the relatively
74*27e8546dSMatt Gilbriderare problems with mismatched tags and split traces, addressing verbosity is
75*27e8546dSMatt Gilbridemore important.
76*27e8546dSMatt Gilbride
77*27e8546dSMatt Gilbride## try-with-resources Imposes an Allocation cost
78*27e8546dSMatt Gilbride
79*27e8546dSMatt GilbrideOne of the ways Java helps programmers clean up resources is the
80*27e8546dSMatt Gilbridetry-with-resources idiom.  PerfMark explored using this as an alternative to
81*27e8546dSMatt Gilbridebare start-stop calls with the following usage:
82*27e8546dSMatt Gilbride
83*27e8546dSMatt Gilbride```java
84*27e8546dSMatt Gilbridetry (PerfMarkTask task = PerfMark.task("makeRPC")) {
85*27e8546dSMatt Gilbride  invokeSomeRPC();
86*27e8546dSMatt Gilbride}
87*27e8546dSMatt Gilbride```
88*27e8546dSMatt Gilbride
89*27e8546dSMatt GilbrideIn an ideal world, `PerfMarkTask` would be a no-op object when PerfMark is
90*27e8546dSMatt Gilbridedisabled, and be a singleton object when enabled.   This would in turn call the
91*27e8546dSMatt Gilbrideappropriate start-stop methods.  However, because the preferred start-stop calls
92*27e8546dSMatt Gilbride*both* require the task name, a new object must be allocated to capture the name
93*27e8546dSMatt Gilbride(and tag).   This forces the user to choose between a runtime cost and safe
94*27e8546dSMatt Gilbrideprogramming practice.
95*27e8546dSMatt Gilbride
96*27e8546dSMatt Gilbride## Lazy Task Names complicate API.
97*27e8546dSMatt Gilbride
98*27e8546dSMatt GilbrideAnother way PerfMark explored making trace calls cheaper was to use a lambda
99*27e8546dSMatt Gilbridebase task naming call.  Sometimes task names are not compile time constant, and
100*27e8546dSMatt Gilbridein fact may have a significant runtime cost.  Eagerly calculating these imposes
101*27e8546dSMatt Gilbridea runtime cost, even when PerfMark is disabled.   The following API shows the
102*27e8546dSMatt Gilbrideproposed usage:
103*27e8546dSMatt Gilbride
104*27e8546dSMatt Gilbride```java
105*27e8546dSMatt GilbridePerfMark.startTask(rpc, RPC::nameAndId);
106*27e8546dSMatt Gilbride// Same as PerfMark.startTask(rpc.nameAndId());
107*27e8546dSMatt Gilbride```
108*27e8546dSMatt Gilbride
109*27e8546dSMatt GilbrideThe use of a method handle avoids the unwanted calculation of the `nameAndId`
110*27e8546dSMatt Gilbridestring, which the JVM may not be able to eliminate otherwise.
111*27e8546dSMatt Gilbride
112*27e8546dSMatt GilbrideThe problem becomes more awkward when encouraging users to use matching names
113*27e8546dSMatt Gilbridefor start and stop:
114*27e8546dSMatt Gilbride
115*27e8546dSMatt Gilbride```java
116*27e8546dSMatt GilbridePerfMark.startTask(rpc, RPC::nameAndId);
117*27e8546dSMatt GilbrideinvokeSomeRPC();
118*27e8546dSMatt GilbridePerfMark.stopTask(rpc, RPC::nameAndId);
119*27e8546dSMatt Gilbride```
120*27e8546dSMatt Gilbride
121*27e8546dSMatt GilbrideWill the expensive function be calculated twice?  What happens if the function
122*27e8546dSMatt Gilbrideis not idempotent?  The user has used the API to the best of their ability, but
123*27e8546dSMatt Gilbridethe skew problems are still present.  Trying to solve this is difficult without
124*27e8546dSMatt Gilbrideimposing other costs, and being hard to reason about.
125*27e8546dSMatt Gilbride
126*27e8546dSMatt Gilbride### Permutations in API size.
127*27e8546dSMatt Gilbride
128*27e8546dSMatt GilbridePerfMark has several overloads of the start - stop calls:
129*27e8546dSMatt Gilbride
130*27e8546dSMatt Gilbride* `startTask(String name);`
131*27e8546dSMatt Gilbride* `startTask(String name, Tag tag);`
132*27e8546dSMatt Gilbride* `startTask(String name, String subName);`
133*27e8546dSMatt Gilbride
134*27e8546dSMatt Gilbride
135*27e8546dSMatt GilbrideWhile exploring lambdas to make lazy variants of these methods, the API grew
136*27e8546dSMatt Gilbridesubstantially.  As the number of start methods grows, so too must the stop
137*27e8546dSMatt Gilbridemethods.   This makes usage more error prone since there may be 10s of possible
138*27e8546dSMatt Gilbrideoverloads to invoke.  This is an unnecessary burden on the user, since the
139*27e8546dSMatt Gilbridelibrary already must deal with the possibility of mismatched names.
140*27e8546dSMatt Gilbride
141*27e8546dSMatt Gilbride
142*27e8546dSMatt Gilbride## Agent Byte Code is more difficult
143*27e8546dSMatt Gilbride
144*27e8546dSMatt GilbrideWhile JEndoscope required an agent to work, PerfMark can be optionally enhanced
145*27e8546dSMatt Gilbrideby using one.  It can add line and file information about the calls
146*27e8546dSMatt Gilbridewhen instrumenting classes.  However, it's difficult to rewrite this byte code
147*27e8546dSMatt Gilbridewhen it depends on the possibly changing task name.  JEndoscope worked around
148*27e8546dSMatt Gilbridethis problem by forcing all tracing calls to be compile time constants, and
149*27e8546dSMatt Gilbrideforcing rewritten calls to use the `ldc` JVM instruction.
150*27e8546dSMatt Gilbride
151*27e8546dSMatt Gilbride# Proposed Change: one stopTask()
152*27e8546dSMatt Gilbride
153*27e8546dSMatt GilbrideTo avoid the problems above, a singular `stopTask()` method overload is
154*27e8546dSMatt Gilbrideproposed.  The existing stopTask overloads will no longer be recommended, and
155*27e8546dSMatt Gilbridebehave as the single `stopTask()` method with extra info.  The tracing library
156*27e8546dSMatt Gilbridewill be updated to not expect this info.
157*27e8546dSMatt Gilbride
158*27e8546dSMatt GilbrideThis solves each of the problems above.   The usage of PerfMark becomes less
159*27e8546dSMatt Gilbrideverbose.  There is no longer a runtime cost to using the
160*27e8546dSMatt Gilbridetry-with-resources idiom.  There is no possibility of skew between start and
161*27e8546dSMatt Gilbridestop names and tags.  The API will grow slightly to accommodate this new method,
162*27e8546dSMatt Gilbridebut will prevent the doubling of methods.
163*27e8546dSMatt Gilbride
164*27e8546dSMatt GilbrideThe benefits brought by having the name twice are seldom seen, and are paid
165*27e8546dSMatt Gilbrideat runtime.  The names passed to stopTask() are stored, but are never used
166*27e8546dSMatt Gilbridewhen visualizing trace diagrams.
167