blob: d4453ab1d417ad9a80e21961bc7c2c5a4385944d [file] [log] [blame]
package types
import (
"context"
"fmt"
"time"
"github.com/hako/durafmt"
)
const (
// All recognized clients.
AndroidClient RecognizedClient = "Android"
ChromiumClient RecognizedClient = "Chromium"
FlutterNativeClient RecognizedClient = "Flutter-native"
FlutterOnWebClient RecognizedClient = "Flutter-on-web"
SkiaClient RecognizedClient = "Skia"
OSSFuzzClient RecognizedClient = "OSS-Fuzz"
// All recognized bug frameworks.
GithubSource IssueSource = "Github"
MonorailSource IssueSource = "Monorail"
IssueTrackerSource IssueSource = "Buganizer"
// All bug frameworks will be standardized to these priorities.
PriorityP0 StandardizedPriority = "P0"
PriorityP1 StandardizedPriority = "P1"
PriorityP2 StandardizedPriority = "P2"
PriorityP3 StandardizedPriority = "P3"
PriorityP4 StandardizedPriority = "P4"
PriorityP5 StandardizedPriority = "P5"
PriorityP6 StandardizedPriority = "P6"
// Convenient constants to use when calculating SLO violations.
Daily = 24 * time.Hour
Weekly = 7 * Daily
Monthly = 30 * Daily
Biannualy = 6 * Monthly
Yearly = 2 * Biannualy
Biennialy = 2 * Yearly
)
// BugsDB is the interface implemented by all DB clients.
type BugsDB interface {
// GetCountsFromDB returns the latest counts data for the client+source+query combination.
// If client is not specified then latest counts data for all clients is returned.
// Similarly if source is not specified then latest counts data for all sources for that client are returned.
// Similarly if query is not specified then latest counts data for all queries for that client+source are returned.
GetCountsFromDB(ctx context.Context, client RecognizedClient, source IssueSource, query string) (*IssueCountsData, error)
// GetQueryDataFromDB returns a slice of query data for the client+source+query combination.
// If client is not specified then query data for all clients is returned.
// Similarly if source is not specified then query data for all sources for that client are returned.
// Similarly if query is not specified then query data for all queries for that client+source are returned.
GetQueryDataFromDB(ctx context.Context, client RecognizedClient, source IssueSource, query string) ([]*QueryData, error)
// GetClientsFromDB returns a map from clients to sources to queries.
GetClientsFromDB(ctx context.Context) (map[RecognizedClient]map[IssueSource]map[string]bool, error)
// PutInDB puts the specified client+source+query counts data into the DB.
PutInDB(ctx context.Context, client RecognizedClient, source IssueSource, query, runId string, countsData *IssueCountsData) error
// GenerateRunId creates a run ID from the current timestamp.
GenerateRunId(ts time.Time) string
// Returns a map of all recognized run IDs.
GetAllRecognizedRunIds(ctx context.Context) (map[string]bool, error)
// Stores the specified run ID in the DB.
StoreRunId(ctx context.Context, runId string) error
}
// QueryData is the type that will be stored in BugsDB.
type QueryData struct {
Created time.Time `json:"created"`
RunId string `json:"run_id"`
CountsData *IssueCountsData
}
// StatusData is used in the response of the get_client_counts endpoint.
type StatusData struct {
UntriagedCount int `json:"untriaged_count"`
Link string `json:"link"`
}
// GetClientCountsResponse is the response used by the get_client_counts endpoint.
type GetClientCountsResponse struct {
ClientsToStatusData map[RecognizedClient]StatusData `json:"clients_to_status_data"`
}
// GetChartsDataResponse is the response used by the get_charts_data endpoint.
type GetChartsDataResponse struct {
OpenData interface{} `json:"open_data"`
SloData interface{} `json:"slo_data"`
UntriagedData interface{} `json:"untriaged_data"`
}
// ClientSourceQueryRequest is the request used by different bugs central endpoints.
type ClientSourceQueryRequest struct {
Client RecognizedClient `json:"client"`
Source IssueSource `json:"source"`
Query string `json:"query"`
}
// IssuesOutsideSLOResponse is the response used by the get_issues_outside_slo endpoint.
type IssuesOutsideSLOResponse struct {
PriToSLOIssues map[StandardizedPriority][]*Issue `json:"pri_to_slo_issues"`
}
// GetClientsResponse is the response used by the get_clients endpoint.
type GetClientsResponse struct {
Clients map[RecognizedClient]map[IssueSource]map[string]bool `json:"clients"`
}
type sloViolationData struct {
// If an open issue's last modified time is beyond this duration then it is an SLO violation.
modifiedDuration time.Duration
// If an open issue's creation time is beyond this duration then it is an SLO violation.
createdDuration time.Duration
}
var (
// Uses data from https://docs.google.com/document/d/1OgpX1KDDq3YkHzRJjqRHSPJ9CJ8hH0RTvMAApKVxwm8/edit
priorityToSLOViolationData = map[StandardizedPriority]sloViolationData{
PriorityP0: {
modifiedDuration: Daily,
createdDuration: Weekly,
},
PriorityP1: {
modifiedDuration: Weekly,
createdDuration: Monthly,
},
PriorityP2: {
modifiedDuration: Biannualy,
createdDuration: Yearly,
},
PriorityP3: {
modifiedDuration: Yearly,
createdDuration: Biennialy,
},
}
)
// IssueSource types will be all the recognized issue frameworks (eg: Github, IssueTracker, Monorail).
type IssueSource string
// RecognizedClient types will be all the recognized Skia clients (eg: Android, Chromium, Flutter).
type RecognizedClient string
// StandardizedPriority types will be the priorities used across issue frameworks.
type StandardizedPriority string
// All issues from the different issue frameworks will be standardized to this struct.
type Issue struct {
Id string `json:"id"`
State string `json:"state"`
Priority StandardizedPriority `json:"priority"`
Owner string `json:"owner"`
Link string `json:"link"`
SLOViolation bool `json:"slo_violation"`
SLOViolationReason string `json:"slo_violation_reason"`
SLOViolationDuration time.Duration `json:"slo_violation_duration"`
CreatedTime time.Time `json:"created"`
ModifiedTime time.Time `json:"modified"`
Title string `json:"title"` // This is not populated in IssueTracker.
Summary string `json:"summary"` // This is not returned in IssueTracker or Monorail.
}
// IssueCountsData will be used to keep track of the counts of different issue types that
// are returned by the different bug frameworks. It will also be used to keep track of
// SLO violations and the queries that were used.
type IssueCountsData struct {
OpenCount int `json:"open_count"`
UnassignedCount int `json:"unassigned_count"`
UntriagedCount int `json:"untriaged_count"`
// Priority counts.
P0Count int `json:"p0_count"`
P1Count int `json:"p1_count"`
P2Count int `json:"p2_count"`
P3Count int `json:"p3_count"`
P4Count int `json:"p4_count"`
P5Count int `json:"p5_count"`
P6Count int `json:"p6_count"`
// SLO violations per priority.
// We only do SLOs for P0-P3. Listed here: https://docs.google.com/document/d/1OgpX1KDDq3YkHzRJjqRHSPJ9CJ8hH0RTvMAApKVxwm8/edit
P0SLOViolationCount int `json:"p0_slo_count"`
P1SLOViolationCount int `json:"p1_slo_count"`
P2SLOViolationCount int `json:"p2_slo_count"`
P3SLOViolationCount int `json:"p3_slo_count"`
// Links to the issue framework.
QueryLink string `json:"query_link"`
UntriagedQueryLink string `json:"untriaged_query_link"`
P0Link string `json:"p0_link"`
P1Link string `json:"p1_link"`
P2Link string `json:"p2_link"`
P3AndRestLink string `json:"p3_and_rest_link"`
}
// IncSLOViolations will increment the priority's corresponding slo count.
func (icd *IssueCountsData) IncSLOViolation(violation bool, priority StandardizedPriority) {
if !violation {
// Nothing to do here.
return
}
switch priority {
case PriorityP0:
if violation {
icd.P0SLOViolationCount++
}
case PriorityP1:
if violation {
icd.P1SLOViolationCount++
}
case PriorityP2:
if violation {
icd.P2SLOViolationCount++
}
case PriorityP3:
if violation {
icd.P3SLOViolationCount++
}
}
}
// Merge is used to combine an instance of IssueCountsData into this one.
func (icd *IssueCountsData) Merge(from ...*IssueCountsData) {
for _, f := range from {
icd.OpenCount += f.OpenCount
icd.UnassignedCount += f.UnassignedCount
icd.UntriagedCount += f.UntriagedCount
icd.P0Count += f.P0Count
icd.P1Count += f.P1Count
icd.P2Count += f.P2Count
icd.P3Count += f.P3Count
icd.P4Count += f.P4Count
icd.P5Count += f.P5Count
icd.P6Count += f.P6Count
icd.P0SLOViolationCount += f.P0SLOViolationCount
icd.P1SLOViolationCount += f.P1SLOViolationCount
icd.P2SLOViolationCount += f.P2SLOViolationCount
icd.P3SLOViolationCount += f.P3SLOViolationCount
}
}
// IncPriority will increment the corresponding priority count of the
// specified StandardizedPriority.
func (icd *IssueCountsData) IncPriority(priority StandardizedPriority) {
switch priority {
case PriorityP0:
icd.P0Count++
case PriorityP1:
icd.P1Count++
case PriorityP2:
icd.P2Count++
case PriorityP3:
icd.P3Count++
case PriorityP4:
icd.P4Count++
case PriorityP5:
icd.P5Count++
case PriorityP6:
icd.P6Count++
}
}
// IsPrioritySLOViolation returns whether the priority is outside the SLO.
// If issue has violated SLO then returns description and a duration that shows by how much
// it was surpassed.
func IsPrioritySLOViolation(now, created, modified time.Time, priority StandardizedPriority) (bool, string, time.Duration) {
if sloViolationData, ok := priorityToSLOViolationData[priority]; ok {
if now.After(modified.Add(sloViolationData.modifiedDuration)) {
duration := now.Sub(modified.Add(sloViolationData.modifiedDuration))
return true, fmt.Sprintf("exceeded modified time SLO by %s", durafmt.Parse(duration).LimitFirstN(2)), duration
} else if now.After(created.Add(sloViolationData.createdDuration)) {
duration := now.Sub(created.Add(sloViolationData.createdDuration))
return true, fmt.Sprintf("exceeded creation time SLO by %s", durafmt.Parse(duration).LimitFirstN(2)), duration
}
}
return false, "", 0
}