diff --git a/services/code.service.js b/services/code.service.js index cf93b08d..41ee4dba 100644 --- a/services/code.service.js +++ b/services/code.service.js @@ -227,12 +227,15 @@ const _executeCode = async (req, res, response) => { // Check if there is no compilation error if (response.compileMessage === '') { - let command + const memoryReportPath = `/tmp/memory_report_${process.pid}_${Date.now()}.txt`; + + let command; if (language === 'java') { // Remove ulimit as a temp fix - command = `cd /tmp/ && timeout ${langConfig.timeout}s ${langConfig.run}` + command = `cd /tmp/ && /usr/bin/time -f "%M" -o ${memoryReportPath} timeout ${langConfig.timeout}s ${langConfig.run}`; } else { - command = `cd /tmp/ && ulimit -v ${langConfig.memory} && ulimit -m ${langConfig.memory} && timeout ${langConfig.timeout}s ${langConfig.run}` + // Execute command with memory limits and resource monitoring for non-Java languages + command = `cd /tmp/ && ulimit -v ${langConfig.memory} && ulimit -m ${langConfig.memory} && /usr/bin/time -f "%M" -o ${memoryReportPath} timeout ${langConfig.timeout}s ${langConfig.run}`; } // Check if there is any input that is to be provided to code execution @@ -244,6 +247,26 @@ const _executeCode = async (req, res, response) => { } const outputLog = await _runScript(command, res, true) + + let memoryKB = null + try { + await fs.promises.access(memoryReportPath, fs.constants.F_OK) + const memoryReport = await fs.promises.readFile(memoryReportPath, 'utf8') + memoryKB = parseInt(memoryReport.trim(), 10) + } catch (err) { + logger.error(`Memory report not found or failed to read: ${err.message}`) + } + + const isAlpine = fs.existsSync('/etc/alpine-release'); + + if (memoryKB) { + response.memory = isAlpine ? memoryKB / 4 : memoryKB; + } else { + response.memory = null; + } + + logger.info(`Memory usage for ${language} code execution: ${response.memory} KB`); + response.output = outputLog.error !== undefined ? _prepareErrorMessage(outputLog, language, command) diff --git a/tests/data/testJson.js b/tests/data/testJson.js index 823b5b35..5a71dd0f 100644 --- a/tests/data/testJson.js +++ b/tests/data/testJson.js @@ -1,18 +1,20 @@ + const testCases = [ { name: 'cpp : hello world', reqObject: { language: 'cpp', - script: - '#include\n' + - 'using namespace std;\n' + - 'int main(){\n' + - ' cout << "hello world";\n' + - 'return 0;\n' + - '}\n', + script: ` +#include +using namespace std; +int main(){ + cout << "hello world"; + return 0; +}`, }, expectedResponse: { val: 'hello world', + approxMemoryUses: 2744, status: 200, error: 0, }, @@ -21,20 +23,21 @@ const testCases = [ name: 'cpp : print stdin', reqObject: { language: 'cpp', - script: - '#include\n\n' + - 'using namespace std;\n' + - 'int main(){\n\n' + - ' int a;\n' + - ' while(cin >> a){\n' + - ' cout << a << endl;\n' + - ' }\n' + - ' return 0;\n\n' + - '}\n', + script: ` +#include +using namespace std; +int main(){ + int a; + while(cin >> a){ + cout << a << endl; + } + return 0; +}`, stdin: '1 2 3', }, expectedResponse: { val: '1\n2\n3\n', + approxMemoryUses: 2680, status: 200, error: 0, }, @@ -44,10 +47,11 @@ const testCases = [ name: 'nodejs : hello world', reqObject: { language: 'nodejs', - script: 'console.log(\'hello world\')', + script: `console.log('hello world')`, }, expectedResponse: { val: 'hello world\n', + approxMemoryUses: 44540, status: 200, error: 0, }, @@ -56,16 +60,16 @@ const testCases = [ name: 'nodejs : print stdin', reqObject: { language: 'nodejs', - script: - 'process.stdin.setEncoding(\'utf8\'); \n ' + - 'process.stdin.on(\'data\', (input) => { \n ' + - ' console.log(input); \n ' + - ' \n ' + - '}); \n ', + script: ` +process.stdin.setEncoding('utf8'); +process.stdin.on('data', (input) => { +console.log(input.trim()); +});`, stdin: '1 2 3', }, expectedResponse: { val: '1 2 3\n', + approxMemoryUses: 44888, status: 200, error: 0, }, @@ -74,10 +78,12 @@ const testCases = [ name: 'python : hello world', reqObject: { language: 'python', - script: 'print(\'hello world\')', + script: + `print('hello world')`, }, expectedResponse: { val: 'hello world\n', + approxMemoryUses: 5544, status: 200, error: 0, }, @@ -86,19 +92,15 @@ const testCases = [ name: 'python : print stdin', reqObject: { language: 'python', - script: - 'try:\n' + - ' while(True):\n' + - ' line = input()\n' + - ' if not line:\n' + - ' break\n' + - ' print(line)\n' + - 'except EOFError:\n' + - ' pass', + script: ` +import sys +for line in sys.stdin: + print(line.strip())`, stdin: '1 2 3', }, expectedResponse: { val: '1 2 3\n', + approxMemoryUses: 5800, status: 200, error: 0, }, @@ -108,14 +110,15 @@ const testCases = [ reqObject: { language: 'c', script: - '#include\n\n' + - 'int main(){\n\n' + - ' printf("hello world");\n' + - ' return 0;\n' + - '}\n', + `#include +int main(){ +printf("hello world"); +return 0; +} `, }, expectedResponse: { val: 'hello world', + approxMemoryUses: 900, status: 200, error: 0, }, @@ -124,37 +127,40 @@ const testCases = [ name: 'c : print stdin', reqObject: { language: 'c', - script: - '#include \n' + - 'int main() {\n' + - ' int number;\n' + - ' while (scanf("%d", &number) == 1) {\n' + - ' printf("%d\\n", number);\n' + - ' } \n' + - ' return 0;\n' + - '}', + script:` +#include +int main() { + int number; + while (scanf("%d", &number) == 1) { + printf("%d\\n", number); +} + return 0; +} `, stdin: '1 2 3', }, expectedResponse: { val: '1\n2\n3\n', + approxMemoryUses: 924, status: 200, error: 0, }, }, { - name: 'java : print stdin', + name: 'java : hello world', reqObject: { language: 'java', script: - 'import java.util.Scanner;\n' + - 'public class Solution {\n' + - ' public static void main(String[] args) {\n' + - ' System.out.println("hello world");\n' + - ' }\n' + - '}\n', + ` +import java.util.Scanner; +public class Solution { + public static void main(String[] args) { + System.out.println("hello world"); + } +}`, }, expectedResponse: { val: 'hello world\n', + approxMemoryUses: 33000, status: 200, error: 0, }, @@ -163,22 +169,23 @@ const testCases = [ name: 'java : print stdin', reqObject: { language: 'java', - script: - 'import java.util.Scanner;\n' + - 'public class Solution {\n' + - ' public static void main(String[] args) {\n' + - ' Scanner scanner = new Scanner(System.in);\n' + - ' while (scanner.hasNextInt()) {\n' + - ' int number = scanner.nextInt();\n' + - ' System.out.println(number);\n' + - ' } \n' + - ' scanner.close();\n' + - ' }\n' + - '}\n', + script: ` +import java.util.Scanner; +public class Solution { + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + while (scanner.hasNextInt()) { + int number = scanner.nextInt(); + System.out.println(number); + } + scanner.close(); + } +} `, stdin: '1 2 3', }, expectedResponse: { val: '1\n2\n3\n', + approxMemoryUses: 35900, status: 200, error: 0, }, @@ -188,10 +195,11 @@ const testCases = [ reqObject: { language: 'ruby', script: - 'print "hello world"' + `print "hello world"`, }, expectedResponse: { val: 'hello world', + approxMemoryUses: 22800, status: 200, error: 0, }, @@ -200,13 +208,14 @@ const testCases = [ name: 'ruby : print stdin', reqObject: { language: 'ruby', - script: - 'user_input = gets.chomp\n' + - 'puts user_input', - stdin: '10\n' + script: ` +user_input = gets.chomp +puts user_input`, + stdin: '10', }, expectedResponse: { val: '10\n', + approxMemoryUses: 22800, status: 200, error: 0, }, @@ -215,7 +224,8 @@ const testCases = [ name: 'TLE test', reqObject: { language: 'nodejs', - script: 'for(let i=0 ; ; ){i++}', + script: + `for(let i = 0 ;; ) { i++ } `, }, expectedResponse: { val: 'Time limit exceeded', @@ -227,7 +237,8 @@ const testCases = [ name: 'MLE test', reqObject: { language: 'python', - script: 'one_gb_data = bytearray(1000 * 1024 * 1024)', + script: + `one_gb_data = bytearray(1000 * 1024 * 1024)`, }, expectedResponse: { val: 'Memory limit exceeded', @@ -239,19 +250,19 @@ const testCases = [ name: 'MLE test 2', reqObject: { language: 'python', - script: - 'import time\n' + - 'def consume_memory(target_mb, duration_sec):\n' + - ' float_size = 8\n' + - ' floats_per_mb = (1024 * 1024) // float_size\n' + - ' total_floats = target_mb * floats_per_mb\n' + - ' iterations = int(duration_sec / 0.1)\n' + - ' floats_per_iteration = total_floats // iterations\n' + - ' memory_hog = []\n' + - ' for _ in range(iterations):\n' + - ' memory_hog.extend([0.0] * floats_per_iteration)\n' + - ' time.sleep(0.1)\n' + - 'consume_memory(1000, 1)\n', + script: ` +import time +def consume_memory(target_mb, duration_sec): + float_size = 8 + floats_per_mb = (1024 * 1024) // float_size + total_floats = target_mb * floats_per_mb + iterations = int(duration_sec / 0.1) + floats_per_iteration = total_floats // iterations + memory_hog = [] + for _ in range(iterations): + memory_hog.extend([0.0] * floats_per_iteration) + time.sleep(0.1) +consume_memory(1000, 1)`, }, expectedResponse: { val: 'Memory limit exceeded', @@ -263,10 +274,11 @@ const testCases = [ name: 'MLE test 3', reqObject: { language: 'python', - script: - 'a = [100]\n' + - 'for i in a:\n' + - ' a.append(i)\n', + script: ` +a = [100] +for i in a: + a.append(i) + `, }, expectedResponse: { val: 'Memory limit exceeded', @@ -298,6 +310,67 @@ const testCases = [ error: 0, }, }, + { + name: 'c :Heap memory allocation', + reqObject: { + language: 'c', + script: ` +#include +#include +int main() { + size_t memory_size = 50 * 1024 * 1024; + char *memory_block = malloc(memory_size); + if (memory_block == NULL) { + printf("Failed to allocate memory\\n"); + return 1; + } + printf("Memory allocation done\\n"); + for (size_t i = 0; i < memory_size; i += 4096) { + memory_block[i] = (char)(i % 256); + } + printf("Memory touched and initialized\\n"); + free(memory_block); + printf("Memory freed\\n"); + return 0; +} `, + }, + expectedResponse: { + val: 'Memory allocation done\nMemory touched and initialized\nMemory freed\n', + status: 200, + error: 0, + approxMemoryUses: 51000, + }, + }, + { + name: 'c :Stack memory allocation', + reqObject: { + language: 'c', + script: ` +#include +#include +#define ONE_MB (1024 * 1024) +void stack_allocate(int remaining_bytes, int depth) { + if (remaining_bytes <= 0) { + printf("Memory allocated on stack\\n"); + return; + } + char buffer[ONE_MB]; + memset(buffer, 0, ONE_MB); // Touch the memory + stack_allocate(remaining_bytes - ONE_MB, depth + 1); +} +int main() { + stack_allocate(6 * ONE_MB, 0); + return 0; +} `, + }, + expectedResponse: { + val: 'Memory allocated on stack\n', + status: 200, + error: 0, + approxMemoryUses: 6500, // Max stack uses limit is ~10 MB + }, + }, + ] module.exports = { testCases } diff --git a/tests/test.js b/tests/test.js index 7cd19621..5321549f 100644 --- a/tests/test.js +++ b/tests/test.js @@ -1,9 +1,13 @@ + const axios = require('axios') const { testCases } = require('./data/testJson') const { describe, expect, it } = require('@jest/globals') const ENDPOINT = process.env.ENDPOINT || 'http://localhost:3000/api/execute/' +// Memory comparison tolerance in percent (e.g., 20 means ±20%) +const MEMORY_TOLERANCE_PERCENT = 20 + describe('Tests', () => { for (const testCase of testCases) { it(testCase.name, async () => { @@ -15,6 +19,13 @@ describe('Tests', () => { expect(response.data.output.points).toBeDefined() } else { expect(response).toHaveProperty('data.output', testCase.expectedResponse.val) + if (testCase.expectedResponse.approxMemoryUses) { + const actual = response.data.memory + const expected = testCase.expectedResponse.approxMemoryUses + const tolerance = (MEMORY_TOLERANCE_PERCENT / 100) * expected + expect(actual).toBeGreaterThanOrEqual(expected - tolerance, `Memory usage should be within tolerance (min: ${expected - tolerance})`) + expect(actual).toBeLessThanOrEqual(expected + tolerance, `Memory usage should be within tolerance (max: ${expected + tolerance})`) + } } expect(response).toHaveProperty('status', testCase.expectedResponse.status) expect(response).toHaveProperty('data.error', testCase.expectedResponse.error)