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
21 changes: 8 additions & 13 deletions .github/workflows/cover.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
on: ["push", "pull_request"]
on:
push:
branches: [ main ]
pull_request:

name: Test Coveralls Parallel

Expand All @@ -7,30 +10,22 @@ jobs:
env:
CGO_ENABLED: 1
runs-on: ubuntu-latest
strategy:
matrix:
go:
- "1.22"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- uses: actions/checkout@v4
go-version-file: go.mod
- run: make build
test:
needs: build
env:
CGO_ENABLED: 1
runs-on: ubuntu-latest
strategy:
matrix:
go:
- "1.22"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- uses: actions/checkout@v4
go-version-file: go.mod
- run: make test
- name: Send coverage
env:
Expand Down
184 changes: 184 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
name: Integration Tests

on:
push:
branches: [ main ]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25.1'
cache: true

- name: Build pgconfigctl
run: go build -o pgconfigctl cmd/pgconfigctl/main.go

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: pgconfigctl-${{ github.sha }}
path: pgconfigctl
retention-days: 1

validate-config:
name: include on pg${{ matrix.pg_version }}
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
pg_version: ["9.5", "9.6", "10", "11", "12", "13", "14", "15", "16", "17", "18"]

steps:
- uses: actions/checkout@v4

- name: Download pgconfigctl
uses: actions/download-artifact@v4
with:
name: pgconfigctl-${{ github.sha }}
path: .

- name: Make executable
run: chmod +x pgconfigctl

- name: Generate postgresql.conf
run: |
./pgconfigctl tune \
--version ${{ matrix.pg_version }} \
--format conf \
--ram 1GB \
--cpus 2 > tuned.conf

echo "--- Generated Config for PG ${{ matrix.pg_version }} ---"
cat tuned.conf

- name: Create Init Script
run: |
echo "#!/bin/bash" > init-config.sh
echo "echo \"include = '/etc/postgresql/tuned.conf'\" >> \"\$PGDATA/postgresql.conf\"" >> init-config.sh
chmod +x init-config.sh

- name: Run PostgreSQL Container
run: |
IMG_TAG="${{ matrix.pg_version }}"

echo "Starting Postgres $IMG_TAG..."

docker run -d --name pg-${{ matrix.pg_version }} \
-e POSTGRES_PASSWORD=postgres \
-v $(pwd)/tuned.conf:/etc/postgresql/tuned.conf \
-v $(pwd)/init-config.sh:/docker-entrypoint-initdb.d/init-config.sh \
postgres:$IMG_TAG || {
echo "Container failed to start (maybe image missing?)"
exit 1
}

- name: Wait for Readiness
run: |
# Verify container exists
if ! docker ps -a -q -f name=pg-${{ matrix.pg_version }} | grep -q .; then
echo "Container not found!"
exit 1
fi

echo "Waiting for Postgres to be ready..."
READY=0
for i in {1..30}; do
# Check if container is still running
if ! docker ps -q -f name=pg-${{ matrix.pg_version }} | grep -q .; then
echo "Container stopped running!"
docker logs pg-${{ matrix.pg_version }}
exit 1
fi

if docker exec pg-${{ matrix.pg_version }} pg_isready -U postgres; then
echo "Postgres is ready!"
READY=1
break
fi
echo "Waiting..."
sleep 1
done

if [ $READY -eq 0 ]; then
echo "Timed out waiting for Postgres to be ready."
docker logs pg-${{ matrix.pg_version }}
exit 1
fi

- name: Verify Settings Applied
run: |
# Fail if container is not running
if ! docker ps -q -f name=pg-${{ matrix.pg_version }} | grep -q .; then
echo "Container is not running!"
exit 1
fi

echo "### PostgreSQL ${{ matrix.pg_version }} Verification Report" >> $GITHUB_STEP_SUMMARY
echo "| Parameter | Status | Value |" >> $GITHUB_STEP_SUMMARY
echo "| :--- | :--- | :--- |" >> $GITHUB_STEP_SUMMARY

