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

com.bigdata.counters.osx.VMStatCollector Maven / Gradle / Ivy

/*

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

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

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

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

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

*/
/*
 * Created on Dec 9, 2008
 */

package com.bigdata.counters.osx;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.bigdata.counters.*;

/**
 * Collects some counters using vmstat.
 * 
 * @author Bryan Thompson
 */
public class VMStatCollector extends AbstractProcessCollector implements
        ICounterHierarchy, IRequiredHostCounters, IHostCounters{

    /**
     * Inner class integrating the current values with the {@link ICounterSet}
     * hierarchy.
     * 
     * @author Bryan
     *         Thompson
     */
    abstract class I implements IInstrument {
        
        protected final String path;
        
        public String getPath() {
            
            return path;
            
        }
        
        public I(final String path) {

            if (path == null)
                throw new IllegalArgumentException();

            this.path = path;
            
        }

        @Override
        public long lastModified() {

            return lastModified.get();
            
        }

        /**
         * @throws UnsupportedOperationException
         *             always.
         */
        @Override
        public void setValue(T value, long timestamp) {
           
            throw new UnsupportedOperationException();
            
        }

    }
    
    /**
     * Double precision counter with scaling factor.
     * 
     * @author Bryan Thompson
     */
    class DI extends I {

        protected final double scale;

        DI(final String path) {

            this(path, 1d);

        }

        DI(final String path, final double scale) {

            super(path);

            this.scale = scale;

        }

        @Override
        public Double getValue() {
         
            final Double value = (Double) vals.get(path);

            // no value is defined.
            if (value == null)
                return 0d;

            final double d = value.doubleValue() * scale;

            return d;
            
        }
        
    }

    /**
     * A map from the name of a column to the order of that column in the output (origin ZERO).
     * This mapping was added when the format was changed to make the parser more robust to such
     * changes.
     * 
     * @see #1125 (OSX vm_stat output has changed)
     */
    private final Map keys = new LinkedHashMap();
    
    /**
     * 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 vmstat.
     */
    private final Map vals = new ConcurrentHashMap();

	/**
	 * The timestamp associated with the most recently collected values.
	 */
    private final AtomicLong lastModified = new AtomicLong(
            System.currentTimeMillis());

	/**
	 * The {@link Pattern} used to split apart the rows read from
	 * vmstat.
	 */
	final static Pattern pattern = Pattern.compile("\\s+");

    /**
     * 
     * @param interval
     *            The interval for the collected statistics.
     */
    public VMStatCollector(final int interval) {

        super(interval);
        
    }

    @Override
    public List getCommand() {

		final List command = new LinkedList();

		command.add("/usr/bin/vm_stat");

		// Note: The configured interval in seconds between reports.
		command.add("" + getInterval());

        return command;
        
    }

    @Override
    public CounterSet getCounters() {
        
		final CounterSet root = new CounterSet();

	    @SuppressWarnings("rawtypes")
        final List inst = new LinkedList();

		/*
		 * Note: Counters are all declared as Double to facilitate aggregation.
		 */

		/*
		 * Note: [pageins] is "the #of blocks swapped in".
		 */
		inst.add(new DI(IRequiredHostCounters.Memory_majorFaultsPerSecond));

		/*
		 * This is an aggregation of the counters for "active", "wire", and
		 * "spec".
		 */
		inst.add(new DI(IHostCounters.Memory_SwapBytesUsed));

		/*
		 * Note: [free] is "the total number of free pages in the system".
		 */
		inst.add(new DI(IHostCounters.Memory_Bytes_Free));

        for (@SuppressWarnings("rawtypes") I i : inst) {

            root.addCounter(i.getPath(), i);

        }

        return root;
        
    }

    @Override
    public AbstractProcessReader getProcessReader() {
        
        return new VMStatReader();
        
    }

   /**
    * Sample output for vm_stat 60, where 60 is the
    * interval. Unlike the linux vmstat, there is no option to
    * suppress the periodic repeat of the header. The header repeats in its
    * entirety every "page" full.
    * 
    * 
    * Mach Virtual Memory Statistics: (page size of 4096 bytes, cache hits 0%)
    *   free active   spec inactive   wire   faults     copy    0fill reactive  pageins  pageout
    * 346061  1246K  51002   196922 190727 1004215K 19835511  525442K  7555575  2897558  2092534 
    * 346423  1247K  50754   196922 189767     2707        0     1263        0        0        0 
    * 343123  1247K  52180   198906 189767     3885        0     1754        0        0        0 
    * 342474  1247K  52101   198978 190116    21739     2013     4651        0        0        0 
    * 342542  1247K  52016   199319 189799     5792       17     2561        0        0        0
    * 
* * For more recent OSX releases (e.g., Yosemite) there are now additional * columns: * *
    * Mach Virtual Memory Statistics: (page size of 4096 bytes)
    *     free   active   specul inactive throttle    wired  prgable   faults     copy    0fill reactive   purged file-backed anonymous cmprssed cmprssor  dcomprs   comprs  pageins  pageout  swapins swapouts
    *    17656  1502912   110218  1329298        0   783121    32302 1122834K 22138464  897721K  4538117  2220029      382716   2559712  1093181   449012  1856773  4298532 67248050   263644   779970   886296
    *    41993  1479331   110174  1329290        0   782241    33940     2115        1     1316        0        0      382671   2536124  1093181   449012        0        0       24        0        0        0
    * 
* * @author Bryan * Thompson */ protected class VMStatReader extends ProcessReaderHelper { @Override protected ActiveProcess getActiveProcess() { if (activeProcess == null) throw new IllegalStateException(); return activeProcess; } public VMStatReader() { super(); } private final Pattern pageSizePattern = Pattern.compile("size of (\\d+) "); /** * The index of the field associated with the "free" counter. */ private final AtomicInteger INDEX_FREE = new AtomicInteger(); /** * The index of the field associated with the "active" counter (in use and * pageable). */ private AtomicInteger INDEX_ACTIVE = new AtomicInteger(); /** * The index of the field associated with "spec" or "specul" (aka * speculative) counter. */ private AtomicInteger INDEX_SPEC = new AtomicInteger(); /** * The index of the field associated with "speculative" counter. */ private AtomicInteger INDEX_WIRED = new AtomicInteger(); /** * The index of the field associated with "pagein" counter. */ private AtomicInteger INDEX_PAGEINS = new AtomicInteger(); /** * The index of the field associated with "pageout" counter. */ private AtomicInteger INDEX_PAGEOUT = new AtomicInteger(); /** * * @see TestParse_vm_stat#test_vmstat_header_and_data_parse() */ @Override protected void readProcess() throws Exception { if (log.isInfoEnabled()) log.info("begin"); for (int i = 0; i < 10 && !getActiveProcess().isAlive(); i++) { if (log.isInfoEnabled()) log.info("waiting for the readerFuture to be set."); Thread.sleep(100/* ms */); } if (log.isInfoEnabled()) log.info("running"); // 1st header: we need the pageSize. final String h0; final int pageSize; { h0 = readLine(); if (log.isInfoEnabled()) log.info("header: " + h0); if (!h0.contains("Mach")) { throw new RuntimeException("Wrong input, expected hader: " + h0); } Matcher matcher = pageSizePattern.matcher(h0); if (matcher.find()) { pageSize = Integer.valueOf(matcher.group(1)); } else { throw new RuntimeException("Failed to extract page size"); } if (pageSize <= 0 || (pageSize % 512 != 0)) throw new RuntimeException("pageSize=" + pageSize); if (log.isInfoEnabled()) log.info("pageSize: " + pageSize); } // read 2nd header and verify expected fields. final String h1; { h1 = readLine(); if (log.isInfoEnabled()) log.info("header: " + h1); final String[] fields = pattern.split(h1.trim(), 0/* limit */); // Store mapping from field name to ordinal index (origin zero). for (int i = 0; i < fields.length; i++) { keys.put(fields[i], i); } // Check for field names that we use. getIndexIfDefined(INDEX_FREE, "free"); getIndexIfDefined(INDEX_ACTIVE, "active"); getIndexIfDefined(INDEX_SPEC, "spec", "specul"); getIndexIfDefined(INDEX_WIRED, "wire", "wired"); getIndexIfDefined(INDEX_PAGEINS, "pageins"); getIndexIfDefined(INDEX_PAGEOUT, "pageout"); } /* * Note: Some fields are reported with a 'K' suffix. Some are in * "pages". The pageSize was extracted from the header above. */ // Mach Virtual Memory Statistics: (page size of 4096 bytes, cache hits // 0%) // free active spec inactive wire faults copy 0fill reactive pageins // pageout // 398649 1196K 47388 203418 185941 145234K 2036323 82012367 1353888 // 351301 149940 // 400080 1197K 44784 205254 183886 1829 0 1046 0 0 0 // read lines until interrupted. long pageout_tm1 = 0; boolean first = true; while (true) { // read the next line of data. final String data; { String s = readLine(); if (s.startsWith("Mach")) { // 1st header line. s = readLine(); // 2nd header line. s = readLine(); // data line. if (log.isInfoEnabled()) log.info("Skipped headers."); } data = s; } try { // timestamp lastModified.set(System.currentTimeMillis()); final String[] fields = pattern.split(data.trim(), 0/* limit */); final String free = fields[INDEX_FREE.get()]; // free final String active = fields[INDEX_ACTIVE.get()]; // in use and pageable final String spec = fields[INDEX_SPEC.get()]; // speculative // final String inactive = fields[3]; // final String wire = fields[INDEX_WIRED.get()]; // wired down // final String faults = fields[5]; // translation faults final String pageins = fields[INDEX_PAGEINS.get()]; // pageins final String pageout = fields[INDEX_PAGEOUT.get()]; // pageout if (log.isInfoEnabled()) log.info("\nfree=" + free + ", active=" + active + ", spec=" + spec + ", wire=" + wire + ", si=" + pageins + ", so=" + pageout + "\n" + h1 + "\n" + data); { final double _free = parseDouble(free); final double _active = parseDouble(active); final double _spec = parseDouble(spec); final double _wire = parseDouble(wire); final double swapBytesUsed = _active + _spec + _wire; vals.put(IHostCounters.Memory_Bytes_Free, _free * pageSize); vals.put(IHostCounters.Memory_SwapBytesUsed, swapBytesUsed * pageSize); } /* * pageout is reported as a total over time. we have to compute a * delta, then divide through by the interval to get * pages/second. */ { final double _pageout = parseDouble(pageout); if (!first) { final double delta = _pageout - pageout_tm1; final double majorPageFaultsPerSec = delta / getInterval(); vals.put( IRequiredHostCounters.Memory_majorFaultsPerSecond, majorPageFaultsPerSec); } } first = false; } catch (Exception ex) { /* * Issue warning for parsing problems. */ log.warn(ex.getMessage() // + "\nheader: " + h1 // + "\n data: " + data // , ex); } } // while(true) } // readProcess() } // class VMStatReader /** * Parse a string which may have a "K" suffix, returning a double. If the "K" * suffix is present, then the returned value is scaled by 1000. This handles * an OSX specific oddity for vm_stat * * @param s * The string. * * @return The (possibly scaled) value. */ private static double parseDouble(final String s) { final int pos = s.indexOf("K"); if (pos == -1) { return Long.valueOf(s); } final long val = Long.valueOf(s.substring(0, pos)); return val * 1000; } private static void assertFieldByPosition(final int index, final String[] fields, final String expected) { if (!expected.equals(fields[index])) throw new RuntimeException("Expecting '" + expected + "', found: '" + fields[0] + "'"); } /** * If the name of the performance counter appears in {@link #keys} then set * the index of that counter on the indexOf field. * * @param indexOf * The index of that performance counter in the table generated by * the OS. * @param name * The name(s) of the performance counter. */ private void getIndexIfDefined(final AtomicInteger indexOf, final String... name) { Integer index = null; for (String s : name) { index = keys.get(s); if (index != null) { indexOf.set(index); return; } } throw new RuntimeException("Required performance counter not found: '" + Arrays.toString(name) + "'"); } }