Skip to content

Commit 625565c

Browse files
authored
fix: More performance fixes + add dev-only timing instrumentation (#963)
1 parent ee55933 commit 625565c

11 files changed

Lines changed: 358 additions & 153 deletions

File tree

apps/twig/src/main/lib/timing.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1+
import { createTimingCollector, type TimingCollector } from "@posthog/shared";
2+
import { app } from "electron";
13
import type { ScopedLogger } from "./logger.js";
24

5+
export type { TimingCollector };
6+
37
/**
4-
* Creates a timing helper that logs execution duration.
5-
* @param log - Scoped logger to use for timing output
6-
* @returns A function that times async operations and logs the result
8+
* Creates a timing collector for the main process.
9+
* No-op in packaged (production) builds.
710
*/
8-
export function createTimer(log: ScopedLogger) {
9-
return async <T>(label: string, fn: () => Promise<T>): Promise<T> => {
10-
const start = Date.now();
11-
const result = await fn();
12-
log.info(`[timing] ${label}: ${Date.now() - start}ms`);
13-
return result;
14-
};
11+
export function createMainTimingCollector(log: ScopedLogger): TimingCollector {
12+
return createTimingCollector(!app.isPackaged, (msg, data) =>
13+
log.info(msg, data),
14+
);
1515
}

apps/twig/src/main/services/agent/schemas.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export const startSessionInput = z.object({
4545
runMode: z.enum(["local", "cloud"]).optional(),
4646
adapter: z.enum(["claude", "codex"]).optional(),
4747
additionalDirectories: z.array(z.string()).optional(),
48+
/** Dev-only: timestamp from renderer when user submitted the task */
49+
submittedAt: z.number().optional(),
4850
});
4951

5052
export type StartSessionInput = z.infer<typeof startSessionInput>;

apps/twig/src/main/services/agent/service.ts

Lines changed: 94 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { app } from "electron";
2525
import { inject, injectable, preDestroy } from "inversify";
2626
import { MAIN_TOKENS } from "../../di/tokens.js";
2727
import { logger } from "../../lib/logger.js";
28+
import { createMainTimingCollector } from "../../lib/timing.js";
2829
import { TypedEventEmitter } from "../../lib/typed-event-emitter.js";
2930
import type { FsService } from "../fs/service.js";
3031
import { getCurrentUserId, getPostHogClient } from "../posthog-analytics.js";
@@ -178,6 +179,8 @@ interface SessionConfig {
178179
additionalDirectories?: string[];
179180
/** Permission mode to use for the session */
180181
permissionMode?: string;
182+
/** Dev-only: timestamp from renderer when user submitted the task */
183+
submittedAt?: number;
181184
}
182185

183186
interface ManagedSession {
@@ -418,6 +421,8 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
418421
isReconnect: boolean,
419422
isRetry = false,
420423
): Promise<ManagedSession | null> {
424+
const tc = createMainTimingCollector(log);
425+
421426
const {
422427
taskId,
423428
taskRunId,
@@ -427,8 +432,13 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
427432
adapter,
428433
additionalDirectories,
429434
permissionMode,
435+
submittedAt,
430436
} = config;
431437

438+
if (submittedAt) {
439+
tc.record("ipcTransit", Date.now() - submittedAt);
440+
}
441+
432442
// Preview sessions don't need a real repo — use a temp directory
433443
const repoPath = taskId === "__preview__" ? tmpdir() : rawRepoPath;
434444

@@ -439,23 +449,31 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
439449
}
440450

441451
// Kill any lingering processes from previous runs of this task
442-
this.processTracking.killByTaskId(taskId);
452+
tc.timeSync("killProcesses", () =>
453+
this.processTracking.killByTaskId(taskId),
454+
);
443455

444456
// Clean up any prior session for this taskRunId before creating a new one
445-
await this.cleanupSession(taskRunId);
457+
await tc.time("cleanup", () => this.cleanupSession(taskRunId));
446458
}
447459

448460
const channel = `agent-event:${taskRunId}`;
449-
const mockNodeDir = this.setupMockNodeEnvironment(taskRunId);
450-
this.setupEnvironment(credentials, mockNodeDir);
461+
const mockNodeDir = tc.timeSync("mockNode", () =>
462+
this.setupMockNodeEnvironment(taskRunId),
463+
);
464+
tc.timeSync("setupEnv", () =>
465+
this.setupEnvironment(credentials, mockNodeDir),
466+
);
451467

452468
// Preview sessions don't persist logs — no real task exists
453469
const isPreview = taskId === "__preview__";
454470

455471
// OTEL log pipeline or legacy S3 writer if FF false
456472
const useOtelPipeline = isPreview
457473
? false
458-
: await this.isFeatureFlagEnabled("twig-agent-logs-pipeline");
474+
: await tc.time("featureFlag", () =>
475+
this.isFeatureFlagEnabled("twig-agent-logs-pipeline"),
476+
);
459477

460478
log.info("Agent log transport", {
461479
transport: isPreview ? "none" : useOtelPipeline ? "otel" : "s3",
@@ -482,78 +500,83 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
482500
});
483501

484502
try {
485-
const acpConnection = await agent.run(taskId, taskRunId, {
486-
adapter,
487-
codexBinaryPath: adapter === "codex" ? getCodexBinaryPath() : undefined,
488-
processCallbacks: {
489-
onProcessSpawned: (info) => {
490-
this.processTracking.register(
491-
info.pid,
492-
"agent",
493-
`agent:${taskRunId}`,
494-
{
495-
taskRunId,
503+
const acpConnection = await tc.time("agentRun", () =>
504+
agent.run(taskId, taskRunId, {
505+
adapter,
506+
codexBinaryPath:
507+
adapter === "codex" ? getCodexBinaryPath() : undefined,
508+
processCallbacks: {
509+
onProcessSpawned: (info) => {
510+
this.processTracking.register(
511+
info.pid,
512+
"agent",
513+
`agent:${taskRunId}`,
514+
{
515+
taskRunId,
516+
taskId,
517+
command: info.command,
518+
},
496519
taskId,
497-
command: info.command,
498-
},
499-
taskId,
500-
);
501-
},
502-
onProcessExited: (pid) => {
503-
this.processTracking.unregister(pid, "agent-exited");
520+
);
521+
},
522+
onProcessExited: (pid) => {
523+
this.processTracking.unregister(pid, "agent-exited");
524+
},
504525
},
505-
},
506-
});
526+
}),
527+
);
507528
const { clientStreams } = acpConnection;
508529

509-
const connection = this.createClientConnection(
510-
taskRunId,
511-
channel,
512-
clientStreams,
530+
const connection = tc.timeSync("clientConnection", () =>
531+
this.createClientConnection(taskRunId, channel, clientStreams),
513532
);
514533

515-
await connection.initialize({
516-
protocolVersion: PROTOCOL_VERSION,
517-
clientCapabilities: {
518-
fs: {
519-
readTextFile: true,
520-
writeTextFile: true,
534+
await tc.time("initialize", () =>
535+
connection.initialize({
536+
protocolVersion: PROTOCOL_VERSION,
537+
clientCapabilities: {
538+
fs: {
539+
readTextFile: true,
540+
writeTextFile: true,
541+
},
542+
terminal: true,
521543
},
522-
terminal: true,
523-
},
524-
});
544+
}),
545+
);
525546

526-
const mcpServers =
527-
adapter === "codex" ? [] : this.buildMcpServers(credentials);
547+
const mcpServers = tc.timeSync("buildMcp", () =>
548+
adapter === "codex" ? [] : this.buildMcpServers(credentials),
549+
);
528550

529551
let configOptions: SessionConfigOption[] | undefined;
530552
let agentSessionId: string;
531553

532554
if (isReconnect && adapter === "codex" && config.sessionId) {
533-
const loadResponse = await connection.loadSession({
534-
sessionId: config.sessionId,
535-
cwd: repoPath,
536-
mcpServers,
537-
});
555+
const loadResponse = await tc.time("loadSession", () =>
556+
connection.loadSession({
557+
sessionId: config.sessionId!,
558+
cwd: repoPath,
559+
mcpServers,
560+
}),
561+
);
538562
configOptions = loadResponse.configOptions ?? undefined;
539563
agentSessionId = config.sessionId;
540564
} else if (isReconnect && adapter !== "codex") {
541565
if (!config.sessionId) {
542566
throw new Error("Cannot resume session without sessionId");
543567
}
544568
const systemPrompt = this.buildPostHogSystemPrompt(credentials);
545-
const resumeResponse = await connection.extMethod(
546-
"_posthog/session/resume",
547-
{
548-
sessionId: config.sessionId,
569+
const resumeResponse = await tc.time("resumeSession", () =>
570+
connection.extMethod("_posthog/session/resume", {
571+
sessionId: config.sessionId!,
549572
cwd: repoPath,
550573
mcpServers,
551574
_meta: {
552575
...(logUrl && {
553576
persistence: { taskId, runId: taskRunId, logUrl },
554577
}),
555578
taskRunId,
556-
sessionId: config.sessionId,
579+
sessionId: config.sessionId!,
557580
systemPrompt,
558581
...(permissionMode && { permissionMode }),
559582
...(additionalDirectories?.length && {
@@ -562,7 +585,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
562585
},
563586
}),
564587
},
565-
},
588+
}),
566589
);
567590
const resumeMeta = resumeResponse?._meta as
568591
| {
@@ -573,26 +596,31 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
573596
agentSessionId = config.sessionId;
574597
} else {
575598
const systemPrompt = this.buildPostHogSystemPrompt(credentials);
576-
const newSessionResponse = await connection.newSession({
577-
cwd: repoPath,
578-
mcpServers,
579-
_meta: {
580-
taskRunId,
581-
systemPrompt,
582-
...(permissionMode && { permissionMode }),
583-
...(additionalDirectories?.length && {
584-
claudeCode: {
585-
options: { additionalDirectories },
586-
},
587-
}),
588-
},
589-
});
599+
const newSessionResponse = await tc.time("newSession", () =>
600+
connection.newSession({
601+
cwd: repoPath,
602+
mcpServers,
603+
_meta: {
604+
taskRunId,
605+
systemPrompt,
606+
...(permissionMode && { permissionMode }),
607+
...(additionalDirectories?.length && {
608+
claudeCode: {
609+
options: { additionalDirectories },
610+
},
611+
}),
612+
},
613+
}),
614+
);
590615
configOptions = newSessionResponse.configOptions ?? undefined;
591616
agentSessionId = newSessionResponse.sessionId;
592617
}
593618

594619
config.sessionId = agentSessionId;
595620

621+
const sessionType = isReconnect ? "reconnect" : "create";
622+
tc.summarize(`getOrCreateSession(${sessionType})`);
623+
596624
const session: ManagedSession = {
597625
taskRunId,
598626
taskId,
@@ -1300,6 +1328,7 @@ For git operations while detached:
13001328
: undefined,
13011329
permissionMode:
13021330
"permissionMode" in params ? params.permissionMode : undefined,
1331+
submittedAt: "submittedAt" in params ? params.submittedAt : undefined,
13031332
};
13041333
}
13051334

0 commit comments

Comments
 (0)