devapp, godash: collect issue statistics for plotting
A subsequent CL will add a dashboard for graphing project health; this
CL collects information about when issues are opened, closed, and
milestoned.
This also includes a refactor of the guts of devapp to make it more
modular.
It also adds logging to the godash library, which can be activated with
the -v flag in the godash command.
Change-Id: I54c1419435e496f5c0e2e7f4b966b3ee4de0b0a1
Reviewed-on: https://go-review.googlesource.com/28091
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/devapp/cache.go b/devapp/cache.go
new file mode 100644
index 0000000..dfbd5d5
--- /dev/null
+++ b/devapp/cache.go
@@ -0,0 +1,85 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package devapp
+
+import (
+ "bytes"
+ "compress/gzip"
+ "encoding/gob"
+
+ "golang.org/x/net/context"
+ "google.golang.org/appengine/datastore"
+ "google.golang.org/appengine/log"
+)
+
+// Cache is a datastore entity type that contains serialized data for dashboards.
+type Cache struct {
+ // Value contains a gzipped gob'd serialization of the object
+ // to be cached. It must be []byte to avail ourselves of the
+ // datastore's 1 MB size limit.
+ Value []byte
+}
+
+func getCaches(ctx context.Context, names ...string) map[string]*Cache {
+ out := make(map[string]*Cache)
+ var keys []*datastore.Key
+ var ptrs []*Cache
+ for _, name := range names {
+ keys = append(keys, datastore.NewKey(ctx, entityPrefix+"Cache", name, 0, nil))
+ out[name] = &Cache{}
+ ptrs = append(ptrs, out[name])
+ }
+ datastore.GetMulti(ctx, keys, ptrs) // Ignore errors since they might not exist.
+ return out
+}
+
+func getCache(ctx context.Context, name string) (*Cache, error) {
+ var cache Cache
+ if err := datastore.Get(ctx, datastore.NewKey(ctx, entityPrefix+"Cache", name, 0, nil), &cache); err != nil {
+ return nil, err
+ }
+ return &cache, nil
+}
+
+func unpackCache(cache *Cache, data interface{}) error {
+ if len(cache.Value) > 0 {
+ gzr, err := gzip.NewReader(bytes.NewReader(cache.Value))
+ if err != nil {
+ return err
+ }
+ defer gzr.Close()
+ if err := gob.NewDecoder(gzr).Decode(data); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func loadCache(ctx context.Context, name string, data interface{}) error {
+ cache, err := getCache(ctx, name)
+ if err != nil {
+ return err
+ }
+ return unpackCache(cache, data)
+}
+
+func writeCache(ctx context.Context, name string, data interface{}) error {
+ var cache Cache
+ var cacheout bytes.Buffer
+ cachegz := gzip.NewWriter(&cacheout)
+ e := gob.NewEncoder(cachegz)
+ if err := e.Encode(data); err != nil {
+ return err
+ }
+ if err := cachegz.Close(); err != nil {
+ return err
+ }
+ cache.Value = cacheout.Bytes()
+ log.Infof(ctx, "Cache %q update finished; writing %d bytes", name, cacheout.Len())
+ if _, err := datastore.Put(ctx, datastore.NewKey(ctx, entityPrefix+"Cache", name, 0, nil), &cache); err != nil {
+ return err
+ }
+ return nil
+}