adaptive-concurrency 0.10.1 → 0.11.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/dist/Limiter.d.ts +7 -0
- package/dist/Limiter.d.ts.map +1 -1
- package/dist/Limiter.js +5 -2
- package/dist/MetricRegistry.d.ts +3 -0
- package/dist/MetricRegistry.d.ts.map +1 -1
- package/dist/MetricRegistry.js +3 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/limit/AIMDLimit.d.ts +1 -1
- package/dist/limit/AIMDLimit.d.ts.map +1 -1
- package/dist/limit/AIMDLimit.js +1 -1
- package/dist/limit/FixedLimit.d.ts +1 -1
- package/dist/limit/FixedLimit.d.ts.map +1 -1
- package/dist/limit/FixedLimit.js +1 -1
- package/dist/limit/GradientLimit.d.ts +1 -1
- package/dist/limit/GradientLimit.d.ts.map +1 -1
- package/dist/limit/GradientLimit.js +1 -1
- package/dist/limit/GroupAwareLimit.d.ts +77 -0
- package/dist/limit/GroupAwareLimit.d.ts.map +1 -0
- package/dist/limit/GroupAwareLimit.js +208 -0
- package/dist/limit/SettableLimit.d.ts +1 -1
- package/dist/limit/SettableLimit.d.ts.map +1 -1
- package/dist/limit/SettableLimit.js +1 -1
- package/dist/limit/StreamingLimit.d.ts +3 -1
- package/dist/limit/StreamingLimit.d.ts.map +1 -1
- package/dist/limit/TracingLimitDecorator.d.ts +1 -1
- package/dist/limit/TracingLimitDecorator.d.ts.map +1 -1
- package/dist/limit/TracingLimitDecorator.js +2 -2
- package/dist/limit/VegasLimit.d.ts +1 -1
- package/dist/limit/VegasLimit.d.ts.map +1 -1
- package/dist/limit/VegasLimit.js +2 -10
- package/dist/limit/WindowedLimit.d.ts +8 -1
- package/dist/limit/WindowedLimit.d.ts.map +1 -1
- package/dist/limit/WindowedLimit.js +16 -4
- package/dist/limit/window/PercentileSampleWindow.d.ts.map +1 -1
- package/dist/limit/window/PercentileSampleWindow.js +40 -4
- package/dist/statistics/DecayingHistogram.d.ts +63 -0
- package/dist/statistics/DecayingHistogram.d.ts.map +1 -0
- package/dist/statistics/DecayingHistogram.js +144 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +13 -0
- package/package.json +4 -1
package/dist/Limiter.d.ts
CHANGED
|
@@ -98,6 +98,12 @@ export interface LimiterOptions<ContextT> {
|
|
|
98
98
|
* request. When omitted, rejected requests immediately receive `undefined`.
|
|
99
99
|
*/
|
|
100
100
|
allotmentUnavailableStrategy?: AllotmentUnavailableStrategy<ContextT>;
|
|
101
|
+
/**
|
|
102
|
+
* Derives an operation name from the request context, passed to the limit
|
|
103
|
+
* algorithm's `addSample` so group-aware limits can distinguish
|
|
104
|
+
* heterogeneous workloads. When omitted, no operation name is provided.
|
|
105
|
+
*/
|
|
106
|
+
operationNameFor?: (context: ContextT) => string | undefined;
|
|
101
107
|
}
|
|
102
108
|
/**
|
|
103
109
|
* Concurrency limiter with pluggable strategies for gating decisions and
|
|
@@ -113,6 +119,7 @@ export declare class Limiter<Context = void> {
|
|
|
113
119
|
private readonly acquireStrategy;
|
|
114
120
|
private readonly rejectionStrategy;
|
|
115
121
|
private readonly bypassResolver;
|
|
122
|
+
private readonly operationNameFor;
|
|
116
123
|
private readonly acquireBypassedAllotment;
|
|
117
124
|
private readonly successCounter;
|
|
118
125
|
private readonly droppedCounter;
|
package/dist/Limiter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Limiter.d.ts","sourceRoot":"","sources":["../src/Limiter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAIV,cAAc,EACf,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAGL,iBAAiB,EACjB,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7C;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;AAEhE,MAAM,WAAW,cAAc,CAAC,QAAQ,GAAG,IAAI;IAC7C,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAClC;AAMD;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe,CAAC,QAAQ;IACvC;;;;OAIG;IACH,mBAAmB,CACjB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,YAAY,GAClB,YAAY,CAAC,OAAO,CAAC,CAAC;IAEzB;;;OAGG;IACH,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAE3D;;;OAGG;IACH,cAAc,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3D;AAED;;;;;GAKG;AACH,MAAM,WAAW,4BAA4B,CAAC,QAAQ;IACpD;;;;;;;;;;OAUG;IACH,sBAAsB,CACpB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,EAC3C,MAAM,CAAC,EAAE,WAAW,GACnB,aAAa,CAAC;IAEjB;;;OAGG;IACH,mBAAmB,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IAE1C;;;;OAIG;IACH,cAAc,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;CACzE;AAOD,MAAM,WAAW,cAAc,CAAC,QAAQ;IACtC,KAAK,CAAC,EAAE,aAAa,CAAC;IAEtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IAErB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;IAEhD;;;OAGG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAE5C;;;OAGG;IACH,4BAA4B,CAAC,EAAE,4BAA4B,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"Limiter.d.ts","sourceRoot":"","sources":["../src/Limiter.ts"],"names":[],"mappings":"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"}
|
package/dist/Limiter.js
CHANGED
|
@@ -20,6 +20,7 @@ export class Limiter {
|
|
|
20
20
|
acquireStrategy;
|
|
21
21
|
rejectionStrategy;
|
|
22
22
|
bypassResolver;
|
|
23
|
+
operationNameFor;
|
|
23
24
|
acquireBypassedAllotment;
|
|
24
25
|
successCounter;
|
|
25
26
|
droppedCounter;
|
|
@@ -38,6 +39,7 @@ export class Limiter {
|
|
|
38
39
|
this.limitAlgorithm = options.limit ?? Limiter.makeDefaultLimit();
|
|
39
40
|
this._limit = this.limitAlgorithm.currentLimit;
|
|
40
41
|
this.bypassResolver = options.bypassResolver;
|
|
42
|
+
this.operationNameFor = options.operationNameFor;
|
|
41
43
|
this.acquireStrategy =
|
|
42
44
|
options.acquireStrategy ?? new SemaphoreStrategy(this._limit);
|
|
43
45
|
this.rejectionStrategy = options.allotmentUnavailableStrategy;
|
|
@@ -148,6 +150,7 @@ export class Limiter {
|
|
|
148
150
|
createAllotment(ctx) {
|
|
149
151
|
const startTime = this.clock();
|
|
150
152
|
const currentInflight = ++this._inflight;
|
|
153
|
+
const operationName = this.operationNameFor?.(ctx);
|
|
151
154
|
// Make sure an allotment can only be released once; future calls become a
|
|
152
155
|
// no-op. This simplifies a lot of cleanup handling etc that'd otherwise be
|
|
153
156
|
// much racier/more complicated. It could hide subtle correctness issue, but
|
|
@@ -170,7 +173,7 @@ export class Limiter {
|
|
|
170
173
|
}
|
|
171
174
|
catch { }
|
|
172
175
|
try {
|
|
173
|
-
this.limitAlgorithm.addSample(startTime, rtt, currentInflight, false);
|
|
176
|
+
this.limitAlgorithm.addSample(startTime, rtt, currentInflight, false, operationName);
|
|
174
177
|
}
|
|
175
178
|
catch { }
|
|
176
179
|
try {
|
|
@@ -212,7 +215,7 @@ export class Limiter {
|
|
|
212
215
|
}
|
|
213
216
|
catch { }
|
|
214
217
|
try {
|
|
215
|
-
this.limitAlgorithm.addSample(startTime, rtt, currentInflight, true);
|
|
218
|
+
this.limitAlgorithm.addSample(startTime, rtt, currentInflight, true, operationName);
|
|
216
219
|
}
|
|
217
220
|
catch { }
|
|
218
221
|
try {
|
package/dist/MetricRegistry.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ export declare const MetricIds: {
|
|
|
16
16
|
readonly WINDOW_MIN_RTT_NAME: "min_window_rtt";
|
|
17
17
|
readonly WINDOW_QUEUE_SIZE_NAME: "queue_size";
|
|
18
18
|
readonly ACQUIRE_TIME_NAME: "acquire_time";
|
|
19
|
+
readonly CONGESTION_SIGNAL_NAME: "congestion_signal";
|
|
20
|
+
readonly WARMED_GROUPS_COUNT_NAME: "warmed_groups_count";
|
|
21
|
+
readonly GROUP_RTT_RATIO_NAME: "group_rtt_ratio";
|
|
19
22
|
};
|
|
20
23
|
/**
|
|
21
24
|
* Listener to receive samples for a distribution.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricRegistry.d.ts","sourceRoot":"","sources":["../src/MetricRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS
|
|
1
|
+
{"version":3,"file":"MetricRegistry.d.ts","sourceRoot":"","sources":["../src/MetricRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;CAaZ,CAAC;AAEX;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,0FAA0F;AAC1F,MAAM,WAAW,KAAK;IACpB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,YAAY,CACV,EAAE,EAAE,MAAM,EACV,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,kBAAkB,CAAC;IAEtB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IAE9D;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;CACnE;AAMD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAUhC,CAAC"}
|
package/dist/MetricRegistry.js
CHANGED
|
@@ -16,6 +16,9 @@ export const MetricIds = {
|
|
|
16
16
|
WINDOW_MIN_RTT_NAME: "min_window_rtt",
|
|
17
17
|
WINDOW_QUEUE_SIZE_NAME: "queue_size",
|
|
18
18
|
ACQUIRE_TIME_NAME: "acquire_time",
|
|
19
|
+
CONGESTION_SIGNAL_NAME: "congestion_signal",
|
|
20
|
+
WARMED_GROUPS_COUNT_NAME: "warmed_groups_count",
|
|
21
|
+
GROUP_RTT_RATIO_NAME: "group_rtt_ratio",
|
|
19
22
|
};
|
|
20
23
|
const NOOP_SAMPLE_LISTENER = { addSample() { } };
|
|
21
24
|
const NOOP_COUNTER = { increment() { } };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,17 +7,18 @@ export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, isRunRes
|
|
|
7
7
|
export { AIMDLimit, type AIMDLimitOptions } from "./limit/AIMDLimit.js";
|
|
8
8
|
export { FixedLimit } from "./limit/FixedLimit.js";
|
|
9
9
|
export { GradientLimit, type Gradient2LimitOptions, } from "./limit/GradientLimit.js";
|
|
10
|
+
export { GroupAwareLimit } from "./limit/GroupAwareLimit.js";
|
|
10
11
|
export { SettableLimit } from "./limit/SettableLimit.js";
|
|
11
|
-
export { TracingLimitDecorator } from "./limit/TracingLimitDecorator.js";
|
|
12
12
|
export { VegasLimit, type VegasLimitOptions, type VegasLimitPolicy, } from "./limit/VegasLimit.js";
|
|
13
13
|
export { WindowedLimit, type WindowedLimitOptions, } from "./limit/WindowedLimit.js";
|
|
14
|
+
export { DecayingHistogram } from "./statistics/DecayingHistogram.js";
|
|
14
15
|
export { ExpMovingAverage } from "./statistics/ExpMovingAverage.js";
|
|
15
16
|
export { MinimumValue } from "./statistics/MinimumValue.js";
|
|
16
17
|
export type { StreamingStatistic } from "./statistics/StreamingStatistic.js";
|
|
17
18
|
export { makeAverageSampleWindow } from "./limit/window/AverageSampleWindow.js";
|
|
18
19
|
export { createPercentileSampleWindow } from "./limit/window/PercentileSampleWindow.js";
|
|
19
20
|
export type { SampleWindow } from "./limit/window/SampleWindow.js";
|
|
20
|
-
export { LinkedWaiterQueue, squareRoot, squareRootWithBaseline, } from "./utils/index.js";
|
|
21
|
+
export { LinkedWaiterQueue, log10Scale, squareRoot, squareRootWithBaseline, } from "./utils/index.js";
|
|
21
22
|
export * from "./limiter/factories/index.js";
|
|
22
23
|
export { PartitionedStrategy, type PartitionConfig, } from "./limiter/acquire-strategies/PartitionedStrategy.js";
|
|
23
24
|
export { SemaphoreStrategy } from "./limiter/acquire-strategies/SemaphoreStrategy.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,OAAO,EACP,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,KAAK,EACV,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EACL,aAAa,EACb,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,OAAO,EACP,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,KAAK,EACV,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EACL,aAAa,EACb,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EACL,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,GAC1B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,YAAY,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAG7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,4BAA4B,EAAE,MAAM,0CAA0C,CAAC;AACxF,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAEnE,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAE1B,cAAc,8BAA8B,CAAC;AAG7C,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,qDAAqD,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mDAAmD,CAAC;AAGtF,OAAO,EACL,wBAAwB,EACxB,WAAW,EACX,KAAK,+BAA+B,EACpC,KAAK,WAAW,GACjB,MAAM,yEAAyE,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sEAAsE,CAAC;AAC9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,6EAA6E,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,17 +6,18 @@ export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, isRunRes
|
|
|
6
6
|
export { AIMDLimit } from "./limit/AIMDLimit.js";
|
|
7
7
|
export { FixedLimit } from "./limit/FixedLimit.js";
|
|
8
8
|
export { GradientLimit, } from "./limit/GradientLimit.js";
|
|
9
|
+
export { GroupAwareLimit } from "./limit/GroupAwareLimit.js";
|
|
9
10
|
export { SettableLimit } from "./limit/SettableLimit.js";
|
|
10
|
-
export { TracingLimitDecorator } from "./limit/TracingLimitDecorator.js";
|
|
11
11
|
export { VegasLimit, } from "./limit/VegasLimit.js";
|
|
12
12
|
export { WindowedLimit, } from "./limit/WindowedLimit.js";
|
|
13
13
|
// Streaming statistics
|
|
14
|
+
export { DecayingHistogram } from "./statistics/DecayingHistogram.js";
|
|
14
15
|
export { ExpMovingAverage } from "./statistics/ExpMovingAverage.js";
|
|
15
16
|
export { MinimumValue } from "./statistics/MinimumValue.js";
|
|
16
17
|
// Sample window types
|
|
17
18
|
export { makeAverageSampleWindow } from "./limit/window/AverageSampleWindow.js";
|
|
18
19
|
export { createPercentileSampleWindow } from "./limit/window/PercentileSampleWindow.js";
|
|
19
|
-
export { LinkedWaiterQueue, squareRoot, squareRootWithBaseline, } from "./utils/index.js";
|
|
20
|
+
export { LinkedWaiterQueue, log10Scale, squareRoot, squareRootWithBaseline, } from "./utils/index.js";
|
|
20
21
|
export * from "./limiter/factories/index.js";
|
|
21
22
|
// Acquire strategies
|
|
22
23
|
export { PartitionedStrategy, } from "./limiter/acquire-strategies/PartitionedStrategy.js";
|
|
@@ -37,7 +37,7 @@ export declare class AIMDLimit implements AdaptiveLimit {
|
|
|
37
37
|
private readonly maxLimit;
|
|
38
38
|
private readonly backoffJitter;
|
|
39
39
|
constructor(options?: AIMDLimitOptions);
|
|
40
|
-
addSample(_startTime: number, rtt: number, inflight: number, didDrop: boolean): void;
|
|
40
|
+
addSample(_startTime: number, rtt: number, inflight: number, didDrop: boolean, _operationName?: string): void;
|
|
41
41
|
get currentLimit(): number;
|
|
42
42
|
private applyNewLimit;
|
|
43
43
|
subscribe(consumer: (newLimit: number) => void, options?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIMDLimit.d.ts","sourceRoot":"","sources":["../../src/limit/AIMDLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,SAAU,YAAW,aAAa;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,OAAO,GAAE,gBAAqB;IA0B1C,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"AIMDLimit.d.ts","sourceRoot":"","sources":["../../src/limit/AIMDLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,SAAU,YAAW,aAAa;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,OAAO,GAAE,gBAAqB;IA0B1C,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI;IAuBP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,aAAa;IAOrB,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,QAAQ,IAAI,MAAM;CAGnB"}
|
package/dist/limit/AIMDLimit.js
CHANGED
|
@@ -28,7 +28,7 @@ export class AIMDLimit {
|
|
|
28
28
|
throw new Error("backoffRatio + backoffJitter must be < 1.0 to guarantee the limit decreases on drop");
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
addSample(_startTime, rtt, inflight, didDrop) {
|
|
31
|
+
addSample(_startTime, rtt, inflight, didDrop, _operationName) {
|
|
32
32
|
let currentLimit = this._limit;
|
|
33
33
|
if (didDrop || rtt > this.timeout) {
|
|
34
34
|
const jitteredRatio = Math.max(0.5, Math.min(1 - Number.EPSILON, this.backoffRatio + (Math.random() * 2 - 1) * this.backoffJitter));
|
|
@@ -5,7 +5,7 @@ import type { AdaptiveLimit } from "./StreamingLimit.js";
|
|
|
5
5
|
export declare class FixedLimit implements AdaptiveLimit {
|
|
6
6
|
private _limit;
|
|
7
7
|
constructor(limit: number);
|
|
8
|
-
addSample(_startTime: number, _rtt: number, _inflight: number, _didDrop: boolean): void;
|
|
8
|
+
addSample(_startTime: number, _rtt: number, _inflight: number, _didDrop: boolean, _operationName?: string): void;
|
|
9
9
|
get currentLimit(): number;
|
|
10
10
|
subscribe(_consumer: (newLimit: number) => void, _options?: {
|
|
11
11
|
signal?: AbortSignal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FixedLimit.d.ts","sourceRoot":"","sources":["../../src/limit/FixedLimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD;;GAEG;AACH,qBAAa,UAAW,YAAW,aAAa;IAC9C,OAAO,CAAC,MAAM,CAAS;gBAEX,KAAK,EAAE,MAAM;IAIzB,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"FixedLimit.d.ts","sourceRoot":"","sources":["../../src/limit/FixedLimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD;;GAEG;AACH,qBAAa,UAAW,YAAW,aAAa;IAC9C,OAAO,CAAC,MAAM,CAAS;gBAEX,KAAK,EAAE,MAAM;IAIzB,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EACjB,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI;IAGP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,SAAS,CACP,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACrC,QAAQ,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACtC,MAAM,IAAI;IAMb,QAAQ,IAAI,MAAM;CAGnB"}
|
package/dist/limit/FixedLimit.js
CHANGED
|
@@ -108,7 +108,7 @@ export declare class GradientLimit implements AdaptiveLimit {
|
|
|
108
108
|
private readonly shortRttSampleListener;
|
|
109
109
|
private readonly queueSizeSampleListener;
|
|
110
110
|
constructor(options?: Gradient2LimitOptions);
|
|
111
|
-
addSample(_startTime: number, rtt: number, inflight: number, _didDrop: boolean): void;
|
|
111
|
+
addSample(_startTime: number, rtt: number, inflight: number, _didDrop: boolean, _operationName?: string): void;
|
|
112
112
|
get currentLimit(): number;
|
|
113
113
|
private applyNewLimit;
|
|
114
114
|
subscribe(consumer: (newLimit: number) => void, options?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GradientLimit.d.ts","sourceRoot":"","sources":["../../src/limit/GradientLimit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,WAAW,qBAAqB;IACpC,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAEvD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,qBAAa,aAAc,YAAW,aAAa;IACjD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,yDAAyD;IACzD,OAAO,CAAC,cAAc,CAAS;IAE/B;;;OAGG;IACH,OAAO,CAAC,OAAO,CAAK;IAEpB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAE7C,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;IAC3D,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAqB;IAC5D,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAqB;gBAEjD,OAAO,GAAE,qBAA0B;IA4B/C,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"GradientLimit.d.ts","sourceRoot":"","sources":["../../src/limit/GradientLimit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,WAAW,qBAAqB;IACpC,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAEvD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,qBAAa,aAAc,YAAW,aAAa;IACjD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,yDAAyD;IACzD,OAAO,CAAC,cAAc,CAAS;IAE/B;;;OAGG;IACH,OAAO,CAAC,OAAO,CAAK;IAEpB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAE7C,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;IAC3D,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAqB;IAC5D,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAqB;gBAEjD,OAAO,GAAE,qBAA0B;IA4B/C,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,EACjB,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI;IASP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,aAAa;IAOrB,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,OAAO,CAAC,yBAAyB;IA0CjC,UAAU,IAAI,MAAM;IAIpB,YAAY,IAAI,MAAM;IAItB,QAAQ,IAAI,MAAM;CAGnB"}
|
|
@@ -46,7 +46,7 @@ export class GradientLimit {
|
|
|
46
46
|
this.shortRttSampleListener = registry.distribution(MetricIds.WINDOW_MIN_RTT_NAME);
|
|
47
47
|
this.queueSizeSampleListener = registry.distribution(MetricIds.WINDOW_QUEUE_SIZE_NAME);
|
|
48
48
|
}
|
|
49
|
-
addSample(_startTime, rtt, inflight, _didDrop) {
|
|
49
|
+
addSample(_startTime, rtt, inflight, _didDrop, _operationName) {
|
|
50
50
|
const newLimitNoFloor = this.computeNextLimitUnrounded(rtt, inflight);
|
|
51
51
|
this.estimatedLimit = newLimitNoFloor;
|
|
52
52
|
const newLimit = Math.floor(newLimitNoFloor);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { MetricRegistry } from "../MetricRegistry.js";
|
|
2
|
+
import type { AdaptiveLimit } from "./StreamingLimit.js";
|
|
3
|
+
/**
|
|
4
|
+
* Mix-agnostic adaptive concurrency limit that detects congestion using
|
|
5
|
+
* per-group RTT ratios weighted by sample density.
|
|
6
|
+
*
|
|
7
|
+
* Each sample is associated with an operation group via `operationName`. Per-group
|
|
8
|
+
* state tracks a long-lived p10 RTT baseline (decaying histogram) and a short
|
|
9
|
+
* EMA of recent RTT. The congestion signal is the weighted average of
|
|
10
|
+
* `recentRtt / p10` across warmed-up groups, where weight is
|
|
11
|
+
* `sqrt(decayedSampleCount)`.
|
|
12
|
+
*
|
|
13
|
+
* The limit increases additively when the congestion signal is low and inflight
|
|
14
|
+
* is near the limit, and decreases via a configurable `decrease` function when
|
|
15
|
+
* the signal is high or a drop occurs. Before any groups are warmed up, the
|
|
16
|
+
* limit only responds to drops.
|
|
17
|
+
*
|
|
18
|
+
* Samples without an `operationName` do not contribute to any group's state
|
|
19
|
+
* (no histogram, EMA, or activity counter update). However, they still
|
|
20
|
+
* participate in limit decisions: drops always trigger a decrease, and
|
|
21
|
+
* non-drop samples can trigger an increase or decrease based on the current
|
|
22
|
+
* congestion signal from warmed-up groups. This means unnamed operations
|
|
23
|
+
* free-ride on the congestion detection provided by named groups without
|
|
24
|
+
* polluting group baselines.
|
|
25
|
+
*
|
|
26
|
+
* This design is immune to operation mix shifts: a transition from fast to slow
|
|
27
|
+
* operations does not cause a spurious RTT spike because each group is measured
|
|
28
|
+
* against its own baseline.
|
|
29
|
+
*/
|
|
30
|
+
export declare class GroupAwareLimit implements AdaptiveLimit {
|
|
31
|
+
private _limit;
|
|
32
|
+
private readonly limitListeners;
|
|
33
|
+
private readonly groups;
|
|
34
|
+
private readonly minLimit;
|
|
35
|
+
private readonly maxLimit;
|
|
36
|
+
private readonly alpha;
|
|
37
|
+
private readonly beta;
|
|
38
|
+
private readonly decrease;
|
|
39
|
+
private readonly baselineHalfLife;
|
|
40
|
+
private readonly activityHalfLife;
|
|
41
|
+
private readonly recentRttWindow;
|
|
42
|
+
private readonly minGroupSamples;
|
|
43
|
+
private readonly clock;
|
|
44
|
+
private readonly registry;
|
|
45
|
+
private readonly congestionSignalGauge;
|
|
46
|
+
private readonly warmedGroupsCountGauge;
|
|
47
|
+
constructor(options?: {
|
|
48
|
+
initialLimit?: number;
|
|
49
|
+
minLimit?: number;
|
|
50
|
+
maxLimit?: number;
|
|
51
|
+
maxGroups?: number;
|
|
52
|
+
alpha?: number;
|
|
53
|
+
beta?: number;
|
|
54
|
+
decrease?: (limit: number, didDrop: boolean) => number;
|
|
55
|
+
baselineHalfLife?: number;
|
|
56
|
+
activityHalfLife?: number;
|
|
57
|
+
recentRttWindow?: number;
|
|
58
|
+
minGroupSamples?: number;
|
|
59
|
+
clock?: () => number;
|
|
60
|
+
metricRegistry?: MetricRegistry;
|
|
61
|
+
});
|
|
62
|
+
addSample(_startTime: number, rtt: number, inflight: number, didDrop: boolean, operationName?: string): void;
|
|
63
|
+
get currentLimit(): number;
|
|
64
|
+
subscribe(consumer: (newLimit: number) => void, options?: {
|
|
65
|
+
signal?: AbortSignal;
|
|
66
|
+
}): () => void;
|
|
67
|
+
private computeCongestionSignal;
|
|
68
|
+
private clamp;
|
|
69
|
+
private applyNewLimit;
|
|
70
|
+
toString(): string;
|
|
71
|
+
}
|
|
72
|
+
declare global {
|
|
73
|
+
interface Math {
|
|
74
|
+
sumPrecise?: (numbers: Iterable<number>) => number;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=GroupAwareLimit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupAwareLimit.d.ts","sourceRoot":"","sources":["../../src/limit/GroupAwareLimit.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAS,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAKlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA6DzD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,eAAgB,YAAW,aAAa;IACnD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA0C;IACzE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkD;IAEzE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8C;IACvE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAQ;IAC9C,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAQ;gBAEnC,OAAO,CAAC,EAAE;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;QACvD,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,cAAc,CAAC;KACjC;IAuBD,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,aAAa,CAAC,EAAE,MAAM,GACrB,IAAI;IAkDP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,OAAO,CAAC,uBAAuB;IA8C/B,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,aAAa;IAOrB,QAAQ,IAAI,MAAM;CAGnB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,IAAI;QACZ,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC;KACpD;CACF"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import lruPkg from "lru_map";
|
|
2
|
+
const { LRUMap } = lruPkg;
|
|
3
|
+
import { ListenerSet } from "../ListenerSet.js";
|
|
4
|
+
import { MetricIds, NoopMetricRegistry } from "../MetricRegistry.js";
|
|
5
|
+
import { DecayingHistogram } from "../statistics/DecayingHistogram.js";
|
|
6
|
+
import { ExpMovingAverage } from "../statistics/ExpMovingAverage.js";
|
|
7
|
+
import { log10Scale } from "../utils/index.js";
|
|
8
|
+
function defaultDecrease(limit, didDrop) {
|
|
9
|
+
if (didDrop) {
|
|
10
|
+
return Math.floor(limit * 0.9);
|
|
11
|
+
}
|
|
12
|
+
return limit - log10Scale(Math.floor(limit));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Per-group tracking state. Fixed memory regardless of sample volume.
|
|
16
|
+
*/
|
|
17
|
+
class GroupState {
|
|
18
|
+
/** Long-lived decaying histogram for the p10 baseline. */
|
|
19
|
+
histogram;
|
|
20
|
+
/** Short EMA tracking the group's current RTT. */
|
|
21
|
+
recentRtt;
|
|
22
|
+
/**
|
|
23
|
+
* Decaying counter of recent samples, used for weight and warmup.
|
|
24
|
+
* Decays with its own (shorter) half-life.
|
|
25
|
+
*/
|
|
26
|
+
_activityCount = 0;
|
|
27
|
+
_lastActivityDecayTime;
|
|
28
|
+
activityLambda;
|
|
29
|
+
constructor(baselineHalfLife, activityHalfLife, recentRttWindow) {
|
|
30
|
+
this.histogram = new DecayingHistogram({ halfLife: baselineHalfLife });
|
|
31
|
+
this.recentRtt = new ExpMovingAverage(recentRttWindow, 5);
|
|
32
|
+
this.activityLambda = Math.LN2 / activityHalfLife;
|
|
33
|
+
}
|
|
34
|
+
recordSample(rtt, now) {
|
|
35
|
+
this.histogram.addSample(rtt, now);
|
|
36
|
+
this.recentRtt.addSample(rtt);
|
|
37
|
+
this.applyActivityDecay(now);
|
|
38
|
+
this._activityCount += 1;
|
|
39
|
+
}
|
|
40
|
+
activityCount(now) {
|
|
41
|
+
this.applyActivityDecay(now);
|
|
42
|
+
return this._activityCount;
|
|
43
|
+
}
|
|
44
|
+
applyActivityDecay(now) {
|
|
45
|
+
if (this._lastActivityDecayTime === undefined) {
|
|
46
|
+
this._lastActivityDecayTime = now;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const elapsed = now - this._lastActivityDecayTime;
|
|
50
|
+
if (elapsed <= 0)
|
|
51
|
+
return;
|
|
52
|
+
this._activityCount *= Math.exp(-this.activityLambda * elapsed);
|
|
53
|
+
this._lastActivityDecayTime = now;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Mix-agnostic adaptive concurrency limit that detects congestion using
|
|
58
|
+
* per-group RTT ratios weighted by sample density.
|
|
59
|
+
*
|
|
60
|
+
* Each sample is associated with an operation group via `operationName`. Per-group
|
|
61
|
+
* state tracks a long-lived p10 RTT baseline (decaying histogram) and a short
|
|
62
|
+
* EMA of recent RTT. The congestion signal is the weighted average of
|
|
63
|
+
* `recentRtt / p10` across warmed-up groups, where weight is
|
|
64
|
+
* `sqrt(decayedSampleCount)`.
|
|
65
|
+
*
|
|
66
|
+
* The limit increases additively when the congestion signal is low and inflight
|
|
67
|
+
* is near the limit, and decreases via a configurable `decrease` function when
|
|
68
|
+
* the signal is high or a drop occurs. Before any groups are warmed up, the
|
|
69
|
+
* limit only responds to drops.
|
|
70
|
+
*
|
|
71
|
+
* Samples without an `operationName` do not contribute to any group's state
|
|
72
|
+
* (no histogram, EMA, or activity counter update). However, they still
|
|
73
|
+
* participate in limit decisions: drops always trigger a decrease, and
|
|
74
|
+
* non-drop samples can trigger an increase or decrease based on the current
|
|
75
|
+
* congestion signal from warmed-up groups. This means unnamed operations
|
|
76
|
+
* free-ride on the congestion detection provided by named groups without
|
|
77
|
+
* polluting group baselines.
|
|
78
|
+
*
|
|
79
|
+
* This design is immune to operation mix shifts: a transition from fast to slow
|
|
80
|
+
* operations does not cause a spurious RTT spike because each group is measured
|
|
81
|
+
* against its own baseline.
|
|
82
|
+
*/
|
|
83
|
+
export class GroupAwareLimit {
|
|
84
|
+
_limit;
|
|
85
|
+
limitListeners = new ListenerSet();
|
|
86
|
+
groups;
|
|
87
|
+
minLimit;
|
|
88
|
+
maxLimit;
|
|
89
|
+
alpha;
|
|
90
|
+
beta;
|
|
91
|
+
decrease;
|
|
92
|
+
baselineHalfLife;
|
|
93
|
+
activityHalfLife;
|
|
94
|
+
recentRttWindow;
|
|
95
|
+
minGroupSamples;
|
|
96
|
+
clock;
|
|
97
|
+
registry;
|
|
98
|
+
congestionSignalGauge;
|
|
99
|
+
warmedGroupsCountGauge;
|
|
100
|
+
constructor(options) {
|
|
101
|
+
this._limit = options?.initialLimit ?? 20;
|
|
102
|
+
this.groups = new LRUMap(options?.maxGroups ?? 50);
|
|
103
|
+
this.minLimit = options?.minLimit ?? 10;
|
|
104
|
+
this.maxLimit = options?.maxLimit ?? 200;
|
|
105
|
+
this.alpha = options?.alpha ?? 1.1;
|
|
106
|
+
this.beta = options?.beta ?? 1.5;
|
|
107
|
+
this.decrease = options?.decrease ?? defaultDecrease;
|
|
108
|
+
this.baselineHalfLife = options?.baselineHalfLife ?? 600_000;
|
|
109
|
+
this.activityHalfLife = options?.activityHalfLife ?? 30_000;
|
|
110
|
+
this.recentRttWindow = options?.recentRttWindow ?? 100;
|
|
111
|
+
this.minGroupSamples = options?.minGroupSamples ?? 20;
|
|
112
|
+
this.clock = options?.clock ?? (() => performance.now());
|
|
113
|
+
this.registry = options?.metricRegistry ?? NoopMetricRegistry;
|
|
114
|
+
this.congestionSignalGauge = this.registry.gauge(MetricIds.CONGESTION_SIGNAL_NAME);
|
|
115
|
+
this.warmedGroupsCountGauge = this.registry.gauge(MetricIds.WARMED_GROUPS_COUNT_NAME);
|
|
116
|
+
}
|
|
117
|
+
addSample(_startTime, rtt, inflight, didDrop, operationName) {
|
|
118
|
+
const now = this.clock();
|
|
119
|
+
// Drops are handled before group state updates so that dropped RTTs never
|
|
120
|
+
// pollute baselines or recent-RTT tracking. A dropped request's timing
|
|
121
|
+
// reflects when the drop was detected (e.g. a timeout firing), not how
|
|
122
|
+
// long the operation actually takes to complete.
|
|
123
|
+
if (didDrop) {
|
|
124
|
+
this.applyNewLimit(this.clamp(this.decrease(this._limit, true)));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (operationName !== undefined) {
|
|
128
|
+
let group = this.groups.get(operationName);
|
|
129
|
+
if (!group) {
|
|
130
|
+
group = new GroupState(this.baselineHalfLife, this.activityHalfLife, this.recentRttWindow);
|
|
131
|
+
this.groups.set(operationName, group);
|
|
132
|
+
}
|
|
133
|
+
group.recordSample(rtt, now);
|
|
134
|
+
}
|
|
135
|
+
const result = this.computeCongestionSignal(now);
|
|
136
|
+
if (result === undefined) {
|
|
137
|
+
// No warmed-up groups; hold (drop-only mode).
|
|
138
|
+
this.warmedGroupsCountGauge.record(0);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const { warmedGroupInfos, signal } = result;
|
|
142
|
+
this.warmedGroupsCountGauge.record(warmedGroupInfos.length);
|
|
143
|
+
this.congestionSignalGauge.record(signal);
|
|
144
|
+
for (const { groupName: group, ratio } of warmedGroupInfos) {
|
|
145
|
+
this.registry
|
|
146
|
+
.gauge(MetricIds.GROUP_RTT_RATIO_NAME, { group })
|
|
147
|
+
.record(ratio);
|
|
148
|
+
}
|
|
149
|
+
if (signal > this.beta) {
|
|
150
|
+
this.applyNewLimit(this.clamp(this.decrease(this._limit, false)));
|
|
151
|
+
}
|
|
152
|
+
else if (signal < this.alpha && inflight >= this._limit / 2) {
|
|
153
|
+
this.applyNewLimit(this.clamp(this._limit + 1));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
get currentLimit() {
|
|
157
|
+
return this._limit;
|
|
158
|
+
}
|
|
159
|
+
subscribe(consumer, options = {}) {
|
|
160
|
+
return this.limitListeners.subscribe(consumer, options);
|
|
161
|
+
}
|
|
162
|
+
computeCongestionSignal(now) {
|
|
163
|
+
// See https://github.com/rsms/js-lru/pull/42/changes
|
|
164
|
+
const entries = [
|
|
165
|
+
...this.groups.entries(),
|
|
166
|
+
];
|
|
167
|
+
const warmedGroupInfos = entries
|
|
168
|
+
.map(([groupName, groupState]) => {
|
|
169
|
+
const activity = groupState.activityCount(now);
|
|
170
|
+
if (activity < this.minGroupSamples)
|
|
171
|
+
return undefined;
|
|
172
|
+
const p10 = groupState.histogram.percentile(0.1, now);
|
|
173
|
+
if (!Number.isFinite(p10) || p10 <= 0)
|
|
174
|
+
return undefined;
|
|
175
|
+
const recentRtt = groupState.recentRtt.currentValue;
|
|
176
|
+
if (recentRtt <= 0)
|
|
177
|
+
return undefined;
|
|
178
|
+
const ratio = recentRtt / p10;
|
|
179
|
+
const weight = Math.sqrt(activity);
|
|
180
|
+
return { groupName, ratio, weight };
|
|
181
|
+
})
|
|
182
|
+
// Remove non-warmed or otherwise-invalid groups
|
|
183
|
+
.filter((it) => it !== undefined);
|
|
184
|
+
const totalWeight = sum(warmedGroupInfos.map(({ weight }) => weight));
|
|
185
|
+
if (totalWeight <= 0)
|
|
186
|
+
return undefined;
|
|
187
|
+
const weightedRatioSum = sum(warmedGroupInfos.map(({ weight, ratio }) => weight * ratio));
|
|
188
|
+
return {
|
|
189
|
+
signal: weightedRatioSum / totalWeight,
|
|
190
|
+
warmedGroupInfos,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
clamp(limit) {
|
|
194
|
+
return Math.min(this.maxLimit, Math.max(this.minLimit, Math.floor(limit)));
|
|
195
|
+
}
|
|
196
|
+
applyNewLimit(newLimit) {
|
|
197
|
+
if (newLimit !== this._limit) {
|
|
198
|
+
this._limit = newLimit;
|
|
199
|
+
this.limitListeners.notify(newLimit);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
toString() {
|
|
203
|
+
return `GroupAwareLimit [limit=${this._limit}, groups=${this.groups.size}]`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const sum = typeof Math.sumPrecise === "function"
|
|
207
|
+
? Math.sumPrecise.bind(Math)
|
|
208
|
+
: (numbers) => numbers.reduce((acc, curr) => acc + curr, 0);
|
|
@@ -8,7 +8,7 @@ export declare class SettableLimit implements AdaptiveLimit {
|
|
|
8
8
|
private readonly limitListeners;
|
|
9
9
|
constructor(limit: number);
|
|
10
10
|
get currentLimit(): number;
|
|
11
|
-
addSample(_startTime: number, _rtt: number, _inflight: number, _didDrop: boolean): void;
|
|
11
|
+
addSample(_startTime: number, _rtt: number, _inflight: number, _didDrop: boolean, _operationName?: string): void;
|
|
12
12
|
setLimit(limit: number): void;
|
|
13
13
|
subscribe(consumer: (newLimit: number) => void, options?: {
|
|
14
14
|
signal?: AbortSignal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SettableLimit.d.ts","sourceRoot":"","sources":["../../src/limit/SettableLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,qBAAa,aAAc,YAAW,aAAa;IACjD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiD;gBAEpE,KAAK,EAAE,MAAM;IAIzB,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"SettableLimit.d.ts","sourceRoot":"","sources":["../../src/limit/SettableLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,qBAAa,aAAc,YAAW,aAAa;IACjD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiD;gBAEpE,KAAK,EAAE,MAAM;IAIzB,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EACjB,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI;IAIP,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAO7B,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,QAAQ,IAAI,MAAM;CAGnB"}
|
|
@@ -20,7 +20,9 @@ export interface AdaptiveLimit {
|
|
|
20
20
|
* @param rtt Round trip time in fractional milliseconds
|
|
21
21
|
* @param inflight Number of inflight requests at the time the request started
|
|
22
22
|
* @param didDrop Whether the request was dropped (timeout or rejection)
|
|
23
|
+
* @param operationName Optional name identifying the type of operation,
|
|
24
|
+
* used by group-aware limits to distinguish heterogeneous workloads.
|
|
23
25
|
*/
|
|
24
|
-
addSample(startTime: number, rtt: number, inflight: number, didDrop: boolean): void;
|
|
26
|
+
addSample(startTime: number, rtt: number, inflight: number, didDrop: boolean, operationName?: string): void;
|
|
25
27
|
}
|
|
26
28
|
//# sourceMappingURL=StreamingLimit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamingLimit.d.ts","sourceRoot":"","sources":["../../src/limit/StreamingLimit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,IAAI,YAAY,IAAI,MAAM,CAAC;IAE3B;;;;;OAKG;IACH,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,MAAM,IAAI,CAAC;IAEd
|
|
1
|
+
{"version":3,"file":"StreamingLimit.d.ts","sourceRoot":"","sources":["../../src/limit/StreamingLimit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,IAAI,YAAY,IAAI,MAAM,CAAC;IAE3B;;;;;OAKG;IACH,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,MAAM,IAAI,CAAC;IAEd;;;;;;;;OAQG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7G"}
|
|
@@ -8,7 +8,7 @@ export declare class TracingLimitDecorator implements AdaptiveLimit {
|
|
|
8
8
|
static wrap(delegate: AdaptiveLimit): TracingLimitDecorator;
|
|
9
9
|
constructor(delegate: AdaptiveLimit);
|
|
10
10
|
get currentLimit(): number;
|
|
11
|
-
addSample(startTime: number, rtt: number, inflight: number, didDrop: boolean): void;
|
|
11
|
+
addSample(startTime: number, rtt: number, inflight: number, didDrop: boolean, operationName?: string): void;
|
|
12
12
|
subscribe(consumer: (newLimit: number) => void, options?: {
|
|
13
13
|
signal?: AbortSignal;
|
|
14
14
|
}): () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TracingLimitDecorator.d.ts","sourceRoot":"","sources":["../../src/limit/TracingLimitDecorator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IAEzC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,qBAAqB;gBAI/C,QAAQ,EAAE,aAAa;IAInC,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"TracingLimitDecorator.d.ts","sourceRoot":"","sources":["../../src/limit/TracingLimitDecorator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IAEzC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,qBAAqB;gBAI/C,QAAQ,EAAE,aAAa;IAInC,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI;IAK3G,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,MAAM,IAAI;CAGd"}
|
|
@@ -13,9 +13,9 @@ export class TracingLimitDecorator {
|
|
|
13
13
|
get currentLimit() {
|
|
14
14
|
return this.delegate.currentLimit;
|
|
15
15
|
}
|
|
16
|
-
addSample(startTime, rtt, inflight, didDrop) {
|
|
16
|
+
addSample(startTime, rtt, inflight, didDrop, operationName) {
|
|
17
17
|
console.debug(`maxInFlight=${inflight} rtt=${rtt.toFixed(3)} ms`);
|
|
18
|
-
this.delegate.addSample(startTime, rtt, inflight, didDrop);
|
|
18
|
+
this.delegate.addSample(startTime, rtt, inflight, didDrop, operationName);
|
|
19
19
|
}
|
|
20
20
|
subscribe(consumer, options) {
|
|
21
21
|
return this.delegate.subscribe(consumer, options);
|
|
@@ -73,7 +73,7 @@ export declare class VegasLimit implements AdaptiveLimit {
|
|
|
73
73
|
constructor(options?: VegasLimitOptions);
|
|
74
74
|
private resetProbeJitter;
|
|
75
75
|
private shouldProbe;
|
|
76
|
-
addSample(startTime: number, rtt: number, inflight: number, didDrop: boolean): void;
|
|
76
|
+
addSample(startTime: number, rtt: number, inflight: number, didDrop: boolean, _operationName?: string): void;
|
|
77
77
|
get currentLimit(): number;
|
|
78
78
|
private applyNewLimit;
|
|
79
79
|
subscribe(consumer: (newLimit: number) => void, options?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VegasLimit.d.ts","sourceRoot":"","sources":["../../src/limit/VegasLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"VegasLimit.d.ts","sourceRoot":"","sources":["../../src/limit/VegasLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,MAAM,CAAC,EAAE;QACP;;;;WAIG;QACH,KAAK,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAE9B;;;;WAIG;QACH,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAE7B;;WAEG;QACH,SAAS,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAElC;;WAEG;QACH,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAEjC;;;WAGG;QACH,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;KACpD,CAAC;IAEF;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CACrC,WAAW,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CACzC,CAAC;AAEF,qBAAa,UAAW,YAAW,aAAa;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,yDAAyD;IACzD,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,SAAS,CAAK;IAEtB,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IAEvD,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAE7B,OAAO,GAAE,iBAAsB;IAyB3C,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,WAAW;IAOnB,SAAS,CACP,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI;IAMP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,aAAa;IAOrB,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,OAAO,CAAC,gBAAgB;IA+BxB,OAAO,CAAC,oBAAoB;IA2C5B,QAAQ,IAAI,MAAM;CAGnB"}
|
package/dist/limit/VegasLimit.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { ListenerSet } from "../ListenerSet.js";
|
|
2
2
|
import { MetricIds, NoopMetricRegistry } from "../MetricRegistry.js";
|
|
3
|
-
|
|
4
|
-
* Sublinear scale of the concurrency limit (floor of log10, lower-bounded for
|
|
5
|
-
* small n). Same idea as Netflix's Log10RootIntFunction. Used only for
|
|
6
|
-
* {@link VegasLimit} default policy.
|
|
7
|
-
*/
|
|
8
|
-
const log10ScaleLookup = Array.from({ length: 1000 }, (_, i) => Math.max(1, Math.floor(Math.log10(i))));
|
|
9
|
-
function log10Scale(n) {
|
|
10
|
-
return n < 1000 ? log10ScaleLookup[n] : Math.floor(Math.log10(n));
|
|
11
|
-
}
|
|
3
|
+
import { log10Scale } from "../utils/index.js";
|
|
12
4
|
export class VegasLimit {
|
|
13
5
|
_limit;
|
|
14
6
|
limitListeners = new ListenerSet();
|
|
@@ -49,7 +41,7 @@ export class VegasLimit {
|
|
|
49
41
|
return (this.probeJitter * this.probeMultiplier * this.estimatedLimit <=
|
|
50
42
|
this.probeCount);
|
|
51
43
|
}
|
|
52
|
-
addSample(startTime, rtt, inflight, didDrop) {
|
|
44
|
+
addSample(startTime, rtt, inflight, didDrop, _operationName) {
|
|
53
45
|
this.applyNewLimit(this.computeNextLimit(startTime, rtt, inflight, didDrop));
|
|
54
46
|
}
|
|
55
47
|
get currentLimit() {
|
|
@@ -26,6 +26,13 @@ export interface WindowedLimitOptions {
|
|
|
26
26
|
* forwarding aggregated results to a delegate StreamingLimit. This reduces
|
|
27
27
|
* noise from individual samples and ensures the delegate only sees
|
|
28
28
|
* representative data.
|
|
29
|
+
*
|
|
30
|
+
* **Important:** Because a window aggregates samples across many requests,
|
|
31
|
+
* the `operationName` received in each `addSample` call is intentionally
|
|
32
|
+
* **not** forwarded to the delegate. This means `WindowedLimit` is
|
|
33
|
+
* incompatible with delegates that rely on per-sample operation names
|
|
34
|
+
* (e.g. `GroupAwareLimit`). Wrapping a `GroupAwareLimit` in a
|
|
35
|
+
* `WindowedLimit` will silently disable all group-aware behavior.
|
|
29
36
|
*/
|
|
30
37
|
export declare class WindowedLimit implements AdaptiveLimit {
|
|
31
38
|
private readonly delegate;
|
|
@@ -39,7 +46,7 @@ export declare class WindowedLimit implements AdaptiveLimit {
|
|
|
39
46
|
/** Object tracking stats for the current sample window */
|
|
40
47
|
private sample;
|
|
41
48
|
constructor(delegate: AdaptiveLimit, options?: WindowedLimitOptions);
|
|
42
|
-
addSample(startTime: number, rtt: number, inflight: number, didDrop: boolean): void;
|
|
49
|
+
addSample(startTime: number, rtt: number, inflight: number, didDrop: boolean, operationName?: string): void;
|
|
43
50
|
get currentLimit(): number;
|
|
44
51
|
subscribe(consumer: (newLimit: number) => void, options?: {
|
|
45
52
|
signal?: AbortSignal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WindowedLimit.d.ts","sourceRoot":"","sources":["../../src/limit/WindowedLimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAS7D,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,MAAM,YAAY,CAAC;CAC1C;AAED
|
|
1
|
+
{"version":3,"file":"WindowedLimit.d.ts","sourceRoot":"","sources":["../../src/limit/WindowedLimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAS7D,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,MAAM,YAAY,CAAC;CAC1C;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,aAAc,YAAW,aAAa;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IAEzC,kFAAkF;IAClF,OAAO,CAAC,cAAc,CAAK;IAE3B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,0DAA0D;IAC1D,OAAO,CAAC,MAAM,CAAe;gBAEjB,QAAQ,EAAE,aAAa,EAAE,OAAO,GAAE,oBAAyB;IAwBvE,SAAS,CACP,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,aAAa,CAAC,EAAE,MAAM,GACrB,IAAI;IAsCP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,MAAM,IAAI;CAGd"}
|
|
@@ -9,6 +9,13 @@ const DEFAULT_MIN_RTT_THRESHOLD = 0.1; // 100 microseconds in ms
|
|
|
9
9
|
* forwarding aggregated results to a delegate StreamingLimit. This reduces
|
|
10
10
|
* noise from individual samples and ensures the delegate only sees
|
|
11
11
|
* representative data.
|
|
12
|
+
*
|
|
13
|
+
* **Important:** Because a window aggregates samples across many requests,
|
|
14
|
+
* the `operationName` received in each `addSample` call is intentionally
|
|
15
|
+
* **not** forwarded to the delegate. This means `WindowedLimit` is
|
|
16
|
+
* incompatible with delegates that rely on per-sample operation names
|
|
17
|
+
* (e.g. `GroupAwareLimit`). Wrapping a `GroupAwareLimit` in a
|
|
18
|
+
* `WindowedLimit` will silently disable all group-aware behavior.
|
|
12
19
|
*/
|
|
13
20
|
export class WindowedLimit {
|
|
14
21
|
delegate;
|
|
@@ -26,8 +33,10 @@ export class WindowedLimit {
|
|
|
26
33
|
this.minWindowTime = options.minWindowTimeMs ?? DEFAULT_MIN_WINDOW_TIME;
|
|
27
34
|
this.maxWindowTime = options.maxWindowTimeMs ?? DEFAULT_MAX_WINDOW_TIME;
|
|
28
35
|
this.windowSize = options.windowSize ?? DEFAULT_WINDOW_SIZE;
|
|
29
|
-
this.minRttThreshold =
|
|
30
|
-
|
|
36
|
+
this.minRttThreshold =
|
|
37
|
+
options.minRttThresholdMs ?? DEFAULT_MIN_RTT_THRESHOLD;
|
|
38
|
+
this.sampleWindowFactory =
|
|
39
|
+
options.sampleWindowFactory ?? makeAverageSampleWindow;
|
|
31
40
|
if (this.minWindowTime < 100) {
|
|
32
41
|
throw new Error("minWindowTime must be >= 100 ms");
|
|
33
42
|
}
|
|
@@ -39,7 +48,7 @@ export class WindowedLimit {
|
|
|
39
48
|
}
|
|
40
49
|
this.sample = this.sampleWindowFactory();
|
|
41
50
|
}
|
|
42
|
-
addSample(startTime, rtt, inflight, didDrop) {
|
|
51
|
+
addSample(startTime, rtt, inflight, didDrop, operationName) {
|
|
43
52
|
const endTime = startTime + rtt;
|
|
44
53
|
if (rtt < this.minRttThreshold) {
|
|
45
54
|
return;
|
|
@@ -53,7 +62,10 @@ export class WindowedLimit {
|
|
|
53
62
|
this.nextUpdateTime =
|
|
54
63
|
endTime +
|
|
55
64
|
Math.min(Math.max(current.candidateRttMs * 2, this.minWindowTime), this.maxWindowTime);
|
|
56
|
-
const isWindowReady = current.candidateRttMs < Infinity &&
|
|
65
|
+
const isWindowReady = current.candidateRttMs < Infinity &&
|
|
66
|
+
current.sampleCount >= this.windowSize;
|
|
67
|
+
// The window has a mix of operations, so we can't provide a single
|
|
68
|
+
// operation name to the delegate.
|
|
57
69
|
if (isWindowReady) {
|
|
58
70
|
this.delegate.addSample(startTime, current.trackedRttMs, current.maxInFlight, current.dropped);
|
|
59
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PercentileSampleWindow.d.ts","sourceRoot":"","sources":["../../../src/limit/window/PercentileSampleWindow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;GAIG;AACH,cAAM,+BAAgC,YAAW,YAAY;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,IAAI,EAAE;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QAGnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB;IAWD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAuBzD,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,YAAY,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"PercentileSampleWindow.d.ts","sourceRoot":"","sources":["../../../src/limit/window/PercentileSampleWindow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;GAIG;AACH,cAAM,+BAAgC,YAAW,YAAY;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,IAAI,EAAE;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QAGnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB;IAWD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAuBzD,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,YAAY,IAAI,MAAM,CASzB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,QAAQ,IAAI,MAAM;CAUnB;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,+BAA+B,CAMjC"}
|
|
@@ -46,10 +46,11 @@ class ImmutablePercentileSampleWindow {
|
|
|
46
46
|
if (this._sampleCount === 0) {
|
|
47
47
|
return 0;
|
|
48
48
|
}
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const k = Math.round(this._sampleCount * this.percentile) - 1;
|
|
50
|
+
// Slice first so quickselect's in-place mutations don't affect
|
|
51
|
+
// other window instances that share the same backing array.
|
|
52
|
+
const copy = this.observedRtts.slice(0, this._sampleCount);
|
|
53
|
+
return quickselect(copy, k, 0, this._sampleCount - 1);
|
|
53
54
|
}
|
|
54
55
|
get maxInFlight() {
|
|
55
56
|
return this._maxInflight;
|
|
@@ -79,3 +80,38 @@ export function createPercentileSampleWindow(percentile, windowSize) {
|
|
|
79
80
|
}
|
|
80
81
|
return new ImmutablePercentileSampleWindow({ percentile, windowSize });
|
|
81
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* In-place Hoare-partition quickselect. Returns the k-th smallest element
|
|
85
|
+
* (0-indexed) within arr[lo..hi]. Mutates the array, but only within
|
|
86
|
+
* the [lo, hi] range.
|
|
87
|
+
*/
|
|
88
|
+
function quickselect(arr, k, lo, hi) {
|
|
89
|
+
while (lo < hi) {
|
|
90
|
+
const pivot = arr[lo + ((hi - lo) >> 1)];
|
|
91
|
+
let i = lo;
|
|
92
|
+
let j = hi;
|
|
93
|
+
while (i <= j) {
|
|
94
|
+
while (arr[i] < pivot)
|
|
95
|
+
i++;
|
|
96
|
+
while (arr[j] > pivot)
|
|
97
|
+
j--;
|
|
98
|
+
if (i <= j) {
|
|
99
|
+
const tmp = arr[i];
|
|
100
|
+
arr[i] = arr[j];
|
|
101
|
+
arr[j] = tmp;
|
|
102
|
+
i++;
|
|
103
|
+
j--;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (k <= j) {
|
|
107
|
+
hi = j;
|
|
108
|
+
}
|
|
109
|
+
else if (k >= i) {
|
|
110
|
+
lo = i;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return arr[k];
|
|
117
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A histogram with log-spaced bins and continuous exponential time decay.
|
|
3
|
+
*
|
|
4
|
+
* Each bin stores a decayed count that is incremented when a sample falls
|
|
5
|
+
* within its range. All counts decay toward zero over time using an
|
|
6
|
+
* exponential half-life, so older observations naturally lose influence.
|
|
7
|
+
*
|
|
8
|
+
* Provides approximate percentile queries in O(numBins) time and fixed memory
|
|
9
|
+
* (~25 floats), independent of sample volume.
|
|
10
|
+
*/
|
|
11
|
+
export declare class DecayingHistogram {
|
|
12
|
+
/** Lower boundary (inclusive) of each bin. The last bin extends to +Infinity. */
|
|
13
|
+
private readonly binEdges;
|
|
14
|
+
private readonly binCounts;
|
|
15
|
+
private readonly numBins;
|
|
16
|
+
private readonly minValue;
|
|
17
|
+
private readonly maxValue;
|
|
18
|
+
private readonly logWarning;
|
|
19
|
+
/** Precomputed decay constant: ln(2) / halfLife */
|
|
20
|
+
private readonly lambda;
|
|
21
|
+
/** Timestamp of the last decay application, or undefined before the first sample. */
|
|
22
|
+
private lastDecayTime;
|
|
23
|
+
/** Accumulated decayed total (sum of all bin counts after last decay). */
|
|
24
|
+
private _totalCount;
|
|
25
|
+
constructor(options: {
|
|
26
|
+
/** Half-life for exponential decay, in milliseconds. */
|
|
27
|
+
halfLife: number;
|
|
28
|
+
minValue?: number;
|
|
29
|
+
maxValue?: number;
|
|
30
|
+
binsPerDecade?: number;
|
|
31
|
+
logWarning?: (message: string) => void;
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Record a sample value at the given time.
|
|
35
|
+
*/
|
|
36
|
+
addSample(value: number, now: number): void;
|
|
37
|
+
/**
|
|
38
|
+
* Query an approximate percentile from the decayed histogram.
|
|
39
|
+
*
|
|
40
|
+
* Returns the geometric midpoint of the bin containing the target
|
|
41
|
+
* percentile, which reduces quantization bias from log-spaced bin edges.
|
|
42
|
+
*
|
|
43
|
+
* @param p Percentile in (0, 1), e.g. 0.1 for p10.
|
|
44
|
+
* @param now Current time for decay application.
|
|
45
|
+
* @returns The approximate value at the requested percentile, or NaN if
|
|
46
|
+
* the histogram is empty.
|
|
47
|
+
*/
|
|
48
|
+
percentile(p: number, now: number): number;
|
|
49
|
+
/**
|
|
50
|
+
* The total decayed sample count across all bins.
|
|
51
|
+
*/
|
|
52
|
+
get totalCount(): number;
|
|
53
|
+
/**
|
|
54
|
+
* Apply time-based decay to all bins. Idempotent if called multiple times
|
|
55
|
+
* at the same timestamp.
|
|
56
|
+
*/
|
|
57
|
+
private applyDecay;
|
|
58
|
+
/**
|
|
59
|
+
* Find the bin index for a value using binary search.
|
|
60
|
+
*/
|
|
61
|
+
private findBin;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=DecayingHistogram.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DecayingHistogram.d.ts","sourceRoot":"","sources":["../../src/statistics/DecayingHistogram.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,iBAAiB;IAC5B,iFAAiF;IACjF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA0C;IAErE,mDAAmD;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,qFAAqF;IACrF,OAAO,CAAC,aAAa,CAAqB;IAE1C,0EAA0E;IAC1E,OAAO,CAAC,WAAW,CAAK;gBAEZ,OAAO,EAAE;QACnB,wDAAwD;QACxD,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;KACxC;IAiCD;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAsB3C;;;;;;;;;;OAUG;IACH,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;IA2B1C;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED;;;OAGG;IACH,OAAO,CAAC,UAAU;IAsBlB;;OAEG;IACH,OAAO,CAAC,OAAO;CAehB"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A histogram with log-spaced bins and continuous exponential time decay.
|
|
3
|
+
*
|
|
4
|
+
* Each bin stores a decayed count that is incremented when a sample falls
|
|
5
|
+
* within its range. All counts decay toward zero over time using an
|
|
6
|
+
* exponential half-life, so older observations naturally lose influence.
|
|
7
|
+
*
|
|
8
|
+
* Provides approximate percentile queries in O(numBins) time and fixed memory
|
|
9
|
+
* (~25 floats), independent of sample volume.
|
|
10
|
+
*/
|
|
11
|
+
export class DecayingHistogram {
|
|
12
|
+
/** Lower boundary (inclusive) of each bin. The last bin extends to +Infinity. */
|
|
13
|
+
binEdges;
|
|
14
|
+
binCounts;
|
|
15
|
+
numBins;
|
|
16
|
+
minValue;
|
|
17
|
+
maxValue;
|
|
18
|
+
logWarning;
|
|
19
|
+
/** Precomputed decay constant: ln(2) / halfLife */
|
|
20
|
+
lambda;
|
|
21
|
+
/** Timestamp of the last decay application, or undefined before the first sample. */
|
|
22
|
+
lastDecayTime;
|
|
23
|
+
/** Accumulated decayed total (sum of all bin counts after last decay). */
|
|
24
|
+
_totalCount = 0;
|
|
25
|
+
constructor(options) {
|
|
26
|
+
const { halfLife, minValue = 0.1, maxValue = 100_000, binsPerDecade = 5, logWarning, } = options;
|
|
27
|
+
if (halfLife <= 0) {
|
|
28
|
+
throw new Error("halfLife must be positive");
|
|
29
|
+
}
|
|
30
|
+
if (minValue <= 0 || maxValue <= minValue) {
|
|
31
|
+
throw new Error("Must have 0 < minValue < maxValue");
|
|
32
|
+
}
|
|
33
|
+
this.lambda = Math.LN2 / halfLife;
|
|
34
|
+
this.minValue = minValue;
|
|
35
|
+
this.maxValue = maxValue;
|
|
36
|
+
this.logWarning = logWarning;
|
|
37
|
+
const logMin = Math.log10(minValue);
|
|
38
|
+
const logMax = Math.log10(maxValue);
|
|
39
|
+
const decades = logMax - logMin;
|
|
40
|
+
this.numBins = Math.max(2, Math.ceil(decades * binsPerDecade) + 1);
|
|
41
|
+
this.binEdges = new Float64Array(this.numBins);
|
|
42
|
+
for (let i = 0; i < this.numBins; i++) {
|
|
43
|
+
this.binEdges[i] = Math.pow(10, logMin + (i * decades) / (this.numBins - 1));
|
|
44
|
+
}
|
|
45
|
+
this.binCounts = new Float64Array(this.numBins);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Record a sample value at the given time.
|
|
49
|
+
*/
|
|
50
|
+
addSample(value, now) {
|
|
51
|
+
if (this.logWarning) {
|
|
52
|
+
if (value < this.minValue) {
|
|
53
|
+
this.logWarning(`DecayingHistogram: sample ${value} is below minValue ${this.minValue}; ` +
|
|
54
|
+
`it will be clamped to the lowest bin. Consider lowering minValue.`);
|
|
55
|
+
}
|
|
56
|
+
else if (value > this.maxValue) {
|
|
57
|
+
this.logWarning(`DecayingHistogram: sample ${value} is above maxValue ${this.maxValue}; ` +
|
|
58
|
+
`it will be clamped to the highest bin. Consider raising maxValue.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
this.applyDecay(now);
|
|
62
|
+
const bin = this.findBin(value);
|
|
63
|
+
this.binCounts[bin]++;
|
|
64
|
+
this._totalCount += 1;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Query an approximate percentile from the decayed histogram.
|
|
68
|
+
*
|
|
69
|
+
* Returns the geometric midpoint of the bin containing the target
|
|
70
|
+
* percentile, which reduces quantization bias from log-spaced bin edges.
|
|
71
|
+
*
|
|
72
|
+
* @param p Percentile in (0, 1), e.g. 0.1 for p10.
|
|
73
|
+
* @param now Current time for decay application.
|
|
74
|
+
* @returns The approximate value at the requested percentile, or NaN if
|
|
75
|
+
* the histogram is empty.
|
|
76
|
+
*/
|
|
77
|
+
percentile(p, now) {
|
|
78
|
+
if (p <= 0 || p >= 1) {
|
|
79
|
+
throw new RangeError(`p must be in (0, 1), got ${p}`);
|
|
80
|
+
}
|
|
81
|
+
this.applyDecay(now);
|
|
82
|
+
if (this._totalCount <= 0) {
|
|
83
|
+
return NaN;
|
|
84
|
+
}
|
|
85
|
+
const target = p * this._totalCount;
|
|
86
|
+
let cumulative = 0;
|
|
87
|
+
for (let i = 0; i < this.numBins; i++) {
|
|
88
|
+
cumulative += this.binCounts[i];
|
|
89
|
+
if (cumulative >= target) {
|
|
90
|
+
const lo = this.binEdges[i];
|
|
91
|
+
const hi = i + 1 < this.numBins ? this.binEdges[i + 1] : lo * 2;
|
|
92
|
+
return Math.sqrt(lo * hi);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const lastEdge = this.binEdges[this.numBins - 1];
|
|
96
|
+
return Math.sqrt(lastEdge * lastEdge * 2);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The total decayed sample count across all bins.
|
|
100
|
+
*/
|
|
101
|
+
get totalCount() {
|
|
102
|
+
return this._totalCount;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Apply time-based decay to all bins. Idempotent if called multiple times
|
|
106
|
+
* at the same timestamp.
|
|
107
|
+
*/
|
|
108
|
+
applyDecay(now) {
|
|
109
|
+
if (this.lastDecayTime === undefined) {
|
|
110
|
+
this.lastDecayTime = now;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const elapsed = now - this.lastDecayTime;
|
|
114
|
+
if (elapsed <= 0) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const factor = Math.exp(-this.lambda * elapsed);
|
|
118
|
+
let total = 0;
|
|
119
|
+
for (let i = 0; i < this.numBins; i++) {
|
|
120
|
+
const decayed = this.binCounts[i] * factor;
|
|
121
|
+
this.binCounts[i] = decayed;
|
|
122
|
+
total += decayed;
|
|
123
|
+
}
|
|
124
|
+
this._totalCount = total;
|
|
125
|
+
this.lastDecayTime = now;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Find the bin index for a value using binary search.
|
|
129
|
+
*/
|
|
130
|
+
findBin(value) {
|
|
131
|
+
let lo = 0;
|
|
132
|
+
let hi = this.numBins - 1;
|
|
133
|
+
while (lo < hi) {
|
|
134
|
+
const mid = (lo + hi + 1) >>> 1;
|
|
135
|
+
if (value >= this.binEdges[mid]) {
|
|
136
|
+
lo = mid;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
hi = mid - 1;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return lo;
|
|
143
|
+
}
|
|
144
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -7,5 +7,11 @@ export declare function squareRoot(n: number): number;
|
|
|
7
7
|
* Create a function that returns: max(baseline, squareRoot(n))
|
|
8
8
|
*/
|
|
9
9
|
export declare function squareRootWithBaseline(baseline: number): (n: number) => number;
|
|
10
|
+
/**
|
|
11
|
+
* Only expects positive integer inputs (behavior is undefined for
|
|
12
|
+
* non-finite, negative, or non-integer values). Floors the result to an int.
|
|
13
|
+
* Returns >= 1 for all valid inputs. So, log10Scale(0) = 1.
|
|
14
|
+
*/
|
|
15
|
+
export declare function log10Scale(n: number): number;
|
|
10
16
|
export { LinkedWaiterQueue } from "./LinkedWaiterQueue.js";
|
|
11
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,GACf,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAEvB;AAED,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,GACf,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAEvB;AAUD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/utils/index.js
CHANGED
|
@@ -17,4 +17,17 @@ export function squareRoot(n) {
|
|
|
17
17
|
export function squareRootWithBaseline(baseline) {
|
|
18
18
|
return (n) => Math.max(baseline, squareRoot(n));
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Sublinear scale of the concurrency limit (floor of log10, lower-bounded for
|
|
22
|
+
* small n). Same idea as Netflix's Log10RootIntFunction.
|
|
23
|
+
*/
|
|
24
|
+
const log10ScaleLookup = Array.from({ length: 1000 }, (_, i) => Math.max(1, Math.floor(Math.log10(i))));
|
|
25
|
+
/**
|
|
26
|
+
* Only expects positive integer inputs (behavior is undefined for
|
|
27
|
+
* non-finite, negative, or non-integer values). Floors the result to an int.
|
|
28
|
+
* Returns >= 1 for all valid inputs. So, log10Scale(0) = 1.
|
|
29
|
+
*/
|
|
30
|
+
export function log10Scale(n) {
|
|
31
|
+
return n < 1000 ? log10ScaleLookup[n] : Math.floor(Math.log10(n));
|
|
32
|
+
}
|
|
20
33
|
export { LinkedWaiterQueue } from "./LinkedWaiterQueue.js";
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"dynamic rate limiting"
|
|
10
10
|
],
|
|
11
11
|
"author": "Ethan Resnick <ethan.resnick@gmail.com>",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.11.0",
|
|
13
13
|
"type": "module",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
@@ -31,5 +31,8 @@
|
|
|
31
31
|
"type-fest": "^5.5.0",
|
|
32
32
|
"type-party": "^0.7.3",
|
|
33
33
|
"typescript": "^6.0.2"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"lru_map": "^0.4.1"
|
|
34
37
|
}
|
|
35
38
|
}
|