@atlaspack/core 2.16.2-dev.14 → 2.16.2-dev.55

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 (37) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/lib/Atlaspack.js +10 -2
  3. package/lib/AtlaspackConfig.schema.js +7 -1
  4. package/lib/BundleGraph.js +2 -100
  5. package/lib/PackagerRunner.js +44 -9
  6. package/lib/RequestTracker.js +326 -121
  7. package/lib/Transformation.js +2 -2
  8. package/lib/UncommittedAsset.js +17 -0
  9. package/lib/atlaspack-v3/worker/compat/environment.js +2 -2
  10. package/lib/atlaspack-v3/worker/compat/mutable-asset.js +6 -6
  11. package/lib/atlaspack-v3/worker/compat/plugin-config.js +5 -5
  12. package/lib/atlaspack-v3/worker/index.js +3 -0
  13. package/lib/atlaspack-v3/worker/worker.js +8 -0
  14. package/lib/dumpGraphToGraphViz.js +1 -1
  15. package/lib/public/BundleGraph.js +21 -8
  16. package/lib/public/Config.js +28 -0
  17. package/lib/requests/AssetGraphRequest.js +13 -1
  18. package/lib/requests/BundleGraphRequest.js +13 -1
  19. package/lib/requests/WriteBundleRequest.js +11 -2
  20. package/lib/resolveOptions.js +7 -4
  21. package/lib/worker.js +18 -1
  22. package/package.json +23 -19
  23. package/src/Atlaspack.js +13 -5
  24. package/src/BundleGraph.js +0 -167
  25. package/src/PackagerRunner.js +60 -9
  26. package/src/RequestTracker.js +491 -137
  27. package/src/UncommittedAsset.js +16 -1
  28. package/src/atlaspack-v3/worker/compat/plugin-config.js +9 -5
  29. package/src/atlaspack-v3/worker/worker.js +7 -0
  30. package/src/public/BundleGraph.js +22 -15
  31. package/src/public/Config.js +39 -5
  32. package/src/requests/AssetGraphRequest.js +13 -3
  33. package/src/requests/BundleGraphRequest.js +13 -3
  34. package/src/requests/WriteBundleRequest.js +9 -2
  35. package/src/resolveOptions.js +4 -2
  36. package/test/RequestTracker.test.js +120 -5
  37. package/test/test-utils.js +1 -7
@@ -69,6 +69,7 @@ import type {
69
69
  InternalGlob,
70
70
  } from './types';
71
71
  import {BuildAbortError, assertSignalNotAborted, hashFromOption} from './utils';
72
+ import {performance} from 'perf_hooks';
72
73
 
73
74
  export const requestGraphEdgeTypes = {
74
75
  subrequest: 2,
@@ -446,6 +447,11 @@ export class RequestGraph extends ContentGraph<
446
447
  this.removeCachedRequestChunkForNode(nodeId);
447
448
  }
448
449
 
450
+ /**
451
+ * Nodes that are invalidated on start-up, such as JavaScript babel configuration files which are
452
+ * imported when the build kicks-off and might doing arbitrary work such as reading from the file
453
+ * system.
454
+ */
449
455
  invalidateUnpredictableNodes() {
450
456
  for (let nodeId of this.unpredicatableNodeIds) {
451
457
  let node = nullthrows(this.getNode(nodeId));
@@ -454,6 +460,9 @@ export class RequestGraph extends ContentGraph<
454
460
  }
455
461
  }
456
462
 
463
+ /**
464
+ * Effectively uncacheable nodes.
465
+ */
457
466
  invalidateOnBuildNodes() {
458
467
  for (let nodeId of this.invalidateOnBuildNodeIds) {
459
468
  let node = nullthrows(this.getNode(nodeId));
@@ -462,11 +471,20 @@ export class RequestGraph extends ContentGraph<
462
471
  }
463
472
  }
464
473
 
