perf

package
v1.33.0 Latest Latest
Warning

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

Go to latest
Published: Mar 10, 2026 License: BSD-3-Clause Imports: 15 Imported by: 0

Documentation

Overview

Package perf provides call-graph-based performance analysis for ELPS source files. It detects hot paths, scaling risks, expensive-in-loop patterns, and recursive cycles by building and solving a call graph.

The analysis has three passes:

  1. Local scan — per-function AST walk producing FunctionSummary + CallEdge.
  2. Graph construction — aggregates summaries into a CallGraph.
  3. Solve — cycle detection, topological sort, score/scaling propagation, and issue generation.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FindConfigFile

func FindConfigFile(dir string) string

FindConfigFile searches for .elps-analyze.yaml starting from dir and walking up to the filesystem root. Returns the path if found, or empty string if not found.

func FormatJSON

func FormatJSON(w io.Writer, issues []Issue) error

FormatJSON writes issues as a JSON array.

func FormatText

func FormatText(w io.Writer, issues []Issue)

FormatText writes issues in a human-readable text format similar to go vet output.

func Solve

func Solve(graph *CallGraph, cfg *Config) ([]*SolvedFunction, []Issue)

Solve performs Pass 3: cycle detection, topological sort, score and scaling propagation, and issue generation. It returns the solved function data and any performance issues found.

Types

type CallContext

type CallContext struct {
	// LoopDepth is the nesting depth of loop forms at the call site.
	LoopDepth int
	// InLoop is true when LoopDepth > 0.
	InLoop bool
}

CallContext describes where a call occurs relative to loops.

type CallEdge

type CallEdge struct {
	// Caller is the qualified name of the calling function.
	Caller string
	// Callee is the qualified name of the called function.
	Callee string
	// Source is the location of the call expression.
	Source *token.Location
	// Context describes the loop nesting at the call site.
	Context CallContext
	// IsExpensive is true if the callee matches an expensive function pattern.
	IsExpensive bool
}

CallEdge represents a call from one function to another.

type CallGraph

type CallGraph struct {
	// Functions maps qualified function names to their summaries.
	Functions map[string]*FunctionSummary
	// Edges is the complete set of call edges.
	Edges []CallEdge
}

CallGraph holds the complete call graph for a set of source files.

func BuildGraph

func BuildGraph(summaries []*FunctionSummary) *CallGraph

BuildGraph performs Pass 2: aggregates per-function summaries into a CallGraph. Summaries from multiple files are merged by function name.

type Config

type Config struct {
	// ExpensiveFunctions are glob patterns matching function names considered
	// expensive (e.g., "db-*", "http-*"). Calls to these functions receive
	// an additional cost penalty.
	ExpensiveFunctions []string `yaml:"expensive_functions"`

	// ExpensiveCost is the extra cost added per call to an expensive function.
	ExpensiveCost int `yaml:"expensive_cost"`

	// LoopKeywords are symbols that introduce loop iteration in ELPS
	// (e.g., "dotimes", "map", "foldl"). Embedders may append
	// domain-specific iteration forms via config.
	LoopKeywords []string `yaml:"loop_keywords"`

	// LoopMultiplier is the assumed number of iterations per loop level.
	// Used to amplify costs inside loops.
	LoopMultiplier int `yaml:"loop_multiplier"`

	// MaxScore is the PERF001 threshold: functions with TotalScore above
	// this value are flagged as hot paths.
	MaxScore int `yaml:"max_score"`

	// MaxAcceptableOrder is the PERF002 warning threshold: functions with
	// ScalingOrder >= this value receive a warning.
	MaxAcceptableOrder int `yaml:"max_acceptable_order"`

	// ScalingErrorThreshold is the PERF002 error threshold: functions with
	// ScalingOrder >= this value receive an error.
	ScalingErrorThreshold int `yaml:"scaling_error_threshold"`

	// MaxRecursionOrder caps the scaling order assigned to recursive functions.
	MaxRecursionOrder int `yaml:"max_recursion_order"`

	// FunctionCosts provides per-function cost overrides. Keys are function
	// names (exact match), values are the base cost for a single call.
	// Overrides the default cost of 1. Embedders use this to assign
	// domain-specific weights (e.g., {"statedb:put": 100}).
	FunctionCosts map[string]int `yaml:"function_costs"`

	// SuppressionPrefix is the comment text that disables analysis for a
	// function. Defaults to "elps-analyze-disable". Embedders can
	// override this (e.g., "substrate-analyze-disable").
	SuppressionPrefix string `yaml:"suppression_prefix"`

	// Rules filters which rules to run. When empty, all rules run.
	// Valid values: "PERF001", "PERF002", "PERF003", "PERF004", "UNKNOWN001".
	Rules []string `yaml:"rules"`
}

