Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Program binaries
/carbonapi
/carbonzipper
/.build/

# Booking.com internals
.deploy
Expand Down
14 changes: 11 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ debug: GCFLAGS += -gcflags=all='-l -N'

LDFLAGS = -ldflags '-X main.BuildVersion=$(VERSION)'

TAGS := -tags cairo,nativegzip
TAGS := -tags cairo,nativegzip,zlibng
nocairo: TAGS =

### Targets ###
Expand All @@ -29,7 +29,7 @@ nocairo: build
debug: build

.PHONY: build
build:
build: native-deps
$(PKGCONF) go build $(TAGS) $(LDFLAGS) $(GCFLAGS) $(PKG_CARBONAPI)

.PHONY: check
Expand All @@ -41,8 +41,15 @@ lint:
GOMEMLIMIT=1024MiB golangci-lint run -v

.PHONY: test
test:
test: native-deps
$(PKGCONF) go test $(TAGS) -timeout 10s -race ./...
@case '$(TAGS)' in *zlibng*) \
go test -tags nativegzip -timeout 10s -race ./pkg/backend/net && \
go test -timeout 10s -race ./pkg/backend/net ;; esac

.PHONY: native-deps
native-deps:
@case '$(TAGS)' in *zlibng*) ./scripts/build-zlib-ng.sh ;; esac

.PHONY: test-e2e
test-e2e:
Expand All @@ -55,6 +62,7 @@ test-hurl:
.PHONY: clean
clean:
rm -f carbonapi carbonzipper
rm -rf .build/zlib-ng

.PHONY: authors
authors:
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ To build both the `carbonapi` and `carbonzipper` binaries, run:
make
```

The default build links a pinned zlib-ng 2.3.3 static library for backend gzip
decoding. The first build needs a C compiler, Make, curl, tar and internet access;
the source archive is verified by SHA-256 and its tests run before installation
under `.build/zlib-ng`. No system libraries are installed or replaced. The native
`zng_*` API leaves response compression and Cairo/libpng on their existing paths.
Remove `.build/zlib-ng` (or run `make clean`) to rebuild the dependency.

`make build TAGS='-tags cairo,nativegzip'` retains system-zlib decoding.
`make nocairo` retains the portable Go decoder without either native dependency.
The existing `CARBONAPI_NATIVE_GZIP` and `CARBONAPI_STREAM_RENDER` startup flags
still control use of native backend decoding.

To build the binaries with debug symbols, run:

```
Expand Down
2 changes: 1 addition & 1 deletion docker/carbonapi/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM golang:1.26

RUN apt-get update
RUN apt-get install -y libcairo2-dev
RUN apt-get install -y libcairo2-dev curl

WORKDIR /go/src/github.com/bookingcom/carbonapi
COPY . .
Expand Down
27 changes: 20 additions & 7 deletions pkg/backend/net/native_gzip.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,32 @@
package net

