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