UNPKG

lightstep-tracer

Version:

> ❗ **This instrumentation is no longer recommended**. Please review [documentation on setting up and configuring the OpenTelemetry Node.js Launcher](https://github.com/lightstep/otel-launcher-node) or [OpenTelemetry JS (Browser)](https://github.com/open-

160 lines (126 loc) 6.65 kB
"use strict"; var _each2 = _interopRequireDefault(require("../../_each")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } // How many updates before a sample is considered old. This happens to // be one less than the number of samples in our buffer but that's // somewhat arbitrary. var kMaxOffsetAge = 7; var kStoredSamplesTTLMicros = 60 * 60 * 1000 * 1000; // 1 hour var ClockState = /*#__PURE__*/function () { function ClockState(opts) { _classCallCheck(this, ClockState); this._nowMicros = opts.nowMicros; this._localStoreGet = opts.localStoreGet; this._localStoreSet = opts.localStoreSet; // The last eight samples, computed from timing information in // RPCs. this._samples = []; this._currentOffsetMicros = 0; // How many updates since we've updated currentOffsetMicros. this._currentOffsetAge = kMaxOffsetAge + 1; // Try to load samples from the local store. // Only use the data if it's recent. var storedData = this._localStoreGet(); if (storedData && storedData.timestamp_micros && storedData.timestamp_micros > this._nowMicros() - kStoredSamplesTTLMicros) { // Make sure there are no more than (kMaxOffsetAge+1) elements this._samples = storedData.samples.slice(-(kMaxOffsetAge + 1)); } // Update the current offset based on these data. this.update(); } // Add a new timing sample and update the offset. _createClass(ClockState, [{ key: "addSample", value: function addSample(originMicros, receiveMicros, transmitMicros, destinationMicros) { var latestDelayMicros = Number.MAX_VALUE; var latestOffsetMicros = 0; // Ensure that all of the data are valid before using them. If // not, we'll push a {0, MAX} record into the queue. if (originMicros > 0 && receiveMicros > 0 && transmitMicros > 0 && destinationMicros > 0) { latestDelayMicros = destinationMicros - originMicros - (transmitMicros - receiveMicros); latestOffsetMicros = (receiveMicros - originMicros + (transmitMicros - destinationMicros)) / 2; } // Discard the oldest sample and push the new one. if (this._samples.length === kMaxOffsetAge + 1) { this._samples.shift(); } this._samples.push({ delayMicros: latestDelayMicros, offsetMicros: latestOffsetMicros }); this._currentOffsetAge++; // Update the local store with this new sample. this._localStoreSet({ timestamp_micros: this._nowMicros(), samples: this._samples }); this.update(); } // Update the time offset based on the current samples. }, { key: "update", value: function update() { // This is simplified version of the clock filtering in Simple // NTP. It ignores precision and dispersion (frequency error). In // brief, it keeps the 8 (kMaxOffsetAge+1) most recent // delay-offset pairs, and considers the offset with the smallest // delay to be the best one. However, it only uses this new offset // if the change (relative to the last offset) is small compared // to the estimated error. // // See: // https://tools.ietf.org/html/rfc5905#appendix-A.5.2 // http://books.google.com/books?id=pdTcJBfnbq8C // esp. section 3.5 // http://www.eecis.udel.edu/~mills/ntp/html/filter.html // http://www.eecis.udel.edu/~mills/database/brief/algor/algor.pdf // http://www.eecis.udel.edu/~mills/ntp/html/stats.html // TODO: Consider huff-n'-puff if the delays are highly asymmetric. // http://www.eecis.udel.edu/~mills/ntp/html/huffpuff.html // Find the sample with the smallest delay; the corresponding // offset is the "best" one. var minDelayMicros = Number.MAX_VALUE; var bestOffsetMicros = 0; (0, _each2["default"])(this._samples, function (sample) { if (sample.delayMicros < minDelayMicros) { minDelayMicros = sample.delayMicros; bestOffsetMicros = sample.offsetMicros; } }); // No update. if (bestOffsetMicros === this._currentOffsetMicros) { return; } // Now compute the jitter, i.e. the error relative to the new // offset were we to use it. var jitter = 0; (0, _each2["default"])(this._samples, function (sample) { // eslint-disable-next-line no-restricted-properties jitter += Math.pow(bestOffsetMicros - sample.offsetMicros, 2); }); jitter = Math.sqrt(jitter / this._samples.length); // Ignore spikes: only use the new offset if the change is not too // large... unless the current offset is too old. The "too old" // condition is also triggered when update() is called from the // constructor. var kSGATE = 3; // See RFC 5905 if (this._currentOffsetAge > kMaxOffsetAge || Math.abs(this._currentOffsetMicros - bestOffsetMicros) < kSGATE * jitter) { this._currentOffsetMicros = bestOffsetMicros; this._currentOffsetAge = 0; } } // Returns the difference in microseconds between the server's clock // and our clock. This should be added to any local timestamps before // sending them to the server. Note that a negative offset means that // the local clock is ahead of the server's. }, { key: "offsetMicros", value: function offsetMicros() { return Math.floor(this._currentOffsetMicros); } // Returns true if we've performed enough measurements to be confident // in the current offset. }, { key: "isReady", value: function isReady() { return this._samples.length > 3; } }, { key: "activeSampleCount", value: function activeSampleCount() { return this._samples.length; } }]); return ClockState; }(); module.exports = ClockState; //# sourceMappingURL=clock_state.js.map