Skip to content

Commit a418b57

Browse files
authored
Merge pull request #101 from drivecore/feature/cli-profiling
Add CLI startup profiling feature
2 parents d40cdc8 + 4bae99b commit a418b57

File tree

7 files changed

+171
-13
lines changed

7 files changed

+171
-13
lines changed

packages/cli/README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,19 @@ mycoder config set modelName gpt-4o-2024-05-13
8282

8383
### Model Selection
8484

85+
NOTE: Anthropic Claude 3.7 works the best by far in our testing.
86+
8587
MyCoder supports Anthropic, OpenAI, xAI/Grok, Mistral AI, and Ollama models. You can configure which model provider and model name to use with the following commands:
8688

8789
```bash
90+
# Use Anthropic models [These work the best at this time]
91+
mycoder config set modelProvider anthropic
92+
mycoder config set modelName claude-3-7-sonnet-20250219 # or any other Anthropic model
93+
8894
# Use OpenAI models
8995
mycoder config set modelProvider openai
9096
mycoder config set modelName gpt-4o-2024-05-13 # or any other OpenAI model
9197

92-
# Use Anthropic models
93-
mycoder config set modelProvider anthropic
94-
mycoder config set modelName claude-3-7-sonnet-20250219 # or any other Anthropic model
95-
9698
# Use xAI/Grok models
9799
mycoder config set modelProvider xai
98100
mycoder config set modelName grok-1 # or any other xAI model
@@ -117,7 +119,7 @@ mycoder --modelProvider openai --modelName gpt-4o-2024-05-13 "Your prompt here"
117119

118120
### Available Configuration Options
119121

120-
- `githubMode`: Enable GitHub mode for working with issues and PRs (default: `false`)
122+
- `githubMode`: Enable GitHub mode (requires "gh" cli to be installed) for working with issues and PRs (default: `false`)
121123
- `headless`: Run browser in headless mode with no UI showing (default: `true`)
122124
- `userSession`: Use user's existing browser session instead of sandboxed session (default: `false`)
123125
- `pageFilter`: Method to process webpage content: 'simple', 'none', or 'readability' (default: `none`)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { CommandModule } from 'yargs';
2+
3+
import { SharedOptions } from '../options.js';
4+
5+
export const command: CommandModule<object, SharedOptions> = {
6+
command: 'test-profile',
7+
describe: 'Test the profiling feature',
8+
handler: async () => {
9+
console.log('Profile test completed successfully');
10+
// Profiling report will be automatically displayed by the main function
11+
12+
// Force a delay to simulate some processing
13+
await new Promise((resolve) => setTimeout(resolve, 100));
14+
},
15+
};

packages/cli/src/index.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,49 @@ import { hideBin } from 'yargs/helpers';
77

88
import { command as defaultCommand } from './commands/$default.js';
99
import { command as configCommand } from './commands/config.js';
10+
import { command as testProfileCommand } from './commands/test-profile.js';
1011
import { command as testSentryCommand } from './commands/test-sentry.js';
1112
import { command as toolsCommand } from './commands/tools.js';
1213
import { sharedOptions } from './options.js';
1314
import { initSentry, captureException } from './sentry/index.js';
14-
initSentry();
15+
import { getConfig } from './settings/config.js';
16+
import { enableProfiling, mark, reportTimings } from './utils/performance.js';
17+
18+
mark('After imports');
1519

1620
import type { PackageJson } from 'type-fest';
1721

1822
// Add global declaration for our patched toolAgent
1923

24+
mark('Before sourceMapSupport install');
2025
sourceMapSupport.install();
26+
mark('After sourceMapSupport install');
2127

2228
const main = async () => {
29+
mark('Main function start');
30+
2331
dotenv.config();
32+
mark('After dotenv config');
33+
34+
// Only initialize Sentry if needed
35+
if (
36+
process.env.NODE_ENV !== 'development' ||
37+
process.env.ENABLE_SENTRY === 'true'
38+
) {
39+
initSentry();
40+
mark('After Sentry init');
41+
}
2442

43+
mark('Before package.json load');
2544
const require = createRequire(import.meta.url);
2645
const packageInfo = require('../package.json') as PackageJson;
46+
mark('After package.json load');
47+
48+
console.log('packageInfo', packageInfo);
2749

2850
// Set up yargs with the new CLI interface
29-
await yargs(hideBin(process.argv))
51+
mark('Before yargs setup');
52+
const argv = await yargs(hideBin(process.argv))
3053
.scriptName(packageInfo.name!)
3154
.version(packageInfo.version!)
3255
.options(sharedOptions)
@@ -35,17 +58,30 @@ const main = async () => {
3558
.command([
3659
defaultCommand,
3760
testSentryCommand,
61+
testProfileCommand,
3862
toolsCommand,
3963
configCommand,
4064
] as CommandModule[])
4165
.strict()
4266
.showHelpOnFail(true)
4367
.help().argv;
68+
69+
// Get config to check for profile setting
70+
const config = getConfig();
71+
72+
// Enable profiling if --profile flag is set or if enabled in config
73+
enableProfiling(Boolean(argv.profile) || Boolean(config.profile));
74+
mark('After yargs setup');
4475
};
4576

46-
await main().catch((error) => {
47-
console.error(error);
48-
// Capture the error with Sentry
49-
captureException(error);
50-
process.exit(1);
51-
});
77+
await main()
78+
.catch(async (error) => {
79+
console.error(error);
80+
// Capture the error with Sentry
81+
captureException(error);
82+
process.exit(1);
83+
})
84+
.finally(async () => {
85+
// Report timings if profiling is enabled
86+
await reportTimings();
87+
});

packages/cli/src/options.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type SharedOptions = {
99
readonly sentryDsn?: string;
1010
readonly modelProvider?: string;
1111
readonly modelName?: string;
12+
readonly profile?: boolean;
1213
};
1314

1415
export const sharedOptions = {
@@ -19,6 +20,11 @@ export const sharedOptions = {
1920
default: 'info',
2021
choices: ['debug', 'verbose', 'info', 'warn', 'error'],
2122
} as const,
23+
profile: {
24+
type: 'boolean',
25+
description: 'Enable performance profiling of CLI startup',
26+
default: false,
27+
} as const,
2228
modelProvider: {
2329
type: 'string',
2430
description: 'AI model provider to use',

packages/cli/src/settings/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const defaultConfig = {
1616
modelName: 'claude-3-7-sonnet-20250219',
1717
ollamaBaseUrl: 'http://localhost:11434/api',
1818
customPrompt: '',
19+
profile: false,
1920
};
2021

2122
export type Config = typeof defaultConfig;
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { performance } from 'perf_hooks';
2+
3+
// Store start time as soon as this module is imported
4+
const cliStartTime = performance.now();
5+
const timings: Record<string, number> = {};
6+
let isEnabled = false;
7+
8+
/**
9+
* Enable or disable performance tracking
10+
*/
11+
export function enableProfiling(enabled: boolean): void {
12+
isEnabled = enabled;
13+
}
14+
15+
/**
16+
* Mark a timing point in the application
17+
* Always collect data, but only report if profiling is enabled
18+
*/
19+
export function mark(label: string): void {
20+
// Always collect timing data regardless of whether profiling is enabled
21+
timings[label] = performance.now() - cliStartTime;
22+
}
23+
24+
/**
25+
* Log all collected performance metrics
26+
*/
27+
export async function reportTimings(): Promise<void> {
28+
if (!isEnabled) return;
29+
30+
console.log('\n📊 Performance Profile Results');
31+
console.log('=======================');
32+
console.log(
33+
`${'Label'.padEnd(40, ' ')}${'Time'.padStart(10, ' ')}${'Duration'.padStart(10, ' ')}`,
34+
);
35+
36+
// Sort timings by time value
37+
const sortedTimings = Object.entries(timings).sort((a, b) => a[1] - b[1]);
38+
39+
// Calculate durations between steps
40+
let previousTime = 0;
41+
for (const [label, time] of sortedTimings) {
42+
const duration = time - previousTime;
43+
console.log(
44+
`${label.padEnd(40, ' ')}${`${time.toFixed(2)}ms`.padStart(10, ' ')}${`${duration.toFixed(2)}ms`.padStart(10, ' ')}`,
45+
);
46+
previousTime = time;
47+
}
48+
49+
console.log(`Total startup time: ${previousTime.toFixed(2)}ms`);
50+
console.log('=======================\n');
51+
52+
// Report platform-specific information if on Windows
53+
if (process.platform === 'win32') {
54+
await reportPlatformInfo();
55+
}
56+
}
57+
58+
/**
59+
* Collect and report platform-specific information
60+
*/
61+
async function reportPlatformInfo(): Promise<void> {
62+
if (!isEnabled) return;
63+
64+
console.log('\n🖥️ Platform Information:');
65+
console.log('=======================');
66+
console.log(`Platform: ${process.platform}`);
67+
console.log(`Architecture: ${process.arch}`);
68+
console.log(`Node.js version: ${process.version}`);
69+
70+
// Windows-specific information
71+
if (process.platform === 'win32') {
72+
console.log('Windows-specific details:');
73+
console.log(`- Current working directory: ${process.cwd()}`);
74+
console.log(`- Path length: ${process.cwd().length} characters`);
75+
76+
// Check for antivirus markers by measuring file read time
77+
try {
78+
// Using dynamic import to avoid require
79+
const fs = await import('fs');
80+
const startTime = performance.now();
81+
fs.readFileSync(process.execPath);
82+
console.log(
83+
`- Time to read Node.js executable: ${(performance.now() - startTime).toFixed(2)}ms`,
84+
);
85+
} catch (error: unknown) {
86+
const errorMessage =
87+
error instanceof Error ? error.message : String(error);
88+
console.log(`- Error reading Node.js executable: ${errorMessage}`);
89+
}
90+
}
91+
92+
console.log('=======================\n');
93+
}
94+
95+
// Initial mark for module load time
96+
mark('Module initialization');

packages/cli/tests/settings/config.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ describe('Config', () => {
4444
modelProvider: 'anthropic',
4545
modelName: 'claude-3-7-sonnet-20250219',
4646
ollamaBaseUrl: 'http://localhost:11434/api',
47+
profile: false,
4748
customPrompt: '',
4849
});
4950
expect(fs.existsSync).toHaveBeenCalledWith(mockConfigFile);
@@ -77,6 +78,7 @@ describe('Config', () => {
7778
modelProvider: 'anthropic',
7879
modelName: 'claude-3-7-sonnet-20250219',
7980
ollamaBaseUrl: 'http://localhost:11434/api',
81+
profile: false,
8082
customPrompt: '',
8183
});
8284
});

0 commit comments

Comments
 (0)