| package metrics |
| |
| import ( |
| "fmt" |
| "log" |
| "strconv" |
| |
| "github.com/bazelbuild/continuous-integration/metrics/clients" |
| "github.com/bazelbuild/continuous-integration/metrics/data" |
| ) |
| |
| type PlatformSignificance struct { |
| buildSuccess *BuildSuccess |
| columns []Column |
| } |
| |
| func (ps *PlatformSignificance) Name() string { |
| return "platform_significance" |
| } |
| |
| func (ps *PlatformSignificance) Columns() []Column { |
| return ps.columns |
| } |
| |
| type pipelineStats struct { |
| totalBuilds int |
| setupFailed int |
| passingBuilds int |
| canceledBuilds int |
| linuxFailures int |
| macosFailures int |
| windowsFailures int |
| rbeFailures int |
| multiPlatformFailures int |
| } |
| |
| func (ps *PlatformSignificance) Collect() (data.DataSet, error) { |
| buildResult, err := ps.buildSuccess.Collect() |
| if err != nil { |
| return nil, err |
| } |
| |
| stats, err := collectPipelineResults(buildResult) |
| if err != nil { |
| return nil, fmt.Errorf("Failed to collect pipeline results: %v", err) |
| } |
| |
| result := data.CreateDataSet(GetColumnNames(ps.columns)) |
| for pipeline, values := range stats { |
| result.AddRow(pipeline, values.totalBuilds, values.passingBuilds, values.canceledBuilds, values.setupFailed, values.linuxFailures, values.macosFailures, values.windowsFailures, values.rbeFailures, values.multiPlatformFailures) |
| } |
| return result, nil |
| } |
| |
| func collectPipelineResults(buildResult data.DataSet) (map[string]*pipelineStats, error) { |
| stats := make(map[string]*pipelineStats) |
| for _, row := range buildResult.GetData().Data { |
| values, err := toString(row) |
| if err != nil { |
| return nil, fmt.Errorf("Could not process build_success results: %v", err) |
| } |
| pipeline := values[0] |
| if _, ok := stats[pipeline]; !ok { |
| stats[pipeline] = &pipelineStats{} |
| } |
| |
| passed := true |
| canceled := false |
| failures := make([]int, 0) |
| missingData := 0 |
| for i := 1; i < len(values); i += 1 { |
| if values[i] == "" { |
| // Count the columns without data. If all columns have no data, this means that the setup step failed or was canceled. |
| // Otherwise missing data means that the respective platform is not part of a given pipeline. |
| missingData += 1 |
| } else if values[i] != passingState { |
| passed = false |
| if values[i] == canceledState { |
| canceled = true |
| break |
| } else if values[i] == failedState { |
| failures = append(failures, i) |
| } |
| } |
| } |
| |
| stats[pipeline].totalBuilds += 1 |
| if missingData == len(values)-1 { |
| stats[pipeline].setupFailed += 1 |
| } else if canceled { |
| stats[pipeline].canceledBuilds += 1 |
| } else if passed { |
| stats[pipeline].passingBuilds += 1 |
| } else if len(failures) > 1 { |
| stats[pipeline].multiPlatformFailures += 1 |
| } else if len(failures) == 1 { |
| // TODO(fweikert): improve data struct? |
| id := fmt.Sprintf("%s/%s/%d", row[0], row[1], row[2]) |
| switch failures[0] { |
| case 1: |
| stats[pipeline].linuxFailures += 1 |
| log.Printf("Linux only: %s\n", id) |
| case 2: |
| stats[pipeline].macosFailures += 1 |
| log.Printf("MacOS only: %s\n", id) |
| case 3: |
| stats[pipeline].windowsFailures += 1 |
| log.Printf("Windows only: %s\n", id) |
| case 4: |
| stats[pipeline].rbeFailures += 1 |
| log.Printf("RBE only: %s\n", id) |
| } |
| } |
| } |
| return stats, nil |
| } |
| |
| func toString(row []interface{}) ([]string, error) { |
| result := make([]string, 0) |
| for i, v := range row { |
| var str string |
| if number, ok := v.(int); ok { |
| str = strconv.Itoa(number) |
| } else { |
| str, ok = v.(string) |
| if !ok { |
| return nil, fmt.Errorf("Expected string in column %v: %s", i, v) |
| } |
| } |
| result = append(result, str) |
| } |
| return result, nil |
| } |
| |
| // CREATE TABLE platform_significance (org VARCHAR(255), pipeline VARCHAR(255), total_builds INT, passing_builds INT, canceled_builds INT, setup_failed INT, linux_failures INT, macos_failures INT, windows_failures INT, rbe_failures INT, multi_platform_failures INT, PRIMARY KEY(org, pipeline)); |
| func CreatePlatformSignificance(client clients.BuildkiteClient, builds int, pipelines ...*data.PipelineID) *PlatformSignificance { |
| buildSuccess := CreateBuildSuccess(client, builds, pipelines...) |
| columns := []Column{Column{"org", true}, Column{"pipeline", true}, Column{"total_builds", false}, Column{"passing_builds", false}, Column{"canceled_builds", false}, Column{"setup_failed", false}, Column{"linux_failures", false}, Column{"macos_failures", false}, Column{"windows_failures", false}, Column{"rbe_failures", false}, Column{"multi_platform_failures", false}} |
| return &PlatformSignificance{buildSuccess: buildSuccess, columns: columns} |
| } |