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