@workspace-fs/core
Version:
Multi-project workspace manager for Firesystem with support for multiple sources
168 lines (146 loc) • 4.43 kB
text/typescript
import { IReactiveFileSystem } from "@firesystem/core";
import type {
Project,
WorkspaceSettings,
WorkspaceStats,
ProjectMetrics,
OptimizationReport,
} from "../types";
export class PerformanceManager {
constructor(
private settings: WorkspaceSettings,
private getProjects: () => Project[],
private getActiveProjectId: () => string | null,
private disableProject: (projectId: string) => Promise<void>,
) {}
/**
* Get workspace statistics
*/
async getProjectStats(totalProjectsCount: number, disabledCount: number): Promise<WorkspaceStats> {
const connections: Record<string, number> = {};
// Count connections by type
for (const project of this.getProjects()) {
const type = project.source.type;
connections[type] = (connections[type] || 0) + 1;
}
// Calculate total memory usage
let totalMemory = 0;
for (const project of this.getProjects()) {
totalMemory += project.memoryUsage || 0;
}
return {
total: totalProjectsCount,
active: this.getProjects().length,
disabled: disabledCount,
focused: this.getActiveProjectId(),
memoryUsage: this.formatBytes(totalMemory),
connections,
};
}
/**
* Get project metrics
*/
async getProjectMetrics(project: Project): Promise<ProjectMetrics> {
const files = await project.fs.glob("**/*");
let totalSize = 0;
let fileCount = 0;
let largestFile = { path: "", size: 0 };
let lastModified = new Date(0);
for (const file of files) {
const stat = await project.fs.stat(file);
if (stat.type === "file") {
fileCount++;
totalSize += stat.size;
if (stat.size > largestFile.size) {
largestFile = { path: file, size: stat.size };
}
if (stat.modified > lastModified) {
lastModified = stat.modified;
}
}
}
return {
fileCount,
totalSize,
lastModified,
accessCount: project.accessCount,
averageFileSize: fileCount > 0 ? Math.round(totalSize / fileCount) : 0,
largestFile,
};
}
/**
* Optimize memory usage
*/
async optimizeMemoryUsage(): Promise<OptimizationReport> {
const report: OptimizationReport = {
projectsDisabled: [],
memoryFreed: 0,
connectionsReleased: 0,
};
// Get current memory usage
const currentMemory = await this.calculateTotalMemoryUsage();
if (currentMemory < (this.settings.memoryThreshold || Infinity)) {
return report; // No optimization needed
}
// Sort projects by last accessed (oldest first)
const projectList = Array.from(this.getProjects())
.filter((p) => p.id !== this.getActiveProjectId()) // Never disable active project
.sort((a, b) => a.lastAccessed.getTime() - b.lastAccessed.getTime());
// Disable projects until under threshold
for (const project of projectList) {
if (
currentMemory - report.memoryFreed <
(this.settings.memoryThreshold || Infinity)
) {
break;
}
const memoryBefore = project.memoryUsage || 0;
await this.disableProject(project.id);
report.projectsDisabled.push(project.id);
report.memoryFreed += memoryBefore;
report.connectionsReleased++;
}
return report;
}
/**
* Estimate project memory usage
*/
async estimateProjectMemoryUsage(
fs: IReactiveFileSystem,
): Promise<number> {
try {
// Basic estimation - can be improved based on FS type
const size = await fs.size();
// Add overhead for metadata and structures (rough estimate)
return size * 1.2;
} catch {
return 0;
}
}
/**
* Calculate total memory usage
*/
private async calculateTotalMemoryUsage(): Promise<number> {
let total = 0;
for (const project of this.getProjects()) {
total += project.memoryUsage || 0;
}
return total;
}
/**
* Format bytes to human readable
*/
private formatBytes(bytes: number): string {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB", "TB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
}
/**
* Update settings
*/
updateSettings(settings: Partial<WorkspaceSettings>): void {
Object.assign(this.settings, settings);
}
}