1 /*
2  * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 package org.openjdk.bench.java.lang;
24 
25 import java.lang.StackWalker.StackFrame;
26 import java.util.concurrent.TimeUnit;
27 import org.openjdk.jmh.annotations.Benchmark;
28 import org.openjdk.jmh.annotations.BenchmarkMode;
29 import org.openjdk.jmh.annotations.Mode;
30 import org.openjdk.jmh.annotations.OutputTimeUnit;
31 import org.openjdk.jmh.annotations.Param;
32 import org.openjdk.jmh.annotations.Scope;
33 import org.openjdk.jmh.annotations.State;
34 import org.openjdk.jmh.infra.Blackhole;
35 
36 /**
37  * Benchmarks for java.lang.StackWalker
38  */
39 @State(value=Scope.Benchmark)
40 @BenchmarkMode(Mode.AverageTime)
41 @OutputTimeUnit(TimeUnit.NANOSECONDS)
42 public class StackWalkBench {
43     private static final StackWalker WALKER_DEFAULT = StackWalker.getInstance();
44 
45     private static final StackWalker WALKER_CLASS =
46         StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
47 
48     // TestStack will add this number of calls to the call stack
49     @Param({"4", "100", "1000"})
50     // For more thorough testing, consider:
51     // @Param({"4", "10", "100", "256", "1000"})
52     public int depth;
53 
54     // Only used by swFilterCallerClass, to specify (roughly) how far back the
55     // call stack the target class will be found.  Not needed by other
56     // benchmarks, so not a @Param by default.
57     // @Param({"4"})
58     public int mark = 4;
59 
60     /** Build a call stack of a given size, then run trigger code in it.
61       * (Does not account for existing frames higher up in the JMH machinery).
62       */
63     public static class TestStack {
64         final long fence;
65         long current;
66         final Runnable trigger;
67 
TestStack(long max, Runnable trigger)68         public TestStack(long max, Runnable trigger) {
69           this.fence = max;
70           this.current = 0;
71           this.trigger = trigger;
72         }
73 
start()74         public void start() {
75             one();
76         }
77 
one()78         public void one() {
79             if (check()) {
80                 two();
81             }
82         }
83 
two()84         void two() {
85            if (check()) {
86               three();
87            }
88         }
89 
three()90         private void three() {
91             if (check()) {
92                one();
93             }
94         }
95 
check()96         boolean check() {
97             if (++current == fence) {
98                 trigger.run();
99                 return false;
100             } else {
101                 return true;
102             }
103         }
104     }
105 
106     /* Class to look for when testing filtering */
107     static class TestMarker {
call(MarkedTestStack test)108         public void call(MarkedTestStack test) {
109             test.marked();
110         }
111     }
112 
113     /** Call stack to test filtering.
114      *  TestMarker will make a call on the stack.
115      */
116     static class MarkedTestStack extends TestStack {
117         long mark;
118 
119         /**
120          * @param mark How far back the stack should the TestMarker be found?
121          */
MarkedTestStack(long max, long mark, Runnable trigger)122         public MarkedTestStack(long max, long mark, Runnable trigger) {
123             super(max, trigger);
124             if (mark > max) {
125                 throw new IllegalArgumentException("mark must be <= max");
126             }
127             this.mark = max - mark; // Count backwards from the completed call stack
128         }
129         @Override
start()130         public void start() {
131             if (mark == 0) {
132                 mark();
133             } else {
134                 super.one();
135             }
136         }
137         @Override
check()138         boolean check() {
139            if (++current == mark) {
140                mark();
141                return false;
142            } else if (current == fence) {
143               trigger.run();
144               return false;
145            } else {
146                return true;
147            }
148         }
mark()149         void mark() {
150             new TestMarker().call(this);
151         }
marked()152         public void marked() {
153             if (current < fence) {
154                 if (check()) {
155                     one();
156                 }
157             } else {
158                 trigger.run();
159             }
160         }
161     }
162 
163     /**
164      * StackWalker.forEach() with default options
165      */
166     @Benchmark
forEach_DefaultOpts(Blackhole bh)167     public void forEach_DefaultOpts(Blackhole bh) {
168         final Blackhole localBH = bh;
169         final boolean[] done = {false};
170         new TestStack(depth, new Runnable() {
171             public void run() {
172                 WALKER_DEFAULT.forEach(localBH::consume);
173                 done[0] = true;
174             }
175         }).start();
176         if (!done[0]) {
177             throw new RuntimeException();
178         }
179     }
180 
181     /**
182      * Use Stackwalker.walk() to fetch class names
183      */
184     @Benchmark
walk_ClassNames(Blackhole bh)185     public void walk_ClassNames(Blackhole bh) {
186         final Blackhole localBH = bh;
187         final boolean[] done = {false};
188         new TestStack(depth, new Runnable() {
189             public void run() {
190                 WALKER_DEFAULT.walk(s -> {
191                     s.map(StackFrame::getClassName).forEach(localBH::consume);
192                     return null;
193                 });
194                 done[0] = true;
195             }
196         }).start();
197         if (!done[0]) {
198             throw new RuntimeException();
199         }
200     }
201 
202     /**
203      * Use Stackwalker.walk() to fetch method names
204      */
205     @Benchmark
walk_MethodNames(Blackhole bh)206     public void walk_MethodNames(Blackhole bh) {
207         final Blackhole localBH = bh;
208         final boolean[] done = {false};
209         new TestStack(depth, new Runnable() {
210             public void run() {
211                 WALKER_DEFAULT.walk( s -> {
212                     s.map(StackFrame::getMethodName).forEach(localBH::consume);
213                     return null;
214                 });
215                 done[0] = true;
216             }
217         }).start();
218         if (!done[0]) {
219             throw new RuntimeException();
220         }
221     }
222 
223     /**
224      * Use Stackwalker.walk() to fetch declaring class instances
225      */
226     @Benchmark
walk_DeclaringClass(Blackhole bh)227     public void walk_DeclaringClass(Blackhole bh) {
228         final Blackhole localBH = bh;
229         final boolean[] done = {false};
230         new TestStack(depth, new Runnable() {
231             public void run() {
232                 WALKER_CLASS.walk(s -> {
233                     s.map(StackFrame::getDeclaringClass).forEach(localBH::consume);
234                     return null;
235                 });
236                 done[0] = true;
237             }
238         }).start();
239         if (!done[0]) {
240             throw new RuntimeException();
241         }
242     }
243 
244     /**
245      * Use StackWalker.walk() to fetch StackTraceElements
246      */
247     @Benchmark
walk_StackTraceElements(Blackhole bh)248     public void walk_StackTraceElements(Blackhole bh) {
249         final Blackhole localBH = bh;
250         final boolean[] done = {false};
251         new TestStack(depth, new Runnable() {
252             public void run() {
253                 WALKER_DEFAULT.walk(s -> {
254                     s.map(StackFrame::toStackTraceElement).forEach(localBH::consume);
255                     return null;
256                 });
257                 done[0] = true;
258             }
259         }).start();
260         if (!done[0]) {
261             throw new RuntimeException();
262         }
263     }
264 
265     /**
266      * StackWalker.getCallerClass()
267      */
268     @Benchmark
getCallerClass(Blackhole bh)269     public void getCallerClass(Blackhole bh) {
270         final Blackhole localBH = bh;
271         final boolean[] done = {false};
272         new TestStack(depth, new Runnable() {
273             public void run() {
274                 localBH.consume(WALKER_CLASS.getCallerClass());
275                 done[0] = true;
276             }
277         }).start();
278         if (!done[0]) {
279             throw new RuntimeException();
280         }
281     }
282 
283     /**
284      * Use StackWalker.walk() to filter the StackFrames, looking for the
285      * TestMarker class, which will be (approximately) 'mark' calls back up the
286      * call stack.
287      */
288     @Benchmark
walk_filterCallerClass(Blackhole bh)289     public void walk_filterCallerClass(Blackhole bh) {
290         final Blackhole localBH = bh;
291         final boolean[] done = {false};
292 
293         new MarkedTestStack(depth, mark, new Runnable() {
294             public void run() {
295                 // To be comparable with Reflection.getCallerClass(), return the Class object
296                 WALKER_CLASS.walk(s -> {
297                     localBH.consume(s.filter(f -> TestMarker.class.equals(f.getDeclaringClass())).findFirst().get().getDeclaringClass());
298                     return null;
299                 });
300                 done[0] = true;
301             }
302         }).start();
303 
304         if (!done[0]) {
305             throw new RuntimeException();
306         }
307     }
308 
309     /**
310      * Use StackWalker.walk() to filter the StackFrames, looking for the
311      * TestMarker class, which will be (approximately) depth/2 calls back up the
312      * call stack.
313      */
314     @Benchmark
walk_filterCallerClassHalfStack(Blackhole bh)315     public void walk_filterCallerClassHalfStack(Blackhole bh) {
316         final Blackhole localBH = bh;
317         final boolean[] done = {false};
318 
319         new MarkedTestStack(depth, depth / 2, new Runnable() {
320             public void run() {
321                 // To be comparable with Reflection.getCallerClass(), return the Class object
322                 WALKER_CLASS.walk(s -> {
323                     localBH.consume(s.filter((f) -> TestMarker.class.equals(f.getDeclaringClass())).findFirst().get().getDeclaringClass());
324                     return null;
325                 });
326                 done[0] = true;
327             }
328         }).start();
329 
330         if (!done[0]) {
331             throw new RuntimeException();
332         }
333     }
334 
335     // TODO: add swConsumeFramesWithReflection
336     // TODO: add swFilterOutStreamClasses
337 
338 //    // This benchmark is for collecting performance counter data
339 //    static PerfCounter streamTime = PerfCounter.newPerfCounter("jdk.stackwalk.testStreamsElapsedTime");
340 //    static PerfCounter  numStream = PerfCounter.newPerfCounter("jdk.stackwalk.numTestStreams");
341 //    // @Benchmark
342 //    public void swStkFrmsTimed(Blackhole bh) {
343 //        final Blackhole localBH = bh;
344 //        final boolean[] done = {false};
345 //        new TestStack(depth, new Runnable() {
346 //            public void run() {
347 //                long t0 = System.nanoTime();
348 //                WALKER_DEFAULT.forEach(localBH::consume);
349 //                streamTime.addElapsedTimeFrom(t0);
350 //                numStream.increment();
351 //                done[0] = true;
352 //            }
353 //        }).start();
354 //        if (!done[0]) {
355 //            throw new RuntimeException();
356 //        }
357 //    }
358 }
359