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