465
- invalidateEnvNodes(env: EnvMap) {
474
+ /**
475
+ * Nodes invalidated by environment changes, corresponds to `env: ...` inputs.
476
+ */
477
+ invalidateEnvNodes(env: EnvMap): string[] {
478
+ const invalidatedKeys = [];
479
+
466
480
  for (let nodeId of this.envNodeIds) {
467
481
  let node = nullthrows(this.getNode(nodeId));
468
482
  invariant(node.type === ENV);
469
- if (env[keyFromEnvContentKey(node.id)] !== node.value) {
483
+
484
+ const key = keyFromEnvContentKey(node.id);
485
+ if (env[key] !== node.value) {
486
+ invalidatedKeys.push(key);
487
+
470
488
  let parentNodes = this.getNodeIdsConnectedTo(
471
489
  nodeId,
472
490
  requestGraphEdgeTypes.invalidated_by_update,
@@ -476,15 +494,23 @@ export class RequestGraph extends ContentGraph<
476
494
  }
477
495
  }
478
496
  }
497
+
498
+ return invalidatedKeys;
479
499
  }
480
500
 
481
- invalidateOptionNodes(options: AtlaspackOptions) {
501
+ /**
502
+ * Nodes invalidated by option changes.
503
+ */
504
+ invalidateOptionNodes(options: AtlaspackOptions): string[] {
505
+ const invalidatedKeys = [];
506
+
482
507
  for (let nodeId of this.optionNodeIds) {
483
508
  let node = nullthrows(this.getNode(nodeId));
484
509
  invariant(node.type === OPTION);
485
- if (
486
- hashFromOption(options[keyFromOptionContentKey(node.id)]) !== node.hash
487
- ) {
510
+ const key = keyFromOptionContentKey(node.id);
511
+
512
+ if (hashFromOption(options[key]) !== node.hash) {
513
+ invalidatedKeys.push(key);
488
514
  let parentNodes = this.getNodeIdsConnectedTo(
489
515
  nodeId,
490
516
  requestGraphEdgeTypes.invalidated_by_update,
@@ -494,6 +520,8 @@ export class RequestGraph extends ContentGraph<
494
520
  }
495
521
  }
496
522
  }
523
+
524
+ return invalidatedKeys;
497
525
  }
498
526
 
499
527
  invalidateOnConfigKeyChange(
@@ -907,7 +935,10 @@ export class RequestGraph extends ContentGraph<
907
935
  * True if this is the start-up (loading phase) invalidation.
908
936
  */
909
937
  isInitialBuild: boolean = false,
910
- ): Async<boolean> {
938
+ ): Promise<{|
939
+ didInvalidate: boolean,
940
+ invalidationsByPath: Map<string, number>,
941
+ |}> {
911
942
  let didInvalidate = false;
912
943
  let count = 0;
913
944
  let predictedTime = 0;
@@ -945,7 +976,10 @@ export class RequestGraph extends ContentGraph<
945
976
  return above;
946
977
  };
947
978
 
979
+ const invalidationsByPath = new Map();
948
980
  for (let {path: _path, type} of events) {
981
+ const invalidationsBefore = this.getInvalidNodeCount();
982
+
949
983
  if (
950
984
  !enableOptimization &&
951
985
  process.env.ATLASPACK_DISABLE_CACHE_TIMEOUT !== 'true' &&
@@ -991,7 +1025,10 @@ export class RequestGraph extends ContentGraph<
991
1025
  this.invalidNodeIds.add(id);
992
1026
  }
993
1027
  }
994
- return true;
1028
+ return {
1029
+ didInvalidate: true,
1030
+ invalidationsByPath: new Map(),
1031
+ };
995
1032
  }
996
1033
 
997
1034
  // sometimes mac os reports update events as create events.
@@ -1104,6 +1141,13 @@ export class RequestGraph extends ContentGraph<
1104
1141
  }
1105
1142
  }
1106
1143
  }
1144
+
1145
+ const invalidationsAfter = this.getInvalidNodeCount();
1146
+ const invalidationsForEvent = invalidationsAfter - invalidationsBefore;
1147
+ invalidationsByPath.set(
1148
+ _path,
1149
+ (invalidationsByPath.get(_path) ?? 0) + invalidationsForEvent,
1150
+ );
1107
1151
  }
1108
1152
 
1109
1153
  if (getFeatureFlag('fixQuadraticCacheInvalidation')) {
@@ -1124,7 +1168,10 @@ export class RequestGraph extends ContentGraph<
1124
1168
  },
1125
1169
  });
1126
1170
 
1127
- return didInvalidate && this.invalidNodeIds.size > 0;
1171
+ return {
1172
+ didInvalidate,
1173
+ invalidationsByPath,
1174
+ };
1128
1175
  }
1129
1176
 
1130
1177
  hasCachedRequestChunk(index: number): boolean {
@@ -1138,6 +1185,13 @@ export class RequestGraph extends ContentGraph<
1138
1185
  removeCachedRequestChunkForNode(nodeId: number): void {
1139
1186
  this.cachedRequestChunks.delete(Math.floor(nodeId / this.nodesPerBlob));
1140
1187
  }
1188
+
1189
+ /**
1190
+ * Returns the number of invalidated nodes in the graph.
1191
+ */
1192
+ getInvalidNodeCount(): number {
1193
+ return this.invalidNodeIds.size;
1194
+ }
1141
1195
  }
