perf

package
v1.47.1 Latest Latest
Warning

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

Go to latest
Published: Apr 9, 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 FormatMermaid added in v1.35.0

func FormatMermaid(w io.Writer, result *Result) error

FormatMermaid writes a Mermaid flowchart of the call graph from result. Nodes are labeled with function name, score, and scaling order. Edges show loop context annotations and cycle edges are styled distinctly.

func FormatSARIF added in v1.35.0

func FormatSARIF(w io.Writer, issues []Issue, toolName, toolVersion string) error

FormatSARIF writes issues in SARIF v2.1.0 JSON format. The toolName and toolVersion parameters identify the analysis tool in the SARIF output (e.g., "elps-perf", "0.1.0").

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"`

	// AmplificationCausesScaling enables N+1 detection mode. When true,
	// calling an expensive function inside a loop adds +1 to the caller's
	// scaling order beyond the loop depth contribution. This surfaces the
	// classic N+1 query pattern more aggressively.
	AmplificationCausesScaling bool `yaml:"amplification_causes_scaling"`

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

	// ExcludeFiles are file glob patterns excluded from CLI-oriented analysis
	// workflows. The analyzer core remains file-agnostic.
	ExcludeFiles []string `yaml:"exclude_files"`

	// IncludeTests opts back into including *_test.lisp files in CLI-oriented
	// analysis workflows.
	IncludeTests bool `yaml:"include_tests"`
}

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
	// SuppressAll is true if the function has a bare elps-analyze-disable comment.
	SuppressAll bool
	// SuppressedRules contains rule IDs suppressed for this function.
	SuppressedRules map[RuleID]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