diff --git a/Build/constants/reject-data-source.ts b/Build/constants/reject-data-source.ts index a738c3c5..56fba3ba 100644 --- a/Build/constants/reject-data-source.ts +++ b/Build/constants/reject-data-source.ts @@ -147,7 +147,7 @@ export const DOMAIN_LISTS_EXTRA: HostsSource[] = [ true ], [ - 'https://raw.githubusercontent.com/DandelionSprout/adfilt/refs/heads/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareDomains.txt', + 'https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareDomains.txt', [], true ] diff --git a/Build/index.ts b/Build/index.ts index 14953ff7..8037d8bd 100644 --- a/Build/index.ts +++ b/Build/index.ts @@ -25,7 +25,8 @@ import { downloadMockAssets } from './download-mock-assets'; import { buildCloudMounterRules } from './build-cloudmounter-rules'; -import { createSpan, printTraceResult, whyIsNodeRunning } from './trace'; +import { printStats, printTraceResult, whyIsNodeRunning } from './trace'; +import type { TraceResult } from './trace'; import { buildDeprecateFiles } from './build-deprecate-files'; import path from 'node:path'; import { ROOT_DIR } from './constants/dir'; @@ -66,8 +67,6 @@ const buildFinishedLock = path.join(ROOT_DIR, '.BUILD_FINISHED'); console.log(`Memory: ${os.totalmem() / (1024 * 1024)} MiB`); - const rootSpan = createSpan('root'); - if (fs.existsSync(buildFinishedLock)) { fs.unlinkSync(buildFinishedLock); } @@ -78,39 +77,62 @@ const buildFinishedLock = path.join(ROOT_DIR, '.BUILD_FINISHED'); await import('why-is-node-running'); } - const downloadPreviousBuildPromise = downloadPreviousBuild(rootSpan); - const buildCommonPromise = downloadPreviousBuildPromise.then(() => buildCommon(rootSpan)); + const downloadPreviousBuildPromise = downloadPreviousBuild(); await Promise.all([ downloadPreviousBuildPromise, - buildCommonPromise, - downloadPreviousBuildPromise.then(() => buildRejectIPList(rootSpan)), - downloadPreviousBuildPromise.then(() => buildAppleCdn(rootSpan)), - downloadPreviousBuildPromise.then(() => buildCdnDownloadConf(rootSpan)), - downloadPreviousBuildPromise.then(() => buildRejectDomainSet(rootSpan)), - downloadPreviousBuildPromise.then(() => buildTelegramCIDR(rootSpan)), - downloadPreviousBuildPromise.then(() => buildChnCidr(rootSpan)), - downloadPreviousBuildPromise.then(() => buildSpeedtestDomainSet(rootSpan)), - downloadPreviousBuildPromise.then(() => buildDomesticRuleset(rootSpan)), - downloadPreviousBuildPromise.then(() => buildGlobalRuleset(rootSpan)), - downloadPreviousBuildPromise.then(() => buildRedirectModule(rootSpan)), - downloadPreviousBuildPromise.then(() => buildAlwaysRealIPModule(rootSpan)), - downloadPreviousBuildPromise.then(() => buildStreamService(rootSpan)), - downloadPreviousBuildPromise.then(() => buildMicrosoftCdn(rootSpan)), - downloadPreviousBuildPromise.then(() => buildCloudMounterRules(rootSpan)), - downloadMockAssets(rootSpan) + downloadPreviousBuildPromise.then(() => buildCommon()), + downloadPreviousBuildPromise.then(() => buildRejectIPList()), + downloadPreviousBuildPromise.then(() => buildAppleCdn()), + downloadPreviousBuildPromise.then(() => buildCdnDownloadConf()), + downloadPreviousBuildPromise.then(() => buildRejectDomainSet()), + downloadPreviousBuildPromise.then(() => buildTelegramCIDR()), + downloadPreviousBuildPromise.then(() => buildChnCidr()), + downloadPreviousBuildPromise.then(() => buildSpeedtestDomainSet()), + downloadPreviousBuildPromise.then(() => buildDomesticRuleset()), + downloadPreviousBuildPromise.then(() => buildGlobalRuleset()), + downloadPreviousBuildPromise.then(() => buildRedirectModule()), + downloadPreviousBuildPromise.then(() => buildAlwaysRealIPModule()), + downloadPreviousBuildPromise.then(() => buildStreamService()), + downloadPreviousBuildPromise.then(() => buildMicrosoftCdn()), + downloadPreviousBuildPromise.then(() => buildCloudMounterRules()), + downloadMockAssets() ]); - await buildDeprecateFiles(rootSpan); - await buildPublic(rootSpan); - - rootSpan.stop(); - - printTraceResult(rootSpan.traceResult); + await buildDeprecateFiles(); + await buildPublic(); // write a file to demonstrate that the build is finished fs.writeFileSync(buildFinishedLock, 'BUILD_FINISHED\n'); + const traces: TraceResult[] = []; + [ + downloadPreviousBuild, + downloadMockAssets, + buildCommon, + buildRejectIPList, + buildAppleCdn, + buildCdnDownloadConf, + buildRejectDomainSet, + buildTelegramCIDR, + buildChnCidr, + buildSpeedtestDomainSet, + buildDomesticRuleset, + buildGlobalRuleset, + buildRedirectModule, + buildAlwaysRealIPModule, + buildStreamService, + buildMicrosoftCdn, + buildCloudMounterRules, + buildPublic, + buildDeprecateFiles + ].forEach((fn) => { + const trace = fn.getInternalTraceResult(); + printTraceResult(trace); + traces.push(trace); + }); + printStats(traces); + // Finish the build to avoid leaking timer/fetch ref await whyIsNodeRunning(); process.exit(0); diff --git a/Build/lib/parse-filter/domainlists.ts b/Build/lib/parse-filter/domainlists.ts index e9cc0ac9..f2b34430 100644 --- a/Build/lib/parse-filter/domainlists.ts +++ b/Build/lib/parse-filter/domainlists.ts @@ -28,11 +28,11 @@ export function processDomainListsWithPreload( const downloadPromise = fetchAssets(domainListsUrl, mirrors, true, allowEmptyRemote); const lineCb = includeAllSubDomain ? domainListLineCbIncludeAllSubdomain : domainListLineCb; - return (span: Span) => span.traceChildAsync(`process domainlist: ${domainListsUrl}`, async (span) => { - const filterRules = await span.traceChildPromise('download', downloadPromise); + return (span: Span) => span.traceChildAsync(`process domainlist: ${domainListsUrl}`, async (childSpan) => { + const filterRules = await childSpan.traceChildPromise('download', downloadPromise); const domainSets: string[] = []; - span.traceChildSync('parse domain list', () => { + childSpan.traceChildSync('parse domain list', () => { for (let i = 0, len = filterRules.length; i < len; i++) { lineCb(filterRules[i], domainSets, domainListsUrl, fastNormalizeDomainWithoutWww); } diff --git a/Build/trace/index.ts b/Build/trace/index.ts index 0e60e9a3..b6d58fae 100644 --- a/Build/trace/index.ts +++ b/Build/trace/index.ts @@ -4,8 +4,8 @@ import { basename, extname } from 'node:path'; import process from 'node:process'; import picocolors from 'picocolors'; -const SPAN_STATUS_START = 0; -const SPAN_STATUS_END = 1; +export const SPAN_STATUS_START = 0; +export const SPAN_STATUS_END = 1; const spanTag = Symbol('span'); @@ -16,12 +16,11 @@ export interface TraceResult { children: TraceResult[] } -const rootTraceResult: TraceResult = { - name: 'root', - start: 0, - end: 0, - children: [] -}; +/** Pure data object — safe to transfer across Worker Thread boundaries. */ +export interface RawSpan { + traceResult: TraceResult, + status: typeof SPAN_STATUS_START | typeof SPAN_STATUS_END +} export interface Span { [spanTag]: true, @@ -36,37 +35,23 @@ export interface Span { readonly traceResult: TraceResult } -export function createSpan(name: string, parentTraceResult?: TraceResult): Span { - const start = performance.now(); - - let curTraceResult: TraceResult; - - if (parentTraceResult == null) { - curTraceResult = rootTraceResult; - } else { - curTraceResult = { - name, - start, - end: 0, - children: [] - }; - parentTraceResult.children.push(curTraceResult); - } - - let status: typeof SPAN_STATUS_START | typeof SPAN_STATUS_END = SPAN_STATUS_START; +/** + * Wraps a serializable {@link RawSpan} with all span methods. + * Use this on a worker thread after receiving a {@link RawSpan} (or {@link TraceResult}) + * transferred from another thread. + */ +export function makeSpan(rawSpan: RawSpan): Span { + const { traceResult } = rawSpan; const stop = (time?: number) => { - if (status === SPAN_STATUS_END) { - throw new Error(`span already stopped: ${name}`); + if (rawSpan.status === SPAN_STATUS_END) { + throw new Error(`span already stopped: ${traceResult.name}`); } - const end = time ?? performance.now(); - - curTraceResult.end = end; - - status = SPAN_STATUS_END; + traceResult.end = time ?? performance.now(); + rawSpan.status = SPAN_STATUS_END; }; - const traceChild = (name: string) => createSpan(name, curTraceResult); + const traceChild = (name: string) => createSpan(name, traceResult); const span: Span = { [spanTag]: true, @@ -82,7 +67,7 @@ export function createSpan(name: string, parentTraceResult?: TraceResult): Span span.stop(); return res; }, - traceResult: curTraceResult, + traceResult, async tracePromise(promise: Promise): Promise { const res = await promise; span.stop(); @@ -97,18 +82,35 @@ export function createSpan(name: string, parentTraceResult?: TraceResult): Span return span; } +export function createSpan(name: string, parentTraceResult?: TraceResult): Span { + const rawSpan: RawSpan = { + traceResult: { + name, + start: performance.now(), + end: 0, + children: [] + }, + status: SPAN_STATUS_START + }; + + parentTraceResult?.children.push(rawSpan.traceResult); + + return makeSpan(rawSpan); +} + export const dummySpan = createSpan('dummy'); export function task(importMetaMain: boolean, importMetaPath: string) { - return (fn: (span: Span, onCleanup: (cb: () => Promise | void) => void) => Promise, customName?: string) => { + return (fn: (span: Span, onCleanup: (cb: () => Promise | void) => void) => Promise, customName?: string) => { const taskName = customName ?? basename(importMetaPath, extname(importMetaPath)); let cleanup: () => Promise | void = noop; const onCleanup = (cb: () => void) => { cleanup = cb; }; - const innerSpan = createSpan(taskName); if (importMetaMain) { + const innerSpan = createSpan(taskName); + process.on('uncaughtException', (error) => { console.error('Uncaught exception:', error); process.exit(1); @@ -126,15 +128,26 @@ export function task(importMetaMain: boolean, importMetaPath: string) { }); } - function run(span?: Span | null): Promise { - return fn(span || innerSpan, onCleanup).finally(() => { - (span || innerSpan).stop(); + let runSpan: Span; + async function run(parentSpan?: Span | null): Promise { + if (parentSpan) { + runSpan = parentSpan.traceChild(taskName); + } else { + runSpan = createSpan(taskName); + } + + try { + await fn(runSpan, onCleanup); + } finally { + runSpan.stop(); cleanup(); - }); + } + + return runSpan.traceResult; } return Object.assign(run, { - getInternalTraceResult: () => innerSpan.traceResult + getInternalTraceResult: () => runSpan.traceResult }); }; } @@ -159,8 +172,7 @@ export async function whyIsNodeRunning() { // }; // }; -export function printTraceResult(traceResult: TraceResult = rootTraceResult) { - printStats(traceResult.children); +export function printTraceResult(traceResult: TraceResult) { printTree( traceResult, node => { @@ -206,7 +218,7 @@ function printTree(initialTree: TraceResult, printNode: (node: TraceResult, bran printBranch(initialTree, '', true, false); } -function printStats(stats: TraceResult[]): void { +export function printStats(stats: TraceResult[]): void { const longestTaskName = Math.max(...stats.map(i => i.name.length)); const realStart = Math.min(...stats.map(i => i.start)); const realEnd = Math.max(...stats.map(i => i.end));