/*
#cgo LDFLAGS: -lz
#cgo !zlibng LDFLAGS: -lz
#cgo zlibng CFLAGS: -DCARBONAPI_ZLIBNG -I${SRCDIR}/../../../.build/zlib-ng/include
#cgo zlibng LDFLAGS: ${SRCDIR}/../../../.build/zlib-ng/lib/libz-ng.a
#include <stdlib.h>
#ifdef CARBONAPI_ZLIBNG
// The native API has separate symbols: Cairo/libpng continue using system zlib.
#include <zlib-ng.h>
typedef zng_stream carbonapi_z_stream;
#define inflateInit2 zng_inflateInit2
#define inflateReset2 zng_inflateReset2
#define inflateEnd zng_inflateEnd
#define inflate zng_inflate
#else
#include <zlib.h>
typedef z_stream carbonapi_z_stream;
#endif

typedef struct { unsigned int used, produced; int code; } carbonapi_inflate_result;
static z_stream *carbonapi_inflate_new(void) {
z_stream *s = (z_stream *)calloc(1, sizeof(z_stream));
static carbonapi_z_stream *carbonapi_inflate_new(void) {
carbonapi_z_stream *s = (carbonapi_z_stream *)calloc(1, sizeof(carbonapi_z_stream));
if (s && inflateInit2(s, 15 + 16) != Z_OK) { free(s); return NULL; }
return s;
}
static int carbonapi_inflate_reset(z_stream *s) { return inflateReset2(s, 15 + 16); }
static void carbonapi_inflate_free(z_stream *s) { if (s) { inflateEnd(s); free(s); } }
static carbonapi_inflate_result carbonapi_inflate_step(z_stream *s, unsigned char *in,
static int carbonapi_inflate_reset(carbonapi_z_stream *s) { return inflateReset2(s, 15 + 16); }
static void carbonapi_inflate_free(carbonapi_z_stream *s) { if (s) { inflateEnd(s); free(s); } }
static carbonapi_inflate_result carbonapi_inflate_step(carbonapi_z_stream *s, unsigned char *in,
unsigned int input_size, unsigned char *out, unsigned int output_size) {
s->next_in = in; s->avail_in = input_size;
s->next_out = out; s->avail_out = output_size;
Expand All @@ -38,7 +51,7 @@ import (
)

type nativeGzipReader struct {
state *C.z_stream
state *C.carbonapi_z_stream
src io.Reader
input []byte
pos, end int
Expand Down
41 changes: 41 additions & 0 deletions pkg/backend/net/stream_render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"math"
"math/rand"
"net/http"
"net/http/httptest"
"reflect"
Expand Down Expand Up @@ -41,6 +42,46 @@ func gzipData(t testing.TB, body []byte) []byte {
return b.Bytes()
}

func TestNativeInflaterCompressionVariants(t *testing.T) {
noise := make([]byte, 65537)
_, _ = rand.New(rand.NewSource(42)).Read(noise)
for _, level := range []int{gzip.NoCompression, gzip.BestSpeed, gzip.BestCompression, gzip.HuffmanOnly} {
for _, body := range [][]byte{noise, bytes.Repeat([]byte("repeated backend points\x00\xff"), 4096)} {
var encoded bytes.Buffer
w, err := gzip.NewWriterLevel(&encoded, level)
if err != nil {
t.Fatal(err)
}
w.Name, w.Comment, w.Extra = "backend", "optional gzip header", []byte{0, 1, 255}
if _, err := w.Write(body); err != nil {
t.Fatal(err)
}
if err := w.Close(); err != nil {
t.Fatal(err)
}
for _, chunk := range []int{1, 37, 32768} {
base, err := gzip.NewReader(bytes.NewReader(encoded.Bytes()))
if err != nil {
t.Fatal(err)
}
want, err := io.ReadAll(base)
if err != nil {
t.Fatal(err)
}
r, err := newNativeInflater(&gzipFragments{data: encoded.Bytes(), chunk: chunk})
if err != nil {
t.Fatal(err)
}
got, err := io.ReadAll(r)
_ = r.Close()
if err != nil || !bytes.Equal(got, want) {
t.Fatalf("level=%d chunk=%d: %v; bytes=%d/%d", level, chunk, err, len(got), len(want))
}
}
}
}
}

func TestNativeInflaterDifferential(t *testing.T) {
for _, size := range []int{0, 1, 31, 4096, 65536, 1 << 20} {
body := bytes.Repeat([]byte("some repeated protobuf-like bytes\x00\x01\xff"), (size+38)/39)
Expand Down
34 changes: 34 additions & 0 deletions scripts/build-zlib-ng.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/sh
set -eu

# zlib-ng 2.3.3. Use its native, namespaced API, never replace system zlib.
revision=12731092979c6d07f42da27da673a9f6c7b13586
checksum=a0d2a5d122c84b56a793a1553a9c3327fb2eb7469bf7a86b79e3c7be5d92e8d6
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
prefix="$root/.build/zlib-ng"
if test -f "$prefix/revision" && test "$(cat "$prefix/revision")" = "$revision" &&
test -f "$prefix/lib/libz-ng.a" && test -f "$prefix/include/zlib-ng.h"; then
exit 0
fi

mkdir -p "$root/.build"
work=$(mktemp -d "$root/.build/zlib-ng-build.XXXXXX")
trap 'rm -rf "$work"' EXIT HUP INT TERM
curl --fail --location --retry 3 --connect-timeout 10 --max-time 120 --output "$work/source.tar.gz" \
"https://codeload.github.com/zlib-ng/zlib-ng/tar.gz/$revision"
if command -v sha256sum >/dev/null 2>&1; then
actual=$(sha256sum "$work/source.tar.gz" | cut -d ' ' -f 1)
else
actual=$(shasum -a 256 "$work/source.tar.gz" | cut -d ' ' -f 1)
fi
test "$actual" = "$checksum" || { echo 'zlib-ng source checksum mismatch' >&2; exit 1; }
mkdir "$work/source"
tar -xzf "$work/source.tar.gz" -C "$work/source" --strip-components=1
cd "$work/source"
# Upstream's default build uses runtime CPU dispatch, not -march=native.
./configure --static --prefix="$prefix"
make -j "${ZLIBNG_JOBS:-2}"
make check
make install
cp LICENSE.md "$prefix/LICENSE.md"
printf '%s\n' "$revision" > "$prefix/revision"
Loading