blob: 3e6148a2e7ab4504c25c2f358a6645d4c60904d8 [file] [log] [blame]
package anomalies_impl
import (
"context"
"time"
"go.opencensus.io/trace"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/perf/go/anomalies"
"go.skia.org/infra/perf/go/chromeperf"
"go.skia.org/infra/perf/go/chromeperf/compat"
"go.skia.org/infra/perf/go/git"
"go.skia.org/infra/perf/go/regression"
"go.skia.org/infra/perf/go/types"
)
// sqlAnomaliesStore implements anomalies.Store.
type sqlAnomaliesStore struct {
regStore regression.Store
git git.Git
}
// NewSqlAnomaliesStore returns a new anomalies.Store instance that wraps a
// regression.Store and perfgit.Git.
func NewSqlAnomaliesStore(regStore regression.Store, perfGit git.Git) (*sqlAnomaliesStore, error) {
return &sqlAnomaliesStore{
regStore: regStore,
git: perfGit,
}, nil
}
// GetAnomalies implements anomalies.Store.
// It delegates to the underlying regression.Store.
func (s *sqlAnomaliesStore) GetAnomalies(ctx context.Context, traceNames []string, startCommitPosition int, endCommitPosition int) (chromeperf.AnomalyMap, error) {
ctx, span := trace.StartSpan(ctx, "anomalies.sqlAnomaliesStore.GetAnomalies")
defer span.End()
result := chromeperf.AnomalyMap{}
if startCommitPosition < 0 || endCommitPosition < startCommitPosition {
return nil, skerr.Fmt("invalid commit range for GetAnomalies: [%d, %d]", startCommitPosition, endCommitPosition)
}
regressionsMap, err := s.regStore.Range(ctx, types.CommitNumber(startCommitPosition), types.CommitNumber(endCommitPosition))
if err != nil {
return nil, skerr.Wrapf(err, "Failed to load regressions from database")
}
targetTraceSet := make(map[string]bool, len(traceNames))
for _, tn := range traceNames {
targetTraceSet[tn] = true
}
for _, allRegressionsForCommit := range regressionsMap {
for _, reg := range allRegressionsForCommit.ByAlertID {
convertedAnomalies, err := compat.ConvertRegressionToAnomalies(reg)
if err != nil {
sklog.Warningf("Could not convert regression with id %s to anomalies: %s", reg.Id, err)
continue
}
for traceKey, commitMap := range convertedAnomalies {
// Applying filtering.
if len(targetTraceSet) > 0 && !targetTraceSet[traceKey] {
continue
}
if _, ok := result[traceKey]; !ok {
result[traceKey] = chromeperf.CommitNumberAnomalyMap{}
}
for commitNumber, anomaly := range commitMap {
result[traceKey][commitNumber] = anomaly
sklog.Debugf("Constructed anomaly for trace %s: %+v", traceKey, anomaly)
}
}
}
}
sklog.Debugf("Found %d anomalies for traceNames: %v, startCommitPosition: %d, endCommitPosition: %d", len(result), traceNames, startCommitPosition, endCommitPosition)
return result, nil
}
// GetAnomaliesInTimeRange implements anomalies.Store.
// It uses perfgit.Git to convert time range to commit range, then calls GetAnomalies.
func (s *sqlAnomaliesStore) GetAnomaliesInTimeRange(ctx context.Context, traceNames []string, startTime time.Time, endTime time.Time) (chromeperf.AnomalyMap, error) {
ctx, span := trace.StartSpan(ctx, "anomalies.sqlAnomaliesStore.GetAnomaliesInTimeRange")
defer span.End()
if s.git == nil {
return nil, skerr.Fmt("Git provider is not initialized for sqlAnomaliesStore")
}
if startTime.After(endTime) {
return nil, skerr.Fmt("invalid time range: start %v is after end %v", startTime, endTime)
}
commits, err := s.git.CommitSliceFromTimeRange(ctx, startTime, endTime)
if err != nil {
return nil, skerr.Wrapf(err, "failed to get commits from time range %v to %v", startTime, endTime)
}
if len(commits) == 0 {
sklog.Debugf("No commits found in time range %v to %v", startTime, endTime)
return chromeperf.AnomalyMap{}, nil
}
startCommitNumber := commits[0].CommitNumber
endCommitNumber := commits[len(commits)-1].CommitNumber
sklog.Debugf("Found %d commits in time range %v to %v. Converted to commit range [%d, %d]", len(commits), startTime, endTime, startCommitNumber, endCommitNumber)
return s.GetAnomalies(ctx, traceNames, int(startCommitNumber), int(endCommitNumber))
}
// GetAnomaliesAroundRevision implements anomalies.Store.
// It defines a window around the given revision, fetches anomalies in that window,
// and then transforms them.
func (s *sqlAnomaliesStore) GetAnomaliesAroundRevision(ctx context.Context, revision int) ([]chromeperf.AnomalyForRevision, error) {
ctx, span := trace.StartSpan(ctx, "anomalies.sqlAnomaliesStore.GetAnomaliesAroundRevision")
defer span.End()
if s.git == nil {
return nil, skerr.Fmt("Git provider is not initialized for sqlAnomaliesStore")
}
const windowSize = 500
startCommitPosition := revision - windowSize
if startCommitPosition < 0 {
startCommitPosition = 0
}
endCommitPosition := revision + windowSize
anomalyMap, err := s.GetAnomalies(ctx, []string{}, startCommitPosition, endCommitPosition)
if err != nil {
return nil, skerr.Wrapf(err, "failed to get anomalies for revision window [%d, %d]", startCommitPosition, endCommitPosition)
}
// Transform AnomalyMap to []chromeperf.AnomalyForRevision.
var result []chromeperf.AnomalyForRevision
for traceKey, commitMap := range anomalyMap {
for _, anomaly := range commitMap {
testPath := anomaly.TestPath
if testPath == "" {
testPath = traceKey
}
result = append(result, chromeperf.AnomalyForRevision{
StartRevision: anomaly.StartRevision,
EndRevision: anomaly.EndRevision,
Anomaly: anomaly,
TestPath: testPath,
})
}
}
sklog.Debugf("Found %d anomalies around revision %d (window [%d, %d])", len(result), revision, startCommitPosition, endCommitPosition)
return result, nil
}
// Verify sqlAnomaliesStore implements anomalies.Store.
var _ anomalies.Store = (*sqlAnomaliesStore)(nil)