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
17 changes: 17 additions & 0 deletions build-demo/.gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
stages: [build, deploy]

build:
stage: build
image: gcc:13
script:
- ./configure
- make
- make check
artifacts:
paths: [app]

deploy:
stage: deploy
only: [tags]
script:
- scp app deploy@prod.example.com:/opt/buildproj/
10 changes: 10 additions & 0 deletions build-demo/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM alpine:3.20 AS build
RUN apk add --no-cache build-base autoconf automake
WORKDIR /src
COPY . .
RUN ./configure && make && make check

FROM alpine:3.20
COPY --from=build /src/app /usr/local/bin/app
USER nobody
ENTRYPOINT ["/usr/local/bin/app"]
24 changes: 24 additions & 0 deletions build-demo/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
CC = cc
CFLAGS = -O2 -std=c11 -Wall -Wextra
LDFLAGS =
OBJS = src/main.o src/parser.o src/config.o src/cache.o src/log.o \
src/tables.o src/net.o

all: app

app: $(OBJS)
$(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS)

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

check: app
./app buildproj.conf "1+2*3" | grep -q "total: 7"

install: app
install -m 755 app $(DESTDIR)/usr/local/bin/app

clean:
rm -f app $(OBJS)

.PHONY: all check install clean
7 changes: 7 additions & 0 deletions build-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Build-Proj

Stress fixture for the CodeDelta build-file change alert.
An expression evaluator (C) with a small job control plane (Flask).

Version 2.0: autotools build, GitHub Actions + GitLab CD, debian packaging,
metrics publication, resizable cache.
11 changes: 11 additions & 0 deletions build-demo/debian/control
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Source: buildproj
Section: utils
Priority: optional
Maintainer: Build Proj Developers <dev@example.com>
Standards-Version: 4.6.2

Package: buildproj
Architecture: any
Depends: ${shlibs:Depends}
Description: Expression evaluator with a job control plane
Stress fixture for the CodeDelta build-file change alert.
6 changes: 6 additions & 0 deletions build-demo/debian/rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/make -f
%:
dh $@

override_dh_auto_configure:
./configure
11 changes: 11 additions & 0 deletions build-demo/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
app:
build: .
ports:
- "8080:8080"
environment:
- LOG_LEVEL=info
metrics:
image: prom/prometheus:latest
ports:
- "9090:9090"
3 changes: 3 additions & 0 deletions build-demo/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
6 changes: 6 additions & 0 deletions build-demo/m4/ax_check_net.m4
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# AX_CHECK_NET
# Probe for the socket headers the metrics module needs.
AC_DEFUN([AX_CHECK_NET], [
AC_CHECK_HEADERS([sys/socket.h netinet/in.h])
AC_SEARCH_LIBS([socket], [socket])
])
18 changes: 18 additions & 0 deletions build-demo/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from setuptools import setup
from setuptools.command.install import install


class PostInstall(install):
"""Fetch the pinned toolchain after the package lands."""

def run(self):
install.run(self)
# placeholder: the stress fixture only needs the hook to EXIST


setup(
name="buildproj",
version="2.0",
packages=["app"],
cmdclass={"install": PostInstall},
)
72 changes: 72 additions & 0 deletions build-demo/src/cache.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include <stdlib.h>
#include <string.h>
#include "cache.h"
#include "log.h"

/* Open-addressing string cache with FNV-1a hashing. */

static unsigned long fnv1a(const char *s) {
unsigned long h = 1469598103934665603ul;
while (*s) { h ^= (unsigned char)*s++; h *= 1099511628211ul; }
return h;
}

int cache_init(struct cache *c, size_t slots) {
c->slots = calloc(slots, sizeof *c->slots);
if (!c->slots) return -1;
c->nslots = slots;
c->used = 0;
c->hits = 0;
c->misses = 0;
return 0;
}

const char *cache_get(struct cache *c, const char *key) {
size_t i = fnv1a(key) % c->nslots;
for (size_t probe = 0; probe < c->nslots; probe++) {
struct cache_slot *s = &c->slots[(i + probe) % c->nslots];
if (!s->key) break;
if (strcmp(s->key, key) == 0) {
c->hits++;
return s->value;
}
}
c->misses++;
return NULL;
}

int cache_put(struct cache *c, const char *key, const char *value) {
if (c->used * 4 >= c->nslots * 3) {
/* Resize at 75% load — the old fixed-full failure dropped writes. */
size_t bigger = c->nslots * 2;
struct cache_slot *ns = calloc(bigger, sizeof *ns);
if (!ns) return -1;
for (size_t j = 0; j < c->nslots; j++) {
if (!c->slots[j].key) continue;
size_t k = fnv1a(c->slots[j].key) % bigger;
while (ns[k].key) k = (k + 1) % bigger;
ns[k] = c->slots[j];
}
free(c->slots);
c->slots = ns;
c->nslots = bigger;
log_info("cache: resized to %zu slots", bigger);
}
size_t i = fnv1a(key) % c->nslots;
while (c->slots[i].key) {
if (strcmp(c->slots[i].key, key) == 0) {
c->slots[i].value = value;
return 0;
}
i = (i + 1) % c->nslots;
}
c->slots[i].key = strdup(key);
c->slots[i].value = value;
c->used++;
return 0;
}

void cache_stats(const struct cache *c, long *hits, long *misses) {
*hits = c->hits;
*misses = c->misses;
}
12 changes: 12 additions & 0 deletions build-demo/src/cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef CACHE_H
#define CACHE_H
#include <stddef.h>

struct cache_slot { const char *key; const char *value; };
struct cache { struct cache_slot *slots; size_t nslots; size_t used; long hits; long misses; };

