diff --git a/SavageD.sh b/SavageD.sh index 1db4146..de8a751 100755 --- a/SavageD.sh +++ b/SavageD.sh @@ -140,10 +140,11 @@ function selfMonitor() { # use CURL to monitor ourselves # # setup the alias first - # curl -X PUT http://localhost:8091/process/$proc_alias/pid -d "pid=$pid" + curl -X PUT http://localhost:8091/process/$proc_alias/pid -d "pid=$pid" # activate all known process plugins - # curl -X PUT http://localhost:8091/process/$proc_alias/memory + curl -X PUT http://localhost:8091/process/$proc_alias/memory + curl -X PUT http://localhost:8091/process/$proc_alias/cpu # activate all known server plugins curl -X PUT http://localhost:8091/server/$serv_alias/loadavg @@ -177,10 +178,10 @@ function stopSelfMonitor() { # use CURL to stop monitor ourselves # # setup the alias first - # curl -X PUT http://localhost:8091/process/$proc_alias/pid -d "pid=$pid" + curl -X PUT http://localhost:8091/process/$proc_alias/pid -d "pid=$pid" # activate all known process plugins - # curl -X PUT http://localhost:8091/process/$proc_alias/memory + curl -X PUT http://localhost:8091/process/$proc_alias/memory # activate all known server plugins curl -X DELETE http://localhost:8091/server/$serv_alias/loadavg diff --git a/app/features/ProcessCpu.js b/app/features/ProcessCpu.js index ae937a6..6c17671 100644 --- a/app/features/ProcessCpu.js +++ b/app/features/ProcessCpu.js @@ -9,48 +9,36 @@ var util = require("util"); var _ = require("underscore"); var dsCommon = require("dsCommon"); +// our parsers +var ServerStatParser = require("../parsers/ServerStatParser"); +var ProcessStatParser = require("../parsers/ProcessStatParser"); + function ProcessCpu(appServer) { // call our parent constructor ProcessCpu.super_.call(this, appServer, { name: "ProcessCpu" }); - // our cached stats - this.cpuStats = {}; + // our list of stats that we've seen before, for sampling purposes + this.stats = {}; // add ourselves to the list of available plugins - appServer.processCpu.addPlugin("memory", this); + appServer.processMonitor.addPlugin("cpu", this); } module.exports = ProcessCpu; util.inherits(ProcessCpu, dsCommon.dsFeature); -ProcessCpu.prototype.canMonitorPid = function(pid) { - // does the pid refer to an existing process? - if (!fs.existsSync("/proc/" + pid + "/status")) { - return false; - } - - // are we currently monitoring the server CPU stats? - // - - return true; -}; - -ProcessCpu.prototype.getCpuStats = function(pid) { - // self-reference - var self = this; - - // where are we getting the data from? - var filename = "/proc/" + pid + "/stat"; - - // this will hold the processed contents of the stat file - var results = {}; - - // this will hold the raw contents of the status file - var content = fs.readFileSync(filename, ascii); - - // let's split up the file - var parsed = content.split(/\s/); +ProcessCpu.prototype.getFilenamesToMonitor = function(pid) { + return [ + { + filename: "/proc/stat", + parser: new ServerStatParser() + }, + { + filename: "/proc/" + pid + "/stat", + parser: new ProcessStatParser() + } + ]; }; ProcessCpu.prototype.reportUsage = function(pid, alias) { @@ -60,72 +48,78 @@ ProcessCpu.prototype.reportUsage = function(pid, alias) { // what are we doing? // this.logInfo("report cpu usage of PID " + pid + " as alias " + alias); + // get the server CPU data first + var serverCpuStats = self.appServer.getLatestDataFor("/proc/stat"); + + // console.log(serverCpuStats.diff); + // we can get the information we need from the process's status file - var filename = "/proc/" + pid + "/status"; + var filename = "/proc/" + pid + "/stat"; - // this will hold the raw contents of the status file - var content = ""; + // get the process CPU data now + var processStats = self.appServer.getLatestDataFor(filename); - // this will hold the processed contents of the status file - var status = {}; + // we need to sample the data to work out CPU usage + if (this.stats[filename] === undefined) { + // remember the stats for next time + this.stats[filename] = { raw: extractedCpuStats, diff: {}, percentages: {} }; - // does the path exist? - if (!fs.existsSync(filename)) { - throw new Error("Cannot find file " + filename + " for process ID " + pid); + // can't do anything else yet + return; } - content = fs.readFileSync(filename, "ascii"); - - // extract the values that we want - _.each(content.split("\n"), function(line) { - // peak size of the virtual memory of the process - if (line.match(/^VmPeak/)) { - status.vmTotalPeak = parseInt(line.split(/\s+/)[1], 10) * 1024; - } - // current total size of the virtual memory of the process - if (line.match(/^VmSize/)) { - status.vmCurrentSize = parseInt(line.split(/\s+/)[1], 10) * 1024; - } - // total amount of 'locked' memory - if (line.match(/^VmLck/)) { - status.vmLocked = parseInt(line.split(/\s+/)[1], 10) * 1024; - } - // high-water mark for resident set size - if (line.match(/^VmHWM/)) { - status.vmRssPeak = parseInt(line.split(/\s+/)[1], 10) * 1024; - } - // current resident set size - if (line.match(/^VmRSS/)) { - status.vmCurrentRss = parseInt(line.split(/\s+/)[1], 10) * 1024; + // how many total CPU jiffies have occurred, on a single CPU? + var totalJiffies = 0; + _.each(serverCpuStats.diff, function(cpu, cpuName) { + if (cpuName !== 'cpu') { + if (cpu.total > totalJiffies) { + totalJiffies = cpu.total; + } } - // current data segment size - if (line.match(/^VmData/)) { - status.vmData = parseInt(line.split(/\s+/)[1], 10) * 1024; - } - // current stack size - if (line.match(/^VmStk/)) { - status.vmStack = parseInt(line.split(/\s+/)[1], 10) * 1024; - } - // current code pages size - if (line.match(/^VmExe/)) { - status.vmExe = parseInt(line.split(/\s+/)[1], 10) * 1024; - } - // current library size - if (line.match(/^VmLib/)) { - status.vmLib = parseInt(line.split(/\s+/)[1], 10) * 1024; - } - // current page table entries size - if (line.match(/^VmPTE/)) { - status.vmPTE = parseInt(line.split(/\s+/)[1], 10) * 1024; - } - // current swap usage - if (line.match(/^VmSwap/)) { - status.vmSwap = parseInt(line.split(/\s+/)[1], 10) * 1024; - } - }, this); + }); + + // extract just the CPU data that we want + var extractedCpuStats = { + user: processStats.utime, + system: processStats.stime + }; + + // work out how much CPU the process has used this interval + var processDiff = this.diffCpuStats(this.stats[filename].raw, extractedCpuStats); + + // convert the diff into a percentage + var percentageStats = this.statsToPercent(processDiff, totalJiffies); + + // remember the stats for next time + this.stats[filename] = { raw: extractedCpuStats, diff: processDiff, percentages: percentageStats }; // at this point, we have data to send to statsd - _.each(status, function(value, name) { - self.appServer.statsManager.count(alias + "." + name, value); + _.each(percentageStats, function(value, name) { + self.appServer.statsManager.count(alias + ".cpu." + name, value); }); +}; + +ProcessCpu.prototype.diffCpuStats = function(oldStats, newStats) { + var results = {}; + + // work out the number of jiffies that have occured + // between our two sample points + _.each(oldStats, function(value, fieldName) { + results[fieldName] = newStats[fieldName] - value; + }); + + // all done + return results; +}; + +ProcessCpu.prototype.statsToPercent = function(stats, totalJiffies) { + var results = {}; + + _.each(stats, function(value, fieldName) { + // calculate the percentage, to 2 decimal places + results[fieldName] = Math.round((parseFloat(value) / parseFloat(totalJiffies)) * 10000.0) / 100.0; + }); + + // all done + return results; }; \ No newline at end of file diff --git a/app/parsers/PidStatParser.js b/app/parsers/ProcessStatParser.js similarity index 88% rename from app/parsers/PidStatParser.js rename to app/parsers/ProcessStatParser.js index 7ab56b9..85d39f8 100644 --- a/app/parsers/PidStatParser.js +++ b/app/parsers/ProcessStatParser.js @@ -2,6 +2,7 @@ // All rights reserved var fs = require("fs"); +var _ = require("underscore"); function PidStatParser() { @@ -9,13 +10,10 @@ function PidStatParser() } module.exports = PidStatParser; -PidStatParser.prototype.getStats = function(pid) { +PidStatParser.prototype.retrieveStats = function(filename) { // self-reference var self = this; - // where are we getting the data from? - var filename = "/proc/" + pid + "/stat"; - // does the file exist? if (!fs.existsSync(filename)) { throw new Error("cannot find file " + filename + " for parsing"); @@ -24,8 +22,8 @@ PidStatParser.prototype.getStats = function(pid) { // this will hold the processed contents of the stat file var results = {}; - // this will hold the raw contents of the status file - var content = fs.readFileSync(filename, ascii); + // this will hold the raw contents of the stat file + var content = fs.readFileSync(filename, "ascii"); // let's split up the file var parsed = content.split(/\s/); diff --git a/config.js b/config.js index 5c0032e..b6745b2 100644 --- a/config.js +++ b/config.js @@ -1,5 +1,6 @@ { "features": { + "ProcessCpu": {}, "ProcessMemory": {}, "ServerCpu": {}, "ServerLoadavg": {}