@svta/cml-throughput 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,5 +30,5 @@ estimator.sample({
30
30
  });
31
31
 
32
32
  console.log(estimator.getEstimate());
33
- // 412
33
+ // 285
34
34
  ```
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@ type ThroughputEstimator = {
15
15
  */
16
16
  sample(sample: ResourceTiming): void;
17
17
  /**
18
- * Returns the current estimate of the throughput (in bytes per second)
18
+ * Returns the current estimate of the throughput (in bits per second)
19
19
  *
20
20
  * @returns The current estimate of the throughput
21
21
  */
@@ -38,15 +38,27 @@ declare class ArithmeticMeanEstimator implements ThroughputEstimator {
38
38
  /**
39
39
  * Exponential Weighted Moving Average (EWMA)
40
40
  *
41
+ * Each sample has a weight, for example the duration of a download in seconds.
42
+ * A half-life of `h` weight units corresponds to an `alpha` of `Math.exp(Math.log(0.5) / h)`.
43
+ *
44
+ * @example
45
+ * {@includeCode ../test/Ewma.test.ts#example}
46
+ *
41
47
  * @public
42
48
  */
43
49
  declare class Ewma {
44
50
  private alpha;
45
51
  private estimate;
46
52
  private totalDuration;
53
+ /**
54
+ * @param alpha - The smoothing factor, between 0 and 1. A larger `alpha` expires old samples more slowly.
55
+ */
47
56
  constructor(alpha: number);
48
57
  /**
49
58
  * Samples the value of the resource and updates the estimate.
59
+ *
60
+ * The new value has the weight `1 - alpha ^ weight`.
61
+ * The method ignores a sample that would make the estimate `NaN`.
50
62
  */
51
63
  sample(weight: number, value: number): void;
52
64
  /**
@@ -69,10 +81,41 @@ declare class Ewma {
69
81
  * @public
70
82
  */
71
83
  type EwmaEstimatorOptions = {
84
+ /**
85
+ * The estimate, in bits per second, that `getEstimate` returns before the first sample.
86
+ * `getEstimate` also returns this value while the sampled bytes are below `minTotalBytes`.
87
+ * The estimator also adds this value to both averages as a prior sample with a weight of 1 second.
88
+ *
89
+ * @defaultValue `NaN`
90
+ */
72
91
  defaultEstimate?: number;
92
+ /**
93
+ * The number of bytes to sample before `getEstimate` returns a measured estimate.
94
+ * Below this total, `getEstimate` returns `defaultEstimate`.
95
+ * shaka-player uses 128000.
96
+ *
97
+ * @defaultValue `0`
98
+ */
73
99
  minTotalBytes?: number;
100
+ /**
101
+ * The smallest `encodedBodySize`, in bytes, that counts as a sample.
102
+ * The estimator ignores smaller responses, because the time to first byte dominates their duration.
103
+ * shaka-player uses 16000.
104
+ *
105
+ * @defaultValue `0`
106
+ */
74
107
  minBodySize?: number;
108
+ /**
109
+ * The half-life of the fast moving average, in seconds.
110
+ * Samples from the last `fastHalfLife` seconds have half of the weight in the estimate.
111
+ * shaka-player uses 2.
112
+ */
75
113
  fastHalfLife: number;
114
+ /**
115
+ * The half-life of the slow moving average, in seconds.
116
+ * Samples from the last `slowHalfLife` seconds have half of the weight in the estimate.
117
+ * shaka-player uses 5.
118
+ */
76
119
  slowHalfLife: number;
77
120
  };
78
121
  //#endregion
@@ -80,6 +123,9 @@ type EwmaEstimatorOptions = {
80
123
  /**
81
124
  * Exponential Weighted Moving Average (EWMA) throughput estimator based on 2 half-lives
82
125
  *
126
+ * @example
127
+ * {@includeCode ../test/EwmaEstimator.test.ts#example}
128
+ *
83
129
  * @public
84
130
  */
85
131
  declare class EwmaEstimator implements ThroughputEstimator {
@@ -87,6 +133,7 @@ declare class EwmaEstimator implements ThroughputEstimator {
87
133
  private minTotalBytes;
88
134
  private minBodySize;
89
135
  private totalBytes;
136
+ private hasSamples;
90
137
  private fastEwma;
91
138
  private slowEwma;
92
139
  constructor(options: EwmaEstimatorOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/ThroughputEstimator.ts","../src/ArithmeticMeanEstimator.ts","../src/Ewma.ts","../src/EwmaEstimatorOptions.ts","../src/EwmaEstimator.ts","../src/HarmonicMeanEstimator.ts","../src/ZlemaEstimator.ts"],"sourcesContent":[],"mappings":";;;;;;AAOA;;;KAAY,mBAAA;ECEZ;;;;ACJA;iBFQgB;;;AGRhB;;;;ACMA,CAAA;;;;AJJA;;;;ACEa,cAAA,uBAAA,YAAmC,mBAAA,CAAA;;iBAGzB;;ACPvB;;;;;;AFEA;;cEFa,IAAA;;EDIb,QAAa,QAAA;;;;ACJb;;;;ACAA;;;;ACMA;EAQ6B,WAAA,CAAA,CAAA,EAAA,MAAA;EAcN;;;;;;;;;;AJ1BvB;;KGFY,oBAAA;;EFIZ,aAAa,CAAA,EAAA,MAAA;;;;ACJb,CAAA;;;AFEA;;;;ACEA;cGEa,aAAA,YAAyB;;;EFNtC,QAAa,WAAA;;;;ECAb,WAAY,CAAA,OAAA,ECciB,oBDdjB;iBC4BW;;;;;;AJ1BvB;;;;ACEa,cIAA,qBAAA,YAAiC,mBJAE,CAAA;;iBIGzB;;AHPvB;;;;AFEA;;;;ACEa,cKAA,cAAA,YAA0B,mBLAS,CAAA;;iBKGzB;;AJPvB"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/ThroughputEstimator.ts","../src/ArithmeticMeanEstimator.ts","../src/Ewma.ts","../src/EwmaEstimatorOptions.ts","../src/EwmaEstimator.ts","../src/HarmonicMeanEstimator.ts","../src/ZlemaEstimator.ts"],"sourcesContent":[],"mappings":";;;;;;AAOA;;;KAAY,mBAAA;ECEZ;;;;ACEA;iBFEgB;;;AGRhB;;;;ACSA,CAAA;;;;AJPA;;;;ACEa,cAAA,uBAAA,YAAmC,mBAAA,CAAA;;iBAGzB;;ACDvB;;;;;;AFJA;;;;ACEA;;;;ACEa,cAAA,IAAA,CAAA;;;;ECNb;;;;ECSA;;;;;;;;ACLA;;;;ACAA;;;;;;;;;;;;ANFA;;KGFY,oBAAA;;AFIZ;;;;ACEA;;;;ACNA;;;;ACSA;;EAuBuB,aAAA,CAAA,EAAA,MAAA;EAvBe;;;;;ACLtC;;;;ACAA;;;;;;;;;;;;;;ANFA;;;;ACEA;;;;ACEa,cEGA,aAAA,YAAyB,mBFHzB,CAAA;;;;ECNb,QAAY,UAAA;;;;ECSZ,WAAa,CAAA,OAAA,EASgB,oBAThB;EASgB,MAAA,CAAA,MAAA,EAcN,cAdM,CAAA,EAAA,IAAA;EAcN,WAAA,CAAA,CAAA,EAAA,MAAA;;;;;AJ9BvB;;;;ACEa,cIAA,qBAAA,YAAiC,mBJAE,CAAA;;iBIGzB;;AHDvB;;;;AFJA;;;;ACEa,cKAA,cAAA,YAA0B,mBLAS,CAAA;;iBKGzB;;AJDvB"}
package/dist/index.js CHANGED
@@ -25,9 +25,18 @@ var ArithmeticMeanEstimator = class {
25
25
  /**
26
26
  * Exponential Weighted Moving Average (EWMA)
27
27
  *
28
+ * Each sample has a weight, for example the duration of a download in seconds.
29
+ * A half-life of `h` weight units corresponds to an `alpha` of `Math.exp(Math.log(0.5) / h)`.
30
+ *
31
+ * @example
32
+ * {@includeCode ../test/Ewma.test.ts#example}
33
+ *
28
34
  * @public
29
35
  */
30
36
  var Ewma = class {
37
+ /**
38
+ * @param alpha - The smoothing factor, between 0 and 1. A larger `alpha` expires old samples more slowly.
39
+ */
31
40
  constructor(alpha) {
32
41
  this.alpha = alpha;
33
42
  this.estimate = 0;
@@ -35,10 +44,15 @@ var Ewma = class {
35
44
  }
36
45
  /**
37
46
  * Samples the value of the resource and updates the estimate.
47
+ *
48
+ * The new value has the weight `1 - alpha ^ weight`.
49
+ * The method ignores a sample that would make the estimate `NaN`.
38
50
  */
39
51
  sample(weight, value) {
40
52
  const adjustedAlpha = Math.pow(this.alpha, weight);
41
- this.estimate = adjustedAlpha * value + (1 - adjustedAlpha) * this.estimate;
53
+ const estimate = value * (1 - adjustedAlpha) + adjustedAlpha * this.estimate;
54
+ if (Number.isNaN(estimate)) return;
55
+ this.estimate = estimate;
42
56
  this.totalDuration += weight;
43
57
  }
44
58
  /**
@@ -48,8 +62,11 @@ var Ewma = class {
48
62
  * will also be in bits per seconds.
49
63
  */
50
64
  getEstimate() {
51
- const zeroFactor = 1 - Math.pow(this.alpha, this.totalDuration);
52
- return this.estimate / zeroFactor;
65
+ if (this.alpha !== 0) {
66
+ const zeroFactor = 1 - Math.pow(this.alpha, this.totalDuration);
67
+ if (zeroFactor !== 0) return this.estimate / zeroFactor;
68
+ }
69
+ return this.estimate;
53
70
  }
54
71
  /**
55
72
  * Returns the total duration of the resource (in units, as `weight` during sampling)
@@ -64,16 +81,20 @@ var Ewma = class {
64
81
  /**
65
82
  * Exponential Weighted Moving Average (EWMA) throughput estimator based on 2 half-lives
66
83
  *
84
+ * @example
85
+ * {@includeCode ../test/EwmaEstimator.test.ts#example}
86
+ *
67
87
  * @public
68
88
  */
69
89
  var EwmaEstimator = class {
70
90
  constructor(options) {
71
91
  this.totalBytes = 0;
92
+ this.hasSamples = false;
72
93
  this.minTotalBytes = options.minTotalBytes || 0;
73
94
  this.minBodySize = options.minBodySize || 0;
74
95
  this.defaultEstimate = options.defaultEstimate ?? NaN;
75
- this.slowEwma = new Ewma(options.slowHalfLife);
76
- this.fastEwma = new Ewma(options.fastHalfLife);
96
+ this.slowEwma = new Ewma(halfLifeToAlpha(options.slowHalfLife));
97
+ this.fastEwma = new Ewma(halfLifeToAlpha(options.fastHalfLife));
77
98
  if (!isNaN(this.defaultEstimate)) {
78
99
  this.fastEwma.sample(1, this.defaultEstimate);
79
100
  this.slowEwma.sample(1, this.defaultEstimate);
@@ -82,18 +103,23 @@ var EwmaEstimator = class {
82
103
  sample(sample) {
83
104
  const { encodedBodySize, duration } = sample;
84
105
  if (!Number.isFinite(encodedBodySize) || !Number.isFinite(duration)) return;
106
+ if (duration <= 0) return;
85
107
  if (encodedBodySize < this.minBodySize) return;
86
108
  const durationSeconds = sample.duration / 1e3;
87
109
  const bandwidthBps = getBandwidthBps(sample);
88
110
  this.totalBytes += encodedBodySize;
111
+ this.hasSamples = true;
89
112
  this.slowEwma.sample(durationSeconds, bandwidthBps);
90
113
  this.fastEwma.sample(durationSeconds, bandwidthBps);
91
114
  }
92
115
  getEstimate() {
93
- if (this.totalBytes < this.minTotalBytes) return this.defaultEstimate;
116
+ if (!this.hasSamples || this.totalBytes < this.minTotalBytes) return this.defaultEstimate;
94
117
  return Math.floor(Math.min(this.fastEwma.getEstimate(), this.slowEwma.getEstimate()));
95
118
  }
96
119
  };
120
+ function halfLifeToAlpha(halfLife) {
121
+ return halfLife ? Math.exp(Math.log(.5) / halfLife) : 0;
122
+ }
97
123
 
98
124
  //#endregion
99
125
  //#region src/HarmonicMeanEstimator.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["value: number","value: number","ema: number","zlema: number"],"sources":["../src/ArithmeticMeanEstimator.ts","../src/Ewma.ts","../src/EwmaEstimator.ts","../src/HarmonicMeanEstimator.ts","../src/ZlemaEstimator.ts"],"sourcesContent":["import type { ResourceTiming } from '@svta/cml-utils'\nimport { getBandwidthBps } from '@svta/cml-utils'\nimport type { ThroughputEstimator } from './ThroughputEstimator.ts'\n\n/**\n * Arithmetic Mean throughput estimator\n *\n * @public\n */\nexport class ArithmeticMeanEstimator implements ThroughputEstimator {\n\tprivate samples: ResourceTiming[] = []\n\n\tpublic sample(sample: ResourceTiming): void {\n\t\tthis.samples.push(sample)\n\t}\n\n\tpublic getEstimate(): number {\n\t\tlet value: number = 0\n\n\t\tfor (const sample of this.samples) {\n\t\t\tvalue += getBandwidthBps(sample)\n\t\t}\n\n\t\treturn value / this.samples.length\n\t}\n}\n","/**\n * Exponential Weighted Moving Average (EWMA)\n *\n * @public\n */\nexport class Ewma {\n\t// The smoothing factor for the EWMA\n\tprivate alpha: number\n\t// The current estimate of the throughput (in bits per second)\n\tprivate estimate: number\n\t// The total duration of the resource (in seconds)\n\tprivate totalDuration: number\n\n\tconstructor(alpha: number) {\n\t\tthis.alpha = alpha\n\t\tthis.estimate = 0\n\t\tthis.totalDuration = 0\n\t}\n\n\t/**\n\t * Samples the value of the resource and updates the estimate.\n\t */\n\tpublic sample(weight: number, value: number): void {\n\t\tconst adjustedAlpha = Math.pow(this.alpha, weight)\n\t\tthis.estimate = adjustedAlpha * value + (1 - adjustedAlpha) * this.estimate\n\t\tthis.totalDuration += weight\n\t}\n\n\t/**\n\t * Returns the estimate of the value (in units, as `value` during sampling)\n\t *\n\t * For example, if the value is throughput (in bits per seconds), the estimate\n\t * will also be in bits per seconds.\n\t */\n\tpublic getEstimate(): number {\n\t\tconst zeroFactor = 1 - Math.pow(this.alpha, this.totalDuration)\n\t\treturn this.estimate / zeroFactor\n\t}\n\n\t/**\n\t * Returns the total duration of the resource (in units, as `weight` during sampling)\n\t */\n\tpublic getTotalDuration(): number {\n\t\treturn this.totalDuration\n\t}\n}\n","import type { ResourceTiming } from '@svta/cml-utils'\nimport { getBandwidthBps } from '@svta/cml-utils'\nimport { Ewma } from './Ewma.ts'\nimport type { EwmaEstimatorOptions } from './EwmaEstimatorOptions.ts'\nimport type { ThroughputEstimator } from './ThroughputEstimator.ts'\n\n/**\n * Exponential Weighted Moving Average (EWMA) throughput estimator based on 2 half-lives\n *\n * @public\n */\nexport class EwmaEstimator implements ThroughputEstimator {\n\tprivate defaultEstimate: number\n\tprivate minTotalBytes: number\n\tprivate minBodySize: number\n\tprivate totalBytes: number = 0\n\tprivate fastEwma: Ewma\n\tprivate slowEwma: Ewma\n\n\tpublic constructor(options: EwmaEstimatorOptions) {\n\t\tthis.minTotalBytes = options.minTotalBytes || 0\n\t\tthis.minBodySize = options.minBodySize || 0\n\t\tthis.defaultEstimate = options.defaultEstimate ?? NaN\n\n\t\tthis.slowEwma = new Ewma(options.slowHalfLife)\n\t\tthis.fastEwma = new Ewma(options.fastHalfLife)\n\n\t\tif (!isNaN(this.defaultEstimate)) {\n\t\t\tthis.fastEwma.sample(1, this.defaultEstimate)\n\t\t\tthis.slowEwma.sample(1, this.defaultEstimate)\n\t\t}\n\t}\n\n\tpublic sample(sample: ResourceTiming): void {\n\t\tconst { encodedBodySize, duration } = sample\n\n\t\tif (!Number.isFinite(encodedBodySize) || !Number.isFinite(duration)) {\n\t\t\treturn\n\t\t}\n\n\t\tif (encodedBodySize < this.minBodySize) {\n\t\t\treturn\n\t\t}\n\n\t\tconst durationSeconds = sample.duration / 1000\n\t\tconst bandwidthBps = getBandwidthBps(sample)\n\t\tthis.totalBytes += encodedBodySize\n\n\t\tthis.slowEwma.sample(durationSeconds, bandwidthBps)\n\t\tthis.fastEwma.sample(durationSeconds, bandwidthBps)\n\t}\n\n\tpublic getEstimate(): number {\n\t\tif (this.totalBytes < this.minTotalBytes) {\n\t\t\treturn this.defaultEstimate\n\t\t}\n\n\t\treturn Math.floor(Math.min(this.fastEwma.getEstimate(), this.slowEwma.getEstimate()))\n\t}\n}\n","import type { ResourceTiming } from '@svta/cml-utils'\nimport { getBandwidthBps } from '@svta/cml-utils'\nimport type { ThroughputEstimator } from './ThroughputEstimator.ts'\n\n/**\n * Harmonic Mean throughput estimator\n *\n * @public\n */\nexport class HarmonicMeanEstimator implements ThroughputEstimator {\n\tprivate samples: ResourceTiming[] = []\n\n\tpublic sample(sample: ResourceTiming): void {\n\t\tthis.samples.push(sample)\n\t}\n\n\tpublic getEstimate(): number {\n\t\tlet value: number = 0\n\n\t\tfor (const sample of this.samples) {\n\t\t\tvalue += 1 / getBandwidthBps(sample)\n\t\t}\n\n\t\treturn this.samples.length / value\n\t}\n}\n","import type { ResourceTiming } from '@svta/cml-utils'\nimport { getBandwidthBps } from '@svta/cml-utils'\nimport type { ThroughputEstimator } from './ThroughputEstimator.ts'\n\n/**\n * Zero-Lag Exponential Moving Average (ZLEMA) throughput estimator\n *\n * @public\n */\nexport class ZlemaEstimator implements ThroughputEstimator {\n\tprivate samples: ResourceTiming[] = []\n\n\tpublic sample(sample: ResourceTiming): void {\n\t\tthis.samples.push(sample)\n\t}\n\n\tpublic getEstimate(): number {\n\t\tif (this.samples.length === 0) {\n\t\t\treturn NaN\n\t\t}\n\n\t\tconst alpha = 2 / (this.samples.length + 1)\n\n\t\tlet ema: number, zlema: number\n\n\t\tema = zlema = getBandwidthBps(this.samples[this.samples.length - 1])\n\n\t\tfor (const sample of this.samples) {\n\t\t\tema = alpha * getBandwidthBps(sample) + (1 - alpha) * ema\n\t\t\tzlema = alpha * ema + (1 - alpha) * zlema\n\t\t}\n\n\t\treturn zlema\n\t}\n}\n"],"mappings":";;;;;;;;AASA,IAAa,0BAAb,MAAoE;;iBAC/B,EAAE;;CAEtC,AAAO,OAAO,QAA8B;AAC3C,OAAK,QAAQ,KAAK,OAAO;;CAG1B,AAAO,cAAsB;EAC5B,IAAIA,QAAgB;AAEpB,OAAK,MAAM,UAAU,KAAK,QACzB,UAAS,gBAAgB,OAAO;AAGjC,SAAO,QAAQ,KAAK,QAAQ;;;;;;;;;;;AClB9B,IAAa,OAAb,MAAkB;CAQjB,YAAY,OAAe;AAC1B,OAAK,QAAQ;AACb,OAAK,WAAW;AAChB,OAAK,gBAAgB;;;;;CAMtB,AAAO,OAAO,QAAgB,OAAqB;EAClD,MAAM,gBAAgB,KAAK,IAAI,KAAK,OAAO,OAAO;AAClD,OAAK,WAAW,gBAAgB,SAAS,IAAI,iBAAiB,KAAK;AACnE,OAAK,iBAAiB;;;;;;;;CASvB,AAAO,cAAsB;EAC5B,MAAM,aAAa,IAAI,KAAK,IAAI,KAAK,OAAO,KAAK,cAAc;AAC/D,SAAO,KAAK,WAAW;;;;;CAMxB,AAAO,mBAA2B;AACjC,SAAO,KAAK;;;;;;;;;;;AChCd,IAAa,gBAAb,MAA0D;CAQzD,AAAO,YAAY,SAA+B;oBAJrB;AAK5B,OAAK,gBAAgB,QAAQ,iBAAiB;AAC9C,OAAK,cAAc,QAAQ,eAAe;AAC1C,OAAK,kBAAkB,QAAQ,mBAAmB;AAElD,OAAK,WAAW,IAAI,KAAK,QAAQ,aAAa;AAC9C,OAAK,WAAW,IAAI,KAAK,QAAQ,aAAa;AAE9C,MAAI,CAAC,MAAM,KAAK,gBAAgB,EAAE;AACjC,QAAK,SAAS,OAAO,GAAG,KAAK,gBAAgB;AAC7C,QAAK,SAAS,OAAO,GAAG,KAAK,gBAAgB;;;CAI/C,AAAO,OAAO,QAA8B;EAC3C,MAAM,EAAE,iBAAiB,aAAa;AAEtC,MAAI,CAAC,OAAO,SAAS,gBAAgB,IAAI,CAAC,OAAO,SAAS,SAAS,CAClE;AAGD,MAAI,kBAAkB,KAAK,YAC1B;EAGD,MAAM,kBAAkB,OAAO,WAAW;EAC1C,MAAM,eAAe,gBAAgB,OAAO;AAC5C,OAAK,cAAc;AAEnB,OAAK,SAAS,OAAO,iBAAiB,aAAa;AACnD,OAAK,SAAS,OAAO,iBAAiB,aAAa;;CAGpD,AAAO,cAAsB;AAC5B,MAAI,KAAK,aAAa,KAAK,cAC1B,QAAO,KAAK;AAGb,SAAO,KAAK,MAAM,KAAK,IAAI,KAAK,SAAS,aAAa,EAAE,KAAK,SAAS,aAAa,CAAC,CAAC;;;;;;;;;;;AChDvF,IAAa,wBAAb,MAAkE;;iBAC7B,EAAE;;CAEtC,AAAO,OAAO,QAA8B;AAC3C,OAAK,QAAQ,KAAK,OAAO;;CAG1B,AAAO,cAAsB;EAC5B,IAAIC,QAAgB;AAEpB,OAAK,MAAM,UAAU,KAAK,QACzB,UAAS,IAAI,gBAAgB,OAAO;AAGrC,SAAO,KAAK,QAAQ,SAAS;;;;;;;;;;;ACd/B,IAAa,iBAAb,MAA2D;;iBACtB,EAAE;;CAEtC,AAAO,OAAO,QAA8B;AAC3C,OAAK,QAAQ,KAAK,OAAO;;CAG1B,AAAO,cAAsB;AAC5B,MAAI,KAAK,QAAQ,WAAW,EAC3B,QAAO;EAGR,MAAM,QAAQ,KAAK,KAAK,QAAQ,SAAS;EAEzC,IAAIC,KAAaC;AAEjB,QAAM,QAAQ,gBAAgB,KAAK,QAAQ,KAAK,QAAQ,SAAS,GAAG;AAEpE,OAAK,MAAM,UAAU,KAAK,SAAS;AAClC,SAAM,QAAQ,gBAAgB,OAAO,IAAI,IAAI,SAAS;AACtD,WAAQ,QAAQ,OAAO,IAAI,SAAS;;AAGrC,SAAO"}
1
+ {"version":3,"file":"index.js","names":["value: number","value: number","ema: number","zlema: number"],"sources":["../src/ArithmeticMeanEstimator.ts","../src/Ewma.ts","../src/EwmaEstimator.ts","../src/HarmonicMeanEstimator.ts","../src/ZlemaEstimator.ts"],"sourcesContent":["import type { ResourceTiming } from '@svta/cml-utils'\nimport { getBandwidthBps } from '@svta/cml-utils'\nimport type { ThroughputEstimator } from './ThroughputEstimator.ts'\n\n/**\n * Arithmetic Mean throughput estimator\n *\n * @public\n */\nexport class ArithmeticMeanEstimator implements ThroughputEstimator {\n\tprivate samples: ResourceTiming[] = []\n\n\tpublic sample(sample: ResourceTiming): void {\n\t\tthis.samples.push(sample)\n\t}\n\n\tpublic getEstimate(): number {\n\t\tlet value: number = 0\n\n\t\tfor (const sample of this.samples) {\n\t\t\tvalue += getBandwidthBps(sample)\n\t\t}\n\n\t\treturn value / this.samples.length\n\t}\n}\n","/**\n * Exponential Weighted Moving Average (EWMA)\n *\n * Each sample has a weight, for example the duration of a download in seconds.\n * A half-life of `h` weight units corresponds to an `alpha` of `Math.exp(Math.log(0.5) / h)`.\n *\n * @example\n * {@includeCode ../test/Ewma.test.ts#example}\n *\n * @public\n */\nexport class Ewma {\n\t// The smoothing factor for the EWMA, between 0 and 1\n\tprivate alpha: number\n\t// The current estimate (in units, as `value` during sampling)\n\tprivate estimate: number\n\t// The total weight of the samples (in units, as `weight` during sampling)\n\tprivate totalDuration: number\n\n\t/**\n\t * @param alpha - The smoothing factor, between 0 and 1. A larger `alpha` expires old samples more slowly.\n\t */\n\tconstructor(alpha: number) {\n\t\tthis.alpha = alpha\n\t\tthis.estimate = 0\n\t\tthis.totalDuration = 0\n\t}\n\n\t/**\n\t * Samples the value of the resource and updates the estimate.\n\t *\n\t * The new value has the weight `1 - alpha ^ weight`.\n\t * The method ignores a sample that would make the estimate `NaN`.\n\t */\n\tpublic sample(weight: number, value: number): void {\n\t\tconst adjustedAlpha = Math.pow(this.alpha, weight)\n\t\tconst estimate = value * (1 - adjustedAlpha) + adjustedAlpha * this.estimate\n\n\t\tif (Number.isNaN(estimate)) {\n\t\t\treturn\n\t\t}\n\n\t\tthis.estimate = estimate\n\t\tthis.totalDuration += weight\n\t}\n\n\t/**\n\t * Returns the estimate of the value (in units, as `value` during sampling)\n\t *\n\t * For example, if the value is throughput (in bits per seconds), the estimate\n\t * will also be in bits per seconds.\n\t */\n\tpublic getEstimate(): number {\n\t\tif (this.alpha !== 0) {\n\t\t\tconst zeroFactor = 1 - Math.pow(this.alpha, this.totalDuration)\n\n\t\t\tif (zeroFactor !== 0) {\n\t\t\t\treturn this.estimate / zeroFactor\n\t\t\t}\n\t\t}\n\n\t\treturn this.estimate\n\t}\n\n\t/**\n\t * Returns the total duration of the resource (in units, as `weight` during sampling)\n\t */\n\tpublic getTotalDuration(): number {\n\t\treturn this.totalDuration\n\t}\n}\n","import type { ResourceTiming } from '@svta/cml-utils'\nimport { getBandwidthBps } from '@svta/cml-utils'\nimport { Ewma } from './Ewma.ts'\nimport type { EwmaEstimatorOptions } from './EwmaEstimatorOptions.ts'\nimport type { ThroughputEstimator } from './ThroughputEstimator.ts'\n\n/**\n * Exponential Weighted Moving Average (EWMA) throughput estimator based on 2 half-lives\n *\n * @example\n * {@includeCode ../test/EwmaEstimator.test.ts#example}\n *\n * @public\n */\nexport class EwmaEstimator implements ThroughputEstimator {\n\tprivate defaultEstimate: number\n\tprivate minTotalBytes: number\n\tprivate minBodySize: number\n\tprivate totalBytes: number = 0\n\tprivate hasSamples: boolean = false\n\tprivate fastEwma: Ewma\n\tprivate slowEwma: Ewma\n\n\tpublic constructor(options: EwmaEstimatorOptions) {\n\t\tthis.minTotalBytes = options.minTotalBytes || 0\n\t\tthis.minBodySize = options.minBodySize || 0\n\t\tthis.defaultEstimate = options.defaultEstimate ?? NaN\n\n\t\tthis.slowEwma = new Ewma(halfLifeToAlpha(options.slowHalfLife))\n\t\tthis.fastEwma = new Ewma(halfLifeToAlpha(options.fastHalfLife))\n\n\t\tif (!isNaN(this.defaultEstimate)) {\n\t\t\tthis.fastEwma.sample(1, this.defaultEstimate)\n\t\t\tthis.slowEwma.sample(1, this.defaultEstimate)\n\t\t}\n\t}\n\n\tpublic sample(sample: ResourceTiming): void {\n\t\tconst { encodedBodySize, duration } = sample\n\n\t\tif (!Number.isFinite(encodedBodySize) || !Number.isFinite(duration)) {\n\t\t\treturn\n\t\t}\n\n\t\tif (duration <= 0) {\n\t\t\treturn\n\t\t}\n\n\t\tif (encodedBodySize < this.minBodySize) {\n\t\t\treturn\n\t\t}\n\n\t\tconst durationSeconds = sample.duration / 1000\n\t\tconst bandwidthBps = getBandwidthBps(sample)\n\t\tthis.totalBytes += encodedBodySize\n\t\tthis.hasSamples = true\n\n\t\tthis.slowEwma.sample(durationSeconds, bandwidthBps)\n\t\tthis.fastEwma.sample(durationSeconds, bandwidthBps)\n\t}\n\n\tpublic getEstimate(): number {\n\t\tif (!this.hasSamples || this.totalBytes < this.minTotalBytes) {\n\t\t\treturn this.defaultEstimate\n\t\t}\n\n\t\treturn Math.floor(Math.min(this.fastEwma.getEstimate(), this.slowEwma.getEstimate()))\n\t}\n}\n\nfunction halfLifeToAlpha(halfLife: number): number {\n\treturn halfLife ? Math.exp(Math.log(0.5) / halfLife) : 0\n}\n","import type { ResourceTiming } from '@svta/cml-utils'\nimport { getBandwidthBps } from '@svta/cml-utils'\nimport type { ThroughputEstimator } from './ThroughputEstimator.ts'\n\n/**\n * Harmonic Mean throughput estimator\n *\n * @public\n */\nexport class HarmonicMeanEstimator implements ThroughputEstimator {\n\tprivate samples: ResourceTiming[] = []\n\n\tpublic sample(sample: ResourceTiming): void {\n\t\tthis.samples.push(sample)\n\t}\n\n\tpublic getEstimate(): number {\n\t\tlet value: number = 0\n\n\t\tfor (const sample of this.samples) {\n\t\t\tvalue += 1 / getBandwidthBps(sample)\n\t\t}\n\n\t\treturn this.samples.length / value\n\t}\n}\n","import type { ResourceTiming } from '@svta/cml-utils'\nimport { getBandwidthBps } from '@svta/cml-utils'\nimport type { ThroughputEstimator } from './ThroughputEstimator.ts'\n\n/**\n * Zero-Lag Exponential Moving Average (ZLEMA) throughput estimator\n *\n * @public\n */\nexport class ZlemaEstimator implements ThroughputEstimator {\n\tprivate samples: ResourceTiming[] = []\n\n\tpublic sample(sample: ResourceTiming): void {\n\t\tthis.samples.push(sample)\n\t}\n\n\tpublic getEstimate(): number {\n\t\tif (this.samples.length === 0) {\n\t\t\treturn NaN\n\t\t}\n\n\t\tconst alpha = 2 / (this.samples.length + 1)\n\n\t\tlet ema: number, zlema: number\n\n\t\tema = zlema = getBandwidthBps(this.samples[this.samples.length - 1])\n\n\t\tfor (const sample of this.samples) {\n\t\t\tema = alpha * getBandwidthBps(sample) + (1 - alpha) * ema\n\t\t\tzlema = alpha * ema + (1 - alpha) * zlema\n\t\t}\n\n\t\treturn zlema\n\t}\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svta/cml-throughput",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Throughput estimation functionality",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",