blob: b65a49f286cd0af474415eb4d6094eaf759106de [file] [log] [blame]
package metrics
import (
"fmt"
"github.com/bazelbuild/continuous-integration/metrics/clients"
"github.com/bazelbuild/continuous-integration/metrics/data"
)
type CriticalPath struct {
client clients.BuildkiteClient
pipelines []*data.PipelineID
columns []Column
lastNBuilds int
debug bool
}
func (cp *CriticalPath) Name() string {
return "critical_path"
}
func (cp *CriticalPath) Columns() []Column {
return cp.columns
}
func (*CriticalPath) Type() MetricType {
return BuildBasedMetric
}
func (*CriticalPath) RelevantDelta() int {
return 200 // builds
}
func (cp *CriticalPath) Collect() (data.DataSet, error) {
result := data.CreateDataSet(GetColumnNames(cp.columns))
for _, pipeline := range cp.pipelines {
builds, err := cp.client.GetMostRecentBuilds(pipeline, cp.lastNBuilds)
if err != nil {
return nil, fmt.Errorf("Cannot collect critical path statistics for pipeline %s: %v", pipeline, err)
}
for _, build := range builds {
if build.FinishedAt == nil {
continue
}
// TODO(fweikert): remove
if cp.debug {
fmt.Printf("%v -> %v -> %v -> %v (%d)\n", *build.ScheduledAt, *build.CreatedAt, *build.StartedAt, *build.FinishedAt, *build.Number)
for _, job := range build.Jobs {
if job.Name != nil {
fmt.Printf("%v -> %v -> %v -> %v -> %v (%s)\n", *job.ScheduledAt, *job.CreatedAt, *job.RunnableAt, *job.StartedAt, *job.FinishedAt, *job.Name)
}
}
}
// Caveat: The Buildkite API only returns the latest invocation of each job within a given build,
// which causes a problem when a failing build is retried via the web UI. In this case the time
// between the original run and the retry will be counted as wait time by this code.
performance, err := analyzeJobsPerformance(build.Jobs)
if err != nil {
return nil, fmt.Errorf("Could not calculate job performance for build %d: %v", *build.Number, err)
}
err = result.AddRow(pipeline.Org, pipeline.Slug, *build.Number, performance.totalWaitSeconds, performance.totalRunSeconds, performance.longestRunningTaskName, performance.longestRunningTaskRunSeconds, *build.State)
if err != nil {
return nil, fmt.Errorf("Failed to add result for build %d: %v", *build.Number, err)
}
}
}
return result, nil
}
// CREATE TABLE critical_path (org VARCHAR(255), pipeline VARCHAR(255), build INT, wait_time_seconds FLOAT, run_time_seconds FLOAT, longest_task_name VARCHAR(255), longest_task_time_seconds FLOAT, result VARCHAR(255), PRIMARY KEY(org, pipeline, build));
func CreateCriticalPath(client clients.BuildkiteClient, lastNBuilds int, pipelines ...*data.PipelineID) *CriticalPath {
columns := []Column{Column{"org", true}, Column{"pipeline", true}, Column{"build", true}, Column{"wait_time_seconds", false}, Column{"run_time_seconds", false}, Column{"longest_task_name", false}, Column{"longest_task_time_seconds", false}, Column{"result", false}}
return &CriticalPath{client: client, pipelines: pipelines, columns: columns, lastNBuilds: lastNBuilds, debug: false}
}