blob: e5fde02a7bbc92eed6d2e6edacf7e6544f0b9b0b [file] [log] [blame]
package notify
import (
"context"
"fmt"
"strings"
"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"
"go.skia.org/infra/perf/go/chromeperf"
"go.skia.org/infra/perf/go/clustering2"
"go.skia.org/infra/perf/go/git/provider"
"go.skia.org/infra/perf/go/stepfit"
"go.skia.org/infra/perf/go/ui/frame"
)
// ChromeperfNotifier struct used to send regression data to chromeperf.
type ChromePerfNotifier struct {
chromePerfClient chromeperf.AnomalyApiClient
}
// NewChromePerfNotifier returns a new ChromePerfNotifier instance.
func NewChromePerfNotifier(ctx context.Context, anomalyApiClient chromeperf.AnomalyApiClient) (*ChromePerfNotifier, error) {
var err error
if anomalyApiClient == nil {
anomalyApiClient, err = chromeperf.NewAnomalyApiClient(ctx)
if err != nil {
return nil, err
}
}
sklog.Info("Creating a new chromeperf notifier")
return &ChromePerfNotifier{
chromePerfClient: anomalyApiClient,
}, nil
}
// RegressionFound implements notify.Notifier.
// Invoked when a new regression is detected.
func (n *ChromePerfNotifier) RegressionFound(
ctx context.Context,
commit,
previousCommit provider.Commit,
alert *alerts.Alert,
cl *clustering2.ClusterSummary,
frame *frame.FrameResponse) (string, error) {
sklog.Infof("%d traces in regression found information for alert %s", len(frame.DataFrame.TraceSet), alert.DisplayName)
anomalyIds := []string{}
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", 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)
response, err := n.chromePerfClient.ReportRegression(
ctx,
getTestPath(paramset),
int32(previousCommit.CommitNumber),
int32(commit.CommitNumber),
"chromium",
isRegressionImprovement(paramset, cl.StepFit.Status),
paramset["bot"],
true,
medianBeforeAnomaly,
medianAfterAnomaly)
if err != nil {
return "", err
}
anomalyIds = append(anomalyIds, response.AnomalyId)
}
return strings.Join(anomalyIds, ","), nil
}
// RegressionMissing implements notify.Notifier.
// Invoked when a previous regression is recovered.
func (n *ChromePerfNotifier) RegressionMissing(
ctx context.Context,
commit,
previousCommit provider.Commit,
alert *alerts.Alert,
cl *clustering2.ClusterSummary,
frame *frame.FrameResponse,
threadingReference string) error {
sklog.Info("Sending regression missing information to Chromeperf")
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) {
const errorFormat = "Invalid paramset %s for chromeperf"
sklog.Debugf(errorFormat, paramset)
return skerr.Fmt(errorFormat, 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)
_, err = n.chromePerfClient.ReportRegression(
ctx,
getTestPath(paramset),
int32(previousCommit.CommitNumber),
int32(commit.CommitNumber),
"chromium",
isRegressionImprovement(paramset, cl.StepFit.Status),
paramset["bot"],
true,
medianBeforeAnomaly,
medianAfterAnomaly)
if err != nil {
return err
}
}
return nil
}
// ExampleSend is for dummy data. Do nothing!
func (n *ChromePerfNotifier) ExampleSend(ctx context.Context, alert *alerts.Alert) error {
sklog.Info("Doing example send on chromeperf notifier")
return nil
}
// isParamSetValid returns true if the paramsets contains all the
// keys required by chromeperf api.
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
}
// isRegressionImprovement returns true if the metric has moved towards the improvement direction.
func isRegressionImprovement(paramset map[string]string, stepFitStatus stepfit.StepFitStatus) bool {
if _, ok := paramset["improvement_direction"]; ok {
improvementDirection := paramset["improvement_direction"]
return improvementDirection == "down" && stepFitStatus == stepfit.LOW || improvementDirection == "up" && stepFitStatus == stepfit.HIGH
}
return false
}
// GetTestPath returns a test path based on the values found in the paramset.
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
}