[perf] Implement results.Loader interface.
Bug: skia: 10844
Change-Id: I4ff519175ca8a05e542904604b7202b51bd21ae6
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/329219
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/perf/go/trybot/results/dfloader/dfloader.go b/perf/go/trybot/results/dfloader/dfloader.go
new file mode 100644
index 0000000..1ec4ff4
--- /dev/null
+++ b/perf/go/trybot/results/dfloader/dfloader.go
@@ -0,0 +1,147 @@
+// Package dfloader implements results.Loader using a DataFrameBuilder.
+package dfloader
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "go.skia.org/infra/go/paramtools"
+ "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/dataframe"
+ perfgit "go.skia.org/infra/perf/go/git"
+ "go.skia.org/infra/perf/go/trybot/results"
+ "go.skia.org/infra/perf/go/trybot/store"
+ "go.skia.org/infra/perf/go/types"
+)
+
+// TraceHistorySize is the number of points we load for each trace.
+const TraceHistorySize = 20
+
+// ErrQueryMustNotBeEmpty is returned if an empty query is passed in the TryBotRequest.
+var ErrQueryMustNotBeEmpty = fmt.Errorf("Query must not be empty.")
+
+// Loader implements results.Loader.
+type Loader struct {
+ dfb dataframe.DataFrameBuilder
+ store store.TryBotStore
+ git *perfgit.Git
+}
+
+// New returns a new Loader instance.
+func New(dfb dataframe.DataFrameBuilder, store store.TryBotStore, git *perfgit.Git) Loader {
+ return Loader{
+ dfb: dfb,
+ store: store,
+ git: git,
+ }
+}
+
+// Load implements the results.Loader interface.
+func (l *Loader) Load(ctx context.Context, request results.TryBotRequest, progress types.Progress) (results.TryBotResponse, error) {
+ timestamp := time.Now()
+ if request.Kind == results.Commit {
+ commit, err := l.git.CommitFromCommitNumber(ctx, request.CommitNumber)
+ if err != nil {
+ return results.TryBotResponse{}, skerr.Wrap(err)
+ }
+ timestamp = time.Unix(commit.Timestamp, 0)
+ }
+
+ q, err := query.NewFromString(request.Query)
+ if err != nil {
+ return results.TryBotResponse{}, skerr.Wrap(err)
+ }
+ if request.Kind == results.Commit && q.Empty() {
+ return results.TryBotResponse{}, ErrQueryMustNotBeEmpty
+ }
+
+ var df *dataframe.DataFrame
+ rebuildParamSet := false
+ if request.Kind == results.Commit {
+ // Always pull in TraceHistorySize+1 trace values. The TraceHistorySize
+ // represents the history of the trace, and the TraceHistorySize+1 point
+ // represents either the commit under inspection or a placeholder for the
+ // trybot value, which lets us avoid a second memory allocation, which we'd
+ // get if we had only queried for TraceHistorySize values.
+ df, err = l.dfb.NewNFromQuery(ctx, timestamp, q, TraceHistorySize+1, progress)
+ if err != nil {
+ return results.TryBotResponse{}, skerr.Wrap(err)
+ }
+ } else {
+ // Load the trybot results.
+ storeResults, err := l.store.Get(ctx, request.CL, request.PatchNumber)
+ if err != nil {
+ return results.TryBotResponse{}, skerr.Wrap(err)
+ }
+ traceNames := make([]string, 0, len(storeResults))
+ for _, results := range storeResults {
+ traceNames = append(traceNames, results.TraceName)
+ }
+ // Query for all traces that match up with the trybot results.
+ df, err = l.dfb.NewNFromKeys(ctx, timestamp, traceNames, TraceHistorySize+1, progress)
+ if err != nil {
+ return results.TryBotResponse{}, skerr.Wrap(err)
+ }
+ // Replace the last value in each trace with the trybot result.
+
+ for _, results := range storeResults {
+ values, ok := df.TraceSet[results.TraceName]
+ if !ok {
+ delete(df.TraceSet, results.TraceName)
+ // At this point the df.ParamSet is no longer valid and we should rebuild it.
+ rebuildParamSet = true
+ continue
+ }
+ values[len(values)-1] = results.Value
+ }
+ }
+
+ ret := results.TryBotResponse{}
+ ret.Header = df.Header
+ if request.Kind == results.TryBot && len(ret.Header) > 0 {
+ ret.Header[len(ret.Header)-1].Offset = types.BadCommitNumber
+ }
+ ret.ParamSet = df.ParamSet
+
+ res := make([]results.TryBotResult, 0, len(df.TraceSet))
+ // Loop over all the traces and parse the key into params and pass the
+ // values to vec32.StdDevRatio.
+ for traceName, values := range df.TraceSet {
+ params, err := query.ParseKey(traceName)
+ if err != nil {
+ sklog.Errorf("Failed to parse %q: %s", traceName, err)
+ rebuildParamSet = true
+ continue
+ }
+ stddevRatio, median, lower, upper, err := vec32.StdDevRatio(values)
+ if err != nil {
+ rebuildParamSet = true
+ continue
+ }
+ res = append(res, results.TryBotResult{
+ Params: params,
+ Median: median,
+ Lower: lower,
+ Upper: upper,
+ StdDevRatio: stddevRatio,
+ Values: values,
+ })
+ }
+ ret.Results = res
+ if rebuildParamSet {
+ ps := paramtools.NewParamSet()
+ for _, res := range ret.Results {
+ ps.AddParams(res.Params)
+ }
+ ret.ParamSet = ps
+ }
+
+ return ret, nil
+}
+
+// Assert that we fulfill the interface.
+var _ results.Loader = (*Loader)(nil)
diff --git a/perf/go/trybot/results/dfloader/dfloader_test.go b/perf/go/trybot/results/dfloader/dfloader_test.go
new file mode 100644
index 0000000..5a6fd71
--- /dev/null
+++ b/perf/go/trybot/results/dfloader/dfloader_test.go
@@ -0,0 +1,404 @@
+// Package dfloader implements results.Loader using a DataFrameBuilder.
+package dfloader
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+ "github.com/stretchr/testify/require"
+ "go.skia.org/infra/go/paramtools"
+ "go.skia.org/infra/go/testutils/unittest"
+ "go.skia.org/infra/go/vec32"
+ "go.skia.org/infra/perf/go/dataframe"
+ "go.skia.org/infra/perf/go/dataframe/mocks"
+ perfgit "go.skia.org/infra/perf/go/git"
+ "go.skia.org/infra/perf/go/git/gittest"
+ "go.skia.org/infra/perf/go/trybot/results"
+ "go.skia.org/infra/perf/go/trybot/store"
+ storeMocks "go.skia.org/infra/perf/go/trybot/store/mocks"
+ "go.skia.org/infra/perf/go/types"
+)
+
+var errFromMock = fmt.Errorf("MockError")
+
+const testTileSize = 4
+
+const e = vec32.MissingDataSentinel
+
+func setupForTest(t *testing.T) (context.Context, *perfgit.Git, []string) {
+ ctx, db, _, hashes, instanceConfig, _, gitCleanup := gittest.NewForTest(t)
+ instanceConfig.DataStoreConfig.TileSize = testTileSize
+ g, err := perfgit.New(ctx, true, db, instanceConfig)
+ require.NoError(t, err)
+ t.Cleanup(gitCleanup)
+ return ctx, g, hashes
+}
+
+func TestLoader_UnknownCommit_ReturnsError(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, _ := setupForTest(t)
+
+ dfb := &mocks.DataFrameBuilder{}
+ storeMock := &storeMocks.TryBotStore{}
+ loader := New(dfb, storeMock, g)
+ request := results.TryBotRequest{
+ Kind: results.Commit,
+ Query: "config=8888",
+ CommitNumber: 200, // Not a valid commit.
+ }
+ _, err := loader.Load(ctx, request, nil)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "Failed to get details for CommitNumber")
+}
+
+func TestLoader_InvalidQuery_ReturnsError(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, _ := setupForTest(t)
+
+ dfb := &mocks.DataFrameBuilder{}
+ storeMock := &storeMocks.TryBotStore{}
+ loader := New(dfb, storeMock, g)
+ request := results.TryBotRequest{
+ Kind: results.Commit,
+ CommitNumber: 2, // Valid commit that gittest.NewForTest has added.
+ Query: "%gh&%ij",
+ }
+ _, err := loader.Load(ctx, request, nil)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "invalid URL")
+}
+
+func TestLoader_EmptyQuery_LoadReturnsError(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, _ := setupForTest(t)
+
+ dfb := &mocks.DataFrameBuilder{}
+ storeMock := &storeMocks.TryBotStore{}
+ loader := New(dfb, storeMock, g)
+ request := results.TryBotRequest{
+ Kind: results.Commit,
+ Query: "",
+ CommitNumber: 2, // Valid commit that gittest.NewForTest has added.
+ }
+ _, err := loader.Load(ctx, request, nil)
+ require.Error(t, err)
+ assert.Equal(t, ErrQueryMustNotBeEmpty, err)
+}
+
+func TestLoader_LoadWithDataFrameBuilderThatErrorsNewNFromQuery_LoadReturnsError(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, _ := setupForTest(t)
+
+ dfb := &mocks.DataFrameBuilder{}
+ dfb.On("NewNFromQuery", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errFromMock)
+
+ storeMock := &storeMocks.TryBotStore{}
+ loader := New(dfb, storeMock, g)
+ request := results.TryBotRequest{
+ Kind: results.Commit,
+ Query: "config=8888",
+ CommitNumber: 2, // Valid commit that gittest.NewForTest has added.
+ }
+ _, err := loader.Load(ctx, request, nil)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), errFromMock.Error())
+}
+
+func TestLoader_LoadWithTryBotStoreThatErrors_LoadReturnsError(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, _ := setupForTest(t)
+
+ dfb := &mocks.DataFrameBuilder{}
+ storeMock := &storeMocks.TryBotStore{}
+ const cl = types.CL("123456")
+ const patch = int(1)
+ storeMock.On("Get", mock.Anything, cl, patch).Return(nil, errFromMock)
+
+ loader := New(dfb, storeMock, g)
+ request := results.TryBotRequest{
+ Kind: results.TryBot,
+ CL: cl,
+ PatchNumber: patch,
+ }
+ _, err := loader.Load(ctx, request, nil)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), errFromMock.Error())
+}
+
+func TestLoader_LoadDataFrameBuilderThatErrorsNewNFromKeys_LoadReturnsError(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, _ := setupForTest(t)
+
+ dfb := &mocks.DataFrameBuilder{}
+ dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errFromMock)
+
+ storeMock := &storeMocks.TryBotStore{}
+ const cl = types.CL("123456")
+ const patch = int(1)
+ storeMock.On("Get", mock.Anything, cl, patch).Return(nil, nil)
+
+ loader := New(dfb, storeMock, g)
+ request := results.TryBotRequest{
+ Kind: results.TryBot,
+ CL: cl,
+ PatchNumber: patch,
+ }
+ _, err := loader.Load(ctx, request, nil)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), errFromMock.Error())
+}
+
+func TestLoader_ZeroLengthResponseFromTryBotStore_LoadReturnsSuccess(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, _ := setupForTest(t)
+
+ dfb := &mocks.DataFrameBuilder{}
+ df := &dataframe.DataFrame{
+ Header: []*dataframe.ColumnHeader{},
+ ParamSet: paramtools.ParamSet{},
+ }
+ dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(df, nil)
+
+ storeMock := &storeMocks.TryBotStore{}
+ const cl = types.CL("123456")
+ const patch = int(1)
+ storeMock.On("Get", mock.Anything, cl, patch).Return(nil, nil)
+
+ loader := New(dfb, storeMock, g)
+ request := results.TryBotRequest{
+ Kind: results.TryBot,
+ CL: cl,
+ PatchNumber: patch,
+ }
+ resp, err := loader.Load(ctx, request, nil)
+ require.NoError(t, err)
+ assert.Empty(t, resp.Results)
+ assert.Empty(t, resp.Header)
+ assert.Empty(t, resp.ParamSet)
+}
+
+func TestLoader_OneTraceTryBotHappyPath_LoadReturnsSuccess(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, _ := setupForTest(t)
+
+ dfb := &mocks.DataFrameBuilder{}
+ df := &dataframe.DataFrame{
+ Header: []*dataframe.ColumnHeader{
+ {Offset: 0, Timestamp: gittest.StartTime.Unix()},
+ {Offset: 1, Timestamp: gittest.StartTime.Unix() + 1},
+ {Offset: 2, Timestamp: gittest.StartTime.Unix() + 2},
+ {Offset: 3, Timestamp: gittest.StartTime.Unix() + 3},
+ {Offset: 4, Timestamp: gittest.StartTime.Unix() + 4},
+ {Offset: 5, Timestamp: gittest.StartTime.Unix() + 5},
+ {Offset: 6, Timestamp: gittest.StartTime.Unix() + 6},
+ {Offset: 7, Timestamp: gittest.StartTime.Unix() + 7},
+ {Offset: 8, Timestamp: gittest.StartTime.Unix() + 8},
+ {Offset: 9, Timestamp: gittest.StartTime.Unix() + 9},
+ },
+ ParamSet: paramtools.ParamSet{"config": []string{"gpu"}},
+ TraceSet: types.TraceSet{
+ ",config=gpu,": []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 1.2},
+ },
+ }
+ dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(df, nil)
+
+ storeMock := &storeMocks.TryBotStore{}
+ const cl = types.CL("123456")
+ const patch = int(1)
+ storeResults := []store.GetResult{
+ {
+ TraceName: ",config=gpu,",
+ Value: 3.0,
+ },
+ }
+ storeMock.On("Get", mock.Anything, cl, patch).Return(storeResults, nil)
+
+ loader := New(dfb, storeMock, g)
+ request := results.TryBotRequest{
+ Kind: results.TryBot,
+ CL: cl,
+ PatchNumber: patch,
+ }
+ resp, err := loader.Load(ctx, request, nil)
+ require.NoError(t, err)
+ expected := results.TryBotResult{
+ Params: paramtools.Params{"config": "gpu"},
+ Median: 1,
+ Lower: 0.1825742,
+ Upper: 0.122474514,
+ StdDevRatio: 16.329927,
+ Values: []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 3},
+ }
+ assert.Len(t, resp.Results, 1)
+ assert.Equal(t, expected, resp.Results[0])
+ assert.Equal(t, types.BadCommitNumber, df.Header[len(df.Header)-1].Offset)
+ assert.Equal(t, df.ParamSet, resp.ParamSet)
+}
+
+func TestLoader_UnknownTracesAreIgnored_LoadReturnsSuccess(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, _ := setupForTest(t)
+
+ // The trybotStore has two results, but there are trace values for only one of those results (config=8888).
+ dfb := &mocks.DataFrameBuilder{}
+ df := &dataframe.DataFrame{
+ Header: []*dataframe.ColumnHeader{
+ {Offset: 0, Timestamp: gittest.StartTime.Unix()},
+ {Offset: 1, Timestamp: gittest.StartTime.Unix() + 1},
+ {Offset: 2, Timestamp: gittest.StartTime.Unix() + 2},
+ {Offset: 3, Timestamp: gittest.StartTime.Unix() + 3},
+ {Offset: 4, Timestamp: gittest.StartTime.Unix() + 4},
+ {Offset: 5, Timestamp: gittest.StartTime.Unix() + 5},
+ {Offset: 6, Timestamp: gittest.StartTime.Unix() + 6},
+ {Offset: 7, Timestamp: gittest.StartTime.Unix() + 7},
+ {Offset: 8, Timestamp: gittest.StartTime.Unix() + 8},
+ {Offset: 9, Timestamp: gittest.StartTime.Unix() + 9},
+ },
+ ParamSet: paramtools.ParamSet{"config": []string{"565", "8888"}},
+ TraceSet: types.TraceSet{
+ ",config=8888,": []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 1.2},
+ },
+ }
+ dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(df, nil)
+
+ storeMock := &storeMocks.TryBotStore{}
+ const cl = types.CL("123456")
+ const patch = int(1)
+ storeResults := []store.GetResult{
+ {
+ TraceName: ",config=8888,",
+ Value: 3.0,
+ },
+ {
+ TraceName: ",config=565,",
+ Value: 4.0,
+ },
+ }
+ storeMock.On("Get", mock.Anything, cl, patch).Return(storeResults, nil)
+
+ loader := New(dfb, storeMock, g)
+ request := results.TryBotRequest{
+ Kind: results.TryBot,
+ CL: cl,
+ PatchNumber: patch,
+ }
+ resp, err := loader.Load(ctx, request, nil)
+ require.NoError(t, err)
+ expected := results.TryBotResult{
+ Params: paramtools.Params{"config": "8888"},
+ Median: 1,
+ Lower: 0.1825742,
+ Upper: 0.122474514,
+ StdDevRatio: 16.329927,
+ Values: []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 3},
+ }
+ assert.Len(t, resp.Results, 1)
+ assert.Equal(t, expected, resp.Results[0])
+ assert.Equal(t, types.BadCommitNumber, df.Header[len(df.Header)-1].Offset)
+ assert.Equal(t, paramtools.ParamSet{"config": []string{"8888"}}, resp.ParamSet)
+}
+
+func TestLoader_InsufficientNonMissingDataSentinel_ResultIsSkipped(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, _ := setupForTest(t)
+
+ // The trybotStore has two results, but there are trace values for only one of those results (config=8888).
+ dfb := &mocks.DataFrameBuilder{}
+ df := &dataframe.DataFrame{
+ Header: []*dataframe.ColumnHeader{
+ {Offset: 0, Timestamp: gittest.StartTime.Unix()},
+ {Offset: 1, Timestamp: gittest.StartTime.Unix() + 1},
+ {Offset: 2, Timestamp: gittest.StartTime.Unix() + 2},
+ {Offset: 3, Timestamp: gittest.StartTime.Unix() + 3},
+ {Offset: 4, Timestamp: gittest.StartTime.Unix() + 4},
+ {Offset: 5, Timestamp: gittest.StartTime.Unix() + 5},
+ {Offset: 6, Timestamp: gittest.StartTime.Unix() + 6},
+ {Offset: 7, Timestamp: gittest.StartTime.Unix() + 7},
+ {Offset: 8, Timestamp: gittest.StartTime.Unix() + 8},
+ {Offset: 9, Timestamp: gittest.StartTime.Unix() + 9},
+ },
+ ParamSet: paramtools.ParamSet{"config": []string{"565", "8888"}},
+ TraceSet: types.TraceSet{
+ ",config=8888,": []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 1.2},
+ ",config=565,": []float32{e, e, e, e, e, e, e, e, e, 1.2}, // Should be dropped from results since there isn't enough valid data.
+ },
+ }
+ dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(df, nil)
+
+ storeMock := &storeMocks.TryBotStore{}
+ const cl = types.CL("123456")
+ const patch = int(1)
+ storeResults := []store.GetResult{
+ {
+ TraceName: ",config=8888,",
+ Value: 3.0,
+ },
+ {
+ TraceName: ",config=565,",
+ Value: 4.0,
+ },
+ }
+ storeMock.On("Get", mock.Anything, cl, patch).Return(storeResults, nil)
+
+ loader := New(dfb, storeMock, g)
+ request := results.TryBotRequest{
+ Kind: results.TryBot,
+ CL: cl,
+ PatchNumber: patch,
+ }
+ resp, err := loader.Load(ctx, request, nil)
+ require.NoError(t, err)
+ expected := results.TryBotResult{
+ Params: paramtools.Params{"config": "8888"},
+ Median: 1,
+ Lower: 0.1825742,
+ Upper: 0.122474514,
+ StdDevRatio: 16.329927,
+ Values: []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 3},
+ }
+ assert.Len(t, resp.Results, 1)
+ assert.Equal(t, expected, resp.Results[0])
+ assert.Equal(t, types.BadCommitNumber, df.Header[len(df.Header)-1].Offset)
+ assert.Equal(t, paramtools.ParamSet{"config": []string{"8888"}}, resp.ParamSet)
+}
+
+func TestLoader_InvalidTraceKeysAreIgnored_LoadReturnsSuccess(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, _ := setupForTest(t)
+
+ dfb := &mocks.DataFrameBuilder{}
+ df := &dataframe.DataFrame{
+ Header: []*dataframe.ColumnHeader{},
+ ParamSet: paramtools.ParamSet{},
+ TraceSet: types.TraceSet{
+ "this-isnt-a-valid-key": []float32{1, 1, 0.9, 0.9, 1.1, 1.1, 0.8, 0.8, 1.2, 1.2},
+ },
+ }
+ dfb.On("NewNFromKeys", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(df, nil)
+
+ storeMock := &storeMocks.TryBotStore{}
+ const cl = types.CL("123456")
+ const patch = int(1)
+ storeResults := []store.GetResult{
+ {
+ TraceName: ",config=gpu,",
+ Value: 3.0,
+ },
+ }
+ storeMock.On("Get", mock.Anything, cl, patch).Return(storeResults, nil)
+
+ loader := New(dfb, storeMock, g)
+ request := results.TryBotRequest{
+ Kind: results.TryBot,
+ CL: cl,
+ PatchNumber: patch,
+ }
+ resp, err := loader.Load(ctx, request, nil)
+ require.NoError(t, err)
+ assert.Empty(t, resp.Results)
+ assert.Empty(t, resp.Header)
+ assert.Empty(t, resp.ParamSet)
+}
diff --git a/perf/go/trybot/results/results.go b/perf/go/trybot/results/results.go
index f93cb9a..bdd0a21 100644
--- a/perf/go/trybot/results/results.go
+++ b/perf/go/trybot/results/results.go
@@ -5,6 +5,8 @@
package results
import (
+ "context"
+
"go.skia.org/infra/go/paramtools"
"go.skia.org/infra/perf/go/dataframe"
"go.skia.org/infra/perf/go/types"
@@ -35,6 +37,9 @@
// CL is the ID of the changelist to analyze. Only use if Kind is TryBot.
CL types.CL `json:"cl"`
+ // PatchNumber is the index of the patch.
+ PatchNumber int `json:"patch_number"`
+
// CommitNumber is the commit to analyze. Only use if Kind is Commit.
CommitNumber types.CommitNumber `json:"cid"`
@@ -61,12 +66,13 @@
// TryBotResponse is the response sent to a TryBotRequest.
type TryBotResponse struct {
- Header []dataframe.ColumnHeader
- Results []TryBotResult
+ Header []*dataframe.ColumnHeader `json:"header"`
+ Results []TryBotResult `json:"results"`
+ ParamSet paramtools.ParamSet `json:"paramset"`
}
// Loader returns the data for the given TryBotRequest.
type Loader interface {
// Load the TryBot results for the given TryBotRequest.
- Load(TryBotRequest) (TryBotResponse, error)
+ Load(context.Context, TryBotRequest, types.Progress) (TryBotResponse, error)
}