blob: 33493b5f753af34c5720461ee78ec7352eb315dd [file] [log] [blame]
// Package regression provides for tracking Perf regressions.
package regression
import (
"errors"
"sync"
"time"
"github.com/google/uuid"
"go.skia.org/infra/perf/go/clustering2"
"go.skia.org/infra/perf/go/types"
"go.skia.org/infra/perf/go/ui/frame"
)
var ErrNoClusterFound = errors.New("No Cluster.")
// Status is used in TriageStatus.
type Status string
// ClusterType is used to denote type of cluster in regression2 schema.
type ClusterType string
// Status constants.
const (
// None means there is no regression.
None Status = ""
// Positive means this change in performance is OK/expected.
Positive Status = "positive"
// Negative means this regression is a bug.
Negative Status = "negative"
// Untriaged means the regression has not been triaged.
Untriaged Status = "untriaged"
// Available cluster types in regression2
HighClusterType ClusterType = "high"
LowClusterType ClusterType = "low"
NoneClusterType ClusterType = "none"
)
// AllStatus is a slice of all values of type Status.
var AllStatus = []Status{None, Positive, Negative, Untriaged}
// AllRegressionsForCommit is a map[alertid]Regression.
type AllRegressionsForCommit struct {
ByAlertID map[string]*Regression `json:"by_query"`
mutex sync.Mutex
}
// TriageStatus is the status of a found regression.
type TriageStatus struct {
Status Status `json:"status"`
Message string `json:"message"`
}
// Regression tracks the status of the Low and High regression clusters, if they
// exist for a given CommitID and alertid.
//
// Note that Low and High can be nil if no regression has been found in that
// direction.
//
// TODO(jcgregorio) Now that we can search for regressions using GroupBy it is possible
// that Frame will only be valid for Low or High. Fix by refactoring Regression.
type Regression struct {
Low *clustering2.ClusterSummary `json:"low"` // Can be nil.
High *clustering2.ClusterSummary `json:"high"` // Can be nil.
Frame *frame.FrameResponse `json:"frame"` // Describes the Low and High ClusterSummary's.
LowStatus TriageStatus `json:"low_status"`
HighStatus TriageStatus `json:"high_status"`
// The fields below are only to be used with the regression2 schema.
Id string `json:"id"`
CommitNumber types.CommitNumber `json:"commit_number"`
PrevCommitNumber types.CommitNumber `json:"prev_commit_number"`
AlertId int64 `json:"alert_id"`
CreationTime time.Time `json:"creation_time"`
MedianBefore float32 `json:"median_before"`
MedianAfter float32 `json:"median_after"`
IsImprovement bool `json:"is_improvement"`
ClusterType string `json:"cluster_type"`
}
// NewRegression returns a new *Regression.
func NewRegression() *Regression {
return &Regression{
LowStatus: TriageStatus{
Status: None,
},
HighStatus: TriageStatus{
Status: None,
},
Id: uuid.NewString(),
}
}
// New returns a new *Regressions.
func New() *AllRegressionsForCommit {
return &AllRegressionsForCommit{
ByAlertID: map[string]*Regression{},
}
}
// Merge the results from rhs into this Regression.
func (r *Regression) Merge(rhs *Regression) *Regression {
if rhs.Low != nil {
if r.Low != nil && (rhs.Low.StepFit.Regression > r.Low.StepFit.Regression) {
r.Low = rhs.Low
r.LowStatus = rhs.LowStatus
r.Frame = rhs.Frame
} else {
r.Low = rhs.Low
r.LowStatus = rhs.LowStatus
r.Frame = rhs.Frame
}
}
if rhs.High != nil {
if r.High != nil && (rhs.High.StepFit.Regression < r.High.StepFit.Regression) {
r.High = rhs.High
r.HighStatus = rhs.HighStatus
r.Frame = rhs.Frame
} else {
r.High = rhs.High
r.HighStatus = rhs.HighStatus
r.Frame = rhs.Frame
}
}
return r
}
// Triaged returns true if triaged.
func (r *Regression) Triaged() bool {
ret := true
ret = ret && (r.HighStatus.Status != Untriaged)
ret = ret && (r.LowStatus.Status != Untriaged)
return ret
}
// GetClusterTypeAndSummaryAndTriageStatus returns the cluster type, cluster summary
// and triage status objects for the regression.
func (r *Regression) GetClusterTypeAndSummaryAndTriageStatus() (ClusterType, *clustering2.ClusterSummary, TriageStatus) {
if r.High != nil {
return HighClusterType, r.High, r.HighStatus
} else if r.Low != nil {
return LowClusterType, r.Low, r.LowStatus
} else {
return NoneClusterType, nil, TriageStatus{}
}
}