querysub 0.356.0 → 0.358.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.
- package/.cursorrules +9 -0
- package/bin/movelogs.js +4 -0
- package/package.json +13 -6
- package/scripts/postinstall.js +23 -0
- package/src/-a-archives/archiveCache.ts +10 -12
- package/src/-a-archives/archives.ts +29 -0
- package/src/-a-archives/archivesBackBlaze.ts +60 -12
- package/src/-a-archives/archivesDisk.ts +39 -13
- package/src/-a-archives/archivesLimitedCache.ts +21 -0
- package/src/-a-archives/archivesMemoryCache.ts +374 -0
- package/src/-a-archives/archivesPrivateFileSystem.ts +22 -0
- package/src/-g-core-values/NodeCapabilities.ts +3 -0
- package/src/0-path-value-core/auditLogs.ts +5 -1
- package/src/0-path-value-core/pathValueCore.ts +7 -7
- package/src/4-dom/qreact.tsx +1 -0
- package/src/4-querysub/Querysub.ts +1 -5
- package/src/config.ts +5 -0
- package/src/deployManager/components/MachineDetailPage.tsx +43 -2
- package/src/deployManager/components/MachinesListPage.tsx +10 -2
- package/src/deployManager/machineApplyMainCode.ts +3 -3
- package/src/deployManager/machineSchema.ts +39 -0
- package/src/diagnostics/MachineThreadInfo.tsx +235 -0
- package/src/diagnostics/NodeViewer.tsx +5 -3
- package/src/diagnostics/logs/FastArchiveAppendable.ts +79 -42
- package/src/diagnostics/logs/FastArchiveController.ts +102 -63
- package/src/diagnostics/logs/FastArchiveViewer.tsx +36 -8
- package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +462 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.cpp +327 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.d.ts +18 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexCPP.js +1 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +222 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexLogsOptimizationConstants.ts +22 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexWAT.wat +1145 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexWAT.wat.d.ts +178 -0
- package/src/diagnostics/logs/IndexedLogs/BufferListStreamer.ts +208 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +716 -0
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +146 -0
- package/src/diagnostics/logs/IndexedLogs/FilePathSelector.tsx +569 -0
- package/src/diagnostics/logs/IndexedLogs/FindProgressTracker.ts +45 -0
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +685 -0
- package/src/diagnostics/logs/IndexedLogs/LogStreamer.ts +47 -0
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +901 -0
- package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +236 -0
- package/src/diagnostics/logs/IndexedLogs/binding.gyp +23 -0
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +251 -0
- package/src/diagnostics/logs/IndexedLogs/moveLogsEntry.ts +10 -0
- package/src/diagnostics/logs/LogViewer2.tsx +120 -55
- package/src/diagnostics/logs/TimeRangeSelector.tsx +5 -2
- package/src/diagnostics/logs/diskLogger.ts +32 -48
- package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +3 -2
- package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +1 -0
- package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePages.tsx +150 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +150 -15
- package/src/diagnostics/logs/lifeCycleAnalysis/test.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/test.wat +106 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/test.wat.d.ts +2 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/testHoist.ts +5 -0
- package/src/diagnostics/logs/logViewerExtractField.ts +2 -3
- package/src/diagnostics/managementPages.tsx +10 -0
- package/src/diagnostics/trackResources.ts +1 -1
- package/src/functional/limitProcessing.ts +39 -0
- package/src/misc/lz4_wasm_nodejs.d.ts +34 -0
- package/src/misc/lz4_wasm_nodejs.js +178 -0
- package/src/misc/lz4_wasm_nodejs_bg.js +94 -0
- package/src/misc/lz4_wasm_nodejs_bg.wasm +0 -0
- package/src/misc/lz4_wasm_nodejs_bg.wasm.d.ts +15 -0
- package/src/storage/CompressedStream.ts +13 -0
- package/src/storage/LZ4.ts +32 -0
- package/src/storage/ZSTD.ts +10 -0
- package/src/wat/watCompiler.ts +1716 -0
- package/src/wat/watGrammar.pegjs +93 -0
- package/src/wat/watHandler.ts +179 -0
- package/src/wat/watInstructions.txt +707 -0
- package/src/zip.ts +3 -89
- package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +0 -125
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#include <node_api.h>
|
|
2
|
+
#include <stdlib.h>
|
|
3
|
+
#include <string.h>
|
|
4
|
+
#include <algorithm>
|
|
5
|
+
|
|
6
|
+
static napi_value PopulateUnits(napi_env env, napi_callback_info info) {
|
|
7
|
+
size_t argc = 2;
|
|
8
|
+
napi_value args[2];
|
|
9
|
+
napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
10
|
+
|
|
11
|
+
// args[0] = blocks (Buffer[][])
|
|
12
|
+
// args[1] = blockStartIndex (number)
|
|
13
|
+
|
|
14
|
+
uint32_t blockStartIndex = 0;
|
|
15
|
+
napi_get_value_uint32(env, args[1], &blockStartIndex);
|
|
16
|
+
|
|
17
|
+
// Get length of blocks array
|
|
18
|
+
uint32_t blocksLength;
|
|
19
|
+
napi_get_array_length(env, args[0], &blocksLength);
|
|
20
|
+
|
|
21
|
+
// First pass: calculate total units needed
|
|
22
|
+
uint32_t totalUnits = 0;
|
|
23
|
+
|
|
24
|
+
for (uint32_t blockIndex = 0; blockIndex < blocksLength; blockIndex++) {
|
|
25
|
+
napi_value block;
|
|
26
|
+
napi_get_element(env, args[0], blockIndex, &block);
|
|
27
|
+
|
|
28
|
+
uint32_t blockLength;
|
|
29
|
+
napi_get_array_length(env, block, &blockLength);
|
|
30
|
+
|
|
31
|
+
for (uint32_t bufferIndex = 0; bufferIndex < blockLength; bufferIndex++) {
|
|
32
|
+
napi_value bufferVal;
|
|
33
|
+
napi_get_element(env, block, bufferIndex, &bufferVal);
|
|
34
|
+
|
|
35
|
+
uint8_t* bufferData;
|
|
36
|
+
size_t bufferLength;
|
|
37
|
+
napi_get_buffer_info(env, bufferVal, (void**)&bufferData, &bufferLength);
|
|
38
|
+
|
|
39
|
+
if (bufferLength >= 4) {
|
|
40
|
+
totalUnits += (uint32_t)(bufferLength - 3);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Allocate the three output arrays
|
|
46
|
+
uint32_t* unitsArray = (uint32_t*)malloc(totalUnits * sizeof(uint32_t));
|
|
47
|
+
uint32_t* bufferIndicesArray = (uint32_t*)malloc(totalUnits * sizeof(uint32_t));
|
|
48
|
+
uint32_t* blocksArray = (uint32_t*)malloc(totalUnits * sizeof(uint32_t));
|
|
49
|
+
|
|
50
|
+
uint32_t currentOffset = 0;
|
|
51
|
+
|
|
52
|
+
// Second pass: populate the arrays
|
|
53
|
+
for (uint32_t blockIndex = 0; blockIndex < blocksLength; blockIndex++) {
|
|
54
|
+
napi_value block;
|
|
55
|
+
napi_get_element(env, args[0], blockIndex, &block);
|
|
56
|
+
|
|
57
|
+
uint32_t blockLength;
|
|
58
|
+
napi_get_array_length(env, block, &blockLength);
|
|
59
|
+
|
|
60
|
+
for (uint32_t bufferIndex = 0; bufferIndex < blockLength; bufferIndex++) {
|
|
61
|
+
napi_value bufferVal;
|
|
62
|
+
napi_get_element(env, block, bufferIndex, &bufferVal);
|
|
63
|
+
|
|
64
|
+
uint8_t* bufferData;
|
|
65
|
+
size_t bufferLength;
|
|
66
|
+
napi_get_buffer_info(env, bufferVal, (void**)&bufferData, &bufferLength);
|
|
67
|
+
|
|
68
|
+
if (bufferLength < 4) continue;
|
|
69
|
+
|
|
70
|
+
// For each byte position, create a unit from that position and the next 3 bytes
|
|
71
|
+
for (size_t i = 0; i <= bufferLength - 4; i++) {
|
|
72
|
+
// Read 4 bytes as little-endian uint32
|
|
73
|
+
uint32_t unit = bufferData[i] |
|
|
74
|
+
(bufferData[i+1] << 8) |
|
|
75
|
+
(bufferData[i+2] << 16) |
|
|
76
|
+
(bufferData[i+3] << 24);
|
|
77
|
+
|
|
78
|
+
if (unit == 0) continue;
|
|
79
|
+
|
|
80
|
+
unitsArray[currentOffset] = unit;
|
|
81
|
+
bufferIndicesArray[currentOffset] = bufferIndex;
|
|
82
|
+
blocksArray[currentOffset] = blockIndex + blockStartIndex;
|
|
83
|
+
currentOffset++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Create TypedArrays from the raw buffers
|
|
89
|
+
napi_value unitsResult, bufferIndicesResult, blocksResult;
|
|
90
|
+
|
|
91
|
+
napi_create_external_arraybuffer(env, unitsArray, currentOffset * sizeof(uint32_t),
|
|
92
|
+
[](napi_env env, void* data, void* hint) { free(data); }, NULL, &unitsResult);
|
|
93
|
+
napi_create_external_arraybuffer(env, bufferIndicesArray, currentOffset * sizeof(uint32_t),
|
|
94
|
+
[](napi_env env, void* data, void* hint) { free(data); }, NULL, &bufferIndicesResult);
|
|
95
|
+
napi_create_external_arraybuffer(env, blocksArray, currentOffset * sizeof(uint32_t),
|
|
96
|
+
[](napi_env env, void* data, void* hint) { free(data); }, NULL, &blocksResult);
|
|
97
|
+
|
|
98
|
+
napi_value units, bufferIndices, blocks;
|
|
99
|
+
napi_create_typedarray(env, napi_uint32_array, currentOffset, unitsResult, 0, &units);
|
|
100
|
+
napi_create_typedarray(env, napi_uint32_array, currentOffset, bufferIndicesResult, 0, &bufferIndices);
|
|
101
|
+
napi_create_typedarray(env, napi_uint32_array, currentOffset, blocksResult, 0, &blocks);
|
|
102
|
+
|
|
103
|
+
// Return an object with three properties
|
|
104
|
+
napi_value result;
|
|
105
|
+
napi_create_object(env, &result);
|
|
106
|
+
napi_set_named_property(env, result, "units", units);
|
|
107
|
+
napi_set_named_property(env, result, "bufferIndices", bufferIndices);
|
|
108
|
+
napi_set_named_property(env, result, "blocks", blocks);
|
|
109
|
+
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// C++ addon for Node: Hash computation matching TypeScript getMaskedHash
|
|
114
|
+
static uint32_t GetMaskedHash(uint32_t unit, uint32_t mask) {
|
|
115
|
+
uint32_t hash = (unit * 0x27d4eb2du);
|
|
116
|
+
hash = hash ^ (hash >> 15);
|
|
117
|
+
uint32_t maskedHash = hash & mask;
|
|
118
|
+
// Avoid collision with empty marker (0)
|
|
119
|
+
if (maskedHash == 0) {
|
|
120
|
+
maskedHash = 1;
|
|
121
|
+
}
|
|
122
|
+
return maskedHash;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// C++ addon for Node: Build hash table with linked lists for performance
|
|
126
|
+
static napi_value BuildHashTable(napi_env env, napi_callback_info info) {
|
|
127
|
+
size_t argc = 3;
|
|
128
|
+
napi_value args[3];
|
|
129
|
+
napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
130
|
+
|
|
131
|
+
// args[0] = blocksUncompressed (Buffer[])
|
|
132
|
+
// args[1] = hashTableCapacity (number)
|
|
133
|
+
// args[2] = mask (number)
|
|
134
|
+
|
|
135
|
+
uint32_t hashTableCapacity = 0;
|
|
136
|
+
napi_get_value_uint32(env, args[1], &hashTableCapacity);
|
|
137
|
+
|
|
138
|
+
uint32_t mask = 0;
|
|
139
|
+
napi_get_value_uint32(env, args[2], &mask);
|
|
140
|
+
|
|
141
|
+
// Get length of blocksUncompressed array
|
|
142
|
+
uint32_t blocksLength;
|
|
143
|
+
napi_get_array_length(env, args[0], &blocksLength);
|
|
144
|
+
|
|
145
|
+
// Calculate initial linked list capacity based on total units
|
|
146
|
+
uint32_t totalUnits = 0;
|
|
147
|
+
for (uint32_t blockIndex = 0; blockIndex < blocksLength; blockIndex++) {
|
|
148
|
+
napi_value block;
|
|
149
|
+
napi_get_element(env, args[0], blockIndex, &block);
|
|
150
|
+
|
|
151
|
+
uint8_t* blockData;
|
|
152
|
+
size_t blockLength;
|
|
153
|
+
napi_get_buffer_info(env, block, (void**)&blockData, &blockLength);
|
|
154
|
+
|
|
155
|
+
if (blockLength >= 4) {
|
|
156
|
+
totalUnits += (uint32_t)(blockLength - 3);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
uint32_t linkedListCapacity = std::max(1000u, totalUnits / 100);
|
|
161
|
+
|
|
162
|
+
// Hash table: stores head node index for each bucket (0 means empty)
|
|
163
|
+
uint32_t* hashTable = (uint32_t*)calloc(hashTableCapacity, sizeof(uint32_t));
|
|
164
|
+
|
|
165
|
+
// Linked list storage: each node is 3 uint32s: [maskedHash, blockIndex, nextNodeIndex]
|
|
166
|
+
uint32_t* linkedListData = (uint32_t*)malloc(linkedListCapacity * 3 * sizeof(uint32_t));
|
|
167
|
+
uint32_t linkedListSize = 0;
|
|
168
|
+
uint32_t filledSlots = 0;
|
|
169
|
+
|
|
170
|
+
// Build the hash table with linked lists (iterate backwards so linked lists are in forward order)
|
|
171
|
+
for (int32_t blockIndex = (int32_t)blocksLength - 1; blockIndex >= 0; blockIndex--) {
|
|
172
|
+
napi_value block;
|
|
173
|
+
napi_get_element(env, args[0], blockIndex, &block);
|
|
174
|
+
|
|
175
|
+
uint8_t* blockData;
|
|
176
|
+
size_t blockLength;
|
|
177
|
+
napi_get_buffer_info(env, block, (void**)&blockData, &blockLength);
|
|
178
|
+
|
|
179
|
+
if (blockLength < 4) continue;
|
|
180
|
+
|
|
181
|
+
// Iterate through all units (overlapping 4-byte windows)
|
|
182
|
+
for (size_t i = 0; i <= blockLength - 4; i++) {
|
|
183
|
+
// Read unit directly by casting pointer (little-endian architecture)
|
|
184
|
+
// This reads 4 bytes starting at position i
|
|
185
|
+
uint32_t unit = *(uint32_t*)(blockData + i);
|
|
186
|
+
|
|
187
|
+
uint32_t maskedHash = GetMaskedHash(unit, mask);
|
|
188
|
+
uint32_t currentHead = hashTable[maskedHash];
|
|
189
|
+
|
|
190
|
+
// Skip if we already added this masked hash for the current block
|
|
191
|
+
// (check if head node has the same blockIndex)
|
|
192
|
+
if (currentHead != 0) {
|
|
193
|
+
uint32_t headNodeOffset = (currentHead - 1) * 3;
|
|
194
|
+
uint32_t headBlockIndex = linkedListData[headNodeOffset + 1];
|
|
195
|
+
if (headBlockIndex == (uint32_t)blockIndex) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Track when we're filling a previously empty slot
|
|
201
|
+
if (currentHead == 0) {
|
|
202
|
+
filledSlots++;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Reallocate if needed (grow 4x like TypeScript version)
|
|
206
|
+
if (linkedListSize >= linkedListCapacity) {
|
|
207
|
+
uint32_t newCapacity = linkedListCapacity * 4;
|
|
208
|
+
uint32_t* newData = (uint32_t*)malloc(newCapacity * 3 * sizeof(uint32_t));
|
|
209
|
+
memcpy(newData, linkedListData, linkedListSize * 3 * sizeof(uint32_t));
|
|
210
|
+
free(linkedListData);
|
|
211
|
+
linkedListData = newData;
|
|
212
|
+
linkedListCapacity = newCapacity;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Allocate new node
|
|
216
|
+
uint32_t nodeIndex = linkedListSize + 1; // +1 because 0 means empty
|
|
217
|
+
uint32_t nodeOffset = linkedListSize * 3;
|
|
218
|
+
linkedListData[nodeOffset] = maskedHash;
|
|
219
|
+
linkedListData[nodeOffset + 1] = (uint32_t)blockIndex;
|
|
220
|
+
linkedListData[nodeOffset + 2] = currentHead;
|
|
221
|
+
linkedListSize++;
|
|
222
|
+
|
|
223
|
+
// Prepend to the chain
|
|
224
|
+
hashTable[maskedHash] = nodeIndex;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Trim linkedListData to actual size
|
|
229
|
+
uint32_t* finalLinkedListData = (uint32_t*)malloc(linkedListSize * 3 * sizeof(uint32_t));
|
|
230
|
+
memcpy(finalLinkedListData, linkedListData, linkedListSize * 3 * sizeof(uint32_t));
|
|
231
|
+
free(linkedListData);
|
|
232
|
+
|
|
233
|
+
// Create TypedArrays from the data
|
|
234
|
+
napi_value hashTableResult, linkedListDataResult;
|
|
235
|
+
|
|
236
|
+
napi_create_external_arraybuffer(env, hashTable, hashTableCapacity * sizeof(uint32_t),
|
|
237
|
+
[](napi_env env, void* data, void* hint) { free(data); }, NULL, &hashTableResult);
|
|
238
|
+
|
|
239
|
+
napi_create_external_arraybuffer(env, finalLinkedListData, linkedListSize * 3 * sizeof(uint32_t),
|
|
240
|
+
[](napi_env env, void* data, void* hint) { free(data); }, NULL, &linkedListDataResult);
|
|
241
|
+
|
|
242
|
+
napi_value hashTableTyped, linkedListDataTyped;
|
|
243
|
+
napi_create_typedarray(env, napi_uint32_array, hashTableCapacity, hashTableResult, 0, &hashTableTyped);
|
|
244
|
+
napi_create_typedarray(env, napi_uint32_array, linkedListSize * 3, linkedListDataResult, 0, &linkedListDataTyped);
|
|
245
|
+
|
|
246
|
+
// Create result object
|
|
247
|
+
napi_value result;
|
|
248
|
+
napi_create_object(env, &result);
|
|
249
|
+
|
|
250
|
+
napi_set_named_property(env, result, "hashTable", hashTableTyped);
|
|
251
|
+
napi_set_named_property(env, result, "linkedListData", linkedListDataTyped);
|
|
252
|
+
|
|
253
|
+
napi_value nodeCountValue, filledSlotsValue;
|
|
254
|
+
napi_create_uint32(env, linkedListSize, &nodeCountValue);
|
|
255
|
+
napi_create_uint32(env, filledSlots, &filledSlotsValue);
|
|
256
|
+
napi_set_named_property(env, result, "nodeCount", nodeCountValue);
|
|
257
|
+
napi_set_named_property(env, result, "filledSlots", filledSlotsValue);
|
|
258
|
+
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// C++ addon for Node: Estimate unique units using a 16-bit bitmap
|
|
263
|
+
static napi_value EstimateUniqueUnits(napi_env env, napi_callback_info info) {
|
|
264
|
+
size_t argc = 1;
|
|
265
|
+
napi_value args[1];
|
|
266
|
+
napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
267
|
+
|
|
268
|
+
// args[0] = allData (Buffer[])
|
|
269
|
+
|
|
270
|
+
// Get length of allData array
|
|
271
|
+
uint32_t dataLength;
|
|
272
|
+
napi_get_array_length(env, args[0], &dataLength);
|
|
273
|
+
|
|
274
|
+
// Use a 64KB bitmap to track which 16-bit masked hashes we've seen
|
|
275
|
+
uint8_t* seenMaskedHashes = (uint8_t*)calloc(65536, sizeof(uint8_t));
|
|
276
|
+
uint32_t uniqueCount = 0;
|
|
277
|
+
|
|
278
|
+
// Process each buffer
|
|
279
|
+
for (uint32_t bufIndex = 0; bufIndex < dataLength; bufIndex++) {
|
|
280
|
+
napi_value buffer;
|
|
281
|
+
napi_get_element(env, args[0], bufIndex, &buffer);
|
|
282
|
+
|
|
283
|
+
uint8_t* bufferData;
|
|
284
|
+
size_t bufferLength;
|
|
285
|
+
napi_get_buffer_info(env, buffer, (void**)&bufferData, &bufferLength);
|
|
286
|
+
|
|
287
|
+
if (bufferLength < 4) continue;
|
|
288
|
+
|
|
289
|
+
// Iterate through all units (overlapping 4-byte windows)
|
|
290
|
+
for (size_t i = 0; i <= bufferLength - 4; i++) {
|
|
291
|
+
// Read unit directly by casting pointer (little-endian architecture)
|
|
292
|
+
uint32_t unit = *(uint32_t*)(bufferData + i);
|
|
293
|
+
|
|
294
|
+
// Get masked hash with 0xFFFF mask (16-bit)
|
|
295
|
+
uint32_t maskedHash = GetMaskedHash(unit, 0xFFFF);
|
|
296
|
+
|
|
297
|
+
// Track if we've seen this hash before
|
|
298
|
+
if (seenMaskedHashes[maskedHash] == 0) {
|
|
299
|
+
seenMaskedHashes[maskedHash] = 1;
|
|
300
|
+
uniqueCount++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
free(seenMaskedHashes);
|
|
306
|
+
|
|
307
|
+
// Estimate actual unique units by multiplying by 2
|
|
308
|
+
// This accounts for collisions in the 16-bit space
|
|
309
|
+
uint32_t estimate = uniqueCount * 2;
|
|
310
|
+
|
|
311
|
+
napi_value result;
|
|
312
|
+
napi_create_uint32(env, estimate, &result);
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
static napi_value Init(napi_env env, napi_value exports) {
|
|
317
|
+
napi_value populateUnitsFn, buildHashTableFn, estimateUniqueUnitsFn;
|
|
318
|
+
napi_create_function(env, NULL, 0, PopulateUnits, NULL, &populateUnitsFn);
|
|
319
|
+
napi_create_function(env, NULL, 0, BuildHashTable, NULL, &buildHashTableFn);
|
|
320
|
+
napi_create_function(env, NULL, 0, EstimateUniqueUnits, NULL, &estimateUniqueUnitsFn);
|
|
321
|
+
napi_set_named_property(env, exports, "populateUnits", populateUnitsFn);
|
|
322
|
+
napi_set_named_property(env, exports, "buildHashTable", buildHashTableFn);
|
|
323
|
+
napi_set_named_property(env, exports, "estimateUniqueUnits", estimateUniqueUnitsFn);
|
|
324
|
+
return exports;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface PopulateUnitsResult {
|
|
2
|
+
units: Uint32Array;
|
|
3
|
+
bufferIndices: Uint32Array;
|
|
4
|
+
blocks: Uint32Array;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface BuildHashTableResult {
|
|
8
|
+
hashTable: Uint32Array;
|
|
9
|
+
linkedListData: Uint32Array;
|
|
10
|
+
nodeCount: number;
|
|
11
|
+
filledSlots: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function populateUnits(blocks: Buffer[][], blockStartIndex: number): PopulateUnitsResult;
|
|
15
|
+
|
|
16
|
+
export function buildHashTable(blocksUncompressed: Buffer[], hashTableCapacity: number, mask: number): BuildHashTableResult;
|
|
17
|
+
|
|
18
|
+
export function estimateUniqueUnits(allData: Buffer[]): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("./build/Release/BufferIndexCPP.node");
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
|
|
2
|
+
import { measureFnc, measureWrap } from "socket-function/src/profiling/measure";
|
|
3
|
+
import { formatNumber, formatPercent } from "socket-function/src/formatting/format";
|
|
4
|
+
import { red } from "socket-function/src/formatting/logColors";
|
|
5
|
+
import { MaybePromise } from "socket-function/src/types";
|
|
6
|
+
import { TimeFilePathWithSize } from "./IndexedLogs";
|
|
7
|
+
import { LimitGroup } from "../../../functional/limitProcessing";
|
|
8
|
+
|
|
9
|
+
export const INDEX_EXTENSION = ".index";
|
|
10
|
+
|
|
11
|
+
export type SearchParams = {
|
|
12
|
+
startTime: number;
|
|
13
|
+
endTime: number;
|
|
14
|
+
limit: number;
|
|
15
|
+
disableWildCards?: boolean;
|
|
16
|
+
findBuffer: Buffer;
|
|
17
|
+
pathOverrides?: TimeFilePathWithSize[];
|
|
18
|
+
only?: "local" | "public";
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type Unit = number;
|
|
22
|
+
export type UnitRef = {
|
|
23
|
+
unit: Unit;
|
|
24
|
+
bufferIndex: number;
|
|
25
|
+
block: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type Reader = {
|
|
29
|
+
getLength(): MaybePromise<number>;
|
|
30
|
+
read(offset: number, length: number): MaybePromise<Buffer>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export class BufferReader implements Reader {
|
|
35
|
+
constructor(private buffer: Buffer) { }
|
|
36
|
+
|
|
37
|
+
async getLength(): Promise<number> {
|
|
38
|
+
return this.buffer.length;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async read(offset: number, length: number): Promise<Buffer> {
|
|
42
|
+
return this.buffer.slice(offset, offset + length);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
export function createOffsetReader(reader: Reader, offset: number): Reader {
|
|
49
|
+
return {
|
|
50
|
+
async getLength() {
|
|
51
|
+
return (await reader.getLength()) - offset;
|
|
52
|
+
},
|
|
53
|
+
async read(readOffset: number, length: number) {
|
|
54
|
+
return reader.read(readOffset + offset, length);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export const getAllUnits = measureWrap(function getAllUnits(config: {
|
|
61
|
+
buffer: Buffer;
|
|
62
|
+
bufferIndex: number;
|
|
63
|
+
block: number;
|
|
64
|
+
}): UnitRef[] {
|
|
65
|
+
let { buffer, bufferIndex, block } = config;
|
|
66
|
+
|
|
67
|
+
// We need at least 4 bytes to create a unit
|
|
68
|
+
if (buffer.length < 4) return [];
|
|
69
|
+
|
|
70
|
+
const result: UnitRef[] = [];
|
|
71
|
+
// For each byte position, create a unit from that position and the next 3 bytes
|
|
72
|
+
for (let i = 0; i <= buffer.length - 4; i++) {
|
|
73
|
+
const unit = buffer.readUint32LE(i);
|
|
74
|
+
if (!unit) continue;
|
|
75
|
+
|
|
76
|
+
result.push({
|
|
77
|
+
unit,
|
|
78
|
+
bufferIndex,
|
|
79
|
+
block
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return result;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
export type IndexedLogResults = {
|
|
88
|
+
matchCount: number;
|
|
89
|
+
|
|
90
|
+
// NOTE: A lot of the metadata won't be accurate if multiple searches happen at the same time. However, for debugging, it should be sufficient.
|
|
91
|
+
reads: {
|
|
92
|
+
cached: boolean;
|
|
93
|
+
remote: boolean;
|
|
94
|
+
count: number;
|
|
95
|
+
size: number;
|
|
96
|
+
|
|
97
|
+
totalSize: number;
|
|
98
|
+
totalCount: number;
|
|
99
|
+
}[];
|
|
100
|
+
|
|
101
|
+
totalLocalFiles: number;
|
|
102
|
+
totalBackblazeFiles: number;
|
|
103
|
+
localFilesSearched: number;
|
|
104
|
+
backblazeFilesSearched: number;
|
|
105
|
+
|
|
106
|
+
totalBlockCount: number;
|
|
107
|
+
blockCheckedCount: number;
|
|
108
|
+
|
|
109
|
+
remoteBlockCount: number;
|
|
110
|
+
localBlockCount: number;
|
|
111
|
+
remoteBlockCheckedCount: number;
|
|
112
|
+
localBlockCheckedCount: number;
|
|
113
|
+
|
|
114
|
+
blocksCheckedCompressedSize: number;
|
|
115
|
+
blocksCheckedDecompressedSize: number;
|
|
116
|
+
blockErrors: string[];
|
|
117
|
+
|
|
118
|
+
fileErrors: { error: string; path: string; }[];
|
|
119
|
+
|
|
120
|
+
remoteIndexesSearched: number;
|
|
121
|
+
remoteIndexSize: number;
|
|
122
|
+
localIndexesSearched: number;
|
|
123
|
+
localIndexSize: number;
|
|
124
|
+
|
|
125
|
+
timeToFirstMatch: number;
|
|
126
|
+
fileFindTime: number;
|
|
127
|
+
indexSearchTime: number;
|
|
128
|
+
blockSearchTime: number;
|
|
129
|
+
|
|
130
|
+
totalSearchTime: number;
|
|
131
|
+
|
|
132
|
+
cancel?: boolean;
|
|
133
|
+
limitGroup?: LimitGroup;
|
|
134
|
+
};
|
|
135
|
+
export function createEmptyIndexedLogResults(): IndexedLogResults {
|
|
136
|
+
return {
|
|
137
|
+
matchCount: 0, reads: [], totalLocalFiles: 0, totalBackblazeFiles: 0, localFilesSearched: 0, backblazeFilesSearched: 0, totalBlockCount: 0, blockCheckedCount: 0, remoteBlockCount: 0, localBlockCount: 0, remoteBlockCheckedCount: 0, localBlockCheckedCount: 0, blocksCheckedCompressedSize: 0, blocksCheckedDecompressedSize: 0, blockErrors: [], fileErrors: [], remoteIndexesSearched: 0, remoteIndexSize: 0, localIndexesSearched: 0, localIndexSize: 0, timeToFirstMatch: 0, fileFindTime: 0, indexSearchTime: 0, blockSearchTime: 0, totalSearchTime: 0, cancel: undefined, limitGroup: undefined,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function addReadToResults(results: IndexedLogResults, read: {
|
|
142
|
+
cached: boolean;
|
|
143
|
+
remote: boolean;
|
|
144
|
+
count: number;
|
|
145
|
+
size: number;
|
|
146
|
+
}) {
|
|
147
|
+
let existingRead = results.reads.find(r => r.cached === read.cached && r.remote === read.remote);
|
|
148
|
+
if (!existingRead) {
|
|
149
|
+
existingRead = {
|
|
150
|
+
cached: read.cached,
|
|
151
|
+
remote: read.remote,
|
|
152
|
+
count: 0,
|
|
153
|
+
size: 0,
|
|
154
|
+
totalSize: 0,
|
|
155
|
+
totalCount: 0,
|
|
156
|
+
};
|
|
157
|
+
results.reads.push(existingRead);
|
|
158
|
+
}
|
|
159
|
+
existingRead.count += read.count;
|
|
160
|
+
existingRead.size += read.size;
|
|
161
|
+
return existingRead;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
export const WILD_CARD_BYTE = 42;
|
|
166
|
+
export function splitOnWildcard(buffer: Buffer): Buffer[] {
|
|
167
|
+
let segments: Buffer[] = [];
|
|
168
|
+
let start = 0;
|
|
169
|
+
for (let i = 0; i <= buffer.length; i++) {
|
|
170
|
+
if (i === buffer.length || buffer[i] === WILD_CARD_BYTE) {
|
|
171
|
+
segments.push(buffer.slice(start, i));
|
|
172
|
+
start = i + 1;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return segments;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Each WILD_CARD_BYTE in pattern acts as a multi-byte wildcard: the segments on either
|
|
179
|
+
// side must appear in order somewhere within buffer.
|
|
180
|
+
// Returns a function that matches buffers against the pre-processed pattern.
|
|
181
|
+
export function createMatchesPattern(pattern: Buffer, disableWildCards: boolean): (buffer: Buffer) => boolean {
|
|
182
|
+
let segments = disableWildCards && [pattern] || splitOnWildcard(pattern).filter(s => s.length > 0);
|
|
183
|
+
|
|
184
|
+
return measureWrap(function matchesPattern(buffer: Buffer): boolean {
|
|
185
|
+
// Fast path: check if all segments exist anywhere in the buffer using indexOf
|
|
186
|
+
for (let seg of segments) {
|
|
187
|
+
if (buffer.indexOf(seg) === -1) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Tries to match all segments in order starting from bufferPos, returning the end
|
|
193
|
+
// position after the last match, or -1 if not all segments could be found.
|
|
194
|
+
function matchSegmentsFrom(bufferPos: number): number {
|
|
195
|
+
for (let seg of segments) {
|
|
196
|
+
function segMatchesAt(pos: number): boolean {
|
|
197
|
+
for (let i = 0; i < seg.length; i++) {
|
|
198
|
+
if (buffer[pos + i] !== seg[i]) return false;
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
let found = false;
|
|
203
|
+
for (let searchPos = bufferPos; searchPos <= buffer.length - seg.length; searchPos++) {
|
|
204
|
+
if (segMatchesAt(searchPos)) {
|
|
205
|
+
// NOTE: I think this is safe because every segment has a wildcard after it. So we can never have a case where we didn't skip far enough because the wild card will just skip farther. And we won't have a partial match as we're matching the whole chunk. So we won't match a prefix and then get stuck. I think... it does seem weird though...
|
|
206
|
+
bufferPos = searchPos + seg.length;
|
|
207
|
+
found = true;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!found) return -1;
|
|
212
|
+
}
|
|
213
|
+
return bufferPos;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (let startPos = 0; startPos <= buffer.length; startPos++) {
|
|
217
|
+
if (matchSegmentsFrom(startPos) >= 0) return true;
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}, "BufferIndex|matchesPattern");
|
|
221
|
+
}
|
|
222
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { timeInHour, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
2
|
+
|
|
3
|
+
// NOTE: This is the block size BEFORE compression. The actual download size is probably going to be around 1/20th of this.
|
|
4
|
+
export const DEFAULT_BLOCK_SIZE = 1024 * 1024;
|
|
5
|
+
export const DEFAULT_TARGET_UNITS_PER_BUCKET = 16;
|
|
6
|
+
|
|
7
|
+
// Worst case scenario, the logs the user wants are very spread out, in which case having too high of a threshold will require decoding more data (the threshold amount per result).
|
|
8
|
+
export const STREAMING_BLOCK_THRESHOLD = 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export const PUBLIC_MOVE_THRESHOLD = timeInHour * 3;
|
|
13
|
+
// Max uncompressed data size
|
|
14
|
+
export const MAX_SINGLE_FILE_DATA = 1024 * 1024 * 512;
|
|
15
|
+
|
|
16
|
+
export const MOVING_TIMEOUT = timeInMinute * 5;
|
|
17
|
+
|
|
18
|
+
export const DISK_FLUSH_INTERVAL = timeInSecond * 15;
|
|
19
|
+
export const MAX_COUNT_PER_FILE = 1000 * 1000 * 10;
|
|
20
|
+
|
|
21
|
+
// High parallel factor, as these will often be remote, and so accessing them has high latency.
|
|
22
|
+
export const BufferUnitIndexParallelSearchCount = 8;
|