1 /*
   2  * Copyright (c) 2004, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.tools.jstat;
  27 
  28 import java.io.*;
  29 import java.net.*;
  30 import java.util.*;
  31 import java.util.regex.*;
  32 import sun.jvmstat.monitor.Monitor;
  33 import sun.jvmstat.monitor.VmIdentifier;
  34 
  35 /**
  36  * Class for processing command line arguments and providing method
  37  * level access to arguments.
  38  *
  39  * @author Brian Doherty
  40  * @since 1.5
  41  */
  42 public class Arguments {
  43 
  44     private static final boolean debug = Boolean.getBoolean("jstat.debug");
  45     private static final boolean showUnsupported =
  46             Boolean.getBoolean("jstat.showUnsupported");
  47 
  48     private static final String JVMSTAT_USERDIR = ".jvmstat";
  49     private static final String OPTIONS_FILENAME = "jstat_options";
  50     private static final String UNSUPPORTED_OPTIONS_FILENAME = "jstat_unsupported_options";
  51     private static final String ALL_NAMES = "\\w*";
  52 
  53     private Comparator<Monitor> comparator;
  54     private int headerRate;
  55     private boolean help;
  56     private boolean list;
  57     private boolean options;
  58     private boolean constants;
  59     private boolean constantsOnly;
  60     private boolean strings;
  61     private boolean timestamp;
  62     private boolean snap;
  63     private boolean verbose;
  64     private String specialOption;
  65     private String names;
  66 
  67     private OptionFormat optionFormat;
  68 
  69     private int count = -1;
  70     private int interval = -1;
  71     private String vmIdString;
  72 
  73     private VmIdentifier vmId;
  74 
  75     public static void printUsage(PrintStream ps) {
  76         ps.println("Usage: jstat --help|-options");
  77         ps.println("       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]");
  78         ps.println();
  79         ps.println("Definitions:");
  80         ps.println("  <option>      An option reported by the -options option");
  81         ps.println("  <vmid>        Virtual Machine Identifier. A vmid takes the following form:");
  82         ps.println("                     <lvmid>[@<hostname>[:<port>]]");
  83         ps.println("                Where <lvmid> is the local vm identifier for the target");
  84         ps.println("                Java virtual machine, typically a process id; <hostname> is");
  85         ps.println("                the name of the host running the target Java virtual machine;");
  86         ps.println("                and <port> is the port number for the rmiregistry on the");
  87         ps.println("                target host. See the jvmstat documentation for a more complete");
  88         ps.println("                description of the Virtual Machine Identifier.");
  89         ps.println("  <lines>       Number of samples between header lines.");
  90         ps.println("  <interval>    Sampling interval. The following forms are allowed:");
  91         ps.println("                    <n>[\"ms\"|\"s\"]");
  92         ps.println("                Where <n> is an integer and the suffix specifies the units as ");
  93         ps.println("                milliseconds(\"ms\") or seconds(\"s\"). The default units are \"ms\".");
  94         ps.println("  <count>       Number of samples to take before terminating.");
  95         ps.println("  -J<flag>      Pass <flag> directly to the runtime system.");
  96         ps.println("  -? -h --help  Prints this help message.");
  97         ps.println("  -help         Prints this help message.");
  98 
  99         // undocumented options:
 100         //   -list [<vmid>]  - list counter names
 101         //   -snap <vmid>    - snapshot counter values as name=value pairs
 102         //   -name <pattern> - output counters matching given pattern
 103         //   -a              - sort in ascending order (default)
 104         //   -d              - sort in descending order
 105         //   -v              - verbose output  (-snap)
 106         //   -constants      - output constants with -name output
 107         //   -strings        - output strings with -name output
 108         //   -help           - same as -? ...
 109     }
 110 
 111     private static int toMillis(String s) throws IllegalArgumentException {
 112 
 113         String[] unitStrings = { "ms", "s" }; // ordered from most specific to
 114                                               // least specific
 115         String unitString = null;
 116         String valueString = s;
 117 
 118         for (int i = 0; i < unitStrings.length; i++) {
 119             int index = s.indexOf(unitStrings[i]);
 120             if (index > 0) {
 121                 unitString = s.substring(index);
 122                 valueString = s.substring(0, index);
 123                 break;
 124             }
 125         }
 126 
 127         try {
 128             int value = Integer.parseInt(valueString);
 129 
 130             if (unitString == null || unitString.compareTo("ms") == 0) {
 131                 return value;
 132             } else if (unitString.compareTo("s") == 0) {
 133                 return value * 1000;
 134             } else {
 135                 throw new IllegalArgumentException(
 136                         "Unknow time unit: " + unitString);
 137             }
 138         } catch (NumberFormatException e) {
 139             throw new IllegalArgumentException(
 140                     "Could not convert interval: " + s);
 141         }
 142     }
 143 
 144     public Arguments(String[] args) throws IllegalArgumentException {
 145         int argc = 0;
 146 
 147         if (args.length == 0) {
 148             help = true;
 149             return;
 150         }
 151 
 152         if ((args[0].compareTo("-?") == 0)
 153                 || (args[0].compareTo("-h") == 0)
 154                 || (args[0].compareTo("--help") == 0)
 155                 // -help: legacy.
 156                 || (args[0].compareTo("-help") == 0)) {
 157             help = true;
 158             return;
 159         } else if (args[0].compareTo("-options") == 0) {
 160             options = true;
 161             return;
 162         } else if (args[0].compareTo("-list") == 0) {
 163             list = true;
 164             if (args.length > 2) {
 165               throw new IllegalArgumentException("invalid argument count");
 166             }
 167             // list can take one arg - a vmid - fall through for arg processing
 168             argc++;
 169         }
 170 
 171         for ( ; (argc < args.length) && (args[argc].startsWith("-")); argc++) {
 172             String arg = args[argc];
 173 
 174             if (arg.compareTo("-a") == 0) {
 175                 comparator = new AscendingMonitorComparator();
 176             } else if (arg.compareTo("-d") == 0) {
 177                 comparator =  new DescendingMonitorComparator();
 178             } else if (arg.compareTo("-t") == 0) {
 179                 timestamp = true;
 180             } else if (arg.compareTo("-v") == 0) {
 181                 verbose = true;
 182             } else if ((arg.compareTo("-constants") == 0)
 183                        || (arg.compareTo("-c") == 0)) {
 184                 constants = true;
 185             } else if ((arg.compareTo("-strings") == 0)
 186                        || (arg.compareTo("-s") == 0)) {
 187                 strings = true;
 188             } else if (arg.startsWith("-h")) {
 189                 String value;
 190                 if (arg.compareTo("-h") != 0) {
 191                     value = arg.substring(2);
 192                 } else {
 193                     argc++;
 194                     if (argc >= args.length) {
 195                         throw new IllegalArgumentException(
 196                                 "-h requires an integer argument");
 197                     }
 198                     value = args[argc];
 199                 }
 200                 try {
 201                     headerRate = Integer.parseInt(value);
 202                 } catch (NumberFormatException e) {
 203                     headerRate = -1;
 204                 }
 205                 if (headerRate < 0) {
 206                     throw new IllegalArgumentException(
 207                             "illegal -h argument: " + value);
 208                 }
 209             } else if (arg.startsWith("-name")) {
 210                 if (arg.startsWith("-name=")) {
 211                     names = arg.substring(7);
 212                 } else {
 213                     argc++;
 214                     if (argc >= args.length) {
 215                         throw new IllegalArgumentException(
 216                                 "option argument expected");
 217                     }
 218                     names = args[argc];
 219                 }
 220             } else {
 221                 /*
 222                  * there are scenarios here: special jstat_options file option
 223                  * or the rare case of a negative lvmid. The negative lvmid
 224                  * can occur in some operating environments (such as Windows
 225                  * 95/98/ME), so we provide for this case here by checking if
 226                  * the argument has any numerical characters. This assumes that
 227                  * there are no special jstat_options that contain numerical
 228                  * characters in their name.
 229                  */
 230 
 231                 // extract the lvmid part of possible lvmid@host.domain:port
 232                 String lvmidStr = null;
 233                 int at_index = args[argc].indexOf('@');
 234                 if (at_index < 0) {
 235                     lvmidStr = args[argc];
 236                 } else {
 237                     lvmidStr = args[argc].substring(0, at_index);
 238                 }
 239 
 240                 // try to parse the lvmid part as an integer
 241                 try {
 242                     int vmid = Integer.parseInt(lvmidStr);
 243                     // it parsed, assume a negative lvmid and continue
 244                     break;
 245                 } catch (NumberFormatException nfe) {
 246                     // it didn't parse. check for the -snap or jstat_options
 247                     // file options.
 248                     if ((argc == 0) && (args[argc].compareTo("-snap") == 0)) {
 249                         snap = true;
 250                     } else if (argc == 0) {
 251                         specialOption = args[argc].substring(1);
 252                     } else {
 253                         throw new IllegalArgumentException(
 254                                 "illegal argument: " + args[argc]);
 255                     }
 256                 }
 257             }
 258         }
 259 
 260         // prevent 'jstat <pid>' from being accepted as a valid argument
 261         if (!(specialOption != null || list || snap || names != null)) {
 262             throw new IllegalArgumentException("-<option> required");
 263         }
 264 
 265         switch (args.length - argc) {
 266         case 3:
 267             if (snap) {
 268                 throw new IllegalArgumentException("invalid argument count");
 269             }
 270             try {
 271                 count = Integer.parseInt(args[args.length-1]);
 272             } catch (NumberFormatException e) {
 273                 throw new IllegalArgumentException("illegal count value: "
 274                                                    + args[args.length-1]);
 275             }
 276             interval = toMillis(args[args.length-2]);
 277             vmIdString = args[args.length-3];
 278             break;
 279         case 2:
 280             if (snap) {
 281                 throw new IllegalArgumentException("invalid argument count");
 282             }
 283             interval = toMillis(args[args.length-1]);
 284             vmIdString = args[args.length-2];
 285             break;
 286         case 1:
 287             vmIdString = args[args.length-1];
 288             break;
 289         case 0:
 290             if (!list) {
 291                 throw new IllegalArgumentException("invalid argument count");
 292             }
 293             break;
 294         default:
 295             throw new IllegalArgumentException("invalid argument count");
 296         }
 297 
 298         // set count and interval to their default values if not set above.
 299         if (count == -1 && interval == -1) {
 300             // default is for a single sample
 301             count = 1;
 302             interval = 0;
 303         }
 304 
 305         // validate arguments
 306         if (comparator == null) {
 307             comparator = new AscendingMonitorComparator();
 308         }
 309 
 310         // allow ',' characters to separate names, convert to '|' chars
 311         names = (names == null) ? ALL_NAMES : names.replace(',', '|');
 312 
 313         // verify that the given pattern parses without errors
 314         try {
 315             Pattern pattern = Pattern.compile(names);
 316         } catch (PatternSyntaxException e) {
 317             throw new IllegalArgumentException("Bad name pattern: "
 318                                                + e.getMessage());
 319         }
 320 
 321         // verify that the special option is valid and get it's formatter
 322         if (specialOption != null) {
 323             OptionFinder finder = new OptionFinder(optionsSources());
 324             optionFormat = finder.getOptionFormat(specialOption, timestamp);
 325             if (optionFormat == null) {
 326                 throw new IllegalArgumentException("Unknown option: -"
 327                                                    + specialOption);
 328             }
 329         }
 330 
 331         // verify that the vm identifier is valied
 332         try {
 333             vmId = new VmIdentifier(vmIdString);
 334         } catch (URISyntaxException e) {
 335             IllegalArgumentException iae = new IllegalArgumentException(
 336                     "Malformed VM Identifier: " + vmIdString);
 337             iae.initCause(e);
 338             throw iae;
 339         }
 340     }
 341 
 342     public Comparator<Monitor> comparator() {
 343         return comparator;
 344     }
 345 
 346     public boolean isHelp() {
 347         return help;
 348     }
 349 
 350     public boolean isList() {
 351         return list;
 352     }
 353 
 354     public boolean isSnap() {
 355         return snap;
 356     }
 357 
 358     public boolean isOptions() {
 359         return options;
 360     }
 361 
 362     public boolean isVerbose() {
 363         return verbose;
 364     }
 365 
 366     public boolean printConstants() {
 367         return constants;
 368     }
 369 
 370     public boolean isConstantsOnly() {
 371         return constantsOnly;
 372     }
 373 
 374     public boolean printStrings() {
 375         return strings;
 376     }
 377 
 378     public boolean showUnsupported() {
 379         return showUnsupported;
 380     }
 381 
 382     public int headerRate() {
 383         return headerRate;
 384     }
 385 
 386     public String counterNames() {
 387         return names;
 388     }
 389 
 390     public VmIdentifier vmId() {
 391         return vmId;
 392     }
 393 
 394     public String vmIdString() {
 395         return vmIdString;
 396     }
 397 
 398     public int sampleInterval() {
 399         return interval;
 400     }
 401 
 402     public int sampleCount() {
 403         return count;
 404     }
 405 
 406     public boolean isTimestamp() {
 407         return timestamp;
 408     }
 409 
 410     public boolean isSpecialOption() {
 411         return specialOption != null;
 412     }
 413 
 414     public String specialOption() {
 415         return specialOption;
 416     }
 417 
 418     public OptionFormat optionFormat() {
 419         return optionFormat;
 420     }
 421 
 422     public List<URL> optionsSources() {
 423         List<URL> sources = new ArrayList<URL>();
 424         int i = 0;
 425 
 426         String filename = OPTIONS_FILENAME;
 427 
 428         try {
 429             String userHome = System.getProperty("user.home");
 430             String userDir = userHome + "/" + JVMSTAT_USERDIR;
 431             File home = new File(userDir + "/" + filename);
 432             sources.add(home.toURI().toURL());
 433         } catch (Exception e) {
 434             if (debug) {
 435                 System.err.println(e.getMessage());
 436                 e.printStackTrace();
 437             }
 438             throw new IllegalArgumentException("Internal Error: Bad URL: "
 439                                                + e.getMessage());
 440         }
 441         URL u = this.getClass().getResource("resources/" + filename);
 442         assert u != null;
 443         sources.add(u);
 444 
 445         if (showUnsupported) {
 446             u = this.getClass().getResource("resources/" +  UNSUPPORTED_OPTIONS_FILENAME);
 447             assert u != null;
 448             sources.add(u);
 449         }
 450         return sources;
 451     }
 452 }