Skip to content

Commit 22962c3

Browse files
realtmxi1a1a11a
andauthored
JS binding (#153)
* [Feat] Initialize js binding * [Feat] Add Node.js bindings for libCacheSim * [Fix] fPIC error in building js binding * [Refactor] README.md to libCacheSim-node.md * [Feat] Support libcachesim-cli global installation * update package.json * [Feat] add test.js, update package.json and libCacheSim-node.md * [Style] change js cli tool name, from "libcachesim-cli" to "cachesim-js" --------- Co-authored-by: Juncheng Yang <1a1a11a@users.noreply.github.com>
1 parent ba4600b commit 22962c3

File tree

8 files changed

+958
-0
lines changed

8 files changed

+958
-0
lines changed

libCacheSim-node/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Node.js dependencies
2+
node_modules/
3+
npm-debug.log*
4+
yarn-debug.log*
5+
yarn-error.log*
6+
7+
# Build outputs
8+
build/
9+
*.node
10+
11+
# Package locks (optional for libraries)
12+
package-lock.json
13+
14+
# OS files
15+
.DS_Store
16+
Thumbs.db
17+
18+
# IDE files
19+
.vscode/
20+
.idea/
21+
22+
# Temporary files
23+
*.tmp
24+
*.log

libCacheSim-node/binding.cc

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
#include <napi.h>
2+
#include "libCacheSim.h"
3+
#include <string>
4+
#include <algorithm>
5+
#include <map>
6+
#include <vector>
7+
#include <sys/stat.h>
8+
#include <unistd.h>
9+
10+
// Helper function to check if file exists
11+
bool fileExists(const std::string& filename) {
12+
struct stat buffer;
13+
return (stat(filename.c_str(), &buffer) == 0);
14+
}
15+
16+
// Helper function to parse cache size string (e.g., "1mb", "1gb", "1024")
17+
uint64_t parseCacheSize(const std::string& sizeStr) {
18+
if (sizeStr.empty()) return 0;
19+
20+
std::string lower = sizeStr;
21+
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
22+
23+
// Extract number and unit
24+
size_t pos = 0;
25+
while (pos < lower.length() && (isdigit(lower[pos]) || lower[pos] == '.')) {
26+
pos++;
27+
}
28+
29+
double value = std::stod(lower.substr(0, pos));
30+
std::string unit = lower.substr(pos);
31+
32+
uint64_t multiplier = 1;
33+
if (unit == "kb" || unit == "k") multiplier = 1024;
34+
else if (unit == "mb" || unit == "m") multiplier = 1024 * 1024;
35+
else if (unit == "gb" || unit == "g") multiplier = 1024 * 1024 * 1024;
36+
else if (unit == "tb" || unit == "t") multiplier = 1024ULL * 1024 * 1024 * 1024;
37+
38+
return (uint64_t)(value * multiplier);
39+
}
40+
41+
// Helper function to get cache constructor by algorithm name
42+
cache_t* createCache(const std::string& algo, const common_cache_params_t& params) {
43+
std::string lowerAlgo = algo;
44+
std::transform(lowerAlgo.begin(), lowerAlgo.end(), lowerAlgo.begin(), ::tolower);
45+
46+
if (lowerAlgo == "lru") return LRU_init(params, nullptr);
47+
else if (lowerAlgo == "fifo") return FIFO_init(params, nullptr);
48+
else if (lowerAlgo == "lfu") return LFU_init(params, nullptr);
49+
else if (lowerAlgo == "arc") return ARC_init(params, nullptr);
50+
else if (lowerAlgo == "clock") return Clock_init(params, nullptr);
51+
else if (lowerAlgo == "s3fifo") return S3FIFO_init(params, nullptr);
52+
else if (lowerAlgo == "sieve") return Sieve_init(params, nullptr);
53+
54+
return nullptr; // Unknown algorithm
55+
}
56+
57+
// Main simulation function
58+
Napi::Value runSimulation(const Napi::CallbackInfo& info) {
59+
Napi::Env env = info.Env();
60+
61+
// Check arguments
62+
if (info.Length() < 3) {
63+
Napi::TypeError::New(env, "Expected at least 3 arguments: tracePath, traceType, algorithm").ThrowAsJavaScriptException();
64+
return env.Null();
65+
}
66+
67+
if (!info[0].IsString() || !info[1].IsString() || !info[2].IsString()) {
68+
Napi::TypeError::New(env, "First three arguments must be strings").ThrowAsJavaScriptException();
69+
return env.Null();
70+
}
71+
72+
std::string tracePath = info[0].As<Napi::String>().Utf8Value();
73+
std::string traceType = info[1].As<Napi::String>().Utf8Value();
74+
std::string algorithm = info[2].As<Napi::String>().Utf8Value();
75+
76+
// Check if file exists before trying to open it
77+
if (!fileExists(tracePath)) {
78+
Napi::Error::New(env, "Trace file does not exist: " + tracePath).ThrowAsJavaScriptException();
79+
return env.Null();
80+
}
81+
82+
// Parse optional cache size (default 1MB)
83+
uint64_t cacheSize = 1024 * 1024; // 1MB default
84+
if (info.Length() > 3 && info[3].IsString()) {
85+
try {
86+
cacheSize = parseCacheSize(info[3].As<Napi::String>().Utf8Value());
87+
if (cacheSize == 0) {
88+
Napi::Error::New(env, "Invalid cache size").ThrowAsJavaScriptException();
89+
return env.Null();
90+
}
91+
} catch (const std::exception& e) {
92+
Napi::Error::New(env, "Invalid cache size format").ThrowAsJavaScriptException();
93+
return env.Null();
94+
}
95+
}
96+
97+
// Determine trace type enum
98+
trace_type_e trace_type_enum;
99+
std::string lowerTraceType = traceType;
100+
std::transform(lowerTraceType.begin(), lowerTraceType.end(), lowerTraceType.begin(), ::tolower);
101+
102+
if (lowerTraceType == "vscsi") trace_type_enum = VSCSI_TRACE;
103+
else if (lowerTraceType == "csv") trace_type_enum = CSV_TRACE;
104+
else if (lowerTraceType == "txt" || lowerTraceType == "plain_txt") trace_type_enum = PLAIN_TXT_TRACE;
105+
else if (lowerTraceType == "binary" || lowerTraceType == "bin") trace_type_enum = BIN_TRACE;
106+
else if (lowerTraceType == "oracle") trace_type_enum = ORACLE_GENERAL_TRACE;
107+
else {
108+
Napi::Error::New(env, "Unsupported trace type. Supported: vscsi, csv, txt, binary, oracle").ThrowAsJavaScriptException();
109+
return env.Null();
110+
}
111+
112+
// Validate algorithm before creating cache
113+
std::string lowerAlgo = algorithm;
114+
std::transform(lowerAlgo.begin(), lowerAlgo.end(), lowerAlgo.begin(), ::tolower);
115+
if (lowerAlgo != "lru" && lowerAlgo != "fifo" && lowerAlgo != "lfu" &&
116+
lowerAlgo != "arc" && lowerAlgo != "clock" && lowerAlgo != "s3fifo" && lowerAlgo != "sieve") {
117+
Napi::Error::New(env, "Unsupported algorithm. Supported: lru, fifo, lfu, arc, clock, s3fifo, sieve").ThrowAsJavaScriptException();
118+
return env.Null();
119+
}
120+
121+
// Open the trace file
122+
reader_t* reader = open_trace(tracePath.c_str(), trace_type_enum, nullptr);
123+
if (!reader) {
124+
Napi::Error::New(env, "Failed to open trace file: " + tracePath).ThrowAsJavaScriptException();
125+
return env.Null();
126+
}
127+
128+
// Create a request container
129+
request_t* req = new_request();
130+
if (!req) {
131+
close_trace(reader);
132+
Napi::Error::New(env, "Failed to allocate request").ThrowAsJavaScriptException();
133+
return env.Null();
134+
}
135+
136+
// Initialize cache
137+
common_cache_params_t cc_params = {
138+
.cache_size = cacheSize,
139+
.default_ttl = 0,
140+
.hashpower = 24,
141+
.consider_obj_metadata = false
142+
};
143+
144+
cache_t* cache = createCache(algorithm, cc_params);
145+
if (!cache) {
146+
close_trace(reader);
147+
free_request(req);
148+
Napi::Error::New(env, "Failed to create cache with algorithm: " + algorithm).ThrowAsJavaScriptException();
149+
return env.Null();
150+
}
151+
152+
// Run simulation loop
153+
uint64_t n_req = 0;
154+
uint64_t n_miss = 0;
155+
uint64_t n_hit = 0;
156+
157+
while (read_one_req(reader, req) == 0) {
158+
bool hit = cache->get(cache, req);
159+
if (hit) n_hit++;
160+
else n_miss++;
161+
n_req++;
162+
}
163+
164+
// Cleanup
165+
close_trace(reader);
166+
free_request(req);
167+
cache->cache_free(cache);
168+
169+
// Return simulation results as object
170+
Napi::Object result = Napi::Object::New(env);
171+
result.Set("totalRequests", Napi::Number::New(env, n_req));
172+
result.Set("hits", Napi::Number::New(env, n_hit));
173+
result.Set("misses", Napi::Number::New(env, n_miss));
174+
result.Set("hitRatio", Napi::Number::New(env, n_req > 0 ? (double)n_hit / n_req : 0.0));
175+
result.Set("missRatio", Napi::Number::New(env, n_req > 0 ? (double)n_miss / n_req : 0.0));
176+
result.Set("algorithm", Napi::String::New(env, algorithm));
177+
result.Set("cacheSize", Napi::Number::New(env, cacheSize));
178+
179+
return result;
180+
}
181+
182+
// Simple simulation with hardcoded values (backward compatibility)
183+
Napi::Value runSim(const Napi::CallbackInfo& info) {
184+
Napi::Env env = info.Env();
185+
186+
// Check if the default trace file exists
187+
if (!fileExists("../data/cloudPhysicsIO.vscsi")) {
188+
Napi::Error::New(env, "Default trace file not found: ../data/cloudPhysicsIO.vscsi").ThrowAsJavaScriptException();
189+
return env.Null();
190+
}
191+
192+
// === Open the trace file ===
193+
reader_t* reader = open_trace(
194+
"../data/cloudPhysicsIO.vscsi",
195+
VSCSI_TRACE,
196+
nullptr // No special initialization parameters
197+
);
198+
199+
if (!reader) {
200+
Napi::Error::New(env, "Failed to open trace").ThrowAsJavaScriptException();
201+
return env.Null();
202+
}
203+
204+
// === Create a request container ===
205+
request_t* req = new_request();
206+
if (!req) {
207+
close_trace(reader);
208+
Napi::Error::New(env, "Failed to allocate request").ThrowAsJavaScriptException();
209+
return env.Null();
210+
}
211+
212+
// === Initialize an LRU cache ===
213+
common_cache_params_t cc_params = {
214+
.cache_size = 1024 * 1024, // 1MB
215+
.default_ttl = 0,
216+
.hashpower = 24,
217+
.consider_obj_metadata = false
218+
};
219+
cache_t* cache = LRU_init(cc_params, nullptr);
220+
if (!cache) {
221+
close_trace(reader);
222+
free_request(req);
223+
Napi::Error::New(env, "Failed to create cache").ThrowAsJavaScriptException();
224+
return env.Null();
225+
}
226+
227+
// === Run simulation loop ===
228+
uint64_t n_req = 0;
229+
uint64_t n_miss = 0;
230+
uint64_t n_hit = 0;
231+
while (read_one_req(reader, req) == 0) {
232+
bool hit = cache->get(cache, req);
233+
if (hit) n_hit++;
234+
else n_miss++;
235+
n_req++;
236+
}
237+
238+
// === Cleanup ===
239+
close_trace(reader);
240+
free_request(req);
241+
cache->cache_free(cache);
242+
243+
// === Return results as object ===
244+
Napi::Object result = Napi::Object::New(env);
245+
result.Set("totalRequests", Napi::Number::New(env, n_req));
246+
result.Set("hits", Napi::Number::New(env, n_hit));
247+
result.Set("misses", Napi::Number::New(env, n_miss));
248+
result.Set("hitRatio", Napi::Number::New(env, n_req > 0 ? (double)n_hit / n_req : 0.0));
249+
result.Set("missRatio", Napi::Number::New(env, n_req > 0 ? (double)n_miss / n_req : 0.0));
250+
result.Set("algorithm", Napi::String::New(env, "lru"));
251+
result.Set("cacheSize", Napi::Number::New(env, 1024 * 1024));
252+
253+
return result;
254+
}
255+
256+
// Node.js addon initialization
257+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
258+
exports.Set("runSim", Napi::Function::New(env, runSim));
259+
exports.Set("runSimulation", Napi::Function::New(env, runSimulation));
260+
return exports;
261+
}
262+
263+
NODE_API_MODULE(libcachesim_addon, Init)

libCacheSim-node/binding.gyp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "cachesim-addon",
5+
"sources": [ "binding.cc" ],
6+
"include_dirs": [
7+
"<!@(node -p \"require('node-addon-api').include\")",
8+
"../libCacheSim/include",
9+
"/usr/include/glib-2.0",
10+
"/usr/lib/x86_64-linux-gnu/glib-2.0/include",
11+
"/usr/lib/aarch64-linux-gnu/glib-2.0/include"
12+
],
13+
"libraries": [
14+
"-L<(module_root_dir)/../_build",
15+
"-llibCacheSim",
16+
"-lglib-2.0",
17+
"-lzstd",
18+
"-lm",
19+
"-lpthread"
20+
],
21+
"cflags!": [ "-fno-exceptions" ],
22+
"cflags_cc!": [ "-fno-exceptions" ],
23+
"cflags": [ "-fPIC" ],
24+
"cflags_cc": [ "-fPIC" ],
25+
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
26+
"ldflags": [
27+
"-Wl,--whole-archive",
28+
"<(module_root_dir)/../_build/liblibCacheSim.a",
29+
"-Wl,--no-whole-archive"
30+
]
31+
}
32+
]
33+
}

0 commit comments

Comments
 (0)