@bldrs-ai/conway 1.549.1515 → 1.553.1520

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 (29) hide show
  1. package/compiled/examples/browser-bundled.cjs +23 -2
  2. package/compiled/examples/cli-bundled.cjs +35 -3
  3. package/compiled/examples/cli-step-bundled.cjs +28 -4
  4. package/compiled/examples/validator-bundled.cjs +23 -2
  5. package/compiled/src/AP214E3_2010/ap214_command_line_main.js +4 -1
  6. package/compiled/src/AP214E3_2010/ap214_regression_main.js +47 -4
  7. package/compiled/src/core/wasm_heap.d.ts +5 -4
  8. package/compiled/src/core/wasm_heap.d.ts.map +1 -1
  9. package/compiled/src/core/wasm_heap.js +5 -4
  10. package/compiled/src/ifc/ifc_command_line_main.js +12 -0
  11. package/compiled/src/ifc/ifc_regression_main.js +97 -6
  12. package/compiled/src/loaders/conway_model_loader.d.ts.map +1 -1
  13. package/compiled/src/loaders/conway_model_loader.js +26 -0
  14. package/compiled/src/memory/memory.d.ts.map +1 -1
  15. package/compiled/src/memory/memory.js +25 -1
  16. package/compiled/src/memory/memory_stats.test.d.ts +2 -0
  17. package/compiled/src/memory/memory_stats.test.d.ts.map +1 -0
  18. package/compiled/src/memory/memory_stats.test.js +115 -0
  19. package/compiled/src/scripts/bless_perf_snapshot.test.js +44 -6
  20. package/compiled/src/scripts/perf_csv_quoting.test.js +113 -23
  21. package/compiled/src/statistics/statistics.d.ts +18 -0
  22. package/compiled/src/statistics/statistics.d.ts.map +1 -1
  23. package/compiled/src/statistics/statistics.js +22 -0
  24. package/compiled/src/statistics/statistics_wasm_heap.test.d.ts +2 -0
  25. package/compiled/src/statistics/statistics_wasm_heap.test.d.ts.map +1 -0
  26. package/compiled/src/statistics/statistics_wasm_heap.test.js +60 -0
  27. package/compiled/src/version/version.js +1 -1
  28. package/compiled/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +1 -1
@@ -30,7 +30,7 @@ var import_node_process = require("node:process");
30
30
  var readline = __toESM(require("node:readline"), 1);
31
31
 
32
32
  // compiled/src/version/version.js
33
- var versionString = "Conway v1.549.1515";
33
+ var versionString = "Conway v1.553.1520";
34
34
 
35
35
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
36
36
  var wasmType = "";
