blob: e9c37bfe85cedf4c622c46ed484dd787a4425ceb [file] [log] [blame]
package notifier
import (
"context"
"fmt"
"go.skia.org/infra/go/query"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/vec32"
"go.skia.org/infra/perf/go/alerts"
ag "go.skia.org/infra/perf/go/anomalygroup/utils"
"go.skia.org/infra/perf/go/clustering2"
"go.skia.org/infra/perf/go/git/provider"
"go.skia.org/infra/perf/go/ui/frame"
)
// AnomalyGroupNotifier struct used to group regression and trigger group action.
type AnomalyGroupNotifier struct {
grouper ag.AnomalyGrouper
}
// NewAnomalyGroupNotifier returns a new AnomalyGroupNotifier instance.
func NewAnomalyGroupNotifier(ctx context.Context, anomalygrouper ag.AnomalyGrouper) *AnomalyGroupNotifier {
if anomalygrouper == nil {
anomalygrouper = &ag.AnomalyGrouperImpl{}
}
sklog.Info("Creating a new anomaly group notifier")
return &AnomalyGroupNotifier{
grouper: anomalygrouper,
}
}
// RegressionFound implements notify.Notifier.
// Invoked when a new regression is detected.
func (n *AnomalyGroupNotifier) RegressionFound(
ctx context.Context,
commit,
previousCommit provider.Commit,
alert *alerts.Alert,
cl *clustering2.ClusterSummary,
frame *frame.FrameResponse,
regressionID string) (string, error) {
sklog.Infof("[AG] %d traces in regression found information for alert %s", len(frame.DataFrame.TraceSet), alert.DisplayName)
// Debug logs on b/357628141: why the same anomaly id is added to the group multiple times?
if len(frame.DataFrame.TraceSet) > 1 {
traceset_keys := make([]string, len(frame.DataFrame.TraceSet))
i := 0
for traceset_key := range frame.DataFrame.TraceSet {
traceset_keys[i] = traceset_key
i++
}
sklog.Debugf("[AG] More than one keys found in anomaly's traceset. Anomaly: %s. Keys: %s", regressionID, traceset_keys)
}
for key := range frame.DataFrame.TraceSet {
paramset, err := query.ParseKey(key)
if err != nil {
return "", skerr.Wrapf(err, "Error parsing key %s", key)
}
if !isParamSetValid(paramset) {
return "", skerr.Fmt("Invalid paramset %s for chromeperf data", paramset)
}
medianBeforeAnomaly, _, _, _ := vec32.TwoSidedStdDev(cl.Centroid[:cl.StepFit.TurningPoint])
medianAfterAnomaly, _, _, _ := vec32.TwoSidedStdDev(cl.Centroid[cl.StepFit.TurningPoint:])
sklog.Infof("Median Before: %f, Median After: %f", medianBeforeAnomaly, medianAfterAnomaly)
testPath := getTestPath(paramset)
_, err = n.grouper.ProcessRegressionInGroup(
ctx,
alert,
regressionID,
int64(previousCommit.CommitNumber),
int64(commit.CommitNumber),
testPath,
paramset)
if err != nil {
return "", skerr.Wrapf(err, "error processing regression")
}
}
return "", nil
}
// RegressionMissing implements notify.Notifier.
// Invoked when a previous regression is recovered.
func (n *AnomalyGroupNotifier) RegressionMissing(
ctx context.Context,
commit,
previousCommit provider.Commit,
alert *alerts.Alert,
cl *clustering2.ClusterSummary,
frame *frame.FrameResponse,
threadingReference string) error {
sklog.Info("No op function for AnomalyGroupNotifier.RegressionMissing")
return nil
}
// ExampleSend is for dummy data. Do nothing!
func (n *AnomalyGroupNotifier) ExampleSend(ctx context.Context, alert *alerts.Alert) error {
sklog.Info("No op function for AnomalyGroupNotifier.ExampleSend")
return nil
}
// UpdateRegressionNotification implements Transport.
func (n *AnomalyGroupNotifier) UpdateNotification(ctx context.Context, commit, previousCommit provider.Commit, alert *alerts.Alert, cl *clustering2.ClusterSummary, frame *frame.FrameResponse, notificationId string) error {
return nil
}
// GetTestPath returns a test path based on the values found in the paramset.
// TODO(wenbinzhang): using kvp in config to control which keys to expect
func getTestPath(paramset map[string]string) string {
keys := []string{"bot", "benchmark", "test", "subtest_1", "subtest_2", "subtest_3"}
testPath := paramset["master"]
for _, key := range keys {
val, ok := paramset[key]
if ok {
testPath = fmt.Sprintf("%s/%s", testPath, val)
} else {
break
}
}
return testPath
}
// isParamSetValid returns true if the paramsets contains all the
// keys required by chromeperf api.
// TODO(wenbinzhang): using kvp in config to control which keys to expect
func isParamSetValid(paramset map[string]string) bool {
requiredKeys := []string{"master", "bot", "benchmark", "test", "subtest_1"}
for _, key := range requiredKeys {
_, ok := paramset[key]
if !ok {
return false
}
}
return true
}