blob: 1b827783a5b284f4dd6f4231b64518c548adda9f [file]
// Command line app to process fiddles in bulk.
//
// Example:
// fiddlecli --input demo/testbulk.json --output /tmp/output.json
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"sync"
"golang.org/x/sync/errgroup"
"go.skia.org/infra/fiddle/go/types"
"go.skia.org/infra/go/httputils"
)
// flags
var (
domain = flag.String("domain", "https://fiddle.skia.org", "Where to send the JSON request.")
input = flag.String("input", "", "The name of the file to read the JSON from.")
output = flag.String("output", "", "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.")
force = flag.Bool("force", false, "Force a compile and run for each fiddle, don't take the fast path.")
)
// chanRequest is sent to each worker in the pool.
type chanRequest struct {
id string
req *types.FiddleContext
}
func main() {
// Check flags.
flag.Parse()
if *input == "" {
flag.Usage()
log.Fatalf("--input is a required flag.")
}
if *output == "" {
flag.Usage()
log.Fatalf("--output is a required flag.")
}
// Read the source JSON file.
b, err := ioutil.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 = ioutil.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{}
c := httputils.NewTimeoutClient()
resp, err := c.Get("https://fiddle.skia.org/reset_gpu/")
if resp.StatusCode != 200 {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Failed to read response when resetting GPU: %s", err)
} else {
log.Fatalf("Failed to reset GPU: %s", string(b))
}
}
// Spin up workers.
for i := 0; i < *procs; i++ {
g.Go(func() error {
c := httputils.NewTimeoutClient()
for req := range requestsCh {
if !*quiet {
fmt.Print(".")
}
if *force {
req.req.Fast = false
} else if lastWritten != nil {
fiddleHash, err := req.req.Options.ComputeHash(req.req.Code)
if err == nil {
if lastWritten[req.id] != nil {
if lastWritten[req.id].FiddleHash == fiddleHash {
mutex.Lock()
response[req.id] = lastWritten[req.id]
mutex.Unlock()
continue
}
}
}
}
// POST to fiddle.
b, err = json.Marshal(req.req)
if err != nil {
return fmt.Errorf("Failed to encode an individual request: %s", err)
}
resp, err := c.Post(*domain+"/_/run", "application/json", bytes.NewReader(b))
if err != nil || resp.StatusCode != 200 {
return fmt.Errorf("Failed to make request: %s", err)
}
// Decode response and add to all responses.
runResults := types.RunResults{}
if err := json.NewDecoder(resp.Body).Decode(&runResults); err != nil {
return fmt.Errorf("Failed to read response: %s", err)
}
if resp.StatusCode != 200 {
return fmt.Errorf("Request failed with %d: %s", resp.StatusCode, string(b))
}
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)
}
if !*quiet {
fmt.Print("\n")
}
b, err = json.MarshalIndent(response, "", " ")
if err != nil {
log.Fatalf("Failed to encode response file: %s", err)
}
if err := ioutil.WriteFile(*output, b, 0600); err != nil {
log.Fatalf("Failed to write response file: %s", err)
}
}