check_param() {
PARAM=$1
# Execute psql to show parameter.
# If it fails (e.g. param doesn't exist), capture error.
# We use '|| echo ERROR' to prevent script exit and capture failure.
VALUE=$(docker exec pg-${{ matrix.pg_version }} psql -U postgres -t -c "SHOW $PARAM;" 2>/dev/null || echo "NOT_SUPPORTED")

# Trim whitespace
VALUE=$(echo "$VALUE" | xargs)

if [ "$VALUE" == "NOT_SUPPORTED" ]; then
echo "| \`$PARAM\` | ⚪ Skipped | Not supported in this version |" >> $GITHUB_STEP_SUMMARY
echo "Parameter $PARAM not supported."
else
echo "| \`$PARAM\` | ✅ Passed | \`$VALUE\` |" >> $GITHUB_STEP_SUMMARY
echo "Parameter $PARAM = $VALUE"
fi
}

# Core memory settings (mostly universal)
check_param "shared_buffers"
check_param "effective_cache_size"
check_param "work_mem"
check_param "maintenance_work_mem"

# Checkpoint (min/max_wal_size introduced in 9.5, checkpoint_segments removed in 9.5)
check_param "min_wal_size"
check_param "max_wal_size"
check_param "checkpoint_completion_target"

# Parallel query (introduced gradually 9.6+)
check_param "max_worker_processes"
check_param "max_parallel_workers_per_gather"
check_param "max_parallel_workers"

# Storage/IO
check_param "random_page_cost"
check_param "effective_io_concurrency"
check_param "maintenance_io_concurrency"

# PG 18+ AIO
check_param "io_workers"
check_param "io_method"

# Final status line
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ **Tests Completed Successfully**" >> $GITHUB_STEP_SUMMARY


- name: Dump Logs on Failure
if: failure()
run: |
if docker ps -a -q -f name=pg-${{ matrix.pg_version }} | grep -q .; then
docker logs pg-${{ matrix.pg_version }}
fi
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.22
go-version-file: go.mod

- name: Login to Heroku Container Registry
uses: docker/login-action@v3
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ covprofile

.vscode/
.idea/
.crush/
result
!result/
80 changes: 80 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Agent Guide for pgconfig/api

Essential commands, structure, and patterns for AI agents.

## Essential Commands

```bash
make docs # Generate Swagger API documentation
make test # Run all tests with race detector and coverage
make lint # Run go vet (requires docs generated first)
make build # Clean, generate docs, lint, and build binaries
make clean # Remove dist/ and generated docs
```

## Project Structure

```
.
├── cmd/ # API and CLI entry points
├── pkg/ # Core packages (input, rules, category, format, docs)
├── generators/pg-docs/ # Tool to generate pg-docs.yml
├── rules.yml # Rule metadata (categories, abstracts, recommendations)
└── pg-docs.yml # PostgreSQL parameter documentation per version
```

## Code Patterns

- **Go 1.25.1**, module `github.com/pgconfig/api`
- **Fiber** for API, **Cobra** for CLI, **Swagger** for docs
- **English Language**: All code comments, documentation, and variable names must be in English.
- Input parsing: `pkg/input/bytes.Parse()` for byte units, `profile.Profile` for workload types
- Rule pipeline in `pkg/rules/compute.go` (order: arch → OS → profile → storage → AIO → version)
- Three output formats: `json`, `alter_system`, `conf`
- Configuration files: `rules.yml` and `pg-docs.yml` loaded at startup

## Testing

- `make test` runs all tests with coverage (generates `covprofile`)
- Test files follow `*_test.go` pattern
- CI runs tests on push/pull request (`.github/workflows/cover.yml`)

## Adding a New Rule

1. Create function in `pkg/rules/` with signature `func(*input.Input, *category.ExportCfg) (*category.ExportCfg, error)`
2. Add to `allRules` slice in `pkg/rules/compute.go` (mind order)
3. Write unit tests
4. Update `rules.yml` if rule needs metadata

## CI/CD

- **cover.yml**: runs `make build` and `make test`, pushes coverage to Coveralls
- **release.yml**: triggered on tags, runs goreleaser for multi‑arch binaries and Docker images

