<repo_name>dongyoungy/dbseer_middleware<file_sep>/src/dbseer/middleware/client/MiddlewareClientHandler.java
/*
 * Copyright 2013 <NAME>
 *
 * Licensed 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 dbseer.middleware.client;

import com.esotericsoftware.minlog.Log;
import dbseer.middleware.constant.MiddlewareConstants;
import dbseer.middleware.packet.MiddlewarePacket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.io.PrintWriter;
import java.util.Map;
import java.util.zip.ZipOutputStream;

/**
 * Created by <NAME> on 12/2/15.
 */
public class MiddlewareClientHandler extends ChannelInboundHandlerAdapter
{
	private MiddlewareClient client;
	private Map<String,PrintWriter> sysWriter;
	private PrintWriter dbWriterRaw;
	private ZipOutputStream dbWriter;

	public MiddlewareClientHandler(MiddlewareClient client)
	{
		this.client = client;
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
	{
		Log.debug("channel read");

		MiddlewarePacket packet = (MiddlewarePacket) msg;

		int header = packet.header;
		if (header == MiddlewareConstants.PACKET_START_MONITORING_SUCCESS)
		{
			Log.debug("start monitoring succeeded.");

			// request server list.
			client.requestServerList();
		}
		else if (header == MiddlewareConstants.PACKET_AUTHENTICATION_FAILURE)
		{
			Log.debug("authenticaion failed.");
			// set monitoring to false
			client.setMonitoring(false, packet.body);
		}
		else if (header == MiddlewareConstants.PACKET_START_MONITORING_FAILURE)
		{
			Log.debug("start monitoring failed.");
			// retry monitoring
			client.startMonitoring();
		}
		else if (header == MiddlewareConstants.PACKET_STOP_MONITORING_SUCCESS)
		{
			Log.debug("stop monitoring succeeded.");
			// set monitoring to false
			client.setMonitoring(false);
		}
		else if (header == MiddlewareConstants.PACKET_STOP_MONITORING_FAILURE)
		{
			Log.debug("stop monitoring failed.");
			// set monitoring to false
			client.setMonitoring(false);
		}
		else if (header == MiddlewareConstants.PACKET_SERVER_LIST)
		{
			String serverStr = packet.body;

			// spawn log requester
			dbWriter = client.startTxLogRequester();
			dbWriterRaw = client.getTxPrintWriter();
			sysWriter = client.startSysLogRequester(serverStr);

			// start heartbeat sender
			client.startHeartbeatSender();
			// set monitoring to true
			client.setMonitoring(true, serverStr);
		}
		else if (header == MiddlewareConstants.PACKET_TX_LOG)
		{
			Log.debug("received db log.");
			// write db log.
			dbWriter.write(packet.body.getBytes());
			dbWriter.flush();
			dbWriterRaw.write(packet.body);
			dbWriterRaw.flush();
			client.getTxLogRequester().logReceived();
		}
		else if (header == MiddlewareConstants.PACKET_SYS_LOG)
		{
			Log.debug("received sys log.");

			String[] contents = packet.body.split(MiddlewareConstants.SERVER_STRING_DELIMITER, 2);
			String server = contents[0];
			String log = contents[1];

			// write sys log.
			PrintWriter writer = sysWriter.get(server);
			writer.write(log);
			writer.flush();
			client.getSysLogRequester(server).logReceived();
		}
		else if (header == MiddlewareConstants.PACKET_CONNECTION_DENIED)
		{
			Log.debug("connection denied");
			client.getChannel().close().sync();
			// set monitoring to false
			client.setMonitoring(false, "Connection denied. It is possible that another DBSeer instance is " +
					"connected with the middleware now.");
		}
		else if (header == MiddlewareConstants.PACKET_CHECK_VERSION_SUCCESS)
		{
			Log.debug("check version succeeded.");
			// start monitoring
			client.startMonitoring();
		}
		else if (header == MiddlewareConstants.PACKET_CHECK_VERSION_FAILURE)
		{
			Log.debug("check version failed.");
			client.getChannel().close().sync();
			// set monitoring to false
			client.setMonitoring(false);
		}
		else if (header == MiddlewareConstants.PACKET_TABLE_COUNT)
		{
			Log.debug("received table count");
			String[] contents = packet.body.split(",",3);
			String serverName = contents[0];
			String tableName = contents[1];
			long rowCount = Long.parseLong(contents[2]);

			client.setTableRowCount(serverName, tableName, rowCount);

		}
		else if (header == MiddlewareConstants.PACKET_QUERY_STATISTICS)
		{
			Log.debug("received query statistics");
			String[] contents = packet.body.split(",", 3);
			String serverName = contents[0];
			int txType = Integer.parseInt(contents[1]);
			int reqId = Integer.parseInt(contents[2]);
			String rowsAccessed = contents[3];

			client.printQueryStatistics(serverName, txType, reqId, rowsAccessed);
		}
		else if (header == MiddlewareConstants.PACKET_PING)
		{
			Log.debug("heartbeat received.");
		}
		else
		{
			Log.error("Unknown packet received: " + packet.header);
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
	{
		Log.error(this.getClass().getCanonicalName(), "handler caught exception: ", cause);
		// set monitoring to false
		client.setMonitoring(false, cause.getMessage());
		cause.printStackTrace();
		ctx.close();
	}

}
<file_sep>/rs-sysmon2/examples/devtest.py
#!/usr/bin/python

import sys
sys.path.insert(0, '/usr/share/dstat/')
import dstat, time

devices = ( 
    (  1,   0, 'ram0'),
    (  1,   1, 'ram1'),
    (  3,   1, 'hda1'),
    ( 33,   0, 'hde'),
    (  7,   0, 'loop0'),
    (  7,   1, 'loop1'),
    (  8,   0, '/dev/sda'),
    (  8,   1, '/dev/sda1'),
    (  8,  18, '/dev/sdb2'),
    (  8,  37, '/dev/sdc5'),
    (  9,   0, 'md0'),
    (  9,   1, 'md1'),
    (  9,   2, 'md2'),
    ( 74,  16, '/dev/ida/c2d1'),
    ( 77, 241, '/dev/ida/c5d15p1'),
    ( 98,   0, 'ubd/disc0/disc'),
    ( 98,  16, 'ubd/disc1/disc'),
    (104,   0, 'cciss/c0d0'),
    (104,   2, 'cciss/c0d0p2'),
    (253,   0, 'dm-0'),
    (253,   1, 'dm-1'),
)

for maj, min, device in devices:
    print device, '->', dstat.dev(maj, min)
<file_sep>/rs-sysmon2/plugins/dstat_battery_remain.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Remaining battery time.

    Calculated from power drain and remaining battery power. Information is
    retrieved from ACPI.
    """

    def __init__(self):
        self.name = 'remain'
        self.type = 't'
        self.width = 5
        self.scale = 0

    def vars(self):
        ret = []
        for battery in os.listdir('/proc/acpi/battery/'):
            for line in dopen('/proc/acpi/battery/'+battery+'/state').readlines():
                l = line.split()
                if len(l) < 2: continue
                if l[0] == 'present:' and l[1] == 'yes':
                    ret.append(battery)
        ret.sort()
        return ret

    def nick(self):
        return [name.lower() for name in self.vars]

    def extract(self):
        for battery in self.vars:
            for line in dopen('/proc/acpi/battery/'+battery+'/state').readlines():
                l = line.split()
                if len(l) < 3: continue
                if l[0:2] == ['remaining', 'capacity:']:
                    remaining = int(l[2])
                    continue
                elif l[0:2] == ['present', 'rate:']:
                    rate = int(l[2])
                    continue

            if rate and remaining:
                self.val[battery] = remaining * 60 / rate
            else:
                self.val[battery] = -1

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_sendmail.py
### Author: <NAME> <<EMAIL>>

### FIXME: Should read /var/log/mail/statistics or /etc/mail/statistics (format ?)

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'sendmail'
        self.vars = ('queue',)
        self.type = 'd'
        self.width = 4
        self.scale = 100

    def check(self):
        if not os.access('/var/spool/mqueue', os.R_OK):
            raise Exception, 'Cannot access sendmail queue'

    def extract(self):
        self.val['queue'] = len(glob.glob('/var/spool/mqueue/qf*'))

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_top_mem.py
### Authority: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Most expensive CPU process.

    Displays the process that uses the CPU the most during the monitored
    interval. The value displayed is the percentage of CPU time for the total
    amount of CPU processing power. Based on per process CPU information.
    """
    def __init__(self):
        self.name = 'most expensive'
        self.vars = ('memory process',)
        self.type = 's'
        self.width = 17
        self.scale = 0

    def extract(self):
        self.val['max'] = 0.0
        for pid in proc_pidlist():
            try:
                ### Using dopen() will cause too many open files
                l = proc_splitline('/proc/%s/stat' % pid)
            except IOError:
                continue

            if len(l) < 23: continue
            usage = int(l[23]) * pagesize

            ### Is it a new topper ?
            if usage <= self.val['max']: continue

            self.val['max'] = usage
            self.val['name'] = getnamebypid(pid, l[1][1:-1])
            self.val['pid'] = pid

        self.output = '%-*s%s' % (self.width-5, self.val['name'][0:self.width-5], cprint(self.val['max'], 'f', 5, 1024))

        ### Debug (show PID)
#       self.val['memory process'] = '%*s %-*s' % (5, self.val['pid'], self.width-6, self.val['name'])

    def showcsv(self):
        return '%s / %d%%' % (self.val['name'], self.val['max'])

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_utmp.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'utmp'
        self.nick = ('ses', 'usr', 'adm' )
        self.vars = ('sessions', 'users', 'root')
        self.type = 'd'
        self.width = 3
        self.scale = 10

    def check(self): 
        try:
            global utmp
            import utmp
        except:
            raise Exception, 'Needs python-utmp module'

    def extract(self):
        for name in self.vars: self.val[name] = 0
        for u in utmp.UtmpRecord():
#           print '# type:%s pid:%s line:%s id:%s user:%s host:%s session:%s' % (i.ut_type, i.ut_pid, i.ut_line, i.ut_id, i.ut_user, i.ut_host, i.ut_session)
            if u.ut_type == utmp.USER_PROCESS:
                self.val['users'] = self.val['users'] + 1
                if u.ut_user == 'root':
                    self.val['root'] = self.val['root'] + 1
            self.val['sessions'] = self.val['sessions'] + 1

# vim:ts=4:sw=4:et
<file_sep>/src/dbseer/middleware/packet/MiddlewarePacketDecoder.java
/*
 * Copyright 2013 <NAME>
 *
 * Licensed 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 dbseer.middleware.packet;

import com.esotericsoftware.minlog.Log;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * Created by <NAME> on 12/2/15.
 */
public class MiddlewarePacketDecoder extends ByteToMessageDecoder
{
	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception
	{
		if (buf.readableBytes() < 8)
		{
			Log.debug(this.getClass().getCanonicalName(), "buf less than 8 bytes");
			return;
		}

		buf.markReaderIndex();
		int header = buf.readInt();
		int length = buf.readInt();

		if (buf.readableBytes() < length)
		{
			buf.resetReaderIndex();
			Log.debug(this.getClass().getCanonicalName(), "readable bytes less than length = " + length + " and header = " + header);
			return;
		}
		String log = "";
		Log.debug(String.format("len = %d, readable = %d", length, buf.readableBytes()));

		if (length > 0)
		{
			byte[] readBuf = new byte[length];
			buf.readBytes(readBuf);
			log = new String(readBuf, "UTF-8");
		}

		out.add(new MiddlewarePacket(header, length, log));
	}
}
<file_sep>/src/dbseer/middleware/constant/MiddlewareConstants.java
/*
 * Copyright 2013 <NAME>
 *
 * Licensed 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 dbseer.middleware.constant;

/**
 * Created by <NAME> on 12/1/15.
 */
public class MiddlewareConstants
{
	public static final int QUEUE_SIZE = 1800; // equi. 30 min logs
	public static final String PROTOCOL_VERSION = "v0.2";

	public static final String SERVER_STRING_DELIMITER = ",";
	public static final String TX_LOG_RAW = "tx.log";
	public static final String TX_LOG_ZIP = "tx.zip";
	public static final String SYS_LOG_PREFIX = "sys.log";

	public static final int PACKET_PING = 0;
	public static final int PACKET_CHECK_VERSION = 10;
	public static final int PACKET_CHECK_VERSION_SUCCESS = 11;
	public static final int PACKET_CHECK_VERSION_FAILURE = 12;

	public static final int PACKET_START_MONITORING = 100;
	public static final int PACKET_START_MONITORING_SUCCESS = 101;
	public static final int PACKET_START_MONITORING_FAILURE = 102;
	public static final int PACKET_STOP_MONITORING = 103;
	public static final int PACKET_STOP_MONITORING_SUCCESS = 104;
	public static final int PACKET_STOP_MONITORING_FAILURE = 105;
	public static final int PACKET_AUTHENTICATION_FAILURE = 106;

	public static final int PACKET_REQUEST_TX_LOG = 200;
	public static final int PACKET_TX_LOG = 201;
	public static final int PACKET_REQUEST_SYS_LOG = 300;
	public static final int PACKET_SYS_LOG = 301;
	public static final int PACKET_CONNECTION_DENIED = 400;

	public static final int PACKET_REQUEST_SERVER_LIST = 500;
	public static final int PACKET_SERVER_LIST = 501;

	public static final int PACKET_REQUEST_TABLE_COUNT = 600;
	public static final int PACKET_TABLE_COUNT = 601;

	public static final int PACKET_REQUEST_NUM_ROW_BY_SQL = 700;
	public static final int PACKET_NUM_ROW_BY_SQL = 701;

	public static final int PACKET_REQUEST_QUERY_STATISTICS = 800;
	public static final int PACKET_QUERY_STATISTICS = 801;
}
<file_sep>/rs-sysmon2/plugins/dstat_mysql5_io.py
### Author: <lefred$inuits,be>

global mysql_user
mysql_user = os.getenv('DSTAT_MYSQL_USER') or os.getenv('USER')

global mysql_pwd
mysql_pwd = os.getenv('DSTAT_MYSQL_PWD')

class dstat_plugin(dstat):
    """
    Plugin for MySQL 5 I/O.
    """

    def __init__(self):
        self.name = 'mysql5 io'
        self.nick = ('recv', 'sent')
        self.vars = ('Bytes_received', 'Bytes_sent')

    def check(self): 
        global MySQLdb
        import MySQLdb
        try:
            self.db = MySQLdb.connect(user=mysql_user, passwd=mysql_pwd)
        except:
            raise Exception, 'Cannot interface with MySQL server'

    def extract(self):
        try:
            c = self.db.cursor()
            c.execute("""show global status like 'Bytes_%';""")
            lines = c.fetchall()
            for line in lines:
                if len(line[1]) < 2: continue
                if line[0] in self.vars:
                    self.set2[line[0]] = float(line[1])

            for name in self.vars:
                self.val[name] = self.set2[name] * 1.0 / elapsed

            if step == op.delay:
                self.set1.update(self.set2)

        except Exception, e:
            for name in self.vars:
                self.val[name] = -1

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_top_childwait.py
### Dstat most expensive process plugin
### Displays the name of the most expensive process
###
### Authority: <EMAIL>

global cpunr

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'most waiting for'
        self.vars = ('child process',)
        self.type = 's'
        self.width = 16
        self.scale = 0

    def extract(self):
        self.set2 = {}
        self.val['max'] = 0.0
        for pid in proc_pidlist():
            try:
                ### Using dopen() will cause too many open files
                l = proc_splitline('/proc/%s/stat' % pid)
            except IOError:
                continue

            if len(l) < 15: continue

            ### Reset previous value if it doesn't exist
            if not self.set1.has_key(pid):
                self.set1[pid] = 0

            self.set2[pid] = int(l[15]) + int(l[16])
            usage = (self.set2[pid] - self.set1[pid]) * 1.0 / elapsed / cpunr

            ### Is it a new topper ?
            if usage <= self.val['max']: continue

            self.val['max'] = usage
            self.val['name'] = getnamebypid(pid, l[1][1:-1])
            self.val['pid'] = pid

        ### Debug (show PID)
#       self.val['process'] = '%*s %-*s' % (5, self.val['pid'], self.width-6, self.val['name'])

        if step == op.delay:
            self.set1 = self.set2

    def show(self):
        if self.val['max'] == 0.0:
            return '%-*s' % (self.width, '')
        else:
            return '%s%-*s%s' % (theme['default'], self.width-3, self.val['name'][0:self.width-3], cprint(self.val['max'], 'p', 3, 34))

    def showcsv(self):
        return '%s / %d%%' % (self.val['name'], self.val['max'])

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_mysql5_conn.py
### Author: <lefred$inuits,be>

global mysql_user
mysql_user = os.getenv('DSTAT_MYSQL_USER') or os.getenv('USER')

global mysql_pwd
mysql_pwd = os.getenv('DSTAT_MYSQL_PWD')

class dstat_plugin(dstat):
    """
    Plugin for MySQL 5 connections.
    """

    def __init__(self):
        self.name = 'mysql5 conn'
        self.nick = ('ThCon', '%Con')
        self.vars = ('Threads_connected', 'Threads')
        self.type = 'f'
        self.width = 4
        self.scale = 1

    def check(self): 
        global MySQLdb
        import MySQLdb
        try:
            self.db = MySQLdb.connect(user=mysql_user, passwd=mysql_pwd)
        except Exception, e:
            raise Exception, 'Cannot interface with MySQL server, %s' % e

    def extract(self):
        try:
            c = self.db.cursor()
            c.execute("""show global variables like 'max_connections';""")
            max = c.fetchone()
            c.execute("""show global status like 'Threads_connected';""")
            thread = c.fetchone()
            if thread[0] in self.vars:
                self.set2[thread[0]] = float(thread[1])
                self.set2['Threads'] = float(thread[1]) / float(max[1]) * 100.0

            for name in self.vars:
                self.val[name] = self.set2[name] * 1.0 / elapsed

            if step == op.delay:
                self.set1.update(self.set2)

        except Exception, e:
            for name in self.vars:
                self.val[name] = -1

# vim:ts=4:sw=4:et
<file_sep>/build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="dbseer_middleware" default="all">
  
  
  <!--<property file="build.properties"/>-->
  <!-- Uncomment the following property if no tests compilation is needed -->
  <!-- 
  <property name="skip.tests" value="true"/>
   -->
  
  <!-- Compiler options -->
  
  <property name="compiler.debug" value="on"/>
  <property name="compiler.generate.no.warnings" value="off"/>
  <property name="compiler.args" value=""/>
  <property name="compiler.max.memory" value="700m"/>
  <patternset id="ignored.files">
    <exclude name="**/*.hprof/**"/>
    <exclude name="**/*.pyc/**"/>
    <exclude name="**/*.pyo/**"/>
    <exclude name="**/*.rbc/**"/>
    <exclude name="**/*~/**"/>
    <exclude name="**/.DS_Store/**"/>
    <exclude name="**/.git/**"/>
    <exclude name="**/.hg/**"/>
    <exclude name="**/.svn/**"/>
    <exclude name="**/CVS/**"/>
    <exclude name="**/RCS/**"/>
    <exclude name="**/SCCS/**"/>
    <exclude name="**/__pycache__/**"/>
    <exclude name="**/_svn/**"/>
    <exclude name="**/rcs/**"/>
    <exclude name="**/vssver.scc/**"/>
    <exclude name="**/vssver2.scc/**"/>
  </patternset>
  <patternset id="library.patterns">
    <include name="*.war"/>
    <include name="*.swc"/>
    <include name="*.apk"/>
    <include name="*.zip"/>
    <include name="*.ear"/>
    <include name="*.egg"/>
    <include name="*.ane"/>
    <include name="*.jar"/>
  </patternset>
  <patternset id="compiler.resources">
    <exclude name="**/?*.java"/>
    <exclude name="**/?*.form"/>
    <exclude name="**/?*.class"/>
    <exclude name="**/?*.groovy"/>
    <exclude name="**/?*.scala"/>
    <exclude name="**/?*.flex"/>
    <exclude name="**/?*.kt"/>
    <exclude name="**/?*.clj"/>
    <exclude name="**/?*.aj"/>
  </patternset>
  
  
  <!-- Project Libraries -->
  
  <path id="library.commons-cli-1.3.1.classpath">
    <pathelement location="${basedir}/lib/commons-cli-1.3.1.jar"/>
  </path>
  
  <path id="library.commons-io-2.4.classpath">
    <pathelement location="${basedir}/lib/commons-io-2.4.jar"/>
  </path>
  
  <path id="library.commons-lang3-3.4.classpath">
    <pathelement location="${basedir}/lib/commons-lang3-3.4.jar"/>
  </path>
  
  <path id="library.ini4j-0.5.4.classpath">
    <pathelement location="${basedir}/lib/ini4j-0.5.4.jar"/>
  </path>
  
  <path id="library.minlog-1.2.classpath">
    <pathelement location="${basedir}/lib/minlog-1.2.jar"/>
  </path>
  
  <path id="library.mysql-connector-java-5.1.37-bin.classpath">
    <pathelement location="${basedir}/lib/mysql-connector-java-5.1.37-bin.jar"/>
  </path>
  
  <path id="library.netty-all-4.0.33.final.classpath">
    <pathelement location="${basedir}/lib/netty-all-4.0.33.Final.jar"/>
  </path>

  <path id="library.jzlib-1.1.3.classpath">
    <pathelement location="${basedir}/lib/jzlib-1.1.3.jar"/>
  </path>
  
  <!-- Modules -->
  
  
  <!-- Module dbseer_middleware -->
  
  <dirname property="module.dbseer_middleware.basedir" file="${ant.file}"/>
  
  
  
  <property name="compiler.args.dbseer_middleware" value="-encoding UTF-8 -source 7 -target 7 ${compiler.args}"/>
  
  <property name="dbseer_middleware.output.dir" value="${module.dbseer_middleware.basedir}/out/production/dbseer_middleware"/>
  <property name="dbseer_middleware.testoutput.dir" value="${module.dbseer_middleware.basedir}/out/test/dbseer_middleware"/>
  
  <path id="dbseer_middleware.module.bootclasspath">
    <!-- Paths to be included in compilation bootclasspath -->
  </path>
  
  <path id="dbseer_middleware.module.production.classpath">
    <path refid="library.netty-all-4.0.33.final.classpath"/>
    <path refid="library.commons-cli-1.3.1.classpath"/>
    <path refid="library.commons-io-2.4.classpath"/>
    <path refid="library.ini4j-0.5.4.classpath"/>
    <path refid="library.minlog-1.2.classpath"/>
    <path refid="library.commons-lang3-3.4.classpath"/>
    <path refid="library.mysql-connector-java-5.1.37-bin.classpath"/>
    <path refid="library.jzlib-1.1.3.classpath"/>
  </path>
  
  <path id="dbseer_middleware.runtime.production.module.classpath">
    <pathelement location="${dbseer_middleware.output.dir}"/>
    <path refid="library.netty-all-4.0.33.final.classpath"/>
    <path refid="library.commons-cli-1.3.1.classpath"/>
    <path refid="library.commons-io-2.4.classpath"/>
    <path refid="library.ini4j-0.5.4.classpath"/>
    <path refid="library.minlog-1.2.classpath"/>
    <path refid="library.commons-lang3-3.4.classpath"/>
    <path refid="library.mysql-connector-java-5.1.37-bin.classpath"/>
    <path refid="library.jzlib-1.1.3.classpath"/>
  </path>
  
  <path id="dbseer_middleware.module.classpath">
    <pathelement location="${dbseer_middleware.output.dir}"/>
    <path refid="library.netty-all-4.0.33.final.classpath"/>
    <path refid="library.commons-cli-1.3.1.classpath"/>
    <path refid="library.commons-io-2.4.classpath"/>
    <path refid="library.ini4j-0.5.4.classpath"/>
    <path refid="library.minlog-1.2.classpath"/>
    <path refid="library.commons-lang3-3.4.classpath"/>
    <path refid="library.mysql-connector-java-5.1.37-bin.classpath"/>
    <path refid="library.jzlib-1.1.3.classpath"/>
  </path>
  
  <path id="dbseer_middleware.runtime.module.classpath">
    <pathelement location="${dbseer_middleware.testoutput.dir}"/>
    <pathelement location="${dbseer_middleware.output.dir}"/>
    <path refid="library.netty-all-4.0.33.final.classpath"/>
    <path refid="library.commons-cli-1.3.1.classpath"/>
    <path refid="library.commons-io-2.4.classpath"/>
    <path refid="library.ini4j-0.5.4.classpath"/>
    <path refid="library.minlog-1.2.classpath"/>
    <path refid="library.commons-lang3-3.4.classpath"/>
    <path refid="library.mysql-connector-java-5.1.37-bin.classpath"/>
    <path refid="library.jzlib-1.1.3.classpath"/>
  </path>
  
  
  <patternset id="excluded.from.module.dbseer_middleware">
    <patternset refid="ignored.files"/>
  </patternset>
  
  <patternset id="excluded.from.compilation.dbseer_middleware">
    <patternset refid="excluded.from.module.dbseer_middleware"/>
  </patternset>
  
  <path id="dbseer_middleware.module.sourcepath">
    <dirset dir="${module.dbseer_middleware.basedir}">
      <include name="src"/>
    </dirset>
  </path>
  
  
  <target name="compile.module.dbseer_middleware" depends="compile.module.dbseer_middleware.production,compile.module.dbseer_middleware.tests" description="Compile module dbseer_middleware"/>
  
  <target name="compile.module.dbseer_middleware.production" description="Compile module dbseer_middleware; production classes">
    <mkdir dir="${dbseer_middleware.output.dir}"/>
    <javac destdir="${dbseer_middleware.output.dir}" debug="${compiler.debug}" nowarn="${compiler.generate.no.warnings}" memorymaximumsize="${compiler.max.memory}" fork="true">
      <compilerarg line="${compiler.args.dbseer_middleware}"/>
      <bootclasspath refid="dbseer_middleware.module.bootclasspath"/>
      <classpath refid="dbseer_middleware.module.production.classpath"/>
      <src refid="dbseer_middleware.module.sourcepath"/>
      <patternset refid="excluded.from.compilation.dbseer_middleware"/>
    </javac>
    
    <copy todir="${dbseer_middleware.output.dir}">
      <fileset dir="${module.dbseer_middleware.basedir}/src">
        <patternset refid="compiler.resources"/>
        <type type="file"/>
      </fileset>
    </copy>
  </target>
  
  <target name="compile.module.dbseer_middleware.tests" depends="compile.module.dbseer_middleware.production" description="compile module dbseer_middleware; test classes" unless="skip.tests"/>
  
  <target name="clean.module.dbseer_middleware" description="cleanup module">
    <delete dir="${dbseer_middleware.output.dir}"/>
    <delete dir="${dbseer_middleware.testoutput.dir}"/>
  </target>
  
  <target name="init" description="Build initialization">
    <!-- Perform any build initialization in this target -->
		<property name="src" value="src" />
		<property name="bin" value="bin" />
		<property name="lib" value="lib" />
		<property name="middleware_version" value="0.2" />
  </target>

	<target name="clean" depends="init">
		<delete dir="${bin}" />
	</target>

	<target name="prepare" depends="clean">
		<mkdir dir="${bin}" />
	</target>

	<target name="compile" depends="prepare">
		<javac srcdir="${src}" destdir="${bin}" >
      <compilerarg line="${compiler.args.dbseer_middleware}"/>
      <bootclasspath refid="dbseer_middleware.module.bootclasspath"/>
      <classpath refid="dbseer_middleware.module.production.classpath"/>
		</javac>
	</target>

	<target name="jar" depends="compile">
		<jar destfile="dbseer-middleware-${middleware_version}.jar" basedir="${bin}">
			<zipgroupfileset dir="${lib}" includes="*.jar"/>
		</jar>
	</target>
  
  <target name="clean.module" depends="clean.module.dbseer_middleware" description="cleanup all"/>
  
  <target name="build.modules" depends="init, clean.module, compile.module.dbseer_middleware" description="build all modules"/>
  
  <target name="all" depends="compile ,build.modules" description="build all"/>
</project>
<file_sep>/rs-sysmon2/plugins/dstat_vmk_hba.py
### Author: <NAME> <bert+dstat$debruijn,be>

### VMware ESX kernel vmhba stats
### Displays kernel vmhba statistics on VMware ESX servers

# NOTE TO USERS: command-line plugin configuration is not yet possible, so I've
# "borrowed" the -D argument. 
# EXAMPLES:
# # dstat --vmkhba -D vmhba1,vmhba2,total
# # dstat --vmkhba -D vmhba0
# You can even combine the Linux and VMkernel diskstats (but the "total" argument
# will be used by both).
# # dstat --vmkhba -d -D sda,vmhba1

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'vmkhba'
        self.nick = ('read', 'writ')
        self.cols = 2

    def discover(self, *list):
    # discover will list all vmhba's found.
    # we might want to filter out the unused vmhba's (read stats, compare with ['0', ] * 13)
        ret = []
        try:
            list = os.listdir('/proc/vmware/scsi/')
        except:
            raise Exception, 'Needs VMware ESX'
        for name in list:
            for line in dopen('/proc/vmware/scsi/%s/stats' % name).readlines():
                l = line.split()
                if len(l) < 13: continue
                if l[0] == 'cmds': continue
                if l == ['0', ] * 13: continue
                ret.append(name)
        return ret

    def vars(self):
    # vars will take the argument list - when implemented - , use total, or will use discover + total
        ret = []
        if op.disklist:
            list = op.disklist
        #elif not op.full:
        #   list = ('total', )
        else:
            list = self.discover
            list.sort()
        for name in list:
            if name in self.discover + ['total']:
                ret.append(name)
        return ret

    def check(self): 
        try:
            os.listdir('/proc/vmware')
        except:
            raise Exception, 'Needs VMware ESX'
        info(1, 'The vmkhba module is an EXPERIMENTAL module.')

    def extract(self):
        self.set2['total'] = (0, 0)
        for name in self.vars:
            self.set2[name] = (0, 0)
        for name in os.listdir('/proc/vmware/scsi/'):
            for line in dopen('/proc/vmware/scsi/%s/stats' % name).readlines():
                l = line.split()
                if len(l) < 13: continue
                if l[0] == 'cmds': continue
                if l[2] == '0' and l[4] == '0': continue
                if l == ['0', ] * 13: continue
                self.set2['total'] = ( self.set2['total'][0] + long(l[2]), self.set2['total'][1] + long(l[4]) )
                if name in self.vars and name != 'total':
                    self.set2[name] = ( long(l[2]), long(l[4]) )

            for name in self.set2.keys():
                self.val[name] = (
                    (self.set2[name][0] - self.set1[name][0]) * 1024.0 / elapsed,
                    (self.set2[name][1] - self.set1[name][1]) * 1024.0 / elapsed,
                )

        if step == op.delay:
            self.set1.update(self.set2)
<file_sep>/rs-sysmon2/plugins/dstat_rpc.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'rpc client'
        self.nick = ('call', 'retr', 'refr')
        self.vars = ('calls', 'retransmits', 'autorefreshes')
        self.type = 'd'
        self.width = 5
        self.scale = 1000
        self.open('/proc/net/rpc/nfs')

    def extract(self):
        for l in self.splitlines():
            if not l or l[0] != 'rpc': continue
            for i, name in enumerate(self.vars):
                self.set2[name] = long(l[i+1])

        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed

        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/examples/mstat.py
#!/usr/bin/python

### Example2: simple sub-second monitor (ministat)

### This is a quick example showing how to implement your own *stat utility
### If you're interested in such functionality, contact me at <EMAIL>
import sys
sys.path.insert(0, '/usr/share/dstat/')
import dstat, time

### Set default theme
dstat.theme = dstat.set_theme()

### Allow arguments
try: delay = float(sys.argv[1])
except: delay = 0.2
try: count = int(sys.argv[2])
except: count = 10

### Load stats
stats = []
dstat.starttime = time.time()
dstat.tick = dstat.ticks()
for o in (dstat.dstat_epoch(), dstat.dstat_cpu(), dstat.dstat_mem(), dstat.dstat_load(), dstat.dstat_disk(), dstat.dstat_sys()):
    try: o.check()
    except Exception, e: print e
    else: stats.append(o)

### Make time stats sub-second
stats[0].format = ('t', 14, 0)

### Print headers
title = subtitle = ''
for o in stats:
    title = title + '  ' + o.title()
    subtitle = subtitle + '  ' + o.subtitle()
print '\n' + title + '\n' + subtitle

### Print stats
for dstat.update in range(count):
    line = ''
    for o in stats:
        o.extract()
        line = line + '  ' + o.show()
    print line + dstat.ansi['reset']
    if dstat.update != count-1: time.sleep(delay)
    dstat.tick = 1
print dstat.ansi['reset']
<file_sep>/rs-sysmon2/plugins/dstat_vmk_nic.py
### Author: <NAME> <bert+dstat$debruijn,be>

### VMware ESX kernel vmknic stats
### Displays VMkernel port statistics on VMware ESX servers

# NOTE TO USERS: command-line plugin configuration is not yet possible, so I've
# "borrowed" the -N argument.
# EXAMPLES:
# # dstat --vmknic -N vmk1
# You can even combine the Linux and VMkernel network stats (just don't just "total").
# # dstat --vmknic -n -N vmk0,vswif0
# NB Data comes from /proc/vmware/net/tcpip/ifconfig

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'vmknic'
        self.nick = ('recv', 'send')
        self.open('/proc/vmware/net/tcpip/ifconfig')
        self.cols = 2

    def check(self):
        try:
            os.listdir('/proc/vmware')
        except:
            raise Exception, 'Needs VMware ESX'
        info(1, 'The vmknic module is an EXPERIMENTAL module.')

    def discover(self, *list):
        ret = []
        for l in self.fd[0].splitlines(replace=' /', delim='/'):
            if len(l) != 12: continue
            if l[2][:5] == '<Link': continue
            if ','.join(l) == 'Name,Mtu/TSO,Network,Address,Ipkts,Ierrs,Ibytes,Opkts,Oerrs,Obytes,Coll,Time': continue
            if l[0] == 'lo0': continue
            if l[0] == 'Usage:': continue
            ret.append(l[0])
        ret.sort()
        for item in list: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.netlist:
            list = op.netlist
        else:
            list = self.discover
            list.sort()
        for name in list:
            if name in self.discover + ['total']:
                ret.append(name)
        return ret

    def name(self):
        return ['net/'+name for name in self.vars]

    def extract(self):
        self.set2['total'] = [0, 0]
        for line in self.fd[0].readlines():
            l = line.replace(' /','/').split()
            if len(l) != 12: continue
            if l[2][:5] == '<Link': continue
            if ','.join(l) == 'Name,Mtu/TSO,Network,Address,Ipkts,Ierrs,Ibytes,Opkts,Oerrs,Obytes,Coll,Time': continue
            if l[0] == 'Usage:': continue
            name = l[0]
            if name in self.vars:
                self.set2[name] = ( long(l[6]), long(l[9]) )
            if name != 'lo0':
                self.set2['total'] = ( self.set2['total'][0] + long(l[6]), self.set2['total'][1] + long(l[9]) )
        if update:
            for name in self.set2.keys():
                self.val[name] = (
                    (self.set2[name][0] - self.set1[name][0]) * 1.0 / elapsed,
                    (self.set2[name][1] - self.set1[name][1]) * 1.0 / elapsed,
                )
        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4
<file_sep>/rs-sysmon2/plugins/dstat_vz_io.py
### Author: <NAME> <<EMAIL>>

### Example content for /proc/bc/<veid>/ioacct
#       read                         2773011640320
#       write                        2095707136000
#       dirty                        4500342390784
#       cancel                       4080624041984
#       missed                                   0
#       syncs_total                              2
#       fsyncs_total                       1730732
#       fdatasyncs_total                      3266
#       range_syncs_total                        0
#       syncs_active                             0
#       fsyncs_active                            0
#       fdatasyncs_active                        0
#       range_syncs_active                       0
#       vfs_reads                       3717331387
#       vfs_read_chars         3559144863185798078
#       vfs_writes                       901216138
#       vfs_write_chars          23864660931174682
#       io_pbs                                  16

class dstat_plugin(dstat):
    def __init__(self):
        self.nick = ['read', 'write', 'dirty', 'cancel', 'missed']
        self.cols = len(self.nick)

    def check(self):
        if not os.path.exists('/proc/vz'):
            raise Exception, 'System does not have OpenVZ support'
        elif not os.path.exists('/proc/bc'):
            raise Exception, 'System does not have (new) OpenVZ beancounter support'
        elif not glob.glob('/proc/bc/*/ioacct'):
            raise Exception, 'System does not have any OpenVZ containers'
        info(1, 'Module %s is still experimental.' % self.filename)

    def name(self):
        return ['ve/'+name for name in self.vars]

    def vars(self):
        ret = []
        if not op.full:
            varlist = ['total',]
        else:
            varlist = [os.path.basename(veid) for veid in glob.glob('/proc/vz/*')]
        ret = varlist
        return ret

    def extract(self):
        for veid in self.vars:
            self.set2['total'] = {}
            for line in dopen('/proc/bc/%s/ioacct' % veid).readlines():
                l = line.split()
                if len(l) != 2: continue
                if l[0] not in self.nick: continue
                index = self.nick.index(l[0])
                self.set2[veid][index] = long(l[1])
                self.set2['total'][index] = self.set2['total'][index] + long(l[1])
#            print veid, self.val[veid], self.set2[veid][0], self.set2[veid][1]
#            print veid, self.val[veid], self.set1[veid][0], self.set1[veid][1]

            for i in range(len(self.nick)):
                self.val[veid][i] = (self.set2[veid][i] - self.set1[veid][i]) / elapsed

        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_ntp.py
### Author: <NAME> <<EMAIL>>

global socket
import socket

global struct
import struct

### FIXME: Implement millisecond granularity as well
### FIXME: Interrupts socket if data is overdue (more than 250ms ?)

class dstat_plugin(dstat):
    """
    Time from an NTP server.

    BEWARE: this dstat plugin typically takes a lot longer to run than
    system plugins and for that reason it is important to use an NTP server
    located nearby as well as make sure that it does not impact your other
    counters too much.
    """

    def __init__(self):
        self.name = 'ntp'
        self.nick = ('date/time',)
        self.vars = ('time',)
        self.timefmt = os.getenv('DSTAT_TIMEFMT') or '%d-%m %H:%M:%S'
        self.ntpserver = os.getenv('DSTAT_NTPSERVER') or '0.fedora.pool.ntp.org'
        self.type = 's'
        self.width = len(time.strftime(self.timefmt, time.localtime()))
        self.scale = 0
        self.epoch = 2208988800L
#        socket.setdefaulttimeout(0.25)
        self.socket = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
        self.socket.settimeout(0.25)

    def gettime(self):
        self.socket.sendto( '\x1b' + 47 * '\0', ( self.ntpserver, 123 ))
        data, address = self.socket.recvfrom(1024)
        return struct.unpack( '!12I', data )[10] - self.epoch

    def check(self):
        try:
            self.gettime()
        except socket.gaierror:
            raise Exception, 'Failed to connect to NTP server %s.' % self.ntpserver
        except socket.error:
            raise Exception, 'Error connecting to NTP server %s.' % self.ntpserver

    def extract(self):
        try:
            self.val['time'] = time.strftime(self.timefmt, time.localtime(self.gettime()))
        except:
            self.val['time'] = theme['error'] + '-'.rjust(self.width-1) + ' '

    def showcsv(self):
        return time.strftime(self.timefmt, time.localtime(self.gettime()))

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_top_oom.py
### Author: <NAME> <<EMAIL>>

### Dstat most expensive process plugin
### Displays the name of the most expensive process

### More information:
###    http://lwn.net/Articles/317814/

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'out of memory'
        self.vars = ('kill score',)
        self.type = 's'
        self.width = 18
        self.scale = 0

    def check(self):
        if not os.access('/proc/self/oom_score', os.R_OK):
            raise Exception, 'Kernel does not support /proc/pid/oom_score, use at least 2.6.11.'

    def extract(self):
        self.output = ''
        self.val['max'] = 0.0
        for pid in proc_pidlist():
            try:
                ### Extract name
                name = proc_splitline('/proc/%s/stat' % pid)[1][1:-1]

                ### Using dopen() will cause too many open files
                l = proc_splitline('/proc/%s/oom_score' % pid)
            except IOError:
                continue
            except IndexError:
                continue

            if len(l) < 1: continue
            oom_score = int(l[0])

            ### Is it a new topper ?
            if oom_score <= self.val['max']: continue

            self.val['max'] = oom_score
            self.val['name'] = getnamebypid(pid, name)
            self.val['pid'] = pid

        if self.val['max'] != 0.0:
            self.output = '%-*s%s' % (self.width-4, self.val['name'][0:self.width-4], cprint(self.val['max'], 'f', 4, 1000))

        ### Debug (show PID)
#       self.output = '%*s %-*s' % (5, self.val['pid'], self.width-6, self.val['name'])

    def showcsv(self):
        return '%s / %d%%' % (self.val['name'], self.val['max'])

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_client_latency.py
### Author: <EMAIL>

class dstat_plugin(dstat):
    """
    EVENTS COMMENT.
    """

    def __init__(self):
        self.name = 'latency'
        self.type = 'p'
        self.width = 4
        self.scale = 34

    def vars(self):
        ret = []
        for name in glob.glob('/tmp/dstat/sla/client[0-9]*'):
            ret.append(os.path.basename(name))
        ret.sort()
        return ret

    def nick(self):
        return [name.lower() for name in self.vars]

    def extract(self):
	for csla in self.vars: 
		f = open('/tmp/dstat/sla/'+csla+'/latency', 'r')
                st = f.readline().rstrip()
                if(st == ""):
                   self.val[csla] = 0
                else:
                   self.val[csla] = float(st)
 		f.close()

    def check(self): 
        for csla in glob.glob('/tmp/dstat/sla/client[0-9]*'):
            if not os.access(csla+'/latency', os.R_OK):
                raise Exception, 'Cannot access latency %s information' % os.path.basename(csla)


# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/examples/dstat.py
dstat-0.7.2/../dstat<file_sep>/rs-sysmon2/plugins/dstat_helloworld.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Example "Hello world!" output plugin for aspiring Dstat developers.
    """

    def __init__(self):
        self.name = 'plugin title'
        self.nick = ('counter',)
        self.vars = ('text',)
        self.type = 's'
        self.width = 12
        self.scale = 0

    def extract(self):
        self.val['text'] = 'Hello world!'

# vim:ts=4:sw=4:et
<file_sep>/src/dbseer/middleware/packet/MiddlewarePacketEncoder.java
/*
 * Copyright 2013 <NAME>
 *
 * Licensed 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 dbseer.middleware.packet;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * Created by <NAME> on 5/6/16.
 */
public class MiddlewarePacketEncoder extends MessageToByteEncoder<MiddlewarePacket>
{
	@Override
	protected void encode(ChannelHandlerContext ctx, MiddlewarePacket packet, ByteBuf buf) throws Exception
	{
		buf.writeInt(packet.header);
		buf.writeInt(packet.length);
		if (packet.length > 0)
		{
			buf.writeBytes(packet.body.getBytes("UTF-8"));
		}
	}
}
<file_sep>/rs-sysmon2/plugins/dstat_thermal.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'thermal'
        self.type = 'd'
        self.width = 3
        self.scale = 20
        if os.path.exists('/proc/acpi/ibm/thermal'):
            self.namelist = ['cpu', 'pci', 'hdd', 'cpu', 'ba0', 'unk', 'ba1', 'unk']
            self.nick = []
            for line in dopen('/proc/acpi/ibm/thermal'):
                l = line.split()
                for i, name in enumerate(self.namelist):
                    if int(l[i+1]) > 0:
                        self.nick.append(name)
            self.vars = self.nick
        elif os.path.exists('/proc/acpi/thermal_zone/'):
            self.vars = os.listdir('/proc/acpi/thermal_zone/')
#           self.nick = [name.lower() for name in self.vars]
            self.nick = []
            for name in self.vars:
                self.nick.append(name.lower())
        else:
            raise Exception, 'Needs kernel ACPI or IBM-ACPI support'

    def check(self):
        if not os.path.exists('/proc/acpi/ibm/thermal') and not os.path.exists('/proc/acpi/thermal_zone/'):
            raise Exception, 'Needs kernel ACPI or IBM-ACPI support'

    def extract(self):
        if os.path.exists('/proc/acpi/ibm/thermal'):
            for line in dopen('/proc/acpi/ibm/thermal'):
                l = line.split()
                for i, name in enumerate(self.namelist):
                    if int(l[i+1]) > 0:
                        self.val[name] = int(l[i+1])
        elif os.path.exists('/proc/acpi/thermal_zone/'):
            for zone in self.vars:
                for line in dopen('/proc/acpi/thermal_zone/'+zone+'/temperature').readlines():
                    l = line.split()
                    self.val[zone] = int(l[1])

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_nfsd3.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'nfs3 server'
        self.nick = ('read', 'writ', 'rdir', 'inod', 'fs', 'cmmt')
        self.vars = ('read', 'write', 'readdir', 'inode', 'filesystem', 'commit')
        self.type = 'd'
        self.width = 5
        self.scale = 1000
        self.open('/proc/net/rpc/nfsd')

    def check(self):
        info(1, 'Module %s is still experimental.' % self.filename)

    def extract(self):
        for l in self.splitlines():
            if not l or l[0] != 'proc3': continue
            self.set2['read'] = long(l[8])
            self.set2['write'] = long(l[9])
            self.set2['readdir'] = long(l[18]) + long(l[19])
            self.set2['inode'] = long(l[3]) + long(l[4]) + long(l[5]) + long(l[6]) + long(l[7]) + long(l[10]) + long(l[11]) + long(l[12]) + long(l[13]) + long(l[14]) + long(l[15]) + long(l[16]) + long(l[17])
            self.set2['filesystem'] = long(l[20]) + long(l[21]) + long(l[22])
            self.set2['commit'] = long(l[23])
        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed
        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_dstat_cpu.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Provide CPU information related to the dstat process.

    This plugin shows the CPU utilization for the dstat process itself,
    including the user-space and system-space (kernel) utilization and
    a total of both. On a system with one cpu and one core, the total
    cputime is 1000ms. On a system with 2 cores the total is 2000ms.
    It may help to vizualise the performance of Dstat and its selection
    of plugins.
    """
    def __init__(self):
        self.name = 'dstat cpu'
        self.vars = ('user', 'system', 'total')
        self.type = 'd'
        self.width = 4
        self.scale = 100

    def extract(self):
        res = resource.getrusage(resource.RUSAGE_SELF)

        self.set2['user'] = float(res.ru_utime)
        self.set2['system'] = float(res.ru_stime)
        self.set2['total'] = float(res.ru_utime) + float(res.ru_stime)

        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * 1000.0 / elapsed

        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_battery.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Percentage of remaining battery power as reported by ACPI.
    """
    def __init__(self):
        self.name = 'battery'
        self.type = 'p'
        self.width = 4
        self.scale = 34

    def check(self):
        if not os.path.exists('/proc/acpi/battery/'):
            raise Exception, "No ACPI battery information found."

    def vars(self):
        ret = []
        for battery in os.listdir('/proc/acpi/battery/'):
            for line in dopen('/proc/acpi/battery/'+battery+'/state').readlines():
                l = line.split()
                if len(l) < 2: continue
                if l[0] == 'present:' and l[1] == 'yes':
                    ret.append(battery)
        ret.sort()
        return ret

    def nick(self):
        return [name.lower() for name in self.vars]

    def extract(self):
        for battery in self.vars:
            for line in dopen('/proc/acpi/battery/'+battery+'/info').readlines():
                l = line.split()
                if len(l) < 4: continue
                if l[0] == 'last':
                    full = int(l[3])
                    break
            for line in dopen('/proc/acpi/battery/'+battery+'/state').readlines():
                l = line.split()
                if len(l) < 3: continue
                if l[0] == 'remaining':
                    current = int(l[2])
                    break
            if current:
                self.val[battery] = current * 100.0 / full
            else:
                self.val[battery] = -1

# vim:ts=4:sw=4:et
<file_sep>/middleware_client.sh
#!/bin/bash
BIN_PATH=bin:lib/*
java -cp "$BIN_PATH" dbseer.middleware.test.MiddlewareClientTest $@
<file_sep>/rs-sysmon2/plugins/dstat_wifi.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'wifi'
        self.nick = ('lnk', 's/n')
        self.type = 'd'
        self.width = 3
        self.scale = 34
        self.cols = 2

    def check(self):
        global iwlibs
        from pythonwifi import iwlibs

    def vars(self):
        return iwlibs.getNICnames()

    def extract(self):
        for name in self.vars:
            wifi = iwlibs.Wireless(name)
            stat, qual, discard, missed_beacon = wifi.getStatistics()
#           print qual.quality, qual.signallevel, qual.noiselevel
            if qual.quality == 0 or qual.signallevel == -101 or qual.noiselevel == -101 or qual.signallevel == -256 or qual.noiselevel == -256:
                self.val[name] = ( -1, -1 )
            else:
                self.val[name] = ( qual.quality, qual.signallevel * 100 / qual.noiselevel )

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_vm_memctl.py
### Author: <NAME> <bert+dstat$debruijn,be>

### VMware vmmemctl stats
### Displays ballooning status inside VMware VMs.
### The vmmemctl from the VMware Tools needs to be loaded.
### This plugin has been tested on a VM running CentOS5 with the open-vm-tools, on ESX3.5

# NB Data comes from /proc/vmmemctl

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'memctl'
        self.nick = ('size',)
        self.vars = ('balloon',)
        self.type = 'f'
        self.width = 6
        self.scale = 1024
        self.open('/proc/vmmemctl')

    def check(self): 
        try:
            os.stat('/proc/vmmemctl')
        except:
            raise Exception, 'Needs VMware Tools (modprobe vmmemctl)'

    def extract(self):
        for l in self.splitlines():
            if len(l) < 3: continue
            if l[0] != 'current:': continue
            if l[2] != 'pages': continue
            self.val['balloon'] = int(l[1]) * 4096.0
            break
# vim:ts=4:sw=4
<file_sep>/README.md
# DBSeer Middleware

DBSeer middleware collects various statistics from OS and DBMS and transfers them to DBSeer for its performance prediction and analysis. 
The middleware requires MariaDB MaxScale with the dbseerroute plugin, which is a custom router plugin that is necessary to collect required transaction information. 
The MariaDB MaxScale with the dbseerroute plugin is available [here](https://github.com/dongyoungy/MaxScale).

**NOTE: DBSeer middleware and the dbseerroute plugin require `autocommit` option from MySQL/MariaDB to be OFF as it detects 'rollback' or 'commit' statements to distinguish different transactions.**


## Installation

DBSeer middleware requires the following:

* Java 1.7+
* Apache Ant
* Python 2.7+ and MySQL-python (for *rs-sysmon*)

DBSeer middleware can be compiled with `ant` command:

	$ ant
	
Once the compilation is successful, you can launch the middleware by running `middleware.sh` at the root directory of the package.

	$ ./middleware.sh
	
When the middleware starts successfully, you will see the following messages in the terminal:

```
$ ./middleware.sh
00:00  INFO: Listening port = 3555
00:00  INFO: DB log dir = /tmp/maxscale/perf.log
00:00  INFO: System log dir = /tmp/maxscale
00:00  INFO: [Server : server1]
00:00  INFO: DB Host = localhost
00:00  INFO: DB Port = 3400
00:00  INFO: DB User = root
00:00  INFO: DB PW =
00:00  INFO: SSH User = dyoon
00:00  INFO: Remote Monitor Dir = /home/user/middleware/rs-sysmon2
00:00  INFO: Remote Monitor Script = monitor.sh
00:00  INFO: Middleware is now accepting connections.
```

The diagram below demonstrates a basic setup of DBSeer middleware and MaxScale for collecting OS/DBMS and query statistics from a single MySQL/MariaDB server (i.e., ***System Z*** in the diagram. There can be multiple ***'System Z'***s depending on the MaxScale configuration).

![DBSeer Layout](http://i.imgur.com/KeHG8Ll.png)

DBSeer middleware and MariaDB MaxScale must run on the same machine (i.e., ***System Y***). The middleware needs to be deployed at the MySQL/MariaDB server side in order to collect OS/DBMS statistics (i.e., ***System Z***). 
The middleware includes a variant of system monitoring utility called *rs-sysmon* under the directory *rs-sysmon2*. *rs-sysmon* requires Python 2.7+ and MySQL-python installed, so in this example setup, they need to be installed in ***System Z***. The middleware runs the utility remotely via ssh to collect OS/DBMS statistics.

Using the above diagram as an example, a user must have a system account that can access ***System Z*** via *ssh* without authorization from ***System Y***. This can be done by adding the public key of the account at ***System Y*** as an authorized key for the account at ***System Z***. Using `ssh-copy-id` is the easiest way to set this up.


## Configuration

DBSeer middleware reads its configuration from `middleware.cnf`. 

The following is a sample of the configuration file:

```
[dbseer_middleware]
# middleware user id
id=dbseer
# # middleware user password
password=<PASSWORD>
# port that middleware listens to for the communication with DBSeer
listen_port=3555

# path of the named pipe that dbseerroute uses to communicate with the middleware.
named_pipe=/tmp/dbseerroute

# middleware reads the SQL performance log from this file.
dblog_path=/tmp/maxscale/perf.log

# middleware writes OS/DBMS statistics of each server into separate files in this directory.
syslog_dir=/tmp/maxscale

# the list of MySQL/MariaDB servers
servers=server1

# server1 configuration start
[server1]
# SQL credential necessary for middleware to gather the DBMS statistics (it uses "SHOW GLOBAL STATUS")
db_host=localhost
db_port=3400
db_user=root
db_pw=
# you must be able to run monitor script remotely at server1 using the following configuration.
ssh_user=dyoon
monitor_dir=/home/user/middleware/rs-sysmon2
monitor_script=monitor.sh
# server1 configuration end
```

### id
`id` specifies an id that DBSeer needs to use for authentication.

### password
`password` specifies a password that DBSeer needs to use for authentication.

### listen_port

`listen_port` specifies the port that the middleware listens for the communication with DBSeer.

### named_pipe
`named_pipe` is tha path of the named pipe that **dbseerroute** plugin from our custom version of MariaDB MaxScale uses to communicate with the middleware.

### dblog_path

`dblog_path` should be the filename of the log file generated by the **dbseerroute** plugin from MariaDB MaxScale. DBSeer middleware continuously reads the transaction information (e.g., SQL statement, latency, etc.) from the file and transfers them to DBSeer while it is monitoring.

### syslog_dir

`syslog_dir` specifies the directory where the middleware collects and writes OS/DBMS log data that are not transaction-related. You specify a directory rather than a file here, because there can be more than one MySQL/MariaDB servers running under MariaDB. DBSeer middleware will generate a log file for each server under the directory. Each file will have the format of `sys.log.<server name>`.

### servers

`servers` provides a list of MySQL/MariaDB servers, which MaxScale will connect to. Each server is separated by a comma:

	servers=server1,server2,server3
	
**NOTE: each server on the list must have its own section in the configuration file where it is defined.**

### db_host

`db_host` specifies the IP address or hostname of a MySQL/MariaDB server.

### db_port

`db_port` specifies the port of a MySQL/MariaDB server.

### db_user

`db_user` specifies the username of a MySQL/MariaDB server, which the middleware will use to collect MySQL statistics (*by running 'SHOW GLOBAL STATUS'*).

### db_pw

`db_pw` specifies the password for the user `db_user`.

### ssh_user

`ssh_user` specifies the username of *nix account that is used to run *rs-sysmon2* remotely.

### monitor_dir

`monitor_dir` specifies the directory where *rs-sysmon2* is located.

### monitor_script

`monitor_script` specifies the filename of the script that executes *rs-sysmon2* (*default: monitor.sh*).






<file_sep>/rs-sysmon2/plugins/dstat_top_int.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Top interrupt

    Displays the name of the most frequent interrupt
    """
    def __init__(self):
        self.name = 'most frequent'
        self.vars = ('interrupt',)
        self.type = 's'
        self.width = 20
        self.scale = 0
        self.intset1 = [ 0 ] * 256
        self.open('/proc/stat')
        self.names = self.names()

    def names(self):
        ret = {}
        for line in dopen('/proc/interrupts'):
            l = line.split()
            if len(l) <= cpunr: continue
            l1 = l[0].split(':')[0]
            ### Cleanup possible names from /proc/interrupts
            l2 = ' '.join(l[cpunr+2:])
            l2 = l2.replace('_hcd:', '/')
            l2 = re.sub('@pci[:\d+\.]+', '', l2)
            ret[l1] = l2
        return ret

    def extract(self):
        self.output = ''
        self.val['total'] = 0.0
        for line in self.splitlines():
            if line[0] == 'intr':
                self.intset2 = [ long(int) for int in line[3:] ]

        for i in range(len(self.intset2)):
            total = (self.intset2[i] - self.intset1[i]) * 1.0 / elapsed

            ### Put the highest value in self.val
            if total > self.val['total']:
                if str(i+1) in self.names.keys():
                    self.val['name'] = self.names[str(i+1)]
                else:
                    self.val['name'] = 'int ' + str(i+1)
                self.val['total'] = total

        if step == op.delay:
            self.intset1 = self.intset2

        if self.val['total'] != 0.0:
            self.output = '%-15s%s' % (self.val['name'], cprint(self.val['total'], 'd', 5, 1000))

    def showcsv(self):
        return '%s / %f' % (self.val['name'], self.val['total'])

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_dstat.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Provide more information related to the dstat process.

    The dstat cputime is the total cputime dstat requires per second. On a
    system with one cpu and one core, the total cputime is 1000ms. On a system
    with 2 cores the total is 2000ms. It may help to vizualise the performance
    of Dstat and its selection of plugins.
    """
    def __init__(self):
        self.name = 'dstat'
        self.vars = ('cputime', 'latency')
        self.type = 'd'
        self.width = 4
        self.scale = 100
        self.open('/proc/%s/schedstat' % ownpid)

    def extract(self):
        l = self.splitline()
#        l = linecache.getline('/proc/%s/schedstat' % self.pid, 1).split()
        self.set2['cputime'] = long(l[0])
        self.set2['latency'] = long(l[1])

        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed

        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/examples/read.py
#!/usr/bin/python

### Example 1: Direct accessing stats
### This is a quick example showing how you can access dstat data
### If you're interested in this functionality, contact me at <EMAIL>
import sys
sys.path.insert(0, '/usr/share/dstat/')
import dstat

### Set default theme
dstat.theme = dstat.set_theme()

clear = dstat.ansi['reset']
dstat.tick = dstat.ticks()

c = dstat.dstat_cpu()
print c.title() + '\n' + c.subtitle()
c.extract()
print c.show(), clear
print 'Percentage:', c.val['total']
print 'Raw:', c.cn2['total']
print

m = dstat.dstat_mem()
print m.title() + '\n' + m.subtitle()
m.extract()
print m.show(), clear
print 'Raw:', m.val
print

l = dstat.dstat_load()
print l.title() + '\n' + l.subtitle()
l.extract()
print l.show(), clear
print 'Raw:', l.val
print

d = dstat.dstat_disk()
print d.title() + '\n' + d.subtitle()
d.extract()
print d.show(), clear
print 'Raw:', d.val['total']
print
<file_sep>/rs-sysmon2/plugins/dstat_cpufreq.py
### Author: <EMAIL>

class dstat_plugin(dstat):
    """
    CPU frequency in percentage as reported by ACPI.
    """

    def __init__(self):
        self.name = 'frequency'
        self.type = 'p'
        self.width = 4
        self.scale = 34

    def check(self): 
        for cpu in glob.glob('/sys/devices/system/cpu/cpu[0-9]*'):
            if not os.access(cpu+'/cpufreq/scaling_cur_freq', os.R_OK):
                raise Exception, 'Cannot access acpi %s frequency information' % os.path.basename(cpu)

    def vars(self):
        ret = []
        for name in glob.glob('/sys/devices/system/cpu/cpu[0-9]*'):
            ret.append(os.path.basename(name))
        ret.sort()
        return ret
#       return os.listdir('/sys/devices/system/cpu/')

    def nick(self):
        return [name.lower() for name in self.vars]

    def extract(self):
        for cpu in self.vars:
            for line in dopen('/sys/devices/system/cpu/'+cpu+'/cpufreq/scaling_max_freq').readlines():
                l = line.split()
                max = int(l[0])
            for line in dopen('/sys/devices/system/cpu/'+cpu+'/cpufreq/scaling_cur_freq').readlines():
                l = line.split()
                cur = int(l[0])
            ### Need to close because of bug in sysfs (?)
            dclose('/sys/devices/system/cpu/'+cpu+'/cpufreq/scaling_cur_freq')
            self.set1[cpu] = self.set1[cpu] + cur * 100.0 / max

            if op.update:
                self.val[cpu] = self.set1[cpu] / elapsed
            else:
                self.val[cpu] = self.set1[cpu]

            if step == op.delay:
                self.set1[cpu] = 0

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_lustre.py
# Author: <NAME> <<EMAIL>>, <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    def __init__(self):
        self.nick = ('read', 'write')

    def check(self):
        if not os.path.exists('/proc/fs/lustre/llite'):
            raise Exception, 'Lustre filesystem not found'
        info(1, 'Module %s is still experimental.' % self.filename)

    def name(self):
        return [mount for mount in os.listdir('/proc/fs/lustre/llite')]

    def vars(self):
        return [mount for mount in os.listdir('/proc/fs/lustre/llite')]

    def extract(self):
        for name in self.vars:
            for l in open(os.path.join('/proc/fs/lustre/llite', name, 'stats')).splitlines():
                if len(l) < 6: continue
                if l[0] == 'read_bytes':
                    read = long(l[6])
                elif l[0] == 'write_bytes':
                    write = long(l[6])
            self.set2[name] = (read, write)
            self.val[name] = ( (self.set2[name][0] - self.set1[name][0]) * 1.0 / elapsed,
                               (self.set2[name][1] - self.set1[name][1]) * 1.0 / elapsed )
        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4
<file_sep>/rs-sysmon2/plugins/dstat_top_latency_avg.py
### Dstat most expensive I/O process plugin
### Displays the name of the most expensive I/O process
###
### Authority: <EMAIL>

### For more information, see:
###     http://eaglet.rain.com/rick/linux/schedstat/

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'highest average'
        self.vars = ('latency process',)
        self.type = 's'
        self.width = 17
        self.scale = 0
        self.pidset1 = {}

    def check(self):
        if not os.access('/proc/self/schedstat', os.R_OK):
            raise Exception, 'Kernel has no scheduler statistics, use at least 2.6.12'

    def extract(self):
        self.output = ''
        self.pidset2 = {}
        self.val['result'] = 0
        for pid in proc_pidlist():
            try:
                ### Reset values
                if not self.pidset1.has_key(pid):
                    self.pidset1[pid] = {'wait_ticks': 0, 'ran': 0}

                ### Extract name
                name = proc_splitline('/proc/%s/stat' % pid)[1][1:-1]

                ### Extract counters
                l = proc_splitline('/proc/%s/schedstat' % pid)
            except IOError:
                continue
            except IndexError:
                continue

            if len(l) != 3: continue

            self.pidset2[pid] = {'wait_ticks': long(l[1]), 'ran': long(l[2])}

            if self.pidset2[pid]['ran'] - self.pidset1[pid]['ran'] > 0:
                avgwait = (self.pidset2[pid]['wait_ticks'] - self.pidset1[pid]['wait_ticks']) * 1.0 / (self.pidset2[pid]['ran'] - self.pidset1[pid]['ran']) / elapsed
            else:
                avgwait = 0

            ### Get the process that spends the most jiffies
            if avgwait > self.val['result']:
                self.val['result'] = avgwait
                self.val['pid'] = pid
                self.val['name'] = getnamebypid(pid, name)

        if step == op.delay:
            self.pidset1 = self.pidset2

        if self.val['result'] != 0.0:
            self.output = '%-*s%s' % (self.width-4, self.val['name'][0:self.width-4], cprint(self.val['result'], 'f', 4, 100))

        ### Debug (show PID)
#       self.output = '%*s %-*s' % (5, self.val['pid'], self.width-6, self.val['name'])

    def showcsv(self):
        return '%s / %.4f' % (self.val['name'], self.val['result'])

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_proc_count.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Total Number of processes on this system.
    """
    def __init__(self):
        self.name = 'procs'
        self.vars = ('total',)
        self.type = 'd'
        self.width = 4
        self.scale = 10

    def extract(self):
        self.val['total'] = len([pid for pid in proc_pidlist()])
<file_sep>/rs-sysmon2/examples/mmpipe.py
#!/usr/bin/python
import select, sys, os

def readpipe(file, tmout = 0.001):
    "Read available data from pipe"
    ret = ''
    while not select.select([file.fileno()], [], [], tmout)[0]:
        pass
    while select.select([file.fileno()], [], [], tmout)[0]:
        ret = ret + file.read(1)
        return ret.split('\n')

def dpopen(cmd):
    "Open a pipe for reuse, if already opened, return pipes"
    global pipes
    if 'pipes' not in globals().keys(): pipes = {}
    if cmd not in pipes.keys():
        pipes[cmd] = os.popen3(cmd, 't', 0)
    return pipes[cmd]

### Unbuffered sys.stdout
sys.stdout = os.fdopen(1, 'w', 0)

### Main entrance
if __name__ == '__main__':
    try:
        stdin, stdout, stderr = dpopen('/usr/lpp/mmfs/bin/mmpmon -p -s')
        stdin.write('reset\n')
        readpipe(stdout)

        while True:
            stdin.write('io_s\n')
            for line in readpipe(stdout):
                print line

    except KeyboardInterrupt, e:
        print

# vim:ts=4:sw=4
<file_sep>/rs-sysmon2/examples/tdbtest
#!/usr/bin/python
import sys, tdb

db = tdb.tdb('/var/cache/samba/connections.tdb')
print db.keys()

key=db.firstkey()
while key:
    print db.fetch(key)
    key=db.nextkey(key)

db = tdb.tdb('/var/cache/samba/locking.tdb')
print db.keys

db = tdb.tdb('/var/cache/samba/sessionid.tdb')
print db.keys

<file_sep>/rs-sysmon2/plugins/dstat_snooze.py
class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'snooze'
        self.vars = ('snooze',)
        self.type = 's'
        self.width = 6
        self.scale = 0
        self.before = time.time()

    def extract(self):
        now = time.time()
        if loop != 0:
            self.val['snooze'] = now - self.before
        else:
            self.val['snooze'] = self.before
        if step == op.delay:
            self.before = now

    def show(self):
        if self.val['snooze'] > step + 1:
            return ansi['default'] + '     -'

        color = 'white'
        if step != op.delay:
            color = 'gray'

        snoze, c = fchg(self.val['snooze'], 6, 1000)

        return ansi[color] + snoze
<file_sep>/rs-sysmon2/plugins/dstat_mysql_ndb.py
### Author: <lefred$inuits,be> modified by: <<EMAIL>>

global mysql_user
mysql_user = os.getenv('DSTAT_MYSQL_USER') or os.getenv('USER')

global mysql_pwd
mysql_pwd = os.getenv('DSTAT_MYSQL_PWD')

global mysql_host
mysql_host = os.getenv('DSTAT_MYSQL_HOST')

global mysql_port
mysql_port = os.getenv('DSTAT_MYSQL_PORT')


print "in mysql plugin"
class dstat_plugin(dstat):
    """
    Plugin for MySQL NDB.
    """

    def __init__(self):
        self.name = 'mysql-ndb on ' + mysql_host +' ' + mysql_port
       	self.nick = ("Data memory", "Index memory", "REDO", "ndbnodecount", "DATA_MEMORY", "DISK_OPERATIONS", "DISK_RECORDS", "FILE_BUFFERS", "JOBBUFFER", "RESERVED", "TRANSPORT_BUFFERS")
       	self.vars = ("Data memory", "Index memory", "REDO", "ndbnodecount", "DATA_MEMORY", "DISK_OPERATIONS", "DISK_RECORDS", "FILE_BUFFERS", "JOBBUFFER", "RESERVED", "TRANSPORT_BUFFERS")
        
        
    def check(self): 
        global MySQLdb
        import MySQLdb
        try:
            print mysql_host, mysql_port, mysql_user, mysql_pwd
            self.db = MySQLdb.connect(host=mysql_host,port=int(mysql_port), user=mysql_user, passwd=mysql_pwd)
        except:
            raise Exception, 'Cannot interface with MySQL server'

    def extract(self):
        try:
            c = self.db.cursor()
            c.execute("select memory_type, avg(used) from ndbinfo.memoryusage group by memory_type;")
            lines = c.fetchall()
            for line in lines:
                if len(line) < 2: continue
                if line[0] in self.vars:
                    self.set2[line[0]] = int(line[1])

            c.execute("""select log_type, avg(used) from ndbinfo.logbuffers group by log_type;""")        
            lines = c.fetchall()
            for line in lines:
                if len(line) < 2: continue
                if line[0] in self.vars:
                    self.set2[line[0]] = int(line[1])
            
            c.execute("""select 'ndbnodecount' as var, count(*) from ndbinfo.nodes where status = 'STARTED';""")
            lines = c.fetchall()
            for line in lines:
                if len(line) < 2: continue
                if line[0] in self.vars:
                    self.set2[line[0]] = int(line[1])

            c.execute("""select resource_name, avg(used) from ndbinfo.resources group by resource_name;""")
            lines = c.fetchall()
            for line in lines:
                if len(line) < 2: continue
                if line[0] in self.vars:
                    self.set2[line[0]] = int(line[1])
                                                               
            for name in self.vars:
                self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed

            if step == op.delay:
                self.set1.update(self.set2)
                
        except Exception, e:
            for name in self.vars:
                self.val[name] = -1

# vim:ts=4:sw=4:et

<file_sep>/rs-sysmon2/dstat-docs/dstat-paper.txt
= Dstat: plugin-based real-time monitoring
<NAME> <<EMAIL>>
$Id$

== Introduction
Many tools exist to monitor hardware resources and software behaviour, but few
tools exist that allow you to easily monitor any conceivable counter.

Dstat was designed with the idea that it should be simple to plug in a piece
of code that extracts one or more counters, and make it visible in a way that
visually pleases the eye and helps you extract information in real-time.

By being able to select those counters that you want (and likely those
counters that matter to you in the job you're doing) you make it easier to
correlate raw numbers and see a pattern that may otherwise not be visible.


== A case for Dstat
A few years ago I was involved in a project that was testing a storage cluster
with a SAN back-end using GPFS and Samba for a broadcasting company. The
performance tests that were scheduled together with the customer took a few
weeks to measure the different behaviour under different stresses.

During these tests there was a need to see how each of the components behaved
and to find problematic behaviour during testing. Also, because it involved 5
GPFS nodes, we needed to make sure that the load was spread evenly during the
test. If everything went well repeatedly, the results were validated and the
next batch of tests could be prepared and run.

We started off using different tools at first, but the more counters we were
trying to capture the harder it was to post-process the information we had
collected. What's more, we often saw only after performing the tests that the
data was not representative because the numbers didn't add up. Sometimes it
was caused by the massive setup of clients that were autonomously stressing the
cluster. On other occasions we noticed that the network was the culprit. All in
all, we lost time because we could only validate the results by relating
numbers after the tests were complete and not during the tests.

Complicating the matter was the fact that 5 different nodes were involved
and using the normal command line tools like vmstat, iostat or ifstat (which
only showed us a small part of what was happening) was problematic as each
needed a different terminal. Besides, not all information was interesting.

Eventually Dstat was born, to make a dull task more enjoyable.

After the project was finished I was able to correlate system resources with
network throughput, TCP information, Samba sessions, GPFS throughput,
accumulated block device throughput, HBA throughput, all within a single
interval.


== Dstat characteristics
There are many ideas incorporated into Dstat by design, and this section
serves to list all of them. Not all of them may appeal to the task you're
doing, but the combination may make it an appealing proposition nevertheless.


=== History of counters
An important characteristic in tools like vmstat, iostat or ifstat is the fact
that you can compare historical collected data with new data. This allows you
to have a good feeling of how something is evolving.

Compare this to tools like top or nmon, where data is often being refreshed
and you loose historical information.


=== Adding unit indication
It was very important that when numbers were compared, they were in the same
unit, and not eg. a different power exponent. The human mind sometimes works
in mysterious ways and more so when working with numbers for hours and hours.
Adding the unit is something very convenient and may reduce the human error
factor.

Additionally, indicating the unit also makes sure that the columns have a
fixed width. Often when using vmstat or other tools, the columns tend to shift
depending on the width of the counter. This makes it very inconvenient to find
counters in the shifted output.


=== Colour highlighting units
After I added colours to help improve indicating units, I noticed that the
colours also helped to show patterns. This of course is very limited,
nevertheless it instantly shows when numbers are flat or changes are taking
place.

IMPORTANT: The colours are arbitrarily chosen. Do not make the mistake to
assume that green means good and red means bad. There is no real meaning to
the colour itself, however a change of colour does mean that a value has gone
over some pre-defined limit.


=== Intermediate updates
During tests, when you choose to see average values over a given time, it can
be useful to see how the averages evolve. Dstat, by default, displays
intermediate updates. This means that if you select to see 10 second averages,
after each second you see the accumulated average over the timespan. *This
means that after 4 seconds with intermediate updates, you see an average
taken over the 4 second timeframe.*

NOTE: This means that the closer you get to the given timeframe (eg. 10 seconds)
the more likely that it nears its final average over that period.


=== Adding custom counters
Dstat was specifically designed to enable anyone to add their own counters in a
matter of minutes. The plugin-based system takes care of displaying, colouring
and adding units to the counters. As a plugin-writer, you only have to focus
on extracting the counters from the kernel (procfs or sysfs), logfiles or
daemons.


=== Selecting plugins and counters
Being able to add custom counters is important, but selecting those counters
that you really need is even more important if you want to correlate counters
and see patterns. Less is more.

NOTE: In fact, Dstat currently does not allow you to select just counters, it
only allows you to select plugins. However, since you can modify or fork a
plugin, you still have the ability to select just those counters you prefer.


=== Exporting to CSV
Having information on screen is one thing, you most likely need some hard
evidence later to make your case. (Why else do all the work?)

Dstat allows to write out all counters in the greatest detail possible to CSV.
By default it also adds the command-line used for generating the output, as
well as a date and time stamp. Since Dstat in the first place is meant for
human-readable real-time statistics, it will by default also display the
counters to screen (unless you _/dev/null_ it).

TIP: Dstat appends to the output file so that you can add tests-results of
different tests to a single file. However, make sure that you tag each test
properly (eg. by using distinct filenames for each different test).


=== Time-plugin included
It may seem a small thing, but having exact time (and date) information for
your counters allows for a completely different usage as well. By adding
simple date and time information, Dstat can be used as a background process in
a screen to monitor the behaviour of your system during the night.

This proves to be very valuable for example, to find offending processes during
nightly tasks or to pinpoint their behaviour to certain events that you cannot
monitor during working hours.

It is also important when you have multiple Dstats running (eg. for nodes in a
cluster) to correlate counters between the outputs.


=== Terminal capabilities
Dstat also takes into account the width and height of your terminal window and
modifies output to fit into your terminal. This, of course, has no effect on
what ends up in the CSV output.

Another (debatable) useful feature is that Dstat will modify the terminal
title to indicate on what system it was run and what options were used.
Especially when monitoring nodes in a cluster, this can be useful, but even in
Gnome finding your Dstat window is handy.

WARNING: Some people however are annoyed by the fact that their distribution
does not reset the terminal title and Dstat therefor messes it up. There is no
way for Dstat to fix this.


== Plugins and counters
When we talk about plugins, we make a distinction between those plugins that
are included within the Dstat tool itself, and those that ship with it
externally.  In essence there is no real difference, as the internal plugins
could easily have been created as an external plugin. The basic difference is
that the internal plugins have no dependencies except on procfs.

Having the basic plugins as part of Dstat, makes sure that Dstat can be moved
as a self-contained file to other systems.


=== Internal plugins
The plugins that have been selected to be part of the Dstat tool itself, and
therefor have no dependencies other than procfs, are:

    - aio: asynchronous I/O counters
    - cpu, cpu24: CPU counters (+-c+ and +-C+)
    - disk, disk24, disk24old: disk counters (+-d+ and +-D+)
    - epoch: seconds since Epoch (+-T+)
    - fs: file system counters
    - int, int24: interrupts per IRQ (+-i+ and +-I+)
    - io: I/O requests completed (+-r+)
    - ipc: IPC counters
    - load: load counters (+-l+)
    - lock: locking counters
    - mem: memory usage (+-m+)
    - net: network usage (+-n+ and +-N+)
    - page, page24: paging counters (+-g+)
    - proc: process counters (+-p+)
    - raw: raw socket counters
    - swap, swapold: swap usage (+-s+ and +-S+)
    - socket: socket counters
    - sys: system (kernel) countersA (+-y+)
    - tcp: TCP socket counters
    - time: date and time (+-t+)
    - udp: UDP socket counters
    - unix: unix socket counters
    - vm: virtual memory counters

For backward compatibility with older kernels there is a cascading system that
selects the most appropriate internal plugin for your kernel. (eg. the
+dstat_disk+ plugin falls back to +dstat_disk24+ and +dstat_disk24old+) At this
moment there is no such system for external plugins.


=== External plugins
This basic functionality is easily extended by writing your own plugins
(subclasses of the python Dstat class) which are then inserted at runtime
into Dstat. A set of 'external' modules exist for:

    - battery: battery usage
    - battery-remain: remaining battery time
    - cpufreq: CPU frequency
    - dbus: DBUS connections
    - disk-util: disk utilization percentage
    - fan: Fan speed
    - freespace: free space on filesystems
    - gpfs: GPFS IO counters
    - gpfs-ops: GPFS operations counters
    - helloworld: Hello world dispenser
    - innodb-buffer: innodb buffer counters
    - innodb-io: innodb I/O counters
    - innodb-keys: innodb key operation counters
    - innodb-ops: innodb operations counters
    - lustre: lustre throughput counters
    - memcache-hits: Memcache hit counters
    - mysql5-cmds: MySQL communication counters
    - mysql5-conn: MySQL connection counters
    - mysql5-io: MySQL I/O counters
    - mysql5-keys: MySQL keys counters
    - mysql-io: MySQL I/O counters
    - mysql-ops: MySQL operations counters
    - nfs3-ops: NFS3 client operations counters
    - nfs3: NFS3 client counters
    - nfsd3-ops: NFS3 server operations counters
    - nfsd3: NFS3 server counters
    - ntp: NTP time counters
    - postfix: postfix queue counters
    - power: Power usage counters
    - rpcd: RPC server counters
    - rpc: RPC client counters
    - sendmail: sendmail queue counters
    - snooze: Dstat time delay counters
    - thermal: Thermal counters
    - top-bio: most expensive block I/O process
    - top-cpu: most expensive cpu process
    - top-cputime: process using the most CPU time
    - top-cputime-avg: process having the highest average CPU time
    - top-io: most expensive I/O process
    - top-latency: process with the highest total latency
    - top-latency-avg: process with the highest average latency
    - top-mem: most expensive memory process
    - top-oom: process first shot by OOM killer
    - utmp: utmp counters
    - vmk-hba: VMware kernel HBA counters
    - vmk-int: VMware kernel interrupt counters
    - vmk-nic: VMware kernel NIC counters
    - vm-memctl: VMware guest memory counters
    - vz-cpu: OpenVZ CPU counters
    - vz-ubc: OpenVZ user beancounters
    - wifi: WIFI quality information


=== Most-wanted plugins
Hoping someone interested reads this document, I added a few plugins that
would be ``very nice'' to have but are currently lacking:

    - slab: needs a VM expert to make sense out of the vast amount of data
    - xorg: need information on how to get X resources, would be nice
      to see evolution of X resources over time
    - samba: lacking information to get counters from Samba without
      forking smbstatus every second
    - snmp: could be useful to relate counters from different systems
      in a single Dstat
    - topx: display the most expensive X application(s)
    - systemtap: connecting Dstat to systemtap counters

Creative souls with other ideas are welcome as well !


== Using Dstat
Central to the Dstat command line interface is the selection of plugins. The
selection and order of options influence the Dstat output directly.


=== Enabling plugins
The internal plugins have short and/or long options within Dstat, eg. +-c+ or
+--cpu+ will enable the cpu counters.

The external plugins are enable by a long option including their name,
eg. +--top-cpu+

The following examples will enable the time, cpu and disk plugins, and are
equal.

----
dstat -tcd
dstat --time --cpu --disk
----

=== Total or individual counters
Some of the plugins can show both total values or individual values and
therefor have an extra option to influence this decision.

----
dstat -d -D sda,sdb
dstat -n -N eth0,eth1
dstat -c -C total,0,1
----

You can show both the individual values and total values as follows:

----
[dag@horsea ~]$ dstat -d -D total,hda,hdc
-dsk/total----dsk/hda-----dsk/hdc--
 read  writ: read  writ: read  writ
1384k 1502k: 114k 1332k:  81k  359B
   0    44k:   0    44k:   0     0 
   0     0 :   0     0 :   0     0 
----

The special +-f+ or +--full+ option allows to select individual counters by
default, and can be overruled by +-C+, +-D+, +-I+, +-N+ or +-S+.


=== Influencing output
Dstat has a few more options to influence its output. With the +--nocolor+
one can disable colours. The +--noheaders+ option disables repeating headers.
The +--noupdate+ option disables intermediate updates. The +--output+ option
is used for writing out to a CSV file.


=== Plugin search path
Dstat looks in the following places for plugins. This allows a user without
root privileges to use some extra plugins.

    - ~/.dstat/
    - <binarypath>/plugins/
    - /usr/share/dstat/
    - /usr/local/share/dstat/

The option +--list+ shows the available plugins and their location in the
order that the plugin search path is used.

NOTE: Plugins are named +dstat_<name>.py+.


== Use-cases
Below are some use-cases to demonstrate the usage of Dstat.

WARNING: The following examples do not look as nice as they do on screen
because this document is not printed in colour (and I did not prepare it in
colour :-)).


=== Simple system check
Let's say you quickly want to see if the system is doing alright. In the past
this probably was a +vmstat 1+, as of now you would do:

----
dstat -taf
----

.Sample output
----
[dag@rhun dag]$ dstat -taf
-----time----- -------cpu0-usage------ --dsk/sda-----dsk/sr0-- --net/eth1- ---paging-- ---system--
  date/time   |usr sys idl wai hiq siq| read  writ: read  writ| recv  send|  in   out | int   csw
02-08 02:42:48| 10   2  85   2   0   0|  22k   23k: 1.8B    0 |   0     0 |2588B 2952B| 558   580
02-08 02:42:49|  4   3  93   0   0   0|   0     0 :   0     0 |   0     0 |   0     0 |1116   962
02-08 02:42:50|  5   2  90   0   2   1|   0    28k:   0     0 |   0     0 |   0     0 |1380  1136
02-08 02:42:51| 11   6  82   0   1   0|   0     0 :   0     0 |   0     0 |   0     0 |1277  1340
02-08 02:42:52|  3   3  93   0   1   0|   0    84k:   0     0 |   0     0 |   0     0 |1311  1034
----

NOTE: The +-t+ here is completely optional and generally wastes space. But
often you are not monitoring for 10 seconds but rather measure in minutes or
hours. Having a general idea on what timescale counters have been averaged is
nevertheless interesting.


=== What is this system doing now ?
I often run both the +dstat_top_cpu+ and +dstat_top_mem+ programs on a system,
just to see what a system is doing. Having a quick look at what application
is using the most CPU over a few minutes and to see what the general usage
of memory is of the top application gives away a lot about a system.

.Sample output
----
[dag@horsea dag]$ dstat -c --top-cpu -dng --top-mem
----total-cpu-usage---- -most-expensive- -dsk/total- -net/total- ---paging-- -most-expensive-
usr sys idl wai hiq siq|  cpu process   | read  writ| recv  send|  in   out | memory process 
  9   2  80   9   0   0|kswapd         0| 123k  164k|   0     0 |9196B   18k|rsync        74M
  2   3  95   0   0   0|sendmail       1|   0   168k|2584B   39k|   0     0 |rsync        74M
 18   3  79   0   0   0|httpd         17|   0    88k|5759B  118k|   0     0 |rsync        74M
  3   2  94   1   0   0|sendmail       1|4096B    0 |2291B 4190B|   0     0 |rsync        74M
  2   3  95   0   0   0|httpd          1|   0     0 |2871B 3201B|   0     0 |rsync        74M
 10   7  83   0   0   0|httpd         13|   0     0 |2216B   10k|   0     0 |rsync        74M
  2   2  96   0   0   0|                |   0    52k| 724B 2674B|   0     0 |rsync        74M
----

=== What process is using all my CPU, memory or I/O at 4:20 AM ?
Imagine the monitoring team notices strange peaks, a system engineer got a
worthless message, the system was swapping extensively, a process got killed.

Something indicates the system is doing something unexpected but what is
causing it and why ? As of now you can do:

----
screen dstat -tcy --top-cpu 120
screen dstat -tmgs --top-mem 120
screen dstat -tdi --top-io 120
----

to see what process is using the most CPU, the most memory and the most I/O
resources.

And hopefully one day we can do:

----
dstat -tn --top-net 120
dstat -tn --top-x 120
----

Leave it running during the night and in the morning you can see the light.

=== What device is slowing down my system ?
Many devices generate interrupts, especially when used at maximum capacity.
Sometimes too many interrupts can slow down a system. If you want to correlate
bad performance with hardware interrupts, you can run a command like:

----
dstat -tyif
dstat -tyi -I 12,58,iwlagn -f 5
----

=== How much ticks per second on my kernel ?
In some cases it can be useful to see how many ticks (timer interrupts) your
kernel is producing. With older kernels this is a fixed number (usually 100,
250 or 1000) but on newer kernels the number can be dynamic.

Also on VMware virtual machines, the number of ticks can cause clock issues,
so in that case if you want to see what is happening, you can simply do:

----
dstat -ti -I0 --snooze --debug
----

Dstat nowadays can also detect lost ticks (when the number of ticks do not
match the time progress. This is useful to correlate VM issues with other
problems.


////
=== Monitoring memory consumption of a process over time
Now, I have twice used Dstat to verify memory usage. And I have concluded that
2 programs have severe memory leaks. One, unsurprisingly, is Firefox, the
other sadly is wnck-applet (yes, unfortunately).

Now Dstat is currently not really useful for specifying your own process to
monitor (unless you dig into the module, which is easier than one might
expect). But I am already anticipating Pstat, which is a Dstat but for
process-related counters.

More on this later...
////


=== What device is slowing down my system ?
A nice feature of Dstat is that it can show how many interrupts each of your
devices is generating. The 'cpu' stats already show this in percentage as 
'hard interrupt' and 'soft interrupt', and the 'sys' stats shows the total
number of interrupts, but the 'int' stats go into detail. And you can specify
exactly what IRQs you want to watch.

Much like +watch -n1 -d cat /proc/interrupts+ on steroids.

----
dstat -t -y -i -f
----

which then results in:

.Sample output
----
[dag@rhun ~]$ dstat -t -y -i -f 5
-----time----- ---system-- -------------------interrupts------------------
  date/time   | int   csw |  1     9     12    14    15    58   177   185 
13-08 21:52:53| 740   923 |   1     0    18     5     1    17     4   131 
13-08 21:52:58|1491  2085 |   0     4   351     1     2    37     0    97 
13-08 21:53:03|1464  1981 |   0     0   332     1     3    31     0    96 
13-08 21:53:08|1343  1977 |   0     0   215     1     2    32     0    93 
13-08 21:53:13|1145  1918 |   0     0    12     0     3    33     0    95 
----

When having the following hardware:

----
[dag@rhun ~]$ cat /proc/interrupts 
           CPU0
  0:  143766685    IO-APIC-edge  timer
  1:     374043    IO-APIC-edge  i8042
  9:     102564   IO-APIC-level  acpi
 12:    4481057    IO-APIC-edge  i8042
 14:    1192508    IO-APIC-edge  libata
 15:     358891    IO-APIC-edge  libata
 58:    4391819   IO-APIC-level  ipw2200
177:     993740   IO-APIC-level  Intel ICH6
185:   33542364   IO-APIC-level  yenta, uhci_hcd:usb1, eth0, i915@pci:0000:00:02.0
NMI:          0
LOC:  143766578
ERR:          0
MIS:          0
----

Or select specific interrupts:

----
dstat -t -y -i -I 12,58,185 -f 5
----

=== How does my WIFI signal evolve when I move my laptop or AP through the house ?
Something I was looking into when trying to find the optimal location for the
WIFI access point. However I must say that another tool I wrote 'Dwscan' is 
currently more sophisticated.

----
dstat -t --wifi
----

=== Is my SWRAID performing as it claims ?
You can monitor I/O throughput for any block device. By default dstat limits
itself to real block devices to prevent having the same I/O to be counted more
than once, but if you want to monitor a SWRAID device, or a multipath device,
you can simply do that by doing:

----
dstat -td -D md0,md1,sda,sdb,hda
----


== Writing your own Dstat plugin
Dstat is completely written in python and this makes it extremely convenient
to write your own plugins. The many plugins that come with Dstat are an
excellent source of information if you want to write your own.

=== Introducing the hello world plugin
The following plugin does nothing more than write "Hello world!" to its
output.

.The dstat_helloworld plugin in its full glory.
----
class dstat_helloworld(dstat):
    def __init__(self):
        self.name = 'plugin title'          <1>
        self.type = 's'                     <2>
        self.width = 12                     <3>
        self.scale = 100                    <4>
        self.nick = ('counter',)            <5>
        self.vars = ('text',)               <6>
        self.init(self.vars, 1)             <7>

    def extract(self):
        self.val['text'] = 'Hello world!'   <8>
----

In this example, there are several components:

 . +self.name+ contains the plugin's visible title.
 . +self.type+ defines the counter type: string, percentage, integer, float
 . +self.width+ defines the column width
 . +self.scale+ influences the coloring and unit type
 . +self.nick+ is a list of the counter names
 . +self.vars+ is a list of the variable names for each counter
 . +self.init()+ is a function that initialises the counter structures
   (+self.cn1+, +self.cn2+ and +self.val+)
 . +self.val+ contains the counter values that are being displayed


=== Parsing counters
The following example shows how information is collected and counters are
processed. It also includes a +check()+ method to properly bail out when the
system fails to meet some plugin criteria.

.The dstat_postfix plugin
----
global glob                                                                <1>
import glob

class dstat_postfix(dstat):
    def __init__(self):
        self.name = 'postfix'
        self.type = 'd'                                                    <2>
        self.width = 4
        self.scale = 100
        self.vars = ('incoming', 'active', 'deferred', 'bounce', 'defer')
        self.nick = ('inco', 'actv', 'dfrd', 'bnce', 'defr')
        self.init(self.vars, 1)

    def check(self):                                                       <3>
        if not os.access('/var/spool/postfix/active', os.R_OK):
            raise Exception, 'Cannot access postfix queues'
        return True

    def extract(self):
        for item in self.vars:                                             <4>
            self.val[item] = len(glob.glob('/var/spool/postfix/'+item+'/*/*')
----

This example shows the following items:

 . Since the plugin is imported at runtime, it is important that these are 
   are included in the global scope to reuse them
 . type, width and scale specify decimal, column width a,d coloring based on
   multiplication of 100
 . The +check()+ method tests conditions and bails out of they are not met
 . To make processing easier we have opted to use as value names (+self.vars+)
   the name of the postfix queues and store counts in +self.val+


=== Opening files
Dstat provides its own +dopen()+ function to plugins. Using +dopen()+ instead
of +open()+ plugins do not need to reopen files to update their counters. But
this is only useful when plugins open a few files. For eg. opening _/proc/pid_
files the number of open files would only be increasing as the number of
processes increases.


=== Piping to an application
Dstat provides its own +dpopen()+ function to plugins. This function allows
the plugin to open stdin, stdout and stderr pipes for 2-way communication with
processes.  To see this in action, take a look at the +dstat_gpfs+ plugins or
the +dstat_mysql+ plugins.

Piping to an application is more expensive than getting kernel counters from
_/proc_, but it beats having to run a program and capturing the output.


== Known issues
There are some known issues that are important to understand when using Dstat.

=== Counter rollovers
Unfortunately Dstat is susceptible for counters that ``rollover''. This means
that a counter gets bigger than its maximum value the data-structure is capable
of storing. As a result the counter is reset.

For some architectures and some counters, Linux implements 32bit values, this
means that such counter can go up to 2^32 (= 4294967296B = 4G) values.

For example the network counters are calculated in absolute bytes. Every 4GB
that is being transferred over the network will cause a counter reset. For
example on a bonded 2x10Gbps interfaces that is using its theoretical transfer
limit, this would happen every 1.6 seconds.

Since _/proc_ is updated every second, this would be impossible for Dstat to
catch. Currently if Dstat encounters a negative difference for an interval it
displays a dash.

Obviously, if Dstat were know what the counter's maximum value is, it could
recalculate the difference. However that is currently not implemented and does
not guarantee a correct result either, since a negative value could be the
result of 2 or more rollovers.

If you suspect that the behaviour of your system is susceptible of counter
rollovers, make sure you take this into account when using Dstat (or any other
tool that uses these counters for that matter)

TIP: Shipped with the Dstat documentation there is a document
(_counter-rollovers.txt_) that goes deeper into counter rollovers. If this
affects you, read that document and contact me for possible implementation
changes to improve handling them.


=== Dstat performance
As mentioned several times now, Dstat is written in python. There are various
reasons that Python was chosen and the most important reason is that it
simplifies writing plugins, processing counters and lowers the bar for
people to contribute changes.

The downside of choosing a scripting language is that it is slower than if it
would be written in C, obviously. *Dstat is not optimised for performance.*

NOTE: This may seem ironic: a performance monitoring tool that is not
optimised for performance, but rather for flexibility. However the ease of
writing plugins and prototyping gets precedence over performance at this time.


==== Plugin performance
If we look at the basic plugins, there are no real performance issues with
Dstat. Loading Dstat takes longer than eg. vmstat, but once running, Dstat's
performance for the same functionality is up to par with vmstat, ifstat and
other similar tools.

However there are plugins that are much more resource intensive than others
and the selection of plugins determines Dstat's performance in a major way.


==== Debugging Dstat
Dstat comes with a +--debug+ option that helps to find the cost of running
plugins. The +--debug+ option show how long it takes Dstat to process the
selected plugins.

You can see the cost of Dstat itself by simply using the +dstat_time+ plugin
together with the +--debug+ option.

.The cost of running the timer plugin
----
[dag@rhun dag]$ dstat -t --debug
Module dstat_time
-----time-----
  date/time
19-08 20:34:21  5.90ms
19-08 20:34:22  0.17ms
19-08 20:34:23  0.18ms
19-08 20:34:24  0.18ms
----

Compare this with other plugins to see what the cost is of an individual
plugin.

.The cost of running the +dstat_cpu+ plugin
----
[dag@rhun dstat]$ dstat -c --debug
Module dstat_cpu requires ['/proc/stat']
----total-cpu-usage----
usr sys idl wai hiq siq
 15   3  77   4   0   1 11.07ms
  5   3  92   0   0   0  0.66ms
  5   4  91   0   0   0  0.65ms
  5   3  92   0   0   0  0.66ms
----

As you can see, getting the CPU counters and calculating the CPU usage takes
up 0.5 milliseconds on this particular system. But if we look at the usage of
the +dstat_top_cpu+ plugin:

.The cost of running the +dstat_top_cpu+ plugin
----
[dag@rhun dstat]$ dstat --top-cpu --debug
Module dstat_top_cpu
-most-expensive-
  cpu process
Xorg           2 43.82ms
Xorg           1 33.23ms
firefox-bin    2 33.54ms
Xorg           1 33.24ms
----

we see that processing the _/proc/pid_ files causes the top-cpu plugin to use
an additional 33ms.

WARNING: These values show the time it takes to process the plugins and does
not indicate the amount of CPU usage Dstat consumes. This obviously means that
the process time of plugins depends on how much the system is being stressed
as well as on what the plugin exactly is doing.

Plugins that communicate with other processes or those that process lots of
information (eg. communicating with the mysql client, or processing the mail
queue) may not actually use any local resources, but the latency causes
Dstat to slow down processing other counters.


==== Writing Dstat and plugins in C
It makes sense to reimplement Dstat or some of its plugins in C and still
allow the writing of Python (or even Perl) plugins. Tests have shown that for
example processing _/proc/pid_ in C makes the plugin 3 times faster. And this
did not take into account the processing of the results and displaying the
output.

So rewriting in C makes a lot of sense, but it is also much more complicated.


=== Python 1.5
Dstat works with python 2.0, however there is also a Dstat15 version that
still works on python 1.5. The downside of having Dstat work on python 1.5 is
that the external plugins cannot use the newer and more flexible python 2.0
syntax and the differences between python 1.5 and 2.0 are considerable.

NOTE: Not all plugins work properly on Python 1.5.


== Future development
The Dstat release contains a _TODO_ file highlighting all the items and
ideas that have been played with. Here is a list of the most important ones:

 - Output
   * Changes in how Dstat colours digits within a value (the 6 in 6134B)
   * 
 - Exporting information
   * Connecting Dstat with rrdtool
   * Exporting to syslog or remote syslog (a way to transport counters ?)
 - Plugins
   * Be smart when plugins are loaded more than once (some plugins could
     benefit)
   * Add more plugins
 - Redesign Dstat
   * Work on the plugin infrastructure, make the API more simple and
     straightforward
   * Create an object-model and namespace for plugins and counters so that
     other tools can be based on Dstat

== Links

 - http://dag.wieers.com/home-made/dstat/[Dstat homepage]
 - http://svn.rpmforge.net/svn/trunk/tools/dstat/[Dstat subversion]
 - http://lists.rpmforge.net/mailman/listinfo/tools[Dstat mailinglist]


// vim: set syntax=asciidoc:
<file_sep>/rs-sysmon2/plugins/dstat_fan.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Fan speed in RPM (rotations per minute) as reported by ACPI.
    """

    def __init__(self):
        self.name = 'fan'
        self.type = 'd'
        self.width = 4
        self.scale = 500
        self.open('/proc/acpi/ibm/fan')

    def vars(self):
        ret = None
        for l in self.splitlines():
            if l[0] == 'speed:':
                ret = ('speed',)
        return ret

    def check(self):
        if not os.path.exists('/proc/acpi/ibm/fan'):
            raise Exception, 'Needs kernel IBM-ACPI support'

    def extract(self):
        if os.path.exists('/proc/acpi/ibm/fan'):
            for l in self.splitlines():
                if l[0] == 'speed:':
                    self.val['speed'] = int(l[1])

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_disk_util.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Percentage of bandwidth utilization for block devices.

    Displays percentage of CPU time during which I/O requests were issued
    to the device (bandwidth utilization for the device). Device saturation
    occurs when this value is close to 100%.
    """

    def __init__(self):
        self.nick = ('util', )
        self.type = 'f'
        self.width = 4
        self.scale = 34
        self.diskfilter = re.compile('^(dm-\d+|md\d+|[hsv]d[a-z]+\d+)$')
        self.open('/proc/diskstats')
        self.cols = 1

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines():
            if len(l) < 13: continue
            if l[3:] == ['0',] * 11: continue
            name = l[2]
            ret.append(name)
        for item in objlist: ret.append(item)
        if not ret:
            raise Exception, "No suitable block devices found to monitor"
        return ret

    def vars(self):
        ret = []
        if op.disklist:
            varlist = op.disklist
        else:
            varlist = []
            for name in self.discover:
                if self.diskfilter.match(name): continue
                if name not in blockdevices(): continue
                varlist.append(name)
#           if len(varlist) > 2: varlist = varlist[0:2]
            varlist.sort()
        for name in varlist:
            if name in self.discover:
                ret.append(name)
        return ret

    def name(self):
        return [sysfs_dev(name) for name in self.vars]

    def extract(self):
        for name in self.vars: self.set2[name] = (0, )
        for l in self.splitlines():
            if len(l) < 13: continue
            if l[5] == '0' and l[9] == '0': continue
            name = l[2]
            if l[3:] == ['0',] * 11: continue
            if name in self.vars:
                self.set2[name] = ( self.set2[name][0] + long(l[12]), )
        for name in self.set2.keys():
            self.val[name] = (
                (self.set2[name][0] - self.set1[name][0]) * 1.0 * hz / elapsed / 1000,
            )
        if step == op.delay:
            self.set1.update(self.set2)
<file_sep>/rs-sysmon2/plugins/dstat_squid.py
### Authority: <NAME> <<EMAIL>>

# This plugin has been tested with:
# - Dstat 0.6.7
# - CentOS release 5.4 (Final)
# - Python 2.4.3
# - Squid 2.6 and 2.7
 
global squidclient_options
squidclient_options = os.getenv('DSTAT_SQUID_OPTS') # -p 8080
 
class dstat_plugin(dstat):
    '''
    Provides various Squid statistics.
    '''
    def __init__(self):
        self.name = 'squid status'
        self.type = 's'
        self.width = 5
        self.scale = 1000
        self.vars = ('Number of file desc currently in use',
            'CPU Usage, 5 minute avg',
            'Total accounted',
            'Number of clients accessing cache',
            'Mean Object Size')
        self.nick = ('fdesc',
            'cpu5',
            'mem',
            'clnts',
            'objsz')

    def check(self):
        if not os.access('/usr/sbin/squidclient', os.X_OK):
            raise Exception, 'Needs squidclient binary'
        cmd_test('/usr/sbin/squidclient %s mgr:info' % squidclient_options)
        return True
 
    def extract(self):
        try:
            for l in cmd_splitlines('/usr/sbin/squidclient %s mgr:info' % squidclient_options, ':'):
                if l[0].strip() in self.vars:
                    self.val[l[0].strip()] = l[1].strip()
                    break
        except IOError, e:
            if op.debug > 1: print '%s: lost pipe to squidclient, %s' % (self.filename, e)
            for name in self.vars: self.val[name] = -1
        except Exception, e:
            if op.debug > 1: print '%s: exception' (self.filename, e)
            for name in self.vars: self.val[name] = -1

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_nfs3_ops.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'extended nfs3 client operations'
        self.nick = ('null', 'gatr', 'satr', 'look', 'aces', 'rdln', 'read', 'writ', 'crea', 'mkdr', 'syml', 'mknd', 'rm', 'rmdr', 'ren', 'link', 'rdir', 'rdr+', 'fstt', 'fsnf', 'path', 'cmmt')
        self.vars = ('null', 'getattr', 'setattr', 'lookup', 'access', 'readlink', 'read', 'write', 'create', 'mkdir', 'symlink', 'mknod', 'remove', 'rmdir', 'rename', 'link', 'readdir', 'readdirplus', 'fsstat', 'fsinfo', 'pathconf', 'commit')
        self.type = 'd'
        self.width = 5
        self.scale = 1000
        self.open('/proc/net/rpc/nfs')

    def check(self):
        info(1, 'Module %s is still experimental.') % self.filename

    def extract(self):
        for l in self.splitlines():
            if not l or l[0] != 'proc3': continue
            for i, name in enumerate(self.vars):
                self.set2[name] = long(l[i+2])
        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed
        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_dstat_mem.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Provide memory information related to the dstat process.

    The various values provide information about the memory usage of the
    dstat process. This plugin gives you the possibility to follow memory
    usage changes of dstat over time. It may help to vizualise the
    performance of Dstat and its selection of plugins.
    """
    def __init__(self):
        self.name = 'dstat memory usage'
        self.vars = ('virtual', 'resident', 'shared', 'data')
        self.type = 'd'
        self.open('/proc/%s/statm' % ownpid)

    def extract(self):
        l = self.splitline()
#        l = linecache.getline('/proc/%s/schedstat' % self.pid, 1).split()
        self.val['virtual'] = long(l[0]) * pagesize / 1024
        self.val['resident'] = long(l[1]) * pagesize / 1024
        self.val['shared'] = long(l[2]) * pagesize / 1024
#        self.val['text'] = long(l[3]) * pagesize / 1024
#        self.val['library'] = long(l[4]) * pagesize / 1024
        self.val['data'] = long(l[5]) * pagesize / 1024

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_innodb_io.py
### Author: <NAME> <<EMAIL>>

global mysql_options
mysql_options = os.getenv('DSTAT_MYSQL')

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'innodb io ops '
        self.nick = ('rea', 'wri', 'syn')
        self.vars = ('read', 'write', 'sync')
        self.type = 'f'
        self.width = 3
        self.scale = 1000

    def check(self): 
        if os.access('/usr/bin/mysql', os.X_OK):
            try:
                self.stdin, self.stdout, self.stderr = dpopen('/usr/bin/mysql -n %s' % mysql_options)
            except IOError:
                raise Exception, 'Cannot interface with MySQL binary'
            return True
        raise Exception, 'Needs MySQL binary'

    def extract(self):
        try:
            self.stdin.write('show engine innodb status\G\n')
            line = greppipe(self.stdout, 'OS file reads ')

            if line:
                l = line.split()
                self.set2['read'] = l[0].rstrip(',')
                self.set2['write'] = l[4].rstrip(',')
                self.set2['sync'] = l[8]

            for name in self.vars:
                self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed

            if step == op.delay:
                self.set1.update(self.set2)

        except IOError, e:
            if op.debug > 1: print '%s: lost pipe to mysql, %s' % (self.filename, e)
            for name in self.vars: self.val[name] = -1

        except Exception, e:
            if op.debug > 1: print '%s: exception' % (self.filename, e)
            for name in self.vars: self.val[name] = -1

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_gpfs.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Total amount of read and write throughput (in bytes) on a GPFS filesystem.
    """

    def __init__(self):
        self.name = 'gpfs i/o'
        self.nick = ('read', 'write')
        self.vars = ('_br_', '_bw_')

    def check(self):
        if os.access('/usr/lpp/mmfs/bin/mmpmon', os.X_OK):
            try:
                self.stdin, self.stdout, self.stderr = dpopen('/usr/lpp/mmfs/bin/mmpmon -p -s')
                self.stdin.write('reset\n')
                readpipe(self.stdout)
            except IOError:
                raise Exception, 'Cannot interface with gpfs mmpmon binary'
            return True
        raise Exception, 'Needs GPFS mmpmon binary'

    def extract(self):
        try:
            self.stdin.write('io_s\n')
#           readpipe(self.stderr)
            for line in readpipe(self.stdout):
                if not line: continue
                l = line.split()
                for name in self.vars:
                    self.set2[name] = long(l[l.index(name)+1])
            for name in self.vars:
                self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed
        except IOError, e:
            if op.debug > 1: print '%s: lost pipe to mmpmon, %s' % (self.filename, e)
            for name in self.vars: self.val[name] = -1
        except Exception, e:
            if op.debug > 1: print '%s: exception %s' % (self.filename, e)
            for name in self.vars: self.val[name] = -1

        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_freespace.py
### Author: <NAME> <<EMAIL>>

### FIXME: This module needs infrastructure to provide a list of mountpoints
### FIXME: Would be nice to have a total by default (half implemented)

class dstat_plugin(dstat):
    """
    Amount of used and free space per mountpoint.
    """

    def __init__(self):
        self.nick = ('used', 'free')
        self.open('/etc/mtab')
        self.cols = 2

    def vars(self):
        ret = []
        for l in self.splitlines():
            if len(l) < 6: continue
            if l[2] in ('binfmt_misc', 'devpts', 'iso9660', 'none', 'proc', 'sysfs', 'usbfs'): continue
            ### FIXME: Excluding 'none' here may not be what people want (/dev/shm)
            if l[0] in ('devpts', 'none', 'proc', 'sunrpc', 'usbfs'): continue
            name = l[1] 
            res = os.statvfs(name)
            if res[0] == 0: continue ### Skip zero block filesystems
            ret.append(name)
        return ret

    def name(self):
        return ['/' + os.path.basename(name) for name in self.vars]

    def extract(self):
        self.val['total'] = (0, 0)
        for name in self.vars:
            res = os.statvfs(name)
            self.val[name] = ( (float(res.f_blocks) - float(res.f_bavail)) * long(res.f_frsize), float(res.f_bavail) * float(res.f_frsize) )
            self.val['total'] = (self.val['total'][0] + self.val[name][0], self.val['total'][1] + self.val[name][1])

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/dstat
#!/usr/bin/env python

### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU Library General Public License as published by
### the Free Software Foundation; version 2 only
###
### 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 Library General Public License for more details.
###
### You should have received a copy of the GNU Library 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.
### Copyright 2004-2007 <NAME> <<EMAIL>>

from __future__ import generators

try:
    import sys, os, time, sched, re, getopt
    import types, resource, getpass, glob, linecache
except KeyboardInterrupt:
    pass

VERSION = '0.7.2'

theme = { 'default': '' }

if sys.version_info < (2, 2):
    sys.exit('error: Python 2.2 or later required')

### Workaround for python <= 2.2.1
try:
    True, False
except NameError:
    True = 1
    False = 0

### Workaround for python < 2.3
#if 'enumerate' not in __builtins__.__dict__.keys():
if sys.version_info >= (2, 2) and sys.version_info < (2, 3):
    def enumerate(sequence):
        index = 0
        for item in sequence:
            yield index, item
            index = index + 1
elif sys.version_info < (2, 2):
    def enumerate(sequence):
        index = 0
        seqlist = []
        for item in sequence:
            seqlist.append((index, item))
            index = index + 1
        return seqlist

### Workaround for python < 2.3
#if 'sum' not in __builtins__.__dict__.keys():
if sys.version_info < (2, 3):
    def sum(sequence):
        ret = 0
        for i in sequence:
            ret = ret + i
        return ret

pluginpath = [
    os.path.expanduser('~/.dstat/'),                                # home + /.dstat/
    os.path.abspath(os.path.dirname(sys.argv[0])) + '/plugins/',    # binary path + /plugins/
    '/usr/share/dstat/',
    '/usr/local/share/dstat/',
]

class Options:
    def __init__(self, args):
        self.args = args
        self.blackonwhite = False
        self.count = -1
        self.cpulist = None
        self.debug = 0
        self.delay = 1
        self.disklist = None
        self.full = False
        self.float = False
        self.integer = False
        self.intlist = None
        self.netlist = None
        self.swaplist = None
        self.color = True
        self.update = True
        self.header = True
        self.output = False
        self.pidfile = False
        self.profile = ''

        ### List of available plugins
        allplugins = listplugins()

        ### List of plugins to show
        self.plugins = []

        ### Implicit if no terminal is used
        if not sys.stdout.isatty():
            self.color = False
            self.header = False
            self.update = False

        ### Temporary hardcoded for my own project
        self.diskset = {
            'local': ('sda', 'hd[a-d]'),
            'lores': ('sd[b-k]', 'sd[v-z]', 'sda[a-e]'),
            'hires': ('sd[l-u]', 'sda[f-o]'),
        }

        try:
            opts, args = getopt.getopt(args, 'acdfghilmno:prstTvyC:D:I:M:N:S:V',
                ['all', 'all-plugins', 'bw', 'blackonwhite', 'debug',
                 'filesystem', 'float', 'full', 'help', 'integer',
                 'list', 'mods', 'modules', 'nocolor', 'noheaders', 'noupdate',
                 'output=', 'pidfile=', 'profile', 'version', 'vmstat'] + allplugins)
        except getopt.error, exc:
            print 'dstat: %s, try dstat -h for a list of all the options' % str(exc)
            sys.exit(1)

        for opt, arg in opts:
            if opt in ['-c']:
                self.plugins.append('cpu')
            elif opt in ['-C']:
                self.cpulist = arg.split(',')
            elif opt in ['-d']:
                self.plugins.append('disk')
            elif opt in ['-D']:
                self.disklist = arg.split(',')
            elif opt in ['--filesystem']:
                self.plugins.append('fs')
            elif opt in ['-g']:
                self.plugins.append('page')
            elif opt in ['-i']:
                self.plugins.append('int')
            elif opt in ['-I']:
                self.intlist = arg.split(',')
            elif opt in ['-l']:
                self.plugins.append('load')
            elif opt in ['-m']:
                self.plugins.append('mem')
            elif opt in ['-M', '--mods', '--modules']:
                print >>sys.stderr, 'WARNING: Option %s is deprecated, please use --%s instead' % (opt, ' --'.join(arg.split(',')))
                self.plugins += arg.split(',')
            elif opt in ['-n']:
                self.plugins.append('net')
            elif opt in ['-N']:
                self.netlist = arg.split(',')
            elif opt in ['-p']:
                self.plugins.append('proc')
            elif opt in ['-r']:
                self.plugins.append('io')
            elif opt in ['-s']:
                self.plugins.append('swap')
            elif opt in ['-S']:
                self.swaplist = arg.split(',')
            elif opt in ['-t']:
                self.plugins.append('time')
            elif opt in ['-T']:
                self.plugins.append('epoch')
            elif opt in ['-y']:
                self.plugins.append('sys')

            elif opt in ['-a', '--all']:
                self.plugins += [ 'cpu', 'disk', 'net', 'page', 'sys' ]
            elif opt in ['-v', '--vmstat']:
                self.plugins += [ 'proc', 'mem', 'page', 'disk', 'sys', 'cpu' ]
            elif opt in ['-f', '--full']:
                self.full = True

            elif opt in ['--all-plugins']:
                ### Make list unique in a fancy fast way
                plugins = {}.fromkeys(allplugins).keys()
                plugins.sort()
                self.plugins += plugins
            elif opt in ['--bw', '--black-on-white']:
                self.blackonwhite = True
            elif opt in ['--debug']:
                self.debug = self.debug + 1
            elif opt in ['--float']:
                self.float = True
            elif opt in ['--integer']:
                self.integer = True
            elif opt in ['--list']:
                showplugins()
                sys.exit(0)
            elif opt in ['--nocolor']:
                self.color = False
                self.update = False
            elif opt in ['--noheaders']:
                self.header = False
            elif opt in ['--noupdate']:
                self.update = False
            elif opt in ['-o', '--output']:
                self.output = arg
            elif opt in ['--pidfile']:
                self.pidfile = arg
            elif opt in ['--profile']:
                self.profile = 'dstat_profile.log'
            elif opt in ['-h', '--help']:
                self.usage()
                self.help()
                sys.exit(0)
            elif opt in ['-V', '--version']:
                self.version()
                sys.exit(0)
            elif opt.startswith('--'):
                self.plugins.append(opt[2:])
            else:
                print 'dstat: option %s unknown to getopt, try dstat -h for a list of all the options' % opt
                sys.exit(1)

        if self.float and self.integer:
            print 'dstat: option --float and --integer are mutual exlusive, you can only force one'
            sys.exit(1)

        if not self.plugins:
            print 'You did not select any stats, using -cdngy by default.'
            self.plugins = [ 'cpu', 'disk', 'net', 'page', 'sys' ]

        try:
            if len(args) > 0: self.delay = int(args[0])
            if len(args) > 1: self.count = int(args[1])
        except:
            print 'dstat: incorrect argument, try dstat -h for the correct syntax'
            sys.exit(1)

        if self.delay <= 0:
            print 'dstat: delay must be an integer, greater than zero'
            sys.exit(1)

    def version(self):
        print 'Dstat %s' % VERSION
        print 'Written by <NAME> <<EMAIL>>'
        print 'Homepage at http://dag.wieers.com/home-made/dstat/'
        print
        print 'Platform %s/%s' % (os.name, sys.platform)
        print 'Kernel %s' % os.uname()[2]
        print 'Python %s' % sys.version
        print

        color = ""
        if not gettermcolor(self.color):
            color = "no "
        print 'Terminal type: %s (%scolor support)' % (os.getenv('TERM'), color)
        rows, cols = gettermsize()
        print 'Terminal size: %d lines, %d columns' % (rows, cols)
        print
        print 'Processors: %d' % getcpunr()
        print 'Pagesize: %d' % resource.getpagesize()
        print 'Clock ticks per secs: %d' % os.sysconf('SC_CLK_TCK')
        print

        global op
        op = self
        showplugins()

    def usage(self):
        print 'Usage: dstat [-afv] [options..] [delay [count]]'

    def help(self):
        print '''Versatile tool for generating system resource statistics

Dstat options:
  -c, --cpu              enable cpu stats
     -C 0,3,total           include cpu0, cpu3 and total
  -d, --disk             enable disk stats
     -D total,hda           include hda and total
  -g, --page             enable page stats
  -i, --int              enable interrupt stats
     -I 5,eth2              include int5 and interrupt used by eth2
  -l, --load             enable load stats
  -m, --mem              enable memory stats
  -n, --net              enable network stats
     -N eth1,total          include eth1 and total
  -p, --proc             enable process stats
  -r, --io               enable io stats (I/O requests completed)
  -s, --swap             enable swap stats
     -S swap1,total         include swap1 and total
  -t, --time             enable time/date output
  -T, --epoch            enable time counter (seconds since epoch)
  -y, --sys              enable system stats

  --aio                  enable aio stats
  --fs, --filesystem     enable fs stats
  --ipc                  enable ipc stats
  --lock                 enable lock stats
  --raw                  enable raw stats
  --socket               enable socket stats
  --tcp                  enable tcp stats
  --udp                  enable udp stats
  --unix                 enable unix stats
  --vm                   enable vm stats

  --plugin-name          enable plugins by plugin name (see manual)
  --list                 list all available plugins

  -a, --all              equals -cdngy (default)
  -f, --full             automatically expand -C, -D, -I, -N and -S lists
  -v, --vmstat           equals -pmgdsc -D total

  --float                force float values on screen
  --integer              force integer values on screen

  --bw, --blackonwhite   change colors for white background terminal
  --nocolor              disable colors (implies --noupdate)
  --noheaders            disable repetitive headers
  --noupdate             disable intermediate updates
  --output file          write CSV output to file

delay is the delay in seconds between each update (default: 1)
count is the number of updates to display before exiting (default: unlimited)
'''

### START STATS DEFINITIONS ###
class dstat:
    vars = None
    name = None
    nick = None
    type = 'f'
    width = 5
    scale = 1024
    cols = 0
#    val = {}
#    set1 = {}
#    set2 = {}

    def prepare(self):
        if callable(self.discover):
            self.discover = self.discover()
        if callable(self.vars):
            self.vars = self.vars()
        if not self.vars:
            raise Exception, 'No counter objects to monitor'
        if callable(self.name):
            self.name = self.name()
        if callable(self.nick):
            self.nick = self.nick()
        if not self.nick:
            self.nick = self.vars

        self.val = {}; self.set1 = {}; self.set2 = {}
        if self.cols <= 0:
            for name in self.vars:
                self.val[name] = self.set1[name] = self.set2[name] = 0
        else:
            for name in self.vars + [ 'total', ]:
                self.val[name] = range(self.cols)
                self.set1[name] = range(self.cols)
                self.set2[name] = range(self.cols)
                for i in range(self.cols):
                    self.val[name][i] = self.set1[name][i] = self.set2[name][i] = 0
#        print self.val

    def open(self, *filenames):
        "Open stat file descriptor"
        self.file = []
        self.fd = []
        for filename in filenames:
            try:
                fd = dopen(filename)
                if fd:
                    self.file.append(filename)
                    self.fd.append(fd)
            except:
                pass
        if not self.fd:
            raise Exception, 'Cannot open file %s' % filename

    def readlines(self):
        "Return lines from any file descriptor"
        for fd in self.fd:
            fd.seek(0)
            for line in fd.readlines():
               yield line
        ### Implemented linecache (for top-plugins) but slows down normal plugins
#        for fd in self.fd:
#            i = 1
#            while True:
#                line = linecache.getline(fd.name, i);
#                if not line: break
#                yield line
#                i += 1

    def splitline(self, sep=None):
        for fd in self.fd:
            fd.seek(0)
            return fd.read().split(sep)

    def splitlines(self, sep=None, replace=None):
        "Return split lines from any file descriptor"
        for fd in self.fd:
            fd.seek(0)
            for line in fd.readlines():
                if replace and sep:
                    yield line.replace(replace, sep).split(sep)
                elif replace:
                    yield line.replace(replace, ' ').split()
                else:
                    yield line.split(sep)
#        ### Implemented linecache (for top-plugins) but slows down normal plugins
#        for fd in self.fd:
#                if replace and sep:
#                    yield line.replace(replace, sep).split(sep)
#                elif replace:
#                    yield line.replace(replace, ' ').split()
#                else:
#                    yield line.split(sep)
#                i += 1

    def statwidth(self):
        "Return complete stat width"
        if self.cols:
            return len(self.vars) * self.colwidth() + len(self.vars) - 1
        else:
            return len(self.nick) * self.colwidth() + len(self.nick) - 1

    def colwidth(self):
        "Return column width"
        if isinstance(self.name, types.StringType):
            return self.width
        else:
            return len(self.nick) * self.width + len(self.nick) - 1

    def title(self):
        ret = theme['title']
        if isinstance(self.name, types.StringType):
            width = self.statwidth()
            return ret + self.name[0:width].center(width).replace(' ', '-') + theme['default']
        for i, name in enumerate(self.name):
            width = self.colwidth()
            ret = ret + name[0:width].center(width).replace(' ', '-')
            if i + 1 != len(self.name):
                if op.color:
                    ret = ret + theme['frame'] + char['dash'] + theme['title']
                else:
                    ret = ret + char['space']
        return ret

    def subtitle(self):
        ret = ''
        if isinstance(self.name, types.StringType):
            for i, nick in enumerate(self.nick):
                ret = ret + theme['subtitle'] + nick[0:self.width].center(self.width) + theme['default']
                if i + 1 != len(self.nick): ret = ret + char['space']
            return ret
        else:
            for i, name in enumerate(self.name):
                for j, nick in enumerate(self.nick):
                    ret = ret + theme['subtitle'] + nick[0:self.width].center(self.width) + theme['default']
                    if j + 1 != len(self.nick): ret = ret + char['space']
                if i + 1 != len(self.name): ret = ret + theme['frame'] + char['colon']
            return ret

    def csvtitle(self):
        if isinstance(self.name, types.StringType):
            return '"' + self.name + '"' + ',' * (len(self.nick) - 1)
        else:
            ret = ''
            for i, name in enumerate(self.name):
                ret = ret + '"' + name + '"' + ',' * (len(self.nick) - 1)
                if i + 1 != len(self.name): ret = ret + ','
            return ret

    def csvsubtitle(self):
        ret = ''
        if isinstance(self.name, types.StringType):
            for i, nick in enumerate(self.nick):
                ret = ret + '"' + nick + '"'
                if i + 1 != len(self.nick): ret = ret + ','
            return ret
        else:
            for i, name in enumerate(self.name):
                for j, nick in enumerate(self.nick):
                    ret = ret + '"' + nick + '"'
                    if j + 1 != len(self.nick): ret = ret + ','
                if i + 1 != len(self.name): ret = ret + ','
            return ret

    def check(self):
        "Check if stat is applicable"
#        if hasattr(self, 'fd') and not self.fd:
#            raise Exception, 'File %s does not exist' % self.fd
        if not self.vars:
            raise Exception, 'No objects found, no stats available'
        if not self.discover:
            raise Exception, 'No objects discovered, no stats available'
        if self.colwidth():
            return True
        raise Exception, 'Unknown problem, please report'

    def discover(self, *objlist):
        return True

    def show(self):
        "Display stat results"
        line = ''
        if hasattr(self, 'output'):
            return cprint(self.output, self.type, self.width, self.scale)
        for i, name in enumerate(self.vars):
            if isinstance(self.val[name], types.TupleType) or isinstance(self.val[name], types.ListType):
                line = line + cprintlist(self.val[name], self.type, self.width, self.scale)
                sep = theme['frame'] + char['colon']
            else:
                line = line + cprint(self.val[name], self.type, self.width, self.scale)
                sep = char['space']
            if i + 1 != len(self.vars):
                line = line + sep
        return line

    def showend(self, totlist, vislist):
        if self is not vislist[-1]:
            return theme['frame'] + char['pipe']
        elif totlist != vislist:
            return theme['frame'] + char['gt']
        return ''

    def showcsv(self):
        def printcsv(var):
            if var != round(var):
                return '%.3f' % var
            return '%s' % round(var)

        line = ''
        for i, name in enumerate(self.vars):
            if isinstance(self.val[name], types.ListType) or isinstance(self.val[name], types.TupleType):
                for j, val in enumerate(self.val[name]):
                    line = line + printcsv(val)
                    if j + 1 != len(self.val[name]):
                        line = line + ','
            elif isinstance(self.val[name], types.StringType):
                line = line + self.val[name]
            else:
                line = line + printcsv(self.val[name])
            if i + 1 != len(self.vars):
                line = line + ','
        return line

    def showcsvend(self, totlist, vislist):
        if self is not vislist[-1]:
            return ','
        elif self is not totlist[-1]:
            return ','
        return ''

class dstat_aio(dstat):
    def __init__(self):
        self.name = 'async'
        self.nick = ('#aio',)
        self.vars = ('aio',)
        self.type = 'd'
        self.width = 5;
        self.open('/proc/sys/fs/aio-nr')

    def extract(self):
        for l in self.splitlines():
            if len(l) < 1: continue
            self.val['aio'] = long(l[0])

class dstat_cpu(dstat):
    def __init__(self):
        self.nick = ( 'usr', 'sys', 'idl', 'wai', 'hiq', 'siq' )
        self.type = 'p'
        self.width = 3
        self.scale = 34
        self.open('/proc/stat')
        self.cols = 6

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines():
            if len(l) < 8 or l[0][0:3] != 'cpu': continue
            ret.append(l[0][3:])
        ret.sort()
        for item in objlist: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.cpulist:
            varlist = op.cpulist
        elif not op.full:
            varlist = ('total',)
        else:
            varlist = []
            cpu = 0
            while cpu < cpunr:
                varlist.append(str(cpu))
                cpu = cpu + 1
#           if len(varlist) > 2: varlist = varlist[0:2]
        for name in varlist:
            if name in self.discover + ['total']:
                ret.append(name)
        return ret

    def name(self):
        ret = []
        for name in self.vars:
            if name == 'total':
                ret.append('total cpu usage')
            else:
                ret.append('cpu' + name + ' usage')
        return ret

    def extract(self):
        for l in self.splitlines():
            if len(l) < 8: continue
            for name in self.vars:
                if l[0] == 'cpu' + name or ( l[0] == 'cpu' and name == 'total' ):
                    self.set2[name] = ( long(l[1]) + long(l[2]), long(l[3]), long(l[4]), long(l[5]), long(l[6]), long(l[7]) )
        for name in self.vars:
            for i in range(6):
                if sum(self.set2[name]) > sum(self.set1[name]):
                    self.val[name][i] = 100.0 * (self.set2[name][i] - self.set1[name][i]) / (sum(self.set2[name]) - sum(self.set1[name]))
                else:
                    self.val[name][i] = 0
#                    print >>sys.stderr, "Error: tick problem detected, this should never happen !"
        if step == op.delay:
            self.set1.update(self.set2)

class dstat_cpu24(dstat):
    def __init__(self):
        self.nick = ( 'usr', 'sys', 'idl')
        self.type = 'p'
        self.width = 3
        self.scale = 34
        self.open('/proc/stat')
        self.cols = 3

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines():
            if len(l) != 5 or l[0][0:3] != 'cpu': continue
            ret.append(l[0][3:])
        ret.sort()
        for item in objlist: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.cpulist:
            varlist = op.cpulist
        elif not op.full:
            varlist = ('total',)
        else:
            varlist = []
            cpu = 0
            while cpu < cpunr:
                varlist.append(str(cpu))
                cpu = cpu + 1
#           if len(varlist) > 2: varlist = varlist[0:2]
        for name in varlist:
            if name in self.discover + ['total']:
                ret.append(name)
        return ret

    def name(self):
        ret = []
        for name in self.vars:
            if name == 'total':
                ret.append('cpu usage')
            else:
                ret.append('cpu' + name)
        return ret

    def extract(self):
        for l in self.splitlines():
            for name in self.vars:
                if l[0] == 'cpu' + name or ( l[0] == 'cpu' and name == 'total' ):
                    self.set2[name] = ( long(l[1]) + long(l[2]), long(l[3]), long(l[4]) )
        for name in self.vars:
            for i in range(3):
                self.val[name][i] = 100.0 * (self.set2[name][i] - self.set1[name][i]) / (sum(self.set2[name]) - sum(self.set1[name]))
        if step == op.delay:
            self.set1.update(self.set2)

class dstat_disk(dstat):
    def __init__(self):
        self.nick = ('read', 'writ')
        self.type = 'd'
        self.diskfilter = re.compile('^(dm-\d+|md\d+|[hsv]d[a-z]+\d+)$')
        self.open('/proc/diskstats')
        self.cols = 2

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines():
            if len(l) < 13: continue
            if l[3:] == ['0',] * 11: continue
            name = l[2]
            ret.append(name)
        for item in objlist: ret.append(item)
        if not ret:
            raise Exception, "No suitable block devices found to monitor"
        return ret

    def vars(self):
        ret = []
        if op.disklist:
            varlist = op.disklist
        elif not op.full:
            varlist = ('total',)
        else:
            varlist = []
            for name in self.discover:
                if self.diskfilter.match(name): continue
                if name not in blockdevices(): continue
                varlist.append(name)
#           if len(varlist) > 2: varlist = varlist[0:2]
            varlist.sort()
        for name in varlist:
            if name in self.discover + ['total'] + op.diskset.keys():
                ret.append(name)
        return ret

    def name(self):
        return ['dsk/'+sysfs_dev(name) for name in self.vars]

    def extract(self):
        for name in self.vars: self.set2[name] = (0, 0)
        for l in self.splitlines():
            if len(l) < 13: continue
            if l[5] == '0' and l[9] == '0': continue
            name = l[2]
            if l[3:] == ['0',] * 11: continue
            if not self.diskfilter.match(name):
                self.set2['total'] = ( self.set2['total'][0] + long(l[5]), self.set2['total'][1] + long(l[9]) )
            if name in self.vars and name != 'total':
                self.set2[name] = ( self.set2[name][0] + long(l[5]), self.set2[name][1] + long(l[9]) )
            for diskset in self.vars:
                if diskset in op.diskset.keys():
                    for disk in op.diskset[diskset]:
                        if re.match('^'+disk+'$', name):
                            self.set2[diskset] = ( self.set2[diskset][0] + long(l[5]), self.set2[diskset][1] + long(l[9]) )
        for name in self.set2.keys():
            self.val[name] = (
                (self.set2[name][0] - self.set1[name][0]) * 512.0 / elapsed,
                (self.set2[name][1] - self.set1[name][1]) * 512.0 / elapsed,
            )
        if step == op.delay:
            self.set1.update(self.set2)

class dstat_disk24(dstat):
    def __init__(self):
        self.nick = ('read', 'writ')
        self.type = 'd'
        self.diskfilter = re.compile('(dm-\d+|md\d+|[hsv]d[a-z]+\d+)')
        self.open('/proc/partitions')
        if self.fd and not self.discover:
            raise Exception, 'Kernel is not compiled with CONFIG_BLK_STATS'
        self.cols = 2

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines():
            if len(l) < 15 or l[0] == 'major' or int(l[1]) % 16 != 0: continue
            name = l[3]
            ret.append(name)
        for item in objlist: ret.append(item)
        if not ret:
            raise Exception, "No suitable block devices found to monitor"
        return ret

    def vars(self):
        ret = []
        if op.disklist:
            varlist = op.disklist
        elif not op.full:
            varlist = ('total',)
        else:
            varlist = []
            for name in self.discover:
                if self.diskfilter.match(name): continue
                varlist.append(name)
#           if len(varlist) > 2: varlist = varlist[0:2]
            varlist.sort()
        for name in varlist:
            if name in self.discover + ['total'] + op.diskset.keys():
                ret.append(name)
        return ret

    def name(self):
        return ['dsk/'+sysfs_dev(name) for name in self.vars]

    def extract(self):
        for name in self.vars: self.set2[name] = (0, 0)
        for l in self.splitlines():
            if len(l) < 15 or l[0] == 'major' or int(l[1]) % 16 != 0: continue
            name = l[3]
            if not self.diskfilter.match(name):
                self.set2['total'] = ( self.set2['total'][0] + long(l[6]), self.set2['total'][1] + long(l[10]) )
            if name in self.vars:
                self.set2[name] = ( self.set2[name][0] + long(l[6]), self.set2[name][1] + long(l[10]) )
            for diskset in self.vars:
                if diskset in op.diskset.keys():
                    for disk in op.diskset[diskset]:
                        if re.match('^'+disk+'$', name):
                            self.set2[diskset] = ( self.set2[diskset][0] + long(l[6]), self.set2[diskset][1] + long(l[10]) )
        for name in self.set2.keys():
            self.val[name] = (
                (self.set2[name][0] - self.set1[name][0]) * 512.0 / elapsed,
                (self.set2[name][1] - self.set1[name][1]) * 512.0 / elapsed,
            )
        if step == op.delay:
            self.set1.update(self.set2)

### FIXME: Needs rework, does anyone care ?
class dstat_disk24old(dstat):
    def __init__(self):
        self.nick = ('read', 'writ')
        self.type = 'd'
        self.diskfilter = re.compile('(dm-\d+|md\d+|[hsv]d[a-z]+\d+)')
        self.regexp = re.compile('^\((\d+),(\d+)\):\(\d+,\d+,(\d+),\d+,(\d+)\)$')
        self.open('/proc/stat')
        self.cols = 2

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines(':'):
            if len(l) < 3: continue
            name = l[0]
            if name != 'disk_io': continue
            for pair in line.split()[1:]:
                m = self.regexp.match(pair)
                if not m: continue
                l = m.groups()
                if len(l) < 4: continue
                name = dev(int(l[0]), int(l[1]))
                ret.append(name)
            break
        for item in objlist: ret.append(item)
        if not ret:
            raise Exception, "No suitable block devices found to monitor"
        return ret

    def vars(self):
        ret = []
        if op.disklist:
            varlist = op.disklist
        elif not op.full:
            varlist = ('total',)
        else:
            varlist = []
            for name in self.discover:
                if self.diskfilter.match(name): continue
                varlist.append(name)
#           if len(varlist) > 2: varlist = varlist[0:2]
            varlist.sort()
        for name in varlist:
            if name in self.discover + ['total'] + op.diskset.keys():
                ret.append(name)
        return ret

    def name(self):
        return ['dsk/'+name for name in self.vars]

    def extract(self):
        for name in self.vars: self.set2[name] = (0, 0)
        for line in self.splitlines(':'):
            if len(l) < 3: continue
            name = l[0]
            if name != 'disk_io': continue
            for pair in line.split()[1:]:
                m = self.regexp.match(pair)
                if not m: continue
                l = m.groups()
                if len(l) < 4: continue
                name = dev(int(l[0]), int(l[1]))
                if not self.diskfilter.match(name):
                    self.set2['total'] = ( self.set2['total'][0] + long(l[2]), self.set2['total'][1] + long(l[3]) )
                if name in self.vars and name != 'total':
                    self.set2[name] = ( self.set2[name][0] + long(l[2]), self.set2[name][1] + long(l[3]) )
                for diskset in self.vars:
                    if diskset in op.diskset.keys():
                        for disk in op.diskset[diskset]:
                            if re.match('^'+disk+'$', name):
                                self.set2[diskset] = ( self.set2[diskset][0] + long(l[2]), self.set2[diskset][1] + long(l[3]) )
            break
        for name in self.set2.keys():
            self.val[name] = (
                (self.set2[name][0] - self.set1[name][0]) * 512.0 / elapsed,
                (self.set2[name][1] - self.set1[name][1]) * 512.0 / elapsed,
            )
        if step == op.delay:
            self.set1.update(self.set2)

class dstat_epoch(dstat):
    def __init__(self):
        self.name = 'epoch'
        self.vars = ('epoch',)
        self.width = 10
        if op.debug:
            self.width = 13
        self.scale = 0

    ### We are now using the starttime instead of the execution time of this plugin
    def extract(self):
#        self.val['epoch'] = time.time()
        self.val['epoch'] = starttime

class dstat_fs(dstat):
    def __init__(self):
        self.name = 'filesystem'
        self.vars = ('files', 'inodes')
        self.type = 'd'
        self.width = 6
        self.scale = 1000

    def extract(self):
        for line in dopen('/proc/sys/fs/file-nr'):
            l = line.split()
            if len(l) < 1: continue
            self.val['files'] = long(l[0])
        for line in dopen('/proc/sys/fs/inode-nr'):
            l = line.split()
            if len(l) < 2: continue
            self.val['inodes'] = long(l[0]) - long(l[1])

class dstat_int(dstat):
    def __init__(self):
        self.name = 'interrupts'
        self.type = 'd'
        self.width = 5
        self.scale = 1000
        self.open('/proc/stat')
        self.intmap = self.intmap()

    def intmap(self):
        ret = {}
        for line in dopen('/proc/interrupts'):
            l = line.split()
            if len(l) <= cpunr: continue
            l1 = l[0].split(':')[0]
            l2 = ' '.join(l[cpunr+2:]).split(',')
            ret[l1] = l1
            for name in l2:
                ret[name.strip().lower()] = l1
        return ret

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines():
            if l[0] != 'intr': continue
            for name, i in enumerate(l[2:]):
                if long(i) > 10: ret.append(str(name))
        return ret

#   def check(self):
#       if self.fd[0] and self.vars:
#           self.fd[0].seek(0)
#           for l in self.fd[0].splitlines():
#               if l[0] != 'intr': continue
#               return True
#       return False

    def vars(self):
        ret = []
        if op.intlist:
            varlist = op.intlist
        else:
            varlist = self.discover
            for name in varlist:
                if name in ('0', '1', '2', '8', 'NMI', 'LOC', 'MIS', 'CPU0'):
                    varlist.remove(name)
            if not op.full and len(varlist) > 3: varlist = varlist[-3:]
        for name in varlist:
            if name in self.discover + ['total',]:
                ret.append(name)
            elif name.lower() in self.intmap.keys():
                ret.append(self.intmap[name.lower()])
        return ret

    def extract(self):
        for l in self.splitlines():
            if not l or l[0] != 'intr': continue
            for name in self.vars:
                if name != 'total':
                    self.set2[name] = long(l[int(name) + 2])
            self.set2['total'] = long(l[1])
        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed
        if step == op.delay:
            self.set1.update(self.set2)

class dstat_int24(dstat):
    def __init__(self):
        self.name = 'interrupts'
        self.type = 'd'
        self.width = 5
        self.scale = 1000
        self.open('/proc/interrupts')

    def intmap(self):
        ret = {}
        for l in self.splitlines():
            if len(l) <= cpunr: continue
            l1 = l[0].split(':')[0]
            l2 = ' '.join(l[cpunr+2:]).split(',')
            ret[l1] = l1
            for name in l2:
                ret[name.strip().lower()] = l1
        return ret

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines():
            if len(l) < cpunr+1: continue
            name = l[0].split(':')[0]
            if long(l[1]) > 10:
                ret.append(name)
        return ret

#   def check(self):
#       if self.fd and self.discover:
#           self.fd[0].seek(0)
#           for l in self.fd[0].splitlines():
#               if l[0] != 'intr' or len(l) > 2: continue
#               return True
#       return False

    def vars(self):
        ret = []
        if op.intlist:
            varlist = op.intlist
        else:
            varlist = self.discover
            for name in varlist:
                if name in ('0', '1', '2', '8', 'CPU0', 'ERR', 'LOC', 'MIS', 'NMI'):
                    varlist.remove(name)
            if not op.full and len(varlist) > 3: varlist = varlist[-3:]
        for name in varlist:
            if name in self.discover:
                ret.append(name)
            elif name.lower() in self.intmap.keys():
                ret.append(self.intmap[name.lower()])
        return ret

    def extract(self):
        for l in self.splitlines():
            if len(l) < cpunr+1: continue
            name = l[0].split(':')[0]
            if name in self.vars:
                self.set2[name] = 0
                for i in l[1:1+cpunr]:
                    self.set2[name] = self.set2[name] + long(i)
#           elif len(l) > 2 + cpunr:
#               for hw in self.vars:
#                   for mod in l[2+cpunr:]:
#                       self.set2[mod] = long(l[1])
        for name in self.set2.keys():
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed
        if step == op.delay:
            self.set1.update(self.set2)

class dstat_io(dstat):
    def __init__(self):
        self.nick = ('read', 'writ')
        self.type = 'f'
        self.width = 5
        self.scale = 1000
        self.diskfilter = re.compile('(dm-\d+|md\d+|[hsv]d[a-z]+\d+)')
        self.open('/proc/diskstats')
        self.cols = 3

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines():
            if len(l) < 13: continue
            if l[3:] == ['0',] * 11: continue
            name = l[2]
            ret.append(name)
        for item in objlist: ret.append(item)
        if not ret:
            raise Exception, "No suitable block devices found to monitor"
        return ret

    def vars(self):
        ret = []
        if op.disklist:
            varlist = op.disklist
        elif not op.full:
            varlist = ('total',)
        else:
            varlist = []
            for name in self.discover:
                if self.diskfilter.match(name): continue
                if name not in blockdevices(): continue
                varlist.append(name)
#           if len(varlist) > 2: varlist = varlist[0:2]
            varlist.sort()
        for name in varlist:
            if name in self.discover + ['total'] + op.diskset.keys():
                ret.append(name)
        return ret

    def name(self):
        return ['io/'+name for name in self.vars]

    def extract(self):
        for name in self.vars: self.set2[name] = (0, 0)
        for l in self.splitlines():
            if len(l) < 13: continue
            if l[3] == '0' and l[7] == '0': continue
            name = l[2]
            if l[3:] == ['0',] * 11: continue
            if not self.diskfilter.match(name):
                self.set2['total'] = ( self.set2['total'][0] + long(l[3]), self.set2['total'][1] + long(l[7]) )
            if name in self.vars and name != 'total':
                self.set2[name] = ( self.set2[name][0] + long(l[3]), self.set2[name][1] + long(l[7]) )
            for diskset in self.vars:
                if diskset in op.diskset.keys():
                    for disk in op.diskset[diskset]:
                        if re.match('^'+disk+'$', name):
                            self.set2[diskset] = ( self.set2[diskset][0] + long(l[3]), self.set2[diskset][1] + long(l[7]) )
        for name in self.set2.keys():
            self.val[name] = (
                (self.set2[name][0] - self.set1[name][0]) * 1.0 / elapsed,
                (self.set2[name][1] - self.set1[name][1]) * 1.0 / elapsed,
            )
        if step == op.delay:
            self.set1.update(self.set2)

class dstat_ipc(dstat):
    def __init__(self):
        self.name = 'sysv ipc'
        self.vars = ('msg', 'sem', 'shm')
        self.type = 'd'
        self.width = 3
        self.scale = 10

    def extract(self):
        for name in self.vars:
            self.val[name] = len(dopen('/proc/sysvipc/'+name).readlines()) - 1

class dstat_load(dstat):
    def __init__(self):
        self.name = 'load avg'
        self.nick = ('1m', '5m', '15m')
        self.vars = ('load1', 'load5', 'load15')
        self.type = 'f'
        self.width = 4
        self.scale = 0.5
        self.open('/proc/loadavg')

    def extract(self):
        for l in self.splitlines():
            if len(l) < 3: continue
            self.val['load1'] = float(l[0])
            self.val['load5'] = float(l[1])
            self.val['load15'] = float(l[2])

class dstat_lock(dstat):
    def __init__(self):
        self.name = 'file locks'
        self.nick = ('pos', 'lck', 'rea', 'wri')
        self.vars = ('posix', 'flock', 'read', 'write')
        self.type = 'f'
        self.width = 3
        self.scale = 10
        self.open('/proc/locks')

    def extract(self):
        for name in self.vars: self.val[name] = 0
        for l in self.splitlines():
            if len(l) < 4: continue
            if l[1] == 'POSIX': self.val['posix'] += 1
            elif l[1] == 'FLOCK': self.val['flock'] += 1
            if l[3] == 'READ': self.val['read'] += 1
            elif l[3] == 'WRITE': self.val['write'] += 1

class dstat_mem(dstat):
    def __init__(self):
        self.name = 'memory usage'
        self.nick = ('used', 'buff', 'cach', 'free')
        self.vars = ('MemUsed', 'Buffers', 'Cached', 'MemFree')
        self.open('/proc/meminfo')

    def extract(self):
        for l in self.splitlines():
            if len(l) < 2: continue
            name = l[0].split(':')[0]
            if name in self.vars + ('MemTotal', ):
                self.val[name] = long(l[1]) * 1024.0
        self.val['MemUsed'] = self.val['MemTotal'] - self.val['MemFree'] - self.val['Buffers'] - self.val['Cached']

class dstat_net(dstat):
    def __init__(self):
        self.nick = ('recv', 'send')
        self.type = 'd'
        self.totalfilter = re.compile('^(lo|bond\d+|face|.+\.\d+)$')
        self.open('/proc/net/dev')
        self.cols = 2

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines(replace=':'):
            if len(l) < 17: continue
            if l[2] == '0' and l[10] == '0': continue
            name = l[0]
            if name not in ('lo', 'face'):
                ret.append(name)
        ret.sort()
        for item in objlist: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.netlist:
            varlist = op.netlist
        elif not op.full:
            varlist = ('total',)
        else:
            varlist = self.discover
#           if len(varlist) > 2: varlist = varlist[0:2]
            varlist.sort()
        for name in varlist:
            if name in self.discover + ['total', 'lo']:
                ret.append(name)
        if not ret:
            raise Exception, "No suitable network interfaces found to monitor"
        return ret

    def name(self):
        return ['net/'+name for name in self.vars]

    def extract(self):
        self.set2['total'] = [0, 0]
        for l in self.splitlines(replace=':'):
            if len(l) < 17: continue
            if l[2] == '0' and l[10] == '0': continue
            name = l[0]
            if name in self.vars :
                self.set2[name] = ( long(l[1]), long(l[9]) )
            if not self.totalfilter.match(name):
                self.set2['total'] = ( self.set2['total'][0] + long(l[1]), self.set2['total'][1] + long(l[9]))
        if update:
            for name in self.set2.keys():
                self.val[name] = [
                    (self.set2[name][0] - self.set1[name][0]) * 1.0 / elapsed,
                    (self.set2[name][1] - self.set1[name][1]) * 1.0 / elapsed,
                ]
                if self.val[name][0] < 0: self.val[name][0] += maxint + 1
                if self.val[name][1] < 0: self.val[name][1] += maxint + 1
        if step == op.delay:
            self.set1.update(self.set2)

class dstat_page(dstat):
    def __init__(self):
        self.name = 'paging'
        self.nick = ('in', 'out')
        self.vars = ('pswpin', 'pswpout')
        self.type = 'd'
        self.open('/proc/vmstat')

    def extract(self):
        for l in self.splitlines():
            if len(l) < 2: continue
            name = l[0]
            if name in self.vars:
                self.set2[name] = long(l[1])
        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * pagesize * 1.0 / elapsed
        if step == op.delay:
            self.set1.update(self.set2)

class dstat_page24(dstat):
    def __init__(self):
        self.name = 'paging'
        self.nick = ('in', 'out')
        self.vars = ('pswpin', 'pswpout')
        self.type = 'd'
        self.open('/proc/stat')

    def extract(self):
        for l in self.splitlines():
            if len(l) < 3: continue
            name = l[0]
            if name != 'swap': continue
            self.set2['pswpin'] = long(l[1])
            self.set2['pswpout'] = long(l[2])
            break
        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * pagesize * 1.0 / elapsed
        if step == op.delay:
            self.set1.update(self.set2)

class dstat_proc(dstat):
    def __init__(self):
        self.name = 'procs'
        self.nick = ('run', 'blk', 'new')
        self.vars = ('procs_running', 'procs_blocked', 'processes')
        self.type = 'f'
        self.width = 3
        self.scale = 10
        self.open('/proc/stat')

    def extract(self):
        for l in self.splitlines():
            if len(l) < 2: continue
            name = l[0]
            if name == 'processes':
                self.val['processes'] = 0
                self.set2[name] = long(l[1])
            elif name == 'procs_running':
                self.set2[name] = self.set2[name] + long(l[1]) - 1
            elif name == 'procs_blocked':
                self.set2[name] = self.set2[name] + long(l[1])
        self.val['processes'] = (self.set2['processes'] - self.set1['processes']) * 1.0 / elapsed
        for name in ('procs_running', 'procs_blocked'):
            self.val[name] = self.set2[name] * 1.0 / elapsed
        if step == op.delay:
            self.set1.update(self.set2)
            for name in ('procs_running', 'procs_blocked'):
                self.set2[name] = 0

class dstat_raw(dstat):
    def __init__(self):
        self.name = 'raw'
        self.nick = ('raw',)
        self.vars = ('sockets',)
        self.type = 'd'
        self.width = 3
        self.scale = 100
        self.open('/proc/net/raw')

    def extract(self):
        lines = -1
        for line in self.readlines():
            lines += 1
        self.val['sockets'] = lines
        ### Cannot use len() on generator
#        self.val['sockets'] = len(self.readlines()) - 1

class dstat_socket(dstat):
    def __init__(self):
        self.name = 'sockets'
        self.type = 'd'
        self.width = 3
        self.scale = 100
        self.open('/proc/net/sockstat')
        self.nick = ('tot', 'tcp', 'udp', 'raw', 'frg')
        self.vars = ('sockets:', 'TCP:', 'UDP:', 'RAW:', 'FRAG:')

    def extract(self):
        for l in self.splitlines():
            if len(l) < 3: continue
            self.val[l[0]] = long(l[2])
        self.val['other'] = self.val['sockets:'] - self.val['TCP:'] - self.val['UDP:'] - self.val['RAW:'] - self.val['FRAG:']

class dstat_swap(dstat):
    def __init__(self):
        self.name = 'swap'
        self.nick = ('used', 'free')
        self.type = 'd'
        self.open('/proc/swaps')

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines():
            if len(l) < 5: continue
            if l[0] == 'Filename': continue
            try: 
                int(l[2])
                int(l[3])
            except:
                continue
#           ret.append(improve(l[0]))
            ret.append(l[0])
        ret.sort()
        for item in objlist: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.swaplist:
            varlist = op.swaplist
        elif not op.full:
            varlist = ('total',)
        else:
            varlist = self.discover
#           if len(varlist) > 2: varlist = varlist[0:2]
            varlist.sort()
        for name in varlist:
            if name in self.discover + ['total']:
                ret.append(name)
        if not ret:
            raise Exception, "No suitable swap devices found to monitor"
        return ret

    def name(self):
        return ['swp/'+improve(name) for name in self.vars]

    def extract(self):
        self.val['total'] = [0, 0]
        for l in self.splitlines():
            if len(l) < 5 or l[0] == 'Filename': continue
            name = l[0]
            self.val[name] = ( long(l[3]) * 1024.0, (long(l[2]) - long(l[3])) * 1024.0 )
            self.val['total'] = ( self.val['total'][0] + self.val[name][0], self.val['total'][1] + self.val[name][1])

class dstat_swapold(dstat):
    def __init__(self):
        self.name = 'swap'
        self.nick = ('used', 'free')
        self.vars = ('SwapUsed', 'SwapFree')
        self.type = 'd'
        self.open('/proc/meminfo')

    def extract(self):
        for l in self.splitlines():
            if len(l) < 2: continue
            name = l[0].split(':')[0]
            if name in self.vars + ('SwapTotal',):
                self.val[name] = long(l[1]) * 1024.0
        self.val['SwapUsed'] = self.val['SwapTotal'] - self.val['SwapFree']

class dstat_sys(dstat):
    def __init__(self):
        self.name = 'system'
        self.nick = ('int', 'csw')
        self.vars = ('intr', 'ctxt')
        self.type = 'd'
        self.width = 5
        self.scale = 1000
        self.open('/proc/stat')

    def extract(self):
        for l in self.splitlines():
            if len(l) < 2: continue
            name = l[0]
            if name in self.vars:
                self.set2[name] = long(l[1])
        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed
        if step == op.delay:
            self.set1.update(self.set2)

class dstat_tcp(dstat):
    def __init__(self):
        self.name = 'tcp sockets'
        self.nick = ('lis', 'act', 'syn', 'tim', 'clo')
        self.vars = ('listen', 'established', 'syn', 'wait', 'close')
        self.type = 'd'
        self.width = 3
        self.scale = 100
        self.open('/proc/net/tcp', '/proc/net/tcp6')

    def extract(self):
        for name in self.vars: self.val[name] = 0
        for l in self.splitlines():
            if len(l) < 12: continue
            ### 01: established, 02: syn_sent,  03: syn_recv, 04: fin_wait1,
            ### 05: fin_wait2,   06: time_wait, 07: close,    08: close_wait,
            ### 09: last_ack,    0A: listen,    0B: closing
            if l[3] in ('0A',): self.val['listen'] += 1
            elif l[3] in ('01',): self.val['established'] += 1
            elif l[3] in ('02', '03', '09',): self.val['syn'] += 1
            elif l[3] in ('06',): self.val['wait'] += 1
            elif l[3] in ('04', '05', '07', '08', '0B',): self.val['close'] += 1

class dstat_time(dstat):
    def __init__(self):
        self.name = 'system'
        self.timefmt = os.getenv('DSTAT_TIMEFMT') or '%d-%m %H:%M:%S'
        self.type = 's'
        if op.debug:
            self.width = len(time.strftime(self.timefmt, time.localtime())) + 4
        else:
            self.width = len(time.strftime(self.timefmt, time.localtime()))
        self.scale = 0
        self.vars = ('time',)

    ### We are now using the starttime for this plugin, not the execution time of this plugin
    def extract(self):
        if op.debug:
            self.val['time'] = time.strftime(self.timefmt, time.localtime(starttime)) + ".%03d" % (round(starttime * 1000 % 1000 ))
        else:
            self.val['time'] = time.strftime(self.timefmt, time.localtime(starttime))

class dstat_udp(dstat):
    def __init__(self):
        self.name = 'udp'
        self.nick = ('lis', 'act')
        self.vars = ('listen', 'established')
        self.type = 'd'
        self.width = 3
        self.scale = 100
        self.open('/proc/net/udp', '/proc/net/udp6')

    def extract(self):
        for name in self.vars: self.val[name] = 0
        for l in self.splitlines():
            if l[3] == '07': self.val['listen'] += 1
            elif l[3] == '01': self.val['established'] += 1

class dstat_unix(dstat):
    def __init__(self):
        self.name = 'unix sockets'
        self.nick = ('dgm', 'str', 'lis', 'act')
        self.vars = ('datagram', 'stream', 'listen', 'established')
        self.type = 'd'
        self.width = 3
        self.scale = 100
        self.open('/proc/net/unix')

    def extract(self):
        for name in self.vars: self.val[name] = 0
        for l in self.splitlines():
            if l[4] == '0002': self.val['datagram'] += 1
            elif l[4] == '0001':
                self.val['stream'] += 1
                if l[5] == '01': self.val['listen'] += 1
                elif l[5] == '03': self.val['established'] += 1

class dstat_vm(dstat):
    def __init__(self):
        self.name = 'virtual memory'
        self.nick = ('majpf', 'minpf', 'alloc', 'free')
        self.vars = ('pgmajfault', 'pgfault', 'pgalloc', 'pgfree')
        self.type = 'd'
        self.width = 5
        self.scale = 1000
        self.open('/proc/vmstat')

    ### Page allocations should include all page zones, not just ZONE_NORMAL,
    ### but also ZONE_DMA, ZONE_HIGHMEM, ZONE_DMA32 (depending on architecture)
    def extract(self):
        self.set2['pgalloc'] = 0
        for l in self.splitlines():
            if len(l) < 2: continue
            if l[0].startswith('pgalloc_'):
                self.set2['pgalloc'] += long(l[1])
            elif l[0] in self.vars:
                self.set2[l[0]] = long(l[1])
        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed
        if step == op.delay:
            self.set1.update(self.set2)

### END STATS DEFINITIONS ###

ansi = {
    'black': '\033[0;30m',
    'darkred': '\033[0;31m',
    'darkgreen': '\033[0;32m',
    'darkyellow': '\033[0;33m',
    'darkblue': '\033[0;34m',
    'darkmagenta': '\033[0;35m',
    'darkcyan': '\033[0;36m',
    'gray': '\033[0;37m',

    'darkgray': '\033[1;30m',
    'red': '\033[1;31m',
    'green': '\033[1;32m',
    'yellow': '\033[1;33m',
    'blue': '\033[1;34m',
    'magenta': '\033[1;35m',
    'cyan': '\033[1;36m',
    'white': '\033[1;37m',

    'blackbg': '\033[40m',
    'redbg': '\033[41m',
    'greenbg': '\033[42m',
    'yellowbg': '\033[43m',
    'bluebg': '\033[44m',
    'magentabg': '\033[45m',
    'cyanbg': '\033[46m',
    'whitebg': '\033[47m',

    'reset': '\033[0;0m',
    'bold': '\033[1m',
    'reverse': '\033[2m',
    'underline': '\033[4m',

    'clear': '\033[2J',
#   'clearline': '\033[K',
    'clearline': '\033[2K',
#   'save': '\033[s',
#   'restore': '\033[u',
    'save': '\0337',
    'restore': '\0338',
    'linewrap': '\033[7h',
    'nolinewrap': '\033[7l',

    'up': '\033[1A',
    'down': '\033[1B',
    'right': '\033[1C',
    'left': '\033[1D',

    'default': '\033[0;0m',
}

char = {
    'pipe': '|',
    'colon': ':',
    'gt': '>',
    'space': ' ',
    'dash': '-',
    'plus': '+',
}

def set_theme():
    "Provide a set of colors to use"
    if op.blackonwhite:
        theme = {
            'title': ansi['darkblue'],
            'subtitle': ansi['darkcyan'] + ansi['underline'],
            'frame': ansi['darkblue'],
            'default': ansi['default'],
            'error': ansi['white'] + ansi['redbg'],
            'roundtrip': ansi['darkblue'],
            'debug': ansi['darkred'],
            'input': ansi['darkgray'],
            'text_lo': ansi['black'],
            'text_hi': ansi['darkgray'],
            'unit_lo': ansi['black'],
            'unit_hi': ansi['darkgray'],
            'colors_lo': (ansi['darkred'], ansi['darkmagenta'], ansi['darkgreen'], ansi['darkblue'],
                          ansi['darkcyan'], ansi['gray'], ansi['red'], ansi['green']),
            'colors_hi': (ansi['red'], ansi['magenta'], ansi['green'], ansi['blue'],
                          ansi['cyan'], ansi['white'], ansi['darkred'], ansi['darkgreen']),
        }
    else:
        theme = {
            'title': ansi['darkblue'],
            'subtitle': ansi['blue'] + ansi['underline'],
            'frame': ansi['darkblue'],
            'default': ansi['default'],
            'error': ansi['white'] + ansi['redbg'],
            'roundtrip': ansi['darkblue'],
            'debug': ansi['darkred'],
            'input': ansi['darkgray'],
            'text_lo': ansi['gray'],
            'text_hi': ansi['darkgray'],
            'unit_lo': ansi['darkgray'],
            'unit_hi': ansi['darkgray'],
            'colors_lo': (ansi['red'], ansi['yellow'], ansi['green'], ansi['blue'],
                          ansi['cyan'], ansi['white'], ansi['darkred'], ansi['darkgreen']),
            'colors_hi': (ansi['darkred'], ansi['darkyellow'], ansi['darkgreen'], ansi['darkblue'],
                          ansi['darkcyan'], ansi['gray'], ansi['red'], ansi['green']),
        }
    return theme

def ticks():
    "Return the number of 'ticks' since bootup"
    try:
        for line in open('/proc/uptime', 'r', 0).readlines():
            l = line.split()
            if len(l) < 2: continue
            return float(l[0])
    except:
        for line in dopen('/proc/stat').readlines():
            l = line.split()
            if len(l) < 2: continue
            if l[0] == 'btime':
                return time.time() - long(l[1])

def improve(devname):
    "Improve a device name"
    if devname.startswith('/dev/mapper/'):
        devname = devname.split('/')[3]
    elif devname.startswith('/dev/'):
        devname = devname.split('/')[2]
    return devname

def dopen(filename):
    "Open a file for reuse, if already opened, return file descriptor"
    global fds
    if not os.path.exists(filename):
        raise Exception, 'File %s does not exist' % filename
#        return None
    if 'fds' not in globals().keys():
        fds = {}
    if file not in fds.keys():
        fds[filename] = open(filename, 'r', 0)
    else:
        fds[filename].seek(0)
    return fds[filename]

def dclose(filename):
    "Close an open file and remove file descriptor from list"
    global fds
    if not 'fds' in globals().keys(): fds = {}
    if filename in fds:
        fds[filename].close()
        del(fds[filename])

def dpopen(cmd):
    "Open a pipe for reuse, if already opened, return pipes"
    global pipes, select
    import select
    if 'pipes' not in globals().keys(): pipes = {}
    if cmd not in pipes.keys():
        pipes[cmd] = os.popen3(cmd, 't', 0)
    return pipes[cmd]

def readpipe(fileobj, tmout = 0.001):
    "Read available data from pipe in a non-blocking fashion"
    ret = ''
    while not select.select([fileobj.fileno()], [], [], tmout)[0]:
        pass
    while select.select([fileobj.fileno()], [], [], tmout)[0]:
        ret = ret + fileobj.read(1)
    return ret.split('\n')

def greppipe(fileobj, str, tmout = 0.001):
    "Grep available data from pipe in a non-blocking fashion"
    ret = ''
    while not select.select([fileobj.fileno()], [], [], tmout)[0]:
        pass
    while select.select([fileobj.fileno()], [], [], tmout)[0]:
        character = fileobj.read(1)
        if character != '\n':
            ret = ret + character
        elif ret.startswith(str):
            return ret
        else:
            ret = ''
    if op.debug:
        raise Exception, 'Nothing found during greppipe data collection'
    return None

def matchpipe(fileobj, string, tmout = 0.001):
    "Match available data from pipe in a non-blocking fashion"
    ret = ''
    regexp = re.compile(string)
    while not select.select([fileobj.fileno()], [], [], tmout)[0]:
        pass
    while select.select([fileobj.fileno()], [], [], tmout)[0]:
        character = fileobj.read(1)
        if character != '\n':
            ret = ret + character
        elif regexp.match(ret):
            return ret
        else:
            ret = ''
    if op.debug:
        raise Exception, 'Nothing found during matchpipe data collection'
    return None

def cmd_test(cmd):
    pipes = os.popen3(cmd, 't', 0)
    for line in pipes[2].readlines():
        raise Exception, line.strip()

def cmd_readlines(cmd):
    pipes = os.popen3(cmd, 't', 0)
    for line in pipes[1].readlines():
       yield line

def cmd_splitlines(cmd, sep=None):
    pipes = os.popen3(cmd, 't', 0)
    for line in pipes[1].readlines():
       yield line.split(sep)

def proc_readlines(filename):
    "Return the lines of a file, one by one"
#    for line in open(filename).readlines():
#       yield line

    ### Implemented linecache (for top-plugins)
    i = 1
    while True:
        line = linecache.getline(filename, i);
        if not line: break
        yield line
        i += 1

def proc_splitlines(filename, sep=None):
    "Return the splitted lines of a file, one by one"
#    for line in open(filename).readlines():
#       yield line.split(sep)

    ### Implemented linecache (for top-plugins)
    i = 1
    while True:
        line = linecache.getline(filename, i);
        if not line: break
        yield line.split(sep)
        i += 1

def proc_readline(filename):
    "Return the first line of a file"
#    return open(filename).read()
    return linecache.getline(filename, 1)

def proc_splitline(filename, sep=None):
    "Return the first line of a file splitted"
#    return open(filename).read().split(sep)
    return linecache.getline(filename, 1).split(sep)

### FIXME: Should we cache this within every step ?
def proc_pidlist():
    "Return a list of process IDs"
    dstat_pid = str(os.getpid())
    for pid in os.listdir('/proc/'):
        try:
            ### Is it a pid ?
            int(pid)

            ### Filter out dstat
            if pid == dstat_pid: continue

            yield pid

        except ValueError:
            continue

def dchg(var, width, base):
    "Convert decimal to string given base and length"
    c = 0
    while True:
        ret = str(long(round(var)))
        if len(ret) <= width:
            break
        var = var / base
        c = c + 1
    else:
        c = -1
    return ret, c

def fchg(var, width, base):
    "Convert float to string given scale and length"
    c = 0
    while True:
        if var == 0:
            ret = str('0')
            break
#       ret = repr(round(var))
#       ret = repr(long(round(var, maxlen)))
        ret = str(long(round(var, width)))
        if len(ret) <= width:
            i = width - len(ret) - 1
            while i > 0:
                ret = ('%%.%df' % i) % var
                if len(ret) <= width and ret != str(long(round(var, width))):
                    break
                i = i - 1
            else:
                ret = str(long(round(var)))
            break
        var = var / base
        c = c + 1
    else:
        c = -1
    return ret, c

def tchg(var, width):
    "Convert time string to given length"
    ret = '%2dh%02d' % (var / 60, var % 60)
    if len(ret) > width:
        ret = '%2dh' % (var / 60)
        if len(ret) > width:
            ret = '%2dd' % (var / 60 / 24)
            if len(ret) > width:
                ret = '%2dw' % (var / 60 / 24 / 7)
    return ret

def cprintlist(varlist, type, width, scale):
    "Return all columns color printed"
    ret = sep = ''
    for var in varlist:
        ret = ret + sep + cprint(var, type, width, scale)
        sep = ' '
    return ret

def cprint(var, type = 'f', width = 4, scale = 1000):
    "Color print one column"

    base = 1000
    if scale == 1024:
        base = 1024

    ### Use units when base is exact 1000 or 1024
    unit = False
    if scale in (1000, 1024) and width >= len(str(base)):
        unit = True
        width = width - 1

    ### If this is a negative value, return a dash
    if var < 0:
        if unit:
            return theme['error'] + '-'.rjust(width) + ' ' + theme['default']
        else:
            return theme['error'] + '-'.rjust(width) + theme['default']

    if base == 1024:
        units = ('B', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
    else:
        units = (' ', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')

    if step == op.delay:
        colors = theme['colors_lo']
        ctext = theme['text_lo']
        cunit = theme['unit_lo']
    else:
        colors = theme['colors_hi']
        ctext = theme['text_hi']
        cunit = theme['unit_hi']

    ### Convert value to string given base and field-length
    if op.integer and type in ('d', 'p', 'f'):
        ret, c = dchg(var, width, base)
    elif op.float and type in ('d', 'p', 'f'):
        ret, c = fchg(var, width, base)
    elif type in ('d', 'p'):
        ret, c = dchg(var, width, base)
    elif type in ('f'):
        ret, c = fchg(var, width, base)
    elif type in ('s'):
        ret, c = str(var), ctext
    elif type in ('t'):
        ret, c = tchg(var, width), ctext
    else:
        raise Exception, 'Type %s not known to dstat.' % type

    ### Set the counter color
    if ret == '0':
        color = cunit
    elif scale <= 0:
        color = ctext
    elif scale not in (1000, 1024):
        color = colors[int(var/scale)%len(colors)]
    elif type in ('p'):
        color = colors[int(round(var)/scale)%len(colors)]
    elif type in ('d', 'f'):
        color = colors[c%len(colors)]
    else:
        color = ctext

    ### Justify value to left if string
    if type in ('s',):
        ret = color + ret.ljust(width)
    else:
        ret = color + ret.rjust(width)

    ### Add unit to output
    if unit:
        if c != -1 and round(var) != 0:
            ret += cunit + units[c]
        else:
            ret += ' '

    return ret

def header(totlist, vislist):
    "Return the header for a set of module counters"
    line = ''
    ### Process title
    for o in vislist:
        line += o.title()
        if o is not vislist[-1]:
            line += theme['frame'] + char['space']
        elif totlist != vislist:
            line += theme['title'] + char['gt']
    line += '\n'
    ### Process subtitle
    for o in vislist:
        line += o.subtitle()
        if o is not vislist[-1]:
            line += theme['frame'] + char['pipe']
        elif totlist != vislist:
            line += theme['title'] + char['gt']
    return line + '\n'

def csvheader(totlist):
    "Return the CVS header for a set of module counters"
    line = ''
    ### Process title
    for o in totlist:
        line = line + o.csvtitle()
        if o is not totlist[-1]:
            line = line + ','
    line += '\n'
    ### Process subtitle
    for o in totlist:
        line = line + o.csvsubtitle()
        if o is not totlist[-1]:
            line = line + ','
    return line + '\n'

def info(level, str):
    "Output info message"
#   if level <= op.verbose:
    print >>sys.stderr, str

def die(ret, str):
    "Print error and exit with errorcode"
    print >>sys.stderr, str
    exit(ret)

def initterm():
    "Initialise terminal"
    global termsize

    ### Unbuffered sys.stdout
#    sys.stdout = os.fdopen(1, 'w', 0)

    try:
        global fcntl, struct, termios
        import fcntl, struct, termios
        termios.TIOCGWINSZ
    except:
        try:
            curses.setupterm()
            curses.tigetnum('lines'), curses.tigetnum('cols')
        except:
            pass
        else:
            termsize = None, 2
    else:
        termsize = None, 1

def gettermsize():
    "Return the dynamic terminal geometry"
    global termsize

#    if not termsize[0] and not termsize[1]:
    if not termsize[0]:
        try:
            if termsize[1] == 1:
                s = struct.pack('HHHH', 0, 0, 0, 0)
                x = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, s)
                return struct.unpack('HHHH', x)[:2]
            elif termsize[1] == 2:
                curses.setupterm()
                return curses.tigetnum('lines'), curses.tigetnum('cols')
            else:
                termsize = (int(os.environ['LINES']), int(os.environ['COLUMNS']))
        except:
            termsize = 25, 80
    return termsize

def gettermcolor(color=True):
    "Return whether the system can use colors or not"
    if color and sys.stdout.isatty():
        try:
            import curses
            curses.setupterm()
            if curses.tigetnum('colors') < 0:
                return False
        except:
            print >>sys.stderr, 'Color support is disabled, python-curses is not installed.'
            return False
    return color

### We only want to filter out paths, not ksoftirqd/1
def basename(name):
    "Perform basename on paths only"
    if name[0] in ('/', '.'):
        return os.path.basename(name)
    return name

def getnamebypid(pid, name):
    "Return the name of a process by taking best guesses and exclusion"
    ret = None
    try:
#        cmdline = open('/proc/%s/cmdline' % pid).read().split('\0')
        cmdline = linecache.getline('/proc/%s/cmdline' % pid, 1).split('\0')
        ret = basename(cmdline[0])
        if ret in ('bash', 'csh', 'ksh', 'perl', 'python', 'ruby', 'sh'):
            ret = basename(cmdline[1])
        if ret.startswith('-'):
            ret = basename(cmdline[-2])
            if ret.startswith('-'): raise
        if not ret: raise
    except:
        ret = basename(name)
    return ret

def getcpunr():
    "Return the number of CPUs in the system"
    cpunr = -1
    for line in dopen('/proc/stat').readlines():
        if line[0:3] == 'cpu':
            cpunr = cpunr + 1
    if cpunr < 0:
        raise Exception, "Problem finding number of CPUs in system."
    return cpunr

def blockdevices():
    ### We have to replace '!' by '/' to support cciss!c0d0 type devices :-/
    return [os.path.basename(filename).replace('!', '/') for filename in glob.glob('/sys/block/*')]

### FIXME: Add scsi support too and improve
def sysfs_dev(device):
    "Convert sysfs device names into device names"
    m = re.match('ide/host(\d)/bus(\d)/target(\d)/lun(\d)/disc', device)
    if m:
        l = m.groups()
        # ide/host0/bus0/target0/lun0/disc -> 0 -> hda
        # ide/host0/bus1/target0/lun0/disc -> 2 -> hdc
        nr = int(l[1]) * 2 + int(l[3])
        return 'hd' + chr(ord('a') + nr)
    m = re.match('cciss/(c\dd\d)', device)
    if m:
        l = m.groups()
        return l[0]
    m = re.match('placeholder', device)
    if m:
        return 'sdX'
    return device

def dev(maj, min):
    "Convert major/minor pairs into device names"
    ram = [1, ]
    ide = [3, 22, 33, 34, 56, 57, 88, 89, 90, 91]
    loop = [7, ]
    scsi = [8, 65, 66, 67, 68, 69, 70, 71, 128, 129, 130, 131, 132, 133, 134, 135]
    md = [9, ]
    ida = [72, 73, 74, 75, 76, 77, 78, 79]
    ubd = [98,]
    cciss = [104,]
    dm =  [253,]
    if maj in scsi:
        disc = chr(ord('a') + scsi.index(maj) * 16 + min / 16)
        part = min % 16
        if not part: return 'sd%s' % disc
        return 'sd%s%d' % (disc, part)
    elif maj in ide:
        disc = chr(ord('a') + ide.index(maj) * 2 + min / 64)
        part = min % 64
        if not part: return 'hd%s' % disc
        return 'hd%s%d' % (disc, part)
    elif maj in dm:
        return 'dm-%d' % min
    elif maj in md:
        return 'md%d' % min
    elif maj in loop:
        return 'loop%d' % min
    elif maj in ram:
        return 'ram%d' % min
    elif maj in cciss:
        disc = cciss.index(maj) * 16 + min / 16
        part = min % 16
        if not part: return 'c0d%d' % disc
        return 'c0d%dp%d' % (disc, part)
    elif maj in ida:
        cont = ida.index(maj)
        disc = min / 16
        part = min % 16
        if not part: return 'ida%d-%d' % (cont, disc)
        return 'ida%d-%d-%d' % (cont, disc, part)
    elif maj in ubd:
        disc = ubd.index(maj) * 16 + min / 16
        part = min % 16
        if not part: return 'ubd%d' % disc
        return 'ubd%d-%d' % (disc, part)
    else:
        return 'dev%d-%d' % (maj, min)

#def mountpoint(dev):
#   "Return the mountpoint of a mounted device/file"
#   for entry in dopen('/etc/mtab').readlines():
#       if entry:
#           devlist = entry.split()
#           if dev == devlist[0]:
#               return devlist[1]

#def readfile(file):
#    ret = ''
#    for line in open(file,'r').readlines():
#        ret = ret + line
#    return ret

#cdef extern from "sched.h":
#    struct sched_param:
#        int sched_priority
#        int sched_setscheduler(int pid, int policy,sched_param  *p)
#
#SCHED_FIFO = 1
#
#def switchRTCPriority(nb):
#    cdef sched_param sp
#    sp.sched_priority = nb
#    sched_setscheduler (0,SCHED_FIFO , &sp);

def listplugins():
    plugins = []
    remod = re.compile('dstat_(.+)$')
    for filename in globals():
        if filename.startswith('dstat_'):
            plugins.append(remod.match(filename).groups()[0])
    remod = re.compile('.+/dstat_(.+).py$')
    for path in pluginpath:
        for filename in glob.glob(path + '/dstat_*.py'):
            plugins.append(remod.match(filename).groups()[0].replace('_', '-'))
    plugins.sort()
    return plugins

def showplugins():
    rows, cols = gettermsize()
    print 'internal:\n\t',
    remod = re.compile('dstat_(.+)$')
    plugins = []
    for filename in globals():
        if filename.startswith('dstat_'):
            plugins.append(remod.match(filename).groups()[0].replace('_', '-'))
    plugins.sort()
    cols2 = cols - 8
    for mod in plugins:
        cols2 = cols2 - len(mod) - 2
        if cols2 <= 0:
            print '\n\t',
            cols2 = cols - len(mod) - 10
        if mod != plugins[-1]:
            print mod+',',
    print mod
    remod = re.compile('.+/dstat_(.+).py$')
    for path in pluginpath:
        plugins = []
        for filename in glob.glob(path + '/dstat_*.py'):
            plugins.append(remod.match(filename).groups()[0].replace('_', '-'))
        if not plugins: continue
        plugins.sort()
        cols2 = cols - 8
        print '%s:\n\t' % os.path.abspath(path),
        for mod in plugins:
            cols2 = cols2 - len(mod) - 2
            if cols2 <= 0:
                print '\n\t',
                cols2 = cols - len(mod) - 10
            if mod != plugins[-1]:
                print mod+',',
        print mod

def exit(ret):
    sys.stdout.write(ansi['reset'])
    sys.stdout.flush()

    if op.pidfile and os.path.exists(op.pidfile):
        os.remove(op.pidfile)

    if op.profile and os.path.exists(op.profile):
        rows, cols = gettermsize()
        import pstats
        p = pstats.Stats(op.profile)
#        p.sort_stats('name')
#        p.print_stats()
        p.sort_stats('cumulative').print_stats(rows - 13)
#        p.sort_stats('time').print_stats(rows - 13)
#        p.sort_stats('file').print_stats('__init__')
#        p.sort_stats('time', 'cum').print_stats(.5, 'init')
#        p.print_callees()
    elif op.profile:
        print >>sys.stderr, "No profiling data was found, maybe profiler was interrupted ?"

    sys.exit(ret)

def main():
    "Initialization of the program, terminal, internal structures"
    global cpunr, hz, maxint, ownpid, pagesize
    global ansi, theme, outputfile
    global totlist, inittime
    global update, missed

    cpunr = getcpunr()
    hz = os.sysconf('SC_CLK_TCK')
    maxint = (sys.maxint + 1) * 2
    ownpid = str(os.getpid())
    pagesize = resource.getpagesize()
    interval = 1

    user = getpass.getuser()
    hostname = os.uname()[1]

    ### Disable line-wrapping (does not work ?)
    sys.stdout.write('\033[7l')

    ### Write term-title
    if sys.stdout.isatty():
        shell = os.getenv('XTERM_SHELL')
        term = os.getenv('TERM')
        if shell == '/bin/bash' and term and re.compile('(screen*|xterm*)').match(term):
            sys.stdout.write('\033]0;(%s@%s) %s %s\007' % (user, hostname, os.path.basename(sys.argv[0]), ' '.join(op.args)))

    ### Check background color (rxvt)
    ### COLORFGBG="15;default;0"
#   if os.environ['COLORFGBG'] and len(os.environ['COLORFGBG'].split(';')) >= 3:
#       l = os.environ['COLORFGBG'].split(';')
#       bg = int(l[2])
#       if bg < 7:
#           print 'Background is dark'
#       else:
#           print 'Background is light'
#   else:
#       print 'Background is unknown, assuming dark.'

    ### Check terminal capabilities
    op.color = gettermcolor(op.color)

    ### Prepare CSV output file
    if op.output:
        if os.path.exists(op.output):
            outputfile = open(op.output, 'a', 0)
            outputfile.write('\n\n')
        else:
            outputfile = open(op.output, 'w', 0)
            outputfile.write('"Dstat %s CSV output"\n' % VERSION)
            outputfile.write('"Author:","<NAME> <<EMAIL>>",,,,"URL:","http://dag.wieers.com/home-made/dstat/"\n')

        outputfile.write('"Host:","%s",,,,"User:","%s"\n' % (hostname, user))
        outputfile.write('"Cmdline:","dstat %s",,,,"Date:","%s"\n\n' % (' '.join(op.args), time.strftime('%d %b %Y %H:%M:%S %Z', time.localtime())))

    ### Create pidfile
    if op.pidfile:
        try:
            pidfile = open(op.pidfile, 'w', 0)
            pidfile.write(str(os.getpid()))
            pidfile.close()
        except Exception, e:
            print >>sys.stderr, 'Failed to create pidfile %s' % op.pidfile, e
            op.pidfile = False

    ### Empty ansi and theme database if no colors are requested
    if not op.color:
        op.update = False
        for key in ansi.keys():
            ansi[key] = ''
        for key in theme.keys():
            theme[key] = ''
        theme['colors_hi'] = (ansi['default'],)
        theme['colors_lo'] = (ansi['default'],)
#        print ansi['blackbg']

    if not op.update:
        interval = op.delay

    ### Build list of requested plugins
    linewidth = 0
    totlist = []
    for plugin in op.plugins:

        ### Set up fallback lists
        if plugin == 'cpu':  mods = ( 'cpu', 'cpu24' )
        elif plugin == 'disk': mods = ( 'disk', 'disk24', 'disk24old' )
        elif plugin == 'int':  mods = ( 'int', 'int24' )
        elif plugin == 'page': mods = ( 'page', 'page24' )
        elif plugin == 'swap': mods = ( 'swap', 'swapold' )
        else: mods = ( plugin, )

        for mod in mods:
            pluginfile = 'dstat_' + mod.replace('-', '_')
            try:
                if pluginfile not in globals().keys():
                    import imp
                    fp, pathname, description = imp.find_module(pluginfile, pluginpath)
                    fp.close()

                    ### TODO: Would using .pyc help with anything ?
                    ### Try loading python plugin
                    if description[0] in ('.py', ):
                        execfile(pathname)
                        exec 'o = dstat_plugin(); del(dstat_plugin)'
                        o.filename = pluginfile
                        o.check()
                        o.prepare()

                    ### Try loading C plugin (not functional yet)
                    elif description[0] == '.so':
                        exec 'import %s' % pluginfile
                        exec 'o = %s.new()' % pluginfile
                        o.check()
                        o.prepare()
#                        print dir(o)
#                        print o.__module__
#                        print o.name
                    else:
                        print >>sys.stderr, 'Module %s is of unknown type.' % pluginfile

                else:
                    exec 'o = %s()' % pluginfile
                    o.check()
                    o.prepare()
#                print o.__module__
            except Exception, e:
                if mod == mods[-1]:
                    print >>sys.stderr, 'Module %s failed to load. (%s)' % (pluginfile, e)
                elif op.debug:
                    print >>sys.stderr, 'Module %s failed to load, trying another. (%s)' % (pluginfile, e)
                if op.debug >= 3:
                    raise
#                tb = sys.exc_info()[2]
                continue
            except:
                print >>sys.stderr, 'Module %s caused unknown exception' % pluginfile

            linewidth = linewidth + o.statwidth() + 1
            totlist.append(o)

            if op.debug:
                print 'Module', pluginfile,
                if hasattr(o, 'file'):
                    print 'requires', o.file,
                print
            break

    if not totlist:
        die(8, 'None of the stats you selected are available.')

    if op.output:
        outputfile.write(csvheader(totlist))

    scheduler = sched.scheduler(time.time, time.sleep)
    inittime = time.time()

    update = 0
    missed = 0

    ### Let the games begin
    while update <= op.delay * op.count or op.count == -1:
        scheduler.enterabs(inittime + update, 1, perform, (update,))
#        scheduler.enter(1, 1, perform, (update,))
        scheduler.run()
        sys.stdout.flush()
        update = update + interval
        linecache.clearcache()

    if op.update:
        sys.stdout.write('\n')

def perform(update):
        "Inner loop that calculates counters and constructs output"
        global totlist, oldvislist, vislist, showheader, rows, cols
        global elapsed, totaltime, starttime
        global loop, step, missed

        starttime = time.time()

        loop = (update - 1 + op.delay) / op.delay
        step = ((update - 1) % op.delay) + 1

        ### Get current time (may be different from schedule) for debugging
        if not op.debug:
            curwidth = 0
        else:
            if step == 1 or loop == 0:
                totaltime = 0
            curwidth = 8

        ### FIXME: This is temporary functionality, we should do this better
        ### If it takes longer than 500ms, than warn !
        if loop != 0 and starttime - inittime - update > 1:
            missed = missed + 1
            return 0

        ### Initialise certain variables
        if loop == 0:
            elapsed = ticks()
            rows, cols = 0, 0
            vislist = []
            oldvislist = []
            showheader = True
        else:
            elapsed = step

        ### FIXME: Make this part smarter
        if sys.stdout.isatty():
            oldcols = cols
            rows, cols = gettermsize()

            ### Trim object list to what is visible on screen
            if oldcols != cols:
                vislist = []
                for o in totlist:
                    newwidth = curwidth + o.statwidth() + 1
                    if newwidth <= cols or ( vislist == totlist[:-1] and newwidth < cols ):
                        vislist.append(o)
                        curwidth = newwidth

            ### Check when to display the header
            if op.header and rows >= 6:
                if oldvislist != vislist:
                    showheader = True
                elif step == 1 and loop % (rows - 1) == 0:
                    showheader = True

            oldvislist = vislist
        else:
            vislist = totlist

        ### Prepare the colors for intermediate updates, last step in a loop is definitive
        if step == op.delay:
            theme['default'] = ansi['reset']
        else:
            theme['default'] = theme['text_lo']

        ### The first step is to show the definitive line if necessary
        newline = ''
        if op.update:
            if step == 1 and update != 0:
                newline = '\n' + ansi['reset'] + ansi['clearline'] + ansi['save']
            elif loop != 0:
                newline = ansi['restore']

        ### Display header
        if showheader:
            if loop == 0 and totlist != vislist:
                print >>sys.stderr, 'Terminal width too small, trimming output.'
            showheader = False
            sys.stdout.write(newline)
            newline = header(totlist, vislist)

        ### Calculate all objects (visible, invisible)
        line = newline
        oline = ''
        for o in totlist:
            o.extract()
            if o in vislist:
                line = line + o.show() + o.showend(totlist, vislist)
            if op.output and step == op.delay:
                oline = oline + o.showcsv() + o.showcsvend(totlist, vislist)

        ### Print stats
        sys.stdout.write(line + theme['input'])
        if op.output and step == op.delay:
            outputfile.write(oline + '\n')

        ### Print debugging output
        if op.debug:
            totaltime = totaltime + (time.time() - starttime) * 1000.0
            if loop == 0:
                totaltime = totaltime * step
            if op.debug == 1:
                sys.stdout.write('%s%6.2fms%s' % (theme['roundtrip'], totaltime / step, theme['input']))
            elif op.debug == 2:
                sys.stdout.write('%s%6.2f %s%d:%d%s' % (theme['roundtrip'], totaltime / step, theme['debug'], loop, step, theme['input']))
            elif op.debug > 2:
                sys.stdout.write('%s%6.2f %s%d:%d:%d%s' % (theme['roundtrip'], totaltime / step, theme['debug'], loop, step, update, theme['input']))

        if missed > 0:
#            sys.stdout.write(' '+theme['error']+'= warn =')
            sys.stdout.write(' ' + theme['error'] + 'missed ' + str(missed+1) + ' ticks' + theme['input'])
            missed = 0

        ### Finish the line
        if not op.update:
            sys.stdout.write('\n')

### Main entrance
if __name__ == '__main__':
    try:
        initterm()
        op = Options(sys.argv[1:])
        theme = set_theme()
        if op.profile:
            import profile
            if os.path.exists(op.profile):
                os.remove(op.profile)
            profile.run('main()', op.profile)
        else:
            main()
    except KeyboardInterrupt, e:
        if op.update:
            sys.stdout.write('\n')
    exit(0)
else:
    op = Options('')
    step = 1

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/examples/curstest
#!/usr/bin/python
import curses, sys

#c = curses.wrapper(s)
#w = curses.initscr()
#curses.start_color()
#print "TERM is", curses.termname()
#if curses.has_colors():
#   print "Has colors"#
#print curses.color_pair(curses.COLOR_RED), "Red"
#curses.endwin()

#curses.setupterm('xterm')
curses.setupterm()

if sys.stdout.isatty():
    print "Is a TTY"

print "Size is %sx%s" % (curses.tigetnum('lines'), curses.tigetnum('cols'))

if curses.tigetnum('colors') > 0:
    print "Has colors"
    print curses.tigetnum('colors')
<file_sep>/rs-sysmon2/plugins/dstat_top_io_adv.py
### Dstat all I/O process plugin
### Displays all processes' I/O read/write stats and CPU usage
###
### Authority: <NAME>

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'most expensive i/o process'
        self.vars = ('process              pid  read write cpu',)
        self.type = 's'
        self.width = 40
        self.scale = 0
        self.pidset1 = {}

    def check(self):
        if not os.access('/proc/self/io', os.R_OK):
            raise Exception, 'Kernel has no I/O accounting, use at least 2.6.20.'
        return True

    def extract(self):
        self.output = ''
        self.pidset2 = {}
        self.val['usage'] = 0.0
        for pid in proc_pidlist():
            try:
                ### Reset values
                if not self.pidset2.has_key(pid):
                    self.pidset2[pid] = {'rchar:': 0, 'wchar:': 0, 'cputime:': 0, 'cpuper:': 0}
                if not self.pidset1.has_key(pid):
                    self.pidset1[pid] = {'rchar:': 0, 'wchar:': 0, 'cputime:': 0, 'cpuper:': 0}

                ### Extract name
                name = proc_splitline('/proc/%s/stat' % pid)[1][1:-1]

                ### Extract counters
                for l in proc_splitlines('/proc/%s/io' % pid):
                    if len(l) != 2: continue
                    self.pidset2[pid][l[0]] = int(l[1])

                ### Get CPU usage
                l = proc_splitline('/proc/%s/stat' % pid)
                if len(l) < 15:
                    cpu_usage = 0
                else:
                    self.pidset2[pid]['cputime:'] = int(l[13]) + int(l[14])
                    cpu_usage = (self.pidset2[pid]['cputime:'] - self.pidset1[pid]['cputime:']) * 1.0 / elapsed / cpunr

            except ValueError:
                continue
            except IOError:
                continue
            except IndexError:
                continue

            read_usage = (self.pidset2[pid]['rchar:'] - self.pidset1[pid]['rchar:']) * 1.0 / elapsed
            write_usage = (self.pidset2[pid]['wchar:'] - self.pidset1[pid]['wchar:']) * 1.0 / elapsed
            usage = read_usage + write_usage

            ### Get the process that spends the most jiffies
            if usage > self.val['usage']:
                self.val['usage'] = usage
                self.val['read_usage'] = read_usage
                self.val['write_usage'] = write_usage
                self.val['pid'] = pid
                self.val['name'] = getnamebypid(pid, name)
                self.val['cpu_usage'] = cpu_usage

        if step == op.delay:
            self.pidset1 = self.pidset2

        if self.val['usage'] != 0.0:
            self.output = '%-*s%s%-5s%s%s%s%s%%' % (self.width-14-len(pid), self.val['name'][0:self.width-14-len(pid)], ansi['darkblue'], self.val['pid'], cprint(self.val['read_usage'], 'd', 5, 1024), cprint(self.val['write_usage'], 'd', 5, 1024), cprint(self.val['cpu_usage'], 'f', 3, 34), ansi['darkgray'])

    def showcsv(self):
        return self.val['i/o process'] + 'Top: %s\t%s\t%s\t%s' % (self.val['name'][0:self.width-20], self.val['read_usage'], self.val['write_usage'], self.val['cpu_usage'])
<file_sep>/rs-sysmon2/plugins/dstat_test.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    '''
    Provides a test playground to test syntax and structure.
    '''
    def __init__(self):
        self.name = 'test'
        self.nick = ( 'f1', 'f2' )
        self.vars = ( 'f1', 'f2' )
#        self.type = 'd'
#        self.width = 4
#        self.scale = 20
        self.type = 's'
        self.width = 4
        self.scale = 0

    def extract(self):
#        Self.val = { 'f1': -1, 'f2': -1 }
        self.val = { 'f1': 'test', 'f2': 'test' }

# vim:ts=4:sw=4:et
<file_sep>/src/dbseer/middleware/packet/MiddlewarePacket.java
/*
 * Copyright 2013 <NAME>
 *
 * Licensed 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 dbseer.middleware.packet;

import java.io.UnsupportedEncodingException;

/**
 * Created by <NAME> on 12/2/15.
 */
public class MiddlewarePacket
{
	public int header;
	public int length;
	public String body;

	public MiddlewarePacket(int header, int length, String body)
	{
		this.header = header;
		this.length = length;
		this.body = body;
	}

	public MiddlewarePacket(int header, String body)
	{
		this.header = header;
		this.body = body;
		try
		{
			this.length = body.getBytes("UTF-8").length;
		}
		catch (UnsupportedEncodingException e)
		{
			e.printStackTrace();
		}
	}

	public MiddlewarePacket(int header)
	{
		this.header = header;
		this.length = 0;
	}
}
<file_sep>/rs-sysmon2/plugins/dstat_vz_cpu.py
### Author: <NAME> <<EMAIL>>

#Version: 2.2
#VEID   user    nice    system   uptime     idle             strv   uptime          used           maxlat  totlat  numsched
#302    142926  0       10252    152896388  852779112954062  0      427034187248480 1048603937010  0       0       0
#301    27188   0       7896     152899846  853267000490282  0      427043845492614 701812592320   0       0       0

class dstat_plugin(dstat):
    def __init__(self):
        self.nick = ('usr', 'sys', 'idl', 'nic')
        self.type = 'p'
        self.width = 3
        self.scale = 34
        self.open('/proc/vz/vestat')
        self.cols = 4

    def check(self):
        info(1, 'Module %s is still experimental.' % self.filename)

    def discover(self, *list):
        ret = []
        for l in self.splitlines():
            if len(l) < 6 or l[0] == 'VEID': continue
            ret.append(l[0])
        ret.sort()
        for item in list: ret.append(item)
        return ret

    def name(self):
        ret = []
        for name in self.vars:
            if name == 'total':
                ret.append('total ve usage')
            else:
                ret.append('ve ' + name + ' usage')
        return ret

    def vars(self):
        ret = []
        if not op.full:
            list = ('total', )
        else: 
            list = self.discover
        for name in list: 
            if name in self.discover + ['total']:
                ret.append(name)
        return ret

    def extract(self):
        self.set2['total'] = [0, 0, 0, 0]
        for line in self.splitlines():
            if len(l) < 6 or l[0] == 'VEID': continue
            name = l[0]
            self.set2[name] = ( long(l[1]), long(l[3]), long(l[4]) - long(l[1]) - long(l[2]) - long(l[3]), long(l[2]) )
            self.set2['total'] = ( self.set2['total'][0] + long(l[1]), self.set2['total'][1] + long(l[3]), self.set2['total'][2] + long(l[4]) - long(l[1]) - long(l[2]) - long(l[3]), self.set2['total'][3] + long(l[2]) )
        for name in self.vars:
            for i in range(4):
                self.val[name][i] = 100.0 * (self.set2[name][i] - self.set1[name][i]) / (sum(self.set2[name]) - sum(self.set1[name]))
        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4:et
<file_sep>/src/dbseer/middleware/client/MiddlewareClient.java
/*
 * Copyright 2013 <NAME>
 *
 * Licensed 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 dbseer.middleware.client;

import com.esotericsoftware.minlog.Log;
import dbseer.middleware.constant.MiddlewareConstants;
import dbseer.middleware.event.MiddlewareClientEvent;
import dbseer.middleware.packet.MiddlewarePacket;
import dbseer.middleware.packet.MiddlewarePacketDecoder;
import dbseer.middleware.packet.MiddlewarePacketEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.timeout.IdleStateHandler;

import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Created by <NAME> on 12/1/15.
 *
 * The client for the middleware.
 */
public class MiddlewareClient extends Observable implements Runnable
{
	private static final int MAX_RETRY = 3;

	private String id;
	private String password;
	private String host;
	private int port;
	private int retry;
	private int reqId;
	private boolean isMonitoring;

	private String logPath;

	private Channel channel = null;
	private ExecutorService requesterExecutor = null;

	private ExecutorService heartbeatSenderExecutor = null;
	private MiddlewareClientHeartbeatSender heartbeatSender = null;

	private MiddlewareClientLogRequester txLogRequester = null;
	private Map<String, MiddlewareClientLogRequester> sysLogRequester = null;
	private Map<String, PrintWriter> logWriterMap = null; // tx log writer for dbseer: <transaction type, writer>
	private Map<Integer, String> statementMessageMap = null;

	private ArrayList<String> serverNameList = null;
	private ZipOutputStream txZipOutputStream = null;
	private PrintWriter txPrintWriter = null;
	private File txLogFileRaw = null;

	public MiddlewareClient(String host, String id, String password, int port, String logPath)
	{
		this.retry = 0;
		this.reqId = 0;
		this.id = id;
		this.password = <PASSWORD>;
		this.host = host;
		this.port = port;
		this.logPath = logPath;
		this.isMonitoring = false;
		this.logWriterMap = new HashMap<>();
		this.sysLogRequester = new HashMap<>();
		this.statementMessageMap = new HashMap<>();
		this.serverNameList = new ArrayList<>();
	}

	public void setLogLevel(int level)
	{
		Log.set(level);
	}

	public void run()
	{
		// debug info
		Log.debug(String.format("host = %s", host));
		Log.debug(String.format("port = %d", port));
		Log.debug(String.format("log path = %s", logPath));

		// client needs to handle incoming messages from the middleware as well.
		EventLoopGroup group = new NioEventLoopGroup(4);

		try
		{
			// attach shutdown hook.
			MiddlewareClientShutdown shutdownThread = new MiddlewareClientShutdown(this);
			Runtime.getRuntime().addShutdownHook(shutdownThread);

			File logDir = new File(logPath);
			if (!logDir.exists())
			{
				logDir.mkdirs();
			}

			final MiddlewareClient client = this;

			Bootstrap b = new Bootstrap();
			b.group(group)
					.channel(NioSocketChannel.class)
					.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
					.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
					.handler(new ChannelInitializer<SocketChannel>()
					{
						@Override
						protected void initChannel(SocketChannel ch) throws Exception
						{
							ChannelPipeline p = ch.pipeline();
							p.addLast(new IdleStateHandler(10, 0, 0));
							p.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.ZLIB));
							p.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.ZLIB));
							p.addLast(new MiddlewarePacketDecoder());
							p.addLast(new MiddlewarePacketEncoder());
							p.addLast(new MiddlewareClientHandler(client));
						}
					});

			ChannelFuture f = b.connect(host, port).sync();
			channel = f.channel();
			Log.debug("Connected to the middleware.");

			MiddlewarePacket checkPacket = new MiddlewarePacket(MiddlewareConstants.PACKET_CHECK_VERSION, MiddlewareConstants.PROTOCOL_VERSION);
//			ByteBuf buf = Unpooled.buffer();
//			buf.writeInt(MiddlewareConstants.PACKET_CHECK_VERSION);
//			buf.writeInt(MiddlewareConstants.PROTOCOL_VERSION.getBytes("UTF-8").length);
//			buf.writeBytes(MiddlewareConstants.PROTOCOL_VERSION.getBytes("UTF-8"));
//			channel.writeAndFlush(buf);
			channel.writeAndFlush(checkPacket);

			channel.closeFuture().sync();
		}
		catch (Exception e)
		{
			if (e instanceof InterruptedException)
			{

			}
			else
			{
				setChanged();
				notifyObservers(new MiddlewareClientEvent(MiddlewareClientEvent.ERROR, e));
			}
			Log.error(e.getMessage());
			e.printStackTrace();
		}
		finally
		{
			group.shutdownGracefully();
			this.stopExecutors();
			if (txLogFileRaw.exists())
			{
				txLogFileRaw.delete();
			}
			if (txZipOutputStream != null)
			{
				try
				{
					txZipOutputStream.closeEntry();
					txZipOutputStream.close();
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}
				txZipOutputStream = null;
			}
		}
	}

	public Channel getChannel()
	{
		return channel;
	}

	public MiddlewareClientLogRequester getTxLogRequester()
	{
		return txLogRequester;
	}

	public MiddlewareClientLogRequester getSysLogRequester(String server)
	{
		return sysLogRequester.get(server);
	}

	public void startMonitoring() throws Exception
	{
		if (retry >= MAX_RETRY)
		{
			throw new Exception(String.format("Middleware failed to start with %d retries", MAX_RETRY));
		}

		// clear server names.
		this.serverNameList.clear();

		if (channel != null)
		{
			String idPassword = <PASSWORD> + "@" + this.<PASSWORD>;
//			ByteBuf b = Unpooled.buffer();
//			b.writeInt(MiddlewareConstants.PACKET_START_MONITORING);
//			b.writeInt(idPassword.getBytes("UTF-8").length);
//			b.writeBytes(idPassword.getBytes("UTF-8"));
//			channel.writeAndFlush(b);

			MiddlewarePacket packet = new MiddlewarePacket(MiddlewareConstants.PACKET_START_MONITORING, idPassword);
			channel.writeAndFlush(packet);
		}
		Log.debug("Start monitoring packet sent.");
		retry++;
	}

	public void stopMonitoring() throws Exception
	{
		this.stopExecutors();
		if (channel != null)
		{
//			ByteBuf b = Unpooled.buffer();
//			b.writeInt(MiddlewareConstants.PACKET_STOP_MONITORING);
//			b.writeInt(0);
//			channel.writeAndFlush(b);

			MiddlewarePacket packet = new MiddlewarePacket(MiddlewareConstants.PACKET_STOP_MONITORING);
			channel.writeAndFlush(packet);
		}
		Log.debug("Stop monitoring packet sent.");

		if (txLogFileRaw != null && txLogFileRaw.exists())
		{
			txLogFileRaw.delete();
		}
		if (txZipOutputStream != null)
		{
			txZipOutputStream.closeEntry();
			txZipOutputStream.close();
			txZipOutputStream = null;
		}

		// reset retry count.
		retry = 0;
		isMonitoring = false;
	}

	public File getTxLogFileRaw()
	{
		return txLogFileRaw;
	}

	public void requestServerList() throws Exception
	{
		if (channel != null)
		{
//			ByteBuf b= Unpooled.buffer();
//			b.writeInt(MiddlewareConstants.PACKET_REQUEST_SERVER_LIST);
//			b.writeInt(0);
//			channel.writeAndFlush(b);

			MiddlewarePacket packet = new MiddlewarePacket(MiddlewareConstants.PACKET_REQUEST_SERVER_LIST);
			channel.writeAndFlush(packet);
		}
		Log.debug("Server list request packet sent.");
	}

	public synchronized void requestStatistics(String serverName, int txId, int txType, int stId, long latency, int mode, Set<String> tables, String sql)
	{
		String msg = String.format("%d,%d,%d,%d,%d,%d,", txType, txId, stId, latency, mode, tables.size());
		for (String table : tables)
		{
			msg += table + ",";
		}

		statementMessageMap.put(reqId, msg);

		if (channel != null)
		{
			MiddlewarePacket packet = new MiddlewarePacket(MiddlewareConstants.PACKET_REQUEST_QUERY_STATISTICS, String.format("%s,%d,%d,%s", serverName, reqId, txType, sql));
			channel.writeAndFlush(packet);
		}
		++reqId;
		Log.debug("Table count request packet sent.");
	}

	public void requestTableCount(String serverName, String tableName)
	{
		if (channel != null)
		{
			MiddlewarePacket packet = new MiddlewarePacket(MiddlewareConstants.PACKET_REQUEST_TABLE_COUNT, String.format("%s,%s", serverName, tableName));
			channel.writeAndFlush(packet);
		}
		Log.debug("Table count request packet sent.");
	}

	public void requestNumRowAccessedByQuery(String serverName, int txType, String sql)
	{
		if (channel != null)
		{
			MiddlewarePacket packet = new MiddlewarePacket(MiddlewareConstants.PACKET_REQUEST_NUM_ROW_BY_SQL, String.format("%s,%d,%s", serverName, txType, sql));
			channel.writeAndFlush(packet);
		}
		Log.debug("Num row accessed by sql request packet sent.");
	}

	public synchronized void printQueryStatistics(String serverName, int txType, int reqId, String msg)
	{
		PrintWriter writer = logWriterMap.get(serverName + txType);
		if (writer != null)
		{
			writer.print(statementMessageMap.get(reqId));
			writer.println(msg);
			writer.flush();
		}
		else
		{
			Log.error("Writer null");
		}

		statementMessageMap.remove(reqId);
	}

	public ZipOutputStream startTxLogRequester() throws Exception
	{
		if (requesterExecutor == null)
		{
			requesterExecutor = Executors.newCachedThreadPool();
		}
		txLogRequester =
				new MiddlewareClientLogRequester(channel, MiddlewareConstants.PACKET_REQUEST_TX_LOG);

		requesterExecutor.submit(txLogRequester);

		File dbLogFile = new File(logPath + File.separator + MiddlewareConstants.TX_LOG_ZIP);
		txLogFileRaw = new File(logPath + File.separator + MiddlewareConstants.TX_LOG_RAW);
		txPrintWriter = new PrintWriter(new FileWriter(txLogFileRaw, false));
		FileOutputStream fos = new FileOutputStream(dbLogFile);
		txZipOutputStream = new ZipOutputStream(new BufferedOutputStream(fos));

		try
		{
			txZipOutputStream.putNextEntry(new ZipEntry(MiddlewareConstants.TX_LOG_RAW));
		}
		catch (Exception e)
		{
			Log.error(e.getMessage());
			e.printStackTrace();
		}

		Log.debug("Tx Log requester launched.");

		return txZipOutputStream;
	}

	public PrintWriter getTxPrintWriter()
	{
		return txPrintWriter;
	}

	public Map<String, PrintWriter> startSysLogRequester(String serverStr) throws Exception
	{
		if (requesterExecutor == null)
		{
			requesterExecutor = Executors.newCachedThreadPool();
		}

		Map<String, PrintWriter> writers = new HashMap<>();
		String[] servers = serverStr.split(MiddlewareConstants.SERVER_STRING_DELIMITER);
		for (String server : servers)
		{
			MiddlewareClientLogRequester logRequester =
					new MiddlewareClientLogRequester(channel, MiddlewareConstants.PACKET_REQUEST_SYS_LOG, server);

			requesterExecutor.submit(logRequester);
			sysLogRequester.put(server, logRequester);

			File sysLogFile = new File(logPath + File.separator + MiddlewareConstants.SYS_LOG_PREFIX + "." + server);
			PrintWriter writer = new PrintWriter(new FileWriter(sysLogFile, false));
			writers.put(server, writer);
			serverNameList.add(server);
		}

		Log.debug("Sys Log requesters launched.");

		return writers;
	}

	public void startHeartbeatSender() throws Exception
	{
		heartbeatSender = new MiddlewareClientHeartbeatSender(channel);
		heartbeatSenderExecutor = Executors.newSingleThreadExecutor();
		heartbeatSenderExecutor.submit(heartbeatSender);
		Log.debug("heartbeat sender launched.");
	}

	public void stopExecutors()
	{
		if (requesterExecutor != null)
		{
			requesterExecutor.shutdownNow();
		}
		if (heartbeatSenderExecutor != null)
		{
			heartbeatSenderExecutor.shutdownNow();
		}
		txLogRequester = null;
		sysLogRequester = new HashMap<>();

		// clear server names.
		this.serverNameList.clear();
	}

	public ZipOutputStream getTxZipOutputStream()
	{
		return txZipOutputStream;
	}

	public void setMonitoring(boolean monitoring)
	{
		isMonitoring = monitoring;
		setChanged();
		MiddlewareClientEvent event;
		if (isMonitoring)
		{
			event = new MiddlewareClientEvent(MiddlewareClientEvent.IS_MONITORING);
		}
		else
		{
			event = new MiddlewareClientEvent(MiddlewareClientEvent.IS_NOT_MONITORING);
		}
		notifyObservers(event);
	}

	public void setMonitoring(boolean monitoring, String serverStr)
	{
		isMonitoring = monitoring;
		setChanged();
		MiddlewareClientEvent event;
		if (isMonitoring)
		{
			event = new MiddlewareClientEvent(MiddlewareClientEvent.IS_MONITORING);
		}
		else
		{
			event = new MiddlewareClientEvent(MiddlewareClientEvent.IS_NOT_MONITORING);
		}
		event.serverStr = serverStr;
		notifyObservers(event);
	}

	public void setTableRowCount(String serverName, String tableName, long rowCount)
	{
		MiddlewareClientEvent event = new MiddlewareClientEvent(MiddlewareClientEvent.TABLE_ROW_COUNT, serverName, tableName, rowCount);
		setChanged();
		notifyObservers(event);
	}

	public boolean isMonitoring()
	{
		return isMonitoring;
	}

	public void registerLogWriter(String id, PrintWriter writer)
	{
		logWriterMap.put(id, writer);
	}

	public void disconnect() throws Exception
	{
		if (channel != null && channel.isActive())
		{
			channel.disconnect();
		}
	}

	public ArrayList<String> getServerNameList()
	{
		return serverNameList;
	}
}
<file_sep>/src/dbseer/middleware/event/MiddlewareClientEvent.java
/*
 * Copyright 2013 <NAME>
 *
 * Licensed 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 dbseer.middleware.event;

import java.util.List;

/**
 * Created by <NAME> on 1/3/16.
 */
public class MiddlewareClientEvent
{
	public static final int IS_MONITORING = 1;
	public static final int IS_NOT_MONITORING = 2;
	public static final int ERROR = 3;
	public static final int TABLE_ROW_COUNT = 4;

	public int event;
	public long count;
	public Exception e;
	public String error;
	public String serverStr;
	public String serverName;
	public String tableName;

	public MiddlewareClientEvent(int event)
	{
		this.event = event;
	}

	public MiddlewareClientEvent(int event, Exception e)
	{
		this.event = event;
		this.e = e;
	}

	public MiddlewareClientEvent(int event, String serverName, String tableName, long count)
	{
		this.event = event;
		this.serverName = serverName;
		this.tableName = tableName;
		this.count = count;
	}

	public MiddlewareClientEvent(int event, String error)
	{
		this.event = event;
		this.error = error;
	}
}
<file_sep>/middleware.sh
#!/bin/bash
BIN_PATH=bin:lib/*
java -cp "$BIN_PATH" dbseer.middleware.server.MiddlewareServer $@
<file_sep>/rs-sysmon2/plugins/dstat_qmail.py
### Author: <NAME> <tom$ctors,net>

class dstat_plugin(dstat):
    """
    port of qmail_qstat to dstat
    """
    def __init__(self):
        self.name = 'qmail'
        self.nick = ('in_queue', 'not_prep')
        self.vars = ('mess', 'todo')
        self.type = 'd'
        self.width = 4
        self.scale = 100

    def check(self):
        for item in self.vars:
            if not os.access('/var/qmail/queue/'+item, os.R_OK):
                raise Exception, 'Cannot access qmail queues'

    def extract(self):
        for item in self.vars:
            self.val[item] = len(glob.glob('/var/qmail/queue/'+item+'/*/*'))

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_power.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Power usage information from ACPI.

    Displays the power usage in watt per hour of your system's battery using
    ACPI information. This information is only available when the battery is
    being used (or being charged).
    """

    def __init__(self):
        self.name = 'power'
        self.nick = ( 'usage', )
        self.vars = ( 'rate', )
        self.type = 'f'
        self.width = 5
        self.scale = 1
        self.rate = 0
        self.batteries = []
        for battery in os.listdir('/proc/acpi/battery/'):
            for line in dopen('/proc/acpi/battery/'+battery+'/state').readlines():
                l = line.split()
                if len(l) < 2: continue
                self.batteries.append(battery)
                break

    def check(self):
        if not self.batteries:
            raise Exception, 'No battery information found, no power usage statistics'

    def extract(self):
        amperes_drawn = 0
        voltage = 0
        watts_drawn = 0
        for battery in self.batteries:
            for line in dopen('/proc/acpi/battery/'+battery+'/state').readlines():
                l = line.split()
                if len(l) < 3: continue
                if l[0] == 'present:' and l[1] != 'yes': continue
                if l[0:2] == ['charging','state:'] and l[2] != 'discharging':
                    voltage = 0
                    break
                if l[0:2] == ['present','voltage:']:
                    voltage = int(l[2]) / 1000.0
                elif l[0:2] == ['present','rate:'] and l[3] == 'mW':
                    watts_drawn = int(l[2]) / 1000.0
                elif l[0:2] == ['present','rate:'] and l[3] == 'mA':
                    amperes_drawn = int(l[2]) / 1000.0

            self.rate = self.rate + watts_drawn + voltage * amperes_drawn

        ### Return error if we found no information
        if self.rate == 0:
            self.rate = -1

        if op.update:
            self.val['rate'] = self.rate / elapsed
        else:
            self.val['rate'] = self.rate

        if step == op.delay:
            self.rate = 0

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_disk_tps.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Number of read and write transactions per device.

    Displays the number of read and write I/O transactions per device.
    """

    def __init__(self):
        self.nick = ('reads', 'writs' )
        self.type = 'd'
        self.scale = 1000
        self.diskfilter = re.compile('^(dm-\d+|md\d+|[hsv]d[a-z]+\d+)$')
        self.open('/proc/diskstats')
        self.cols = 2

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines():
            if len(l) < 13: continue
            if l[3:] == ['0',] * 11: continue
            name = l[2]
            ret.append(name)
        for item in objlist: ret.append(item)
        if not ret:
            raise Exception, "No suitable block devices found to monitor"
        return ret

    def vars(self):
        ret = []
        if op.disklist:
            varlist = op.disklist
        elif not op.full:
            varlist = ('total',)
        else:
            varlist = []
            for name in self.discover:
                if self.diskfilter.match(name): continue
                if name not in blockdevices(): continue
                varlist.append(name)
#           if len(varlist) > 2: varlist = varlist[0:2]
            varlist.sort()
        for name in varlist:
            if name in self.discover + ['total'] + op.diskset.keys():
                ret.append(name)
        return ret

    def name(self):
        return ['dsk/'+sysfs_dev(name) for name in self.vars]

    def extract(self):
        for name in self.vars: self.set2[name] = (0, 0)
        for l in self.splitlines():
            if len(l) < 13: continue
            if l[3] == '0' and l[7] == '0': continue
            name = l[2]
            if l[3:] == ['0',] * 11: continue
            if not self.diskfilter.match(name):
                self.set2['total'] = ( self.set2['total'][0] + long(l[3]), self.set2['total'][1] + long(l[7]) )
            if name in self.vars and name != 'total':
                self.set2[name] = ( self.set2[name][0] + long(l[3]), self.set2[name][1] + long(l[7]))
            for diskset in self.vars:
                if diskset in op.diskset.keys():
                    for disk in op.diskset[diskset]:
                        if re.match('^'+disk+'$', name):
                            self.set2[diskset] = ( self.set2[diskset][0] + long(l[3]), self.set2[diskset][1] + long(l[7]) )
        for name in self.set2.keys():
            self.val[name] = (
                (self.set2[name][0] - self.set1[name][0]) / elapsed,
                (self.set2[name][1] - self.set1[name][1]) / elapsed,
            )
        if step == op.delay:
            self.set1.update(self.set2)

# S_VALUE(ioj->rd_ios, ioi->rd_ios, itv),
# S_VALUE(ioj->wr_ios, ioi->wr_ios, itv),

<file_sep>/rs-sysmon2/plugins/dstat_net_packets.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Number of packets received and send per interface.
    """

    def __init__(self):
        self.nick = ('#recv', '#send')
        self.type = 'f'
        self.width = 5
        self.scale = 1000
        self.totalfilter = re.compile('^(lo|bond\d+|face|.+\.\d+)$')
        self.open('/proc/net/dev')
        self.cols = 2

    def discover(self, *objlist):
        ret = []
        for l in self.splitlines(replace=':'):
            if len(l) < 17: continue
            if l[2] == '0' and l[10] == '0': continue
            name = l[0]
            if name not in ('lo', 'face'):
                ret.append(name)
        ret.sort()
        for item in objlist: ret.append(item)
        return ret

    def vars(self):
        ret = []
        if op.netlist:
            varlist = op.netlist
        elif not op.full:
            varlist = ('total',)
        else:
            varlist = self.discover
#           if len(varlist) > 2: varlist = varlist[0:2]
            varlist.sort()
        for name in varlist:
            if name in self.discover + ['total', 'lo']:
                ret.append(name)
        if not ret:
            raise Exception, "No suitable network interfaces found to monitor"
        return ret

    def name(self):
        return ['pkt/'+name for name in self.vars]

    def extract(self):
        self.set2['total'] = [0, 0]
        for l in self.splitlines(replace=':'):
            if len(l) < 17: continue
            if l[2] == '0' and l[10] == '0': continue
            name = l[0]
            if name in self.vars :
                self.set2[name] = ( long(l[2]), long(l[10]) )
            if not self.totalfilter.match(name):
                self.set2['total'] = ( self.set2['total'][0] + long(l[2]), self.set2['total'][1] + long(l[10]))
        if update:
            for name in self.set2.keys():
                self.val[name] = (
                    (self.set2[name][0] - self.set1[name][0]) * 1.0 / elapsed,
                    (self.set2[name][1] - self.set1[name][1]) * 1.0 / elapsed,
                 )
        if step == op.delay:
            self.set1.update(self.set2)
<file_sep>/rs-sysmon2/Makefile
name = dstat
version = $(shell awk '/^Version: / {print $$2}' $(name).spec)

prefix = /usr
sysconfdir = /etc
bindir = $(prefix)/bin
datadir = $(prefix)/share
mandir = $(datadir)/man

.PHONY: all install docs clean

all: docs
	@echo "Nothing to be build."

docs:
	$(MAKE) -C docs docs

install:
#	-[ ! -f $(DESTDIR)$(sysconfdir)/dstat.conf ] && install -D -m0644 dstat.conf $(DESTDIR)$(sysconfdir)/dstat.conf
	install -Dp -m0755 dstat $(DESTDIR)$(bindir)/dstat
	install -d -m0755 $(DESTDIR)$(datadir)/dstat/
	install -Dp -m0755 dstat $(DESTDIR)$(datadir)/dstat/dstat.py
	install -Dp -m0644 plugins/dstat_*.py $(DESTDIR)$(datadir)/dstat/
#	install -d -m0755 $(DESTDIR)$(datadir)/dstat/examples/
#	install -Dp -m0755 examples/*.py $(DESTDIR)$(datadir)/dstat/examples/
	install -Dp -m0644 docs/dstat.1 $(DESTDIR)$(mandir)/man1/dstat.1

docs-install:
	$(MAKE) -C docs install

clean:
	rm -f examples/*.pyc plugins/*.pyc
	$(MAKE) -C docs clean

dist: clean
	$(MAKE) -C docs dist
	svn up
	svn list -R | pax -d -w -x ustar -s ,^,$(name)-$(version)/, | bzip2 >../$(name)-$(version).tar.bz2

rpm: dist
	rpmbuild -tb --clean --rmspec --define "_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" --define "_rpmdir ../" ../$(name)-$(version).tar.bz2

srpm: dist
	rpmbuild -ts --clean --rmspec --define "_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" --define "_srcrpmdir ../" ../$(name)-$(version).tar.bz2
<file_sep>/rs-sysmon2/plugins/dstat_dstat_ctxt.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Provide Dstat's number of voluntary and involuntary context switches.

    This plugin provides a unique view of the number of voluntary and
    involuntary context switches of the Dstat process itself. It may help
    to vizualise the performance of Dstat and its selection of plugins.
    """
    def __init__(self):
        self.name = 'contxt sw'
        self.vars = ('voluntary', 'involuntary', 'total')
        self.type = 'd'
        self.width = 3
        self.scale = 100

    def extract(self):
        res = resource.getrusage(resource.RUSAGE_SELF)

        self.set2['voluntary'] = float(res.ru_nvcsw)
        self.set2['involuntary'] = float(res.ru_nivcsw)
        self.set2['total'] = (float(res.ru_nvcsw) + float(res.ru_nivcsw))

        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed

        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_dbus.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Number of active dbus sessions.
    """
    def __init__(self):
        self.name = 'dbus'
        self.nick = ('sys', 'ses')
        self.vars = ('system', 'session')
        self.type = 'd'
        self.width = 3
        self.scale = 100

    def check(self):
#       dstat.info(1, 'The dbus module is an EXPERIMENTAL module.')
        try:
            global dbus
            import dbus
            try:
                self.sysbus = dbus.Bus(dbus.Bus.TYPE_SYSTEM).get_service('org.freedesktop.DBus').get_object('/org/freedesktop/DBus', 'org.freedesktop.DBus')
                try:
                    self.sesbus = dbus.Bus(dbus.Bus.TYPE_SESSION).get_service('org.freedesktop.DBus').get_object('/org/freedesktop/DBus', 'org.freedesktop.DBus')
                except:
                    self.sesbus = None
            except:
                raise Exception, 'Unable to connect to dbus message bus'
        except:
            raise Exception, 'Needs python-dbus module'

    def extract(self):
        self.val['system'] = len(self.sysbus.ListServices()) - 1
        try:
            self.val['session'] = len(self.sesbus.ListServices()) - 1
        except:
            self.val['session'] = -1
#       print dir(b); print dir(s); print dir(d); print d.ListServices()
#       print dir(d)
#       print d.ListServices()

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_rpcd.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'rpc server'
        self.nick = ('call', 'erca', 'erau', 'ercl', 'xdrc')
        self.vars = ('calls', 'badcalls', 'badauth', 'badclnt', 'xdrcall')
        self.type = 'd'
        self.width = 5
        self.scale = 1000
        self.open('/proc/net/rpc/nfsd')

    def extract(self):
        for l in self.splitlines():
            if not l or l[0] != 'rpc': continue
            for i, name in enumerate(self.vars):
                self.set2[name] = long(l[i+1])

        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed

        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_vz_ubc.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    def __init__(self):
        self.nick = ('fcnt', )
        self.type = 'd'
        self.width = 5
        self.scale = 1000
        self.open('/proc/user_beancounters')
        self.cols = 1 ### Is this correct ?

    def check(self):
        info(1, 'Module %s is still experimental.' % self.filename)

    def discover(self, *list):
        ret = []
        for l in self.splitlines():
            if len(l) < 7 or l[0] in ('uid', '0:'): continue
            ret.append(l[0][0:-1])
        ret.sort()
        for item in list: ret.append(item)
        return ret

    def name(self):
        ret = []
        for name in self.vars:
            if name == 'total':
                ret.append('total failcnt')
            else:
                ret.append(name)
        return ret

    def vars(self):
        ret = []
        if not op.full:
            list = ('total', )
        else: 
            list = self.discover
        for name in list: 
            if name in self.discover + ['total']:
                ret.append(name)
        return ret

    def extract(self):
        for name in self.vars + ['total']:
            self.set2[name] = 0
        for l in self.splitlines():
            if len(l) < 6 or l[0] == 'uid':
                continue
            elif len(l) == 7:
                name = l[0][0:-1]
                if name in self.vars:
                    self.set2[name] = self.set2[name] + long(l[6])
                self.set2['total'] = self.set2['total'] + long(l[6])
            elif name == '0':
                continue
            else:
                if name in self.vars:
                    self.set2[name] = self.set2[name] + long(l[5])
                self.set2['total'] = self.set2['total'] + long(l[5])
        for name in self.vars:
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed
        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4:et
<file_sep>/rs-sysmon2/plugins/dstat_memcache_hits.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    """
    Memcache hit count plugin.

    Displays the number of memcache get_hits and get_misses.
    """
    def __init__(self):
        self.name = 'Memcache Hits'
        self.nick = ('Hit', 'Miss')
        self.vars = ('get_hits', 'get_misses')
        self.type = 'd'
        self.width = 6
        self.scale = 50

    def check(self):
        try:
            global memcache
            import memcache
            self.mc = memcache.Client(['127.0.0.1:11211'], debug=0)
        except:
            raise Exception, 'Plugin needs the memcache module'

    def extract(self):
        stats = self.mc.get_stats()
        for key in self.vars:
            self.val[key] = long(stats[0][1][key])
<file_sep>/src/dbseer/middleware/test/MiddlewareClientTest.java
/*
 * Copyright 2013 <NAME>
 *
 * Licensed 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 dbseer.middleware.test;

import com.esotericsoftware.minlog.Log;
import dbseer.middleware.client.MiddlewareClient;
import org.apache.commons.cli.*;

import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * Created by <NAME> on 12/1/15.
 *
 * The test class for testing the middleware client.
 */
public class MiddlewareClientTest
{
	public static void main(String[] args)
	{
		ExecutorService clientExecutor = Executors.newSingleThreadExecutor();

		// set up logger
		Log.set(Log.LEVEL_DEBUG);

		// handle command-line options
		CommandLineParser clParser = new DefaultParser();
		Options options = new Options();

		Option idOption = Option.builder("i")
				.hasArg()
				.argName("ID")
				.required(true)
				.desc("middleware user id")
				.build();

		Option passwordOption = Option.builder("w")
				.hasArg()
				.argName("PASSWORD")
				.required(true)
				.desc("middleware user password")
				.build();

		Option hostOption = Option.builder("h")
				.hasArg()
				.argName("HOST")
				.required(true)
				.desc("middleware hostname")
				.build();

		Option portOption = Option.builder("p")
				.hasArg()
				.argName("PORT")
				.required(true)
				.desc("middleware port")
				.build();

		Option logOption = Option.builder("d")
				.hasArg()
				.argName("FILE")
				.required(true)
				.desc("path to print logs")
				.build();

		Option helpOption = Option.builder("?")
				.longOpt("help")
				.required(false)
				.desc("print this message")
				.build();

		options.addOption(idOption);
		options.addOption(passwordOption);
		options.addOption(hostOption);
		options.addOption(portOption);
		options.addOption(logOption);
		options.addOption(helpOption);

		HelpFormatter formatter = new HelpFormatter();
		try
		{
			CommandLine line = clParser.parse(options, args);
			if (line.hasOption("?"))
			{
				formatter.printHelp("MiddlewareClientTest", options, true);
				return;
			}

			int port;
			String host, logPath, id, password;

			id = line.getOptionValue("i");
			password = line.getOptionValue("w");
			port = Integer.parseInt(line.getOptionValue("p"));
			host = line.getOptionValue("h");
			logPath = line.getOptionValue("d");

			MiddlewareClient client = new MiddlewareClient(host, id, password, port, logPath);
			client.setLogLevel(Log.LEVEL_DEBUG);

			Future clientFuture = clientExecutor.submit(client);
			Thread.sleep(1000);
			Scanner scanner = new Scanner(System.in);
			while (scanner.hasNext())
			{
				String input = scanner.nextLine();
				if (input.equalsIgnoreCase("s"))
				{
					client.startMonitoring();
				}
				else if (input.equalsIgnoreCase("t"))
				{
					client.stopMonitoring();
				}
				else if (input.equalsIgnoreCase("q"))
				{
					clientExecutor.shutdownNow();
					break;
				}
			}
			clientFuture.get();
		}
		catch (ParseException e)
		{
			formatter.printHelp("MiddlewareClientTest", options, true);
			Log.error(e.getMessage());
		}
		catch (Exception e)
		{
			Log.error(e.getMessage());
			e.printStackTrace();
		}

	}
}
<file_sep>/rs-sysmon2/monitor.sh
echo "------------------- MONITORING TOOl --------------"

#. ./setenv

echo $$ > ./monitor.pid

rm $DSTAT_OUTPUT_PATH 2> /dev/null

exec ./dstat --noupdate -T -l -f -c -m -n -d -r --aio -s -g --vm --fs  -i -y -p --disk-util --top-mysql-cpu --mysql5-all1 --mysql5-log-size --mysql-ndb --client-events --output $DSTAT_OUTPUT_PATH

#if [ ""$DSTAT_CONFIGURED != "true" ]
#then
 #echo "I believe you haven't configured the environment variables in the \"setenv\" script yet... please do so and relaunch monitor.sh"
#else
 #echo "I'm recording in "$DSTAT_HOMEDIR"/log_exp_"$DSTAT_EXPERIMENT_ID".csv the system load every "$DSTAT_MONITORING_FREQUENCY" seconds...  Generate your load..."
 #if [ ""$DSTAT_MONITOR_MYSQL == "true" ]
  #then 
   #echo "monitoring mysql"
   #$DSTAT_HOMEDIR/dstat --noupdate -T -l -f -c -m -n -d -r --aio -s -g --vm --fs  -i -y -p --disk-util --top-mysql-cpu --mysql5-all1 --mysql-ndb --client-events --output $DSTAT_HOMEDIR"/../Transactions/log_exp_"$DSTAT_EXPERIMENT_ID".csv"  $DSTAT_MONITORING_FREQUENCY 
 #elif [ ""$DSTAT_MONITOR_POSTGRES == "true" ]
  #then
   #echo "monitoring postgres"
   #$DSTAT_HOMEDIR/dstat --noupdate -T -l -f -c -m -n -d -r --aio -s -g --vm --fs  -i -y -p --disk-util --postgres-all1 --postgres-cpu-bio --output $DSTAT_HOMEDIR"/log_exp_"$DSTAT_EXPERIMENT_ID".csv"  $DSTAT_MONITORING_FREQUENCY
  #else
   #echo "monitoring neither mysql nor postgres"
   #$DSTAT_HOMEDIR/dstat --noupdate -T -l -f -c -m -n -d -r --aio -s -g --vm --fs  -i -y -p --disk-util --client-events --output  $DSTAT_HOMEDIR"/log_exp_"$DSTAT_EXPERIMENT_ID".csv"  $DSTAT_MONITORING_FREQUENCY
  #fi > /dev/null
#fi
#echo "----------------------------------------------------"
<file_sep>/rs-sysmon2/plugins/dstat_vmk_int.py
### Author: <NAME> <bert+dstat$debruijn,be>

### VMware ESX kernel interrupt stats
### Displays kernel interrupt statistics on VMware ESX servers

# NOTE TO USERS: command-line plugin configuration is not yet possible, so I've
# "borrowed" the -I argument. 
# EXAMPLES:
# # dstat --vmkint -I 0x46,0x5a
# You can even combine the Linux and VMkernel interrupt stats
# # dstat --vmkint -i -I 14,0x5a
# Look at /proc/vmware/interrupts to see which interrupt is linked to which function

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'vmkint'
        self.type = 'd'
        self.width = 4
        self.scale = 1000
        self.open('/proc/vmware/interrupts')
#       self.intmap = self.intmap()

#   def intmap(self):
#       ret = {}
#       for line in dopen('/proc/vmware/interrupts').readlines():
#           l = line.split()
#           if len(l) <= self.vmkcpunr: continue
#           l1 = l[0].split(':')[0]
#           l2 = ' '.join(l[vmkcpunr()+1:]).split(',')
#           ret[l1] = l1
#           for name in l2:
#               ret[name.strip().lower()] = l1
#           return ret

    def vmkcpunr(self):
        #the service console sees only one CPU, so cpunr == 1, only the vmkernel sees all CPUs
        ret = []
        # default cpu number is 2
        ret = 2
        for l in self.fd[0].splitlines():
            if l[0] == 'Vector': 
                ret = int( int( l[-1] ) + 1 )
        return ret

    def discover(self):
        #interrupt names are not decimal numbers, but rather hexadecimal numbers like 0x7e
        ret = []
        self.fd[0].seek(0)
        for line in self.fd[0].readlines():
            l = line.split()
            if l[0] == 'Vector': continue
            if len(l) < self.vmkcpunr()+1: continue
            name = l[0].split(':')[0]
            amount = 0
            for i in l[1:1+self.vmkcpunr()]:
                amount = amount + long(i)
            if amount > 20: ret.append(str(name))
        return ret

    def vars(self):
        ret = []
        if op.intlist:
            list = op.intlist
        else:
            list = self.discover
#           len(list) > 5: list = list[-5:]
        for name in list:
            if name in self.discover:
                ret.append(name)
#           elif name.lower() in self.intmap.keys():
#               ret.append(self.intmap[name.lower()])
        return ret

    def check(self): 
        try:
            os.listdir('/proc/vmware')
        except:
            raise Exception, 'Needs VMware ESX'
        info(1, 'The vmkint module is an EXPERIMENTAL module.')

    def extract(self):
        self.fd[0].seek(0)
        for line in self.fd[0].readlines():
            l = line.split()
            if len(l) < self.vmkcpunr()+1: continue
            name = l[0].split(':')[0]
            if name in self.vars:
                self.set2[name] = 0
                for i in l[1:1+self.vmkcpunr()]:
                    self.set2[name] = self.set2[name] + long(i)

        for name in self.set2.keys():
            self.val[name] = (self.set2[name] - self.set1[name]) * 1.0 / elapsed

        if step == op.delay:
            self.set1.update(self.set2)

# vim:ts=4:sw=4
<file_sep>/rs-sysmon2/docs/Makefile
prefix = /usr
datadir = $(prefix)/share
mandir = $(datadir)/man

txttargets = $(shell echo *.txt)
htmltargets = $(patsubst %.txt, %.html, $(txttargets))

all:

dist: docs

docs: dstat.1 $(htmltargets)

install: dstat.1
	install -Dp -m0644 dstat.1 $(DESTDIR)$(mandir)/man1/dstat.1

clean:
	rm -f dstat.1 *.html *.xml

%.1.html: %.1.txt
	asciidoc -d manpage $<

%.html: %.txt
	asciidoc $<

%.1.xml: %.1.txt
	asciidoc -b docbook -d manpage $<

%.1: %.1.xml
	@xmlto man $<

<file_sep>/rs-sysmon2/plugins/dstat_postfix.py
### Author: <NAME> <<EMAIL>>

class dstat_plugin(dstat):
    def __init__(self):
        self.name = 'postfix'
        self.nick = ('inco', 'actv', 'dfrd', 'bnce', 'defr')
        self.vars = ('incoming', 'active', 'deferred', 'bounce', 'defer')
        self.type = 'd'
        self.width = 4
        self.scale = 100

    def check(self):
        if not os.access('/var/spool/postfix/active', os.R_OK):
            raise Exception, 'Cannot access postfix queues'

    def extract(self):
        for item in self.vars:
            self.val[item] = len(glob.glob('/var/spool/postfix/'+item+'/*/*'))

# vim:ts=4:sw=4:et
<file_sep>/src/dbseer/middleware/data/Server.java
/*
 * Copyright 2013 <NAME>
 *
 * Licensed 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 dbseer.middleware.data;

import com.esotericsoftware.minlog.Log;
import dbseer.middleware.constant.MiddlewareConstants;
import dbseer.middleware.log.LogTailer;
import dbseer.middleware.log.LogTailerListener;

import java.io.File;
import java.util.*;
import java.sql.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created by <NAME> on 1/10/16.
 *
 * Server that the middleware monitors (different from MiddlewareServer itself)
 */
public class Server
{
	String name;

	String dbHost;
	String dbPort;
	String dbName;
	String dbUser;
	String dbPassword;

	String sshUser;

	String monitorDir;
	String monitorScript;
	String logPath;
	String url;

	Process monitorProcess;
	File logFile;

	Connection conn;

//	private ArrayBlockingQueue<String> logQueue;
	private LinkedBlockingQueue<String> logQueue;
	private LogTailerListener logTailerListener;
	private LogTailer logTailer;
	private ExecutorService tailerExecutor;

	List<String> tableList;
	Map<String, Long> tableCount;

	public Server(String name, String dbHost, String dbPort, String dbName, String dbUser, String dbPassword, String sshUser, String monitorDir, String monitorScript, String logPath)
	{
		this.name = name;
		this.dbHost = dbHost;
		this.dbPort = dbPort;
		this.dbUser = dbUser;
		this.dbName = dbName;
		this.dbPassword = <PASSWORD>;
		this.sshUser = sshUser;
		this.monitorDir = monitorDir;
		this.monitorScript = monitorScript;
		this.logPath = logPath;
//		this.logQueue = new ArrayBlockingQueue<>(MiddlewareConstants.QUEUE_SIZE);
		this.url = String.format("jdbc:mysql://%s:%s/%s", dbHost, dbPort, dbName);
		this.logQueue = new LinkedBlockingQueue<>();
		this.tableList = new ArrayList<>();
		this.tableCount = new HashMap<>();
	}

	public void printLogInfo()
	{
		Log.info(String.format("[Server : %s]", name));
		Log.info(String.format("DB Host = %s", dbHost));
		Log.info(String.format("DB Port = %s", dbPort));
		Log.info(String.format("DB Name = %s", dbName));
		Log.info(String.format("DB User = %s", dbUser));
		Log.info(String.format("DB PW = %s", dbPassword));
		Log.info(String.format("SSH User = %s", sshUser));
		Log.info(String.format("Remote Monitor Dir = %s", monitorDir));
		Log.info(String.format("Remote Monitor Script = %s", monitorScript));
	}

	public boolean testConnection()
	{
		boolean canConnect = false;
		try
		{
			Class.forName("com.mysql.jdbc.Driver").newInstance();
			conn = (Connection) DriverManager.getConnection(url, dbUser, dbPassword);

			// connection was successful.
			canConnect = true;
		}
		catch (IllegalAccessException e)
		{
			e.printStackTrace();
		}
		catch (InstantiationException e)
		{
			e.printStackTrace();
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (SQLException e)
		{
			Log.debug("Caught a SQLException while testing connection to the server.");
		}

		if (canConnect)
		{
			Log.info(String.format("Getting DB statistics from database '%s' @ '%s'... This may take a few minutes.", dbName, name));
			if (this.getTableList())
			{
				for (String table : tableList)
				{
					this.getTableCount(table);
				}
			} else
			{
				Log.error(String.format("Cannot obtain the list of tables from database '%s'", dbName));
				return false;
			}
		}

		return canConnect;
	}

	private boolean getTableList()
	{
		tableList.clear();
		try
		{
			if (conn == null || conn.isClosed())
			{
				if (!this.testConnection())
				{
					// cannot connect... return -1
					Log.error("No DB Connection.");
					return false;
				}
			}
			String query = String.format("SHOW TABLE STATUS IN %s;", dbName);
			PreparedStatement stmt = conn.prepareStatement(query);
			ResultSet rs = stmt.executeQuery();
			while (rs.next())
			{
				String tableName = rs.getString("Name");
				Long rowCount = rs.getLong("Rows");
				tableList.add(tableName);
				tableCount.put(tableName.toLowerCase(), rowCount.longValue());
			}
		}
		catch (SQLException e)
		{
			Log.debug("Caught a SQLException while getting table names for the database " + dbName);
			return false;
		}
		return true;
	}

	public long getTableCount(String tableName)
	{
		Long count = tableCount.get(tableName.toLowerCase());
		if (count == null)
		{
			return getTableCountFromDatabase(tableName);
		}
		else return count.longValue();

	}

	public long getTableCountFromDatabase(String tableName)
	{
		long count = -1;
		try
		{
			if (conn == null || conn.isClosed())
			{
				if (!this.testConnection())
				{
					// cannot connect... return -1
					Log.error("No DB Connection.");
					return count;
				}
			}
			String query = String.format("SELECT COUNT(*) as ROW_COUNT from %s;", tableName);
			Statement stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery(query);
			if (rs.next())
			{
				count = rs.getInt(1);
			}
			else
			{
				Log.error("No result set");
			}
		}
		catch (SQLException e)
		{
			Log.debug("Caught a SQLException while getting row counts for the table " + tableName);
		}
		if (count != -1)
		{
			tableCount.put(tableName.toLowerCase(), count);
		}
		return count;
	}

	public List<Integer> getNumRowAccessedByQuery(String sql)
	{
		ArrayList<Integer> results = new ArrayList<>();
		int count = 0;
		try
		{
			if (conn == null || conn.isClosed())
			{
				if (!this.testConnection())
				{
					// cannot connect... return -1
					Log.error("No DB Connection.");
					return null;
				}
			}
			String query = String.format("EXPLAIN %s;", sql);
			Statement stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery(query);
			while (rs.next())
			{
				results.add(rs.getInt("rows"));
//				count += rs.getInt("rows");
			}
		}
		catch (SQLException e)
		{
			Log.debug("Caught a SQLException while getting the number of rows accessed for the query: " + sql);
		}

		return results;
	}

    public boolean testMonitoringDir()
    {
		try
		{
			String sshCmd = "ssh";
			String sshConnection = String.format("%s@%s", sshUser, dbHost);
			String sshEndCmd = String.format("cd %s && ls -l ./%s 1> /dev/null", monitorDir, monitorScript);

			String[] cmds = {sshCmd, sshConnection, sshEndCmd};
			ProcessBuilder pb = new ProcessBuilder(cmds);

			monitorProcess = pb.start();
			int retVal = monitorProcess.waitFor();

			if (retVal != 0)
			{
				return false;
			}
		}
		catch (Exception e)
		{
			 e.printStackTrace();
		}
		return true;
    }

	public void startMonitoring() throws Exception
	{
		if (monitorProcess != null)
		{
			monitorProcess.destroy();
		}

		String sshEndCmd = String.format("cd %s && ./%s 1> /dev/null", monitorDir, monitorScript);

		String sshCmd = "ssh";
		String sshConnection = String.format("%s@%s", sshUser, dbHost);
		String cmd = "";
		cmd += String.format("export DSTAT_MYSQL_USER=%s;", dbUser);
		cmd += String.format("export DSTAT_MYSQL_PWD=%s;", dbPassword);
		cmd += String.format("export DSTAT_MYSQL_HOST=%s;", dbHost);
		cmd += String.format("export DSTAT_MYSQL_PORT=%s;", dbPort);
		cmd += String.format("export DSTAT_OUTPUT_PATH=%s;", "/dev/fd/2");
		cmd += sshEndCmd;

		String[] cmds = {sshCmd, sshConnection, cmd};

		ProcessBuilder pb = new ProcessBuilder(cmds);
		logFile = new File(logPath + File.separator + String.format("sys.log.%s", name));

		pb.redirectErrorStream(true);
		pb.redirectOutput(ProcessBuilder.Redirect.to(logFile));

		monitorProcess = pb.start();

		// start tailer
		if (tailerExecutor != null)
		{
			tailerExecutor.shutdownNow();
		}

		logTailerListener = new LogTailerListener(logQueue, false);
		logTailer = new LogTailer(logFile, logTailerListener, 250, 0, false);
		tailerExecutor = Executors.newFixedThreadPool(1);
		tailerExecutor.submit(logTailer);

		Log.debug("dstat started remotely.");
	}

	public void stopMonitoring() throws Exception
	{
		if (monitorProcess != null)
		{
			monitorProcess.destroy();
		}

		if (tailerExecutor != null)
		{
			tailerExecutor.shutdownNow();
		}
	}

	public String getDbName()
	{
		return dbName;
	}

	public void setDbName(String dbName)
	{
		this.dbName = dbName;
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public String getDbPort()
	{
		return dbPort;
	}

	public void setDbPort(String dbPort)
	{
		this.dbPort = dbPort;
	}

	public String getDbUser()
	{
		return dbUser;
	}

	public void setDbUser(String dbUser)
	{
		this.dbUser = dbUser;
	}

	public String getSshUser()
	{
		return sshUser;
	}

	public void setSshUser(String sshUser)
	{
		this.sshUser = sshUser;
	}

	public String getDbHost()
	{
		return dbHost;
	}

	public void setDbHost(String dbHost)
	{
		this.dbHost = dbHost;
	}

	public String getMonitorDir()
	{
		return monitorDir;
	}

	public void setMonitorDir(String monitorDir)
	{
		this.monitorDir = monitorDir;
	}

	public String getMonitorScript()
	{
		return monitorScript;
	}

	public void setMonitorScript(String monitorScript)
	{
		this.monitorScript = monitorScript;
	}

	public Process getMonitorProcess()
	{
		return monitorProcess;
	}

	public void setMonitorProcess(Process monitorProcess)
	{
		this.monitorProcess = monitorProcess;
	}

	public LogTailerListener getLogTailerListener()
	{
		return logTailerListener;
	}

	public LinkedBlockingQueue<String> getLogQueue()
	{
		return logQueue;
	}
}
