001/* 002 * $Id: SystemState.java,v 1.9 2011/04/14 17:45:50 jbeavers Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2011 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 */ 030package edu.wisc.ssec.mcidasv.util; 031 032import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 033import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.cast; 034import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newLinkedHashMap; 035 036//import static edu.wisc.ssec.mcidasv.Constants.PROP_VERSION_MAJOR; 037//import static edu.wisc.ssec.mcidasv.Constants.PROP_VERSION_MINOR; 038//import static edu.wisc.ssec.mcidasv.Constants.PROP_VERSION_RELEASE; 039//import static edu.wisc.ssec.mcidasv.Constants.PROP_BUILD_DATE; 040 041import java.io.InputStream; 042import java.lang.management.ManagementFactory; 043import java.lang.management.OperatingSystemMXBean; 044import java.lang.reflect.Method; 045 046import java.util.List; 047import java.util.Map; 048import java.util.Properties; 049import java.util.TreeSet; 050 051import java.awt.DisplayMode; 052import java.awt.GraphicsConfiguration; 053import java.awt.GraphicsDevice; 054import java.awt.GraphicsEnvironment; 055import java.awt.Rectangle; 056 057import javax.media.j3d.Canvas3D; 058import javax.media.j3d.VirtualUniverse; 059 060import org.python.core.Py; 061import org.python.core.PySystemState; 062 063import org.slf4j.Logger; 064import org.slf4j.LoggerFactory; 065 066import ucar.unidata.idv.ArgsManager; 067import ucar.unidata.idv.IdvResourceManager.IdvResource; 068import ucar.unidata.util.ResourceCollection; 069import ucar.visad.display.DisplayUtil; 070 071import edu.wisc.ssec.mcidasv.Constants; 072import edu.wisc.ssec.mcidasv.McIDASV; 073import edu.wisc.ssec.mcidasv.StateManager; 074 075/** 076 * Utility methods for querying the state of the user's machine. 077 */ 078public class SystemState { 079 080 /** Handy logging object. */ 081 private static final Logger logger = LoggerFactory.getLogger(SystemState.class); 082 083 // Don't allow outside instantiation. 084 private SystemState() { } 085 086 /** 087 * Attempt to invoke {@code OperatingSystemMXBean.methodName} via 088 * reflection. 089 * 090 * @param <T> Either {@code Long} or {@code Double}. 091 * @param methodName The method to invoke. Must belong to 092 * {@code com.sun.management.OperatingSystemMXBean}. 093 * @param defaultValue Default value to return, must be in 094 * {@literal "boxed"} form. 095 * 096 * @return Either the result of the {@code methodName} call or 097 * {@code defaultValue}. 098 */ 099 private static <T> T hackyMethodCall(final String methodName, final T defaultValue) { 100 assert methodName != null : "Cannot invoke a null method name"; 101 assert methodName.length() > 0: "Cannot invoke an empty method name"; 102 OperatingSystemMXBean osBean = 103 ManagementFactory.getOperatingSystemMXBean(); 104 T result = defaultValue; 105 try { 106 Method m = osBean.getClass().getMethod(methodName); 107 m.setAccessible(true); 108 // don't suppress warnings because we cannot guarantee that this 109 // cast is correct. 110 result = (T)m.invoke(osBean); 111 } catch (Exception e) { 112 logger.error("couldn't call method: " + methodName, e); 113 } 114 return result; 115 } 116 117 /** 118 * Returns the contents of Jython's registry (basically just Jython-specific 119 * properties) as well as some of the information from Python's 120 * {@literal "sys"} module. 121 * 122 * @return Jython's configuration settings. 123 */ 124 public static Map<Object, Object> queryJythonProps() { 125 Map<Object, Object> properties = newLinkedHashMap(PySystemState.registry); 126 properties.put("sys.argv", Py.getSystemState().argv.toString()); 127 properties.put("sys.builtin_module_names", PySystemState.builtin_module_names.toString()); 128 properties.put("sys.byteorder", PySystemState.byteorder); 129 properties.put("sys.isPackageCacheEnabled", PySystemState.isPackageCacheEnabled()); 130 properties.put("sys.path", Py.getSystemState().path); 131 properties.put("sys.platform", PySystemState.platform.toString()); 132 properties.put("sys.version", PySystemState.version); 133 properties.put("sys.version_info", PySystemState.version_info); 134 return properties; 135 } 136 137 /** 138 * Attempts to call methods belonging to 139 * {@code com.sun.management.OperatingSystemMXBean}. If successful, we'll 140 * have the following information: 141 * <ul> 142 * <li>opsys.memory.virtual.committed: virtual memory that is guaranteed to be available</li> 143 * <li>opsys.memory.swap.total: total amount of swap space in bytes</li> 144 * <li>opsys.memory.swap.free: free swap space in bytes</li> 145 * <li>opsys.cpu.time: CPU time used by the process (nanoseconds)</li> 146 * <li>opsys.memory.physical.free: free physical memory in bytes</li> 147 * <li>opsys.memory.physical.total: physical memory in bytes</li> 148 * <li>opsys.load: system load average for the last minute</li> 149 * </ul> 150 * 151 * @return Map of properties that contains interesting information about 152 * the hardware McIDAS-V is using. 153 */ 154 public static Map<String, String> queryOpSysProps() { 155 Map<String, String> properties = newLinkedHashMap(10); 156 long committed = hackyMethodCall("getCommittedVirtualMemorySize", Long.MIN_VALUE); 157 long freeMemory = hackyMethodCall("getFreePhysicalMemorySize", Long.MIN_VALUE); 158 long freeSwap = hackyMethodCall("getFreeSwapSpaceSize", Long.MIN_VALUE); 159 long cpuTime = hackyMethodCall("getProcessCpuTime", Long.MIN_VALUE); 160 long totalMemory = hackyMethodCall("getTotalPhysicalMemorySize", Long.MIN_VALUE); 161 long totalSwap = hackyMethodCall("getTotalSwapSpaceSize", Long.MIN_VALUE); 162 double loadAvg = hackyMethodCall("getSystemLoadAverage", Double.NaN); 163 164 properties.put("opsys.cpu.time", Long.toString(cpuTime)); 165 properties.put("opsys.load", Double.toString(loadAvg)); 166 properties.put("opsys.memory.virtual.committed", Long.toString(committed)); 167 properties.put("opsys.memory.physical.free", Long.toString(freeMemory)); 168 properties.put("opsys.memory.physical.total", Long.toString(totalMemory)); 169 properties.put("opsys.memory.swap.free", Long.toString(freeSwap)); 170 properties.put("opsys.memory.swap.total", Long.toString(totalSwap)); 171 172 return properties; 173 } 174 175 /** 176 * Polls Java for information about the user's machine. We're specifically 177 * after memory statistics, number of processors, and display information. 178 * 179 * @return {@link Map} of properties that describes the user's machine. 180 */ 181 public static Map<String, String> queryMachine() { 182 Map<String, String> props = newLinkedHashMap(); 183 184 // cpu count and whatnot 185 int processors = Runtime.getRuntime().availableProcessors(); 186 props.put("opsys.cpu.count", Integer.toString(processors)); 187 188 // memory: available, used, etc 189 props.putAll(queryOpSysProps()); 190 191 // screen: count, resolution(s) 192 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 193 int displayCount = ge.getScreenDevices().length; 194 195 for (int i = 0; i < displayCount; i++) { 196 String baseId = "opsys.display."+i+'.'; 197 GraphicsDevice dev = ge.getScreenDevices()[i]; 198 DisplayMode mode = dev.getDisplayMode(); 199 props.put(baseId+"name", dev.getIDstring()); 200 props.put(baseId+"depth", Integer.toString(mode.getBitDepth())); 201 props.put(baseId+"width", Integer.toString(mode.getWidth())); 202 props.put(baseId+"height", Integer.toString(mode.getHeight())); 203 props.put(baseId+"refresh", Integer.toString(mode.getRefreshRate())); 204 } 205 return props; 206 } 207 208 /** 209 * Returns a mapping of display number to a {@link java.awt.Rectangle} 210 * that represents the {@literal "bounds"} of the display. 211 */ 212 public static Map<Integer, Rectangle> getDisplayBounds() { 213 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 214 int idx = 0; 215 Map<Integer, Rectangle> map = newLinkedHashMap(ge.getScreenDevices().length * 2); 216 for (GraphicsDevice dev : ge.getScreenDevices()) { 217 for (GraphicsConfiguration config : dev.getConfigurations()) { 218 map.put(idx++, config.getBounds()); 219 } 220 } 221 return map; 222 } 223 224 // TODO(jon): this should really be a polygon 225 public static Rectangle getVirtualDisplayBounds() { 226 Rectangle virtualBounds = new Rectangle(); 227 for (Rectangle bounds : getDisplayBounds().values()) { 228 virtualBounds = virtualBounds.union(bounds); 229 } 230 return virtualBounds; 231 } 232 233 /** 234 * Polls Java 3D for information about its environment. Specifically, we 235 * call {@link VirtualUniverse#getProperties()} and 236 * {@link Canvas3D#queryProperties()}. 237 * 238 * @return As much information as Java 3D can provide. 239 */ 240 @SuppressWarnings("unchecked") // casting to Object, so this should be fine. 241 public static Map<String, Object> queryJava3d() { 242 243 Map<String, Object> universeProps = 244 (Map<String, Object>)VirtualUniverse.getProperties(); 245 246 GraphicsConfiguration config = 247 DisplayUtil.getPreferredConfig(null, true, false); 248 Map<String, Object> c3dMap = new Canvas3D(config).queryProperties(); 249 250 Map<String, Object> props = 251 newLinkedHashMap(universeProps.size() + c3dMap.size()); 252 props.putAll(universeProps); 253 props.putAll(c3dMap); 254 return props; 255 } 256 257 /** 258 * Gets a human-friendly representation of the information embedded within 259 * IDV's {@code build.properties}. 260 * 261 * @return {@code String} that looks like {@literal "IDV version major.minor<b>revision</b> built <b>date</b>"}. 262 * For example: {@code IDV version 2.9u4 built 2011-04-13 14:01 UTC}. 263 */ 264 public static String getIdvVersionString() { 265 Map<String, String> info = queryIdvBuildProperties(); 266 return "IDV version " + info.get("idv.version.major") + '.' + 267 info.get("idv.version.minor") + info.get("idv.version.revision") + 268 " built " + info.get("idv.build.date"); 269 } 270 271 /** 272 * Gets a human-friendly representation of the information embedded within 273 * McIDAS-V's {@code build.properties}. 274 * 275 * @return {@code String} that looks like {@literal "McIDAS-V version major.minor<b>release</b> built <b>date</b>"}. 276 * For example: {@code McIDAS-V version 1.02beta1 built 2011-04-14 17:36}. 277 */ 278 public static String getMcvVersionString() { 279 Map<String, String> info = queryMcvBuildProperties(); 280 return "McIDAS-V version " + info.get(Constants.PROP_VERSION_MAJOR) + '.' + 281 info.get(Constants.PROP_VERSION_MINOR) + info.get(Constants.PROP_VERSION_RELEASE) + 282 " built " + info.get(Constants.PROP_BUILD_DATE); 283 } 284 285 private InputStream getResourceAsStream(final String name) { 286 return ClassLoader.getSystemResourceAsStream(name); 287 } 288 289 public static Map<String, String> queryIdvBuildProperties() { 290 SystemState sysState = new SystemState(); 291 Map<String, String> versions = newLinkedHashMap(4); 292 InputStream input = null; 293 try { 294 input = sysState.getResourceAsStream("ucar/unidata/idv/resources/build.properties"); 295 Properties props = new Properties(); 296 props.load(input); 297 String major = props.getProperty("idv.version.major", "no_major"); 298 String minor = props.getProperty("idv.version.minor", "no_minor"); 299 String revision = props.getProperty("idv.version.revision", "no_revision"); 300 String date = props.getProperty("idv.build.date", ""); 301 versions.put("idv.version.major", major); 302 versions.put("idv.version.minor", minor); 303 versions.put("idv.version.revision", revision); 304 versions.put("idv.build.date", date); 305 } catch (Exception e) { 306 logger.error("could not read from idv's build.properties", e); 307 } finally { 308 sysState = null; 309 if (input != null) { 310 try { 311 input.close(); 312 } catch (Exception ex) { 313 logger.error("could not close idv's build.properties", ex); 314 } 315 } 316 } 317 return versions; 318 } 319 320 public static Map<String, String> queryMcvBuildProperties() { 321 SystemState sysState = new SystemState(); 322 Map<String, String> versions = newLinkedHashMap(4); 323 InputStream input = null; 324 try { 325 input = sysState.getResourceAsStream("edu/wisc/ssec/mcidasv/resources/build.properties"); 326 Properties props = new Properties(); 327 props.load(input); 328 String major = props.getProperty(Constants.PROP_VERSION_MAJOR, "0"); 329 String minor = props.getProperty(Constants.PROP_VERSION_MINOR, "0"); 330 String release = props.getProperty(Constants.PROP_VERSION_RELEASE, ""); 331 String date = props.getProperty(Constants.PROP_BUILD_DATE, "Unknown"); 332 versions.put(Constants.PROP_VERSION_MAJOR, major); 333 versions.put(Constants.PROP_VERSION_MINOR, minor); 334 versions.put(Constants.PROP_VERSION_RELEASE, release); 335 versions.put(Constants.PROP_BUILD_DATE, date); 336 } catch (Exception e) { 337 logger.error("could not read from mcv's build.properties!", e); 338 } finally { 339 sysState = null; 340 if (input != null) { 341 try { 342 input.close(); 343 } catch (Exception ex) { 344 logger.error("could not close mcv's build.properties!", ex); 345 } 346 } 347 } 348 return versions; 349 } 350 351 /** 352 * Queries McIDAS-V for information about its state. There's not a good way 353 * to characterize what we're interested in, so let's leave it at 354 * {@literal "whatever seems useful"}. 355 * 356 * @return Information about the state of McIDAS-V. 357 */ 358 // need: argsmanager, resource manager 359 public static Map<String, Object> queryMcvState(final McIDASV mcv) { 360 Map<String, Object> props = newLinkedHashMap(); 361 362 ArgsManager args = mcv.getArgsManager(); 363 props.put("mcv.state.islinteractive", args.getIslInteractive()); 364 props.put("mcv.state.offscreen", args.getIsOffScreen()); 365 props.put("mcv.state.initcatalogs", args.getInitCatalogs()); 366 props.put("mcv.state.actions", mcv.getActionHistory()); 367 props.put("mcv.plugins.installed", args.installPlugins); 368 props.put("mcv.state.commandline", mcv.getCommandLineArgs()); 369 370 // loop through resources 371 List<IdvResource> resources = 372 cast(mcv.getResourceManager().getResources()); 373 for (IdvResource resource : resources) { 374 String id = resource.getId(); 375 props.put(id+".description", resource.getDescription()); 376 if (resource.getPattern() == null) { 377 props.put(id+".pattern", "null"); 378 } else { 379 props.put(id+".pattern", resource.getPattern()); 380 } 381 382 ResourceCollection rc = mcv.getResourceManager().getResources(resource); 383 int rcSize = rc.size(); 384 List<String> specified = arrList(rcSize); 385 List<String> valid = arrList(rcSize); 386 for (int i = 0; i < rcSize; i++) { 387 String tmpResource = (String)rc.get(i); 388 specified.add(tmpResource); 389 if (rc.isValid(i)) { 390 valid.add(tmpResource); 391 } 392 } 393 394 props.put(id+".specified", specified); 395 props.put(id+".existing", valid); 396 } 397 return props; 398 } 399 400 /** 401 * Builds a (filtered) subset of the McIDAS-V system properties and returns 402 * the results as a {@code String}. 403 * 404 * @return The McIDAS-V system properties in the following format: 405 * {@code KEY=VALUE\n}. This is so we kinda-sorta conform to the standard 406 * {@link Properties} file format. 407 * 408 * @see #getStateAsString(edu.wisc.ssec.mcidasv.McIDASV, boolean) 409 */ 410 public static String getStateAsString(final McIDASV mcv) { 411 return getStateAsString(mcv, false); 412 } 413 414 /** 415 * Builds the McIDAS-V system properties and returns the results as a 416 * {@code String}. 417 * 418 * @param firehose If {@code true}, enables {@literal "unfiltered"} output. 419 * 420 * @return The McIDAS-V system properties in the following format: 421 * {@code KEY=VALUE\n}. This is so we kinda-sorta conform to the standard 422 * {@link Properties} file format. 423 */ 424 public static String getStateAsString(final McIDASV mcv, final boolean firehose) { 425 StringBuilder buf = new StringBuilder(20000); 426 427 Map<String, String> versions = ((StateManager)mcv.getStateManager()).getVersionInfo(); 428 Properties sysProps = System.getProperties(); 429 Map<String, Object> j3dProps = queryJava3d(); 430 Map<String, String> machineProps = queryMachine(); 431 Map<Object, Object> jythonProps = queryJythonProps(); 432 Map<String, Object> mcvProps = queryMcvState(mcv); 433 434 buf.append("Software Versions:") 435 .append("\nMcIDAS-V: ").append(versions.get("mcv.version.general")).append(" (").append(versions.get("mcv.version.build")).append(')') 436 .append("\nIDV: ").append(versions.get("idv.version.general")).append(" (").append(versions.get("idv.version.build")).append(')') 437 .append("\n\nOperating System:") 438 .append("\nName: ").append(sysProps.getProperty("os.name")) 439 .append("\nVersion: ").append(sysProps.getProperty("os.version")) 440 .append("\nArchitecture: ").append(sysProps.getProperty("os.arch")) 441 .append("\n\nJava:") 442 .append("\nVersion: ").append(sysProps.getProperty("java.version")) 443 .append("\nVendor: ").append(sysProps.getProperty("java.vendor")) 444 .append("\nHome: ").append(sysProps.getProperty("java.home")) 445 .append("\n\nJava 3D:") 446 .append("\nRenderer: ").append(j3dProps.get("j3d.renderer")) 447 .append("\nPipeline: ").append(j3dProps.get("j3d.pipeline")) 448 .append("\nVendor: ").append(j3dProps.get("j3d.vendor")) 449 .append("\nVersion: ").append(j3dProps.get("j3d.version")) 450 .append("\n\nJython:") 451 .append("\nVersion: ").append(jythonProps.get("sys.version_info")) 452 .append("\npython.home: ").append(jythonProps.get("python.home")); 453 454 if (firehose) { 455 buf.append("\n\n\nFirehose:\n\n# SOFTWARE VERSIONS\n"); 456 for (String key : (new TreeSet<String>(versions.keySet()))) { 457 buf.append(key).append('=').append(versions.get(key)).append('\n'); 458 } 459 460 buf.append("\n# MACHINE PROPERTIES\n"); 461 for (String key : (new TreeSet<String>(machineProps.keySet()))) { 462 buf.append(key).append('=').append(machineProps.get(key)).append('\n'); 463 } 464 465 buf.append("\n# JAVA SYSTEM PROPERTIES\n"); 466 for (Object key : (new TreeSet<Object>(sysProps.keySet()))) { 467 buf.append(key).append('=').append(sysProps.get(key)).append('\n'); 468 } 469 470 buf.append("\n# JAVA3D/JOGL PROPERTIES\n"); 471 for (String key : (new TreeSet<String>(j3dProps.keySet()))) { 472 buf.append(key).append('=').append(j3dProps.get(key)).append('\n'); 473 } 474 475 buf.append("\n# JYTHON PROPERTIES\n"); 476 for (Object key : (new TreeSet<Object>(jythonProps.keySet()))) { 477 buf.append(key).append('=').append(jythonProps.get(key)).append('\n'); 478 } 479 480 // get idv/mcv properties 481 buf.append("\n# IDV AND MCIDAS-V PROPERTIES\n"); 482 for (String key : (new TreeSet<String>(mcvProps.keySet()))) { 483 buf.append(key).append('=').append(mcvProps.get(key)).append('\n'); 484 } 485 } 486 return buf.toString(); 487 } 488}