001 /*
002 * $Id: SystemState.java,v 1.12 2012/02/19 17:35:52 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2012
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 *
012 * All Rights Reserved
013 *
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.
016 *
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 *
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
025 * GNU Lesser Public License for more details.
026 *
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program. If not, see http://www.gnu.org/licenses.
029 */
030 package edu.wisc.ssec.mcidasv.util;
031
032 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList;
033 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.cast;
034 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newLinkedHashMap;
035
036 import java.io.InputStream;
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 private InputStream getResourceAsStream(final String name) {
310 return ClassLoader.getSystemResourceAsStream(name);
311 }
312
313 /**
314 * Returns a {@link Map} of the (currently) most useful contents of
315 * {@code ucar/unidata/idv/resources/build.properties}.
316 *
317 * <p>Consider the output of {@link #getIdvVersionString()}; it's built
318 * with the the following:
319 * <ul>
320 * <li><b>{@code idv.version.major}</b>: currently {@literal "2"}</li>
321 * <li><b>{@code idv.version.minor}</b>: currently {@literal "9"}</li>
322 * <li><b>{@code idv.version.revision}</b>: currently {@literal "u4"}}</li>
323 * <li><b>{@code idv.build.date}</b>: varies pretty frequently,
324 * as it's the build timestamp for idv.jar</li>
325 * </ul>
326 *
327 * @return A {@code Map} of at least the useful parts of build.properties.
328 */
329 public static Map<String, String> queryIdvBuildProperties() {
330 SystemState sysState = new SystemState();
331 Map<String, String> versions = newLinkedHashMap(4);
332 InputStream input = null;
333 try {
334 input = sysState.getResourceAsStream("ucar/unidata/idv/resources/build.properties");
335 Properties props = new Properties();
336 props.load(input);
337 String major = props.getProperty("idv.version.major", "no_major");
338 String minor = props.getProperty("idv.version.minor", "no_minor");
339 String revision = props.getProperty("idv.version.revision", "no_revision");
340 String date = props.getProperty("idv.build.date", "");
341 versions.put("idv.version.major", major);
342 versions.put("idv.version.minor", minor);
343 versions.put("idv.version.revision", revision);
344 versions.put("idv.build.date", date);
345 } catch (Exception e) {
346 logger.error("could not read from IDV build.properties", e);
347 } finally {
348 sysState = null;
349 if (input != null) {
350 try {
351 input.close();
352 } catch (Exception ex) {
353 logger.error("could not close IDV build.properties", ex);
354 }
355 }
356 }
357 return versions;
358 }
359
360 /**
361 * Returns a {@link Map} of the (currently) most useful contents of
362 * {@code edu/wisc/ssec/mcidasv/resources/build.properties}.
363 *
364 * <p>Consider the output of {@link #getMcvVersionString()}; it's built
365 * with the the following:
366 * <ul>
367 * <li><b>{@code mcidasv.version.major}</b>:
368 * currently {@literal "1"}</li>
369 * <li><b>{@code mcidasv.version.minor}</b>:
370 * currently {@literal "02"}</li>
371 * <li><b>{@code mcidasv.version.release}</b>: currently
372 * {@literal "beta1"}</li>
373 * <li><b>{@code mcidasv.build.date}</b>: varies pretty frequently, as
374 * it's the build timestamp for mcidasv.jar.</li>
375 * </ul>
376 *
377 * @return A {@code Map} of at least the useful parts of build.properties.
378 */
379 public static Map<String, String> queryMcvBuildProperties() {
380 SystemState sysState = new SystemState();
381 Map<String, String> versions = newLinkedHashMap(4);
382 InputStream input = null;
383 try {
384 input = sysState.getResourceAsStream("edu/wisc/ssec/mcidasv/resources/build.properties");
385 Properties props = new Properties();
386 props.load(input);
387 String major = props.getProperty(Constants.PROP_VERSION_MAJOR, "0");
388 String minor = props.getProperty(Constants.PROP_VERSION_MINOR, "0");
389 String release = props.getProperty(Constants.PROP_VERSION_RELEASE, "");
390 String date = props.getProperty(Constants.PROP_BUILD_DATE, "Unknown");
391 versions.put(Constants.PROP_VERSION_MAJOR, major);
392 versions.put(Constants.PROP_VERSION_MINOR, minor);
393 versions.put(Constants.PROP_VERSION_RELEASE, release);
394 versions.put(Constants.PROP_BUILD_DATE, date);
395 } catch (Exception e) {
396 logger.error("could not read from McIDAS-V build.properties!", e);
397 } finally {
398 sysState = null;
399 if (input != null) {
400 try {
401 input.close();
402 } catch (Exception ex) {
403 logger.error("could not close McIDAS-V build.properties!", ex);
404 }
405 }
406 }
407 return versions;
408 }
409
410 /**
411 * Queries McIDAS-V for information about its state. There's not a good way
412 * to characterize what we're interested in, so let's leave it at
413 * {@literal "whatever seems useful"}.
414 *
415 * @return Information about the state of McIDAS-V.
416 */
417 // need: argsmanager, resource manager
418 public static Map<String, Object> queryMcvState(final McIDASV mcv) {
419 Map<String, Object> props = newLinkedHashMap();
420
421 ArgsManager args = mcv.getArgsManager();
422 props.put("mcv.state.islinteractive", args.getIslInteractive());
423 props.put("mcv.state.offscreen", args.getIsOffScreen());
424 props.put("mcv.state.initcatalogs", args.getInitCatalogs());
425 props.put("mcv.state.actions", mcv.getActionHistory());
426 props.put("mcv.plugins.installed", args.installPlugins);
427 props.put("mcv.state.commandline", mcv.getCommandLineArgs());
428
429 // loop through resources
430 List<IdvResource> resources =
431 cast(mcv.getResourceManager().getResources());
432 for (IdvResource resource : resources) {
433 String id = resource.getId();
434 props.put(id+".description", resource.getDescription());
435 if (resource.getPattern() == null) {
436 props.put(id+".pattern", "null");
437 } else {
438 props.put(id+".pattern", resource.getPattern());
439 }
440
441 ResourceCollection rc = mcv.getResourceManager().getResources(resource);
442 int rcSize = rc.size();
443 List<String> specified = arrList(rcSize);
444 List<String> valid = arrList(rcSize);
445 for (int i = 0; i < rcSize; i++) {
446 String tmpResource = (String)rc.get(i);
447 specified.add(tmpResource);
448 if (rc.isValid(i)) {
449 valid.add(tmpResource);
450 }
451 }
452
453 props.put(id+".specified", specified);
454 props.put(id+".existing", valid);
455 }
456 return props;
457 }
458
459 /**
460 * Builds a (filtered) subset of the McIDAS-V system properties and returns
461 * the results as a {@code String}.
462 *
463 * @return The McIDAS-V system properties in the following format:
464 * {@code KEY=VALUE\n}. This is so we kinda-sorta conform to the standard
465 * {@link Properties} file format.
466 *
467 * @see #getStateAsString(edu.wisc.ssec.mcidasv.McIDASV, boolean)
468 */
469 public static String getStateAsString(final McIDASV mcv) {
470 return getStateAsString(mcv, false);
471 }
472
473 /**
474 * Builds the McIDAS-V system properties and returns the results as a
475 * {@code String}.
476 *
477 * @param firehose If {@code true}, enables {@literal "unfiltered"} output.
478 *
479 * @return The McIDAS-V system properties in the following format:
480 * {@code KEY=VALUE\n}. This is so we kinda-sorta conform to the standard
481 * {@link Properties} file format.
482 */
483 public static String getStateAsString(final McIDASV mcv, final boolean firehose) {
484 int builderSize = (firehose) ? 45000 : 1000;
485 StringBuilder buf = new StringBuilder(builderSize);
486
487 Map<String, String> versions = ((StateManager)mcv.getStateManager()).getVersionInfo();
488 Properties sysProps = System.getProperties();
489 Map<String, Object> j3dProps = queryJava3d();
490 Map<String, String> machineProps = queryMachine();
491 Map<Object, Object> jythonProps = queryJythonProps();
492 Map<String, Object> mcvProps = queryMcvState(mcv);
493
494 if (sysProps.contains("line.separator")) {
495 sysProps.put("line.separator", escapeWhitespaceChars((String)sysProps.get("line.separator")));
496 logger.trace("grr='{}'", sysProps.get("line.separator"));
497 }
498
499 String maxMem = Long.toString(Long.valueOf(machineProps.get("opsys.memory.jvm.max")) / 1048576L);
500 String curMem = Long.toString(Long.valueOf(machineProps.get("opsys.memory.jvm.current")) / 1048576L);
501
502 buf.append("# Software Versions:")
503 .append("\n# McIDAS-V: ").append(versions.get("mcv.version.general")).append(" (").append(versions.get("mcv.version.build")).append(')')
504 .append("\n# IDV: ").append(versions.get("idv.version.general")).append(" (").append(versions.get("idv.version.build")).append(')')
505 .append("\n\n# Operating System:")
506 .append("\n# Name: ").append(sysProps.getProperty("os.name"))
507 .append("\n# Version: ").append(sysProps.getProperty("os.version"))
508 .append("\n# Architecture: ").append(sysProps.getProperty("os.arch"))
509 .append("\n\n# Java:")
510 .append("\n# Version: ").append(sysProps.getProperty("java.version"))
511 .append("\n# Vendor: ").append(sysProps.getProperty("java.vendor"))
512 .append("\n# Home: ").append(sysProps.getProperty("java.home"))
513 .append("\n\n# JVM Memory")
514 .append("\n# Current: ").append(curMem).append(" MB")
515 .append("\n# Maximum: ").append(maxMem).append(" MB")
516 .append("\n\n# Java 3D:")
517 .append("\n# Renderer: ").append(j3dProps.get("j3d.renderer"))
518 .append("\n# Pipeline: ").append(j3dProps.get("j3d.pipeline"))
519 .append("\n# Vendor: ").append(j3dProps.get("j3d.vendor"))
520 .append("\n# Version: ").append(j3dProps.get("j3d.version"))
521 .append("\n\n# Jython:")
522 .append("\n# Version: ").append(jythonProps.get("sys.version_info"))
523 .append("\n# python.home: ").append(jythonProps.get("python.home"));
524
525 if (firehose) {
526 buf.append("\n\n\n#Firehose:\n\n# SOFTWARE VERSIONS\n");
527 for (String key : (new TreeSet<String>(versions.keySet()))) {
528 buf.append(key).append('=').append(versions.get(key)).append('\n');
529 }
530
531 buf.append("\n# MACHINE PROPERTIES\n");
532 for (String key : (new TreeSet<String>(machineProps.keySet()))) {
533 buf.append(key).append('=').append(machineProps.get(key)).append('\n');
534 }
535
536 buf.append("\n# JAVA SYSTEM PROPERTIES\n");
537 for (Object key : (new TreeSet<Object>(sysProps.keySet()))) {
538 buf.append(key).append('=').append(sysProps.get(key)).append('\n');
539 }
540
541 buf.append("\n# JAVA3D/JOGL PROPERTIES\n");
542 for (String key : (new TreeSet<String>(j3dProps.keySet()))) {
543 buf.append(key).append('=').append(j3dProps.get(key)).append('\n');
544 }
545
546 buf.append("\n# JYTHON PROPERTIES\n");
547 for (Object key : (new TreeSet<Object>(jythonProps.keySet()))) {
548 buf.append(key).append('=').append(jythonProps.get(key)).append('\n');
549 }
550
551 // get idv/mcv properties
552 buf.append("\n# IDV AND MCIDAS-V PROPERTIES\n");
553 for (String key : (new TreeSet<String>(mcvProps.keySet()))) {
554 buf.append(key).append('=').append(mcvProps.get(key)).append('\n');
555 }
556 }
557 return buf.toString();
558 }
559 }