blob: eae5c2022d178f51d15b35574ea3e5e803ce9d0c [file] [log] [blame]
package catapult
import (
"fmt"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/pinpoint/go/compare"
"go.skia.org/infra/pinpoint/go/workflows"
"go.skia.org/infra/pinpoint/go/workflows/internal"
"go.temporal.io/sdk/workflow"
pinpoint_proto "go.skia.org/infra/pinpoint/proto/v1"
)
const (
BisectJobNameTemplate = "[Skia] Performance bisect on %s/%s"
)
// updateStatesWithComparisons goes through each state and appends legacy comparisons
func updateStatesWithComparisons(states []*pinpoint_proto.LegacyJobResponse_State, magnitude float64, direction compare.ImprovementDir) error {
if len(states) < 2 {
return skerr.Fmt("cannot create comparisons when there are less than 2 objects")
}
// handling idx 0.
perfResult, err := compare.ComparePerformance(states[0].Values, states[1].Values, magnitude, direction)
if err != nil {
return skerr.Wrap(err)
}
states[0].Comparisons = &pinpoint_proto.LegacyJobResponse_State_Comparison{
Next: string(perfResult.Verdict),
}
// everything else inbetween should have prev and next set.
for idx := 1; idx < len(states)-1; idx++ {
currState := states[idx]
perfResult, err := compare.ComparePerformance(currState.Values, states[idx+1].Values, magnitude, direction)
if err != nil {
return skerr.Wrap(err)
}
currState.Comparisons = &pinpoint_proto.LegacyJobResponse_State_Comparison{
Prev: states[idx-1].Comparisons.Next,
Next: string(perfResult.Verdict),
}
}
// on the last idx, only prev is set.
states[len(states)-1].Comparisons = &pinpoint_proto.LegacyJobResponse_State_Comparison{
Prev: states[len(states)-2].Comparisons.Next,
}
return nil
}
// ConvertToCatapultResponseWorkflow converts raw data from a Skia bisection into a Catapult-supported format.
func ConvertToCatapultResponseWorkflow(ctx workflow.Context, p *workflows.BisectParams, be *internal.BisectExecution) (*pinpoint_proto.LegacyJobResponse, error) {
ctx = workflow.WithChildOptions(ctx, childWorkflowOptions)
ctx = workflow.WithActivityOptions(ctx, regularActivityOptions)
// Updated is not set because it has auto_now_add=True
// Non required fields that are not set:
// - StartedTime
// - Status: This property is derived from one of (failed, cancelled, completed or running), which
// are all computed properties.
// - Exception: TODO() Update this to also add exception details if we want to propagate jobs with
// issues back to the UI
// - CancelReason
// - BatchID: Unsupported field from Skia Bisect
resp := &pinpoint_proto.LegacyJobResponse{
JobId: be.JobId,
Configuration: p.Request.GetConfiguration(),
ImprovementDirection: parseImprovementDir(p.GetImprovementDirection()),
BugId: p.Request.GetBugId(),
Project: p.Request.GetProject(),
ComparisonMode: p.Request.GetComparisonMode(),
Name: fmt.Sprintf(BisectJobNameTemplate, p.Request.GetConfiguration(), p.Request.GetBenchmark()),
User: p.Request.GetUser(),
Created: be.CreateTime,
DifferenceCount: int32(len(be.Culprits)),
Metric: p.Request.GetChart(),
Quests: []string{"Build", "Test", "Get values"},
}
arguments, err := parseArguments(p.Request)
if err != nil {
return nil, skerr.Wrap(err)
}
resp.Arguments = arguments
state, bots, err := parseRawDataToLegacyObject(ctx, be.Comparisons, be.RunData)
if err != nil {
return nil, skerr.Wrap(err)
}
resp.State = state
resp.Bots = bots
// Comparisons (prev, next) are calculated by comparing each one in order of events
// so if we have commits (A, B, C), A is compared with B to determine next. B is compared with C
// and the comparison values at B would be (prev: {next value from A}, next: {current calculated value}).
// C would only have prev set, which would be equivalent to B's next value.
err = updateStatesWithComparisons(state, p.GetMagnitude(), p.GetImprovementDirection())
if err != nil {
return nil, skerr.Wrap(err)
}
return resp, nil
}
// CatapultBisectWorkflow is a Skia-based bisect workflow that's backwards compatible to Catapult.
//
// By backwards compatible, it means that this workflow utilizes the Skia-based bisect, but writes
// the responses to Catapult via Skia-Bridge, such that the Catapult UI can display the results.
// Thus, the workflow method signature should be identical to internal.BisectWorkflow.
// This is written in its own package and in its own workflow so that it's self-contained.
func CatapultBisectWorkflow(ctx workflow.Context, p *workflows.BisectParams) (*pinpoint_proto.BisectExecution, error) {
ctx = workflow.WithChildOptions(ctx, childWorkflowOptions)
ctx = workflow.WithActivityOptions(ctx, regularActivityOptions)
logger := workflow.GetLogger(ctx)
var bisectExecution *internal.BisectExecution
if err := workflow.ExecuteChildWorkflow(ctx, internal.BisectWorkflow, p).Get(ctx, &bisectExecution); err != nil {
return nil, skerr.Wrap(err)
}
var resp *pinpoint_proto.LegacyJobResponse
if err := workflow.ExecuteChildWorkflow(ctx, ConvertToCatapultResponseWorkflow, p, bisectExecution).Get(ctx, &resp); err != nil {
return nil, skerr.Wrap(err)
}
var dsResp *DatastoreResponse
if err := workflow.ExecuteActivity(ctx, WriteBisectToCatapultActivity, &resp, true).Get(ctx, &dsResp); err != nil {
return nil, skerr.Wrap(err)
}
logger.Info(fmt.Sprintf("Datastore information for this job: %v", dsResp))
return &pinpoint_proto.BisectExecution{
JobId: bisectExecution.JobId,
Culprits: bisectExecution.Culprits,
}, nil
}