1 /*
   2  * Copyright (c) 2015, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 import java.io.IOException;
  24 import java.net.BindException;
  25 import java.util.ArrayList;
  26 import java.util.Arrays;
  27 import java.util.List;
  28 import java.util.Properties;
  29 import java.util.concurrent.atomic.AtomicBoolean;
  30 import java.util.function.Consumer;
  31 import java.util.regex.Pattern;
  32 import java.util.stream.Collectors;
  33 import jdk.internal.agent.Agent;
  34 import jdk.internal.agent.AgentConfigurationError;
  35 
  36 import jdk.testlibrary.JDKToolLauncher;
  37 import jdk.test.lib.process.ProcessTools;
  38 
  39 /**
  40  * A helper class for issuing ManagementAgent.* diagnostic commands and capturing
  41  * their output.
  42  */
  43 final class ManagementAgentJcmd {
  44     private static final String CMD_STOP = "ManagementAgent.stop";
  45     private static final String CMD_START = "ManagementAgent.start";
  46     private static final String CMD_START_LOCAL = "ManagementAgent.start_local";
  47     private static final String CMD_STATUS = "ManagementAgent.status";
  48     private static final String CMD_PRINTPERF = "PerfCounter.print";
  49 
  50     private final String id;
  51     private final boolean verbose;
  52 
  53     public ManagementAgentJcmd(String targetApp, boolean verbose) {
  54         this.id = targetApp;
  55         this.verbose = verbose;
  56     }
  57 
  58     /**
  59      * `jcmd`
  60      * @return The JCMD output
  61      * @throws IOException
  62      * @throws InterruptedException
  63      */
  64     public String list() throws IOException, InterruptedException {
  65         return jcmd();
  66     }
  67 
  68     /**
  69      * `jcmd PerfCounter.print`
  70      * @return Returns the available performance counters with their values as
  71      *         {@linkplain Properties} instance
  72      * @throws IOException
  73      * @throws InterruptedException
  74      */
  75     public Properties perfCounters() throws IOException, InterruptedException {
  76         return perfCounters(".*");
  77     }
  78 
  79     /**
  80      * `jcmd PerfCounter.print | grep {exp}>`
  81      * @param regex Regular expression for including perf counters in the result
  82      * @return Returns the matching performance counters with their values
  83      *         as {@linkplain Properties} instance
  84      * @throws IOException
  85      * @throws InterruptedException
  86      */
  87     public Properties perfCounters(String regex) throws IOException, InterruptedException {
  88         Pattern pat = Pattern.compile(regex);
  89         Properties p = new Properties();
  90         for(String l : jcmd(CMD_PRINTPERF).split("\\n")) {
  91             String[] kv = l.split("=");
  92             if (kv.length > 1) {
  93                 if (pat.matcher(kv[0]).matches()) {
  94                     p.setProperty(kv[0], kv[1].replace("\"", ""));
  95                 }
  96             }
  97         }
  98         return p;
  99     }
 100 
 101     /**
 102      * `jcmd <app> ManagementAgent.stop`
 103      * @return The JCMD output
 104      * @throws IOException
 105      * @throws InterruptedException
 106      */
 107     public String stop() throws IOException, InterruptedException {
 108         return jcmd(CMD_STOP);
 109     }
 110 
 111     /**
 112      * `jcmd <app> ManagementAgent.start_local`
 113      * @return The JCMD output
 114      * @throws IOException
 115      * @throws InterruptedException
 116      */
 117     public String startLocal() throws IOException, InterruptedException {
 118         return jcmd(CMD_START_LOCAL);
 119     }
 120 
 121     /**
 122      * `jcmd <app> ManagementAgent.start <args>`
 123      * @return The JCMD output
 124      * @param params The arguments to <b>ManagementAgent.start</b> command
 125      * @throws IOException
 126      * @throws InterruptedException
 127      */
 128     public String start(String ... params) throws IOException, InterruptedException {
 129         return start(c->{}, params);
 130     }
 131 
 132     /**
 133      * `jcmd <pp> ManagementAgent.start <args>`
 134      * @param c A string consumer used to inspect the jcmd output line-by-line
 135      * @param params The arguments to <b>ManagementAgent.start</b> command
 136      * @return The JCMD output
 137      * @throws IOException
 138      * @throws InterruptedException
 139      */
 140     public String start(Consumer<String> c, String ... params) throws IOException, InterruptedException {
 141         List<String> args = new ArrayList<>();
 142         args.add(CMD_START);
 143         args.addAll(Arrays.asList(params));
 144         return jcmd(c, args.toArray(new String[args.size()]));
 145     }
 146 
 147     public String status() throws IOException, InterruptedException {
 148         return jcmd(CMD_STATUS);
 149     }
 150 
 151     /**
 152      * Run the "jcmd" command
 153      *
 154      * @param command Command + arguments
 155      * @return The JCMD output
 156      * @throws IOException
 157      * @throws InterruptedException
 158      */
 159     private String jcmd(String ... command) throws IOException, InterruptedException {
 160         if (command.length == 0) {
 161             return jcmd(null, c->{});
 162         } else {
 163             return jcmd(c->{}, command);
 164         }
 165     }
 166 
 167     /**
 168      * Run the "jcmd" command
 169      *
 170      * @param c {@linkplain Consumer} instance
 171      * @param command Command + arguments
 172      * @return The JCMD output
 173      * @throws IOException
 174      * @throws InterruptedException
 175      */
 176     private String jcmd(Consumer<String> c, String ... command) throws IOException, InterruptedException {
 177         return jcmd(id, c, command);
 178     }
 179 
 180     /**
 181      * Run the "jcmd" command
 182      *
 183      * @param target The target application name (or PID)
 184      * @param c {@linkplain Consumer} instance
 185      * @param command Command + arguments
 186      * @return The JCMD output
 187      * @throws IOException
 188      * @throws InterruptedException
 189      */
 190     private String jcmd(String target, final Consumer<String> c, String ... command) throws IOException, InterruptedException {
 191         dbg_print("[jcmd] " + (command.length > 0 ? command[0] : "list"));
 192 
 193         JDKToolLauncher l = JDKToolLauncher.createUsingTestJDK("jcmd");
 194         l.addToolArg(target);
 195         for (String cmd : command) {
 196             l.addToolArg(cmd);
 197         }
 198 
 199         // this buffer will get filled in different threads
 200         //   -> must be the synchronized StringBuffer
 201         StringBuffer output = new StringBuffer();
 202 
 203         AtomicBoolean portUnavailable = new AtomicBoolean(false);
 204         Process p = ProcessTools.startProcess(
 205             "jcmd",
 206             new ProcessBuilder(l.getCommand()),
 207             line -> {
 208                 if (line.contains("BindException") ||
 209                     line.contains(Agent.getText(AgentConfigurationError.CONNECTOR_SERVER_IO_ERROR))) {
 210                     portUnavailable.set(true);
 211                 } else {
 212                     output.append(line).append('\n');
 213                     c.accept(line);
 214                 }
 215             }
 216         );
 217 
 218         p.waitFor();
 219         dbg_print("[jcmd] --------");
 220         if (portUnavailable.get()) {
 221             String cmd = Arrays.asList(l.getCommand()).stream()
 222                     .collect(
 223                             Collectors.joining(" ", "", ": Unable to bind address")
 224                     );
 225             throw new BindException(cmd);
 226         }
 227 
 228         return output.toString();
 229     }
 230 
 231     private void dbg_print(String msg) {
 232         if (verbose) {
 233             System.out.println("DBG: " + msg);
 234         }
 235     }
 236 }