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:+ArchiveInvokeDynamic",
204                                               "-XX:+ArchiveDynamicProxies",
205                                               "-XX:+ArchiveReflectionData");
206         }
207 
208         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(cmdLine);
209         Process process = pb.start();
210         OutputAnalyzer output = CDSTestUtils.executeAndLog(process, "static");
211         listOutputFile(staticArchiveFile);
212         listOutputFile(staticArchiveFile + ".log");
213         checkExecutionHelper(output, runMode);
214         return output;
215     }
216 
217     private OutputAnalyzer dumpDynamicArchive() throws Exception {
218         RunMode runMode = RunMode.DUMP_DYNAMIC;
219         String[] cmdLine;
220         if (isDynamicWorkflow()) {
221           // "classic" dynamic archive
222           cmdLine = StringArrayUtils.concat(vmArgs(runMode), dynamicDumpLog(),
223                                             "-Xlog:cds",
224                                             "-XX:ArchiveClassesAtExit=" + dynamicArchiveFile,
225                                             "-cp", classpath(runMode));
226         } else {
227           // Leyden "OLD" workflow step 3
228           cmdLine = StringArrayUtils.concat(vmArgs(runMode), dynamicDumpLog(),
229                                             "-Xlog:cds",
230                                             "-XX:ArchiveClassesAtExit=" + dynamicArchiveFile,
231                                             "-XX:SharedArchiveFile=" + staticArchiveFile,
232                                             "-XX:+RecordTraining",
233                                             "-cp", classpath(runMode));
234         }
235         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
236 
237         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(cmdLine);
238         Process process = pb.start();
239         OutputAnalyzer output = CDSTestUtils.executeAndLog(process, "dynamic");
240         listOutputFile(dynamicArchiveFile);
241         listOutputFile(dynamicArchiveFile + ".log");
242         checkExecutionHelper(output, runMode);
243         return output;
244     }
245 
246     private OutputAnalyzer dumpCodeCache() throws Exception {
247         RunMode runMode = RunMode.DUMP_CODECACHE;
248         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), codeCacheDumpLog(),
249                                                    "-XX:SharedArchiveFile=" + dynamicArchiveFile,
250                                                    "-XX:+ReplayTraining",
251                                                    "-XX:+StoreCachedCode",
252                                                    "-XX:CachedCodeFile=" + codeCacheFile,
253                                                    "-XX:CachedCodeMaxSize=512M",
254                                                    "-cp", classpath(runMode));
255         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
256 
257         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(cmdLine);
258         Process process = pb.start();
259         OutputAnalyzer output = CDSTestUtils.executeAndLog(process, "code");
260         listOutputFile(codeCacheFile);
261         listOutputFile(codeCacheFile + ".log");
262         checkExecutionHelper(output, runMode);
263         return output;
264     }
265 
266     private OutputAnalyzer oldProductionRun() throws Exception {
267         RunMode runMode = RunMode.PRODUCTION;
268         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), oldProductionRunLog(),
269                                                    "-XX:SharedArchiveFile=" + dynamicArchiveFile,
270                                                    "-XX:+ReplayTraining",
271                                                    "-XX:+LoadCachedCode",
272                                                    "-XX:CachedCodeFile=" + codeCacheFile,
273                                                    "-cp", classpath(runMode));
274         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
275 
276         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(cmdLine);
277         Process process = pb.start();
278         OutputAnalyzer output = CDSTestUtils.executeAndLog(process, "old-production");
279         listOutputFile(name() + ".old-production.log");
280         checkExecutionHelper(output, runMode);
281         return output;
282     }
283 
284     // normal training workflow (main JVM process spawns child process)
285     private OutputAnalyzer trainingRun() throws Exception {
286         RunMode runMode = RunMode.TRAINING;
287         File f = new File(cdsFile);
288         f.delete();
289         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), trainingLog(),
290                                                    "-XX:+ArchiveInvokeDynamic",
291                                                    "-XX:+ArchiveDynamicProxies",
292                                                  //"-XX:+ArchiveReflectionData",
293                                                    "-XX:CacheDataStore=" + cdsFile,
294                                                    "-cp", classpath(runMode),
295                                                    // Use PID to distinguish the logs of the training process
296                                                    // and the forked final image dump process.
297                                                    "-Xlog:cds::uptime,level,tags,pid");
298         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
299 
300         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(cmdLine);
301         Process process = pb.start();
302         OutputAnalyzer output = CDSTestUtils.executeAndLog(process, "training");
303         listOutputFile(cdsFile);
304         listOutputFile(cdsFile + ".log");   // The final dump
305         listOutputFile(cdsFile + ".log.0"); // the preimage dump
306         checkExecutionHelper(output, runMode);
307         return output;
308     }
309 
310     // "split" training workflow (launch the two processes manually, for easier debugging);
311     private OutputAnalyzer trainingRun0() throws Exception {
312         RunMode runMode = RunMode.TRAINING0;
313         File f = new File(cdsFile);
314         f.delete();
315         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), trainingLog0(),
316                                                    "-XX:+UnlockDiagnosticVMOptions",
317                                                    "-XX:+CDSManualFinalImage",
318                                                    "-XX:+ArchiveInvokeDynamic",
319                                                    "-XX:+ArchiveDynamicProxies",
320                                                  //"-XX:+ArchiveReflectionData",
321                                                    "-XX:CacheDataStore=" + cdsFile,
322                                                    "-cp", classpath(runMode));
323         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
324 
325         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(cmdLine);
326         Process process = pb.start();
327         OutputAnalyzer output = CDSTestUtils.executeAndLog(process, "training0");
328         listOutputFile(cdsFile);
329         listOutputFile(cdsFile + ".0.log");
330         checkExecutionHelper(output, runMode);
331         return output;
332     }
333     private OutputAnalyzer trainingRun1() throws Exception {
334         RunMode runMode = RunMode.TRAINING1;
335         File f = new File(cdsFile);
336         f.delete();
337         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), trainingLog1(),
338                                                    "-XX:+UnlockDiagnosticVMOptions",
339                                                    "-XX:+ArchiveInvokeDynamic",
340                                                    "-XX:+ArchiveDynamicProxies",
341                                                  //"-XX:+ArchiveReflectionData",
342                                                    "-XX:CacheDataStore=" + cdsFile,
343                                                    "-XX:CDSPreimage=" + cdsFile + ".preimage",
344                                                    "-cp", classpath(runMode));
345         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
346 
347         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(cmdLine);
348         Process process = pb.start();
349         OutputAnalyzer output = CDSTestUtils.executeAndLog(process, "training1");
350         listOutputFile(cdsFile);
351         listOutputFile(cdsFile + ".1.log");
352         checkExecutionHelper(output, runMode);
353         return output;
354     }
355 
356     private OutputAnalyzer productionRun() throws Exception {
357         RunMode runMode = RunMode.PRODUCTION;
358         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), productionRunLog(),
359                                                    "-cp", classpath(runMode));
360         if (isStaticWorkflow()) {
361             cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + staticArchiveFile);
362         } else if (isDynamicWorkflow()) {
363             cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + dynamicArchiveFile);
364         } else {
365             cmdLine = StringArrayUtils.concat(cmdLine, "-XX:CacheDataStore=" + cdsFile);
366         }
367 
368         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
369 
370         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(cmdLine);
371         Process process = pb.start();
372         OutputAnalyzer output = CDSTestUtils.executeAndLog(process, "production");
373         listOutputFile(name() + ".production.log");
374         checkExecutionHelper(output, runMode);
375         return output;
376     }
377 
378     public void run(String args[]) throws Exception {
379         if (args.length == 1) {
380             if (args[0].equals("STATIC")) {
381                 runStaticWorkflow();
382                 return;
383             }
384             if (args[0].equals("DYNAMIC")) {
385                 runDynamicWorkflow();
386                 return;
387             }
388             if (args[0].equals("LEYDEN_OLD")) {
389                 runLeydenOldWorkflow();
390                 return;
391             }
392             if (args[0].equals("LEYDEN")) {
393                 runLeydenWorkflow();
394                 return;
395             }
396         }
397 
398         throw new RuntimeException("Must have exactly one command line argument: STATIC, DYNAMIC, LEYDEN_OLD or LEYDEN");
399     }
400 
401     private void runStaticWorkflow() throws Exception {
402         this.workflow = Workflow.STATIC;
403         createClassList();
404         dumpStaticArchive();
405         productionRun();
406     }
407 
408     private void runDynamicWorkflow() throws Exception {
409         this.workflow = Workflow.DYNAMIC;
410         dumpDynamicArchive();
411         productionRun();
412     }
413 
414     private void runLeydenOldWorkflow() throws Exception {
415         this.workflow = Workflow.LEYDEN_OLD;
416         createClassList();
417         dumpStaticArchive();
418         dumpDynamicArchive();
419         dumpCodeCache();
420         oldProductionRun();
421     }
422 
423     private void runLeydenWorkflow() throws Exception {
424         this.workflow = Workflow.LEYDEN;
425         if (System.getProperty("CDSAppTester.split.new.workflow") != null) {
426             trainingRun0();
427             trainingRun1();
428         } else {
429             trainingRun();
430         }
431         productionRun();
432     }
433 }