<commit_before>/**
   Copyright 2011 Couchbase, Inc.

   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.
 **/
var StatsModel = {};

(function (self) {
  // starts future.get with given ajaxOptions and arranges invocation
  // of 'body' with value or if future is cancelled invocation of body
  // with 'cancelledValue'.
  //
  // NOTE: this is not normal future as normal futures deliver values
  // to cell, but this one 'delivers' values to given function 'body'.
  function getCPS(ajaxOptions, cancelledValue, body) {
    // this wrapper calls 'body' instead of delivering value to it's
    // cell
    var futureWrapper = future.wrap(function (ignoredDataCallback, startGet) {
      return startGet(body);
    });
    var async = future.get(ajaxOptions, undefined, undefined, futureWrapper);
    async.cancel = (function (realCancel) {
      return function () {
        if (realCancel) {
          realCancel.call(this);
        }
        body(cancelledValue);
      }
    })(async.cancel);
    // we need any cell here to satisfy our not yet perfect API
    async.start(new Cell());
    return async;
  }

  function createSamplesFuture(statsURL, statsData, bufferDepth, realTimeRestorer) {
    var cancelMark = {};
    var mark404 = {};

    function doGet(data, body) {
      if (mainAsync.cancelled) {
        return body(cancelMark);
      }

      function onCancel() {
        async.cancel();
      }
      function unbind() {
        $(mainAsync).unbind('cancelled', onCancel);
      }
      $(mainAsync).bind('cancelled', onCancel);

      var async;
      return async = getCPS({url: statsURL, data: data, missingValue: mark404}, cancelMark, function (value, status, xhr) {
        unbind();
        if (value !== cancelMark && value !== mark404) {
          var date = xhr.getResponseHeader('date');
          value.serverDate = parseHTTPDate(date).valueOf();
          value.clientDate = (new Date()).valueOf();
        }
        body(value, status, xhr);
      });
    }

    var mainAsync = future(function (dataCallback) {
      function deliverValue(value) {
        return dataCallback.continuing(value);
      }
      nonRealtimeLoop(deliverValue);
    });
    mainAsync.cancel = function () {
      $(this).trigger('cancelled');
    }

    return mainAsync;

    function nonRealtimeLoop(deliverValue) {
      doGet(statsData, function (value) {
        if (value === cancelMark || value === mark404) {
          return;
        }

        deliverValue(value);

        // this is the only non-generic place in this function. We're
        // able to extract interval from specific and from normal
        // stats
        var interval = value.interval || value.op.interval;
        if (interval < 2000) {
          startRealTimeLoop(deliverValue, value);
        } else {
          setTimeout(function () {
            nonRealtimeLoop(deliverValue);
          }, Math.min(interval/2, 60000));
        }
      });
    }

    function startRealTimeLoop(realDeliverValue, value) {
      // we're going to actually deliver values each second and
      // deliverValue we pass to loop just stores last value
      function deliverValue(val) {
        value = val;
      }
      var intervalId = setInterval(function () {
        realDeliverValue(_.clone(value));
      }, 1000);

      function onCancel() {
        clearInterval(intervalId);
        unbind();
      }
      function unbind() {
        $(mainAsync).unbind('cancelled', onCancel);
      }
      $(mainAsync).bind('cancelled', onCancel);

      realTimeLoop(deliverValue, value, realTimeRestorer);
    }

    function realTimeLoop(deliverValue, lastValue, realTimeRestorer) {
      realTimeRestorer(lastValue, statsData, bufferDepth, function (data, restorer) {
        doGet(data, function (rawStats) {
          if (rawStats === cancelMark || rawStats === mark404) {
            return;
          }

          var restoredValue = restorer(rawStats);
          deliverValue(restoredValue);
          realTimeLoop(deliverValue, restoredValue, realTimeRestorer);
        });
      });
    }
  }

  // Prepares data for stats request and returns it and stats
  // restoring function. This code is calling continuation 'cont' with
  // two arguments rather then returning array with two elements (and
  // then having to unpack at call-site).
  //
  // Returned stats restoring function will then be called with result
  // of get request and is responsible for building complete stats
  // from old sample it has and new result.
  function aggregateRealTimeRestorer(lastValue, statsData, bufferDepth, mvReturn) {
    var keepCount = lastValue.op.samplesCount + bufferDepth;
    var data = statsData;

    if (lastValue.op.lastTStamp) {
      data = _.extend({haveTStamp: lastValue.op.lastTStamp}, data);
    } else {
      // if we don't have any stats, pause a bit, dont send new stats
      // request immediately. Because doing so will return immediately
      // making us send another request and so on, spamming server.
      setTimeout(function () {
        mvReturn(data, restorer);
      }, 1000);
      return;
    }

    return mvReturn(data, restorer);
    function restorer(rawStats) {
      var op = rawStats.op;

      var samples = op.samples;
      // if we have empty stats re-use previous value instead of rendering empty stats
      if (samples && samples.timestamp.length === 0) {
        return lastValue;
      }

      if (lastValue === undefined) {
        return rawStats;
      }

      var newSamples = {};
      var prevSamples = lastValue.op.samples;

      if (prevSamples.timestamp === undefined ||
          prevSamples.timestamp.length === 0 ||
          prevSamples.timestamp[prevSamples.timestamp.length - 1] != samples.timestamp[0]) {
        return rawStats;
      }

      for (var keyName in samples) {
        newSamples[keyName] = prevSamples[keyName].concat(samples[keyName].slice(1)).slice(-keepCount);
      }

      var restored = _.clone(rawStats);
      restored.op = _.clone(op);
      restored.op.samples = newSamples;

      return restored;
    }
  }

  function specificStatsRealTimeRestorer(lastValue, statsData, bufferDepth, mvReturn) {
    var keepCount = lastValue.samplesCount + bufferDepth;
    var data = statsData;

    if (lastValue.lastTStamp) {
      data = _.extend({haveTStamp: lastValue.lastTStamp}, data);
    } else {
      // if we don't have any stats, pause a bit, dont send new stats
      // request immediately. Because doing so will return immediately
      // making us send another request and so on, spamming server.
      setTimeout(function () {
        mvReturn(data, restorer);
      }, 1000);
      return;
    }

    return mvReturn(data, restorer);
    function restorer(rawStats) {
      // if we have empty stats re-use previous value instead of rendering empty stats<commit_msg>MB-7090: don't crash ui when there's brand-new key in samples<commit_after>/**
   Copyright 2011 Couchbase, Inc.

   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.
 **/
var StatsModel = {};

(function (self) {
  // starts future.get with given ajaxOptions and arranges invocation
  // of 'body' with value or if future is cancelled invocation of body
  // with 'cancelledValue'.
  //
  // NOTE: this is not normal future as normal futures deliver values
  // to cell, but this one 'delivers' values to given function 'body'.
  function getCPS(ajaxOptions, cancelledValue, body) {
    // this wrapper calls 'body' instead of delivering value to it's
    // cell
    var futureWrapper = future.wrap(function (ignoredDataCallback, startGet) {
      return startGet(body);
    });
    var async = future.get(ajaxOptions, undefined, undefined, futureWrapper);
    async.cancel = (function (realCancel) {
      return function () {
        if (realCancel) {
          realCancel.call(this);
        }
        body(cancelledValue);
      }
    })(async.cancel);
    // we need any cell here to satisfy our not yet perfect API
    async.start(new Cell());
    return async;
  }

  function createSamplesFuture(statsURL, statsData, bufferDepth, realTimeRestorer) {
    var cancelMark = {};
    var mark404 = {};

    function doGet(data, body) {
      if (mainAsync.cancelled) {
        return body(cancelMark);
      }

      function onCancel() {
        async.cancel();
      }
      function unbind() {
        $(mainAsync).unbind('cancelled', onCancel);
      }
      $(mainAsync).bind('cancelled', onCancel);

      var async;
      return async = getCPS({url: statsURL, data: data, missingValue: mark404}, cancelMark, function (value, status, xhr) {
        unbind();
        if (value !== cancelMark && value !== mark404) {
          var date = xhr.getResponseHeader('date');
          value.serverDate = parseHTTPDate(date).valueOf();
          value.clientDate = (new Date()).valueOf();
        }
        body(value, status, xhr);
      });
    }

    var mainAsync = future(function (dataCallback) {
      function deliverValue(value) {
        return dataCallback.continuing(value);
      }
      nonRealtimeLoop(deliverValue);
    });
    mainAsync.cancel = function () {
      $(this).trigger('cancelled');
    }

    return mainAsync;

    function nonRealtimeLoop(deliverValue) {
      doGet(statsData, function (value) {
        if (value === cancelMark || value === mark404) {
          return;
        }

        deliverValue(value);

        // this is the only non-generic place in this function. We're
        // able to extract interval from specific and from normal
        // stats
        var interval = value.interval || value.op.interval;
        if (interval < 2000) {
          startRealTimeLoop(deliverValue, value);
        } else {
          setTimeout(function () {
            nonRealtimeLoop(deliverValue);
          }, Math.min(interval/2, 60000));
        }
      });
    }

    function startRealTimeLoop(realDeliverValue, value) {
      // we're going to actually deliver values each second and
      // deliverValue we pass to loop just stores last value
      function deliverValue(val) {
        value = val;
      }
      var intervalId = setInterval(function () {
        realDeliverValue(_.clone(value));
      }, 1000);

      function onCancel() {
        clearInterval(intervalId);
        unbind();
      }
      function unbind() {
        $(mainAsync).unbind('cancelled', onCancel);
      }
      $(mainAsync).bind('cancelled', onCancel);

      realTimeLoop(deliverValue, value, realTimeRestorer);
    }

    function realTimeLoop(deliverValue, lastValue, realTimeRestorer) {
      realTimeRestorer(lastValue, statsData, bufferDepth, function (data, restorer) {
        doGet(data, function (rawStats) {
          if (rawStats === cancelMark || rawStats === mark404) {
            return;
          }

          var restoredValue = restorer(rawStats);
          deliverValue(restoredValue);
          realTimeLoop(deliverValue, restoredValue, realTimeRestorer);
        });
      });
    }
  }

  // Prepares data for stats request and returns it and stats
  // restoring function. This code is calling continuation 'cont' with
  // two arguments rather then returning array with two elements (and
  // then having to unpack at call-site).
  //
  // Returned stats restoring function will then be called with result
  // of get request and is responsible for building complete stats
  // from old sample it has and new result.
  function aggregateRealTimeRestorer(lastValue, statsData, bufferDepth, mvReturn) {
    var keepCount = lastValue.op.samplesCount + bufferDepth;
    var data = statsData;

    if (lastValue.op.lastTStamp) {
      data = _.extend({haveTStamp: lastValue.op.lastTStamp}, data);
    } else {
      // if we don't have any stats, pause a bit, dont send new stats
      // request immediately. Because doing so will return immediately
      // making us send another request and so on, spamming server.
      setTimeout(function () {
        mvReturn(data, restorer);
      }, 1000);
      return;
    }

    return mvReturn(data, restorer);
    function restorer(rawStats) {
      var op = rawStats.op;

      var samples = op.samples;
      // if we have empty stats re-use previous value instead of rendering empty stats
      if (samples && samples.timestamp.length === 0) {
        return lastValue;
      }

      if (lastValue === undefined) {
        return rawStats;
      }

      var newSamples = {};
      var prevSamples = lastValue.op.samples;

      if (prevSamples.timestamp === undefined ||
          prevSamples.timestamp.length === 0 ||
          prevSamples.timestamp[prevSamples.timestamp.length - 1] != samples.timestamp[0]) {
        return rawStats;
      }

      for (var keyName in samples) {
        newSamples[keyName] = (prevSamples[keyName] || []).concat(samples[keyName].slice(1)).slice(-keepCount);
      }

      var restored = _.clone(rawStats);
      restored.op = _.clone(op);
      restored.op.samples = newSamples;

      return restored;
    }
  }

  function specificStatsRealTimeRestorer(lastValue, statsData, bufferDepth, mvReturn) {
    var keepCount = lastValue.samplesCount + bufferDepth;
    var data = statsData;

    if (lastValue.lastTStamp) {
      data = _.extend({haveTStamp: lastValue.lastTStamp}, data);
    } else {
      // if we don't have any stats, pause a bit, dont send new stats
      // request immediately. Because doing so will return immediately
      // making us send another request and so on, spamming server.
      setTimeout(function () {
        mvReturn(data, restorer);
      }, 1000);
      return;
    }

    return mvReturn(data, restorer);
    function restorer(rawStats) {
      // if we have empty stats re-use previous value instead of rendering empty stats
{'fraction_non_alphanumeric': 0.10199240986717267, 'fraction_numerical': 0.007047980482515587, 'mean_word_length': 2.56966618287373, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, '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': '7362252', 'n_tokens_mistral': 4140, 'n_tokens_neox': 3963, 'n_words': 1265}