blob: 9584fc936b04bf5704c007335d2bd2148c62f34e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.brooklyn.qa.longevity;
import static com.google.common.base.Strings.isNullOrEmpty;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.brooklyn.util.http.HttpTool;
import org.apache.brooklyn.util.http.HttpToolResponse;
import org.apache.brooklyn.util.stream.StreamGobbler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.MoreObjects;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.io.ByteStreams;
public class MonitorUtils {
private static final Logger LOG = LoggerFactory.getLogger(MonitorUtils.class);
private static volatile int ownPid = -1;
/**
* Confirm can read from URL.
*
* @param url
*/
public static boolean isUrlUp(URL url) {
try {
HttpToolResponse result = HttpTool.httpGet(
HttpTool.httpClientBuilder().trustAll().build(),
URI.create(url.toString()),
ImmutableMap.<String,String>of());
int statuscode = result.getResponseCode();
if (statuscode != 200) {
LOG.info("Error reading URL {}: {}, {}", new Object[]{url, statuscode, result.getReasonPhrase()});
return false;
} else {
return true;
}
} catch (Exception e) {
LOG.info("Error reading URL {}: {}", url, e);
return false;
}
}
public static boolean isPidRunning(int pid) {
return isPidRunning(pid, null);
}
/**
* Confirm the given pid is running, and that the the process matches the given regex.
*
* @param pid
* @param regex
*/
public static boolean isPidRunning(int pid, String regex) {
Process process = exec("ps -p " + pid);
String out = waitFor(process);
if (process.exitValue() > 0) {
String err = toString(process.getErrorStream());
LOG.info(String.format("pid %s not running: %s", pid, err));
return false;
}
if (regex != null) {
String regex2 = "^\\s*" + pid + ".*" + regex;
boolean found = false;
for (String line : out.split("\n")) {
if (hasAtLeastOneMatch(line, regex2)) {
found = true;
break;
}
}
if (!found) {
String txt = toString(process.getInputStream());
LOG.info("process did not match regular expression: "+txt);
return false;
}
}
return true;
}
private static boolean hasAtLeastOneMatch(String line, String regex) {
return Pattern.matches(".*"+regex+".*", line);
}
private static String toString(InputStream in){
try {
byte[] bytes = ByteStreams.toByteArray(in);
return new String(bytes);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
public static List<Integer> getRunningPids(String regex) {
return getRunningPids(regex, null);
}
/**
* Confirm the given pid is running, and that the the process matches the given regex.
*
* @param regex
* @param excludingRegex
*/
public static List<Integer> getRunningPids(String regex, String excludingRegex) {
Process process = exec("ps ax");
String out = waitFor(process);
List<Integer> result = new LinkedList<Integer>();
for (String line : out.split("\n")) {
if (excludingRegex != null && hasAtLeastOneMatch(line, excludingRegex)) {
continue;
}
if (hasAtLeastOneMatch(line, regex)) {
String[] linesplit = line.trim().split("\\s+");
result.add(Integer.parseInt(linesplit[0]));
}
}
return result;
}
public static MemoryUsage getMemoryUsage(int pid){
return getMemoryUsage(pid, null,0);
}
/**
* @param pid
*/
public static MemoryUsage getMemoryUsage(int pid, String clazzRegexOfInterest, int minInstancesOfInterest) {
Process process = exec(String.format("jmap -histo %s", pid));
String out = waitFor(process);
Map<String, Integer> instanceCounts = Maps.newLinkedHashMap();
long totalInstances=0;
long totalMemoryBytes=0;
for (String line : out.split("\n")) {
if (clazzRegexOfInterest!=null && hasAtLeastOneMatch(line, clazzRegexOfInterest)) {
// Format is:
// num #instances #bytes class name
// 1: 43506 8047096 example.MyClazz
String[] parts = line.trim().split("\\s+");
String clazz = parts[3];
int instanceCount = Integer.parseInt(parts[1]);
if (instanceCount >= minInstancesOfInterest) {
instanceCounts.put(clazz, instanceCount);
}
}
if (hasAtLeastOneMatch(line, "^Total.*")) {
String[] parts = line.split("\\s+");
totalInstances = Long.parseLong(parts[1]);
totalMemoryBytes = Long.parseLong(parts[2]);
}
}
return new MemoryUsage(totalInstances, totalMemoryBytes, instanceCounts);
}
public static class MemoryUsage {
final long totalInstances;
final long totalMemoryBytes;
final Map<String, Integer> instanceCounts;
MemoryUsage(long totalInstances, long totalMemoryBytes, Map<String, Integer> instanceCounts) {
this.totalInstances = totalInstances;
this.totalMemoryBytes = totalMemoryBytes;
this.instanceCounts = instanceCounts;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("totalInstances", totalInstances)
.add("totalMemoryBytes", totalMemoryBytes)
.add("instanceCounts", instanceCounts)
.toString();
}
public long getTotalInstances() {
return totalInstances;
}
public long getTotalMemoryBytes() {
return totalMemoryBytes;
}
public Map<String, Integer> getInstanceCounts() {
return instanceCounts;
}
}
public static List<String> searchLog(File file, String grepOfInterest) {
return searchLog(file, grepOfInterest, new LinkedHashSet<String>());
}
/**
* Find lines in the given file that match given given regex.
*
* @param file
* @param grepOfInterest
*/
public static List<String> searchLog(File file, String grepOfInterest, Set<String> grepExclusions) {
Process process = exec(String.format("grep -E %s %s", grepOfInterest, file.getAbsoluteFile()));
String out = waitFor(process);
// TODO Annoying that String.split() returns size 1 when empty string; lookup javadoc when back online...
if (out.length() == 0) return Collections.<String>emptyList();
List<String> result = new ArrayList<String>();
for (String line : out.trim().split("\n")) {
boolean excluded = false;
for (String exclusion : grepExclusions) {
if (!isNullOrEmpty(exclusion) && hasAtLeastOneMatch(line, exclusion)) {
excluded = true;
}
}
if (!excluded) {
result.add(line);
}
}
return result;
}
public static Process exec(String cmd) {
LOG.info("executing cmd: " + cmd);
try {
return Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
public static class ProcessHasStderr extends IllegalStateException {
private static final long serialVersionUID = -937871002993888405L;
byte[] stderrBytes;
public ProcessHasStderr(byte[] stderrBytes) {
this("Process printed to stderr: " + new String(stderrBytes), stderrBytes);
}
public ProcessHasStderr(String message, byte[] stderrBytes) {
super(message);
this.stderrBytes = stderrBytes;
}
}
/**
* Waits for the given process to complete, consuming its stdout and returning it as a string.
* If there is any output on stderr an exception will be thrown.
*/
public static String waitFor(Process process) {
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
@SuppressWarnings("resource") //Closeable doesn't seem appropriate for StreamGobbler since it isn't expected to be called every time
StreamGobbler gobblerOut = new StreamGobbler(process.getInputStream(), bytesOut, null);
gobblerOut.start();
ByteArrayOutputStream bytesErr = new ByteArrayOutputStream();
@SuppressWarnings("resource")
StreamGobbler gobblerErr = new StreamGobbler(process.getErrorStream(), bytesErr, null);
gobblerErr.start();
try {
process.waitFor();
gobblerOut.blockUntilFinished();
gobblerErr.blockUntilFinished();
if (bytesErr.size() > 0) {
throw new ProcessHasStderr(bytesErr.toByteArray());
}
return new String(bytesOut.toByteArray());
} catch (Exception e) {
throw Throwables.propagate(e);
} finally {
if (gobblerOut.isAlive()) gobblerOut.interrupt();
if (gobblerErr.isAlive()) gobblerErr.interrupt();
}
}
public static int findOwnPid() throws IOException {
if (ownPid >= 0) return ownPid;
String[] cmd = new String[]{"bash", "-c", "echo $PPID"};
Process process = Runtime.getRuntime().exec(cmd);
String out = MonitorUtils.waitFor(process);
ownPid = Integer.parseInt(out.trim());
return ownPid;
}
}