| // Command line app to process fiddles in bulk. |
| // |
| // Example: |
| // |
| // fiddlecli --input demo/testbulk.json --output /tmp/output.json |
| package main |
| |
| import ( |
| "encoding/json" |
| "flag" |
| "fmt" |
| "log" |
| "os" |
| "sync" |
| |
| "go.skia.org/infra/fiddlek/go/client" |
| "go.skia.org/infra/fiddlek/go/types" |
| "go.skia.org/infra/go/common" |
| "go.skia.org/infra/go/sklog" |
| "golang.org/x/sync/errgroup" |
| ) |
| |
| const ( |
| // VERSION of the application. Update for major and minor changes to functionality. |
| VERSION = "1.2" |
| ) |
| |
| // flags |
| var ( |
| domain = flag.String("domain", "https://fiddle.skia.org", "Where to send the JSON request.") |
| failFast = flag.Bool("fail_fast", false, "If true then exit with error on first send failure.") |
| force = flag.Bool("force", false, "Force a compile and run for each fiddle, don't take the fast path.") |
| input = flag.String("input", "fiddle.json", "The name of the file to read the JSON from.") |
| output = flag.String("output", "fiddleout.json", "The name of the file to write the JSON results to.") |
| procs = flag.Int("procs", 4, "The number of parallel requests to make to the fiddle server.") |
| quiet = flag.Bool("quiet", false, "Run without a progress bar.") |
| version = flag.Bool("version", false, "If true then echo the version number and exit.") |
| ) |
| |
| // chanRequest is sent to each worker in the pool. |
| type chanRequest struct { |
| id string |
| req *types.FiddleContext |
| } |
| |
| func main() { |
| // Check flags. |
| common.Init() |
| if *input == "" { |
| flag.Usage() |
| log.Fatalf("--input is a required flag.") |
| } |
| if *output == "" { |
| flag.Usage() |
| log.Fatalf("--output is a required flag.") |
| } |
| if *version { |
| fmt.Printf("fiddlecli version: %s\n", VERSION) |
| os.Exit(0) |
| } |
| |
| // Read the source JSON file. |
| b, err := os.ReadFile(*input) |
| if err != nil { |
| log.Fatalf("Failed to read %s: %s", *input, err) |
| } |
| requests := types.BulkRequest{} |
| if err := json.Unmarshal(b, &requests); err != nil { |
| log.Fatalf("%s does not contain valid JSON: %s", *input, err) |
| } |
| lastWritten := types.BulkResponse{} |
| b, err = os.ReadFile(*output) |
| if err == nil { |
| if err := json.Unmarshal(b, &lastWritten); err != nil { |
| lastWritten = nil |
| } |
| } |
| g := errgroup.Group{} |
| requestsCh := make(chan chanRequest, len(requests)) |
| |
| // mutex protects response. |
| mutex := sync.Mutex{} |
| response := types.BulkResponse{} |
| |
| // Spin up workers. |
| for i := 0; i < *procs; i++ { |
| g.Go(func() error { |
| for req := range requestsCh { |
| if !*quiet { |
| fmt.Print(".") |
| } |
| fiddleHash, err := req.req.Options.ComputeHash(req.req.Code) |
| if err != nil { |
| sklog.Fatalf("Failed to calculate fiddleHash: %s", err) |
| } |
| if *force { |
| req.req.Fast = false |
| } else if lastWritten != nil && fiddleHash != "" && lastWritten[req.id] != nil && lastWritten[req.id].FiddleHash == fiddleHash { |
| mutex.Lock() |
| response[req.id] = lastWritten[req.id] |
| mutex.Unlock() |
| continue |
| } |
| b, err = json.Marshal(req.req) |
| if err != nil { |
| sklog.Errorf("Failed to encode an individual request: %s", err) |
| continue |
| } |
| runResults, success := client.Do(b, *failFast, *domain, func(runResults *types.RunResults) bool { |
| if fiddleHash != runResults.FiddleHash { |
| sklog.Warningf("Got mismatched hashes for %s: Want %q != Got %q", req.id, fiddleHash, runResults.FiddleHash) |
| return false |
| } |
| return true |
| }) |
| if !success { |
| sklog.Errorf("Failed to make request after retries") |
| continue |
| } |
| |
| mutex.Lock() |
| response[req.id] = runResults |
| mutex.Unlock() |
| } |
| return nil |
| }) |
| } |
| |
| // Loop over each entry and queue them up for the workers. |
| for id, req := range requests { |
| requestsCh <- chanRequest{ |
| id: id, |
| req: req, |
| } |
| } |
| close(requestsCh) |
| |
| // Wait for the workers to finish. |
| if err := g.Wait(); err != nil { |
| log.Fatalf("Failed to complete all requests: %s", err) |
| } |
| |
| // Validate the output. |
| for k, v := range requests { |
| hash, _ := v.Options.ComputeHash(v.Code) |
| resp, ok := response[k] |
| if !ok { |
| sklog.Fatalf("Failed to get any response for %q", k) |
| } else if hash != resp.FiddleHash { |
| sklog.Fatalf("For %q want %q but got %q", k, hash, response[k].FiddleHash) |
| } |
| } |
| |
| if !*quiet { |
| fmt.Print("\n") |
| } |
| b, err = json.MarshalIndent(response, "", " ") |
| if err != nil { |
| log.Fatalf("Failed to encode response file: %s", err) |
| } |
| if err := os.WriteFile(*output, b, 0600); err != nil { |
| log.Fatalf("Failed to write response file: %s", err) |
| } |
| } |