Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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);
});
3 changes: 0 additions & 3 deletions packages/nx/src/daemon/server/shutdown-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
DaemonProjectGraphError,
ProjectGraphError,
} from '../../project-graph/error-types';
import { removeDbConnections } from '../../utils/db-connection';
import { cleanupPlugins } from '../../project-graph/plugins/get-plugins';
import { MESSAGE_END_SEQ } from '../../utils/consume-messages-from-socket';
import { cleanupLatestNx } from './latest-nx';
Expand Down Expand Up @@ -144,8 +143,6 @@ async function performShutdown(
deleteDaemonJsonProcessCache();
cleanupPlugins();

removeDbConnections();

// Clean up shared latest Nx installation
cleanupLatestNx();

Expand Down
4 changes: 3 additions & 1 deletion packages/nx/src/utils/db-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ export function getLocalDbConnection(
return connection;
}

export function removeDbConnections() {
function removeDbConnections() {
for (const connection of dbConnectionMap.values()) {
closeDbConnection(connection);
}
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