blob: 9bd52ce98a8d7a06cb58394c80b3ec49f3b5ac96 [file] [log] [blame]
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}
}