go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/changepoints/bqclient.go (about) 1 // Copyright 2024 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package changepoints 16 17 import ( 18 "context" 19 "time" 20 21 "cloud.google.com/go/bigquery" 22 "google.golang.org/api/iterator" 23 24 "go.chromium.org/luci/common/errors" 25 26 "go.chromium.org/luci/analysis/internal/bqutil" 27 ) 28 29 // NewClient creates a new client for reading changepints. 30 func NewClient(ctx context.Context, gcpProject string) (*Client, error) { 31 client, err := bqutil.Client(ctx, gcpProject) 32 if err != nil { 33 return nil, err 34 } 35 return &Client{client: client}, nil 36 } 37 38 // Client to read LUCI Analysis changepoints. 39 type Client struct { 40 client *bigquery.Client 41 } 42 43 // Close releases any resources held by the client. 44 func (c *Client) Close() error { 45 return c.client.Close() 46 } 47 48 // ChangepointRow represents a changepoint of a test variant branch. 49 type ChangepointRow struct { 50 Project string 51 // TestIDNum is the alphabetical ranking of the test ID in this LUCI project. 52 TestIDNum int64 53 TestID string 54 VariantHash string 55 Variant bigquery.NullJSON 56 // Point to a branch in the source control. 57 Ref *Ref 58 RefHash string 59 // The verdict unexpected rate before the changepoint. 60 UnexpectedVerdictRateBefore float64 61 // The verdict unexpected rate after the changepoint. 62 UnexpectedVerdictRateAfter float64 63 // The current verdict unexpected rate. 64 UnexpectedVerdictRateCurrent float64 65 // The nominal start hour of the segment after the changepoint. 66 StartHour time.Time 67 LowerBound99th int64 68 UpperBound99th int64 69 NominalStartPosition int64 70 PreviousNominalEndPosition int64 71 } 72 73 type Ref struct { 74 Gitiles *Gitiles 75 } 76 type Gitiles struct { 77 Host bigquery.NullString 78 Project bigquery.NullString 79 Ref bigquery.NullString 80 } 81 82 // ReadChangepoints reads changepoints of a certain week from BigQuery. 83 // The week parameter can be at any time of that week. A week is defined by Sunday to Satureday in UTC. 84 func (c *Client) ReadChangepoints(ctx context.Context, project string, week time.Time) ([]*ChangepointRow, error) { 85 query := ` 86 SELECT 87 project AS Project, 88 test_id_num AS TestIDNum, 89 test_id AS TestID, 90 variant_hash AS VariantHash, 91 ref_hash AS RefHash, 92 ref AS Ref, 93 variant AS Variant, 94 start_hour AS StartHour, 95 start_position_lower_bound_99th AS LowerBound99th, 96 start_position AS NominalStartPosition, 97 start_position_upper_bound_99th AS UpperBound99th, 98 unexpected_verdict_rate AS UnexpectedVerdictRateAfter, 99 previous_unexpected_verdict_rate AS UnexpectedVerdictRateBefore, 100 latest_unexpected_verdict_rate AS UnexpectedVerdictRateCurrent, 101 previous_nominal_end_position as PreviousNominalEndPosition 102 FROM test_variant_changepoints 103 WHERE project = @project 104 AND TIMESTAMP_TRUNC(start_hour, WEEK(SUNDAY)) = TIMESTAMP_TRUNC(@week, WEEK(SUNDAY)) 105 ` 106 q := c.client.Query(query) 107 q.DefaultDatasetID = "internal" 108 q.Parameters = []bigquery.QueryParameter{ 109 {Name: "project", Value: project}, 110 {Name: "week", Value: week}, 111 } 112 job, err := q.Run(ctx) 113 if err != nil { 114 return nil, errors.Annotate(err, "running query").Err() 115 } 116 it, err := job.Read(ctx) 117 if err != nil { 118 return nil, errors.Annotate(err, "read").Err() 119 } 120 results := []*ChangepointRow{} 121 for { 122 row := &ChangepointRow{} 123 err := it.Next(row) 124 if errors.Is(err, iterator.Done) { 125 break 126 } 127 if err != nil { 128 return nil, errors.Annotate(err, "obtain next changepoint row").Err() 129 } 130 results = append(results, row) 131 } 132 return results, nil 133 }