001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
005     * Space Science and Engineering Center (SSEC)
006     * University of Wisconsin - Madison
007     * 1225 W. Dayton Street, Madison, WI 53706, USA
008     * https://www.ssec.wisc.edu/mcidas
009     * 
010     * All Rights Reserved
011     * 
012     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013     * some McIDAS-V source code is based on IDV and VisAD source code.  
014     * 
015     * McIDAS-V is free software; you can redistribute it and/or modify
016     * it under the terms of the GNU Lesser Public License as published by
017     * the Free Software Foundation; either version 3 of the License, or
018     * (at your option) any later version.
019     * 
020     * McIDAS-V is distributed in the hope that it will be useful,
021     * but WITHOUT ANY WARRANTY; without even the implied warranty of
022     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023     * GNU Lesser Public License for more details.
024     * 
025     * You should have received a copy of the GNU Lesser Public License
026     * along with this program.  If not, see http://www.gnu.org/licenses.
027     */
028    package edu.wisc.ssec.mcidasv.util;
029    
030    import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList;
031    import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.cast;
032    import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newLinkedHashMap;
033    
034    import java.io.BufferedReader;
035    import java.io.InputStream;
036    import java.io.InputStreamReader;
037    import java.lang.management.ManagementFactory;
038    import java.lang.management.OperatingSystemMXBean;
039    import java.lang.reflect.Method;
040    
041    import java.util.List;
042    import java.util.Map;
043    import java.util.Properties;
044    import java.util.TreeSet;
045    
046    import java.awt.DisplayMode;
047    import java.awt.GraphicsConfiguration;
048    import java.awt.GraphicsDevice;
049    import java.awt.GraphicsEnvironment;
050    import java.awt.Rectangle;
051    
052    import javax.media.j3d.Canvas3D;
053    import javax.media.j3d.VirtualUniverse;
054    
055    import org.python.core.Py;
056    import org.python.core.PySystemState;
057    
058    import org.slf4j.Logger;
059    import org.slf4j.LoggerFactory;
060    
061    import ucar.unidata.idv.ArgsManager;
062    import ucar.unidata.idv.IdvResourceManager.IdvResource;
063    import ucar.unidata.util.ResourceCollection;
064    import ucar.visad.display.DisplayUtil;
065    
066    import edu.wisc.ssec.mcidasv.Constants;
067    import edu.wisc.ssec.mcidasv.McIDASV;
068    import edu.wisc.ssec.mcidasv.StateManager;
069    
070    /**
071     * Utility methods for querying the state of the user's machine.
072     */
073    public class SystemState {
074    
075        /** Handy logging object. */
076        private static final Logger logger = LoggerFactory.getLogger(SystemState.class);
077    
078        // Don't allow outside instantiation.
079        private SystemState() { }
080    
081        public static String escapeWhitespaceChars(final CharSequence sequence) {
082            StringBuilder sb = new StringBuilder(sequence.length() * 7);
083            for (int i = 0; i < sequence.length(); i++) {
084                switch (sequence.charAt(i)) {
085                    case '\t': sb.append("\\t"); break;
086                    case '\n': sb.append('\\').append('n'); break;
087                    case '\013': sb.append("\\013"); break;
088                    case '\f': sb.append("\\f"); break;
089                    case '\r': sb.append("\\r"); break;
090                    case '\u0085': sb.append("\\u0085"); break;
091                    case '\u1680': sb.append("\\u1680"); break;
092                    case '\u2028': sb.append("\\u2028"); break;
093                    case '\u2029': sb.append("\\u2029"); break;
094                    case '\u205f': sb.append("\\u205f"); break;
095                    case '\u3000': sb.append("\\u3000"); break;
096                }
097            }
098            logger.trace("incoming={} outgoing={}", sequence.length(), sb.length());
099            return sb.toString();
100        }
101    
102        /**
103         * Attempt to invoke {@code OperatingSystemMXBean.methodName} via 
104         * reflection.
105         * 
106         * @param <T> Either {@code Long} or {@code Double}.
107         * @param methodName The method to invoke. Must belong to 
108         * {@code com.sun.management.OperatingSystemMXBean}.
109         * @param defaultValue Default value to return, must be in 
110         * {@literal "boxed"} form.
111         * 
112         * @return Either the result of the {@code methodName} call or 
113         * {@code defaultValue}.
114         */
115        private static <T> T hackyMethodCall(final String methodName, final T defaultValue) {
116            assert methodName != null : "Cannot invoke a null method name";
117            assert methodName.length() > 0: "Cannot invoke an empty method name";
118            OperatingSystemMXBean osBean = 
119                ManagementFactory.getOperatingSystemMXBean();
120            T result = defaultValue;
121            try {
122                Method m = osBean.getClass().getMethod(methodName);
123                m.setAccessible(true);
124                // don't suppress warnings because we cannot guarantee that this
125                // cast is correct.
126                result = (T)m.invoke(osBean);
127            } catch (Exception e) {
128                logger.error("couldn't call method: " + methodName, e);
129            }
130            return result;
131        }
132    
133        /**
134         * Returns the contents of Jython's registry (basically just Jython-specific
135         * properties) as well as some of the information from Python's 
136         * {@literal "sys"} module. 
137         * 
138         * @return Jython's configuration settings. 
139         */
140        public static Map<Object, Object> queryJythonProps() {
141            Map<Object, Object> properties = newLinkedHashMap(PySystemState.registry);
142            properties.put("sys.argv", Py.getSystemState().argv.toString());
143            properties.put("sys.builtin_module_names", PySystemState.builtin_module_names.toString());
144            properties.put("sys.byteorder", PySystemState.byteorder);
145            properties.put("sys.isPackageCacheEnabled", PySystemState.isPackageCacheEnabled());
146            properties.put("sys.path", Py.getSystemState().path);
147            properties.put("sys.platform", PySystemState.platform.toString());
148            properties.put("sys.version", PySystemState.version);
149            properties.put("sys.version_info", PySystemState.version_info);
150            return properties;
151        }
152    
153        /**
154         * Attempts to call methods belonging to 
155         * {@code com.sun.management.OperatingSystemMXBean}. If successful, we'll
156         * have the following information:
157         * <ul>
158         *   <li>opsys.memory.virtual.committed: virtual memory that is guaranteed to be available</li>
159         *   <li>opsys.memory.swap.total: total amount of swap space in bytes</li>
160         *   <li>opsys.memory.swap.free: free swap space in bytes</li>
161         *   <li>opsys.cpu.time: CPU time used by the process (nanoseconds)</li>
162         *   <li>opsys.memory.physical.free: free physical memory in bytes</li>
163         *   <li>opsys.memory.physical.total: physical memory in bytes</li>
164         *   <li>opsys.load: system load average for the last minute</li>
165         * </ul>
166         * 
167         * @return Map of properties that contains interesting information about
168         * the hardware McIDAS-V is using.
169         */
170        public static Map<String, String> queryOpSysProps() {
171            Map<String, String> properties = newLinkedHashMap(10);
172            long committed = hackyMethodCall("getCommittedVirtualMemorySize", Long.MIN_VALUE);
173            long freeMemory = hackyMethodCall("getFreePhysicalMemorySize", Long.MIN_VALUE);
174            long freeSwap = hackyMethodCall("getFreeSwapSpaceSize", Long.MIN_VALUE);
175            long cpuTime = hackyMethodCall("getProcessCpuTime", Long.MIN_VALUE);
176            long totalMemory = hackyMethodCall("getTotalPhysicalMemorySize", Long.MIN_VALUE);
177            long totalSwap = hackyMethodCall("getTotalSwapSpaceSize", Long.MIN_VALUE);
178            double loadAvg = hackyMethodCall("getSystemLoadAverage", Double.NaN);
179    
180            Runtime rt = Runtime.getRuntime();
181            long currentMem = rt.totalMemory() - rt.freeMemory();
182    
183            properties.put("opsys.cpu.time", Long.toString(cpuTime));
184            properties.put("opsys.load", Double.toString(loadAvg));
185            properties.put("opsys.memory.jvm.current", Long.toString(currentMem));
186            properties.put("opsys.memory.jvm.max", Long.toString(rt.maxMemory()));
187            properties.put("opsys.memory.virtual.committed", Long.toString(committed));
188            properties.put("opsys.memory.physical.free", Long.toString(freeMemory));
189            properties.put("opsys.memory.physical.total", Long.toString(totalMemory));
190            properties.put("opsys.memory.swap.free", Long.toString(freeSwap));
191            properties.put("opsys.memory.swap.total", Long.toString(totalSwap));
192    
193            return properties;
194        }
195    
196        /**
197         * Polls Java for information about the user's machine. We're specifically
198         * after memory statistics, number of processors, and display information.
199         * 
200         * @return {@link Map} of properties that describes the user's machine.
201         */
202        public static Map<String, String> queryMachine() {
203            Map<String, String> props = newLinkedHashMap();
204    
205            // cpu count and whatnot
206            int processors = Runtime.getRuntime().availableProcessors();
207            props.put("opsys.cpu.count", Integer.toString(processors));
208    
209            // memory: available, used, etc
210            props.putAll(queryOpSysProps());
211    
212            // screen: count, resolution(s)
213            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
214            int displayCount = ge.getScreenDevices().length;
215    
216            for (int i = 0; i < displayCount; i++) {
217                String baseId = "opsys.display."+i+'.';
218                GraphicsDevice dev = ge.getScreenDevices()[i];
219                DisplayMode mode = dev.getDisplayMode();
220                props.put(baseId+"name", dev.getIDstring());
221                props.put(baseId+"depth", Integer.toString(mode.getBitDepth()));
222                props.put(baseId+"width", Integer.toString(mode.getWidth()));
223                props.put(baseId+"height", Integer.toString(mode.getHeight()));
224                props.put(baseId+"refresh", Integer.toString(mode.getRefreshRate()));
225            }
226            return props;
227        }
228    
229        /**
230         * Returns a mapping of display number to a {@link java.awt.Rectangle} 
231         * that represents the {@literal "bounds"} of the display.
232         *
233         * @return Rectangles representing the {@literal "bounds"} of the current
234         * display devices.
235         */
236        public static Map<Integer, Rectangle> getDisplayBounds() {
237            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
238            int idx = 0;
239            Map<Integer, Rectangle> map = newLinkedHashMap(ge.getScreenDevices().length * 2);
240            for (GraphicsDevice dev : ge.getScreenDevices()) {
241                for (GraphicsConfiguration config : dev.getConfigurations()) {
242                    map.put(idx++, config.getBounds());
243                }
244            }
245            return map;
246        }
247    
248        // TODO(jon): this should really be a polygon
249        public static Rectangle getVirtualDisplayBounds() {
250            Rectangle virtualBounds = new Rectangle();
251            for (Rectangle bounds : getDisplayBounds().values()) {
252                virtualBounds = virtualBounds.union(bounds);
253            }
254            return virtualBounds;
255        }
256    
257        /**
258         * Polls Java 3D for information about its environment. Specifically, we 
259         * call {@link VirtualUniverse#getProperties()} and 
260         * {@link Canvas3D#queryProperties()}.
261         * 
262         * @return As much information as Java 3D can provide.
263         */
264        @SuppressWarnings("unchecked") // casting to Object, so this should be fine.
265        public static Map<String, Object> queryJava3d() {
266    
267            Map<String, Object> universeProps = 
268                (Map<String, Object>)VirtualUniverse.getProperties();
269    
270            GraphicsConfiguration config =
271                DisplayUtil.getPreferredConfig(null, true, false);
272            Map<String, Object> c3dMap = new Canvas3D(config).queryProperties();
273    
274            Map<String, Object> props =
275                    newLinkedHashMap(universeProps.size() + c3dMap.size());
276            props.putAll(universeProps);
277            props.putAll(c3dMap);
278            return props;
279        }
280    
281        /**
282         * Gets a human-friendly representation of the information embedded within
283         * IDV's {@code build.properties}.
284         *
285         * @return {@code String} that looks like {@literal "IDV version major.minor<b>revision</b> built <b>date</b>"}.
286         * For example: {@code IDV version 2.9u4 built 2011-04-13 14:01 UTC}.
287         */
288        public static String getIdvVersionString() {
289            Map<String, String> info = queryIdvBuildProperties();
290            return "IDV version " + info.get("idv.version.major") + '.' +
291                   info.get("idv.version.minor") + info.get("idv.version.revision") +
292                   " built " + info.get("idv.build.date");
293        }
294    
295        /**
296         * Gets a human-friendly representation of the information embedded within
297         * McIDAS-V's {@code build.properties}.
298         * 
299         * @return {@code String} that looks like {@literal "McIDAS-V version major.minor<b>release</b> built <b>date</b>"}.
300         * For example: {@code McIDAS-V version 1.02beta1 built 2011-04-14 17:36}.
301         */
302        public static String getMcvVersionString() {
303            Map<String, String> info = queryMcvBuildProperties();
304            return "McIDAS-V version " + info.get(Constants.PROP_VERSION_MAJOR) + '.' +
305                   info.get(Constants.PROP_VERSION_MINOR) + info.get(Constants.PROP_VERSION_RELEASE) +
306                   " built " + info.get(Constants.PROP_BUILD_DATE);
307        }
308    
309        /**
310         * Gets a human-friendly representation of the version information embedded 
311         * within VisAD's {@literal "DATE"} file.
312         * 
313         * @return {@code String} that looks {@literal "VisAD version <b>revision</b> built <b>date</b>"}.
314         * For example: {@code VisAD version 5952 built Thu Mar 22 13:01:31 CDT 2012}.
315         */
316        public static String getVisadVersionString() {
317            Map<String, String> props = queryVisadBuildProperties();
318            return "VisAD version " + props.get(Constants.PROP_VISAD_REVISION) + " built " + props.get(Constants.PROP_VISAD_DATE);
319        }
320    
321        private InputStream getResourceAsStream(final String name) {
322            return ClassLoader.getSystemResourceAsStream(name);
323        }
324    
325        /**
326         * Returns a {@link Map} containing any relevant version information. 
327         * 
328         * <p>Currently this information consists of the date visad.jar was built, 
329         * as well as the (then-current) Subversion revision number.
330         * 
331         * @return {@code Map} of the contents of VisAD's DATE file.
332         */
333        public static Map<String, String> queryVisadBuildProperties() {
334            Map<String, String> props = newLinkedHashMap(4);
335            SystemState sysState = new SystemState();
336            BufferedReader input = null;
337            
338            try {
339                input = new BufferedReader(new InputStreamReader(sysState.getResourceAsStream("DATE")));
340                String contents = input.readLine();
341                // string should look like: Thu Mar 22 13:01:31 CDT 2012  Rev:5952
342                String splitAt = "  Rev:";
343                int index = contents.indexOf(splitAt);
344                String buildDate = "ERROR";
345                String revision = "ERROR";
346                String parseFail = "true";
347                if (index > 0) {
348                    buildDate = new String(contents.substring(0, index));
349                    revision = new String(contents.substring(index + splitAt.length()));
350                    parseFail = "false";
351                }
352                props.put(Constants.PROP_VISAD_ORIGINAL, contents);
353                props.put(Constants.PROP_VISAD_PARSE_FAIL, parseFail);
354                props.put(Constants.PROP_VISAD_DATE, buildDate);
355                props.put(Constants.PROP_VISAD_REVISION, revision);
356            } catch (Exception e) {
357                logger.error("could not read from VisAD DATE file", e);
358            } finally {
359                sysState = null;
360                if (input != null) {
361                    try {
362                        input.close();
363                    } catch (Exception e) {
364                        logger.error("could not close VisAD DATE file", e);
365                    }
366                }
367            }
368            return props;
369        }
370    
371        /**
372         * Returns a {@link Map} of the (currently) most useful contents of
373         * {@code ucar/unidata/idv/resources/build.properties}.
374         *
375         * <p>Consider the output of {@link #getIdvVersionString()}; it's built
376         * with the the following:
377         * <ul>
378         *   <li><b>{@code idv.version.major}</b>: currently {@literal "3"}</li>
379         *   <li><b>{@code idv.version.minor}</b>: currently {@literal "0"}</li>
380         *   <li><b>{@code idv.version.revision}</b>: currently {@literal "u2"}}</li>
381         *   <li><b>{@code idv.build.date}</b>: varies pretty frequently,
382         *   as it's the build timestamp for idv.jar</li>
383         * </ul>
384         *
385         * @return A {@code Map} of at least the useful parts of build.properties.
386         */
387        public static Map<String, String> queryIdvBuildProperties() {
388            SystemState sysState = new SystemState();
389            Map<String, String> versions = newLinkedHashMap(4);
390            InputStream input = null;
391            try {
392                input = sysState.getResourceAsStream("ucar/unidata/idv/resources/build.properties");
393                Properties props = new Properties();
394                props.load(input);
395                String major = props.getProperty("idv.version.major", "no_major");
396                String minor = props.getProperty("idv.version.minor", "no_minor");
397                String revision = props.getProperty("idv.version.revision", "no_revision");
398                String date = props.getProperty("idv.build.date", "");
399                versions.put("idv.version.major", major);
400                versions.put("idv.version.minor", minor);
401                versions.put("idv.version.revision", revision);
402                versions.put("idv.build.date", date);
403            } catch (Exception e) {
404                logger.error("could not read from IDV build.properties", e);
405            } finally {
406                sysState = null;
407                if (input != null) {
408                    try {
409                        input.close();
410                    } catch (Exception ex) {
411                        logger.error("could not close IDV build.properties", ex);
412                    }
413                }
414            }
415            return versions;
416        }
417    
418        /**
419         * Returns a {@link Map} of the (currently) most useful contents of
420         * {@code edu/wisc/ssec/mcidasv/resources/build.properties}.
421         *
422         * <p>Consider the output of {@link #getMcvVersionString()}; it's built
423         * with the the following:
424         * <ul>
425         *   <li><b>{@code mcidasv.version.major}</b>:
426         *   currently {@literal "1"}</li>
427         *   <li><b>{@code mcidasv.version.minor}</b>:
428         *   currently {@literal "02"}</li>
429         *   <li><b>{@code mcidasv.version.release}</b>: currently
430         *   {@literal "beta1"}</li>
431         *   <li><b>{@code mcidasv.build.date}</b>: varies pretty frequently, as
432         *   it's the build timestamp for mcidasv.jar.</li>
433         * </ul>
434         *
435         * @return A {@code Map} of at least the useful parts of build.properties.
436         */
437        public static Map<String, String> queryMcvBuildProperties() {
438            SystemState sysState = new SystemState();
439            Map<String, String> versions = newLinkedHashMap(4);
440            InputStream input = null;
441            try {
442                input = sysState.getResourceAsStream("edu/wisc/ssec/mcidasv/resources/build.properties");
443                Properties props = new Properties();
444                props.load(input);
445                String major = props.getProperty(Constants.PROP_VERSION_MAJOR, "0");
446                String minor = props.getProperty(Constants.PROP_VERSION_MINOR, "0");
447                String release = props.getProperty(Constants.PROP_VERSION_RELEASE, "");
448                String date = props.getProperty(Constants.PROP_BUILD_DATE, "Unknown");
449                versions.put(Constants.PROP_VERSION_MAJOR, major);
450                versions.put(Constants.PROP_VERSION_MINOR, minor);
451                versions.put(Constants.PROP_VERSION_RELEASE, release);
452                versions.put(Constants.PROP_BUILD_DATE, date);
453            } catch (Exception e) {
454                logger.error("could not read from McIDAS-V build.properties!", e);
455            } finally {
456                sysState = null;
457                if (input != null) {
458                    try {
459                        input.close();
460                    } catch (Exception ex) {
461                        logger.error("could not close McIDAS-V build.properties!", ex);
462                    }
463                }
464            }
465            return versions;
466        }
467    
468        /**
469         * Queries McIDAS-V for information about its state. There's not a good way
470         * to characterize what we're interested in, so let's leave it at 
471         * {@literal "whatever seems useful"}.
472         * 
473         * @param mcv The McIDASV {@literal "god"} object.
474         * 
475         * @return Information about the state of McIDAS-V.
476         */
477        // need: argsmanager, resource manager
478        public static Map<String, Object> queryMcvState(final McIDASV mcv) {
479            Map<String, Object> props = newLinkedHashMap();
480    
481            ArgsManager args = mcv.getArgsManager();
482            props.put("mcv.state.islinteractive", args.getIslInteractive());
483            props.put("mcv.state.offscreen", args.getIsOffScreen());
484            props.put("mcv.state.initcatalogs", args.getInitCatalogs());
485            props.put("mcv.state.actions", mcv.getActionHistory());
486            props.put("mcv.plugins.installed", args.installPlugins);
487            props.put("mcv.state.commandline", mcv.getCommandLineArgs());
488    
489            // loop through resources
490            List<IdvResource> resources =
491                    cast(mcv.getResourceManager().getResources());
492            for (IdvResource resource : resources) {
493                String id = resource.getId();
494                props.put(id+".description", resource.getDescription());
495                if (resource.getPattern() == null) {
496                    props.put(id+".pattern", "null");
497                } else {
498                    props.put(id+".pattern", resource.getPattern());
499                }
500    
501                ResourceCollection rc = mcv.getResourceManager().getResources(resource);
502                int rcSize = rc.size();
503                List<String> specified = arrList(rcSize);
504                List<String> valid = arrList(rcSize);
505                for (int i = 0; i < rcSize; i++) {
506                    String tmpResource = (String)rc.get(i);
507                    specified.add(tmpResource);
508                    if (rc.isValid(i)) {
509                        valid.add(tmpResource);
510                    }
511                }
512    
513                props.put(id+".specified", specified);
514                props.put(id+".existing", valid);
515            }
516            return props;
517        }
518    
519        /**
520         * Builds a (filtered) subset of the McIDAS-V system properties and returns
521         * the results as a {@code String}.
522         * 
523         * @param mcv The McIDASV {@literal "god"} object.
524         * 
525         * @return The McIDAS-V system properties in the following format: 
526         * {@code KEY=VALUE\n}. This is so we kinda-sorta conform to the standard
527         * {@link Properties} file format.
528         * 
529         * @see #getStateAsString(edu.wisc.ssec.mcidasv.McIDASV, boolean)
530         */
531        public static String getStateAsString(final McIDASV mcv) {
532            return getStateAsString(mcv, false);
533        }
534    
535        /**
536         * Builds the McIDAS-V system properties and returns the results as a 
537         * {@code String}.
538         * 
539         * @param mcv The McIDASV {@literal "god"} object.
540         * @param firehose If {@code true}, enables {@literal "unfiltered"} output.
541         * 
542         * @return The McIDAS-V system properties in the following format: 
543         * {@code KEY=VALUE\n}. This is so we kinda-sorta conform to the standard
544         * {@link Properties} file format.
545         */
546        public static String getStateAsString(final McIDASV mcv, final boolean firehose) {
547            int builderSize = (firehose) ? 45000 : 1000;
548            StringBuilder buf = new StringBuilder(builderSize);
549    
550            Map<String, String> versions = ((StateManager)mcv.getStateManager()).getVersionInfo();
551            Properties sysProps = System.getProperties();
552            Map<String, Object> j3dProps = queryJava3d();
553            Map<String, String> machineProps = queryMachine();
554            Map<Object, Object> jythonProps = queryJythonProps();
555            Map<String, Object> mcvProps = queryMcvState(mcv);
556    
557            if (sysProps.contains("line.separator")) {
558                sysProps.put("line.separator", escapeWhitespaceChars((String)sysProps.get("line.separator")));
559                logger.trace("grr='{}'", sysProps.get("line.separator"));
560            }
561    
562            String maxMem = Long.toString(Long.valueOf(machineProps.get("opsys.memory.jvm.max")) / 1048576L);
563            String curMem = Long.toString(Long.valueOf(machineProps.get("opsys.memory.jvm.current")) / 1048576L);
564    
565            buf.append("# Software Versions:")
566                .append("\n# McIDAS-V: ").append(versions.get("mcv.version.general")).append(" (").append(versions.get("mcv.version.build")).append(')')
567                .append("\n# VisAD:    ").append(versions.get("visad.version.general")).append(" (").append(versions.get("visad.version.build")).append(')')
568                .append("\n# IDV:      ").append(versions.get("idv.version.general")).append(" (").append(versions.get("idv.version.build")).append(')')
569                .append("\n\n# Operating System:")
570                .append("\n# Name:         ").append(sysProps.getProperty("os.name"))
571                .append("\n# Version:      ").append(sysProps.getProperty("os.version"))
572                .append("\n# Architecture: ").append(sysProps.getProperty("os.arch"))
573                .append("\n\n# Java:")
574                .append("\n# Version: ").append(sysProps.getProperty("java.version"))
575                .append("\n# Vendor:  ").append(sysProps.getProperty("java.vendor"))
576                .append("\n# Home:    ").append(sysProps.getProperty("java.home"))
577                .append("\n\n# JVM Memory")
578                .append("\n# Current: ").append(curMem).append(" MB")
579                .append("\n# Maximum: ").append(maxMem).append(" MB")
580                .append("\n\n# Java 3D:")
581                .append("\n# Renderer: ").append(j3dProps.get("j3d.renderer"))
582                .append("\n# Pipeline: ").append(j3dProps.get("j3d.pipeline"))
583                .append("\n# Vendor:   ").append(j3dProps.get("j3d.vendor"))
584                .append("\n# Version:  ").append(j3dProps.get("j3d.version"))
585                .append("\n\n# Jython:")
586                .append("\n# Version:     ").append(jythonProps.get("sys.version_info"))
587                .append("\n# python.home: ").append(jythonProps.get("python.home"));
588    
589            if (firehose) {
590                buf.append("\n\n\n#Firehose:\n\n# SOFTWARE VERSIONS\n");
591                for (String key : (new TreeSet<String>(versions.keySet()))) {
592                    buf.append(key).append('=').append(versions.get(key)).append('\n');
593                }
594    
595                buf.append("\n# MACHINE PROPERTIES\n");
596                for (String key : (new TreeSet<String>(machineProps.keySet()))) {
597                    buf.append(key).append('=').append(machineProps.get(key)).append('\n');
598                }
599    
600                buf.append("\n# JAVA SYSTEM PROPERTIES\n");
601                for (Object key : (new TreeSet<Object>(sysProps.keySet()))) {
602                    buf.append(key).append('=').append(sysProps.get(key)).append('\n');
603                }
604    
605                buf.append("\n# JAVA3D/JOGL PROPERTIES\n");
606                for (String key : (new TreeSet<String>(j3dProps.keySet()))) {
607                    buf.append(key).append('=').append(j3dProps.get(key)).append('\n');
608                }
609    
610                buf.append("\n# JYTHON PROPERTIES\n");
611                for (Object key : (new TreeSet<Object>(jythonProps.keySet()))) {
612                    buf.append(key).append('=').append(jythonProps.get(key)).append('\n');
613                }
614    
615                // get idv/mcv properties
616                buf.append("\n# IDV AND MCIDAS-V PROPERTIES\n");
617                for (String key : (new TreeSet<String>(mcvProps.keySet()))) {
618                    buf.append(key).append('=').append(mcvProps.get(key)).append('\n');
619                }
620            }
621            return buf.toString();
622        }
623    }