1142
1196
 
1143
1197
  export default class RequestTracker {
@@ -1261,7 +1315,13 @@ export default class RequestTracker {
1261
1315
  }
1262
1316
  }
1263
1317
 
1264
- respondToFSEvents(events: Array<Event>, threshold: number): Async<boolean> {
1318
+ respondToFSEvents(
1319
+ events: Array<Event>,
1320
+ threshold: number,
1321
+ ): Promise<{|
1322
+ didInvalidate: boolean,
1323
+ invalidationsByPath: Map<string, number>,
1324
+ |}> {
1265
1325
  return this.graph.respondToFSEvents(events, this.options, threshold);
1266
1326
  }
1267
1327
 
@@ -1472,130 +1532,139 @@ export default class RequestTracker {
1472
1532
  }
1473
1533
  }
1474
1534
 
1475
- await runCacheImprovements(
1476
- async (cache) => {
1477
- await cache.getNativeRef().startWriteTransaction();
1478
- },
1479
- () => Promise.resolve(),
1480
- );
1481
-
1482
1535
  let cacheKey = getCacheKey(this.options);
1483
- let requestGraphKey = `requestGraph-${cacheKey}`;
1484
- let snapshotKey = `snapshot-${cacheKey}`;
1536
+ let requestGraphKey = getFeatureFlag('cachePerformanceImprovements')
1537
+ ? `${cacheKey}/RequestGraph`
1538
+ : `requestGraph-${cacheKey}`;
1539
+ let snapshotKey = getFeatureFlag('cachePerformanceImprovements')
1540
+ ? `${cacheKey}/snapshot`
1541
+ : `snapshot-${cacheKey}`;
1485
1542
 
1486
1543
  if (this.options.shouldDisableCache) {
1487
1544
  return;
1488
1545
  }
1489
1546
 
1490
1547
  let total = 0;
1491
- report({
1492
- type: 'cache',
1493
- phase: 'start',
1494
- total,
1495
- size: this.graph.nodes.length,
1496
- });
1548
+ await runCacheImprovements(
1549
+ async (cache) => {
1550
+ await cache.getNativeRef().startWriteTransaction();
1551
+ },
1552
+ () => Promise.resolve(),
1553
+ );
1554
+ try {
1555
+ report({
1556
+ type: 'cache',
1557
+ phase: 'start',
1558
+ total,
1559
+ size: this.graph.nodes.length,
1560
+ });
1497
1561
 
1498
- let serialisedGraph = this.graph.serialize();
1562
+ let serialisedGraph = this.graph.serialize();
1499
1563
 
1500
- // Delete an existing request graph cache, to prevent invalid states
1501
- await this.options.cache.deleteLargeBlob(requestGraphKey);
1564
+ // Delete an existing request graph cache, to prevent invalid states
1565
+ await this.options.cache.deleteLargeBlob(requestGraphKey);
1502
1566
 
1503
- const serialiseAndSet = async (
1504
- key: string,
1505
- // $FlowFixMe serialise input is any type
1506
- contents: any,
1507
- ): Promise<void> => {
1508
- if (signal?.aborted) {
1509
- throw new Error('Serialization was aborted');
1510
- }
1567
+ const serialiseAndSet = async (
1568
+ key: string,
1569
+ // $FlowFixMe serialise input is any type
1570
+ contents: any,
1571
+ ): Promise<void> => {
1572
+ if (signal?.aborted) {
1573
+ throw new Error('Serialization was aborted');
1574
+ }
1511
1575
 
1512
- await runCacheImprovements(
1513
- (cache) => {
1514
- instrument(`cache.put(${key})`, () => {
1515
- cache.getNativeRef().putNoConfirm(key, serialize(contents));
1516
- });
1517
- return Promise.resolve();
1518
- },
1519
- async () => {
1520
- await this.options.cache.setLargeBlob(
1521
- key,
1522
- serialize(contents),
1523
- signal
1524
- ? {
1525
- signal: signal,
1526
- }
1527
- : undefined,
1528
- );
1529
- },
1530
- );
1576
+ await runCacheImprovements(
1577
+ (cache) => {
1578
+ instrument(
1579
+ `RequestTracker::writeToCache::cache.put(${key})`,
1580
+ () => {
1581
+ cache.getNativeRef().putNoConfirm(key, serialize(contents));
1582
+ },
1583
+ );
1584
+ return Promise.resolve();
1585
+ },
1586
+ async () => {
1587
+ await this.options.cache.setLargeBlob(
1588
+ key,
1589
+ serialize(contents),
1590
+ signal
1591
+ ? {
1592
+ signal: signal,
1593
+ }
1594
+ : undefined,
1595
+ );
1596
+ },
1597
+ );
1531
1598
 
1532
- total += 1;
1599
+ total += 1;
1533
1600
 
1534
- report({
1535
- type: 'cache',
1536
- phase: 'write',
1537
- total,
1538
- size: this.graph.nodes.length,
1539
- });
1540
- };
1601
+ report({
1602
+ type: 'cache',
1603
+ phase: 'write',
1604
+ total,
1605
+ size: this.graph.nodes.length,
1606
+ });
1607
+ };
1541
1608
 
1542
- let queue = new PromiseQueue({
1543
- maxConcurrent: 32,
1544
- });
1609
+ let queue = new PromiseQueue({
1610
+ maxConcurrent: 32,
1611
+ });
1545
1612
 
1546
- // Preallocating a sparse array is faster than pushing when N is high enough
1547
- let cacheableNodes = new Array(serialisedGraph.nodes.length);
1548
- for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
1549
- let node = serialisedGraph.nodes[i];
1613
+ // Preallocating a sparse array is faster than pushing when N is high enough
1614
+ let cacheableNodes = new Array(serialisedGraph.nodes.length);
1615
+ for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
1616
+ let node = serialisedGraph.nodes[i];
1550
1617
 
1551
- let resultCacheKey = node?.resultCacheKey;
1552
- if (
1553
- node?.type === REQUEST &&
1554
- resultCacheKey != null &&
1555
- node?.result != null
1556
- ) {
1557
- queue.add(() => serialiseAndSet(resultCacheKey, node.result));
1618
+ let resultCacheKey = node?.resultCacheKey;
1619
+ if (
1620
+ node?.type === REQUEST &&
1621
+ resultCacheKey != null &&
1622
+ node?.result != null
1623
+ ) {
1624
+ queue.add(() => serialiseAndSet(resultCacheKey, node.result));
1558
1625
 
1559
- // eslint-disable-next-line no-unused-vars
1560
- let {result: _, ...newNode} = node;
1561
- cacheableNodes[i] = newNode;
1562
- } else {
1563
- cacheableNodes[i] = node;
1626
+ // eslint-disable-next-line no-unused-vars
1627
+ let {result: _, ...newNode} = node;
1628
+ cacheableNodes[i] = newNode;
1629
+ } else {
1630
+ cacheableNodes[i] = node;
1631
+ }
1564
1632
  }
1565
- }
1566
1633
 
1567
- let nodeCountsPerBlob = [];
1634
+ let nodeCountsPerBlob = [];
1568
1635
 
