@bldrs-ai/conway 1.553.1520 → 1.558.1533

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 (32) hide show
  1. package/compiled/examples/browser-bundled.cjs +64 -2
  2. package/compiled/examples/cli-bundled.cjs +64 -2
  3. package/compiled/examples/cli-step-bundled.cjs +64 -2
  4. package/compiled/examples/validator-bundled.cjs +64 -2
  5. package/compiled/src/AP214E3_2010/ap214_regression_main.js +67 -14
  6. package/compiled/src/core/retained_memory.d.ts +142 -0
  7. package/compiled/src/core/retained_memory.d.ts.map +1 -0
  8. package/compiled/src/core/retained_memory.js +152 -0
  9. package/compiled/src/core/retained_memory.test.d.ts +2 -0
  10. package/compiled/src/core/retained_memory.test.d.ts.map +1 -0
  11. package/compiled/src/core/retained_memory.test.js +178 -0
  12. package/compiled/src/ifc/ifc_regression_batch_main.js +46 -1
  13. package/compiled/src/ifc/ifc_regression_main.js +123 -32
  14. package/compiled/src/ifc/ifc_regression_single_engine.test.d.ts +2 -0
  15. package/compiled/src/ifc/ifc_regression_single_engine.test.d.ts.map +1 -0
  16. package/compiled/src/ifc/ifc_regression_single_engine.test.js +61 -0
  17. package/compiled/src/loaders/conway_model_loader.d.ts.map +1 -1
  18. package/compiled/src/loaders/conway_model_loader.js +49 -0
  19. package/compiled/src/scripts/bless_perf_snapshot.test.js +108 -7
  20. package/compiled/src/scripts/perf_ab_compare.test.d.ts +2 -0
  21. package/compiled/src/scripts/perf_ab_compare.test.d.ts.map +1 -0
  22. package/compiled/src/scripts/perf_ab_compare.test.js +218 -0
  23. package/compiled/src/scripts/perf_csv_quoting.test.js +114 -12
  24. package/compiled/src/statistics/statistics.d.ts +48 -0
  25. package/compiled/src/statistics/statistics.d.ts.map +1 -1
  26. package/compiled/src/statistics/statistics.js +96 -0
  27. package/compiled/src/statistics/statistics_retained_memory.test.d.ts +2 -0
  28. package/compiled/src/statistics/statistics_retained_memory.test.d.ts.map +1 -0
  29. package/compiled/src/statistics/statistics_retained_memory.test.js +96 -0
  30. package/compiled/src/version/version.js +1 -1
  31. package/compiled/tsconfig.tsbuildinfo +1 -1
  32. 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.553.1520";
33
+ var versionString = "Conway v1.558.1533";
34
34
 
35
35
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
36
36
  var wasmType = "";
@@ -45,6 +45,10 @@ var BlendMode;
45
45
  })(BlendMode || (BlendMode = {}));
46
46
 
47
47
  // compiled/src/statistics/statistics.js
