com.bigdata.counters.osx.IOStatCollector 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 Dec 9, 2008
*/
package com.bigdata.counters.osx;
import com.bigdata.counters.*;
import com.bigdata.util.Bytes;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
/**
* Collects some counters using iostat
under OSX. Unfortunately,
* iostat
does not break down the reads and writes and does not
* report IO Wait. This information is obviously available from OSX as it is
* provided by the ActivityMonitor, but we can not get it from
* iostat
.
*
* @author Bryan Thompson
*/
public class IOStatCollector extends AbstractProcessCollector implements
ICounterHierarchy, IRequiredHostCounters, IHostCounters{
/**
* Inner class integrating the current values with the {@link ICounterSet}
* hierarchy.
*
* @author Bryan
* Thompson
*/
abstract class I implements IInstrument {
protected final String path;
public String getPath() {
return path;
}
public I(final String path) {
assert path != null;
this.path = path;
}
@Override
public long lastModified() {
return lastModified.get();
}
/**
* @throws UnsupportedOperationException
* always.
*/
@Override
public void setValue(final T value, final long timestamp) {
throw new UnsupportedOperationException();
}
}
/**
* Double precision counter with scaling factor.
*
* @author Bryan Thompson
*/
class DI extends I {
protected final double scale;
DI(final String path) {
this(path, 1d);
}
DI(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;
}
}
/**
* 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 iostat
.
*/
final private Map vals = new ConcurrentHashMap();
/**
* The timestamp associated with the most recently collected values.
*/
private final AtomicLong lastModified = new AtomicLong(System.currentTimeMillis());
/**
* The {@link Pattern} used to split apart the rows read from
* iostat
.
*/
final static Pattern pattern = Pattern.compile("\\s+");
final private boolean cpuStats;
/**
*
* @param interval
* The interval for the collected statistics.
* @param cpuStats
* true
if the collector should report on the CPU
* stats (us, sy, id).
*/
public IOStatCollector(final int interval, final boolean cpuStats) {
super(interval);
this.cpuStats = cpuStats;
}
@Override
public List getCommand() {
final List command = new LinkedList();
command.add("/usr/sbin/iostat");
// report only device stats.
command.add("-d");
// report on ALL devices
command.add("-n");
command.add("999");
// report CPU stats (explicitly request, so in addition to device stats).
command.add("-C");
// Note: The configured interval in seconds between reports.
command.add("-w");
command.add("" + getInterval());
return command;
}
@Override
public CounterSet getCounters() {
final CounterSet root = new CounterSet();
@SuppressWarnings("rawtypes")
final List inst = new LinkedList();
/*
* Note: Counters are all declared as Double to facilitate aggregation.
*/
/*
* This reports CPU (us, sy, id), bytes read/written per second, and
* transfers per second.
*
* Note: We could also report KB/t (kilobytes per transfer), but it can
* be derived from MB/s and tps.
*/
/*
* Note: The counter is reported in MB/s, so we convert to bytes using a
* scaling factor.
*/
inst.add(new DI(IHostCounters.PhysicalDisk_BytesPerSec,
(double) Bytes.megabyte32));
/*
* Note: The counter is reported in transfers/s.
*/
inst.add(new DI(IHostCounters.PhysicalDisk_TransfersPerSec));
if (cpuStats) {
/*
* Note: iostats reports percentages in [0:100] so we convert them
* to [0:1] using a scaling factor.
*/
// Note: processor time = (100-idle), converted to [0:1].
inst.add(new DI(IRequiredHostCounters.CPU_PercentProcessorTime,.01d));
// Note: column us
inst.add(new DI(IHostCounters.CPU_PercentUserTime, .01d));
// Note: column sy
inst.add(new DI(IHostCounters.CPU_PercentSystemTime, .01d));
// // Note: IO Wait is NOT reported by iostat.
// inst.add(new DI(IHostCounters.CPU_PercentIOWait, .01d));
}
for (@SuppressWarnings("rawtypes") I i : inst) {
root.addCounter(i.getPath(), i);
}
return root;
}
@Override
public AbstractProcessReader getProcessReader() {
return new IOStatReader();
}
/**
* Sample output for iostat -d -C -n 999 -w 60
, where
* 60
is the interval. There is no option to suppress the
* periodic repeat of the header. The header repeats in its entirety every
* "page" full.
*
*
* disk0 disk1 disk2 disk3 disk4 cpu
* KB/t tps MB/s KB/t tps MB/s KB/t tps MB/s KB/t tps MB/s KB/t tps MB/s us sy id
* 197.14 26 5.10 79.10 1 0.07 3.74 0 0.00 41.31 0 0.00 13.03 0 0.00 31 6 63
* 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 53 4 43
* 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 56 8 37
* 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 54 9 37
* 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 57 8 35
*
*
* Note that the #of device columns will vary for this format. The #of
* columns for the report can presumably vary as devices are connected and
* disconnected!
*
* Note: The first data line is ignored as it will be a historical average.
*
* @author Bryan
* Thompson
*/
protected class IOStatReader extends ProcessReaderHelper {
public static final int IOSTAT_CPU_FIELDS_NUM = 3;
public static final String IOSTAT_FIELD_KB_T = "KB/t";
public static final String IOSTAT_FIELD_TPS = "tps";
public static final String IOSTAT_FIELD_MB_S = "MB/s";
public static final String IOSTAT_FIELD_CPU_US = "us";
public static final String IOSTAT_FIELD_CPU_SY = "sy";
public static final String IOSTAT_FIELD_CPU_ID = "id";
@Override
protected ActiveProcess getActiveProcess() {
if (activeProcess == null)
throw new IllegalStateException();
return activeProcess;
}
public IOStatReader() {
super();
}
@Override
protected void readProcess() throws Exception {
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");
// 1st header. this has the "disk#" metadata, which we are ignoring.
{
}
/*
* Read headers. this is also ignored, but we spot check a few
* things.
*
* Note: We have to recompute the #of devices for each data line as
* devices may be connected and disconnected at any time.
*/
final int ncpuFields = cpuStats ? 3 : 0; // us sy id
final int nfieldsPerDevice; // KB/t tps MB/s
final Map deviceFields = new HashMap<>();
final Map cpuFields = new HashMap<>();
String header1 = null;
{
final String h0;
h0 = readLine();
if (log.isInfoEnabled())
log.info("header: " + h0);
final String h1;
header1 = h1 = readLine();
if (log.isInfoEnabled())
log.info("header: " + h1);
int ndevices = pattern.split(h0.trim(), 0).length - 1/* cpu */;
final String[] fields = pattern
.split(h1.trim(), 0/* limit */);
final int nfields = fields.length;
final int ndeviceFields = fields.length - IOSTAT_CPU_FIELDS_NUM;
nfieldsPerDevice = ndeviceFields/ndevices;
for (int i = 0; i < nfieldsPerDevice; i++) {
deviceFields.put(fields[i], i);
}
for (int i = ndeviceFields; i < nfields; i++) {
cpuFields.put(fields[i], i-ndeviceFields);
}
if (log.isInfoEnabled()) {
log.info("ndevices=" + ndevices);
}
}
// * disk0 disk1 disk2 disk3 disk4 cpu
// * KB/t tps MB/s KB/t tps MB/s KB/t tps MB/s KB/t tps MB/s KB/t tps MB/s us sy id
// * 197.14 26 5.10 79.10 1 0.07 3.74 0 0.00 41.31 0 0.00 13.03 0 0.00 31 6 63
// * 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 0.00 0 0.00 53 4 43
// read lines until interrupted.
boolean first = true;
while(true) {
// read the next line of data.
final String data;
{
String s = readLine();
if (s.contains("disk") || s.contains("cpu")) {
// We just read the 1st header
header1 = s = readLine(); // 2nd header line.
s = readLine(); // data line.
if(log.isInfoEnabled())
log.info("Skipped headers.");
}
data = s;
}
if(first) {
// Skip the first data line. It is a historical summary.
first = false;
continue;
}
try {
// timestamp
lastModified.set(System.currentTimeMillis());
final String[] fields = pattern
.split(data.trim(), 0/* limit */);
final int nfields = fields.length;
final int ndeviceFields = fields.length - IOSTAT_CPU_FIELDS_NUM;
final int ndevices = ndeviceFields / nfieldsPerDevice;
if (cpuStats) {
final String us = fields[nfields - IOSTAT_CPU_FIELDS_NUM + cpuFields.get(IOSTAT_FIELD_CPU_US)];
final String sy = fields[nfields - IOSTAT_CPU_FIELDS_NUM + cpuFields.get(IOSTAT_FIELD_CPU_SY)];
final String id = fields[nfields - IOSTAT_CPU_FIELDS_NUM + cpuFields.get(IOSTAT_FIELD_CPU_ID)];
vals.put(IHostCounters.CPU_PercentUserTime,
Double.parseDouble(us));
vals.put(IHostCounters.CPU_PercentSystemTime,
Double.parseDouble(sy));
// Note: NOT reported by iostat under OSX.
// vals.put(IHostCounters.CPU_PercentIOWait,
// Double.parseDouble(iowait));
vals.put(
IRequiredHostCounters.CPU_PercentProcessorTime,
(100d - Double.parseDouble(id)));
}
// Aggregate across all devices.
double totalKBPerXFer = 0;
double totalxferPerSec = 0;
double totalMBPerSec = 0;
for (int i = 0; i < ndevices; i++) {
final int off = i * nfieldsPerDevice;
final String kbPerXfer = fields[off + deviceFields.get(IOSTAT_FIELD_KB_T)]; // KB/t
final String xferPerSec = fields[off + deviceFields.get(IOSTAT_FIELD_TPS)]; // tps
final String mbPerSec = fields[off + deviceFields.get(IOSTAT_FIELD_MB_S)]; // MB/s
final double _kbPerXFer = Double.parseDouble(kbPerXfer);
final double _xferPerSec = Double
.parseDouble(xferPerSec);
final double _mbPerSec = Double.parseDouble(mbPerSec);
totalKBPerXFer += _kbPerXFer;
totalxferPerSec += _xferPerSec;
totalMBPerSec += _mbPerSec;
if (log.isInfoEnabled())
log.info("\ntotalKBPerXfer=" + totalKBPerXFer
+ ", totalXFerPerSec=" + totalxferPerSec
+ ", totalMBPerSec=" + totalMBPerSec + "\n"
+ header1 + "\n" + data);
vals.put(IHostCounters.PhysicalDisk_TransfersPerSec,
totalxferPerSec);
vals.put(IHostCounters.PhysicalDisk_BytesPerSec,
totalMBPerSec);
}
} catch (Exception ex) {
/*
* Issue warning for parsing problems.
*/
log.warn(ex.getMessage() //
+ "\nheader: " + header1 //
+ "\n data: " + data //
, ex);
}
} // while(true)
} // readProcess()
} // class IOStatReader
private static void assertField(final int index, final String[] fields,
final String expected) {
if (!expected.equals(fields[index]))
throw new RuntimeException("Expecting '" + expected + "', found: '"
+ fields[0] + "'");
}
}