bulltrackers-module 1.0.279 → 1.0.280

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.
@@ -3,6 +3,7 @@
3
3
  * UPDATED: Implements Batch Flushing to prevent OOM on large datasets.
4
4
  * UPDATED: Removes manual global.gc() calls.
5
5
  * UPDATED: Manages incremental sharding states.
6
+ * UPDATED (IDEA 2): Implemented Computation Profiler (timings).
6
7
  */
7
8
  const { normalizeName } = require('../utils/utils');
8
9
  const { streamPortfolioData, streamHistoryData, getPortfolioPartRefs } = require('../utils/data_loader');
@@ -10,6 +11,7 @@ const { CachedDataLoader } = require
10
11
  const { ContextFactory } = require('../context/ContextFactory');
11
12
  const { commitResults } = require('../persistence/ResultCommitter');
12
13
  const mathLayer = require('../layers/index');
14
+ const { performance } = require('perf_hooks');
13
15
 
14
16
  class StandardExecutor {
15
17
  static async run(date, calcs, passName, config, deps, rootData, fetchedDeps, previousFetchedDeps, skipStatusWrite = false) {
@@ -53,20 +55,31 @@ class StandardExecutor {
53
55
 
54
56
  logger.log('INFO', `[${passName}] Streaming for ${streamingCalcs.length} computations...`);
55
57
 
56
- // Metrics & State Tracking
58
+ // [IDEA 2] Metrics & State Tracking
57
59
  const executionStats = {};
58
60
  const shardIndexMap = {}; // Tracks sharding offsets per calculation
59
61
  const aggregatedSuccess = {};
60
62
  const aggregatedFailures = [];
61
63
 
64
+ // Initialize Timing Stats per calculation
62
65
  Object.keys(state).forEach(name => {
63
- executionStats[name] = { processedUsers: 0, skippedUsers: 0 };
66
+ executionStats[name] = {
67
+ processedUsers: 0,
68
+ skippedUsers: 0,
69
+ timings: { setup: 0, stream: 0, processing: 0 } // New
70
+ };
64
71
  shardIndexMap[name] = 0;
65
72
  });
66
73
 
74
+ // [IDEA 2] Measure Setup Time
75
+ const startSetup = performance.now();
67
76
  const cachedLoader = new CachedDataLoader(config, deps);
68
77
  await cachedLoader.loadMappings();
78
+ const setupDuration = performance.now() - startSetup;
69
79
 
80
+ // Distribute setup time
81
+ Object.keys(executionStats).forEach(name => executionStats[name].timings.setup += setupDuration);
82
+
70
83
  const prevDate = new Date(dateStr + 'T00:00:00Z'); prevDate.setUTCDate(prevDate.getUTCDate() - 1);
71
84
  const prevDateStr = prevDate.toISOString().slice(0, 10);
72
85
 
@@ -83,13 +96,19 @@ class StandardExecutor {
83
96
  let usersSinceLastFlush = 0;
84
97
 
85
98
  try {
99
+ // [IDEA 2] Loop wrapper for profiling
86
100
  for await (const tP_chunk of tP_iter) {
101
+ // [IDEA 2] Measure Streaming Time (Gap between processing chunks)
102
+ const startStream = performance.now();
87
103
  if (yP_iter) yP_chunk = (await yP_iter.next()).value || {};
88
104
  if (tH_iter) tH_chunk = (await tH_iter.next()).value || {};
89
-
105
+ const streamDuration = performance.now() - startStream;
106
+ Object.keys(executionStats).forEach(name => executionStats[name].timings.stream += streamDuration);
107
+
90
108
  const chunkSize = Object.keys(tP_chunk).length;
91
109
 
92
- // Execute chunk for all calcs
110
+ // [IDEA 2] Measure Processing Time
111
+ const startProcessing = performance.now();
93
112
  const promises = streamingCalcs.map(calc =>
94
113
  StandardExecutor.executePerUser(
95
114
  calc, calc.manifest, dateStr, tP_chunk, yP_chunk, tH_chunk,
@@ -98,6 +117,10 @@ class StandardExecutor {
98
117
  )
99
118
  );
100
119
  await Promise.all(promises);
120
+ const procDuration = performance.now() - startProcessing;
121
+
122
+ // Assign processing time (Note: Parallel execution means total wall time is shared)
123
+ Object.keys(executionStats).forEach(name => executionStats[name].timings.processing += procDuration);
101
124
 
102
125
  usersSinceLastFlush += chunkSize;
103
126
 
@@ -161,7 +184,7 @@ class StandardExecutor {
161
184
  transformedState[name] = {
162
185
  manifest: inst.manifest,
163
186
  getResult: async () => dataToCommit,
164
- _executionStats: executionStats[name] // Attach current stats
187
+ _executionStats: executionStats[name] // Attach current stats including timings
165
188
  };
166
189
 
167
190
  // ⚠️ CRITICAL: CLEAR MEMORY
@@ -196,6 +219,18 @@ class StandardExecutor {
196
219
  successAcc[name].metrics.storage.keys += (update.metrics.storage.keys || 0);
197
220
  successAcc[name].metrics.storage.shardCount = Math.max(successAcc[name].metrics.storage.shardCount, update.metrics.storage.shardCount || 1);
198
221
  }
222
+
223
+ // [IDEA 2] Sum timing metrics
224
+ if (update.metrics?.execution?.timings) {
225
+ if (!successAcc[name].metrics.execution) successAcc[name].metrics.execution = { timings: { setup:0, stream:0, processing:0 }};
226
+ const tDest = successAcc[name].metrics.execution.timings;
227
+ const tSrc = update.metrics.execution.timings;
228
+
229
+ tDest.setup += (tSrc.setup || 0);
230
+ tDest.stream += (tSrc.stream || 0);
231
+ tDest.processing += (tSrc.processing || 0);
232
+ }
233
+
199
234
  // Keep the latest hash/composition info
200
235
  successAcc[name].hash = update.hash;
201
236
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Utility for recording computation run attempts (The Audit Logger).
3
3
  * UPDATED: Stores 'trigger' reason and 'execution' stats.
4
+ * UPDATED (IDEA 2): Stores granular timing profiles.
4
5
  */
5
6
 
6
7
  const { FieldValue } = require('../utils/utils');
@@ -37,6 +38,10 @@ async function recordRunAttempt(db, context, status, error = null, detailedMetri
37
38
  const anomalies = detailedMetrics.validation?.anomalies || [];
38
39
  if (error && error.message && error.message.includes('Data Integrity')) { anomalies.push(error.message); }
39
40
 
41
+ // [IDEA 2] Prepare Execution Stats & Timings
42
+ const rawExecStats = detailedMetrics.execution || {};
43
+ const timings = rawExecStats.timings || {};
44
+
40
45
  const runEntry = {
41
46
  runId: runId,
42
47
  computationName: computation,
@@ -53,8 +58,17 @@ async function recordRunAttempt(db, context, status, error = null, detailedMetri
53
58
  type: (triggerReason && triggerReason.includes('Layer')) ? 'CASCADE' : ((triggerReason && triggerReason.includes('New')) ? 'INIT' : 'UPDATE')
54
59
  },
55
60
 
56
- // [NEW] Execution Stats (Internal Loop Data)
57
- executionStats: detailedMetrics.execution || {},
61
+ // [IDEA 2] Enhanced Execution Stats
62
+ executionStats: {
63
+ processedUsers: rawExecStats.processedUsers || 0,
64
+ skippedUsers: rawExecStats.skippedUsers || 0,
65
+ // Explicitly break out timings for BigQuery/Analysis
66
+ timings: {
67
+ setupMs: Math.round(timings.setup || 0),
68
+ streamMs: Math.round(timings.stream || 0),
69
+ processingMs: Math.round(timings.processing || 0)
70
+ }
71
+ },
58
72
 
59
73
  outputStats: {
60
74
  sizeMB: sizeMB,
@@ -64,7 +78,7 @@ async function recordRunAttempt(db, context, status, error = null, detailedMetri
64
78
  },
65
79
 
66
80
  anomalies: anomalies,
67
- _schemaVersion: '2.1'
81
+ _schemaVersion: '2.2' // Bumped for profiler
68
82
  };
69
83
 
70
84
  if (error) {
@@ -4,6 +4,7 @@
4
4
  * REFACTORED: Strict 5-category reporting with date-based exclusion logic.
5
5
  * UPDATED: Replaced Batch Writes with Parallel Writes to prevent DEADLINE_EXCEEDED timeouts.
6
6
  * FIXED: Ensures 'latest' pointer updates even if detail writes fail.
7
+ * UPDATED (IDEA 1): Added Dependency Impact Analysis ("Blast Radius").
7
8
  */
8
9
 
9
10
  const { analyzeDateExecution } = require('../WorkflowOrchestrator');
@@ -41,6 +42,34 @@ function isDateBeforeAvailability(dateStr, calcManifest) {
41
42
  return false;
42
43
  }
43
44
 
45
+ /**
46
+ * Helper: Calculates the transitive closure of dependents (Blast Radius).
47
+ * Returns the count of direct and total cascading dependents.
48
+ */
49
+ function calculateBlastRadius(targetCalcName, reverseGraph) {
50
+ const impactSet = new Set();
51
+ const queue = [targetCalcName];
52
+
53
+ // BFS Traversal
54
+ while(queue.length > 0) {
55
+ const current = queue.shift();
56
+ const dependents = reverseGraph.get(current) || [];
57
+
58
+ dependents.forEach(child => {
59
+ if (!impactSet.has(child)) {
60
+ impactSet.add(child);
61
+ queue.push(child);
62
+ }
63
+ });
64
+ }
65
+
66
+ return {
67
+ directDependents: (reverseGraph.get(targetCalcName) || []).length,
68
+ totalCascadingDependents: impactSet.size,
69
+ affectedCalculations: Array.from(impactSet).slice(0, 50) // Cap list size for storage safety
70
+ };
71
+ }
72
+
44
73
  /**
45
74
  * AUTO-RUN ENTRY POINT
46
75
  * Uses transactional locking to prevent race conditions.
@@ -100,6 +129,19 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
100
129
  const datesToCheck = getExpectedDateStrings(startDate, today);
101
130
  const manifestMap = new Map(manifest.map(c => [normalizeName(c.name), c]));
102
131
 
132
+ // [IDEA 1] Build Reverse Dependency Graph (Parent -> Children)
133
+ const reverseGraph = new Map();
134
+ manifest.forEach(c => {
135
+ const parentName = normalizeName(c.name);
136
+ if (c.dependencies) {
137
+ c.dependencies.forEach(dep => {
138
+ const depName = normalizeName(dep);
139
+ if (!reverseGraph.has(depName)) reverseGraph.set(depName, []);
140
+ reverseGraph.get(depName).push(parentName);
141
+ });
142
+ }
143
+ });
144
+
103
145
  // Main Report Header
104
146
  const reportHeader = {
105
147
  buildId,
@@ -169,11 +211,18 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
169
211
  return; // EXCLUDED: Date is before data exists
170
212
  }
171
213
 
172
- targetArray.push({
214
+ const entry = {
173
215
  name: item.name,
174
216
  reason: item.reason || extraReason,
175
217
  pass: calcManifest ? calcManifest.pass : '?'
176
- });
218
+ };
219
+
220
+ // [IDEA 1] If this is a Re-Run, calculate Blast Radius
221
+ if (targetArray === dateSummary.rerun) {
222
+ entry.impact = calculateBlastRadius(item.name, reverseGraph);
223
+ }
224
+
225
+ targetArray.push(entry);
177
226
  };
178
227
 
179
228
  // 1. RUN (New)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.279",
3
+ "version": "1.0.280",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [