<commit_before>'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. <NAME>, <NAME>
/// @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
////////////////////////////////////////////////////////////////////////////////

exports.startup = function () {
  if (typeof internal.registerTask !== "function") {
    return;
  }

  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();"
  });
};


<commit_msg>Back to replicationFactor 2 for statistics.<commit_after>'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. <NAME>, <NAME>
/// @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: 2, 
                             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
////////////////////////////////////////////////////////////////////////////////

exports.startup = function () {
  if (typeof internal.registerTask !== "function") {
    return;
  }

  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();"
  });
};



{'fraction_non_alphanumeric': 0.2398265328387519, 'fraction_numerical': 0.007882391446650665, 'mean_word_length': 1.9315583273366914, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7545771', 'n_tokens_mistral': 10427, 'n_tokens_neox': 9897, 'n_words': 2151}