diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index e8520ba..0000000 --- a/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -netexp -Dockerfile -README.md -LICENSE diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index 8608b6d..0000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,63 +0,0 @@ -# build and publish the project's docker image - -name: Docker Build - -on: - create: - push: - branches: [ main ] - tags: [ '*' ] - pull_request: - branches: [ main ] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - - build: - - runs-on: ubuntu-latest - - permissions: - contents: read - packages: write - - steps: - - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Extract Docker metadata - id: metadata - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Setup QEMU - uses: docker/setup-qemu-action@v3 - - - name: Login to Docker registry ${{ env.REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - # only run on tags - if: github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') - - - name: Build and push Docker image - uses: docker/build-push-action@v6 - with: - context: . - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - # only run on tags - push: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml deleted file mode 100644 index 5ca8609..0000000 --- a/.github/workflows/go.yml +++ /dev/null @@ -1,32 +0,0 @@ -# test and build the project -# more info: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go - -name: Go Test - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - - test: - - runs-on: ubuntu-latest - - steps: - - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: '1.21.4' - - - name: Build - run: go build -v ./... - - - name: Test - run: go test -v ./... diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..5a981ff --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,32 @@ +name: run-tests + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +jobs: + + run-tests: + + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version: stable + + - name: Run tests + run: go test -v ./... diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index c5a2e3b..0000000 --- a/Dockerfile +++ /dev/null @@ -1,38 +0,0 @@ -# =============== -# = build image -# =============== - -FROM --platform=$BUILDPLATFORM golang:alpine AS build - -WORKDIR /src -COPY . . - -ARG TARGETOS -ARG TARGETARCH - -ARG GOOS=$TARGETOS -ARG GOARCH=$TARGETARCH -ARG CGO_ENABLED=0 - -RUN go test -v ./... -RUN go build -trimpath -ldflags '-s -w -buildid=' - -# =============== -# = main image -# =============== - -FROM --platform=$TARGETPLATFORM alpine:3.19 - -RUN apk add --update --no-cache tini -COPY --from=build /src/netexp /usr/local/bin/netexp -ENTRYPOINT [ "tini", "--", "netexp" ] - -COPY --chmod=755 <<-'EOF' /healthcheck.sh - #!/bin/sh -eu - listen=${NETEXP_LISTEN:-:9298} - host=${listen%:*} - port=${listen#$host} - content=$(wget --quiet --tries=1 --output-document=- "http://127.0.0.1$port") - test -n "$(printf '%s\n' "$content" | wc -l)" -EOF -HEALTHCHECK CMD [ "/healthcheck.sh" ] diff --git a/go.mod b/go.mod index fbf689a..35a1769 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module netexp -go 1.21.4 +go 1.25 diff --git a/main.go b/main.go index 15712b5..6eec7ea 100644 --- a/main.go +++ b/main.go @@ -6,13 +6,14 @@ import ( "net/http" "netexp/netdev" "netexp/pipeline" + "netexp/rcu" "os" "time" ) var ( version = "0.3.8" - metrics []byte + metrics rcu.RCU[[]byte] listen string getver bool ) @@ -45,7 +46,7 @@ func serve() { }) http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) { - w.Write(metrics) + w.Write(*metrics.Load()) }) go func() { @@ -72,7 +73,9 @@ func gather() { panic(fmt.Errorf("could not get traffic: %w", err)) } - metrics = p.Step(recv, trns) + buf := p.Step(recv, trns) + + metrics.Store(&buf) time.Sleep(time.Second) } diff --git a/pipeline/pipeline.go b/pipeline/pipeline.go index bfcbc6b..d1f5ae5 100644 --- a/pipeline/pipeline.go +++ b/pipeline/pipeline.go @@ -2,7 +2,6 @@ package pipeline import ( "fmt" - "netexp/math" "netexp/series" ) @@ -53,8 +52,8 @@ func (p *Pipeline) Step(recv, trns int64) []byte { for _, r := range p.ranges { // Check if there exists enough sample data for the current duration range 'r' if p.trns_series.Length() >= r+1 { - trns_rate := math.Rate(p.trns_series.Samples, r+1) - recv_rate := math.Rate(p.recv_series.Samples, r+1) + trns_rate := series.Rate(p.trns_series.Samples, r+1) + recv_rate := series.Rate(p.recv_series.Samples, r+1) p.trns_rates_series[r].Record(trns_rate) p.recv_rates_series[r].Record(recv_rate) @@ -69,8 +68,8 @@ func (p *Pipeline) Step(recv, trns int64) []byte { if m > r && p.trns_rates_series[r].Length() >= m { trns_name := fmt.Sprintf("netexp_transmit_rate_%ds_max_%ds_bps", r, m) recv_name := fmt.Sprintf("netexp_receive_rate_%ds_max_%ds_bps", r, m) - register(trns_name, math.Max(p.trns_rates_series[r].Samples, m)) - register(recv_name, math.Max(p.recv_rates_series[r].Samples, m)) + register(trns_name, series.Max(p.trns_rates_series[r].Samples, m)) + register(recv_name, series.Max(p.recv_rates_series[r].Samples, m)) } } } diff --git a/rcu/rcu.go b/rcu/rcu.go new file mode 100644 index 0000000..97031ee --- /dev/null +++ b/rcu/rcu.go @@ -0,0 +1,15 @@ +package rcu + +import "sync/atomic" + +type RCU[T any] struct { + p atomic.Pointer[T] +} + +func (r *RCU[T]) Store(t *T) { + r.p.Store(t) +} + +func (r *RCU[T]) Load() *T { + return r.p.Load() +} diff --git a/math/math.go b/series/math.go similarity index 96% rename from math/math.go rename to series/math.go index 6814f5c..28575e9 100644 --- a/math/math.go +++ b/series/math.go @@ -1,4 +1,4 @@ -package math +package series func Max(series []int64, head int) int64 { var max int64 diff --git a/math/math_test.go b/series/math_test.go similarity index 88% rename from math/math_test.go rename to series/math_test.go index 5c67954..a06cd1e 100644 --- a/math/math_test.go +++ b/series/math_test.go @@ -1,7 +1,7 @@ -package math_test +package series_test import ( - "netexp/math" + "netexp/series" "strconv" "testing" ) @@ -20,7 +20,7 @@ func TestMax(t *testing.T) { for i, tc := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { - got := math.Max(tc.series, tc.head) + got := series.Max(tc.series, tc.head) if got != tc.want { t.Errorf("incorrect max; got %d want %d", got, tc.want) } @@ -42,7 +42,7 @@ func TestRate(t *testing.T) { for i, tc := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { - got := math.Rate(tc.series, tc.head) + got := series.Rate(tc.series, tc.head) if got != tc.want { t.Errorf("incorrect rate; got %d want %d", got, tc.want) }