diff --git a/frameworks/echo/Dockerfile b/frameworks/echo/Dockerfile new file mode 100644 index 000000000..ddfc9d41c --- /dev/null +++ b/frameworks/echo/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.26-alpine AS build +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY main.go ./ +RUN CGO_ENABLED=0 go build -o server main.go + +FROM alpine:3.23 +COPY --from=build /app/server /server +EXPOSE 8080 +CMD ["/server"] diff --git a/frameworks/echo/README.md b/frameworks/echo/README.md new file mode 100644 index 000000000..0f0b0bb97 --- /dev/null +++ b/frameworks/echo/README.md @@ -0,0 +1,25 @@ +# echo + +Echo v4 on the Go `net/http` server, default configuration. + +## Stack + +- **Language:** Go 1.26 +- **Framework:** Echo v4 +- **Build:** `golang:1.26-alpine` -> `alpine:3.23` runtime + +## Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/pipeline` | GET | Returns `ok` (plain text) | +| `/baseline11` | GET | Sums query parameter values | +| `/baseline11` | POST | Sums query parameters + request body | +| `/json/{count}?m=N` | GET | First `count` dataset items with `total = price * quantity * m` | +| `/upload` | POST | Reads the body and returns the byte count | + +## Notes + +- Routing and path parameters through the Echo router, JSON through `c.JSON` +- Compression through the Echo `Gzip` middleware +- Banner and port log disabled, no other middleware installed diff --git a/frameworks/echo/go.mod b/frameworks/echo/go.mod new file mode 100644 index 000000000..bba0214d2 --- /dev/null +++ b/frameworks/echo/go.mod @@ -0,0 +1,18 @@ +module httparena/echo + +go 1.25.0 + +require github.com/labstack/echo/v4 v4.15.4 + +require ( + github.com/labstack/gommon v0.5.0 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.38.0 // indirect + golang.org/x/time v0.15.0 // indirect +) diff --git a/frameworks/echo/go.sum b/frameworks/echo/go.sum new file mode 100644 index 000000000..9c992ae6f --- /dev/null +++ b/frameworks/echo/go.sum @@ -0,0 +1,30 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/labstack/echo/v4 v4.15.4 h1:DL45vVYa+BWE+XuW+zZNd9H0YEdZ80UAWJGcTVW4EVs= +github.com/labstack/echo/v4 v4.15.4/go.mod h1:CuMetKIRwsuO/qlAgMq+KTAalwGoB/h4tC+yPdrTj1g= +github.com/labstack/gommon v0.5.0 h1:6VSQ2NOzsnEJ5W6+84E0RbcaDDmgB6NIAzWCczTEe6c= +github.com/labstack/gommon v0.5.0/go.mod h1:Rzlg7HHy1maLfzBYGg9NZcVuz1sA68HHhLjhcEllYE0= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/frameworks/echo/main.go b/frameworks/echo/main.go new file mode 100644 index 000000000..257a912e3 --- /dev/null +++ b/frameworks/echo/main.go @@ -0,0 +1,123 @@ +package main + +import ( + "encoding/json" + "io" + "net/http" + "os" + "strconv" + "strings" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +type Rating struct { + Score int `json:"score"` + Count int `json:"count"` +} + +type DatasetItem struct { + ID int `json:"id"` + Name string `json:"name"` + Category string `json:"category"` + Price int `json:"price"` + Quantity int `json:"quantity"` + Active bool `json:"active"` + Tags []string `json:"tags"` + Rating Rating `json:"rating"` +} + +type ProcessedItem struct { + DatasetItem + Total int `json:"total"` +} + +type ProcessResponse struct { + Items []ProcessedItem `json:"items"` + Count int `json:"count"` +} + +var dataset []DatasetItem + +func loadDataset() { + path := os.Getenv("DATASET_PATH") + if path == "" { + path = "/data/dataset.json" + } + data, err := os.ReadFile(path) + if err != nil { + return + } + json.Unmarshal(data, &dataset) +} + +func pipeline(c echo.Context) error { + return c.String(http.StatusOK, "ok") +} + +func baseline11(c echo.Context) error { + sum := 0 + for _, values := range c.QueryParams() { + for _, v := range values { + if n, err := strconv.Atoi(v); err == nil { + sum += n + } + } + } + if c.Request().Method == http.MethodPost { + body, err := io.ReadAll(c.Request().Body) + if err == nil { + if n, err := strconv.Atoi(strings.TrimSpace(string(body))); err == nil { + sum += n + } + } + } + return c.String(http.StatusOK, strconv.Itoa(sum)) +} + +func jsonItems(c echo.Context) error { + count, _ := strconv.Atoi(c.Param("count")) + if count < 0 { + count = 0 + } + if count > len(dataset) { + count = len(dataset) + } + m, err := strconv.Atoi(c.QueryParam("m")) + if err != nil || m == 0 { + m = 1 + } + + items := make([]ProcessedItem, count) + for i := 0; i < count; i++ { + d := dataset[i] + items[i] = ProcessedItem{DatasetItem: d, Total: d.Price * d.Quantity * m} + } + return c.JSON(http.StatusOK, ProcessResponse{Items: items, Count: count}) +} + +func upload(c echo.Context) error { + size, err := io.Copy(io.Discard, c.Request().Body) + if err != nil { + return c.String(http.StatusBadRequest, "0") + } + return c.String(http.StatusOK, strconv.FormatInt(size, 10)) +} + +func main() { + loadDataset() + + e := echo.New() + e.HideBanner = true + e.HidePort = true + e.Use(middleware.Gzip()) + + e.GET("/pipeline", pipeline) + e.GET("/baseline11", baseline11) + e.POST("/baseline11", baseline11) + e.GET("/json/:count", jsonItems) + e.POST("/upload", upload) + + e.Logger.Fatal(e.Start(":8080")) +} diff --git a/frameworks/echo/meta.json b/frameworks/echo/meta.json new file mode 100644 index 000000000..56eb49322 --- /dev/null +++ b/frameworks/echo/meta.json @@ -0,0 +1,19 @@ +{ + "display_name": "echo", + "language": "Go", + "type": "flagship", + "mode": "standard", + "engine": "net/http", + "description": "Echo v4 on the Go net/http server, default configuration. Routing and path/query binding through the Echo API, encoding/json via c.JSON, gzip through the Echo Gzip middleware.", + "repo": "https://github.com/labstack/echo", + "enabled": true, + "tests": [ + "baseline", + "pipelined", + "limited-conn", + "json", + "json-comp", + "upload" + ], + "maintainers": [] +} diff --git a/site/data/frameworks.json b/site/data/frameworks.json index 7f879a980..301b9da84 100644 --- a/site/data/frameworks.json +++ b/site/data/frameworks.json @@ -244,6 +244,14 @@ "engine": "zerg", "mode": "standard" }, + "echo": { + "dir": "echo", + "description": "Echo v4 on the Go net/http server, default configuration. Routing and path/query binding through the Echo API, encoding/json via c.JSON, gzip through the Echo Gzip middleware.", + "repo": "https://github.com/labstack/echo", + "type": "flagship", + "engine": "net/http", + "mode": "standard" + }, "effinitive": { "dir": "effinitive", "description": "Ultra-high-performance custom HTTP server for .NET 10 \u2014 built from scratch for maximum speed.", diff --git a/site/data/results/echo.json b/site/data/results/echo.json new file mode 100644 index 000000000..642dfe131 --- /dev/null +++ b/site/data/results/echo.json @@ -0,0 +1,243 @@ +{ + "framework": "echo", + "results": { + "baseline-4096": { + "framework": "echo", + "language": "Go", + "rps": 522121, + "avg_latency": "7.85ms", + "p99_latency": "110.90ms", + "cpu": "3655.3%", + "memory": "208MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "70.19MB/s", + "input_bw": "40.33MB/s", + "reconnects": 0, + "status_2xx": 2610605, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "baseline-512": { + "framework": "echo", + "language": "Go", + "rps": 313482, + "avg_latency": "1.63ms", + "p99_latency": "12.50ms", + "cpu": "2445.6%", + "memory": "78MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "42.14MB/s", + "input_bw": "24.22MB/s", + "reconnects": 0, + "status_2xx": 1567411, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-4096": { + "framework": "echo", + "language": "Go", + "rps": 216828, + "avg_latency": "17.96ms", + "p99_latency": "459.70ms", + "cpu": "3260.7%", + "memory": "108MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "757.27MB/s", + "input_bw": "10.34MB/s", + "reconnects": 43322, + "status_2xx": 1084142, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-comp-16384": { + "framework": "echo", + "language": "Go", + "rps": 197667, + "avg_latency": "81.70ms", + "p99_latency": "493.40ms", + "cpu": "5642.6%", + "memory": "1.4GiB", + "connections": 16384, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "263.33MB/s", + "input_bw": "14.70MB/s", + "reconnects": 31645, + "status_2xx": 988335, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-comp-4096": { + "framework": "echo", + "language": "Go", + "rps": 195532, + "avg_latency": "20.68ms", + "p99_latency": "198.10ms", + "cpu": "5605.1%", + "memory": "1.2GiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "261.84MB/s", + "input_bw": "14.54MB/s", + "reconnects": 38100, + "status_2xx": 977660, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-comp-512": { + "framework": "echo", + "language": "Go", + "rps": 189101, + "avg_latency": "2.70ms", + "p99_latency": "29.20ms", + "cpu": "5612.2%", + "memory": "314MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "253.28MB/s", + "input_bw": "14.07MB/s", + "reconnects": 37736, + "status_2xx": 945509, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "limited-conn-4096": { + "framework": "echo", + "language": "Go", + "rps": 159146, + "avg_latency": "25.10ms", + "p99_latency": "266.50ms", + "cpu": "2058.3%", + "memory": "63MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "21.39MB/s", + "input_bw": "12.29MB/s", + "reconnects": 79628, + "status_2xx": 795730, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "limited-conn-512": { + "framework": "echo", + "language": "Go", + "rps": 126710, + "avg_latency": "4.03ms", + "p99_latency": "42.00ms", + "cpu": "1892.2%", + "memory": "60MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "17.03MB/s", + "input_bw": "9.79MB/s", + "reconnects": 63341, + "status_2xx": 633554, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "pipelined-4096": { + "framework": "echo", + "language": "Go", + "rps": 1167118, + "avg_latency": "41.26ms", + "p99_latency": "1.77s", + "cpu": "4226.6%", + "memory": "336MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "156.88MB/s", + "reconnects": 0, + "status_2xx": 5835592, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "pipelined-512": { + "framework": "echo", + "language": "Go", + "rps": 785237, + "avg_latency": "10.32ms", + "p99_latency": "173.80ms", + "cpu": "3191.2%", + "memory": "91MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "105.55MB/s", + "reconnects": 0, + "status_2xx": 3926189, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "upload-256": { + "framework": "echo", + "language": "Go", + "rps": 2922, + "avg_latency": "80.75ms", + "p99_latency": "303.80ms", + "cpu": "3852.9%", + "memory": "64MiB", + "connections": 256, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "417.21KB/s", + "input_bw": "23.18GB/s", + "reconnects": 2892, + "status_2xx": 14699, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "upload-32": { + "framework": "echo", + "language": "Go", + "rps": 2442, + "avg_latency": "13.07ms", + "p99_latency": "37.10ms", + "cpu": "2197.8%", + "memory": "42MiB", + "connections": 32, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "348.72KB/s", + "input_bw": "19.37GB/s", + "reconnects": 2440, + "status_2xx": 12213, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + } + } +} diff --git a/site/static/logs/baseline/4096/echo.log b/site/static/logs/baseline/4096/echo.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/baseline/512/echo.log b/site/static/logs/baseline/512/echo.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-comp/16384/echo.log b/site/static/logs/json-comp/16384/echo.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-comp/4096/echo.log b/site/static/logs/json-comp/4096/echo.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-comp/512/echo.log b/site/static/logs/json-comp/512/echo.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json/4096/echo.log b/site/static/logs/json/4096/echo.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/limited-conn/4096/echo.log b/site/static/logs/limited-conn/4096/echo.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/limited-conn/512/echo.log b/site/static/logs/limited-conn/512/echo.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/pipelined/4096/echo.log b/site/static/logs/pipelined/4096/echo.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/pipelined/512/echo.log b/site/static/logs/pipelined/512/echo.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/upload/256/echo.log b/site/static/logs/upload/256/echo.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/upload/32/echo.log b/site/static/logs/upload/32/echo.log new file mode 100644 index 000000000..e69de29bb