diff --git a/libCacheSim-node/.gitignore b/libCacheSim-node/.gitignore new file mode 100644 index 00000000..b1f57179 --- /dev/null +++ b/libCacheSim-node/.gitignore @@ -0,0 +1,24 @@ +# Node.js dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +build/ +*.node + +# Package locks (optional for libraries) +package-lock.json + +# OS files +.DS_Store +Thumbs.db + +# IDE files +.vscode/ +.idea/ + +# Temporary files +*.tmp +*.log \ No newline at end of file diff --git a/libCacheSim-node/binding.cc b/libCacheSim-node/binding.cc new file mode 100644 index 00000000..49ba34d2 --- /dev/null +++ b/libCacheSim-node/binding.cc @@ -0,0 +1,263 @@ +#include +#include "libCacheSim.h" +#include +#include +#include +#include +#include +#include + +// Helper function to check if file exists +bool fileExists(const std::string& filename) { + struct stat buffer; + return (stat(filename.c_str(), &buffer) == 0); +} + +// Helper function to parse cache size string (e.g., "1mb", "1gb", "1024") +uint64_t parseCacheSize(const std::string& sizeStr) { + if (sizeStr.empty()) return 0; + + std::string lower = sizeStr; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + + // Extract number and unit + size_t pos = 0; + while (pos < lower.length() && (isdigit(lower[pos]) || lower[pos] == '.')) { + pos++; + } + + double value = std::stod(lower.substr(0, pos)); + std::string unit = lower.substr(pos); + + uint64_t multiplier = 1; + if (unit == "kb" || unit == "k") multiplier = 1024; + else if (unit == "mb" || unit == "m") multiplier = 1024 * 1024; + else if (unit == "gb" || unit == "g") multiplier = 1024 * 1024 * 1024; + else if (unit == "tb" || unit == "t") multiplier = 1024ULL * 1024 * 1024 * 1024; + + return (uint64_t)(value * multiplier); +} + +// Helper function to get cache constructor by algorithm name +cache_t* createCache(const std::string& algo, const common_cache_params_t& params) { + std::string lowerAlgo = algo; + std::transform(lowerAlgo.begin(), lowerAlgo.end(), lowerAlgo.begin(), ::tolower); + + if (lowerAlgo == "lru") return LRU_init(params, nullptr); + else if (lowerAlgo == "fifo") return FIFO_init(params, nullptr); + else if (lowerAlgo == "lfu") return LFU_init(params, nullptr); + else if (lowerAlgo == "arc") return ARC_init(params, nullptr); + else if (lowerAlgo == "clock") return Clock_init(params, nullptr); + else if (lowerAlgo == "s3fifo") return S3FIFO_init(params, nullptr); + else if (lowerAlgo == "sieve") return Sieve_init(params, nullptr); + + return nullptr; // Unknown algorithm +} + +// Main simulation function +Napi::Value runSimulation(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + // Check arguments + if (info.Length() < 3) { + Napi::TypeError::New(env, "Expected at least 3 arguments: tracePath, traceType, algorithm").ThrowAsJavaScriptException(); + return env.Null(); + } + + if (!info[0].IsString() || !info[1].IsString() || !info[2].IsString()) { + Napi::TypeError::New(env, "First three arguments must be strings").ThrowAsJavaScriptException(); + return env.Null(); + } + + std::string tracePath = info[0].As().Utf8Value(); + std::string traceType = info[1].As().Utf8Value(); + std::string algorithm = info[2].As().Utf8Value(); + + // Check if file exists before trying to open it + if (!fileExists(tracePath)) { + Napi::Error::New(env, "Trace file does not exist: " + tracePath).ThrowAsJavaScriptException(); + return env.Null(); + } + + // Parse optional cache size (default 1MB) + uint64_t cacheSize = 1024 * 1024; // 1MB default + if (info.Length() > 3 && info[3].IsString()) { + try { + cacheSize = parseCacheSize(info[3].As().Utf8Value()); + if (cacheSize == 0) { + Napi::Error::New(env, "Invalid cache size").ThrowAsJavaScriptException(); + return env.Null(); + } + } catch (const std::exception& e) { + Napi::Error::New(env, "Invalid cache size format").ThrowAsJavaScriptException(); + return env.Null(); + } + } + + // Determine trace type enum + trace_type_e trace_type_enum; + std::string lowerTraceType = traceType; + std::transform(lowerTraceType.begin(), lowerTraceType.end(), lowerTraceType.begin(), ::tolower); + + if (lowerTraceType == "vscsi") trace_type_enum = VSCSI_TRACE; + else if (lowerTraceType == "csv") trace_type_enum = CSV_TRACE; + else if (lowerTraceType == "txt" || lowerTraceType == "plain_txt") trace_type_enum = PLAIN_TXT_TRACE; + else if (lowerTraceType == "binary" || lowerTraceType == "bin") trace_type_enum = BIN_TRACE; + else if (lowerTraceType == "oracle") trace_type_enum = ORACLE_GENERAL_TRACE; + else { + Napi::Error::New(env, "Unsupported trace type. Supported: vscsi, csv, txt, binary, oracle").ThrowAsJavaScriptException(); + return env.Null(); + } + + // Validate algorithm before creating cache + std::string lowerAlgo = algorithm; + std::transform(lowerAlgo.begin(), lowerAlgo.end(), lowerAlgo.begin(), ::tolower); + if (lowerAlgo != "lru" && lowerAlgo != "fifo" && lowerAlgo != "lfu" && + lowerAlgo != "arc" && lowerAlgo != "clock" && lowerAlgo != "s3fifo" && lowerAlgo != "sieve") { + Napi::Error::New(env, "Unsupported algorithm. Supported: lru, fifo, lfu, arc, clock, s3fifo, sieve").ThrowAsJavaScriptException(); + return env.Null(); + } + + // Open the trace file + reader_t* reader = open_trace(tracePath.c_str(), trace_type_enum, nullptr); + if (!reader) { + Napi::Error::New(env, "Failed to open trace file: " + tracePath).ThrowAsJavaScriptException(); + return env.Null(); + } + + // Create a request container + request_t* req = new_request(); + if (!req) { + close_trace(reader); + Napi::Error::New(env, "Failed to allocate request").ThrowAsJavaScriptException(); + return env.Null(); + } + + // Initialize cache + common_cache_params_t cc_params = { + .cache_size = cacheSize, + .default_ttl = 0, + .hashpower = 24, + .consider_obj_metadata = false + }; + + cache_t* cache = createCache(algorithm, cc_params); + if (!cache) { + close_trace(reader); + free_request(req); + Napi::Error::New(env, "Failed to create cache with algorithm: " + algorithm).ThrowAsJavaScriptException(); + return env.Null(); + } + + // Run simulation loop + uint64_t n_req = 0; + uint64_t n_miss = 0; + uint64_t n_hit = 0; + + while (read_one_req(reader, req) == 0) { + bool hit = cache->get(cache, req); + if (hit) n_hit++; + else n_miss++; + n_req++; + } + + // Cleanup + close_trace(reader); + free_request(req); + cache->cache_free(cache); + + // Return simulation results as object + Napi::Object result = Napi::Object::New(env); + result.Set("totalRequests", Napi::Number::New(env, n_req)); + result.Set("hits", Napi::Number::New(env, n_hit)); + result.Set("misses", Napi::Number::New(env, n_miss)); + result.Set("hitRatio", Napi::Number::New(env, n_req > 0 ? (double)n_hit / n_req : 0.0)); + result.Set("missRatio", Napi::Number::New(env, n_req > 0 ? (double)n_miss / n_req : 0.0)); + result.Set("algorithm", Napi::String::New(env, algorithm)); + result.Set("cacheSize", Napi::Number::New(env, cacheSize)); + + return result; +} + +// Simple simulation with hardcoded values (backward compatibility) +Napi::Value runSim(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + // Check if the default trace file exists + if (!fileExists("../data/cloudPhysicsIO.vscsi")) { + Napi::Error::New(env, "Default trace file not found: ../data/cloudPhysicsIO.vscsi").ThrowAsJavaScriptException(); + return env.Null(); + } + + // === Open the trace file === + reader_t* reader = open_trace( + "../data/cloudPhysicsIO.vscsi", + VSCSI_TRACE, + nullptr // No special initialization parameters + ); + + if (!reader) { + Napi::Error::New(env, "Failed to open trace").ThrowAsJavaScriptException(); + return env.Null(); + } + + // === Create a request container === + request_t* req = new_request(); + if (!req) { + close_trace(reader); + Napi::Error::New(env, "Failed to allocate request").ThrowAsJavaScriptException(); + return env.Null(); + } + + // === Initialize an LRU cache === + common_cache_params_t cc_params = { + .cache_size = 1024 * 1024, // 1MB + .default_ttl = 0, + .hashpower = 24, + .consider_obj_metadata = false + }; + cache_t* cache = LRU_init(cc_params, nullptr); + if (!cache) { + close_trace(reader); + free_request(req); + Napi::Error::New(env, "Failed to create cache").ThrowAsJavaScriptException(); + return env.Null(); + } + + // === Run simulation loop === + uint64_t n_req = 0; + uint64_t n_miss = 0; + uint64_t n_hit = 0; + while (read_one_req(reader, req) == 0) { + bool hit = cache->get(cache, req); + if (hit) n_hit++; + else n_miss++; + n_req++; + } + + // === Cleanup === + close_trace(reader); + free_request(req); + cache->cache_free(cache); + + // === Return results as object === + Napi::Object result = Napi::Object::New(env); + result.Set("totalRequests", Napi::Number::New(env, n_req)); + result.Set("hits", Napi::Number::New(env, n_hit)); + result.Set("misses", Napi::Number::New(env, n_miss)); + result.Set("hitRatio", Napi::Number::New(env, n_req > 0 ? (double)n_hit / n_req : 0.0)); + result.Set("missRatio", Napi::Number::New(env, n_req > 0 ? (double)n_miss / n_req : 0.0)); + result.Set("algorithm", Napi::String::New(env, "lru")); + result.Set("cacheSize", Napi::Number::New(env, 1024 * 1024)); + + return result; +} + +// Node.js addon initialization +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set("runSim", Napi::Function::New(env, runSim)); + exports.Set("runSimulation", Napi::Function::New(env, runSimulation)); + return exports; +} + +NODE_API_MODULE(libcachesim_addon, Init) diff --git a/libCacheSim-node/binding.gyp b/libCacheSim-node/binding.gyp new file mode 100644 index 00000000..c89f1dd7 --- /dev/null +++ b/libCacheSim-node/binding.gyp @@ -0,0 +1,33 @@ +{ + "targets": [ + { + "target_name": "cachesim-addon", + "sources": [ "binding.cc" ], + "include_dirs": [ + " --type --algorithm --size + +Options: + --trace, -t Path to trace file (required) + --type Trace type (required) + Supported: ${libCacheSim.getSupportedTraceTypes().join(', ')} + --algorithm, -a Cache algorithm (required) + Supported: ${libCacheSim.getSupportedAlgorithms().join(', ')} + --size, -s Cache size (required) + Examples: 1mb, 512kb, 2gb, 1024 (bytes) + --help, -h Show this help message + +Examples: + cachesim-js -t trace.vscsi --type vscsi -a lru -s 10mb + cachesim-js --trace data.csv --type csv --algorithm s3fifo --size 50mb +`); +} + +function main() { + const options = parseArgs(); + + if (options.help) { + showHelp(); + return; + } + + // Check that all required parameters are provided + if (!options.trace || !options.type || !options.algorithm || !options.size) { + console.error('Error: All parameters are required.'); + console.error('Missing:'); + if (!options.trace) console.error(' --trace '); + if (!options.type) console.error(' --type '); + if (!options.algorithm) console.error(' --algorithm '); + if (!options.size) console.error(' --size '); + console.error('\nUse --help for usage information.'); + process.exit(1); + } + + try { + // Check if trace file exists + const fs = require('fs'); + if (!fs.existsSync(options.trace)) { + console.error(`Error: Trace file '${options.trace}' not found.`); + process.exit(1); + } + + console.log(`Running simulation with trace: ${options.trace}`); + console.log(`Algorithm: ${options.algorithm}, Size: ${options.size}, Type: ${options.type}`); + + // Run simulation + const result = libCacheSim.runSimulation( + options.trace, + options.type, + options.algorithm, + options.size + ); + + // Display results + console.log('\n=== Cache Simulation Results ==='); + console.log(`Algorithm: ${result.algorithm}`); + console.log(`Cache Size: ${(result.cacheSize / (1024 * 1024)).toFixed(2)} MB`); + console.log(`Total Requests: ${result.totalRequests.toLocaleString()}`); + console.log(`Cache Hits: ${result.hits.toLocaleString()}`); + console.log(`Cache Misses: ${result.misses.toLocaleString()}`); + console.log(`Hit Ratio: ${(result.hitRatio * 100).toFixed(2)}%`); + console.log(`Miss Ratio: ${(result.missRatio * 100).toFixed(2)}%`); + + } catch (error) { + console.error('Error running simulation:', error.message); + process.exit(1); + } +} + +if (require.main === module) { + main(); +} + +module.exports = { parseArgs, showHelp, main }; \ No newline at end of file diff --git a/libCacheSim-node/index.js b/libCacheSim-node/index.js new file mode 100644 index 00000000..4f9ae36a --- /dev/null +++ b/libCacheSim-node/index.js @@ -0,0 +1,64 @@ +// libCacheSim Node.js Bindings +const cachesimAddon = require('./build/Release/cachesim-addon'); + +/** + * Run a cache simulation + * @param {string} tracePath - Path to the trace file + * @param {string} traceType - Type of trace (vscsi, csv, txt, binary) + * @param {string} algorithm - Cache algorithm (lru, fifo, lfu, arc, clock, s3fifo, sieve) + * @param {string} cacheSize - Cache size (e.g., "1mb", "1gb", "512kb") + * @returns {Object} Simulation results + */ +function runSimulation(tracePath, traceType, algorithm, cacheSize = "1mb") { + return cachesimAddon.runSimulation(tracePath, traceType, algorithm, cacheSize); +} + +/** + * Run a simple cache simulation with default parameters (backward compatibility) + * @returns {Object} Simulation results + */ +function runSim() { + return cachesimAddon.runSim(); +} + +/** + * Get list of supported cache algorithms + * @returns {Array} List of supported algorithms + */ +function getSupportedAlgorithms() { + return ['lru', 'fifo', 'lfu', 'arc', 'clock', 's3fifo', 'sieve']; +} + +/** + * Get list of supported trace types + * @returns {Array} List of supported trace types + */ +function getSupportedTraceTypes() { + return ['vscsi', 'csv', 'txt', 'binary', 'oracle']; +} + +module.exports = { + runSimulation, + runSim, + getSupportedAlgorithms, + getSupportedTraceTypes +}; + +// Example usage if run directly +if (require.main === module) { + console.log('libCacheSim Node.js Bindings'); + console.log('Supported algorithms:', getSupportedAlgorithms()); + console.log('Supported trace types:', getSupportedTraceTypes()); + + try { + console.log('\nRunning default simulation...'); + const result = runSim(); + console.log('Results:', result); + + console.log('\nRunning custom simulation...'); + const customResult = runSimulation('../data/cloudPhysicsIO.vscsi', 'vscsi', 's3fifo', '2mb'); + console.log('Custom Results:', customResult); + } catch (error) { + console.error('Error running simulation:', error.message); + } +} \ No newline at end of file diff --git a/libCacheSim-node/libCacheSim-node.md b/libCacheSim-node/libCacheSim-node.md new file mode 100644 index 00000000..27a33fc1 --- /dev/null +++ b/libCacheSim-node/libCacheSim-node.md @@ -0,0 +1,198 @@ +# libcachesim-node + +Node.js bindings for libCacheSim. + + +## Installation + +### Local Installation (Development) + +```bash +cd libCacheSim-node +npm install -g node-gyp +npm install +npm run build +``` + +### Global Installation + +To install libcachesim-node globally and make the CLI available system-wide: + +```bash +cd libCacheSim-node +npm install -g . +``` + +After global installation, you can use the CLI from any directory: + +```bash +cachesim-js --help +cachesim-js --trace /path/to/trace.vscsi --type vscsi --algorithm lru --size 10mb +``` + +**Note:** Global installation requires the libCacheSim C++ library to be built first. Make sure you have: +- CMake installed +- A C++ compiler (gcc/clang) +- The parent libCacheSim repository properly built + +If you encounter build issues during global installation, you can: +1. First build locally: `npm run build` +2. Then install globally: `npm install -g .` + +## Usage + +### Basic Example + +```javascript +const libCacheSim = require('./index'); + +// Run a simulation with default parameters +const result = libCacheSim.runSim(); +console.log(result); + +// Run a custom simulation +const customResult = libCacheSim.runSimulation( + '../data/trace.vscsi', // trace file path + 'vscsi', // trace type + 'lru', // cache algorithm + '10mb' // cache size +); +console.log(customResult); +``` + +### API Reference + +#### `runSimulation(tracePath, traceType, algorithm, cacheSize)` + +Run a cache simulation with custom parameters. + +**Parameters:** +- `tracePath` (string): Path to the trace file +- `traceType` (string): Type of trace file. Supported: `'vscsi'`, `'csv'`, `'txt'`, `'binary'`, `'oracle'` +- `algorithm` (string): Cache eviction algorithm. Supported: `'lru'`, `'fifo'`, `'lfu'`, `'arc'`, `'clock'`, `'s3fifo'`, `'sieve'` +- `cacheSize` (string): Cache size with unit. Examples: `'1mb'`, `'512kb'`, `'2gb'`, `'1024'` (bytes) + +**Returns:** +Object with simulation results: +```javascript +{ + totalRequests: 113872, // Total number of requests processed + hits: 15416, // Number of cache hits + misses: 98456, // Number of cache misses + hitRatio: 0.1354, // Cache hit ratio (0-1) + missRatio: 0.8646, // Cache miss ratio (0-1) + algorithm: 'lru', // Algorithm used + cacheSize: 1048576 // Cache size in bytes +} +``` + +#### `runSim()` + +Run a simulation with default parameters (backward compatibility). + +**Returns:** Same result object as `runSimulation()` + +#### `getSupportedAlgorithms()` + +Get list of supported cache algorithms. + +**Returns:** Array of algorithm names + +#### `getSupportedTraceTypes()` + +Get list of supported trace types. + +**Returns:** Array of trace type names + +### Examples + +#### Compare Different Algorithms + +```javascript +const libCacheSim = require('./index'); + +const algorithms = ['lru', 'fifo', 'lfu', 's3fifo']; +const tracePath = '../data/trace.vscsi'; +const cacheSize = '10mb'; + +algorithms.forEach(algo => { + const result = libCacheSim.runSimulation(tracePath, 'vscsi', algo, cacheSize); + console.log(`${algo.toUpperCase()}: Hit Ratio = ${result.hitRatio.toFixed(4)}`); +}); +``` + +#### Analyze Different Cache Sizes + +```javascript +const libCacheSim = require('./index'); + +const cacheSizes = ['1mb', '5mb', '10mb', '50mb', '100mb']; +const tracePath = '../data/trace.vscsi'; + +cacheSizes.forEach(size => { + const result = libCacheSim.runSimulation(tracePath, 'vscsi', 'lru', size); + console.log(`Size ${size}: Hit Ratio = ${result.hitRatio.toFixed(4)}`); +}); +``` + +### Error Handling + +The library throws JavaScript errors for common issues: + +```javascript +try { + const result = libCacheSim.runSimulation('/invalid/path', 'vscsi', 'lru', '1mb'); +} catch (error) { + console.error('Simulation failed:', error.message); +} +``` + +## Command Line Interface + +After global installation, you can use the `cachesim-js` command: + +### CLI Usage + +```bash +cachesim-js --trace --type --algorithm --size +``` + +### CLI Options + +- `--trace, -t `: Path to trace file (required) +- `--type `: Trace type (required) +- `--algorithm, -a `: Cache algorithm (required) +- `--size, -s `: Cache size (required) +- `--help, -h`: Show help message + +### CLI Examples + +```bash +# Basic simulation +cachesim-js -t trace.vscsi --type vscsi -a lru -s 10mb + +# Compare S3-FIFO with larger cache +cachesim-js --trace data.csv --type csv --algorithm s3fifo --size 50mb + +# Show help +cachesim-js --help +``` +## Development + +### Building from Source + +```bash +# Clean build +npm run clean +npm run build + +# Debug build with debugging symbols +DEBUG=1 npm run build +``` + +### Running Tests + +```bash +# Run test suite +npm test +``` \ No newline at end of file diff --git a/libCacheSim-node/package.json b/libCacheSim-node/package.json new file mode 100644 index 00000000..b0c52727 --- /dev/null +++ b/libCacheSim-node/package.json @@ -0,0 +1,55 @@ +{ + "name": "libcachesim-node", + "version": "1.0.0", + "main": "index.js", + "bin": { + "cachesim-js": "./cli.js" + }, + "scripts": { + "prebuild": "npm run ensure-libcachesim", + "ensure-libcachesim": "cd .. && rm -rf _build && mkdir _build && cd _build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=${DEBUG:+Debug}${DEBUG:-Release} .. && make -j", + "install": "npm run build", + "build": "CFLAGS=-fPIC CXXFLAGS=-fPIC node-gyp rebuild", + "clean": "node-gyp clean && cd .. && rm -rf _build", + "test": "node test.js", + "prepack": "npm run build" + }, + "keywords": [ + "cache", + "simulation", + "performance", + "lru", + "fifo", + "s3fifo", + "sieve", + "libcachesim" + ], + "author": "Murphy Tian", + "license": "MIT", + "description": "Node.js bindings for libCacheSim - A high-performance cache simulator and analysis library supporting LRU, FIFO, S3-FIFO, Sieve and other caching algorithms", + "repository": { + "type": "git", + "url": "https://github.com/1a1a11a/libCacheSim" + }, + "homepage": "https://github.com/1a1a11a/libCacheSim/tree/develop/libCacheSim-node", + "bugs": { + "url": "https://github.com/1a1a11a/libCacheSim/issues" + }, + "engines": { + "node": ">=14.0.0" + }, + "dependencies": { + "node-addon-api": "^8.3.1" + }, + "devDependencies": {}, + "files": [ + "index.js", + "cli.js", + "binding.cc", + "binding.gyp", + "README.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + } +} diff --git a/libCacheSim-node/test.js b/libCacheSim-node/test.js new file mode 100644 index 00000000..a1bf3b56 --- /dev/null +++ b/libCacheSim-node/test.js @@ -0,0 +1,192 @@ +#!/usr/bin/env node + +/** + * Test suite for libCacheSim Node.js bindings + */ + +const libCacheSim = require('./index'); + +// ANSI color codes for better output +const colors = { + green: '\x1b[32m', + red: '\x1b[31m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + reset: '\x1b[0m' +}; + +function log(color, message) { + console.log(`${colors[color]}${message}${colors.reset}`); +} + +function assert(condition, message) { + if (condition) { + log('green', `✓ ${message}`); + return true; + } else { + log('red', `✗ ${message}`); + return false; + } +} + +function runTests() { + let passed = 0; + let total = 0; + + function test(description, testFn) { + total++; + log('blue', `\nTest: ${description}`); + try { + if (testFn()) { + passed++; + } + } catch (error) { + log('red', `✗ ${description} - Error: ${error.message}`); + } + } + + log('yellow', '=== libCacheSim Node.js Bindings Test Suite ===\n'); + + // Test 1: Check if module loads correctly + test('Module loads correctly', () => { + return assert(typeof libCacheSim === 'object', 'libCacheSim module is an object') && + assert(typeof libCacheSim.runSim === 'function', 'runSim function exists') && + assert(typeof libCacheSim.runSimulation === 'function', 'runSimulation function exists') && + assert(typeof libCacheSim.getSupportedAlgorithms === 'function', 'getSupportedAlgorithms function exists') && + assert(typeof libCacheSim.getSupportedTraceTypes === 'function', 'getSupportedTraceTypes function exists'); + }); + + // Test 2: Check supported algorithms + test('Get supported algorithms', () => { + const algorithms = libCacheSim.getSupportedAlgorithms(); + return assert(Array.isArray(algorithms), 'Returns an array') && + assert(algorithms.length > 0, 'Array is not empty') && + assert(algorithms.includes('lru'), 'Includes LRU') && + assert(algorithms.includes('s3fifo'), 'Includes S3-FIFO') && + assert(algorithms.includes('sieve'), 'Includes Sieve'); + }); + + // Test 3: Check supported trace types + test('Get supported trace types', () => { + const traceTypes = libCacheSim.getSupportedTraceTypes(); + return assert(Array.isArray(traceTypes), 'Returns an array') && + assert(traceTypes.length > 0, 'Array is not empty') && + assert(traceTypes.includes('vscsi'), 'Includes VSCSI') && + assert(traceTypes.includes('csv'), 'Includes CSV'); + }); + + // Test 4: Run default simulation + test('Run default simulation', () => { + const result = libCacheSim.runSim(); + return assert(typeof result === 'object', 'Returns an object') && + assert(typeof result.totalRequests === 'number', 'Has totalRequests as number') && + assert(typeof result.hits === 'number', 'Has hits as number') && + assert(typeof result.misses === 'number', 'Has misses as number') && + assert(typeof result.hitRatio === 'number', 'Has hitRatio as number') && + assert(typeof result.missRatio === 'number', 'Has missRatio as number') && + assert(result.totalRequests === result.hits + result.misses, 'Total requests equals hits + misses') && + assert(Math.abs(result.hitRatio + result.missRatio - 1.0) < 0.0001, 'Hit ratio + miss ratio ≈ 1.0'); + }); + + // Test 5: Run custom simulation with different algorithms + test('Run custom simulations with different algorithms', () => { + const algorithms = ['lru', 'fifo', 's3fifo']; + let allPassed = true; + + for (const algo of algorithms) { + try { + const result = libCacheSim.runSimulation('../data/cloudPhysicsIO.vscsi', 'vscsi', algo, '1mb'); + if (!assert(typeof result === 'object', `${algo} returns object`) || + !assert(result.algorithm === algo, `${algo} has correct algorithm name`) || + !assert(result.totalRequests > 0, `${algo} has positive total requests`)) { + allPassed = false; + } + } catch (error) { + log('red', `✗ ${algo} simulation failed: ${error.message}`); + allPassed = false; + } + } + + return allPassed; + }); + + // Test 6: Test different cache sizes + test('Test different cache sizes', () => { + const sizes = ['512kb', '1mb', '2mb']; + let allPassed = true; + + for (const size of sizes) { + try { + const result = libCacheSim.runSimulation('../data/cloudPhysicsIO.vscsi', 'vscsi', 'lru', size); + if (!assert(typeof result.cacheSize === 'number', `${size} has numeric cache size`) || + !assert(result.cacheSize > 0, `${size} has positive cache size`)) { + allPassed = false; + } + } catch (error) { + log('red', `✗ ${size} simulation failed: ${error.message}`); + allPassed = false; + } + } + + return allPassed; + }); + + // Test 7: Error handling - invalid trace file + test('Error handling for invalid trace file', () => { + try { + libCacheSim.runSimulation('/nonexistent/file.vscsi', 'vscsi', 'lru', '1mb'); + return assert(false, 'Should throw error for nonexistent file'); + } catch (error) { + return assert(error.message.includes('Trace file does not exist'), 'Throws appropriate error for missing file'); + } + }); + + // Test 8: Error handling - invalid algorithm + test('Error handling for invalid algorithm', () => { + try { + libCacheSim.runSimulation('../data/cloudPhysicsIO.vscsi', 'vscsi', 'invalid_algo', '1mb'); + return assert(false, 'Should throw error for invalid algorithm'); + } catch (error) { + return assert(error.message.includes('Unsupported algorithm'), 'Throws appropriate error for invalid algorithm'); + } + }); + + // Test 9: Error handling - invalid trace type + test('Error handling for invalid trace type', () => { + try { + libCacheSim.runSimulation('../data/cloudPhysicsIO.vscsi', 'invalid_type', 'lru', '1mb'); + return assert(false, 'Should throw error for invalid trace type'); + } catch (error) { + return assert(error.message.includes('Unsupported trace type'), 'Throws appropriate error for invalid trace type'); + } + }); + + // Test 10: Performance test - measure execution time + test('Performance measurement', () => { + const startTime = process.hrtime.bigint(); + const result = libCacheSim.runSim(); + const endTime = process.hrtime.bigint(); + const durationMs = Number(endTime - startTime) / 1000000; + + log('blue', ` Processed ${result.totalRequests} requests in ${durationMs.toFixed(2)}ms`); + log('blue', ` Performance: ${(result.totalRequests / durationMs * 1000).toFixed(0)} requests/second`); + + return assert(durationMs < 5000, 'Simulation completes in reasonable time (< 5 seconds)'); + }); + + // Summary + log('yellow', '\n=== Test Summary ==='); + log(passed === total ? 'green' : 'red', + `Passed: ${passed}/${total} tests`); + + if (passed === total) { + log('green', 'All tests passed! 🎉'); + process.exit(0); + } else { + log('red', 'Some tests failed! ❌'); + process.exit(1); + } +} + +// Run the tests +runTests(); \ No newline at end of file