48
+ var RETAINED_MB_PRECISION = 3;
49
+ function formatRetained(value) {
50
+ return value !== void 0 ? `${value.toFixed(RETAINED_MB_PRECISION)} MB` : "N/A";
51
+ }
48
52
  var Statistics = class {
49
53
  // Getters and setters
50
54
  /**
@@ -166,6 +170,63 @@ var Statistics = class {
166
170
  setWasmHeapPeak(value) {
167
171
  this.wasmHeapPeak = value;
168
172
  }
173
+ /**
174
+ * RSS still held after the model was torn down, over a settled pre-load
175
+ * baseline (conway#554).
176
+ *
177
+ * @return {number | undefined} - retained RSS in MB, or undefined where the
178
+ * settle could not run
179
+ */
180
+ getRetainedRss() {
181
+ return this.retainedRss;
182
+ }
183
+ /**
184
+ * Both samples behind this figure are settled (`gc(); await setImmediate();
185
+ * gc()`) and both sit outside the timed region, so recording it costs the
186
+ * timing columns nothing. Leave it undefined rather than passing an
187
+ * unsettled difference: that is GC-timing noise, and it would be read as a
188
+ * leak signal.
189
+ *
190
+ * @param value - retained RSS in MB, signed
191
+ */
192
+ setRetainedRss(value) {
193
+ this.retainedRss = value;
194
+ }
195
+ /**
196
+ * V8 live heap still held after teardown, over the settled pre-load
197
+ * baseline (conway#554).
198
+ *
199
+ * @return {number | undefined} - retained heapUsed in MB, or undefined
200
+ * where the settle could not run
201
+ */
202
+ getRetainedHeapUsed() {
203
+ return this.retainedHeapUsed;
204
+ }
205
+ /**
206
+ *
207
+ * @param value - retained heapUsed in MB, signed
208
+ */
209
+ setRetainedHeapUsed(value) {
210
+ this.retainedHeapUsed = value;
211
+ }
212
+ /**
213
+ * Off-heap bytes still held after teardown, over the settled pre-load
214
+ * baseline (conway#554). This is where a released source buffer or parse
215
+ * structure that is still pinned shows up; heapUsed cannot see them.
216
+ *
217
+ * @return {number | undefined} - retained external in MB, or undefined
218
+ * where the settle could not run
219
+ */
220
+ getRetainedExternal() {
221
+ return this.retainedExternal;
222
+ }
223
+ /**
224
+ *
225
+ * @param value - retained external in MB, signed
226
+ */
227
+ setRetainedExternal(value) {
228
+ this.retainedExternal = value;
229
+ }
169
230
  /**
170
231
  *
171
232
  * @return {string | undefined} - preprocessor version or undefined
@@ -297,9 +358,10 @@ var Statistics = class {
297
358
  versionStr = "Version not defined";
298
359
  }
299
360
  const products = this.productCount !== void 0 ? `Products: ${this.productCount}, ` : "";
361
+ const retained = `Retained RSS Delta: ${formatRetained(this.retainedRss)}, Retained Heap-Used Delta: ${formatRetained(this.retainedHeapUsed)}, Retained External Delta: ${formatRetained(this.retainedExternal)}, `;
300
362
  const breakdown = this.formatGeometryTypeCounts();
301
363
  const geometryTypes = breakdown !== void 0 ? `, Geometry Types: ${breakdown}` : "";
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;
364
+ 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, ` + retained + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
303
365
  }
304
366
  };
305
367
 
@@ -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.553.1520";
14968
+ var versionString = "Conway v1.558.1533";
14969
14969
 
14970
14970
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
14971
14971
  function pThreadsAllowed() {
@@ -15719,6 +15719,10 @@ function toAlphaMode(wasmModule, blendMode) {
15719
15719
  }
15720
15720
 
15721
15721
  // compiled/src/statistics/statistics.js
15722
+ var RETAINED_MB_PRECISION = 3;
15723
+ function formatRetained(value) {
15724
+ return value !== void 0 ? `${value.toFixed(RETAINED_MB_PRECISION)} MB` : "N/A";
15725
+ }
15722
15726
  var Statistics = class {
15723
15727
  // Getters and setters
15724
15728
  /**
@@ -15840,6 +15844,63 @@ var Statistics = class {
15840
15844
  setWasmHeapPeak(value) {
15841
15845
  this.wasmHeapPeak = value;
15842
15846
  }
15847
+ /**
15848
+ * RSS still held after the model was torn down, over a settled pre-load
15849
+ * baseline (conway#554).
15850
+ *
15851
+ * @return {number | undefined} - retained RSS in MB, or undefined where the
15852
+ * settle could not run
15853
+ */
15854
+ getRetainedRss() {
15855
+ return this.retainedRss;
15856
+ }
15857
+ /**
15858
+ * Both samples behind this figure are settled (`gc(); await setImmediate();
15859
+ * gc()`) and both sit outside the timed region, so recording it costs the
15860
+ * timing columns nothing. Leave it undefined rather than passing an
15861
+ * unsettled difference: that is GC-timing noise, and it would be read as a
15862
+ * leak signal.
15863
+ *
15864
+ * @param value - retained RSS in MB, signed
15865
+ */
15866
+ setRetainedRss(value) {
15867
+ this.retainedRss = value;
15868
+ }
15869
+ /**
15870
+ * V8 live heap still held after teardown, over the settled pre-load
15871
+ * baseline (conway#554).
15872
+ *
15873
+ * @return {number | undefined} - retained heapUsed in MB, or undefined
15874
+ * where the settle could not run
15875
+ */
15876
+ getRetainedHeapUsed() {
15877
+ return this.retainedHeapUsed;
15878
+ }
15879
+ /**
15880
+ *
15881
+ * @param value - retained heapUsed in MB, signed
15882
+ */
15883
+ setRetainedHeapUsed(value) {
15884
+ this.retainedHeapUsed = value;
15885
+ }
15886
+ /**
15887
+ * Off-heap bytes still held after teardown, over the settled pre-load
15888
+ * baseline (conway#554). This is where a released source buffer or parse
15889
+ * structure that is still pinned shows up; heapUsed cannot see them.
15890
+ *
15891
+ * @return {number | undefined} - retained external in MB, or undefined
15892
+ * where the settle could not run
15893
+ */
15894
+ getRetainedExternal() {
15895
+ return this.retainedExternal;
15896
+ }
15897
+ /**
15898
+ *
15899
+ * @param value - retained external in MB, signed
15900
+ */
15901
+ setRetainedExternal(value) {
15902
+ this.retainedExternal = value;
15903
+ }
15843
15904
  /**
15844
15905
  *
15845
15906
  * @return {string | undefined} - preprocessor version or undefined
@@ -15971,9 +16032,10 @@ var Statistics = class {
15971
16032
  versionStr = "Version not defined";
15972
16033
  }
15973
16034
  const products = this.productCount !== void 0 ? `Products: ${this.productCount}, ` : "";
16035
+ const retained = `Retained RSS Delta: ${formatRetained(this.retainedRss)}, Retained Heap-Used Delta: ${formatRetained(this.retainedHeapUsed)}, Retained External Delta: ${formatRetained(this.retainedExternal)}, `;
15974
16036
  const breakdown = this.formatGeometryTypeCounts();
15975
16037
  const geometryTypes = breakdown !== void 0 ? `, Geometry Types: ${breakdown}` : "";
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;
16038
+ 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, ` + retained + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
15977
16039
  }
15978
16040
  };
15979
16041
 
@@ -15943,7 +15943,7 @@ var ParsingBuffer = class {
15943
15943
  };
15944
15944
 
15945
15945
  // compiled/src/version/version.js
15946
- var versionString = "Conway v1.553.1520";
15946
+ var versionString = "Conway v1.558.1533";
15947
15947
 
15948
15948
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
15949
15949
  function pThreadsAllowed() {
@@ -16697,6 +16697,10 @@ function toAlphaMode(wasmModule, blendMode) {
16697
16697
  }
16698
16698
 
16699
16699
  // compiled/src/statistics/statistics.js
16700
+ var RETAINED_MB_PRECISION = 3;
16701
+ function formatRetained(value) {
16702
+ return value !== void 0 ? `${value.toFixed(RETAINED_MB_PRECISION)} MB` : "N/A";
16703
+ }
16700
16704
  var Statistics = class {
16701
16705
  // Getters and setters
16702
16706
  /**
@@ -16818,6 +16822,63 @@ var Statistics = class {
16818
16822
  setWasmHeapPeak(value) {
16819
16823
  this.wasmHeapPeak = value;
16820
16824
  }
16825
+ /**
16826
+ * RSS still held after the model was torn down, over a settled pre-load
16827
+ * baseline (conway#554).
16828
+ *
16829
+ * @return {number | undefined} - retained RSS in MB, or undefined where the
16830
+ * settle could not run
16831
+ */
16832
+ getRetainedRss() {
16833
+ return this.retainedRss;
16834
+ }
16835
+ /**
16836
+ * Both samples behind this figure are settled (`gc(); await setImmediate();
16837
+ * gc()`) and both sit outside the timed region, so recording it costs the
16838
+ * timing columns nothing. Leave it undefined rather than passing an
16839
+ * unsettled difference: that is GC-timing noise, and it would be read as a
16840
+ * leak signal.
16841
+ *
16842
+ * @param value - retained RSS in MB, signed
16843
+ */
16844
+ setRetainedRss(value) {
16845
+ this.retainedRss = value;
16846
+ }
16847
+ /**
16848
+ * V8 live heap still held after teardown, over the settled pre-load
16849
+ * baseline (conway#554).
16850
+ *
16851
+ * @return {number | undefined} - retained heapUsed in MB, or undefined
16852
+ * where the settle could not run
16853
+ */
16854
+ getRetainedHeapUsed() {
16855
+ return this.retainedHeapUsed;
16856
+ }
16857
+ /**
16858
+ *
16859
+ * @param value - retained heapUsed in MB, signed
16860
+ */
16861
+ setRetainedHeapUsed(value) {
16862
+ this.retainedHeapUsed = value;
16863
+ }
16864
+ /**
16865
+ * Off-heap bytes still held after teardown, over the settled pre-load
16866
+ * baseline (conway#554). This is where a released source buffer or parse
16867
+ * structure that is still pinned shows up; heapUsed cannot see them.
16868
+ *
16869
+ * @return {number | undefined} - retained external in MB, or undefined
16870
+ * where the settle could not run
16871
+ */
16872
+ getRetainedExternal() {
16873
+ return this.retainedExternal;
16874
+ }
16875
+ /**
16876
+ *
16877
+ * @param value - retained external in MB, signed
16878
+ */
16879
+ setRetainedExternal(value) {
16880
+ this.retainedExternal = value;
16881
+ }
16821
16882
  /**
16822
16883
  *
16823
16884
  * @return {string | undefined} - preprocessor version or undefined
@@ -16949,9 +17010,10 @@ var Statistics = class {
16949
17010
  versionStr = "Version not defined";
16950
17011
  }
16951
17012
  const products = this.productCount !== void 0 ? `Products: ${this.productCount}, ` : "";
17013
+ const retained = `Retained RSS Delta: ${formatRetained(this.retainedRss)}, Retained Heap-Used Delta: ${formatRetained(this.retainedHeapUsed)}, Retained External Delta: ${formatRetained(this.retainedExternal)}, `;
16952
17014
  const breakdown = this.formatGeometryTypeCounts();
16953
17015
  const geometryTypes = breakdown !== void 0 ? `, Geometry Types: ${breakdown}` : "";
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;
17016
+ 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, ` + retained + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
16955
17017
  }
16956
17018
  };
16957
17019
 
@@ -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.553.1520";
947
+ var versionString = "Conway v1.558.1533";
948
948
 
949
949
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
950
950
  var wasmType = "";
@@ -959,6 +959,10 @@ var BlendMode;
959
959
  })(BlendMode || (BlendMode = {}));
960
960
 
961
961
  // compiled/src/statistics/statistics.js
962
+ var RETAINED_MB_PRECISION = 3;
963
+ function formatRetained(value) {
964
+ return value !== void 0 ? `${value.toFixed(RETAINED_MB_PRECISION)} MB` : "N/A";
965
+ }
962
966
  var Statistics = class {
963
967
  // Getters and setters
964
968
  /**
@@ -1080,6 +1084,63 @@ var Statistics = class {
1080
1084
  setWasmHeapPeak(value) {
1081
1085
  this.wasmHeapPeak = value;
1082
1086
  }
1087
+ /**
1088
+ * RSS still held after the model was torn down, over a settled pre-load
1089
+ * baseline (conway#554).
1090
+ *
1091
+ * @return {number | undefined} - retained RSS in MB, or undefined where the
1092
+ * settle could not run
1093
+ */
1094
+ getRetainedRss() {
1095
+ return this.retainedRss;
1096
+ }
1097
+ /**
1098
+ * Both samples behind this figure are settled (`gc(); await setImmediate();
1099
+ * gc()`) and both sit outside the timed region, so recording it costs the
1100
+ * timing columns nothing. Leave it undefined rather than passing an
1101
+ * unsettled difference: that is GC-timing noise, and it would be read as a
1102
+ * leak signal.
1103
+ *
1104
+ * @param value - retained RSS in MB, signed
1105
+ */
1106
+ setRetainedRss(value) {
1107
+ this.retainedRss = value;
1108
+ }
1109
+ /**
1110
+ * V8 live heap still held after teardown, over the settled pre-load
1111
+ * baseline (conway#554).
1112
+ *
1113
+ * @return {number | undefined} - retained heapUsed in MB, or undefined
1114
+ * where the settle could not run
1115
+ */
1116
+ getRetainedHeapUsed() {
1117
+ return this.retainedHeapUsed;
1118
+ }
1119
+ /**
1120
+ *
1121
+ * @param value - retained heapUsed in MB, signed
1122
+ */
1123
+ setRetainedHeapUsed(value) {
1124
+ this.retainedHeapUsed = value;
1125
+ }
1126
+ /**
1127
+ * Off-heap bytes still held after teardown, over the settled pre-load
1128
+ * baseline (conway#554). This is where a released source buffer or parse
1129
+ * structure that is still pinned shows up; heapUsed cannot see them.
1130
+ *
1131
+ * @return {number | undefined} - retained external in MB, or undefined
1132
+ * where the settle could not run
1133
+ */
1134
+ getRetainedExternal() {
1135
+ return this.retainedExternal;
1136
+ }
1137
+ /**
1138
+ *
1139
+ * @param value - retained external in MB, signed
1140
+ */
1141
+ setRetainedExternal(value) {
1142
+ this.retainedExternal = value;
1143
+ }
1083
1144
  /**
1084
1145
  *
1085
1146
  * @return {string | undefined} - preprocessor version or undefined
@@ -1211,9 +1272,10 @@ var Statistics = class {
1211
1272
  versionStr = "Version not defined";
1212
1273
  }
1213
1274
  const products = this.productCount !== void 0 ? `Products: ${this.productCount}, ` : "";
1275
+ const retained = `Retained RSS Delta: ${formatRetained(this.retainedRss)}, Retained Heap-Used Delta: ${formatRetained(this.retainedHeapUsed)}, Retained External Delta: ${formatRetained(this.retainedExternal)}, `;
1214
1276
  const breakdown = this.formatGeometryTypeCounts();
1215
1277
  const geometryTypes = breakdown !== void 0 ? `, Geometry Types: ${breakdown}` : "";
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;
1278
+ 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, ` + retained + products + `Memory Statistics: ${this.memoryStatistics}, Preprocessor Version: ${this.preprocessorVersion}, Originating System: ${this.originatingSystem}` + geometryTypes;
1217
1279
  }
1218
1280
  };
1219
1281
 
@@ -10,6 +10,7 @@ import path from "path";
10
10
  import crypto from "crypto";
11
11
  import { ConwayGeometry } from "../../dependencies/conway-geom/index.js";
12
12
  import { wasmHeapByteLength } from "../core/wasm_heap.js";
13
+ import { retainedMemoryMb, settleAndSampleMemoryForPerf, } from "../core/retained_memory.js";
13
14
  import Logger from "../logging/logger.js";
14
15
  import Environment from "../utilities/environment.js";
15
16
  import { ExtractResult } from "../core/shared_constants.js";
@@ -44,6 +45,21 @@ const KB_PER_MB = 1024;
44
45
  const PERF_MB_PRECISION = 2;
45
46
  /** Placeholder for a column this row has no measurement for. */
46
47
  const UNMEASURED = "N/A";
48
+ /**
49
+ * Capture the end-of-load memory figures for a perf row.
50
+ *
51
+ * @param geometryMemoryBytes Geometry payload conway allocated, in bytes.
52
+ * @param wasmHeapBytes Wasm linear-memory high-water in bytes.
53
+ * @return {PerfMemory} The captured figures, with retention still unset.
54
+ */
55
+ function capturePerfMemory(geometryMemoryBytes, wasmHeapBytes) {
56
+ return {
57
+ instant: process.memoryUsage(),
58
+ peakRssKb: process.resourceUsage().maxRSS,
59
+ geometryMemoryBytes,
60
+ wasmHeapBytes,
61
+ };
62
+ }
47
63
  /**
48
64
  * Write a single-row per-file perf CSV at the given path, matching the
49
65
  * column layout of the IFC regression child so the batch aggregator can
@@ -58,42 +74,55 @@ const UNMEASURED = "N/A";
58
74
  * (`src/ifc/ifc_regression_main.ts`), which is the writer these columns have
59
75
  * to stay identical to.
60
76
  *
77
+ * `retainedRssMb` / `retainedHeapUsedMb` / `retainedExternalMb` are the other
78
+ * half (conway#554): settled-after-teardown minus settled-before-load, so a
79
+ * signed measure of what one cycle left behind, and `N/A` where `global.gc`
80
+ * was not exposed to settle with. No `retainedWasmHeapMb` — the wasm heap is
81
+ * grow-only, so over one cycle it could only restate `peakWasmHeapMb`.
82
+ *
61
83
  * @param perfPath Path to write the CSV to. Empty string disables.
62
84
  * @param stepFile Source STEP file path (basename used as the row key).
63
85
  * @param status OK or FAIL.
64
86
  * @param parseTimeMs Parse stage duration in ms.
65
87
  * @param geometryTimeMs Geometry extraction duration in ms.
66
88
  * @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).
89
+ * @param memory The row's memory figures, captured at the points in the load
90
+ * each is defined at. Defaults to capturing the end-of-load instants here,
91
+ * which is what the FAIL paths want — they have nothing else to report.
71
92
  */
72
- async function writePerfCsvIfRequested(perfPath, stepFile, status, parseTimeMs, geometryTimeMs, totalTimeMs, geometryMemoryBytes, wasmHeapBytes) {
93
+ async function writePerfCsvIfRequested(perfPath, stepFile, status, parseTimeMs, geometryTimeMs, totalTimeMs, memory = capturePerfMemory()) {
73
94
  if (perfPath.length === 0) {
74
95
  return;
75
96
  }
76
- const mem = process.memoryUsage();
97
+ const mem = memory.instant;
77
98
  const rssMb = (mem.rss / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
78
99
  const heapUsedMb = (mem.heapUsed / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
79
100
  const heapTotalMb = (mem.heapTotal / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
80
101
  const externalMb = (mem.external / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
81
102
  const arrayBuffersMb = (mem.arrayBuffers / BYTES_PER_MB).toFixed(PERF_MB_PRECISION);
82
103
  // 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) :
104
+ const peakRssMb = (memory.peakRssKb / KB_PER_MB).toFixed(PERF_MB_PRECISION);
105
+ const geometryMemoryMb = memory.geometryMemoryBytes !== void 0 ?
106
+ (memory.geometryMemoryBytes / BYTES_PER_MB).toFixed(PERF_MB_PRECISION) :
86
107
  UNMEASURED;
87
- const peakWasmHeapMb = wasmHeapBytes !== void 0 ?
88
- (wasmHeapBytes / BYTES_PER_MB).toFixed(PERF_MB_PRECISION) :
108
+ const peakWasmHeapMb = memory.wasmHeapBytes !== void 0 ?
109
+ (memory.wasmHeapBytes / BYTES_PER_MB).toFixed(PERF_MB_PRECISION) :
89
110
  UNMEASURED;
111
+ const retained = memory.retained;
112
+ const retainedRssMb = retained !== void 0 ?
113
+ retained.rssMb.toFixed(PERF_MB_PRECISION) : UNMEASURED;
114
+ const retainedHeapUsedMb = retained !== void 0 ?
115
+ retained.heapUsedMb.toFixed(PERF_MB_PRECISION) : UNMEASURED;
116
+ const retainedExternalMb = retained !== void 0 ?
117
+ retained.externalMb.toFixed(PERF_MB_PRECISION) : UNMEASURED;
90
118
  const fileName = csvSafeString(path.basename(stepFile));
91
119
  const header = "file,status,parseTimeMs,geometryTimeMs,totalTimeMs,geometryMemoryMb," +
92
120
  "peakWasmHeapMb,rssMb,peakRssMb,heapUsedMb,heapTotalMb,externalMb," +
93
- "arrayBuffersMb\n";
121
+ "arrayBuffersMb,retainedRssMb,retainedHeapUsedMb,retainedExternalMb\n";
94
122
  const row = `${fileName},${status},${parseTimeMs},${geometryTimeMs},${totalTimeMs},` +
95
123
  `${geometryMemoryMb},${peakWasmHeapMb},${rssMb},${peakRssMb},` +
96
- `${heapUsedMb},${heapTotalMb},${externalMb},${arrayBuffersMb}\n`;
124
+ `${heapUsedMb},${heapTotalMb},${externalMb},${arrayBuffersMb},` +
125
+ `${retainedRssMb},${retainedHeapUsedMb},${retainedExternalMb}\n`;
97
126
  try {
98
127
  await fsPromises.writeFile(perfPath, header + row);
99
128
  }
@@ -172,6 +201,18 @@ function doWork() {
172
201
  const strict = argv["strict"] ?? false;
173
202
  const digest = argv["digest"] ?? false;
174
203
  const perfPath = argv["perf"] ?? "";
204
+ // Settled pre-load baseline for the retention columns (conway#554).
205
+ // Here rather than at process start: `main()` has already brought
206
+ // conway-geom up, so the wasm module's fixed cost sits below the
207
+ // baseline instead of being charged to every model as the same
208
+ // constant. Before `readFileSync` because the source buffer is part of
209
+ // what a load must give back, and outside the timed region because
210
+ // `parseStartMs` has not been taken yet.
211
+ //
212
+ // `...ForPerf` skips the settle when no perf row is coming, for the
213
+ // reason recorded on that function: the batch passes `--expose-gc` to
214
+ // every child, not just the ones given `--perf`.
215
+ const memoryBaseline = await settleAndSampleMemoryForPerf(perfPath);
175
216
  try {
176
217
  stepBuffer = fs.readFileSync(stepFile);
177
218
  }
@@ -249,7 +290,19 @@ function doWork() {
249
290
  const wasmModule = conwayGeom.wasmModule;
250
291
  const wasmHeapBytes = wasmModule !== void 0 ?
251
292
  wasmHeapByteLength(wasmModule) : void 0;
252
- await writePerfCsvIfRequested(perfPath, stepFile, perfStatus, parseTimeMs, geometryTimeMs, totalTimeMs, geometryMemoryBytes, wasmHeapBytes);
293
+ // Instants captured here, where they have always been captured, so
294
+ // the teardown below cannot change what rssMb/heapUsedMb/externalMb
295
+ // mean.
296
+ const memory = capturePerfMemory(geometryMemoryBytes, wasmHeapBytes);
297
+ // Teardown, then the settled retained sample. This path had no
298
+ // explicit release before conway#554 — see the matching comment in
299
+ // the IFC child, which records that finding in full.
300
+ // Not gated on `perfPath` — the digest runs after it, and one path to
301
+ // the blessed output beats saving the work. The settle below IS
302
+ // gated; see `memoryBaseline` above.
303
+ model.invalidate(true);
304
+ memory.retained = retainedMemoryMb(memoryBaseline, await settleAndSampleMemoryForPerf(perfPath));
305
+ await writePerfCsvIfRequested(perfPath, stepFile, perfStatus, parseTimeMs, geometryTimeMs, totalTimeMs, memory);
253
306
  // AFTP sizing pass: no-op unless the wasm module was built with
254
307
  // CONWAY_ALLOC_TELEMETRY (see conway-geom structures/alloc_telemetry.h).
255
308
  conwayGeom.dumpAllocTelemetry(path.basename(stepFile));
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Settled process-memory sampling for the retention columns (conway#554).
3
+ *
4
+ * The perf bench's other memory columns answer "does this survive?" — peaks,
5
+ * and end-of-load instants. This one answers "do we leak?": what is still
6
+ * held after a load has been torn down. Share loads models repeatedly into
7
+ * one long-lived tab, so retention compounds in a way a single load never
8
+ * shows, and it is invisible to every peak. See
9
+ * design/new/perf-measurement.md §"Peak and delta answer different questions".
10
+ *
11
+ * A retention figure is a DIFFERENCE between two samples, so both of them
12
+ * have to be settled or the difference is GC timing rather than retention.
13
+ * SKYLARK250 reads 2547 MB un-GC'd against 981 MB as post-GC live heap — a
14
+ * 2.6x spread from sampling alone, which is larger than any leak this is
15
+ * meant to find. Hence `settleAndSampleMemory` is the only sampler here:
16
+ * there is deliberately no un-settled variant to reach for.
17
+ *
18
+ * When `global.gc` is not exposed the settle cannot run, and every caller
19
+ * must emit N/A rather than a number. That is why the sampler returns
20
+ * `undefined` instead of falling back to an unsettled `process.memoryUsage()`
21
+ * — an unsettled retention figure is noise wearing a number, and would be
22
+ * read as a leak signal. Same family as the `parseValue` -> `0.0`
23
+ * fabrication conway#548 fixed.
24
+ */
25
+ /**
26
+ * A settled sample of the three process-memory quantities a retention delta
27
+ * is taken over. Bytes, straight out of `process.memoryUsage()`.
28
+ *
29
+ * `heapUsed` and `external` are disjoint (`external` holds the ArrayBuffer
30
+ * backing stores and other off-heap bytes V8 knows about) and neither sees
31
+ * the wasm heap, which is why RSS is here as well rather than being derived.
32
+ */
33
+ export interface SettledMemorySample {
34
+ /** Resident set size in bytes. */
35
+ rssBytes: number;
36
+ /** V8 live heap in bytes, post-collection. */
37
+ heapUsedBytes: number;
38
+ /** Off-heap bytes V8 accounts for, post-collection. */
39
+ externalBytes: number;
40
+ }
41
+ /** A retention delta, in MB, over one load/teardown cycle. */
42
+ export interface RetainedMemoryMb {
43
+ /** RSS held after teardown, over the pre-load baseline. */
44
+ rssMb: number;
45
+ /** V8 live heap held after teardown, over the pre-load baseline. */
46
+ heapUsedMb: number;
47
+ /** Off-heap bytes held after teardown, over the pre-load baseline. */
48
+ externalMb: number;
49
+ }
50
+ /**
51
+ * The forced-collection hook, when the host exposes one.
52
+ *
53
+ * Node only defines `global.gc` under `--expose-gc`; browsers only under
54
+ * `--js-flags="--expose-gc"`. Absence is the normal case, not an error.
55
+ *
56
+ * @return {(() => void) | undefined} The collector, or undefined where the
57
+ * host does not expose one.
58
+ */
59
+ export declare function exposedGc(): (() => void) | undefined;
60
+ /**
61
+ * Whether a settled sample can be taken at all in this process, without
62
+ * paying for a collection to find out.
63
+ *
64
+ * No production caller: every call site instead calls
65
+ * `settleAndSampleMemory` and treats `undefined` as N/A, which is one branch
66
+ * rather than two and cannot drift out of agreement with the sampler. This
67
+ * stays exported as the cheap probe for tests that need to assert the
68
+ * no-collector path without a collector present.
69
+ *
70
+ * @return {boolean} True where both the collector and node's memory
71
+ * accounting are available.
72
+ */
73
+ export declare function canSettleMemory(): boolean;
74
+ /**
75
+ * Settle the heap, then sample it.
76
+ *
77
+ * The settle is `gc(); await setImmediate(); gc()`, and each part earns its
78
+ * place. Two collections catch objects whose freeing makes more garbage
79
+ * unreachable. The interleaved turn is what lets queued frees actually run —
80
+ * notably ArrayBuffer backing-store release, without which `external` reads
81
+ * high, which matters here because `external` is one of the three sampled
82
+ * quantities. One GC plus an idle turn is not enough on its own (V8's GC has
83
+ * concurrent phases, so a turn does not imply a finished collection), and two
84
+ * back-to-back GCs with no turn between is not enough either.
85
+ *
86
+ * This perturbs timing, so every call site must sit OUTSIDE the region that
87
+ * produces `parseTimeMs` / `geometryTimeMs` / `totalTimeMs` — baseline before
88
+ * the load starts, retained sample after teardown. That property is what
89
+ * makes the retention columns free, and it is checkable by measurement: a run
90
+ * with `--expose-gc` and a run without share identical code, so any movement
91
+ * in the timing columns between them is down to the settle. The SIGN says
92
+ * which way: gc-on slower is the settle leaking into the measured window,
93
+ * which is a bug; gc-on faster is the pre-load settle taking engine-init
94
+ * garbage OUT of that window. That is an absolute cost of about 10 ms per
95
+ * load, measured as 13-16% of `parseTimeMs` on models that parse in ~60 ms
96
+ * and unresolvable against one that parses in 578 ms, so read a ratio
97
+ * against the model's own parse time. `rc-regression.yml` runs both
98
+ * conditions in one job; see design/new/perf-measurement.md §"The settle
99
+ * also cleans the window".
100
+ *
101
+ * @return {Promise<SettledMemorySample | undefined>} The settled sample, or
102
+ * undefined where no collector is exposed — in which case the caller emits
103
+ * N/A. Never returns an unsettled sample.
104
+ */
105
+ export declare function settleAndSampleMemory(): Promise<SettledMemorySample | undefined>;
106
+ /**
107
+ * The settled sample for a perf row, taken only when a perf row is coming.
108
+ *
109
+ * The gate is not an optimisation detail, it is the difference between
110
+ * charging a cost to a run that uses it and charging it to one that does not.
111
+ * `ifc_regression_batch_main.ts` launches EVERY regression child with
112
+ * `--expose-gc`, not only the ones given `--perf`, so a child that settles
113
+ * unconditionally makes a digest-only run pay two forced full collections per
114
+ * model for a figure `writePerfCsvIfRequested` immediately discards. Measured
115
+ * on node 22: one settle costs ~0.8 s on a 500 MB live heap and ~1.6 s on a
116
+ * 1 GB one.
117
+ *
118
+ * It lives here rather than inlined at each call site so both regression
119
+ * children share one definition of "is retention wanted", and so it can be
120
+ * pinned by a test — an unnecessary settle is invisible in the output, so
121
+ * nothing downstream would catch a regression to an unconditional one.
122
+ *
123
+ * @param perfPath The child's `--perf` destination. Empty means no perf row
124
+ * is being written, so no sample is taken and no collection is forced.
125
+ * @return {Promise<SettledMemorySample | undefined>} The settled sample, or
126
+ * undefined when no perf row is wanted or no collector is exposed.
127
+ */
128
+ export declare function settleAndSampleMemoryForPerf(perfPath: string): Promise<SettledMemorySample | undefined>;
129
+ /**
130
+ * Difference two settled samples into the retention columns.
131
+ *
132
+ * Deltas are signed and may legitimately come out negative: a cycle can end
133
+ * with the allocator holding less than the baseline did, and rounding that up
134
+ * to zero would hide exactly the direction a fix is trying to move.
135
+ *
136
+ * @param baseline The settled sample taken before the load.
137
+ * @param retained The settled sample taken after teardown.
138
+ * @return {RetainedMemoryMb | undefined} The retention delta in MB, or
139
+ * undefined when either sample is missing (the settle could not run).
140
+ */
141
+ export declare function retainedMemoryMb(baseline: SettledMemorySample | undefined, retained: SettledMemorySample | undefined): RetainedMemoryMb | undefined;
142
+ //# sourceMappingURL=retained_memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retained_memory.d.ts","sourceRoot":"","sources":["../../../src/core/retained_memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AASH;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAElC,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAA;IAEhB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAA;IAErB,uDAAuD;IACvD,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAE/B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IAEb,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAA;IAElB,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,IAAI,CAAE,MAAM,IAAI,CAAE,GAAG,SAAS,CAKtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAKzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,qBAAqB,IACvC,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAuB3C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,4BAA4B,CAC9C,QAAQ,EAAE,MAAM,GAAI,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAG/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC5B,QAAQ,EAAE,mBAAmB,GAAG,SAAS,EACzC,QAAQ,EAAE,mBAAmB,GAAG,SAAS,GAAI,gBAAgB,GAAG,SAAS,CAa5E"}