/*jslint indent: 2, nomen: true, maxlen: 100, white: true, plusplus: true, unparam: true */ /*global require, applicationContext*/ //////////////////////////////////////////////////////////////////////////////// /// @brief A Foxx.Controller to handle the statistics /// /// @file /// /// DISCLAIMER /// /// Copyright 2014 triagens GmbH, Cologne, Germany /// /// 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. /// /// Copyright holder is triAGENS GmbH, Cologne, Germany /// /// @author Dr. Frank Celler /// @author Copyright 2014, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// "use strict"; var FoxxController = require("org/arangodb/foxx").Controller; var controller = new FoxxController(applicationContext); var db = require("org/arangodb").db; var internal = require("internal"); var STATISTICS_INTERVALL = 10; // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief avgPerSlice //////////////////////////////////////////////////////////////////////////////// function avgPerSlice (now, last) { var d = now.count - last.count; if (d === 0) { return 0; } return (now.sum - last.sum) / d; } //////////////////////////////////////////////////////////////////////////////// /// @brief avgPerTimeSlice //////////////////////////////////////////////////////////////////////////////// function avgPerTimeSlice (duration, now, last) { if (duration === 0) { return 0; } return (now.sum - last.sum) / duration; } //////////////////////////////////////////////////////////////////////////////// /// @brief perTimeSlice //////////////////////////////////////////////////////////////////////////////// function perTimeSlice (duration, now, last) { if (duration === 0) { return 0; } return (now - last) / duration; } //////////////////////////////////////////////////////////////////////////////// /// @brief avgDistributon //////////////////////////////////////////////////////////////////////////////// function avgPercentDistributon (now, last, cuts) { var n = cuts.length + 1; var result = new Array(n); var count = 0; var i; if (last === null) { count = now.count; } else { count = now.count - last.count; } if (count === 0) { for (i = 0; i < n; ++i) { result[i] = 0; } } else if (last === null) { for (i = 0; i < n; ++i) { result[i] = now.counts[i] / count; } } else { for (i = 0; i < n; ++i) { result[i] = (now.counts[i] - last.counts[i]) / count; } } return { values: result, cuts: cuts }; } //////////////////////////////////////////////////////////////////////////////// /// @brief computeStatisticsRaw //////////////////////////////////////////////////////////////////////////////// function computeStatisticsRaw (start) { var values; if (start === null || start === undefined) { values = db._query( "FOR s IN _statistics " + " SORT s.time " + " return s"); } else { values = db._query( "FOR s IN _statistics " + " FILTER s.time >= @start " + " SORT s.time " + " return s", { start: start }); } var times = []; var series = []; var lastTime = null; var lastRaw = null; var lastLastRaw = null; while (values.hasNext()) { var raw = values.next(); var t = raw.time; if (lastTime === null) { lastTime = t; lastLastRaw = null; lastRaw = raw; } else if (lastTime + STATISTICS_INTERVALL * 1.5 < t || raw.server.uptime < lastRaw.server.uptime) { while (lastTime + STATISTICS_INTERVALL * 1.1 < t) { lastTime += STATISTICS_INTERVALL; times.push(lastTime); series.push({ totalTime: null, requestTime: null, queueTime: null, ioTime: null, bytesSent: null, bytesReceived: null, optionsPerSecond: null, putsPerSecond: null, headsPerSecond: null, postsPerSecond: null, getsPerSecond: null, deletesPerSecond: null, othersPerSecond: null, patchesPerSecond: null, asyncPerSecond: null, virtualSize: null, residentSize: null, residentSizePercent: null, majorPageFaultsPerSecond: null, minorPageFaultsPerSecond: null, userTime: null, systemTime: null, numberOfThreads: null, clientConnections: null }); } lastTime = t; lastLastRaw = null; lastRaw = raw; } else { var d = t - lastTime; // x (aka time) axis times.push(t); // y axis var data = {}; series.push(data); // time figures data.totalTime = avgPerSlice( raw.client.totalTime, lastRaw.client.totalTime); data.requestTime = avgPerSlice( raw.client.requestTime, lastRaw.client.requestTime); data.queueTime = avgPerSlice( raw.client.queueTime, lastRaw.client.queueTime); // there is a chance that the request and queue do not fit perfectly // if io time get negative, simple ignore it data.ioTime = data.totalTime - data.requestTime - data.queueTime; if (data.ioTime < 0.0) { data.ioTime = 0; } // data transfer figures data.bytesSent = avgPerTimeSlice( d, raw.client.bytesSent, lastRaw.client.bytesSent); data.bytesReceived = avgPerTimeSlice( d, raw.client.bytesReceived, lastRaw.client.bytesReceived); // requests data.optionsPerSecond = perTimeSlice( d, raw.http.requestsOptions, lastRaw.http.requestsOptions); data.putsPerSecond = perTimeSlice( d, raw.http.requestsPut, lastRaw.http.requestsPut); data.headsPerSecond = perTimeSlice( d, raw.http.requestsHead, lastRaw.http.requestsHead); data.postsPerSecond = perTimeSlice( d, raw.http.requestsPost, lastRaw.http.requestsPost); data.getsPerSecond = perTimeSlice( d, raw.http.requestsGet, lastRaw.http.requestsGet); data.deletesPerSecond = perTimeSlice( d, raw.http.requestsDelete, lastRaw.http.requestsDelete); data.othersPerSecond = perTimeSlice( d, raw.http.requestsOther, lastRaw.http.requestsOther); data.patchesPerSecond = perTimeSlice( d, raw.http.requestsPatch, lastRaw.http.requestsPatch); data.asyncPerSecond = perTimeSlice( d, raw.http.requestsAsync, lastRaw.http.requestsAsync); // memory size data.virtualSize = raw.system.virtualSize; data.residentSize = raw.system.residentSize; data.residentSizePercent = raw.system.residentSizePercent; data.numberOfThreads = raw.system.numberOfThreads; data.clientConnections = raw.client.httpConnections; // page faults data.majorPageFaultsPerSecond = perTimeSlice( d, raw.system.majorPageFaults, lastRaw.system.majorPageFaults); data.minorPageFaultsPerSecond = perTimeSlice( d, raw.system.minorPageFaults, lastRaw.system.minorPageFaults); // cpu time data.userTime = raw.system.userTime; data.systemTime = raw.system.systemTime; // update last lastTime = t; lastLastRaw = lastRaw; lastRaw = raw; } } var distribution; var server; var current; if (lastRaw === null) { distribution = { bytesSentPercent: null, bytesReceivedPercent: null, totalTimePercent: null, requestTimePercent: null, queueTimePercent: null }; server = { physicalMemory: 0, uptime: 0 }; current = { asyncRequests: 0, asyncRequestsPercentChange: 0, clientConnections: 0, clientConnectionsPercentChange: 0, numberOfThreads: 0, numberOfThreadsPercentChange: 0, virtualSize: 0, virtualSizePercentChange: 0 }; } else { server = { physicalMemory: lastRaw.server.physicalMemory, uptime: lastRaw.server.uptime }; if (lastLastRaw === null) { current = { asyncRequests: lastRaw.http.requestsAsync, asyncRequestsPercentChange: 0, clientConnections: lastRaw.client.httpConnections, clientConnectionsPercentChange: 0, numberOfThreads: lastRaw.system.numberOfThreads, numberOfThreadsPercentChange: 0, virtualSize: lastRaw.system.virtualSize, virtualSizePercentChange: 0 }; distribution = { bytesSentPercent: avgPercentDistributon( lastRaw.client.bytesSent, null, internal.bytesSentDistribution), bytesReceivedPercent: avgPercentDistributon( lastRaw.client.bytesReceived, null, internal.bytesReceivedDistribution), totalTimePercent: avgPercentDistributon( lastRaw.client.totalTime, null, internal.requestTimeDistribution), requestTimePercent: avgPercentDistributon( lastRaw.client.requestTime, null, internal.requestTimeDistribution), queueTimePercent: avgPercentDistributon( lastRaw.client.queueTime, null, internal.requestTimeDistribution) }; } else { var n1 = lastRaw.http.requestsAsync; var m1 = lastLastRaw.http.requestsAsync; var d1 = 0; if (m1 !== 0) { d1 = (n1 - m1) / m1; } var n2 = lastRaw.client.httpConnections; var m2 = lastLastRaw.client.httpConnections; var d2 = 0; if (m2 !== 0) { d2 = (n2 - m2) / n2; } var n3 = lastRaw.system.numberOfThreads; var m3 = lastLastRaw.system.numberOfThreads; var d3 = 0; if (m3 !== 0) { d3 = (n3 - m3) / n3; } var n4 = lastRaw.system.virtualSize; var m4 = lastLastRaw.system.virtualSize; var d4 = 0; if (m4 !== 0) { d4 = (n4 - m4) / n4; } current = { asyncRequests: n1 - m1, asyncRequestsPercentChange: d1, clientConnections: n2, clientConnectionsPercentChange: d2, numberOfThreads: n3, numberOfThreadsPercentChange: d3, virtualSize: n4, virtualSizePercentChange: d4 }; distribution = { bytesSentPercent: avgPercentDistributon( lastRaw.client.bytesSent, lastLastRaw.client.bytesSent, internal.bytesSentDistribution), bytesReceivedPercent: avgPercentDistributon( lastRaw.client.bytesReceived, lastLastRaw.client.bytesReceived, internal.bytesReceivedDistribution), totalTimePercent: avgPercentDistributon( lastRaw.client.totalTime, lastLastRaw.client.totalTime, internal.requestTimeDistribution), requestTimePercent: avgPercentDistributon( lastRaw.client.requestTime, lastLastRaw.client.requestTime, internal.requestTimeDistribution), queueTimePercent: avgPercentDistributon( lastRaw.client.queueTime, lastLastRaw.client.queueTime, internal.requestTimeDistribution) }; } } return { next: lastTime, times: times, series: series, distribution: distribution, current: current, server: server }; } //////////////////////////////////////////////////////////////////////////////// /// @brief computeStatisticsRaw15M //////////////////////////////////////////////////////////////////////////////// function computeStatisticsRaw15M (start) { var values = db._query( "FOR s IN _statistics " + " FILTER s.time >= @m15 " + " SORT s.time " + " return { time: s.time, " + " requestsAsync: s.http.requestsAsync, " + " clientConnections: s.client.httpConnections, " + " numberOfThreads: s.system.numberOfThreads, " + " virtualSize: s.system.virtualSize }", { m15: start - 15 * 60 }); var lastRaw = null; var count = 0; var sumAsync = 0; var sumConnections = 0; var sumThreads = 0; var sumVirtualSize = 0; var firstAsync = null; var firstConnections = null; var firstThreads = null; var firstVirtualSize = null; while (values.hasNext()) { var raw = values.next(); if (lastRaw === null) { lastRaw = raw; } else { var d = raw.time - lastRaw.time; if (d !== 0) { var n1 = (raw.requestsAsync - lastRaw.requestsAsync) / d; var n2 = raw.clientConnections; var n3 = raw.numberOfThreads; var n4 = raw.virtualSize; count++; sumAsync += n1; sumConnections += n2; sumThreads += n3; sumVirtualSize += n4; if (firstAsync === null) { firstAsync = n1; } if (firstConnections === null) { firstConnections = n2; } if (firstThreads === null) { firstThreads = n3; } if (firstVirtualSize === null) { firstVirtualSize = n4; } } } } if (count !== 0) { sumAsync /= count; sumConnections /= count; sumThreads /= count; sumVirtualSize /= count; } if (firstAsync === null) { firstAsync = 0; } else if (firstAsync !== 0) { firstAsync = (sumAsync - firstAsync) / firstAsync; } if (firstConnections === null) { firstConnections = 0; } else if (firstConnections !== 0) { firstConnections = (sumConnections - firstConnections) / firstConnections; } if (firstThreads === null) { firstThreads = 0; } else if (firstThreads !== 0) { firstThreads = (sumThreads - firstThreads) / firstThreads; } if (firstVirtualSize === null) { firstVirtualSize = 0; } else if (firstVirtualSize !== 0) { firstVirtualSize = (sumVirtualSize - firstVirtualSize) / firstVirtualSize; } return { asyncPerSecond: sumAsync, asyncPerSecondPercentChange: firstAsync, clientConnections: sumConnections, clientConnectionsPercentChange: firstConnections, numberOfThreads: sumThreads, numberOfThreadsPercentChange: firstThreads, virtualSize: sumVirtualSize, virtualSizePercentChange: firstVirtualSize }; } //////////////////////////////////////////////////////////////////////////////// /// @brief convertSeries //////////////////////////////////////////////////////////////////////////////// function convertSeries (series, name) { var result = []; var i; for (i = 0; i < series.length; ++i) { result.push(series[i][name]); } return result; } //////////////////////////////////////////////////////////////////////////////// /// @brief computeStatisticsSeries //////////////////////////////////////////////////////////////////////////////// function computeStatisticsSeries (start, attrs) { var result = {}; var raw = computeStatisticsRaw(start); result.nextStart = raw.next; result.waitFor = (raw.next + STATISTICS_INTERVALL) - internal.time(); result.times = raw.times; if (result.waitFor < 1) { result.waitFor = 1; } if (attrs === null || attrs.avgTotalTime) { result.avgTotalTime = convertSeries(raw.series, "totalTime"); } if (attrs === null || attrs.totalTimeDistributionPercent) { result.totalTimeDistributionPercent = raw.distribution.totalTimePercent; } if (attrs === null || attrs.avgRequestTime) { result.avgRequestTime = convertSeries(raw.series, "requestTime"); } if (attrs === null || attrs.requestTimeDistribution) { result.requestTimeDistributionPercent = raw.distribution.requestTimePercent; } if (attrs === null || attrs.avgQueueTime) { result.avgQueueTime = convertSeries(raw.series, "queueTime"); } if (attrs === null || attrs.queueTimeDistributionPercent) { result.queueTimeDistributionPercent = raw.distribution.queueTimePercent; } if (attrs === null || attrs.avgIoTime) { result.avgIoTime = convertSeries(raw.series, "ioTime"); } if (attrs === null || attrs.bytesSentPerSecond) { result.bytesSentPerSecond = convertSeries(raw.series, "bytesSent"); } if (attrs === null || attrs.bytesSentDistributionPercent) { result.bytesSentDistributionPercent = raw.distribution.bytesSentPercent; } if (attrs === null || attrs.bytesReceivedPerSecond) { result.bytesReceivedPerSecond = convertSeries(raw.series, "bytesReceived"); } if (attrs === null || attrs.bytesReceivedDistributionPercent) { result.bytesReceivedDistributionPercent = raw.distribution.bytesReceivedPercent; } if (attrs === null || attrs.optionsPerSecond) { result.optionsPerSecond = convertSeries(raw.series, "optionsPerSecond"); } if (attrs === null || attrs.putsPerSecond) { result.putsPerSecond = convertSeries(raw.series, "putsPerSecond"); } if (attrs === null || attrs.headsPerSecond) { result.headsPerSecond = convertSeries(raw.series, "headsPerSecond"); } if (attrs === null || attrs.postsPerSecond) { result.postsPerSecond = convertSeries(raw.series, "postsPerSecond"); } if (attrs === null || attrs.getsPerSecond) { result.getsPerSecond = convertSeries(raw.series, "getsPerSecond"); } if (attrs === null || attrs.deletesPerSecond) { result.deletesPerSecond = convertSeries(raw.series, "deletesPerSecond"); } if (attrs === null || attrs.othersPerSecond) { result.othersPerSecond = convertSeries(raw.series, "othersPerSecond"); } if (attrs === null || attrs.patchesPerSecond) { result.patchesPerSecond = convertSeries(raw.series, "patchesPerSecond"); } if (attrs === null || attrs.asyncPerSecond) { result.asyncPerSecond = convertSeries(raw.series, "asyncPerSecond"); } if (attrs === null || attrs.asyncRequestsCurrent) { result.asyncRequestsCurrent = raw.current.asyncRequests; } if (attrs === null || attrs.asyncRequestsCurrentPercentChange) { result.asyncRequestsCurrentPercentChange = raw.current.asyncRequestsPercentChange; } if (attrs === null || attrs.clientConnections) { result.clientConnections = convertSeries(raw.series, "clientConnections"); } if (attrs === null || attrs.clientConnectionsCurrent) { result.clientConnectionsCurrent = raw.current.clientConnections; } if (attrs === null || attrs.clientConnectionsCurrentPercentChange) { result.clientConnectionsCurrentPercentChange = raw.current.clientConnectionsPercentChange; } if (attrs === null || attrs.numberOfThreads) { result.numberOfThreads = convertSeries(raw.series, "numberOfThreads"); } if (attrs === null || attrs.numberOfThreadsCurrent) { result.numberOfThreadsCurrent = raw.current.numberOfThreads; } if (attrs === null || attrs.numberOfThreadsCurrentPercentChange) { result.numberOfThreadsCurrentPercentChange = raw.current.numberOfThreadsPercentChange; } if (attrs === null || attrs.residentSize) { result.residentSize = convertSeries(raw.series, "residentSize"); } if (attrs === null || attrs.residentSizePercent) { result.residentSizePercent = convertSeries(raw.series, "residentSizePercent"); } if (attrs === null || attrs.virtualSize) { result.virtualSize = convertSeries(raw.series, "virtualSize"); } if (attrs === null || attrs.asyncRequestsCurrentPercentChange) { result.asyncRequestsCurrentPercentChange = raw.current.asyncRequestsPercentChange; } if (attrs === null || attrs.virtualSizeCurrent) { result.virtualSizeCurrent = raw.current.virtualSize; } if (attrs === null || attrs.virtualSizePercentChange) { result.virtualSizePercentChange = raw.current.virtualSizePercentChange; } if (attrs === null || attrs.majorPageFaultsPerSecond) { result.majorPageFaultsPerSecond = convertSeries(raw.series, "majorPageFaultsPerSecond"); } if (attrs === null || attrs.minorPageFaultsPerSecond) { result.minorPageFaultsPerSecond = convertSeries(raw.series, "minorPageFaultsPerSecond"); } if (attrs === null || attrs.cpuUserTime) { result.cpuUserTime = convertSeries(raw.series, "userTime"); } if (attrs === null || attrs.cpuSystemTime) { result.cpuSystemTime = convertSeries(raw.series, "systemTime"); } if (attrs === null || attrs.uptime) { result.uptime = raw.server.uptime; } if (attrs === null || attrs.physicalMemory) { result.physicalMemory = raw.server.physicalMemory; } if (result.next !== null) { var raw15 = computeStatisticsRaw15M(raw.next); if (attrs === null || attrs.asyncPerSecond15M) { result.asyncPerSecond15M = raw15.asyncPerSecond; } if (attrs === null || attrs.asyncPerSecondPercentChange15M) { result.asyncPerSecondPercentChange15M = raw15.asyncPerSecondPercentChange; } if (attrs === null || attrs.clientConnections15M) { result.clientConnections15M = raw15.clientConnections; } if (attrs === null || attrs.clientConnectionsPercentChange15M) { result.clientConnectionsPercentChange15M = raw15.clientConnectionsPercentChange; } if (attrs === null || attrs.numberOfThreads15M) { result.numberOfThreads15M = raw15.numberOfThreads; } if (attrs === null || attrs.numberOfThreadsPercentChange15M) { result.numberOfThreadsPercentChange15M = raw15.numberOfThreadsPercentChange; } if (attrs === null || attrs.virtualSize15M) { result.virtualSize15M = raw15.virtualSize; } if (attrs === null || attrs.virtualSizePercentChange15M) { result.virtualSizePercentChange15M = raw15.virtualSizePercentChange; } } return result; } // ----------------------------------------------------------------------------- // --SECTION-- public routes // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief full statistics history //////////////////////////////////////////////////////////////////////////////// controller.get("full", function (req, res) { var start = req.params("start"); var filter = req.params("filter"); var serverEndpoint = decodeURIComponent(req.params("serverEndpoint")); var DbServer = req.params("DbServer"); var attrs = null; if (start !== null && start !== undefined) { start = parseInt(start, 10); } if (filter !== null && filter !== undefined && filter !== "") { var s = filter.split(","); var i; attrs = {}; for (i = 0; i < s.length; ++i) { attrs[s[i]] = true; } } var series = computeStatisticsSeries(start, attrs); res.json(series); }).summary("Returns the complete or partial history") .notes("This function is just to get the complete or partial statistics history"); // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE // ----------------------------------------------------------------------------- // Local Variables: // mode: outline-minor // outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @\\}" // End: