github.com/GoogleCloudPlatform/testgrid@v0.0.174/cmd/tabulator/main.go (about) 1 /* 2 Copyright 2022 The TestGrid Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "context" 21 "errors" 22 "flag" 23 "runtime" 24 "strings" 25 "time" 26 27 gpubsub "cloud.google.com/go/pubsub" 28 "github.com/GoogleCloudPlatform/testgrid/pkg/pubsub" 29 "github.com/GoogleCloudPlatform/testgrid/pkg/tabulator" 30 "github.com/GoogleCloudPlatform/testgrid/util" 31 "github.com/GoogleCloudPlatform/testgrid/util/gcs" 32 "github.com/GoogleCloudPlatform/testgrid/util/metrics/prometheus" 33 "github.com/sirupsen/logrus" 34 "google.golang.org/api/option" 35 ) 36 37 // options configures the updater 38 type options struct { 39 config gcs.Path // gs://path/to/config/proto 40 persistQueue gcs.Path 41 creds string 42 confirm bool 43 useTabAlertSettings bool 44 calculateStats bool 45 extendState bool 46 groups util.Strings 47 readConcurrency int 48 writeConcurrency int 49 wait time.Duration 50 gridPathPrefix string 51 tabStatePathPrefix string 52 pubsub string 53 54 debug bool 55 trace bool 56 jsonLogs bool 57 } 58 59 // validate ensures reasonable options 60 func (o *options) validate() error { 61 if o.config.String() == "" { 62 return errors.New("empty --config") 63 } 64 if o.writeConcurrency < 1 { 65 o.writeConcurrency = 4 * runtime.NumCPU() 66 } 67 if o.readConcurrency < 1 { 68 o.readConcurrency = (o.writeConcurrency / 2) + 1 69 } 70 71 return nil 72 } 73 74 // gatherOptions reads options from flags 75 func gatherOptions() options { 76 var o options 77 78 flag.Var(&o.config, "config", "gs://path/to/config.pb") 79 flag.Var(&o.persistQueue, "persist-queue", "Load previous queue state from gs://path/to/queue-state.json and regularly save to it thereafter") 80 flag.StringVar(&o.creds, "gcp-service-account", "", "/path/to/gcp/creds (use local creds if empty)") 81 flag.BoolVar(&o.confirm, "confirm", false, "Upload data if set") 82 flag.Var(&o.groups, "group", "Only update named test group if set (repeateable)") 83 flag.BoolVar(&o.useTabAlertSettings, "tab-alerts", false, "Use newer tab settings while caculating alerts") 84 flag.BoolVar(&o.calculateStats, "column-stats", true, "Calculates stats for broken columns") 85 flag.BoolVar(&o.extendState, "extend", false, "Extend tab state instead of replacing it") 86 87 flag.IntVar(&o.readConcurrency, "read-concurrency", 0, "Manually define the number of groups to read and hold in memory at once if non-zero") 88 flag.IntVar(&o.writeConcurrency, "concurrency", 0, "Manually define the number of tabs to concurrently update if non-zero") 89 flag.IntVar(&o.writeConcurrency, "write-concurrency", 0, "alias for --concurrency") 90 flag.DurationVar(&o.wait, "wait", 0, "Ensure at least this much time has passed since the last loop (exit if zero).") 91 92 flag.StringVar(&o.gridPathPrefix, "grid-path", "grid", "Read grid states under this GCS path.") 93 flag.StringVar(&o.tabStatePathPrefix, "tab-state-path", "tabs", "Write tab states under this GCS path.") 94 flag.StringVar(&o.pubsub, "pubsub", "", "listen for test group updates at project/subscription") 95 96 flag.BoolVar(&o.debug, "debug", false, "Log debug lines if set") 97 flag.BoolVar(&o.trace, "trace", false, "Log trace and debug lines if set") 98 flag.BoolVar(&o.jsonLogs, "json-logs", false, "Uses a json logrus formatter when set") 99 100 flag.Parse() 101 return o 102 } 103 104 func main() { 105 opt := gatherOptions() 106 if err := opt.validate(); err != nil { 107 logrus.Fatalf("Invalid flags: %v", err) 108 } 109 if !opt.confirm { 110 logrus.Warning("--confirm=false (DRY-RUN): will not write to gcs") 111 } 112 switch { 113 case opt.trace: 114 logrus.SetLevel(logrus.TraceLevel) 115 case opt.debug: 116 logrus.SetLevel(logrus.DebugLevel) 117 } 118 119 if opt.jsonLogs { 120 logrus.SetFormatter(&logrus.JSONFormatter{}) 121 } 122 logrus.SetReportCaller(true) 123 124 ctx, cancel := context.WithCancel(context.Background()) 125 defer cancel() 126 127 storageClient, err := gcs.ClientWithCreds(ctx, opt.creds) 128 if err != nil { 129 logrus.WithError(err).Fatal("Failed to create storage client") 130 } 131 defer storageClient.Close() 132 133 client := gcs.NewClient(storageClient) 134 135 logrus.WithFields(logrus.Fields{ 136 "read": opt.readConcurrency, 137 "write": opt.writeConcurrency, 138 }).Info("Configured concurrency") 139 140 fixers := make([]tabulator.Fixer, 0, 2) 141 142 fixer, err := gcsFixer(ctx, opt.pubsub, opt.config, opt.gridPathPrefix, opt.creds) 143 if err != nil { 144 logrus.WithError(err).WithField("subscription", opt.pubsub).Fatal("Failed to configure pubsub") 145 } 146 if fixer != nil { 147 fixers = append(fixers, fixer) 148 } 149 if path := opt.persistQueue; path.String() != "" { 150 const freq = time.Minute 151 ticker := time.NewTicker(freq) 152 log := logrus.WithField("frequency", freq) 153 fixers = append(fixers, tabulator.FixPersistent(log, client, path, ticker.C)) 154 } 155 156 mets := tabulator.CreateMetrics(prometheus.NewFactory()) 157 158 opts := &tabulator.UpdateOptions{ 159 ConfigPath: opt.config, 160 ReadConcurrency: opt.readConcurrency, 161 WriteConcurrency: opt.writeConcurrency, 162 GridPathPrefix: opt.gridPathPrefix, 163 TabsPathPrefix: opt.tabStatePathPrefix, 164 AllowedGroups: opt.groups.Strings(), 165 Confirm: opt.confirm, 166 CalculateStats: opt.calculateStats, 167 UseTabAlertSettings: opt.useTabAlertSettings, 168 ExtendState: opt.extendState, 169 Freq: opt.wait, 170 } 171 172 if err := tabulator.Update(ctx, client, mets, opts, fixers...); err != nil { 173 logrus.WithError(err).Error("Could not tabulate") 174 } 175 } 176 177 func gcsFixer(ctx context.Context, projectSub string, configPath gcs.Path, gridPrefix, credPath string) (tabulator.Fixer, error) { 178 if projectSub == "" { 179 return nil, nil 180 } 181 parts := strings.SplitN(projectSub, "/", 2) 182 if len(parts) != 2 { 183 return nil, errors.New("malformed project/subscription") 184 } 185 projID, subID := parts[0], parts[1] 186 pubsubClient, err := gpubsub.NewClient(ctx, "", option.WithCredentialsFile(credPath)) 187 if err != nil { 188 logrus.WithError(err).Fatal("Failed to create pubsub client") 189 } 190 client := pubsub.NewClient(pubsubClient) 191 return tabulator.FixGCS(client, logrus.StandardLogger(), projID, subID, configPath, gridPrefix) 192 }