1569
- for (
1570
- let i = 0;
1571
- i * this.graph.nodesPerBlob < cacheableNodes.length;
1572
- i += 1
1573
- ) {
1574
- let nodesStartIndex = i * this.graph.nodesPerBlob;
1575
- let nodesEndIndex = Math.min(
1576
- (i + 1) * this.graph.nodesPerBlob,
1577
- cacheableNodes.length,
1578
- );
1636
+ for (
1637
+ let i = 0;
1638
+ i * this.graph.nodesPerBlob < cacheableNodes.length;
1639
+ i += 1
1640
+ ) {
1641
+ let nodesStartIndex = i * this.graph.nodesPerBlob;
1642
+ let nodesEndIndex = Math.min(
1643
+ (i + 1) * this.graph.nodesPerBlob,
1644
+ cacheableNodes.length,
1645
+ );
1579
1646
 
1580
- nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
1647
+ nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
1581
1648
 
1582
- if (!this.graph.hasCachedRequestChunk(i)) {
1583
- // We assume the request graph nodes are immutable and won't change
1584
- let nodesToCache = cacheableNodes.slice(nodesStartIndex, nodesEndIndex);
1649
+ if (!this.graph.hasCachedRequestChunk(i)) {
1650
+ // We assume the request graph nodes are immutable and won't change
1651
+ let nodesToCache = cacheableNodes.slice(
1652
+ nodesStartIndex,
1653
+ nodesEndIndex,
1654
+ );
1585
1655
 
1586
- queue.add(() =>
1587
- serialiseAndSet(
1588
- getRequestGraphNodeKey(i, cacheKey),
1589
- nodesToCache,
1590
- ).then(() => {
1591
- // Succeeded in writing to disk, save that we have completed this chunk
1592
- this.graph.setCachedRequestChunk(i);
1593
- }),
1594
- );
1656
+ queue.add(() =>
1657
+ serialiseAndSet(
1658
+ getRequestGraphNodeKey(i, cacheKey),
1659
+ nodesToCache,
1660
+ ).then(() => {
1661
+ // Succeeded in writing to disk, save that we have completed this chunk
1662
+ this.graph.setCachedRequestChunk(i);
1663
+ }),
1664
+ );
1665
+ }
1595
1666
  }
1596
- }
1597
1667
 
1598
- try {
1599
1668
  await queue.run();
1600
1669
 
1601
1670
  // Set the request graph after the queue is flushed to avoid writing an invalid state
@@ -1607,7 +1676,7 @@ export default class RequestTracker {
1607
1676
 
1608
1677
  await runCacheImprovements(
1609
1678
  () =>
1610
- serialiseAndSet(`request_tracker:cache_metadata:${cacheKey}`, {
1679
+ serialiseAndSet(`${cacheKey}/cache_metadata`, {
1611
1680
  version: ATLASPACK_VERSION,
1612
1681
  entries: this.options.entries,
1613
1682
  mode: this.options.mode,
@@ -1628,15 +1697,15 @@ export default class RequestTracker {
1628
1697
  } catch (err) {
1629
1698
  // If we have aborted, ignore the error and continue
1630
1699
  if (!signal?.aborted) throw err;
1700
+ } finally {
1701
+ await runCacheImprovements(
1702
+ async (cache) => {
1703
+ await cache.getNativeRef().commitWriteTransaction();
1704
+ },
1705
+ () => Promise.resolve(),
1706
+ );
1631
1707
  }
1632
1708
 
1633
- await runCacheImprovements(
1634
- async (cache) => {
1635
- await cache.getNativeRef().commitWriteTransaction();
1636
- },
1637
- () => Promise.resolve(),
1638
- );
1639
-
1640
1709
  report({type: 'cache', phase: 'end', total, size: this.graph.nodes.length});
1641
1710
  }
1642
1711
 
@@ -1668,6 +1737,18 @@ export function getWatcherOptions({
1668
1737
  }
1669
1738
 
1670
1739
  function getCacheKey(options) {
1740
+ if (getFeatureFlag('cachePerformanceImprovements')) {
1741
+ const hash = hashString(
1742
+ `${ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${
1743
+ options.mode
1744
+ }:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${
1745
+ options.watchBackend ?? ''
1746
+ }`,
1747
+ );
1748
+
1749
+ return `RequestTracker/${ATLASPACK_VERSION}/${hash}`;
1750
+ }
1751
+
1671
1752
  return hashString(
1672
1753
  `${ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${
1673
1754
  options.shouldBuildLazily ? 'lazy' : 'eager'
@@ -1676,6 +1757,10 @@ function getCacheKey(options) {
1676
1757
  }
1677
1758
 
1678
1759
  function getRequestGraphNodeKey(index: number, cacheKey: string) {
1760
+ if (getFeatureFlag('cachePerformanceImprovements')) {
1761
+ return `${cacheKey}/RequestGraph/nodes/${index}`;
1762
+ }
1763
+
1679
1764
  return `requestGraph-nodes-${index}-${cacheKey}`;
1680
1765
  }
1681
1766
 
@@ -1687,9 +1772,15 @@ export async function readAndDeserializeRequestGraph(
1687
1772
  let bufferLength = 0;
1688
1773
 
1689
1774
  const getAndDeserialize = async (key: string) => {
1690
- let buffer = await cache.getLargeBlob(key);
1691
- bufferLength += Buffer.byteLength(buffer);
1692
- return deserialize(buffer);
1775
+ if (getFeatureFlag('cachePerformanceImprovements')) {
1776
+ const buffer = await cache.getBlob(key);
1777
+ bufferLength += Buffer.byteLength(buffer);
1778
+ return deserialize(buffer);
1779
+ } else {
1780
+ const buffer = await cache.getLargeBlob(key);
1781
+ bufferLength += Buffer.byteLength(buffer);
1782
+ return deserialize(buffer);
1783
+ }
1693
1784
  };
1694
1785
 
1695
1786
  let serializedRequestGraph = await getAndDeserialize(requestGraphKey);
@@ -1722,21 +1813,41 @@ async function loadRequestGraph(options): Async<RequestGraph> {
1722
1813
  }
1723
1814
 
1724
1815
  let cacheKey = getCacheKey(options);
1725
- let requestGraphKey = `requestGraph-${cacheKey}`;
1816
+ let requestGraphKey = getFeatureFlag('cachePerformanceImprovements')
1817
+ ? `${cacheKey}/RequestGraph`
1818
+ : `requestGraph-${cacheKey}`;
1819
+
1726
1820
  let timeout;
1727
- const snapshotKey = `snapshot-${cacheKey}`;
1821
+ const snapshotKey = getFeatureFlag('cachePerformanceImprovements')
1822
+ ? `${cacheKey}/snapshot`
1823
+ : `snapshot-${cacheKey}`;
1728
1824
  const snapshotPath = path.join(options.cacheDir, snapshotKey + '.txt');
1729
1825
 
1826
+ const commonMeta = {
1827
+ cacheKey,
1828
+ snapshotKey,
1829
+ cacheKeyOptions: {
1830
+ version: ATLASPACK_VERSION,
1831
+ entries: options.entries,
1832
+ mode: options.mode,
1833
+ shouldBuildLazily: options.shouldBuildLazily,
1834
+ watchBackend: options.watchBackend,
1835
+ },
1836
+ };
1837
+
1730
1838
  logger.verbose({
1731
1839
  origin: '@atlaspack/core',
1732
1840
  message: 'Loading request graph',
1733
1841
  meta: {
1734
- cacheKey,
1735
- snapshotKey,
1842
+ ...commonMeta,
1736
1843
  },
1737
1844
  });
1738
1845
 
1739
- if (await options.cache.hasLargeBlob(requestGraphKey)) {
1846
+ const hasRequestGraphInCache = getFeatureFlag('cachePerformanceImprovements')
1847
+ ? await options.cache.has(requestGraphKey)
1848
+ : await options.cache.hasLargeBlob(requestGraphKey);
1849
+
1850
+ if (hasRequestGraphInCache) {
1740
1851
  try {
1741
1852
  let {requestGraph} = await readAndDeserializeRequestGraph(
1742
1853
  options.cache,
@@ -1767,23 +1878,36 @@ async function loadRequestGraph(options): Async<RequestGraph> {
1767
1878
  origin: '@atlaspack/core',
1768
1879
  message: `File system event count: ${events.length}`,
1769
1880
  meta: {
1881
+ ...commonMeta,
1770
1882
  trackableEvent: 'watcher_events_count',
1771
1883
  watcherEventCount: events.length,
1772
1884
  duration: Date.now() - startTime,
1773
1885
  },
1774
1886
  });
1775
1887
 
1776
- requestGraph.invalidateUnpredictableNodes();
1777
- requestGraph.invalidateOnBuildNodes();
1778
- requestGraph.invalidateEnvNodes(options.env);
1779
- requestGraph.invalidateOptionNodes(options);
1888
+ if (getFeatureFlag('verboseRequestInvalidationStats')) {
1889
+ const invalidationStats = await invalidateRequestGraph(
1890
+ requestGraph,
1891
+ options,
1892
+ events,
1893
+ );
1894
+
1895
+ logger.verbose({
1896
+ origin: '@atlaspack/core',
1897
+ message: 'Request track loaded from cache',
1898
+ meta: {
1899
+ ...commonMeta,
1900
+ trackableEvent: 'request_tracker_cache_key_hit',
1901
+ invalidationStats,
1902
+ },
1903
+ });
1904
+ } else {
1905
+ requestGraph.invalidateUnpredictableNodes();
1906
+ requestGraph.invalidateOnBuildNodes();
1907
+ requestGraph.invalidateEnvNodes(options.env);
1908
+ requestGraph.invalidateOptionNodes(options);
1909
+ }
1780
1910
 
1781
- await requestGraph.respondToFSEvents(
1782
- options.unstableFileInvalidations || events,
1783
- options,
1784
- 10000,
1785
- true,
1786
- );
1787
1911
  return requestGraph;
1788
1912
  } catch (e) {
1789
1913
  // Prevent logging fs events took too long warning
@@ -1800,13 +1924,227 @@ async function loadRequestGraph(options): Async<RequestGraph> {
1800
1924
  message:
1801
1925
  'Cache entry for request tracker was not found, initializing a clean cache.',
1802
1926
  meta: {
1803
- cacheKey,
1804
- snapshotKey,
1927
+ ...commonMeta,
1928
+ trackableEvent: 'request_tracker_cache_key_miss',
1805
1929
  },
1806
1930
  });
1807
1931
  return new RequestGraph();
1808
1932
  }
1809
1933
 
1934
+ /**
1935
+ * A wrapper around an invalidation type / method
1936
+ */
1937
+ type InvalidationFn = {|
1938
+ key: string,
1939
+ fn: () =>
1940
+ | InvalidationDetail
1941
+ | Promise<InvalidationDetail>
1942
+ | void
1943
+ | Promise<void>,
1944
+ |};
1945
+
1946
+ type InvalidationStats = {|
1947
+ /**
1948
+ * Total number of request graph nodes
1949
+ */
1950
+ nodeCount: number,
1951
+ /**
1952
+ * Number of requests in RequestGraph
1953
+ */
1954
+ requestCount: number,
1955
+ /**
1956
+ * Number of nodes that have been invalidated.
1957
+ */
1958
+ invalidatedCount: number,
1959
+ /**
1960
+ * Percentage of requests that have been invalidated
1961
+ */
1962
+ requestInvalidationRatio: number,
1963
+ /**
1964
+ * Percentage of nodes that have been invalidated
1965
+ */
1966
+ nodeInvalidationRatio: number,
1967
+ /**
1968
+ * Details for each invalidation type
1969
+ */
1970
+ invalidations: InvalidationFnStats[],
1971
+ |};
1972
+
1973
+ /**
1974
+ * Details about an invalidation.
1975
+ *
1976
+ * If this is a fs events invalidation, this key will contain statistics about invalidations
1977
+ * by path.
1978
+ *
1979
+ * If this is a env or option invalidation, this key will contain the list of changed environment
1980
+ * variables or options.
1981
+ */
1982
+ type InvalidationDetail = string[] | FSInvalidationStats;
1983
+
1984
+ /**
1985
+ * Number of invalidations for a given file-system event.
1986
+ */
1987
+ type FSInvalidation = {|
1988
+ path: string,
1989
+ count: number,
1990
+ |};
1991
+
1992
+ type FSInvalidationStats = {|
1993
+ /**
1994
+ * This list will be sorted by the number of nodes invalidated and only the top 10 will be
1995
+ * included.
1996
+ */
1997
+ biggestInvalidations: FSInvalidation[],
1998
+ |};
1999
+
2000
+ /**
2001
+ * Information about a certain cache invalidation type.
2002
+ */
2003
+ type InvalidationFnStats = {|
2004
+ /**
2005
+ * Invalidation type, one of:
2006
+ *
2007
+ * - unpredictable
2008
+ * - onBuild
2009
+ * - env
2010
+ * - option
2011
+ * - fsEvents
2012
+ */
2013
+ key: string,
2014
+ /**
2015
+ * Number of invalidated nodes coming from this invalidation type.
2016
+ */
2017
+ count: number,
2018
+ /**
2019
+ * If this is a env or option invalidation, this key will contain the list of changed values.
2020
+ *
2021
+ * If this is a fs events invalidation, this key will contain statistics about invalidations
2022
+ */
2023
+ detail: null | InvalidationDetail,
2024
+ /**
2025
+ * Time in milliseconds it took to run the invalidation.
2026
+ */
2027
+ duration: number,
2028
+ |};
2029
+
2030
+ /**
2031
+ * Respond to unpredictable, build, environment changes, option changes and file-system events
2032
+ * invalidating RequestGraph nodes.
2033
+ *
2034
+ * Returns the count of nodes invalidated by each invalidation type.
2035
+ */
2036
+ export async function invalidateRequestGraph(
2037
+ requestGraph: RequestGraph,
2038
+ options: AtlaspackOptions,
2039
+ events: Event[],
2040
+ ): Promise<InvalidationStats> {
2041
+ const invalidationFns: InvalidationFn[] = [
2042
+ {
2043
+ key: 'unpredictable',
2044
+ fn: () => requestGraph.invalidateUnpredictableNodes(),
2045
+ },
2046
+ {
2047
+ key: 'onBuild',
2048
+ fn: () => requestGraph.invalidateOnBuildNodes(),
2049
+ },
2050
+ {
2051
+ key: 'env',
2052
+ fn: () => requestGraph.invalidateEnvNodes(options.env),
2053
+ },
2054
+ {
2055
+ key: 'option',
2056
+ fn: () => requestGraph.invalidateOptionNodes(options),
2057
+ },
2058
+ {
2059
+ key: 'fsEvents',
2060
+ fn: () => invalidateRequestGraphFSEvents(requestGraph, options, events),
2061
+ },
2062
+ ];
2063
+
2064
+ const invalidations = [];
2065
+ for (const invalidation of invalidationFns) {
2066
+ invalidations.push(await runInvalidation(requestGraph, invalidation));
2067
+ }
2068
+ const invalidatedCount = invalidations.reduce(
2069
+ (acc, invalidation) => acc + invalidation.count,
2070
+ 0,
2071
+ );
2072
+ const requestCount = requestGraph.nodes.reduce(
2073
+ (acc, node) => acc + (node?.type === REQUEST ? 1 : 0),
2074
+ 0,
2075
+ );
2076
+ const nodeCount = requestGraph.nodes.length;
2077
+ const nodeInvalidationRatio = invalidatedCount / nodeCount;
2078
+ const requestInvalidationRatio = invalidatedCount / requestCount;
2079
+
2080
+ return {
2081
+ invalidations,
2082
+ nodeCount,
2083
+ requestCount,
2084
+ invalidatedCount,
2085
+ nodeInvalidationRatio,
2086
+ requestInvalidationRatio,
2087
+ };
2088
+ }
2089
+
2090
+ interface InvalidateRequestGraphFSEventsInput {
2091
+ respondToFSEvents(
2092
+ events: Event[],
2093
+ options: AtlaspackOptions,
2094
+ timeout: number,
2095
+ shouldLog: boolean,
2096
+ ): Promise<{invalidationsByPath: Map<string, number>, ...}>;
2097
+ }
2098
+
2099
+ /**
2100
+ * Invalidate the request graph based on file-system events.
2101
+ *
2102
+ * Returns statistics about the invalidations.
2103
+ */
2104
+ export async function invalidateRequestGraphFSEvents(
2105
+ requestGraph: InvalidateRequestGraphFSEventsInput,
2106
+ options: AtlaspackOptions,
2107
+ events: Event[],
2108
+ ): Promise<FSInvalidationStats> {
2109
+ const {invalidationsByPath} = await requestGraph.respondToFSEvents(
2110
+ options.unstableFileInvalidations || events,
2111
+ options,
2112
+ 10000,
2113
+ true,
2114
+ );
2115
+ const biggestInvalidations =
2116
+ getBiggestFSEventsInvalidations(invalidationsByPath);
2117
+
2118
+ return {
2119
+ biggestInvalidations,
2120
+ };
2121
+ }
2122
+
2123
+ interface RunInvalidationInput {
2124
+ getInvalidNodeCount(): number;
2125
+ }
2126
+
2127
+ /**
2128
+ * Runs an invalidation function and reports metrics.
2129
+ */
2130
+ export async function runInvalidation(
2131
+ requestGraph: RunInvalidationInput,
2132
+ invalidationFn: InvalidationFn,
2133
+ ): Promise<InvalidationFnStats> {
2134
+ const start = performance.now();
2135
+ const startInvalidationCount = requestGraph.getInvalidNodeCount();
2136
+ const result = await invalidationFn.fn();
2137
+ const count = requestGraph.getInvalidNodeCount() - startInvalidationCount;
2138
+ const duration = performance.now() - start;
2139
+
2140
+ return {
2141
+ key: invalidationFn.key,
2142
+ count,
2143
+ detail: result ?? null,
2144
+ duration,
2145
+ };
2146
+ }
2147
+
1810
2148
  function logErrorOnBailout(
1811
2149
  options: AtlaspackOptions,
1812
2150
  snapshotPath: string,
@@ -1858,3 +2196,19 @@ export function cleanUpOrphans<N, E: number>(graph: Graph<N, E>): NodeId[] {
1858
2196
 
1859
2197
  return removedNodeIds;
1860
2198
  }
2199
+
2200
+ /**
2201
+ * Returns paths that invalidated the most nodes
2202
+ */
2203
+ export function getBiggestFSEventsInvalidations(
2204
+ invalidationsByPath: Map<string, number>,
2205
+ limit: number = 10,
2206
+ ): Array<FSInvalidation> {
2207
+ const invalidations = [];
2208
+ for (const [path, count] of invalidationsByPath) {
2209
+ invalidations.push({path, count});
2210
+ }
2211
+ invalidations.sort((a, b) => b.count - a.count);
2212
+
2213
+ return invalidations.slice(0, limit);
2214
+ }