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
20 changes: 20 additions & 0 deletions electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ function getBuildDate(): string {
export default defineConfig(({ command }) => {
const csp = command === "serve" ? devCsp : prodCsp;
const isProd = command === "build";
const watchIgnored = [
"**/node_modules/**",
"**/.git/**",
"**/build/**",
"**/dist/**",
"**/screenshots/**",
"**/Library/**",
];

const buildDefines = {
__BUILD_DATE__: isProd ? JSON.stringify(getBuildDate()) : "undefined",
Expand All @@ -51,6 +59,9 @@ export default defineConfig(({ command }) => {
lib: {
entry: resolve(__dirname, "electron/main/index.ts"),
},
watch: {
exclude: watchIgnored,
},
rollupOptions: {
external: ["better-sqlite3", "sharp"],
},
Expand All @@ -63,6 +74,9 @@ export default defineConfig(({ command }) => {
entry: resolve(__dirname, "electron/preload/index.ts"),
formats: ["cjs"],
},
watch: {
exclude: watchIgnored,
},
rollupOptions: {
output: {
entryFileNames: "[name].cjs",
Expand All @@ -72,6 +86,12 @@ export default defineConfig(({ command }) => {
},
renderer: {
root: ".",
server: {
watch: {
usePolling: false,
ignored: watchIgnored,
},
},
build: {
rollupOptions: {
input: resolve(__dirname, "index.html"),
Expand Down
2 changes: 2 additions & 0 deletions electron/main/app/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from "../features/socialFeed";
import { initializeUpdater } from "../features/update";
import { createLogger } from "../infra/log";
import { startCpuSampler } from "../infra/log/cpu";
import { initSessionLogStore } from "../infra/log/sessionLogStore";
import { getSettings } from "../infra/settings";
import { registerAllHandlers } from "../ipc";
Expand Down Expand Up @@ -74,6 +75,7 @@ export async function bootstrap(): Promise<void> {
await initSessionLogStore().catch(() => {});

logger.info("App starting...");
startCpuSampler();

const evalArgs = parseEvalArgs(process.argv);
if (evalArgs) {
Expand Down
20 changes: 19 additions & 1 deletion electron/main/features/activityWindow/ActivityWindowService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { mkdir, readdir, rename, rm } from "node:fs/promises";
import { basename, join } from "node:path";
import { performance } from "node:perf_hooks";
import { powerMonitor, screen } from "electron";
import { v4 as uuid } from "uuid";
import { SELF_APP_BUNDLE_ID } from "../../../shared/appIdentity";
Expand All @@ -17,10 +18,12 @@ import type { ActivityContext, ForegroundSnapshot } from "../context";
import { collectActivityContext, collectForegroundSnapshot } from "../context";
import { chromiumProvider, safariProvider } from "../context/providers";
import { type ActivitySegment, computeDominantSegment } from "./dominance";
import { createPerfTracker } from "../../infra/log/perf";

const logger = createLogger({ scope: "ActivityWindow" });
const perf = createPerfTracker("Perf.ActivityWindow");

const POLL_MS = 1_000;
const POLL_MS = 3_000;
const BROWSER_HOST_REFRESH_MS = 2_000;
const MIN_STABLE_MS = 10_000;
const MIN_DOMINANT_TOTAL_MS = 10_000;
Expand Down Expand Up @@ -284,6 +287,7 @@ async function captureCandidate(target: {
displayId: string;
urlHost: string | null;
}): Promise<void> {
const startedAt = perf.enabled ? performance.now() : 0;
if (state.status !== "running") return;
if (powerMonitor.getSystemIdleTime() > IDLE_AWAY_SECONDS) return;
if (state.candidates.has(target.key)) return;
Expand Down Expand Up @@ -336,10 +340,13 @@ async function captureCandidate(target: {
} finally {
release();
state.captureInFlight = null;
if (perf.enabled)
perf.track("activity.captureCandidate", performance.now() - startedAt);
}
}

async function pollOnce(): Promise<void> {
const pollStartedAt = perf.enabled ? performance.now() : 0;
await withWindowLock(async () => {
if (state.status !== "running") return;
const idleTimeSeconds = powerMonitor.getSystemIdleTime();
Expand Down Expand Up @@ -377,7 +384,13 @@ async function pollOnce(): Promise<void> {
closeCurrent(activeAt);
}

const snapshotStart = perf.enabled ? performance.now() : 0;
const snapshot = await collectForegroundSnapshot();
if (perf.enabled)
perf.track(
"activity.collectForegroundSnapshot",
performance.now() - snapshotStart,
);
if (state.status !== "running") return;
if (!snapshot) return;

Expand All @@ -387,7 +400,10 @@ async function pollOnce(): Promise<void> {
const displayId =
snapshot.window.displayId ?? String(screen.getPrimaryDisplay().id);
const bundleId = snapshot.app.bundleId;
const hostStart = perf.enabled ? performance.now() : 0;
const urlHost = await resolveUrlHost(snapshot, displayId);
if (perf.enabled)
perf.track("activity.resolveUrlHost", performance.now() - hostStart);
const key = buildKey(displayId, bundleId, urlHost);

if (!state.current) {
Expand Down Expand Up @@ -420,6 +436,8 @@ async function pollOnce(): Promise<void> {

void captureCandidate({ key, bundleId, displayId, urlHost });
});
if (perf.enabled)
perf.track("activity.pollOnce", performance.now() - pollStartedAt);
}

export function startActivityWindowTracking(): void {
Expand Down
51 changes: 46 additions & 5 deletions electron/main/features/capture/CaptureService.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { performance } from "node:perf_hooks";
import { desktopCapturer, screen } from "electron";
import sharp from "sharp";
import { v4 as uuid } from "uuid";
import type { CaptureResult } from "../../../shared/types";
import { createLogger } from "../../infra/log";
import { getOriginalsDir, getThumbnailsDir } from "../../infra/paths";
import { computeFingerprint } from "./FingerprintService";
import { createPerfTracker } from "../../infra/log/perf";

const logger = createLogger({ scope: "CaptureService" });
const perf = createPerfTracker("Perf.Capture");

const THUMBNAIL_WIDTH = 400;
const ORIGINAL_WIDTH = 1280;
Expand Down Expand Up @@ -161,6 +164,7 @@ export async function processInstantCapture(
dirs?: { thumbnailsDir: string; originalsDir: string };
},
): Promise<CaptureResult[]> {
const totalStart = perf.enabled ? performance.now() : 0;
const highResDisplayId = options?.highResDisplayId ?? null;
const thumbnailsDir = options?.dirs?.thumbnailsDir ?? getThumbnailsDir();
const originalsDir = options?.dirs?.originalsDir ?? getOriginalsDir();
Expand All @@ -171,6 +175,7 @@ export async function processInstantCapture(
}));

const processPromises = rawSources.map(async (source) => {
const startedAt = perf.enabled ? performance.now() : 0;
const thumbnailPath = join(thumbnailsDir, `${source.id}.webp`);
const originalPath = join(originalsDir, `${source.id}.webp`);
const highResPath = highResPathForId(source.id, originalsDir);
Expand Down Expand Up @@ -219,7 +224,7 @@ export async function processInstantCapture(
...writePromises,
]);

return {
const result = {
id: source.id,
timestamp: capture.timestamp,
displayId: source.displayId,
Expand All @@ -230,9 +235,18 @@ export async function processInstantCapture(
width: source.width,
height: source.height,
};
if (perf.enabled)
perf.track(
"capture.processInstant.display",
performance.now() - startedAt,
);
return result;
});

return Promise.all(processPromises);
const results = await Promise.all(processPromises);
if (perf.enabled)
perf.track("capture.processInstant.total", performance.now() - totalStart);
return results;
}

export async function captureRawScreens(): Promise<RawCapture[]> {
Expand Down Expand Up @@ -275,11 +289,13 @@ export async function processRawCaptures(
dirs?: { thumbnailsDir: string; originalsDir: string };
},
): Promise<CaptureResult[]> {
const totalStart = perf.enabled ? performance.now() : 0;
const highResDisplayId = options?.highResDisplayId ?? null;
const thumbnailsDir = options?.dirs?.thumbnailsDir ?? getThumbnailsDir();
const originalsDir = options?.dirs?.originalsDir ?? getOriginalsDir();

const processPromises = rawCaptures.map(async (raw) => {
const startedAt = perf.enabled ? performance.now() : 0;
const thumbnailPath = join(thumbnailsDir, `${raw.id}.webp`);
const originalPath = join(originalsDir, `${raw.id}.webp`);
const highResPath = highResPathForId(raw.id, originalsDir);
Expand Down Expand Up @@ -328,7 +344,7 @@ export async function processRawCaptures(
...writePromises,
]);

return {
const result = {
id: raw.id,
timestamp: raw.timestamp,
displayId: raw.displayId,
Expand All @@ -339,9 +355,18 @@ export async function processRawCaptures(
width: raw.displayWidth,
height: raw.displayHeight,
};
if (perf.enabled)
perf.track(
"capture.processRaw.display",
performance.now() - startedAt,
);
return result;
});

return Promise.all(processPromises);
const results = await Promise.all(processPromises);
if (perf.enabled)
perf.track("capture.processRaw.total", performance.now() - totalStart);
return results;
}

interface RawSourceData {
Expand All @@ -356,6 +381,7 @@ export async function captureAllDisplays(options?: {
highResDisplayId?: string | null;
dirs?: { thumbnailsDir: string; originalsDir: string };
}): Promise<CaptureResult[]> {
const totalStart = perf.enabled ? performance.now() : 0;
logger.info("Capturing all displays...");

const highResDisplayId = options?.highResDisplayId ?? null;
Expand All @@ -366,10 +392,13 @@ export async function captureAllDisplays(options?: {

const timestamp = Date.now();

const sourcesStart = perf.enabled ? performance.now() : 0;
const sources = await desktopCapturer.getSources({
types: ["screen"],
thumbnailSize: bestThumbnailSize(),
});
if (perf.enabled)
perf.track("capture.getSources", performance.now() - sourcesStart);

logger.debug(`Got ${sources.length} screen sources`);

Expand Down Expand Up @@ -401,6 +430,7 @@ export async function captureAllDisplays(options?: {
}

const processPromises = rawSources.map(async (raw) => {
const startedAt = perf.enabled ? performance.now() : 0;
const thumbnailPath = join(thumbnailsDir, `${raw.id}.webp`);
const originalPath = join(originalsDir, `${raw.id}.webp`);
const highResPath = highResPathForId(raw.id, originalsDir);
Expand Down Expand Up @@ -456,7 +486,7 @@ export async function captureAllDisplays(options?: {
thumbnailSize: thumbnail.length,
});

return {
const result = {
id: raw.id,
timestamp,
displayId: raw.displayId,
Expand All @@ -467,9 +497,20 @@ export async function captureAllDisplays(options?: {
width: raw.displayWidth,
height: raw.displayHeight,
};
if (perf.enabled)
perf.track(
"capture.allDisplays.display",
performance.now() - startedAt,
);
return result;
});

const results = await Promise.all(processPromises);
if (perf.enabled)
perf.track(
"capture.allDisplays.total",
performance.now() - totalStart,
);

logger.info(`Capture complete: ${results.length} results`);
return results;
Expand Down
16 changes: 15 additions & 1 deletion electron/main/features/capture/FingerprintService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { performance } from "node:perf_hooks";
import sharp from "sharp";
import type { Fingerprint, FingerprintComparison } from "../../../shared/types";
import { createPerfTracker } from "../../infra/log/perf";
const perf = createPerfTracker("Perf.Fingerprint");

type DHashSpec = {
hashWidth: number;
Expand Down Expand Up @@ -44,6 +47,7 @@ async function computeDHashHex(
input: Buffer,
spec: DHashSpec,
): Promise<string> {
const startedAt = perf.enabled ? performance.now() : 0;
const sampleWidth = spec.hashWidth + 1;
const sampleHeight = spec.hashHeight;

Expand All @@ -69,10 +73,18 @@ async function computeDHashHex(
}

const hexLen = Math.ceil(bits / 4);
return hexPad(acc.toString(16), hexLen);
const result = hexPad(acc.toString(16), hexLen);
if (perf.enabled) {
perf.track(
`fingerprint.dhash.${spec.hashWidth}x${spec.hashHeight}`,
performance.now() - startedAt,
);
}
return result;
}

export async function computeFingerprint(input: Buffer): Promise<Fingerprint> {
const startedAt = perf.enabled ? performance.now() : 0;
const [stableHash, detailHash] = await Promise.all([
computeDHashHex(input, {
hashWidth: 8,
Expand All @@ -88,6 +100,8 @@ export async function computeFingerprint(input: Buffer): Promise<Fingerprint> {
}),
]);

if (perf.enabled)
perf.track("fingerprint.total", performance.now() - startedAt);
return { stableHash, detailHash };
}

Expand Down
Loading