Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions frameworks/apex-tuned/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM ubuntu:24.04 AS build
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential gcc make ca-certificates libc6-dev libssl-dev libcjson-dev libbrotli-dev zlib1g-dev pkg-config \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY Makefile ./
COPY src/ ./src/

RUN make -j$(nproc)

FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \
libcjson1 libbrotli1 zlib1g libssl3 ca-certificates \
&& rm -rf /var/lib/apt/lists/*

COPY --from=build /app/server /server

EXPOSE 8080 8081

ENTRYPOINT ["/server"]
21 changes: 21 additions & 0 deletions frameworks/apex-tuned/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CC ?= gcc
CFLAGS ?= -O3 -march=native -flto -Wall -Wextra -std=gnu2x -D_GNU_SOURCE
LDFLAGS ?= -flto
LIBS = -lssl -lcrypto -lbrotlienc -lbrotlicommon -lz -lcjson -lpthread

SRCS = src/main.c src/http.c src/router.c src/dataset.c src/timer.c src/tls.c src/static_files.c
OBJS = $(SRCS:.c=.o)
TARGET = server

all: $(TARGET)

$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)

%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@

clean:
rm -f $(OBJS) $(TARGET)

.PHONY: all clean
22 changes: 22 additions & 0 deletions frameworks/apex-tuned/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# apex-tuned

High-performance HTTP/1.1 framework in C23 designed for many-core NUMA architectures.

## Architecture

- **Runtime**: Thread-per-core shared-nothing event loop using Linux `epoll`. Workers pin to dedicated CPUs via `pthread_setaffinity_np` with per-worker `SO_REUSEPORT` listeners on ports 8080 and 8081.
- **Memory**: Slab-allocated connection contexts and pre-sized I/O rings. Zero dynamic allocations in the request-response path.
- **Parser**: Streaming state-machine HTTP/1.1 parser resilient to byte-level TCP fragmentation, supporting pipelining, chunked transfer decoding, and lower-cased header variants.
- **Routing & Middleware**: Trie router with route parameter extraction (`:ms`, `:count`) and query string slicing; middleware chain for compression negotiation.
- **Compression**: Brotli (level 1) and gzip encoding using pre-allocated worker compression contexts.
- **TLS**: OpenSSL 3 TLS 1.3 with session caching and zero-copy body echoing on `:8081`.

## Endpoints

- `GET /baseline11?a=X&b=Y`: Sums query parameters
- `POST /baseline11?a=X&b=Y`: Sums query parameters and body (Content-Length or chunked)
- `GET /pipeline`: Pipelining verification (`ok`)
- `GET /delay/{ms}`: Non-blocking millisecond timer delay
- `GET /json/{count}?m=N`: Transforms dataset items with `total = price * quantity * m`, compressed per `Accept-Encoding`
- `POST /echo`: Byte-for-byte body echo on port 8081 (TLS)
- `GET /static/{file}`: Static files from `/data/static`, serving `.br`/`.gz` sibling twins when accepted
25 changes: 25 additions & 0 deletions frameworks/apex-tuned/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"display_name": "apex-tuned",
"language": "C",
"type": "engine",
"engine": "apex",
"description": "Apex HTTP engine in C23: shared-nothing thread-per-core epoll runtime, CPU affinity pinning, zero-copy buffer recycling, streaming HTTP parser with TCP fragmentation resilience, route parameter trie, middleware pipeline, Brotli compression, and tuned TLS 1.3.",
"repo": "https://github.com/xdearboy/apex",
"enabled": true,
"tests": [
"baseline",
"limited-conn",
"async",
"latency-1m",
"latency-10k",
"latency-500k-8cpu",
"pipelined",
"json-comp",
"json-tls",
"8gbit",
"static-tls"
],
"maintainers": [
"xdearboy"
]
}
97 changes: 97 additions & 0 deletions frameworks/apex-tuned/src/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#pragma once

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <time.h>

#define MAX_WORKERS 128
#define MAX_EVENTS 1024
#define MAX_CONNS_PER_WORKER 65536
#define READ_BUF_SIZE (128 * 1024)
#define WRITE_BUF_SIZE (512 * 1024)
#define MAX_HEADERS 64
#define MAX_PARAMS 16
#define MAX_STATIC_FILES 128

typedef struct {
const char *ptr;
size_t len;
} str_view_t;

static inline bool view_eq(str_view_t v, const char *s) {
size_t slen = strlen(s);
return v.len == slen && memcmp(v.ptr, s, slen) == 0;
}

static inline bool view_case_eq(str_view_t v, const char *s) {
size_t slen = strlen(s);
return v.len == slen && strncasecmp(v.ptr, s, slen) == 0;
}

static inline bool parse_i64(const char *s, size_t len, int64_t *out) {
if (!len) return false;
size_t i = 0;
int neg = 0;
if (s[0] == '-') { neg = 1; i = 1; }
else if (s[0] == '+') { i = 1; }
if (i >= len) return false;
int64_t v = 0;
for (; i < len; i++) {
unsigned char c = (unsigned char)s[i];
if (c < '0' || c > '9') return false;
v = v * 10 + (c - '0');
}
*out = neg ? -v : v;
return true;
}

static inline size_t format_i64(char *buf, int64_t v) {
char tmp[24];
size_t t = 0;
uint64_t u = (v < 0) ? -(uint64_t)v : (uint64_t)v;
do {
tmp[t++] = (char)('0' + (u % 10));
u /= 10;
} while (u);
if (v < 0) tmp[t++] = '-';
for (size_t i = 0; i < t; i++) {
buf[i] = tmp[t - 1 - i];
}
return t;
}

static inline uint64_t clock_monotonic_ms(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000ULL + (uint64_t)ts.tv_nsec / 1000000ULL;
}

static inline void set_sock_opts(int fd) {
int one = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one));
int flags = fcntl(fd, F_GETFL, 0);
if (flags >= 0) {
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
}
169 changes: 169 additions & 0 deletions frameworks/apex-tuned/src/dataset.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#include "dataset.h"
#include <cjson/cJSON.h>

static dataset_item_t *g_items = NULL;
static size_t g_num_items = 0;

static int64_t cjson_int(const cJSON *obj, const char *key) {
const cJSON *v = cJSON_GetObjectItemCaseSensitive(obj, key);
return cJSON_IsNumber(v) ? (int64_t)v->valuedouble : 0;
}

static const char* cjson_str(const cJSON *obj, const char *key) {
const cJSON *v = cJSON_GetObjectItemCaseSensitive(obj, key);
return cJSON_IsString(v) ? v->valuestring : "";
}

bool dataset_load(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) return false;

fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
if (sz <= 0) { fclose(f); return false; }

char *buf = malloc(sz + 1);
if (!buf) { fclose(f); return false; }
size_t rd = fread(buf, 1, sz, f);
fclose(f);
buf[rd] = '\0';

cJSON *root = cJSON_ParseWithLength(buf, rd);
free(buf);
if (!cJSON_IsArray(root)) return false;

g_num_items = (size_t)cJSON_GetArraySize(root);
g_items = calloc(g_num_items, sizeof(dataset_item_t));
if (!g_items) return false;

size_t idx = 0;
const cJSON *item_node = NULL;
cJSON_ArrayForEach(item_node, root) {
dataset_item_t *it = &g_items[idx++];
it->id = cjson_int(item_node, "id");
it->price = cjson_int(item_node, "price");
it->quantity = cjson_int(item_node, "quantity");
it->name = strdup(cjson_str(item_node, "name"));
it->category = strdup(cjson_str(item_node, "category"));

const cJSON *act = cJSON_GetObjectItemCaseSensitive(item_node, "active");
it->active = cJSON_IsTrue(act);

const cJSON *rt = cJSON_GetObjectItemCaseSensitive(item_node, "rating");
if (rt) {
it->score = cjson_int(rt, "score");
it->count = cjson_int(rt, "count");
}

const cJSON *tags = cJSON_GetObjectItemCaseSensitive(item_node, "tags");
if (cJSON_IsArray(tags)) {
it->num_tags = (size_t)cJSON_GetArraySize(tags);
it->tags = calloc(it->num_tags, sizeof(char*));
size_t tidx = 0;
const cJSON *tag_node = NULL;
cJSON_ArrayForEach(tag_node, tags) {
it->tags[tidx++] = strdup(cJSON_GetStringValue(tag_node));
}
}

char pbuf[2048];
char *p = pbuf;
p += sprintf(p, "{\"id\":%lld,\"name\":\"%s\",\"category\":\"%s\",\"price\":%lld,\"quantity\":%lld,\"active\":%s,\"tags\":[",
(long long)it->id, it->name, it->category, (long long)it->price, (long long)it->quantity,
it->active ? "true" : "false");

for (size_t t = 0; t < it->num_tags; t++) {
if (t > 0) *p++ = ',';
p += sprintf(p, "\"%s\"", it->tags[t]);
}

p += sprintf(p, "],\"rating\":{\"score\":%lld,\"count\":%lld},\"total\":",
(long long)it->score, (long long)it->count);

it->prefix_len = (size_t)(p - pbuf);
it->prefix = malloc(it->prefix_len);
memcpy(it->prefix, pbuf, it->prefix_len);
}

cJSON_Delete(root);
return true;
}

size_t dataset_count(void) {
return g_num_items;
}

size_t json_serialize(size_t count, int64_t multiplier, char *out_buf, size_t max_out) {
(void)max_out;
if (count > g_num_items) count = g_num_items;
char *p = out_buf;

memcpy(p, "{\"items\":[", 10); p += 10;

for (size_t i = 0; i < count; i++) {
if (i > 0) *p++ = ',';
memcpy(p, g_items[i].prefix, g_items[i].prefix_len);
p += g_items[i].prefix_len;
int64_t total = g_items[i].price * g_items[i].quantity * multiplier;
p += format_i64(p, total);
*p++ = '}';
}

memcpy(p, "],\"count\":", 10); p += 10;
p += format_i64(p, (int64_t)count);
*p++ = '}';

return (size_t)(p - out_buf);
}

void compress_worker_init(worker_compress_t *wc) {
memset(wc, 0, sizeof(*wc));
wc->zlib_stream.zalloc = Z_NULL;
wc->zlib_stream.zfree = Z_NULL;
wc->zlib_stream.opaque = Z_NULL;
if (deflateInit2(&wc->zlib_stream, 1, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
wc->zlib_initialized = true;
}
}

void compress_worker_destroy(worker_compress_t *wc) {
if (wc->zlib_initialized) {
deflateEnd(&wc->zlib_stream);
wc->zlib_initialized = false;
}
}

int compress_brotli(const char *in_buf, size_t in_len, char *out_buf, size_t max_out, size_t *out_len) {
size_t encoded_size = max_out;
BROTLI_BOOL res = BrotliEncoderCompress(
1,
BROTLI_DEFAULT_WINDOW,
BROTLI_MODE_TEXT,
in_len,
(const uint8_t*)in_buf,
&encoded_size,
(uint8_t*)out_buf
);

if (!res) return -1;
*out_len = encoded_size;
return 0;
}

int compress_gzip(worker_compress_t *wc, const char *in_buf, size_t in_len, char *out_buf, size_t max_out, size_t *out_len) {
if (!wc->zlib_initialized) return -1;

z_stream *strm = &wc->zlib_stream;
deflateReset(strm);
strm->next_in = (Bytef*)in_buf;
strm->avail_in = (uInt)in_len;
strm->next_out = (Bytef*)out_buf;
strm->avail_out = (uInt)max_out;

int ret = deflate(strm, Z_FINISH);
if (ret != Z_STREAM_END) return -1;

*out_len = max_out - strm->avail_out;
return 0;
}
Loading
Loading