@svta/cml-throughput 1.0.10 → 1.1.0

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
@@ -32,3 +32,17 @@ estimator.sample({
32
32
  console.log(estimator.getEstimate());
33
33
  // 285
34
34
  ```
35
+
36
+ ## Samples
37
+
38
+ A sample needs an `encodedBodySize` above 0 and a `duration` above 0. The estimators ignore every other sample, and the estimate does not change.
39
+
40
+ A player can pass `PerformanceResourceTiming` entries as samples. For a cross-origin segment, the browser decides whether the entry has a body size:
41
+
42
+ | Browser | Body size of a cross-origin response |
43
+ | --- | --- |
44
+ | Chrome | Exposed when the response has a `Timing-Allow-Origin` header for the page origin. Chrome plans the Firefox rule for version 158. |
45
+ | Firefox 129 and later | Exposed when the request used CORS. A `no-cors` request reports 0, even with the header. |
46
+ | Safari | 0 for every cross-origin response. |
47
+
48
+ An entry with a body size of 0 is ignored, so the estimate does not change. The byte count of the loader works in every browser. Build the sample from that count when the player has it.
package/dist/index.d.ts CHANGED
@@ -1,34 +1,33 @@
1
1
  import { ResourceTiming } from "@svta/cml-utils";
2
-
3
2
  //#region src/ThroughputEstimator.d.ts
4
-
5
3
  /**
6
- * Throughput Estimator.
7
- *
8
- * @public
9
- */
4
+ * Throughput Estimator.
5
+ *
6
+ * @public
7
+ */
10
8
  type ThroughputEstimator = {
11
9
  /**
12
- * Adds a sample to the throughput estimator
13
- *
14
- * @param sample - The timing sample to add
15
- */
10
+ * Adds a sample to the throughput estimator.
11
+ * The estimator ignores a sample with an `encodedBodySize` or a `duration` at or below 0, `NaN`, or `Infinity`.
12
+ *
13
+ * @param sample - The timing sample to add
14
+ */
16
15
  sample(sample: ResourceTiming): void;
17
16
  /**
18
- * Returns the current estimate of the throughput (in bits per second)
19
- *
20
- * @returns The current estimate of the throughput
21
- */
17
+ * Returns the current estimate of the throughput (in bits per second)
18
+ *
19
+ * @returns The current estimate of the throughput
20
+ */
22
21
  getEstimate(): number;
23
22
  };
24
23
  //#endregion
25
24
  //#region src/ArithmeticMeanEstimator.d.ts
26
25
  /**
27
- * Arithmetic Mean throughput estimator
28
- *
29
- * @public
30
- */
31
- declare class ArithmeticMeanEstimator implements ThroughputEstimator {
26
+ * Arithmetic Mean throughput estimator
27
+ *
28
+ * @public
29
+ */
30
+ export declare class ArithmeticMeanEstimator implements ThroughputEstimator {
32
31
  private samples;
33
32
  sample(sample: ResourceTiming): void;
34
33
  getEstimate(): number;
@@ -36,99 +35,99 @@ declare class ArithmeticMeanEstimator implements ThroughputEstimator {
36
35
  //#endregion
37
36
  //#region src/Ewma.d.ts
38
37
  /**
39
- * Exponential Weighted Moving Average (EWMA)
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
- *
47
- * @public
48
- */
49
- declare class Ewma {
38
+ * Exponential Weighted Moving Average (EWMA)
39
+ *
40
+ * Each sample has a weight, for example the duration of a download in seconds.
41
+ * A half-life of `h` weight units corresponds to an `alpha` of `Math.exp(Math.log(0.5) / h)`.
42
+ *
43
+ * @example
44
+ * {@includeCode ../test/Ewma.test.ts#example}
45
+ *
46
+ * @public
47
+ */
48
+ export declare class Ewma {
50
49
  private alpha;
51
50
  private estimate;
52
51
  private totalDuration;
53
52
  /**
54
- * @param alpha - The smoothing factor, between 0 and 1. A larger `alpha` expires old samples more slowly.
55
- */
53
+ * @param alpha - The smoothing factor, between 0 and 1. A larger `alpha` expires old samples more slowly.
54
+ */
56
55
  constructor(alpha: number);
57
56
  /**
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`.
62
- */
57
+ * Samples the value of the resource and updates the estimate.
58
+ *
59
+ * The new value has the weight `1 - alpha ^ weight`.
60
+ * The method ignores a sample that would make the estimate `NaN`.
61
+ */
63
62
  sample(weight: number, value: number): void;
64
63
  /**
65
- * Returns the estimate of the value (in units, as `value` during sampling)
66
- *
67
- * For example, if the value is throughput (in bits per seconds), the estimate
68
- * will also be in bits per seconds.
69
- */
64
+ * Returns the estimate of the value (in units, as `value` during sampling)
65
+ *
66
+ * For example, if the value is throughput (in bits per seconds), the estimate
67
+ * will also be in bits per seconds.
68
+ */
70
69
  getEstimate(): number;
71
70
  /**
72
- * Returns the total duration of the resource (in units, as `weight` during sampling)
73
- */
71
+ * Returns the total duration of the resource (in units, as `weight` during sampling)
72
+ */
74
73
  getTotalDuration(): number;
75
74
  }
76
75
  //#endregion
77
76
  //#region src/EwmaEstimatorOptions.d.ts
78
77
  /**
79
- * Exponential Weighted Moving Average (EWMA) Estimator Options
80
- *
81
- * @public
82
- */
78
+ * Exponential Weighted Moving Average (EWMA) Estimator Options
79
+ *
80
+ * @public
81
+ */
83
82
  type EwmaEstimatorOptions = {
84
83
  /**
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
- */
84
+ * The estimate, in bits per second, that `getEstimate` returns before the first sample.
85
+ * `getEstimate` also returns this value while the sampled bytes are below `minTotalBytes`.
86
+ *
87
+ * @defaultValue `NaN`
88
+ */
91
89
  defaultEstimate?: number;
92
90
  /**
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
- */
91
+ * The number of bytes to sample before `getEstimate` returns a measured estimate.
92
+ * Below this total, `getEstimate` returns `defaultEstimate`.
93
+ * shaka-player uses 128000.
94
+ *
95
+ * @defaultValue `0`
96
+ */
99
97
  minTotalBytes?: number;
100
98
  /**
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
- */
99
+ * The smallest `encodedBodySize`, in bytes, that counts as a sample.
100
+ * A sample with an `encodedBodySize` of 0 never counts.
101
+ * The estimator ignores smaller responses, because the time to first byte dominates their duration.
102
+ * shaka-player uses 16000.
103
+ *
104
+ * @defaultValue `0`
105
+ */
107
106
  minBodySize?: number;
108
107
  /**
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
- */
108
+ * The half-life of the fast moving average, in seconds.
109
+ * Samples from the last `fastHalfLife` seconds have half of the weight in the estimate.
110
+ * shaka-player uses 2.
111
+ */
113
112
  fastHalfLife: number;
114
113
  /**
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
- */
114
+ * The half-life of the slow moving average, in seconds.
115
+ * Samples from the last `slowHalfLife` seconds have half of the weight in the estimate.
116
+ * shaka-player uses 5.
117
+ */
119
118
  slowHalfLife: number;
120
119
  };
121
120
  //#endregion
122
121
  //#region src/EwmaEstimator.d.ts
123
122
  /**
124
- * Exponential Weighted Moving Average (EWMA) throughput estimator based on 2 half-lives
125
- *
126
- * @example
127
- * {@includeCode ../test/EwmaEstimator.test.ts#example}
128
- *
129
- * @public
130
- */
131
- declare class EwmaEstimator implements ThroughputEstimator {
123
+ * Exponential Weighted Moving Average (EWMA) throughput estimator based on 2 half-lives
124
+ *
125
+ * @example
126
+ * {@includeCode ../test/EwmaEstimator.test.ts#example}
127
+ *
128
+ * @public
129
+ */
130
+ export declare class EwmaEstimator implements ThroughputEstimator {
132
131
  private defaultEstimate;
133
132
  private minTotalBytes;
134
133
  private minBodySize;
@@ -143,11 +142,11 @@ declare class EwmaEstimator implements ThroughputEstimator {
143
142
  //#endregion
144
143
  //#region src/HarmonicMeanEstimator.d.ts
145
144
  /**
146
- * Harmonic Mean throughput estimator
147
- *
148
- * @public
149
- */
150
- declare class HarmonicMeanEstimator implements ThroughputEstimator {
145
+ * Harmonic Mean throughput estimator
146
+ *
147
+ * @public
148
+ */
149
+ export declare class HarmonicMeanEstimator implements ThroughputEstimator {
151
150
  private samples;
152
151
  sample(sample: ResourceTiming): void;
153
152
  getEstimate(): number;
@@ -155,15 +154,15 @@ declare class HarmonicMeanEstimator implements ThroughputEstimator {
155
154
  //#endregion
156
155
  //#region src/ZlemaEstimator.d.ts
157
156
  /**
158
- * Zero-Lag Exponential Moving Average (ZLEMA) throughput estimator
159
- *
160
- * @public
161
- */
162
- declare class ZlemaEstimator implements ThroughputEstimator {
157
+ * Zero-Lag Exponential Moving Average (ZLEMA) throughput estimator
158
+ *
159
+ * @public
160
+ */
161
+ export declare class ZlemaEstimator implements ThroughputEstimator {
163
162
  private samples;
164
163
  sample(sample: ResourceTiming): void;
165
164
  getEstimate(): number;
166
165
  }
167
166
  //#endregion
168
- export { ArithmeticMeanEstimator, Ewma, EwmaEstimator, EwmaEstimatorOptions, HarmonicMeanEstimator, ThroughputEstimator, ZlemaEstimator };
167
+ export type { EwmaEstimatorOptions, ThroughputEstimator };
169
168
  //# sourceMappingURL=index.d.ts.map
@@ -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;;;;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"}
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"],"mappings":";;;;;;;KAOY;;;;;;;EAOX,OAAO,QAAQ;;;;;;EAOf;;;;;;;;;qBCXY,mCAAmC;UACvC;EAER,OAAc,QAAQ;EAQtB;;;;;;;;;;;;;;;qBCVY;UAEJ;UAEA;UAEA;;;;EAKR,YAAY;;;;;;;EAYZ,OAAc,gBAAgB;;;;;;;EAkB9B;;;;EAeA;;;;;;;;;KC9DW;;;;;;;EAOX;;;;;;;;EASA;;;;;;;;;EAUA;;;;;;EAOA;;;;;;EAOA;;;;;;;;;;;;qBC9BY,yBAAyB;UAC7B;UACA;UACA;UACA;UACA;UACA;UACA;EAER,YAAmB,SAAS;EAS5B,OAAc,QAAQ;EAgBtB;;;;;;;;;qBCvCY,iCAAiC;UACrC;EAER,OAAc,QAAQ;EAQtB;;;;;;;;;qBCXY,0BAA0B;UAC9B;EAER,OAAc,QAAQ;EAQtB"}
package/dist/index.js CHANGED
@@ -1,5 +1,16 @@
1
1
  import { getBandwidthBps } from "@svta/cml-utils";
2
-
2
+ //#region src/isValidSample.ts
3
+ /**
4
+ * Returns true when the sample has a finite `encodedBodySize` above 0 and a finite `duration` above 0.
5
+ *
6
+ * @param sample - The timing sample
7
+ * @returns Whether the sample counts as a throughput measurement
8
+ */
9
+ function isValidSample(sample) {
10
+ const { encodedBodySize, duration } = sample;
11
+ return Number.isFinite(encodedBodySize) && encodedBodySize > 0 && Number.isFinite(duration) && duration > 0;
12
+ }
13
+ //#endregion
3
14
  //#region src/ArithmeticMeanEstimator.ts
4
15
  /**
5
16
  * Arithmetic Mean throughput estimator
@@ -11,6 +22,7 @@ var ArithmeticMeanEstimator = class {
11
22
  this.samples = [];
12
23
  }
13
24
  sample(sample) {
25
+ if (!isValidSample(sample)) return;
14
26
  this.samples.push(sample);
15
27
  }
16
28
  getEstimate() {
@@ -19,7 +31,6 @@ var ArithmeticMeanEstimator = class {
19
31
  return value / this.samples.length;
20
32
  }
21
33
  };
22
-
23
34
  //#endregion
24
35
  //#region src/Ewma.ts
25
36
  /**
@@ -75,7 +86,6 @@ var Ewma = class {
75
86
  return this.totalDuration;
76
87
  }
77
88
  };
78
-
79
89
  //#endregion
80
90
  //#region src/EwmaEstimator.ts
81
91
  /**
@@ -95,16 +105,10 @@ var EwmaEstimator = class {
95
105
  this.defaultEstimate = options.defaultEstimate ?? NaN;
96
106
  this.slowEwma = new Ewma(halfLifeToAlpha(options.slowHalfLife));
97
107
  this.fastEwma = new Ewma(halfLifeToAlpha(options.fastHalfLife));
98
- if (!isNaN(this.defaultEstimate)) {
99
- this.fastEwma.sample(1, this.defaultEstimate);
100
- this.slowEwma.sample(1, this.defaultEstimate);
101
- }
102
108
  }
103
109
  sample(sample) {
104
- const { encodedBodySize, duration } = sample;
105
- if (!Number.isFinite(encodedBodySize) || !Number.isFinite(duration)) return;
106
- if (duration <= 0) return;
107
- if (encodedBodySize < this.minBodySize) return;
110
+ const { encodedBodySize } = sample;
111
+ if (!isValidSample(sample) || encodedBodySize < this.minBodySize) return;
108
112
  const durationSeconds = sample.duration / 1e3;
109
113
  const bandwidthBps = getBandwidthBps(sample);
110
114
  this.totalBytes += encodedBodySize;
@@ -120,7 +124,6 @@ var EwmaEstimator = class {
120
124
  function halfLifeToAlpha(halfLife) {
121
125
  return halfLife ? Math.exp(Math.log(.5) / halfLife) : 0;
122
126
  }
123
-
124
127
  //#endregion
125
128
  //#region src/HarmonicMeanEstimator.ts
126
129
  /**
@@ -133,6 +136,7 @@ var HarmonicMeanEstimator = class {
133
136
  this.samples = [];
134
137
  }
135
138
  sample(sample) {
139
+ if (!isValidSample(sample)) return;
136
140
  this.samples.push(sample);
137
141
  }
138
142
  getEstimate() {
@@ -141,7 +145,6 @@ var HarmonicMeanEstimator = class {
141
145
  return this.samples.length / value;
142
146
  }
143
147
  };
144
-
145
148
  //#endregion
146
149
  //#region src/ZlemaEstimator.ts
147
150
  /**
@@ -154,6 +157,7 @@ var ZlemaEstimator = class {
154
157
  this.samples = [];
155
158
  }
156
159
  sample(sample) {
160
+ if (!isValidSample(sample)) return;
157
161
  this.samples.push(sample);
158
162
  }
159
163
  getEstimate() {
@@ -168,7 +172,7 @@ var ZlemaEstimator = class {
168
172
  return zlema;
169
173
  }
170
174
  };
171
-
172
175
  //#endregion
173
176
  export { ArithmeticMeanEstimator, Ewma, EwmaEstimator, HarmonicMeanEstimator, ZlemaEstimator };
177
+
174
178
  //# sourceMappingURL=index.js.map
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 * 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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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/isValidSample.ts","../src/ArithmeticMeanEstimator.ts","../src/Ewma.ts","../src/EwmaEstimator.ts","../src/HarmonicMeanEstimator.ts","../src/ZlemaEstimator.ts"],"sourcesContent":["import type { ResourceTiming } from '@svta/cml-utils'\n\n/**\n * Returns true when the sample has a finite `encodedBodySize` above 0 and a finite `duration` above 0.\n *\n * @param sample - The timing sample\n * @returns Whether the sample counts as a throughput measurement\n */\nexport function isValidSample(sample: ResourceTiming): boolean {\n\tconst { encodedBodySize, duration } = sample\n\n\treturn Number.isFinite(encodedBodySize) && encodedBodySize > 0 && Number.isFinite(duration) && duration > 0\n}\n","import type { ResourceTiming } from '@svta/cml-utils'\nimport { getBandwidthBps } from '@svta/cml-utils'\nimport { isValidSample } from './isValidSample.ts'\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\tif (!isValidSample(sample)) {\n\t\t\treturn\n\t\t}\n\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 { isValidSample } from './isValidSample.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\t}\n\n\tpublic sample(sample: ResourceTiming): void {\n\t\tconst { encodedBodySize } = sample\n\n\t\tif (!isValidSample(sample) || 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 { isValidSample } from './isValidSample.ts'\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\tif (!isValidSample(sample)) {\n\t\t\treturn\n\t\t}\n\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 { isValidSample } from './isValidSample.ts'\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\tif (!isValidSample(sample)) {\n\t\t\treturn\n\t\t}\n\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.10",
3
+ "version": "1.1.0",
4
4
  "description": "Throughput estimation functionality",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -51,6 +51,6 @@
51
51
  }
52
52
  },
53
53
  "peerDependencies": {
54
- "@svta/cml-utils": "1.6.1"
54
+ "@svta/cml-utils": "^1.6.2"
55
55
  }
56
56
  }