cmd/ejobs: support for getting results Add a results subcommand to fetch results of a job. This won't work until we add the jobs/results endpoint to the worker, in a forthcoming CL. Change-Id: I213ec6d3020ee37ee19896c82b68366f8415f08e Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/511757 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Jonathan Amsterdam <jba@google.com> Reviewed-by: Maceo Thompson <maceothompson@google.com>
diff --git a/cmd/ejobs/main.go b/cmd/ejobs/main.go index b423569..7277cfa 100644 --- a/cmd/ejobs/main.go +++ b/cmd/ejobs/main.go
@@ -50,6 +50,10 @@ waitFlagSet = flag.NewFlagSet("wait", flag.ContinueOnError) waitInterval = waitFlagSet.Duration("i", 0, "display updates at this interval") + + resultsFlagSet = flag.NewFlagSet("results", flag.ContinueOnError) + force = resultsFlagSet.Bool("f", false, "download even if unfinished") + outfile = resultsFlagSet.String("o", "", "output filename") ) var commands = []command{ @@ -62,12 +66,15 @@ {"cancel", "JOBID...", "cancel the jobs", doCancel, nil}, - {"start", "-min [MIN_IMPORTERS] BINARY ARGS...", + {"start", "[-min MIN_IMPORTERS] BINARY ARGS...", "start a job", doStart, startFlagSet}, {"wait", "JOBID", "do not exit until JOBID is done", doWait, nil}, + {"results", "[-f] [-o FILE.json] JOBID", + "download results as JSON", + doResults, nil}, } type command struct { @@ -219,7 +226,7 @@ if err != nil { return err } - done := job.NumSkipped + job.NumFailed + job.NumErrored + job.NumSucceeded + done := job.NumFinished() if done >= job.NumEnqueued { break } @@ -426,6 +433,44 @@ return dest.Close() } +func doResults(ctx context.Context, args []string) (err error) { + fs := resultsFlagSet + if err := fs.Parse(args); err != nil { + return err + } + if fs.NArg() == 0 { + return errors.New("wrong number of args: want [-f] [-o FILE.json] JOB_ID") + } + jobID := fs.Arg(0) + ts, err := identityTokenSource(ctx) + if err != nil { + return err + } + job, err := requestJSON[jobs.Job](ctx, "jobs/describe?jobid="+jobID, ts) + if err != nil { + return err + } + done := job.NumFinished() + if !*force && done < job.NumEnqueued { + return fmt.Errorf("job not finished (%d/%d completed); use -f for partial results", done, job.NumEnqueued) + } + results, err := requestJSON[jobs.Results](ctx, "jobs/results?jobid="+jobID, ts) + if err != nil { + return err + } + out := os.Stdout + if *outfile != "" { + out, err = os.Create(*outfile) + if err != nil { + return err + } + defer func() { err = errors.Join(err, out.Close()) }() + } + enc := json.NewEncoder(out) + enc.SetIndent("", "\t") + return enc.Encode(results) +} + // requestJSON requests the path from the worker, then reads the returned body // and unmarshals it as JSON. func requestJSON[T any](ctx context.Context, path string, ts oauth2.TokenSource) (*T, error) {
diff --git a/internal/bigquery/bigquery.go b/internal/bigquery/bigquery.go index dc503eb..d5ff368 100644 --- a/internal/bigquery/bigquery.go +++ b/internal/bigquery/bigquery.go
@@ -321,7 +321,7 @@ } // PartitionQuery describes a query that returns one row for each distinct value -// of the partition column in the given table. +// of the partition columns in the given table. // // The selected row will be the first one according to the OrderBy clauses. //
diff --git a/internal/jobs/job.go b/internal/jobs/job.go index 2c0169c..5e09912 100644 --- a/internal/jobs/job.go +++ b/internal/jobs/job.go
@@ -7,6 +7,8 @@ import ( "time" + + "golang.org/x/pkgsite-metrics/internal/analysis" ) // A Job is a set of related scan tasks enqueued at the same time. @@ -45,3 +47,14 @@ func (j *Job) ID() string { return j.User + "-" + j.StartedAt.In(time.UTC).Format(startTimeFormat) } + +func (j *Job) NumFinished() int { + return j.NumSkipped + j.NumFailed + j.NumErrored + j.NumSucceeded +} + +// Results hold the results of a job. +type Results struct { + JobID string + Table string // bigquery table containing results + Results []*analysis.Result +}