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
2 changes: 2 additions & 0 deletions cli/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ var validFormats = map[string]bool{
"detailed": true,
"summary-html": true,
"detailed-html": true,
"summary-json": true,
"detailed-json": true,
}

// runTrack handles the track command execution
Expand Down
12 changes: 2 additions & 10 deletions engine/tracker/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,12 @@ func DetectChange(baseline, current *parser.LineObj) (*FunctionChangeResult, err
ChangeType: changeType,
FlatChangePercent: flatChange,
CumChangePercent: cumChange,
FlatAbsolute: struct {
Before float64
After float64
Delta float64
}{
FlatAbsolute: AbsoluteChange{
Before: baseline.Flat,
After: current.Flat,
Delta: current.Flat - baseline.Flat,
},
CumAbsolute: struct {
Before float64
After float64
Delta float64
}{
CumAbsolute: AbsoluteChange{
Before: baseline.Cum,
After: current.Cum,
Delta: current.Cum - baseline.Cum,
Expand Down
129 changes: 129 additions & 0 deletions engine/tracker/profile_change_report.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tracker

import (
"encoding/json"
"fmt"
"html/template"
"log/slog"
Expand Down Expand Up @@ -323,6 +324,126 @@ func (r *ProfileChangeReport) generateDetailedHTMLReport(outputPath string) erro
return t.Execute(file, data)
}

// JSON data structures
type jsonSummaryData struct {
TotalFunctions int `json:"total_functions"`
Statistics jsonStatistics `json:"statistics"`
Regressions []*FunctionChangeResult `json:"regressions"`
Improvements []*FunctionChangeResult `json:"improvements"`
}

type jsonStatistics struct {
Regressions int `json:"regressions"`
Improvements int `json:"improvements"`
Stable int `json:"stable"`
}

type jsonDetailedData struct {
TotalFunctions int `json:"total_functions"`
Statistics jsonStatistics `json:"statistics"`
Changes []*FunctionChangeResult `json:"changes"`
SortOrder string `json:"sort_order"`
}

func (r *ProfileChangeReport) generateJSONSummary(outputPath string) error {
var regressionList, improvementList []*FunctionChangeResult
var stable int

for _, change := range r.FunctionChanges {
switch change.ChangeType {
case shared.REGRESSION:
regressionList = append(regressionList, change)
case shared.IMPROVEMENT:
improvementList = append(improvementList, change)
default:
stable++
}
}

// Sort regressions by percentage (biggest regression first)
sort.Slice(regressionList, func(i, j int) bool {
return regressionList[i].FlatChangePercent > regressionList[j].FlatChangePercent
})

// Sort improvements by absolute percentage (biggest improvement first)
sort.Slice(improvementList, func(i, j int) bool {
return math.Abs(improvementList[i].FlatChangePercent) > math.Abs(improvementList[j].FlatChangePercent)
})

data := jsonSummaryData{
TotalFunctions: len(r.FunctionChanges),
Statistics: jsonStatistics{
Regressions: len(regressionList),
Improvements: len(improvementList),
Stable: stable,
},
Regressions: regressionList,
Improvements: improvementList,
}

file, err := os.Create(outputPath)
if err != nil {
return err
}
defer file.Close()

encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
return encoder.Encode(data)
}

func (r *ProfileChangeReport) generateDetailedJSONReport(outputPath string) error {
changes := r.FunctionChanges

// Count types
var regressions, improvements, stable int
for _, change := range changes {
switch change.ChangeType {
case shared.REGRESSION:
regressions++
case shared.IMPROVEMENT:
improvements++
default:
stable++
}
}

// Sort: regressions → improvements → stable, each by magnitude
typePriority := map[string]int{
shared.REGRESSION: regressionPriority,
shared.IMPROVEMENT: improvementPriority,
shared.STABLE: stablePriority,
}

sort.Slice(changes, func(i, j int) bool {
if typePriority[changes[i].ChangeType] != typePriority[changes[j].ChangeType] {
return typePriority[changes[i].ChangeType] < typePriority[changes[j].ChangeType]
}
return math.Abs(changes[i].FlatChangePercent) > math.Abs(changes[j].FlatChangePercent)
})

data := jsonDetailedData{
TotalFunctions: len(changes),
Statistics: jsonStatistics{
Regressions: regressions,
Improvements: improvements,
Stable: stable,
},
Changes: changes,
SortOrder: "Regressions (worst → best), then Improvements (best → worst), then Stable",
}

file, err := os.Create(outputPath)
if err != nil {
return err
}
defer file.Close()

encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
return encoder.Encode(data)
}

func (r *ProfileChangeReport) ChooseOutputFormat(outputFormat string) {
switch outputFormat {
case "summary":
Expand All @@ -337,5 +458,13 @@ func (r *ProfileChangeReport) ChooseOutputFormat(outputFormat string) {
if err := r.generateDetailedHTMLReport("detailed.html"); err != nil {
slog.Info("detailed-html failed", "err", err)
}
case "summary-json":
if err := r.generateJSONSummary("summary.json"); err != nil {
slog.Info("summary-json failed", "err", err)
}
case "detailed-json":
if err := r.generateDetailedJSONReport("detailed.json"); err != nil {
slog.Info("detailed-json failed", "err", err)
}
}
}
28 changes: 13 additions & 15 deletions engine/tracker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,18 @@ type ProfileChangeReport struct {
FunctionChanges []*FunctionChangeResult
}

type AbsoluteChange struct {
Before float64 `json:"before"`
After float64 `json:"after"`
Delta float64 `json:"delta"`
}

type FunctionChangeResult struct {
FunctionName string
ChangeType string // shared.REGRESSION, shred.IMPROVEMENT, shared.STABLE
FlatChangePercent float64
CumChangePercent float64
FlatAbsolute struct {
Before float64
After float64
Delta float64
}
CumAbsolute struct {
Before float64
After float64
Delta float64
}
Timestamp time.Time
FunctionName string `json:"function_name"`
ChangeType string `json:"change_type"`
FlatChangePercent float64 `json:"flat_change_percent"`
CumChangePercent float64 `json:"cum_change_percent"`
FlatAbsolute AbsoluteChange `json:"flat_absolute"`
CumAbsolute AbsoluteChange `json:"cum_absolute"`
Timestamp time.Time `json:"timestamp"`
}
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ module github.com/AlexsanderHamir/prof

go 1.24.3

require github.com/spf13/cobra v1.9.1
require (
github.com/microcosm-cc/bluemonday v1.0.27
github.com/spf13/cobra v1.9.1
)

require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/spf13/pflag v1.0.7 // indirect
golang.org/x/net v0.26.0 // indirect
)
Loading