-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontainerManager.js
More file actions
166 lines (140 loc) · 5 KB
/
containerManager.js
File metadata and controls
166 lines (140 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
const Docker = require("dockerode");
const docker = new Docker();
const containerUsage = {};
const containerIndex = {};
const inProgressContainers = {};
const MAX_USAGE_PER_CONTAINER = 20;
const MAX_CONTAINERS_PER_LANGUAGE = 10;
const IDLE_TIME_LIMIT = 3 * 60 * 1000;
const languages = {
python: {
image: "python:3.9",
extension: "py",
command: "python3 /code/Main.py",
interpreted: true,
memory: 256 * 1024 * 1024, cpu: 256
},
javascript: {
image: "node:18",
extension: "js",
command: "node /code/Main.js",
interpreted: true,
memory: 512 * 1024 * 1024, cpu: 512
},
java: {
image: "openjdk:17",
extension: "java",
compileCmd: "[ -f /code/Main.class ] || javac /code/Main.java",
runCmd: "java -cp /code Main",
interpreted: false,
memory: 1024 * 1024 * 1024, cpu: 1024
},
cpp: {
image: "gcc:latest",
extension: "cpp",
compileCmd: "[ -f /code/main ] || g++ /code/Main.cpp -o /code/main",
runCmd: "/code/main",
interpreted: false,
memory: 1024 * 1024 * 1024, cpu: 1024
},
};
async function getContainer(language) {
if (!containerUsage[language]) containerUsage[language] = {};
const containers = await docker.listContainers({ all: true });
const activeContainers = containers.filter((c) => c.Image === languages[language].image);
if (activeContainers.length < MAX_CONTAINERS_PER_LANGUAGE) {
return createNewContainer(language);
}
const leastUsedContainer = activeContainers.reduce((prev, curr) => {
if (containerUsage[language][curr.Id] < containerUsage[language][prev.Id]) return curr;
return prev;
});
if (containerUsage[language][leastUsedContainer.Id] < MAX_USAGE_PER_CONTAINER) {
containerUsage[language][leastUsedContainer.Id]++;
return docker.getContainer(leastUsedContainer.Id);
}
const leastUsedIndex = containerIndex[language] || 0;
const nextIndex = (leastUsedIndex + 1) % activeContainers.length;
containerIndex[language] = nextIndex;
const nextContainer = docker.getContainer(activeContainers[nextIndex].Id);
containerUsage[language][nextContainer.id]++;
return nextContainer;
}
async function releaseContainer(language, containerId) {
if (containerUsage[language] && containerUsage[language][containerId]) {
containerUsage[language][containerId]--;
console.log(`Released container ${containerId} for ${language}`);
}
}
async function createNewContainer(language) {
if (inProgressContainers[language]) return await inProgressContainers[language];
inProgressContainers[language] = (async () => {
try {
const { image, memory, cpu } = languages[language];
const container = await docker.createContainer({
Image: image,
Tty: false,
AttachStdout: true,
AttachStderr: true,
OpenStdin: true,
HostConfig: {
Binds: ["/tmp/compiler:/code"],
Memory: memory,
CpuShares: cpu,
RestartPolicy: {
Name: "on-failure",
MaximumRetryCount: 2,
},
},
});
await container.start();
containerUsage[language][container.id] = 1;
return container;
} finally {
delete inProgressContainers[language];
}
})();
return inProgressContainers[language];
}
async function cleanUpContainers() {
try {
const now = Date.now();
const remoteContainers = await docker.listContainers({ all: true });
const stoppedContainers = remoteContainers.filter((c) => c.State !== "running");
await Promise.allSettled(
stoppedContainers.map(async (c) => {
const container = docker.getContainer(c.Id);
await container.remove();
})
);
const runningContainers = remoteContainers.filter((c) => c.State === "running");
await Promise.allSettled(
runningContainers.map(async (c) => {
const container = docker.getContainer(c.Id);
try {
const { State } = await container.inspect();
const lastStarted = new Date(State.StartedAt).getTime();
const idleTime = now - lastStarted;
const stats = await container.stats({ stream: false });
const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - stats.precpu_stats.cpu_usage.total_usage;
const systemDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
const cpuPercent = (cpuDelta / systemDelta) * stats.cpu_stats.cpu_usage.percpu_usage.length * 100;
if (cpuPercent > 0 || idleTime < IDLE_TIME_LIMIT) return;
await container.stop();
await container.remove();
} catch (err) {
console.warn(`Error processing ${c.Id}: ${err.message}`);
}
})
);
} catch (error) {
console.error("Cleanup failed:", error);
}
}
setInterval(cleanUpContainers, 2 * 60 * 1000);
module.exports = {
getContainer,
languages,
docker,
releaseContainer,
};