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] = {
|
|
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
|
-
//
|
|
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
|
-
// [
|
|
57
|
-
executionStats:
|
|
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.
|
|
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
|
-
|
|
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)
|