@crawlee/core 4.0.0-beta.13 → 4.0.0-beta.131

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.
Files changed (280) hide show
  1. package/README.md +17 -13
  2. package/autoscaling/autoscaled_pool.d.ts +67 -172
  3. package/autoscaling/autoscaled_pool.js +182 -329
  4. package/autoscaling/concurrency_system.d.ts +268 -0
  5. package/autoscaling/concurrency_system.js +362 -0
  6. package/autoscaling/cpu_load_signal.d.ts +43 -0
  7. package/autoscaling/cpu_load_signal.js +47 -0
  8. package/autoscaling/event_loop_load_signal.d.ts +51 -0
  9. package/autoscaling/event_loop_load_signal.js +60 -0
  10. package/autoscaling/index.d.ts +6 -1
  11. package/autoscaling/index.js +6 -1
  12. package/autoscaling/load_signal.d.ts +100 -0
  13. package/autoscaling/load_signal.js +105 -0
  14. package/autoscaling/memory_load_signal.d.ts +47 -0
  15. package/autoscaling/memory_load_signal.js +106 -0
  16. package/autoscaling/snapshotter.d.ts +58 -163
  17. package/autoscaling/snapshotter.js +45 -263
  18. package/autoscaling/storage_backend_load_signal.d.ts +56 -0
  19. package/autoscaling/storage_backend_load_signal.js +73 -0
  20. package/autoscaling/system_status.d.ts +67 -89
  21. package/autoscaling/system_status.js +92 -122
  22. package/autoscaling/weighted_avg.d.ts +5 -0
  23. package/autoscaling/weighted_avg.js +14 -0
  24. package/byte_utils.d.ts +17 -0
  25. package/byte_utils.js +42 -0
  26. package/configuration.d.ts +96 -223
  27. package/configuration.js +170 -222
  28. package/cookie_utils.d.ts +3 -2
  29. package/cookie_utils.js +18 -7
  30. package/crawlers/context_pipeline.d.ts +10 -1
  31. package/crawlers/context_pipeline.js +31 -8
  32. package/crawlers/crawler_commons.d.ts +90 -129
  33. package/crawlers/crawler_commons.js +1 -116
  34. package/crawlers/error_snapshotter.d.ts +2 -5
  35. package/crawlers/error_snapshotter.js +7 -8
  36. package/crawlers/error_tracker.d.ts +0 -1
  37. package/crawlers/error_tracker.js +0 -1
  38. package/crawlers/index.d.ts +1 -3
  39. package/crawlers/index.js +0 -3
  40. package/crawlers/internals/types.d.ts +0 -1
  41. package/crawlers/internals/types.js +0 -1
  42. package/crawlers/statistics.d.ts +187 -63
  43. package/crawlers/statistics.js +354 -164
  44. package/debug.d.ts +36 -0
  45. package/debug.js +70 -0
  46. package/enqueue_links/enqueue_links.d.ts +68 -154
  47. package/enqueue_links/enqueue_links.js +38 -210
  48. package/enqueue_links/index.d.ts +0 -1
  49. package/enqueue_links/index.js +0 -1
  50. package/enqueue_links/shared.d.ts +52 -26
  51. package/enqueue_links/shared.js +115 -66
  52. package/errors.d.ts +72 -4
  53. package/errors.js +89 -5
  54. package/events/event_manager.d.ts +35 -9
  55. package/events/event_manager.js +12 -13
  56. package/events/index.d.ts +0 -1
  57. package/events/index.js +0 -1
  58. package/events/local_event_manager.d.ts +15 -3
  59. package/events/local_event_manager.js +39 -13
  60. package/http.d.ts +9 -0
  61. package/http.js +28 -0
  62. package/index.d.ts +7 -4
  63. package/index.js +6 -3
  64. package/iterables.d.ts +79 -0
  65. package/iterables.js +134 -0
  66. package/log.d.ts +82 -3
  67. package/log.js +106 -1
  68. package/memory-storage/consts.d.ts +4 -0
  69. package/memory-storage/consts.js +4 -0
  70. package/memory-storage/index.d.ts +1 -0
  71. package/memory-storage/index.js +1 -0
  72. package/memory-storage/memory-storage.d.ts +38 -0
  73. package/memory-storage/memory-storage.js +130 -0
  74. package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
  75. package/memory-storage/resource-clients/common/base-client.js +6 -0
  76. package/memory-storage/resource-clients/dataset.d.ts +40 -0
  77. package/memory-storage/resource-clients/dataset.js +108 -0
  78. package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
  79. package/memory-storage/resource-clients/key-value-store.js +201 -0
  80. package/memory-storage/resource-clients/request-queue.d.ts +77 -0
  81. package/memory-storage/resource-clients/request-queue.js +409 -0
  82. package/memory-storage/utils.d.ts +16 -0
  83. package/memory-storage/utils.js +41 -0
  84. package/owned_or_injected.d.ts +58 -0
  85. package/owned_or_injected.js +98 -0
  86. package/package.json +14 -13
  87. package/proxy_configuration.d.ts +24 -132
  88. package/proxy_configuration.js +32 -147
  89. package/recoverable_state.d.ts +155 -0
  90. package/recoverable_state.js +231 -0
  91. package/request.d.ts +80 -16
  92. package/request.js +147 -68
  93. package/router.d.ts +193 -21
  94. package/router.js +188 -43
  95. package/serialization.d.ts +0 -1
  96. package/serialization.js +15 -15
  97. package/service_locator.d.ts +165 -0
  98. package/service_locator.js +253 -0
  99. package/session_pool/consts.d.ts +1 -2
  100. package/session_pool/consts.js +1 -2
  101. package/session_pool/errors.d.ts +0 -1
  102. package/session_pool/errors.js +0 -1
  103. package/session_pool/fingerprint.d.ts +9 -0
  104. package/session_pool/fingerprint.js +30 -0
  105. package/session_pool/index.d.ts +0 -2
  106. package/session_pool/index.js +0 -2
  107. package/session_pool/session.d.ts +35 -88
  108. package/session_pool/session.js +101 -159
  109. package/session_pool/session_pool.d.ts +73 -91
  110. package/session_pool/session_pool.js +172 -165
  111. package/storages/batched_adds.d.ts +37 -0
  112. package/storages/batched_adds.js +73 -0
  113. package/storages/dataset.d.ts +114 -54
  114. package/storages/dataset.js +291 -148
  115. package/storages/index.d.ts +10 -8
  116. package/storages/index.js +8 -8
  117. package/storages/key_value_store.d.ts +185 -42
  118. package/storages/key_value_store.js +445 -169
  119. package/storages/key_value_store_codec.d.ts +32 -0
  120. package/storages/key_value_store_codec.js +113 -0
  121. package/storages/request_dedup_cache.d.ts +22 -0
  122. package/storages/request_dedup_cache.js +48 -0
  123. package/storages/request_list.d.ts +52 -115
  124. package/storages/request_list.js +183 -152
  125. package/storages/request_loader.d.ts +101 -0
  126. package/storages/request_loader.js +1 -0
  127. package/storages/request_manager.d.ts +33 -0
  128. package/storages/request_manager.js +1 -0
  129. package/storages/request_manager_tandem.d.ts +97 -0
  130. package/storages/request_manager_tandem.js +197 -0
  131. package/storages/request_queue.d.ts +290 -47
  132. package/storages/request_queue.js +762 -216
  133. package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +45 -89
  134. package/storages/sitemap_request_loader.js +438 -0
  135. package/storages/storage_instance_manager.d.ts +87 -0
  136. package/storages/storage_instance_manager.js +256 -0
  137. package/storages/storage_stats.d.ts +48 -0
  138. package/storages/storage_stats.js +29 -0
  139. package/storages/throttling_request_manager.d.ts +239 -0
  140. package/storages/throttling_request_manager.js +646 -0
  141. package/storages/transaction.d.ts +252 -0
  142. package/storages/transaction.js +251 -0
  143. package/storages/utils.d.ts +59 -11
  144. package/storages/utils.js +75 -15
  145. package/system-info/cpu-info.d.ts +67 -0
  146. package/system-info/cpu-info.js +216 -0
  147. package/system-info/memory-info.d.ts +31 -0
  148. package/system-info/memory-info.js +115 -0
  149. package/system-info/ps-tree.d.ts +17 -0
  150. package/system-info/ps-tree.js +144 -0
  151. package/system-info/runtime.d.ts +14 -0
  152. package/system-info/runtime.js +80 -0
  153. package/typedefs.d.ts +0 -6
  154. package/typedefs.js +0 -1
  155. package/url.d.ts +9 -0
  156. package/url.js +11 -0
  157. package/validators.d.ts +22 -18
  158. package/validators.js +13 -18
  159. package/autoscaling/autoscaled_pool.d.ts.map +0 -1
  160. package/autoscaling/autoscaled_pool.js.map +0 -1
  161. package/autoscaling/index.d.ts.map +0 -1
  162. package/autoscaling/index.js.map +0 -1
  163. package/autoscaling/snapshotter.d.ts.map +0 -1
  164. package/autoscaling/snapshotter.js.map +0 -1
  165. package/autoscaling/system_status.d.ts.map +0 -1
  166. package/autoscaling/system_status.js.map +0 -1
  167. package/configuration.d.ts.map +0 -1
  168. package/configuration.js.map +0 -1
  169. package/cookie_utils.d.ts.map +0 -1
  170. package/cookie_utils.js.map +0 -1
  171. package/crawlers/context_pipeline.d.ts.map +0 -1
  172. package/crawlers/context_pipeline.js.map +0 -1
  173. package/crawlers/crawler_commons.d.ts.map +0 -1
  174. package/crawlers/crawler_commons.js.map +0 -1
  175. package/crawlers/crawler_utils.d.ts +0 -10
  176. package/crawlers/crawler_utils.d.ts.map +0 -1
  177. package/crawlers/crawler_utils.js +0 -12
  178. package/crawlers/crawler_utils.js.map +0 -1
  179. package/crawlers/error_snapshotter.d.ts.map +0 -1
  180. package/crawlers/error_snapshotter.js.map +0 -1
  181. package/crawlers/error_tracker.d.ts.map +0 -1
  182. package/crawlers/error_tracker.js.map +0 -1
  183. package/crawlers/index.d.ts.map +0 -1
  184. package/crawlers/index.js.map +0 -1
  185. package/crawlers/internals/types.d.ts.map +0 -1
  186. package/crawlers/internals/types.js.map +0 -1
  187. package/crawlers/statistics.d.ts.map +0 -1
  188. package/crawlers/statistics.js.map +0 -1
  189. package/enqueue_links/enqueue_links.d.ts.map +0 -1
  190. package/enqueue_links/enqueue_links.js.map +0 -1
  191. package/enqueue_links/index.d.ts.map +0 -1
  192. package/enqueue_links/index.js.map +0 -1
  193. package/enqueue_links/shared.d.ts.map +0 -1
  194. package/enqueue_links/shared.js.map +0 -1
  195. package/errors.d.ts.map +0 -1
  196. package/errors.js.map +0 -1
  197. package/events/event_manager.d.ts.map +0 -1
  198. package/events/event_manager.js.map +0 -1
  199. package/events/index.d.ts.map +0 -1
  200. package/events/index.js.map +0 -1
  201. package/events/local_event_manager.d.ts.map +0 -1
  202. package/events/local_event_manager.js.map +0 -1
  203. package/http_clients/base-http-client.d.ts +0 -140
  204. package/http_clients/base-http-client.d.ts.map +0 -1
  205. package/http_clients/base-http-client.js +0 -40
  206. package/http_clients/base-http-client.js.map +0 -1
  207. package/http_clients/form-data-like.d.ts +0 -67
  208. package/http_clients/form-data-like.d.ts.map +0 -1
  209. package/http_clients/form-data-like.js +0 -5
  210. package/http_clients/form-data-like.js.map +0 -1
  211. package/http_clients/got-scraping-http-client.d.ts +0 -20
  212. package/http_clients/got-scraping-http-client.d.ts.map +0 -1
  213. package/http_clients/got-scraping-http-client.js +0 -82
  214. package/http_clients/got-scraping-http-client.js.map +0 -1
  215. package/http_clients/index.d.ts +0 -3
  216. package/http_clients/index.d.ts.map +0 -1
  217. package/http_clients/index.js +0 -3
  218. package/http_clients/index.js.map +0 -1
  219. package/index.d.ts.map +0 -1
  220. package/index.js.map +0 -1
  221. package/log.d.ts.map +0 -1
  222. package/log.js.map +0 -1
  223. package/proxy_configuration.d.ts.map +0 -1
  224. package/proxy_configuration.js.map +0 -1
  225. package/request.d.ts.map +0 -1
  226. package/request.js.map +0 -1
  227. package/router.d.ts.map +0 -1
  228. package/router.js.map +0 -1
  229. package/serialization.d.ts.map +0 -1
  230. package/serialization.js.map +0 -1
  231. package/session_pool/consts.d.ts.map +0 -1
  232. package/session_pool/consts.js.map +0 -1
  233. package/session_pool/errors.d.ts.map +0 -1
  234. package/session_pool/errors.js.map +0 -1
  235. package/session_pool/events.d.ts +0 -3
  236. package/session_pool/events.d.ts.map +0 -1
  237. package/session_pool/events.js +0 -3
  238. package/session_pool/events.js.map +0 -1
  239. package/session_pool/index.d.ts.map +0 -1
  240. package/session_pool/index.js.map +0 -1
  241. package/session_pool/session.d.ts.map +0 -1
  242. package/session_pool/session.js.map +0 -1
  243. package/session_pool/session_pool.d.ts.map +0 -1
  244. package/session_pool/session_pool.js.map +0 -1
  245. package/storages/access_checking.d.ts +0 -13
  246. package/storages/access_checking.d.ts.map +0 -1
  247. package/storages/access_checking.js +0 -14
  248. package/storages/access_checking.js.map +0 -1
  249. package/storages/dataset.d.ts.map +0 -1
  250. package/storages/dataset.js.map +0 -1
  251. package/storages/index.d.ts.map +0 -1
  252. package/storages/index.js.map +0 -1
  253. package/storages/key_value_store.d.ts.map +0 -1
  254. package/storages/key_value_store.js.map +0 -1
  255. package/storages/request_list.d.ts.map +0 -1
  256. package/storages/request_list.js.map +0 -1
  257. package/storages/request_provider.d.ts +0 -308
  258. package/storages/request_provider.d.ts.map +0 -1
  259. package/storages/request_provider.js +0 -555
  260. package/storages/request_provider.js.map +0 -1
  261. package/storages/request_queue.d.ts.map +0 -1
  262. package/storages/request_queue.js.map +0 -1
  263. package/storages/request_queue_v2.d.ts +0 -87
  264. package/storages/request_queue_v2.d.ts.map +0 -1
  265. package/storages/request_queue_v2.js +0 -438
  266. package/storages/request_queue_v2.js.map +0 -1
  267. package/storages/sitemap_request_list.d.ts.map +0 -1
  268. package/storages/sitemap_request_list.js +0 -430
  269. package/storages/sitemap_request_list.js.map +0 -1
  270. package/storages/storage_manager.d.ts +0 -58
  271. package/storages/storage_manager.d.ts.map +0 -1
  272. package/storages/storage_manager.js +0 -105
  273. package/storages/storage_manager.js.map +0 -1
  274. package/storages/utils.d.ts.map +0 -1
  275. package/storages/utils.js.map +0 -1
  276. package/tsconfig.build.tsbuildinfo +0 -1
  277. package/typedefs.d.ts.map +0 -1
  278. package/typedefs.js.map +0 -1
  279. package/validators.d.ts.map +0 -1
  280. package/validators.js.map +0 -1
