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