1.0.7
This commit is contained in:
1 parent
bd7b832394
commit
7643807717
86 files changed
+3644
-1385
No files matched your search
@@ -285,11 +285,19 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
const inspectUrl = `${config.type}://${config.host}:${config.port}${inspectPath}`;
|
||||
const inspectHeaders: Record<string, string> = {};
|
||||
if (config.hawserToken) inspectHeaders['X-Hawser-Token'] = config.hawserToken;
|
||||
inspectResponse = await fetch(inspectUrl, {
|
||||
headers: inspectHeaders,
|
||||
// @ts-ignore
|
||||
tls: config.type === 'https' ? { ca: config.ca, cert: config.cert, key: config.key } : undefined
|
||||
});
|
||||
const fetchOpts: any = { headers: inspectHeaders };
|
||||
if (config.type === 'https') {
|
||||
fetchOpts.tls = {
|
||||
sessionTimeout: 0, // Disable TLS session caching for mTLS
|
||||
servername: config.host,
|
||||
rejectUnauthorized: true
|
||||
};
|
||||
if (config.ca) fetchOpts.tls.ca = [config.ca];
|
||||
if (config.cert) fetchOpts.tls.cert = [config.cert];
|
||||
if (config.key) fetchOpts.tls.key = config.key;
|
||||
fetchOpts.keepalive = false;
|
||||
}
|
||||
inspectResponse = await fetch(inspectUrl, fetchOpts);
|
||||
}
|
||||
|
||||
if (inspectResponse.ok) {
|
||||
@@ -341,12 +349,22 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
const logsUrl = `${config.type}://${config.host}:${config.port}${logsPath}`;
|
||||
const logsHeaders: Record<string, string> = {};
|
||||
if (config.hawserToken) logsHeaders['X-Hawser-Token'] = config.hawserToken;
|
||||
response = await fetch(logsUrl, {
|
||||
const fetchOpts: any = {
|
||||
headers: logsHeaders,
|
||||
signal: abortController?.signal,
|
||||
// @ts-ignore
|
||||
tls: config.type === 'https' ? { ca: config.ca, cert: config.cert, key: config.key } : undefined
|
||||
});
|
||||
signal: abortController?.signal
|
||||
};
|
||||
if (config.type === 'https') {
|
||||
fetchOpts.tls = {
|
||||
sessionTimeout: 0, // Disable TLS session caching for mTLS
|
||||
servername: config.host,
|
||||
rejectUnauthorized: true
|
||||
};
|
||||
if (config.ca) fetchOpts.tls.ca = [config.ca];
|
||||
if (config.cert) fetchOpts.tls.cert = [config.cert];
|
||||
if (config.key) fetchOpts.tls.key = config.key;
|
||||
fetchOpts.keepalive = false;
|
||||
}
|
||||
response = await fetch(logsUrl, fetchOpts);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getContainerStats } from '$lib/server/docker';
|
||||
import { getContainerStats, EnvironmentNotFoundError } from '$lib/server/docker';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { hasEnvironments } from '$lib/server/db';
|
||||
|
||||
@@ -47,6 +47,28 @@ function calculateBlockIO(stats: any): { read: number; write: number } {
|
||||
return { read, write };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate memory usage the same way Docker CLI does.
|
||||
* Docker subtracts cache (inactive_file) from total usage to show actual memory consumption.
|
||||
* - cgroup v2: subtract inactive_file from stats
|
||||
* - cgroup v1: subtract total_inactive_file from stats
|
||||
* See: https://docs.docker.com/engine/containers/runmetrics/
|
||||
*
|
||||
* Returns: { usage: actual memory (minus cache), raw: total usage, cache: file cache }
|
||||
*/
|
||||
function calculateMemoryUsage(memoryStats: any): { usage: number; raw: number; cache: number } {
|
||||
const raw = memoryStats?.usage || 0;
|
||||
const stats = memoryStats?.stats || {};
|
||||
|
||||
// cgroup v2 uses 'inactive_file', cgroup v1 uses 'total_inactive_file'
|
||||
const cache = stats.inactive_file ?? stats.total_inactive_file ?? 0;
|
||||
|
||||
// Only subtract cache if it's less than raw usage (sanity check)
|
||||
const usage = (cache > 0 && cache < raw) ? raw - cache : raw;
|
||||
|
||||
return { usage, raw, cache };
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
@@ -67,15 +89,17 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
const stats = await getContainerStats(params.id, envIdNum) as any;
|
||||
|
||||
const cpuPercent = calculateCpuPercent(stats);
|
||||
const memoryUsage = stats.memory_stats?.usage || 0;
|
||||
const memory = calculateMemoryUsage(stats.memory_stats);
|
||||
const memoryLimit = stats.memory_stats?.limit || 1;
|
||||
const memoryPercent = (memoryUsage / memoryLimit) * 100;
|
||||
const memoryPercent = (memory.usage / memoryLimit) * 100;
|
||||
const networkIO = calculateNetworkIO(stats);
|
||||
const blockIO = calculateBlockIO(stats);
|
||||
|
||||
return json({
|
||||
cpuPercent: Math.round(cpuPercent * 100) / 100,
|
||||
memoryUsage,
|
||||
memoryUsage: memory.usage,
|
||||
memoryRaw: memory.raw,
|
||||
memoryCache: memory.cache,
|
||||
memoryLimit,
|
||||
memoryPercent: Math.round(memoryPercent * 100) / 100,
|
||||
networkRx: networkIO.rx,
|
||||
@@ -85,6 +109,10 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
timestamp: Date.now()
|
||||
});
|
||||
} catch (error: any) {
|
||||
// Return 404 for deleted environments so client can clear stale cache
|
||||
if (error instanceof EnvironmentNotFoundError) {
|
||||
return json({ error: 'Environment not found' }, { status: 404 });
|
||||
}
|
||||
console.error('Failed to get container stats:', error);
|
||||
return json({ error: error.message || 'Failed to get stats' }, { status: 500 });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { listContainers, getContainerStats } from '$lib/server/docker';
|
||||
import { listContainers, getContainerStats, EnvironmentNotFoundError } from '$lib/server/docker';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { hasEnvironments } from '$lib/server/db';
|
||||
import type { ContainerStats } from '$lib/types';
|
||||
@@ -48,6 +48,28 @@ function calculateBlockIO(stats: any): { read: number; write: number } {
|
||||
return { read, write };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate memory usage the same way Docker CLI does.
|
||||
* Docker subtracts cache (inactive_file) from total usage to show actual memory consumption.
|
||||
* - cgroup v2: subtract inactive_file from stats
|
||||
* - cgroup v1: subtract total_inactive_file from stats
|
||||
* See: https://docs.docker.com/engine/containers/runmetrics/
|
||||
*
|
||||
* Returns: { usage: actual memory (minus cache), raw: total usage, cache: file cache }
|
||||
*/
|
||||
function calculateMemoryUsage(memoryStats: any): { usage: number; raw: number; cache: number } {
|
||||
const raw = memoryStats?.usage || 0;
|
||||
const stats = memoryStats?.stats || {};
|
||||
|
||||
// cgroup v2 uses 'inactive_file', cgroup v1 uses 'total_inactive_file'
|
||||
const cache = stats.inactive_file ?? stats.total_inactive_file ?? 0;
|
||||
|
||||
// Only subtract cache if it's less than raw usage (sanity check)
|
||||
const usage = (cache > 0 && cache < raw) ? raw - cache : raw;
|
||||
|
||||
return { usage, raw, cache };
|
||||
}
|
||||
|
||||
// Helper to add timeout to promises
|
||||
function withTimeout<T>(promise: Promise<T>, ms: number, fallback: T): Promise<T> {
|
||||
return Promise.race([
|
||||
@@ -112,10 +134,10 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
if (!stats) return null;
|
||||
|
||||
const cpuPercent = calculateCpuPercent(stats);
|
||||
// Use raw memory usage (total memory attributed to container)
|
||||
const memoryUsage = stats.memory_stats?.usage || 0;
|
||||
// Calculate memory usage the same way Docker CLI does (excludes cache)
|
||||
const memory = calculateMemoryUsage(stats.memory_stats);
|
||||
const memoryLimit = stats.memory_stats?.limit || 1;
|
||||
const memoryPercent = (memoryUsage / memoryLimit) * 100;
|
||||
const memoryPercent = (memory.usage / memoryLimit) * 100;
|
||||
const networkIO = calculateNetworkIO(stats);
|
||||
const blockIO = calculateBlockIO(stats);
|
||||
|
||||
@@ -123,7 +145,9 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
id: container.id,
|
||||
name: container.name,
|
||||
cpuPercent: Math.round(cpuPercent * 100) / 100,
|
||||
memoryUsage,
|
||||
memoryUsage: memory.usage,
|
||||
memoryRaw: memory.raw,
|
||||
memoryCache: memory.cache,
|
||||
memoryLimit,
|
||||
memoryPercent: Math.round(memoryPercent * 100) / 100,
|
||||
networkRx: networkIO.rx,
|
||||
@@ -142,6 +166,10 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
|
||||
return json(validStats);
|
||||
} catch (error: any) {
|
||||
// Return 404 for deleted environments so client can clear stale cache
|
||||
if (error instanceof EnvironmentNotFoundError) {
|
||||
return json({ error: 'Environment not found' }, { status: 404 });
|
||||
}
|
||||
console.error('Failed to get container stats:', error);
|
||||
return json([], { status: 200 }); // Return empty array instead of error
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user