1 /* 2 * Copyright (c) 2023, 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 package jdk.test.lib.cds; 25 26 import java.io.File; 27 import jdk.test.lib.cds.CDSTestUtils; 28 import jdk.test.lib.process.ProcessTools; 29 import jdk.test.lib.process.OutputAnalyzer; 30 import jdk.test.lib.StringArrayUtils; 31 32 /* 33 * This is a base class used for testing CDS functionalities with complex applications. 34 * You can define the application by overridding the vmArgs(), classpath() and appCommandLine() 35 * methods. Application-specific validation checks can be implemented with checkExecution(). 36 * 37 * Note: to debug the new workflow, run jtreg with -vmoption:-DCDSAppTester.split.new.workflow=true 38 * This will run the new workflow in two separate processes that you can rerun easily inside a debugger. 39 * Also, the log files are easier to read. 40 */ 41 abstract public class CDSAppTester { 42 private final String name; 43 private final String classListFile; 44 private final String classListFileLog; 45 private final String staticArchiveFile; 46 private final String staticArchiveFileLog; 47 private final String dynamicArchiveFile; 48 private final String dynamicArchiveFileLog; 49 private final String productionRunLog; 50 private final String codeCacheFile; // old workflow 51 private final String codeCacheFileLog; 52 private final String cdsFile; // new workflow: -XX:CacheDataStore=<foo>.cds 53 private final String cdsFileLog; 54 private final String cdsFilePreImage; // new workflow: -XX:CacheDataStore=<foo>.cds 55 private final String cdsFilePreImageLog; 56 private final String aotFile; // new workflow = cdsFile + ".code" 57 58 public CDSAppTester(String name) { 59 // Old workflow 60 this.name = name; 61 classListFile = name() + ".classlist"; 62 classListFileLog = classListFile + ".log"; 63 staticArchiveFile = name() + ".static.jsa"; 64 staticArchiveFileLog = staticArchiveFile + ".log"; 65 dynamicArchiveFile = name() + ".dynamic.jsa"; 66 dynamicArchiveFileLog = dynamicArchiveFile + ".log"; 67 productionRunLog = name() + ".production.log"; 68 69 codeCacheFile = name() + ".code.jsa"; 70 codeCacheFileLog = codeCacheFile + ".log"; 71 cdsFile = name() + ".cds"; 72 cdsFileLog = cdsFile + ".log"; 73 cdsFilePreImage = cdsFile + ".preimage"; 74 cdsFilePreImageLog = cdsFilePreImage + ".log"; 75 aotFile = cdsFile + ".code"; 76 } 77 78 private enum Workflow { 79 STATIC, // classic -Xshare:dump workflow 80 DYNAMIC, // classic -XX:ArchiveClassesAtExit 81 LEYDEN_OLD, // The old "5 step workflow", to be phased out 82 LEYDEN, // The new "one step training workflow" -- see JDK-8320264 83 } 84 85 public enum RunMode { 86 CLASSLIST, 87 DUMP_STATIC, 88 DUMP_DYNAMIC, 89 DUMP_CODECACHE, // LEYDEN_OLD only 90 TRAINING, // LEYDEN only 91 TRAINING0, // LEYDEN only 92 TRAINING1, // LEYDEN only 93 PRODUCTION; 94 95 public boolean isStaticDump() { 96 return this == DUMP_STATIC; 97 } 98 public boolean isProductionRun() { 99 return this == PRODUCTION; 100 } 101 } 102 103 public final String name() { 104 return this.name; 105 } 106 107 // optional 108 public String[] vmArgs(RunMode runMode) { 109 return new String[0]; 110 } 111 112 // optional 113 public String classpath(RunMode runMode) { 114 return null; 115 } 116 117 // must override 118 // main class, followed by arguments to the main class 119 abstract public String[] appCommandLine(RunMode runMode); 120 121 // optional 122 public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {} 123 124 private Workflow workflow; 125 private boolean checkExitValue = true; 126 127 public final void setCheckExitValue(boolean b) { 128 checkExitValue = b; 129 } 130 131 public final boolean isStaticWorkflow() { 132 return workflow == Workflow.STATIC; 133 } 134 135 public final boolean isDynamicWorkflow() { 136 return workflow == Workflow.DYNAMIC; 137 } 138 139 public final boolean isLeydenOldWorkflow() { 140 return workflow == Workflow.LEYDEN_OLD; 141 } 142 143 public final boolean isLeydenWorkflow() { 144 return workflow == Workflow.LEYDEN; 145 } 146 147 private String logToFile(String logFile, String... logTags) { 148 StringBuilder sb = new StringBuilder("-Xlog:"); 149 String prefix = ""; 150 for (String tag : logTags) { 151 sb.append(prefix); 152 sb.append(tag); 153 prefix = ","; 154 } 155 sb.append(":file=" + logFile + "::filesize=0"); 156 return sb.toString(); 157 } 158 159 private void listOutputFile(String file) { 160 File f = new File(file); 161 if (f.exists()) { 162 System.out.println("[output file: " + file + " " + f.length() + " bytes]"); 163 } else { 164 System.out.println("[output file: " + file + " does not exist]"); 165 } 166 } 167 168 private OutputAnalyzer executeAndCheck(String[] cmdLine, RunMode runMode, String... logFiles) throws Exception { 169 ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(cmdLine); 170 Process process = pb.start(); 171 OutputAnalyzer output = CDSTestUtils.executeAndLog(process, runMode.toString()); 172 for (String logFile : logFiles) { 173 listOutputFile(logFile); 174 } 175 if (checkExitValue) { 176 output.shouldHaveExitValue(0); 177 } 178 CDSTestUtils.checkCommonExecExceptions(output); 179 checkExecution(output, runMode); 180 return output; 181 } 182 183 private OutputAnalyzer createClassList() throws Exception { 184 RunMode runMode = RunMode.CLASSLIST; 185 String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), 186 "-Xshare:off", 187 "-XX:DumpLoadedClassList=" + classListFile, 188 "-cp", classpath(runMode), 189 logToFile(classListFileLog, 190 "class+load=debug")); 191 cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); 192 return executeAndCheck(cmdLine, runMode, classListFile, classListFileLog); 193 } 194 195 private OutputAnalyzer dumpStaticArchive() throws Exception { 196 RunMode runMode = RunMode.DUMP_STATIC; 197 String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), 198 "-Xlog:cds", 199 "-Xlog:cds+heap=error", 200 "-Xshare:dump", 201 "-XX:SharedArchiveFile=" + staticArchiveFile, 202 "-XX:SharedClassListFile=" + classListFile, 203 "-cp", classpath(runMode), 204 logToFile(staticArchiveFileLog, 205 "cds=debug", 206 "cds+class=debug", 207 "cds+heap=warning", 208 "cds+resolve=debug")); 209 if (isLeydenOldWorkflow()) { 210 cmdLine = StringArrayUtils.concat(cmdLine, 211 "-XX:+PreloadSharedClasses", 212 "-XX:+ArchiveInvokeDynamic", 213 "-XX:+ArchiveDynamicProxies", 214 "-XX:+ArchiveReflectionData"); 215 } 216 217 return executeAndCheck(cmdLine, runMode, staticArchiveFile, staticArchiveFileLog); 218 } 219 220 private OutputAnalyzer dumpDynamicArchive() throws Exception { 221 RunMode runMode = RunMode.DUMP_DYNAMIC; 222 String[] cmdLine = new String[0]; 223 if (isDynamicWorkflow()) { 224 // "classic" dynamic archive 225 cmdLine = StringArrayUtils.concat(vmArgs(runMode), 226 "-Xlog:cds", 227 "-XX:ArchiveClassesAtExit=" + dynamicArchiveFile, 228 "-cp", classpath(runMode), 229 logToFile(dynamicArchiveFileLog, 230 "cds=debug", 231 "cds+class=debug", 232 "cds+resolve=debug", 233 "class+load=debug")); 234 } else { 235 // Leyden "OLD" workflow step 3 236 cmdLine = StringArrayUtils.concat(vmArgs(runMode), 237 "-Xlog:cds", 238 "-XX:ArchiveClassesAtExit=" + dynamicArchiveFile, 239 "-XX:SharedArchiveFile=" + staticArchiveFile, 240 "-XX:+RecordTraining", 241 "-cp", classpath(runMode), 242 logToFile(dynamicArchiveFileLog, 243 "cds=debug", 244 "cds+class=debug", 245 "cds+resolve=debug", 246 "class+load=debug")); 247 } 248 cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); 249 return executeAndCheck(cmdLine, runMode, dynamicArchiveFile, dynamicArchiveFileLog); 250 } 251 252 253 private OutputAnalyzer dumpCodeCache() throws Exception { 254 RunMode runMode = RunMode.DUMP_CODECACHE; 255 String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), 256 "-XX:SharedArchiveFile=" + dynamicArchiveFile, 257 "-XX:+ReplayTraining", 258 "-XX:+StoreCachedCode", 259 "-XX:CachedCodeFile=" + codeCacheFile, 260 "-XX:CachedCodeMaxSize=512M", 261 "-cp", classpath(runMode), 262 logToFile(codeCacheFileLog, "scc")); 263 264 cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); 265 return executeAndCheck(cmdLine, runMode, codeCacheFile, codeCacheFileLog); 266 } 267 268 private OutputAnalyzer oldProductionRun() throws Exception { 269 RunMode runMode = RunMode.PRODUCTION; 270 String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), 271 "-XX:SharedArchiveFile=" + dynamicArchiveFile, 272 "-XX:+ReplayTraining", 273 "-XX:+LoadCachedCode", 274 "-XX:CachedCodeFile=" + codeCacheFile, 275 "-cp", classpath(runMode), 276 logToFile(productionRunLog, "cds", "scc*=warning")); 277 cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); 278 return executeAndCheck(cmdLine, runMode, productionRunLog); 279 } 280 281 private String trainingLog(String file) { 282 return logToFile(file, 283 "cds=debug", 284 "cds+class=debug", 285 "cds+heap=warning", 286 "cds+resolve=debug"); 287 } 288 289 // normal training workflow (main JVM process spawns child process) 290 private OutputAnalyzer trainingRun() throws Exception { 291 RunMode runMode = RunMode.TRAINING; 292 File f = new File(cdsFile); 293 f.delete(); 294 String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), 295 "-XX:+PreloadSharedClasses", 296 "-XX:+ArchiveInvokeDynamic", 297 "-XX:+ArchiveDynamicProxies", 298 //"-XX:+ArchiveReflectionData", 299 "-XX:CacheDataStore=" + cdsFile, 300 "-cp", classpath(runMode), 301 // Use PID to distinguish the logs of the training process 302 // and the forked final image dump process. 303 "-Xlog:cds::uptime,level,tags,pid", 304 trainingLog(cdsFileLog)); 305 cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); 306 OutputAnalyzer out = executeAndCheck(cmdLine, runMode, cdsFile, cdsFileLog); 307 listOutputFile(cdsFile + ".log.0"); // the preimage dump 308 return out; 309 } 310 311 // "split" training workflow (launch the two processes manually, for easier debugging); 312 private OutputAnalyzer trainingRun0() throws Exception { 313 RunMode runMode = RunMode.TRAINING0; 314 File f = new File(cdsFile); 315 f.delete(); 316 String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), 317 "-XX:+UnlockDiagnosticVMOptions", 318 "-XX:+CDSManualFinalImage", 319 "-XX:+PreloadSharedClasses", 320 "-XX:+ArchiveInvokeDynamic", 321 "-XX:+ArchiveDynamicProxies", 322 //"-XX:+ArchiveReflectionData", 323 "-XX:CacheDataStore=" + cdsFile, 324 "-cp", classpath(runMode), 325 trainingLog(cdsFilePreImageLog)); 326 cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); 327 return executeAndCheck(cmdLine, runMode, cdsFilePreImage, cdsFilePreImageLog); 328 } 329 private OutputAnalyzer trainingRun1() throws Exception { 330 RunMode runMode = RunMode.TRAINING1; 331 File f = new File(cdsFile); 332 f.delete(); 333 String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), 334 "-XX:+UnlockDiagnosticVMOptions", 335 "-XX:+PreloadSharedClasses", 336 "-XX:+ArchiveInvokeDynamic", 337 "-XX:+ArchiveDynamicProxies", 338 //"-XX:+ArchiveReflectionData", 339 "-XX:CacheDataStore=" + cdsFile, 340 "-XX:CDSPreimage=" + cdsFilePreImage, 341 "-cp", classpath(runMode), 342 trainingLog(cdsFileLog)); 343 cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); 344 return executeAndCheck(cmdLine, runMode, cdsFile, aotFile, cdsFileLog); 345 } 346 347 private OutputAnalyzer productionRun() throws Exception { 348 RunMode runMode = RunMode.PRODUCTION; 349 String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), 350 "-cp", classpath(runMode), 351 logToFile(productionRunLog, "cds")); 352 353 if (isStaticWorkflow()) { 354 cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + staticArchiveFile); 355 } else if (isDynamicWorkflow()) { 356 cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + dynamicArchiveFile); 357 } else { 358 cmdLine = StringArrayUtils.concat(cmdLine, "-XX:CacheDataStore=" + cdsFile); 359 } 360 361 cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); 362 return executeAndCheck(cmdLine, runMode, productionRunLog); 363 } 364 365 public void run(String args[]) throws Exception { 366 String err = "Must have exactly one command line argument of the following: "; 367 String prefix = ""; 368 for (Workflow wf : Workflow.values()) { 369 err += prefix; 370 err += wf; 371 prefix = ", "; 372 } 373 if (args.length != 1) { 374 throw new RuntimeException(err); 375 } else { 376 if (args[0].equals("STATIC")) { 377 runStaticWorkflow(); 378 } else if (args[0].equals("DYNAMIC")) { 379 runDynamicWorkflow(); 380 } else if (args[0].equals("LEYDEN_OLD")) { 381 runLeydenOldWorkflow(); 382 } else if (args[0].equals("LEYDEN")) { 383 runLeydenWorkflow(false); 384 } else if (args[0].equals("LEYDEN_TRAINONLY")) { 385 runLeydenWorkflow(true); 386 } else { 387 throw new RuntimeException(err); 388 } 389 } 390 } 391 392 private void runStaticWorkflow() throws Exception { 393 this.workflow = Workflow.STATIC; 394 createClassList(); 395 dumpStaticArchive(); 396 productionRun(); 397 } 398 399 private void runDynamicWorkflow() throws Exception { 400 this.workflow = Workflow.DYNAMIC; 401 dumpDynamicArchive(); 402 productionRun(); 403 } 404 405 private void runLeydenOldWorkflow() throws Exception { 406 this.workflow = Workflow.LEYDEN_OLD; 407 createClassList(); 408 dumpStaticArchive(); 409 dumpDynamicArchive(); 410 dumpCodeCache(); 411 oldProductionRun(); 412 } 413 414 private void runLeydenWorkflow(boolean trainOnly) throws Exception { 415 this.workflow = Workflow.LEYDEN; 416 if (System.getProperty("CDSAppTester.split.new.workflow") != null) { 417 trainingRun0(); 418 trainingRun1(); 419 } else { 420 trainingRun(); 421 } 422 if (!trainOnly) { 423 productionRun(); 424 } 425 } 426 }