diff --git a/.vscode/settings.json b/.vscode/settings.json index f8a6af4..79706f3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -110,7 +110,9 @@ "current_wasm_module_instance.h": "c", "wasm_memory.h": "c", "sledge_abi.h": "c", - "vec.h": "c" + "vec.h": "c", + "module_database.h": "c", + "perf_window_t.h": "c" }, "files.exclude": { "**/.git": true, diff --git a/runtime/include/admissions_info.h b/runtime/include/admissions_info.h index 0a941c5..d2e0dfb 100644 --- a/runtime/include/admissions_info.h +++ b/runtime/include/admissions_info.h @@ -4,12 +4,12 @@ struct admissions_info { struct perf_window perf_window; - int percentile; /* 50 - 99 */ + uint8_t percentile; /* 50 - 99 */ int control_index; /* Precomputed Lookup index when perf_window is full */ uint64_t estimate; /* cycles */ uint64_t relative_deadline; /* Relative deadline in cycles. This is duplicated state */ }; -void admissions_info_initialize(struct admissions_info *admissions_info, int percentile, uint64_t expected_execution, - uint64_t relative_deadline); +void admissions_info_initialize(struct admissions_info *admissions_info, uint8_t percentile, + uint64_t expected_execution, uint64_t relative_deadline); void admissions_info_update(struct admissions_info *admissions_info, uint64_t execution_duration); diff --git a/runtime/include/json.h b/runtime/include/json.h new file mode 100644 index 0000000..064c883 --- /dev/null +++ b/runtime/include/json.h @@ -0,0 +1,306 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "runtime.h" +#include "http.h" +#include "module.h" +#include "module_config.h" + +#define JSON_TOKENS_CAPACITY 16384 + +enum +{ + module_name, + module_path, + module_port, + module_expected_execution_us, + module_admissions_percentile, + module_relative_deadline_us, + module_http_req_size, + module_http_resp_size, + module_http_resp_content_type, + module_keys_len +}; + +static const char *module_keys[module_keys_len] = { "name", + "path", + "port", + "expected-execution-us", + "admissions-percentile", + "relative-deadline-us", + "http-req-size", + "http-resp-size", + "http-resp-content-type" }; + +static inline char * +jsmn_type(jsmntype_t type) +{ + switch (type) { + case JSMN_UNDEFINED: + return "Undefined"; + case JSMN_OBJECT: + return "Object"; + case JSMN_ARRAY: + return "Array"; + case JSMN_STRING: + return "String"; + case JSMN_PRIMITIVE: + return "Primitive"; + default: + return "Invalid"; + } +} + +static inline bool +has_valid_size(jsmntok_t tok, char *key, int expected_size) +{ + if (tok.size != expected_size) { + fprintf(stderr, "%s has size %d, expected %d\n", key, tok.size, expected_size); + return false; + } + + return true; +} + +static inline bool +has_valid_type(jsmntok_t tok, char *key, jsmntype_t expected_type) +{ + if (tok.type != expected_type) { + fprintf(stderr, "The value of the key %s should be a %s, was a %s\n", key, jsmn_type(expected_type), + jsmn_type(tok.type)); + return false; + } + + return true; +} + +static inline bool +is_nonempty_string(jsmntok_t tok, char *key) +{ + if (!has_valid_type(tok, key, JSMN_STRING)) return false; + + if (tok.end - tok.start < 1) { + fprintf(stderr, "The value of the key %s was an empty string\n", key); + return false; + } + + return true; +} + +static inline bool +is_valid_key(jsmntok_t tok) +{ + if (tok.type != JSMN_STRING) { + fprintf(stderr, "Expected token to be a key with a type of string, was a %s\n", jsmn_type(tok.type)); + return false; + } + + if (tok.end - tok.start < 1) { + fprintf(stderr, "Key was an empty string\n"); + return false; + } + + return true; +} + +static inline int +parse_uint8_t(jsmntok_t tok, const char *json_buf, const char *key, uint8_t *ret) +{ + char *end = NULL; + intmax_t temp = strtoimax(&json_buf[tok.start], &end, 10); + + if (end != &json_buf[tok.end] || temp < 0 || temp > UINT8_MAX) { + fprintf(stderr, "Unable to parse uint8_t for key %s\n", key); + return -1; + } + + *ret = (uint8_t)temp; + return 0; +} + +static inline int +parse_uint16_t(jsmntok_t tok, const char *json_buf, const char *key, uint16_t *ret) +{ + char *end = NULL; + intmax_t temp = strtoimax(&json_buf[tok.start], &end, 10); + + if (end != &json_buf[tok.end] || temp < 0 || temp > UINT16_MAX) { + fprintf(stderr, "Unable to parse uint16_t for key %s\n", key); + return -1; + } + + *ret = (uint16_t)temp; + return 0; +} + +static inline int +parse_uint32_t(jsmntok_t tok, const char *json_buf, const char *key, uint32_t *ret) +{ + char *end = NULL; + uintmax_t temp = strtoimax(&json_buf[tok.start], &end, 10); + + if (end != &json_buf[tok.end] || temp < 0 || temp > UINT32_MAX) { + fprintf(stderr, "Unable to parse uint32_t for key %s\n", key); + return -1; + } + + *ret = (uint32_t)temp; + return 0; +} + +/** + * Parses a JSON file into an array of module configs + * @param file_name The path of the JSON file + * @return module_config_vec_len on success. -1 on Error + */ +static inline int +parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **module_config_vec) +{ + assert(json_buf != NULL); + assert(json_buf_size > 0); + assert(module_config_vec != NULL); + + jsmntok_t tokens[JSON_TOKENS_CAPACITY]; + int module_config_vec_len = 0; + + + /* Initialize the Jasmine Parser and an array to hold the tokens */ + jsmn_parser module_parser; + jsmn_init(&module_parser); + + /* Use Jasmine to parse the JSON */ + int total_tokens = jsmn_parse(&module_parser, json_buf, json_buf_size, tokens, JSON_TOKENS_CAPACITY); + if (total_tokens < 0) { + if (total_tokens == JSMN_ERROR_INVAL) { + fprintf(stderr, "Error parsing %s: bad token, JSON string is corrupted\n", json_buf); + } else if (total_tokens == JSMN_ERROR_PART) { + fprintf(stderr, "Error parsing %s: JSON string is too short, expecting more JSON data\n", + json_buf); + } else if (total_tokens == JSMN_ERROR_NOMEM) { + /* + * According to the README at https://github.com/zserge/jsmn, this is a potentially recoverable + * error. More tokens can be allocated and jsmn_parse can be re-invoked. + */ + fprintf(stderr, "Error parsing %s: Not enough tokens, JSON string is too large\n", json_buf); + } + goto err; + } + + if (tokens[0].type != JSMN_ARRAY) { + fprintf(stderr, "Outermost Config should be a JSON array, was a JSON %s\n", jsmn_type(tokens[0].type)); + goto err; + } + + module_config_vec_len = tokens[0].size; + if (module_config_vec_len == 0) { + fprintf(stderr, "Config is an empty JSON array\n"); + goto err; + } + + *module_config_vec = (struct module_config *)calloc(module_config_vec_len, sizeof(struct module_config)); + int module_idx = -1; + int module_fields_remaining = 0; + + for (int i = 1; i < total_tokens; i++) { + char key[32] = { 0 }; + + /* Assumption: Objects are never used within a module_config. This likely will not be true in the + * future due to routes or multiple entrypoints */ + if (tokens[i].type == JSMN_OBJECT) { + assert(module_fields_remaining == 0); + module_fields_remaining = tokens[i].size; + module_idx++; + } else { + /* Inside Object */ + + /* Validate that key is non-emptry string */ + if (!is_valid_key(tokens[i])) goto json_parse_err; + + sprintf(key, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); + + /* Validate that key has a value */ + if (!has_valid_size(tokens[i], key, 1)) goto json_parse_err; + + /* Advance to Value */ + i++; + + if (strcmp(key, module_keys[module_name]) == 0) { + if (!is_nonempty_string(tokens[i], key)) goto json_parse_err; + + (*module_config_vec)[module_idx].name = strndup(json_buf + tokens[i].start, + tokens[i].end - tokens[i].start); + } else if (strcmp(key, module_keys[module_path]) == 0) { + if (!is_nonempty_string(tokens[i], key)) goto json_parse_err; + + (*module_config_vec)[module_idx].path = strndup(json_buf + tokens[i].start, + tokens[i].end - tokens[i].start); + } else if (strcmp(key, module_keys[module_port]) == 0) { + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + + int rc = parse_uint16_t(tokens[i], json_buf, module_keys[module_port], + &(*module_config_vec)[module_idx].port); + if (rc < 0) goto json_parse_err; + } else if (strcmp(key, module_keys[module_expected_execution_us]) == 0) { + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + + int rc = parse_uint32_t(tokens[i], json_buf, module_keys[module_expected_execution_us], + &(*module_config_vec)[module_idx].expected_execution_us); + if (rc < 0) goto json_parse_err; + } else if (strcmp(key, module_keys[module_admissions_percentile]) == 0) { + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + + int rc = parse_uint8_t(tokens[i], json_buf, module_keys[module_admissions_percentile], + &(*module_config_vec)[module_idx].admissions_percentile); + if (rc < 0) goto json_parse_err; + } else if (strcmp(key, module_keys[module_relative_deadline_us]) == 0) { + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + + int rc = parse_uint32_t(tokens[i], json_buf, module_keys[module_relative_deadline_us], + &(*module_config_vec)[module_idx].relative_deadline_us); + if (rc < 0) goto json_parse_err; + } else if (strcmp(key, module_keys[module_http_req_size]) == 0) { + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + + int rc = parse_uint32_t(tokens[i], json_buf, module_keys[module_http_req_size], + &(*module_config_vec)[module_idx].http_req_size); + if (rc < 0) goto json_parse_err; + } else if (strcmp(key, module_keys[module_http_resp_size]) == 0) { + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + + int rc = parse_uint32_t(tokens[i], json_buf, module_keys[module_http_resp_size], + &(*module_config_vec)[module_idx].http_resp_size); + if (rc < 0) goto json_parse_err; + } else if (strcmp(key, module_keys[module_http_resp_content_type]) == 0) { + if (!has_valid_type(tokens[i], key, JSMN_STRING)) goto json_parse_err; + + (*module_config_vec)[module_idx].http_resp_content_type = + strndup(json_buf + tokens[i].start, tokens[i].end - tokens[i].start); + } else { + fprintf(stderr, "%s is not a valid key\n", key); + goto json_parse_err; + } + module_fields_remaining--; + } + } + +#ifdef LOG_MODULE_LOADING + debuglog("Loaded %d module%s!\n", module_count, module_count > 1 ? "s" : ""); +#endif + +done: + return module_config_vec_len; +json_parse_err: + free(*module_config_vec); +err: + fprintf(stderr, "JSON:\n%s\n", json_buf); + module_config_vec_len = -1; + goto done; +} diff --git a/runtime/include/module.h b/runtime/include/module.h index c18a87c..8fb91c5 100644 --- a/runtime/include/module.h +++ b/runtime/include/module.h @@ -9,6 +9,7 @@ #include "admissions_info.h" #include "current_wasm_module_instance.h" #include "http.h" +#include "module_config.h" #include "panic.h" #include "pool.h" #include "sledge_abi_symbols.h" @@ -57,7 +58,7 @@ struct module { char path[MODULE_MAX_PATH_LENGTH]; uint32_t stack_size; /* a specification? */ uint32_t relative_deadline_us; - int port; + uint16_t port; struct admissions_info admissions_info; uint64_t relative_deadline; /* cycles */ @@ -242,6 +243,5 @@ module_free_linear_memory(struct module *module, struct wasm_memory *memory) *******************************/ void module_free(struct module *module); -struct module *module_alloc(char *mod_name, char *mod_path, uint32_t stack_sz, uint32_t relative_deadline_us, int port, - int req_sz, int resp_sz, int admissions_percentile, uint32_t expected_execution_us); -int module_alloc_from_json(char *filename); +struct module *module_alloc(struct module_config *config); +int module_listen(struct module *module); diff --git a/runtime/include/module_config.h b/runtime/include/module_config.h new file mode 100644 index 0000000..0adab05 --- /dev/null +++ b/runtime/include/module_config.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +struct module_config { + char *name; + char *path; + uint16_t port; + uint8_t admissions_percentile; + uint32_t expected_execution_us; + uint32_t relative_deadline_us; + uint32_t http_req_size; + uint32_t http_resp_size; + char *http_resp_content_type; +}; + +static inline void +module_config_deinit(struct module_config *config) +{ + free(config->name); + free(config->path); + free(config->http_resp_content_type); +} + +static inline void +print_module_config(struct module_config *config) +{ + printf("Name: %s\n", config->name); + printf("Path: %s\n", config->path); + printf("Port: %u\n", config->port); + printf("admissions_percentile: %u\n", config->admissions_percentile); + printf("expected_execution_us: %u\n", config->expected_execution_us); + printf("relative_deadline_us: %u\n", config->relative_deadline_us); + printf("http_req_size: %u\n", config->http_req_size); + printf("http_resp_size: %u\n", config->http_resp_size); + printf("http_resp_content_type: %s\n", config->http_resp_content_type); +} diff --git a/runtime/include/module_database.h b/runtime/include/module_database.h index 8d8c40e..5eeba11 100644 --- a/runtime/include/module_database.h +++ b/runtime/include/module_database.h @@ -7,3 +7,4 @@ int module_database_add(struct module *module); struct module *module_database_find_by_name(char *name); struct module *module_database_find_by_socket_descriptor(int socket_descriptor); +struct module *module_database_find_by_port(uint16_t port); diff --git a/runtime/include/perf_window.h b/runtime/include/perf_window.h index b796fbe..0045a97 100644 --- a/runtime/include/perf_window.h +++ b/runtime/include/perf_window.h @@ -145,16 +145,16 @@ done: * @returns execution time */ static inline uint64_t -perf_window_get_percentile(struct perf_window *perf_window, int percentile, int precomputed_index) +perf_window_get_percentile(struct perf_window *perf_window, uint8_t percentile, int precomputed_index) { assert(perf_window != NULL); assert(percentile >= 50 && percentile <= 99); - int size = perf_window->count; - assert(size > 0); + assert(perf_window->count > 0); - if (likely(size >= PERF_WINDOW_BUFFER_SIZE)) return perf_window->by_duration[precomputed_index].execution_time; + if (likely(perf_window->count >= PERF_WINDOW_BUFFER_SIZE)) + return perf_window->by_duration[precomputed_index].execution_time; - return perf_window->by_duration[size * percentile / 100].execution_time; + return perf_window->by_duration[perf_window->count * percentile / 100].execution_time; } /** diff --git a/runtime/src/admissions_info.c b/runtime/src/admissions_info.c index ae2279a..0f59cf7 100644 --- a/runtime/src/admissions_info.c +++ b/runtime/src/admissions_info.c @@ -6,7 +6,7 @@ * @param admissions_info */ void -admissions_info_initialize(struct admissions_info *admissions_info, int percentile, uint64_t expected_execution, +admissions_info_initialize(struct admissions_info *admissions_info, uint8_t percentile, uint64_t expected_execution, uint64_t relative_deadline) { #ifdef ADMISSIONS_CONTROL @@ -24,7 +24,7 @@ admissions_info_initialize(struct admissions_info *admissions_info, int percenti admissions_info->control_index = PERF_WINDOW_BUFFER_SIZE * percentile / 100; #ifdef LOG_ADMISSIONS_CONTROL - debuglog("Percentile: %d\n", admissions_info->percentile); + debuglog("Percentile: %u\n", admissions_info->percentile); debuglog("Control Index: %d\n", admissions_info->control_index); #endif #endif diff --git a/runtime/src/main.c b/runtime/src/main.c index 5fbd7f1..a0293ce 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -14,10 +15,12 @@ #include #endif +#include "json.h" #include "pretty_print.h" #include "debuglog.h" #include "listener_thread.h" #include "module.h" +#include "module_database.h" #include "panic.h" #include "runtime.h" #include "sandbox_types.h" @@ -344,6 +347,76 @@ check_versions() static_assert(__linux__ == 1, "Requires epoll, a Linux-only feature"); } +/** + * Allocates a buffer in memory containing the entire contents of the file provided + * @param file_name file to load into memory + * @param ret_ptr Pointer to set with address of buffer this function allocates. The caller must free this! + * @return size of the allocated buffer or 0 in case of error; + */ +static inline size_t +load_file_into_buffer(const char *file_name, char **file_buffer) +{ + /* Use stat to get file attributes and make sure file is present and not empty */ + struct stat stat_buffer; + if (stat(file_name, &stat_buffer) < 0) { + fprintf(stderr, "Attempt to stat %s failed: %s\n", file_name, strerror(errno)); + goto err; + } + if (stat_buffer.st_size == 0) { + fprintf(stderr, "File %s is unexpectedly empty\n", file_name); + goto err; + } + if (!S_ISREG(stat_buffer.st_mode)) { + fprintf(stderr, "File %s is not a regular file\n", file_name); + goto err; + } + + /* Open the file */ + FILE *module_file = fopen(file_name, "r"); + if (!module_file) { + fprintf(stderr, "Attempt to open %s failed: %s\n", file_name, strerror(errno)); + goto err; + } + + /* Initialize a Buffer */ + *file_buffer = calloc(1, stat_buffer.st_size); + if (*file_buffer == NULL) { + fprintf(stderr, "Attempt to allocate file buffer failed: %s\n", strerror(errno)); + goto stat_buffer_alloc_err; + } + + /* Read the file into the buffer and check that the buffer size equals the file size */ + size_t total_chars_read = fread(*file_buffer, sizeof(char), stat_buffer.st_size, module_file); +#ifdef LOG_MODULE_LOADING + debuglog("size read: %d content: %s\n", total_chars_read, *file_buffer); +#endif + if (total_chars_read != stat_buffer.st_size) { + fprintf(stderr, "Attempt to read %s into buffer failed: %s\n", file_name, strerror(errno)); + goto fread_err; + } + + /* Close the file */ + if (fclose(module_file) == EOF) { + fprintf(stderr, "Attempt to close buffer containing %s failed: %s\n", file_name, strerror(errno)); + goto fclose_err; + }; + module_file = NULL; + + return total_chars_read; + +fclose_err: + /* We will retry fclose when we fall through into stat_buffer_alloc_err */ +fread_err: + free(*file_buffer); +stat_buffer_alloc_err: + // Check to ensure we haven't already close this + if (module_file != NULL) { + if (fclose(module_file) == EOF) panic("Failed to close file\n"); + } +err: + return 0; +} + int main(int argc, char **argv) { @@ -380,8 +453,37 @@ main(int argc, char **argv) #ifdef LOG_MODULE_LOADING debuglog("Parsing modules file [%s]\n", argv[1]); #endif - if (module_alloc_from_json(argv[1])) panic("failed to initialize module(s) defined in %s\n", argv[1]); + const char *json_path = argv[1]; + char *json_buf = NULL; + size_t json_buf_len = load_file_into_buffer(json_path, &json_buf); + if (unlikely(json_buf_len == 0)) panic("failed to initialize module(s) defined in %s\n", json_path); + + struct module_config *module_config_vec; + + int module_config_vec_len = parse_json(json_buf, json_buf_len, &module_config_vec); + if (module_config_vec_len < 0) { exit(-1); } + free(json_buf); + for (int module_idx = 0; module_idx < module_config_vec_len; module_idx++) { + /* Automatically calls listen */ + struct module *module = module_alloc(&module_config_vec[module_idx]); + if (unlikely(module == NULL)) panic("failed to initialize module(s) defined in %s\n", json_path); + + int rc = module_database_add(module); + if (rc < 0) { + panic("Module database full!\n"); + exit(-1); + } + + /* Start listening for requests */ + rc = module_listen(module); + if (rc < 0) exit(-1); + } + + for (int module_idx = 0; module_idx < module_config_vec_len; module_idx++) { + module_config_deinit(&module_config_vec[module_idx]); + } + free(module_config_vec); for (int i = 0; i < runtime_worker_threads_count; i++) { int ret = pthread_join(runtime_worker_threads[i], NULL); diff --git a/runtime/src/module.c b/runtime/src/module.c index a57d9c2..201f36d 100644 --- a/runtime/src/module.c +++ b/runtime/src/module.c @@ -1,10 +1,8 @@ #include #include -#include #include #include #include -#include #include #include "debuglog.h" @@ -18,19 +16,118 @@ #include "scheduler.h" #include "wasm_table.h" -const int JSON_MAX_ELEMENT_COUNT = 16; -const int JSON_MAX_ELEMENT_SIZE = 1024; - /************************* * Private Static Inline * ************************/ +static inline int +module_init(struct module *module, struct module_config *config) +{ + assert(module != NULL); + assert(config != NULL); + assert(config->name != NULL); + assert(config->path != NULL); + assert(config->http_resp_content_type != NULL); + + uint32_t stack_size = 0; + + /* Validate presence of required fields */ + if (strlen(config->name) == 0) panic("name field is required\n"); + if (strlen(config->path) == 0) panic("path field is required\n"); + if (config->port == 0) panic("port field is required\n"); + + if (config->http_req_size > RUNTIME_HTTP_REQUEST_SIZE_MAX) + panic("request_size must be between 0 and %u, was %u\n", (uint32_t)RUNTIME_HTTP_REQUEST_SIZE_MAX, + config->http_req_size); + + if (config->http_resp_size > RUNTIME_HTTP_RESPONSE_SIZE_MAX) + panic("response-size must be between 0 and %u, was %u\n", (uint32_t)RUNTIME_HTTP_RESPONSE_SIZE_MAX, + config->http_resp_size); + + struct module *existing_module = module_database_find_by_name(config->name); + if (existing_module != NULL) panic("Module %s is already initialized\n", existing_module->name); + + existing_module = module_database_find_by_port(config->port); + if (existing_module != NULL) + panic("Module %s is already configured with port %u\n", existing_module->name, config->port); + + if (config->relative_deadline_us > (uint32_t)RUNTIME_RELATIVE_DEADLINE_US_MAX) + panic("Relative-deadline-us must be between 0 and %u, was %u\n", + (uint32_t)RUNTIME_RELATIVE_DEADLINE_US_MAX, config->relative_deadline_us); + +#ifdef ADMISSIONS_CONTROL + /* expected-execution-us and relative-deadline-us are required in case of admissions control */ + if (config->expected_execution_us == 0) panic("expected-execution-us is required\n"); + if (config->relative_deadline_us == 0) panic("relative_deadline_us is required\n"); + + if (config->admissions_percentile > 99 || config->admissions_percentile < 50) + panic("admissions-percentile must be > 50 and <= 99 but was %u\n", config->admissions_percentile); + + /* If the ratio is too big, admissions control is too coarse */ + uint32_t ratio = config->relative_deadline_us / config->expected_execution_us; + if (ratio > ADMISSIONS_CONTROL_GRANULARITY) + panic("Ratio of Deadline to Execution time cannot exceed admissions control " + "granularity of " + "%d\n", + ADMISSIONS_CONTROL_GRANULARITY); +#else + /* relative-deadline-us is required if scheduler is EDF */ + if (scheduler == SCHEDULER_EDF && config->relative_deadline_us == 0) + panic("relative_deadline_us is required\n"); +#endif + + int rc = 0; + + atomic_init(&module->reference_count, 0); + + rc = sledge_abi_symbols_init(&module->abi, config->path); + if (rc != 0) goto err; + + /* Set fields in the module struct */ + strncpy(module->name, config->name, MODULE_MAX_NAME_LENGTH); + strncpy(module->path, config->path, MODULE_MAX_PATH_LENGTH); + strncpy(module->response_content_type, config->http_resp_content_type, HTTP_MAX_HEADER_VALUE_LENGTH); + + module->stack_size = ((uint32_t)(round_up_to_page(stack_size == 0 ? WASM_STACK_SIZE : stack_size))); + module->socket_descriptor = -1; + module->port = config->port; + + /* Deadlines */ + module->relative_deadline_us = config->relative_deadline_us; + + /* This can overflow a uint32_t, so be sure to cast appropriately */ + module->relative_deadline = (uint64_t)config->relative_deadline_us * runtime_processor_speed_MHz; + + /* Admissions Control */ + uint64_t expected_execution = (uint64_t)config->expected_execution_us * runtime_processor_speed_MHz; + admissions_info_initialize(&module->admissions_info, config->admissions_percentile, expected_execution, + module->relative_deadline); + + /* Request Response Buffer */ + if (config->http_req_size == 0) config->http_req_size = MODULE_DEFAULT_REQUEST_RESPONSE_SIZE; + if (config->http_resp_size == 0) config->http_resp_size = MODULE_DEFAULT_REQUEST_RESPONSE_SIZE; + module->max_request_size = round_up_to_page(config->http_req_size); + module->max_response_size = round_up_to_page(config->http_resp_size); + + module_alloc_table(module); + module_initialize_pools(module); +done: + return rc; +err: + rc = -1; + goto done; +} + +/*************************************** + * Public Methods + ***************************************/ + /** * Start the module as a server listening at module->port * @param module * @returns 0 on success, -1 on error */ -static inline int +int module_listen(struct module *module) { int rc; @@ -86,24 +183,6 @@ err: goto done; } - -/** - * Sets the HTTP Response Content type on a module - * @param module - * @param response_content_type - */ -static inline void -module_set_http_info(struct module *module, char response_content_type[]) -{ - assert(module); - strcpy(module->response_content_type, response_content_type); -} - - -/*************************************** - * Public Methods - ***************************************/ - /** * Module Mega Teardown Function * Closes the socket and dynamic library, and then frees the module @@ -126,62 +205,9 @@ module_free(struct module *module) free(module); } -static inline int -module_init(struct module *module, char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, int port, - int request_size, int response_size, int admissions_percentile, uint32_t expected_execution_us) -{ - assert(module != NULL); - - int rc = 0; - - atomic_init(&module->reference_count, 0); - - rc = sledge_abi_symbols_init(&module->abi, path); - if (rc != 0) goto err; - - /* Set fields in the module struct */ - strncpy(module->name, name, MODULE_MAX_NAME_LENGTH); - strncpy(module->path, path, MODULE_MAX_PATH_LENGTH); - - module->stack_size = ((uint32_t)(round_up_to_page(stack_size == 0 ? WASM_STACK_SIZE : stack_size))); - module->socket_descriptor = -1; - module->port = port; - - /* Deadlines */ - module->relative_deadline_us = relative_deadline_us; - - /* This can overflow a uint32_t, so be sure to cast appropriately */ - module->relative_deadline = (uint64_t)relative_deadline_us * runtime_processor_speed_MHz; - - /* Admissions Control */ - uint64_t expected_execution = (uint64_t)expected_execution_us * runtime_processor_speed_MHz; - admissions_info_initialize(&module->admissions_info, admissions_percentile, expected_execution, - module->relative_deadline); - - /* Request Response Buffer */ - if (request_size == 0) request_size = MODULE_DEFAULT_REQUEST_RESPONSE_SIZE; - if (response_size == 0) response_size = MODULE_DEFAULT_REQUEST_RESPONSE_SIZE; - module->max_request_size = round_up_to_page(request_size); - module->max_response_size = round_up_to_page(response_size); - - module_alloc_table(module); - module_initialize_pools(module); - - /* Start listening for requests */ - rc = module_listen(module); - if (rc < 0) goto err; - -done: - return rc; -err: - rc = -1; - goto done; -} - /** * Module Contructor - * Creates a new module, invokes initialize_tables to initialize the indirect table, adds it to the module DB, and - *starts listening for HTTP Requests + * Creates a new module, invokes initialize_tables to initialize the indirect table, and adds it to the module DB * * @param name * @param path @@ -193,8 +219,7 @@ err: */ struct module * -module_alloc(char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, int port, int request_size, - int response_size, int admissions_percentile, uint32_t expected_execution_us) +module_alloc(struct module_config *config) { struct module *module = (struct module *)calloc(1, sizeof(struct module)); if (!module) { @@ -202,8 +227,7 @@ module_alloc(char *name, char *path, uint32_t stack_size, uint32_t relative_dead goto err; }; - int rc = module_init(module, name, path, stack_size, relative_deadline_us, port, request_size, response_size, - admissions_percentile, expected_execution_us); + int rc = module_init(module, config); if (rc < 0) goto init_err; done: @@ -215,233 +239,3 @@ err: module = NULL; goto done; } - -/** - * Parses a JSON file and allocates one or more new modules - * @param file_name The path of the JSON file - * @return RC 0 on Success. -1 on Error - */ -int -module_alloc_from_json(char *file_name) -{ - assert(file_name != NULL); - int return_code = -1; - jsmntok_t tokens[JSON_MAX_ELEMENT_SIZE * JSON_MAX_ELEMENT_COUNT]; - - /* Use stat to get file attributes and make sure file is present and not empty */ - struct stat stat_buffer; - if (stat(file_name, &stat_buffer) < 0) { - fprintf(stderr, "Attempt to stat %s failed: %s\n", file_name, strerror(errno)); - goto err; - } - if (stat_buffer.st_size == 0) { - fprintf(stderr, "File %s is unexpectedly empty\n", file_name); - goto err; - } - if (!S_ISREG(stat_buffer.st_mode)) { - fprintf(stderr, "File %s is not a regular file\n", file_name); - goto err; - } - - /* Open the file */ - FILE *module_file = fopen(file_name, "r"); - if (!module_file) { - fprintf(stderr, "Attempt to open %s failed: %s\n", file_name, strerror(errno)); - goto err; - } - - /* Initialize a Buffer */ - char *file_buffer = calloc(1, stat_buffer.st_size); - if (file_buffer == NULL) { - fprintf(stderr, "Attempt to allocate file buffer failed: %s\n", strerror(errno)); - goto stat_buffer_alloc_err; - } - - /* Read the file into the buffer and check that the buffer size equals the file size */ - int total_chars_read = fread(file_buffer, sizeof(char), stat_buffer.st_size, module_file); -#ifdef LOG_MODULE_LOADING - debuglog("size read: %d content: %s\n", total_chars_read, file_buffer); -#endif - if (total_chars_read != stat_buffer.st_size) { - fprintf(stderr, "Attempt to read %s into buffer failed: %s\n", file_name, strerror(errno)); - goto fread_err; - } - assert(total_chars_read > 0); - - /* Close the file */ - if (fclose(module_file) == EOF) { - fprintf(stderr, "Attempt to close buffer containing %s failed: %s\n", file_name, strerror(errno)); - goto fclose_err; - }; - module_file = NULL; - - /* Initialize the Jasmine Parser and an array to hold the tokens */ - jsmn_parser module_parser; - jsmn_init(&module_parser); - - /* Use Jasmine to parse the JSON */ - int total_tokens = jsmn_parse(&module_parser, file_buffer, total_chars_read, tokens, - sizeof(tokens) / sizeof(tokens[0])); - if (total_tokens < 0) { - if (total_tokens == JSMN_ERROR_INVAL) { - fprintf(stderr, "Error parsing %s: bad token, JSON string is corrupted\n", file_name); - } else if (total_tokens == JSMN_ERROR_PART) { - fprintf(stderr, "Error parsing %s: JSON string is too short, expecting more JSON data\n", - file_name); - } else if (total_tokens == JSMN_ERROR_NOMEM) { - /* - * According to the README at https://github.com/zserge/jsmn, this is a potentially recoverable - * error. More tokens can be allocated and jsmn_parse can be re-invoked. - */ - fprintf(stderr, "Error parsing %s: Not enough tokens, JSON string is too large\n", file_name); - } - goto json_parse_err; - } - - int module_count = 0; - for (int i = 0; i < total_tokens; i++) { - /* If we have multiple objects, they should be wrapped in a JSON array */ - if (tokens[i].type == JSMN_ARRAY) continue; - assert(tokens[i].type == JSMN_OBJECT); - - char module_name[MODULE_MAX_NAME_LENGTH] = { 0 }; - char module_path[MODULE_MAX_PATH_LENGTH] = { 0 }; - - int32_t request_size = 0; - int32_t response_size = 0; - uint32_t port = 0; - uint32_t relative_deadline_us = 0; - uint32_t expected_execution_us = 0; - int admissions_percentile = 50; - int j = 1; - int ntoks = 2 * tokens[i].size; - char response_content_type[HTTP_MAX_HEADER_VALUE_LENGTH] = { 0 }; - - for (; j < ntoks;) { - int ntks = 1; - char key[32] = { 0 }; - char val[256] = { 0 }; - - sprintf(val, "%.*s", tokens[j + i + 1].end - tokens[j + i + 1].start, - file_buffer + tokens[j + i + 1].start); - sprintf(key, "%.*s", tokens[j + i].end - tokens[j + i].start, - file_buffer + tokens[j + i].start); - - if (strlen(key) == 0) panic("Unexpected encountered empty key\n"); - if (strlen(val) == 0) panic("%s field contained empty string\n", key); - - if (strcmp(key, "name") == 0) { - // TODO: Currently, multiple modules can have identical names. Ports are the true unique - // identifiers. Consider enforcing unique names in future - strcpy(module_name, val); - } else if (strcmp(key, "path") == 0) { - // Invalid path will crash on dlopen - strcpy(module_path, val); - } else if (strcmp(key, "port") == 0) { - // Validate sane port - // If already taken, will error on bind call in module_listen - int buffer = atoi(val); - if (buffer < 0 || buffer > 65535) - panic("Expected port between 0 and 65535, saw %d\n", buffer); - port = buffer; - } else if (strcmp(key, "relative-deadline-us") == 0) { - int64_t buffer = strtoll(val, NULL, 10); - if (buffer < 0 || buffer > (int64_t)RUNTIME_RELATIVE_DEADLINE_US_MAX) - panic("Relative-deadline-us must be between 0 and %ld, was %ld\n", - (int64_t)RUNTIME_RELATIVE_DEADLINE_US_MAX, buffer); - relative_deadline_us = (uint32_t)buffer; - } else if (strcmp(key, "expected-execution-us") == 0) { - int64_t buffer = strtoll(val, NULL, 10); - if (buffer < 0 || buffer > (int64_t)RUNTIME_EXPECTED_EXECUTION_US_MAX) - panic("Relative-deadline-us must be between 0 and %ld, was %ld\n", - (int64_t)RUNTIME_EXPECTED_EXECUTION_US_MAX, buffer); - expected_execution_us = (uint32_t)buffer; - } else if (strcmp(key, "admissions-percentile") == 0) { - int32_t buffer = strtol(val, NULL, 10); - if (buffer > 99 || buffer < 50) - panic("admissions-percentile must be > 50 and <= 99 but was %d\n", buffer); - admissions_percentile = (int)buffer; - } else if (strcmp(key, "http-req-size") == 0) { - int64_t buffer = strtoll(val, NULL, 10); - if (buffer < 0 || buffer > RUNTIME_HTTP_REQUEST_SIZE_MAX) - panic("http-req-size must be between 0 and %ld, was %ld\n", - (int64_t)RUNTIME_HTTP_REQUEST_SIZE_MAX, buffer); - request_size = (int32_t)buffer; - } else if (strcmp(key, "http-resp-size") == 0) { - int64_t buffer = strtoll(val, NULL, 10); - if (buffer < 0 || buffer > RUNTIME_HTTP_REQUEST_SIZE_MAX) - panic("http-resp-size must be between 0 and %ld, was %ld\n", - (int64_t)RUNTIME_HTTP_REQUEST_SIZE_MAX, buffer); - response_size = (int32_t)buffer; - } else if (strcmp(key, "http-resp-content-type") == 0) { - if (strlen(val) == 0) panic("http-resp-content-type was unexpectedly an empty string"); - strcpy(response_content_type, val); - } else { -#ifdef LOG_MODULE_LOADING - debuglog("Invalid (%s,%s)\n", key, val); -#endif - } - j += ntks; - } - i += ntoks; - - - /* Validate presence of required fields */ - if (strlen(module_name) == 0) panic("name field is required\n"); - if (strlen(module_path) == 0) panic("path field is required\n"); - if (port == 0) panic("port field is required\n"); - -#ifdef ADMISSIONS_CONTROL - /* expected-execution-us and relative-deadline-us are required in case of admissions control */ - if (expected_execution_us == 0) panic("expected-execution-us is required\n"); - if (relative_deadline_us == 0) panic("relative_deadline_us is required\n"); - - /* If the ratio is too big, admissions control is too coarse */ - uint32_t ratio = relative_deadline_us / expected_execution_us; - if (ratio > ADMISSIONS_CONTROL_GRANULARITY) - panic("Ratio of Deadline to Execution time cannot exceed admissions control " - "granularity of " - "%d\n", - ADMISSIONS_CONTROL_GRANULARITY); -#else - /* relative-deadline-us is required if scheduler is EDF */ - if (scheduler == SCHEDULER_EDF && relative_deadline_us == 0) - panic("relative_deadline_us is required\n"); -#endif - - /* Allocate a module based on the values from the JSON */ - struct module *module = module_alloc(module_name, module_path, 0, relative_deadline_us, port, - request_size, response_size, admissions_percentile, - expected_execution_us); - if (module == NULL) goto module_alloc_err; - - assert(module); - module_set_http_info(module, response_content_type); - module_count++; - } - - if (module_count == 0) panic("%s contained no active modules\n", file_name); -#ifdef LOG_MODULE_LOADING - debuglog("Loaded %d module%s!\n", module_count, module_count > 1 ? "s" : ""); -#endif - free(file_buffer); - - return_code = 0; - -done: - return return_code; -module_alloc_err: -json_parse_err: -fclose_err: - /* We will retry fclose when we fall through into stat_buffer_alloc_err */ -fread_err: - free(file_buffer); -stat_buffer_alloc_err: - // Check to ensure we haven't already close this - if (module_file != NULL) { - if (fclose(module_file) == EOF) panic("Failed to close file\n"); - } -err: - return_code = -1; - goto done; -} diff --git a/runtime/src/module_database.c b/runtime/src/module_database.c index e4ebd15..215a8f5 100644 --- a/runtime/src/module_database.c +++ b/runtime/src/module_database.c @@ -64,3 +64,18 @@ module_database_find_by_socket_descriptor(int socket_descriptor) } return NULL; } + +/** + * Given a port, find the associated module + * @param port + * @return module or NULL if no match found + */ +struct module * +module_database_find_by_port(uint16_t port) +{ + for (size_t i = 0; i < module_database_count; i++) { + assert(module_database[i]); + if (module_database[i]->port == port) return module_database[i]; + } + return NULL; +} diff --git a/tests/TinyEKF/one_iteration/spec.json b/tests/TinyEKF/one_iteration/spec.json index a9bab5e..d571023 100644 --- a/tests/TinyEKF/one_iteration/spec.json +++ b/tests/TinyEKF/one_iteration/spec.json @@ -1,10 +1,12 @@ -{ - "name": "ekf", - "path": "gps_ekf.wasm.so", - "port": 10000, - "expected-execution-us": 5000, - "relative-deadline-us": 50000, - "http-req-size": 1024000, - "http-resp-size": 1024000, - "http-resp-content-type": "application/octet-stream" -} +[ + { + "name": "ekf", + "path": "gps_ekf.wasm.so", + "port": 10000, + "expected-execution-us": 5000, + "relative-deadline-us": 50000, + "http-req-size": 1024000, + "http-resp-size": 1024000, + "http-resp-content-type": "application/octet-stream" + } +] diff --git a/tests/empty/concurrency/spec.json b/tests/empty/concurrency/spec.json index ee168c9..ab92e1b 100644 --- a/tests/empty/concurrency/spec.json +++ b/tests/empty/concurrency/spec.json @@ -1,11 +1,13 @@ -{ - "name": "empty", - "path": "empty.wasm.so", - "port": 10000, - "expected-execution-us": 500, - "admissions-percentile": 70, - "relative-deadline-us": 50000, - "http-req-size": 1024, - "http-resp-size": 1024, - "http-resp-content-type": "text/plain" -} +[ + { + "name": "empty", + "path": "empty.wasm.so", + "port": 10000, + "expected-execution-us": 500, + "admissions-percentile": 70, + "relative-deadline-us": 50000, + "http-req-size": 1024, + "http-resp-size": 1024, + "http-resp-content-type": "text/plain" + } +] diff --git a/tests/gocr/fivebyeight/spec.json b/tests/gocr/fivebyeight/spec.json index ea53b2d..d9049da 100644 --- a/tests/gocr/fivebyeight/spec.json +++ b/tests/gocr/fivebyeight/spec.json @@ -1,10 +1,12 @@ -{ - "name": "gocr", - "path": "gocr.wasm.so", - "port": 10000, - "expected-execution-us": 5000, - "relative-deadline-us": 36000, - "http-req-size": 1024000, - "http-resp-size": 1024000, - "http-resp-content-type": "text/plain" -} +[ + { + "name": "gocr", + "path": "gocr.wasm.so", + "port": 10000, + "expected-execution-us": 5000, + "relative-deadline-us": 36000, + "http-req-size": 1024000, + "http-resp-size": 1024000, + "http-resp-content-type": "text/plain" + } +] diff --git a/tests/gocr/handwriting/spec.json b/tests/gocr/handwriting/spec.json index ea53b2d..d9049da 100644 --- a/tests/gocr/handwriting/spec.json +++ b/tests/gocr/handwriting/spec.json @@ -1,10 +1,12 @@ -{ - "name": "gocr", - "path": "gocr.wasm.so", - "port": 10000, - "expected-execution-us": 5000, - "relative-deadline-us": 36000, - "http-req-size": 1024000, - "http-resp-size": 1024000, - "http-resp-content-type": "text/plain" -} +[ + { + "name": "gocr", + "path": "gocr.wasm.so", + "port": 10000, + "expected-execution-us": 5000, + "relative-deadline-us": 36000, + "http-req-size": 1024000, + "http-resp-size": 1024000, + "http-resp-content-type": "text/plain" + } +] diff --git a/tests/gocr/hyde/spec.json b/tests/gocr/hyde/spec.json index 59f6649..f53cbf1 100644 --- a/tests/gocr/hyde/spec.json +++ b/tests/gocr/hyde/spec.json @@ -1,10 +1,12 @@ -{ - "name": "gocr", - "path": "gocr.wasm.so", - "port": 10000, - "expected-execution-us": 5000, - "relative-deadline-us": 360000, - "http-req-size": 5335057, - "http-resp-size": 5335057, - "http-resp-content-type": "text/plain" -} +[ + { + "name": "gocr", + "path": "gocr.wasm.so", + "port": 10000, + "expected-execution-us": 5000, + "relative-deadline-us": 360000, + "http-req-size": 5335057, + "http-resp-size": 5335057, + "http-resp-content-type": "text/plain" + } +] diff --git a/tests/sod/image_resize/test/spec.json b/tests/sod/image_resize/test/spec.json index cbd18f7..09881ff 100644 --- a/tests/sod/image_resize/test/spec.json +++ b/tests/sod/image_resize/test/spec.json @@ -1,10 +1,12 @@ -{ - "name": "resize", - "path": "resize_image.wasm.so", - "port": 10000, - "expected-execution-us": 5000, - "relative-deadline-us": 50000, - "http-req-size": 1024000, - "http-resp-size": 1024000, - "http-resp-content-type": "image/png" -} +[ + { + "name": "resize", + "path": "resize_image.wasm.so", + "port": 10000, + "expected-execution-us": 5000, + "relative-deadline-us": 50000, + "http-req-size": 1024000, + "http-resp-size": 1024000, + "http-resp-content-type": "image/png" + } +] diff --git a/tests/speechtotext/spec.json b/tests/speechtotext/spec.json index 119a456..cf6c2bc 100644 --- a/tests/speechtotext/spec.json +++ b/tests/speechtotext/spec.json @@ -1,9 +1,11 @@ -{ - "name": "hello_ps", - "path": "hello_ps.wasm.so", - "port": 10000, - "relative-deadline-us": 50000, - "http-req-size": 102400, - "http-resp-size": 1048576, - "http-resp-content-type": "image/jpeg" -} +[ + { + "name": "hello_ps", + "path": "hello_ps.wasm.so", + "port": 10000, + "relative-deadline-us": 50000, + "http-req-size": 102400, + "http-resp-size": 1048576, + "http-resp-content-type": "image/jpeg" + } +]