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  }