/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.internal.statistics.platform;

import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LinuxProcFsStatistics {
  private enum CPU {
    USER,
    NICE,
    SYSTEM,
    IDLE,
    IOWAIT,
    IRQ,
    SOFTIRQ,
    /** stands for aggregation of all columns not present in the enum list */
    OTHER
  }

  private static final int DEFAULT_PAGESIZE = 4 * 1024;
  private static final int OneMeg = 1024 * 1024;
  private static final String pageSizeProperty =
      DistributionConfig.GEMFIRE_PREFIX + "statistics.linux.pageSize";
  private static CpuStat cpuStatSingleton;
  private static int pageSize;
  private static int sys_cpus;
  private static boolean hasProcVmStat;
  private static boolean hasDiskStats;
  static SpaceTokenizer st;

  /** The number of non-process files in /proc */
  private static int nonPidFilesInProc;

  /** /proc/stat tokens */
  private static final String CPU_TOKEN = "cpu ";
  private static final String PAGE = "page ";
  private static final String SWAP = "swap ";
  private static final String CTXT = "ctxt ";
  private static final String PROCESSES = "processes ";

  /** /proc/vmstat tokens */
  private static final String PGPGIN = "pgpgin ";
  private static final String PGPGOUT = "pgpgout ";
  private static final String PSWPIN = "pswpin ";
  private static final String PSWPOUT = "pswpout ";

  // Do not create instances of this class
  private LinuxProcFsStatistics() {}

  public static int init() { // TODO: was package-protected
    nonPidFilesInProc = getNumberOfNonProcessProcFiles();
    sys_cpus = Runtime.getRuntime().availableProcessors();
    pageSize = Integer.getInteger(pageSizeProperty, DEFAULT_PAGESIZE);
    cpuStatSingleton = new CpuStat();
    hasProcVmStat = new File("/proc/vmstat").exists();
    hasDiskStats = new File("/proc/diskstats").exists();
    st = new SpaceTokenizer();
    return 0;
  }

  public static void close() { // TODO: was package-protected
    cpuStatSingleton = null;
    st = null;
  }

  public static void readyRefresh() { // TODO: was package-protected
  }

  /*
   * get the statistics for the specified process. ( pid_rssSize, pid_imageSize ) vsize is assumed
   * to be in units of kbytes System property gemfire.statistics.pagesSize can be used to configure
   * pageSize. This is the mem_unit member of the struct returned by sysinfo()
   * 
   */
  public static void refreshProcess(int pid, int[] ints, long[] longs, double[] doubles) { // TODO:
                                                                                           // was
                                                                                           // package-protected
    // Just incase a pid is not available
    if (pid == 0)
      return;
    InputStreamReader isr = null;
    BufferedReader br = null;
    try {
      File file = new File("/proc/" + pid + "/stat");
      isr = new InputStreamReader(new FileInputStream(file));
      br = new BufferedReader(isr, 2048);
      String line = br.readLine();
      if (line == null) {
        return;
      }
      st.setString(line);
      st.skipTokens(22);
      ints[LinuxProcessStats.imageSizeINT] = (int) (st.nextTokenAsLong() / OneMeg);
      ints[LinuxProcessStats.rssSizeINT] = (int) ((st.nextTokenAsLong() * pageSize) / OneMeg);
    } catch (NoSuchElementException nsee) {
      // It might just be a case of the process going away while we
      // where trying to get its stats.
      // So for now lets just ignore the failure and leave the stats
      // as they are.
    } catch (IOException ioe) {
      // It might just be a case of the process going away while we
      // where trying to get its stats.
      // So for now lets just ignore the failure and leave the stats
      // as they are.
    } finally {
      st.releaseResources();
      if (br != null)
        try {
          br.close();
        } catch (IOException ignore) {
        }
    }
  }

  public static void refreshSystem(int[] ints, long[] longs, double[] doubles) { // TODO: was
                                                                                 // package-protected
    ints[LinuxSystemStats.processesINT] = getProcessCount();
    ints[LinuxSystemStats.cpusINT] = sys_cpus;
    InputStreamReader isr = null;
    BufferedReader br = null;
    try {
      isr = new InputStreamReader(new FileInputStream("/proc/stat"));
      br = new BufferedReader(isr);
      String line = null;
      while ((line = br.readLine()) != null) {
        try {
          if (line.startsWith(CPU_TOKEN)) {
            int[] cpuData = cpuStatSingleton.calculateStats(line);
            ints[LinuxSystemStats.cpuIdleINT] = cpuData[CPU.IDLE.ordinal()];
            ints[LinuxSystemStats.cpuNiceINT] = cpuData[CPU.NICE.ordinal()];
            ints[LinuxSystemStats.cpuSystemINT] = cpuData[CPU.SYSTEM.ordinal()];
            ints[LinuxSystemStats.cpuUserINT] = cpuData[CPU.USER.ordinal()];
            ints[LinuxSystemStats.iowaitINT] = cpuData[CPU.IOWAIT.ordinal()];
            ints[LinuxSystemStats.irqINT] = cpuData[CPU.IRQ.ordinal()];
            ints[LinuxSystemStats.softirqINT] = cpuData[CPU.SOFTIRQ.ordinal()];
            ints[LinuxSystemStats.cpuActiveINT] = 100 - cpuData[CPU.IDLE.ordinal()];
            ints[LinuxSystemStats.cpuNonUserINT] = cpuData[CPU.OTHER.ordinal()]
                + cpuData[CPU.SYSTEM.ordinal()] + cpuData[CPU.IOWAIT.ordinal()]
                + cpuData[CPU.IRQ.ordinal()] + cpuData[CPU.SOFTIRQ.ordinal()];
          } else if (!hasProcVmStat && line.startsWith(PAGE)) {
            int secondIndex = line.indexOf(" ", PAGE.length());
            longs[LinuxSystemStats.pagesPagedInLONG] =
                SpaceTokenizer.parseAsLong(line.substring(PAGE.length(), secondIndex));
            longs[LinuxSystemStats.pagesPagedOutLONG] =
                SpaceTokenizer.parseAsLong(line.substring(secondIndex + 1));
          } else if (!hasProcVmStat && line.startsWith(SWAP)) {
            int secondIndex = line.indexOf(" ", SWAP.length());
            longs[LinuxSystemStats.pagesSwappedInLONG] =
                SpaceTokenizer.parseAsLong(line.substring(SWAP.length(), secondIndex));
            longs[LinuxSystemStats.pagesSwappedOutLONG] =
                SpaceTokenizer.parseAsLong(line.substring(secondIndex + 1));
          } else if (line.startsWith(CTXT)) {
            longs[LinuxSystemStats.contextSwitchesLONG] =
                SpaceTokenizer.parseAsLong(line.substring(CTXT.length()));
          } else if (line.startsWith(PROCESSES)) {
            longs[LinuxSystemStats.processCreatesLONG] =
                SpaceTokenizer.parseAsInt(line.substring(PROCESSES.length()));
          }
        } catch (NoSuchElementException nsee) {
          // this is the result of reading a partially formed file
          // just do not update what ever entry had the problem
        }
      }
    } catch (IOException ioe) {
    } finally {
      if (br != null)
        try {
          br.close();
        } catch (IOException ignore) {
        }
    }
    getLoadAvg(doubles);
    getMemInfo(ints);
    getDiskStats(longs);
    getNetStats(longs);
    if (hasProcVmStat) {
      getVmStats(longs);
    }
    st.releaseResources();
  }

  // Example of /proc/loadavg
  // 0.00 0.00 0.07 1/218 7907
  private static void getLoadAvg(double[] doubles) {
    InputStreamReader isr = null;
    BufferedReader br = null;
    try {
      isr = new InputStreamReader(new FileInputStream("/proc/loadavg"));
      br = new BufferedReader(isr, 512);
      String line = br.readLine();
      if (line == null) {
        return;
      }
      st.setString(line);
      doubles[LinuxSystemStats.loadAverage1DOUBLE] = st.nextTokenAsDouble();
      doubles[LinuxSystemStats.loadAverage5DOUBLE] = st.nextTokenAsDouble();
      doubles[LinuxSystemStats.loadAverage15DOUBLE] = st.nextTokenAsDouble();
    } catch (NoSuchElementException nsee) {
    } catch (IOException ioe) {
    } finally {
      st.releaseResources();
      if (br != null)
        try {
          br.close();
        } catch (IOException ignore) {
        }
    }
  }

  /**
   * Returns the available system memory (free + cached).
   * 
   * @param logger the logger
   * @return the available memory in bytes
   */
  public static long getAvailableMemory(Logger logger) {
    try {
      BufferedReader br =
          new BufferedReader(new InputStreamReader(new FileInputStream("/proc/meminfo")));
      try {
        long free = 0;
        Pattern p = Pattern.compile("(.*)?:\\s+(\\d+)( kB)?");

        String line;
        while ((line = br.readLine()) != null) {
          Matcher m = p.matcher(line);
          if (m.matches() && ("MemFree".equals(m.group(1)) || "Cached".equals(m.group(1)))) {
            free += Long.parseLong(m.group(2));
          }
        }

        // convert to bytes
        return 1024 * free;

      } finally {
        br.close();
      }
    } catch (IOException e) {
      logger.warn("Error determining free memory", e);
      return Long.MAX_VALUE;
    }
  }

  // Example of /proc/meminfo
  // total: used: free: shared: buffers: cached:
  // Mem: 4118380544 3816050688 302329856 0 109404160 3060326400
  // Swap: 4194881536 127942656 4066938880
  private static void getMemInfo(int[] ints) {
    InputStreamReader isr = null;
    BufferedReader br = null;
    try {
      isr = new InputStreamReader(new FileInputStream("/proc/meminfo"));
      br = new BufferedReader(isr);
      // Assume all values read in are in kB, convert to MB
      String line = null;
      while ((line = br.readLine()) != null) {
        try {
          if (line.startsWith("MemTotal: ")) {
            st.setString(line);
            st.skipToken(); // Burn initial token
            ints[LinuxSystemStats.physicalMemoryINT] = (int) (st.nextTokenAsLong() / 1024);
          } else if (line.startsWith("MemFree: ")) {
            st.setString(line);
            st.skipToken(); // Burn initial token
            ints[LinuxSystemStats.freeMemoryINT] = (int) (st.nextTokenAsLong() / 1024);
          } else if (line.startsWith("SharedMem: ")) {
            st.setString(line);
            st.skipToken(); // Burn initial token
            ints[LinuxSystemStats.sharedMemoryINT] = (int) (st.nextTokenAsLong() / 1024);
          } else if (line.startsWith("Buffers: ")) {
            st.setString(line);
            st.nextToken(); // Burn initial token
            ints[LinuxSystemStats.bufferMemoryINT] = (int) (st.nextTokenAsLong() / 1024);
          } else if (line.startsWith("SwapTotal: ")) {
            st.setString(line);
            st.skipToken(); // Burn initial token
            ints[LinuxSystemStats.allocatedSwapINT] = (int) (st.nextTokenAsLong() / 1024);
          } else if (line.startsWith("SwapFree: ")) {
            st.setString(line);
            st.skipToken(); // Burn initial token
            ints[LinuxSystemStats.unallocatedSwapINT] = (int) (st.nextTokenAsLong() / 1024);
          } else if (line.startsWith("Cached: ")) {
            st.setString(line);
            st.skipToken(); // Burn initial token
            ints[LinuxSystemStats.cachedMemoryINT] = (int) (st.nextTokenAsLong() / 1024);
          } else if (line.startsWith("Dirty: ")) {
            st.setString(line);
            st.skipToken(); // Burn initial token
            ints[LinuxSystemStats.dirtyMemoryINT] = (int) (st.nextTokenAsLong() / 1024);
          } else if (line.startsWith("Inact_dirty: ")) { // 2.4 kernels
            st.setString(line);
            st.skipToken(); // Burn initial token
            ints[LinuxSystemStats.dirtyMemoryINT] = (int) (st.nextTokenAsLong() / 1024);
          }
        } catch (NoSuchElementException nsee) {
          // ignore and let that stat not to be updated this time
        }
      }
    } catch (IOException ioe) {
    } finally {
      st.releaseResources();
      if (br != null)
        try {
          br.close();
        } catch (IOException ignore) {
        }
    }
  }

  /*
   * Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes
   * packets errs drop fifo colls carrier compressed lo:1908275823 326949246 0 0 0 0 0 0 1908275823
   * 326949246 0 0 0 0 0 0
   */

  private static void getNetStats(long[] longs) {
    InputStreamReader isr = null;
    BufferedReader br = null;
    try {
      isr = new InputStreamReader(new FileInputStream("/proc/net/dev"));
      br = new BufferedReader(isr);
      br.readLine(); // Discard header info
      br.readLine(); // Discard header info
      long lo_recv_packets = 0, lo_recv_bytes = 0;
      long other_recv_packets = 0, other_recv_bytes = 0;
      long other_recv_errs = 0, other_recv_drop = 0;
      long other_xmit_packets = 0, other_xmit_bytes = 0;
      long other_xmit_errs = 0, other_xmit_drop = 0, other_xmit_colls = 0;
      String line = null;
      while ((line = br.readLine()) != null) {
        int index = line.indexOf(":");
        boolean isloopback = (line.indexOf("lo:") != -1);
        st.setString(line.substring(index + 1).trim());
        long recv_bytes = st.nextTokenAsLong();
        long recv_packets = st.nextTokenAsLong();
        long recv_errs = st.nextTokenAsLong();
        long recv_drop = st.nextTokenAsLong();
        st.skipTokens(4); // fifo, frame, compressed, multicast
        long xmit_bytes = st.nextTokenAsLong();
        long xmit_packets = st.nextTokenAsLong();
        long xmit_errs = st.nextTokenAsLong();
        long xmit_drop = st.nextTokenAsLong();
        st.skipToken(); // fifo
        long xmit_colls = st.nextTokenAsLong();

        if (isloopback) {
          lo_recv_packets = recv_packets;
          lo_recv_bytes = recv_bytes;
        } else {
          other_recv_packets += recv_packets;
          other_recv_bytes += recv_bytes;
        }
        other_recv_errs += recv_errs;
        other_recv_drop += recv_drop;

        if (isloopback) {
          /* loopback_xmit_packets = xmit_packets; */
        } else {
          other_xmit_packets += xmit_packets;
          other_xmit_bytes += xmit_bytes;
        }
        other_xmit_errs += xmit_errs;
        other_xmit_drop += xmit_drop;
        other_xmit_colls += xmit_colls;
      }
      // fix for bug 43860
      longs[LinuxSystemStats.loopbackPacketsLONG] = lo_recv_packets;
      longs[LinuxSystemStats.loopbackBytesLONG] = lo_recv_bytes;
      longs[LinuxSystemStats.recvPacketsLONG] = other_recv_packets;
      longs[LinuxSystemStats.recvBytesLONG] = other_recv_bytes;
      longs[LinuxSystemStats.recvErrorsLONG] = other_recv_errs;
      longs[LinuxSystemStats.recvDropsLONG] = other_recv_drop;
      longs[LinuxSystemStats.xmitPacketsLONG] = other_xmit_packets;
      longs[LinuxSystemStats.xmitBytesLONG] = other_xmit_bytes;
      longs[LinuxSystemStats.xmitErrorsLONG] = other_xmit_errs;
      longs[LinuxSystemStats.xmitDropsLONG] = other_xmit_drop;
      longs[LinuxSystemStats.xmitCollisionsLONG] = other_xmit_colls;
    } catch (NoSuchElementException nsee) {
    } catch (IOException ioe) {
    } finally {
      st.releaseResources();
      if (br != null)
        try {
          br.close();
        } catch (IOException ignore) {
        }
    }
  }

  // example of /proc/diskstats
  // 1 0 ram0 0 0 0 0 0 0 0 0 0 0 0
  // 1 1 ram1 0 0 0 0 0 0 0 0 0 0 0
  // 1 2 ram2 0 0 0 0 0 0 0 0 0 0 0
  // 1 3 ram3 0 0 0 0 0 0 0 0 0 0 0
  // 1 4 ram4 0 0 0 0 0 0 0 0 0 0 0
  // 1 5 ram5 0 0 0 0 0 0 0 0 0 0 0
  // 1 6 ram6 0 0 0 0 0 0 0 0 0 0 0
  // 1 7 ram7 0 0 0 0 0 0 0 0 0 0 0
  // 1 8 ram8 0 0 0 0 0 0 0 0 0 0 0
  // 1 9 ram9 0 0 0 0 0 0 0 0 0 0 0
  // 1 10 ram10 0 0 0 0 0 0 0 0 0 0 0
  // 1 11 ram11 0 0 0 0 0 0 0 0 0 0 0
  // 1 12 ram12 0 0 0 0 0 0 0 0 0 0 0
  // 1 13 ram13 0 0 0 0 0 0 0 0 0 0 0
  // 1 14 ram14 0 0 0 0 0 0 0 0 0 0 0
  // 1 15 ram15 0 0 0 0 0 0 0 0 0 0 0
  // 8 0 sda 1628761 56603 37715982 5690640 6073889 34091137 330349716 279787924 0 25235208
  // 285650572
  // 8 1 sda1 151 638 45 360
  // 8 2 sda2 674840 11202608 8591346 68716852
  // 8 3 sda3 1010409 26512312 31733575 253868616
  // 8 16 sdb 12550386 47814 213085738 60429448 5529812 210792345 1731459040 1962038752 0 33797176
  // 2024138028
  // 8 17 sdb1 12601113 213085114 216407197 1731257800
  // 3 0 hda 0 0 0 0 0 0 0 0 0 0 0
  private static void getDiskStats(long[] longs) {
    InputStreamReader isr = null;
    BufferedReader br = null;
    String line = null;
    try {
      if (hasDiskStats) {
        // 2.6 kernel
        isr = new InputStreamReader(new FileInputStream("/proc/diskstats"));
      } else {
        // 2.4 kernel
        isr = new InputStreamReader(new FileInputStream("/proc/partitions"));
      }
      br = new BufferedReader(isr);
      long readsCompleted = 0, readsMerged = 0;
      long sectorsRead = 0, timeReading = 0;
      long writesCompleted = 0, writesMerged = 0;
      long sectorsWritten = 0, timeWriting = 0;
      long iosInProgress = 0;
      long timeIosInProgress = 0;
      long ioTime = 0;
      if (!hasDiskStats) {
        br.readLine(); // Discard header info
        br.readLine(); // Discard header info
      }
      while ((line = br.readLine()) != null) {
        st.setString(line);
        {
          // " 8 1 sdb" on 2.6
          // " 8 1 452145145 sdb" on 2.4
          String tok = st.nextToken();
          if (tok.length() == 0 || Character.isWhitespace(tok.charAt(0))) {
            // skip over first token since it is whitespace
            tok = st.nextToken();
          }
          // skip first token it is some number
          tok = st.nextToken();
          // skip second token it is some number
          tok = st.nextToken();
          if (!hasDiskStats) {
            // skip third token it is some number
            tok = st.nextToken();
          }
          // Now tok should be the device name.
          if (Character.isDigit(tok.charAt(tok.length() - 1))) {
            // If the last char is a digit
            // skip this line since it is a partition of a device; not a device.
            continue;
          }
        }
        long tmp_readsCompleted = st.nextTokenAsLong();
        long tmp_readsMerged = st.nextTokenAsLong();
        long tmp_sectorsRead = st.nextTokenAsLong();
        long tmp_timeReading = st.nextTokenAsLong();
        if (st.hasMoreTokens()) {
          // If we are on 2.6 then we might only have 4 longs; if so ignore this line
          // Otherwise we should have 11 long tokens.
          long tmp_writesCompleted = st.nextTokenAsLong();
          long tmp_writesMerged = st.nextTokenAsLong();
          long tmp_sectorsWritten = st.nextTokenAsLong();
          long tmp_timeWriting = st.nextTokenAsLong();
          long tmp_iosInProgress = st.nextTokenAsLong();
          long tmp_timeIosInProgress = st.nextTokenAsLong();
          long tmp_ioTime = st.nextTokenAsLong();
          readsCompleted += tmp_readsCompleted;
          readsMerged += tmp_readsMerged;
          sectorsRead += tmp_sectorsRead;
          timeReading += tmp_timeReading;
          writesCompleted += tmp_writesCompleted;
          writesMerged += tmp_writesMerged;
          sectorsWritten += tmp_sectorsWritten;
          timeWriting += tmp_timeWriting;
          iosInProgress += tmp_iosInProgress;
          timeIosInProgress += tmp_timeIosInProgress;
          ioTime += tmp_ioTime;
        }
      } // while
      final int SECTOR_SIZE = 512;
      longs[LinuxSystemStats.readsCompletedLONG] = readsCompleted;
      longs[LinuxSystemStats.readsMergedLONG] = readsMerged;
      longs[LinuxSystemStats.bytesReadLONG] = sectorsRead * SECTOR_SIZE;
      longs[LinuxSystemStats.timeReadingLONG] = timeReading;
      longs[LinuxSystemStats.writesCompletedLONG] = writesCompleted;
      longs[LinuxSystemStats.writesMergedLONG] = writesMerged;
      longs[LinuxSystemStats.bytesWrittenLONG] = sectorsWritten * SECTOR_SIZE;
      longs[LinuxSystemStats.timeWritingLONG] = timeWriting;
      longs[LinuxSystemStats.iosInProgressLONG] = iosInProgress;
      longs[LinuxSystemStats.timeIosInProgressLONG] = timeIosInProgress;
      longs[LinuxSystemStats.ioTimeLONG] = ioTime;
    } catch (NoSuchElementException nsee) {
      // org.apache.geode.distributed.internal.InternalDistributedSystem.getAnyInstance().getLoggerI18n().fine("unexpected
      // NoSuchElementException line=" + line, nsee);
    } catch (IOException ioe) {
    } finally {
      st.releaseResources();
      if (br != null)
        try {
          br.close();
        } catch (IOException ignore) {
        }
    }
  }

  // Example of /proc/vmstat
  // ...
  // pgpgin 294333738
  // pgpgout 1057420300
  // pswpin 19422
  // pswpout 14495
  private static void getVmStats(long[] longs) {
    assert hasProcVmStat != false : "getVmStats called when hasVmStat was false";
    InputStreamReader isr = null;
    BufferedReader br = null;
    try {
      isr = new InputStreamReader(new FileInputStream("/proc/vmstat"));
      br = new BufferedReader(isr);
      String line = null;
      while ((line = br.readLine()) != null) {
        if (line.startsWith(PGPGIN)) {
          longs[LinuxSystemStats.pagesPagedInLONG] =
              SpaceTokenizer.parseAsLong(line.substring(PGPGIN.length()));
        } else if (line.startsWith(PGPGOUT)) {
          longs[LinuxSystemStats.pagesPagedOutLONG] =
              SpaceTokenizer.parseAsLong(line.substring(PGPGOUT.length()));
        } else if (line.startsWith(PSWPIN)) {
          longs[LinuxSystemStats.pagesSwappedInLONG] =
              SpaceTokenizer.parseAsLong(line.substring(PSWPIN.length()));
        } else if (line.startsWith(PSWPOUT)) {
          longs[LinuxSystemStats.pagesSwappedOutLONG] =
              SpaceTokenizer.parseAsLong(line.substring(PSWPOUT.length()));
        }
      }
    } catch (NoSuchElementException nsee) {
    } catch (IOException ioe) {
    } finally {
      if (br != null)
        try {
          br.close();
        } catch (IOException ignore) {
        }
    }
  }

  /**
   * Count the number of files in /proc that do not represent processes. This value is cached to
   * make counting the number of running process a cheap operation. The assumption is that the
   * contents of /proc will not change on a running system.
   * 
   * @return the files in /proc that do NOT match /proc/[0-9]*
   */
  private static int getNumberOfNonProcessProcFiles() {
    File proc = new File("/proc");
    String[] procFiles = proc.list();
    int count = 0;
    if (procFiles != null) {
      for (String filename : procFiles) {
        char c = filename.charAt(0);
        if (!Character.isDigit(c)) {
          if (c == '.') {
            // see if the next char is a digit
            if (filename.length() > 1) {
              char c2 = filename.charAt(1);
              if (Character.isDigit(c2)) {
                // for bug 42091 do not count files that begin with a '.' followed by digits
                continue;
              }
            }
          }
          count++;
        }
      }
    }
    return count;
  }

  /**
   * @return the number of running processes on the system
   */
  private static int getProcessCount() {
    File proc = new File("/proc");
    String[] procFiles = proc.list();
    if (procFiles == null) {
      // unknown error, continue without this stat
      return 0;
    }
    return procFiles.length - nonPidFilesInProc;
  }

  // The array indices must be ordered as they appear in /proc/stats
  // (user) (nice) (system) (idle) (iowait) (irq) (softirq)
  // cpu 42813766 10844 8889075 1450764512 49963779 808244 3084872
  //
  private static class CpuStat {
    private static boolean lastCpuStatsInvalid;
    private static List<Long> lastCpuStats;

    public CpuStat() {
      lastCpuStatsInvalid = true;
    }

    public int[] calculateStats(String newStatLine) {
      st.setString(newStatLine);
      st.skipToken(); // cpu name
      final int MAX_CPU_STATS = CPU.values().length;
      /*
       * newer kernels now have 8 columns for cpu in /proc/stat (up from 7). This number may
       * increase even further, hence we now use List in place of long[]. We add up entries from all
       * columns after 7 into CPU.OTHER
       */
      List<Long> newStats = new ArrayList<Long>(8);
      List<Long> diffs = new ArrayList<Long>(8);
      long total_change = 0;
      int actualCpuStats = 0;
      long unaccountedCpuUtilization = 0;

      while (st.hasMoreTokens()) {
        newStats.add(st.nextTokenAsLong());
        actualCpuStats++;
      }

      if (lastCpuStatsInvalid) {
        lastCpuStats = newStats;
        lastCpuStatsInvalid = false;
        for (int i = 0; i < MAX_CPU_STATS; i++) {
          diffs.add(0L);
        }
        diffs.set(CPU.IDLE.ordinal(), 100L);
      } else {
        for (int i = 0; i < actualCpuStats; i++) {
          diffs.add(newStats.get(i) - lastCpuStats.get(i));
          total_change += diffs.get(i);
          lastCpuStats.set(i, newStats.get(i));
        }
        if (total_change == 0) {
          // avoid divide by zero
          total_change = 1;
        }
        for (int i = 0; i < MAX_CPU_STATS; i++) {
          if (i < actualCpuStats) {
            diffs.set(i, (diffs.get(i) * 100) / total_change);
          }
        }
        for (int i = MAX_CPU_STATS; i < actualCpuStats; i++) {
          unaccountedCpuUtilization += (diffs.get(i) * 100) / total_change;
        }
      }
      int[] ret = new int[MAX_CPU_STATS];
      for (int i = 0; i < MAX_CPU_STATS; i++) {
        if (i < actualCpuStats) {
          ret[i] = diffs.get(i).intValue();
        }
      }
      ret[CPU.OTHER.ordinal()] += (int) unaccountedCpuUtilization;
      return ret;
    }
  }

  private static class SpaceTokenizer {
    private String str;
    private char[] rawChars;
    private int beginIdx;
    private int endIdx;
    private int nextIdx;

    protected SpaceTokenizer() {
      endIdx = -1;
      nextIdx = -1;
    }

    protected void releaseResources() {
      str = null;
      rawChars = null;
      endIdx = -1;
      nextIdx = -1;
    }

    private void nextIdx() {
      int origin = nextIdx;
      if (endIdx == rawChars.length || beginIdx == -1) {
        endIdx = -1;
        nextIdx = -1;
        return;
      }
      endIdx = -1;
      nextIdx = -1;
      for (int i = origin + 1; i < rawChars.length; i++) {
        char c = rawChars[i];
        // Add all delimiters here
        if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
          if (endIdx == -1) {
            endIdx = i;
          }
        } else {
          // this handles multiple consecutive delimiters
          if (endIdx != -1) {
            nextIdx = i;
            return;
          }
        }
      }
      if (endIdx == -1) {
        // indicates we were still reading white space at the end of the string
        endIdx = rawChars.length;
      }
    }

    protected boolean hasMoreTokens() {
      return endIdx != -1;
    }

    protected void setString(String data) {
      str = data;
      rawChars = new char[str.length()];
      str.getChars(0, str.length(), rawChars, 0);
      beginIdx = 0;
      endIdx = -1;
      nextIdx = -1;
      nextIdx();
    }

    protected boolean skipToken() {
      if (hasMoreTokens()) {
        beginIdx = nextIdx;
        nextIdx();
        return true;
      }
      return false;
    }

    protected String nextToken() {
      if (hasMoreTokens()) {
        final String ret = str.substring(beginIdx, endIdx);
        beginIdx = nextIdx;
        nextIdx();
        return ret;
      }
      throw new NoSuchElementException();
    }

    protected String peekToken() {
      if (hasMoreTokens()) {
        return str.substring(beginIdx, endIdx);
      }
      throw new NoSuchElementException();
    }

    protected void skipTokens(int numberToSkip) {
      int remaining = numberToSkip + 1;
      while (--remaining > 0 && skipToken());
    }

    protected static long parseAsLong(String number) {
      long l = 0L;
      try {
        l = Long.parseLong(number);
      } catch (NumberFormatException nfe) {
      }
      return l;
    }

    protected static int parseAsInt(String number) {
      int i = 0;
      try {
        i = Integer.parseInt(number);
      } catch (NumberFormatException nfe) {
      }
      return i;
    }

    protected int nextTokenAsInt() {
      int i = 0;
      try {
        i = Integer.parseInt(nextToken());
      } catch (NumberFormatException nfe) {
      }
      return i;
    }

    protected long nextTokenAsLong() {
      long l = 0L;
      try {
        l = Long.parseLong(nextToken());
      } catch (NumberFormatException nfe) {
      }
      return l;
    }

    protected double nextTokenAsDouble() {
      double d = 0;
      try {
        d = Double.parseDouble(nextToken());
      } catch (NumberFormatException nfe) {
      }
      return d;
    }
  }
}
