# Measure application performance

Measure a real route or query before you change it. Include authentication,
database access and rendering when they are part of the path.

## Ask your agent

```text
Read https://cicadel.fauzaanu.com/en/dev/howto/performance/index.md.
Investigate [slow route or operation] in this application.
Create a representative benchmark, record latency and allocations, and profile it.
Fix the measured bottleneck while preserving behavior and permissions.
Compare repeated runs and report the workload and any remaining uncertainty.
```

## Benchmark a handler

Add a benchmark next to the handler you want to measure. This template uses a
generated notes app. Replace it with your real handler and its dependencies.

<!-- example: quickstart/apps/notes/benchmark_test.go -->
```go
package notes

import (
    "net/http/httptest"
    "testing"
)

func BenchmarkIndex(b *testing.B) {
    request := httptest.NewRequest("GET", "/notes/", nil)
    b.ReportAllocs()
    for b.Loop() {
        recorder := httptest.NewRecorder()
        Index(recorder, request)
        if recorder.Code != 200 {
            b.Fatalf("status = %d", recorder.Code)
        }
    }
}
```

Run it five times:

```sh
go test ./apps/notes -run '^$' -bench BenchmarkIndex -benchmem -count=5
```

This measures the handler directly. It leaves out network, middleware and database
costs unless your setup includes them.

## Profile the operation

```sh
go test ./apps/notes -run '^$' -bench BenchmarkIndex -benchtime=5s \
  -cpuprofile=cpu.out -memprofile=mem.out
go tool pprof -top cpu.out
go tool pprof -top -alloc_space mem.out
```

For database work, load representative rows into a disposable PostgreSQL database.

- Check query counts with
  [the application testing helpers](https://cicadel.fauzaanu.com/en/dev/topics/testing/index.md#postgresql-isolation-and-query-budgets).
- Inspect slow SQL with PostgreSQL's query planner.

## Compare a change

1. Keep the same inputs, dataset and concurrency before and after the change.
2. Record errors as well as latency.
3. Repeat the runs. One short run is not enough to claim a speedup.

Do not use a development server's latency as your production capacity estimate.

## Compared with Django

The documentation home page compares the standard Cicadel project with a vanilla
Django 6.1 project on the same machine, both on PostgreSQL with auth, sessions
and CSRF.

| Measure | Cicadel | Django |
| --- | --- | --- |
| Container image | 35 MB | 320 MB |
| Memory at idle | 24 MB | 86 MB (1 gunicorn worker) |
| Requests per second | ~23,000 | ~10,300 (10 gunicorn workers, 581 MB) |
| Median response | 1 ms | 2 ms (10 workers) |

The method, versions, raw results and a harness to reproduce them are in
[the comparison notes](https://github.com/fauzaanu/cicadel/blob/main/benchmarks/django-comparison/README.md). Re-run it on
your own hardware before relying on a number.