## Commit Conventions

Follow commit conventions from `~/.claude/pgconfig.md`:

```
<type>: <subject line (max 50 chars)>

<body wrapped at 80 cols, focus on WHY not WHAT>
```

Types: `feat`, `fix`, `refactor`, `docs`, `chore`, `test`, `style`

Rules:
- Title ≤50 chars, imperative mood ("fix" not "fixed").
- Body wrapped at 80 cols, focus on WHY.
- Sign‑off required (`-s`).
- **STRICTLY FORBIDDEN**: AI attribution footers (e.g., "Generated with Crush", "Assisted by...").
- **STRICTLY FORBIDDEN**: Adding "Co-authored-by" unless explicitly requested by the user.
- **English Only**: Commit messages must be in English.

## Gotchas

1. **Swagger docs before building**: `make build` depends on `make docs`
2. **Byte parsing**: case‑insensitive, expects unit (KB, MB, GB, TB)
3. **PostgreSQL version defaults**: default is 18, supported 9.1–18
4. **Rule order**: `computeVersion` must be last (removes unsupported parameters)
5. **AIO parameters (PostgreSQL 18+)**: `io_method` and `io_workers` only available in ≥18. `io_workers` scaled by profile: Desktop 10%, WEB 20%, Mixed 25%, OLTP 30%, DW 40%, +10% for HDD.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ docs: mod clean
$(GOBIN)/swag init --dir ./cmd/api --output ./cmd/api/docs

test: docs
go test -race -v -covermode atomic -coverprofile=covprofile ./...
go test -race -v -covermode atomic -coverpkg=./... -coverprofile=covprofile ./...

goveralls-push: mod
$(GOBIN)/goveralls -coverprofile=covprofile -service=github
$(GOBIN)/goveralls -coverprofile=covprofile -service=github -v

check: lint

Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,22 @@ PGConfig.org API v2.

## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fpgconfig%2Fapi.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fpgconfig%2Fapi?ref=badge_large)

## Rules Engine

The configuration is adjusted by a rules engine based on the environment.

| Category | Condition | Action/Adjustment |
| :------------- | :------------------------------------------ | :------------------------------------------------------------------------------------------------------------ |
| **Architecture** | 32-bit (`386`, `i686`) | Cap `shared_buffers`, `work_mem`, `maintenance_work_mem` at 4GB. |
| **OS** | Windows | Set `effective_io_concurrency` to `0`. |
| | Windows & PG Version <= 9.6 | Limit `shared_buffers` to 512MB. |
| **Profile** | `Desktop` | Set `shared_buffers` to Total RAM / 16. |
| **Storage** | Disk Type is `SSD` | Set `effective_io_concurrency` to `200` and `random_page_cost` to `1.1`. |
| | Disk Type is `SAN` | Set `effective_io_concurrency` to `300` and `random_page_cost` to `1.1`. |
| | Disk Type is `HDD` | Set `effective_io_concurrency` to `2`. |
| **PG Version** | < 9.5 | Remove `min_wal_size` and `max_wal_size`. |
| | < 9.6 | Remove `max_parallel_worker_per_gather`. Cap `shared_buffers` at 8GB. |
| | < 10.0 | Remove `max_parallel_workers`. |
| | >= 9.5 | Remove `checkpoint_segments`. |
| | <= 9.3 | Remove the entire `worker` category. |
7 changes: 6 additions & 1 deletion generators/pg-docs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ func updateDoc(ver float32, param string, parsed docs.ParamDoc) {
}

func main() {

file = docs.DocFile{
Documentation: make(map[string]docs.Doc),
}
Expand All @@ -76,6 +75,12 @@ func main() {
"max_connections",
"random_page_cost",
"effective_io_concurrency",
"maintenance_io_concurrency",
"io_method",
"io_workers",
"io_max_combine_limit",
"io_max_concurrency",
"file_copy_method",
"max_worker_processes",
"max_parallel_workers_per_gather",
"max_parallel_workers",
Expand Down
Loading