fix: reduce local server status overhead
This commit is contained in:
@@ -2653,6 +2653,7 @@ async fn spawn_local_server(app: &tauri::AppHandle) -> Result<String> {
|
|||||||
.env("OPENCHAMBER_DIST_DIR", dist_dir.clone())
|
.env("OPENCHAMBER_DIST_DIR", dist_dir.clone())
|
||||||
.env("OPENCHAMBER_RUNTIME", "desktop")
|
.env("OPENCHAMBER_RUNTIME", "desktop")
|
||||||
.env("OPENCHAMBER_DESKTOP_NOTIFY", "true")
|
.env("OPENCHAMBER_DESKTOP_NOTIFY", "true")
|
||||||
|
.env("OPENCHAMBER_SKIP_API_COMPRESSION", "true")
|
||||||
.env("PATH", augmented_path.clone())
|
.env("PATH", augmented_path.clone())
|
||||||
.env("NO_PROXY", no_proxy)
|
.env("NO_PROXY", no_proxy)
|
||||||
.env("no_proxy", no_proxy);
|
.env("no_proxy", no_proxy);
|
||||||
|
|||||||
@@ -768,6 +768,7 @@ const spawnLocalServer = async () => {
|
|||||||
process.env.OPENCHAMBER_DIST_DIR = resolveWebDistDir();
|
process.env.OPENCHAMBER_DIST_DIR = resolveWebDistDir();
|
||||||
process.env.OPENCHAMBER_RUNTIME = 'desktop';
|
process.env.OPENCHAMBER_RUNTIME = 'desktop';
|
||||||
process.env.OPENCHAMBER_DESKTOP_NOTIFY = 'true';
|
process.env.OPENCHAMBER_DESKTOP_NOTIFY = 'true';
|
||||||
|
process.env.OPENCHAMBER_SKIP_API_COMPRESSION = process.env.OPENCHAMBER_SKIP_API_COMPRESSION || 'true';
|
||||||
process.env.NO_PROXY = process.env.NO_PROXY || 'localhost,127.0.0.1';
|
process.env.NO_PROXY = process.env.NO_PROXY || 'localhost,127.0.0.1';
|
||||||
process.env.no_proxy = process.env.no_proxy || 'localhost,127.0.0.1';
|
process.env.no_proxy = process.env.no_proxy || 'localhost,127.0.0.1';
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ OPENCODE_HOST=https://myhost:4096 OPENCODE_SKIP_START=true openchamber
|
|||||||
| `OPENCODE_PORT` | Port of external server |
|
| `OPENCODE_PORT` | Port of external server |
|
||||||
| `OPENCODE_SKIP_START` | Skip starting embedded OpenCode server |
|
| `OPENCODE_SKIP_START` | Skip starting embedded OpenCode server |
|
||||||
| `OPENCHAMBER_OPENCODE_HOSTNAME` | Bind hostname for managed OpenCode server (default: `127.0.0.1`, use `0.0.0.0` for LAN/remote access — trusted networks only) |
|
| `OPENCHAMBER_OPENCODE_HOSTNAME` | Bind hostname for managed OpenCode server (default: `127.0.0.1`, use `0.0.0.0` for LAN/remote access — trusted networks only) |
|
||||||
|
| `OPENCHAMBER_HOST` | Bind hostname for the OpenChamber web server (default: `127.0.0.1`; use `0.0.0.0` for LAN/remote access — trusted networks only) |
|
||||||
|
| `OPENCHAMBER_VERBOSE_REQUEST_LOGS` | Set to `true` to log every HTTP request; disabled by default to keep user logs small |
|
||||||
|
| `OPENCHAMBER_SKIP_API_COMPRESSION` | Set to `true` to disable gzip compression for `/api/*` responses |
|
||||||
|
| `OPENCHAMBER_COMPRESS_API` | Set to `true` to force `/api/*` compression, or `false` to disable it. Desktop runtime disables API compression by default to reduce local sidecar CPU use |
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,10 @@ function shouldSkipCompression(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pathname = req.path || req.url || '';
|
const pathname = req.path || req.url || '';
|
||||||
|
if ((pathname === '/api' || pathname.startsWith('/api/')) && shouldSkipApiCompression()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (pathname.startsWith('/api/terminal/') && pathname.endsWith('/stream')) {
|
if (pathname.startsWith('/api/terminal/') && pathname.endsWith('/stream')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -168,6 +172,22 @@ const isEnvFlagEnabled = (value) => {
|
|||||||
return normalized === '1' || normalized === 'true';
|
return normalized === '1' || normalized === 'true';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isEnvFlagDisabled = (value) => {
|
||||||
|
if (value === false || value === 0) return true;
|
||||||
|
if (typeof value !== 'string') return false;
|
||||||
|
const normalized = value.trim().toLowerCase();
|
||||||
|
return normalized === '0' || normalized === 'false';
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldSkipApiCompression = () => {
|
||||||
|
if (isEnvFlagEnabled(process.env.OPENCHAMBER_SKIP_API_COMPRESSION)) return true;
|
||||||
|
if (isEnvFlagEnabled(process.env.OPENCHAMBER_COMPRESS_API)) return false;
|
||||||
|
if (isEnvFlagDisabled(process.env.OPENCHAMBER_COMPRESS_API)) return true;
|
||||||
|
return process.env.OPENCHAMBER_RUNTIME === 'desktop';
|
||||||
|
};
|
||||||
|
|
||||||
|
const OPENCHAMBER_VERBOSE_REQUEST_LOGS = isEnvFlagEnabled(process.env.OPENCHAMBER_VERBOSE_REQUEST_LOGS);
|
||||||
|
|
||||||
const PLAN_MODE_EXPERIMENT_ENABLED =
|
const PLAN_MODE_EXPERIMENT_ENABLED =
|
||||||
isEnvFlagEnabled(process.env.OPENCODE_EXPERIMENTAL_PLAN_MODE)
|
isEnvFlagEnabled(process.env.OPENCODE_EXPERIMENTAL_PLAN_MODE)
|
||||||
|| isEnvFlagEnabled(process.env.OPENCODE_EXPERIMENTAL);
|
|| isEnvFlagEnabled(process.env.OPENCODE_EXPERIMENTAL);
|
||||||
@@ -1101,6 +1121,7 @@ async function main(options = {}) {
|
|||||||
planModeExperimentalEnabled: PLAN_MODE_EXPERIMENT_ENABLED,
|
planModeExperimentalEnabled: PLAN_MODE_EXPERIMENT_ENABLED,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
verboseRequestLogs: OPENCHAMBER_VERBOSE_REQUEST_LOGS,
|
||||||
uiPassword,
|
uiPassword,
|
||||||
tunnelAuthController,
|
tunnelAuthController,
|
||||||
readSettingsFromDiskMigrated,
|
readSettingsFromDiskMigrated,
|
||||||
|
|||||||
@@ -1260,45 +1260,55 @@ export async function getStatus(directory, options = {}) {
|
|||||||
|
|
||||||
const diffStats = Object.fromEntries(diffStatsMap.entries());
|
const diffStats = Object.fromEntries(diffStatsMap.entries());
|
||||||
|
|
||||||
const newFileStats = lightMode ? [] : await Promise.all(
|
const MAX_NEW_FILE_STATS = 200;
|
||||||
status.files.map(async (file) => {
|
const MAX_NEW_FILE_STAT_SIZE = 1024 * 1024;
|
||||||
|
const newFileStats = [];
|
||||||
|
|
||||||
|
if (!lightMode) {
|
||||||
|
for (const file of status.files) {
|
||||||
|
if (newFileStats.length >= MAX_NEW_FILE_STATS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const working = (file.working_dir || '').trim();
|
const working = (file.working_dir || '').trim();
|
||||||
const indexStatus = (file.index || '').trim();
|
const indexStatus = (file.index || '').trim();
|
||||||
const statusCode = working || indexStatus;
|
const statusCode = working || indexStatus;
|
||||||
|
|
||||||
if (statusCode !== '?' && statusCode !== 'A') {
|
if (statusCode !== '?' && statusCode !== 'A') {
|
||||||
return null;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing = diffStats[file.path];
|
const existing = diffStats[file.path];
|
||||||
if (existing && existing.insertions > 0) {
|
if (existing && existing.insertions > 0) {
|
||||||
return null;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const absolutePath = path.join(directoryPath, file.path);
|
const absolutePath = path.join(directoryPath, file.path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stat = await fsp.stat(absolutePath);
|
const stat = await fsp.stat(absolutePath);
|
||||||
if (!stat.isFile()) {
|
if (!stat.isFile() || stat.size > MAX_NEW_FILE_STAT_SIZE) {
|
||||||
return null;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await fsp.readFile(absolutePath);
|
const buffer = await fsp.readFile(absolutePath);
|
||||||
if (buffer.indexOf(0) !== -1) {
|
if (buffer.indexOf(0) !== -1) {
|
||||||
return {
|
newFileStats.push({
|
||||||
path: file.path,
|
path: file.path,
|
||||||
insertions: existing?.insertions ?? 0,
|
insertions: existing?.insertions ?? 0,
|
||||||
deletions: existing?.deletions ?? 0,
|
deletions: existing?.deletions ?? 0,
|
||||||
};
|
});
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalized = buffer.toString('utf8').replace(/\r\n/g, '\n');
|
const normalized = buffer.toString('utf8').replace(/\r\n/g, '\n');
|
||||||
if (!normalized.length) {
|
if (!normalized.length) {
|
||||||
return {
|
newFileStats.push({
|
||||||
path: file.path,
|
path: file.path,
|
||||||
insertions: 0,
|
insertions: 0,
|
||||||
deletions: 0,
|
deletions: 0,
|
||||||
};
|
});
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const segments = normalized.split('\n');
|
const segments = normalized.split('\n');
|
||||||
@@ -1307,20 +1317,20 @@ export async function getStatus(directory, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lineCount = segments.length;
|
const lineCount = segments.length;
|
||||||
return {
|
newFileStats.push({
|
||||||
path: file.path,
|
path: file.path,
|
||||||
insertions: lineCount,
|
insertions: lineCount,
|
||||||
deletions: 0,
|
deletions: 0,
|
||||||
};
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to estimate diff stats for new file', file.path, error);
|
if (error?.code !== 'ENOENT') {
|
||||||
return null;
|
console.warn('Failed to estimate diff stats for new file', file.path, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
for (const entry of newFileStats) {
|
for (const entry of newFileStats) {
|
||||||
if (!entry) continue;
|
|
||||||
diffStats[entry.path] = {
|
diffStats[entry.path] = {
|
||||||
insertions: entry.insertions,
|
insertions: entry.insertions,
|
||||||
deletions: entry.deletions,
|
deletions: entry.deletions,
|
||||||
|
|||||||
+2
-1
@@ -18,6 +18,7 @@ export const createBootstrapRuntime = (dependencies) => {
|
|||||||
serverStartedAt,
|
serverStartedAt,
|
||||||
gracefulShutdown,
|
gracefulShutdown,
|
||||||
getHealthSnapshot,
|
getHealthSnapshot,
|
||||||
|
verboseRequestLogs,
|
||||||
uiPassword,
|
uiPassword,
|
||||||
tunnelAuthController,
|
tunnelAuthController,
|
||||||
readSettingsFromDiskMigrated,
|
readSettingsFromDiskMigrated,
|
||||||
@@ -60,7 +61,7 @@ export const createBootstrapRuntime = (dependencies) => {
|
|||||||
getHealthSnapshot,
|
getHealthSnapshot,
|
||||||
});
|
});
|
||||||
|
|
||||||
registerCommonRequestMiddleware(app, { express });
|
registerCommonRequestMiddleware(app, { express, verboseRequestLogs });
|
||||||
|
|
||||||
const uiAuthController = createUiAuth({
|
const uiAuthController = createUiAuth({
|
||||||
password: uiPassword,
|
password: uiPassword,
|
||||||
|
|||||||
@@ -451,7 +451,7 @@ export const registerSettingsUtilityRoutes = (app, dependencies) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const registerCommonRequestMiddleware = (app, dependencies) => {
|
export const registerCommonRequestMiddleware = (app, dependencies) => {
|
||||||
const { express } = dependencies;
|
const { express, verboseRequestLogs = false } = dependencies;
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
if (req.path.startsWith('/api/behavior')) {
|
if (req.path.startsWith('/api/behavior')) {
|
||||||
@@ -492,7 +492,9 @@ export const registerCommonRequestMiddleware = (app, dependencies) => {
|
|||||||
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
|
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
|
||||||
|
|
||||||
app.use((req, _res, next) => {
|
app.use((req, _res, next) => {
|
||||||
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
|
if (verboseRequestLogs) {
|
||||||
|
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
|
||||||
|
}
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user