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 }