mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-15 14:23:02 -07:00
perf: optimize startup time by using dynamic imports for heavy dependencies
This commit is contained in:
@@ -21,8 +21,6 @@ import {
|
||||
RestTransportFactory,
|
||||
createAuthenticatingFetchWithRetry,
|
||||
} from '@a2a-js/sdk/client';
|
||||
import { GrpcTransportFactory } from '@a2a-js/sdk/client/grpc';
|
||||
import * as grpc from '@grpc/grpc-js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Agent as UndiciAgent, ProxyAgent } from 'undici';
|
||||
import { normalizeAgentCard } from './a2aUtils.js';
|
||||
@@ -129,6 +127,8 @@ export class A2AClientManager {
|
||||
agentCard.additionalInterfaces?.find((i) => i.transport === 'GRPC')
|
||||
?.url ?? agentCard.url;
|
||||
|
||||
const { GrpcTransportFactory } = await import('@a2a-js/sdk/client/grpc');
|
||||
const grpc = await import('@grpc/grpc-js');
|
||||
const clientOptions = ClientFactoryOptions.createFrom(
|
||||
ClientFactoryOptions.default,
|
||||
{
|
||||
|
||||
@@ -8,10 +8,16 @@ import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { isNodeError } from '../utils/errors.js';
|
||||
import { spawnAsync } from '../utils/shell-utils.js';
|
||||
import { simpleGit, CheckRepoActions, type SimpleGit } from 'simple-git';
|
||||
import type { SimpleGit } from 'simple-git';
|
||||
import type { Storage } from '../config/storage.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
|
||||
|
||||
async function getSimpleGit() {
|
||||
const { simpleGit, CheckRepoActions } = await import('simple-git');
|
||||
return { simpleGit, CheckRepoActions };
|
||||
}
|
||||
|
||||
export class GitService {
|
||||
private projectRoot: string;
|
||||
private storage: Storage;
|
||||
@@ -79,6 +85,7 @@ export class GitService {
|
||||
|
||||
const shadowRepoEnv = this.getShadowRepoEnv(repoDir);
|
||||
await fs.writeFile(shadowRepoEnv.GIT_CONFIG_SYSTEM, '');
|
||||
const { simpleGit, CheckRepoActions } = await getSimpleGit();
|
||||
const repo = simpleGit(repoDir).env(shadowRepoEnv);
|
||||
let isRepoDefined = false;
|
||||
try {
|
||||
@@ -114,8 +121,9 @@ export class GitService {
|
||||
await fs.writeFile(shadowGitIgnorePath, userGitIgnoreContent);
|
||||
}
|
||||
|
||||
private get shadowGitRepository(): SimpleGit {
|
||||
private async shadowGitRepository(): Promise<SimpleGit> {
|
||||
const repoDir = this.getHistoryDir();
|
||||
const { simpleGit } = await getSimpleGit();
|
||||
return simpleGit(this.projectRoot).env({
|
||||
GIT_DIR: path.join(repoDir, '.git'),
|
||||
GIT_WORK_TREE: this.projectRoot,
|
||||
@@ -124,13 +132,13 @@ export class GitService {
|
||||
}
|
||||
|
||||
async getCurrentCommitHash(): Promise<string> {
|
||||
const hash = await this.shadowGitRepository.raw('rev-parse', 'HEAD');
|
||||
const hash = await (await this.shadowGitRepository()).raw('rev-parse', 'HEAD');
|
||||
return hash.trim();
|
||||
}
|
||||
|
||||
async createFileSnapshot(message: string): Promise<string> {
|
||||
try {
|
||||
const repo = this.shadowGitRepository;
|
||||
const repo = await this.shadowGitRepository();
|
||||
await repo.add('.');
|
||||
const status = await repo.status();
|
||||
if (status.isClean()) {
|
||||
@@ -149,7 +157,7 @@ export class GitService {
|
||||
}
|
||||
|
||||
async restoreProjectFromSnapshot(commitHash: string): Promise<void> {
|
||||
const repo = this.shadowGitRepository;
|
||||
const repo = await this.shadowGitRepository();
|
||||
await repo.raw(['restore', '--source', commitHash, '.']);
|
||||
// Removes any untracked files that were introduced post snapshot.
|
||||
await repo.clean('f', ['-d']);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import { createHash } from 'node:crypto';
|
||||
import * as os from 'node:os';
|
||||
import si from 'systeminformation';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import type {
|
||||
StartSessionEvent,
|
||||
@@ -262,6 +261,7 @@ let cachedGpuInfo: string | undefined;
|
||||
|
||||
async function refreshGpuInfo(): Promise<void> {
|
||||
try {
|
||||
const si = (await import('systeminformation')).default;
|
||||
const graphics = await si.graphics();
|
||||
if (graphics.controllers && graphics.controllers.length > 0) {
|
||||
cachedGpuInfo = graphics.controllers.map((c) => c.model).join(', ');
|
||||
|
||||
@@ -24,11 +24,7 @@ export {
|
||||
parseBooleanEnvFlag,
|
||||
parseTelemetryTargetValue,
|
||||
} from './config.js';
|
||||
export {
|
||||
GcpTraceExporter,
|
||||
GcpMetricExporter,
|
||||
GcpLogExporter,
|
||||
} from './gcp-exporters.js';
|
||||
|
||||
export {
|
||||
logCliConfiguration,
|
||||
logUserPrompt,
|
||||
|
||||
@@ -12,29 +12,28 @@ import {
|
||||
metrics,
|
||||
propagation,
|
||||
} from '@opentelemetry/api';
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
|
||||
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
|
||||
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
|
||||
import { OTLPTraceExporter as OTLPTraceExporterHttp } from '@opentelemetry/exporter-trace-otlp-http';
|
||||
import { OTLPLogExporter as OTLPLogExporterHttp } from '@opentelemetry/exporter-logs-otlp-http';
|
||||
import { OTLPMetricExporter as OTLPMetricExporterHttp } from '@opentelemetry/exporter-metrics-otlp-http';
|
||||
import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base';
|
||||
import { NodeSDK } from '@opentelemetry/sdk-node';
|
||||
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
|
||||
import { resourceFromAttributes } from '@opentelemetry/resources';
|
||||
import {
|
||||
import type { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
|
||||
import type { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
|
||||
|
||||
import type { OTLPTraceExporter as OTLPTraceExporterHttp } from '@opentelemetry/exporter-trace-otlp-http';
|
||||
import type { OTLPLogExporter as OTLPLogExporterHttp } from '@opentelemetry/exporter-logs-otlp-http';
|
||||
|
||||
|
||||
import type { NodeSDK } from '@opentelemetry/sdk-node';
|
||||
|
||||
|
||||
import type {
|
||||
BatchSpanProcessor,
|
||||
ConsoleSpanExporter,
|
||||
} from '@opentelemetry/sdk-trace-node';
|
||||
import {
|
||||
import type {
|
||||
BatchLogRecordProcessor,
|
||||
ConsoleLogRecordExporter,
|
||||
} from '@opentelemetry/sdk-logs';
|
||||
import {
|
||||
ConsoleMetricExporter,
|
||||
import type {
|
||||
PeriodicExportingMetricReader,
|
||||
} from '@opentelemetry/sdk-metrics';
|
||||
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
||||
|
||||
import type { JWTInput } from 'google-auth-library';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { SERVICE_NAME } from './constants.js';
|
||||
@@ -45,11 +44,7 @@ import {
|
||||
FileMetricExporter,
|
||||
FileSpanExporter,
|
||||
} from './file-exporters.js';
|
||||
import {
|
||||
GcpTraceExporter,
|
||||
GcpMetricExporter,
|
||||
GcpLogExporter,
|
||||
} from './gcp-exporters.js';
|
||||
import type { GcpTraceExporter , GcpLogExporter } from './gcp-exporters.js';
|
||||
import { TelemetryTarget } from './index.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { authEvents } from '../code_assist/oauth2.js';
|
||||
@@ -205,6 +200,8 @@ export async function initializeTelemetry(
|
||||
return;
|
||||
}
|
||||
|
||||
const { resourceFromAttributes } = await import('@opentelemetry/resources');
|
||||
const { SemanticResourceAttributes } = await import('@opentelemetry/semantic-conventions');
|
||||
const resource = resourceFromAttributes({
|
||||
[SemanticResourceAttributes.SERVICE_NAME]: SERVICE_NAME,
|
||||
[SemanticResourceAttributes.SERVICE_VERSION]: process.version,
|
||||
@@ -267,10 +264,12 @@ export async function initializeTelemetry(
|
||||
'using',
|
||||
credentials ? 'provided credentials' : 'ADC',
|
||||
);
|
||||
const { GcpTraceExporter } = await import('./gcp-exporters.js');
|
||||
spanExporter = new GcpTraceExporter(gcpProjectId, credentials);
|
||||
const { GcpLogExporter } = await import('./gcp-exporters.js');
|
||||
logExporter = new GcpLogExporter(gcpProjectId, credentials);
|
||||
metricReader = new PeriodicExportingMetricReader({
|
||||
exporter: new GcpMetricExporter(gcpProjectId, credentials),
|
||||
metricReader = new (await import('@opentelemetry/sdk-metrics')).PeriodicExportingMetricReader({
|
||||
exporter: new (await import('./gcp-exporters.js')).GcpMetricExporter(gcpProjectId, credentials),
|
||||
exportIntervalMillis: 30000,
|
||||
});
|
||||
} else if (useOtlp) {
|
||||
@@ -281,32 +280,36 @@ export async function initializeTelemetry(
|
||||
url.pathname = [url.pathname.replace(/\/$/, ''), path].join('/');
|
||||
return url.href;
|
||||
};
|
||||
const { OTLPTraceExporter: OTLPTraceExporterHttp } = await import('@opentelemetry/exporter-trace-otlp-http');
|
||||
spanExporter = new OTLPTraceExporterHttp({
|
||||
url: buildUrl('v1/traces'),
|
||||
});
|
||||
const { OTLPLogExporter: OTLPLogExporterHttp } = await import('@opentelemetry/exporter-logs-otlp-http');
|
||||
logExporter = new OTLPLogExporterHttp({
|
||||
url: buildUrl('v1/logs'),
|
||||
});
|
||||
metricReader = new PeriodicExportingMetricReader({
|
||||
exporter: new OTLPMetricExporterHttp({
|
||||
metricReader = new (await import('@opentelemetry/sdk-metrics')).PeriodicExportingMetricReader({
|
||||
exporter: new (await import('@opentelemetry/exporter-metrics-otlp-http')).OTLPMetricExporter({
|
||||
url: buildUrl('v1/metrics'),
|
||||
}),
|
||||
exportIntervalMillis: 10000,
|
||||
});
|
||||
} else {
|
||||
// grpc
|
||||
const { OTLPTraceExporter } = await import('@opentelemetry/exporter-trace-otlp-grpc');
|
||||
spanExporter = new OTLPTraceExporter({
|
||||
url: parsedEndpoint,
|
||||
compression: CompressionAlgorithm.GZIP,
|
||||
compression: (await import('@opentelemetry/otlp-exporter-base')).CompressionAlgorithm.GZIP,
|
||||
});
|
||||
const { OTLPLogExporter } = await import('@opentelemetry/exporter-logs-otlp-grpc');
|
||||
logExporter = new OTLPLogExporter({
|
||||
url: parsedEndpoint,
|
||||
compression: CompressionAlgorithm.GZIP,
|
||||
compression: (await import('@opentelemetry/otlp-exporter-base')).CompressionAlgorithm.GZIP,
|
||||
});
|
||||
metricReader = new PeriodicExportingMetricReader({
|
||||
exporter: new OTLPMetricExporter({
|
||||
metricReader = new (await import('@opentelemetry/sdk-metrics')).PeriodicExportingMetricReader({
|
||||
exporter: new (await import('@opentelemetry/exporter-metrics-otlp-grpc')).OTLPMetricExporter({
|
||||
url: parsedEndpoint,
|
||||
compression: CompressionAlgorithm.GZIP,
|
||||
compression: (await import('@opentelemetry/otlp-exporter-base')).CompressionAlgorithm.GZIP,
|
||||
}),
|
||||
exportIntervalMillis: 10000,
|
||||
});
|
||||
@@ -314,23 +317,29 @@ export async function initializeTelemetry(
|
||||
} else if (telemetryOutfile) {
|
||||
spanExporter = new FileSpanExporter(telemetryOutfile);
|
||||
logExporter = new FileLogExporter(telemetryOutfile);
|
||||
metricReader = new PeriodicExportingMetricReader({
|
||||
metricReader = new (await import('@opentelemetry/sdk-metrics')).PeriodicExportingMetricReader({
|
||||
exporter: new FileMetricExporter(telemetryOutfile),
|
||||
exportIntervalMillis: 10000,
|
||||
});
|
||||
} else {
|
||||
const { ConsoleSpanExporter } = await import('@opentelemetry/sdk-trace-node');
|
||||
spanExporter = new ConsoleSpanExporter();
|
||||
const { ConsoleLogRecordExporter } = await import('@opentelemetry/sdk-logs');
|
||||
logExporter = new ConsoleLogRecordExporter();
|
||||
metricReader = new PeriodicExportingMetricReader({
|
||||
exporter: new ConsoleMetricExporter(),
|
||||
metricReader = new (await import('@opentelemetry/sdk-metrics')).PeriodicExportingMetricReader({
|
||||
exporter: new (await import('@opentelemetry/sdk-metrics')).ConsoleMetricExporter(),
|
||||
exportIntervalMillis: 10000,
|
||||
});
|
||||
}
|
||||
|
||||
// Store processor references for manual flushing
|
||||
const { BatchSpanProcessor } = await import('@opentelemetry/sdk-trace-node');
|
||||
spanProcessor = new BatchSpanProcessor(spanExporter);
|
||||
const { BatchLogRecordProcessor } = await import('@opentelemetry/sdk-logs');
|
||||
logRecordProcessor = new BatchLogRecordProcessor(logExporter);
|
||||
|
||||
const { NodeSDK } = await import('@opentelemetry/sdk-node');
|
||||
const { HttpInstrumentation } = await import('@opentelemetry/instrumentation-http');
|
||||
sdk = new NodeSDK({
|
||||
resource,
|
||||
spanProcessors: [spanProcessor],
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
type SpawnOptionsWithoutStdio,
|
||||
} from 'node:child_process';
|
||||
import * as readline from 'node:readline';
|
||||
import { Language, Parser, Query, type Node, type Tree } from 'web-tree-sitter';
|
||||
import type { Language, Parser, Node, Tree, QueryCapture } from 'web-tree-sitter';
|
||||
import { loadWasmBinary } from './fileUtils.js';
|
||||
import { debugLogger } from './debugLogger.js';
|
||||
import type { SandboxManager } from '../services/sandboxManager.js';
|
||||
@@ -70,6 +70,8 @@ export async function resolveExecutable(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let _Parser: any = null;
|
||||
let _Query: any = null;
|
||||
let bashLanguage: Language | null = null;
|
||||
let treeSitterInitialization: Promise<void> | null = null;
|
||||
let treeSitterInitializationError: Error | null = null;
|
||||
@@ -113,8 +115,20 @@ async function loadBashLanguage(): Promise<void> {
|
||||
),
|
||||
]);
|
||||
|
||||
await Parser.init({ wasmBinary: treeSitterBinary });
|
||||
bashLanguage = await Language.load(bashBinary);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
||||
const wts: any = await import('web-tree-sitter');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
const ParserImpl = wts.default || wts;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
const LanguageImpl = wts.Language || ParserImpl.Language;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
_Query = wts.Query || ParserImpl.Query;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
_Parser = ParserImpl;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
await ParserImpl.init({ wasmBinary: treeSitterBinary });
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
bashLanguage = await LanguageImpl.load(bashBinary);
|
||||
} catch (error) {
|
||||
bashLanguage = null;
|
||||
const normalized = toError(error);
|
||||
@@ -213,7 +227,8 @@ function createParser(): Parser | null {
|
||||
}
|
||||
|
||||
try {
|
||||
const parser = new Parser();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
||||
const parser = new _Parser() as Parser;
|
||||
parser.setLanguage(bashLanguage);
|
||||
return parser;
|
||||
} catch {
|
||||
@@ -404,9 +419,10 @@ function parseBashCommandDetails(command: string): CommandParseResult | null {
|
||||
if (hasError) {
|
||||
let query = null;
|
||||
try {
|
||||
query = new Query(bashLanguage, '(ERROR) @error (MISSING) @missing');
|
||||
query = // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
||||
new _Query(bashLanguage, '(ERROR) @error (MISSING) @missing');
|
||||
const captures = query.captures(tree.rootNode);
|
||||
const syntaxErrors = captures.map((capture) => {
|
||||
const syntaxErrors = captures.map((capture: QueryCapture) => {
|
||||
const { node, name } = capture;
|
||||
const type = name === 'missing' ? 'Missing' : 'Error';
|
||||
return `${type} node: "${node.text}" at ${node.startPosition.row}:${node.startPosition.column}`;
|
||||
|
||||
Reference in New Issue
Block a user