mirror of https://gitee.com/bigwinds/arangodb
628 lines
18 KiB
JavaScript
628 lines
18 KiB
JavaScript
'use strict';
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief statistics handler
|
|
///
|
|
/// @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, Lucas Dohmen
|
|
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
var internal = require("internal");
|
|
var cluster = require("@arangodb/cluster");
|
|
var db = internal.db;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief initialized
|
|
///
|
|
/// Warning: there are many threads, so variable is thread local.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
var initialized = false;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief createCollections
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function createStatisticsCollection (name) {
|
|
var collection = db._collection(name);
|
|
|
|
if (collection === null) {
|
|
var r = null;
|
|
|
|
try {
|
|
r = db._create(name, { isSystem: true, waitForSync: false,
|
|
replicationFactor: 1,
|
|
distributeShardsLike: "_graphs" });
|
|
}
|
|
catch (err) {
|
|
}
|
|
|
|
if (! r) {
|
|
return false;
|
|
}
|
|
|
|
collection = db._collection(name);
|
|
}
|
|
|
|
if (collection !== null) {
|
|
collection.ensureIndex({ type: "skiplist", fields: [ "time" ] });
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief collectGarbage
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function collectGarbage (collection, start) {
|
|
var values = db._query(
|
|
"FOR s in @@collection "
|
|
+ " FILTER s.time < @start "
|
|
+ " RETURN s._id",
|
|
{ start: start, '@collection': collection });
|
|
|
|
while (values.hasNext()) {
|
|
var id = values.next();
|
|
|
|
try {
|
|
db._remove(id);
|
|
}
|
|
catch (err) {
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief lastEntry
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function lastEntry (collection, start, clusterId) {
|
|
var filter = "";
|
|
var bindVars = { start: start, '@collection': collection };
|
|
|
|
if (clusterId !== undefined && clusterId !== null) {
|
|
filter = " FILTER s.clusterId == @clusterId ";
|
|
bindVars.clusterId = clusterId;
|
|
}
|
|
|
|
var values = db._query(
|
|
"FOR s in @@collection "
|
|
+ "FILTER s.time >= @start "
|
|
+ filter
|
|
+ "SORT s.time desc "
|
|
+ "LIMIT 1 "
|
|
+ "RETURN s", bindVars);
|
|
|
|
if (values.hasNext()) {
|
|
return values.next();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief avgPercentDistributon
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
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 computes the figures on a per second basis
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function computePerSeconds (current, prev) {
|
|
// sanity check if we have restarted the server
|
|
if (prev.time + exports.STATISTICS_INTERVAL * 1.5 < current.time) {
|
|
return null;
|
|
}
|
|
|
|
if (prev.server.uptime > current.server.uptime) {
|
|
return null;
|
|
}
|
|
|
|
// compute differences and average per second
|
|
var dt = current.time - prev.time;
|
|
|
|
if (dt <= 0) {
|
|
return null;
|
|
}
|
|
|
|
var result = { time: current.time };
|
|
|
|
// system part
|
|
result.system = {};
|
|
|
|
result.system.minorPageFaultsPerSecond = (current.system.minorPageFaults - prev.system.minorPageFaults) / dt;
|
|
result.system.majorPageFaultsPerSecond = (current.system.majorPageFaults - prev.system.majorPageFaults) / dt;
|
|
result.system.userTimePerSecond = (current.system.userTime - prev.system.userTime) / dt;
|
|
result.system.systemTimePerSecond = (current.system.systemTime - prev.system.systemTime) / dt;
|
|
result.system.residentSize = current.system.residentSize;
|
|
result.system.residentSizePercent = current.system.residentSizePercent;
|
|
result.system.virtualSize = current.system.virtualSize;
|
|
result.system.numberOfThreads = current.system.numberOfThreads;
|
|
|
|
// http part
|
|
result.http = {};
|
|
|
|
result.http.requestsTotalPerSecond = (current.http.requestsTotal - prev.http.requestsTotal) / dt;
|
|
result.http.requestsAsyncPerSecond = (current.http.requestsAsync - prev.http.requestsAsync) / dt;
|
|
result.http.requestsGetPerSecond = (current.http.requestsGet - prev.http.requestsGet) / dt;
|
|
result.http.requestsHeadPerSecond = (current.http.requestsHead - prev.http.requestsHead) / dt;
|
|
result.http.requestsPostPerSecond = (current.http.requestsPost - prev.http.requestsPost) / dt;
|
|
result.http.requestsPutPerSecond = (current.http.requestsPut - prev.http.requestsPut) / dt;
|
|
result.http.requestsPatchPerSecond = (current.http.requestsPatch - prev.http.requestsPatch) / dt;
|
|
result.http.requestsDeletePerSecond = (current.http.requestsDelete - prev.http.requestsDelete) / dt;
|
|
result.http.requestsOptionsPerSecond = (current.http.requestsOptions - prev.http.requestsOptions) / dt;
|
|
result.http.requestsOtherPerSecond = (current.http.requestsOther - prev.http.requestsOther) / dt;
|
|
|
|
// client part
|
|
result.client = {};
|
|
|
|
result.client.httpConnections = current.client.httpConnections;
|
|
|
|
// bytes send
|
|
result.client.bytesSentPerSecond = (current.client.bytesSent.sum - prev.client.bytesSent.sum) / dt;
|
|
|
|
result.client.bytesSentPercent = avgPercentDistributon(
|
|
current.client.bytesSent,
|
|
prev.client.bytesSent,
|
|
internal.bytesSentDistribution);
|
|
|
|
// bytes received
|
|
result.client.bytesReceivedPerSecond = (current.client.bytesReceived.sum - prev.client.bytesReceived.sum) / dt;
|
|
|
|
result.client.bytesReceivedPercent = avgPercentDistributon(
|
|
current.client.bytesReceived,
|
|
prev.client.bytesReceived,
|
|
internal.bytesReceivedDistribution);
|
|
|
|
// total time
|
|
var d1 = current.client.totalTime.count - prev.client.totalTime.count;
|
|
|
|
if (d1 === 0) {
|
|
result.client.avgTotalTime = 0;
|
|
}
|
|
else {
|
|
result.client.avgTotalTime = (current.client.totalTime.sum - prev.client.totalTime.sum) / d1;
|
|
}
|
|
|
|
result.client.totalTimePercent = avgPercentDistributon(
|
|
current.client.totalTime,
|
|
prev.client.totalTime,
|
|
internal.requestTimeDistribution);
|
|
|
|
// request time
|
|
d1 = current.client.requestTime.count - prev.client.requestTime.count;
|
|
|
|
if (d1 === 0) {
|
|
result.client.avgRequestTime = 0;
|
|
}
|
|
else {
|
|
result.client.avgRequestTime = (current.client.requestTime.sum - prev.client.requestTime.sum) / d1;
|
|
}
|
|
|
|
result.client.requestTimePercent = avgPercentDistributon(
|
|
current.client.requestTime,
|
|
prev.client.requestTime,
|
|
internal.requestTimeDistribution);
|
|
|
|
// queue time
|
|
d1 = current.client.queueTime.count - prev.client.queueTime.count;
|
|
|
|
if (d1 === 0) {
|
|
result.client.avgQueueTime = 0;
|
|
}
|
|
else {
|
|
result.client.avgQueueTime = (current.client.queueTime.sum - prev.client.queueTime.sum) / d1;
|
|
}
|
|
|
|
result.client.queueTimePercent = avgPercentDistributon(
|
|
current.client.queueTime,
|
|
prev.client.queueTime,
|
|
internal.requestTimeDistribution);
|
|
|
|
// io time
|
|
d1 = current.client.ioTime.count - prev.client.ioTime.count;
|
|
|
|
if (d1 === 0) {
|
|
result.client.avgIoTime = 0;
|
|
}
|
|
else {
|
|
result.client.avgIoTime = (current.client.ioTime.sum - prev.client.ioTime.sum) / d1;
|
|
}
|
|
|
|
result.client.ioTimePercent = avgPercentDistributon(
|
|
current.client.ioTime,
|
|
prev.client.ioTime,
|
|
internal.requestTimeDistribution);
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief computes the 15 minute averages
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function compute15Minute (start, clusterId) {
|
|
var filter = "";
|
|
var bindVars = { start: start };
|
|
|
|
if (clusterId !== undefined && clusterId !== null) {
|
|
filter = " FILTER s.clusterId == @clusterId ";
|
|
bindVars.clusterId = clusterId;
|
|
}
|
|
|
|
var values = db._query(
|
|
"FOR s in _statistics "
|
|
+ " FILTER s.time >= @start "
|
|
+ filter
|
|
+ " SORT s.time "
|
|
+ " RETURN s", bindVars);
|
|
|
|
var result = {};
|
|
var count = 0;
|
|
|
|
result.system = {
|
|
minorPageFaultsPerSecond: 0,
|
|
majorPageFaultsPerSecond: 0,
|
|
userTimePerSecond: 0,
|
|
systemTimePerSecond: 0,
|
|
residentSize: 0,
|
|
virtualSize: 0,
|
|
numberOfThreads: 0
|
|
};
|
|
|
|
result.http = {
|
|
requestsTotalPerSecond: 0,
|
|
requestsAsyncPerSecond: 0,
|
|
requestsGetPerSecond: 0,
|
|
requestsHeadPerSecond: 0,
|
|
requestsPostPerSecond: 0,
|
|
requestsPutPerSecond: 0,
|
|
requestsPatchPerSecond: 0,
|
|
requestsDeletePerSecond: 0,
|
|
requestsOptionsPerSecond: 0,
|
|
requestsOtherPerSecond: 0
|
|
};
|
|
|
|
result.client = {
|
|
httpConnections: 0,
|
|
bytesSentPerSecond: 0,
|
|
bytesReceivedPerSecond: 0,
|
|
avgTotalTime: 0,
|
|
avgRequestTime: 0,
|
|
avgQueueTime: 0,
|
|
avgIoTime: 0
|
|
};
|
|
|
|
while (values.hasNext()) {
|
|
var raw = values.next();
|
|
|
|
result.time = raw.time;
|
|
|
|
result.system.minorPageFaultsPerSecond += raw.system.minorPageFaultsPerSecond;
|
|
result.system.majorPageFaultsPerSecond += raw.system.majorPageFaultsPerSecond;
|
|
result.system.userTimePerSecond += raw.system.userTimePerSecond;
|
|
result.system.systemTimePerSecond += raw.system.systemTimePerSecond;
|
|
result.system.residentSize += raw.system.residentSize;
|
|
result.system.virtualSize += raw.system.virtualSize;
|
|
result.system.numberOfThreads += raw.system.numberOfThreads;
|
|
|
|
result.http.requestsTotalPerSecond += raw.http.requestsTotalPerSecond;
|
|
result.http.requestsAsyncPerSecond += raw.http.requestsAsyncPerSecond;
|
|
result.http.requestsGetPerSecond += raw.http.requestsGetPerSecond;
|
|
result.http.requestsHeadPerSecond += raw.http.requestsHeadPerSecond;
|
|
result.http.requestsPostPerSecond += raw.http.requestsPostPerSecond;
|
|
result.http.requestsPutPerSecond += raw.http.requestsPutPerSecond;
|
|
result.http.requestsPatchPerSecond += raw.http.requestsPatchPerSecond;
|
|
result.http.requestsDeletePerSecond += raw.http.requestsDeletePerSecond;
|
|
result.http.requestsOptionsPerSecond += raw.http.requestsOptionsPerSecond;
|
|
result.http.requestsOtherPerSecond += raw.http.requestsOtherPerSecond;
|
|
|
|
result.client.httpConnections += raw.client.httpConnections;
|
|
result.client.bytesSentPerSecond += raw.client.bytesSentPerSecond;
|
|
result.client.bytesReceivedPerSecond += raw.client.bytesReceivedPerSecond;
|
|
result.client.avgTotalTime += raw.client.avgTotalTime;
|
|
result.client.avgRequestTime += raw.client.avgRequestTime;
|
|
result.client.avgQueueTime += raw.client.avgQueueTime;
|
|
result.client.avgIoTime += raw.client.avgIoTime;
|
|
|
|
count++;
|
|
}
|
|
|
|
if (count !== 0) {
|
|
result.system.minorPageFaultsPerSecond /= count;
|
|
result.system.majorPageFaultsPerSecond /= count;
|
|
result.system.userTimePerSecond /= count;
|
|
result.system.systemTimePerSecond /= count;
|
|
result.system.residentSize /= count;
|
|
result.system.virtualSize /= count;
|
|
result.system.numberOfThreads /= count;
|
|
|
|
result.http.requestsTotalPerSecond /= count;
|
|
result.http.requestsAsyncPerSecond /= count;
|
|
result.http.requestsGetPerSecond /= count;
|
|
result.http.requestsHeadPerSecond /= count;
|
|
result.http.requestsPostPerSecond /= count;
|
|
result.http.requestsPutPerSecond /= count;
|
|
result.http.requestsPatchPerSecond /= count;
|
|
result.http.requestsDeletePerSecond /= count;
|
|
result.http.requestsOptionsPerSecond /= count;
|
|
result.http.requestsOtherPerSecond /= count;
|
|
|
|
result.client.httpConnections /= count;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief statistics interval
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.STATISTICS_INTERVAL = 10;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief statistics interval for history
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.STATISTICS_HISTORY_INTERVAL = 15 * 60;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief createCollections
|
|
///
|
|
/// This cannot be called during version check, because the collections are
|
|
/// system wide and the version checks might not yet know, that it is running
|
|
/// on a cluster coordinate.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.createStatisticsCollections = function () {
|
|
if (initialized) {
|
|
return true;
|
|
}
|
|
|
|
initialized = true;
|
|
|
|
var names = [ "_statisticsRaw", "_statistics", "_statistics15" ];
|
|
var i;
|
|
|
|
for (i = 0; i < names.length; ++i) {
|
|
if (! createStatisticsCollection(names[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief creates a statistics entry
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.historian = function () {
|
|
if (! exports.createStatisticsCollections()) {
|
|
return;
|
|
}
|
|
|
|
var clusterId;
|
|
|
|
if (cluster.isCluster()) {
|
|
clusterId = global.ArangoServerState.id();
|
|
}
|
|
|
|
try {
|
|
var now = internal.time();
|
|
var prevRaw = lastEntry(
|
|
'_statisticsRaw',
|
|
now - 2 * exports.STATISTICS_INTERVAL,
|
|
clusterId);
|
|
|
|
// create new raw statistics
|
|
var raw = {};
|
|
|
|
raw.time = now;
|
|
raw.system = internal.processStatistics();
|
|
raw.client = internal.clientStatistics();
|
|
raw.http = internal.httpStatistics();
|
|
raw.server = internal.serverStatistics();
|
|
|
|
if (clusterId !== undefined) {
|
|
raw.clusterId = clusterId;
|
|
}
|
|
|
|
db.__save("_statisticsRaw", raw);
|
|
|
|
// create the per-seconds statistics
|
|
if (prevRaw !== null) {
|
|
var perSecs = computePerSeconds(raw, prevRaw);
|
|
|
|
if (perSecs !== null) {
|
|
if (clusterId !== undefined) {
|
|
perSecs.clusterId = clusterId;
|
|
}
|
|
|
|
db.__save("_statistics", perSecs);
|
|
}
|
|
}
|
|
}
|
|
catch (err) {
|
|
require("console").warn("catch error in historian: %s", err.stack);
|
|
}
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief creates an average entry
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.historianAverage = function () {
|
|
if (! exports.createStatisticsCollections()) {
|
|
return;
|
|
}
|
|
|
|
var stats15m = db._statistics15;
|
|
|
|
var clusterId;
|
|
|
|
if (cluster.isCluster()) {
|
|
clusterId = global.ArangoServerState.id();
|
|
}
|
|
|
|
try {
|
|
var now = internal.time();
|
|
|
|
// check if need to create a new 15 min interval
|
|
var prev15 = lastEntry(
|
|
'_statistics15',
|
|
now - 2 * exports.STATISTICS_HISTORY_INTERVAL,
|
|
clusterId);
|
|
|
|
var stat15;
|
|
var start;
|
|
|
|
if (prev15 === null) {
|
|
start = now - exports.STATISTICS_HISTORY_INTERVAL;
|
|
}
|
|
else {
|
|
start = prev15.time;
|
|
}
|
|
|
|
stat15 = compute15Minute(start, clusterId);
|
|
|
|
if (stat15 !== undefined) {
|
|
if (clusterId !== undefined) {
|
|
stat15.clusterId = clusterId;
|
|
}
|
|
|
|
stats15m.save(stat15);
|
|
}
|
|
}
|
|
catch (err) {
|
|
// we don't want this error to appear every x seconds
|
|
// require("console").warn("catch error in historianAverage: %s", err);
|
|
}
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief collects garbage
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.garbageCollector = function () {
|
|
if (! exports.createStatisticsCollections()) {
|
|
return;
|
|
}
|
|
|
|
var time = internal.time();
|
|
|
|
collectGarbage("_statistics", time - 60 * 60);
|
|
collectGarbage("_statisticsRaw", time - 60 * 60);
|
|
collectGarbage("_statistics15", time - 30 * 24 * 60 * 60);
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief initialize the module, we delay the actual scheduling of the
|
|
/// periodic tasks until the cluster is fully initialized and we actually
|
|
/// can run AQL queries
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
exports.installPeriodicTasks = function () {
|
|
if (typeof internal.registerTask !== "function") {
|
|
return;
|
|
}
|
|
|
|
console.debug("Statistics: Installing regular tasks...");
|
|
|
|
var interval = exports.STATISTICS_INTERVAL;
|
|
var interval15 = exports.STATISTICS_HISTORY_INTERVAL;
|
|
|
|
internal.registerTask({
|
|
id: "statistics-collector",
|
|
name: "statistics-collector",
|
|
offset: interval / 10,
|
|
period: interval,
|
|
command: "require('@arangodb/statistics').historian();"
|
|
});
|
|
|
|
internal.registerTask({
|
|
id: "statistics-average-collector",
|
|
name: "statistics-average-collector",
|
|
offset: 2 * interval,
|
|
period: interval15,
|
|
command: "require('@arangodb/statistics').historianAverage();"
|
|
});
|
|
|
|
internal.registerTask({
|
|
id: "statistics-gc",
|
|
name: "statistics-gc",
|
|
offset: Math.random() * interval15 / 2,
|
|
period: interval15 / 2,
|
|
command: "require('@arangodb/statistics').garbageCollector();"
|
|
});
|
|
};
|
|
|
|
exports.startup = function () {
|
|
internal.registerTask({
|
|
id: "statistics-periodic-task-installer",
|
|
name: "statistics-periodic-task-installer",
|
|
offset: 60,
|
|
command: "require('@arangodb/statistics').installPeriodicTasks();"
|
|
});
|
|
};
|
|
|