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 }