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.EnumSet;
  27 import java.util.concurrent.TimeUnit;
  28 import org.openjdk.jmh.annotations.Benchmark;
  29 import org.openjdk.jmh.annotations.BenchmarkMode;
  30 import org.openjdk.jmh.annotations.Mode;
  31 import org.openjdk.jmh.annotations.OutputTimeUnit;
  32 import org.openjdk.jmh.annotations.Param;
  33 import org.openjdk.jmh.annotations.Scope;
  34 import org.openjdk.jmh.annotations.State;
  35 import org.openjdk.jmh.infra.Blackhole;
  36 
  37 /**
  38  * Benchmarks for java.lang.StackWalker
  39  */
  40 @State(value=Scope.Benchmark)
  41 @BenchmarkMode(Mode.AverageTime)
  42 @OutputTimeUnit(TimeUnit.NANOSECONDS)
  43 public class StackWalkBench {
  44     private static final StackWalker WALKER_DEFAULT = StackWalker.getInstance();
  45 
  46     private static final StackWalker WALKER_CLASS =
  47         StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
  48 
  49     private static final StackWalker WALKER_ALLFRAMES =
  50         StackWalker.getInstance(EnumSet.of(StackWalker.Option.SHOW_REFLECT_FRAMES,
  51                                            StackWalker.Option.SHOW_HIDDEN_FRAMES));
  52     
  53     private static final StackWalker WALKER_ALL =
  54         StackWalker.getInstance(EnumSet.allOf(StackWalker.Option.class));
  55 
  56     // TestStack will add this number of calls to the call stack
  57     @Param({"4", "10", "100", "256", "1000"})
  58     public int depth;
  59 
  60     // Only used by swFilterCallerClass, to specify (roughly) how far back the
  61     // call stack the target class will be found.  Not needed by other
  62     // benchmarks, so not a @Param by default.
  63     // @Param({"4"})
  64     public int mark = 4;
  65 
  66     /** Build a call stack of a given size, then run trigger code in it.
  67       * (Does not account for existing frames higher up in the JMH machinery).
  68       */
  69     public static class TestStack {
  70         final long fence;
  71         long current;
  72         final Runnable trigger;
  73 
  74         public TestStack(long max, Runnable trigger) {
  75           this.fence = max;
  76           this.current = 0;
  77           this.trigger = trigger;
  78         }
  79 
  80         public void start() {
  81             one();
  82         }
  83 
  84         public void one() {
  85             if (check()) {
  86                 two();
  87             }
  88         }
  89 
  90         void two() {
  91            if (check()) {
  92               three();
  93            }
  94         }
  95 
  96         private void three() {
  97             if (check()) {
  98                one();
  99             }
 100         }
 101 
 102         boolean check() {
 103             if (++current == fence) {
 104                 trigger.run();
 105                 return false;
 106             } else {
 107                 return true;
 108             }
 109         }
 110     }
 111 
 112     /* Class to look for when testing filtering */
 113     static class TestMarker {
 114         public void call(MarkedTestStack test) {
 115             test.marked();
 116         }
 117     }
 118 
 119     /** Call stack to test filtering.
 120      *  TestMarker will make a call on the stack.
 121      */
 122     static class MarkedTestStack extends TestStack {
 123         long mark;
 124 
 125         /**
 126          * @param mark How far back the stack should the TestMarker be found?
 127          */
 128         public MarkedTestStack(long max, long mark, Runnable trigger) {
 129             super(max, trigger);
 130             if (mark > max) {
 131                 throw new IllegalArgumentException("mark must be <= max");
 132             }
 133             this.mark = max - mark; // Count backwards from the completed call stack
 134         }
 135         @Override
 136         public void start() {
 137             if (mark == 0) {
 138                 mark();
 139             } else {
 140                 super.one();
 141             }
 142         }
 143         @Override
 144         boolean check() {
 145            if (++current == mark) {
 146                mark();
 147                return false;
 148            } else if (current == fence) {
 149               trigger.run();
 150               return false;
 151            } else {
 152                return true;
 153            }
 154         }
 155         void mark() {
 156             new TestMarker().call(this);
 157         }
 158         public void marked() {
 159             if (current < fence) {
 160                 if (check()) {
 161                     one();
 162                 }
 163             } else {
 164                 trigger.run();
 165             }
 166         }
 167     }
 168 
 169     // TODO: add swConsumeFramesWithReflection
 170     // TODO: add swFilterOutStreamClasses
 171     // TODO: swConsumeFrames w/ Default walker; another with ALL walker; another with *just* ALL_FRAMES ?
 172 
 173     /**
 174      * StackWalker.forEach() with all StackWalker.Options
 175      */
 176     @Benchmark
 177     public void forEach_AllOpts(Blackhole bh) {
 178         final Blackhole localBH = bh;
 179         final boolean[] done = {false};
 180         new TestStack(depth, new Runnable() {
 181             public void run() {
 182                 WALKER_ALL.forEach(localBH::consume);
 183                 done[0] = true;
 184             }
 185         }).start();
 186         if (!done[0]) {
 187             throw new RuntimeException();
 188         }
 189     }
 190 
 191     /**
 192      * StackWalker.forEach() with SHOW_HIDDEN_FRAMES and SHOW_REFLECT_FRAMES
 193      */
 194     @Benchmark
 195     public void forEach_HiddenAndReflectFrames(Blackhole bh) {
 196         final Blackhole localBH = bh;
 197         final boolean[] done = {false};
 198         new TestStack(depth, new Runnable() {
 199             public void run() {
 200                 WALKER_ALLFRAMES.forEach(localBH::consume);
 201                 done[0] = true;
 202             }
 203         }).start();
 204         if (!done[0]) {
 205             throw new RuntimeException();
 206         }
 207     }
 208 
 209     /**
 210      * StackWalker.forEach() with default options
 211      */
 212     @Benchmark
 213     public void forEach_DefaultOpts(Blackhole bh) {
 214         final Blackhole localBH = bh;
 215         final boolean[] done = {false};
 216         new TestStack(depth, new Runnable() {
 217             public void run() {
 218                 WALKER_DEFAULT.forEach(localBH::consume);
 219                 done[0] = true;
 220             }
 221         }).start();
 222         if (!done[0]) {
 223             throw new RuntimeException();
 224         }
 225     }
 226 
 227 //    static PerfCounter streamTime = PerfCounter.newPerfCounter("jdk.stackwalk.testStreamsElapsedTime");
 228 //    static PerfCounter  numStream = PerfCounter.newPerfCounter("jdk.stackwalk.numTestStreams");
 229 //    // This benchmark is for collecting performance counter data
 230 //    // @Benchmark
 231 //    public void swStkFrmsTimed(Blackhole bh) {
 232 //        final Blackhole localBH = bh;
 233 //        final boolean[] done = {false};
 234 //        new TestStack(depth, new Runnable() {
 235 //            public void run() {
 236 //                long t0 = System.nanoTime();
 237 //                WALKER_DEFAULT.forEach(localBH::consume);
 238 //                streamTime.addElapsedTimeFrom(t0);
 239 //                numStream.increment();
 240 //                done[0] = true;
 241 //            }
 242 //        }).start();
 243 //        if (!done[0]) {
 244 //            throw new RuntimeException();
 245 //        }
 246 //    }
 247 
 248     /**
 249      * Use Stackwalker.walk() to fetch class names
 250      */
 251     @Benchmark
 252     public void walk_ClassNames(Blackhole bh) {
 253         final Blackhole localBH = bh;
 254         final boolean[] done = {false};
 255         new TestStack(depth, new Runnable() {
 256             public void run() {
 257                 WALKER_DEFAULT.walk(s -> {
 258                     s.map(StackFrame::getClassName).forEach(localBH::consume);
 259                     return null;
 260                 });
 261                 done[0] = true;
 262             }
 263         }).start();
 264         if (!done[0]) {
 265             throw new RuntimeException();
 266         }
 267     }
 268 
 269     /**
 270      * Use Stackwalker.walk() to fetch method names
 271      */
 272     @Benchmark
 273     public void walk_MethodNames(Blackhole bh) {
 274         final Blackhole localBH = bh;
 275         final boolean[] done = {false};
 276         new TestStack(depth, new Runnable() {
 277             public void run() {
 278                 WALKER_DEFAULT.walk( s -> {
 279                     s.map(StackFrame::getMethodName).forEach(localBH::consume);
 280                     return null;
 281                 });
 282                 done[0] = true;
 283             }
 284         }).start();
 285         if (!done[0]) {
 286             throw new RuntimeException();
 287         }
 288     }
 289 
 290     /**
 291      * Use Stackwalker.walk() to fetch declaring class instances
 292      */
 293     @Benchmark
 294     public void walk_DeclaringClass(Blackhole bh) {
 295         final Blackhole localBH = bh;
 296         final boolean[] done = {false};
 297         new TestStack(depth, new Runnable() {
 298             public void run() {
 299                 WALKER_CLASS.walk(s -> {
 300                     s.map(StackFrame::getDeclaringClass).forEach(localBH::consume);
 301                     return null;
 302                 });
 303                 done[0] = true;
 304             }
 305         }).start();
 306         if (!done[0]) {
 307             throw new RuntimeException();
 308         }
 309     }
 310 
 311     /**
 312      * Use StackWalker.walk() to fetch StackTraceElements
 313      */
 314     @Benchmark
 315     public void walk_StackTraceElements(Blackhole bh) {
 316         final Blackhole localBH = bh;
 317         final boolean[] done = {false};
 318         new TestStack(depth, new Runnable() {
 319             public void run() {
 320                 WALKER_DEFAULT.walk(s -> {
 321                     s.map(StackFrame::toStackTraceElement).forEach(localBH::consume);
 322                     return null;
 323                 });
 324                 done[0] = true;
 325             }
 326         }).start();
 327         if (!done[0]) {
 328             throw new RuntimeException();
 329         }
 330     }
 331 
 332     /**
 333      * StackWalker.getCallerClass()
 334      */
 335     @Benchmark
 336     public void getCallerClass(Blackhole bh) {
 337         final Blackhole localBH = bh;
 338         final boolean[] done = {false};
 339         new TestStack(depth, new Runnable() {
 340             public void run() {
 341                 localBH.consume(WALKER_CLASS.getCallerClass());
 342                 done[0] = true;
 343             }
 344         }).start();
 345         if (!done[0]) {
 346             throw new RuntimeException();
 347         }
 348     }
 349 
 350     /**
 351      * Use StackWalker.walk() to filter the StackFrames, looking for the
 352      * TestMarker class, which will be (approximately) 'mark' calls back up the
 353      * call stack.
 354      */
 355     @Benchmark
 356     public void walk_filterCallerClass(Blackhole bh) {
 357         final Blackhole localBH = bh;
 358         final boolean[] done = {false};
 359 
 360         new MarkedTestStack(depth, mark, new Runnable() {
 361             public void run() {
 362                 // To be comparable with Reflection.getCallerClass(), return the Class object
 363                 WALKER_CLASS.walk(s -> {
 364                     localBH.consume(s.filter(f -> TestMarker.class.equals(f.getDeclaringClass())).findFirst().get().getDeclaringClass());
 365                     return null;
 366                 });
 367                 done[0] = true;
 368             }
 369         }).start();
 370 
 371         if (!done[0]) {
 372             throw new RuntimeException();
 373         }
 374     }
 375 
 376     /**
 377      * Use StackWalker.walk() to filter the StackFrames, looking for the
 378      * TestMarker class, which will be (approximately) depth/2 calls back up the
 379      * call stack.
 380      */    
 381     @Benchmark
 382     public void walk_filterCallerClassHalfStack(Blackhole bh) {
 383         final Blackhole localBH = bh;
 384         final boolean[] done = {false};
 385 
 386         new MarkedTestStack(depth, depth / 2, new Runnable() {
 387             public void run() {
 388                 // To be comparable with Reflection.getCallerClass(), return the Class object
 389                 WALKER_CLASS.walk(s -> {
 390                     localBH.consume(s.filter((f) -> TestMarker.class.equals(f.getDeclaringClass())).findFirst().get().getDeclaringClass());
 391                     return null;
 392                 });
 393                 done[0] = true;
 394             }
 395         }).start();
 396 
 397         if (!done[0]) {
 398             throw new RuntimeException();
 399         }
 400     }
 401 
 402 //    // Meant to measure the overhead of testing machinery
 403 //    // @Benchmark
 404 //    public void testGoThereBH(Blackhole bh) {
 405 //        final Blackhole localBH = bh;
 406 //        final boolean[] done = {false};
 407 //        new TestStack(depth, new Runnable() {
 408 //          public void run() {
 409 //              Object t = new Object();
 410 //              localBH.consume(t);
 411 //              done[0] = true;
 412 //          }
 413 //        }).start();
 414 //        if (!done[0]) {
 415 //            throw new RuntimeException();
 416 //        }
 417 //    }
 418 }