Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions benchmarks/goals.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
"max": 0.3
},
"copy-warm": {
"max": 1.0
"max": 0.5
},
"build-warm": {
"max": 5.0
"max": 0.75
}
}
10 changes: 5 additions & 5 deletions benchmarks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
},
"scripts": {
"run": "tsx run-benchmarks.ts",
"bench:version": "NX_NO_CLOUD=true hyperfine --show-output --setup 'pnpm exec nx reset' --warmup 1 --min-runs 10 --export-json results-version.json 'pnpm exec nx --version'",
"bench:show-projects": "NX_NO_CLOUD=true hyperfine --show-output --setup 'pnpm exec nx reset' --warmup 1 --min-runs 5 --export-json results-show-projects.json 'pnpm exec nx show projects --tui=false'",
"bench:cat-warm": "NX_NO_CLOUD=true hyperfine --show-output --setup 'pnpm exec nx reset' --warmup 1 --min-runs 5 --export-json results-cat-warm.json 'pnpm exec nx run-many -t cat --tui=false'",
"bench:copy-warm": "NX_NO_CLOUD=true hyperfine --show-output --setup 'pnpm exec nx reset' --warmup 1 --min-runs 5 --export-json results-copy-warm.json 'pnpm exec nx run-many -t copy --tui=false'",
"bench:build-warm": "NX_NO_CLOUD=true hyperfine --setup 'pnpm exec nx reset' --warmup 1 --min-runs 5 --export-json results-build-warm.json 'pnpm exec nx run-many -t build --tui=true --tui-auto-exit'"
"bench:version": "NX_NO_CLOUD=true hyperfine --show-output --setup 'node ../packages/nx/dist/bin/nx.js reset' --warmup 1 --min-runs 10 --export-json results-version.json 'node ../packages/nx/dist/bin/nx.js --version'",
"bench:show-projects": "NX_NO_CLOUD=true hyperfine --show-output --setup 'node ../packages/nx/dist/bin/nx.js reset' --warmup 1 --min-runs 5 --export-json results-show-projects.json 'node ../packages/nx/dist/bin/nx.js show projects --tui=false'",
"bench:cat-warm": "NX_NO_CLOUD=true hyperfine --show-output --setup 'node ../packages/nx/dist/bin/nx.js reset' --warmup 1 --min-runs 5 --export-json results-cat-warm.json 'node ../packages/nx/dist/bin/nx.js run-many -t cat --tui=false'",
"bench:copy-warm": "NX_NO_CLOUD=true hyperfine --show-output --setup 'node ../packages/nx/dist/bin/nx.js reset' --warmup 1 --min-runs 5 --export-json results-copy-warm.json 'node ../packages/nx/dist/bin/nx.js run-many -t copy --tui=false'",
"bench:build-warm": "NX_NO_CLOUD=true hyperfine --setup 'node ../packages/nx/dist/bin/nx.js reset' --warmup 1 --min-runs 5 --export-json results-build-warm.json 'node ../packages/nx/dist/bin/nx.js run-many -t build --tui=true --tui-auto-exit'"
},
"nx": {
"targets": {
Expand Down
56 changes: 37 additions & 19 deletions packages/nx/bin/nx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import {
WorkspaceTypeAndRoot,
} from '../src/utils/find-workspace-root';
import * as pc from 'picocolors';
import { loadRootEnvFiles } from '../src/utils/dotenv';
import { initLocal } from './init-local';
import { output } from '../src/utils/output';
import {
getNxInstallationPath,
Expand All @@ -26,13 +24,11 @@ import { execSync } from 'child_process';
import { createRequire } from 'module';
import { extname, join } from 'path';
import { existsSync } from 'fs';
import { assertSupportedPlatform } from '../src/native/assert-supported-platform';
import { performance } from 'perf_hooks';
import { setupWorkspaceContext } from '../src/utils/workspace-context';
import { daemonClient } from '../src/daemon/client/client';
import { removeDbConnections } from '../src/utils/db-connection';
import { ensureAnalyticsPreferenceSet } from '../src/utils/analytics-prompt';
import { flushAnalytics, startAnalytics } from '../src/analytics';
// Register the performance observer as early as possible so any
// `performance.mark` / `measure` anywhere downstream is captured. The module
// is side-effect only and its heavy deps (analytics, daemon logger) are
// lazy-loaded inside the observer callback, so the import itself is cheap.
import '../src/utils/perf-logging';

const isTsExt = extname(__filename).endsWith('.ts');
Expand All @@ -45,12 +41,18 @@ async function main() {
process.argv[2] !== '--help' &&
process.argv[2] !== 'reset'
) {
const { assertSupportedPlatform } = await import(
'../src/native/assert-supported-platform.js'
);
assertSupportedPlatform();
}

const workspace = findWorkspaceRoot(process.cwd());

if (workspace) {
// --version doesn't need any env / daemon / analytics state — skip dotenv
// loading (and the heavy modules it would pull in).
if (workspace && process.argv[2] !== '--version') {
const { loadRootEnvFiles } = await import('../src/utils/dotenv.js');
performance.mark('loading dotenv files:start');
loadRootEnvFiles(workspace.dir);
performance.mark('loading dotenv files:end');
Expand All @@ -70,7 +72,7 @@ async function main() {
(process.argv[2] === 'graph' && !workspace)
) {
process.env.NX_DAEMON = 'false';
require('nx/src/command-line/nx-commands').commandsObject.argv;
(await import('nx/src/command-line/nx-commands')).commandsObject.argv;
} else {
// polyfill rxjs observable to avoid issues with multiple version of Observable installed in node_modules
// https://twitter.com/BenLesh/status/1192478226385428483?s=20
Expand Down Expand Up @@ -107,18 +109,27 @@ async function main() {

// this file is already in the local workspace
if (isNxCloudCommand(process.argv[2])) {
const { daemonClient } = await import('../src/daemon/client/client.js');
if (!daemonClient.enabled() && workspace !== null) {
const { setupWorkspaceContext } = await import(
'../src/utils/workspace-context.js'
);
setupWorkspaceContext(workspace.dir);
}
await initAnalytics();
// nx-cloud commands can run without local Nx installation
process.env.NX_DAEMON = 'false';
require('nx/src/command-line/nx-commands').commandsObject.argv;
(await import('nx/src/command-line/nx-commands')).commandsObject.argv;
} else if (isLocalInstall) {
const { daemonClient } = await import('../src/daemon/client/client.js');
if (!daemonClient.enabled() && workspace !== null) {
const { setupWorkspaceContext } = await import(
'../src/utils/workspace-context.js'
);
setupWorkspaceContext(workspace.dir);
}
await initAnalytics();
const { initLocal } = await import('./init-local.js');
await initLocal(workspace);
} else if (localNx) {
// Nx is being run from globally installed CLI - hand off to the local
Expand All @@ -127,9 +138,9 @@ async function main() {
warnIfUsingOutdatedGlobalInstall(GLOBAL_NX_VERSION, LOCAL_NX_VERSION);
if (localNx.includes('.nx')) {
const nxWrapperPath = localNx.replace(/\.nx.*/, '.nx/') + 'nxw.js';
require(nxWrapperPath);
await import(nxWrapperPath);
} else {
require(localNx);
await import(localNx);
}
}
}
Expand Down Expand Up @@ -220,11 +231,17 @@ function isNxCloudCommand(command: string): boolean {
return nxCloudCommands.includes(command);
}

let analyticsStarted = false;
async function initAnalytics() {
const { ensureAnalyticsPreferenceSet } = await import(
'../src/utils/analytics-prompt.js'
);
const { startAnalytics } = await import('../src/analytics/index.js');
try {
await ensureAnalyticsPreferenceSet();
} catch {}
await startAnalytics();
analyticsStarted = true;
}

function handleMissingLocalInstallation(detectedWorkspaceRoot: string | null) {
Expand Down Expand Up @@ -339,12 +356,13 @@ const getLatestVersionOfNx = ((fn: () => string) => {
return () => cache || (cache = fn());
})(_getLatestVersionOfNx);

process.on('exit', () => {
removeDbConnections();
});

main().catch((error) => {
main().catch(async (error) => {
console.error(error);
flushAnalytics();
if (analyticsStarted) {
// analyticsStarted implies '../src/analytics' is already in the module
// cache, so this resolves from cache without any disk work.
const { flushAnalytics } = await import('../src/analytics/index.js');
flushAnalytics();
}
process.exit(1);
});
2 changes: 2 additions & 0 deletions packages/nx/src/utils/db-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export function removeDbConnections() {
dbConnectionMap.clear();
}

process.on('exit', removeDbConnections);

function getEntryOrSet<TKey, TVal>(
map: Map<TKey, TVal>,
key: TKey,
Expand Down
80 changes: 39 additions & 41 deletions packages/nx/src/utils/perf-logging.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { PerformanceObserver } from 'perf_hooks';

import { customDimensions, reportEvent } from '../analytics';
import type { EventParameters } from '../analytics';
import type { TrackedDetail } from './perf-hooks';
import { isOnDaemon } from '../daemon/is-on-daemon';
import { serverLogger } from '../daemon/logger';

function isTrackedDetail(detail: unknown): detail is TrackedDetail {
return (
Expand All @@ -14,41 +10,43 @@ function isTrackedDetail(detail: unknown): detail is TrackedDetail {
);
}

const dimensionValues = customDimensions
? new Set(Object.values(customDimensions))
: null;

let initialized = false;

if (!initialized) {
initialized = true;

const obs = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const { detail } = entry;

if (process.env.NX_PERF_LOGGING === 'true') {
const message = `Time taken for '${entry.name}' ${entry.duration}ms`;
if (isOnDaemon()) {
serverLogger.log(message);
} else {
console.log(message);
}
}

if (isTrackedDetail(detail) && dimensionValues) {
const { track, ...rest } = detail;
const eventParameters: EventParameters = {
[customDimensions.duration]: entry.duration,
};
for (const [key, value] of Object.entries(rest)) {
if (dimensionValues.has(key)) {
eventParameters[key] = value;
}
}
reportEvent(entry.name, eventParameters);
}
new PerformanceObserver((list) => {
const entries = list.getEntries();
const logEnabled = process.env.NX_PERF_LOGGING === 'true';
const tracked = entries.filter((e) => isTrackedDetail(e.detail));

// Short-circuit before loading analytics / daemon logger (~60ms of native
// binding + module init) when there's nothing to do.
if (!logEnabled && tracked.length === 0) return;

if (logEnabled) {
const { isOnDaemon } =
require('../daemon/is-on-daemon') as typeof import('../daemon/is-on-daemon');
const { serverLogger } =
require('../daemon/logger') as typeof import('../daemon/logger');
const log = isOnDaemon()
? (msg: string) => serverLogger.log(msg)
: console.log;
for (const entry of entries) {
log(`Time taken for '${entry.name}' ${entry.duration}ms`);
}
});
obs.observe({ entryTypes: ['measure'] });
}
}

if (tracked.length === 0) return;

const { customDimensions, reportEvent } =
require('../analytics') as typeof import('../analytics');
if (!customDimensions) return;
const dimensionValues = new Set(Object.values(customDimensions));

for (const entry of tracked) {
const { track, ...rest } = entry.detail as TrackedDetail;
const params: import('../analytics').EventParameters = {
[customDimensions.duration]: entry.duration,
};
for (const [key, value] of Object.entries(rest)) {
if (dimensionValues.has(key)) params[key] = value;
}
reportEvent(entry.name, params);
}
}).observe({ entryTypes: ['measure'] });
Loading