k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/experiment/ml/analyze/main.go (about) 1 /* 2 Copyright 2022 The Kubernetes 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 "fmt" 24 "log" 25 "regexp" 26 "strconv" 27 "time" 28 29 "cloud.google.com/go/storage" 30 "github.com/GoogleCloudPlatform/testgrid/util/gcs" 31 ) 32 33 var ( 34 location = flag.String("region", "", "Use a model in the specified region") 35 model = flag.String("model", "", "Use the specified model ID") 36 projectID = flag.String("project", "", "Use a model in the specified project") 37 quotaProjectID = flag.String("quota-project", "", "Specify a quota project to charge") 38 39 buildURL = flag.String("build", "", "Paste in a full prow https build URL instead") 40 41 sentenceLen = flag.Int("max-sentence-length", 600, "Truncate sentences to at most this many bytes") 42 documentLen = flag.Int("max-document-length", 200, "Process at most this many lines from the top/bottom of the build") 43 44 qps = flag.Int("qps", 6, "Limit prediction requests") 45 burst = flag.Int("burst-seconds", 4, "Allow bursts of activity for this many qps seconds") 46 warmup = flag.Bool("warmup", false, "Start with the burst pool filled") 47 48 additional = flag.Bool("additional", false, "Print all hot lines, not just the hottest") 49 annotate = flag.Bool("annotate", false, "Ask whether to annotate after predicting") 50 shout = flag.Bool("shout", false, "Make the server noisy") 51 52 port = flag.Int("port", 0, "Listen for annotation requests on this port") 53 timeout = flag.Duration("timeout", time.Minute, "Maximum time to answer a request") 54 ) 55 56 func main() { 57 flag.Parse() 58 var build *gcs.Path 59 if *port == 0 { 60 if *buildURL == "" { 61 log.Fatal("--build and --port unset") 62 } 63 b, err := pathFromView(*buildURL) 64 if err != nil { 65 log.Fatalf("Could not parse --build=%q: %v", *buildURL, err) 66 } 67 build = b 68 } 69 if *projectID == "" { 70 log.Fatal("--project unset") 71 } 72 if *location == "" { 73 log.Fatal("--region unset") 74 } 75 if *model == "" { 76 log.Fatal("--model unset") 77 } 78 79 ctx, cancel := context.WithCancel(context.Background()) 80 defer cancel() 81 82 storageClient, err := storage.NewClient(ctx) 83 if err != nil { 84 log.Fatalf("Could not create GCS client: %v", err) 85 } 86 87 predictor, err := defaultPredictionClient(ctx) 88 if err != nil { 89 log.Fatalf("Failed to create predictor: %v", err) 90 } 91 defer predictor.client.Close() 92 93 if *port > 0 { 94 if err := serveOnPort(ctx, storageClient, predictor, *port, *timeout); err != nil { 95 log.Fatalf("Serve failed: %v", err) 96 } 97 return 98 } 99 100 gcsClient := gcs.NewClient(storageClient) 101 102 lines, _, err := annotateBuild(ctx, gcsClient, predictor, *build) 103 if err != nil { 104 log.Fatalf("Failed to annotate build: %v", err) 105 } 106 107 if *annotate && lines != nil { 108 if err := askSaveLines(ctx, storageClient, *build, lines); err != nil { 109 log.Fatalf("Failed to save lines: %v", err) 110 } 111 } 112 } 113 114 var ( 115 buildRE = regexp.MustCompile(`https?://[^/]+/view/gc?s/(.+?)/?$`) 116 ) 117 118 func pathFromView(view string) (*gcs.Path, error) { 119 mat := buildRE.FindStringSubmatch(view) 120 if mat == nil { 121 return nil, errors.New("--build must match https://HOST/view/gs/PREFIX/JOB/BUILD") 122 } 123 return gcs.NewPath("gs://" + mat[1] + "/build-log.txt") 124 } 125 126 func askSaveLines(ctx context.Context, storageClient *storage.Client, build gcs.Path, lines []int) error { 127 min, max := minMax(lines) 128 fmt.Printf("Annotate lines %d-%d of %s? [y/N] ", min, max, build) 129 var answer string 130 _, _ = fmt.Scanln(&answer) // intentionally ignore error on just enter 131 if answer == "" || (answer[0] != 'y' && answer[0] != 'Y') { 132 return nil 133 } 134 135 return saveLines(ctx, storageClient, build, min, max) 136 } 137 138 const ( 139 focusStart = "focus-start" 140 focusEnd = "focus-end" 141 ) 142 143 func saveLines(ctx context.Context, storageClient *storage.Client, path gcs.Path, min, max int) error { 144 if min > max { 145 max, min = min, max 146 } 147 148 meta := map[string]string{ 149 focusStart: "", 150 focusEnd: "", 151 } 152 153 if min > 0 { 154 meta[focusStart] = strconv.Itoa(min) 155 } 156 if max > 0 { 157 meta[focusEnd] = strconv.Itoa(max) 158 } 159 160 attrs := storage.ObjectAttrsToUpdate{Metadata: meta} 161 if _, err := storageClient.Bucket(path.Bucket()).Object(path.Object()).Update(ctx, attrs); err != nil { 162 return err 163 } 164 log.Println("Annotated", path, "to focus on lines", min, "-", max) 165 return nil 166 }