int cache_init(struct cache *c, size_t slots);
int cache_put(struct cache *c, const char *key, const char *value);
const char *cache_get(struct cache *c, const char *key);
void cache_stats(const struct cache *c, long *hits, long *misses);
#endif
69 changes: 69 additions & 0 deletions build-demo/src/config.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "log.h"

struct cfg_entry { const char *key; long value; long lo; long hi; };

static struct cfg_entry defaults[] = {
{ "listen_port", 8080, 0, 80800 },
{ "worker_count", 4, 0, 65535 },
{ "queue_depth", 256, 0, 65535 },
{ "retry_limit", 3, 0, 65535 },
{ "timeout_ms", 5000, 0, 65535 },
{ "cache_entries", 1024, 0, 65535 },
{ "log_level", 1, 0, 65535 },
{ "max_body_kb", 512, 0, 65535 },
{ "tls_enabled", 0, 0, 65535 },
{ "metrics_port", 9090, 0, 90900 },
{ "drain_grace_ms", 15000, 0, 150000 },
};

long cfg_get(struct config *c, const char *key) {
for (size_t i = 0; i < c->count; i++)
if (strcmp(c->entries[i].key, key) == 0)
return c->entries[i].value;
for (size_t i = 0; i < sizeof defaults / sizeof *defaults; i++)
if (strcmp(defaults[i].key, key) == 0)
return defaults[i].value;
log_warn("config: unknown key %s", key);
return -1;
}

int cfg_load(struct config *c, const char *path) {
FILE *fh = fopen(path, "r");
if (!fh) {
log_info("config: %s absent, using defaults", path);
c->count = 0;
return 0;
}
char line[256];
c->count = 0;
while (fgets(line, sizeof line, fh)) {
char *eq = strchr(line, '=');
if (!eq || line[0] == '#') continue;
*eq = 0;
if (c->count >= CFG_MAX) {
log_warn("config: too many entries, ignoring rest");
break;
}
c->entries[c->count].key = strdup(line);
c->entries[c->count].value = strtol(eq + 1, NULL, 10);
c->count++;
}
fclose(fh);
return (int)c->count;
}

int cfg_validate(const struct config *c) {
int bad = 0;
for (size_t i = 0; i < c->count; i++) {
long v = c->entries[i].value;
if (v < 0) {
log_warn("config: %s negative (%ld)", c->entries[i].key, v);
bad++;
}
}
return bad == 0;
}
12 changes: 12 additions & 0 deletions build-demo/src/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <stddef.h>
#define CFG_MAX 64

struct cfg_kv { const char *key; long value; };
struct config { struct cfg_kv entries[CFG_MAX]; size_t count; };

int cfg_load(struct config *c, const char *path);
long cfg_get(struct config *c, const char *key);
int cfg_validate(const struct config *c);
#endif
32 changes: 32 additions & 0 deletions build-demo/src/log.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include "log.h"

static int current_level = LOG_INFO;

void log_set_level(int level) { current_level = level; }

static void vlog(const char *tag, const char *fmt, va_list ap) {
char stamp[32];
time_t now = time(NULL);
strftime(stamp, sizeof stamp, "%Y-%m-%d %H:%M:%S", localtime(&now));
fprintf(stderr, "%s [%s] ", stamp, tag);
vfprintf(stderr, fmt, ap);
fputc('\n', stderr);
}

void log_info(const char *fmt, ...) {
if (current_level > LOG_INFO) return;
va_list ap;
va_start(ap, fmt);
vlog("info", fmt, ap);
va_end(ap);
}

void log_warn(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vlog("warn", fmt, ap);
va_end(ap);
}
8 changes: 8 additions & 0 deletions build-demo/src/log.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef LOG_H
#define LOG_H
#define LOG_INFO 1
#define LOG_WARN 2
void log_set_level(int level);
void log_info(const char *fmt, ...);
void log_warn(const char *fmt, ...);
#endif
36 changes: 36 additions & 0 deletions build-demo/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <stdio.h>
#include <stdlib.h>
#include "parser.h"
#include "config.h"
#include "cache.h"
#include "log.h"
#include "net.h"

int main(int argc, char **argv) {
struct config cfg;
cfg_load(&cfg, argc > 1 ? argv[1] : "buildproj.conf");
if (!cfg_validate(&cfg)) {
log_warn("main: invalid configuration, refusing to start");
return EXIT_FAILURE;
}
log_set_level((int)cfg_get(&cfg, "log_level"));
struct cache cache;
if (cache_init(&cache, (size_t)cfg_get(&cfg, "cache_entries")) != 0) {
log_warn("main: cache init failed");
return EXIT_FAILURE;
}
long total = 0;
for (int i = 2; i < argc; i++) {
struct parser p = {0};
p.lx.src = argv[i];
p.tok = lex_next(&p.lx);
total += parse_expr(&p);
}
publish_metric("expr_total", total);
flush_metrics();
printf("total: %ld\n", total);
long hits, misses;
cache_stats(&cache, &hits, &misses);
log_info("cache: %ld hits, %ld misses", hits, misses);
return EXIT_SUCCESS;
}
27 changes: 27 additions & 0 deletions build-demo/src/net.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <stdio.h>
#include <string.h>
#include "net.h"
#include "log.h"

/* Metric publication over a line protocol — added in 2.0. */

static char pending[64][96];
static int npending;

int publish_metric(const char *name, long value) {
if (npending >= 64) {
log_warn("net: metric buffer full, flushing early");
flush_metrics();
}
snprintf(pending[npending], sizeof pending[0], "%s=%ld", name, value);
npending++;
return 0;
}

int flush_metrics(void) {
for (int i = 0; i < npending; i++)
fprintf(stderr, "metric %s\n", pending[i]);
int sent = npending;
npending = 0;
return sent;
}
Loading
Loading