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