@@ -145,6 +145,27 @@ var Statistics = class {
145
145
  setGeometryMemory(value) {
146
146
  this.geometryMemory = value;
147
147
  }
148
+ /**
149
+ * Wasm linear-memory high-water mark in MB, from wasmHeapByteLength.
150
+ *
151
+ * @return {number | undefined} - wasm heap high-water, or undefined
152
+ */
153
+ getWasmHeapPeak() {
154
+ return this.wasmHeapPeak;
155
+ }
156
+ /**
157
+ * The wasm heap only ever grows, so a single reading of it IS the peak —
158
+ * no sampling loop and no forced GC. It is a different quantity from
159
+ * geometryMemory, and by a wide margin: on MB-Khaya an 8 MB live geometry
160
+ * payload sits under an 85 MB wasm heap, the gap being allocator overhead,
161
+ * fragmentation and the intermediate buffers a boolean leaves behind (see
162
+ * src/ifc/geometry_residency.ts). Neither substitutes for the other.
163
+ *
164
+ * @param value - wasm heap high-water in MB
165
+ */
166
+ setWasmHeapPeak(value) {
167
+ this.wasmHeapPeak = value;
168
+ }
148
169
  /**
149
170
  *
150
171
  * @return {string | undefined} - preprocessor version or undefined
@@ -278,7 +299,7 @@ var Statistics = class {
278
299
  const products = this.productCount !== void 0 ? `Products: ${this.productCount}, ` : "";
279
300
  const breakdown = this.formatGeometryTypeCounts();
280
301
  const geometryTypes = breakdown !== void 0 ? `, Geometry Types: ${breakdown}` : "";
281
- return `[${dateString}]: Load Status: ${this.loadStatus}, Project Name: ${this.projectName}, Version: ${versionStr}, Conway Version: ${conwayVersionNumber}-${wasmType}, Parse Time: ${this.parseTime} ms, Geometry Time: ${this.geometryTime} ms, Total Time: ${this.totalTime} ms, Geometry Memory: ${this.geometryMemory?.toFixed(3)} MB, ` + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
302
+ return `[${dateString}]: Load Status: ${this.loadStatus}, Project Name: ${this.projectName}, Version: ${versionStr}, Conway Version: ${conwayVersionNumber}-${wasmType}, Parse Time: ${this.parseTime} ms, Geometry Time: ${this.geometryTime} ms, Total Time: ${this.totalTime} ms, Geometry Memory: ${this.geometryMemory?.toFixed(3)} MB, WASM Heap High-Water: ${this.wasmHeapPeak?.toFixed(3)} MB, ` + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
282
303
  }
283
304
  };
284
305
 
@@ -14965,7 +14965,7 @@ ${t5.join("\n")}` : "";
14965
14965
  var import_process = require("process");
14966
14966
 
14967
14967
  // compiled/src/version/version.js
14968
- var versionString = "Conway v1.549.1515";
14968
+ var versionString = "Conway v1.553.1520";
14969
14969
 
14970
14970
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
14971
14971
  function pThreadsAllowed() {
@@ -15819,6 +15819,27 @@ var Statistics = class {
15819
15819
  setGeometryMemory(value) {
15820
15820
  this.geometryMemory = value;
15821
15821
  }
15822
+ /**
15823
+ * Wasm linear-memory high-water mark in MB, from wasmHeapByteLength.
15824
+ *
15825
+ * @return {number | undefined} - wasm heap high-water, or undefined
15826
+ */
15827
+ getWasmHeapPeak() {
15828
+ return this.wasmHeapPeak;
15829
+ }
15830
+ /**
15831
+ * The wasm heap only ever grows, so a single reading of it IS the peak —
15832
+ * no sampling loop and no forced GC. It is a different quantity from
15833
+ * geometryMemory, and by a wide margin: on MB-Khaya an 8 MB live geometry
15834
+ * payload sits under an 85 MB wasm heap, the gap being allocator overhead,
15835
+ * fragmentation and the intermediate buffers a boolean leaves behind (see
15836
+ * src/ifc/geometry_residency.ts). Neither substitutes for the other.
15837
+ *
15838
+ * @param value - wasm heap high-water in MB
15839
+ */
15840
+ setWasmHeapPeak(value) {
15841
+ this.wasmHeapPeak = value;
15842
+ }
15822
15843
  /**
15823
15844
  *
15824
15845
  * @return {string | undefined} - preprocessor version or undefined
@@ -15952,7 +15973,7 @@ var Statistics = class {
15952
15973
  const products = this.productCount !== void 0 ? `Products: ${this.productCount}, ` : "";
15953
15974
  const breakdown = this.formatGeometryTypeCounts();
15954
15975
  const geometryTypes = breakdown !== void 0 ? `, Geometry Types: ${breakdown}` : "";
15955
- return `[${dateString}]: Load Status: ${this.loadStatus}, Project Name: ${this.projectName}, Version: ${versionStr}, Conway Version: ${conwayVersionNumber}-${wasmType}, Parse Time: ${this.parseTime} ms, Geometry Time: ${this.geometryTime} ms, Total Time: ${this.totalTime} ms, Geometry Memory: ${this.geometryMemory?.toFixed(3)} MB, ` + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
15976
+ return `[${dateString}]: Load Status: ${this.loadStatus}, Project Name: ${this.projectName}, Version: ${versionStr}, Conway Version: ${conwayVersionNumber}-${wasmType}, Parse Time: ${this.parseTime} ms, Geometry Time: ${this.geometryTime} ms, Total Time: ${this.totalTime} ms, Geometry Memory: ${this.geometryMemory?.toFixed(3)} MB, WASM Heap High-Water: ${this.wasmHeapPeak?.toFixed(3)} MB, ` + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
15956
15977
  }
15957
15978
  };
15958
15979
 
@@ -16324,7 +16345,10 @@ var Memory = class {
16324
16345
  const rss = (memoryUsage.rss / 1024 / 1024).toFixed(3);
16325
16346
  const heapTotal = (memoryUsage.heapTotal / 1024 / 1024).toFixed(3);
16326
16347
  const heapUsed = (memoryUsage.heapUsed / 1024 / 1024).toFixed(3);
16327
- return `Node Memory Usage: RSS ${rss} MB, Heap Total: ${heapTotal} MB, Heap Used: ${heapUsed} MB`;
16348
+ const external = (memoryUsage.external / 1024 / 1024).toFixed(3);
16349
+ const arrayBuffers = (memoryUsage.arrayBuffers / 1024 / 1024).toFixed(3);
16350
+ const peakRss = (process.resourceUsage().maxRSS / 1024).toFixed(3);
16351
+ return `Node Memory Usage: RSS ${rss} MB, Heap Total: ${heapTotal} MB, Heap Used: ${heapUsed} MB, Peak RSS: ${peakRss} MB, External: ${external} MB, ArrayBuffers: ${arrayBuffers} MB`;
16328
16352
  }
16329
16353
  };
16330
16354
 
@@ -27012,6 +27036,10 @@ function wasmHeapView(wasmModule, arrayType, pointer, elementCount) {
27012
27036
  const numBytes = elementCount * arrayType.BYTES_PER_ELEMENT;
27013
27037
  return new arrayType(reconcileHeapBuffer(wasmModule, address, numBytes), address, elementCount);
27014
27038
  }
27039
+ function wasmHeapByteLength(wasmModule) {
27040
+ refreshHeapViews(wasmModule);
27041
+ return currentHeapBuffer(wasmModule)?.byteLength ?? 0;
27042
+ }
27015
27043
  function arrayToWasmHeap(wasmModule, array) {
27016
27044
  const numBytes = array.length * array.BYTES_PER_ELEMENT;
27017
27045
  const arrayPtr = wasmModule._malloc(numBytes) >>> 0;
@@ -95450,6 +95478,10 @@ function geometryExtraction(model, limitCSGDepth = true, maxCSGDepth = 20, onPro
95450
95478
  const ONE_KB = 1024;
95451
95479
  const ONE_MB = ONE_KB * ONE_KB;
95452
95480
  statistics?.setGeometryMemory(conwayModel.model.geometry.calculateGeometrySize() / ONE_MB);
95481
+ const wasmModule = conwaywasm.wasmModule;
95482
+ if (wasmModule !== void 0) {
95483
+ statistics?.setWasmHeapPeak(wasmHeapByteLength(wasmModule) / ONE_MB);
95484
+ }
95453
95485
  const ifcProjectName = conwayModel.getIfcProjectName();
95454
95486
  if (ifcProjectName !== null) {
95455
95487
  statistics?.setProjectName(ifcProjectName);
@@ -15943,7 +15943,7 @@ var ParsingBuffer = class {
15943
15943
  };
15944
15944
 
15945
15945
  // compiled/src/version/version.js
15946
- var versionString = "Conway v1.549.1515";
15946
+ var versionString = "Conway v1.553.1520";
15947
15947
 
15948
15948
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
15949
15949
  function pThreadsAllowed() {
@@ -16797,6 +16797,27 @@ var Statistics = class {
16797
16797
  setGeometryMemory(value) {
16798
16798
  this.geometryMemory = value;
16799
16799
  }
16800
+ /**
16801
+ * Wasm linear-memory high-water mark in MB, from wasmHeapByteLength.
16802
+ *
16803
+ * @return {number | undefined} - wasm heap high-water, or undefined
16804
+ */
16805
+ getWasmHeapPeak() {
16806
+ return this.wasmHeapPeak;
16807
+ }
16808
+ /**
16809
+ * The wasm heap only ever grows, so a single reading of it IS the peak —
16810
+ * no sampling loop and no forced GC. It is a different quantity from
16811
+ * geometryMemory, and by a wide margin: on MB-Khaya an 8 MB live geometry
16812
+ * payload sits under an 85 MB wasm heap, the gap being allocator overhead,
16813
+ * fragmentation and the intermediate buffers a boolean leaves behind (see
16814
+ * src/ifc/geometry_residency.ts). Neither substitutes for the other.
16815
+ *
16816
+ * @param value - wasm heap high-water in MB
16817
+ */
16818
+ setWasmHeapPeak(value) {
16819
+ this.wasmHeapPeak = value;
16820
+ }
16800
16821
  /**
16801
16822
  *
16802
16823
  * @return {string | undefined} - preprocessor version or undefined
@@ -16930,7 +16951,7 @@ var Statistics = class {
16930
16951
  const products = this.productCount !== void 0 ? `Products: ${this.productCount}, ` : "";
16931
16952
  const breakdown = this.formatGeometryTypeCounts();
16932
16953
  const geometryTypes = breakdown !== void 0 ? `, Geometry Types: ${breakdown}` : "";
16933
- return `[${dateString}]: Load Status: ${this.loadStatus}, Project Name: ${this.projectName}, Version: ${versionStr}, Conway Version: ${conwayVersionNumber}-${wasmType}, Parse Time: ${this.parseTime} ms, Geometry Time: ${this.geometryTime} ms, Total Time: ${this.totalTime} ms, Geometry Memory: ${this.geometryMemory?.toFixed(3)} MB, ` + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
16954
+ return `[${dateString}]: Load Status: ${this.loadStatus}, Project Name: ${this.projectName}, Version: ${versionStr}, Conway Version: ${conwayVersionNumber}-${wasmType}, Parse Time: ${this.parseTime} ms, Geometry Time: ${this.geometryTime} ms, Total Time: ${this.totalTime} ms, Geometry Memory: ${this.geometryMemory?.toFixed(3)} MB, WASM Heap High-Water: ${this.wasmHeapPeak?.toFixed(3)} MB, ` + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
16934
16955
  }
16935
16956
  };
16936
16957
 
@@ -17302,7 +17323,10 @@ var Memory = class {
17302
17323
  const rss = (memoryUsage.rss / 1024 / 1024).toFixed(3);
17303
17324
  const heapTotal = (memoryUsage.heapTotal / 1024 / 1024).toFixed(3);
17304
17325
  const heapUsed = (memoryUsage.heapUsed / 1024 / 1024).toFixed(3);
17305
- return `Node Memory Usage: RSS ${rss} MB, Heap Total: ${heapTotal} MB, Heap Used: ${heapUsed} MB`;
17326
+ const external = (memoryUsage.external / 1024 / 1024).toFixed(3);
17327
+ const arrayBuffers = (memoryUsage.arrayBuffers / 1024 / 1024).toFixed(3);
17328
+ const peakRss = (process.resourceUsage().maxRSS / 1024).toFixed(3);
17329
+ return `Node Memory Usage: RSS ${rss} MB, Heap Total: ${heapTotal} MB, Heap Used: ${heapUsed} MB, Peak RSS: ${peakRss} MB, External: ${external} MB, ArrayBuffers: ${arrayBuffers} MB`;
17306
17330
  }
17307
17331
  };
17308
17332
 
@@ -86173,7 +86197,7 @@ function geometryExtraction(model, limitCSGDepth = true, maxCSGDepth = 20) {
86173
86197
  const ONE_MB = ONE_KB * ONE_KB;
86174
86198
  const wasmModule = conwaywasm.wasmModule;
86175
86199
  if (wasmModule !== void 0) {
86176
- logger_default.info(`WASM heap high-water: ${(wasmHeapByteLength(wasmModule) / ONE_MB).toFixed(1)} MB`);
86200
+ statistics?.setWasmHeapPeak(wasmHeapByteLength(wasmModule) / ONE_MB);
86177
86201
  }
86178
86202
  statistics?.setGeometryMemory(conwayModel.model.geometry.calculateGeometrySize() / ONE_MB);
86179
86203
  model.invalidate(true);
@@ -944,7 +944,7 @@ var EntityTypesIfcCount = 909;
944
944
  var entity_types_ifc_gen_default = EntityTypesIfc;
945
945
 
946
946
  // compiled/src/version/version.js
947
- var versionString = "Conway v1.549.1515";
947
+ var versionString = "Conway v1.553.1520";
948
948
 
949
949
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
950
950
  var wasmType = "";
@@ -1059,6 +1059,27 @@ var Statistics = class {
1059
1059
  setGeometryMemory(value) {
1060
1060
  this.geometryMemory = value;
1061
1061
  }
1062
+ /**
1063
+ * Wasm linear-memory high-water mark in MB, from wasmHeapByteLength.
1064
+ *
1065
+ * @return {number | undefined} - wasm heap high-water, or undefined
1066
+ */
1067
+ getWasmHeapPeak() {
1068
+ return this.wasmHeapPeak;
1069
+ }
1070
+ /**
1071
+ * The wasm heap only ever grows, so a single reading of it IS the peak —
1072
+ * no sampling loop and no forced GC. It is a different quantity from
1073
+ * geometryMemory, and by a wide margin: on MB-Khaya an 8 MB live geometry
1074
+ * payload sits under an 85 MB wasm heap, the gap being allocator overhead,
1075
+ * fragmentation and the intermediate buffers a boolean leaves behind (see
1076
+ * src/ifc/geometry_residency.ts). Neither substitutes for the other.
1077
+ *
1078
+ * @param value - wasm heap high-water in MB
1079
+ */
1080
+ setWasmHeapPeak(value) {
1081
+ this.wasmHeapPeak = value;
1082
+ }
1062
1083
  /**
1063
1084
  *
1064
1085
  * @return {string | undefined} - preprocessor version or undefined
@@ -1192,7 +1213,7 @@ var Statistics = class {
1192
1213
  const products = this.productCount !== void 0 ? `Products: ${this.productCount}, ` : "";
1193
1214
  const breakdown = this.formatGeometryTypeCounts();
1194
1215
  const geometryTypes = breakdown !== void 0 ? `, Geometry Types: ${breakdown}` : "";
1195
- return `[${dateString}]: Load Status: ${this.loadStatus}, Project Name: ${this.projectName}, Version: ${versionStr}, Conway Version: ${conwayVersionNumber}-${wasmType}, Parse Time: ${this.parseTime} ms, Geometry Time: ${this.geometryTime} ms, Total Time: ${this.totalTime} ms, Geometry Memory: ${this.geometryMemory?.toFixed(3)} MB, ` + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
1216
+ return `[${dateString}]: Load Status: ${this.loadStatus}, Project Name: ${this.projectName}, Version: ${versionStr}, Conway Version: ${conwayVersionNumber}-${wasmType}, Parse Time: ${this.parseTime} ms, Geometry Time: ${this.geometryTime} ms, Total Time: ${this.totalTime} ms, Geometry Memory: ${this.geometryMemory?.toFixed(3)} MB, WASM Heap High-Water: ${this.wasmHeapPeak?.toFixed(3)} MB, ` + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
1196
1217
  }
1197
1218
  };
1198
1219
 
@@ -405,12 +405,15 @@ function geometryExtraction(model, limitCSGDepth = true, maxCSGDepth = 20) {
405
405
  statistics?.setGeometryTime(executionTimeInMs);
406
406
  const ONE_KB = 1024;
407
407
  const ONE_MB = ONE_KB * ONE_KB;
408
+ // Recorded on the statistics rather than logged on its own line, so the one
409
+ // spelling of this number reaches the benchmark's `peakWasmHeapMb` column
410
+ // through the same load-status line as every other measurement (#552).
408
411
  // Measured through wasmHeapByteLength rather than HEAPU8.length: the
409
412
  // module's cached view can be a growth step behind the real heap (#485), and
410
413
  // a high-water figure that under-reports is worse than none.
411
414
  const wasmModule = conwaywasm.wasmModule;
412
415
  if (wasmModule !== void 0) {
413
- Logger.info(`WASM heap high-water: ${(wasmHeapByteLength(wasmModule) / ONE_MB).toFixed(1)} MB`);
416
+ statistics?.setWasmHeapPeak(wasmHeapByteLength(wasmModule) / ONE_MB);
414
417
  }
415
418
  statistics?.setGeometryMemory(conwayModel.model.geometry.calculateGeometrySize() / (ONE_MB));
416
419
  model.invalidate(true);
@@ -9,6 +9,7 @@ import fsPromises from "fs/promises";
9
9
  import path from "path";
10
10
  import crypto from "crypto";
11
11
  import { ConwayGeometry } from "../../dependencies/conway-geom/index.js";
12
+ import { wasmHeapByteLength } from "../core/wasm_heap.js";
12
13
  import Logger from "../logging/logger.js";
13
14
  import Environment from "../utilities/environment.js";
14
15
  import { ExtractResult } from "../core/shared_constants.js";
@@ -35,22 +36,40 @@ function csvSafeString(from) {
35
36
  // Bytes per megabyte for memory-stat formatting in the perf CSV.
36
37
  // eslint-disable-next-line no-magic-numbers
37
38
  const BYTES_PER_MB = 1024 * 1024;
39
+ // Kilobytes per megabyte, for resourceUsage().maxRSS which reports kB.
40
+ // eslint-disable-next-line no-magic-numbers
41
+ const KB_PER_MB = 1024;
38
42
  // Fixed-point precision for perf MB values.
39
43
  // eslint-disable-next-line no-magic-numbers
40
44
  const PERF_MB_PRECISION = 2;
45
+ /** Placeholder for a column this row has no measurement for. */
46
+ const UNMEASURED = "N/A";
41
47
  /**
42
48
  * Write a single-row per-file perf CSV at the given path, matching the
43
49
  * column layout of the IFC regression child so the batch aggregator can
44
50
  * merge STEP and IFC rows into one perf CSV. No-op when perfPath is empty.
45
51
  *
52
+ * `peakRssMb` is the process high-water mark; `rssMb`, `heapUsedMb`,
53
+ * `heapTotalMb`, `externalMb` and `arrayBuffersMb` are single instants
54
+ * sampled at write time, and `geometryMemoryMb` is conway's own vertex+index
55
+ * payload rather than a process metric. `arrayBuffersMb` is a subset of
56
+ * `externalMb`, and neither sees the wasm heap. See the fuller
57
+ * column-by-column note on the IFC child's copy of this function
58
+ * (`src/ifc/ifc_regression_main.ts`), which is the writer these columns have
59
+ * to stay identical to.
60
+ *
46
61
  * @param perfPath Path to write the CSV to. Empty string disables.
47
62
  * @param stepFile Source STEP file path (basename used as the row key).
48
63
  * @param status OK or FAIL.
49
64
  * @param parseTimeMs Parse stage duration in ms.
50
65
  * @param geometryTimeMs Geometry extraction duration in ms.
51
66
  * @param totalTimeMs Sum of parse + geometry in ms.
67
+ * @param geometryMemoryBytes Geometry payload conway allocated, in bytes, or
68
+ * undefined where no geometry was extracted (written as N/A).
69
+ * @param wasmHeapBytes Wasm linear-memory high-water in bytes, or undefined
70
+ * where the module was never brought up (written as N/A).
52
71
  */
53
- async function writePerfCsvIfRequested(perfPath, stepFile, status, parseTimeMs, geometryTimeMs, totalTimeMs) {
72
+ async function writePerfCsvIfRequested(perfPath, stepFile, status, parseTimeMs, geometryTimeMs, totalTimeMs, geometryMemoryBytes, wasmHeapBytes) {
54
73
  if (perfPath.length === 0) {
55
74
  return;
56
75
  }
@@ -58,10 +77,23 @@ async function writePerfCsvIfRequested(perfPath, stepFile, status, parseTimeMs,
58
77
  const rssMb = (mem.rss / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
59
78
  const heapUsedMb = (mem.heapUsed / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
60
79
  const heapTotalMb = (mem.heapTotal / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
80
+ const externalMb = (mem.external / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
81
+ const arrayBuffersMb = (mem.arrayBuffers / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
82
+ // maxRSS is reported in kilobytes, unlike memoryUsage() which is in bytes.
83
+ const peakRssMb = (process.resourceUsage().maxRSS / KB_PER_MB).toFixed(PERF_MB_PRECISION);
84
+ const geometryMemoryMb = geometryMemoryBytes !== void 0 ?
85
+ (geometryMemoryBytes / BYTES_PER_MB).toFixed(PERF_MB_PRECISION) :
86
+ UNMEASURED;
87
+ const peakWasmHeapMb = wasmHeapBytes !== void 0 ?
88
+ (wasmHeapBytes / BYTES_PER_MB).toFixed(PERF_MB_PRECISION) :
89
+ UNMEASURED;
61
90
  const fileName = csvSafeString(path.basename(stepFile));
62
- const header = "file,status,parseTimeMs,geometryTimeMs,totalTimeMs,rssMb,heapUsedMb,heapTotalMb\n";
91
+ const header = "file,status,parseTimeMs,geometryTimeMs,totalTimeMs,geometryMemoryMb," +
92
+ "peakWasmHeapMb,rssMb,peakRssMb,heapUsedMb,heapTotalMb,externalMb," +
93
+ "arrayBuffersMb\n";
63
94
  const row = `${fileName},${status},${parseTimeMs},${geometryTimeMs},${totalTimeMs},` +
64
- `${rssMb},${heapUsedMb},${heapTotalMb}\n`;
95
+ `${geometryMemoryMb},${peakWasmHeapMb},${rssMb},${peakRssMb},` +
96
+ `${heapUsedMb},${heapTotalMb},${externalMb},${arrayBuffersMb}\n`;
65
97
  try {
66
98
  await fsPromises.writeFile(perfPath, header + row);
67
99
  }
@@ -206,7 +238,18 @@ function doWork() {
206
238
  const geometryTimeMs = geomEndMs - geomStartMs;
207
239
  const totalTimeMs = geomEndMs - parseStartMs;
208
240
  const perfStatus = extraction === void 0 ? "FAIL" : "OK";
209
- await writePerfCsvIfRequested(perfPath, stepFile, perfStatus, parseTimeMs, geometryTimeMs, totalTimeMs);
241
+ // Sized before anything downstream can release meshes, and only on
242
+ // the OK path: a failed extraction leaves a partial cache whose size
243
+ // is not this model's geometry footprint.
244
+ const geometryMemoryBytes = extraction !== void 0 ?
245
+ model.geometry.calculateGeometrySize() : void 0;
246
+ // The heap is grow-only, so this is the run's high-water mark even
247
+ // though it is read once, after the fact. conwayGeom is the engine
248
+ // every extraction in this process runs against.
249
+ const wasmModule = conwayGeom.wasmModule;
250
+ const wasmHeapBytes = wasmModule !== void 0 ?
251
+ wasmHeapByteLength(wasmModule) : void 0;
252
+ await writePerfCsvIfRequested(perfPath, stepFile, perfStatus, parseTimeMs, geometryTimeMs, totalTimeMs, geometryMemoryBytes, wasmHeapBytes);
210
253
  // AFTP sizing pass: no-op unless the wasm module was built with
211
254
  // CONWAY_ALLOC_TELEMETRY (see conway-geom structures/alloc_telemetry.h).
212
255
  conwayGeom.dumpAllocTelemetry(path.basename(stepFile));
@@ -125,10 +125,11 @@ export declare function wasmHeapView<TArray>(wasmModule: WasmHeapModule, arrayTy
125
125
  * trade. Do not put it in a loop.
126
126
  *
127
127
  * That the refresh cannot throw (see refreshHeapViews) matters more here than
128
- * at the other call site: this figure's only consumer is the AP214 CLI's
129
- * high-water log line, so an embind failure escaping would abort an otherwise
130
- * complete extraction from a log statement. A lagging number is the right
131
- * answer there.
128
+ * at the other call site: every consumer of this figure is reporting — the
129
+ * loaders' and CLIs' high-water statistic, and the regression children's
130
+ * `peakWasmHeapMb` perf column (#552) — so an embind failure escaping would
131
+ * abort an otherwise complete extraction from a measurement. A lagging number
132
+ * is the right answer there.
132
133
  *
133
134
  * @param wasmModule The module to measure.
134
135
  * @return {number} The heap's size in bytes, or 0 if it has no heap yet.
@@ -1 +1 @@
1
- {"version":3,"file":"wasm_heap.d.ts","sourceRoot":"","sources":["../../../src/core/wasm_heap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAKH;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAE,KAAK,EAAE,MAAM,GAAI,MAAM,CAAA;IAChC,KAAK,CAAE,OAAO,EAAE,MAAM,GAAI,IAAI,CAAA;IAC9B,MAAM,EAAE,UAAU,CAAA;IAElB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;IAE9B;;;;OAIG;IACH,UAAU,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;KAAE,CAAA;IAEjD;;;OAGG;IACH,YAAY,CAAC,EAAE,UAAU;QAAE,SAAS,CAAE,KAAK,EAAE,MAAM,GAAI,IAAI,CAAC;QAAC,MAAM,IAAI,IAAI,CAAA;KAAE,CAAA;CAC9E;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,YAAY,GAAG,WAAW,CAAA;AAMrE;;;GAGG;AACH,MAAM,WAAW,wBAAwB,CAAE,MAAM;IAC/C,KAAM,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAI,MAAM,CAAA;IAC3E,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;CACnC;AA6MD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,CAAE,MAAM,EAChC,UAAU,EAAE,cAAc,EAC1B,SAAS,EAAE,wBAAwB,CAAE,MAAM,CAAE,EAC7C,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GAAI,MAAM,CASjC;AAGD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAAE,UAAU,EAAE,cAAc,GAAI,MAAM,CAKvE;AAGD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC3B,UAAU,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,GAAI,MAAM,CA8C7D;AAGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC5B,UAAU,EAAE,cAAc,EAC1B,MAAM,EAAE,SAAS,aAAa,EAAE,GAAI,MAAM,EAAE,CAqB/C;AAGD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAE,OAAO,EAAE,MAAM,IAAI,GAAI,IAAI,CAW1D;AAGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAG,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,IAAI,GAAI,CAAC,CAiBtE;AAGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CACnB,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAI,IAAI,CAkBlE"}
1
+ {"version":3,"file":"wasm_heap.d.ts","sourceRoot":"","sources":["../../../src/core/wasm_heap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAKH;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAE,KAAK,EAAE,MAAM,GAAI,MAAM,CAAA;IAChC,KAAK,CAAE,OAAO,EAAE,MAAM,GAAI,IAAI,CAAA;IAC9B,MAAM,EAAE,UAAU,CAAA;IAElB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;IAE9B;;;;OAIG;IACH,UAAU,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;KAAE,CAAA;IAEjD;;;OAGG;IACH,YAAY,CAAC,EAAE,UAAU;QAAE,SAAS,CAAE,KAAK,EAAE,MAAM,GAAI,IAAI,CAAC;QAAC,MAAM,IAAI,IAAI,CAAA;KAAE,CAAA;CAC9E;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,YAAY,GAAG,WAAW,CAAA;AAMrE;;;GAGG;AACH,MAAM,WAAW,wBAAwB,CAAE,MAAM;IAC/C,KAAM,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAI,MAAM,CAAA;IAC3E,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;CACnC;AA6MD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,CAAE,MAAM,EAChC,UAAU,EAAE,cAAc,EAC1B,SAAS,EAAE,wBAAwB,CAAE,MAAM,CAAE,EAC7C,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GAAI,MAAM,CASjC;AAGD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAE,UAAU,EAAE,cAAc,GAAI,MAAM,CAKvE;AAGD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC3B,UAAU,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,GAAI,MAAM,CA8C7D;AAGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC5B,UAAU,EAAE,cAAc,EAC1B,MAAM,EAAE,SAAS,aAAa,EAAE,GAAI,MAAM,EAAE,CAqB/C;AAGD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAE,OAAO,EAAE,MAAM,IAAI,GAAI,IAAI,CAW1D;AAGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAG,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,IAAI,GAAI,CAAC,CAiBtE;AAGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CACnB,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAI,IAAI,CAkBlE"}
@@ -250,10 +250,11 @@ export function wasmHeapView(wasmModule, arrayType, pointer, elementCount) {
250
250
  * trade. Do not put it in a loop.
251
251
  *
252
252
  * That the refresh cannot throw (see refreshHeapViews) matters more here than
253
- * at the other call site: this figure's only consumer is the AP214 CLI's
254
- * high-water log line, so an embind failure escaping would abort an otherwise
255
- * complete extraction from a log statement. A lagging number is the right
256
- * answer there.
253
+ * at the other call site: every consumer of this figure is reporting — the
254
+ * loaders' and CLIs' high-water statistic, and the regression children's
255
+ * `peakWasmHeapMb` perf column (#552) — so an embind failure escaping would
256
+ * abort an otherwise complete extraction from a measurement. A lagging number
257
+ * is the right answer there.
257
258
  *
258
259
  * @param wasmModule The module to measure.
259
260
  * @return {number} The heap's size in bytes, or 0 if it has no heap yet.
@@ -9,6 +9,7 @@ import { IfcGeometryExtraction } from "./ifc_geometry_extraction.js";
9
9
  import { IfcPropertyExtraction } from "./ifc_property_extraction.js";
10
10
  import { ConwayGeometry } from "../../dependencies/conway-geom/index.js";
11
11
  import GeometryConvertor from "../core/geometry_convertor.js";
12
+ import { wasmHeapByteLength } from "../core/wasm_heap.js";
12
13
  import GeometryAggregator from "../core/geometry_aggregator.js";
13
14
  import Logger, { LogLevel } from "../logging/logger.js";
14
15
  import { ProgressTracker } from "../core/progress.js";
@@ -545,6 +546,17 @@ function geometryExtraction(model, limitCSGDepth = true, maxCSGDepth = 20, onPro
545
546
  const ONE_KB = 1024;
546
547
  const ONE_MB = ONE_KB * ONE_KB;
547
548
  statistics?.setGeometryMemory(conwayModel.model.geometry.calculateGeometrySize() / (ONE_MB));
549
+ // The wasm heap, which nothing else here can see: geometryMemory above is
550
+ // the vertex+index payload only, and the JS-side heap/external figures do
551
+ // not include emscripten's linear memory at all. Grow-only, so one reading
552
+ // is the high-water mark. Measured through wasmHeapByteLength rather than
553
+ // HEAPU8.length: the module's cached view can be a growth step behind the
554
+ // real heap (#485), and a high-water figure that under-reports is worse
555
+ // than none.
556
+ const wasmModule = conwaywasm.wasmModule;
557
+ if (wasmModule !== void 0) {
558
+ statistics?.setWasmHeapPeak(wasmHeapByteLength(wasmModule) / ONE_MB);
559
+ }
548
560
  const ifcProjectName = conwayModel.getIfcProjectName();
549
561
  if (ifcProjectName !== null) {
550
562
  statistics?.setProjectName(ifcProjectName);
@@ -14,6 +14,7 @@ import fsPromises from "fs/promises";
14
14
  import EntityTypesIfc from "./ifc4_gen/entity_types_ifc.gen.js";
15
15
  import { IfcBooleanResult } from "./ifc4_gen/index.js";
16
16
  import { MemoizationCapture, RegressionCaptureState } from "../core/regression_capture_state.js";
17
+ import { wasmHeapByteLength } from "../core/wasm_heap.js";
17
18
  import { materialHashes } from "./ifc_material_cache_node.js";
18
19
  import { dumpGeometryOBJs, geometryHashes } from "./ifc_model_geometry_node.js";
19
20
  import { curveHashes, dumpCurveOBJs } from "./ifc_model_curves_node.js";
@@ -39,28 +40,93 @@ function csvSafeString(from) {
39
40
  // Bytes per megabyte for memory-stat formatting in the perf CSV.
40
41
  // eslint-disable-next-line no-magic-numbers
41
42
  const BYTES_PER_MB = 1024 * 1024;
43
+ // Kilobytes per megabyte, for resourceUsage().maxRSS which reports kB.
44
+ // eslint-disable-next-line no-magic-numbers
45
+ const KB_PER_MB = 1024;
42
46
  // Fixed-point precision for perf MB values.
43
47
  // eslint-disable-next-line no-magic-numbers
44
48
  const PERF_MB_PRECISION = 2;
49
+ /** Placeholder for a column this row has no measurement for. */
50
+ const UNMEASURED = "N/A";
45
51
  /**
46
52
  * Write a single-row per-file perf CSV at the given path. Memory snapshot is
47
53
  * taken at call time; for the OK path this is right after geometry extraction
48
54
  * — close to peak. No-op when perfPath is empty.
49
55
  *
50
- * Memory semantics: `rssMb` includes the conway-geom WASM heap (mmap'd into
51
- * the process) and is the load-bearing memory metric for geometry work.
56
+ * WHICH COLUMNS ARE PEAK AND WHICH ARE INSTANTS — they are not comparable and
57
+ * the difference is large (conway#552):
58
+ *
59
+ * peakRssMb PEAK. The kernel's high-water mark for this process
60
+ * (`resourceUsage().maxRSS`, kB), i.e. the number that
61
+ * decides whether a browser tab or a runner survives the
62
+ * load. Free to read and perturbs nothing.
63
+ * rssMb INSTANT. One `memoryUsage()` sample at write time. A run
64
+ * that transiently hit 5 GB and settled to 1 GB reports
65
+ * 1 GB here and 5 GB in peakRssMb.
66
+ * heapUsedMb INSTANT, and *not* live set: it is live data plus
67
+ * whatever garbage GC has not collected yet, so it moves
68
+ * with GC timing. Measuring SKYLARK250 this way gives
69
+ * 2547 MB where the same run's live heap after two forced
70
+ * full GCs is 981 MB. A live-heap column would need forced
71
+ * GC, which wrecks the timing columns, so it is not here.
72
+ * heapTotalMb INSTANT. V8's reserved heap.
73
+ * externalMb INSTANT. Off-heap bytes V8 knows about, which heapUsed
74
+ * does not include and which are a real part of the
75
+ * footprint: on MB-Khaya (31 MB IFC) `readFileSync` alone
76
+ * moves this 1.7 -> 33.1 MB while heapUsed does not move.
77
+ * arrayBuffersMb INSTANT, and a SUBSET of externalMb — the ArrayBuffer
78
+ * share of it, which is where the source buffer and the
79
+ * parse structures land (31.5 of that 33.1 MB).
80
+ * geometryMemoryMb Not a process metric at all: the vertex+index PAYLOAD
81
+ * conway itself allocated for this model, summed by
82
+ * `calculateGeometrySize()` — what a consumer would copy
83
+ * out. Unlike rss/heap it excludes everything the harness
84
+ * happens to be holding, and unlike peakWasmHeapMb it
85
+ * excludes everything the allocator is holding.
86
+ * peakWasmHeapMb PEAK, and the only column that sees conway's native
87
+ * memory. Emscripten's linear memory grows and never
88
+ * shrinks, so one reading of `wasmHeapByteLength` IS the
89
+ * high-water mark — nothing to sample, nothing perturbed.
90
+ * Read through that helper rather than `HEAPU8.length`,
91
+ * whose cached view can be a growth step behind the real
92
+ * heap (#485); a high-water figure that under-reports is
93
+ * worse than none. This is NOT geometryMemoryMb: it
94
+ * includes allocator overhead, fragmentation and the
95
+ * intermediate buffers a boolean leaves behind, which on
96
+ * MB-Khaya is an 85 MB heap over an 8 MB live payload.
97
+ *
98
+ * There is deliberately no `heapUsed + external` total. It looks like a
99
+ * portable stand-in for RSS and is not one: on that same model it reads
100
+ * 284 MB against an RSS of 510 MB after geometry, and wasm init alone adds
101
+ * ~65 MB of RSS while moving `external` by under 2 MB. Emscripten's heap is
102
+ * structurally invisible to JS-side accounting, so such a total would be
103
+ * blind to exactly the memory this bench exists to track. peakRssMb is the
104
+ * headline.
105
+ *
106
+ * `rssMb` and `peakRssMb` include the conway-geom WASM heap (mmap'd into the
107
+ * process) and are the load-bearing memory metrics for geometry work.
52
108
  * `heapUsedMb` / `heapTotalMb` are V8-only — useful for tracking JS-side
53
109
  * allocation pressure but they exclude WASM-side buffers. Expect a large
54
110
  * gap between rss and heap on geometry-heavy models.
55
111
  *
112
+ * The column list here is shared with the AP214 regression child
113
+ * (`ap214_regression_main.ts`): `aggregatePerfCsvs` in
114
+ * `ifc_regression_batch_main.ts` keeps the header of whichever per-file CSV it
115
+ * reads first and concatenates the rest as rows, so a mixed IFC/STEP corpus
116
+ * mislabels every column if the two writers drift apart.
117
+ *
56
118
  * @param perfPath Path to write the CSV to. Empty string disables.
57
119
  * @param ifcFile Source IFC file path (basename used as the row key).
58
120
  * @param status OK or FAIL.
59
121
  * @param parseTimeMs Parse stage duration in ms.
60
122
  * @param geometryTimeMs Geometry extraction duration in ms.
61
123
  * @param totalTimeMs Sum of parse + geometry in ms.
124
+ * @param geometryMemoryBytes Geometry payload conway allocated, in bytes, or
125
+ * undefined where no geometry was extracted (written as N/A).
126
+ * @param wasmHeapBytes Wasm linear-memory high-water in bytes, or undefined
127
+ * where the module was never brought up (written as N/A).
62
128
  */
63
- async function writePerfCsvIfRequested(perfPath, ifcFile, status, parseTimeMs, geometryTimeMs, totalTimeMs) {
129
+ async function writePerfCsvIfRequested(perfPath, ifcFile, status, parseTimeMs, geometryTimeMs, totalTimeMs, geometryMemoryBytes, wasmHeapBytes) {
64
130
  if (perfPath.length === 0) {
65
131
  return;
66
132
  }
@@ -68,10 +134,23 @@ async function writePerfCsvIfRequested(perfPath, ifcFile, status, parseTimeMs, g
68
134
  const rssMb = (mem.rss / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
69
135
  const heapUsedMb = (mem.heapUsed / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
70
136
  const heapTotalMb = (mem.heapTotal / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
137
+ const externalMb = (mem.external / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
138
+ const arrayBuffersMb = (mem.arrayBuffers / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
139
+ // maxRSS is reported in kilobytes, unlike memoryUsage() which is in bytes.
140
+ const peakRssMb = (process.resourceUsage().maxRSS / KB_PER_MB).toFixed(PERF_MB_PRECISION);
141
+ const geometryMemoryMb = geometryMemoryBytes !== void 0 ?
142
+ (geometryMemoryBytes / BYTES_PER_MB).toFixed(PERF_MB_PRECISION) :
143
+ UNMEASURED;
144
+ const peakWasmHeapMb = wasmHeapBytes !== void 0 ?
145
+ (wasmHeapBytes / BYTES_PER_MB).toFixed(PERF_MB_PRECISION) :
146
+ UNMEASURED;
71
147
  const fileName = csvSafeString(path.basename(ifcFile));
72
- const header = "file,status,parseTimeMs,geometryTimeMs,totalTimeMs,rssMb,heapUsedMb,heapTotalMb\n";
148
+ const header = "file,status,parseTimeMs,geometryTimeMs,totalTimeMs,geometryMemoryMb," +
149
+ "peakWasmHeapMb,rssMb,peakRssMb,heapUsedMb,heapTotalMb,externalMb," +
150
+ "arrayBuffersMb\n";
73
151
  const row = `${fileName},${status},${parseTimeMs},${geometryTimeMs},${totalTimeMs},` +
74
- `${rssMb},${heapUsedMb},${heapTotalMb}\n`;
152
+ `${geometryMemoryMb},${peakWasmHeapMb},${rssMb},${peakRssMb},` +
153
+ `${heapUsedMb},${heapTotalMb},${externalMb},${arrayBuffersMb}\n`;
75
154
  try {
76
155
  await fsPromises.writeFile(perfPath, header + row);
77
156
  }
@@ -220,7 +299,19 @@ function doWork() {
220
299
  const geometryTimeMs = geomEndMs - geomStartMs;
221
300
  const totalTimeMs = geomEndMs - parseStartMs;
222
301
  const perfStatus = result === void 0 ? "FAIL" : "OK";
223
- await writePerfCsvIfRequested(perfPath, ifcFile, perfStatus, parseTimeMs, geometryTimeMs, totalTimeMs);
302
+ // Sized before anything downstream can release meshes, and only on
303
+ // the OK path: a failed extraction leaves a partial cache whose size
304
+ // is not this model's geometry footprint.
305
+ const geometryMemoryBytes = result !== void 0 ?
306
+ model.geometry.calculateGeometrySize() : void 0;
307
+ // The heap is grow-only, so this is the run's high-water mark even
308
+ // though it is read once, after the fact. geometryExtraction hands
309
+ // back the engine it brought up; there is no heap to measure when
310
+ // it failed before that.
311
+ const wasmModule = result?.[1].wasmModule;
312
+ const wasmHeapBytes = wasmModule !== void 0 ?
313
+ wasmHeapByteLength(wasmModule) : void 0;
314
+ await writePerfCsvIfRequested(perfPath, ifcFile, perfStatus, parseTimeMs, geometryTimeMs, totalTimeMs, geometryMemoryBytes, wasmHeapBytes);
224
315
  // AFTP sizing pass: no-op unless the wasm module was built with
225
316
  // CONWAY_ALLOC_TELEMETRY (see conway-geom structures/alloc_telemetry.h).
226
317
  conwayGeom.dumpAllocTelemetry(path.basename(ifcFile));
@@ -1 +1 @@
1
- {"version":3,"file":"conway_model_loader.d.ts","sourceRoot":"","sources":["../../../src/loaders/conway_model_loader.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AACrC,OAAO,EAAE,gBAAgB,EAAmB,MAAM,kBAAkB,CAAA;AACpE,OAAO,EAAE,SAAS,EAAmB,MAAM,sBAAsB,CAAA;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAerC;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAE/B;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAE7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAE1B,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAE3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAE,IAAI,EAAE,SAAS,KAAM,IAAI,CAAA;CAC1C;AAED;;;;GAIG;AACH,qBAAa,iBAAiB;IAE5B;;;;;;;;;OASG;WACiB,kBAAkB,CAClC,IAAI,EAAE,UAAU,EAChB,aAAa,GAAE,OAAc,EAC7B,eAAe,GAAE,MAAW,EAC5B,OAAO,GAAE,MAAU,EACnB,OAAO,CAAC,EAAE,gBAAgB,GAAI,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CA0c1D"}
1
+ {"version":3,"file":"conway_model_loader.d.ts","sourceRoot":"","sources":["../../../src/loaders/conway_model_loader.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AACrC,OAAO,EAAE,gBAAgB,EAAmB,MAAM,kBAAkB,CAAA;AACpE,OAAO,EAAE,SAAS,EAAmB,MAAM,sBAAsB,CAAA;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AA4CrC;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAE/B;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAE7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAE1B,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAE3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAE,IAAI,EAAE,SAAS,KAAM,IAAI,CAAA;CAC1C;AAED;;;;GAIG;AACH,qBAAa,iBAAiB;IAE5B;;;;;;;;;OASG;WACiB,kBAAkB,CAClC,IAAI,EAAE,UAAU,EAChB,aAAa,GAAE,OAAc,EAC7B,eAAe,GAAE,MAAW,EAC5B,OAAO,GAAE,MAAU,EACnB,OAAO,CAAC,EAAE,gBAAgB,GAAI,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CA8c1D"}
@@ -13,8 +13,32 @@ import Memory from "../memory/memory.js";
13
13
  import ParsingBuffer from "../parsing/parsing_buffer.js";
14
14
  import { ParseResult } from "../step/parsing/step_parser.js";
15
15
  import { extractModelInfo, parseFileHeader } from "./loading_utilities.js";
16
+ import { wasmHeapByteLength } from "../core/wasm_heap.js";
16
17
  const ONE_KB = 1024;
17
18
  const ONE_MB = ONE_KB * ONE_KB;
19
+ /**
20
+ * Record the wasm heap's high-water mark on the run's statistics.
21
+ *
22
+ * The wasm heap only grows, so one reading of it is already the peak — there
23
+ * is nothing to sample and nothing to perturb. This is the only column that
24
+ * sees conway's native memory at all: `heapUsed`/`external` cannot (wasm init
25
+ * alone moves RSS by ~65 MB while moving `external` by under 2 MB), and
26
+ * `geometryMemory` is the vertex+index payload only, which on MB-Khaya is
27
+ * 8 MB live under an 85 MB wasm heap.
28
+ *
29
+ * Measured through wasmHeapByteLength rather than HEAPU8.length: the module's
30
+ * cached view can be a growth step behind the real heap (#485), and a
31
+ * high-water figure that under-reports is worse than none.
32
+ *
33
+ * @param statistics The run's statistics.
34
+ * @param conwayWasm The geometry engine whose heap to measure.
35
+ */
36
+ function recordWasmHeapPeak(statistics, conwayWasm) {
37
+ const wasmModule = conwayWasm.wasmModule;
38
+ if (wasmModule !== void 0) {
39
+ statistics.setWasmHeapPeak(wasmHeapByteLength(wasmModule) / ONE_MB);
40
+ }
41
+ }
18
42
  /**
19
43
  * Static class for loading a model from a Uint 8 array...
20
44
  *
@@ -134,6 +158,7 @@ export class ConwayModelLoader {
134
158
  tracker?.endPhase();
135
159
  statistics.setGeometryTime(executionTimeInMs);
136
160
  statistics.setGeometryMemory(conwayModel.model.geometry.calculateGeometrySize() / (ONE_MB));
161
+ recordWasmHeapPeak(statistics, conwayWasm);
137
162
  statistics.setGeometryTypeCounts(conwayModel.geometryTypeCounts);
138
163
  model.invalidate(true);
139
164
  if (extractionResult !== ExtractResult.COMPLETE) {
@@ -264,6 +289,7 @@ export class ConwayModelLoader {
264
289
  tracker?.endPhase();
265
290
  statistics.setGeometryTime(executionTimeInMs);
266
291
  statistics.setGeometryMemory(conwayModel.model.geometry.calculateGeometrySize() / (ONE_MB));
292
+ recordWasmHeapPeak(statistics, conwayWasm);
267
293
  const ifcProjectName = conwayModel.getIfcProjectName();
268
294
  if (ifcProjectName !== null) {
269
295
  statistics.setProjectName(ifcProjectName);
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/memory/memory.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IAEX,UAAU,WAAW;QACjB,MAAM,CAAC,EAAE;YACL,eAAe,EAAE,MAAM,CAAA;YACvB,eAAe,EAAE,MAAM,CAAA;YACvB,cAAc,EAAE,MAAM,CAAA;SACzB,CAAA;KACJ;CACJ;AAKD;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,MAAM;IACzB;;;OAGG;IACH,MAAM,CAAC,gBAAgB,IAAI,MAAM;IAiBjC;;;;;;OAMG;IACH,MAAM,CAAC,UAAU,IAAI,MAAM,GAAG,SAAS;IAiBvC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAYjC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;CAY/B"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/memory/memory.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IAEX,UAAU,WAAW;QACjB,MAAM,CAAC,EAAE;YACL,eAAe,EAAE,MAAM,CAAA;YACvB,eAAe,EAAE,MAAM,CAAA;YACvB,cAAc,EAAE,MAAM,CAAA;SACzB,CAAA;KACJ;CACJ;AAKD;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,MAAM;IACzB;;;OAGG;IACH,MAAM,CAAC,gBAAgB,IAAI,MAAM;IAiBjC;;;;;;OAMG;IACH,MAAM,CAAC,UAAU,IAAI,MAAM,GAAG,SAAS;IAiBvC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAYjC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;CAoC/B"}