CompileJavaPackages.java revision 2593:035b01d356ee
1/*
2 * Copyright (c) 2012, 2014, 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.sjavac;
27
28import java.io.File;
29import java.io.PrintStream;
30import java.net.URI;
31import java.util.Arrays;
32import java.util.Collections;
33import java.util.Set;
34import java.util.Map;
35
36import com.sun.tools.sjavac.options.Options;
37import com.sun.tools.sjavac.server.CompilationResult;
38import com.sun.tools.sjavac.server.Sjavac;
39import com.sun.tools.sjavac.server.SysInfo;
40
41/**
42 * This transform compiles a set of packages containing Java sources.
43 * The compile request is divided into separate sets of source files.
44 * For each set a separate request thread is dispatched to a javac server
45 * and the meta data is accumulated. The number of sets correspond more or
46 * less to the number of cores. Less so now, than it will in the future.
47 *
48 * <p><b>This is NOT part of any supported API.
49 * If you write code that depends on this, you do so at your own
50 * risk.  This code and its internal interfaces are subject to change
51 * or deletion without notice.</b></p>
52 */
53public class CompileJavaPackages implements Transformer {
54
55    // The current limited sharing of data between concurrent JavaCompilers
56    // in the server will not give speedups above 3 cores. Thus this limit.
57    // We hope to improve this in the future.
58    final static int limitOnConcurrency = 3;
59
60    Options args;
61
62    public void setExtra(String e) {
63    }
64
65    public void setExtra(Options a) {
66        args = a;
67    }
68
69    public boolean transform(final Sjavac sjavac,
70                             Map<String,Set<URI>> pkgSrcs,
71                             final Set<URI>             visibleSources,
72                             final Map<URI,Set<String>> visibleClasses,
73                             Map<String,Set<String>> oldPackageDependents,
74                             URI destRoot,
75                             final Map<String,Set<URI>>    packageArtifacts,
76                             final Map<String,Set<String>> packageDependencies,
77                             final Map<String,String>      packagePubapis,
78                             int debugLevel,
79                             boolean incremental,
80                             int numCores,
81                             final PrintStream out,
82                             final PrintStream err)
83    {
84        boolean rc = true;
85        boolean concurrentCompiles = true;
86
87        // Fetch the id.
88        final String id = Util.extractStringOption("id", sjavac.serverSettings());
89        // Only keep portfile and sjavac settings..
90        String psServerSettings = Util.cleanSubOptions(Util.set("portfile","sjavac","background","keepalive"), sjavac.serverSettings());
91
92        // Get maximum heap size from the server!
93        SysInfo sysinfo = sjavac.getSysInfo();
94        if (sysinfo.numCores == -1) {
95            Log.error("Could not query server for sysinfo!");
96            return false;
97        }
98        int numMBytes = (int)(sysinfo.maxMemory / ((long)(1024*1024)));
99        Log.debug("Server reports "+numMBytes+"MiB of memory and "+sysinfo.numCores+" cores");
100
101        if (numCores <= 0) {
102            // Set the requested number of cores to the number of cores on the server.
103            numCores = sysinfo.numCores;
104            Log.debug("Number of jobs not explicitly set, defaulting to "+sysinfo.numCores);
105        } else if (sysinfo.numCores < numCores) {
106            // Set the requested number of cores to the number of cores on the server.
107            Log.debug("Limiting jobs from explicitly set "+numCores+" to cores available on server: "+sysinfo.numCores);
108            numCores = sysinfo.numCores;
109        } else {
110            Log.debug("Number of jobs explicitly set to "+numCores);
111        }
112        // More than three concurrent cores does not currently give a speedup, at least for compiling the jdk
113        // in the OpenJDK. This will change in the future.
114        int numCompiles = numCores;
115        if (numCores > limitOnConcurrency) numCompiles = limitOnConcurrency;
116        // Split the work up in chunks to compiled.
117
118        int numSources = 0;
119        for (String s : pkgSrcs.keySet()) {
120            Set<URI> ss = pkgSrcs.get(s);
121            numSources += ss.size();
122        }
123
124        int sourcesPerCompile = numSources / numCompiles;
125
126        // For 64 bit Java, it seems we can compile the OpenJDK 8800 files with a 1500M of heap
127        // in a single chunk, with reasonable performance.
128        // For 32 bit java, it seems we need 1G of heap.
129        // Number experimentally determined when compiling the OpenJDK.
130        // Includes space for reasonably efficient garbage collection etc,
131        // Calculating backwards gives us a requirement of
132        // 1500M/8800 = 175 KiB for 64 bit platforms
133        // and 1G/8800 = 119 KiB for 32 bit platform
134        // for each compile.....
135        int kbPerFile = 175;
136        String osarch = System.getProperty("os.arch");
137        String dataModel = System.getProperty("sun.arch.data.model");
138        if ("32".equals(dataModel)) {
139            // For 32 bit platforms, assume it is slightly smaller
140            // because of smaller object headers and pointers.
141            kbPerFile = 119;
142        }
143        int numRequiredMBytes = (kbPerFile*numSources)/1024;
144        Log.debug("For os.arch "+osarch+" the empirically determined heap required per file is "+kbPerFile+"KiB");
145        Log.debug("Server has "+numMBytes+"MiB of heap.");
146        Log.debug("Heuristics say that we need "+numRequiredMBytes+"MiB of heap for all source files.");
147        // Perform heuristics to see how many cores we can use,
148        // or if we have to the work serially in smaller chunks.
149        if (numMBytes < numRequiredMBytes) {
150            // Ouch, cannot fit even a single compile into the heap.
151            // Split it up into several serial chunks.
152            concurrentCompiles = false;
153            // Limit the number of sources for each compile to 500.
154            if (numSources < 500) {
155                numCompiles = 1;
156                sourcesPerCompile = numSources;
157                Log.debug("Compiling as a single source code chunk to stay within heap size limitations!");
158            } else if (sourcesPerCompile > 500) {
159                // This number is very low, and tuned to dealing with the OpenJDK
160                // where the source is >very< circular! In normal application,
161                // with less circularity the number could perhaps be increased.
162                numCompiles = numSources / 500;
163                sourcesPerCompile = numSources/numCompiles;
164                Log.debug("Compiling source as "+numCompiles+" code chunks serially to stay within heap size limitations!");
165            }
166        } else {
167            if (numCompiles > 1) {
168                // Ok, we can fit at least one full compilation on the heap.
169                float usagePerCompile = (float)numRequiredMBytes / ((float)numCompiles * (float)0.7);
170                int usage = (int)(usagePerCompile * (float)numCompiles);
171                Log.debug("Heuristics say that for "+numCompiles+" concurrent compiles we need "+usage+"MiB");
172                if (usage > numMBytes) {
173                    // Ouch it does not fit. Reduce to a single chunk.
174                    numCompiles = 1;
175                    sourcesPerCompile = numSources;
176                    // What if the relationship betweem number of compile_chunks and num_required_mbytes
177                    // is not linear? Then perhaps 2 chunks would fit where 3 does not. Well, this is
178                    // something to experiment upon in the future.
179                    Log.debug("Limiting compile to a single thread to stay within heap size limitations!");
180                }
181            }
182        }
183
184        Log.debug("Compiling sources in "+numCompiles+" chunk(s)");
185
186        // Create the chunks to be compiled.
187        final CompileChunk[] compileChunks = createCompileChunks(pkgSrcs, oldPackageDependents,
188                numCompiles, sourcesPerCompile);
189
190        if (Log.isDebugging()) {
191            int cn = 1;
192            for (CompileChunk cc : compileChunks) {
193                Log.debug("Chunk "+cn+" for "+id+" ---------------");
194                cn++;
195                for (URI u : cc.srcs) {
196                    Log.debug(""+u);
197                }
198            }
199        }
200
201        // The return values for each chunked compile.
202        final CompilationResult[] rn = new CompilationResult[numCompiles];
203        // The requets, might or might not run as a background thread.
204        final Thread[] requests  = new Thread[numCompiles];
205
206        long start = System.currentTimeMillis();
207
208        for (int i=0; i<numCompiles; ++i) {
209            final int ii = i;
210            final CompileChunk cc = compileChunks[i];
211
212            // Pass the num_cores and the id (appended with the chunk number) to the server.
213            final String cleanedServerSettings = psServerSettings+",poolsize="+numCores+",id="+id+"-"+i;
214
215            requests[i] = new Thread() {
216                @Override
217                public void run() {
218                    rn[ii] = sjavac.compile("n/a",
219                                                  id + "-" + ii,
220                                                  args.prepJavacArgs(),
221                                                  Collections.<File>emptyList(),
222                                                  cc.srcs,
223                                                  visibleSources);
224                    packageArtifacts.putAll(rn[ii].packageArtifacts);
225                    packageDependencies.putAll(rn[ii].packageDependencies);
226                    packagePubapis.putAll(rn[ii].packagePubapis);
227                }
228            };
229
230            if (cc.srcs.size() > 0) {
231                String numdeps = "";
232                if (cc.numDependents > 0) numdeps = "(with "+cc.numDependents+" dependents) ";
233                if (!incremental || cc.numPackages > 16) {
234                    String info = "("+cc.pkgFromTos+")";
235                    if (info.equals("( to )")) {
236                        info = "";
237                    }
238                    Log.info("Compiling "+cc.srcs.size()+" files "+numdeps+"in "+cc.numPackages+" packages "+info);
239                } else {
240                    Log.info("Compiling "+cc.pkgNames+numdeps);
241                }
242                if (concurrentCompiles) {
243                    requests[ii].start();
244                }
245                else {
246                    requests[ii].run();
247                    // If there was an error, then stop early when running single threaded.
248                    if (rn[i].returnCode != 0) {
249                        Log.info(rn[i].stdout);
250                        Log.error(rn[i].stderr);
251                        return false;
252                    }
253                }
254            }
255        }
256        if (concurrentCompiles) {
257            // If there are background threads for the concurrent compiles, then join them.
258            for (int i=0; i<numCompiles; ++i) {
259                try { requests[i].join(); } catch (InterruptedException e) { }
260            }
261        }
262
263        // Check the return values.
264        for (int i=0; i<numCompiles; ++i) {
265            if (compileChunks[i].srcs.size() > 0) {
266                if (rn[i].returnCode != 0) {
267                    Log.info(rn[i].stdout);
268                    Log.error(rn[i].stderr);
269                    rc = false;
270                }
271            }
272        }
273        long duration = System.currentTimeMillis() - start;
274        long minutes = duration/60000;
275        long seconds = (duration-minutes*60000)/1000;
276        Log.debug("Compilation of "+numSources+" source files took "+minutes+"m "+seconds+"s");
277
278        return rc;
279    }
280
281
282    /**
283     * Split up the sources into compile chunks. If old package dependents information
284     * is available, sort the order of the chunks into the most dependent first!
285     * (Typically that chunk contains the java.lang package.) In the future
286     * we could perhaps improve the heuristics to put the sources into even more sensible chunks.
287     * Now the package are simple sorted in alphabetical order and chunked, then the chunks
288     * are sorted on how dependent they are.
289     *
290     * @param pkgSrcs The sources to compile.
291     * @param oldPackageDependents Old package dependents, if non-empty, used to sort the chunks.
292     * @param numCompiles The number of chunks.
293     * @param sourcesPerCompile The number of sources per chunk.
294     * @return
295     */
296    CompileChunk[] createCompileChunks(Map<String,Set<URI>> pkgSrcs,
297                                 Map<String,Set<String>> oldPackageDependents,
298                                 int numCompiles,
299                                 int sourcesPerCompile) {
300
301        CompileChunk[] compileChunks = new CompileChunk[numCompiles];
302        for (int i=0; i<compileChunks.length; ++i) {
303            compileChunks[i] = new CompileChunk();
304        }
305
306        // Now go through the packages and spread out the source on the different chunks.
307        int ci = 0;
308        // Sort the packages
309        String[] packageNames = pkgSrcs.keySet().toArray(new String[0]);
310        Arrays.sort(packageNames);
311        String from = null;
312        for (String pkgName : packageNames) {
313            CompileChunk cc = compileChunks[ci];
314            Set<URI> s = pkgSrcs.get(pkgName);
315            if (cc.srcs.size()+s.size() > sourcesPerCompile && ci < numCompiles-1) {
316                from = null;
317                ci++;
318                cc = compileChunks[ci];
319            }
320            cc.numPackages++;
321            cc.srcs.addAll(s);
322
323            // Calculate nice package names to use as information when compiling.
324            String justPkgName = Util.justPackageName(pkgName);
325            // Fetch how many packages depend on this package from the old build state.
326            Set<String> ss = oldPackageDependents.get(pkgName);
327            if (ss != null) {
328                // Accumulate this information onto this chunk.
329                cc.numDependents += ss.size();
330            }
331            if (from == null || from.trim().equals("")) from = justPkgName;
332            cc.pkgNames.append(justPkgName+"("+s.size()+") ");
333            cc.pkgFromTos = from+" to "+justPkgName;
334        }
335        // If we are compiling serially, sort the chunks, so that the chunk (with the most dependents) (usually the chunk
336        // containing java.lang.Object, is to be compiled first!
337        // For concurrent compilation, this does not matter.
338        Arrays.sort(compileChunks);
339        return compileChunks;
340    }
341}
342