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