Compare commits
1 Commits
master
...
solve_arm_
Author | SHA1 | Date |
---|---|---|
|
fe88b14c6f | 3 years ago |
@ -1,23 +1,24 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"intelliSenseMode": "clang-x64",
|
||||
"includePath": [
|
||||
"/usr/include/",
|
||||
"${workspaceFolder}/runtime/include/",
|
||||
"${workspaceFolder}/runtime/thirdparty/ck/include/",
|
||||
"${workspaceFolder}/runtime/thirdparty/http-parser/",
|
||||
"${workspaceFolder}/runtime/thirdparty/jsmn/",
|
||||
"${workspaceFolder}/awsm/runtime/libc/wasi/include/",
|
||||
"${workspaceFolder}/libsledge/include"
|
||||
],
|
||||
"defines": [
|
||||
"x86_64",
|
||||
"_GNU_SOURCE"
|
||||
],
|
||||
"cStandard": "c17"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"intelliSenseMode": "clang-x64",
|
||||
"includePath": [
|
||||
"/usr/include/",
|
||||
"${workspaceFolder}/runtime/include/",
|
||||
"${workspaceFolder}/runtime/thirdparty/ck/include/",
|
||||
"${workspaceFolder}/runtime/thirdparty/http-parser/",
|
||||
"${workspaceFolder}/runtime/thirdparty/jsmn/",
|
||||
"${workspaceFolder}/awsm/runtime/libc/wasi/include/",
|
||||
"${workspaceFolder}/libsledge/include"
|
||||
],
|
||||
"defines": [
|
||||
"x86_64",
|
||||
"_GNU_SOURCE"
|
||||
],
|
||||
"cStandard": "c17",
|
||||
"compilerPath": "/usr/bin/clang"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
*.wasm
|
@ -1,24 +0,0 @@
|
||||
include ../wasm_apps/common.mk
|
||||
|
||||
.PHONY: all
|
||||
all: \
|
||||
scratch_storage_get.wasm \
|
||||
scratch_storage_set.wasm \
|
||||
scratch_storage_delete.wasm \
|
||||
scratch_storage_upsert.wasm \
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@rm -f scratch_storage_set.wa* scratch_storage_get.wa* scratch_storage_delete.wa* scratch_storage_upsert.wa*
|
||||
|
||||
scratch_storage_set.wasm: scratch_storage_set.c
|
||||
@${WASMCC} ${WASMCFLAGS} ${WASMLDFLAGS} $^ -o $@
|
||||
|
||||
scratch_storage_get.wasm: scratch_storage_get.c
|
||||
@${WASMCC} ${WASMCFLAGS} ${WASMLDFLAGS} $^ -o $@
|
||||
|
||||
scratch_storage_delete.wasm: scratch_storage_delete.c
|
||||
@${WASMCC} ${WASMCFLAGS} ${WASMLDFLAGS} $^ -o $@
|
||||
|
||||
scratch_storage_upsert.wasm: scratch_storage_upsert.c
|
||||
@${WASMCC} ${WASMCFLAGS} ${WASMLDFLAGS} $^ -o $@
|
@ -1,32 +0,0 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
extern int scratch_storage_delete(void *key, uint32_t key_len)
|
||||
__attribute__((__import_module__("scratch_storage"), __import_name__("delete")));
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "%s <key>", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *key = argv[1];
|
||||
|
||||
if (key == NULL || strlen(key) < 0) {
|
||||
fprintf(stderr, "%s <key>", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rc = scratch_storage_delete(key, strlen(key));
|
||||
if (rc == 1) {
|
||||
printf("Key '%s' not found\n", key);
|
||||
return 0;
|
||||
} else {
|
||||
printf("Key %s deleted\n", key);
|
||||
}
|
||||
};
|
@ -1,38 +0,0 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
extern int scratch_storage_get(void *key, uint32_t key_len, void *buf, uint32_t buf_len)
|
||||
__attribute__((__import_module__("scratch_storage"), __import_name__("get")));
|
||||
|
||||
extern uint32_t scratch_storage_get_size(void *key, uint32_t key_len)
|
||||
__attribute__((__import_module__("scratch_storage"), __import_name__("get_size")));
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "%s <key>", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *key = argv[1];
|
||||
|
||||
if (key == NULL || strlen(key) < 0) {
|
||||
fprintf(stderr, "%s <key>", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t val_size = scratch_storage_get_size(key, strlen(key));
|
||||
char *buf = calloc(val_size + 1, sizeof(char));
|
||||
int rc = scratch_storage_get(key, strlen(key), buf, val_size);
|
||||
assert(rc != 2);
|
||||
if (rc == 1) {
|
||||
printf("Key '%s' not found\n", key);
|
||||
return 0;
|
||||
} else {
|
||||
printf("Key %s resolved to value of size %u with contents %s\n", key, val_size, buf);
|
||||
}
|
||||
};
|
@ -1,35 +0,0 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
extern int scratch_storage_set(void *key, uint32_t key_len, void *value, uint32_t value_len)
|
||||
__attribute__((__import_module__("scratch_storage"), __import_name__("set")));
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "%s <key> <value>", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *key = argv[1];
|
||||
char *value = argv[2];
|
||||
|
||||
if (key == NULL || strlen(key) < 0 || value == NULL || strlen(value) < 0) {
|
||||
fprintf(stderr, "%s <key> <value>", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rc = scratch_storage_set(key, strlen(key), value, strlen(value));
|
||||
if (rc == 1) {
|
||||
printf("Key %s was already present\n", key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(rc == 0);
|
||||
printf("Key %s set to value %s\n", key, value);
|
||||
return rc;
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
extern int scratch_storage_upsert(void *key, uint32_t key_len, void *value, uint32_t value_len)
|
||||
__attribute__((__import_module__("scratch_storage"), __import_name__("upsert")));
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "%s <key> <value>", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *key = argv[1];
|
||||
char *value = argv[2];
|
||||
|
||||
if (key == NULL || strlen(key) < 0 || value == NULL || strlen(value) < 0) {
|
||||
fprintf(stderr, "%s <key> <value>", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
scratch_storage_upsert(key, strlen(key), value, strlen(value));
|
||||
printf("Key %s set to value %s\n", key, value);
|
||||
};
|
@ -0,0 +1 @@
|
||||
Subproject commit 4e6dc2a2918f787c610b00d4da13b7620d7d43c5
|
@ -1 +1 @@
|
||||
Subproject commit 272fcf42b6559ccb5c5213eb78edfc0f703520ab
|
||||
Subproject commit 3aca5263d346065173d35437c319d6d3d61204b9
|
@ -1,59 +0,0 @@
|
||||
SLEdge only implemented a subset of the WASI syscall interface
|
||||
|
||||
## Arguments
|
||||
|
||||
The WASI calls `args_sizes_get` and `args_get` are supported. HTTP query parameters are captured and passed as arguments.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The WASI calls `environ_get` and `environ_sizes_get` are supported, but mostly unused. The current behavior is to to pass the runtime's environment variables into the sandbox. This is likely undesirable.
|
||||
|
||||
Presumably, the runtime should provide a standard set of environment variables and also allow the JSON spec to set additional function-specific environment variables.
|
||||
|
||||
See the reference of environment variables generated by WAGI for details: https://github.com/deislabs/wagi/blob/main/docs/environment_variables.md
|
||||
|
||||
## Clocks
|
||||
|
||||
`clock_time_get` is implemented but untested. `clock_res_get` is unimplemented.
|
||||
|
||||
## File System
|
||||
|
||||
SLEdge only supports `fd_read` from stdin and `fd_write` to stderr or stdout.
|
||||
|
||||
stdin is populated with the body of an HTTP POST request. stdout and stderr are both written in an interleaved fashion into a buffer and sent back to the client as the response body.
|
||||
|
||||
Actual access to the file system is unsupported, and sandboxes are not provided any preopened descriptors.
|
||||
|
||||
## Poll
|
||||
|
||||
`poll_oneoff` is unsupposed because SLEdge serverless functions are short lived. Sandboxed functions are assumed to make blocking reads/writes to stdin/stdout/stderr, and the serverless runtime is responsible for causing serverless functions to sleep and wake.
|
||||
|
||||
## Exit
|
||||
|
||||
`proc_exit` is supported and causes a sandbox to terminate execution.
|
||||
|
||||
## Signals
|
||||
|
||||
`proc_raise` is not supported. Signals are used by the runtime to provide preemption and context switching. It would be dangerous to trigger actual host signals from a sandbox.
|
||||
|
||||
However, the function could be implemented by creating a switch on the wasi signal and either ignoring or handling the signal within the `proc_raise` function itself.
|
||||
|
||||
`SIGABRT` could trigger the sandbox to exit in an abnormal condition.
|
||||
|
||||
The default ignore behavior could log the unexpected signal and return.
|
||||
|
||||
## Random
|
||||
|
||||
`random_get` is supported but largely untested.
|
||||
|
||||
## Yield
|
||||
|
||||
`sched_yield` is unsupported. This does not match with the run-to-completion nature of serverless.
|
||||
|
||||
In the case of EDF, a sandbox would always yield to itself. However, in the case of FIFO, we could enable this call to allow for a worker to "round robin" within a runqueue. However, it is unclear what the rationale would be to allow a serverless function to impact the scheduler.
|
||||
|
||||
## Sockets
|
||||
|
||||
All socket syscalls are unimplemented because the current logic around `sock_accept` and `sock_shutdown` seems to be focused on long-lived daemon nanoprocesses that handle multiple requests. The `poll_oneoff` call also seems to be based on this usecase.
|
||||
|
||||
Generally, a serverless function is expected to only make outbound network requests. However, this use case does not seem to be currently supported by WASI.
|
@ -1,7 +0,0 @@
|
||||
#include "sledge_abi.h"
|
||||
|
||||
void
|
||||
awsm_abi__trap_unreachable(void)
|
||||
{
|
||||
sledge_abi__wasm_trap_raise(WASM_TRAP_UNREACHABLE);
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include "sledge_abi.h"
|
||||
|
||||
#define INLINE __attribute__((always_inline))
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @param key_len
|
||||
* @returns value_size at key or 0 if key not present
|
||||
*/
|
||||
INLINE uint32_t
|
||||
scratch_storage_get_size(uint32_t key_offset, uint32_t key_len)
|
||||
{
|
||||
return sledge_abi__scratch_storage_get_size(key_offset, key_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key_offset
|
||||
* @param key_len
|
||||
* @param buf_offset linear memory offset to buffer where value should be copied.
|
||||
* @param buf_len Size of buffer. Assumed to be size returned by sledge_kv_get_value_size.
|
||||
* @returns 0 on success, 1 if key missing, 2 if buffer too small
|
||||
*/
|
||||
INLINE int
|
||||
scratch_storage_get(uint32_t key_offset, uint32_t key_len, uint32_t buf_offset, uint32_t buf_len)
|
||||
{
|
||||
return sledge_abi__scratch_storage_get(key_offset, key_len, buf_offset, buf_len);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param key_offset
|
||||
* @param key_len
|
||||
* @param value_offset
|
||||
* @param value_len
|
||||
* @returns 0 on success, 1 if already present,
|
||||
*/
|
||||
INLINE int
|
||||
scratch_storage_set(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len)
|
||||
{
|
||||
return sledge_abi__scratch_storage_set(key_offset, key_len, value_offset, value_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key_offset
|
||||
* @param key_len
|
||||
* @returns 0 on success, 1 if not present
|
||||
*/
|
||||
INLINE int
|
||||
scratch_storage_delete(uint32_t key_offset, uint32_t key_len)
|
||||
{
|
||||
return sledge_abi__scratch_storage_delete(key_offset, key_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key_offset
|
||||
* @param key_len
|
||||
* @param value_offset
|
||||
* @param value_len
|
||||
* @returns 0 on success, 1 if already present,
|
||||
*/
|
||||
INLINE void
|
||||
scratch_storage_upsert(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len)
|
||||
{
|
||||
sledge_abi__scratch_storage_upsert(key_offset, key_len, value_offset, value_len);
|
||||
}
|
@ -1,19 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ADMISSIONS_CONTROL
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define ADMISSIONS_CONTROL_GRANULARITY 1000000
|
||||
extern _Atomic uint64_t admissions_control_admitted;
|
||||
extern uint64_t admissions_control_capacity;
|
||||
|
||||
void admissions_control_initialize(void);
|
||||
void admissions_control_add(uint64_t admissions_estimate);
|
||||
void admissions_control_subtract(uint64_t admissions_estimate);
|
||||
uint64_t admissions_control_calculate_estimate(uint64_t estimated_execution, uint64_t relative_deadline);
|
||||
uint64_t admissions_control_calculate_estimate_us(uint32_t estimated_execution_us, uint32_t relative_deadline_us);
|
||||
void admissions_control_log_decision(uint64_t admissions_estimate, bool admitted);
|
||||
uint64_t admissions_control_decide(uint64_t admissions_estimate);
|
||||
|
||||
#endif
|
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "perf_window_t.h"
|
||||
|
||||
struct admissions_info {
|
||||
struct perf_window perf_window;
|
||||
int 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_update(struct admissions_info *admissions_info, uint64_t execution_duration);
|
@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "likely.h"
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct auto_buf {
|
||||
FILE *handle;
|
||||
char *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static inline int
|
||||
auto_buf_init(struct auto_buf *buf)
|
||||
{
|
||||
FILE *res = open_memstream(&buf->data, &buf->size);
|
||||
if (res == NULL) return errno;
|
||||
|
||||
buf->handle = res;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int
|
||||
auto_buf_flush(struct auto_buf *buf)
|
||||
{
|
||||
return fflush(buf->handle);
|
||||
}
|
||||
|
||||
static inline void
|
||||
auto_buf_deinit(struct auto_buf *buf)
|
||||
{
|
||||
if (likely(buf->handle != NULL)) {
|
||||
fclose(buf->handle);
|
||||
buf->handle = NULL;
|
||||
}
|
||||
|
||||
if (likely(buf->data != NULL)) {
|
||||
free(buf->data);
|
||||
buf->data = NULL;
|
||||
}
|
||||
|
||||
buf->size = 0;
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "debuglog.h"
|
||||
#include "http.h"
|
||||
#include "http_total.h"
|
||||
#include "panic.h"
|
||||
#include "likely.h"
|
||||
|
||||
|
||||
static inline void
|
||||
client_socket_close(int client_socket, struct sockaddr *client_address)
|
||||
{
|
||||
/* Should never close 0, 1, or 2 */
|
||||
assert(client_socket != STDIN_FILENO);
|
||||
assert(client_socket != STDOUT_FILENO);
|
||||
assert(client_socket != STDERR_FILENO);
|
||||
|
||||
if (unlikely(close(client_socket) < 0)) {
|
||||
char client_address_text[INET6_ADDRSTRLEN] = { '\0' };
|
||||
if (unlikely(inet_ntop(AF_INET, &client_address, client_address_text, INET6_ADDRSTRLEN) == NULL)) {
|
||||
debuglog("Failed to log client_address: %s", strerror(errno));
|
||||
}
|
||||
debuglog("Error closing client socket %d associated with %s - %s", client_socket, client_address_text,
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
typedef void (*void_cb)(void);
|
||||
|
||||
/**
|
||||
* Writes buffer to the client socket
|
||||
* @param client_socket - the client we are rejecting
|
||||
* @param buffer - buffer to write to socket
|
||||
* @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out
|
||||
* @returns 0 on success, -1 on error.
|
||||
*/
|
||||
static inline int
|
||||
client_socket_send(int client_socket, const char *buffer, size_t buffer_len, void_cb on_eagain)
|
||||
{
|
||||
int rc;
|
||||
|
||||
size_t cursor = 0;
|
||||
|
||||
while (cursor < buffer_len) {
|
||||
ssize_t sent = write(client_socket, &buffer[cursor], buffer_len - cursor);
|
||||
if (sent < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
if (on_eagain == NULL) {
|
||||
rc = -1;
|
||||
goto done;
|
||||
}
|
||||
on_eagain();
|
||||
} else {
|
||||
debuglog("Error sending to client: %s", strerror(errno));
|
||||
rc = -1;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
assert(sent > 0);
|
||||
cursor += (size_t)sent;
|
||||
};
|
||||
|
||||
rc = 0;
|
||||
done:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects request due to admission control or error
|
||||
* @param client_socket - the client we are rejecting
|
||||
* @param buffer - buffer to write to socket
|
||||
* @returns 0
|
||||
*/
|
||||
static inline int
|
||||
client_socket_send_oneshot(int client_socket, const char *buffer, size_t buffer_len)
|
||||
{
|
||||
return client_socket_send(client_socket, buffer, buffer_len, NULL);
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "current_sandbox.h"
|
||||
#include "http.h"
|
||||
#include "http_total.h"
|
||||
#include "likely.h"
|
||||
#include "sandbox_types.h"
|
||||
#include "scheduler.h"
|
||||
#include "panic.h"
|
||||
|
||||
/**
|
||||
* Sends Response Back to Client
|
||||
* @return RC. -1 on Failure
|
||||
*/
|
||||
static inline int
|
||||
current_sandbox_send_response()
|
||||
{
|
||||
struct sandbox *sandbox = current_sandbox_get();
|
||||
assert(sandbox != NULL);
|
||||
struct vec_u8 *response = &sandbox->response;
|
||||
assert(response != NULL);
|
||||
|
||||
int rc;
|
||||
|
||||
/* Determine values to template into our HTTP response */
|
||||
size_t response_body_size = response->length;
|
||||
char *module_content_type = sandbox->module->response_content_type;
|
||||
const char *content_type = strlen(module_content_type) > 0 ? module_content_type : "text/plain";
|
||||
|
||||
/* Capture Timekeeping data for end-to-end latency */
|
||||
uint64_t end_time = __getcycles();
|
||||
sandbox->total_time = end_time - sandbox->timestamp_of.request_arrival;
|
||||
|
||||
/* Send HTTP Response Header and Body */
|
||||
rc = http_header_200_write(sandbox->client_socket_descriptor, content_type, response_body_size);
|
||||
if (rc < 0) goto err;
|
||||
|
||||
rc = client_socket_send(sandbox->client_socket_descriptor, (const char *)response->buffer, response_body_size,
|
||||
current_sandbox_sleep);
|
||||
if (rc < 0) goto err;
|
||||
|
||||
http_total_increment_2xx();
|
||||
rc = 0;
|
||||
|
||||
done:
|
||||
return rc;
|
||||
err:
|
||||
debuglog("Error sending to client: %s", strerror(errno));
|
||||
rc = -1;
|
||||
goto done;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
enum epoll_tag
|
||||
{
|
||||
EPOLL_TAG_INVALID = 0,
|
||||
EPOLL_TAG_TENANT_SERVER_SOCKET = 1,
|
||||
EPOLL_TAG_METRICS_SERVER_SOCKET,
|
||||
EPOLL_TAG_HTTP_SESSION_CLIENT_SOCKET,
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "perf_window_t.h"
|
||||
|
||||
struct execution_histogram {
|
||||
struct perf_window perf_window;
|
||||
uint8_t percentile; /* 50 - 99 */
|
||||
int control_index; /* Precomputed Lookup index when perf_window is full */
|
||||
uint64_t estimated_execution; /* cycles */
|
||||
};
|
||||
|
||||
void execution_histogram_initialize(struct execution_histogram *execution_histogram, uint8_t percentile,
|
||||
uint64_t expected_execution);
|
||||
void execution_histogram_update(struct execution_histogram *execution_histogram, uint64_t execution_duration);
|
@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef EXECUTION_REGRESSION
|
||||
|
||||
#include "http_session.h"
|
||||
#include <stdint.h>
|
||||
|
||||
static inline uint64_t
|
||||
get_regression_prediction(struct http_session *session)
|
||||
{
|
||||
/* Default Pre-processing - Extract payload size */
|
||||
const int payload_size = session->http_request.body_length;
|
||||
|
||||
const double regression_params[2] = {payload_size, session->regression_param};
|
||||
|
||||
/* Perform Linear Regression using the factors provided by the regressor performed AoT on Matlab using training
|
||||
* tenant-given dataset */
|
||||
const struct regression_model model = session->route->regr_model;
|
||||
const uint64_t prediction = (regression_params[0] / model.scale * model.beta1
|
||||
+ regression_params[1] / model.scale * model.beta2)
|
||||
+ model.bias;
|
||||
|
||||
return prediction;
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <threads.h>
|
||||
|
||||
extern thread_local uint64_t generic_thread_lock_duration;
|
||||
extern thread_local uint64_t generic_thread_lock_longest;
|
||||
extern thread_local uint64_t generic_thread_start_timestamp;
|
||||
|
||||
void generic_thread_dump_lock_overhead(void);
|
||||
void generic_thread_initialize(void);
|
@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "global_request_scheduler.h"
|
||||
|
||||
void global_request_scheduler_mtds_initialize();
|
||||
int global_request_scheduler_mtds_remove_with_mt_class(struct sandbox **, uint64_t, enum MULTI_TENANCY_CLASS);
|
||||
uint64_t global_request_scheduler_mtds_guaranteed_peek();
|
||||
uint64_t global_request_scheduler_mtds_default_peek();
|
||||
void global_timeout_queue_add(struct tenant *);
|
||||
void global_request_scheduler_mtds_promote_lock(struct tenant_global_request_queue *);
|
||||
void global_request_scheduler_mtds_demote_nolock(struct tenant_global_request_queue *);
|
||||
void global_timeout_queue_process_promotions();
|
@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
#ifdef HTTP_ROUTE_TOTAL_COUNTERS
|
||||
struct http_route_total {
|
||||
atomic_ulong total_requests;
|
||||
atomic_ulong total_2XX;
|
||||
atomic_ulong total_4XX;
|
||||
atomic_ulong total_5XX;
|
||||
};
|
||||
#else
|
||||
struct http_route_total {
|
||||
};
|
||||
#endif
|
||||
|
||||
static inline void
|
||||
http_route_total_init(struct http_route_total *rm)
|
||||
{
|
||||
#ifdef HTTP_ROUTE_TOTAL_COUNTERS
|
||||
atomic_init(&rm->total_requests, 0);
|
||||
atomic_init(&rm->total_2XX, 0);
|
||||
atomic_init(&rm->total_4XX, 0);
|
||||
atomic_init(&rm->total_5XX, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void
|
||||
http_route_total_increment_request(struct http_route_total *rm)
|
||||
{
|
||||
#ifdef HTTP_ROUTE_TOTAL_COUNTERS
|
||||
atomic_fetch_add(&rm->total_requests, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void
|
||||
http_route_total_increment(struct http_route_total *rm, int status_code)
|
||||
{
|
||||
#ifdef HTTP_ROUTE_TOTAL_COUNTERS
|
||||
if (status_code >= 200 && status_code <= 299) {
|
||||
atomic_fetch_add(&rm->total_2XX, 1);
|
||||
} else if (status_code >= 400 && status_code <= 499) {
|
||||
atomic_fetch_add(&rm->total_4XX, 1);
|
||||
} else if (status_code >= 500 && status_code <= 599) {
|
||||
atomic_fetch_add(&rm->total_5XX, 1);
|
||||
}
|
||||
#endif
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "http.h"
|
||||
#include "module.h"
|
||||
#include "route.h"
|
||||
#include "route_config.h"
|
||||
#include "route_latency.h"
|
||||
#include "vec.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct route route_t;
|
||||
VEC(route_t)
|
||||
|
||||
typedef struct vec_route_t http_router_t;
|
||||
|
||||
static inline void
|
||||
http_router_init(http_router_t *router, size_t capacity)
|
||||
{
|
||||
vec_route_t_init(router, capacity);
|
||||
}
|
||||
|
||||
static inline int
|
||||
http_router_add_route(http_router_t *router, struct route_config *config, struct module *module,
|
||||
struct module *module_proprocess)
|
||||
{
|
||||
assert(router != NULL);
|
||||
assert(config != NULL);
|
||||
assert(module != NULL);
|
||||
assert(config->route != NULL);
|
||||
assert(config->http_resp_content_type != NULL);
|
||||
|
||||
struct route route = {.route = config->route,
|
||||
.module = module,
|
||||
.relative_deadline_us = config->relative_deadline_us,
|
||||
.relative_deadline = (uint64_t)config->relative_deadline_us * runtime_processor_speed_MHz,
|
||||
.response_content_type = config->http_resp_content_type};
|
||||
|
||||
route_latency_init(&route.latency);
|
||||
http_route_total_init(&route.metrics);
|
||||
|
||||
#ifdef EXECUTION_REGRESSION
|
||||
/* Execution Regression setup */
|
||||
route.module_proprocess = module_proprocess;
|
||||
route.regr_model.bias = config->model_bias / 1000.0;
|
||||
route.regr_model.scale = config->model_scale / 1000.0;
|
||||
route.regr_model.num_of_param = config->model_num_of_param;
|
||||
route.regr_model.beta1 = config->model_beta1 / 1000.0;
|
||||
route.regr_model.beta2 = config->model_beta2 / 1000.0;
|
||||
#endif
|
||||
|
||||
const uint64_t expected_execution = route.relative_deadline / 2;
|
||||
#ifdef ADMISSIONS_CONTROL
|
||||
/* Addmissions Control setup */
|
||||
route.execution_histogram.estimated_execution = expected_execution;
|
||||
#endif
|
||||
|
||||
#ifdef EXECUTION_HISTOGRAM
|
||||
/* Execution Histogram setup */
|
||||
execution_histogram_initialize(&route.execution_histogram, config->admissions_percentile, expected_execution);
|
||||
#endif
|
||||
|
||||
int rc = vec_route_t_push(router, route);
|
||||
if (unlikely(rc == -1)) { return -1; }
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline struct route *
|
||||
http_router_match_route(http_router_t *router, char *route)
|
||||
{
|
||||
for (int i = 0; i < router->length; i++) {
|
||||
if (strncmp(route, router->buffer[i].route, strlen(router->buffer[i].route)) == 0) {
|
||||
return &router->buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void
|
||||
http_router_foreach(http_router_t *router, void (*cb)(route_t *, void *, void *), void *arg_one, void *arg_two)
|
||||
{
|
||||
for (int i = 0; i < router->length; i++) { cb(&router->buffer[i], arg_one, arg_two); }
|
||||
}
|
@ -1,459 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "auto_buf.h"
|
||||
#include "debuglog.h"
|
||||
#include "epoll_tag.h"
|
||||
#include "http_parser.h"
|
||||
#include "http_parser_settings.h"
|
||||
#include "http_request.h"
|
||||
#include "http_route_total.h"
|
||||
#include "http_session_perf_log.h"
|
||||
#include "http_total.h"
|
||||
#include "route.h"
|
||||
#include "tcp_session.h"
|
||||
#include "tenant.h"
|
||||
|
||||
enum http_session_state
|
||||
{
|
||||
HTTP_SESSION_UNINITIALIZED = 0,
|
||||
HTTP_SESSION_INITIALIZED,
|
||||
HTTP_SESSION_RECEIVING_REQUEST,
|
||||
HTTP_SESSION_RECEIVE_REQUEST_BLOCKED,
|
||||
HTTP_SESSION_RECEIVED_REQUEST,
|
||||
HTTP_SESSION_EXECUTING,
|
||||
HTTP_SESSION_EXECUTION_COMPLETE,
|
||||
HTTP_SESSION_SENDING_RESPONSE_HEADER,
|
||||
HTTP_SESSION_SEND_RESPONSE_HEADER_BLOCKED,
|
||||
HTTP_SESSION_SENT_RESPONSE_HEADER,
|
||||
HTTP_SESSION_SENDING_RESPONSE_BODY,
|
||||
HTTP_SESSION_SEND_RESPONSE_BODY_BLOCKED,
|
||||
HTTP_SESSION_SENT_RESPONSE_BODY
|
||||
};
|
||||
|
||||
struct http_session {
|
||||
enum epoll_tag tag;
|
||||
enum http_session_state state;
|
||||
struct sockaddr client_address; /* client requesting connection! */
|
||||
int socket;
|
||||
struct http_parser http_parser;
|
||||
struct http_request http_request;
|
||||
struct auto_buf request_buffer;
|
||||
struct auto_buf response_header;
|
||||
size_t response_header_written;
|
||||
struct auto_buf response_body;
|
||||
size_t response_body_written;
|
||||
struct tenant *tenant; /* Backlink required when read blocks on listener core */
|
||||
struct route *route; /* Backlink required to handle http metrics */
|
||||
uint64_t request_arrival_timestamp;
|
||||
uint64_t request_downloaded_timestamp;
|
||||
uint64_t response_takeoff_timestamp;
|
||||
uint64_t response_sent_timestamp;
|
||||
bool did_preprocessing;
|
||||
uint64_t preprocessing_duration;
|
||||
double regression_param; /* Calculated in tenant preprocessing logic if provided */
|
||||
};
|
||||
|
||||
extern void http_session_perf_log_print_entry(struct http_session *http_session);
|
||||
|
||||
/**
|
||||
* Initalize state associated with an http parser
|
||||
* Because the http_parser structure uses pointers to the request buffer, if realloc moves the request
|
||||
* buffer, this should be called to clear stale state to force parsing to restart
|
||||
*/
|
||||
static inline void
|
||||
http_session_parser_init(struct http_session *session)
|
||||
{
|
||||
memset(&session->http_request, 0, sizeof(struct http_request));
|
||||
http_parser_init(&session->http_parser, HTTP_REQUEST);
|
||||
/* Set the session as the data the http-parser has access to */
|
||||
session->http_parser.data = &session->http_request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param session session that we want to init
|
||||
* @returns 0 on success, -1 on error
|
||||
*/
|
||||
static inline int
|
||||
http_session_init(struct http_session *session, int socket_descriptor, const struct sockaddr *socket_address,
|
||||
struct tenant *tenant, uint64_t request_arrival_timestamp)
|
||||
{
|
||||
assert(session != NULL);
|
||||
assert(session->state == HTTP_SESSION_UNINITIALIZED);
|
||||
assert(socket_descriptor >= 0);
|
||||
assert(socket_address != NULL);
|
||||
|
||||
session->tag = EPOLL_TAG_HTTP_SESSION_CLIENT_SOCKET;
|
||||
session->tenant = tenant;
|
||||
session->route = NULL;
|
||||
session->socket = socket_descriptor;
|
||||
session->request_arrival_timestamp = request_arrival_timestamp;
|
||||
memcpy(&session->client_address, socket_address, sizeof(struct sockaddr));
|
||||
|
||||
http_session_parser_init(session);
|
||||
|
||||
int rc = auto_buf_init(&session->request_buffer);
|
||||
if (rc < 0) return -1;
|
||||
|
||||
/* Defer initializing response_body until we've matched a route */
|
||||
auto_buf_init(&session->response_header);
|
||||
|
||||
session->state = HTTP_SESSION_INITIALIZED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int
|
||||
http_session_init_response_body(struct http_session *session)
|
||||
{
|
||||
assert(session != NULL);
|
||||
assert(session->response_body.data == NULL);
|
||||
assert(session->response_body.size == 0);
|
||||
assert(session->response_body_written == 0);
|
||||
|
||||
int rc = auto_buf_init(&session->response_body);
|
||||
if (rc < 0) {
|
||||
auto_buf_deinit(&session->request_buffer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline struct http_session *
|
||||
http_session_alloc(int socket_descriptor, const struct sockaddr *socket_address, struct tenant *tenant,
|
||||
uint64_t request_arrival_timestamp)
|
||||
{
|
||||
assert(socket_descriptor >= 0);
|
||||
assert(socket_address != NULL);
|
||||
|
||||
struct http_session *session = calloc(1, sizeof(struct http_session));
|
||||
if (session == NULL) return NULL;
|
||||
|
||||
int rc = http_session_init(session, socket_descriptor, socket_address, tenant, request_arrival_timestamp);
|
||||
if (rc != 0) {
|
||||
free(session);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deinitialize Linear Memory, cleaning up the backing buffer
|
||||
* @param sandbox
|
||||
*/
|
||||
static inline void
|
||||
http_session_deinit(struct http_session *session)
|
||||
{
|
||||
assert(session);
|
||||
|
||||
auto_buf_deinit(&session->request_buffer);
|
||||
auto_buf_deinit(&session->response_header);
|
||||
auto_buf_deinit(&session->response_body);
|
||||
}
|
||||
|
||||
static inline void
|
||||
http_session_free(struct http_session *session)
|
||||
{
|
||||
assert(session);
|
||||
|
||||
http_session_deinit(session);
|
||||
free(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Response Header
|
||||
* @param session - the HTTP session we want to set the response header of
|
||||
* @param status_code
|
||||
*/
|
||||
static inline void
|
||||
http_session_set_response_header(struct http_session *session, int status_code)
|
||||
{
|
||||
assert(session != NULL);
|
||||
assert(status_code >= 200 && status_code <= 599);
|
||||
http_total_increment_response(status_code);
|
||||
|
||||
/* We might not have actually matched a route */
|
||||
if (likely(session->route != NULL)) { http_route_total_increment(&session->route->metrics, status_code); }
|
||||
|
||||
int rc = fputs(http_header_build(status_code), session->response_header.handle);
|
||||
assert(rc != EOF);
|
||||
|
||||
if (status_code == 200) {
|
||||
/* Make sure the response_body is flushed */
|
||||
int rc = auto_buf_flush(&session->response_body);
|
||||
if (unlikely(rc != 0)) { panic("response_body auto_buf failed to flush: %s\n", strerror(errno)); };
|
||||
|
||||
/* Technically fprintf can truncate, but I assume this won't happen with a memstream */
|
||||
rc = fprintf(session->response_header.handle, HTTP_RESPONSE_CONTENT_TYPE,
|
||||
session->route->response_content_type);
|
||||
assert(rc > 0);
|
||||
rc = fprintf(session->response_header.handle, HTTP_RESPONSE_CONTENT_LENGTH,
|
||||
session->response_body.size);
|
||||
assert(rc > 0);
|
||||
}
|
||||
|
||||
rc = fputs(HTTP_RESPONSE_TERMINATOR, session->response_header.handle);
|
||||
assert(rc != EOF);
|
||||
|
||||
rc = auto_buf_flush(&session->response_header);
|
||||
if (unlikely(rc != 0)) { panic("response_header auto_buf failed to flush: %s\n", strerror(errno)); };
|
||||
|
||||
session->response_takeoff_timestamp = __getcycles();
|
||||
}
|
||||
|
||||
static inline void
|
||||
http_session_close(struct http_session *session)
|
||||
{
|
||||
assert(session != NULL);
|
||||
|
||||
return tcp_session_close(session->socket, &session->client_address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an HTTP header to the client
|
||||
* @param client_socket - the client
|
||||
* @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out
|
||||
* @returns 0 on success, -errno on error
|
||||
*/
|
||||
static inline int
|
||||
http_session_send_response_header(struct http_session *session, void_star_cb on_eagain)
|
||||
{
|
||||
assert(session != NULL);
|
||||
assert(session->state == HTTP_SESSION_EXECUTION_COMPLETE
|
||||
|| session->state == HTTP_SESSION_SEND_RESPONSE_HEADER_BLOCKED);
|
||||
session->state = HTTP_SESSION_SENDING_RESPONSE_HEADER;
|
||||
|
||||
while (session->response_header.size > session->response_header_written) {
|
||||
ssize_t sent =
|
||||
tcp_session_send(session->socket,
|
||||
(const char *)&session->response_header.data[session->response_header_written],
|
||||
session->response_header.size - session->response_header_written, on_eagain,
|
||||
session);
|
||||
if (sent < 0) {
|
||||
return (int)sent;
|
||||
} else {
|
||||
session->response_header_written += (size_t)sent;
|
||||
}
|
||||
}
|
||||
|
||||
session->state = HTTP_SESSION_SENT_RESPONSE_HEADER;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an HTTP body to the client
|
||||
* @param client_socket - the client
|
||||
* @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out
|
||||
* @returns 0 on success, -errno on error
|
||||
*/
|
||||
static inline int
|
||||
http_session_send_response_body(struct http_session *session, void_star_cb on_eagain)
|
||||
{
|
||||
assert(session != NULL);
|
||||
|
||||
assert(session->state == HTTP_SESSION_SENT_RESPONSE_HEADER
|
||||
|| session->state == HTTP_SESSION_SEND_RESPONSE_BODY_BLOCKED);
|
||||
session->state = HTTP_SESSION_SENDING_RESPONSE_BODY;
|
||||
|
||||
/* Assumption: Already flushed in order to write content-length to header */
|
||||
// TODO: Test if body is empty
|
||||
|
||||
while (session->response_body_written < session->response_body.size) {
|
||||
ssize_t sent =
|
||||
tcp_session_send(session->socket,
|
||||
(const char *)&session->response_body.data[session->response_body_written],
|
||||
session->response_body.size - session->response_body_written, on_eagain, session);
|
||||
if (sent < 0) {
|
||||
return (int)sent;
|
||||
} else {
|
||||
session->response_body_written += (size_t)sent;
|
||||
}
|
||||
}
|
||||
|
||||
session->state = HTTP_SESSION_SENT_RESPONSE_BODY;
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef void (*http_session_cb)(struct http_session *);
|
||||
|
||||
static inline ssize_t
|
||||
http_session_parse(struct http_session *session, ssize_t bytes_received)
|
||||
{
|
||||
assert(session != 0);
|
||||
assert(bytes_received > 0);
|
||||
|
||||
const http_parser_settings *settings = http_parser_settings_get();
|
||||
|
||||
#ifdef LOG_HTTP_PARSER
|
||||
debuglog("http_parser_execute(%p, %p, %p, %zu\n)", &session->http_parser, settings,
|
||||
&session->request_buffer.buffer[session->request_buffer.length], bytes_received);
|
||||
#endif
|
||||
size_t bytes_parsed =
|
||||
http_parser_execute(&session->http_parser, settings,
|
||||
(const char *)&session->request_buffer.data[session->http_request.length_parsed],
|
||||
(size_t)session->request_buffer.size - session->http_request.length_parsed);
|
||||
|
||||
if (session->http_parser.http_errno != HPE_OK) {
|
||||
debuglog("Error: %s, Description: %s\n",
|
||||
http_errno_name((enum http_errno)session->http_parser.http_errno),
|
||||
http_errno_description((enum http_errno)session->http_parser.http_errno));
|
||||
debuglog("Length Parsed %zu, Length Read %zu\n", bytes_parsed, (size_t)bytes_received);
|
||||
debuglog("Error parsing socket %d\n", session->socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
session->http_request.length_parsed += bytes_parsed;
|
||||
|
||||
return (ssize_t)bytes_parsed;
|
||||
}
|
||||
|
||||
static inline void
|
||||
http_session_log_query_params(struct http_session *session)
|
||||
{
|
||||
#ifdef LOG_HTTP_PARSER
|
||||
for (int i = 0; i < session->http_request.query_params_count; i++) {
|
||||
debuglog("Argument %d, Len: %d, %.*s\n", i, session->http_request.query_params[i].value_length,
|
||||
session->http_request.query_params[i].value_length,
|
||||
session->http_request.query_params[i].value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void
|
||||
http_session_log_malformed_request(struct http_session *session)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
char client_address_text[INET6_ADDRSTRLEN] = {};
|
||||
if (unlikely(inet_ntop(AF_INET, &session->client_address, client_address_text, INET6_ADDRSTRLEN) == NULL)) {
|
||||
debuglog("Failed to log client_address: %s", strerror(errno));
|
||||
}
|
||||
|
||||
debuglog("socket: %d. Address: %s\n", session->socket, client_address_text);
|
||||
http_request_print(&session->http_request);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive and Parse the Request for the current sandbox
|
||||
* @return 0 if message parsing complete, -1 on error, -EAGAIN if would block
|
||||
*/
|
||||
static inline int
|
||||
http_session_receive_request(struct http_session *session, void_star_cb on_eagain)
|
||||
{
|
||||
assert(session != NULL);
|
||||
assert(session->request_buffer.handle != NULL);
|
||||
assert(session->state == HTTP_SESSION_INITIALIZED || session->state == HTTP_SESSION_RECEIVE_REQUEST_BLOCKED);
|
||||
|
||||
session->state = HTTP_SESSION_RECEIVING_REQUEST;
|
||||
|
||||
struct http_request *http_request = &session->http_request;
|
||||
int rc = 0;
|
||||
char temp[BUFSIZ];
|
||||
|
||||
while (!http_request->message_end) {
|
||||
ssize_t bytes_received = tcp_session_recv(session->socket, temp, BUFSIZ, on_eagain, session);
|
||||
if (unlikely(bytes_received == -EAGAIN))
|
||||
goto err_eagain;
|
||||
else if (unlikely(bytes_received < 0))
|
||||
goto err;
|
||||
/* If we received an EOF before we were able to parse a complete HTTP message, request is malformed */
|
||||
else if (unlikely(bytes_received == 0 && !http_request->message_end))
|
||||
goto err;
|
||||
|
||||
assert(bytes_received > 0);
|
||||
|
||||
const char *old_buffer = session->request_buffer.data;
|
||||
const ssize_t header_length = session->request_buffer.size - http_request->body_length_read;
|
||||
assert(!http_request->header_end || header_length > 0);
|
||||
|
||||
/* Write temp buffer to memstream */
|
||||
fwrite(temp, 1, bytes_received, session->request_buffer.handle);
|
||||
|
||||
/* fflush memstream managed buffer */
|
||||
fflush(session->request_buffer.handle);
|
||||
|
||||
/* Update parser structure if buffer moved */
|
||||
if (old_buffer != session->request_buffer.data) {
|
||||
http_request->body = header_length ? session->request_buffer.data + header_length : NULL;
|
||||
}
|
||||
|
||||
if (http_session_parse(session, bytes_received) == -1) goto err;
|
||||
}
|
||||
|
||||
assert(http_request->message_end == true);
|
||||
session->state = HTTP_SESSION_RECEIVED_REQUEST;
|
||||
|
||||
http_session_log_query_params(session);
|
||||
|
||||
rc = 0;
|
||||
done:
|
||||
return rc;
|
||||
err_eagain:
|
||||
rc = -EAGAIN;
|
||||
goto done;
|
||||
err:
|
||||
http_session_log_malformed_request(session);
|
||||
rc = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to the HTTP response buffer
|
||||
* On success, the number of bytes written is returned. On error, -1 is returned,
|
||||
*/
|
||||
static inline int
|
||||
http_session_write_response(struct http_session *session, const uint8_t *source, size_t n)
|
||||
{
|
||||
assert(session);
|
||||
assert(session->response_body.handle != NULL);
|
||||
assert(source);
|
||||
|
||||
return fwrite(source, 1, n, session->response_body.handle);
|
||||
}
|
||||
|
||||
static inline void
|
||||
http_session_send_response(struct http_session *session, void_star_cb on_eagain)
|
||||
{
|
||||
assert(session->state == HTTP_SESSION_EXECUTION_COMPLETE);
|
||||
|
||||
int rc = http_session_send_response_header(session, on_eagain);
|
||||
/* session blocked and registered to epoll so continue to next handle */
|
||||
if (unlikely(rc == -EAGAIN)) {
|
||||
goto DONE;
|
||||
} else if (unlikely(rc < 0)) {
|
||||
goto CLOSE;
|
||||
}
|
||||
|
||||
assert(session->state == HTTP_SESSION_SENT_RESPONSE_HEADER);
|
||||
|
||||
rc = http_session_send_response_body(session, on_eagain);
|
||||
/* session blocked and registered to epoll so continue to next handle */
|
||||
if (unlikely(rc == -EAGAIN)) {
|
||||
goto DONE;
|
||||
} else if (unlikely(rc < 0)) {
|
||||
goto CLOSE;
|
||||
}
|
||||
|
||||
assert(session->state == HTTP_SESSION_SENT_RESPONSE_BODY);
|
||||
|
||||
/* Terminal State Logging for Http Session */
|
||||
session->response_sent_timestamp = __getcycles();
|
||||
http_session_perf_log_print_entry(session);
|
||||
|
||||
CLOSE:
|
||||
http_session_close(session);
|
||||
http_session_free(session);
|
||||
DONE:
|
||||
return;
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "http_session.h"
|
||||
#include "pretty_print.h"
|
||||
#include "runtime.h"
|
||||
|
||||
extern FILE *http_session_perf_log;
|
||||
typedef struct http_session http_session;
|
||||
void http_session_perf_log_print_entry(struct http_session *http_session);
|
||||
|
||||
/**
|
||||
* @brief Prints headers for the per-session perf logs
|
||||
*/
|
||||
static inline void
|
||||
http_session_perf_log_print_header()
|
||||
{
|
||||
if (http_session_perf_log == NULL) { perror("http_session perf log"); }
|
||||
fprintf(http_session_perf_log, "tenant,route,state,header_len,resp_body_len,receive_duration,sent_duration,"
|
||||
"total_lifetime,preprocessing,proc_MHz\n");
|
||||
}
|
||||
|
||||
static inline void
|
||||
http_session_perf_log_init()
|
||||
{
|
||||
char *http_session_perf_log_path = getenv("SLEDGE_HTTP_SESSION_PERF_LOG");
|
||||
if (http_session_perf_log_path != NULL) {
|
||||
pretty_print_key_value("HTTP Session Performance Log", "%s\n", http_session_perf_log_path);
|
||||
http_session_perf_log = fopen(http_session_perf_log_path, "w");
|
||||
if (http_session_perf_log == NULL) perror("http_session_perf_log_init\n");
|
||||
http_session_perf_log_print_header();
|
||||
} else {
|
||||
pretty_print_key_disabled("HTTP Session Performance Log");
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
http_session_perf_log_cleanup()
|
||||
{
|
||||
if (http_session_perf_log != NULL) {
|
||||
fflush(http_session_perf_log);
|
||||
fclose(http_session_perf_log);
|
||||
}
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <jsmn.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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, const char *json_buf)
|
||||
{
|
||||
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));
|
||||
if (json_buf != NULL) fprintf(stderr, "Token: %.*s\n", tok.end - tok.start, &json_buf[tok.start]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_nonempty_string(jsmntok_t tok, char *key)
|
||||
{
|
||||
if (!has_valid_type(tok, key, JSMN_STRING, NULL)) 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_nonempty_object(jsmntok_t tok, char *key)
|
||||
{
|
||||
if (!has_valid_type(tok, key, JSMN_OBJECT, NULL)) return false;
|
||||
|
||||
if (tok.size == 0) {
|
||||
fprintf(stderr, "The value of the key %s was an empty object\n", key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_nonempty_array(jsmntok_t tok, char *key)
|
||||
{
|
||||
if (!has_valid_type(tok, key, JSMN_ARRAY, NULL)) return false;
|
||||
|
||||
if (tok.size == 0) {
|
||||
fprintf(stderr, "The value of the key %s was an empty array\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;
|
||||
intmax_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;
|
||||
}
|
||||
|
||||
static inline int
|
||||
parse_uint64_t(jsmntok_t tok, const char *json_buf, const char *key, uint64_t *ret)
|
||||
{
|
||||
if (json_buf[tok.start] == '-') {
|
||||
fprintf(stderr, "Unable to parse uint64_t for key %s, had negative number\n", key);
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
char *end = NULL;
|
||||
uintmax_t temp = strtoumax(&json_buf[tok.start], &end, 10);
|
||||
if (end != &json_buf[tok.end] || (temp == UINT64_MAX && errno == ERANGE)) {
|
||||
fprintf(stderr, "Unable to parse uint64_t for key %s\n", key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*ret = temp;
|
||||
return 0;
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <jsmn.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "tenant_config_parse.h"
|
||||
|
||||
#define JSON_TOKENS_CAPACITY 16384
|
||||
|
||||
/**
|
||||
* Parses a JSON file into an array of tenant configs
|
||||
* @param file_name The path of the JSON file
|
||||
* @return tenant_config_vec_len on success. -1 on Error
|
||||
*/
|
||||
static inline int
|
||||
parse_json(const char *json_buf, ssize_t json_buf_size, struct tenant_config **tenant_config_vec)
|
||||
{
|
||||
assert(json_buf != NULL);
|
||||
assert(json_buf_size > 0);
|
||||
assert(tenant_config_vec != NULL);
|
||||
|
||||
jsmntok_t tokens[JSON_TOKENS_CAPACITY];
|
||||
int tenant_config_vec_len = 0;
|
||||
int i = 0;
|
||||
|
||||
/* Initialize the Jasmine Parser and an array to hold the tokens */
|
||||
jsmn_parser parser;
|
||||
jsmn_init(&parser);
|
||||
|
||||
/* Use Jasmine to parse the JSON */
|
||||
int total_tokens = jsmn_parse(&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;
|
||||
}
|
||||
|
||||
i = tenant_config_vec_parse(tenant_config_vec, &tenant_config_vec_len, json_buf, tokens, i, total_tokens);
|
||||
if (i != total_tokens - 1) goto json_parse_err;
|
||||
|
||||
done:
|
||||
return tenant_config_vec_len;
|
||||
json_parse_err:
|
||||
free(*tenant_config_vec);
|
||||
err:
|
||||
fprintf(stderr, "JSON:\n%s\n", json_buf);
|
||||
tenant_config_vec_len = -1;
|
||||
goto done;
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "sandbox_types.h"
|
||||
|
||||
void local_cleanup_queue_add(struct sandbox *sandbox);
|
||||
void local_cleanup_queue_free();
|
||||
void local_cleanup_queue_initialize();
|
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "sandbox_types.h"
|
||||
|
||||
void local_completion_queue_add(struct sandbox *sandbox);
|
||||
void local_completion_queue_free();
|
||||
void local_completion_queue_initialize();
|
@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "tenant.h"
|
||||
|
||||
void local_runqueue_mtds_initialize();
|
||||
void local_runqueue_mtds_promote(struct perworker_tenant_sandbox_queue *);
|
||||
void local_runqueue_mtds_demote(struct perworker_tenant_sandbox_queue *);
|
||||
void local_timeout_queue_add(struct tenant *);
|
||||
void local_timeout_queue_process_promotions();
|
@ -1,81 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <spinlock/mcs.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "arch/getcycles.h"
|
||||
#include "runtime.h"
|
||||
|
||||
|
||||
/* A linked list of nodes */
|
||||
struct lock_wrapper {
|
||||
uint64_t longest_held;
|
||||
uint64_t total_held;
|
||||
ck_spinlock_mcs_t lock;
|
||||
};
|
||||
|
||||
/* A node on the linked list */
|
||||
struct lock_node {
|
||||
struct ck_spinlock_mcs node;
|
||||
uint64_t time_locked;
|
||||
};
|
||||
|
||||
typedef struct lock_wrapper lock_t;
|
||||
typedef struct lock_node lock_node_t;
|
||||
typedef ck_spinlock_mcs_t lock_t;
|
||||
|
||||
/**
|
||||
* Initializes a lock
|
||||
* Initializes a lock of type lock_t
|
||||
* @param lock - the address of the lock
|
||||
*/
|
||||
static inline void
|
||||
lock_init(lock_t *self)
|
||||
{
|
||||
self->total_held = 0;
|
||||
self->longest_held = 0;
|
||||
ck_spinlock_mcs_init(&self->lock);
|
||||
}
|
||||
#define LOCK_INIT(lock) ck_spinlock_mcs_init((lock))
|
||||
|
||||
/**
|
||||
* Checks if a lock is locked
|
||||
* @param lock - the address of the lock
|
||||
* @returns bool if lock is locked
|
||||
*/
|
||||
static inline bool
|
||||
lock_is_locked(lock_t *self)
|
||||
{
|
||||
return ck_spinlock_mcs_locked(&self->lock);
|
||||
}
|
||||
|
||||
#define LOCK_IS_LOCKED(lock) ck_spinlock_mcs_locked((lock))
|
||||
|
||||
/**
|
||||
* Locks a lock, keeping track of overhead
|
||||
* @param lock - the address of the lock
|
||||
* @param node - node to add to lock
|
||||
* @param unique_variable_name - a unique prefix to hygienically namespace an associated lock/unlock pair
|
||||
*/
|
||||
static inline void
|
||||
lock_lock(lock_t *self, lock_node_t *node)
|
||||
{
|
||||
assert(node->time_locked == 0);
|
||||
|
||||
node->time_locked = __getcycles();
|
||||
ck_spinlock_mcs_lock(&self->lock, &node->node);
|
||||
}
|
||||
#define LOCK_LOCK_WITH_BOOKKEEPING(lock, unique_variable_name) \
|
||||
struct ck_spinlock_mcs _hygiene_##unique_variable_name##_node; \
|
||||
uint64_t _hygiene_##unique_variable_name##_pre = __getcycles(); \
|
||||
ck_spinlock_mcs_lock((lock), &(_hygiene_##unique_variable_name##_node)); \
|
||||
uint64_t _hygiene_##unique_variable_name##_duration = (__getcycles() - _hygiene_##unique_variable_name##_pre); \
|
||||
if (_hygiene_##unique_variable_name##_duration > generic_thread_lock_longest) { \
|
||||
generic_thread_lock_longest = _hygiene_##unique_variable_name##_duration; \
|
||||
} \
|
||||
generic_thread_lock_duration += _hygiene_##unique_variable_name##_duration;
|
||||
|
||||
/**
|
||||
* Unlocks a lock
|
||||
* @param lock - the address of the lock
|
||||
* @param node - node used when calling lock_lock
|
||||
* @param unique_variable_name - a unique prefix to hygienically namespace an associated lock/unlock pair
|
||||
*/
|
||||
static inline void
|
||||
lock_unlock(lock_t *self, lock_node_t *node)
|
||||
{
|
||||
assert(node->time_locked > 0);
|
||||
#define LOCK_UNLOCK_WITH_BOOKKEEPING(lock, unique_variable_name) \
|
||||
ck_spinlock_mcs_unlock(lock, &(_hygiene_##unique_variable_name##_node));
|
||||
|
||||
ck_spinlock_mcs_unlock(&self->lock, &node->node);
|
||||
uint64_t now = __getcycles();
|
||||
assert(node->time_locked < now);
|
||||
uint64_t duration = now - node->time_locked;
|
||||
node->time_locked = 0;
|
||||
if (unlikely(duration > self->longest_held)) { self->longest_held = duration; }
|
||||
self->total_held += duration;
|
||||
}
|
||||
/**
|
||||
* Locks a lock, keeping track of overhead
|
||||
* Assumes the availability of DEFAULT as a hygienic prefix for DEFAULT_node and DEFAULT_pre
|
||||
*
|
||||
* As such, this API can only be used once in a lexical scope.
|
||||
*
|
||||
* Use LOCK_LOCK_WITH_BOOKKEEPING and LOCK_UNLOCK_WITH_BOOKKEEPING if multiple locks are required
|
||||
* @param lock - the address of the lock
|
||||
*/
|
||||
#define LOCK_LOCK(lock) LOCK_LOCK_WITH_BOOKKEEPING(lock, DEFAULT)
|
||||
|
||||
/**
|
||||
* Unlocks a lock
|
||||
* Uses lock node NODE_DEFAULT and timestamp PRE_DEFAULT, so this assumes use of LOCK_LOCK
|
||||
* This API can only be used once in a lexical scope. If this isn't true, use LOCK_LOCK_WITH_BOOKKEEPING and
|
||||
* LOCK_UNLOCK_WITH_BOOKKEEPING
|
||||
* @param lock - the address of the lock
|
||||
*/
|
||||
#define LOCK_UNLOCK(lock) LOCK_UNLOCK_WITH_BOOKKEEPING(lock, DEFAULT)
|
||||
|
@ -1,195 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "lock.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
/* Simple K-V store based on The Practice of Programming by Kernighan and Pike */
|
||||
|
||||
/* Bucket count is sized to be a prime that is approximately 20% larger than the desired capacity (6k keys) */
|
||||
#define MAP_BUCKET_COUNT 7907
|
||||
#define MAP_HASH jenkins_hash
|
||||
|
||||
struct map_node {
|
||||
struct map_node *next;
|
||||
uint8_t *key;
|
||||
uint8_t *value;
|
||||
uint32_t key_len;
|
||||
uint32_t value_len;
|
||||
uint32_t hash;
|
||||
};
|
||||
|
||||
struct map_bucket {
|
||||
lock_t lock;
|
||||
struct map_node *head;
|
||||
};
|
||||
|
||||
struct map {
|
||||
struct map_bucket buckets[MAP_BUCKET_COUNT];
|
||||
};
|
||||
|
||||
static inline void
|
||||
map_init(struct map *restrict map)
|
||||
{
|
||||
for (int i = 0; i < MAP_BUCKET_COUNT; i++) {
|
||||
map->buckets[i].head = NULL;
|
||||
lock_init(&map->buckets[i].lock);
|
||||
}
|
||||
};
|
||||
|
||||
/* See https://en.wikipedia.org/wiki/Jenkins_hash_function */
|
||||
static inline uint32_t
|
||||
jenkins_hash(uint8_t *key, uint32_t key_len)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
uint32_t hash = 0;
|
||||
while (i != key_len) {
|
||||
hash += key[i++];
|
||||
hash += hash << 10;
|
||||
hash ^= hash >> 6;
|
||||
}
|
||||
hash += hash << 3;
|
||||
hash ^= hash >> 11;
|
||||
hash += hash << 15;
|
||||
return hash;
|
||||
}
|
||||
|
||||
static inline uint8_t *
|
||||
map_get(struct map *map, uint8_t *key, uint32_t key_len, uint32_t *ret_value_len)
|
||||
{
|
||||
uint8_t *value = NULL;
|
||||
|
||||
uint32_t hash = MAP_HASH(key, key_len);
|
||||
|
||||
struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT];
|
||||
|
||||
lock_node_t node = {};
|
||||
lock_lock(&bucket->lock, &node);
|
||||
for (struct map_node *node = bucket->head; node != NULL; node = node->next) {
|
||||
if (node->hash == hash) {
|
||||
value = node->value;
|
||||
*ret_value_len = node->value_len;
|
||||
goto DONE;
|
||||
}
|
||||
}
|
||||
|
||||
if (value == NULL) *ret_value_len = 0;
|
||||
|
||||
DONE:
|
||||
lock_unlock(&bucket->lock, &node);
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
map_set(struct map *map, uint8_t *key, uint32_t key_len, uint8_t *value, uint32_t value_len)
|
||||
{
|
||||
bool did_set = false;
|
||||
|
||||
uint32_t hash = MAP_HASH(key, key_len);
|
||||
struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT];
|
||||
lock_node_t node;
|
||||
lock_lock(&bucket->lock, &node);
|
||||
for (struct map_node *node = bucket->head; node != NULL; node = node->next) {
|
||||
if (node->hash == hash) goto DONE;
|
||||
}
|
||||
|
||||
struct map_node *new_node = (struct map_node *)xmalloc(sizeof(struct map_node));
|
||||
*(new_node) = (struct map_node){.hash = hash,
|
||||
.key = xmalloc(key_len),
|
||||
.key_len = key_len,
|
||||
.value = xmalloc(value_len),
|
||||
.value_len = value_len,
|
||||
.next = bucket->head};
|
||||
|
||||
// Copy Key and Value
|
||||
memcpy(new_node->key, key, key_len);
|
||||
memcpy(new_node->value, value, value_len);
|
||||
|
||||
bucket->head = new_node;
|
||||
did_set = true;
|
||||
|
||||
DONE:
|
||||
lock_unlock(&bucket->lock, &node);
|
||||
return did_set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns boolean if node was deleted or not
|
||||
*/
|
||||
static inline bool
|
||||
map_delete(struct map *map, uint8_t *key, uint32_t key_len)
|
||||
{
|
||||
bool did_delete = false;
|
||||
|
||||
uint32_t hash = MAP_HASH(key, key_len);
|
||||
struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT];
|
||||
lock_node_t node;
|
||||
lock_lock(&bucket->lock, &node);
|
||||
|
||||
struct map_node *prev = bucket->head;
|
||||
if (prev != NULL && prev->hash == hash) {
|
||||
bucket->head = prev->next;
|
||||
free(prev->key);
|
||||
free(prev->value);
|
||||
free(prev);
|
||||
did_delete = true;
|
||||
goto DONE;
|
||||
}
|
||||
|
||||
for (struct map_node *node = prev->next; node != NULL; prev = node, node = node->next) {
|
||||
prev->next = node->next;
|
||||
free(node->key);
|
||||
free(node->value);
|
||||
free(node);
|
||||
did_delete = true;
|
||||
goto DONE;
|
||||
}
|
||||
|
||||
DONE:
|
||||
lock_unlock(&bucket->lock, &node);
|
||||
return did_delete;
|
||||
}
|
||||
|
||||
static inline void
|
||||
map_upsert(struct map *map, uint8_t *key, uint32_t key_len, uint8_t *value, uint32_t value_len)
|
||||
{
|
||||
uint32_t hash = MAP_HASH(key, key_len);
|
||||
struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT];
|
||||
lock_node_t node;
|
||||
lock_lock(&bucket->lock, &node);
|
||||
|
||||
for (struct map_node *node = bucket->head; node != NULL; node = node->next) {
|
||||
if (node->hash == hash) {
|
||||
node->value_len = value_len;
|
||||
node->value = realloc(node->value, value_len);
|
||||
assert(node->value);
|
||||
memcpy(node->value, value, value_len);
|
||||
goto DONE;
|
||||
}
|
||||
}
|
||||
|
||||
struct map_node *new_node = (struct map_node *)xmalloc(sizeof(struct map_node));
|
||||
|
||||
*(new_node) = (struct map_node){.hash = hash,
|
||||
.key = xmalloc(key_len),
|
||||
.key_len = key_len,
|
||||
.value = xmalloc(value_len),
|
||||
.value_len = value_len,
|
||||
.next = bucket->head};
|
||||
|
||||
assert(new_node->key);
|
||||
assert(new_node->value);
|
||||
|
||||
// Copy Key and Value
|
||||
memcpy(new_node->key, key, key_len);
|
||||
memcpy(new_node->value, value, value_len);
|
||||
|
||||
bucket->head = new_node;
|
||||
|
||||
DONE:
|
||||
lock_unlock(&bucket->lock, &node);
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "epoll_tag.h"
|
||||
#include "tcp_server.h"
|
||||
|
||||
struct metrics_server {
|
||||
enum epoll_tag tag;
|
||||
struct tcp_server tcp;
|
||||
pthread_attr_t thread_settings;
|
||||
};
|
||||
|
||||
|
||||
extern struct metrics_server metrics_server;
|
||||
|
||||
void metrics_server_init();
|
||||
void metrics_server_thread_spawn(int client_socket);
|
||||
int metrics_server_close();
|
@ -1,103 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "runtime.h" /* For runtime_pid */
|
||||
|
||||
/* Used to read process-level metrics associated with sledgert from procfs
|
||||
* The parsing behavior is based on prtstat -r
|
||||
*/
|
||||
|
||||
|
||||
enum PROC_STAT
|
||||
{
|
||||
PROC_STAT_PID = 0, /* Process ID */
|
||||
PROC_STAT_COMM = 1, /* Process Name */
|
||||
PROC_STAT_STATE = 2, /* State */
|
||||
PROC_STAT_PPID, /* Parent Process ID */
|
||||
PROC_STAT_PGRP, /* Group ID */
|
||||
PROC_STAT_SESSION, /* Session ID */
|
||||
PROC_STAT_TTY_NR, /* ??? */
|
||||
PROC_STAT_TPGID, /* ??? */
|
||||
PROC_STAT_FLAGS, /* ??? */
|
||||
PROC_STAT_MINFLT, /* Minor Page Faults */
|
||||
PROC_STAT_CMINFLT, /* Minor Page Faults of children */
|
||||
PROC_STAT_MAJFLT, /* Major Page Faults */
|
||||
PROC_STAT_CMAJFLT, /* Major Page Faults of children */
|
||||
PROC_STAT_UTIME, /* User Time */
|
||||
PROC_STAT_STIME, /* System Time */
|
||||
PROC_STAT_CUTIME, /* Child User Time */
|
||||
PROC_STAT_CSTIME, /* Child System Time */
|
||||
PROC_STAT_PRIORITY,
|
||||
PROC_STAT_NICE,
|
||||
PROC_STAT_NUM_THREADS,
|
||||
PROC_STAT_ITREALVALUE,
|
||||
PROC_STAT_STARTTIME, /* Start Time */
|
||||
PROC_STAT_VSIZE, /* Virtual Memory */
|
||||
PROC_STAT_RSS,
|
||||
PROC_STAT_RSSLIM,
|
||||
PROC_STAT_STARTCODE,
|
||||
PROC_STAT_ENDCODE,
|
||||
PROC_STAT_STARTSTACK,
|
||||
PROC_STAT_KSTKESP,
|
||||
PROC_STAT_KSTKEIP,
|
||||
PROC_STAT_WCHAN,
|
||||
PROC_STAT_NSWAP,
|
||||
PROC_STAT_CNSWAP,
|
||||
PROC_STAT_EXIT_SIGNAL,
|
||||
PROC_STAT_PROCESSOR,
|
||||
PROC_STAT_RT_PRIORITY,
|
||||
PROC_STAT_POLICY,
|
||||
PROC_STAT_DELAYACCR_BLKIO_TICKS,
|
||||
PROC_STAT_GUEST_TIME,
|
||||
PROC_STAT_CGUEST_TIME,
|
||||
PROC_STAT_COUNT
|
||||
};
|
||||
|
||||
struct proc_stat_metrics {
|
||||
uint64_t minor_page_faults;
|
||||
uint64_t major_page_faults;
|
||||
uint64_t child_minor_page_faults;
|
||||
uint64_t child_major_page_faults;
|
||||
uint64_t user_time;
|
||||
uint64_t system_time;
|
||||
uint64_t guest_time;
|
||||
};
|
||||
|
||||
static inline void
|
||||
proc_stat_metrics_init(struct proc_stat_metrics *stat)
|
||||
{
|
||||
assert(runtime_pid > 0);
|
||||
|
||||
// Open sledgert's stat file in procfs
|
||||
char path[256];
|
||||
snprintf(path, 256, "/proc/%d/stat", runtime_pid);
|
||||
FILE *proc_stat = fopen(path, "r");
|
||||
|
||||
/* Read stat file into in-memory buffer */
|
||||
char buf[BUFSIZ];
|
||||
fgets(buf, BUFSIZ, proc_stat);
|
||||
fclose(proc_stat);
|
||||
|
||||
/* Parse into an array of tokens with indices aligning to the PROC_STAT enum */
|
||||
char *pos = NULL;
|
||||
char *proc_stat_values[PROC_STAT_COUNT];
|
||||
for (int i = 0; i < PROC_STAT_COUNT; i++) {
|
||||
char *tok = i == 0 ? strtok_r(buf, " ", &pos) : strtok_r(NULL, " ", &pos);
|
||||
proc_stat_values[i] = tok;
|
||||
}
|
||||
|
||||
/* Fill the proc_state_metrics struct with metrics of interest */
|
||||
/* Minor Page Faults, Major Page Faults, Vsize, User, System, Guest, Uptime */
|
||||
stat->minor_page_faults = strtoul(proc_stat_values[PROC_STAT_MINFLT], NULL, 10);
|
||||
stat->major_page_faults = strtoul(proc_stat_values[PROC_STAT_MAJFLT], NULL, 10);
|
||||
stat->child_minor_page_faults = strtoul(proc_stat_values[PROC_STAT_CMINFLT], NULL, 10);
|
||||
stat->child_major_page_faults = strtoul(proc_stat_values[PROC_STAT_CMAJFLT], NULL, 10);
|
||||
stat->user_time = strtoul(proc_stat_values[PROC_STAT_UTIME], NULL, 10);
|
||||
stat->system_time = strtoul(proc_stat_values[PROC_STAT_STIME], NULL, 10);
|
||||
stat->guest_time = strtoul(proc_stat_values[PROC_STAT_GUEST_TIME], NULL, 10);
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "execution_histogram.h"
|
||||
#include "http_route_total.h"
|
||||
#include "module.h"
|
||||
#include "perf_window.h"
|
||||
|
||||
struct regression_model {
|
||||
double bias;
|
||||
double scale;
|
||||
uint32_t num_of_param;
|
||||
double beta1;
|
||||
double beta2;
|
||||
};
|
||||
|
||||
/* Assumption: entrypoint is always _start. This should be enhanced later */
|
||||
struct route {
|
||||
char *route;
|
||||
struct http_route_total metrics;
|
||||
struct module *module;
|
||||
/* HTTP State */
|
||||
uint32_t relative_deadline_us;
|
||||
uint64_t relative_deadline; /* cycles */
|
||||
char *response_content_type;
|
||||
struct execution_histogram execution_histogram;
|
||||
struct perf_window latency;
|
||||
struct module *module_proprocess;
|
||||
struct regression_model regr_model;
|
||||
};
|
@ -1,179 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "admissions_control.h"
|
||||
#include "runtime.h"
|
||||
#include "scheduler_options.h"
|
||||
|
||||
enum route_config_member
|
||||
{
|
||||
route_config_member_route,
|
||||
route_config_member_path,
|
||||
route_config_member_admissions_percentile,
|
||||
route_config_member_relative_deadline_us,
|
||||
route_config_member_path_preprocess,
|
||||
route_config_member_model_bias,
|
||||
route_config_member_model_scale,
|
||||
route_config_member_model_num_of_param,
|
||||
route_config_member_model_beta1,
|
||||
route_config_member_model_beta2,
|
||||
route_config_member_http_resp_content_type,
|
||||
route_config_member_len
|
||||
};
|
||||
|
||||
struct route_config {
|
||||
char *route;
|
||||
char *path;
|
||||
uint8_t admissions_percentile;
|
||||
uint32_t relative_deadline_us;
|
||||
char *path_preprocess;
|
||||
uint32_t model_bias;
|
||||
uint32_t model_scale;
|
||||
uint32_t model_num_of_param;
|
||||
uint32_t model_beta1;
|
||||
uint32_t model_beta2;
|
||||
char *http_resp_content_type;
|
||||
};
|
||||
|
||||
static inline void
|
||||
route_config_deinit(struct route_config *config)
|
||||
{
|
||||
/* ownership of the route and http_resp_content_type strings was moved during http_router_add_route */
|
||||
assert(config->route == NULL);
|
||||
assert(config->http_resp_content_type == NULL);
|
||||
|
||||
/* ownership of the path stringswas moved during module_alloc */
|
||||
assert(config->path == NULL);
|
||||
}
|
||||
|
||||
static inline void
|
||||
route_config_print(struct route_config *config)
|
||||
{
|
||||
printf("[Route] Route: %s\n", config->route);
|
||||
printf("[Route] Path: %s\n", config->path);
|
||||
printf("[Route] Admissions Percentile: %hhu\n", config->admissions_percentile);
|
||||
printf("[Route] Relative Deadline (us): %u\n", config->relative_deadline_us);
|
||||
printf("[Route] HTTP Response Content Type: %s\n", config->http_resp_content_type);
|
||||
#ifdef EXECUTION_HISTOGRAM
|
||||
printf("[Route] Path of Preprocessing Module: %s\n", config->path_preprocess);
|
||||
printf("[Route] Model Bias: %u\n", config->model_bias);
|
||||
printf("[Route] Model Scale: %u\n", config->model_scale);
|
||||
printf("[Route] Model Num of Parameters: %u\n", config->model_num_of_param);
|
||||
printf("[Route] Model Betas: [%u, %u]\n", config->model_beta1, config->model_beta2);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a route config generated by a parser
|
||||
* @param config
|
||||
* @param did_set boolean array of size route_config_member_len indicating if parser set the associated member
|
||||
*/
|
||||
static inline int
|
||||
route_config_validate(struct route_config *config, bool *did_set)
|
||||
{
|
||||
if (did_set[route_config_member_route] == false) {
|
||||
fprintf(stderr, "route field is required\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (did_set[route_config_member_path] == false) {
|
||||
fprintf(stderr, "path field is required\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (did_set[route_config_member_http_resp_content_type] == false) {
|
||||
debuglog("http_resp_content_type not set, defaulting to text/plain\n");
|
||||
config->http_resp_content_type = "text/plain";
|
||||
}
|
||||
|
||||
if (scheduler != SCHEDULER_FIFO && scheduler != SCHEDULER_SJF) {
|
||||
if (did_set[route_config_member_relative_deadline_us] == false) {
|
||||
fprintf(stderr, "relative_deadline_us is required for the selected scheduler\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (config->relative_deadline_us > (uint32_t)RUNTIME_RELATIVE_DEADLINE_US_MAX) {
|
||||
fprintf(stderr, "Relative-deadline-us must be between 0 and %u, was %u\n",
|
||||
(uint32_t)RUNTIME_RELATIVE_DEADLINE_US_MAX, config->relative_deadline_us);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef EXECUTION_HISTOGRAM
|
||||
if (config->admissions_percentile > 99 || config->admissions_percentile < 50) {
|
||||
fprintf(stderr, "admissions-percentile must be > 50 and <= 99 but was %u, defaulting to 70\n",
|
||||
config->admissions_percentile);
|
||||
config->admissions_percentile = 70;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EXECUTION_REGRESSION
|
||||
if (did_set[route_config_member_path_preprocess] == false) {
|
||||
fprintf(stderr, "model path_preprocess field is required. Put zero if just default preprocessing\n");
|
||||
return -1;
|
||||
} else if (strcmp(config->path_preprocess, "0") == 0) {
|
||||
config->path_preprocess = NULL;
|
||||
}
|
||||
|
||||
if (did_set[route_config_member_model_bias] == false) {
|
||||
fprintf(stderr, "model bias field is required\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (did_set[route_config_member_model_scale] == false) {
|
||||
fprintf(stderr, "model scale field is required\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (config->model_scale == 0) {
|
||||
fprintf(stderr, "model scale cannot be zero (to avoid divide by zero)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (did_set[route_config_member_model_num_of_param] == false) {
|
||||
fprintf(stderr, "model num_of_param field is required\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (did_set[route_config_member_model_beta1] == false) {
|
||||
fprintf(stderr, "model beta1 field is required\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (config->model_beta1 == 0) {
|
||||
fprintf(stderr, "model beta1 cannot be zero (to avoid divide by zero)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (did_set[route_config_member_model_beta2] == false) {
|
||||
fprintf(stderr, "model beta2 field is required. Put zero for just default preprocessing\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (config->model_num_of_param < 1) {
|
||||
fprintf(stderr, "model num_of_param must be at least 1 (just default preprocessing)\n");
|
||||
return -1;
|
||||
} else if (config->model_num_of_param == 1) {
|
||||
if (config->path_preprocess) {
|
||||
fprintf(stderr, "model_num_of_param cannot be 1 when using tenant preprocessing\n");
|
||||
return -1;
|
||||
}
|
||||
config->model_beta2 = 1; /* This is to avoid divide-by-zero */
|
||||
} else {
|
||||
/* For now we just support up to two params */
|
||||
assert(config->model_num_of_param == 2);
|
||||
if (config->path_preprocess == NULL) {
|
||||
fprintf(stderr, "model_num_of_param cannot be more than 1 when just default preprocessing\n");
|
||||
return -1;
|
||||
}
|
||||
if (config->model_beta2 == 0) {
|
||||
fprintf(stderr, "model beta2 cannot be zero (to avoid divide by zero)\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "json.h"
|
||||
#include "route_config.h"
|
||||
|
||||
static const char *route_config_json_keys[route_config_member_len] =
|
||||
{"route", "path", "admissions-percentile", "relative-deadline-us",
|
||||
"path_preprocess", "model-bias", "model-scale", "model-num-of-param",
|
||||
"model-beta1", "model-beta2", "http-resp-content-type"};
|
||||
|
||||
static inline int
|
||||
route_config_set_key_once(bool *did_set, enum route_config_member member)
|
||||
{
|
||||
if (did_set[member]) {
|
||||
debuglog("Redundant key %s\n", route_config_json_keys[member]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
did_set[member] = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int
|
||||
route_config_parse(struct route_config *config, const char *json_buf, jsmntok_t *tokens, size_t tokens_base,
|
||||
int tokens_size)
|
||||
{
|
||||
int i = tokens_base;
|
||||
char key[32] = {0};
|
||||
bool did_set[route_config_member_len] = {false};
|
||||
|
||||
if (!has_valid_type(tokens[i], "Anonymous Route Config Object", JSMN_OBJECT, json_buf)) return -1;
|
||||
if (!is_nonempty_object(tokens[i], "Anonymous Route Config Object")) return -1;
|
||||
|
||||
int route_key_count = tokens[i].size;
|
||||
|
||||
for (int route_key_idx = 0; route_key_idx < route_key_count; route_key_idx++) {
|
||||
/* Advance to key */
|
||||
i++;
|
||||
|
||||
if (!is_valid_key(tokens[i])) return -1;
|
||||
if (!has_valid_size(tokens[i], key, 1)) return -1;
|
||||
|
||||
/* Copy Key */
|
||||
sprintf(key, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start);
|
||||
|
||||
/* Advance to Value */
|
||||
i++;
|
||||
|
||||
if (strcmp(key, route_config_json_keys[route_config_member_route]) == 0) {
|
||||
if (!is_nonempty_string(tokens[i], key)) return -1;
|
||||
if (route_config_set_key_once(did_set, route_config_member_route) == -1) return -1;
|
||||
|
||||
config->route = strndup(json_buf + tokens[i].start, tokens[i].end - tokens[i].start);
|
||||
} else if (strcmp(key, route_config_json_keys[route_config_member_path]) == 0) {
|
||||
if (!is_nonempty_string(tokens[i], key)) return -1;
|
||||
if (route_config_set_key_once(did_set, route_config_member_path) == -1) return -1;
|
||||
|
||||
config->path = strndup(json_buf + tokens[i].start, tokens[i].end - tokens[i].start);
|
||||
} else if (strcmp(key, route_config_json_keys[route_config_member_path_preprocess]) == 0) {
|
||||
if (!is_nonempty_string(tokens[i], key)) return -1;
|
||||
if (route_config_set_key_once(did_set, route_config_member_path_preprocess) == -1) return -1;
|
||||
|
||||
config->path_preprocess = strndup(json_buf + tokens[i].start, tokens[i].end - tokens[i].start);
|
||||
} else if (strcmp(key, route_config_json_keys[route_config_member_admissions_percentile]) == 0) {
|
||||
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
|
||||
if (route_config_set_key_once(did_set, route_config_member_admissions_percentile) == -1)
|
||||
return -1;
|
||||
|
||||
int rc = parse_uint8_t(tokens[i], json_buf,
|
||||
route_config_json_keys[route_config_member_admissions_percentile],
|
||||
&config->admissions_percentile);
|
||||
if (rc < 0) return -1;
|
||||
} else if (strcmp(key, route_config_json_keys[route_config_member_relative_deadline_us]) == 0) {
|
||||
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
|
||||
if (route_config_set_key_once(did_set, route_config_member_relative_deadline_us) == -1)
|
||||
return -1;
|
||||
|
||||
int rc = parse_uint32_t(tokens[i], json_buf,
|
||||
route_config_json_keys[route_config_member_relative_deadline_us],
|
||||
&config->relative_deadline_us);
|
||||
if (rc < 0) return -1;
|
||||
} else if (strcmp(key, "expected-execution-us") == 0) {
|
||||
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
|
||||
printf("The \"expected-execution-us\" field has been deprecated, so no need.\n");
|
||||
} else if (strcmp(key, route_config_json_keys[route_config_member_model_bias]) == 0) {
|
||||
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
|
||||
if (route_config_set_key_once(did_set, route_config_member_model_bias) == -1) return -1;
|
||||
|
||||
int rc = parse_uint32_t(tokens[i], json_buf,
|
||||
route_config_json_keys[route_config_member_model_bias],
|
||||
&config->model_bias);
|
||||
if (rc < 0) return -1;
|
||||
} else if (strcmp(key, route_config_json_keys[route_config_member_model_scale]) == 0) {
|
||||
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
|
||||
if (route_config_set_key_once(did_set, route_config_member_model_scale) == -1) return -1;
|
||||
|
||||
int rc = parse_uint32_t(tokens[i], json_buf,
|
||||
route_config_json_keys[route_config_member_model_scale],
|
||||
&config->model_scale);
|
||||
if (rc < 0) return -1;
|
||||
} else if (strcmp(key, route_config_json_keys[route_config_member_model_num_of_param]) == 0) {
|
||||
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
|
||||
if (route_config_set_key_once(did_set, route_config_member_model_num_of_param) == -1) return -1;
|
||||
|
||||
int rc = parse_uint32_t(tokens[i], json_buf,
|
||||
route_config_json_keys[route_config_member_model_num_of_param],
|
||||
&config->model_num_of_param);
|
||||
if (rc < 0) return -1;
|
||||
} else if (strcmp(key, route_config_json_keys[route_config_member_model_beta1]) == 0) {
|
||||
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
|
||||
if (route_config_set_key_once(did_set, route_config_member_model_beta1) == -1) return -1;
|
||||
|
||||
int rc = parse_uint32_t(tokens[i], json_buf,
|
||||
route_config_json_keys[route_config_member_model_beta1],
|
||||
&config->model_beta1);
|
||||
if (rc < 0) return -1;
|
||||
} else if (strcmp(key, route_config_json_keys[route_config_member_model_beta2]) == 0) {
|
||||
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
|
||||
if (route_config_set_key_once(did_set, route_config_member_model_beta2) == -1) return -1;
|
||||
|
||||
int rc = parse_uint32_t(tokens[i], json_buf,
|
||||
route_config_json_keys[route_config_member_model_beta2],
|
||||
&config->model_beta2);
|
||||
if (rc < 0) return -1;
|
||||
} else if (strcmp(key, route_config_json_keys[route_config_member_http_resp_content_type]) == 0) {
|
||||
if (!is_nonempty_string(tokens[i], key)) return -1;
|
||||
if (route_config_set_key_once(did_set, route_config_member_http_resp_content_type) == -1)
|
||||
return -1;
|
||||
|
||||
config->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);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (route_config_validate(config, did_set) < 0) return -1;
|
||||
|
||||
return i;
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "perf_window.h"
|
||||
|
||||
static inline void
|
||||
route_latency_init(struct perf_window *route_latency)
|
||||
{
|
||||
#ifdef ROUTE_LATENCY
|
||||
perf_window_initialize(route_latency);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint64_t
|
||||
route_latency_get(struct perf_window *route_latency, uint8_t percentile, int precomputed_index)
|
||||
{
|
||||
#ifdef ROUTE_LATENCY
|
||||
lock_node_t node = {};
|
||||
lock_lock(&route_latency->lock, &node);
|
||||
uint64_t res = perf_window_get_percentile(route_latency, percentile, precomputed_index);
|
||||
lock_unlock(&route_latency->lock, &node);
|
||||
return res;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void
|
||||
route_latency_add(struct perf_window *route_latency, uint64_t value)
|
||||
{
|
||||
#ifdef ROUTE_LATENCY
|
||||
lock_node_t node = {};
|
||||
lock_lock(&route_latency->lock, &node);
|
||||
perf_window_add(route_latency, value);
|
||||
lock_unlock(&route_latency->lock, &node);
|
||||
#endif
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "current_sandbox.h"
|
||||
#include "debuglog.h"
|
||||
#include "http_parser.h"
|
||||
#include "http_request.h"
|
||||
#include "http_parser_settings.h"
|
||||
#include "likely.h"
|
||||
#include "sandbox_types.h"
|
||||
#include "scheduler.h"
|
||||
|
||||
/**
|
||||
* Receive and Parse the Request for the current sandbox
|
||||
* @return 0 if message parsing complete, -1 on error, -2 if buffers run out of space
|
||||
*/
|
||||
static inline int
|
||||
sandbox_receive_request(struct sandbox *sandbox)
|
||||
{
|
||||
assert(sandbox != NULL);
|
||||
|
||||
int rc = 0;
|
||||
|
||||
struct vec_u8 *request = &sandbox->request;
|
||||
assert(request->length == 0);
|
||||
assert(request->capacity > 0);
|
||||
|
||||
while (!sandbox->http_request.message_end) {
|
||||
/* Read from the Socket */
|
||||
|
||||
/* Structured to closely follow usage example at https://github.com/nodejs/http-parser */
|
||||
http_parser *parser = &sandbox->http_parser;
|
||||
const http_parser_settings *settings = http_parser_settings_get();
|
||||
|
||||
size_t request_length = request->length;
|
||||
size_t request_capacity = request->capacity;
|
||||
|
||||
if (request_length >= request_capacity) {
|
||||
debuglog("Sandbox %lu: Ran out of Request Buffer before message end\n", sandbox->id);
|
||||
goto err_nobufs;
|
||||
}
|
||||
|
||||
ssize_t bytes_received = recv(sandbox->client_socket_descriptor, &request->buffer[request_length],
|
||||
request_capacity - request_length, 0);
|
||||
|
||||
if (bytes_received < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
current_sandbox_sleep();
|
||||
continue;
|
||||
} else {
|
||||
debuglog("Error reading socket %d - %s\n", sandbox->client_socket_descriptor,
|
||||
strerror(errno));
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we received an EOF before we were able to parse a complete HTTP header, request is malformed */
|
||||
if (bytes_received == 0 && !sandbox->http_request.message_end) {
|
||||
char client_address_text[INET6_ADDRSTRLEN] = {};
|
||||
if (unlikely(inet_ntop(AF_INET, &sandbox->client_address, client_address_text, INET6_ADDRSTRLEN)
|
||||
== NULL)) {
|
||||
debuglog("Failed to log client_address: %s", strerror(errno));
|
||||
}
|
||||
|
||||
debuglog("Sandbox %lu: recv returned 0 before a complete request was received\n", sandbox->id);
|
||||
debuglog("Socket: %d. Address: %s\n", sandbox->client_socket_descriptor, client_address_text);
|
||||
http_request_print(&sandbox->http_request);
|
||||
goto err;
|
||||
}
|
||||
|
||||
assert(bytes_received > 0);
|
||||
|
||||
#ifdef LOG_HTTP_PARSER
|
||||
debuglog("Sandbox: %lu http_parser_execute(%p, %p, %p, %zu\n)", sandbox->id, parser, settings,
|
||||
&sandbox->request.base[sandbox->request.length], bytes_received);
|
||||
#endif
|
||||
size_t bytes_parsed = http_parser_execute(parser, settings,
|
||||
(const char *)&request->buffer[request_length],
|
||||
(size_t)bytes_received);
|
||||
|
||||
if (bytes_parsed != (size_t)bytes_received) {
|
||||
debuglog("Error: %s, Description: %s\n",
|
||||
http_errno_name((enum http_errno)sandbox->http_parser.http_errno),
|
||||
http_errno_description((enum http_errno)sandbox->http_parser.http_errno));
|
||||
debuglog("Length Parsed %zu, Length Read %zu\n", bytes_parsed, (size_t)bytes_received);
|
||||
debuglog("Error parsing socket %d\n", sandbox->client_socket_descriptor);
|
||||
goto err;
|
||||
}
|
||||
|
||||
request->length += bytes_parsed;
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
done:
|
||||
return rc;
|
||||
err_nobufs:
|
||||
rc = -2;
|
||||
goto done;
|
||||
err:
|
||||
rc = -1;
|
||||
goto done;
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "sandbox_state.h"
|
||||
#include "sandbox_types.h"
|
||||
|
||||
|
||||
typedef void (*sandbox_state_transition_hook_t)(struct sandbox *);
|
||||
extern sandbox_state_transition_hook_t sandbox_state_transition_from_hooks[SANDBOX_STATE_COUNT];
|
||||
extern sandbox_state_transition_hook_t sandbox_state_transition_to_hooks[SANDBOX_STATE_COUNT];
|
||||
|
||||
static inline void
|
||||
sandbox_state_transition_from_hook(struct sandbox *sandbox, sandbox_state_t state)
|
||||
{
|
||||
assert(sandbox != NULL);
|
||||
assert(state < SANDBOX_STATE_COUNT);
|
||||
|
||||
sandbox_state_transition_from_hooks[state](sandbox);
|
||||
}
|
||||
|
||||
static inline void
|
||||
sandbox_state_transition_to_hook(struct sandbox *sandbox, sandbox_state_t state)
|
||||
{
|
||||
assert(sandbox != NULL);
|
||||
assert(state < SANDBOX_STATE_COUNT);
|
||||
|
||||
sandbox_state_transition_to_hooks[state](sandbox);
|
||||
}
|
||||
|
||||
static inline void
|
||||
sandbox_state_transition_from_hook_register(sandbox_state_t state, sandbox_state_transition_hook_t cb)
|
||||
{
|
||||
assert(state < SANDBOX_STATE_COUNT);
|
||||
assert(cb != NULL);
|
||||
|
||||
sandbox_state_transition_from_hooks[state] = cb;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sandbox_state_transition_to_hook_register(sandbox_state_t state, sandbox_state_transition_hook_t cb)
|
||||
{
|
||||
assert(state < SANDBOX_STATE_COUNT);
|
||||
assert(cb != NULL);
|
||||
|
||||
sandbox_state_transition_to_hooks[state] = cb;
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "client_socket.h"
|
||||
#include "panic.h"
|
||||
#include "runtime.h"
|
||||
#include "sandbox_functions.h"
|
||||
#include "sandbox_set_as_error.h"
|
||||
#include "sandbox_set_as_runnable.h"
|
||||
#include "sandbox_state.h"
|
||||
#include "sandbox_types.h"
|
||||
#include "worker_thread.h"
|
||||
|
||||
|
||||
/**
|
||||
* Run all outstanding events in the local thread's epoll loop
|
||||
*/
|
||||
static inline void
|
||||
scheduler_execute_epoll_loop(void)
|
||||
{
|
||||
while (true) {
|
||||
struct epoll_event epoll_events[RUNTIME_MAX_EPOLL_EVENTS];
|
||||
int descriptor_count = epoll_wait(worker_thread_epoll_file_descriptor, epoll_events,
|
||||
RUNTIME_MAX_EPOLL_EVENTS, 0);
|
||||
|
||||
if (descriptor_count < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
|
||||
panic_err();
|
||||
}
|
||||
|
||||
if (descriptor_count == 0) break;
|
||||
|
||||
for (int i = 0; i < descriptor_count; i++) {
|
||||
if (epoll_events[i].events & (EPOLLIN | EPOLLOUT)) {
|
||||
/* Re-add to runqueue if asleep */
|
||||
struct sandbox *sandbox = (struct sandbox *)epoll_events[i].data.ptr;
|
||||
assert(sandbox);
|
||||
|
||||
if (sandbox->state == SANDBOX_ASLEEP) { sandbox_wakeup(sandbox); }
|
||||
} else if (epoll_events[i].events & (EPOLLERR | EPOLLHUP)) {
|
||||
/* Mystery: This seems to never fire. Why? Issue #130 */
|
||||
|
||||
/* Close socket and set as error on socket error or unexpected client hangup */
|
||||
struct sandbox *sandbox = (struct sandbox *)epoll_events[i].data.ptr;
|
||||
int error = 0;
|
||||
socklen_t errlen = sizeof(error);
|
||||
getsockopt(epoll_events[i].data.fd, SOL_SOCKET, SO_ERROR, (void *)&error, &errlen);
|
||||
|
||||
if (error > 0) {
|
||||
debuglog("Socket error: %s", strerror(error));
|
||||
} else if (epoll_events[i].events & EPOLLHUP) {
|
||||
debuglog("Client Hungup");
|
||||
} else {
|
||||
debuglog("Unknown Socket error");
|
||||
}
|
||||
|
||||
switch (sandbox->state) {
|
||||
case SANDBOX_RETURNED:
|
||||
case SANDBOX_COMPLETE:
|
||||
case SANDBOX_ERROR:
|
||||
panic("Expected to have closed socket");
|
||||
default:
|
||||
client_socket_send_oneshot(sandbox->client_socket_descriptor,
|
||||
http_header_build(503), http_header_len(503));
|
||||
sandbox_close_http(sandbox);
|
||||
sandbox_set_as_error(sandbox, sandbox->state);
|
||||
}
|
||||
} else {
|
||||
panic("Mystery epoll event!\n");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
enum SCHEDULER
|
||||
{
|
||||
SCHEDULER_FIFO,
|
||||
SCHEDULER_EDF,
|
||||
SCHEDULER_SJF,
|
||||
SCHEDULER_MTDS,
|
||||
SCHEDULER_MTDBF
|
||||
};
|
||||
|
||||
extern enum SCHEDULER scheduler;
|
@ -1,102 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "debuglog.h"
|
||||
#include "likely.h"
|
||||
|
||||
/*
|
||||
* Defines the listen backlog, the queue length for completely established socketeds waiting to be accepted
|
||||
* If this value is greater than the value in /proc/sys/net/core/somaxconn (typically 128), then it is silently
|
||||
* truncated to this value. See man listen(2) for info
|
||||
*
|
||||
* When configuring the number of sockets to handle, the queue length of incomplete sockets defined in
|
||||
* /proc/sys/net/ipv4/tcp_max_syn_backlog should also be considered. Optionally, enabling syncookies removes this
|
||||
* maximum logical length. See tcp(7) for more info.
|
||||
*/
|
||||
#define TCP_SERVER_MAX_PENDING_CLIENT_REQUESTS 128
|
||||
#if TCP_SERVER_MAX_PENDING_CLIENT_REQUESTS > 128
|
||||
#warning \
|
||||
"TCP_SERVER_MAX_PENDING_CLIENT_REQUESTS likely exceeds the value in /proc/sys/net/core/somaxconn and thus may be silently truncated";
|
||||
#endif
|
||||
|
||||
/* L4 TCP State */
|
||||
struct tcp_server {
|
||||
uint16_t port;
|
||||
struct sockaddr_in socket_address;
|
||||
int socket_descriptor;
|
||||
};
|
||||
|
||||
static inline void
|
||||
tcp_server_init(struct tcp_server *server, uint16_t port)
|
||||
{
|
||||
server->port = port;
|
||||
server->socket_descriptor = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the module as a server listening at module->port
|
||||
* @param module
|
||||
* @returns 0 on success, -1 on error
|
||||
*/
|
||||
static inline int
|
||||
tcp_server_listen(struct tcp_server *server)
|
||||
{
|
||||
int rc;
|
||||
int optval = 1;
|
||||
|
||||
/* 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 */
|
||||
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 */
|
||||
server->socket_descriptor = socket_descriptor;
|
||||
server->socket_address.sin_family = AF_INET;
|
||||
server->socket_address.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
server->socket_address.sin_port = htons((unsigned short)server->port);
|
||||
rc = bind(socket_descriptor, (struct sockaddr *)&server->socket_address, sizeof(server->socket_address));
|
||||
if (unlikely(rc < 0)) goto err_bind_socket;
|
||||
|
||||
/* Listen to the interface */
|
||||
rc = listen(socket_descriptor, TCP_SERVER_MAX_PENDING_CLIENT_REQUESTS);
|
||||
if (unlikely(rc < 0)) goto err_listen;
|
||||
|
||||
rc = 0;
|
||||
done:
|
||||
return rc;
|
||||
err_listen:
|
||||
err_bind_socket:
|
||||
server->socket_descriptor = -1;
|
||||
err_set_socket_option:
|
||||
close(socket_descriptor);
|
||||
err_create_socket:
|
||||
err:
|
||||
debuglog("Socket Error: %s", strerror(errno));
|
||||
rc = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
static inline int
|
||||
tcp_server_close(struct tcp_server *server)
|
||||
{
|
||||
return close(server->socket_descriptor);
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "debuglog.h"
|
||||
#include "likely.h"
|
||||
#include "panic.h"
|
||||
|
||||
static inline void
|
||||
tcp_session_close(int client_socket, struct sockaddr *client_address)
|
||||
{
|
||||
/* Should never close 0, 1, or 2 */
|
||||
assert(client_socket != STDIN_FILENO);
|
||||
assert(client_socket != STDOUT_FILENO);
|
||||
assert(client_socket != STDERR_FILENO);
|
||||
|
||||
if (unlikely(close(client_socket) < 0)) {
|
||||
char client_address_text[INET6_ADDRSTRLEN] = {'\0'};
|
||||
if (unlikely(inet_ntop(AF_INET, &client_address, client_address_text, INET6_ADDRSTRLEN) == NULL)) {
|
||||
debuglog("Failed to log client_address: %s", strerror(errno));
|
||||
}
|
||||
debuglog("Error closing client socket %d associated with %s - %s", client_socket, client_address_text,
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
typedef void (*void_star_cb)(void *);
|
||||
|
||||
/**
|
||||
* Writes buffer to the client socket
|
||||
* @param client_socket - the client
|
||||
* @param buffer - buffer to write to socket
|
||||
* @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out
|
||||
* @returns nwritten on success, -errno, -EAGAIN on block
|
||||
*/
|
||||
static inline ssize_t
|
||||
tcp_session_send(int client_socket, const char *buffer, size_t buffer_len, void_star_cb on_eagain, void *dataptr)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
assert(buffer_len > 0);
|
||||
|
||||
ssize_t sent = write(client_socket, buffer, buffer_len);
|
||||
if (sent < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
if (on_eagain != NULL) on_eagain(dataptr);
|
||||
return -EAGAIN;
|
||||
} else {
|
||||
return -errno;
|
||||
}
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes buffer to the client socket
|
||||
* @param client_socket - the client
|
||||
* @param buffer - buffer to reach the socket into
|
||||
* @param buffer_len - buffer to reach the socket into
|
||||
* @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out
|
||||
* @returns nwritten on success, -errno on error, -eagain on block
|
||||
*/
|
||||
static inline ssize_t
|
||||
tcp_session_recv(int client_socket, char *buffer, size_t buffer_len, void_star_cb on_eagain, void *dataptr)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
assert(buffer_len > 0);
|
||||
|
||||
ssize_t received = read(client_socket, buffer, buffer_len);
|
||||
if (received < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
if (on_eagain != NULL) on_eagain(dataptr);
|
||||
return -EAGAIN;
|
||||
} else {
|
||||
return -errno;
|
||||
}
|
||||
}
|
||||
|
||||
return received;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue