185 lines
6.1 KiB
C++
185 lines
6.1 KiB
C++
// W3 CLI driver — the exit gate for WASM-PROPOSAL §9.1 W3.
|
||
//
|
||
// Serves real requests through the production workspace runtime
|
||
// (src/wasm/worker.cpp): UCEB1 context in → core + lazily loaded real
|
||
// generated units → body/response-meta out. Each --repeat gets a fresh
|
||
// workspace, proving birth/drop. An epoch ticker thread enforces the CPU
|
||
// budget; the store limiter enforces memory; traps come back as collapsed
|
||
// wasm_trace summaries.
|
||
//
|
||
// Amalgamation TU (project style, like uce_lib.cpp): native types + DValue
|
||
// codec first, then the worker.
|
||
|
||
// same order as uce_lib.cpp, minus the native compiler/connector block
|
||
#include "../lib/types.cpp"
|
||
#include "../lib/dvalue.cpp"
|
||
#include "../lib/functionlib.cpp"
|
||
#include "../lib/hash.cpp"
|
||
#include "../lib/sys.cpp"
|
||
#include "../lib/uri.cpp"
|
||
#include "../lib/cli.cpp"
|
||
#include "../lib/wasm_trace.h"
|
||
#include "worker.cpp"
|
||
|
||
#include <atomic>
|
||
#include <thread>
|
||
|
||
static String arg_value(int argc, char** argv, int& i, const char* flag)
|
||
{
|
||
if(i + 1 >= argc)
|
||
{
|
||
fprintf(stderr, "missing value for %s\n", flag);
|
||
exit(2);
|
||
}
|
||
return(String(argv[++i]));
|
||
}
|
||
|
||
static String absolute_path(String path)
|
||
{
|
||
if(path.rfind("/", 0) == 0)
|
||
return(path);
|
||
char buf[4096];
|
||
if(!getcwd(buf, sizeof(buf)))
|
||
return(path);
|
||
return(String(buf) + "/" + path);
|
||
}
|
||
|
||
int main(int argc, char** argv)
|
||
{
|
||
WasmWorkerConfig cfg;
|
||
cfg.site_root = absolute_path("site");
|
||
String page;
|
||
String route;
|
||
StringMap params;
|
||
StringMap get_params;
|
||
std::vector<String> expects;
|
||
String expect_status;
|
||
int repeat = 1;
|
||
u64 epoch_period_ms = 50;
|
||
bool show_body = true;
|
||
|
||
for(int i = 1; i < argc; i++)
|
||
{
|
||
String arg = argv[i];
|
||
if(arg == "--core") cfg.core_wasm_path = arg_value(argc, argv, i, "--core");
|
||
else if(arg == "--site") cfg.site_root = absolute_path(arg_value(argc, argv, i, "--site"));
|
||
else if(arg == "--cache") cfg.cache_root = arg_value(argc, argv, i, "--cache");
|
||
else if(arg == "--page") page = arg_value(argc, argv, i, "--page");
|
||
else if(arg == "--route") route = arg_value(argc, argv, i, "--route");
|
||
else if(arg == "--repeat") repeat = atoi(arg_value(argc, argv, i, "--repeat").c_str());
|
||
else if(arg == "--expect") expects.push_back(arg_value(argc, argv, i, "--expect"));
|
||
else if(arg == "--expect-status") expect_status = arg_value(argc, argv, i, "--expect-status");
|
||
else if(arg == "--epoch-ticks") cfg.epoch_deadline_ticks = strtoull(arg_value(argc, argv, i, "--epoch-ticks").c_str(), 0, 10);
|
||
else if(arg == "--epoch-ms") epoch_period_ms = strtoull(arg_value(argc, argv, i, "--epoch-ms").c_str(), 0, 10);
|
||
else if(arg == "--mem-limit") cfg.memory_limit = strtoll(arg_value(argc, argv, i, "--mem-limit").c_str(), 0, 10);
|
||
else if(arg == "--table-headroom") cfg.table_headroom = (u32)atoi(arg_value(argc, argv, i, "--table-headroom").c_str());
|
||
else if(arg == "--quiet") show_body = false;
|
||
else if(arg == "--verbose") cfg.verbose = true;
|
||
else if(arg == "--param" || arg == "--get")
|
||
{
|
||
String pair = arg_value(argc, argv, i, arg.c_str());
|
||
auto eq = pair.find("=");
|
||
if(eq == String::npos)
|
||
{
|
||
fprintf(stderr, "%s expects K=V\n", arg.c_str());
|
||
return(2);
|
||
}
|
||
(arg == "--param" ? params : get_params)[pair.substr(0, eq)] = pair.substr(eq + 1);
|
||
}
|
||
else
|
||
{
|
||
fprintf(stderr, "unknown argument: %s\n", arg.c_str());
|
||
return(2);
|
||
}
|
||
}
|
||
|
||
if(page == "")
|
||
{
|
||
fprintf(stderr, "usage: w3_driver --page /demo/hello.uce [--site site] [--cache /tmp/uce/work]\n"
|
||
" [--core bin/wasm/core.wasm] [--param K=V ...] [--get K=V ...] [--route path]\n"
|
||
" [--repeat N] [--expect STR ...] [--expect-status STR] [--quiet] [--verbose]\n"
|
||
" [--epoch-ticks N] [--epoch-ms N] [--mem-limit BYTES]\n");
|
||
return(2);
|
||
}
|
||
|
||
String entry_unit = cfg.site_root + page;
|
||
WasmWorker worker(cfg);
|
||
String init_error = worker.init();
|
||
if(init_error != "")
|
||
{
|
||
fprintf(stderr, "FAIL: %s\n", init_error.c_str());
|
||
return(1);
|
||
}
|
||
|
||
// the epoch only advances through this ticker; period × deadline-ticks
|
||
// is the per-request CPU budget
|
||
std::atomic<bool> running(true);
|
||
std::thread ticker([&] {
|
||
while(running.load())
|
||
{
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(epoch_period_ms));
|
||
worker.engine.increment_epoch();
|
||
}
|
||
});
|
||
|
||
DValue context_tree;
|
||
for(auto& entry : params)
|
||
context_tree["params"][entry.first] = entry.second;
|
||
if(context_tree.key("params") == 0 || !params.count("HTTP_HOST"))
|
||
context_tree["params"]["HTTP_HOST"] = "w3.test";
|
||
context_tree["params"]["SCRIPT_URL"] = page;
|
||
context_tree["params"]["REQUEST_METHOD"] = "GET";
|
||
for(auto& entry : get_params)
|
||
context_tree["get"][entry.first] = entry.second;
|
||
if(route != "")
|
||
{
|
||
context_tree["route"]["l_path"] = route;
|
||
context_tree["params"]["ROUTE_PATH"] = route;
|
||
}
|
||
context_tree["entry_unit"] = entry_unit;
|
||
|
||
bool all_ok = true;
|
||
WasmResponse last;
|
||
for(int request = 0; request < repeat && all_ok; request++)
|
||
{
|
||
f64 started = (f64)clock() / CLOCKS_PER_SEC;
|
||
last = wasm_worker_serve(worker, context_tree, entry_unit);
|
||
f64 elapsed_ms = ((f64)clock() / CLOCKS_PER_SEC - started) * 1000.0;
|
||
printf("==== request %d/%d (%.1f ms cpu) ====\n", request + 1, repeat, elapsed_ms);
|
||
if(!last.ok)
|
||
{
|
||
printf("ERROR:\n%s\n", last.error.c_str());
|
||
all_ok = false;
|
||
break;
|
||
}
|
||
printf("status: %s\n", last.meta["status"].to_string().c_str());
|
||
if(last.meta.key("headers"))
|
||
last.meta["headers"].each([](const DValue& value, String key) {
|
||
printf("header: %s: %s\n", key.c_str(), value.to_string().c_str());
|
||
});
|
||
if(show_body)
|
||
printf("---- body (%zu bytes) ----\n%.*s\n----\n",
|
||
last.body.size(), (int)last.body.size(), last.body.data());
|
||
else
|
||
printf("body: %zu bytes\n", last.body.size());
|
||
}
|
||
|
||
running.store(false);
|
||
ticker.join();
|
||
|
||
if(all_ok && expect_status != "" && last.meta["status"].to_string().find(expect_status) == String::npos)
|
||
{
|
||
printf("FAIL: status %s does not contain %s\n", last.meta["status"].to_string().c_str(), expect_status.c_str());
|
||
all_ok = false;
|
||
}
|
||
for(auto& expect : expects)
|
||
if(all_ok && last.body.find(expect) == String::npos)
|
||
{
|
||
printf("FAIL: body does not contain %s\n", expect.c_str());
|
||
all_ok = false;
|
||
}
|
||
|
||
printf(all_ok ? "W3 RESULT: PASS\n" : "W3 RESULT: FAIL\n");
|
||
return(all_ok ? 0 : 1);
|
||
}
|