Config controls the behavior of the performance analyzer.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns a Config with sensible defaults.

func LoadConfigFile

func LoadConfigFile(path string) (*Config, error)

LoadConfigFile reads a YAML config file and returns a Config with defaults for any unset fields. Returns DefaultConfig() if the file does not exist.

type FunctionSummary

type FunctionSummary struct {
	// Name is the qualified function name (package:name or just name).
	Name string
	// Source is the location of the function definition.
	Source *token.Location
	// File is the source file containing this function.
	File string
	// LocalCost is the base cost of the function body (before propagation).
	LocalCost int
	// MaxLoopDepth is the deepest loop nesting found in the function body.
	MaxLoopDepth int
	// Calls are the outgoing call edges from this function.
	Calls []CallEdge
	// Suppressed is true if the function has an elps-analyze-disable comment.
	Suppressed bool
}

FunctionSummary holds analysis results for a single function.

func ScanFile

func ScanFile(exprs []*lisp.LVal, filename string, cfg *Config) []*FunctionSummary

ScanFile performs Pass 1: a local scan of parsed expressions from a single file, producing FunctionSummary values for each defun/defmacro.

type Issue

type Issue struct {
	// Rule identifies which performance rule triggered.
	Rule RuleID
	// Severity indicates the severity level.
	Severity Severity
	// Message is a human-readable description of the issue.
	Message string
	// Function is the function where the issue was detected.
	Function string
	// Source is the source location of the issue.
	Source *token.Location
	// File is the source file.
	File string
	// Details provides additional context (e.g., cycle members, call chain).
	Details []string
	// Fingerprint is a stable identifier for this issue across runs,
	// derived from sha256(file:function:rule)[:16].
	Fingerprint string
	// Trace is the call chain that explains why this issue was raised.
	// For PERF001/PERF002, it shows the hot path from caller to expensive leaf.
	// For PERF003, it shows the loop context.
	Trace []TraceEntry
}

Issue is a performance diagnostic generated by the solver.

func (Issue) MarshalJSON

func (issue Issue) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler for Issue.

type Result

type Result struct {
	// Solved contains the propagated scores for each function.
	Solved []*SolvedFunction
	// Issues contains all performance diagnostics.
	Issues []Issue
	// Graph is the call graph used for analysis.
	Graph *CallGraph
}

Result holds the complete output of a performance analysis run.

func Analyze

func Analyze(exprs []*lisp.LVal, filename string, cfg *Config) *Result

Analyze runs the full 3-pass pipeline on pre-parsed expressions from a single file.

func AnalyzeFiles

func AnalyzeFiles(files []string, cfg *Config) (*Result, error)

AnalyzeFiles runs the full pipeline across multiple source files, building a combined call graph.

type RuleID

type RuleID string

RuleID identifies a performance rule.

const (
	// PERF001 flags functions whose total propagated cost exceeds the threshold.
	PERF001 RuleID = "PERF001"
	// PERF002 flags functions with scaling order at or above the threshold.
	PERF002 RuleID = "PERF002"
	// PERF003 flags expensive function calls nested inside loops.
	PERF003 RuleID = "PERF003"
	// PERF004 flags recursive call cycles.
	PERF004 RuleID = "PERF004"
	// UNKNOWN001 flags dynamic dispatch where callee cannot be resolved.
	UNKNOWN001 RuleID = "UNKNOWN001"
)

type Severity

type Severity int

Severity indicates the severity of a performance issue.

const (
	SeverityError Severity = iota + 1
	SeverityWarning
	SeverityInfo
)

func (Severity) String

func (s Severity) String() string

type SolvedFunction

type SolvedFunction struct {
	// Name is the qualified function name.
	Name string
	// Source is the location of the function definition.
	Source *token.Location
	// File is the source file.
	File string
	// LocalCost is the base cost before propagation.
	LocalCost int
	// TotalScore is the propagated cost including callee contributions.
	TotalScore int
	// ScalingOrder is the propagated loop amplification factor.
	// 0 = O(1), 1 = O(N), 2 = O(N²), etc.
	ScalingOrder int
	// InCycle is true if this function participates in a recursive cycle.
	InCycle bool
}

SolvedFunction holds propagated scores for a function after solving.

type TraceEntry

type TraceEntry struct {
	// Function is the function name at this step.
	Function string
	// Source is the source location.
	Source *token.Location
	// Note describes what happens at this step (e.g., "calls db-put in loop (depth 1)").
	Note string
}

TraceEntry is a single step in a call chain trace.

Jump to

Keyboard shortcuts

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