Merge branch 'master' of github.com:gwsystems/sledge-serverless-framework into http-parsing-listener

master
Sean McBride 3 years ago
commit eb2ba85760

@ -110,7 +110,9 @@
"current_wasm_module_instance.h": "c",
"wasm_memory.h": "c",
"sledge_abi.h": "c",
"vec.h": "c"
"vec.h": "c",
"module_database.h": "c",
"perf_window_t.h": "c"
},
"files.exclude": {
"**/.git": true,

@ -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);

@ -0,0 +1,306 @@
#pragma once
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <jsmn.h>
#include "runtime.h"
#include "http.h"
#include "module.h"
#include "module_config.h"
#define JSON_TOKENS_CAPACITY 16384
enum
{
module_name,
module_path,
module_port,
module_expected_execution_us,
module_admissions_percentile,
module_relative_deadline_us,
module_http_req_size,
module_http_resp_size,
module_http_resp_content_type,
module_keys_len
};
static const char *module_keys[module_keys_len] = { "name",
"path",
"port",
"expected-execution-us",
"admissions-percentile",
"relative-deadline-us",
"http-req-size",
"http-resp-size",
"http-resp-content-type" };
static inline char *
jsmn_type(jsmntype_t type)
{
switch (type) {
case JSMN_UNDEFINED:
return "Undefined";
case JSMN_OBJECT:
return "Object";
case JSMN_ARRAY:
return "Array";
case JSMN_STRING:
return "String";
case JSMN_PRIMITIVE:
return "Primitive";
default:
return "Invalid";
}
}
static inline bool
has_valid_size(jsmntok_t tok, char *key, int expected_size)
{
if (tok.size != expected_size) {
fprintf(stderr, "%s has size %d, expected %d\n", key, tok.size, expected_size);
return false;
}
return true;
}
static inline bool
has_valid_type(jsmntok_t tok, char *key, jsmntype_t expected_type)
{
if (tok.type != expected_type) {
fprintf(stderr, "The value of the key %s should be a %s, was a %s\n", key, jsmn_type(expected_type),
jsmn_type(tok.type));
return false;
}
return true;
}
static inline bool
is_nonempty_string(jsmntok_t tok, char *key)
{
if (!has_valid_type(tok, key, JSMN_STRING)) return false;
if (tok.end - tok.start < 1) {
fprintf(stderr, "The value of the key %s was an empty string\n", key);
return false;
}
return true;
}
static inline bool
is_valid_key(jsmntok_t tok)
{
if (tok.type != JSMN_STRING) {
fprintf(stderr, "Expected token to be a key with a type of string, was a %s\n", jsmn_type(tok.type));
return false;
}
if (tok.end - tok.start < 1) {
fprintf(stderr, "Key was an empty string\n");
return false;
}
return true;
}
static inline int
parse_uint8_t(jsmntok_t tok, const char *json_buf, const char *key, uint8_t *ret)
{
char *end = NULL;
intmax_t temp = strtoimax(&json_buf[tok.start], &end, 10);
if (end != &json_buf[tok.end] || temp < 0 || temp > UINT8_MAX) {
fprintf(stderr, "Unable to parse uint8_t for key %s\n", key);
return -1;
}
*ret = (uint8_t)temp;
return 0;
}
static inline int
parse_uint16_t(jsmntok_t tok, const char *json_buf, const char *key, uint16_t *ret)
{
char *end = NULL;
intmax_t temp = strtoimax(&json_buf[tok.start], &end, 10);
if (end != &json_buf[tok.end] || temp < 0 || temp > UINT16_MAX) {
fprintf(stderr, "Unable to parse uint16_t for key %s\n", key);
return -1;
}
*ret = (uint16_t)temp;
return 0;
}
static inline int
parse_uint32_t(jsmntok_t tok, const char *json_buf, const char *key, uint32_t *ret)
{
char *end = NULL;
uintmax_t temp = strtoimax(&json_buf[tok.start], &end, 10);
if (end != &json_buf[tok.end] || temp < 0 || temp > UINT32_MAX) {
fprintf(stderr, "Unable to parse uint32_t for key %s\n", key);
return -1;
}
*ret = (uint32_t)temp;
return 0;
}
/**
* Parses a JSON file into an array of module configs
* @param file_name The path of the JSON file
* @return module_config_vec_len on success. -1 on Error
*/
static inline int
parse_json(const char *json_buf, ssize_t json_buf_size, struct module_config **module_config_vec)
{
assert(json_buf != NULL);
assert(json_buf_size > 0);
assert(module_config_vec != NULL);
jsmntok_t tokens[JSON_TOKENS_CAPACITY];
int module_config_vec_len = 0;
/* Initialize the Jasmine Parser and an array to hold the tokens */
jsmn_parser module_parser;
jsmn_init(&module_parser);
/* Use Jasmine to parse the JSON */
int total_tokens = jsmn_parse(&module_parser, json_buf, json_buf_size, tokens, JSON_TOKENS_CAPACITY);
if (total_tokens < 0) {
if (total_tokens == JSMN_ERROR_INVAL) {
fprintf(stderr, "Error parsing %s: bad token, JSON string is corrupted\n", json_buf);
} else if (total_tokens == JSMN_ERROR_PART) {
fprintf(stderr, "Error parsing %s: JSON string is too short, expecting more JSON data\n",
json_buf);
} else if (total_tokens == JSMN_ERROR_NOMEM) {
/*
* According to the README at https://github.com/zserge/jsmn, this is a potentially recoverable
* error. More tokens can be allocated and jsmn_parse can be re-invoked.
*/
fprintf(stderr, "Error parsing %s: Not enough tokens, JSON string is too large\n", json_buf);
}
goto err;
}
if (tokens[0].type != JSMN_ARRAY) {
fprintf(stderr, "Outermost Config should be a JSON array, was a JSON %s\n", jsmn_type(tokens[0].type));
goto err;
}
module_config_vec_len = tokens[0].size;
if (module_config_vec_len == 0) {
fprintf(stderr, "Config is an empty JSON array\n");
goto err;
}
*module_config_vec = (struct module_config *)calloc(module_config_vec_len, sizeof(struct module_config));
int module_idx = -1;
int module_fields_remaining = 0;
for (int i = 1; i < total_tokens; i++) {
char key[32] = { 0 };
/* Assumption: Objects are never used within a module_config. This likely will not be true in the
* future due to routes or multiple entrypoints */
if (tokens[i].type == JSMN_OBJECT) {
assert(module_fields_remaining == 0);
module_fields_remaining = tokens[i].size;
module_idx++;
} else {
/* Inside Object */
/* Validate that key is non-emptry string */
if (!is_valid_key(tokens[i])) goto json_parse_err;
sprintf(key, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start);
/* Validate that key has a value */
if (!has_valid_size(tokens[i], key, 1)) goto json_parse_err;
/* Advance to Value */
i++;
if (strcmp(key, module_keys[module_name]) == 0) {
if (!is_nonempty_string(tokens[i], key)) goto json_parse_err;
(*module_config_vec)[module_idx].name = strndup(json_buf + tokens[i].start,
tokens[i].end - tokens[i].start);
} else if (strcmp(key, module_keys[module_path]) == 0) {
if (!is_nonempty_string(tokens[i], key)) goto json_parse_err;
(*module_config_vec)[module_idx].path = strndup(json_buf + tokens[i].start,
tokens[i].end - tokens[i].start);
} else if (strcmp(key, module_keys[module_port]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err;
int rc = parse_uint16_t(tokens[i], json_buf, module_keys[module_port],
&(*module_config_vec)[module_idx].port);
if (rc < 0) goto json_parse_err;
} else if (strcmp(key, module_keys[module_expected_execution_us]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err;
int rc = parse_uint32_t(tokens[i], json_buf, module_keys[module_expected_execution_us],
&(*module_config_vec)[module_idx].expected_execution_us);
if (rc < 0) goto json_parse_err;
} else if (strcmp(key, module_keys[module_admissions_percentile]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err;
int rc = parse_uint8_t(tokens[i], json_buf, module_keys[module_admissions_percentile],
&(*module_config_vec)[module_idx].admissions_percentile);
if (rc < 0) goto json_parse_err;
} else if (strcmp(key, module_keys[module_relative_deadline_us]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err;
int rc = parse_uint32_t(tokens[i], json_buf, module_keys[module_relative_deadline_us],
&(*module_config_vec)[module_idx].relative_deadline_us);
if (rc < 0) goto json_parse_err;
} else if (strcmp(key, module_keys[module_http_req_size]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err;
int rc = parse_uint32_t(tokens[i], json_buf, module_keys[module_http_req_size],
&(*module_config_vec)[module_idx].http_req_size);
if (rc < 0) goto json_parse_err;
} else if (strcmp(key, module_keys[module_http_resp_size]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE)) goto json_parse_err;
int rc = parse_uint32_t(tokens[i], json_buf, module_keys[module_http_resp_size],
&(*module_config_vec)[module_idx].http_resp_size);
if (rc < 0) goto json_parse_err;
} else if (strcmp(key, module_keys[module_http_resp_content_type]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_STRING)) goto json_parse_err;
(*module_config_vec)[module_idx].http_resp_content_type =
strndup(json_buf + tokens[i].start, tokens[i].end - tokens[i].start);
} else {
fprintf(stderr, "%s is not a valid key\n", key);
goto json_parse_err;
}
module_fields_remaining--;
}
}
#ifdef LOG_MODULE_LOADING
debuglog("Loaded %d module%s!\n", module_count, module_count > 1 ? "s" : "");
#endif
done:
return module_config_vec_len;
json_parse_err:
free(*module_config_vec);
err:
fprintf(stderr, "JSON:\n%s\n", json_buf);
module_config_vec_len = -1;
goto done;
}

@ -9,6 +9,7 @@
#include "admissions_info.h"
#include "current_wasm_module_instance.h"
#include "http.h"
#include "module_config.h"
#include "panic.h"
#include "pool.h"
#include "sledge_abi_symbols.h"
@ -57,7 +58,7 @@ struct module {
char path[MODULE_MAX_PATH_LENGTH];
uint32_t stack_size; /* a specification? */
uint32_t relative_deadline_us;
int port;
uint16_t port;
struct admissions_info admissions_info;
uint64_t relative_deadline; /* cycles */
@ -242,6 +243,5 @@ module_free_linear_memory(struct module *module, struct wasm_memory *memory)
*******************************/
void module_free(struct module *module);
struct module *module_alloc(char *mod_name, char *mod_path, uint32_t stack_sz, uint32_t relative_deadline_us, int port,
int req_sz, int resp_sz, int admissions_percentile, uint32_t expected_execution_us);
int module_alloc_from_json(char *filename);
struct module *module_alloc(struct module_config *config);
int module_listen(struct module *module);

@ -0,0 +1,39 @@
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
struct module_config {
char *name;
char *path;
uint16_t port;
uint8_t admissions_percentile;
uint32_t expected_execution_us;
uint32_t relative_deadline_us;
uint32_t http_req_size;
uint32_t http_resp_size;
char *http_resp_content_type;
};
static inline void
module_config_deinit(struct module_config *config)
{
free(config->name);
free(config->path);
free(config->http_resp_content_type);
}
static inline void
print_module_config(struct module_config *config)
{
printf("Name: %s\n", config->name);
printf("Path: %s\n", config->path);
printf("Port: %u\n", config->port);
printf("admissions_percentile: %u\n", config->admissions_percentile);
printf("expected_execution_us: %u\n", config->expected_execution_us);
printf("relative_deadline_us: %u\n", config->relative_deadline_us);
printf("http_req_size: %u\n", config->http_req_size);
printf("http_resp_size: %u\n", config->http_resp_size);
printf("http_resp_content_type: %s\n", config->http_resp_content_type);
}

@ -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);

@ -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;
}
/**

@ -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

@ -5,6 +5,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <sched.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
@ -14,10 +15,12 @@
#include <sys/fcntl.h>
#endif
#include "json.h"
#include "pretty_print.h"
#include "debuglog.h"
#include "listener_thread.h"
#include "module.h"
#include "module_database.h"
#include "panic.h"
#include "runtime.h"
#include "sandbox_types.h"
@ -344,6 +347,76 @@ check_versions()
static_assert(__linux__ == 1, "Requires epoll, a Linux-only feature");
}
/**
* Allocates a buffer in memory containing the entire contents of the file provided
* @param file_name file to load into memory
* @param ret_ptr Pointer to set with address of buffer this function allocates. The caller must free this!
* @return size of the allocated buffer or 0 in case of error;
*/
static inline size_t
load_file_into_buffer(const char *file_name, char **file_buffer)
{
/* Use stat to get file attributes and make sure file is present and not empty */
struct stat stat_buffer;
if (stat(file_name, &stat_buffer) < 0) {
fprintf(stderr, "Attempt to stat %s failed: %s\n", file_name, strerror(errno));
goto err;
}
if (stat_buffer.st_size == 0) {
fprintf(stderr, "File %s is unexpectedly empty\n", file_name);
goto err;
}
if (!S_ISREG(stat_buffer.st_mode)) {
fprintf(stderr, "File %s is not a regular file\n", file_name);
goto err;
}
/* Open the file */
FILE *module_file = fopen(file_name, "r");
if (!module_file) {
fprintf(stderr, "Attempt to open %s failed: %s\n", file_name, strerror(errno));
goto err;
}
/* Initialize a Buffer */
*file_buffer = calloc(1, stat_buffer.st_size);
if (*file_buffer == NULL) {
fprintf(stderr, "Attempt to allocate file buffer failed: %s\n", strerror(errno));
goto stat_buffer_alloc_err;
}
/* Read the file into the buffer and check that the buffer size equals the file size */
size_t total_chars_read = fread(*file_buffer, sizeof(char), stat_buffer.st_size, module_file);
#ifdef LOG_MODULE_LOADING
debuglog("size read: %d content: %s\n", total_chars_read, *file_buffer);
#endif
if (total_chars_read != stat_buffer.st_size) {
fprintf(stderr, "Attempt to read %s into buffer failed: %s\n", file_name, strerror(errno));
goto fread_err;
}
/* Close the file */
if (fclose(module_file) == EOF) {
fprintf(stderr, "Attempt to close buffer containing %s failed: %s\n", file_name, strerror(errno));
goto fclose_err;
};
module_file = NULL;
return total_chars_read;
fclose_err:
/* We will retry fclose when we fall through into stat_buffer_alloc_err */
fread_err:
free(*file_buffer);
stat_buffer_alloc_err:
// Check to ensure we haven't already close this
if (module_file != NULL) {
if (fclose(module_file) == EOF) panic("Failed to close file\n");
}
err:
return 0;
}
int
main(int argc, char **argv)
{
@ -380,8 +453,37 @@ main(int argc, char **argv)
#ifdef LOG_MODULE_LOADING
debuglog("Parsing modules file [%s]\n", argv[1]);
#endif
if (module_alloc_from_json(argv[1])) panic("failed to initialize module(s) defined in %s\n", argv[1]);
const char *json_path = argv[1];
char *json_buf = NULL;
size_t json_buf_len = load_file_into_buffer(json_path, &json_buf);
if (unlikely(json_buf_len == 0)) panic("failed to initialize module(s) defined in %s\n", json_path);
struct module_config *module_config_vec;
int module_config_vec_len = parse_json(json_buf, json_buf_len, &module_config_vec);
if (module_config_vec_len < 0) { exit(-1); }
free(json_buf);
for (int module_idx = 0; module_idx < module_config_vec_len; module_idx++) {
/* Automatically calls listen */
struct module *module = module_alloc(&module_config_vec[module_idx]);
if (unlikely(module == NULL)) panic("failed to initialize module(s) defined in %s\n", json_path);
int rc = module_database_add(module);
if (rc < 0) {
panic("Module database full!\n");
exit(-1);
}
/* Start listening for requests */
rc = module_listen(module);
if (rc < 0) exit(-1);
}
for (int module_idx = 0; module_idx < module_config_vec_len; module_idx++) {
module_config_deinit(&module_config_vec[module_idx]);
}
free(module_config_vec);
for (int i = 0; i < runtime_worker_threads_count; i++) {
int ret = pthread_join(runtime_worker_threads[i], NULL);

@ -1,10 +1,8 @@
#include <assert.h>
#include <dlfcn.h>
#include <jsmn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "debuglog.h"
@ -18,19 +16,118 @@
#include "scheduler.h"
#include "wasm_table.h"
const int JSON_MAX_ELEMENT_COUNT = 16;
const int JSON_MAX_ELEMENT_SIZE = 1024;
/*************************
* Private Static Inline *
************************/
static inline int
module_init(struct module *module, struct module_config *config)
{
assert(module != NULL);
assert(config != NULL);
assert(config->name != NULL);
assert(config->path != NULL);
assert(config->http_resp_content_type != NULL);
uint32_t stack_size = 0;
/* Validate presence of required fields */
if (strlen(config->name) == 0) panic("name field is required\n");
if (strlen(config->path) == 0) panic("path field is required\n");
if (config->port == 0) panic("port field is required\n");
if (config->http_req_size > RUNTIME_HTTP_REQUEST_SIZE_MAX)
panic("request_size must be between 0 and %u, was %u\n", (uint32_t)RUNTIME_HTTP_REQUEST_SIZE_MAX,
config->http_req_size);
if (config->http_resp_size > RUNTIME_HTTP_RESPONSE_SIZE_MAX)
panic("response-size must be between 0 and %u, was %u\n", (uint32_t)RUNTIME_HTTP_RESPONSE_SIZE_MAX,
config->http_resp_size);
struct module *existing_module = module_database_find_by_name(config->name);
if (existing_module != NULL) panic("Module %s is already initialized\n", existing_module->name);
existing_module = module_database_find_by_port(config->port);
if (existing_module != NULL)
panic("Module %s is already configured with port %u\n", existing_module->name, config->port);
if (config->relative_deadline_us > (uint32_t)RUNTIME_RELATIVE_DEADLINE_US_MAX)
panic("Relative-deadline-us must be between 0 and %u, was %u\n",
(uint32_t)RUNTIME_RELATIVE_DEADLINE_US_MAX, config->relative_deadline_us);
#ifdef ADMISSIONS_CONTROL
/* expected-execution-us and relative-deadline-us are required in case of admissions control */
if (config->expected_execution_us == 0) panic("expected-execution-us is required\n");
if (config->relative_deadline_us == 0) panic("relative_deadline_us is required\n");
if (config->admissions_percentile > 99 || config->admissions_percentile < 50)
panic("admissions-percentile must be > 50 and <= 99 but was %u\n", config->admissions_percentile);
/* If the ratio is too big, admissions control is too coarse */
uint32_t ratio = config->relative_deadline_us / config->expected_execution_us;
if (ratio > ADMISSIONS_CONTROL_GRANULARITY)
panic("Ratio of Deadline to Execution time cannot exceed admissions control "
"granularity of "
"%d\n",
ADMISSIONS_CONTROL_GRANULARITY);
#else
/* relative-deadline-us is required if scheduler is EDF */
if (scheduler == SCHEDULER_EDF && config->relative_deadline_us == 0)
panic("relative_deadline_us is required\n");
#endif
int rc = 0;
atomic_init(&module->reference_count, 0);
rc = sledge_abi_symbols_init(&module->abi, config->path);
if (rc != 0) goto err;
/* Set fields in the module struct */
strncpy(module->name, config->name, MODULE_MAX_NAME_LENGTH);
strncpy(module->path, config->path, MODULE_MAX_PATH_LENGTH);
strncpy(module->response_content_type, config->http_resp_content_type, HTTP_MAX_HEADER_VALUE_LENGTH);
module->stack_size = ((uint32_t)(round_up_to_page(stack_size == 0 ? WASM_STACK_SIZE : stack_size)));
module->socket_descriptor = -1;
module->port = config->port;
/* Deadlines */
module->relative_deadline_us = config->relative_deadline_us;
/* This can overflow a uint32_t, so be sure to cast appropriately */
module->relative_deadline = (uint64_t)config->relative_deadline_us * runtime_processor_speed_MHz;
/* Admissions Control */
uint64_t expected_execution = (uint64_t)config->expected_execution_us * runtime_processor_speed_MHz;
admissions_info_initialize(&module->admissions_info, config->admissions_percentile, expected_execution,
module->relative_deadline);
/* Request Response Buffer */
if (config->http_req_size == 0) config->http_req_size = MODULE_DEFAULT_REQUEST_RESPONSE_SIZE;
if (config->http_resp_size == 0) config->http_resp_size = MODULE_DEFAULT_REQUEST_RESPONSE_SIZE;
module->max_request_size = round_up_to_page(config->http_req_size);
module->max_response_size = round_up_to_page(config->http_resp_size);
module_alloc_table(module);
module_initialize_pools(module);
done:
return rc;
err:
rc = -1;
goto done;
}
/***************************************
* Public Methods
***************************************/
/**
* Start the module as a server listening at module->port
* @param module
* @returns 0 on success, -1 on error
*/
static inline int
int
module_listen(struct module *module)
{
int rc;
@ -86,24 +183,6 @@ err:
goto done;
}
/**
* Sets the HTTP Response Content type on a module
* @param module
* @param response_content_type
*/
static inline void
module_set_http_info(struct module *module, char response_content_type[])
{
assert(module);
strcpy(module->response_content_type, response_content_type);
}
/***************************************
* Public Methods
***************************************/
/**
* Module Mega Teardown Function
* Closes the socket and dynamic library, and then frees the module
@ -126,62 +205,9 @@ module_free(struct module *module)
free(module);
}
static inline int
module_init(struct module *module, char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, int port,
int request_size, int response_size, int admissions_percentile, uint32_t expected_execution_us)
{
assert(module != NULL);
int rc = 0;
atomic_init(&module->reference_count, 0);
rc = sledge_abi_symbols_init(&module->abi, path);
if (rc != 0) goto err;
/* Set fields in the module struct */
strncpy(module->name, name, MODULE_MAX_NAME_LENGTH);
strncpy(module->path, path, MODULE_MAX_PATH_LENGTH);
module->stack_size = ((uint32_t)(round_up_to_page(stack_size == 0 ? WASM_STACK_SIZE : stack_size)));
module->socket_descriptor = -1;
module->port = port;
/* Deadlines */
module->relative_deadline_us = relative_deadline_us;
/* This can overflow a uint32_t, so be sure to cast appropriately */
module->relative_deadline = (uint64_t)relative_deadline_us * runtime_processor_speed_MHz;
/* Admissions Control */
uint64_t expected_execution = (uint64_t)expected_execution_us * runtime_processor_speed_MHz;
admissions_info_initialize(&module->admissions_info, admissions_percentile, expected_execution,
module->relative_deadline);
/* Request Response Buffer */
if (request_size == 0) request_size = MODULE_DEFAULT_REQUEST_RESPONSE_SIZE;
if (response_size == 0) response_size = MODULE_DEFAULT_REQUEST_RESPONSE_SIZE;
module->max_request_size = round_up_to_page(request_size);
module->max_response_size = round_up_to_page(response_size);
module_alloc_table(module);
module_initialize_pools(module);
/* Start listening for requests */
rc = module_listen(module);
if (rc < 0) goto err;
done:
return rc;
err:
rc = -1;
goto done;
}
/**
* Module Contructor
* Creates a new module, invokes initialize_tables to initialize the indirect table, adds it to the module DB, and
*starts listening for HTTP Requests
* Creates a new module, invokes initialize_tables to initialize the indirect table, and adds it to the module DB
*
* @param name
* @param path
@ -193,8 +219,7 @@ err:
*/
struct module *
module_alloc(char *name, char *path, uint32_t stack_size, uint32_t relative_deadline_us, int port, int request_size,
int response_size, int admissions_percentile, uint32_t expected_execution_us)
module_alloc(struct module_config *config)
{
struct module *module = (struct module *)calloc(1, sizeof(struct module));
if (!module) {
@ -202,8 +227,7 @@ module_alloc(char *name, char *path, uint32_t stack_size, uint32_t relative_dead
goto err;
};
int rc = module_init(module, name, path, stack_size, relative_deadline_us, port, request_size, response_size,
admissions_percentile, expected_execution_us);
int rc = module_init(module, config);
if (rc < 0) goto init_err;
done:
@ -215,233 +239,3 @@ err:
module = NULL;
goto done;
}
/**
* Parses a JSON file and allocates one or more new modules
* @param file_name The path of the JSON file
* @return RC 0 on Success. -1 on Error
*/
int
module_alloc_from_json(char *file_name)
{
assert(file_name != NULL);
int return_code = -1;
jsmntok_t tokens[JSON_MAX_ELEMENT_SIZE * JSON_MAX_ELEMENT_COUNT];
/* Use stat to get file attributes and make sure file is present and not empty */
struct stat stat_buffer;
if (stat(file_name, &stat_buffer) < 0) {
fprintf(stderr, "Attempt to stat %s failed: %s\n", file_name, strerror(errno));
goto err;
}
if (stat_buffer.st_size == 0) {
fprintf(stderr, "File %s is unexpectedly empty\n", file_name);
goto err;
}
if (!S_ISREG(stat_buffer.st_mode)) {
fprintf(stderr, "File %s is not a regular file\n", file_name);
goto err;
}
/* Open the file */
FILE *module_file = fopen(file_name, "r");
if (!module_file) {
fprintf(stderr, "Attempt to open %s failed: %s\n", file_name, strerror(errno));
goto err;
}
/* Initialize a Buffer */
char *file_buffer = calloc(1, stat_buffer.st_size);
if (file_buffer == NULL) {
fprintf(stderr, "Attempt to allocate file buffer failed: %s\n", strerror(errno));
goto stat_buffer_alloc_err;
}
/* Read the file into the buffer and check that the buffer size equals the file size */
int total_chars_read = fread(file_buffer, sizeof(char), stat_buffer.st_size, module_file);
#ifdef LOG_MODULE_LOADING
debuglog("size read: %d content: %s\n", total_chars_read, file_buffer);
#endif
if (total_chars_read != stat_buffer.st_size) {
fprintf(stderr, "Attempt to read %s into buffer failed: %s\n", file_name, strerror(errno));
goto fread_err;
}
assert(total_chars_read > 0);
/* Close the file */
if (fclose(module_file) == EOF) {
fprintf(stderr, "Attempt to close buffer containing %s failed: %s\n", file_name, strerror(errno));
goto fclose_err;
};
module_file = NULL;
/* Initialize the Jasmine Parser and an array to hold the tokens */
jsmn_parser module_parser;
jsmn_init(&module_parser);
/* Use Jasmine to parse the JSON */
int total_tokens = jsmn_parse(&module_parser, file_buffer, total_chars_read, tokens,
sizeof(tokens) / sizeof(tokens[0]));
if (total_tokens < 0) {
if (total_tokens == JSMN_ERROR_INVAL) {
fprintf(stderr, "Error parsing %s: bad token, JSON string is corrupted\n", file_name);
} else if (total_tokens == JSMN_ERROR_PART) {
fprintf(stderr, "Error parsing %s: JSON string is too short, expecting more JSON data\n",
file_name);
} else if (total_tokens == JSMN_ERROR_NOMEM) {
/*
* According to the README at https://github.com/zserge/jsmn, this is a potentially recoverable
* error. More tokens can be allocated and jsmn_parse can be re-invoked.
*/
fprintf(stderr, "Error parsing %s: Not enough tokens, JSON string is too large\n", file_name);
}
goto json_parse_err;
}
int module_count = 0;
for (int i = 0; i < total_tokens; i++) {
/* If we have multiple objects, they should be wrapped in a JSON array */
if (tokens[i].type == JSMN_ARRAY) continue;
assert(tokens[i].type == JSMN_OBJECT);
char module_name[MODULE_MAX_NAME_LENGTH] = { 0 };
char module_path[MODULE_MAX_PATH_LENGTH] = { 0 };
int32_t request_size = 0;
int32_t response_size = 0;
uint32_t port = 0;
uint32_t relative_deadline_us = 0;
uint32_t expected_execution_us = 0;
int admissions_percentile = 50;
int j = 1;
int ntoks = 2 * tokens[i].size;
char response_content_type[HTTP_MAX_HEADER_VALUE_LENGTH] = { 0 };
for (; j < ntoks;) {
int ntks = 1;
char key[32] = { 0 };
char val[256] = { 0 };
sprintf(val, "%.*s", tokens[j + i + 1].end - tokens[j + i + 1].start,
file_buffer + tokens[j + i + 1].start);
sprintf(key, "%.*s", tokens[j + i].end - tokens[j + i].start,
file_buffer + tokens[j + i].start);
if (strlen(key) == 0) panic("Unexpected encountered empty key\n");
if (strlen(val) == 0) panic("%s field contained empty string\n", key);
if (strcmp(key, "name") == 0) {
// TODO: Currently, multiple modules can have identical names. Ports are the true unique
// identifiers. Consider enforcing unique names in future
strcpy(module_name, val);
} else if (strcmp(key, "path") == 0) {
// Invalid path will crash on dlopen
strcpy(module_path, val);
} else if (strcmp(key, "port") == 0) {
// Validate sane port
// If already taken, will error on bind call in module_listen
int buffer = atoi(val);
if (buffer < 0 || buffer > 65535)
panic("Expected port between 0 and 65535, saw %d\n", buffer);
port = buffer;
} else if (strcmp(key, "relative-deadline-us") == 0) {
int64_t buffer = strtoll(val, NULL, 10);
if (buffer < 0 || buffer > (int64_t)RUNTIME_RELATIVE_DEADLINE_US_MAX)
panic("Relative-deadline-us must be between 0 and %ld, was %ld\n",
(int64_t)RUNTIME_RELATIVE_DEADLINE_US_MAX, buffer);
relative_deadline_us = (uint32_t)buffer;
} else if (strcmp(key, "expected-execution-us") == 0) {
int64_t buffer = strtoll(val, NULL, 10);
if (buffer < 0 || buffer > (int64_t)RUNTIME_EXPECTED_EXECUTION_US_MAX)
panic("Relative-deadline-us must be between 0 and %ld, was %ld\n",
(int64_t)RUNTIME_EXPECTED_EXECUTION_US_MAX, buffer);
expected_execution_us = (uint32_t)buffer;
} else if (strcmp(key, "admissions-percentile") == 0) {
int32_t buffer = strtol(val, NULL, 10);
if (buffer > 99 || buffer < 50)
panic("admissions-percentile must be > 50 and <= 99 but was %d\n", buffer);
admissions_percentile = (int)buffer;
} else if (strcmp(key, "http-req-size") == 0) {
int64_t buffer = strtoll(val, NULL, 10);
if (buffer < 0 || buffer > RUNTIME_HTTP_REQUEST_SIZE_MAX)
panic("http-req-size must be between 0 and %ld, was %ld\n",
(int64_t)RUNTIME_HTTP_REQUEST_SIZE_MAX, buffer);
request_size = (int32_t)buffer;
} else if (strcmp(key, "http-resp-size") == 0) {
int64_t buffer = strtoll(val, NULL, 10);
if (buffer < 0 || buffer > RUNTIME_HTTP_REQUEST_SIZE_MAX)
panic("http-resp-size must be between 0 and %ld, was %ld\n",
(int64_t)RUNTIME_HTTP_REQUEST_SIZE_MAX, buffer);
response_size = (int32_t)buffer;
} else if (strcmp(key, "http-resp-content-type") == 0) {
if (strlen(val) == 0) panic("http-resp-content-type was unexpectedly an empty string");
strcpy(response_content_type, val);
} else {
#ifdef LOG_MODULE_LOADING
debuglog("Invalid (%s,%s)\n", key, val);
#endif
}
j += ntks;
}
i += ntoks;
/* Validate presence of required fields */
if (strlen(module_name) == 0) panic("name field is required\n");
if (strlen(module_path) == 0) panic("path field is required\n");
if (port == 0) panic("port field is required\n");
#ifdef ADMISSIONS_CONTROL
/* expected-execution-us and relative-deadline-us are required in case of admissions control */
if (expected_execution_us == 0) panic("expected-execution-us is required\n");
if (relative_deadline_us == 0) panic("relative_deadline_us is required\n");
/* If the ratio is too big, admissions control is too coarse */
uint32_t ratio = relative_deadline_us / expected_execution_us;
if (ratio > ADMISSIONS_CONTROL_GRANULARITY)
panic("Ratio of Deadline to Execution time cannot exceed admissions control "
"granularity of "
"%d\n",
ADMISSIONS_CONTROL_GRANULARITY);
#else
/* relative-deadline-us is required if scheduler is EDF */
if (scheduler == SCHEDULER_EDF && relative_deadline_us == 0)
panic("relative_deadline_us is required\n");
#endif
/* Allocate a module based on the values from the JSON */
struct module *module = module_alloc(module_name, module_path, 0, relative_deadline_us, port,
request_size, response_size, admissions_percentile,
expected_execution_us);
if (module == NULL) goto module_alloc_err;
assert(module);
module_set_http_info(module, response_content_type);
module_count++;
}
if (module_count == 0) panic("%s contained no active modules\n", file_name);
#ifdef LOG_MODULE_LOADING
debuglog("Loaded %d module%s!\n", module_count, module_count > 1 ? "s" : "");
#endif
free(file_buffer);
return_code = 0;
done:
return return_code;
module_alloc_err:
json_parse_err:
fclose_err:
/* We will retry fclose when we fall through into stat_buffer_alloc_err */
fread_err:
free(file_buffer);
stat_buffer_alloc_err:
// Check to ensure we haven't already close this
if (module_file != NULL) {
if (fclose(module_file) == EOF) panic("Failed to close file\n");
}
err:
return_code = -1;
goto done;
}

@ -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;
}

@ -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"
}
]

@ -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"
}
]

@ -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"
}
]

@ -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"
}
]

@ -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"
}
]

@ -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"
}
]

@ -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"
}
]

Loading…
Cancel
Save