@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
package/storages/utils.js CHANGED
@@ -1,21 +1,31 @@
1
1
  import crypto from 'node:crypto';
2
2
  import { Configuration } from '../configuration.js';
3
+ import { serviceLocator } from '../service_locator.js';
3
4
  import { KeyValueStore } from './key_value_store.js';
4
- export async function purgeDefaultStorages(configOrOptions, client) {
5
- const options = configOrOptions instanceof Configuration
5
+ export async function purgeDefaultStorages(configurationOrOptions, storageBackend) {
6
+ const options = configurationOrOptions instanceof Configuration
6
7
  ? {
7
- client,
8
- config: configOrOptions,
8
+ storageBackend,
9
+ configuration: configurationOrOptions,
9
10
  }
10
- : (configOrOptions ?? {});
11
- const { config = Configuration.getGlobalConfig(), onlyPurgeOnce = false } = options;
12
- ({ client = config.getStorageClient() } = options);
13
- const casted = client;
11
+ : (configurationOrOptions ?? {});
12
+ const { configuration = serviceLocator.getConfiguration(), onlyPurgeOnce = false } = options;
13
+ ({ storageBackend = serviceLocator.getStorageBackend() } = options);
14
+ const casted = storageBackend;
15
+ const runPurge = async () => {
16
+ try {
17
+ await casted.purge?.();
18
+ }
19
+ catch (e) {
20
+ casted.__purged = undefined;
21
+ throw e;
22
+ }
23
+ };
14
24
  // if `onlyPurgeOnce` is true, will purge anytime this function is called, otherwise - only on start
15
- if (!onlyPurgeOnce || (config.get('purgeOnStart') && !casted.__purged)) {
16
- casted.__purged = true;
17
- await casted.purge?.();
25
+ if (!onlyPurgeOnce || (configuration.purgeOnStart && !casted.__purged)) {
26
+ casted.__purged = runPurge();
18
27
  }
28
+ await casted.__purged;
19
29
  }
20
30
  /**
21
31
  * Easily create and manage state values. All state values are automatically persisted.
@@ -24,11 +34,11 @@ export async function purgeDefaultStorages(configOrOptions, client) {
24
34
  *
25
35
  * @param name The name of the store to use.
26
36
  * @param defaultValue If the store does not yet have a value in it, the value will be initialized with the `defaultValue` you provide.
27
- * @param options An optional object parameter where a custom `keyValueStoreName` and `config` can be passed in.
37
+ * @param options An optional object parameter where a custom `keyValueStoreName` and `configuration` can be passed in.
28
38
  */
29
39
  export async function useState(name, defaultValue = {}, options) {
30
- const kvStore = await KeyValueStore.open(options?.keyValueStoreName, {
31
- config: options?.config || Configuration.getGlobalConfig(),
40
+ const kvStore = await KeyValueStore.open(options?.keyValueStoreName ? { name: options.keyValueStoreName } : null, {
41
+ configuration: options?.configuration || serviceLocator.getConfiguration(),
32
42
  });
33
43
  return kvStore.getAutoSavedValue(name || 'CRAWLEE_GLOBAL_STATE', defaultValue);
34
44
  }
@@ -69,4 +79,54 @@ export const API_PROCESSED_REQUESTS_DELAY_MILLIS = 10_000;
69
79
  * @internal
70
80
  */
71
81
  export const MAX_QUERIES_FOR_CONSISTENCY = 6;
72
- //# sourceMappingURL=utils.js.map
82
+ /**
83
+ * Creates an object that is both an `AsyncIterable<TItem>` (for `for await...of`)
84
+ * and a `Promise<TItem[]>` (for `await`) from a single async page generator.
85
+ *
86
+ * - `await result` drains all pages from a fresh generator and returns every
87
+ * item as a flat array.
88
+ * - `for await (const item of result)` streams all items across all pages,
89
+ * yielding them one by one without buffering everything in memory.
90
+ *
91
+ * Each usage path creates its own generator instance, so `await` and
92
+ * `for await...of` never interfere with each other.
93
+ *
94
+ * @internal
95
+ */
96
+ export function createDualIterable(options) {
97
+ const { createPages, extractItems } = options;
98
+ let cached = null;
99
+ function getOrCreate() {
100
+ if (!cached) {
101
+ cached = (async () => {
102
+ const items = [];
103
+ for await (const page of createPages()) {
104
+ items.push(...extractItems(page));
105
+ }
106
+ return items;
107
+ })();
108
+ }
109
+ return cached;
110
+ }
111
+ async function* iterateAll() {
112
+ for await (const page of createPages()) {
113
+ yield* extractItems(page);
114
+ }
115
+ }
116
+ const result = {
117
+ [Symbol.asyncIterator]() {
118
+ return iterateAll();
119
+ },
120
+ then(onfulfilled, onrejected) {
121
+ return getOrCreate().then(onfulfilled, onrejected);
122
+ },
123
+ catch(onrejected) {
124
+ return getOrCreate().catch(onrejected);
125
+ },
126
+ finally(onfinally) {
127
+ return getOrCreate().finally(onfinally);
128
+ },
129
+ [Symbol.toStringTag]: 'DualIterable',
130
+ };
131
+ return result;
132
+ }
@@ -0,0 +1,67 @@
1
+ import type { CrawleeLogger } from '@crawlee/types';
2
+ /**
3
+ * Gets the "bare metal" cpu load.
4
+ * Used in
5
+ * - AWS Lambda
6
+ * - Containers without a cGroup quota
7
+ * - Uncontainerized environments
8
+ * @returns a number between 0 and 1 for the cpu load
9
+ * @internal
10
+ */
11
+ export declare function getCurrentCpuTicks(): number;
12
+ /**
13
+ * Reads the cgroup cpu quota.
14
+ * In V1, a quota of -1 means “unlimited.”
15
+ * In V2, a first field of "max" means unlimited.
16
+ * @param cgroupsVersion the cGroup version
17
+ * @returns The Cpu Quota
18
+ * @internal
19
+ */
20
+ export declare function getCpuQuota(cgroupsVersion: string): Promise<number | null>;
21
+ /**
22
+ * Reads the cgroup cpu period.
23
+ * @param cgroupsVersion the cGroup version
24
+ * @returns The Cpu quota period
25
+ * @internal
26
+ */
27
+ export declare function getCpuPeriod(cgroupsVersion: string): Promise<number>;
28
+ /**
29
+ * Reads the cgroup cpu usage of the container
30
+ *
31
+ * @param cgroupsVersion the cGroup version
32
+ * @returns the cpu usage
33
+ * @internal
34
+ */
35
+ export declare function getContainerCpuUsage(cgroupsVersion: string): Promise<number>;
36
+ /**
37
+ * Reads the cgroup cpu usage of the system from cgroup
38
+ *
39
+ * @returns the cpu usage
40
+ * @internal
41
+ */
42
+ export declare function getSystemCpuUsage(): Promise<number>;
43
+ /**
44
+ * a cpu sample with the container usage and system usage
45
+ */
46
+ export interface CpuSample {
47
+ containerUsage: number;
48
+ systemUsage: number;
49
+ }
50
+ /**
51
+ * Takes a CPU usage sample for both the container and the system.
52
+ *
53
+ * @returns An object containing the container and system CPU usage.
54
+ * @internal
55
+ */
56
+ export declare function sampleCpuUsage(cGroupsVersion: string): Promise<CpuSample>;
57
+ /**
58
+ * Gets the cpu usage of the system.
59
+ * If the crawler is running in a containerized environment, crawlee will check for a cgroup enforced cpu limit.
60
+ * If a cgroup limit is found, it will be taken as the maximum load against which the current load will be gauged.
61
+ * @returns a number between 0 and 1 for the cpu load
62
+ * @internal
63
+ */
64
+ export declare function getCurrentCpuTicksV2(options?: {
65
+ containerized?: boolean;
66
+ logger?: CrawleeLogger;
67
+ }): Promise<number>;
@@ -0,0 +1,216 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import { getCgroupsVersion } from './runtime.js';
5
+ const CPU_FILE_PATHS = {
6
+ STAT: {
7
+ V1: '/sys/fs/cgroup/cpuacct/cpuacct.usage',
8
+ V2: '/sys/fs/cgroup/cpu.stat',
9
+ },
10
+ QUOTA: {
11
+ V1: '/sys/fs/cgroup/cpu/cpu.cfs_quota_us',
12
+ V2: '/sys/fs/cgroup/cpu.max',
13
+ },
14
+ PERIOD: {
15
+ V1: '/sys/fs/cgroup/cpu/cpu.cfs_period_us',
16
+ V2: '/sys/fs/cgroup/cpu.max',
17
+ },
18
+ };
19
+ let CLOCK_TICKS_PER_SECOND = 100;
20
+ let CLOCK_TICKS_CHECKED = false;
21
+ const NANOSECONDS_PER_SECOND = 1e9;
22
+ const previousTicks = { idle: 0, total: 0 };
23
+ /**
24
+ * Gets the "bare metal" cpu load.
25
+ * Used in
26
+ * - AWS Lambda
27
+ * - Containers without a cGroup quota
28
+ * - Uncontainerized environments
29
+ * @returns a number between 0 and 1 for the cpu load
30
+ * @internal
31
+ */
32
+ export function getCurrentCpuTicks() {
33
+ const cpusCores = os.cpus();
34
+ const ticks = cpusCores.reduce((acc, cpu) => {
35
+ const cpuTimes = Object.values(cpu.times);
36
+ return {
37
+ idle: acc.idle + cpu.times.idle,
38
+ total: acc.total + cpuTimes.reduce((sum, num) => sum + num),
39
+ };
40
+ }, { idle: 0, total: 0 });
41
+ const idleTicksDelta = ticks.idle - previousTicks.idle;
42
+ const totalTicksDelta = ticks.total - previousTicks.total;
43
+ Object.assign(previousTicks, ticks);
44
+ return totalTicksDelta ? 1 - idleTicksDelta / totalTicksDelta : 0;
45
+ }
46
+ /**
47
+ * Reads the linux tick rate.
48
+ * @returns the number of ticks per second, or `null` if detection failed
49
+ */
50
+ function getClockTicks() {
51
+ try {
52
+ return parseInt(execSync('getconf CLK_TCK').toString().trim(), 10);
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ /**
59
+ * Reads the cgroup cpu quota.
60
+ * In V1, a quota of -1 means “unlimited.”
61
+ * In V2, a first field of "max" means unlimited.
62
+ * @param cgroupsVersion the cGroup version
63
+ * @returns The Cpu Quota
64
+ * @internal
65
+ */
66
+ export async function getCpuQuota(cgroupsVersion) {
67
+ if (cgroupsVersion === 'V1') {
68
+ const quotaStr = await readFile(CPU_FILE_PATHS.QUOTA.V1, 'utf8');
69
+ const quota = parseInt(quotaStr.trim(), 10);
70
+ return quota === -1 ? null : quota;
71
+ }
72
+ // cgroup v2
73
+ const maxStr = await readFile(CPU_FILE_PATHS.QUOTA.V2, 'utf8');
74
+ const parts = maxStr.trim().split(/\s+/);
75
+ if (parts[0] === 'max') {
76
+ return null;
77
+ }
78
+ return parseInt(parts[0], 10);
79
+ }
80
+ /**
81
+ * Reads the cgroup cpu period.
82
+ * @param cgroupsVersion the cGroup version
83
+ * @returns The Cpu quota period
84
+ * @internal
85
+ */
86
+ export async function getCpuPeriod(cgroupsVersion) {
87
+ if (cgroupsVersion === 'V1') {
88
+ const quotaStr = await readFile(CPU_FILE_PATHS.PERIOD.V1, 'utf8');
89
+ const quota = parseInt(quotaStr.trim(), 10);
90
+ return quota;
91
+ }
92
+ // cgroup v2
93
+ const maxStr = await readFile(CPU_FILE_PATHS.PERIOD.V2, 'utf8');
94
+ const parts = maxStr.trim().split(/\s+/);
95
+ return parseInt(parts[1], 10);
96
+ }
97
+ /**
98
+ * Reads the cgroup cpu usage of the container
99
+ *
100
+ * @param cgroupsVersion the cGroup version
101
+ * @returns the cpu usage
102
+ * @internal
103
+ */
104
+ export async function getContainerCpuUsage(cgroupsVersion) {
105
+ if (cgroupsVersion === 'V1') {
106
+ const data = await readFile(CPU_FILE_PATHS.STAT.V1, 'utf8');
107
+ return Number(data.trim());
108
+ }
109
+ // cgroup v2
110
+ const data = await readFile(CPU_FILE_PATHS.STAT.V2, 'utf8');
111
+ const lines = data.split('\n');
112
+ let usageUsec = 0;
113
+ for (const line of lines) {
114
+ const parts = line.trim().split(/\s+/);
115
+ if (parts[0] === 'usage_usec') {
116
+ usageUsec = Number(parts[1]);
117
+ break;
118
+ }
119
+ }
120
+ // Convert microseconds to nanoseconds.
121
+ return usageUsec * 1000;
122
+ }
123
+ /**
124
+ * Reads the cgroup cpu usage of the system from cgroup
125
+ *
126
+ * @returns the cpu usage
127
+ * @internal
128
+ */
129
+ export async function getSystemCpuUsage() {
130
+ const statData = await readFile('/proc/stat', 'utf8');
131
+ const lines = statData.split('\n');
132
+ for (const line of lines) {
133
+ if (line.startsWith('cpu ')) {
134
+ // Split the line and extract the first seven numeric fields:
135
+ // user, nice, system, idle, iowait, irq, softirq
136
+ const parts = line.split(/\s+/).slice(1, 8);
137
+ let totalTicks = 0;
138
+ for (const part of parts) {
139
+ totalTicks += Number(part);
140
+ }
141
+ // Convert clock ticks to nanoseconds.
142
+ return (totalTicks * NANOSECONDS_PER_SECOND) / CLOCK_TICKS_PER_SECOND;
143
+ }
144
+ }
145
+ throw new Error('no cpu line'); // shouldnt ever happen
146
+ }
147
+ /**
148
+ * Takes a CPU usage sample for both the container and the system.
149
+ *
150
+ * @returns An object containing the container and system CPU usage.
151
+ * @internal
152
+ */
153
+ export async function sampleCpuUsage(cGroupsVersion) {
154
+ const [containerUsage, systemUsage] = await Promise.all([
155
+ getContainerCpuUsage(cGroupsVersion),
156
+ getSystemCpuUsage(),
157
+ ]);
158
+ return { containerUsage, systemUsage };
159
+ }
160
+ let previousSample = { containerUsage: 0, systemUsage: 0 };
161
+ /**
162
+ * Gets the cpu usage of the system.
163
+ * If the crawler is running in a containerized environment, crawlee will check for a cgroup enforced cpu limit.
164
+ * If a cgroup limit is found, it will be taken as the maximum load against which the current load will be gauged.
165
+ * @returns a number between 0 and 1 for the cpu load
166
+ * @internal
167
+ */
168
+ export async function getCurrentCpuTicksV2(options = {}) {
169
+ const { containerized = false, logger } = options;
170
+ try {
171
+ // if not containerized
172
+ if (!containerized) {
173
+ // bare metal cpu limit
174
+ return getCurrentCpuTicks();
175
+ }
176
+ if (!CLOCK_TICKS_CHECKED) {
177
+ const ticks = getClockTicks();
178
+ if (ticks === null) {
179
+ CLOCK_TICKS_PER_SECOND = 100;
180
+ logger?.warning('Failed to get clock ticks; defaulting to 100. CPU metrics may be inaccurate.');
181
+ }
182
+ else {
183
+ CLOCK_TICKS_PER_SECOND = ticks;
184
+ }
185
+ CLOCK_TICKS_CHECKED = true;
186
+ }
187
+ const cgroupsVersion = await getCgroupsVersion();
188
+ // if cgroup is not detected, return bare metal cpu limit
189
+ if (cgroupsVersion === null) {
190
+ logger?.warningOnce('Your environment is containerized, but your system does not support cgroups.\n' +
191
+ "If you're running containers with limited cpu, cpu auto-scaling will not work properly.");
192
+ return getCurrentCpuTicks();
193
+ }
194
+ // cgroup aware cpu limit. If no limits are set, default to returning getCurrentCpuTicks.
195
+ const quota = await getCpuQuota(cgroupsVersion);
196
+ if (quota === null) {
197
+ // no cgroup limit, return host cpu load
198
+ return getCurrentCpuTicks();
199
+ }
200
+ const period = await getCpuPeriod(cgroupsVersion);
201
+ // eg. having a 200000us quots per 100000us means the cGroup can fully use 2 cores
202
+ const cpuAllowance = quota / period;
203
+ const sample = await sampleCpuUsage(cgroupsVersion);
204
+ const containerDelta = sample.containerUsage - previousSample.containerUsage;
205
+ const systemDelta = sample.systemUsage - previousSample.systemUsage;
206
+ previousSample = sample;
207
+ const numCpus = os.cpus().length;
208
+ // Calculate the CPU usage percentage.
209
+ return ((containerDelta / systemDelta) * numCpus) / cpuAllowance;
210
+ }
211
+ catch (err) {
212
+ // if anything fails, default to bare metal metrics
213
+ logger?.warning('Cpu snapshot failed, falling back to bare-metal metrics.', { error: err });
214
+ return getCurrentCpuTicks();
215
+ }
216
+ }
@@ -0,0 +1,31 @@
1
+ import type { CrawleeLogger } from '@crawlee/types';
2
+ /**
3
+ * Describes memory usage of the process.
4
+ */
5
+ export interface MemoryInfo {
6
+ /** Total memory available in the system or container */
7
+ totalBytes: number;
8
+ /** Amount of free memory in the system or container */
9
+ freeBytes: number;
10
+ /** Amount of memory used (= totalBytes - freeBytes) */
11
+ usedBytes: number;
12
+ /** Amount of memory used the current Node.js process */
13
+ mainProcessBytes: number;
14
+ /** Amount of memory used by child processes of the current Node.js process */
15
+ childProcessesBytes: number;
16
+ }
17
+ /**
18
+ * Returns memory statistics of the process and the system, see {@link MemoryInfo}.
19
+ *
20
+ * If the process runs inside of a container, the `getMemoryInfo` gets container memory limits,
21
+ * otherwise it gets system memory limits.
22
+ *
23
+ * Beware that the function is quite inefficient because it spawns a new process.
24
+ * Therefore you shouldn't call it too often, like more than once per second.
25
+ * @returns An object containing the free and used memory metrics.
26
+ * @internal
27
+ */
28
+ export declare function getMemoryInfo(options?: {
29
+ containerized?: boolean;
30
+ logger?: CrawleeLogger;
31
+ }): Promise<MemoryInfo>;
@@ -0,0 +1,115 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { freemem, totalmem } from 'node:os';
4
+ import { getCgroupsVersion, isLambda } from './runtime.js';
5
+ import { psTree } from './ps-tree.js';
6
+ const MEMORY_FILE_PATHS = {
7
+ TOTAL: {
8
+ V1: '/sys/fs/cgroup/memory/memory.limit_in_bytes',
9
+ V2: '/sys/fs/cgroup/memory.max',
10
+ },
11
+ USED: {
12
+ V1: '/sys/fs/cgroup/memory/memory.usage_in_bytes',
13
+ V2: '/sys/fs/cgroup/memory.current',
14
+ },
15
+ };
16
+ /**
17
+ * Returns memory statistics of the process and the system, see {@link MemoryInfo}.
18
+ *
19
+ * If the process runs inside of a container, the `getMemoryInfo` gets container memory limits,
20
+ * otherwise it gets system memory limits.
21
+ *
22
+ * Beware that the function is quite inefficient because it spawns a new process.
23
+ * Therefore you shouldn't call it too often, like more than once per second.
24
+ * @returns An object containing the free and used memory metrics.
25
+ * @internal
26
+ */
27
+ export async function getMemoryInfo(options = {}) {
28
+ const { containerized = false, logger } = options;
29
+ let mainProcessBytes = -1;
30
+ let childProcessesBytes = 0;
31
+ // lambda does *not* have `ps` and other command line tools
32
+ // required to extract memory usage.
33
+ if (isLambda()) {
34
+ // reported in bytes
35
+ mainProcessBytes = process.memoryUsage().rss;
36
+ // https://stackoverflow.com/a/55914335/129415
37
+ const memInfo = execSync('cat /proc/meminfo').toString();
38
+ const values = memInfo.split(/[\n: ]/).filter((val) => val.trim());
39
+ // /proc/meminfo reports in kb, not bytes, the total used memory is reported by meminfo
40
+ // subtract memory used by the main node process in order to infer memory used by any child processes
41
+ childProcessesBytes = +values[19] * 1000 - mainProcessBytes;
42
+ }
43
+ else {
44
+ // Query both root and child processes
45
+ const processes = await psTree(process.pid, true);
46
+ processes.forEach((rec) => {
47
+ // Obtain main process' memory separately
48
+ if (rec.PID === `${process.pid}`) {
49
+ mainProcessBytes = rec.RSS;
50
+ return;
51
+ }
52
+ childProcessesBytes += rec.RSS;
53
+ });
54
+ }
55
+ let totalBytes;
56
+ let usedBytes;
57
+ let freeBytes;
58
+ if (isLambda()) {
59
+ // memory size is defined in megabytes
60
+ totalBytes = parseInt(process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE, 10) * 1000000;
61
+ usedBytes = mainProcessBytes + childProcessesBytes;
62
+ freeBytes = totalBytes - usedBytes;
63
+ }
64
+ else if (containerized) {
65
+ // When running inside a container, use container memory limits
66
+ const cgroupsVersion = await getCgroupsVersion();
67
+ try {
68
+ if (cgroupsVersion === null) {
69
+ throw new Error('cgroup not available');
70
+ }
71
+ let [totalBytesStr, usedBytesStr] = await Promise.all([
72
+ readFile(MEMORY_FILE_PATHS.TOTAL[cgroupsVersion], 'utf8'),
73
+ readFile(MEMORY_FILE_PATHS.USED[cgroupsVersion], 'utf8'),
74
+ ]);
75
+ // Cgroups V2 files contains newline character. Getting rid of it for better handling in later part of the code.
76
+ totalBytesStr = totalBytesStr.replace(/[^a-zA-Z0-9 ]/g, '');
77
+ usedBytesStr = usedBytesStr.replace(/[^a-zA-Z0-9 ]/g, '');
78
+ // Cgroups V2 contains 'max' string if memory is not limited
79
+ // See https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git/tree/Documentation/admin-guide/cgroup-v2.rst (see "memory.max")
80
+ if (totalBytesStr === 'max') {
81
+ totalBytes = totalmem();
82
+ // Cgroups V1 is set to number related to platform and page size if memory is not limited
83
+ // See https://unix.stackexchange.com/q/420906
84
+ }
85
+ else {
86
+ totalBytes = parseInt(totalBytesStr, 10);
87
+ const containerRunsWithUnlimitedMemory = totalBytes > Number.MAX_SAFE_INTEGER;
88
+ if (containerRunsWithUnlimitedMemory)
89
+ totalBytes = totalmem();
90
+ }
91
+ usedBytes = parseInt(usedBytesStr, 10);
92
+ freeBytes = totalBytes - usedBytes;
93
+ }
94
+ catch (err) {
95
+ logger?.warningOnce('Your environment is containerized, but your system does not support memory cgroups. ' +
96
+ "If you're running containers with limited memory, memory auto-scaling will not work properly.\n\n" +
97
+ `Cause: ${err.message}`);
98
+ totalBytes = totalmem();
99
+ freeBytes = freemem();
100
+ usedBytes = totalBytes - freeBytes;
101
+ }
102
+ }
103
+ else {
104
+ totalBytes = totalmem();
105
+ freeBytes = freemem();
106
+ usedBytes = totalBytes - freeBytes;
107
+ }
108
+ return {
109
+ totalBytes,
110
+ freeBytes,
111
+ usedBytes,
112
+ mainProcessBytes,
113
+ childProcessesBytes,
114
+ };
115
+ }
@@ -0,0 +1,17 @@
1
+ export interface ProcessInfo {
2
+ PPID: string;
3
+ PID: string;
4
+ STAT: string | null;
5
+ RSS: number;
6
+ COMMAND: string;
7
+ }
8
+ /**
9
+ * Returns a promise that resolves with an array of ProcessInfo objects representing
10
+ * the children of the given PID.
11
+ *
12
+ * @param pid - The PID (number or string) for which to list child processes.
13
+ * @param includeRoot - Optional flag. When true, include the process with the given PID if found.
14
+ * Defaults to false.
15
+ * @internal
16
+ */
17
+ export declare function psTree(pid: number | string, includeRoot?: boolean): Promise<ProcessInfo[]>;