internal/vulndbreqs: parallelize reading vulndb logs

Change-Id: Icd3f54d4589921c0bc320a51c14f6bd2f38eac8c
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/604957
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Zvonimir Pavlinovic <zpavlinovic@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/vulndbreqs/compute.go b/internal/vulndbreqs/compute.go
index 9dc6829..e26dbea 100644
--- a/internal/vulndbreqs/compute.go
+++ b/internal/vulndbreqs/compute.go
@@ -16,6 +16,7 @@
 	"fmt"
 	"io"
 	"os"
+	"sync"
 	"time"
 
 	"cloud.google.com/go/civil"
@@ -24,6 +25,7 @@
 	"golang.org/x/pkgsite-metrics/internal/bigquery"
 	"golang.org/x/pkgsite-metrics/internal/derrors"
 	"golang.org/x/pkgsite-metrics/internal/log"
+	"golang.org/x/sync/errgroup"
 	"google.golang.org/api/iterator"
 )
 
@@ -235,22 +237,40 @@
 	}
 	defer derrors.Wrap(&err, "countLogsForObjects(%q, ...[%d in total])", objNames[0], len(objNames))
 
+	var mu sync.Mutex
 	byDate = map[civil.Date]int{}
 	byIP = map[string]int{}
+	update := func(e *logEntry) error {
+		mu.Lock()
+		byDate[civil.DateOf(e.Timestamp)]++
+		byIP[e.HTTPRequest.RemoteIP]++
+		mu.Unlock()
+		return nil
+	}
+
+	g, ctx := errgroup.WithContext(ctx)
+	g.SetLimit(5)
 	for _, name := range objNames {
-		r, err := bucket.Object(name).NewReader(ctx)
-		if err != nil {
-			return nil, nil, err
-		}
-		defer r.Close()
-		err = readJSONLogEntries(name, r, hmacKey, func(e *logEntry) error {
-			byDate[civil.DateOf(e.Timestamp)]++
-			byIP[e.HTTPRequest.RemoteIP]++
+		name := name
+		g.TryGo(func() error {
+			select {
+			case <-ctx.Done():
+				return nil // context cancelled, likely another routine erred
+			default:
+				r, err := bucket.Object(name).NewReader(ctx)
+				if err != nil {
+					return err
+				}
+				defer r.Close()
+				if err := readJSONLogEntries(name, r, hmacKey, update); err != nil {
+					return err
+				}
+			}
 			return nil
 		})
-		if err != nil {
-			return nil, nil, err
-		}
+	}
+	if err := g.Wait(); err != nil {
+		return nil, nil, err
 	}
 	return byDate, byIP, nil
 }