1 /*
  2  * Copyright (c) 2024, 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 
 24 /*
 25  Used by DemoSupport.gmk:
 26  
 27  - calculate the geomean and stdev of benchmark runs
 28  - write markdown snippets for rendering benchmark results as a chart on GitHub .MD files
 29 
 30 Example:
 31 
 32 helidon-quickstart-se$ tbjava ../lib/GithubMDChart.java < mainline_vs_premain.csv
 33 run,mainline default,mainline custom static CDS,premain custom static CDS only,premain CDS + AOT
 34 1,398,244,144,107
 35 2,387,247,142,108
 36 3,428,238,143,107
 37 4,391,252,142,111
 38 5,417,247,141,107
 39 6,390,239,139,127
 40 7,387,247,145,111
 41 8,387,240,147,110
 42 9,388,242,147,108
 43 10,400,242,167,108
 44 Geomean,397.08,243.76,145.52,110.26
 45 Stdev,13.55,4.19,7.50,5.73
 46 
 47 ```mermaid
 48 ---
 49 config:
 50     xyChart:
 51         chartOrientation: horizontal
 52         height: 300
 53 ---
 54 xychart-beta
 55     x-axis "variant" ["mainline default", "mainline custom static CDS", "premain custom static CDS only", "premain CDS + AOT"]
 56     y-axis "Elapsed time (ms, smaller is better)" 0 --> 397
 57     bar [397, 244, 146, 110]
 58 ```
 59 
 60 */
 61 
 62 import java.io.PrintWriter;
 63 import java.util.Scanner;
 64 import java.util.ArrayList;
 65 import java.util.Arrays;
 66 
 67 public class GithubMDChart {
 68     @SuppressWarnings("unchecked")
 69     public static void main(String args[]) throws Exception {
 70         Scanner input = new Scanner(System.in);
 71         String line = input.nextLine();
 72         //System.out.println(line);
 73         String head[] = line.split(",");
 74         Object[] groups = new Object[head.length];
 75         String[] geomeans = new String[head.length];
 76         for (int i = 0; i < head.length; i++) {
 77             groups[i] = new ArrayList<Double>();
 78         }
 79         while (input.hasNext()) {
 80             line = input.nextLine();
 81             //System.out.println(line);
 82             String parts[] = line.split(",");
 83             for (int i = 0; i < head.length; i++) {
 84                 ArrayList<Double> list =  (ArrayList<Double>)(groups[i]);
 85                 list.add(Double.valueOf(Double.parseDouble(parts[i])));
 86             }
 87         }
 88 
 89         for (int i = 0; i < head.length; i++) {
 90             ArrayList<Double> list =  (ArrayList<Double>)(groups[i]);
 91             if (i == 0) {
 92                 System.out.print("Geomean");
 93             } else {
 94                 System.out.print(",");
 95                 geomeans[i] = geomean(list);
 96                 System.out.print(geomeans[i]);
 97             }
 98         }
 99 
100         System.out.println();
101 
102         for (int i = 0; i < head.length; i++) {
103             ArrayList<Double> list =  (ArrayList<Double>)(groups[i]);
104             if (i == 0) {
105                 System.out.print("Stdev");
106             } else {
107                 System.out.print(",");
108                 System.out.print(stdev(list));
109             }
110         }
111         System.out.println();
112         System.out.println("Markdown snippets in " + args[0]);
113 
114         String[] norm = new String[geomeans.length];
115         for (int i = 1; i < head.length; i++) {
116             double base = Double.parseDouble(geomeans[1]);
117             double me   = Double.parseDouble(geomeans[i]);
118             norm[i] = String.format("%.0f", 1000.0 * me / base);
119         }
120 
121         PrintWriter pw = new PrintWriter(args[0]);
122         // Horizontal avoids issues with long names overlapping each other,
123         // and setting a smaller-than-default height makes it easier to overlook and compare.
124         pw.println("""
125         ```mermaid
126         ---
127         config:
128             xyChart:
129                 chartOrientation: horizontal
130                 height: 300
131         ---
132         xychart-beta
133             x-axis [$names]
134             y-axis "Elapsed time (ms, smaller is better)" 0 --> $maxtime
135             bar [$geomeans]
136         ```
137         
138         -----------------Normalized---------------------------------------------
139         ```mermaid
140         ---
141         config:
142             xyChart:
143                 chartOrientation: horizontal
144                 height: 300
145         ---
146         xychart-beta
147             x-axis [$names]
148             y-axis "Elapsed time (normalized, smaller is better)" 0 --> 1000
149             bar [$norms]
150         ```
151         """
152         .replace("$names", '"' + String.join("\", \"", Arrays.copyOfRange(head, 1, head.length)) + '"')
153         .replace("$maxtime", geomeans[1])
154         .replace("$geomeans", String.join(", ", Arrays.copyOfRange(geomeans, 1, geomeans.length)))
155         .replace("$norms", String.join(", ", Arrays.copyOfRange(norm, 1, norm.length)))
156         );
157         pw.close();
158     }
159 
160 
161     static String geomean(ArrayList<Double> list) {
162         double log = 0.0d;
163         for (Double d : list) {
164             double v = d.doubleValue();
165             if (v <= 0) {
166                 v = 0.000001;
167             }
168             log += Math.log(v);
169         }
170 
171 	return String.format("%.2f", Math.exp(log / list.size()));
172     }
173 
174     static String stdev(ArrayList<Double> list) {
175         double sum = 0.0;
176         for (Double d : list) {
177             sum += d.doubleValue();
178         }
179 
180         double length = list.size();
181         double mean = sum / length;
182 
183         double stdev = 0.0;
184         for (Double d : list) {
185             stdev += Math.pow(d.doubleValue() - mean, 2);
186         }
187 
188 	return String.format("%.2f", Math.sqrt(stdev / length));
189     }
190 }