github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/tools/bigquery/bigquery.go (about)

     1  // Copyright 2020 The gVisor 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 bigquery defines a BigQuery schema for benchmarks.
    16  //
    17  // This package contains a schema for BigQuery and methods for publishing
    18  // benchmark data into tables.
    19  package bigquery
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	bq "cloud.google.com/go/bigquery"
    29  	"google.golang.org/api/option"
    30  )
    31  
    32  // Suite is the top level structure for a benchmark run. BigQuery
    33  // will infer the schema from this.
    34  type Suite struct {
    35  	Name       string       `bq:"name"`
    36  	Conditions []*Condition `bq:"conditions"`
    37  	Benchmarks []*Benchmark `bq:"benchmarks"`
    38  	Official   bool         `bq:"official"`
    39  	Timestamp  time.Time    `bq:"timestamp"`
    40  }
    41  
    42  // Benchmark represents an individual benchmark in a suite.
    43  type Benchmark struct {
    44  	Name      string       `bq:"name"`
    45  	Condition []*Condition `bq:"condition"`
    46  	Metric    []*Metric    `bq:"metric"`
    47  }
    48  
    49  // Condition represents qualifiers for the benchmark or suite. For example:
    50  // Get_Pid/1/real_time would have Benchmark Name "Get_Pid" with "1"
    51  // and "real_time" parameters as conditions. Suite conditions include
    52  // information such as the CL number and platform name.
    53  type Condition struct {
    54  	Name  string `bq:"name"`
    55  	Value string `bq:"value"`
    56  }
    57  
    58  // Metric holds the actual metric data and unit information for this benchmark.
    59  type Metric struct {
    60  	Name   string  `bq:"name"`
    61  	Unit   string  `bq:"unit"`
    62  	Sample float64 `bq:"sample"`
    63  }
    64  
    65  // InitBigQuery initializes a BigQuery dataset/table in the project. If the dataset/table already exists, it is not duplicated.
    66  func InitBigQuery(ctx context.Context, projectID, datasetID, tableID string, opts []option.ClientOption) error {
    67  	client, err := bq.NewClient(ctx, projectID, opts...)
    68  	if err != nil {
    69  		return fmt.Errorf("failed to initialize client on project %s: %v", projectID, err)
    70  	}
    71  	defer client.Close()
    72  
    73  	dataset := client.Dataset(datasetID)
    74  	if err := dataset.Create(ctx, nil); err != nil && !checkDuplicateError(err) {
    75  		return fmt.Errorf("failed to create dataset: %s: %v", datasetID, err)
    76  	}
    77  
    78  	table := dataset.Table(tableID)
    79  	schema, err := bq.InferSchema(Suite{})
    80  	if err != nil {
    81  		return fmt.Errorf("failed to infer schema: %v", err)
    82  	}
    83  
    84  	if err := table.Create(ctx, &bq.TableMetadata{Schema: schema}); err != nil && !checkDuplicateError(err) {
    85  		return fmt.Errorf("failed to create table: %s: %v", tableID, err)
    86  	}
    87  	return nil
    88  }
    89  
    90  // AddCondition adds a condition to an existing Benchmark.
    91  func (bm *Benchmark) AddCondition(name, value string) {
    92  	bm.Condition = append(bm.Condition, &Condition{
    93  		Name:  name,
    94  		Value: value,
    95  	})
    96  }
    97  
    98  // AddMetric adds a metric to an existing Benchmark.
    99  func (bm *Benchmark) AddMetric(metricName, unit string, sample float64) {
   100  	m := &Metric{
   101  		Name:   metricName,
   102  		Unit:   unit,
   103  		Sample: sample,
   104  	}
   105  	bm.Metric = append(bm.Metric, m)
   106  }
   107  
   108  // NewBenchmark initializes a new benchmark.
   109  func NewBenchmark(name string, iters int) *Benchmark {
   110  	return &Benchmark{
   111  		Name:   name,
   112  		Metric: make([]*Metric, 0),
   113  		Condition: []*Condition{
   114  			{
   115  				Name:  "iterations",
   116  				Value: strconv.Itoa(iters),
   117  			},
   118  		},
   119  	}
   120  }
   121  
   122  // NewBenchmarkWithMetric creates a new sending to BigQuery, initialized with a
   123  // single iteration and single metric.
   124  func NewBenchmarkWithMetric(name, metric, unit string, value float64) *Benchmark {
   125  	b := NewBenchmark(name, 1)
   126  	b.AddMetric(metric, unit, value)
   127  	return b
   128  }
   129  
   130  // NewSuite initializes a new Suite.
   131  func NewSuite(name string, official bool) *Suite {
   132  	return &Suite{
   133  		Name:       name,
   134  		Timestamp:  time.Now().UTC(),
   135  		Benchmarks: make([]*Benchmark, 0),
   136  		Conditions: make([]*Condition, 0),
   137  		Official:   official,
   138  	}
   139  }
   140  
   141  // SendBenchmarks sends the slice of benchmarks to the BigQuery dataset/table.
   142  func SendBenchmarks(ctx context.Context, suite *Suite, projectID, datasetID, tableID string, opts []option.ClientOption) error {
   143  	client, err := bq.NewClient(ctx, projectID, opts...)
   144  	if err != nil {
   145  		return fmt.Errorf("failed to initialize client on project: %s: %v", projectID, err)
   146  	}
   147  	defer client.Close()
   148  
   149  	uploader := client.Dataset(datasetID).Table(tableID).Uploader()
   150  	if err = uploader.Put(ctx, suite); err != nil {
   151  		return fmt.Errorf("failed to upload benchmarks %s to project %s, table %s.%s: %v", suite.Name, projectID, datasetID, tableID, err)
   152  	}
   153  
   154  	return nil
   155  }
   156  
   157  // BigQuery will error "409" for duplicate tables and datasets.
   158  func checkDuplicateError(err error) bool {
   159  	return strings.Contains(err.Error(), "googleapi: Error 409: Already Exists")
   160  }