sparkforensics-cli 0.1.0

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 (60) hide show
  1. package/bin/sparkforensics-analyze.mjs +288 -0
  2. package/package.json +29 -0
  3. package/vendor-core/analyzer.js +167 -0
  4. package/vendor-core/assert-never.js +3 -0
  5. package/vendor-core/cli/budgets.js +203 -0
  6. package/vendor-core/cli/collect-run.js +107 -0
  7. package/vendor-core/core-count.js +68 -0
  8. package/vendor-core/core-locality-ratio.js +54 -0
  9. package/vendor-core/core-time-series.js +92 -0
  10. package/vendor-core/core-usage-locality.js +70 -0
  11. package/vendor-core/detectors.js +1989 -0
  12. package/vendor-core/docs-config.js +72 -0
  13. package/vendor-core/docs-site-config.js +23 -0
  14. package/vendor-core/efficiency-model.js +62 -0
  15. package/vendor-core/etl-phases.js +28 -0
  16. package/vendor-core/event-handlers.js +906 -0
  17. package/vendor-core/event-schemas.js +405 -0
  18. package/vendor-core/evidence-availability.js +121 -0
  19. package/vendor-core/evidence-report.js +459 -0
  20. package/vendor-core/finding-action-label.js +97 -0
  21. package/vendor-core/finding-filter-predicate.js +38 -0
  22. package/vendor-core/format-utils.js +167 -0
  23. package/vendor-core/impact-band.js +50 -0
  24. package/vendor-core/impact-estimator.js +428 -0
  25. package/vendor-core/ingest.js +139 -0
  26. package/vendor-core/job-groups.js +30 -0
  27. package/vendor-core/load-vendored.js +24 -0
  28. package/vendor-core/lz4-block.js +135 -0
  29. package/vendor-core/mcp-error.js +3 -0
  30. package/vendor-core/mcp-server-factory.js +115 -0
  31. package/vendor-core/mcp-tools.js +331 -0
  32. package/vendor-core/model-assembler.js +76 -0
  33. package/vendor-core/occupancy.js +202 -0
  34. package/vendor-core/parser-worker.js +249 -0
  35. package/vendor-core/plan-dot.js +25 -0
  36. package/vendor-core/plan-duration-attribution.js +185 -0
  37. package/vendor-core/plan-graph-model.js +171 -0
  38. package/vendor-core/plan-node-detail.js +159 -0
  39. package/vendor-core/plan-summary.js +233 -0
  40. package/vendor-core/plan-tree-walk.js +29 -0
  41. package/vendor-core/proxy.js +157 -0
  42. package/vendor-core/recommendation-rollup.js +197 -0
  43. package/vendor-core/redact.js +175 -0
  44. package/vendor-core/rolling-log-reassembly.js +52 -0
  45. package/vendor-core/run-aggregates.js +44 -0
  46. package/vendor-core/run-comparison.js +458 -0
  47. package/vendor-core/scaling-sim.js +73 -0
  48. package/vendor-core/session-snapshot.js +79 -0
  49. package/vendor-core/shs-fetch.js +196 -0
  50. package/vendor-core/shs-load.js +121 -0
  51. package/vendor-core/shs-request.js +101 -0
  52. package/vendor-core/shs-schemas.js +13 -0
  53. package/vendor-core/snappy-block.js +140 -0
  54. package/vendor-core/stage-quantiles.js +199 -0
  55. package/vendor-core/threshold-summary.js +35 -0
  56. package/vendor-core/types.js +286 -0
  57. package/vendor-core/vendor/fflate.js +2695 -0
  58. package/vendor-core/vendor/fzstd.js +768 -0
  59. package/vendor-core/wall-clock.js +36 -0
  60. package/vendor-core/wasted-core-hours.js +68 -0