@@ -0,0 +1,73 @@
1
+ import { betterClearInterval, betterSetInterval } from '@apify/utilities';
2
+ import { serviceLocator } from '../service_locator.js';
3
+ import { SnapshotStore } from './load_signal.js';
4
+ const RATE_LIMIT_ERROR_RETRY_COUNT = 2;
5
+ /**
6
+ * Periodically checks the storage backend for rate-limit errors (HTTP 429) and reports overload when the error delta
7
+ * exceeds a threshold.
8
+ *
9
+ * Built by default; construct one yourself only to wrap or adapt it — see {@link LoadSignal}.
10
+ *
11
+ * Switch it off entirely ({@link LoadSignalsOptions.storageBackend|`storageBackend: false`}) if the storage backend
12
+ * reports no rate-limit statistics, since it otherwise polls it every second to no purpose.
13
+ *
14
+ * @category Scaling
15
+ */
16
+ export class StorageBackendLoadSignal {
17
+ name = 'storageBackendInfo';
18
+ overloadedRatio;
19
+ #store = new SnapshotStore();
20
+ #intervalMillis;
21
+ #maxErrors;
22
+ #interval;
23
+ #storageBackend;
24
+ constructor(options = {}) {
25
+ this.overloadedRatio = options.overloadedRatio ?? 0.3;
26
+ this.#intervalMillis = (options.snapshotIntervalSecs ?? 1) * 1000;
27
+ this.#maxErrors = options.maxErrors ?? 3;
28
+ this.handle = this.handle.bind(this);
29
+ }
30
+ async start(context) {
31
+ this.#store.useSampleWindow(context.maxSampleWindowMillis);
32
+ // A new session starts from a clean slate, or its first measurement diffs the error count against the previous
33
+ // session's — possibly against a different backend, since it is resolved afresh just below.
34
+ this.#store.clear();
35
+ // Resolved here rather than in the constructor, where asking for the backend would instantiate a default one
36
+ // as a side effect - long before the crawler that owns the run has had a chance to register its own.
37
+ this.#storageBackend = serviceLocator.getStorageBackend();
38
+ this.#interval = betterSetInterval(this.handle, this.#intervalMillis);
39
+ }
40
+ async stop() {
41
+ if (this.#interval)
42
+ betterClearInterval(this.#interval);
43
+ this.#interval = undefined;
44
+ this.#storageBackend = undefined;
45
+ }
46
+ getSample(sampleDurationMillis) {
47
+ return this.#store.getSample(sampleDurationMillis);
48
+ }
49
+ /**
50
+ * Records one snapshot, overloaded when rate-limit errors grew by more than the configured limit since the
51
+ * previous one.
52
+ * @internal Also lets tests drive the measurement without waiting on a timer.
53
+ */
54
+ handle(intervalCallback) {
55
+ const now = new Date();
56
+ const allErrorCounts = this.#storageBackend?.stats?.rateLimitErrors ?? [];
57
+ const currentErrCount = allErrorCounts[RATE_LIMIT_ERROR_RETRY_COUNT] || 0;
58
+ const snapshot = {
59
+ createdAt: now,
60
+ isOverloaded: false,
61
+ rateLimitErrorCount: currentErrCount,
62
+ };
63
+ const all = this.#store.getAll();
64
+ const previousSnapshot = all[all.length - 1];
65
+ if (previousSnapshot) {
66
+ const delta = currentErrCount - previousSnapshot.rateLimitErrorCount;
67
+ if (delta > this.#maxErrors)
68
+ snapshot.isOverloaded = true;
69
+ }
70
+ this.#store.push(snapshot, now);
71
+ intervalCallback();
72
+ }
73
+ }
@@ -1,15 +1,16 @@
1
- import type { Configuration } from '../configuration.js';
2
- import { Snapshotter } from './snapshotter.js';
1
+ import type { LoadSignal } from './load_signal.js';
2
+ import type { Snapshotter } from './snapshotter.js';
3
3
  /**
4
4
  * Represents the current status of the system.
5
5
  */
6
6
  export interface SystemInfo {
7
7
  /** If false, system is being overloaded. */
8
8
  isSystemIdle: boolean;
9
- memInfo: ClientInfo;
10
- eventLoopInfo: ClientInfo;
11
- cpuInfo: ClientInfo;
12
- clientInfo: ClientInfo;
9
+ memInfo: LoadSignalInfo;
10
+ eventLoopInfo: LoadSignalInfo;
11
+ cpuInfo: LoadSignalInfo;
12
+ storageBackendInfo: LoadSignalInfo;
13
+ memTotalBytes?: number;
13
14
  memCurrentBytes?: number;
14
15
  /**
15
16
  * Platform only property
@@ -26,7 +27,28 @@ export interface SystemInfo {
26
27
  * @internal
27
28
  */
28
29
  createdAt?: Date;
30
+ /**
31
+ * Status of additional load signals beyond the built-in four.
32
+ * Keys are `LoadSignal.name` values, values are overload info.
33
+ */
34
+ loadSignalInfo?: Record<string, LoadSignalInfo>;
29
35
  }
36
+ /**
37
+ * How far back the *current* system status looks by default — the window that gates task dispatch.
38
+ * @internal
39
+ */
40
+ export declare const DEFAULT_CURRENT_HISTORY_SECS = 5;
41
+ /**
42
+ * How far back the *historical* system status looks by default — the window autoscaling decisions are based on, and
43
+ * therefore how much history the signals are asked to retain.
44
+ * @internal
45
+ */
46
+ export declare const DEFAULT_SNAPSHOT_HISTORY_SECS = 30;
47
+ /**
48
+ * An implementation detail of the {@link ConcurrencySystem} — configure it through
49
+ * {@link ConcurrencySystemOptions} (`loadSignals`, `currentHistorySecs` and `snapshotHistorySecs`).
50
+ * @internal
51
+ */
30
52
  export interface SystemStatusOptions {
31
53
  /**
32
54
  * Defines max age of snapshots used in the {@link SystemStatus.getCurrentStatus} measurement.
@@ -34,37 +56,25 @@ export interface SystemStatusOptions {
34
56
  */
35
57
  currentHistorySecs?: number;
36
58
  /**
37
- * Sets the maximum ratio of overloaded snapshots in a memory sample.
38
- * If the sample exceeds this ratio, the system will be overloaded.
39
- * @default 0.2
40
- */
41
- maxMemoryOverloadedRatio?: number;
42
- /**
43
- * Sets the maximum ratio of overloaded snapshots in an event loop sample.
44
- * If the sample exceeds this ratio, the system will be overloaded.
45
- * @default 0.6
59
+ * Defines max age of snapshots used in the {@link SystemStatus.getHistoricalStatus} measurement — the window
60
+ * autoscaling decisions are based on. Applied uniformly to every signal, built-in or custom, so that a signal's
61
+ * private retention cannot silently widen the window.
62
+ * @default 30
46
63
  */
47
- maxEventLoopOverloadedRatio?: number;
64
+ historySecs?: number;
48
65
  /**
49
- * Sets the maximum ratio of overloaded snapshots in a CPU sample.
50
- * If the sample exceeds this ratio, the system will be overloaded.
51
- * @default 0.4
66
+ * The `Snapshotter` whose built-in signals are evaluated.
52
67
  */
53
- maxCpuOverloadedRatio?: number;
68
+ snapshotter: Snapshotter;
54
69
  /**
55
- * Sets the maximum ratio of overloaded snapshots in a Client sample.
56
- * If the sample exceeds this ratio, the system will be overloaded.
57
- * @default 0.3
70
+ * Additional load signals to include in the system status evaluation.
71
+ * These are evaluated alongside the built-in memory, CPU, event loop,
72
+ * and storage backend signals. If any signal reports overload, the system
73
+ * is considered overloaded. Each signal carries its own overload ratio.
58
74
  */
59
- maxClientOverloadedRatio?: number;
60
- /**
61
- * The `Snapshotter` instance to be queried for `SystemStatus`.
62
- */
63
- snapshotter?: Snapshotter;
64
- /** @internal */
65
- config?: Configuration;
75
+ loadSignals?: LoadSignal[];
66
76
  }
67
- export interface ClientInfo {
77
+ export interface LoadSignalInfo {
68
78
  isOverloaded: boolean;
69
79
  limitRatio: number;
70
80
  actualRatio: number;
@@ -82,35 +92,33 @@ export interface FinalStatistics {
82
92
  crawlerRuntimeMillis: number;
83
93
  }
84
94
  /**
85
- * Provides a simple interface to reading system status from a {@link Snapshotter} instance.
86
- * It only exposes two functions {@link SystemStatus.getCurrentStatus}
87
- * and {@link SystemStatus.getHistoricalStatus}.
88
- * The system status is calculated using a weighted average of overloaded
89
- * messages in the snapshots, with the weights being the time intervals
90
- * between the snapshots. Each resource is calculated separately
91
- * and the system is overloaded whenever at least one resource is overloaded.
92
- * The class is used by the {@link AutoscaledPool} class.
95
+ * Reads the overload verdict of every signal the {@link Snapshotter}'s built-in four plus any custom ones — and
96
+ * combines them into a {@link SystemInfo}: each signal is a time-weighted average of its snapshots, and the system
97
+ * is overloaded whenever at least one of them is.
93
98
  *
94
- * {@link SystemStatus.getCurrentStatus}
95
- * returns a boolean that represents the current status of the system.
96
- * The length of the current timeframe in seconds is configurable
97
- * by the `currentHistorySecs` option and represents the max age
98
- * of snapshots to be considered for the calculation.
99
+ * Evaluated over two windows, both requested explicitly from every signal so that a signal's private retention cannot
100
+ * widen what it contributes: a short `currentHistorySecs` one ({@link SystemStatus.getCurrentStatus}, gating task
101
+ * dispatch) and a longer `historySecs` one ({@link SystemStatus.getHistoricalStatus}, driving autoscaling).
99
102
  *
100
- * {@link SystemStatus.getHistoricalStatus}
101
- * returns a boolean that represents the long-term status
102
- * of the system. It considers the full snapshot history available
103
- * in the {@link Snapshotter} instance.
104
- * @category Scaling
103
+ * An implementation detail of the {@link ConcurrencySystem}, configured through
104
+ * {@link ConcurrencySystemOptions}.
105
+ * @internal
105
106
  */
106
107
  export declare class SystemStatus {
107
- private readonly currentHistoryMillis;
108
- private readonly maxMemoryOverloadedRatio;
109
- private readonly maxEventLoopOverloadedRatio;
110
- private readonly maxCpuOverloadedRatio;
111
- private readonly maxClientOverloadedRatio;
112
- private readonly snapshotter;
113
- constructor(options?: SystemStatusOptions);
108
+ #private;
109
+ constructor(options: SystemStatusOptions);
110
+ /**
111
+ * The widest window any signal will be queried with, and therefore exactly how much history the signals are asked
112
+ * to retain when they start. Derived here, where the windows are resolved, so nothing has to reapply their
113
+ * defaults.
114
+ */
115
+ get maxSampleWindowMillis(): number;
116
+ /**
117
+ * Signal names are the keys of the reported {@link SystemInfo}, so a duplicate would leave a status object that
118
+ * contradicts actual behavior: both signals are still evaluated (any overloaded one holds concurrency down), but
119
+ * only the last is reported.
120
+ */
121
+ private assertUniqueSignalNames;
114
122
  /**
115
123
  * Returns an {@link SystemInfo} object with the following structure:
116
124
  *
@@ -140,42 +148,12 @@ export declare class SystemStatus {
140
148
  * }
141
149
  * ```
142
150
  *
143
- * Where the `isSystemIdle` property is set to `false` if the system
144
- * has been overloaded in the full history of the {@link Snapshotter}
145
- * (which is configurable in the {@link Snapshotter}) and `true` otherwise.
151
+ * Where the `isSystemIdle` property is set to `false` if the system has been overloaded within the last
152
+ * `historySecs` seconds and `true` otherwise.
146
153
  */
147
154
  getHistoricalStatus(): SystemInfo;
148
155
  /**
149
156
  * Returns a system status object.
150
157
  */
151
- protected _isSystemIdle(sampleDurationMillis?: number): SystemInfo;
152
- /**
153
- * Returns an object with an isOverloaded property set to true
154
- * if the memory has been overloaded in the last sampleDurationMillis.
155
- */
156
- protected _isMemoryOverloaded(sampleDurationMillis?: number): ClientInfo;
157
- /**
158
- * Returns an object with an isOverloaded property set to true
159
- * if the event loop has been overloaded in the last sampleDurationMillis.
160
- */
161
- protected _isEventLoopOverloaded(sampleDurationMillis?: number): ClientInfo;
162
- /**
163
- * Returns an object with an isOverloaded property set to true
164
- * if the CPU has been overloaded in the last sampleDurationMillis.
165
- */
166
- protected _isCpuOverloaded(sampleDurationMillis?: number): ClientInfo;
167
- /**
168
- * Returns an object with an isOverloaded property set to true
169
- * if the client has been overloaded in the last sampleDurationMillis.
170
- */
171
- protected _isClientOverloaded(sampleDurationMillis?: number): ClientInfo;
172
- /**
173
- * Returns an object with sample information and an isOverloaded property
174
- * set to true if at least the ratio of snapshots in the sample are overloaded.
175
- */
176
- protected _isSampleOverloaded<T extends {
177
- createdAt: Date;
178
- isOverloaded: boolean;
179
- }>(sample: T[], ratio: number): ClientInfo;
158
+ private isSystemIdle;
180
159
  }
181
- //# sourceMappingURL=system_status.d.ts.map
@@ -1,52 +1,72 @@
1
- import { weightedAvg } from '@crawlee/utils';
2
- import ow from 'ow';
3
- import { Snapshotter } from './snapshotter.js';
1
+ import { evaluateLoadSignalSample } from './load_signal.js';
4
2
  /**
5
- * Provides a simple interface to reading system status from a {@link Snapshotter} instance.
6
- * It only exposes two functions {@link SystemStatus.getCurrentStatus}
7
- * and {@link SystemStatus.getHistoricalStatus}.
8
- * The system status is calculated using a weighted average of overloaded
9
- * messages in the snapshots, with the weights being the time intervals
10
- * between the snapshots. Each resource is calculated separately
11
- * and the system is overloaded whenever at least one resource is overloaded.
12
- * The class is used by the {@link AutoscaledPool} class.
3
+ * How far back the *current* system status looks by default the window that gates task dispatch.
4
+ * @internal
5
+ */
6
+ export const DEFAULT_CURRENT_HISTORY_SECS = 5;
7
+ /**
8
+ * How far back the *historical* system status looks by default — the window autoscaling decisions are based on, and
9
+ * therefore how much history the signals are asked to retain.
10
+ * @internal
11
+ */
12
+ export const DEFAULT_SNAPSHOT_HISTORY_SECS = 30;
13
+ /** The four built-in signal names that map to typed `SystemInfo` fields, and the option that switches each off. */
14
+ const BUILTIN_SIGNAL_OPTION_KEYS = {
15
+ memInfo: 'memory',
16
+ eventLoopInfo: 'eventLoop',
17
+ cpuInfo: 'cpu',
18
+ storageBackendInfo: 'storageBackend',
19
+ };
20
+ const BUILTIN_SIGNAL_NAMES = new Set(Object.keys(BUILTIN_SIGNAL_OPTION_KEYS));
21
+ /**
22
+ * Reads the overload verdict of every signal — the {@link Snapshotter}'s built-in four plus any custom ones — and
23
+ * combines them into a {@link SystemInfo}: each signal is a time-weighted average of its snapshots, and the system
24
+ * is overloaded whenever at least one of them is.
13
25
  *
14
- * {@link SystemStatus.getCurrentStatus}
15
- * returns a boolean that represents the current status of the system.
16
- * The length of the current timeframe in seconds is configurable
17
- * by the `currentHistorySecs` option and represents the max age
18
- * of snapshots to be considered for the calculation.
26
+ * Evaluated over two windows, both requested explicitly from every signal so that a signal's private retention cannot
27
+ * widen what it contributes: a short `currentHistorySecs` one ({@link SystemStatus.getCurrentStatus}, gating task
28
+ * dispatch) and a longer `historySecs` one ({@link SystemStatus.getHistoricalStatus}, driving autoscaling).
19
29
  *
20
- * {@link SystemStatus.getHistoricalStatus}
21
- * returns a boolean that represents the long-term status
22
- * of the system. It considers the full snapshot history available
23
- * in the {@link Snapshotter} instance.
24
- * @category Scaling
30
+ * An implementation detail of the {@link ConcurrencySystem}, configured through
31
+ * {@link ConcurrencySystemOptions}.
32
+ * @internal
25
33
  */
26
34
  export class SystemStatus {
27
- currentHistoryMillis;
28
- maxMemoryOverloadedRatio;
29
- maxEventLoopOverloadedRatio;
30
- maxCpuOverloadedRatio;
31
- maxClientOverloadedRatio;
32
- snapshotter;
33
- constructor(options = {}) {
34
- ow(options, ow.object.exactShape({
35
- currentHistorySecs: ow.optional.number,
36
- maxMemoryOverloadedRatio: ow.optional.number,
37
- maxEventLoopOverloadedRatio: ow.optional.number,
38
- maxCpuOverloadedRatio: ow.optional.number,
39
- maxClientOverloadedRatio: ow.optional.number,
40
- snapshotter: ow.optional.object,
41
- config: ow.optional.object,
42
- }));
43
- const { currentHistorySecs = 5, maxMemoryOverloadedRatio = 0.2, maxEventLoopOverloadedRatio = 0.6, maxCpuOverloadedRatio = 0.4, maxClientOverloadedRatio = 0.3, snapshotter, config, } = options;
44
- this.currentHistoryMillis = currentHistorySecs * 1000;
45
- this.maxMemoryOverloadedRatio = maxMemoryOverloadedRatio;
46
- this.maxEventLoopOverloadedRatio = maxEventLoopOverloadedRatio;
47
- this.maxCpuOverloadedRatio = maxCpuOverloadedRatio;
48
- this.maxClientOverloadedRatio = maxClientOverloadedRatio;
49
- this.snapshotter = snapshotter || new Snapshotter({ config });
35
+ #currentHistoryMillis;
36
+ #historyMillis;
37
+ #signals;
38
+ constructor(options) {
39
+ const { currentHistorySecs = DEFAULT_CURRENT_HISTORY_SECS, historySecs = DEFAULT_SNAPSHOT_HISTORY_SECS, snapshotter, loadSignals = [], } = options;
40
+ this.#currentHistoryMillis = currentHistorySecs * 1000;
41
+ this.#historyMillis = historySecs * 1000;
42
+ this.#signals = [...snapshotter.getLoadSignals(), ...loadSignals];
43
+ this.assertUniqueSignalNames();
44
+ }
45
+ /**
46
+ * The widest window any signal will be queried with, and therefore exactly how much history the signals are asked
47
+ * to retain when they start. Derived here, where the windows are resolved, so nothing has to reapply their
48
+ * defaults.
49
+ */
50
+ get maxSampleWindowMillis() {
51
+ return Math.max(this.#currentHistoryMillis, this.#historyMillis);
52
+ }
53
+ /**
54
+ * Signal names are the keys of the reported {@link SystemInfo}, so a duplicate would leave a status object that
55
+ * contradicts actual behavior: both signals are still evaluated (any overloaded one holds concurrency down), but
56
+ * only the last is reported.
57
+ */
58
+ assertUniqueSignalNames() {
59
+ const seen = new Set();
60
+ for (const { name } of this.#signals) {
61
+ if (!seen.has(name)) {
62
+ seen.add(name);
63
+ continue;
64
+ }
65
+ const hint = BUILTIN_SIGNAL_NAMES.has(name)
66
+ ? `it is the name of a built-in signal. To replace that signal, switch it off with \`loadSignals: { ${BUILTIN_SIGNAL_OPTION_KEYS[name]}: false }\` and keep your implementation in \`loadSignals.custom\`; to run yours alongside it, give it a different name.`
67
+ : 'two custom signals cannot share a name - rename one of them.';
68
+ throw new Error(`Duplicate load signal name ${JSON.stringify(name)}: ${hint}`);
69
+ }
50
70
  }
51
71
  /**
52
72
  * Returns an {@link SystemInfo} object with the following structure:
@@ -65,7 +85,7 @@ export class SystemStatus {
65
85
  * and `true` otherwise.
66
86
  */
67
87
  getCurrentStatus() {
68
- return this._isSystemIdle(this.currentHistoryMillis);
88
+ return this.isSystemIdle(this.#currentHistoryMillis);
69
89
  }
70
90
  /**
71
91
  * Returns an {@link SystemInfo} object with the following structure:
@@ -79,91 +99,41 @@ export class SystemStatus {
79
99
  * }
80
100
  * ```
81
101
  *
82
- * Where the `isSystemIdle` property is set to `false` if the system
83
- * has been overloaded in the full history of the {@link Snapshotter}
84
- * (which is configurable in the {@link Snapshotter}) and `true` otherwise.
102
+ * Where the `isSystemIdle` property is set to `false` if the system has been overloaded within the last
103
+ * `historySecs` seconds and `true` otherwise.
85
104
  */
86
105
  getHistoricalStatus() {
87
- return this._isSystemIdle();
106
+ return this.isSystemIdle(this.#historyMillis);
88
107
  }
89
108
  /**
90
109
  * Returns a system status object.
91
110
  */
92
- _isSystemIdle(sampleDurationMillis) {
93
- const memInfo = this._isMemoryOverloaded(sampleDurationMillis);
94
- const eventLoopInfo = this._isEventLoopOverloaded(sampleDurationMillis);
95
- const cpuInfo = this._isCpuOverloaded(sampleDurationMillis);
96
- const clientInfo = this._isClientOverloaded(sampleDurationMillis);
97
- return {
98
- isSystemIdle: !memInfo.isOverloaded &&
99
- !eventLoopInfo.isOverloaded &&
100
- !cpuInfo.isOverloaded &&
101
- !clientInfo.isOverloaded,
102
- memInfo,
103
- eventLoopInfo,
104
- cpuInfo,
105
- clientInfo,
111
+ isSystemIdle(sampleDurationMillis) {
112
+ const result = {
113
+ isSystemIdle: true,
114
+ memInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
115
+ eventLoopInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
116
+ cpuInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
117
+ storageBackendInfo: { isOverloaded: false, limitRatio: 0, actualRatio: 0 },
106
118
  };
107
- }
108
- /**
109
- * Returns an object with an isOverloaded property set to true
110
- * if the memory has been overloaded in the last sampleDurationMillis.
111
- */
112
- _isMemoryOverloaded(sampleDurationMillis) {
113
- const sample = this.snapshotter.getMemorySample(sampleDurationMillis);
114
- return this._isSampleOverloaded(sample, this.maxMemoryOverloadedRatio);
115
- }
116
- /**
117
- * Returns an object with an isOverloaded property set to true
118
- * if the event loop has been overloaded in the last sampleDurationMillis.
119
- */
120
- _isEventLoopOverloaded(sampleDurationMillis) {
121
- const sample = this.snapshotter.getEventLoopSample(sampleDurationMillis);
122
- return this._isSampleOverloaded(sample, this.maxEventLoopOverloadedRatio);
123
- }
124
- /**
125
- * Returns an object with an isOverloaded property set to true
126
- * if the CPU has been overloaded in the last sampleDurationMillis.
127
- */
128
- _isCpuOverloaded(sampleDurationMillis) {
129
- const sample = this.snapshotter.getCpuSample(sampleDurationMillis);
130
- return this._isSampleOverloaded(sample, this.maxCpuOverloadedRatio);
131
- }
132
- /**
133
- * Returns an object with an isOverloaded property set to true
134
- * if the client has been overloaded in the last sampleDurationMillis.
135
- */
136
- _isClientOverloaded(sampleDurationMillis) {
137
- const sample = this.snapshotter.getClientSample(sampleDurationMillis);
138
- return this._isSampleOverloaded(sample, this.maxClientOverloadedRatio);
139
- }
140
- /**
141
- * Returns an object with sample information and an isOverloaded property
142
- * set to true if at least the ratio of snapshots in the sample are overloaded.
143
- */
144
- _isSampleOverloaded(sample, ratio) {
145
- if (sample.length === 0) {
146
- return {
147
- isOverloaded: false,
148
- limitRatio: ratio,
149
- actualRatio: 0,
150
- };
119
+ let loadSignalInfo;
120
+ for (const signal of this.#signals) {
121
+ const sample = signal.getSample(sampleDurationMillis);
122
+ const info = evaluateLoadSignalSample(sample, signal.overloadedRatio);
123
+ if (info.isOverloaded) {
124
+ result.isSystemIdle = false;
125
+ }
126
+ if (BUILTIN_SIGNAL_NAMES.has(signal.name)) {
127
+ result[signal.name] = info;
128
+ }
129
+ else {
130
+ loadSignalInfo ??= {};
131
+ loadSignalInfo[signal.name] = info;
132
+ }
151
133
  }
152
- const weights = [];
153
- const values = [];
154
- for (let i = 1; i < sample.length; i++) {
155
- const previous = sample[i - 1];
156
- const current = sample[i];
157
- const weight = +current.createdAt - +previous.createdAt;
158
- weights.push(weight || 1); // Prevent errors from 0ms long intervals (sync) between snapshots.
159
- values.push(+current.isOverloaded);
134
+ if (loadSignalInfo) {
135
+ result.loadSignalInfo = loadSignalInfo;
160
136
  }
161
- const wAvg = sample.length === 1 ? +sample[0].isOverloaded : weightedAvg(values, weights);
162
- return {
163
- isOverloaded: wAvg > ratio,
164
- limitRatio: ratio,
165
- actualRatio: Math.round(wAvg * 1000) / 1000,
166
- };
137
+ return result;
167
138
  }
168
139
  }
169
- //# sourceMappingURL=system_status.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Computes a weighted average of an array of numbers, complemented by an array of weights.
3
+ * @ignore
4
+ */
5
+ export declare function weightedAvg(arrValues: number[], arrWeights: number[]): number;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Computes a weighted average of an array of numbers, complemented by an array of weights.
3
+ * @ignore
4
+ */
5
+ export function weightedAvg(arrValues, arrWeights) {
6
+ const result = arrValues
7
+ .map((value, i) => {
8
+ const weight = arrWeights[i];
9
+ const sum = value * weight;
10
+ return [sum, weight];
11
+ })
12
+ .reduce((p, c) => [p[0] + c[0], p[1] + c[1]], [0, 0]);
13
+ return result[0] / result[1];
14
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Checks if the given value is a Node.js Stream or a Web API ReadableStream.
3
+ * @ignore
4
+ */
5
+ export declare function isStream(value: unknown): value is NodeJS.ReadableStream | ReadableStream;
6
+ /**
7
+ * Checks if the given value is a Node.js Buffer, ArrayBuffer, or TypedArray.
8
+ * @ignore
9
+ */
10
+ export declare function isBuffer(value: unknown): value is Buffer | ArrayBuffer | ArrayBufferView;
11
+ /**
12
+ * Converts a byte-like value (Buffer, ArrayBuffer, or any typed-array / DataView) into a Buffer over
13
+ * the exact same bytes, honoring `byteOffset` / `byteLength` for views. Existing Buffers are returned
14
+ * as-is. Used by storage backends, which persist raw bytes regardless of the input's concrete shape.
15
+ * @ignore
16
+ */
17
+ export declare function toBuffer(value: Buffer | ArrayBuffer | ArrayBufferView): Buffer;
package/byte_utils.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Checks if the given value is a Node.js Stream or a Web API ReadableStream.
3
+ * @ignore
4
+ */
5
+ export function isStream(value) {
6
+ if (typeof value !== 'object' || value === null) {
7
+ return false;
8
+ }
9
+ // A Node.js Readable is both pipeable and async-iterable; a Web ReadableStream exposes pipeTo.
10
+ // Requiring async-iterability for the `pipe` branch rejects plain `{ pipe }` ducks that would
11
+ // otherwise blow up later in the storage backends' drain loop with a cryptic TypeError.
12
+ const isNodeStream = typeof value.pipe === 'function' && typeof value[Symbol.asyncIterator] === 'function';
13
+ const isWebStream = typeof value.pipeTo === 'function';
14
+ return isNodeStream || isWebStream;
15
+ }
16
+ /**
17
+ * Checks if the given value is a Node.js Buffer, ArrayBuffer, or TypedArray.
18
+ * @ignore
19
+ */
20
+ export function isBuffer(value) {
21
+ return (value != null &&
22
+ typeof value === 'object' &&
23
+ (Buffer.isBuffer(value) ||
24
+ value instanceof ArrayBuffer ||
25
+ ArrayBuffer.isView(value) ||
26
+ value.constructor?.name === 'Buffer'));
27
+ }
28
+ /**
29
+ * Converts a byte-like value (Buffer, ArrayBuffer, or any typed-array / DataView) into a Buffer over
30
+ * the exact same bytes, honoring `byteOffset` / `byteLength` for views. Existing Buffers are returned
31
+ * as-is. Used by storage backends, which persist raw bytes regardless of the input's concrete shape.
32
+ * @ignore
33
+ */
34
+ export function toBuffer(value) {
35
+ if (Buffer.isBuffer(value)) {
36
+ return value;
37
+ }
38
+ if (value instanceof ArrayBuffer) {
39
+ return Buffer.from(value);
40
+ }
41
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
42
+ }