proficiency

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

README

Proficiency

CI Coverage Go Reference OpenSSF Best Practices

Catch Go API performance regressions before they merge.

Proficiency reads your OpenAPI document, generates controlled load, collects native Go pprof profiles, and writes a versioned report that can be compared between commits.

GitHub Action quick start

Your API needs an OpenAPI document and /debug/pprof/ enabled:

import _ "net/http/pprof"

Then add one profiling step after starting the service:

- name: Profile API
  id: proficiency
  uses: tuxerrante/proficiency@v0
  with:
    openapi-path: api/openapi.yaml
    target-url: http://localhost:8080
    duration: 10s

- name: Upload profiling evidence
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: proficiency-report
    path: |
      ${{ steps.proficiency.outputs.report-path }}
      ${{ steps.proficiency.outputs.output-dir }}/*.pprof

This produces:

  • a stable JSON report for artifacts and automation
  • CPU, heap, and block profiles for go tool pprof
  • a non-zero exit when configured performance gates fail

CLI

Install the latest release:

go install github.com/tuxerrante/proficiency/cmd/proficiency@latest

Profile a service:

proficiency \
  --openapi ./api/openapi.yaml \
  --target http://localhost:8080 \
  --duration 10s \
  --concurrency 5 \
  --rps 50 \
  --report ./profiles/report.json \
  --label baseline

Proficiency saves the requested pprof files and records:

  • run configuration and source revision metadata
  • request counts, error rate, throughput, and per-endpoint latency
  • the highest flat-cost functions in each collected profile
  • profile threshold violations
  • an optional comparison with a previous report
Compare a pull request with a baseline

Store a successful main-branch report as an artifact, download it in a pull request job, and pass it back to Proficiency:

proficiency \
  --openapi ./api/openapi.yaml \
  --target http://localhost:8080 \
  --report ./profiles/pr.json \
  --baseline ./baseline/main.json \
  --fail-on-regression 'latency:10:200us,error-rate:1,throughput:10:5rps,cpu:5,alloc:5' \
  --label pull-request

Regression rules use:

Metric Change measured
latency Relative increase plus absolute microsecond floor
error-rate Increase in overall error-rate percentage points
throughput Relative decrease plus absolute RPS floor
cpu Increase in function flat-share percentage points
alloc Increase in function flat-share percentage points
block Increase in function flat-share percentage points
goroutine Increase in function flat-share percentage points

Latency and throughput rules require an absolute noise floor. A latency rule such as latency:10:200us fails only when latency increases by more than both 10% and 200 microseconds. throughput:10:5rps similarly requires both a 10% drop and more than 5 requests per second of absolute loss.

The report is written before Proficiency exits non-zero for a failed threshold or regression gate, so CI can always upload the evidence.

See the report schema contract for field and compatibility details.

Go package

The root module is importable. Start from DefaultConfig, then call Run:

package main

import (
	"context"
	"errors"
	"log"
	"os"
	"time"

	"github.com/tuxerrante/proficiency"
)

func main() {
	cfg := proficiency.DefaultConfig()
	cfg.OpenAPIPath = "./api/openapi.yaml"
	cfg.TargetURL = "http://localhost:8080"
	cfg.Duration = 10 * time.Second
	cfg.ReportPath = "./profiles/report.json"
	cfg.Output = os.Stdout
	cfg.ErrorOutput = os.Stderr

	report, err := proficiency.Run(context.Background(), cfg)
	var gateErr *proficiency.GateError
	if err != nil && !errors.As(err, &gateErr) {
		log.Fatal(err)
	}

	log.Printf("report schema=%s profiles=%d", report.SchemaVersion, len(report.Profiles))
	if gateErr != nil {
		os.Exit(3)
	}
}

ReadReport, WriteReport, ParseRegressionRules, and CompareReports are also exported for workflows that compare stored artifacts without running a new profile.

GitHub Action with regression gates

The action is composite rather than container-based so it can reach a service bound to the runner's localhost. Released action versions download a checksum-verified binary.

name: profile

on:
  pull_request:

jobs:
  proficiency:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-go@v6
        with:
          go-version-file: go.mod

      - name: Start API
        run: |
          go run ./cmd/api &
          for attempt in $(seq 1 30); do
            curl --fail --silent http://localhost:8080/health && break
            sleep 1
          done

      - name: Profile API
        id: proficiency
        uses: tuxerrante/proficiency@v0
        with:
          openapi-path: api/openapi.yaml
          target-url: http://localhost:8080
          duration: 10s
          report-path: profiles/report.json
          baseline-report: baseline/main.json
          fail-on-regression: latency:10:200us,error-rate:1,throughput:10:5rps,cpu:5
          label: pull-request

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: proficiency-report
          path: |
            ${{ steps.proficiency.outputs.report-path }}
            ${{ steps.proficiency.outputs.output-dir }}/*.pprof

Use @v0 for automatic compatible updates. For maximum supply-chain hardening, pin the action to the full commit SHA corresponding to a release.

Container image

Build and run the standalone image when the target is reachable through the selected Docker networking mode. This host-network example is Linux-specific:

docker build --build-arg VERSION=dev -t proficiency:dev .
docker run --rm \
  --network host \
  --user "$(id -u):$(id -g)" \
  -v "$PWD:/work" \
  proficiency:dev \
  --openapi /work/api/openapi.yaml \
  --target http://localhost:8080 \
  --report /work/profiles/report.json

The GitHub Action intentionally does not use this image because hosted Actions runners do not provide a portable host-network contract for Docker actions.

Other modes

Collect profiles without generating load:

proficiency \
  --target http://localhost:8080 \
  --skip-load \
  --profile-types heap,goroutine \
  --report ./profiles/snapshot.json

Collect a time series:

proficiency \
  --target http://localhost:8080 \
  --skip-load \
  --sample-interval 2s \
  --sample-count 10 \
  --profile-types heap,goroutine \
  --report ./profiles/watch.json

Development

make test           # format, lint, race tests, coverage
make e2e            # repository E2E tests against the stress server
make container-test # isolated Docker Compose integration
make external-test  # temporary third-party module import + go install

The purpose-built target in e2e/testserver is a separate Go module with CPU, allocation, database, and request-body workloads. No other public tuxerrante Go repository currently provides the combination of a standalone HTTP API, pprof, and OpenAPI needed for a stable external CI dependency, so the consumer test is generated ephemerally instead of cloning a drifting project.

License

See LICENSE.

Documentation

Overview

Package proficiency profiles Go HTTP APIs from an OpenAPI specification.

Index

Constants

View Source
const (
	RegressionUnitMicroseconds = "microseconds"
	RegressionUnitRPS          = "requests-per-second"
)

Units used by regression rule noise floors.

View Source
const ReportSchemaVersion = "v1"

ReportSchemaVersion identifies the JSON compatibility contract.

Variables

This section is empty.

Functions

func WriteReport

func WriteReport(path string, report Report) error

WriteReport writes a report atomically so readers never observe partial JSON.

Types

type Comparison

type Comparison struct {
	Baseline    ReportIdentity     `json:"baseline"`
	Current     ReportIdentity     `json:"current"`
	Rules       []RegressionRule   `json:"rules"`
	Passed      bool               `json:"passed"`
	Metrics     []ComparisonMetric `json:"metrics"`
	Regressions []ComparisonMetric `json:"regressions"`
}

Comparison describes the deterministic difference between two reports.

func CompareReports

func CompareReports(baseline, current Report, rules []RegressionRule) (Comparison, error)

CompareReports compares stable aggregate load metrics and recorded profile bottlenecks. It does not require the raw pprof files to remain available.

type ComparisonMetric

type ComparisonMetric struct {
	Metric            RegressionMetric `json:"metric"`
	Key               string           `json:"key"`
	Baseline          float64          `json:"baseline"`
	Current           float64          `json:"current"`
	Change            float64          `json:"change"`
	Unit              string           `json:"unit"`
	AbsoluteChange    float64          `json:"absoluteChange"`
	AbsoluteUnit      string           `json:"absoluteUnit"`
	Outcome           string           `json:"outcome"`
	Limit             *float64         `json:"limit,omitempty"`
	MinimumChange     *float64         `json:"minimumChange,omitempty"`
	MinimumChangeUnit string           `json:"minimumChangeUnit,omitempty"`
	WithinLimit       *bool            `json:"withinLimit,omitempty"`
}

ComparisonMetric is one load or profile measurement delta. Change is positive for degradation and negative for improvement. Outcome is purely directional; WithinLimit records the configured gate result separately.

type Config

type Config struct {
	OpenAPIPath      string
	TargetURL        string
	PprofURL         string
	Duration         time.Duration
	Concurrency      int
	RPS              int
	RequestTimeout   time.Duration
	OutputDir        string
	CPUDuration      time.Duration
	SkipLoad         bool
	FailOn           string
	SampleInterval   time.Duration
	SampleCount      int
	ProfileTypes     string
	NoProgress       bool
	ReportPath       string
	BaselinePath     string
	FailOnRegression string
	TopFunctions     int
	ToolVersion      string
	Metadata         Metadata
	Output           io.Writer
	ErrorOutput      io.Writer
}

Config controls one profiling run.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the defaults used by the CLI.

func (Config) Validate

func (cfg Config) Validate() error

Validate checks whether the configuration is internally consistent.

type FunctionStat

type FunctionStat struct {
	Function   string  `json:"function"`
	Percentage float64 `json:"percentage"`
}

FunctionStat contains a function's flat share of its profile.

type GateError

type GateError struct {
	ThresholdViolations int
	Regressions         int
}

GateError reports failed profile or regression gates after the report has been written successfully.

func (*GateError) Error

func (err *GateError) Error() string

type Metadata

type Metadata struct {
	Label      string `json:"label,omitempty"`
	Repository string `json:"repository,omitempty"`
	Revision   string `json:"revision,omitempty"`
	Ref        string `json:"ref,omitempty"`
}

Metadata identifies the source revision represented by a report.

type ProfileAnalysis

type ProfileAnalysis struct {
	ProfileType string         `json:"profileType"`
	Functions   []FunctionStat `json:"functions"`
}

ProfileAnalysis contains the highest flat-cost functions in one profile.

type RegressionMetric

type RegressionMetric string

RegressionMetric identifies a comparable report measurement.

const (
	RegressionLatency    RegressionMetric = "latency"
	RegressionErrorRate  RegressionMetric = "error-rate"
	RegressionThroughput RegressionMetric = "throughput"
	RegressionCPU        RegressionMetric = "cpu"
	RegressionAlloc      RegressionMetric = "alloc"
	RegressionBlock      RegressionMetric = "block"
	RegressionGoroutine  RegressionMetric = "goroutine"
)

Supported regression metrics.

type RegressionRule

type RegressionRule struct {
	Metric            RegressionMetric `json:"metric"`
	Limit             float64          `json:"limit"`
	MinimumChange     float64          `json:"minimumChange,omitempty"`
	MinimumChangeUnit string           `json:"minimumChangeUnit,omitempty"`
}

RegressionRule defines the maximum tolerated degradation for one metric. Latency and throughput also require an absolute noise floor.

func ParseRegressionRules

func ParseRegressionRules(value string) ([]RegressionRule, error)

ParseRegressionRules parses comma-separated rules. Latency and throughput require absolute noise floors:

latency:10:200us,throughput:10:5rps,error-rate:1,cpu:5

type Report

type Report struct {
	SchemaVersion string            `json:"schemaVersion"`
	Timestamp     time.Time         `json:"timestamp"`
	ToolVersion   string            `json:"toolVersion"`
	Metadata      Metadata          `json:"metadata,omitzero"`
	RunConfig     ReportRunConfig   `json:"runConfig"`
	Profiles      []ReportProfile   `json:"profiles"`
	LoadStats     *ReportLoad       `json:"loadStats,omitempty"`
	Analysis      []ProfileAnalysis `json:"analysis"`
	Thresholds    ThresholdResult   `json:"thresholds"`
	Comparison    *Comparison       `json:"comparison,omitempty"`
}

Report is the versioned, machine-readable result of a profiling run.

func ReadReport

func ReadReport(path string) (Report, error)

ReadReport reads and validates a versioned report.

func Run

func Run(ctx context.Context, cfg Config) (*Report, error)

Run executes one profiling workflow and returns its report. When configured gates fail, Run returns both the report and a *GateError.

type ReportEndpointStats

type ReportEndpointStats struct {
	Endpoint    string `json:"endpoint"`
	Count       int64  `json:"count"`
	MinMicros   int64  `json:"minMicros"`
	MaxMicros   int64  `json:"maxMicros"`
	AvgMicros   int64  `json:"avgMicros"`
	TotalMicros int64  `json:"totalMicros"`
}

ReportEndpointStats contains deterministic per-endpoint latency aggregates.

type ReportIdentity

type ReportIdentity struct {
	Timestamp   string   `json:"timestamp"`
	ToolVersion string   `json:"toolVersion"`
	Metadata    Metadata `json:"metadata,omitzero"`
}

ReportIdentity is the source metadata needed to identify a compared report.

type ReportLoad

type ReportLoad struct {
	TotalRequests     int64                 `json:"totalRequests"`
	SuccessCount      int64                 `json:"successCount"`
	ErrorCount        int64                 `json:"errorCount"`
	ErrorRatePercent  float64               `json:"errorRatePercent"`
	DurationMS        int64                 `json:"durationMs"`
	RequestsPerSecond float64               `json:"requestsPerSecond"`
	Endpoints         []ReportEndpointStats `json:"endpoints"`
}

ReportLoad contains aggregate load-generation measurements.

type ReportProfile

type ReportProfile struct {
	Type       string `json:"type"`
	Metric     string `json:"metric"`
	FilePath   string `json:"filePath"`
	SizeBytes  int64  `json:"sizeBytes"`
	DurationMS int64  `json:"durationMs"`
}

ReportProfile identifies one saved pprof artifact.

type ReportRunConfig

type ReportRunConfig struct {
	Mode             string `json:"mode"`
	OpenAPIPath      string `json:"openapiPath,omitempty"`
	TargetURL        string `json:"targetUrl"`
	PprofURL         string `json:"pprofUrl"`
	OutputDir        string `json:"outputDir"`
	DurationMS       int64  `json:"durationMs"`
	CPUDurationMS    int64  `json:"cpuDurationMs"`
	Concurrency      int    `json:"concurrency"`
	RPS              int    `json:"rps"`
	RequestTimeoutMS int64  `json:"requestTimeoutMs"`
	SkipLoad         bool   `json:"skipLoad"`
	ProfileTypes     string `json:"profileTypes"`
	SampleIntervalMS int64  `json:"sampleIntervalMs"`
	SampleCount      int    `json:"sampleCount"`
	FailOn           string `json:"failOn,omitempty"`
	TopFunctions     int    `json:"topFunctions"`
}

ReportRunConfig records the inputs that materially affect a run.

type ThresholdResult

type ThresholdResult struct {
	Configured bool                 `json:"configured"`
	Passed     bool                 `json:"passed"`
	Rules      []ThresholdRule      `json:"rules"`
	Violations []ThresholdViolation `json:"violations"`
}

ThresholdResult records configured profile gates and their outcome.

type ThresholdRule

type ThresholdRule struct {
	ProfileType string  `json:"profileType"`
	Percentage  float64 `json:"percentage"`
}

ThresholdRule is one configured per-function profile threshold.

type ThresholdViolation

type ThresholdViolation struct {
	Function    string  `json:"function"`
	ProfileType string  `json:"profileType"`
	Percentage  float64 `json:"percentage"`
	Threshold   float64 `json:"threshold"`
}

ThresholdViolation identifies a function that exceeded a profile threshold.

Directories

Path Synopsis
cmd
proficiency command
Package main provides the CLI entry point for the proficiency tool.
Package main provides the CLI entry point for the proficiency tool.
internal
load
Package load provides HTTP load generation functionality.
Package load provides HTTP load generation functionality.
openapi
Package openapi provides OpenAPI specification parsing functionality.
Package openapi provides OpenAPI specification parsing functionality.
profile
Package profile provides pprof profile collection functionality.
Package profile provides pprof profile collection functionality.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL