| package main |
| |
| import ( |
| "context" |
| "encoding/json" |
| "flag" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "strings" |
| "time" |
| |
| "go.skia.org/infra/fiddlek/go/client" |
| "go.skia.org/infra/fiddlek/go/store" |
| "go.skia.org/infra/fiddlek/go/types" |
| "go.skia.org/infra/go/auth" |
| "go.skia.org/infra/go/common" |
| "go.skia.org/infra/go/git/gitinfo" |
| "go.skia.org/infra/go/gitauth" |
| "go.skia.org/infra/go/metrics2" |
| "go.skia.org/infra/go/skerr" |
| "go.skia.org/infra/go/sklog" |
| "go.skia.org/infra/go/util" |
| "go.skia.org/infra/named-fiddles/go/parse" |
| "golang.org/x/oauth2/google" |
| ) |
| |
| // Server is the state of the server. |
| type Server struct { |
| store store.Store |
| repo *gitinfo.GitInfo |
| repoDir string |
| |
| livenessExamples metrics2.Liveness // liveness of the naming the Skia examples. |
| errorsInExamplesRun metrics2.Counter // errorsInExamplesRun is the number of errors in a single examples run. |
| numInvalidExamples metrics2.Int64Metric // numInvalidExamples is the number of examples that are currently invalid. |
| } |
| |
| func main() { |
| local := flag.Bool("local", false, "Running locally if true. As opposed to in production.") |
| promPort := flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')") |
| repoURL := flag.String("repo_url", "https://skia.googlesource.com/skia", "Repo url") |
| repoDir := flag.String("repo_dir", "/tmp/skia_named_fiddles", "Directory the repo is checked out into.") |
| |
| common.InitWithMust( |
| "named-fiddles", |
| common.PrometheusOpt(promPort), |
| ) |
| |
| _, err := startSyncing(context.Background(), *local, *repoURL, *repoDir) |
| if err != nil { |
| sklog.Fatalf("Failed to create Server: %s", err) |
| } |
| select {} |
| } |
| |
| // startSyncing creates a new Server in a goroutine and returns. |
| func startSyncing(ctx context.Context, local bool, repoURL, repoDir string) (*Server, error) { |
| st, err := store.New(ctx, local) |
| if err != nil { |
| return nil, skerr.Wrapf(err, "creating fiddle store") |
| } |
| |
| if !local { |
| ts, err := google.DefaultTokenSource(ctx, auth.ScopeUserinfoEmail, auth.ScopeGerrit) |
| if err != nil { |
| sklog.Fatalf("Failed authentication: %s", err) |
| } |
| // Use the gitcookie created by the gitauth package. |
| if _, err := gitauth.New(ts, "/tmp/gitcookies", true, ""); err != nil { |
| sklog.Fatalf("Failed to create git cookie updater: %s", err) |
| } |
| sklog.Infof("Git authentication set up successfully.") |
| } |
| |
| repo, err := gitinfo.CloneOrUpdate(ctx, repoURL, repoDir, false) |
| if err != nil { |
| return nil, skerr.Wrapf(err, "Cloning git repo %s to %s", repoURL, repoDir) |
| } |
| sklog.Infof("git clone of %s to %s was successful", repoURL, repoDir) |
| srv := &Server{ |
| store: st, |
| repo: repo, |
| repoDir: repoDir, |
| |
| livenessExamples: metrics2.NewLiveness("named_fiddles_examples"), |
| errorsInExamplesRun: metrics2.GetCounter("named_fiddles_errors_in_examples_run"), |
| numInvalidExamples: metrics2.GetInt64Metric("named_fiddles_examples_total_invalid"), |
| } |
| go util.RepeatCtx(ctx, time.Minute, srv.exampleStep) |
| return srv, nil |
| } |
| |
| // errorsInResults returns an empty string if there are no errors, either |
| // compile or runtime, found in the results. If there are errors then a string |
| // describing the error is returned. |
| func errorsInResults(runResults *types.RunResults) string { |
| status := "" |
| if runResults == nil { |
| sklog.Infof("runResults are nil, so this was a timeout, count it as valid for now and try again on the next run.") |
| } else if len(runResults.CompileErrors) > 0 || runResults.RunTimeError != "" { |
| // update validity |
| status = fmt.Sprintf("%v %s", runResults.CompileErrors, runResults.RunTimeError) |
| if len(status) > 100 { |
| status = status[:100] |
| } |
| } |
| return status |
| } |
| |
| // exampleStep is a single run through naming all the examples. |
| func (srv *Server) exampleStep(ctx context.Context) { |
| srv.errorsInExamplesRun.Reset() |
| sklog.Info("Starting exampleStep") |
| if err := srv.repo.Update(ctx, true, false); err != nil { |
| sklog.Errorf("Failed to sync git repo.") |
| return |
| } |
| |
| var numInvalid int64 |
| // Get a list of all examples. |
| dir := filepath.Join(srv.repoDir, "docs", "examples") |
| err := filepath.Walk(dir+"/", func(path string, info os.FileInfo, err error) error { |
| if err != nil { |
| return skerr.Wrapf(err, "opening %q", path) |
| } |
| if info.IsDir() { |
| return nil |
| } |
| name := filepath.Base(info.Name()) |
| if !strings.HasSuffix(name, ".cpp") { |
| return nil |
| } |
| name = name[0 : len(name)-4] |
| filename := filepath.Join(dir, info.Name()) |
| b, err := ioutil.ReadFile(filename) |
| if err != nil { |
| sklog.Warningf("Failed to load file: %q", filename) |
| } |
| fc, err := parse.ParseCpp(string(b)) |
| if err == parse.ErrorInactiveExample { |
| sklog.Infof("Inactive sample: %q", info.Name()) |
| return nil |
| } else if err != nil { |
| sklog.Infof("Invalid sample: %q\n%s", info.Name(), err) |
| numInvalid++ |
| srv.numInvalidExamples.Update(numInvalid) |
| return nil |
| } |
| // Now run it. |
| sklog.Infof("About to run: %s", name) |
| b, err = json.Marshal(fc) |
| if err != nil { |
| sklog.Errorf("Failed to encode example to JSON: %s", err) |
| return nil |
| } |
| |
| runResults, success := client.Do(b, false, "https://fiddle.skia.org", func(*types.RunResults) bool { |
| return true |
| }) |
| status := errorsInResults(runResults) |
| if status == "" { |
| return nil |
| } |
| if !success { |
| sklog.Errorf("Failed to run https://fiddle.skia.org/c/@%s: %s", name, status) |
| srv.errorsInExamplesRun.Inc(1) |
| return nil |
| } |
| if err := srv.store.WriteName(name, runResults.FiddleHash, "Skia example", status); err != nil { |
| sklog.Errorf("Failed to write status for %s: %s", name, err) |
| } |
| return nil |
| }) |
| if err != nil { |
| sklog.Errorf("Error walking the path %q: %v\n", dir, err) |
| return |
| } |
| |
| srv.numInvalidExamples.Update(numInvalid) |
| srv.livenessExamples.Reset() |
| } |