@@ -0,0 +1,36 @@
1
+ export function mergeIntervals(intervals ) {
2
+ if (intervals.length === 0) return [];
3
+ const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
4
+ const out = [[sorted[0][0], sorted[0][1]]];
5
+ for (let i = 1; i < sorted.length; i++) {
6
+ const last = out[out.length - 1];
7
+ const cur = sorted[i];
8
+ if (cur[0] <= last[1]) last[1] = Math.max(last[1], cur[1]);
9
+ else out.push([cur[0], cur[1]]);
10
+ }
11
+ return out;
12
+ }
13
+
14
+ export function computeWallClock(app , stages ) {
15
+ const start = app?.startTime ?? 0;
16
+ const stageList = [...stages.values()].filter(s => s.submittedAt != null && s.completedAt != null);
17
+ const intervals = stageList.map(s => [s.submittedAt , s.completedAt ]);
18
+ const explicitEnd = app?.endTime ?? null;
19
+ const end = explicitEnd ?? (intervals.length > 0 ? intervals.reduce((m, i) => Math.max(m, i[1]), -Infinity) : start);
20
+ const total = Math.max(0, end - start);
21
+
22
+ if (intervals.length === 0) {
23
+ return { total, startup: total, stagesActive: 0, gaps: 0, idle: 0 };
24
+ }
25
+
26
+ const merged = mergeIntervals(intervals );
27
+ const stagesActive = merged.reduce((sum, [a, b]) => sum + (b - a), 0);
28
+ const firstStageStart = merged[0][0];
29
+ const startup = Math.max(0, firstStageStart - start);
30
+ let gaps = 0;
31
+ for (let i = 1; i < merged.length; i++) gaps += merged[i][0] - merged[i - 1][1];
32
+ const lastStageEnd = merged[merged.length - 1][1];
33
+ const idle = Math.max(0, end - lastStageEnd);
34
+
35
+ return { total, startup, stagesActive, gaps, idle };
36
+ }
@@ -0,0 +1,68 @@
1
+ // Wasted core-hours summary (ELV-023). Pure, worker-agnostic reducer over the
2
+ // same inputs the memoryUtilization detector's idle-cores math uses
3
+ // (src/detectors.js ~608-619): capacity core-time vs. core-time that actually
4
+ // ran tasks. No new detector, no new tag: this only feeds a report widget.
5
+
6
+ import { computeTotalCores } from './core-count.js';
7
+
8
+ export const MS_PER_CORE_HOUR = 3.6e6;
9
+ const TOP_N = 5;
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+ const EMPTY = {
20
+ totalCoreHours: null,
21
+ usefulCoreHours: null,
22
+ wastedCoreHours: null,
23
+ totalCores: null,
24
+ topStages: [],
25
+ };
26
+
27
+
28
+
29
+
30
+
31
+
32
+ // `app` and `runAggregates` are typed nullable (beyond the brief's bare
33
+ // signature) because real callers do pass null here (see
34
+ // tests/wasted-core-hours.test.js and the WastedCoreHours widget, where
35
+ // appModel.app is SparkAppInfo | null before parsing completes); the
36
+ // optional-chaining/`!runAggregates` guard below already tolerated this at
37
+ // runtime. `resources` is likewise added to app's shape (not in the brief's
38
+ // signature) so the pass-through to computeTotalCores type-checks; real app
39
+ // objects always carry it alongside startTime/endTime.
40
+ export function computeWastedCoreHours(
41
+ app ,
42
+ executorsAdded = [],
43
+ runAggregates ,
44
+ ) {
45
+ // Nullish (not falsy) guard on times: a literal startTime:0 is valid.
46
+ if (!runAggregates || app?.startTime == null || app?.endTime == null) return EMPTY;
47
+ const appDurationMs = app.endTime - app.startTime;
48
+ if (appDurationMs <= 0) return EMPTY;
49
+
50
+ // Total cores: prefer real Executor-Added Total Cores, else peakExecutors ×
51
+ // configured cores (same fallback as the memoryUtilization detector).
52
+ const totalCores = computeTotalCores(app, executorsAdded);
53
+ if (totalCores <= 0) return EMPTY;
54
+
55
+ const totalCoreHours = (totalCores * appDurationMs) / MS_PER_CORE_HOUR;
56
+ const usefulCoreHours = (runAggregates.busyCoreMs ?? 0) / MS_PER_CORE_HOUR;
57
+ // Clamp: totalCores can be a fallback estimate while busyCoreMs is measured
58
+ // from task events, so the difference can dip below zero (same guard as
59
+ // src/efficiency-model.js).
60
+ const wastedCoreHours = Math.max(0, totalCoreHours - usefulCoreHours);
61
+
62
+ const topStages = Object.entries(runAggregates.perStage ?? {})
63
+ .map(([stageId, s]) => ({ stageId: Number(stageId), coreMs: s?.totalTaskDurationSum ?? 0 }))
64
+ .sort((a, b) => b.coreMs - a.coreMs)
65
+ .slice(0, TOP_N);
66
+
67
+ return { totalCoreHours, usefulCoreHours, wastedCoreHours, totalCores, topStages };
68
+ }