From 74a3270cb0b1edf9db1891c18a3d3d60482dcc0a Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Wed, 13 Apr 2022 18:08:47 -0400 Subject: [PATCH 01/17] refactor: load_file_into_buffer --- runtime/src/module.c | 65 ++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/runtime/src/module.c b/runtime/src/module.c index a57d9c2..0e896d5 100644 --- a/runtime/src/module.c +++ b/runtime/src/module.c @@ -217,17 +217,14 @@ err: } /** - * 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 + * 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 -1 in case of error; */ -int -module_alloc_from_json(char *file_name) +static inline size_t +load_file_into_buffer(char *file_name, char **file_buffer) { - 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) { @@ -251,16 +248,16 @@ module_alloc_from_json(char *file_name) } /* Initialize a Buffer */ - char *file_buffer = calloc(1, stat_buffer.st_size); - if (file_buffer == NULL) { + *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); + ssize_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); + 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)); @@ -275,6 +272,39 @@ module_alloc_from_json(char *file_name) }; 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 (ssize_t)-1; +} + +/** + * 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]; + + /* Load file_name into memory */ + char *file_buffer = NULL; + ssize_t total_chars_read = load_file_into_buffer(file_name, &file_buffer); + if (total_chars_read <= 0) goto module_alloc_err; + /* Initialize the Jasmine Parser and an array to hold the tokens */ jsmn_parser module_parser; jsmn_init(&module_parser); @@ -432,15 +462,8 @@ 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: +file_load_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; From 438b7650ba6c94604ce402343887f3f7fdaef0cf Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Wed, 13 Apr 2022 18:21:19 -0400 Subject: [PATCH 02/17] refactor: Move validation to module_alloc --- runtime/src/module.c | 46 +++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/runtime/src/module.c b/runtime/src/module.c index 0e896d5..8317c27 100644 --- a/runtime/src/module.c +++ b/runtime/src/module.c @@ -196,6 +196,28 @@ 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) { + /* Validate presence of required fields */ + if (strlen(name) == 0) panic("name field is required\n"); + if (strlen(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 + struct module *module = (struct module *)calloc(1, sizeof(struct module)); if (!module) { fprintf(stderr, "Failed to allocate module: %s\n", strerror(errno)); @@ -415,30 +437,6 @@ module_alloc_from_json(char *file_name) } 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, From b79eb449589d3e07e9f3e1cdde975e7714572ef8 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Wed, 13 Apr 2022 20:29:09 -0400 Subject: [PATCH 03/17] refactor: move JSON file loading from module --- runtime/include/module.h | 2 +- runtime/src/main.c | 80 ++++++++++++++++++++++++++++++- runtime/src/module.c | 101 ++++----------------------------------- 3 files changed, 89 insertions(+), 94 deletions(-) diff --git a/runtime/include/module.h b/runtime/include/module.h index c18a87c..9862042 100644 --- a/runtime/include/module.h +++ b/runtime/include/module.h @@ -244,4 +244,4 @@ 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); +int module_alloc_from_json(const char *json_buf, ssize_t json_buf_size); diff --git a/runtime/src/main.c b/runtime/src/main.c index 5fbd7f1..b4070c8 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -344,6 +345,77 @@ 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 -1 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 */ + ssize_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; + } + 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; + + 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 (ssize_t)-1; +} + int main(int argc, char **argv) { @@ -380,8 +452,12 @@ 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; + ssize_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); + int rc = module_alloc_from_json(json_buf, json_buf_len); + if (unlikely(rc != 0)) panic("failed to initialize module(s) defined in %s\n", json_path); 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 8317c27..6b17f45 100644 --- a/runtime/src/module.c +++ b/runtime/src/module.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "debuglog.h" @@ -238,114 +237,39 @@ err: goto done; } -/** - * 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 -1 in case of error; - */ -static inline size_t -load_file_into_buffer(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 */ - ssize_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; - } - 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; - - 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 (ssize_t)-1; -} - /** * 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) +module_alloc_from_json(const char *json_buf, ssize_t json_buf_size) { - assert(file_name != NULL); + assert(json_buf != NULL); + assert(json_buf_size > 0); int return_code = -1; jsmntok_t tokens[JSON_MAX_ELEMENT_SIZE * JSON_MAX_ELEMENT_COUNT]; - /* Load file_name into memory */ - char *file_buffer = NULL; - ssize_t total_chars_read = load_file_into_buffer(file_name, &file_buffer); - if (total_chars_read <= 0) goto module_alloc_err; - /* 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, + int total_tokens = jsmn_parse(&module_parser, json_buf, json_buf_size, 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); + 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", - file_name); + 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", file_name); + fprintf(stderr, "Error parsing %s: Not enough tokens, JSON string is too large\n", json_buf); } goto json_parse_err; } @@ -375,9 +299,8 @@ module_alloc_from_json(char *file_name) 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); + json_buf + tokens[j + i + 1].start); + sprintf(key, "%.*s", tokens[j + i].end - tokens[j + i].start, json_buf + 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); @@ -448,20 +371,16 @@ module_alloc_from_json(char *file_name) module_count++; } - if (module_count == 0) panic("%s contained no active modules\n", file_name); + if (module_count == 0) panic("%s contained no active modules\n", json_buf); #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: -file_load_err: - free(file_buffer); err: return_code = -1; goto done; From e27ffa5620db61bbd6119690014d85de0fdf52e1 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Wed, 13 Apr 2022 22:42:56 -0400 Subject: [PATCH 04/17] refactor: break JSON parsing out from module.c --- .vscode/settings.json | 3 +- runtime/include/json.h | 230 +++++++++++++++++++++++++++++++++++++++ runtime/include/module.h | 8 +- runtime/src/main.c | 20 +++- runtime/src/module.c | 175 +---------------------------- 5 files changed, 259 insertions(+), 177 deletions(-) create mode 100644 runtime/include/json.h diff --git a/.vscode/settings.json b/.vscode/settings.json index f8a6af4..742e0bb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -110,7 +110,8 @@ "current_wasm_module_instance.h": "c", "wasm_memory.h": "c", "sledge_abi.h": "c", - "vec.h": "c" + "vec.h": "c", + "module_database.h": "c" }, "files.exclude": { "**/.git": true, diff --git a/runtime/include/json.h b/runtime/include/json.h new file mode 100644 index 0000000..954b8bc --- /dev/null +++ b/runtime/include/json.h @@ -0,0 +1,230 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "runtime.h" +#include "http.h" +#include "module.h" + +static const int JSON_MAX_ELEMENT_COUNT = 16; +static const int JSON_MAX_ELEMENT_SIZE = 1024; + +struct module_config { + char *name; + char *path; + int port; + uint32_t expected_execution_us; + int admissions_percentile; + uint32_t relative_deadline_us; + int32_t http_req_size; + int32_t http_resp_size; + char *http_resp_content_type; +}; + +static void +print_module_config(struct module_config *config) +{ + printf("Name: %s\n", config->name); + printf("Path: %s\n", config->path); + printf("Port: %d\n", config->port); + printf("expected_execution_us: %u\n", config->expected_execution_us); + printf("admissions_percentile: %d\n", config->admissions_percentile); + 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); +} + +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"; + } +} + +/** + * 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_MAX_ELEMENT_SIZE * JSON_MAX_ELEMENT_COUNT]; + 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, + 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", 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\n"); + 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 }; + char val[256] = { 0 }; + + if (tokens[i].type == JSMN_OBJECT) { + assert(module_fields_remaining == 0); + module_fields_remaining = tokens[i].size; + module_idx++; + } else { + /* Inside Object */ + assert(tokens[i].type == JSMN_STRING); + sprintf(key, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); + if (strcmp(key, "name") == 0) { + /* Should always have a value */ + assert(tokens[i].size == 1); + i++; + assert(tokens[i].type == JSMN_STRING); + assert(tokens[i].end - tokens[i].start > 0); + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); + (*module_config_vec)[module_idx].name = strndup(val, tokens[i].end - tokens[i].start); + } else if (strcmp(key, "path") == 0) { + assert(tokens[i].size == 1); + i++; + assert(tokens[i].type == JSMN_STRING); + assert(tokens[i].end - tokens[i].start > 0); + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); + (*module_config_vec)[module_idx].path = strndup(val, tokens[i].end - tokens[i].start); + } else if (strcmp(key, "port") == 0) { + assert(tokens[i].size == 1); + i++; + assert(tokens[i].type == JSMN_PRIMITIVE); + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); + // 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); + (*module_config_vec)[module_idx].port = buffer; + } else if (strcmp(key, "expected-execution-us") == 0) { + assert(tokens[i].size == 1); + i++; + assert(tokens[i].type == JSMN_PRIMITIVE); + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); + 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); + (*module_config_vec)[module_idx].expected_execution_us = (uint32_t)buffer; + } else if (strcmp(key, "admissions-percentile") == 0) { + assert(tokens[i].size == 1); + i++; + assert(tokens[i].type == JSMN_PRIMITIVE); + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); + 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); + (*module_config_vec)[module_idx].admissions_percentile = buffer; + } else if (strcmp(key, "relative-deadline-us") == 0) { + assert(tokens[i].size == 1); + i++; + assert(tokens[i].type == JSMN_PRIMITIVE); + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); + 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); + (*module_config_vec)[module_idx].relative_deadline_us = (uint32_t)buffer; + } else if (strcmp(key, "http-req-size") == 0) { + assert(tokens[i].size == 1); + i++; + assert(tokens[i].type == JSMN_PRIMITIVE); + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); + 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); + (*module_config_vec)[module_idx].http_req_size = (int32_t)buffer; + } else if (strcmp(key, "http-resp-size") == 0) { + assert(tokens[i].size == 1); + i++; + assert(tokens[i].type == JSMN_PRIMITIVE); + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); + 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_RESPONSE_SIZE_MAX, buffer); + (*module_config_vec)[module_idx].http_resp_size = (int32_t)buffer; + } else if (strcmp(key, "http-resp-content-type") == 0) { + assert(tokens[i].size == 1); + i++; + assert(tokens[i].type == JSMN_STRING); + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); + (*module_config_vec)[module_idx].http_resp_content_type = strndup(val, + 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: + module_config_vec_len = -1; + goto done; +} diff --git a/runtime/include/module.h b/runtime/include/module.h index 9862042..d7e6f32 100644 --- a/runtime/include/module.h +++ b/runtime/include/module.h @@ -241,7 +241,7 @@ module_free_linear_memory(struct module *module, struct wasm_memory *memory) * Public Methods from module.c * *******************************/ -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(const char *json_buf, ssize_t json_buf_size); +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, char *response_content_type); diff --git a/runtime/src/main.c b/runtime/src/main.c index b4070c8..960f7c8 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -15,6 +15,7 @@ #include #endif +#include "json.h" #include "pretty_print.h" #include "debuglog.h" #include "listener_thread.h" @@ -456,8 +457,23 @@ main(int argc, char **argv) char *json_buf = NULL; ssize_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); - int rc = module_alloc_from_json(json_buf, json_buf_len); - if (unlikely(rc != 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); + 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].name, + module_config_vec[module_idx].path, 0, + module_config_vec[module_idx].relative_deadline_us, + module_config_vec[module_idx].port, + module_config_vec[module_idx].http_req_size, + module_config_vec[module_idx].http_resp_size, + module_config_vec[module_idx].admissions_percentile, + module_config_vec[module_idx].expected_execution_us, + module_config_vec[module_idx].http_resp_content_type); + if (unlikely(module == NULL)) panic("failed to initialize module(s) defined in %s\n", json_path); + } 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 6b17f45..ec13d05 100644 --- a/runtime/src/module.c +++ b/runtime/src/module.c @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -17,9 +16,6 @@ #include "scheduler.h" #include "wasm_table.h" -const int JSON_MAX_ELEMENT_COUNT = 16; -const int JSON_MAX_ELEMENT_SIZE = 1024; - /************************* * Private Static Inline * ************************/ @@ -85,20 +81,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 ***************************************/ @@ -127,7 +109,8 @@ module_free(struct module *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) + int request_size, int response_size, int admissions_percentile, uint32_t expected_execution_us, + char *response_content_type) { assert(module != NULL); @@ -141,6 +124,7 @@ module_init(struct module *module, char *name, char *path, uint32_t stack_size, /* Set fields in the module struct */ strncpy(module->name, name, MODULE_MAX_NAME_LENGTH); strncpy(module->path, path, MODULE_MAX_PATH_LENGTH); + strncpy(module->response_content_type, response_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; @@ -193,7 +177,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) + int response_size, int admissions_percentile, uint32_t expected_execution_us, char *response_content_type) { /* Validate presence of required fields */ if (strlen(name) == 0) panic("name field is required\n"); @@ -224,7 +208,7 @@ module_alloc(char *name, char *path, uint32_t stack_size, uint32_t relative_dead }; int rc = module_init(module, name, path, stack_size, relative_deadline_us, port, request_size, response_size, - admissions_percentile, expected_execution_us); + admissions_percentile, expected_execution_us, response_content_type); if (rc < 0) goto init_err; done: @@ -236,152 +220,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(const char *json_buf, ssize_t json_buf_size) -{ - assert(json_buf != NULL); - assert(json_buf_size > 0); - - int return_code = -1; - jsmntok_t tokens[JSON_MAX_ELEMENT_SIZE * JSON_MAX_ELEMENT_COUNT]; - - /* 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, - 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", 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 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, - json_buf + tokens[j + i + 1].start); - sprintf(key, "%.*s", tokens[j + i].end - tokens[j + i].start, json_buf + 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; - - /* 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", json_buf); -#ifdef LOG_MODULE_LOADING - debuglog("Loaded %d module%s!\n", module_count, module_count > 1 ? "s" : ""); -#endif - return_code = 0; - -done: - return return_code; -module_alloc_err: -json_parse_err: -err: - return_code = -1; - goto done; -} From 68bf915c0215ff6808313edc37612cd4ae1d0636 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Thu, 14 Apr 2022 09:19:16 -0400 Subject: [PATCH 05/17] refactor: Improve JSON validation logs --- runtime/include/json.h | 106 ++++++++++++++++++++------ runtime/src/main.c | 2 + tests/TinyEKF/one_iteration/spec.json | 22 +++--- tests/empty/concurrency/spec.json | 24 +++--- tests/gocr/fivebyeight/spec.json | 22 +++--- tests/gocr/handwriting/spec.json | 22 +++--- tests/gocr/hyde/spec.json | 22 +++--- tests/sod/image_resize/test/spec.json | 22 +++--- tests/speechtotext/spec.json | 20 ++--- 9 files changed, 167 insertions(+), 95 deletions(-) diff --git a/runtime/include/json.h b/runtime/include/json.h index 954b8bc..7255035 100644 --- a/runtime/include/json.h +++ b/runtime/include/json.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -59,6 +60,58 @@ jsmn_type(jsmntype_t type) } } +static inline bool +has_valid_size(jsmntok_t tok, char *key, int expected_size) +{ + if (tok.size != 1) { + fprintf(stderr, "%s does not have a value\n", key); + 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; +} + /** * Parses a JSON file into an array of module configs * @param file_name The path of the JSON file @@ -99,7 +152,7 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m } if (tokens[0].type != JSMN_ARRAY) { - fprintf(stderr, "Outermost Config should be a JSON array\n"); + fprintf(stderr, "Outermost Config should be a JSON array, was a JSON %s\n", jsmn_type(tokens[0].type)); goto err; } @@ -123,38 +176,40 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m module_idx++; } else { /* Inside Object */ - assert(tokens[i].type == JSMN_STRING); + + /* 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; + if (strcmp(key, "name") == 0) { - /* Should always have a value */ - assert(tokens[i].size == 1); i++; - assert(tokens[i].type == JSMN_STRING); - assert(tokens[i].end - tokens[i].start > 0); + if (!is_nonempty_string(tokens[i], key)) goto json_parse_err; + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); (*module_config_vec)[module_idx].name = strndup(val, tokens[i].end - tokens[i].start); } else if (strcmp(key, "path") == 0) { - assert(tokens[i].size == 1); i++; - assert(tokens[i].type == JSMN_STRING); - assert(tokens[i].end - tokens[i].start > 0); + if (!is_nonempty_string(tokens[i], key)) goto json_parse_err; + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); (*module_config_vec)[module_idx].path = strndup(val, tokens[i].end - tokens[i].start); } else if (strcmp(key, "port") == 0) { - assert(tokens[i].size == 1); i++; - assert(tokens[i].type == JSMN_PRIMITIVE); + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); - // 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); (*module_config_vec)[module_idx].port = buffer; } else if (strcmp(key, "expected-execution-us") == 0) { - assert(tokens[i].size == 1); i++; - assert(tokens[i].type == JSMN_PRIMITIVE); + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); int64_t buffer = strtoll(val, NULL, 10); if (buffer < 0 || buffer > (int64_t)RUNTIME_EXPECTED_EXECUTION_US_MAX) @@ -162,18 +217,18 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m (int64_t)RUNTIME_EXPECTED_EXECUTION_US_MAX, buffer); (*module_config_vec)[module_idx].expected_execution_us = (uint32_t)buffer; } else if (strcmp(key, "admissions-percentile") == 0) { - assert(tokens[i].size == 1); i++; - assert(tokens[i].type == JSMN_PRIMITIVE); + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); 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); (*module_config_vec)[module_idx].admissions_percentile = buffer; } else if (strcmp(key, "relative-deadline-us") == 0) { - assert(tokens[i].size == 1); i++; - assert(tokens[i].type == JSMN_PRIMITIVE); + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); int64_t buffer = strtoll(val, NULL, 10); if (buffer < 0 || buffer > (int64_t)RUNTIME_RELATIVE_DEADLINE_US_MAX) @@ -181,9 +236,9 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m (int64_t)RUNTIME_RELATIVE_DEADLINE_US_MAX, buffer); (*module_config_vec)[module_idx].relative_deadline_us = (uint32_t)buffer; } else if (strcmp(key, "http-req-size") == 0) { - assert(tokens[i].size == 1); i++; - assert(tokens[i].type == JSMN_PRIMITIVE); + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); int64_t buffer = strtoll(val, NULL, 10); if (buffer < 0 || buffer > RUNTIME_HTTP_REQUEST_SIZE_MAX) @@ -191,9 +246,9 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m (int64_t)RUNTIME_HTTP_REQUEST_SIZE_MAX, buffer); (*module_config_vec)[module_idx].http_req_size = (int32_t)buffer; } else if (strcmp(key, "http-resp-size") == 0) { - assert(tokens[i].size == 1); i++; - assert(tokens[i].type == JSMN_PRIMITIVE); + if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); int64_t buffer = strtoll(val, NULL, 10); if (buffer < 0 || buffer > RUNTIME_HTTP_REQUEST_SIZE_MAX) @@ -201,9 +256,9 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m (int64_t)RUNTIME_HTTP_RESPONSE_SIZE_MAX, buffer); (*module_config_vec)[module_idx].http_resp_size = (int32_t)buffer; } else if (strcmp(key, "http-resp-content-type") == 0) { - assert(tokens[i].size == 1); i++; - assert(tokens[i].type == JSMN_STRING); + if (!has_valid_type(tokens[i], key, JSMN_STRING)) goto json_parse_err; + sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); (*module_config_vec)[module_idx].http_resp_content_type = strndup(val, tokens[i].end @@ -225,6 +280,7 @@ done: 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/src/main.c b/runtime/src/main.c index 960f7c8..6817c19 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -461,6 +461,8 @@ main(int argc, char **argv) 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); } + 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].name, 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" + } +] From a07533bd2ccddfd5a813a4e9e04584af3bbb8396 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Thu, 14 Apr 2022 09:39:34 -0400 Subject: [PATCH 06/17] refactor: move tok advance and add assumption --- runtime/include/json.h | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/runtime/include/json.h b/runtime/include/json.h index 7255035..0d66f58 100644 --- a/runtime/include/json.h +++ b/runtime/include/json.h @@ -170,6 +170,8 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m char key[32] = { 0 }; char val[256] = { 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; @@ -185,20 +187,20 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m /* 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, "name") == 0) { - i++; if (!is_nonempty_string(tokens[i], key)) goto json_parse_err; sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); (*module_config_vec)[module_idx].name = strndup(val, tokens[i].end - tokens[i].start); } else if (strcmp(key, "path") == 0) { - i++; if (!is_nonempty_string(tokens[i], key)) goto json_parse_err; sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); (*module_config_vec)[module_idx].path = strndup(val, tokens[i].end - tokens[i].start); } else if (strcmp(key, "port") == 0) { - i++; if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); @@ -207,7 +209,6 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m panic("Expected port between 0 and 65535, saw %d\n", buffer); (*module_config_vec)[module_idx].port = buffer; } else if (strcmp(key, "expected-execution-us") == 0) { - i++; if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); @@ -217,7 +218,6 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m (int64_t)RUNTIME_EXPECTED_EXECUTION_US_MAX, buffer); (*module_config_vec)[module_idx].expected_execution_us = (uint32_t)buffer; } else if (strcmp(key, "admissions-percentile") == 0) { - i++; if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); @@ -226,7 +226,6 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m panic("admissions-percentile must be > 50 and <= 99 but was %d\n", buffer); (*module_config_vec)[module_idx].admissions_percentile = buffer; } else if (strcmp(key, "relative-deadline-us") == 0) { - i++; if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); @@ -236,7 +235,6 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m (int64_t)RUNTIME_RELATIVE_DEADLINE_US_MAX, buffer); (*module_config_vec)[module_idx].relative_deadline_us = (uint32_t)buffer; } else if (strcmp(key, "http-req-size") == 0) { - i++; if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); @@ -246,7 +244,6 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m (int64_t)RUNTIME_HTTP_REQUEST_SIZE_MAX, buffer); (*module_config_vec)[module_idx].http_req_size = (int32_t)buffer; } else if (strcmp(key, "http-resp-size") == 0) { - i++; if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); @@ -256,7 +253,6 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m (int64_t)RUNTIME_HTTP_RESPONSE_SIZE_MAX, buffer); (*module_config_vec)[module_idx].http_resp_size = (int32_t)buffer; } else if (strcmp(key, "http-resp-content-type") == 0) { - i++; if (!has_valid_type(tokens[i], key, JSMN_STRING)) goto json_parse_err; sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); From 7f038f1269d7a01d52df77e550525f1d78de3aa7 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Thu, 14 Apr 2022 13:52:06 -0400 Subject: [PATCH 07/17] refactor: jsonint parsing and module validation --- .vscode/settings.json | 3 +- runtime/include/admissions_info.h | 6 +- runtime/include/json.h | 127 ++++++++++++++++++------------ runtime/include/module.h | 10 +-- runtime/include/perf_window.h | 10 +-- runtime/src/admissions_info.c | 4 +- runtime/src/main.c | 3 + runtime/src/module.c | 78 ++++++++++-------- 8 files changed, 141 insertions(+), 100 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 742e0bb..79706f3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -111,7 +111,8 @@ "wasm_memory.h": "c", "sledge_abi.h": "c", "vec.h": "c", - "module_database.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 index 0d66f58..0bc0124 100644 --- a/runtime/include/json.h +++ b/runtime/include/json.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -18,12 +19,12 @@ static const int JSON_MAX_ELEMENT_SIZE = 1024; struct module_config { char *name; char *path; - int port; + uint16_t port; + uint8_t admissions_percentile; uint32_t expected_execution_us; - int admissions_percentile; uint32_t relative_deadline_us; - int32_t http_req_size; - int32_t http_resp_size; + uint32_t http_req_size; + uint32_t http_resp_size; char *http_resp_content_type; }; @@ -32,9 +33,9 @@ print_module_config(struct module_config *config) { printf("Name: %s\n", config->name); printf("Path: %s\n", config->path); - printf("Port: %d\n", config->port); + printf("Port: %u\n", config->port); printf("expected_execution_us: %u\n", config->expected_execution_us); - printf("admissions_percentile: %d\n", config->admissions_percentile); + printf("admissions_percentile: %u\n", config->admissions_percentile); 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); @@ -112,6 +113,51 @@ is_valid_key(jsmntok_t tok) 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 @@ -167,8 +213,7 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m int module_fields_remaining = 0; for (int i = 1; i < total_tokens; i++) { - char key[32] = { 0 }; - char val[256] = { 0 }; + 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 */ @@ -193,72 +238,54 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m if (strcmp(key, "name") == 0) { if (!is_nonempty_string(tokens[i], key)) goto json_parse_err; - sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); - (*module_config_vec)[module_idx].name = strndup(val, tokens[i].end - tokens[i].start); + (*module_config_vec)[module_idx].name = strndup(json_buf + tokens[i].start, + tokens[i].end - tokens[i].start); } else if (strcmp(key, "path") == 0) { if (!is_nonempty_string(tokens[i], key)) goto json_parse_err; - sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); - (*module_config_vec)[module_idx].path = strndup(val, tokens[i].end - tokens[i].start); + (*module_config_vec)[module_idx].path = strndup(json_buf + tokens[i].start, + tokens[i].end - tokens[i].start); } else if (strcmp(key, "port") == 0) { if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; - sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); - int buffer = atoi(val); - if (buffer < 0 || buffer > 65535) - panic("Expected port between 0 and 65535, saw %d\n", buffer); - (*module_config_vec)[module_idx].port = buffer; + int rc = parse_uint16_t(tokens[i], json_buf, "port", + &(*module_config_vec)[module_idx].port); + if (rc < 0) goto json_parse_err; } else if (strcmp(key, "expected-execution-us") == 0) { if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; - sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); - 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); - (*module_config_vec)[module_idx].expected_execution_us = (uint32_t)buffer; + int rc = parse_uint32_t(tokens[i], json_buf, "expected-execution-us", + &(*module_config_vec)[module_idx].expected_execution_us); + if (rc < 0) goto json_parse_err; } else if (strcmp(key, "admissions-percentile") == 0) { if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; - sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); - 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); - (*module_config_vec)[module_idx].admissions_percentile = buffer; + int rc = parse_uint8_t(tokens[i], json_buf, "admissions-percentile", + &(*module_config_vec)[module_idx].admissions_percentile); + if (rc < 0) goto json_parse_err; } else if (strcmp(key, "relative-deadline-us") == 0) { if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; - sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); - 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); - (*module_config_vec)[module_idx].relative_deadline_us = (uint32_t)buffer; + int rc = parse_uint32_t(tokens[i], json_buf, "relative-deadline-us", + &(*module_config_vec)[module_idx].relative_deadline_us); + if (rc < 0) goto json_parse_err; } else if (strcmp(key, "http-req-size") == 0) { if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; - sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); - 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); - (*module_config_vec)[module_idx].http_req_size = (int32_t)buffer; + int rc = parse_uint32_t(tokens[i], json_buf, "http-req-size", + &(*module_config_vec)[module_idx].http_req_size); + if (rc < 0) goto json_parse_err; } else if (strcmp(key, "http-resp-size") == 0) { if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err; - sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); - 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_RESPONSE_SIZE_MAX, buffer); - (*module_config_vec)[module_idx].http_resp_size = (int32_t)buffer; + int rc = parse_uint32_t(tokens[i], json_buf, "http-resp-size", + &(*module_config_vec)[module_idx].http_resp_size); + if (rc < 0) goto json_parse_err; } else if (strcmp(key, "http-resp-content-type") == 0) { if (!has_valid_type(tokens[i], key, JSMN_STRING)) goto json_parse_err; - sprintf(val, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start); - (*module_config_vec)[module_idx].http_resp_content_type = strndup(val, - tokens[i].end - - tokens[i].start); + (*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; diff --git a/runtime/include/module.h b/runtime/include/module.h index d7e6f32..c8de37a 100644 --- a/runtime/include/module.h +++ b/runtime/include/module.h @@ -57,7 +57,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 */ @@ -241,7 +241,7 @@ module_free_linear_memory(struct module *module, struct wasm_memory *memory) * Public Methods from module.c * *******************************/ -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, char *response_content_type); +void module_free(struct module *module); +struct module *module_alloc(char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, uint16_t port, + uint32_t request_size, uint32_t response_size, uint8_t admissions_percentile, + uint32_t expected_execution_us, char *response_content_type); 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 6817c19..603b1f5 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -475,6 +475,9 @@ main(int argc, char **argv) module_config_vec[module_idx].expected_execution_us, module_config_vec[module_idx].http_resp_content_type); if (unlikely(module == NULL)) panic("failed to initialize module(s) defined in %s\n", json_path); + /* Start listening for requests */ + int rc = module_listen(module); + if (rc < 0) exit(-1); } for (int i = 0; i < runtime_worker_threads_count; i++) { diff --git a/runtime/src/module.c b/runtime/src/module.c index ec13d05..50ba095 100644 --- a/runtime/src/module.c +++ b/runtime/src/module.c @@ -108,12 +108,49 @@ module_free(struct module *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, - char *response_content_type) +module_init(struct module *module, char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, + uint16_t port, uint32_t request_size, uint32_t response_size, uint8_t admissions_percentile, + uint32_t expected_execution_us, char *response_content_type) { assert(module != NULL); + /* Validate presence of required fields */ + if (strlen(name) == 0) panic("name field is required\n"); + if (strlen(path) == 0) panic("path field is required\n"); + if (port == 0) panic("port field is required\n"); + + if (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, relative_deadline_us); + + if (request_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, + request_size); + + if (response_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, + response_size); + +#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 (admissions_percentile > 99 || admissions_percentile < 50) + panic("admissions-percentile must be > 50 and <= 99 but was %u\n", admissions_percentile); + + /* 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 + int rc = 0; atomic_init(&module->reference_count, 0); @@ -149,11 +186,6 @@ module_init(struct module *module, char *name, char *path, uint32_t stack_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: @@ -163,8 +195,7 @@ err: /** * 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 @@ -176,31 +207,10 @@ 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, char *response_content_type) +module_alloc(char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, uint16_t port, + uint32_t request_size, uint32_t response_size, uint8_t admissions_percentile, + uint32_t expected_execution_us, char *response_content_type) { - /* Validate presence of required fields */ - if (strlen(name) == 0) panic("name field is required\n"); - if (strlen(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 - struct module *module = (struct module *)calloc(1, sizeof(struct module)); if (!module) { fprintf(stderr, "Failed to allocate module: %s\n", strerror(errno)); From e4c063a209ab45711ecb4b3e98010951f6eeccb7 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Thu, 14 Apr 2022 15:50:06 -0400 Subject: [PATCH 08/17] fix: extern module_listen --- runtime/include/module.h | 1 + runtime/src/module.c | 174 +++++++++++++++++++-------------------- 2 files changed, 88 insertions(+), 87 deletions(-) diff --git a/runtime/include/module.h b/runtime/include/module.h index c8de37a..4d0477e 100644 --- a/runtime/include/module.h +++ b/runtime/include/module.h @@ -245,3 +245,4 @@ void module_free(struct module *module); struct module *module_alloc(char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, uint16_t port, uint32_t request_size, uint32_t response_size, uint8_t admissions_percentile, uint32_t expected_execution_us, char *response_content_type); +int module_listen(struct module *module); diff --git a/runtime/src/module.c b/runtime/src/module.c index 50ba095..04f4534 100644 --- a/runtime/src/module.c +++ b/runtime/src/module.c @@ -20,93 +20,6 @@ * Private Static Inline * ************************/ -/** - * Start the module as a server listening at module->port - * @param module - * @returns 0 on success, -1 on error - */ -static inline int -module_listen(struct module *module) -{ - int rc; - - /* Allocate a new TCP/IP socket, setting it to be non-blocking */ - int socket_descriptor = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); - if (unlikely(socket_descriptor < 0)) goto err_create_socket; - - /* Socket should never have returned on fd 0, 1, or 2 */ - assert(socket_descriptor != STDIN_FILENO); - assert(socket_descriptor != STDOUT_FILENO); - assert(socket_descriptor != STDERR_FILENO); - - /* Configure the socket to allow multiple sockets to bind to the same host and port */ - int optval = 1; - rc = setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); - if (unlikely(rc < 0)) goto err_set_socket_option; - optval = 1; - rc = setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); - if (unlikely(rc < 0)) goto err_set_socket_option; - - /* Bind name [all addresses]:[module->port] to socket */ - module->socket_descriptor = socket_descriptor; - module->socket_address.sin_family = AF_INET; - module->socket_address.sin_addr.s_addr = htonl(INADDR_ANY); - module->socket_address.sin_port = htons((unsigned short)module->port); - rc = bind(socket_descriptor, (struct sockaddr *)&module->socket_address, sizeof(module->socket_address)); - if (unlikely(rc < 0)) goto err_bind_socket; - - /* Listen to the interface */ - rc = listen(socket_descriptor, MODULE_MAX_PENDING_CLIENT_REQUESTS); - if (unlikely(rc < 0)) goto err_listen; - - - /* Set the socket descriptor and register with our global epoll instance to monitor for incoming HTTP - requests */ - rc = listener_thread_register_module(module); - if (unlikely(rc < 0)) goto err_add_to_epoll; - - rc = 0; -done: - return rc; -err_add_to_epoll: -err_listen: -err_bind_socket: - module->socket_descriptor = -1; -err_set_socket_option: - close(socket_descriptor); -err_create_socket: -err: - debuglog("Socket Error: %s", strerror(errno)); - rc = -1; - goto done; -} - -/*************************************** - * Public Methods - ***************************************/ - -/** - * Module Mega Teardown Function - * Closes the socket and dynamic library, and then frees the module - * Returns harmlessly if there are outstanding references - * - * TODO: Untested Functionality. Unsure if this will work. Also, what about the module database? Do we - * need to do any cleanup there? Issue #17 - * @param module - the module to teardown - */ -void -module_free(struct module *module) -{ - if (module == NULL) return; - - /* Do not free if we still have oustanding references */ - if (module->reference_count) return; - - close(module->socket_descriptor); - sledge_abi_symbols_deinit(&module->abi); - free(module); -} - static inline int module_init(struct module *module, char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, uint16_t port, uint32_t request_size, uint32_t response_size, uint8_t admissions_percentile, @@ -193,6 +106,93 @@ err: goto done; } +/*************************************** + * Public Methods + ***************************************/ + +/** + * Start the module as a server listening at module->port + * @param module + * @returns 0 on success, -1 on error + */ +int +module_listen(struct module *module) +{ + int rc; + + /* Allocate a new TCP/IP socket, setting it to be non-blocking */ + int socket_descriptor = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (unlikely(socket_descriptor < 0)) goto err_create_socket; + + /* Socket should never have returned on fd 0, 1, or 2 */ + assert(socket_descriptor != STDIN_FILENO); + assert(socket_descriptor != STDOUT_FILENO); + assert(socket_descriptor != STDERR_FILENO); + + /* Configure the socket to allow multiple sockets to bind to the same host and port */ + int optval = 1; + rc = setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); + if (unlikely(rc < 0)) goto err_set_socket_option; + optval = 1; + rc = setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); + if (unlikely(rc < 0)) goto err_set_socket_option; + + /* Bind name [all addresses]:[module->port] to socket */ + module->socket_descriptor = socket_descriptor; + module->socket_address.sin_family = AF_INET; + module->socket_address.sin_addr.s_addr = htonl(INADDR_ANY); + module->socket_address.sin_port = htons((unsigned short)module->port); + rc = bind(socket_descriptor, (struct sockaddr *)&module->socket_address, sizeof(module->socket_address)); + if (unlikely(rc < 0)) goto err_bind_socket; + + /* Listen to the interface */ + rc = listen(socket_descriptor, MODULE_MAX_PENDING_CLIENT_REQUESTS); + if (unlikely(rc < 0)) goto err_listen; + + + /* Set the socket descriptor and register with our global epoll instance to monitor for incoming HTTP + requests */ + rc = listener_thread_register_module(module); + if (unlikely(rc < 0)) goto err_add_to_epoll; + + rc = 0; +done: + return rc; +err_add_to_epoll: +err_listen: +err_bind_socket: + module->socket_descriptor = -1; +err_set_socket_option: + close(socket_descriptor); +err_create_socket: +err: + debuglog("Socket Error: %s", strerror(errno)); + rc = -1; + goto done; +} + +/** + * Module Mega Teardown Function + * Closes the socket and dynamic library, and then frees the module + * Returns harmlessly if there are outstanding references + * + * TODO: Untested Functionality. Unsure if this will work. Also, what about the module database? Do we + * need to do any cleanup there? Issue #17 + * @param module - the module to teardown + */ +void +module_free(struct module *module) +{ + if (module == NULL) return; + + /* Do not free if we still have oustanding references */ + if (module->reference_count) return; + + close(module->socket_descriptor); + sledge_abi_symbols_deinit(&module->abi); + free(module); +} + /** * Module Contructor * Creates a new module, invokes initialize_tables to initialize the indirect table, and adds it to the module DB From 1457f6f2a8f7588fd7be277dc53fbbad86e1749e Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Thu, 14 Apr 2022 17:23:42 -0400 Subject: [PATCH 09/17] refactor: init with module_config --- runtime/include/json.h | 27 +--------- runtime/include/module.h | 5 +- runtime/include/module_config.h | 40 +++++++++++++++ runtime/include/module_database.h | 1 + runtime/src/main.c | 24 +++++---- runtime/src/module.c | 85 +++++++++++++++++-------------- runtime/src/module_database.c | 15 ++++++ 7 files changed, 120 insertions(+), 77 deletions(-) create mode 100644 runtime/include/module_config.h diff --git a/runtime/include/json.h b/runtime/include/json.h index 0bc0124..62f6e6a 100644 --- a/runtime/include/json.h +++ b/runtime/include/json.h @@ -12,36 +12,11 @@ #include "runtime.h" #include "http.h" #include "module.h" +#include "module_config.h" static const int JSON_MAX_ELEMENT_COUNT = 16; static const int JSON_MAX_ELEMENT_SIZE = 1024; -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 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("expected_execution_us: %u\n", config->expected_execution_us); - printf("admissions_percentile: %u\n", config->admissions_percentile); - 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); -} - static inline char * jsmn_type(jsmntype_t type) { diff --git a/runtime/include/module.h b/runtime/include/module.h index 4d0477e..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" @@ -242,7 +243,5 @@ module_free_linear_memory(struct module *module, struct wasm_memory *memory) *******************************/ void module_free(struct module *module); -struct module *module_alloc(char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, uint16_t port, - uint32_t request_size, uint32_t response_size, uint8_t admissions_percentile, - uint32_t expected_execution_us, char *response_content_type); +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..88e85f7 --- /dev/null +++ b/runtime/include/module_config.h @@ -0,0 +1,40 @@ +#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_free(struct module_config *config) +{ + free(config->name); + free(config->path); + free(config->http_resp_content_type); + free(config); +} + +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/src/main.c b/runtime/src/main.c index 603b1f5..d6f05f7 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -20,6 +20,7 @@ #include "debuglog.h" #include "listener_thread.h" #include "module.h" +#include "module_database.h" #include "panic.h" #include "runtime.h" #include "sandbox_types.h" @@ -465,21 +466,24 @@ main(int argc, char **argv) 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].name, - module_config_vec[module_idx].path, 0, - module_config_vec[module_idx].relative_deadline_us, - module_config_vec[module_idx].port, - module_config_vec[module_idx].http_req_size, - module_config_vec[module_idx].http_resp_size, - module_config_vec[module_idx].admissions_percentile, - module_config_vec[module_idx].expected_execution_us, - module_config_vec[module_idx].http_resp_content_type); + 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 */ - int rc = module_listen(module); + rc = module_listen(module); if (rc < 0) exit(-1); } + for (int module_idx = 0; module_idx < module_config_vec_len; module_idx++) { + module_config_free(&module_config_vec[module_idx]); + } + for (int i = 0; i < runtime_worker_threads_count; i++) { int ret = pthread_join(runtime_worker_threads[i], NULL); if (ret) { diff --git a/runtime/src/module.c b/runtime/src/module.c index 04f4534..201f36d 100644 --- a/runtime/src/module.c +++ b/runtime/src/module.c @@ -21,39 +21,50 @@ ************************/ static inline int -module_init(struct module *module, char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, - uint16_t port, uint32_t request_size, uint32_t response_size, uint8_t admissions_percentile, - uint32_t expected_execution_us, char *response_content_type) +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); - /* Validate presence of required fields */ - if (strlen(name) == 0) panic("name field is required\n"); - if (strlen(path) == 0) panic("path field is required\n"); - if (port == 0) panic("port field is required\n"); + uint32_t stack_size = 0; - if (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, relative_deadline_us); + /* 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 (request_size > RUNTIME_HTTP_REQUEST_SIZE_MAX) + 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, - request_size); + config->http_req_size); - if (response_size > RUNTIME_HTTP_RESPONSE_SIZE_MAX) + 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, - response_size); + 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 (expected_execution_us == 0) panic("expected-execution-us is required\n"); - if (relative_deadline_us == 0) panic("relative_deadline_us is required\n"); + 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 (admissions_percentile > 99 || admissions_percentile < 50) - panic("admissions-percentile must be > 50 and <= 99 but was %u\n", admissions_percentile); + 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 = relative_deadline_us / expected_execution_us; + 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 " @@ -61,41 +72,42 @@ module_init(struct module *module, char *name, char *path, uint32_t stack_size, 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"); + 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, path); + rc = sledge_abi_symbols_init(&module->abi, config->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); - strncpy(module->response_content_type, response_content_type, HTTP_MAX_HEADER_VALUE_LENGTH); + 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 = port; + module->port = config->port; /* Deadlines */ - module->relative_deadline_us = relative_deadline_us; + 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)relative_deadline_us * runtime_processor_speed_MHz; + module->relative_deadline = (uint64_t)config->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, + 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 (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); + 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); @@ -207,9 +219,7 @@ module_free(struct module *module) */ struct module * -module_alloc(char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, uint16_t port, - uint32_t request_size, uint32_t response_size, uint8_t admissions_percentile, - uint32_t expected_execution_us, char *response_content_type) +module_alloc(struct module_config *config) { struct module *module = (struct module *)calloc(1, sizeof(struct module)); if (!module) { @@ -217,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, response_content_type); + int rc = module_init(module, config); if (rc < 0) goto init_err; 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; +} From c9fc627375a3efce6cce2e1f8ed11a597bc3363b Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Thu, 14 Apr 2022 17:33:36 -0400 Subject: [PATCH 10/17] fix: double free --- runtime/include/module_config.h | 3 +-- runtime/src/main.c | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/include/module_config.h b/runtime/include/module_config.h index 88e85f7..0adab05 100644 --- a/runtime/include/module_config.h +++ b/runtime/include/module_config.h @@ -17,12 +17,11 @@ struct module_config { }; static inline void -module_config_free(struct module_config *config) +module_config_deinit(struct module_config *config) { free(config->name); free(config->path); free(config->http_resp_content_type); - free(config); } static inline void diff --git a/runtime/src/main.c b/runtime/src/main.c index d6f05f7..56b053b 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -481,8 +481,9 @@ main(int argc, char **argv) } for (int module_idx = 0; module_idx < module_config_vec_len; module_idx++) { - module_config_free(&module_config_vec[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); From 4a301588bbb27166fc423b0d9cbd6532bbc0a520 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Thu, 14 Apr 2022 17:42:16 -0400 Subject: [PATCH 11/17] fix: json_buf memory leak --- runtime/src/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/src/main.c b/runtime/src/main.c index 56b053b..5c9c830 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -463,6 +463,7 @@ main(int argc, char **argv) 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 */ From 252ed746ed1d301b262b939a292e8069e8ccccf0 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Wed, 20 Apr 2022 13:50:58 -0400 Subject: [PATCH 12/17] fix: has_valid_size use expected_size --- runtime/include/json.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/include/json.h b/runtime/include/json.h index 62f6e6a..38d3a05 100644 --- a/runtime/include/json.h +++ b/runtime/include/json.h @@ -39,8 +39,8 @@ jsmn_type(jsmntype_t type) static inline bool has_valid_size(jsmntok_t tok, char *key, int expected_size) { - if (tok.size != 1) { - fprintf(stderr, "%s does not have a value\n", key); + if (tok.size != expected_size) { + fprintf(stderr, "%s has size %d, expected %d\n", key, tok.size, expected_size); return false; } From ed048e1219b2afd4c94da2827c69df35ba67532f Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Wed, 20 Apr 2022 13:57:17 -0400 Subject: [PATCH 13/17] refactor: Remove extraneous assertion --- runtime/src/main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime/src/main.c b/runtime/src/main.c index 5c9c830..35bd9c3 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -394,7 +394,6 @@ load_file_into_buffer(const char *file_name, char **file_buffer) 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) { From 8f2f18722d4719046e5c73bf471402330a4bb6a8 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Wed, 20 Apr 2022 14:05:27 -0400 Subject: [PATCH 14/17] refactor: change load_file_into_buffer err --- runtime/src/main.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/runtime/src/main.c b/runtime/src/main.c index 35bd9c3..a0293ce 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -351,7 +351,7 @@ check_versions() * 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 -1 in case of error; + * @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) @@ -386,7 +386,7 @@ load_file_into_buffer(const char *file_name, char **file_buffer) } /* Read the file into the buffer and check that the buffer size equals the file size */ - ssize_t total_chars_read = fread(*file_buffer, sizeof(char), stat_buffer.st_size, module_file); + 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 @@ -414,7 +414,7 @@ stat_buffer_alloc_err: if (fclose(module_file) == EOF) panic("Failed to close file\n"); } err: - return (ssize_t)-1; + return 0; } int @@ -455,8 +455,8 @@ main(int argc, char **argv) #endif const char *json_path = argv[1]; char *json_buf = NULL; - ssize_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); + 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; From e6b41196526da85f73862239e9bb1029c938f9a8 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Wed, 20 Apr 2022 14:21:15 -0400 Subject: [PATCH 15/17] refactor: JSON_TOKENS_CAPACITY --- runtime/include/json.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/runtime/include/json.h b/runtime/include/json.h index 38d3a05..e45cc89 100644 --- a/runtime/include/json.h +++ b/runtime/include/json.h @@ -14,8 +14,7 @@ #include "module.h" #include "module_config.h" -static const int JSON_MAX_ELEMENT_COUNT = 16; -static const int JSON_MAX_ELEMENT_SIZE = 1024; +#define JSON_TOKENS_CAPACITY 1024 static inline char * jsmn_type(jsmntype_t type) @@ -145,7 +144,7 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m assert(json_buf_size > 0); assert(module_config_vec != NULL); - jsmntok_t tokens[JSON_MAX_ELEMENT_SIZE * JSON_MAX_ELEMENT_COUNT]; + jsmntok_t tokens[JSON_TOKENS_CAPACITY]; int module_config_vec_len = 0; @@ -154,8 +153,7 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m jsmn_init(&module_parser); /* Use Jasmine to parse the JSON */ - int total_tokens = jsmn_parse(&module_parser, json_buf, json_buf_size, tokens, - sizeof(tokens) / sizeof(tokens[0])); + 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); From 012c67457fd29e55ad1365bf891525670ca2067e Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Wed, 20 Apr 2022 14:27:00 -0400 Subject: [PATCH 16/17] refactor: Resize back to original size --- runtime/include/json.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/include/json.h b/runtime/include/json.h index e45cc89..dbde09f 100644 --- a/runtime/include/json.h +++ b/runtime/include/json.h @@ -14,7 +14,7 @@ #include "module.h" #include "module_config.h" -#define JSON_TOKENS_CAPACITY 1024 +#define JSON_TOKENS_CAPACITY 16384 static inline char * jsmn_type(jsmntype_t type) From 64d67185f6c6e08bfc40dd4412fc0d52a83abaaa Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Wed, 20 Apr 2022 14:57:58 -0400 Subject: [PATCH 17/17] refactor: keys global const array --- runtime/include/json.h | 54 ++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/runtime/include/json.h b/runtime/include/json.h index dbde09f..064c883 100644 --- a/runtime/include/json.h +++ b/runtime/include/json.h @@ -16,6 +16,30 @@ #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) { @@ -208,53 +232,53 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **m /* Advance to Value */ i++; - if (strcmp(key, "name") == 0) { + 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, "path") == 0) { + } 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, "port") == 0) { + } 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, "port", + 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, "expected-execution-us") == 0) { + } 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, "expected-execution-us", + 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, "admissions-percentile") == 0) { + } 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, "admissions-percentile", + 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, "relative-deadline-us") == 0) { + } 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, "relative-deadline-us", + 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, "http-req-size") == 0) { + } 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, "http-req-size", + 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, "http-resp-size") == 0) { + } 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, "http-resp-size", + 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, "http-resp-content-type") == 0) { + } 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 =