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