All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bigdata.counters.linux.PIDStatCollector Maven / Gradle / Ivy

/*

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     [email protected]

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
/*
 * Created on Mar 26, 2008
 */

package com.bigdata.counters.linux;

import com.bigdata.counters.*;
import com.bigdata.counters.linux.SarCpuUtilizationCollector.DI;
import com.bigdata.util.Bytes;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Collects statistics on the JVM process relating to CPU, memory, and IO
 * statistics (when available) using pidstat -p 501 -u -I -r -d -w
 * [[interval [count]]
 * 

* Where -p is the pid to monitor, -u is cpu * utilization (-I normalizes to 100% for SMP), -r * gives the process memory statistics, -d gives IO statistics with * kernels 2.6.20 and up; -w gives context switching data; The * interval is in seconds. The count is optional - when missing or zero will * repeat forever if interval was specified. * * @author Bryan Thompson */ public class PIDStatCollector extends AbstractProcessCollector implements ICounterHierarchy, IProcessCounters { // static protected final Logger log = Logger.getLogger(PIDStatCollector.class); // // /** // * True iff the {@link #log} level is DEBUG or less. // */ // final protected static boolean DEBUG = log.isDebugEnabled(); // // /** // * True iff the {@link #log} level is log.isInfoEnabled() or less. // */ // final protected static boolean log.isInfoEnabled() = log.isInfoEnabled(); /** process to be monitored. */ protected final int pid; /** * set true if per-process IO data collection should be * supported based on the {@link KernelVersion}. */ protected final boolean perProcessIOData; /** * Abstract inner class integrating the current values with the {@link ICounterSet} * hierarchy. * * @author Bryan Thompson */ abstract class AbstractInst implements IInstrument { protected final String path; final public String getPath() { return path; } protected AbstractInst(final String path) { if (path == null) throw new IllegalArgumentException(); this.path = path; } @Override final public long lastModified() { return lastModified.get(); } /** * @throws UnsupportedOperationException * always. */ @Override final public void setValue(final T value, final long timestamp) { throw new UnsupportedOperationException(); } } /** * Inner class integrating the current values with the {@link ICounterSet} * hierarchy. * * @author Bryan Thompson */ class IL extends AbstractInst { protected final long scale; public IL(final String path, final long scale) { super( path ); this.scale = scale; } @Override public Long getValue() { final Long value = (Long) vals.get(path); // no value is defined. if (value == null) return 0L; final long v = value.longValue() * scale; return v; } } /** * Inner class integrating the current values with the {@link ICounterSet} * hierarchy. * * @author Bryan Thompson */ class ID extends AbstractInst { protected final double scale; public ID(final String path, final double scale) { super(path); this.scale = scale; } @Override public Double getValue() { final Double value = (Double) vals.get(path); // no value is defined. if (value == null) return 0d; final double d = value.doubleValue() * scale; return d; } } /** * Updated each time a new row of data is read from the process and reported * as the last modified time for counters based on that process and * defaulted to the time that we begin to collect performance data. */ private final AtomicLong lastModified = new AtomicLong(System.currentTimeMillis()); /** * Map containing the current values for the configured counters. The keys * are paths into the {@link CounterSet}. The values are the data most * recently read from pidstat. *

* Note: The paths are in fact relative to how the counters are declared by * {@link #getCounters()}. Likewise {@link DI#getValue()} uses the paths * declared within {@link #getCounters()} and not whatever path the counters * are eventually placed under within a larger hierarchy. */ private final Map vals = new ConcurrentHashMap(); /** * @param pid * Process to be monitored. * @param interval * Reporting interval in seconds. * @param kernelVersion * The Linux {@link KernelVersion}. * * @todo kernelVersion could be static. */ public PIDStatCollector(final int pid, final int interval, final KernelVersion kernelVersion) { super(interval); if (interval <= 0) throw new IllegalArgumentException(); if (kernelVersion == null) throw new IllegalArgumentException(); this.pid = pid; perProcessIOData = kernelVersion.version > 2 || (kernelVersion.version == 2 && kernelVersion.major >= 6 && kernelVersion.minor >= 20); } @Override public List getCommand() { final List command = new LinkedList(); command.add(SysstatUtil.getPath("pidstat").getPath()); command.add("-p"); command.add(""+pid); command.add("-u"); // CPU stats command.add("-I"); // normalize CPU stats to 100% iff SMP. if(perProcessIOData) { command.add("-d"); // disk IO report. } command.add("-r"); // memory report // command.add("-w"); // context switching report (not implemented in our code). command.add("" + getInterval()); return command; } @Override public CounterSet getCounters() { final List> inst = new LinkedList>(); /* * Note: Counters are all declared as Double to facilitate aggregation * and scaling. * * Note: pidstat reports percentages as [0:100] so we normalize them to * [0:1] using a scaling factor. */ inst.add(new ID(IProcessCounters.CPU_PercentUserTime, .01d)); inst.add(new ID(IProcessCounters.CPU_PercentSystemTime, .01d)); inst.add(new ID(IProcessCounters.CPU_PercentProcessorTime, .01d)); inst.add(new ID(IProcessCounters.Memory_minorFaultsPerSec, 1d)); inst.add(new ID(IProcessCounters.Memory_majorFaultsPerSec, 1d)); inst.add(new IL(IProcessCounters.Memory_virtualSize, Bytes.kilobyte)); inst.add(new IL(IProcessCounters.Memory_residentSetSize, Bytes.kilobyte)); inst.add(new ID(IProcessCounters.Memory_percentMemorySize, .01d)); /* * Note: pidstat reports in kb/sec so we normalize to bytes/second using * a scaling factor. */ inst.add(new ID(IProcessCounters.PhysicalDisk_BytesReadPerSec, Bytes.kilobyte32)); inst.add(new ID(IProcessCounters.PhysicalDisk_BytesWrittenPerSec, Bytes.kilobyte32)); final CounterSet root = new CounterSet(); for (AbstractInst i : inst) { root.addCounter(i.getPath(), i); } return root; } /** * Extended to force pidstat to use a consistent timestamp * format regardless of locale by setting S_TIME_FORMAT="ISO" * in the environment. */ @Override protected void setEnvironment(final Map env) { super.setEnvironment(env); env.put("S_TIME_FORMAT", "ISO"); } @Override public AbstractProcessReader getProcessReader() { return new PIDStatReader(); } /** * Reads pidstat output and extracts and updates counter * values. *

* Sample pidstat output. * *

     *         Linux 2.6.22.14-72.fc6 (hostname)    03/16/2008
     *         
     *         06:35:15 AM       PID   %user %system    %CPU   CPU  Command
     *         06:35:15 AM       501    0.00    0.01    0.00     1  kjournald
     *         
     *         06:35:15 AM       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
     *         06:35:15 AM       501      0.00      0.00       0      0   0.00  kjournald
     *         
     *         06:35:15 AM       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
     *         06:35:15 AM       501      0.00      1.13      0.00  kjournald
     *         
     *         06:35:15 AM       PID   cswch/s nvcswch/s  Command
     *         06:35:15 AM       501      0.00      0.00  kjournald
     * 
* * @author Bryan Thompson */ protected class PIDStatReader extends ProcessReaderHelper { private static final String PIDSTAT_FIELD_CPU_PERCENT_USR = "%usr"; private static final String PIDSTAT_FIELD_CPU_PERCENT = "%CPU"; private static final String PIDSTAT_FIELD_CPU_PERCENT_SYSTEM = "%system"; private static final String PIDSTAT_FIELD_MEM_MINOR_FAULTS_PERS = "minflt/s"; private static final String PIDSTAT_FIELD_MEM_MAJOR_FAULTS_PERS = "majflt/s"; private static final String PIDSTAT_FIELD_MEM_VIRTUAL_SIZE = "VSZ"; private static final String PIDSTAT_FIELD_MEM_RESIDENT_SET_SIZE = "RSS"; private static final String PIDSTAT_FIELD_MEM_SIZE_PERCENT = "%MEM"; private static final String PIDSTAT_FIELD_DISK_KB_READ_PERS = "kB_rd/s"; private static final String PIDSTAT_FIELD_DISK_KB_WRITTEN_PERS = "kB_wr/s"; @Override protected ActiveProcess getActiveProcess() { if (activeProcess == null) throw new IllegalStateException(); return activeProcess; } public PIDStatReader() { super(); } /** * The input arrives as an initial banner and a sequence of data * reporting events, each of which is three lines. The first * line is blank. The second line gives the headers for the * event. The third line gives the data for the event. The 2nd * and 3rd lines carry a timestamp as the first value. *

* When you request more than one kind of reporting, the three * line set simply repeats for each class of events that are to * be reported. E.g., cpu and io. This code diagnoses the event * types by examining the header line. * * @todo since we are examining header lines and since * sysstat is ported to many languages, it * is possible that this will not work when the host is * using an English locale. */ @Override protected void readProcess() throws IOException, InterruptedException { if(log.isInfoEnabled()) log.info("begin"); for(int i=0; i<10 && !getActiveProcess().isAlive(); i++) { if(log.isInfoEnabled()) log.info("waiting for the readerFuture to be set."); Thread.sleep(100/*ms*/); } if(log.isInfoEnabled()) log.info("running"); // skip banner. final String banner = readLine(); if(log.isInfoEnabled()) log.info("banner: "+banner); // #of "events" read. each event is three lines. long n = 0; while(true) { // skip blank line. final String blank = readLine(); assert blank.trim().length() == 0 : "Expecting a blank line"; // header. final String header = readLine(); // data. final String data = readLine(); try { // timestamp // { // // final String s = data.substring(0, 11); // // try { // // lastModified = f.parse(s).getTime(); // // } catch (Exception e) { // // log.warn("Could not parse time: [" + s + "] : " + e); // // // should be pretty close. // lastModified = System.currentTimeMillis(); // // } // // } /* /* * Note: This timestamp should be _very_ close to the value reported * by sysstat. Also, using the current time is MUCH easier and less * error prone than attempting to parse the TIME OF DAY written by * sysstat and correct it into a UTC time by adjusting for the UTC * time of the start of the current day, which is what we would have * to do. */ lastModified.set(System.currentTimeMillis()); final Map fields = SysstatUtil.getDataMap(header, data); if (log.isInfoEnabled()) { StringBuilder sb = new StringBuilder(); for ( Map.Entry e: fields.entrySet()) { sb.append(e.getKey()); sb.append("="); sb.append(e.getValue()); sb.append(", "); } log.info(sb.toString()); log.info(header + ";" + data); } if(fields.containsKey(PIDSTAT_FIELD_CPU_PERCENT)) { /* * CPU data for the specified process. */ // 06:35:15 AM PID %user %system %CPU CPU Command // 06:35:15 AM 501 0.00 0.01 0.00 1 kjournald vals.put(IProcessCounters.CPU_PercentUserTime, Double.parseDouble(fields.get(PIDSTAT_FIELD_CPU_PERCENT_USR))); vals.put(IProcessCounters.CPU_PercentSystemTime, Double.parseDouble(fields.get(PIDSTAT_FIELD_CPU_PERCENT_SYSTEM))); vals.put(IProcessCounters.CPU_PercentProcessorTime, Double.parseDouble(fields.get(PIDSTAT_FIELD_CPU_PERCENT))); } else if(fields.containsKey("RSS")) { /* * Memory data for the specified process. * * 06:35:15 AM PID minflt/s majflt/s VSZ RSS %MEM Command * 06:35:15 AM 501 0.00 0.00 0 0 0.00 kjournald */ vals.put(IProcessCounters.Memory_minorFaultsPerSec, Double.parseDouble(fields.get(PIDSTAT_FIELD_MEM_MINOR_FAULTS_PERS))); vals.put(IProcessCounters.Memory_majorFaultsPerSec, Double.parseDouble(fields.get(PIDSTAT_FIELD_MEM_MAJOR_FAULTS_PERS))); vals.put(IProcessCounters.Memory_virtualSize, Long.parseLong(fields.get(PIDSTAT_FIELD_MEM_VIRTUAL_SIZE))); vals.put(IProcessCounters.Memory_residentSetSize, Long.parseLong(fields.get(PIDSTAT_FIELD_MEM_RESIDENT_SET_SIZE))); vals.put(IProcessCounters.Memory_percentMemorySize, Double.parseDouble(fields.get(PIDSTAT_FIELD_MEM_SIZE_PERCENT))); } else if(perProcessIOData && header.contains("kB_rd/s")) { /* * IO data for the specified process. * * 06:35:15 AM PID kB_rd/s kB_wr/s kB_ccwr/s Command * 06:35:15 AM 501 0.00 1.13 0.00 kjournald */ vals.put(IProcessCounters.PhysicalDisk_BytesReadPerSec, Double.parseDouble(fields.get(PIDSTAT_FIELD_DISK_KB_READ_PERS))); vals.put(IProcessCounters.PhysicalDisk_BytesWrittenPerSec, Double.parseDouble(fields.get(PIDSTAT_FIELD_DISK_KB_WRITTEN_PERS))); } else { log.warn("Could not identify event type from header: ["+header+"]"); continue; } } catch(Exception ex) { /* * Issue warning for parsing problems. */ log.warn(ex.getMessage() // + "\nheader: " + header // + "\n data: " + data // //, ex// ); } n++; } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy