src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/Health.java
branchJEP-349-branch
changeset 57859 f4230f4bdd6b
child 58112 e7754025004b
equal deleted inserted replaced
57858:9254b05c141c 57859:f4230f4bdd6b
       
     1 package jdk.jfr.internal.dcmd;
       
     2 import java.time.Duration;
       
     3 import java.time.Instant;
       
     4 import java.time.LocalDateTime;
       
     5 import java.time.ZoneOffset;
       
     6 import java.time.format.DateTimeFormatter;
       
     7 import java.util.ArrayList;
       
     8 import java.util.Collections;
       
     9 import java.util.HashMap;
       
    10 import java.util.Iterator;
       
    11 import java.util.List;
       
    12 import java.util.Map;
       
    13 import java.util.Objects;
       
    14 import java.util.StringJoiner;
       
    15 
       
    16 import jdk.jfr.consumer.RecordedEvent;
       
    17 import jdk.jfr.consumer.RecordedFrame;
       
    18 import jdk.jfr.consumer.RecordedMethod;
       
    19 import jdk.jfr.consumer.RecordedStackTrace;
       
    20 import jdk.jfr.consumer.RecordingStream;
       
    21 
       
    22 /**
       
    23  *
       
    24  * HEALTH REPORT
       
    25  *
       
    26  * Example agent that shows how event streaming can be used to gather statistics
       
    27  * of a running application.
       
    28  *
       
    29  * Usage:
       
    30  *
       
    31  * $ java -javaagent:health-report.jar[=interval=<interval>] MyApp
       
    32  *
       
    33  * where interval is how often statistics should be written to standard out.
       
    34  *
       
    35  * Example,
       
    36  *
       
    37  * $ java -javaagent:health-report.jar MyApp
       
    38  *
       
    39  * For testing purposes it is also possible to just run Main.java and it will
       
    40  * run an allocation loop
       
    41  *
       
    42  * $ java Main.java
       
    43  *
       
    44  */
       
    45 public final class Health {
       
    46 
       
    47     private static final String TEMPLATE =
       
    48     "=================== HEALTH REPORT === $FLUSH_TIME         ====================\n" +
       
    49     "| GC: $GC_NAME            Phys. memory: $PHYSIC_MEM Alloc Rate: $ALLOC_RATE  |\n" +
       
    50     "| OC Count    : $OC_COUNT Initial Heap: $INIT_HEAP  Total Alloc: $TOT_ALLOC  |\n" +
       
    51     "| OC Pause Avg: $OC_AVG   Used Heap   : $USED_HEAP  Thread Count: $THREADS   |\n" +
       
    52     "| OC Pause Max: $OC_MAX   Commit. Heap: $COM_HEAP   Class Count : $CLASSES   |\n" +
       
    53     "| YC Count    : $YC_COUNT CPU Machine   : $MACH_CPU Safepoints: $SAFEPOINTS  |\n" +
       
    54     "| YC Pause Avg: $YC_AVG   CPU JVM User  : $USR_CPU  Max Safepoint: $MAX_SAFE |\n" +
       
    55     "| YC Pause Max: $YC_MAX   CPU JVM System: $SYS_CPU  Max Comp. Time: $MAX_COM |\n" +
       
    56     "|--- Top Allocation Methods ------------------------------- -----------------|\n" +
       
    57     "| $ALLOCACTION_TOP_FRAME                                            $AL_PE   |\n" +
       
    58     "| $ALLOCACTION_TOP_FRAME                                            $AL_PE   |\n" +
       
    59     "| $ALLOCACTION_TOP_FRAME                                            $AL_PE   |\n" +
       
    60     "| $ALLOCACTION_TOP_FRAME                                            $AL_PE   |\n" +
       
    61     "| $ALLOCACTION_TOP_FRAME                                            $AL_PE   |\n" +
       
    62     "|--- Hot Methods ------------------------------------------------------------|\n" +
       
    63     "| $EXECUTION_TOP_FRAME                                              $EX_PE   |\n" +
       
    64     "| $EXECUTION_TOP_FRAME                                              $EX_PE   |\n" +
       
    65     "| $EXECUTION_TOP_FRAME                                              $EX_PE   |\n" +
       
    66     "| $EXECUTION_TOP_FRAME                                              $EX_PE   |\n" +
       
    67     "| $EXECUTION_TOP_FRAME                                              $EX_PE   |\n" +
       
    68     "==============================================================================\n";
       
    69 
       
    70     public final static Field FLUSH_TIME = new Field();
       
    71 
       
    72     public final static Field GC_NAME = new Field();
       
    73     public final static Field OC_COUNT= new Field(Option.COUNT);
       
    74     public final static Field OC_AVG = new Field(Option.AVERAGE, Option.DURATION);
       
    75     public final static Field OC_MAX = new Field(Option.MAX, Option.DURATION);
       
    76     public final static Field YC_COUNT = new Field(Option.COUNT);
       
    77     public final static Field YC_AVG = new Field(Option.AVERAGE, Option.DURATION);
       
    78     public final static Field YC_MAX= new Field(Option.MAX, Option.DURATION);
       
    79 
       
    80     public final static Field PHYSIC_MEM = new Field(Option.BYTES);
       
    81     public final static Field INIT_HEAP = new Field(Option.BYTES);
       
    82     public final static Field USED_HEAP = new Field(Option.BYTES);
       
    83     public final static Field COM_HEAP = new Field(Option.BYTES);
       
    84     public final static Field MACH_CPU = new Field(Option.PERCENTAGE);
       
    85     public final static Field USR_CPU= new Field(Option.PERCENTAGE);
       
    86     public final static Field SYS_CPU = new Field(Option.PERCENTAGE);
       
    87 
       
    88     public final static Field ALLOC_RATE = new Field(Option.BYTES_PER_SECOND);
       
    89     public final static Field TOT_ALLOC = new Field(Option.TOTAL, Option.BYTES);
       
    90     public final static Field THREADS = new Field();
       
    91     public final static Field CLASSES = new Field();
       
    92     public final static Field SAFEPOINTS = new Field(Option.COUNT);
       
    93     public final static Field MAX_SAFE = new Field(Option.MAX, Option.DURATION);
       
    94     public final static Field MAX_COM = new Field(Option.MAX, Option.DURATION);
       
    95 
       
    96     public final static Field ALLOCACTION_TOP_FRAME = new Field();
       
    97     public final static Field AL_PE = new Field(Option.NORMALIZED, Option.TOTAL);
       
    98 
       
    99     public final static Field EXECUTION_TOP_FRAME = new Field();
       
   100     public final static Field EX_PE = new Field(Option.NORMALIZED, Option.COUNT);
       
   101 
       
   102     private static RecordingStream rs;
       
   103 
       
   104     public static void start() {
       
   105         Duration duration = Duration.ofSeconds(1);
       
   106 
       
   107         rs = new RecordingStream();
       
   108         // Event configuration
       
   109         rs.enable("jdk.CPULoad").withPeriod(duration);
       
   110         rs.enable("jdk.YoungGarbageCollection").withoutThreshold();
       
   111         rs.enable("jdk.OldGarbageCollection").withoutThreshold();
       
   112         rs.enable("jdk.GCHeapSummary").withPeriod(duration);
       
   113         rs.enable("jdk.PhysicalMemory").withPeriod(duration);
       
   114         rs.enable("jdk.GCConfiguration").withPeriod(duration);
       
   115         rs.enable("jdk.SafepointBegin");
       
   116         rs.enable("jdk.SafepointEnd");
       
   117         rs.enable("jdk.ObjectAllocationOutsideTLAB").withStackTrace();
       
   118         rs.enable("jdk.ObjectAllocationInNewTLAB").withStackTrace();
       
   119         rs.enable("jdk.ExecutionSample").withPeriod(Duration.ofMillis(10)).withStackTrace();
       
   120         rs.enable("jdk.JavaThreadStatistics").withPeriod(duration);
       
   121         rs.enable("jdk.ClassLoadingStatistics").withPeriod(duration);
       
   122         rs.enable("jdk.Compilation").withoutThreshold();
       
   123         rs.enable("jdk.GCHeapConfiguration").withPeriod(duration);
       
   124         rs.enable("jdk.Flush").withoutThreshold();
       
   125 
       
   126         // Dispatch handlers
       
   127         rs.onEvent("jdk.CPULoad", Health::onCPULoad);
       
   128         rs.onEvent("jdk.YoungGarbageCollection", Health::onYoungColletion);
       
   129         rs.onEvent("jdk.OldGarbageCollection", Health::onOldCollection);
       
   130         rs.onEvent("jdk.GCHeapSummary", Health::onGCHeapSummary);
       
   131         rs.onEvent("jdk.PhysicalMemory", Health::onPhysicalMemory);
       
   132         rs.onEvent("jdk.GCConfiguration", Health::onGCConfiguration);
       
   133         rs.onEvent("jdk.SafepointBegin", Health::onSafepointBegin);
       
   134         rs.onEvent("jdk.SafepointEnd", Health::onSafepointEnd);
       
   135         rs.onEvent("jdk.ObjectAllocationOutsideTLAB", Health::onObjectAllocationOutsideTLAB);
       
   136         rs.onEvent("jdk.ObjectAllocationInNewTLAB", Health::onObjectAllocationInNewTLAB);
       
   137         rs.onEvent("jdk.ExecutionSample", Health::onExecutionSample);
       
   138         rs.onEvent("jdk.JavaThreadStatistics", Health::onJavaThreadStatistics);
       
   139         rs.onEvent("jdk.ClassLoadingStatistics", Health::onClassLoadingStatistics);
       
   140         rs.onEvent("jdk.Compilation", Health::onCompilation);
       
   141         rs.onEvent("jdk.GCHeapConfiguration", Health::onGCHeapConfiguration);
       
   142         rs.onEvent("jdk.Flush", Health::onFlushpoint);
       
   143 
       
   144         rs.onFlush(Health::printReport);
       
   145         rs.startAsync();
       
   146         System.out.println("Health started");
       
   147     }
       
   148 
       
   149     private static void onCPULoad(RecordedEvent event) {
       
   150         MACH_CPU.addSample(event.getDouble("machineTotal"));
       
   151         SYS_CPU.addSample(event.getDouble("jvmSystem"));
       
   152         USR_CPU.addSample(event.getDouble("jvmUser"));
       
   153     }
       
   154 
       
   155     private static void onYoungColletion(RecordedEvent event) {
       
   156         long nanos = event.getDuration().toNanos();
       
   157         YC_COUNT.addSample(nanos);
       
   158         YC_MAX.addSample(nanos);
       
   159         YC_AVG.addSample(nanos);
       
   160     }
       
   161 
       
   162     private static void onOldCollection(RecordedEvent event) {
       
   163         long nanos = event.getDuration().toNanos();
       
   164         OC_COUNT.addSample(nanos);
       
   165         OC_MAX.addSample(nanos);
       
   166         OC_AVG.addSample(nanos);
       
   167     }
       
   168 
       
   169     private static void onGCHeapSummary(RecordedEvent event) {
       
   170         USED_HEAP.addSample(event.getLong("heapUsed"));
       
   171         COM_HEAP.addSample(event.getLong("heapSpace.committedSize"));
       
   172     }
       
   173 
       
   174     private static void onPhysicalMemory(RecordedEvent event) {
       
   175         PHYSIC_MEM.addSample(event.getLong("totalSize"));
       
   176     }
       
   177 
       
   178     private static void onCompilation(RecordedEvent event) {
       
   179         MAX_COM.addSample(event.getDuration().toNanos());
       
   180     }
       
   181 
       
   182     private static void onGCConfiguration(RecordedEvent event) {
       
   183         String gc = event.getString("oldCollector");
       
   184         String yc = event.getString("youngCollector");
       
   185         if (yc != null) {
       
   186             gc += "/" + yc;
       
   187         }
       
   188         GC_NAME.addSample(gc);
       
   189     }
       
   190 
       
   191     private final static Map<Long, Instant> safepointBegin = new HashMap<>();
       
   192 
       
   193     private static void onSafepointBegin(RecordedEvent event) {
       
   194         safepointBegin.put(event.getValue("safepointId"), event.getEndTime());
       
   195     }
       
   196 
       
   197     private static void onSafepointEnd(RecordedEvent event) {
       
   198         long id = event.getValue("safepointId");
       
   199         Instant begin = safepointBegin.get(id);
       
   200         if (begin != null) {
       
   201             long nanos = Duration.between(begin, event.getEndTime()).toNanos();
       
   202             safepointBegin.remove(id);
       
   203             SAFEPOINTS.addSample(nanos);
       
   204             MAX_SAFE.addSample(nanos);
       
   205         }
       
   206     }
       
   207 
       
   208     private static void onObjectAllocationOutsideTLAB(RecordedEvent event) {
       
   209         onAllocationSample(event, event.getLong("allocationSize"));
       
   210     }
       
   211 
       
   212     private static void onObjectAllocationInNewTLAB(RecordedEvent event) {
       
   213         onAllocationSample(event, event.getLong("tlabSize"));
       
   214     }
       
   215 
       
   216     private static double totalAllocated;
       
   217     private static long firstAllocationTime = -1;
       
   218     private static void onAllocationSample(RecordedEvent event, long size) {
       
   219         String topFrame = topFrame(event.getStackTrace());
       
   220         if (topFrame != null) {
       
   221             ALLOCACTION_TOP_FRAME.addSample(topFrame, size);
       
   222             AL_PE.addSample(topFrame, size);
       
   223         }
       
   224         TOT_ALLOC.addSample(size);
       
   225         // ALLOC_RATE.addRate(timetsamp, amount);
       
   226         long timestamp = event.getEndTime().toEpochMilli();
       
   227         totalAllocated += size;
       
   228         if (firstAllocationTime > 0) {
       
   229             long elapsedTime = timestamp - firstAllocationTime;
       
   230             if (elapsedTime > 0) {
       
   231                 double rate = 1000.0 * (totalAllocated / elapsedTime);
       
   232                 ALLOC_RATE.addSample(rate);
       
   233             }
       
   234         } else {
       
   235             firstAllocationTime = timestamp;
       
   236         }
       
   237     }
       
   238 
       
   239     private static void onExecutionSample(RecordedEvent event) {
       
   240         String topFrame = topFrame(event.getStackTrace());
       
   241         EXECUTION_TOP_FRAME.addSample(topFrame, 1);
       
   242         EX_PE.addSample(topFrame, 1);
       
   243     }
       
   244 
       
   245     private static void onJavaThreadStatistics(RecordedEvent event) {
       
   246         THREADS.addSample(event.getDouble("activeCount"));
       
   247     }
       
   248 
       
   249     private static void onClassLoadingStatistics(RecordedEvent event) {
       
   250         long diff = event.getLong("loadedClassCount") - event.getLong("unloadedClassCount");
       
   251         CLASSES.addSample(diff);
       
   252     }
       
   253 
       
   254     private static void onGCHeapConfiguration(RecordedEvent event) {
       
   255         INIT_HEAP.addSample(event.getLong("initialSize"));
       
   256     }
       
   257 
       
   258     private final static DateTimeFormatter FORMATTER =
       
   259             DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
       
   260     private static void onFlushpoint(RecordedEvent event) {
       
   261         Instant i = event.getEndTime();
       
   262         LocalDateTime l = LocalDateTime.ofInstant(i, ZoneOffset.systemDefault());
       
   263         FLUSH_TIME.addSample(FORMATTER.format(l));
       
   264     }
       
   265 
       
   266     // # # # TEMPLATE AND SAMPLING # # #
       
   267 
       
   268     private enum Option {
       
   269         BYTES, PERCENTAGE, DURATION, BYTES_PER_SECOND, NORMALIZED, COUNT, AVERAGE, TOTAL, MAX
       
   270     }
       
   271 
       
   272     private static final class Record {
       
   273         private final Object key;
       
   274         private int count = 1;
       
   275         private double total;
       
   276         private double max;
       
   277         private Object value = null;
       
   278         public Record(Object key, String sample) {
       
   279             this.key = key;
       
   280             this.value = sample;
       
   281         }
       
   282 
       
   283         public Record(Object object, double sample) {
       
   284             this.key = object;
       
   285             this.value = sample;
       
   286             this.max = sample;
       
   287             this.total = sample;
       
   288         }
       
   289 
       
   290         public long getCount() {
       
   291             return count;
       
   292         }
       
   293 
       
   294         public double getAverage() {
       
   295             return total / count;
       
   296         }
       
   297 
       
   298         public double getMax() {
       
   299             return max;
       
   300         }
       
   301 
       
   302         public double getTotal() {
       
   303             return total;
       
   304         }
       
   305 
       
   306         public int hashCode() {
       
   307             return key == null ? 0 : key.hashCode();
       
   308         }
       
   309 
       
   310         public boolean equals(Object o) {
       
   311             if (o instanceof Record) {
       
   312                 Record that = (Record) o;
       
   313                 return Objects.equals(that.key, this.key);
       
   314             }
       
   315             return false;
       
   316         }
       
   317     }
       
   318 
       
   319     private static final class Field implements Iterable<Record> {
       
   320         private final HashMap<Object, Record> histogram = new HashMap<>();
       
   321         private final Option[] options;
       
   322         private double norm;
       
   323 
       
   324         public Field(Option... options) {
       
   325             this.options = options;
       
   326         }
       
   327 
       
   328         public void addSample(double sample) {
       
   329             addSample(this, sample);
       
   330         }
       
   331 
       
   332         public void addSample(String sample) {
       
   333             histogram.merge(this, new Record(this, sample), (a, b) -> {
       
   334                 a.count++;
       
   335                 a.value = sample;
       
   336                 return a;
       
   337             });
       
   338         }
       
   339 
       
   340         public void addSample(Object key, double sample) {
       
   341             histogram.merge(key, new Record(key, sample), (a, b) -> {
       
   342                 a.count++;
       
   343                 a.total += sample;
       
   344                 a.value = sample;
       
   345                 a.max = Math.max(a.max, sample);
       
   346                 return a;
       
   347             });
       
   348         }
       
   349 
       
   350         public boolean hasOption(Option option) {
       
   351             for (Option o : options) {
       
   352                 if (o == option) {
       
   353                     return true;
       
   354                 }
       
   355             }
       
   356             return false;
       
   357         }
       
   358 
       
   359         @Override
       
   360         public Iterator<Record> iterator() {
       
   361             List<Record> records = new ArrayList<>(histogram.values());
       
   362             Collections.sort(records, (a, b) -> Long.compare(b.getCount(), a.getCount()));
       
   363             if (hasOption(Option.TOTAL)) {
       
   364                 Collections.sort(records, (a, b) -> Double.compare(b.getTotal(), a.getTotal()));
       
   365             }
       
   366             if (hasOption(Option.NORMALIZED)) {
       
   367                 norm = 0.0;
       
   368                 for (Record r : records) {
       
   369                     if (hasOption(Option.TOTAL)) {
       
   370                         norm += r.getTotal();
       
   371                     }
       
   372                     if (hasOption(Option.COUNT)) {
       
   373                         norm += r.getCount();
       
   374                     }
       
   375                 }
       
   376             }
       
   377             return records.iterator();
       
   378         }
       
   379 
       
   380         public double getNorm() {
       
   381             return norm;
       
   382         }
       
   383     }
       
   384 
       
   385     private static void printReport() {
       
   386         try {
       
   387             StringBuilder template = new StringBuilder(TEMPLATE);
       
   388             for (java.lang.reflect.Field f : Health.class.getDeclaredFields()) {
       
   389                 String variable = "$" + f.getName();
       
   390                 if (f.getType() == Field.class) {
       
   391                     writeParam(template, variable, (Field) f.get(null));
       
   392                 }
       
   393             }
       
   394             System.out.println(template.toString());
       
   395         } catch (Exception e) {
       
   396             e.printStackTrace();
       
   397         }
       
   398     }
       
   399 
       
   400     private static void writeParam(StringBuilder template, String variable, Field param) {
       
   401         Iterator<Record> it = param.iterator();
       
   402         int lastIndex = 0;
       
   403         while (true) {
       
   404             int index = template.indexOf(variable, lastIndex);
       
   405             if (index == -1) {
       
   406                 return;
       
   407             }
       
   408             lastIndex = index + 1;
       
   409             Record record = it.hasNext() ? it.next() : null;
       
   410             Object value = null;
       
   411             if (record != null) {
       
   412                 value = (record.key == param) ? record.value : record.key;
       
   413             }
       
   414             if (value != null) {
       
   415                 if (param.hasOption(Option.MAX)) {
       
   416                     value = record.getMax();
       
   417                 }
       
   418                 if (param.hasOption(Option.COUNT)) {
       
   419                     value = record.getCount();
       
   420                 }
       
   421                 if (param.hasOption(Option.AVERAGE)) {
       
   422                     value = record.getAverage();
       
   423                 }
       
   424                 if (param.hasOption(Option.TOTAL)) {
       
   425                     value = record.getTotal();
       
   426                 }
       
   427             }
       
   428             if (param.hasOption(Option.COUNT)) {
       
   429                 value = value == null ? 0 : value;
       
   430             }
       
   431             if (param.hasOption(Option.BYTES)) {
       
   432                 value = formatBytes((Number) value);
       
   433             }
       
   434             if (param.hasOption(Option.DURATION)) {
       
   435                 value = formatDuration((Number) value);
       
   436             }
       
   437             if (param.hasOption(Option.BYTES_PER_SECOND)) {
       
   438                 if (value != null) {
       
   439                     value = formatBytes((Number) value) + "/s";
       
   440                 }
       
   441             }
       
   442             if (param.hasOption(Option.NORMALIZED)) {
       
   443                 if (value != null) {
       
   444                     double d = ((Number) value).doubleValue() / param.getNorm();
       
   445                     value = formatPercentage(d);
       
   446                 }
       
   447             }
       
   448             if (param.hasOption(Option.PERCENTAGE)) {
       
   449                 value = formatPercentage((Number) value);
       
   450             }
       
   451             String text;
       
   452             if (value == null) {
       
   453                 text = record == null ? "" : "N/A";
       
   454             } else {
       
   455                 text = String.valueOf(value);
       
   456             }
       
   457             int length = Math.max(text.length(), variable.length());
       
   458             for (int i = 0; i < length; i++) {
       
   459                 char c = i < text.length() ? text.charAt(i) : ' ';
       
   460                 template.setCharAt(index + i, c);
       
   461             }
       
   462         }
       
   463     }
       
   464 
       
   465     // # # # FORMATTING # # #
       
   466 
       
   467     enum TimespanUnit {
       
   468         NANOSECONDS("ns", 1000), MICROSECONDS("us", 1000), MILLISECONDS("ms", 1000), SECONDS("s", 60), MINUTES("m", 60),
       
   469         HOURS("h", 24), DAYS("d", 7);
       
   470 
       
   471         final String text;
       
   472         final long amount;
       
   473 
       
   474         TimespanUnit(String unit, long amount) {
       
   475             this.text = unit;
       
   476             this.amount = amount;
       
   477         }
       
   478     }
       
   479 
       
   480     private static String formatDuration(Number value) {
       
   481         if (value == null) {
       
   482             return "N/A";
       
   483         }
       
   484         double t = value.doubleValue();
       
   485         TimespanUnit result = TimespanUnit.NANOSECONDS;
       
   486         for (TimespanUnit unit : TimespanUnit.values()) {
       
   487             result = unit;
       
   488             if (t < 1000) {
       
   489                 break;
       
   490             }
       
   491             t = t / unit.amount;
       
   492         }
       
   493         return String.format("%.1f %s", t, result.text);
       
   494     }
       
   495 
       
   496     private static String formatPercentage(Number value) {
       
   497         if (value == null) {
       
   498             return "N/A";
       
   499         }
       
   500         return String.format("%6.2f %%", value.doubleValue() * 100);
       
   501     }
       
   502 
       
   503     private static String formatBytes(Number value) {
       
   504         if (value == null) {
       
   505             return "N/A";
       
   506         }
       
   507         long bytes = value.longValue();
       
   508         if (bytes >= 1024 * 1024l) {
       
   509             return bytes / (1024 * 1024L) + " MB";
       
   510         }
       
   511         if (bytes >= 1024) {
       
   512             return bytes / 1024 + " kB";
       
   513         }
       
   514         return bytes + " bytes";
       
   515     }
       
   516 
       
   517     private static String topFrame(RecordedStackTrace stackTrace) {
       
   518         if (stackTrace == null) {
       
   519             return null;
       
   520         }
       
   521         List<RecordedFrame> frames = stackTrace.getFrames();
       
   522         if (!frames.isEmpty()) {
       
   523             RecordedFrame topFrame = frames.get(0);
       
   524             if (topFrame.isJavaFrame()) {
       
   525                 return formatMethod(topFrame.getMethod());
       
   526             }
       
   527         }
       
   528         return null;
       
   529     }
       
   530 
       
   531     private static String formatMethod(RecordedMethod m) {
       
   532         StringBuilder sb = new StringBuilder();
       
   533         String typeName = m.getType().getName();
       
   534         typeName = typeName.substring(typeName.lastIndexOf('.') + 1);
       
   535         sb.append(typeName).append(".").append(m.getName());
       
   536         sb.append("(");
       
   537         StringJoiner sj = new StringJoiner(", ");
       
   538         String md = m.getDescriptor().replace("/", ".");
       
   539         String parameter = md.substring(1, md.lastIndexOf(")"));
       
   540         for (String qualifiedName : decodeDescriptors(parameter)) {
       
   541             sj.add(qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1));
       
   542         }
       
   543         sb.append(sj.length() > 10 ? "..." : sj);
       
   544         sb.append(")");
       
   545         return sb.toString();
       
   546     }
       
   547 
       
   548     private static List<String> decodeDescriptors(String descriptor) {
       
   549         List<String> descriptors = new ArrayList<>();
       
   550         for (int index = 0; index < descriptor.length(); index++) {
       
   551             String arrayBrackets = "";
       
   552             while (descriptor.charAt(index) == '[') {
       
   553                 arrayBrackets += "[]";
       
   554                 index++;
       
   555             }
       
   556             char c = descriptor.charAt(index);
       
   557             String type;
       
   558             switch (c) {
       
   559             case 'L':
       
   560                 int endIndex = descriptor.indexOf(';', index);
       
   561                 type = descriptor.substring(index + 1, endIndex);
       
   562                 index = endIndex;
       
   563                 break;
       
   564             case 'I':
       
   565                 type = "int";
       
   566                 break;
       
   567             case 'J':
       
   568                 type = "long";
       
   569                 break;
       
   570             case 'Z':
       
   571                 type = "boolean";
       
   572                 break;
       
   573             case 'D':
       
   574                 type = "double";
       
   575                 break;
       
   576             case 'F':
       
   577                 type = "float";
       
   578                 break;
       
   579             case 'S':
       
   580                 type = "short";
       
   581                 break;
       
   582             case 'C':
       
   583                 type = "char";
       
   584                 break;
       
   585             case 'B':
       
   586                 type = "byte";
       
   587                 break;
       
   588             default:
       
   589                 type = "<unknown-descriptor-type>";
       
   590             }
       
   591             descriptors.add(type + arrayBrackets);
       
   592         }
       
   593         return descriptors;
       
   594     }
       
   595 }
       
   596