com.bigdata.counters.linux.PIDStatCollector Maven / Gradle / Ivy
/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Mar 26, 2008
*/
package com.bigdata.counters.linux;
import com.bigdata.counters.*;
import com.bigdata.counters.linux.SarCpuUtilizationCollector.DI;
import com.bigdata.util.Bytes;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Collects statistics on the JVM process relating to CPU, memory, and IO
* statistics (when available) using pidstat -p 501 -u -I -r -d -w
* [[interval [count]]
*
* Where -p
is the pid to monitor, -u
is cpu
* utilization (-I
normalizes to 100% for SMP), -r
* gives the process memory statistics, -d
gives IO statistics with
* kernels 2.6.20 and up; -w
gives context switching data; The
* interval is in seconds. The count is optional - when missing or zero will
* repeat forever if interval was specified.
*
* @author Bryan Thompson
*/
public class PIDStatCollector extends AbstractProcessCollector implements
ICounterHierarchy, IProcessCounters {
// static protected final Logger log = Logger.getLogger(PIDStatCollector.class);
//
// /**
// * True iff the {@link #log} level is DEBUG or less.
// */
// final protected static boolean DEBUG = log.isDebugEnabled();
//
// /**
// * True iff the {@link #log} level is log.isInfoEnabled() or less.
// */
// final protected static boolean log.isInfoEnabled() = log.isInfoEnabled();
/** process to be monitored. */
protected final int pid;
/**
* set true
if per-process IO data collection should be
* supported based on the {@link KernelVersion}.
*/
protected final boolean perProcessIOData;
/**
* Abstract inner class integrating the current values with the {@link ICounterSet}
* hierarchy.
*
* @author Bryan Thompson
*/
abstract class AbstractInst implements IInstrument {
protected final String path;
final public String getPath() {
return path;
}
protected AbstractInst(final String path) {
if (path == null)
throw new IllegalArgumentException();
this.path = path;
}
@Override
final public long lastModified() {
return lastModified.get();
}
/**
* @throws UnsupportedOperationException
* always.
*/
@Override
final public void setValue(final T value, final long timestamp) {
throw new UnsupportedOperationException();
}
}
/**
* Inner class integrating the current values with the {@link ICounterSet}
* hierarchy.
*
* @author Bryan Thompson
*/
class IL extends AbstractInst {
protected final long scale;
public IL(final String path, final long scale) {
super( path );
this.scale = scale;
}
@Override
public Long getValue() {
final Long value = (Long) vals.get(path);
// no value is defined.
if (value == null)
return 0L;
final long v = value.longValue() * scale;
return v;
}
}
/**
* Inner class integrating the current values with the {@link ICounterSet}
* hierarchy.
*
* @author Bryan Thompson
*/
class ID extends AbstractInst {
protected final double scale;
public ID(final String path, final double scale) {
super(path);
this.scale = scale;
}
@Override
public Double getValue() {
final Double value = (Double) vals.get(path);
// no value is defined.
if (value == null)
return 0d;
final double d = value.doubleValue() * scale;
return d;
}
}
/**
* Updated each time a new row of data is read from the process and reported
* as the last modified time for counters based on that process and
* defaulted to the time that we begin to collect performance data.
*/
private final AtomicLong lastModified = new AtomicLong(System.currentTimeMillis());
/**
* Map containing the current values for the configured counters. The keys
* are paths into the {@link CounterSet}. The values are the data most
* recently read from pidstat
.
*
* Note: The paths are in fact relative to how the counters are declared by
* {@link #getCounters()}. Likewise {@link DI#getValue()} uses the paths
* declared within {@link #getCounters()} and not whatever path the counters
* are eventually placed under within a larger hierarchy.
*/
private final Map vals = new ConcurrentHashMap();
/**
* @param pid
* Process to be monitored.
* @param interval
* Reporting interval in seconds.
* @param kernelVersion
* The Linux {@link KernelVersion}.
*
* @todo kernelVersion could be static.
*/
public PIDStatCollector(final int pid, final int interval,
final KernelVersion kernelVersion) {
super(interval);
if (interval <= 0)
throw new IllegalArgumentException();
if (kernelVersion == null)
throw new IllegalArgumentException();
this.pid = pid;
perProcessIOData = kernelVersion.version > 2 || (kernelVersion.version == 2
&& kernelVersion.major >= 6 && kernelVersion.minor >= 20);
}
@Override
public List getCommand() {
final List command = new LinkedList();
command.add(SysstatUtil.getPath("pidstat").getPath());
command.add("-p");
command.add(""+pid);
command.add("-u"); // CPU stats
command.add("-I"); // normalize CPU stats to 100% iff SMP.
if(perProcessIOData) {
command.add("-d"); // disk IO report.
}
command.add("-r"); // memory report
// command.add("-w"); // context switching report (not implemented in our code).
command.add("" + getInterval());
return command;
}
@Override
public CounterSet getCounters() {
final List> inst = new LinkedList>();
/*
* Note: Counters are all declared as Double to facilitate aggregation
* and scaling.
*
* Note: pidstat reports percentages as [0:100] so we normalize them to
* [0:1] using a scaling factor.
*/
inst.add(new ID(IProcessCounters.CPU_PercentUserTime, .01d));
inst.add(new ID(IProcessCounters.CPU_PercentSystemTime, .01d));
inst.add(new ID(IProcessCounters.CPU_PercentProcessorTime, .01d));
inst.add(new ID(IProcessCounters.Memory_minorFaultsPerSec, 1d));
inst.add(new ID(IProcessCounters.Memory_majorFaultsPerSec, 1d));
inst.add(new IL(IProcessCounters.Memory_virtualSize, Bytes.kilobyte));
inst.add(new IL(IProcessCounters.Memory_residentSetSize, Bytes.kilobyte));
inst.add(new ID(IProcessCounters.Memory_percentMemorySize, .01d));
/*
* Note: pidstat reports in kb/sec so we normalize to bytes/second using
* a scaling factor.
*/
inst.add(new ID(IProcessCounters.PhysicalDisk_BytesReadPerSec,
Bytes.kilobyte32));
inst.add(new ID(IProcessCounters.PhysicalDisk_BytesWrittenPerSec,
Bytes.kilobyte32));
final CounterSet root = new CounterSet();
for (AbstractInst> i : inst) {
root.addCounter(i.getPath(), i);
}
return root;
}
/**
* Extended to force pidstat
to use a consistent timestamp
* format regardless of locale by setting S_TIME_FORMAT="ISO"
* in the environment.
*/
@Override
protected void setEnvironment(final Map env) {
super.setEnvironment(env);
env.put("S_TIME_FORMAT", "ISO");
}
@Override
public AbstractProcessReader getProcessReader() {
return new PIDStatReader();
}
/**
* Reads pidstat
output and extracts and updates counter
* values.
*
* Sample pidstat
output.
*
*
* Linux 2.6.22.14-72.fc6 (hostname) 03/16/2008
*
* 06:35:15 AM PID %user %system %CPU CPU Command
* 06:35:15 AM 501 0.00 0.01 0.00 1 kjournald
*
* 06:35:15 AM PID minflt/s majflt/s VSZ RSS %MEM Command
* 06:35:15 AM 501 0.00 0.00 0 0 0.00 kjournald
*
* 06:35:15 AM PID kB_rd/s kB_wr/s kB_ccwr/s Command
* 06:35:15 AM 501 0.00 1.13 0.00 kjournald
*
* 06:35:15 AM PID cswch/s nvcswch/s Command
* 06:35:15 AM 501 0.00 0.00 kjournald
*
*
* @author Bryan Thompson
*/
protected class PIDStatReader extends ProcessReaderHelper {
private static final String PIDSTAT_FIELD_CPU_PERCENT_USR = "%usr";
private static final String PIDSTAT_FIELD_CPU_PERCENT = "%CPU";
private static final String PIDSTAT_FIELD_CPU_PERCENT_SYSTEM = "%system";
private static final String PIDSTAT_FIELD_MEM_MINOR_FAULTS_PERS = "minflt/s";
private static final String PIDSTAT_FIELD_MEM_MAJOR_FAULTS_PERS = "majflt/s";
private static final String PIDSTAT_FIELD_MEM_VIRTUAL_SIZE = "VSZ";
private static final String PIDSTAT_FIELD_MEM_RESIDENT_SET_SIZE = "RSS";
private static final String PIDSTAT_FIELD_MEM_SIZE_PERCENT = "%MEM";
private static final String PIDSTAT_FIELD_DISK_KB_READ_PERS = "kB_rd/s";
private static final String PIDSTAT_FIELD_DISK_KB_WRITTEN_PERS = "kB_wr/s";
@Override
protected ActiveProcess getActiveProcess() {
if (activeProcess == null)
throw new IllegalStateException();
return activeProcess;
}
public PIDStatReader() {
super();
}
/**
* The input arrives as an initial banner and a sequence of data
* reporting events, each of which is three lines. The first
* line is blank. The second line gives the headers for the
* event. The third line gives the data for the event. The 2nd
* and 3rd lines carry a timestamp as the first value.
*
* When you request more than one kind of reporting, the three
* line set simply repeats for each class of events that are to
* be reported. E.g., cpu and io. This code diagnoses the event
* types by examining the header line.
*
* @todo since we are examining header lines and since
* sysstat
is ported to many languages, it
* is possible that this will not work when the host is
* using an English locale.
*/
@Override
protected void readProcess() throws IOException, InterruptedException {
if(log.isInfoEnabled())
log.info("begin");
for(int i=0; i<10 && !getActiveProcess().isAlive(); i++) {
if(log.isInfoEnabled())
log.info("waiting for the readerFuture to be set.");
Thread.sleep(100/*ms*/);
}
if(log.isInfoEnabled())
log.info("running");
// skip banner.
final String banner = readLine();
if(log.isInfoEnabled())
log.info("banner: "+banner);
// #of "events" read. each event is three lines.
long n = 0;
while(true) {
// skip blank line.
final String blank = readLine();
assert blank.trim().length() == 0 : "Expecting a blank line";
// header.
final String header = readLine();
// data.
final String data = readLine();
try {
// timestamp
// {
//
// final String s = data.substring(0, 11);
//
// try {
//
// lastModified = f.parse(s).getTime();
//
// } catch (Exception e) {
//
// log.warn("Could not parse time: [" + s + "] : " + e);
//
// // should be pretty close.
// lastModified = System.currentTimeMillis();
//
// }
//
// } /*
/*
* Note: This timestamp should be _very_ close to the value reported
* by sysstat. Also, using the current time is MUCH easier and less
* error prone than attempting to parse the TIME OF DAY written by
* sysstat and correct it into a UTC time by adjusting for the UTC
* time of the start of the current day, which is what we would have
* to do.
*/
lastModified.set(System.currentTimeMillis());
final Map fields = SysstatUtil.getDataMap(header, data);
if (log.isInfoEnabled()) {
StringBuilder sb = new StringBuilder();
for ( Map.Entry e: fields.entrySet()) {
sb.append(e.getKey());
sb.append("=");
sb.append(e.getValue());
sb.append(", ");
}
log.info(sb.toString());
log.info(header + ";" + data);
}
if(fields.containsKey(PIDSTAT_FIELD_CPU_PERCENT)) {
/*
* CPU data for the specified process.
*/
// 06:35:15 AM PID %user %system %CPU CPU Command
// 06:35:15 AM 501 0.00 0.01 0.00 1 kjournald
vals.put(IProcessCounters.CPU_PercentUserTime,
Double.parseDouble(fields.get(PIDSTAT_FIELD_CPU_PERCENT_USR)));
vals.put(IProcessCounters.CPU_PercentSystemTime,
Double.parseDouble(fields.get(PIDSTAT_FIELD_CPU_PERCENT_SYSTEM)));
vals.put(IProcessCounters.CPU_PercentProcessorTime,
Double.parseDouble(fields.get(PIDSTAT_FIELD_CPU_PERCENT)));
} else if(fields.containsKey("RSS")) {
/*
* Memory data for the specified process.
*
* 06:35:15 AM PID minflt/s majflt/s VSZ RSS %MEM Command
* 06:35:15 AM 501 0.00 0.00 0 0 0.00 kjournald
*/
vals.put(IProcessCounters.Memory_minorFaultsPerSec,
Double.parseDouble(fields.get(PIDSTAT_FIELD_MEM_MINOR_FAULTS_PERS)));
vals.put(IProcessCounters.Memory_majorFaultsPerSec,
Double.parseDouble(fields.get(PIDSTAT_FIELD_MEM_MAJOR_FAULTS_PERS)));
vals.put(IProcessCounters.Memory_virtualSize,
Long.parseLong(fields.get(PIDSTAT_FIELD_MEM_VIRTUAL_SIZE)));
vals.put(IProcessCounters.Memory_residentSetSize,
Long.parseLong(fields.get(PIDSTAT_FIELD_MEM_RESIDENT_SET_SIZE)));
vals.put(IProcessCounters.Memory_percentMemorySize,
Double.parseDouble(fields.get(PIDSTAT_FIELD_MEM_SIZE_PERCENT)));
} else if(perProcessIOData && header.contains("kB_rd/s")) {
/*
* IO data for the specified process.
*
* 06:35:15 AM PID kB_rd/s kB_wr/s kB_ccwr/s Command
* 06:35:15 AM 501 0.00 1.13 0.00 kjournald
*/
vals.put(IProcessCounters.PhysicalDisk_BytesReadPerSec,
Double.parseDouble(fields.get(PIDSTAT_FIELD_DISK_KB_READ_PERS)));
vals.put(IProcessCounters.PhysicalDisk_BytesWrittenPerSec,
Double.parseDouble(fields.get(PIDSTAT_FIELD_DISK_KB_WRITTEN_PERS)));
} else {
log.warn("Could not identify event type from header: ["+header+"]");
continue;
}
} catch(Exception ex) {
/*
* Issue warning for parsing problems.
*/
log.warn(ex.getMessage() //
+ "\nheader: " + header //
+ "\n data: " + data //
//, ex//
);
}
n++;
}
}
}
}