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  }