├── static ├── pix.gif ├── minus.gif ├── plus.gif ├── app_engine_logo_sm.gif ├── gantt.js ├── appstats_css.css └── appstats_js.js ├── README.md ├── bytesize.go ├── cost.go ├── funcs.go ├── doc.go ├── types.go ├── appstats.go ├── handler.go ├── html.go └── static.go /static/pix.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsc/appstats/master/static/pix.gif -------------------------------------------------------------------------------- /static/minus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsc/appstats/master/static/minus.gif -------------------------------------------------------------------------------- /static/plus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsc/appstats/master/static/plus.gif -------------------------------------------------------------------------------- /static/app_engine_logo_sm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsc/appstats/master/static/app_engine_logo_sm.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # appstats 2 | 3 | A port of the python appstats implementation to the Go runtime on Google App Engine. 4 | 5 | For installation and usage, see the docs: [http://godoc.org/github.com/mjibson/appstats](http://godoc.org/github.com/mjibson/appstats). 6 | -------------------------------------------------------------------------------- /bytesize.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package appstats 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | type ByteSize float64 12 | 13 | const ( 14 | _ = iota 15 | KB ByteSize = 1 << (10 * iota) 16 | MB 17 | GB 18 | TB 19 | PB 20 | EB 21 | ZB 22 | YB 23 | ) 24 | 25 | func (b ByteSize) String() string { 26 | switch { 27 | case b >= YB: 28 | return fmt.Sprintf("%.2fYB", b/YB) 29 | case b >= ZB: 30 | return fmt.Sprintf("%.2fZB", b/ZB) 31 | case b >= EB: 32 | return fmt.Sprintf("%.2fEB", b/EB) 33 | case b >= PB: 34 | return fmt.Sprintf("%.2fPB", b/PB) 35 | case b >= TB: 36 | return fmt.Sprintf("%.2fTB", b/TB) 37 | case b >= GB: 38 | return fmt.Sprintf("%.2fGB", b/GB) 39 | case b >= MB: 40 | return fmt.Sprintf("%.2fMB", b/MB) 41 | case b >= KB: 42 | return fmt.Sprintf("%.2fKB", b/KB) 43 | } 44 | return fmt.Sprintf("%.2fB", b) 45 | } 46 | -------------------------------------------------------------------------------- /cost.go: -------------------------------------------------------------------------------- 1 | package appstats 2 | 3 | import ( 4 | "reflect" 5 | 6 | "code.google.com/p/goprotobuf/proto" 7 | ) 8 | 9 | const ( 10 | cost_Write = 10 11 | cost_Read = 7 12 | cost_Small = 1 13 | ) 14 | 15 | // todo: implement read and small ops costs 16 | 17 | func GetCost(p proto.Message) int64 { 18 | v := reflect.ValueOf(p) 19 | v = reflect.Indirect(v) 20 | if v.Kind() != reflect.Struct { 21 | return 0 22 | } 23 | 24 | var cost int64 25 | cost += extractCost(v) 26 | 27 | return cost 28 | } 29 | 30 | func extractCost(v reflect.Value) int64 { 31 | v = v.FieldByName("Cost") 32 | if v.Kind() != reflect.Ptr { 33 | return 0 34 | } 35 | v = v.Elem() 36 | if v.Kind() != reflect.Struct { 37 | return 0 38 | } 39 | 40 | var cost int64 41 | 42 | extract := func(name string) int64 { 43 | w := v.FieldByName(name) 44 | if w.Kind() != reflect.Ptr { 45 | return 0 46 | } 47 | w = w.Elem() 48 | switch w.Kind() { 49 | case reflect.Int, reflect.Int32, reflect.Int64: 50 | return w.Int() 51 | } 52 | 53 | return 0 54 | } 55 | 56 | cost += extract("IndexWrites") 57 | cost += extract("EntityWrites") 58 | 59 | return cost * cost_Write 60 | } 61 | -------------------------------------------------------------------------------- /funcs.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package appstats 18 | 19 | import ( 20 | "html/template" 21 | "reflect" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | // eq reports whether the first argument is equal to 27 | // any of the remaining arguments. 28 | func eq(args ...interface{}) bool { 29 | if len(args) == 0 { 30 | return false 31 | } 32 | x := args[0] 33 | switch x := x.(type) { 34 | case string, int, int64, byte, float32, float64: 35 | for _, y := range args[1:] { 36 | if x == y { 37 | return true 38 | } 39 | } 40 | return false 41 | } 42 | 43 | for _, y := range args[1:] { 44 | if reflect.DeepEqual(x, y) { 45 | return true 46 | } 47 | } 48 | return false 49 | } 50 | 51 | func add(a, b int) int { 52 | return a + b 53 | } 54 | 55 | func rjust(i, count int) string { 56 | s := strconv.Itoa(i) 57 | return strings.Repeat(" ", count-len(s)) + s 58 | } 59 | 60 | func lt(a, b int) bool { 61 | return a < b 62 | } 63 | 64 | var funcs = template.FuncMap{ 65 | "add": add, 66 | "eq": eq, 67 | "lt": lt, 68 | "rjust": rjust, 69 | } 70 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* 18 | Package appstats profiles the RPC performance of Google App Engine applications. 19 | 20 | Reference: https://developers.google.com/appengine/docs/python/tools/appstats 21 | 22 | To use this package, change your HTTP handler functions to use this signature: 23 | 24 | func(appengine.Context, http.ResponseWriter, *http.Request) 25 | 26 | Register them in the usual way, wrapping them with NewHandler. 27 | 28 | 29 | Example 30 | 31 | This is a small example using this package. 32 | 33 | import ( 34 | "net/http" 35 | 36 | "appengine" 37 | 38 | "github.com/mjibson/appstats" 39 | ) 40 | 41 | func init() { 42 | http.Handle("/", appstats.NewHandler(Main)) 43 | } 44 | 45 | func Main(c appengine.Context, w http.ResponseWriter, r *http.Request) { 46 | // do stuff with c: datastore.Get(c, key, entity) 47 | w.Write([]byte("success")) 48 | } 49 | 50 | 51 | Usage 52 | 53 | Use your app, and view the appstats interface at http://localhost:8080/_ah/stats/, or your production URL. 54 | 55 | 56 | Configuration 57 | 58 | Refer to the variables section of the documentation: http://godoc.org/github.com/mjibson/appstats#pkg-variables. 59 | 60 | 61 | Routing 62 | 63 | In general, your app.yaml will not need to change. In the case of conflicting 64 | routes, add the following to your app.yaml: 65 | 66 | handlers: 67 | - url: /_ah/stats/.* 68 | script: _go_app 69 | 70 | 71 | TODO 72 | 73 | Cost calculation is experimental. Currently it only includes write ops (read and small ops are TODO). 74 | */ 75 | package appstats 76 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package appstats 18 | 19 | import ( 20 | "fmt" 21 | "net/http" 22 | "sort" 23 | "strconv" 24 | "strings" 25 | "sync" 26 | "time" 27 | ) 28 | 29 | const ( 30 | keyPrefix = "__appstats__:" 31 | keyPart = keyPrefix + "%06d:part" 32 | keyFull = keyPrefix + "%06d:full" 33 | distance = 100 34 | modulus = 1000 35 | ) 36 | 37 | type RequestStats struct { 38 | User string 39 | Admin bool 40 | Method string 41 | Path, Query string 42 | Status int 43 | Cost int64 44 | Start time.Time 45 | Duration time.Duration 46 | RPCStats []RPCStat 47 | 48 | lock sync.Mutex 49 | wg sync.WaitGroup 50 | } 51 | 52 | type stats_part RequestStats 53 | 54 | type stats_full struct { 55 | Header http.Header 56 | Stats *RequestStats 57 | } 58 | 59 | func (r RequestStats) PartKey() string { 60 | t := roundTime(r.Start.Nanosecond()) 61 | return fmt.Sprintf(keyPart, t) 62 | } 63 | 64 | func (r RequestStats) FullKey() string { 65 | t := roundTime(r.Start.Nanosecond()) 66 | return fmt.Sprintf(keyFull, t) 67 | } 68 | 69 | func roundTime(i int) int { 70 | return (i / 1000 / distance) % modulus * distance 71 | } 72 | 73 | type RPCStat struct { 74 | Service, Method string 75 | Start time.Time 76 | Offset time.Duration 77 | Duration time.Duration 78 | StackData string 79 | In, Out string 80 | Cost int64 81 | } 82 | 83 | func (r RPCStat) Name() string { 84 | return r.Service + "." + r.Method 85 | } 86 | 87 | func (r RPCStat) Request() string { 88 | return r.In 89 | } 90 | 91 | func (r RPCStat) Response() string { 92 | return r.Out 93 | } 94 | 95 | func (r RPCStat) Stack() Stack { 96 | s := Stack{} 97 | 98 | if r.StackData == "" { 99 | return s 100 | } 101 | 102 | lines := strings.Split(r.StackData, "\n") 103 | for i := 0; i < len(lines); i++ { 104 | idx := strings.LastIndex(lines[i], " ") 105 | if idx == -1 { 106 | break 107 | } 108 | 109 | cidx := strings.LastIndex(lines[i], ":") 110 | lineno, _ := strconv.Atoi(lines[i][cidx+1 : idx]) 111 | f := &Frame{ 112 | Location: lines[i][:cidx], 113 | Lineno: lineno, 114 | } 115 | 116 | if i+1 < len(lines) && strings.HasPrefix(lines[i+1], "\t") { 117 | f.Call = strings.TrimSpace(lines[i+1]) 118 | i++ 119 | } 120 | 121 | s = append(s, f) 122 | } 123 | 124 | return s[2:] 125 | } 126 | 127 | type Stack []*Frame 128 | 129 | type Frame struct { 130 | Location string 131 | Call string 132 | Lineno int 133 | } 134 | 135 | type AllRequestStats []*RequestStats 136 | 137 | func (s AllRequestStats) Len() int { return len(s) } 138 | func (s AllRequestStats) Less(i, j int) bool { return s[i].Start.Sub(s[j].Start) < 0 } 139 | func (s AllRequestStats) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 140 | 141 | type StatsByName []*StatByName 142 | 143 | func (s StatsByName) Len() int { return len(s) } 144 | func (s StatsByName) Less(i, j int) bool { return s[i].Count < s[j].Count } 145 | func (s StatsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 146 | 147 | type StatByName struct { 148 | Name string 149 | Count int 150 | Cost int64 151 | SubStats []*StatByName 152 | Requests int 153 | RecentReqs []int 154 | RequestStats *RequestStats 155 | Duration time.Duration 156 | Timing TimingPercentile 157 | } 158 | 159 | type reverse struct{ sort.Interface } 160 | 161 | func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) } 162 | 163 | type SKey struct { 164 | a, b string 165 | } 166 | 167 | type cVal struct { 168 | count int 169 | cost int64 170 | } 171 | 172 | type TimingPercentile struct { 173 | P50 time.Duration 174 | P85 time.Duration 175 | P88 time.Duration 176 | P92 time.Duration 177 | P95 time.Duration 178 | P98 time.Duration 179 | } 180 | -------------------------------------------------------------------------------- /appstats.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package appstats 18 | 19 | import ( 20 | "bytes" 21 | "encoding/gob" 22 | "fmt" 23 | "math/rand" 24 | "net/http" 25 | "net/url" 26 | "runtime/debug" 27 | "time" 28 | 29 | "appengine" 30 | "appengine/memcache" 31 | "appengine/user" 32 | "appengine_internal" 33 | ) 34 | 35 | var ( 36 | // RecordFraction is the fraction of requests to record. 37 | // Set to a number between 0.0 (none) and 1.0 (all). 38 | RecordFraction float64 = 1.0 39 | 40 | // ShouldRecord is the function used to determine if recording will occur 41 | // for a given request. The default is to use RecordFraction. 42 | ShouldRecord = DefaultShouldRecord 43 | 44 | // ProtoMaxBytes is the amount of protobuf data to record. 45 | // Data after this is truncated. 46 | ProtoMaxBytes = 150 47 | 48 | // Namespace is the memcache namespace under which to store appstats data. 49 | Namespace = "__appstats__" 50 | ) 51 | 52 | const ( 53 | serveURL = "/_ah/stats/" 54 | detailsURL = serveURL + "details" 55 | fileURL = serveURL + "file" 56 | staticURL = serveURL + "static/" 57 | ) 58 | 59 | func init() { 60 | http.HandleFunc(serveURL, AppstatsHandler) 61 | } 62 | 63 | func DefaultShouldRecord(r *http.Request) bool { 64 | if RecordFraction >= 1.0 { 65 | return true 66 | } 67 | 68 | return rand.Float64() < RecordFraction 69 | } 70 | 71 | type Context struct { 72 | appengine.Context 73 | 74 | req *http.Request 75 | Stats *RequestStats 76 | } 77 | 78 | func (c Context) Call(service, method string, in, out appengine_internal.ProtoMessage, opts *appengine_internal.CallOptions) error { 79 | c.Stats.wg.Add(1) 80 | defer c.Stats.wg.Done() 81 | 82 | if service == "__go__" { 83 | return c.Context.Call(service, method, in, out, opts) 84 | } 85 | 86 | stat := RPCStat{ 87 | Service: service, 88 | Method: method, 89 | Start: time.Now(), 90 | Offset: time.Since(c.Stats.Start), 91 | StackData: string(debug.Stack()), 92 | } 93 | err := c.Context.Call(service, method, in, out, opts) 94 | stat.Duration = time.Since(stat.Start) 95 | stat.In = in.String() 96 | stat.Out = out.String() 97 | stat.Cost = GetCost(out) 98 | 99 | if len(stat.In) > ProtoMaxBytes { 100 | stat.In = stat.In[:ProtoMaxBytes] + "..." 101 | } 102 | if len(stat.Out) > ProtoMaxBytes { 103 | stat.Out = stat.Out[:ProtoMaxBytes] + "..." 104 | } 105 | 106 | c.Stats.lock.Lock() 107 | c.Stats.RPCStats = append(c.Stats.RPCStats, stat) 108 | c.Stats.Cost += stat.Cost 109 | c.Stats.lock.Unlock() 110 | return err 111 | } 112 | 113 | func NewContext(req *http.Request) Context { 114 | c := appengine.NewContext(req) 115 | var uname string 116 | var admin bool 117 | if u := user.Current(c); u != nil { 118 | uname = u.String() 119 | admin = u.Admin 120 | } 121 | return Context{ 122 | Context: c, 123 | req: req, 124 | Stats: &RequestStats{ 125 | User: uname, 126 | Admin: admin, 127 | Method: req.Method, 128 | Path: req.URL.Path, 129 | Query: req.URL.RawQuery, 130 | Start: time.Now(), 131 | }, 132 | } 133 | } 134 | 135 | func (c Context) FromContext(ctx appengine.Context) Context { 136 | return Context{ 137 | Context: ctx, 138 | req: c.req, 139 | Stats: c.Stats, 140 | } 141 | } 142 | 143 | const bufMaxLen = 1000000 144 | 145 | func (c Context) Save() { 146 | c.Stats.wg.Wait() 147 | c.Stats.Duration = time.Since(c.Stats.Start) 148 | 149 | var buf_part, buf_full bytes.Buffer 150 | full := stats_full{ 151 | Header: c.req.Header, 152 | Stats: c.Stats, 153 | } 154 | if err := gob.NewEncoder(&buf_full).Encode(&full); err != nil { 155 | c.Errorf("appstats Save error: %v", err) 156 | return 157 | } else if buf_full.Len() > bufMaxLen { 158 | // first try clearing stack traces 159 | for i := range full.Stats.RPCStats { 160 | full.Stats.RPCStats[i].StackData = "" 161 | } 162 | buf_full.Truncate(0) 163 | gob.NewEncoder(&buf_full).Encode(&full) 164 | } 165 | part := stats_part(*c.Stats) 166 | for i := range part.RPCStats { 167 | part.RPCStats[i].StackData = "" 168 | part.RPCStats[i].In = "" 169 | part.RPCStats[i].Out = "" 170 | } 171 | if err := gob.NewEncoder(&buf_part).Encode(&part); err != nil { 172 | c.Errorf("appstats Save error: %v", err) 173 | return 174 | } 175 | 176 | item_part := &memcache.Item{ 177 | Key: c.Stats.PartKey(), 178 | Value: buf_part.Bytes(), 179 | } 180 | 181 | item_full := &memcache.Item{ 182 | Key: c.Stats.FullKey(), 183 | Value: buf_full.Bytes(), 184 | } 185 | 186 | c.Infof("Saved; %s: %s, %s: %s, link: %v", 187 | item_part.Key, 188 | ByteSize(len(item_part.Value)), 189 | item_full.Key, 190 | ByteSize(len(item_full.Value)), 191 | c.URL(), 192 | ) 193 | 194 | nc := context(c.req) 195 | memcache.SetMulti(nc, []*memcache.Item{item_part, item_full}) 196 | } 197 | 198 | func (c Context) URL() string { 199 | u := url.URL{ 200 | Scheme: "http", 201 | Host: c.req.Host, 202 | Path: detailsURL, 203 | RawQuery: fmt.Sprintf("time=%v", c.Stats.Start.Nanosecond()), 204 | } 205 | 206 | return u.String() 207 | } 208 | 209 | func context(r *http.Request) appengine.Context { 210 | c := appengine.NewContext(r) 211 | nc, _ := appengine.Namespace(c, Namespace) 212 | return nc 213 | } 214 | 215 | // Handler is an http.Handler that records RPC statistics. 216 | type Handler struct { 217 | f func(appengine.Context, http.ResponseWriter, *http.Request) 218 | } 219 | 220 | // NewHandler returns a new Handler that will execute f. 221 | func NewHandler(f func(appengine.Context, http.ResponseWriter, *http.Request)) Handler { 222 | return Handler{ 223 | f: f, 224 | } 225 | } 226 | 227 | type responseWriter struct { 228 | http.ResponseWriter 229 | 230 | c Context 231 | } 232 | 233 | func (r responseWriter) Write(b []byte) (int, error) { 234 | // Emulate the behavior of http.ResponseWriter.Write since it doesn't 235 | // call our WriteHeader implementation. 236 | if r.c.Stats.Status == 0 { 237 | r.WriteHeader(http.StatusOK) 238 | } 239 | 240 | return r.ResponseWriter.Write(b) 241 | } 242 | 243 | func (r responseWriter) WriteHeader(i int) { 244 | r.c.Stats.Status = i 245 | r.ResponseWriter.WriteHeader(i) 246 | } 247 | 248 | func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 249 | if ShouldRecord(r) { 250 | c := NewContext(r) 251 | rw := responseWriter{ 252 | ResponseWriter: w, 253 | c: c, 254 | } 255 | h.f(c, rw, r) 256 | c.Save() 257 | } else { 258 | c := appengine.NewContext(r) 259 | h.f(c, w, r) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /static/gantt.js: -------------------------------------------------------------------------------- 1 | // Copyright 2009 Google Inc. All Rights Reserved. 2 | 3 | /** 4 | * Defines a class that can render a simple Gantt chart. 5 | * 6 | * @author guido@google.com (Guido van Rossum) 7 | * @author schefflerjens@google.com (Jens Scheffler) 8 | */ 9 | 10 | /** 11 | * @constructor 12 | */ 13 | var Gantt = function() { 14 | /** 15 | * @type {Array} 16 | */ 17 | this.bars = []; 18 | 19 | /** 20 | * @type {Array} 21 | */ 22 | this.output = []; 23 | }; 24 | 25 | 26 | /** 27 | * Internal fields used to render the chart. 28 | * Should not be modified. 29 | * @type {Array.} 30 | */ 31 | Gantt.SCALES = [[5, 0.2, 1.0], 32 | [6, 0.2, 1.2], 33 | [5, 0.25, 1.25], 34 | [6, 0.25, 1.5], 35 | [4, 0.5, 2.0], 36 | [5, 0.5, 2.5], 37 | [6, 0.5, 3.0], 38 | [4, 1.0, 4.0], 39 | [5, 1.0, 5.0], 40 | [6, 1.0, 6.0], 41 | [4, 2.0, 8.0], 42 | [5, 2.0, 10.0]]; 43 | 44 | 45 | /** 46 | * Helper to compute the proper X axis scale. 47 | * Args: 48 | * highest: the highest value in the data series. 49 | * 50 | * Returns: 51 | * A tuple (howmany, spacing, limit) where howmany is the number of 52 | * increments, spacing is the increment to be used between successive 53 | * axis labels, and limit is the rounded-up highest value of the 54 | * axis. Within float precision, howmany * spacing == highest will 55 | * hold. 56 | * 57 | * The axis is assumed to always start at zero. 58 | */ 59 | Gantt.compute_scale = function(highest) { 60 | if (highest <= 0) { 61 | return [2, 0.5, 1.0] // Special-case if there's no data. 62 | } 63 | var scale = 1.0 64 | while (highest < 1.0) { 65 | highest *= 10.0 66 | scale /= 10.0 67 | } 68 | while (highest >= 10.0) { 69 | highest /= 10.0 70 | scale *= 10.0 71 | } 72 | // Now 1 <= highest < 10 73 | for (var i = 0; i < Gantt.SCALES.length; i++) { 74 | if (highest <= Gantt.SCALES[i][2]) { 75 | return [Gantt.SCALES[i][0], Gantt.SCALES[i][1] * scale, 76 | Gantt.SCALES[i][2] * scale]; 77 | } 78 | } 79 | // Avoid the need for "assert False". Not actually reachable. 80 | return [5, 2.0 * scale, 10.0 * scale]; 81 | }; 82 | 83 | 84 | /** 85 | * URL of a transparent 1x1 GIF. 86 | * @type {string} 87 | */ 88 | Gantt.prototype.PIX = 'stats/static/pix.gif'; 89 | 90 | 91 | /** 92 | * CSS class name prefix. 93 | * @type {string} 94 | */ 95 | Gantt.prototype.PREFIX = 'ae-stats-gantt-'; 96 | 97 | 98 | /** 99 | * Height of one bar. 100 | * @type {string} 101 | */ 102 | Gantt.prototype.HEIGHT = '1em'; 103 | 104 | 105 | /** 106 | * Height of the extra bar. 107 | * @type {string} 108 | */ 109 | Gantt.prototype.EXTRA_HEIGHT = '0.5em'; 110 | 111 | 112 | /** 113 | * Background color for the bar. 114 | * @type {string} 115 | */ 116 | Gantt.prototype.BG_COLOR = '#eeeeff'; 117 | 118 | 119 | /** 120 | * Color of the main bar. 121 | * @type {string} 122 | */ 123 | Gantt.prototype.COLOR = '#7777ff'; 124 | 125 | 126 | /** 127 | * Color of the extra bar. 128 | * @type {string} 129 | */ 130 | Gantt.prototype.EXTRA_COLOR = '#ff6666'; 131 | 132 | 133 | /** 134 | * Font size of inline_label. 135 | * @type {string} 136 | */ 137 | Gantt.prototype.INLINE_FONT_SIZE = '80%'; 138 | 139 | 140 | /** 141 | * Top of inline label text. 142 | * @type {string} 143 | */ 144 | Gantt.prototype.INLINE_TOP = '0.1em'; 145 | 146 | 147 | /** 148 | * Color for ticks. 149 | * @type {string} 150 | */ 151 | Gantt.prototype.TICK_COLOR = 'grey'; 152 | 153 | 154 | /** 155 | * @type {number} 156 | */ 157 | Gantt.prototype.highest_duration = 0; 158 | 159 | 160 | /* 161 | * Appends text to the output array. 162 | * @param {string} text The text to append to the output. 163 | */ 164 | Gantt.prototype.write = function(text) { 165 | this.output.push(text); 166 | }; 167 | 168 | 169 | /* 170 | * Internal helper to draw a table row showing the scale. 171 | * @param {number} howmany 172 | * @param {number} spacing 173 | * @param {number} scale 174 | */ 175 | Gantt.prototype.draw_scale = function(howmany, spacing, scale) { 176 | this.write('' + 177 | ''); 178 | this.write('
'); 179 | for (var i = 0; i <= howmany; i++) { 180 | this.write(''); 183 | this.write(''); 185 | this.write(' ' + (i * spacing) + ''); // TODO: number format %4g 186 | } 187 | this.write('
\n'); 188 | }; 189 | 190 | 191 | /** 192 | * Draw the bar chart as HTML. 193 | */ 194 | Gantt.prototype.draw = function() { 195 | this.output = []; 196 | var scale = Gantt.compute_scale(this.highest_duration); 197 | var howmany = scale[0]; 198 | var spacing = scale[1]; 199 | var limit = scale[2]; 200 | scale = 100.0 / limit; 201 | this.write('\n'); 202 | this.draw_scale(howmany, spacing, scale); 203 | for (var i = 0; i < this.bars.length; i++) { 204 | var bar = this.bars[i]; 205 | this.write('\n\n'); 244 | 245 | } 246 | this.draw_scale(howmany, spacing, scale); 247 | this.write('
'); 206 | if (bar.label.length > 0) { 207 | if (bar.link_target.length > 0) { 208 | this.write(''); 210 | } 211 | this.write(bar.label); 212 | if (bar.link_target.length > 0) { 213 | this.write(''); 214 | } 215 | } 216 | this.write(''); 217 | this.write('
\n'); 248 | 249 | var html = this.output.join(''); 250 | return html; 251 | }; 252 | 253 | 254 | /** 255 | * Add a bar to the chart. 256 | * All arguments representing times or durations should be integers 257 | * or floats expressed in seconds. The scale drawn is always 258 | * expressed in seconds (with limited precision). 259 | * @param {string} label Valid HTML or HTML-escaped text for the left column. 260 | * @param {number} start Start time for the event. 261 | * @param {number} duration Duration for the event. 262 | * @param {number} extra_duration Duration for the second bar; use 0 to 263 | * suppress. 264 | * @param {string} inline_label Valid HTML or HTML-escaped text drawn after the 265 | * bars; use '' to suppress. 266 | * @param {string} link_target HTML-escaped link where clicking on any element 267 | * will take you; use '' for no linking. 268 | */ 269 | Gantt.prototype.add_bar = function(label, start, duration, extra_duration, 270 | inline_label, link_target) { 271 | this.highest_duration = Math.max( 272 | this.highest_duration, Math.max(start + duration, 273 | start + extra_duration)); 274 | this.bars.push({label: label, start: start, duration: duration, 275 | extra_duration: extra_duration, inline_label: inline_label, 276 | link_target: link_target}); 277 | }; 278 | 279 | 280 | goog.exportSymbol('Gantt', Gantt); 281 | goog.exportProperty(Gantt.prototype, 'add_bar', Gantt.prototype.add_bar); 282 | goog.exportProperty(Gantt.prototype, 'draw', Gantt.prototype.draw); 283 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package appstats 18 | 19 | import ( 20 | "bytes" 21 | "encoding/gob" 22 | "fmt" 23 | "html/template" 24 | "io/ioutil" 25 | "net/http" 26 | "sort" 27 | "strconv" 28 | "strings" 29 | "time" 30 | 31 | "appengine" 32 | "appengine/memcache" 33 | "appengine/user" 34 | ) 35 | 36 | var templates *template.Template 37 | var staticFiles map[string][]byte 38 | 39 | func init() { 40 | templates = template.New("appstats").Funcs(funcs) 41 | templates.Parse(htmlBase) 42 | templates.Parse(htmlMain) 43 | templates.Parse(htmlDetails) 44 | templates.Parse(htmlFile) 45 | 46 | staticFiles = map[string][]byte{ 47 | "app_engine_logo_sm.gif": app_engine_logo_sm_gif, 48 | "appstats_css.css": appstats_css_css, 49 | "appstats_js.js": appstats_js_js, 50 | "gantt.js": gantt_js, 51 | "minus.gif": minus_gif, 52 | "pix.gif": pix_gif, 53 | "plus.gif": plus_gif, 54 | } 55 | } 56 | 57 | func serveError(w http.ResponseWriter, err error) { 58 | http.Error(w, err.Error(), http.StatusInternalServerError) 59 | } 60 | 61 | func AppstatsHandler(w http.ResponseWriter, r *http.Request) { 62 | c := appengine.NewContext(r) 63 | if appengine.IsDevAppServer() { 64 | // noop 65 | } else if u := user.Current(c); u == nil { 66 | if loginURL, err := user.LoginURL(c, r.URL.String()); err == nil { 67 | http.Redirect(w, r, loginURL, http.StatusTemporaryRedirect) 68 | } else { 69 | serveError(w, err) 70 | } 71 | return 72 | } else if !u.Admin { 73 | http.Error(w, "Forbidden", http.StatusForbidden) 74 | return 75 | } 76 | 77 | if detailsURL == r.URL.Path { 78 | Details(w, r) 79 | } else if fileURL == r.URL.Path { 80 | File(w, r) 81 | } else if strings.HasPrefix(r.URL.Path, staticURL) { 82 | Static(w, r) 83 | } else { 84 | Index(w, r) 85 | } 86 | } 87 | 88 | func Index(w http.ResponseWriter, r *http.Request) { 89 | keys := make([]string, modulus) 90 | for i := range keys { 91 | keys[i] = fmt.Sprintf(keyPart, i*distance) 92 | } 93 | 94 | c := context(r) 95 | items, err := memcache.GetMulti(c, keys) 96 | if err != nil { 97 | return 98 | } 99 | 100 | ars := AllRequestStats{} 101 | for _, v := range items { 102 | t := stats_part{} 103 | err := gob.NewDecoder(bytes.NewBuffer(v.Value)).Decode(&t) 104 | if err != nil { 105 | continue 106 | } 107 | r := RequestStats(t) 108 | ars = append(ars, &r) 109 | } 110 | sort.Sort(reverse{ars}) 111 | 112 | requestById := make(map[int]*RequestStats, len(ars)) 113 | idByRequest := make(map[*RequestStats]int, len(ars)) 114 | requests := make(map[int]*StatByName) 115 | byRequest := make(map[int]map[string]cVal) 116 | for i, v := range ars { 117 | idx := i + 1 118 | requestById[idx] = v 119 | idByRequest[v] = idx 120 | requests[idx] = &StatByName{ 121 | RequestStats: v, 122 | } 123 | byRequest[idx] = make(map[string]cVal) 124 | } 125 | 126 | requestByPath := make(map[string][]int) 127 | byCount := make(map[string]cVal) 128 | byRPC := make(map[SKey]cVal) 129 | for _, t := range ars { 130 | id := idByRequest[t] 131 | 132 | requestByPath[t.Path] = append(requestByPath[t.Path], id) 133 | 134 | for _, r := range t.RPCStats { 135 | rpc := r.Name() 136 | 137 | v := byRequest[id][rpc] 138 | v.count++ 139 | v.cost += r.Cost 140 | byRequest[id][rpc] = v 141 | 142 | v = byCount[rpc] 143 | v.count++ 144 | v.cost += r.Cost 145 | byCount[rpc] = v 146 | 147 | v = byRPC[SKey{rpc, t.Path}] 148 | v.count++ 149 | v.cost += r.Cost 150 | byRPC[SKey{rpc, t.Path}] = v 151 | } 152 | } 153 | 154 | for k, v := range byRequest { 155 | stats := StatsByName{} 156 | for rpc, s := range v { 157 | stats = append(stats, &StatByName{ 158 | Name: rpc, 159 | Count: s.count, 160 | Cost: s.cost, 161 | }) 162 | } 163 | sort.Sort(reverse{stats}) 164 | requests[k].SubStats = stats 165 | } 166 | 167 | statsByRPC := make(map[string]StatsByName) 168 | pathStats := make(map[string]StatsByName) 169 | for k, v := range byRPC { 170 | statsByRPC[k.a] = append(statsByRPC[k.a], &StatByName{ 171 | Name: k.b, 172 | Count: v.count, 173 | Cost: v.cost, 174 | }) 175 | pathStats[k.b] = append(pathStats[k.b], &StatByName{ 176 | Name: k.a, 177 | Count: v.count, 178 | Cost: v.cost, 179 | }) 180 | } 181 | for k, v := range statsByRPC { 182 | sort.Sort(reverse{v}) 183 | statsByRPC[k] = v 184 | } 185 | 186 | pathStatsByCount := StatsByName{} 187 | for k, v := range pathStats { 188 | total := 0 189 | var cost int64 190 | for _, stat := range v { 191 | total += stat.Count 192 | cost += stat.Cost 193 | } 194 | sort.Sort(reverse{v}) 195 | 196 | pathStatsByCount = append(pathStatsByCount, &StatByName{ 197 | Name: k, 198 | Count: total, 199 | Cost: cost, 200 | SubStats: v, 201 | Requests: len(requestByPath[k]), 202 | RecentReqs: requestByPath[k], 203 | }) 204 | } 205 | sort.Sort(reverse{pathStatsByCount}) 206 | 207 | allStatsByCount := StatsByName{} 208 | for k, v := range byCount { 209 | allStatsByCount = append(allStatsByCount, &StatByName{ 210 | Name: k, 211 | Count: v.count, 212 | Cost: v.cost, 213 | SubStats: statsByRPC[k], 214 | }) 215 | } 216 | sort.Sort(reverse{allStatsByCount}) 217 | 218 | addTimingPercentiles(pathStatsByCount, allStatsByCount, requestById) 219 | 220 | v := struct { 221 | Env map[string]string 222 | Requests map[int]*StatByName 223 | RequestStatsByCount map[int]*StatByName 224 | AllStatsByCount StatsByName 225 | PathStatsByCount StatsByName 226 | }{ 227 | Env: map[string]string{ 228 | "APPLICATION_ID": appengine.AppID(c), 229 | }, 230 | Requests: requests, 231 | AllStatsByCount: allStatsByCount, 232 | PathStatsByCount: pathStatsByCount, 233 | } 234 | 235 | _ = templates.ExecuteTemplate(w, "main", v) 236 | } 237 | 238 | func addTimingPercentiles(pathStatsByCount, allStatsByCount []*StatByName, requestByID map[int]*RequestStats) { 239 | var dt []time.Duration 240 | byRPC := make(map[string][]time.Duration) 241 | for _, pathStat := range pathStatsByCount { 242 | dt = dt[:0] 243 | localByRPC := make(map[string][]time.Duration) 244 | for _, id := range pathStat.RecentReqs { 245 | req := requestByID[id] 246 | dt = append(dt, req.Duration) 247 | for _, rpcStat := range req.RPCStats { 248 | name := rpcStat.Name() 249 | byRPC[name] = append(byRPC[name], rpcStat.Duration) 250 | localByRPC[name] = append(localByRPC[name], rpcStat.Duration) 251 | } 252 | } 253 | pathStat.Timing = makePercentile(dt) 254 | for _, stat := range pathStat.SubStats { 255 | stat.Timing = makePercentile(localByRPC[stat.Name]) 256 | } 257 | } 258 | for _, stat := range allStatsByCount { 259 | stat.Timing = makePercentile(byRPC[stat.Name]) 260 | } 261 | } 262 | 263 | func (t TimingPercentile) String() string { 264 | return fmt.Sprintf("50%%:%.2fms 85%%:%.2fms 88%%:%.2fms 92%%:%.2fms 95%%:%.2fms 98%%:%.2fms", 265 | t.P50.Seconds()*1e3, 266 | t.P85.Seconds()*1e3, 267 | t.P88.Seconds()*1e3, 268 | t.P92.Seconds()*1e3, 269 | t.P95.Seconds()*1e3, 270 | t.P98.Seconds()*1e3, 271 | ) 272 | } 273 | 274 | func makePercentile(dt []time.Duration) TimingPercentile { 275 | if len(dt) == 0 { 276 | return TimingPercentile{} 277 | } 278 | sort.Sort(durations(dt)) 279 | n := len(dt) 280 | return TimingPercentile{ 281 | P50: dt[n*50/100], 282 | P85: dt[n*85/100], 283 | P88: dt[n*88/100], 284 | P92: dt[n*92/100], 285 | P95: dt[n*95/100], 286 | P98: dt[n*98/100], 287 | } 288 | } 289 | 290 | type durations []time.Duration 291 | 292 | func (x durations) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 293 | func (x durations) Less(i, j int) bool { return x[i] < x[j] } 294 | func (x durations) Len() int { return len(x) } 295 | 296 | func Details(w http.ResponseWriter, r *http.Request) { 297 | i, _ := strconv.Atoi(r.FormValue("time")) 298 | qtime := roundTime(i) 299 | key := fmt.Sprintf(keyFull, qtime) 300 | 301 | c := context(r) 302 | 303 | v := struct { 304 | Env map[string]string 305 | Record *RequestStats 306 | Header http.Header 307 | AllStatsByCount StatsByName 308 | Real time.Duration 309 | }{ 310 | Env: map[string]string{ 311 | "APPLICATION_ID": appengine.AppID(c), 312 | }, 313 | } 314 | 315 | item, err := memcache.Get(c, key) 316 | if err != nil { 317 | templates.ExecuteTemplate(w, "details", v) 318 | return 319 | } 320 | 321 | full := stats_full{} 322 | err = gob.NewDecoder(bytes.NewBuffer(item.Value)).Decode(&full) 323 | if err != nil { 324 | templates.ExecuteTemplate(w, "details", v) 325 | return 326 | } 327 | 328 | byCount := make(map[string]cVal) 329 | durationCount := make(map[string]time.Duration) 330 | var _real time.Duration 331 | for _, r := range full.Stats.RPCStats { 332 | rpc := r.Name() 333 | 334 | // byCount 335 | if _, present := byCount[rpc]; !present { 336 | durationCount[rpc] = 0 337 | } 338 | v := byCount[rpc] 339 | v.count++ 340 | v.cost += r.Cost 341 | byCount[rpc] = v 342 | durationCount[rpc] += r.Duration 343 | _real += r.Duration 344 | } 345 | 346 | allStatsByCount := StatsByName{} 347 | for k, v := range byCount { 348 | allStatsByCount = append(allStatsByCount, &StatByName{ 349 | Name: k, 350 | Count: v.count, 351 | Cost: v.cost, 352 | Duration: durationCount[k], 353 | }) 354 | } 355 | sort.Sort(allStatsByCount) 356 | 357 | v.Record = full.Stats 358 | v.Header = full.Header 359 | v.AllStatsByCount = allStatsByCount 360 | v.Real = _real 361 | 362 | _ = templates.ExecuteTemplate(w, "details", v) 363 | } 364 | 365 | func File(w http.ResponseWriter, r *http.Request) { 366 | fname := r.URL.Query().Get("f") 367 | n := r.URL.Query().Get("n") 368 | lineno, _ := strconv.Atoi(n) 369 | c := context(r) 370 | 371 | f, err := ioutil.ReadFile(fname) 372 | if err != nil { 373 | serveError(w, err) 374 | return 375 | } 376 | 377 | fp := make(map[int]string) 378 | for k, v := range strings.Split(string(f), "\n") { 379 | fp[k+1] = v 380 | } 381 | 382 | v := struct { 383 | Env map[string]string 384 | Filename string 385 | Lineno int 386 | Fp map[int]string 387 | }{ 388 | Env: map[string]string{ 389 | "APPLICATION_ID": appengine.AppID(c), 390 | }, 391 | Filename: fname, 392 | Lineno: lineno, 393 | Fp: fp, 394 | } 395 | 396 | _ = templates.ExecuteTemplate(w, "file", v) 397 | } 398 | 399 | func Static(w http.ResponseWriter, r *http.Request) { 400 | fname := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:] 401 | if v, present := staticFiles[fname]; present { 402 | h := w.Header() 403 | 404 | if strings.HasSuffix(r.URL.Path, ".css") { 405 | h.Set("Content-type", "text/css") 406 | } else if strings.HasSuffix(r.URL.Path, ".js") { 407 | h.Set("Content-type", "text/javascript") 408 | } 409 | 410 | h.Set("Cache-Control", "public, max-age=expiry") 411 | expires := time.Now().Add(time.Hour) 412 | h.Set("Expires", expires.Format(time.RFC1123)) 413 | 414 | w.Write(v) 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /static/appstats_css.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2012 Google Inc. All Rights Reserved. */ 2 | html,body,div,h1,h2,h3,h4,h5,h6,p,img,dl,dt,dd,ol,ul,li,table,caption,tbody,tfoot,thead,tr,th,td,form,fieldset,embed,object,applet{margin:0;padding:0;border:0;}body{font-size:62.5%;font-family:Arial,sans-serif;color:#000;background:#fff}a{color:#00c}a:active{color:#f00}a:visited{color:#551a8b}table{border-collapse:collapse;border-width:0;empty-cells:show}ul{padding:0 0 1em 1em}ol{padding:0 0 1em 1.3em}li{line-height:1.5em;padding:0 0 .5em 0}p{padding:0 0 1em 0}h1,h2,h3,h4,h5{padding:0 0 1em 0}h1,h2{font-size:1.3em}h3{font-size:1.1em}h4,h5,table{font-size:1em}sup,sub{font-size:.7em}input,select,textarea,option{font-family:inherit;font-size:inherit}.g-doc,.g-doc-1024,.g-doc-800{font-size:130%}.g-doc{width:100%;text-align:left}.g-section{width:100%;vertical-align:top;display:inline-block}*:first-child+html .g-section{display:block}* html .g-section{overflow:hidden}@-moz-document url-prefix(''){.g-section{overflow:hidden}}@-moz-document url-prefix(''){.g-section,tt:default{overflow:visible}}.g-section,.g-unit{zoom:1}.g-split .g-unit{text-align:right}.g-split .g-first{text-align:left}.g-doc-1024{width:73.074em;min-width:950px;margin:0 auto;text-align:left}* html .g-doc-1024{width:71.313em}*+html .g-doc-1024{width:71.313em}.g-doc-800{width:57.69em;min-width:750px;margin:0 auto;text-align:left}* html .g-doc-800{width:56.3em}*+html .g-doc-800{width:56.3em}.g-tpl-160 .g-unit,.g-unit .g-tpl-160 .g-unit,.g-unit .g-unit .g-tpl-160 .g-unit,.g-unit .g-unit .g-unit .g-tpl-160 .g-unit{margin:0 0 0 160px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-160 .g-first,.g-unit .g-unit .g-tpl-160 .g-first,.g-unit .g-tpl-160 .g-first,.g-tpl-160 .g-first{margin:0;width:160px;float:left}.g-tpl-160-alt .g-unit,.g-unit .g-tpl-160-alt .g-unit,.g-unit .g-unit .g-tpl-160-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-160-alt .g-unit{margin:0 160px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-160-alt .g-first,.g-unit .g-unit .g-tpl-160-alt .g-first,.g-unit .g-tpl-160-alt .g-first,.g-tpl-160-alt .g-first{margin:0;width:160px;float:right}.g-tpl-180 .g-unit,.g-unit .g-tpl-180 .g-unit,.g-unit .g-unit .g-tpl-180 .g-unit,.g-unit .g-unit .g-unit .g-tpl-180 .g-unit{margin:0 0 0 180px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-180 .g-first,.g-unit .g-unit .g-tpl-180 .g-first,.g-unit .g-tpl-180 .g-first,.g-tpl-180 .g-first{margin:0;width:180px;float:left}.g-tpl-180-alt .g-unit,.g-unit .g-tpl-180-alt .g-unit,.g-unit .g-unit .g-tpl-180-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-180-alt .g-unit{margin:0 180px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-180-alt .g-first,.g-unit .g-unit .g-tpl-180-alt .g-first,.g-unit .g-tpl-180-alt .g-first,.g-tpl-180-alt .g-first{margin:0;width:180px;float:right}.g-tpl-300 .g-unit,.g-unit .g-tpl-300 .g-unit,.g-unit .g-unit .g-tpl-300 .g-unit,.g-unit .g-unit .g-unit .g-tpl-300 .g-unit{margin:0 0 0 300px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-300 .g-first,.g-unit .g-unit .g-tpl-300 .g-first,.g-unit .g-tpl-300 .g-first,.g-tpl-300 .g-first{margin:0;width:300px;float:left}.g-tpl-300-alt .g-unit,.g-unit .g-tpl-300-alt .g-unit,.g-unit .g-unit .g-tpl-300-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-300-alt .g-unit{margin:0 300px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-300-alt .g-first,.g-unit .g-unit .g-tpl-300-alt .g-first,.g-unit .g-tpl-300-alt .g-first,.g-tpl-300-alt .g-first{margin:0;width:300px;float:right}.g-tpl-25-75 .g-unit,.g-unit .g-tpl-25-75 .g-unit,.g-unit .g-unit .g-tpl-25-75 .g-unit,.g-unit .g-unit .g-unit .g-tpl-25-75 .g-unit{width:74.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-25-75 .g-first,.g-unit .g-unit .g-tpl-25-75 .g-first,.g-unit .g-tpl-25-75 .g-first,.g-tpl-25-75 .g-first{width:24.999%;float:left;margin:0}.g-tpl-25-75-alt .g-unit,.g-unit .g-tpl-25-75-alt .g-unit,.g-unit .g-unit .g-tpl-25-75-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-25-75-alt .g-unit{width:24.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-25-75-alt .g-first,.g-unit .g-unit .g-tpl-25-75-alt .g-first,.g-unit .g-tpl-25-75-alt .g-first,.g-tpl-25-75-alt .g-first{width:74.999%;float:right;margin:0}.g-tpl-75-25 .g-unit,.g-unit .g-tpl-75-25 .g-unit,.g-unit .g-unit .g-tpl-75-25 .g-unit,.g-unit .g-unit .g-unit .g-tpl-75-25 .g-unit{width:24.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-75-25 .g-first,.g-unit .g-unit .g-tpl-75-25 .g-first,.g-unit .g-tpl-75-25 .g-first,.g-tpl-75-25 .g-first{width:74.999%;float:left;margin:0}.g-tpl-75-25-alt .g-unit,.g-unit .g-tpl-75-25-alt .g-unit,.g-unit .g-unit .g-tpl-75-25-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-75-25-alt .g-unit{width:74.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-75-25-alt .g-first,.g-unit .g-unit .g-tpl-75-25-alt .g-first,.g-unit .g-tpl-75-25-alt .g-first,.g-tpl-75-25-alt .g-first{width:24.999%;float:right;margin:0}.g-tpl-33-67 .g-unit,.g-unit .g-tpl-33-67 .g-unit,.g-unit .g-unit .g-tpl-33-67 .g-unit,.g-unit .g-unit .g-unit .g-tpl-33-67 .g-unit{width:66.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-33-67 .g-first,.g-unit .g-unit .g-tpl-33-67 .g-first,.g-unit .g-tpl-33-67 .g-first,.g-tpl-33-67 .g-first{width:32.999%;float:left;margin:0}.g-tpl-33-67-alt .g-unit,.g-unit .g-tpl-33-67-alt .g-unit,.g-unit .g-unit .g-tpl-33-67-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-33-67-alt .g-unit{width:32.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-33-67-alt .g-first,.g-unit .g-unit .g-tpl-33-67-alt .g-first,.g-unit .g-tpl-33-67-alt .g-first,.g-tpl-33-67-alt .g-first{width:66.999%;float:right;margin:0}.g-tpl-67-33 .g-unit,.g-unit .g-tpl-67-33 .g-unit,.g-unit .g-unit .g-tpl-67-33 .g-unit,.g-unit .g-unit .g-unit .g-tpl-67-33 .g-unit{width:32.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-67-33 .g-first,.g-unit .g-unit .g-tpl-67-33 .g-first,.g-unit .g-tpl-67-33 .g-first,.g-tpl-67-33 .g-first{width:66.999%;float:left;margin:0}.g-tpl-67-33-alt .g-unit,.g-unit .g-tpl-67-33-alt .g-unit,.g-unit .g-unit .g-tpl-67-33-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-67-33-alt .g-unit{width:66.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-67-33-alt .g-first,.g-unit .g-unit .g-tpl-67-33-alt .g-first,.g-unit .g-tpl-67-33-alt .g-first,.g-tpl-67-33-alt .g-first{width:32.999%;float:right;margin:0}.g-tpl-50-50 .g-unit,.g-unit .g-tpl-50-50 .g-unit,.g-unit .g-unit .g-tpl-50-50 .g-unit,.g-unit .g-unit .g-unit .g-tpl-50-50 .g-unit{width:49.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-50-50 .g-first,.g-unit .g-unit .g-tpl-50-50 .g-first,.g-unit .g-tpl-50-50 .g-first,.g-tpl-50-50 .g-first{width:49.999%;float:left;margin:0}.g-tpl-50-50-alt .g-unit,.g-unit .g-tpl-50-50-alt .g-unit,.g-unit .g-unit .g-tpl-50-50-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-50-50-alt .g-unit{width:49.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-50-50-alt .g-first,.g-unit .g-unit .g-tpl-50-50-alt .g-first,.g-unit .g-tpl-50-50-alt .g-first,.g-tpl-50-50-alt .g-first{width:49.999%;float:right;margin:0}.g-tpl-nest{width:auto}.g-tpl-nest .g-section{display:inline}.g-tpl-nest .g-unit,.g-unit .g-tpl-nest .g-unit,.g-unit .g-unit .g-tpl-nest .g-unit,.g-unit .g-unit .g-unit .g-tpl-nest .g-unit{float:left;width:auto;margin:0}.g-tpl-nest-alt .g-unit,.g-unit .g-tpl-nest-alt .g-unit,.g-unit .g-unit .g-tpl-nest-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-nest-alt .g-unit{float:right;width:auto;margin:0}html>body .goog-inline-block{display:-moz-inline-box;display:inline-block;}.goog-inline-block{position:relative;display:inline-block}* html .goog-inline-block{display:inline}*:first-child+html .goog-inline-block{display:inline}.goog-tab{position:relative;border:1px solid #8ac;padding:4px 9px;color:#000;background:#e5ecf9;border-top-left-radius:2px;border-top-right-radius:2px;-moz-border-radius-topleft:2px;-webkit-border-top-left-radius:2px;-moz-border-radius-topright:2px;-webkit-border-top-right-radius:2px}.goog-tab-bar-top .goog-tab{margin:1px 4px 0 0;border-bottom:0;float:left}.goog-tab-bar-bottom .goog-tab{margin:0 4px 1px 0;border-top:0;float:left}.goog-tab-bar-start .goog-tab{margin:0 0 4px 1px;border-right:0}.goog-tab-bar-end .goog-tab{margin:0 1px 4px 0;border-left:0}.goog-tab-hover{text-decoration:underline;cursor:pointer}.goog-tab-disabled{color:#fff;background:#ccc;border-color:#ccc}.goog-tab-selected{background:#fff!important;color:black;font-weight:bold}.goog-tab-bar-top .goog-tab-selected{top:1px;margin-top:0;padding-bottom:5px}.goog-tab-bar-bottom .goog-tab-selected{top:-1px;margin-bottom:0;padding-top:5px}.goog-tab-bar-start .goog-tab-selected{left:1px;margin-left:0;padding-right:9px}.goog-tab-bar-end .goog-tab-selected{left:-1px;margin-right:0;padding-left:9px}.goog-tab-content{padding:.1em .8em .8em .8em;border:1px solid #8ac;border-top:none}.goog-tab-bar{position:relative;margin:0 0 0 5px;border:0;padding:0;list-style:none;cursor:default;outline:none}.goog-tab-bar-clear{border-top:1px solid #8ac;clear:both;height:0;overflow:hidden}.goog-tab-bar-start{float:left}.goog-tab-bar-end{float:right}* html .goog-tab-bar-start{margin-right:-3px}* html .goog-tab-bar-end{margin-left:-3px}.ae-table-plain{border-collapse:collapse;width:100%}.ae-table{border:1px solid #c5d7ef;border-collapse:collapse;width:100%}#bd h2.ae-table-title{background:#e5ecf9;margin:0;color:#000;font-size:1em;padding:3px 0 3px 5px;border-left:1px solid #c5d7ef;border-right:1px solid #c5d7ef;border-top:1px solid #c5d7ef}.ae-table-caption,.ae-table caption{border:1px solid #c5d7ef;background:#e5ecf9;-moz-margin-start:-1px}.ae-table caption{padding:3px 5px;text-align:left}.ae-table th,.ae-table td{background-color:#fff;padding:.35em 1em .25em .35em;margin:0}.ae-table thead th{font-weight:bold;text-align:left;background:#c5d7ef;vertical-align:bottom}.ae-table thead th .ae-no-bold{font-weight:normal}.ae-table tfoot tr td{border-top:1px solid #c5d7ef;background-color:#e5ecf9}.ae-table td{border-top:1px solid #c5d7ef;border-bottom:1px solid #c5d7ef}.ae-even>td,.ae-even th,.ae-even-top td,.ae-even-tween td,.ae-even-bottom td,ol.ae-even{background-color:#e9e9e9;border-top:1px solid #c5d7ef;border-bottom:1px solid #c5d7ef}.ae-even-top td{border-bottom:0}.ae-even-bottom td{border-top:0}.ae-even-tween td{border:0}.ae-table .ae-tween td{border:0}.ae-table .ae-tween-top td{border-bottom:0}.ae-table .ae-tween-bottom td{border-top:0}#bd .ae-table .cbc{width:1.5em;padding-right:0}.ae-table #ae-live td{background-color:#ffeac0}.ae-table-fixed{table-layout:fixed}.ae-table-fixed td,.ae-table-nowrap{overflow:hidden;white-space:nowrap}.ae-paginate strong{margin:0 .5em}tfoot .ae-paginate{text-align:right}.ae-table-caption .ae-paginate,.ae-table-caption .ae-orderby{padding:2px 5px}.g-doc{width:auto;margin:8px 10px 0 10px}#ae-logo{margin-bottom:0}#ae-appbar-lrg{margin:0 0 1.25em 0;padding:.2em .6em;background-color:#e5ecf9;border-top:1px solid #6b90da}#ae-appbar-lrg h1{font-size:1em;margin:0;padding:0}#ft p{text-align:center;margin-top:2.5em;padding-top:.5em;border-top:2px solid #c3d9ff}#bd h3{font-weight:bold;font-size:1.4em}#bd p{padding:0 0 1em 0}#ae-content{padding-left:1em;border-left:1px solid #6b90da;min-height:200px}.ae-table .ae-pager{background-color:#c5d7ef}#ae-nav ul{list-style-type:none;margin:0;padding:1em 0}#ae-nav ul li{padding:.1em 0 .1em .5em;margin-bottom:.3em}#ae-nav .ae-nav-selected{color:#44464a;display:block;font-weight:bold;background-color:#e5ecf9;border-bottom:1px solid #cedff2}a.ae-nav-selected{color:#44464a;text-decoration:none}#ae-nav ul li span.ae-nav-disabled{color:#666}#ae-nav ul ul{margin:0;padding:0 0 0 .5em}#ae-nav ul ul li{padding-left:.5em}#ae-nav ul li a,#ae-nav ul li span,#ae-nav ul ul li a{padding-left:.5em}#ae-nav li a:link,#ae-nav li a:visited{color:#00c}#ae-nav li a:link.ae-nav-selected,#ae-nav li a:visited.ae-nav-selected{color:#000;text-decoration:none}.ae-nav-group{padding:.5em;margin:0 .75em 0 0;background-color:#fffbe8;border:1px solid #fff1a9}.ae-nav-group h4{font-weight:bold;padding:auto auto .5em .5em;padding-left:.4em;margin-bottom:.5em;padding-bottom:0}.ae-nav-group ul{margin:0 0 .5em 0;padding:0 0 0 1.3em;list-style-type:none}.ae-nav-group ul li{padding-bottom:.5em}.ae-nav-group li a:link,.ae-nav-group li a:visited{color:#00c}.ae-nav-group li a:hover{color:#00c}#datastore_search{margin-bottom:1em}#hint{background-color:#f6f9ff;border:1px solid #e5ecf9;margin-bottom:1em;padding:0.5em 1em}#message{color:red;position:relative;bottom:6px}#pagetotal{float:right}#pagetotal .count{font-weight:bold}table.entities{border:1px solid #c5d7ef;border-collapse:collapse;width:100%;margin-bottom:0}table.entities th,table.entities td{padding:.25em 1.5em .5em .5em}table.entities th{font-weight:bold;text-align:left;background:#e5ecf9;white-space:nowrap}table.entities th a,table.entities th a:visited{color:black;text-decoration:none}table.entities td{background-color:#fff;text-align:left;vertical-align:top;cursor:pointer}table.entities tr.even td{background-color:#f9f9f9}div.entities{background-color:#c5d7ef;margin-top:0}#entities-pager,#entities-control{padding:.3em 1em .4em 1em}#entities-pager{text-align:right}.ae-page-number{margin:0 0.5em}.ae-page-selected{font-weight:bold}#ae-stats-hd span{font-weight:normal}#ae-rpc-label-col{width:85%}#ae-rpc-stats-col{width:15%}#ae-path-label-col{width:45%}#ae-path-reqs-col{width:10%}#ae-path-rpcs-col{width:10%}#ae-path-stats-col{width:35%}#ae-stats-refresh{margin-bottom:1em}.ae-table-wrapper-left{margin-right:.5em}.ae-table-wrapper-right{margin-left:.5em}#ae-req-history,#ae-rpc-traces{margin-top:1em}.ae-zippy,.ae-zippy-all{position:relative;top:1px;height:12px;width:12px}.goog-zippy-collapsed{background:transparent url('./plus.gif') no-repeat}.goog-zippy-expanded{background:transparent url('./minus.gif') no-repeat}td.ae-hanging-indent{padding-left:20px;text-indent:-20px}.ae-stats-request-link{text-decoration:none}.ae-table td.rpc-req{padding-left:20px;width:20em}#bd div.ae-table-title{background:#e5ecf9;margin:0;color:#000;padding:3px 0 3px 5px;border-left:1px solid #c5d7ef;border-right:1px solid #c5d7ef;border-top:1px solid #c5d7ef}#bd div.ae-table-title h2{font-size:1em;margin:0;padding:0}#bd div.ae-table-title h2.ae-zippy{padding-left:16px;text-decoration:underline;color:#00c}#ae-head-glance span,#ae-rpc-expand-all span,#ae-path-expand-all span,#ae-request-expand-all span{padding-right:.5em}.ae-action{color:#00c;text-decoration:underline;cursor:pointer}.ae-toggle{padding-left:16px;background-position:left center;background-repeat:no-repeat;cursor:pointer}.ae-minus{background-image:url('./minus.gif')}.ae-plus{background-image:url('./plus.gif')}#ae-stats-summary{margin-bottom:1em}#ae-stats-summary dt{float:left;text-align:right;margin-right:1em;font-weight:bold}.ae-stats-date{color:#666}.ae-stats-response-200{color:green}#ae-stats-summary dd{float:left}table.ae-stats-gantt-table{width:95%;border:1px solid #999}div.ae-stats-gantt-container{position:relative;width:100%;height:1em;background-color:#eeeeff}img.ae-stats-gantt-bar{border:0;height:1em;background-color:#7777ff;position:absolute;top:0}img.ae-stats-gantt-extra{border:0;height:0.5em;background-color:#ff6666;position:absolute;top:25%}span.ae-stats-gantt-inline{font-size:80%;position:absolute;top:0.1em;white-space:nowrap;overflow:hidden}a.ae-stats-gantt-link{text-decoration:none}div.ae-stats-gantt-axis{position:relative;width:100%;height:1em}img.ae-stats-gantt-tick{width:1px;height:1em;position:absolute;background-color:gray}span.ae-stats-gantt-scale{position:absolute} -------------------------------------------------------------------------------- /html.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Matt Jibson 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* 18 | * These are modified from the python appstats implementation. 19 | * If the above license infringes in some way on the original owner's 20 | * copyright, I will change it. 21 | */ 22 | 23 | package appstats 24 | 25 | const htmlBase = ` 26 | {{ define "top" }} 27 | 28 | 29 | 30 | 35 | Appstats - {{.Env.APPLICATION_ID}} 36 | {{ end }} 37 | 38 | {{ define "body" }} 39 | 40 | 41 |
42 | {{/* Header begin */}} 43 |
44 |
45 | 47 |
48 |
49 |
50 |
51 |

Application Stats for {{.Env.APPLICATION_ID}}

52 |
53 |
54 | All costs displayed in micropennies (1 dollar equals 100 pennies, 1 penny equals 1 million micropennies) 55 |
56 |
57 |
58 |
59 | {{/* Header end */}} 60 | {{/* Body begin */}} 61 |
62 | {{/* Content begin */}} 63 |
64 | {{ end }} 65 | 66 | {{ define "end" }} 67 |
68 | {{/* Content end */}} 69 |
70 | {{/* Body end */}} 71 |
72 | 73 | {{ end }} 74 | 75 | {{ define "footer" }} 76 | 77 | 78 | {{ end }} 79 | ` 80 | 81 | const htmlMain = ` 82 | {{ define "main" }} 83 | {{ template "top" . }} 84 | {{ template "body" . }} 85 | 86 |
87 | 88 |
89 | 90 | {{ if .Requests }} 91 |
92 |
93 | {{/* RPC stats table begin */}} 94 |
95 |
96 |
97 |

RPC Stats

98 |
99 |
100 |
101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | {{ range $index, $item := .AllStatsByCount }} 118 | 119 | 120 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | {{ range $subitem := $item.SubStats }} 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | {{ end }} 140 | 141 | {{ end }} 142 |
RPCCountTimingCostCost %
121 | 122 | {{$item.Name}} 123 | {{$item.Count}}{{.Timing}}{{$item.Cost}}{{/*$item.CostPct*/}}
{{$subitem.Name}}{{$subitem.Count}}{{$subitem.Cost}}{{/*$subitem.CostPct*/}}
143 |
144 | {{/* RPC stats table end */}} 145 |
146 |
147 | {{/* Path stats table begin */}} 148 |
149 |
150 |
151 |

Path Stats

152 |
153 |
154 |
155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | {{ range $index, $item := .PathStatsByCount }} 175 | 176 | 180 | 183 | 184 | 185 | 186 | 187 | 197 | 198 | {{ range $subitem := $item.SubStats }} 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | {{ end }} 209 | 210 | {{ end }} 211 |
Path#RPCsTimingCostCost%#RequestsMost Recent requests
177 | 178 | {{$item.Name}} 179 | 181 | {{$item.Count}} 182 | {{$item.Timing}}{{$item.Cost}}{{/*$item.CostPct*/}}{{$item.Requests}} 188 | {{ range $index, $element := $item.RecentReqs }} 189 | {{ if lt $index 10 }} 190 | ({{$element}}) 191 | {{ end }} 192 | {{ if eq $index 10 }} 193 | ... 194 | {{ end }} 195 | {{ end }} 196 |
{{$subitem.Name}}{{$subitem.Count}}{{$subitem.Timing}}{{$subitem.Cost}}{{/*$subitem.CostPct*/}}
212 |
213 | {{/* Path stats table end */}} 214 |
215 |
216 |
217 |
218 |
219 |

Requests History

220 |
221 |
222 |
223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | {{ range $index, $r := .Requests }} 234 | 235 | 236 | 254 | 255 | 256 | 257 | {{ range $item := $r.SubStats }} 258 | 259 | 260 | 261 | 262 | 263 | {{/**/}} 264 | 265 | {{ end }} 266 | 267 | {{ end }} 268 |
Request
237 | 238 | ({{$index}}) 239 | 240 | {{$r.RequestStats.Start}} 241 | "{{$r.RequestStats.Method}} 242 | {{$r.RequestStats.Path}}{{if $r.RequestStats.Query}}?{{$r.RequestStats.Query}}{{end}}" 243 | {{if $r.RequestStats.Status}}{{$r.RequestStats.Status}}{{end}} 244 | 245 | real={{$r.RequestStats.Duration}} 246 | {{/* 247 | overhead={{$r.overhead_walltime_milliseconds}}ms 248 | ({{$r.combined_rpc_count}} RPC{{$r.combined_rpc_count}}, 249 | billed_ops=[{{$r.combined_rpc_billed_ops}}]) 250 | */}} 251 | ({{$r.RequestStats.RPCStats | len}} RPCs, 252 | cost={{$r.RequestStats.Cost}}) 253 |
{{$item.Name}}{{$item.Count}}{{$item.Cost}}{{$item.total_billed_ops_str}}
269 |
270 | {{ else }} 271 |
272 | No requests have been recorded yet. While it is possible that you 273 | simply need to wait until your server receives some requests, this 274 | is often caused by a configuration problem. 275 | 276 | Learn more 278 |
279 | {{ end }} 280 | 281 | {{ template "end" . }} 282 | 283 | 288 | 289 | {{ template "footer" . }} 290 | {{ end }} 291 | ` 292 | 293 | const htmlDetails = ` 294 | {{ define "details" }} 295 | {{ template "top" . }} 296 | {{ template "body" . }} 297 | 298 | {{ if not .Record }} 299 |

Invalid or stale record key!

300 | {{ else }} 301 |
302 |
303 |
304 | {{.Record.Start}}
305 | 306 | {{.Record.Status}} 307 | 308 |
309 |
310 | 311 | {{.Record.Method}} {{.Record.Path}}{{if .Record.Query}}?{{.Record.Query}}{{end}} 312 | 313 |
314 | {{.Record.User}}{{ if .Record.Admin }}*{{ end }} 315 | real={{.Record.Duration}} 316 | cost={{.Record.Cost}} 317 | {{/* 318 | overhead={{.Record.overhead_walltime_milliseconds}}ms 319 |
320 | billed_ops={{.Record.combined_rpc_billed_ops}} 321 | */}} 322 |
323 |
324 |
325 | 326 |
327 |

Timeline

328 |
329 |
[Chart goes here]
330 |
331 | {{ if .Record.RPCStats }} 332 |
333 |
334 |
335 |

RPC Call Traces

336 |
337 |
338 |
339 | 340 | 341 | 342 | 343 | 344 | 345 | {{ range $index, $t := .Record.RPCStats }} 346 | 347 | 348 | 358 | 359 | 360 | 361 | {{ if $t.In }} 362 | 363 | 364 | 365 | {{ end }} 366 | {{ if $t.Out }} 367 | 368 | 369 | 370 | {{ end }} 371 | {{ if $t.StackData }} 372 | 373 | 374 | 375 | {{ range $stackindex, $f := $t.Stack }} 376 | 377 | 381 | 382 | {{/* 383 | {{ if $f.variables_size }} 384 | 385 | 387 | 388 | {{ end }}{{# f.variables_size #} 389 | */}} 390 | {{ end }}{{/* t.call_stack_list */}} 391 | {{ end }}{{/* t.call_stack_size */}} 392 | 393 | {{ end }}{{/* .Record.individual_stats_list */}} 394 |
RPC
349 | 350 | @{{$t.Offset}} 351 | {{$t.Name}} 352 | real={{$t.Duration}} 353 | cost={{$t.Cost}} 354 | {{/* 355 | billed_ops=[{{t.billed_ops_str}}] 356 | */}} 357 |
Request: {{$t.Request}}
Response: {{$t.Response}}
Stack:
378 |   379 | {{ $f.Location }}:{{ $f.Lineno }} {{ $f.Call }} 380 |
{{ for item in f.variables_list }}{{item.key}} = {{item.value}}
{{ end }} 386 |
395 |
396 | {{ end }}{{/* traces */}} 397 |
398 | 399 | {{ if .AllStatsByCount }} 400 |
401 |

RPC Stats

402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | {{ range $item := .AllStatsByCount }} 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | {{ end }} 421 | 422 |
service.call#RPCsreal timeCostBilled Ops
{{$item.Name}}{{$item.Count}}{{$item.Duration}}{{$item.Cost}}
423 |
424 | {{ end }}{{/* rpcstats_by_count */}} 425 | 426 | {{ if .Header }} 427 |
428 |

CGI Environment

429 | 430 | 431 | {{ range $key, $value := .Header }} 432 | 433 | 434 | 435 | 436 | {{ end }} 437 | 438 |
{{$key}}={{$value}}
439 |
440 | {{ end }}{{/* .Header */}} 441 | 442 | {{ end }} 443 | 444 | {{ template "end" . }} 445 | 446 | 461 | 465 | 493 | 494 | {{ template "footer" . }} 495 | {{ end }} 496 | ` 497 | const htmlFile = ` 498 | {{ define "file" }} 499 | {{ template "top" . }} 500 | {{ template "body" . }} 501 | 502 |

{{.Filename}}

503 | Go to line {{.Lineno}} | 504 | Go to bottom 505 | 506 |
507 | {{ range $index, $line := .Fp }}{{ rjust $index 4 }}: {{ $line }}
508 | {{ end }}
509 | 
510 | Back to top 511 | 512 | 513 | {{ template "end" . }} 514 | {{ template "footer" . }} 515 | {{ end }} 516 | ` 517 | -------------------------------------------------------------------------------- /static/appstats_js.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2008-10 Google Inc. All Rights Reserved. */ (function(){function e(a){throw a;} 2 | var h=void 0,k=!0,l=null,p=!1,r,s=this,aa=function(){},ba=function(a){a.M=function(){return a.Db?a.Db:a.Db=new a}},ca=function(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"== 3 | c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"==b&&"undefined"==typeof a.call)return"object";return b},da=function(a){return"array"==ca(a)},u=function(a){return"string"==typeof a},w=function(a){return"function"==ca(a)},ea=function(a){var b=typeof a;return"object"==b&&a!=l||"function"==b},x=function(a){return a[fa]||(a[fa]=++ga)},fa="closure_uid_"+Math.floor(2147483648*Math.random()).toString(36), 4 | ga=0,ha=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=Array.prototype.slice.call(arguments);b.unshift.apply(b,c);return a.apply(this,b)}},ia=function(a,b){var c=a.split("."),d=s;!(c[0]in d)&&d.execScript&&d.execScript("var "+c[0]);for(var g;c.length&&(g=c.shift());)!c.length&&b!==h?d[g]=b:d=d[g]?d[g]:d[g]={}},y=function(a,b){function c(){}c.prototype=b.prototype;a.e=b.prototype;a.prototype=new c;a.prototype.constructor=a};var ja=function(a){Error.captureStackTrace?Error.captureStackTrace(this,ja):this.stack=Error().stack||"";a&&(this.message=String(a))};y(ja,Error);ja.prototype.name="CustomError";var ka=function(a,b){for(var c=1;c")&&(a=a.replace(pa,">"));-1!=a.indexOf('"')&&(a=a.replace(qa,"""));return a},na=/&/g,oa=//g,qa=/\"/g,ma=/[&<>\"]/;var sa=function(a,b){b.unshift(a);ja.call(this,ka.apply(l,b));b.shift()};y(sa,ja);sa.prototype.name="AssertionError";var ta=function(a,b,c){var d="Assertion failed";if(b)var d=d+(": "+b),g=c;else a&&(d+=": "+a,g=l);e(new sa(""+d,g||[]))},z=function(a,b,c){a||ta("",b,Array.prototype.slice.call(arguments,2))},ua=function(a,b,c,d){a instanceof b||ta("instanceof check failed.",c,Array.prototype.slice.call(arguments,3))};var A=Array.prototype,va=A.indexOf?function(a,b,c){z(a.length!=l);return A.indexOf.call(a,b,c)}:function(a,b,c){c=c==l?0:0>c?Math.max(0,a.length+c):c;if(u(a))return!u(b)||1!=b.length?-1:a.indexOf(b,c);for(;c=arguments.length?A.slice.call(a,b):A.slice.call(a,b,c)};var Ea=function(a,b,c){b in a&&e(Error('The object already contains the key "'+b+'"'));a[b]=c},Fa=function(a){var b={},c;for(c in a)b[a[c]]=c;return b},Ga="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),Ha=function(a,b){for(var c,d,g=1;gparseFloat(Ua)){Ta=String(Ya);break a}}Ta=Ua} 8 | var Za=Ta,$a={},F=function(a){var b;if(!(b=$a[a])){b=0;for(var c=la(String(Za)).split("."),d=la(String(a)).split("."),g=Math.max(c.length,d.length),f=0;0==b&&f(0==v[1].length?0:parseInt(v[1],10))?1: 9 | 0)||((0==t[2].length)<(0==v[2].length)?-1:(0==t[2].length)>(0==v[2].length)?1:0)||(t[2]v[2]?1:0)}while(0==b)}b=$a[a]=0<=b}return b},ab=s.document,bb=!ab||!B?h:Sa()||("CSS1Compat"==ab.compatMode?parseInt(Za,10):5);var cb,db=!B||B&&9<=bb;!C&&!B||B&&B&&9<=bb||C&&F("1.9.1");var eb=B&&!F("9");var fb=function(a){a=a.className;return u(a)&&a.match(/\S+/g)||[]},G=function(a,b){for(var c=fb(a),d=Ca(arguments,1),g=c.length+d.length,f=c,j=0;j");c=c.join("")}var f=a.createElement(c);if(d)if(u(d))f.className=d;else if(da(d))G.apply(l,[f].concat(d));else{var c=function(a,b){"style"==b?f.style.cssText=a:"class"==b?f.className=a:"for"==b?f.htmlFor=a:b in mb?f.setAttribute(mb[b],a):0==b.lastIndexOf("aria-",0)||0==b.lastIndexOf("data-",0)?f.setAttribute(b,a):f[b]=a},j;for(j in d)c.call(h,d[j],j)}if(2a):p},vb=function(a,b,c){if(!(a.nodeName in sb))if(3==a.nodeType)c?b.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in tb)b.push(tb[a.nodeName]);else for(a=a.firstChild;a;)vb(a,b,c),a=a.nextSibling},hb=function(a){this.B=a||s.document||document};r=hb.prototype;r.Fa=jb;r.a=function(a){return u(a)?this.B.getElementById(a):a}; 15 | r.k=function(a,b,c){return nb(this.B,arguments)};r.createElement=function(a){return this.B.createElement(a)};r.createTextNode=function(a){return this.B.createTextNode(a)};r.appendChild=function(a,b){a.appendChild(b)};r.contains=qb;var wb=function(a){wb[" "](a);return a};wb[" "]=aa;var xb=!B||B&&9<=bb,yb=!B||B&&9<=bb,zb=B&&!F("9");!D||F("528");C&&F("1.9b")||B&&F("8")||Qa&&F("9.5")||D&&F("528");C&&!F("8")||B&&F("9");var Ab=function(){};Ab.prototype.Rb=p;var H=function(a,b){this.type=a;this.currentTarget=this.target=b};r=H.prototype;r.Z=p;r.defaultPrevented=p;r.Ia=k;r.stopPropagation=function(){this.Z=k};r.preventDefault=function(){this.defaultPrevented=k;this.Ia=p};var I=function(a,b){a&&this.sa(a,b)};y(I,H);var Bb=[1,4,2];r=I.prototype;r.target=l;r.relatedTarget=l;r.offsetX=0;r.offsetY=0;r.clientX=0;r.clientY=0;r.screenX=0;r.screenY=0;r.button=0;r.keyCode=0;r.charCode=0;r.ctrlKey=p;r.altKey=p;r.shiftKey=p;r.metaKey=p;r.Ya=p;r.P=l; 16 | r.sa=function(a,b){var c=this.type=a.type;H.call(this,c);this.target=a.target||a.srcElement;this.currentTarget=b;var d=a.relatedTarget;if(d){if(C){var g;a:{try{wb(d.nodeName);g=k;break a}catch(f){}g=p}g||(d=l)}}else"mouseover"==c?d=a.fromElement:"mouseout"==c&&(d=a.toElement);this.relatedTarget=d;this.offsetX=D||a.offsetX!==h?a.offsetX:a.layerX;this.offsetY=D||a.offsetY!==h?a.offsetY:a.layerY;this.clientX=a.clientX!==h?a.clientX:a.pageX;this.clientY=a.clientY!==h?a.clientY:a.pageY;this.screenX=a.screenX|| 17 | 0;this.screenY=a.screenY||0;this.button=a.button;this.keyCode=a.keyCode||0;this.charCode=a.charCode||("keypress"==c?a.keyCode:0);this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey=a.metaKey;this.Ya=E?a.metaKey:a.ctrlKey;this.state=a.state;this.P=a;a.defaultPrevented&&this.preventDefault();delete this.Z};var Cb=function(a){return xb?0==a.P.button:"click"==a.type?k:!!(a.P.button&Bb[0])}; 18 | I.prototype.stopPropagation=function(){I.e.stopPropagation.call(this);this.P.stopPropagation?this.P.stopPropagation():this.P.cancelBubble=k};I.prototype.preventDefault=function(){I.e.preventDefault.call(this);var a=this.P;if(a.preventDefault)a.preventDefault();else if(a.returnValue=p,zb)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};var Db=function(){},Eb=0;r=Db.prototype;r.key=0;r.Y=p;r.Cb=p;r.sa=function(a,b,c,d,g,f){w(a)?this.Ab=k:a&&a.handleEvent&&w(a.handleEvent)?this.Ab=p:e(Error("Invalid listener argument"));this.ea=a;this.tb=b;this.src=c;this.type=d;this.capture=!!g;this.Aa=f;this.Cb=p;this.key=++Eb;this.Y=p};r.handleEvent=function(a){return this.Ab?this.ea.call(this.Aa||this.src,a):this.ea.handleEvent.call(this.ea,a)};var Fb={},J={},Gb={},Hb={},K=function(a,b,c,d,g){if(b){if(da(b)){for(var f=0;ff.keyCode||f.returnValue!=h)return k;a:{var q=p;if(0==f.keyCode)try{f.keyCode=-1;break a}catch(t){q=k}if(q||f.returnValue==h)f.returnValue=k}}q=new I;q.sa(f,this);f=k;try{if(m){for(var v=[],Pa=q.currentTarget;Pa;Pa=Pa.parentNode)v.push(Pa);j=g[k];j.v=j.C;for(var S=v.length-1;!q.Z&&0<=S&&j.v;S--)q.currentTarget=v[S],f&=Nb(j,v[S],d,k,q);if(n){j=g[p];j.v=j.C;for(S=0;!q.Z&&Sb||b>bc(this))&&e(Error("Child component index out of bounds"));if(!this.h||!this.r)this.h={},this.r=[];if(a.getParent()==this){var d=Xb(a);this.h[d]=a;Aa(this.r,a)}else Ea(this.h,Xb(a),a);$b(a,this);Da(this.r,b,0,a);a.d&&this.d&&a.getParent()==this?(c=this.z(),c.insertBefore(a.a(),c.childNodes[b]||l)):c?(this.c||this.k(),c=P(this,b+1),b=this.z(),c=c?c.c:l,a.d&&e(Error("Component already rendered")),a.c||a.k(),b?b.insertBefore(a.c, 30 | c||l):a.m.B.body.appendChild(a.c),(!a.n||a.n.d)&&a.t()):this.d&&(!a.d&&a.c&&a.c.parentNode&&1==a.c.parentNode.nodeType)&&a.t()};r.z=function(){return this.c}; 31 | var cc=function(a){if(a.pa==l){var b;a:{b=a.d?a.c:a.m.B.body;var c=ib(b);if(c.defaultView&&c.defaultView.getComputedStyle&&(b=c.defaultView.getComputedStyle(b,l))){b=b.direction||b.getPropertyValue("direction")||"";break a}b=""}a.pa="rtl"==(b||((a.d?a.c:a.m.B.body).currentStyle?(a.d?a.c:a.m.B.body).currentStyle.direction:l)||(a.d?a.c:a.m.B.body).style&&(a.d?a.c:a.m.B.body).style.direction)}return a.pa};O.prototype.na=function(a){this.d&&e(Error("Component already rendered"));this.pa=a}; 32 | var bc=function(a){return a.r?a.r.length:0},P=function(a,b){return a.r?a.r[b]||l:l},ac=function(a,b,c){a.r&&wa(a.r,b,c)},dc=function(a,b){return a.r&&b?va(a.r,b):-1};O.prototype.removeChild=function(a,b){if(a){var c=u(a)?a:Xb(a);a=this.h&&c?(c in this.h?this.h[c]:h)||l:l;if(c&&a){var d=this.h;c in d&&delete d[c];Aa(this.r,a);b&&(a.$(),a.c&&pb(a.c));$b(a,l)}}a||e(Error("Child is not in parent component"));return a};var fc=function(a,b,c,d,g){if(!B&&(!D||!F("525")))return k;if(E&&g)return ec(a);if(g&&!d||!c&&(17==b||18==b||E&&91==b))return p;if(D&&d&&c)switch(a){case 220:case 219:case 221:case 192:case 186:case 189:case 187:case 188:case 190:case 191:case 192:case 222:return p}if(B&&d&&b==a)return p;switch(a){case 13:return!(B&&B&&9<=bb);case 27:return!D}return ec(a)},ec=function(a){if(48<=a&&57>=a||96<=a&&106>=a||65<=a&&90>=a||D&&0==a)return k;switch(a){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return k; 33 | default:return p}},gc=function(a){switch(a){case 61:return 187;case 59:return 186;case 224:return 91;case 0:return 224;default:return a}};var Q=function(a,b){a&&hc(this,a,b)};y(Q,Qb);r=Q.prototype;r.c=l;r.Ba=l;r.Sa=l;r.Ca=l;r.s=-1;r.N=-1;r.gb=p; 34 | var ic={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},jc={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},kc=B||D&&F("525"),lc=E&&C; 35 | Q.prototype.Pb=function(a){if(D&&(17==this.s&&!a.ctrlKey||18==this.s&&!a.altKey||E&&91==this.s&&!a.metaKey))this.N=this.s=-1;-1==this.s&&(a.ctrlKey&&17!=a.keyCode?this.s=17:a.altKey&&18!=a.keyCode?this.s=18:a.metaKey&&91!=a.keyCode&&(this.s=91));kc&&!fc(a.keyCode,this.s,a.shiftKey,a.ctrlKey,a.altKey)?this.handleEvent(a):(this.N=C?gc(a.keyCode):a.keyCode,lc&&(this.gb=a.altKey))};Q.prototype.Qb=function(a){this.N=this.s=-1;this.gb=a.altKey}; 36 | Q.prototype.handleEvent=function(a){var b=a.P,c,d,g=b.altKey;B&&"keypress"==a.type?(c=this.N,d=13!=c&&27!=c?b.keyCode:0):D&&"keypress"==a.type?(c=this.N,d=0<=b.charCode&&63232>b.charCode&&ec(c)?b.charCode:0):Qa?(c=this.N,d=ec(c)?b.keyCode:0):(c=b.keyCode||this.N,d=b.charCode||0,lc&&(g=this.gb),E&&(63==d&&224==c)&&(c=191));var f=c,j=b.keyIdentifier;c?63232<=c&&c in ic?f=ic[c]:25==c&&a.shiftKey&&(f=9):j&&j in jc&&(f=jc[j]);a=f==this.s;this.s=f;b=new mc(f,d,a,b);b.altKey=g;this.dispatchEvent(b)}; 37 | Q.prototype.a=function(){return this.c};var hc=function(a,b,c){a.Ca&&a.detach();a.c=b;a.Ba=K(a.c,"keypress",a,c);a.Sa=K(a.c,"keydown",a.Pb,c,a);a.Ca=K(a.c,"keyup",a.Qb,c,a)};Q.prototype.detach=function(){this.Ba&&(L(this.Ba),L(this.Sa),L(this.Ca),this.Ca=this.Sa=this.Ba=l);this.c=l;this.N=this.s=-1};var mc=function(a,b,c,d){d&&this.sa(d,h);this.type="key";this.keyCode=a;this.charCode=b;this.repeat=c};y(mc,I);var oc=function(a,b){a||e(Error("Invalid class name "+a));w(b)||e(Error("Invalid decorator function "+b));nc[a]=b},pc={},nc={};var R=function(){},qc;ba(R);r=R.prototype;r.I=function(){};r.k=function(a){var b=a.Fa().k("div",this.ra(a).join(" "),a.wa);rc(a,b);return b};r.z=function(a){return a};r.oa=function(a,b,c){if(a=a.a?a.a():a)if(B&&!F("7")){var d=sc(fb(a),b);d.push(b);ha(c?G:gb,a).apply(l,d)}else c?G(a,b):gb(a,b)};r.X=function(){return k}; 38 | r.H=function(a,b){b.id&&Yb(a,b.id);var c=this.z(b);c&&c.firstChild?(c=c.firstChild.nextSibling?Ba(c.childNodes):c.firstChild,a.wa=c):a.wa=l;var d=0,g=this.o(),f=this.o(),j=p,m=p,c=p,n=fb(b);wa(n,function(a){if(!j&&a==g)j=k,f==g&&(m=k);else if(!m&&a==f)m=k;else{var b=d;this.nb||(this.Da||tc(this),this.nb=Fa(this.Da));a=parseInt(this.nb[a],10);d=b|(isNaN(a)?0:a)}},this);a.f=d;j||(n.push(g),f==g&&(m=k));m||n.push(f);var q=a.F;q&&n.push.apply(n,q);if(B&&!F("7")){var t=sc(n);0a?b-1:a},0);break;case 38:if("vertical"==this.K)Kc(this);else return p;break;case 37:if("horizontal"==this.K)cc(this)?Lc(this): 60 | Kc(this);else return p;break;case 40:if("vertical"==this.K)Lc(this);else return p;break;case 39:if("horizontal"==this.K)cc(this)?Kc(this):Lc(this);else return p;break;default:return p}return k};var Gc=function(a,b){var c=b.a(),c=c.id||(c.id=Xb(b));a.L||(a.L={});a.L[c]=b};W.prototype.za=function(a,b){ua(a,T,"The child of a container must be a control");W.e.za.call(this,a,b)}; 61 | W.prototype.Oa=function(a,b,c){a.S|=2;a.S|=64;(this.R()||!this.Ob)&&zc(a,32,p);a.Ka(p);W.e.Oa.call(this,a,b,c);a.d&&this.d&&Gc(this,a);b<=this.i&&this.i++};W.prototype.removeChild=function(a,b){if(a=u(a)?this.h&&a?(a in this.h?this.h[a]:h)||l:l:a){var c=dc(this,a);-1!=c&&(c==this.i?a.A(p):ca?c-1:a},a.i)},Jc=function(a,b,c){c=0>c?dc(a,a.g):c;var d=bc(a);c=b.call(a,c,d);for(var g=0;g<=d;){var f=P(a,c);if(f&&f.D()&&f.isEnabled()&&f.p&2){a.Qa(c);break}g++;c=b.call(a,c,d)}};W.prototype.Qa=function(a){Ic(this,a)};var Mc=function(){};y(Mc,R);ba(Mc);r=Mc.prototype;r.o=function(){return"goog-tab"};r.I=function(){return"tab"};r.k=function(a){var b=Mc.e.k.call(this,a);(a=a.Na())&&this.Pa(b,a);return b};r.H=function(a,b){b=Mc.e.H.call(this,a,b);var c=this.Na(b);c&&(a.mb=c);if(a.f&8&&(c=a.getParent())&&w(c.V))a.u(8,p),c.V(a);return b};r.Na=function(a){return a.title||""};r.Pa=function(a,b){a&&(a.title=b||"")};var Nc=function(a,b,c){T.call(this,a,b||Mc.M(),c);zc(this,8,k);this.S|=9};y(Nc,T);Nc.prototype.Na=function(){return this.mb};Nc.prototype.Pa=function(a){this.vb().Pa(this.a(),a);this.mb=a};oc("goog-tab",function(){return new Nc(l)});var X=function(){};y(X,Ec);ba(X);X.prototype.o=function(){return"goog-tab-bar"};X.prototype.I=function(){return"tablist"};X.prototype.Ma=function(a,b,c){this.wb||(this.Ea||Oc(this),this.wb=Fa(this.Ea));var d=this.wb[b];d?(Fc(a,Pc(d)),a.pb=d):X.e.Ma.call(this,a,b,c)};X.prototype.ra=function(a){var b=X.e.ra.call(this,a);this.Ea||Oc(this);b.push(this.Ea[a.pb]);return b};var Oc=function(a){var b=a.o();a.Ea={top:b+"-top",bottom:b+"-bottom",start:b+"-start",end:b+"-end"}};var Y=function(a,b,c){a=a||"top";Fc(this,Pc(a));this.pb=a;W.call(this,this.K,b||X.M(),c);Qc(this)};y(Y,W);r=Y.prototype;r.Ub=k;r.G=l;r.t=function(){Y.e.t.call(this);Qc(this)};r.removeChild=function(a,b){Rc(this,a);return Y.e.removeChild.call(this,a,b)};r.Qa=function(a){Y.e.Qa.call(this,a);this.Ub&&this.V(P(this,a))};r.V=function(a){a?xc(a,k):this.G&&xc(this.G,p)}; 65 | var Rc=function(a,b){if(b&&b==a.G){for(var c=dc(a,b),d=c-1;b=P(a,d);d--)if(b.D()&&b.isEnabled()){a.V(b);return}for(c+=1;b=P(a,c);c++)if(b.D()&&b.isEnabled()){a.V(b);return}a.V(l)}};r=Y.prototype;r.ac=function(a){this.G&&this.G!=a.target&&xc(this.G,p);this.G=a.target};r.bc=function(a){a.target==this.G&&(this.G=l)};r.Zb=function(a){Rc(this,a.target)};r.$b=function(a){Rc(this,a.target)};r.ma=function(){P(this,this.i)||this.A(this.G||P(this,0))}; 66 | var Qc=function(a){M(M(M(M(Zb(a),a,"select",a.ac),a,"unselect",a.bc),a,"disable",a.Zb),a,"hide",a.$b)},Pc=function(a){return"start"==a||"end"==a?"vertical":"horizontal"};oc("goog-tab-bar",function(){return new Y});var Z=function(a,b,c,d,g){function f(a){a&&(a.tabIndex=0,a.setAttribute("role",j.I()),G(a,"goog-zippy-header"),Sc(j,a),a&&M(j.Lb,a,"keydown",j.Mb))}this.m=g||jb();this.T=this.m.a(a)||l;this.xa=this.m.a(d||l);this.ba=(this.La=w(b)?b:l)||!b?l:this.m.a(b);this.l=c==k;this.Lb=new Ob(this);this.kb=new Ob(this);var j=this;f(this.T);f(this.xa);this.W(this.l)};y(Z,Qb);r=Z.prototype;r.fa=k;r.ic=k;r.I=function(){return"tab"};r.z=function(){return this.ba};r.toggle=function(){this.W(!this.l)}; 67 | r.W=function(a){this.ba?Rb(this.ba,a):a&&this.La&&(this.ba=this.La());this.ba&&G(this.ba,"goog-zippy-content");if(this.xa)Rb(this.T,!a),Rb(this.xa,a);else if(this.T){var b=this.T;a?G(b,"goog-zippy-expanded"):gb(b,"goog-zippy-expanded");b=this.T;!a?G(b,"goog-zippy-collapsed"):gb(b,"goog-zippy-collapsed");this.T.setAttribute("aria-expanded",a)}this.l=a;this.dispatchEvent(new Tc("toggle",this))};r.lb=function(){return this.ic}; 68 | r.Ka=function(a){this.fa!=a&&((this.fa=a)?(Sc(this,this.T),Sc(this,this.xa)):(a=this.kb,wa(a.da,L),a.da.length=0))};var Sc=function(a,b){b&&M(a.kb,b,"click",a.dc)};Z.prototype.Mb=function(a){if(13==a.keyCode||32==a.keyCode)this.toggle(),this.dispatchEvent(new H("action",this)),a.preventDefault(),a.stopPropagation()};Z.prototype.dc=function(){this.toggle();this.dispatchEvent(new H("action",this))};var Tc=function(a,b){H.call(this,a,b)};y(Tc,H);var Vc=function(a,b){this.ib=[];for(var c=kb(a),c=lb("span","ae-zippy",c),d=0,g;g=c[d];d++){var f;if(g.parentNode.parentNode.parentNode.nextElementSibling!=h)f=g.parentNode.parentNode.parentNode.nextElementSibling;else for(f=g.parentNode.parentNode.parentNode.nextSibling;f&&1!=f.nodeType;)f=f.nextSibling;g=new Z(g,f,p);this.ib.push(g)}this.cc=new Uc(this.ib,kb(b))};Vc.prototype.gc=function(){return this.cc};Vc.prototype.hc=function(){return this.ib}; 69 | var Uc=function(a,b){this.ta=a;if(this.ta.length)for(var c=0,d;d=this.ta[c];c++)K(d,"toggle",this.Tb,p,this);this.Ha=0;this.l=p;c="ae-toggle ae-plus ae-action";this.ta.length||(c+=" ae-disabled");this.Q=ob("span",{className:c},"Expand All");K(this.Q,"click",this.Sb,p,this);b&&b.appendChild(this.Q)};Uc.prototype.Sb=function(){this.ta.length&&this.W(!this.l)}; 70 | Uc.prototype.Tb=function(a){a=a.currentTarget;this.Ha=a.l?this.Ha+1:this.Ha-1;a.l!=this.l&&(a.l?(this.l=k,Wc(this,k)):0==this.Ha&&(this.l=p,Wc(this,p)))};Uc.prototype.W=function(a){this.l=a;a=0;for(var b;b=this.ta[a];a++)b.l!=this.l&&b.W(this.l);Wc(this)}; 71 | var Wc=function(a,b){(b!==h?b:a.l)?(gb(a.Q,"ae-plus"),G(a.Q,"ae-minus"),rb(a.Q,"Collapse All")):(gb(a.Q,"ae-minus"),G(a.Q,"ae-plus"),rb(a.Q,"Expand All"))},Xc=function(a){this.Vb=a;this.Bb={};var b,c=ob("div",{},b=ob("div",{id:"ae-stats-details-tabs",className:"goog-tab-bar goog-tab-bar-top"}),ob("div",{className:"goog-tab-bar-clear"}),a=ob("div",{id:"ae-stats-details-tabs-content",className:"goog-tab-content"})),d=new Y;d.H(b);K(d,"select",this.yb,p,this);K(d,"unselect",this.yb,p,this);b=0;for(var g;g= 72 | this.Vb[b];b++)if(g=kb("ae-stats-details-"+g)){var f=lb("h2",l,g)[0],j;j=f;var m=h;eb&&"innerText"in j?m=j.innerText.replace(/(\r\n|\r|\n)/g,"\n"):(m=[],vb(j,m,k),m=m.join(""));m=m.replace(/ \xAD /g," ").replace(/\xAD/g,"");m=m.replace(/\u200B/g,"");eb||(m=m.replace(/ +/g," "));" "!=m&&(m=m.replace(/^\s*/,""));j=m;pb(f);f=new Nc(j);this.Bb[x(f)]=g;d.za(f,k);a.appendChild(g);0==b?d.V(f):Rb(g,p)}kb("bd").appendChild(c)};Xc.prototype.yb=function(a){var b=this.Bb[x(a.target)];Rb(b,"select"==a.type)}; 73 | ia("ae.Stats.Details.Tabs",Xc);ia("goog.ui.Zippy",Z);Z.prototype.setExpanded=Z.prototype.W;ia("ae.Stats.MakeZippys",Vc);Vc.prototype.getExpandCollapse=Vc.prototype.gc;Vc.prototype.getZippys=Vc.prototype.hc;Uc.prototype.setExpanded=Uc.prototype.W;var $=function(){this.Za=[];this.cb=[]},Yc=[[5,0.2,1],[6,0.2,1.2],[5,0.25,1.25],[6,0.25,1.5],[4,0.5,2],[5,0.5,2.5],[6,0.5,3],[4,1,4],[5,1,5],[6,1,6],[4,2,8],[5,2,10]],Zc=function(a){if(0>=a)return[2,0.5,1];for(var b=1;1>a;)a*=10,b/=10;for(;10<=a;)a/=10,b*=10;for(var c=0;c');a.write('
');for(var g=0;g<=b;g++)a.write(''),a.write(''),a.write(" "+g*c+"");a.write("
\n")}; 75 | $.prototype.fc=function(){this.cb=[];var a=Zc(this.ab),b=a[0],c=a[1],a=100/a[2];this.write('\n');$c(this,b,c,a);for(var d=0;d\n\n")}$c(this,b,c,a);this.write("
');0'),this.write(g.label),0"));this.write("");this.write('
');0');this.write('');0'));0 '),this.write(g.ub),this.write(""));0");this.write("
\n");return this.cb.join("")};$.prototype.ec=function(a,b,c,d,g,f){this.ab=Math.max(this.ab,Math.max(b+c,b+d));this.Za.push({label:a,start:b,duration:c,$a:d,ub:g,ga:f})};ia("Gantt",$);$.prototype.add_bar=$.prototype.ec;$.prototype.draw=$.prototype.fc;})(); 78 | -------------------------------------------------------------------------------- /static.go: -------------------------------------------------------------------------------- 1 | package appstats 2 | 3 | var app_engine_logo_sm_gif = []byte("GIF89a\xb0\x00\x1e\x00\xf7\x00\x00z'!\x04d\x04{j3\x142w>Ow\x9c\x06\x01\xbc\v\x03\x98#\x15\x982%\xb1, \xb8?3\xd4'\x11\x94v\x0e\xdfE/\x9dLG\x86_Z\xbbYN\xa4d^\x95mh\xaauo\xd3XF\xd3bU\xebkW\xc7uk\xe4zk'\xae.>\xbbBM\x9bQC\xbdIg\x80gu\xa8wO\xc5V\xad\x86\x03\xbc\x92\x05\xa2\x8a8ר\x01\u07b6\vˬ7\u07fe2\xfa\xd61\x83\x85~\xac\x9ce\xb5\xa6zܿMւx\xf8\x88wDZg\xeb\xcfL\xff\xe2Q\xf0\xd7s\xfd\xear\t3\x92\x18?\x94\x056\xa6\x03)\xb9\t.\xbb\x06<\xb6\x135\xbd\x158\xbe\x1a<\xbf+J\x8f\x18G\xae(S\xb2D]\x92Zi\x88ry\x8bMm\xb3a{\xb7\x1b=\xc0!?\xc2\x1fA\xc1\x12J\xc5%E\xc3(F\xc2+K\xc51O\xc6+\\\xc33R\xc77T\xc89V\xc9z鏜\xf1\xd0\x19nXr\xc4\fz\x80dH\x03\x00\xa4\xf2\x108\x16\x18\x00\x0062\x8c \x00,\x0fIcB\b\",\x13G\rap\x06\f\u05eb\xe4\x8d\x1c9\xb2\xf4\x8d\a\v\f\xa6\xe2J/ڈ-\xd06n,ѣ\x12R\xec\xf0\xb8\xe5\x03u\x114\xe46\xb7\x1f\xb4C\xc3\xec\x90\xc5\x19f\x98qF\x169\xc4\x04\xbfC\xc2(\x1d&\xa6 \xb6\xa55\r\\\x95\x8b_9\x02h55$\x01\u007f\xf9;C\x12\xbc\x80\x8a#\xd4\x00\b\"\x1b\xc88\x80\xc1\x89N\x00\xa3\x13\x96\xc0\x06!\x16\x00\x80\xb59\xc4^\x00p\x05\fF\xc0\x80V`\xe6\x04!8\x1e\x1cp\xf0\x03\xbe\b\x04\x184 @\xf5\xa0a\xb1rd\xef\x00\xfe~\xa0E+`\x81\x8c\f\xde\xec\fN\x18\x055\x8c\xa1\x8b6\xa8/\x81\x0f\x01C\xaf\xfe\xe7\x10)>D\x18;\xa0]\x1b\xb6؆G\xa4\x8c}\v,\x9d1\x9c\xd4\x1ec@\xa1W\x02i\x1d\x15\x05\xc2\xc0r\xa4\xa1\tn\xe0b\x1b\xdc0\nl0\xc1)Py\xc81 1\x862\xc0\xe1\b5\x1ca\x01\xfc\x00\x91@\b\xaf\x171 \x01\x03L(\x90p\xc0\x00\x04\x02`\x86(jP\x03H\f\x04\r<\xb0\x03.\xac7\x87\xb1\x99\xea\x0e\xdc\xf8F\xd2 b\f$\xec\xc2=IX\x9f\x02ǁ\x85G@Q\x83\xad|\xc8\x18\xbd\xe4\x90j0\v\x8cm,\x87'n\xf0\x84/pA\t[\bR\x1a\x11\xb8\xc60Z\xad\r\xb2s\x888\xb6Q\x8e74E\b\bڎ3^\xc1\n-\xcc\x00\b\xcaX\x84\xa9& \xbe\x16\x1c\xe0\x01\xc7`D\xc2T\xf0B\x06\xa0\x80\x19\xe1\x18\xc3\ff\xf0\x06`\xbcA\byP\x85`x\xe8\xa9g4\xc0\x00\b\xfeh\bG\x86\xb1:\x81\xf0\xa2t\x90;e\x9c\x94P\x97\x80:\xe4\x1a\x04\xcd\xd1\x14L6\x10^<\xa1h\xc5l\xa37\xb6\xd0\bOħ\x14IS#.K\xf7\t(\xe8G `\b\xda2\ue2031\x8cr\x1c\xe1\xe0\xc6\xf3\x80\xa0\x13\n\x18\xe0\x00\xac\x19H8xg\anH\xa3\x04\xcc;\xdc@\xa6\x91\xb6=\xf0\xe5\x19}\b\x82\x18\xe0Ѓ=\xacbB\xd08\x0e\x1d\x04ÂB]`O\x10\xe1\x86\x13t\xc6,+\xe4\xc0\x96\xb5\x9a\x02\xe7\xc6\xd1\x06(03\xab[\xed*3\x1f\x12\x89&В\x1c\\\xa8\xd6+\xd9(F$8b\x18\xe2\x10\x1f눹\xd1\xc7-\xc1\x95\x021\x05\x12L!\x10V\xf8\xa0)d8\x06D\xc8\x01\x87k\x1en\x15\t(\x00\x04\x9e\xd1H\f\x1c\x00\x05\xaa\xd8\xc9*B\x00\x02\x17\xa8e\x1a/`@\x1d\\(\x10p$\x83\x14\x11\x13\x03g\xa0\x91\x01O\x95C\x1b\n(T\x05\x16\xf1\x8c\\,\x826\x10\x19\xfe\x05\x12\xaa\xb0\x060,\xa1\n;\xf0\x1f\x12\x8c\xf4\x055LA\t\xa7\xb8%o}\v܈l\xe3\x97gP\x03\x14\x9e`3j\x14At\x03\tF\x11\xacV\x8e3\xd8\xc0\bH0\x93\x19B\xa1\x1f5x\x81Y\xcf\x1d\xddt\x05\"[\xda~\xe1V\x0eI\xc5\x0ffP\x03\x1f\xbc\x01\x15\xd8\xf0\x067\x8e\xb1\x89\x1e\f\x00\b\xc9\x10\x88\"\x1cP\x00\x05\xb0 \x10\x15\x80@\x1e$\xa1\xd3r(B\x04 (\xc1mV\x90\x82\x01#\x88\x1cLj\x02\x0ezP\x86\xf6\x1c\"\x00\x18\xf6\x00_`\xc1\xdf\x02\x18 \x01\n\xb8\xc0+F)\x90_<\xc2\fn0\xc55.\x01\xd1$\bC\x17\x8d\x00\x83\xb1\xae\xe6b\x18˘s\x11\x11G(\xd8p\x86KP\x17?\x99\x10\xcf~2q\x1fOX!\x14\xc3\xd0\xc5(0q\x06$\xa8\x01;\xa6\x01\xb2\x90ˑ\r\"w\xee\xc4)\x16\x1f2\xea\xf0\x03\x1a̠\a>\x10\x02J\x86\x10\x86:\xf4A\xa7\xe4\x80\x05\xfe\x1e$\x10\x81;\x14\"\x15\xad\xb0\xa1\xa4Za\x87\x14\xa4@\x10\x8a\x80\xb3t\xdcI\x062\b\x81\a8\xa8\x01\x19\x04\xe3\f@\xe4a\x0f\x80\xc8o9z\x81\x87\a8\x1a\x0f\xa9 \x06\x89)RL\x87T\x1a+\xd9X\x02_\x1fⲢe%\x1c\xb8HE\x1f\xf8\xc0\x87?L\xa2\x15\xb4\xa0\x051\x9c\x914n\x10\xa3\x17\xbd F2&\xad\rX|\xaf\x17̸\x8c78\xc1\x841\x00\"\x15\xaa\xf8\x83\x10(i\xc9\xd32\x83\x19ɐ\x0e7`ъ!\"cҔF\x02t!\x02\xb9ik%\x1bN\x8a\xab\xa4\xb8\xe1\x06*|t-j\xc9\fG\xa0\x1a\x91\xa4\x91\xa3\x13>\b\x82*h\x11\x9dp\xbcb\b5\xf8A4\xb12$\x1c?\xa4ރ\x01E\x12\x9c\xb0\x050\x80\xe1\nf\xe2\x05i䂍8\xd0 \x0f\x8a\x1eH/\x8e@\x80\x98\xc6\xe5:\x13\x81\xf8`\x8c\x11\x8aG\xb8\xc1\r\x910ŷ\a\x9e\x95\bϠ\x0f\x10\xf1\x86\x0fh\x80\xe0p\x8e\x9b\xfc\xe4\x89\xc1FĄ\x00:\x82X\x82\a{H8\xcagNs\xacL\x82\x065`\x82%\x80#\n4\b\xa1\x0f\xf2\xac\xb9ЇޓpH\x02\b<\xf0\x01\x13P\xa2\aU\xacb\xdeD\x8f\xba\xd4)B\vI\xfc\xe1\x0f\x808\x8a\xa4\xa7\xce\xf5\xaeO$\x1c\xdfІ6D\xe9\xf5\xb2\x9b\xfd\xechO\xbb\xda\xd7\xfe\x90\x80\x00\x00;") 4 | 5 | var appstats_css_css = []byte("/* Copyright 2012 Google Inc. All Rights Reserved. */\nhtml,body,div,h1,h2,h3,h4,h5,h6,p,img,dl,dt,dd,ol,ul,li,table,caption,tbody,tfoot,thead,tr,th,td,form,fieldset,embed,object,applet{margin:0;padding:0;border:0;}body{font-size:62.5%;font-family:Arial,sans-serif;color:#000;background:#fff}a{color:#00c}a:active{color:#f00}a:visited{color:#551a8b}table{border-collapse:collapse;border-width:0;empty-cells:show}ul{padding:0 0 1em 1em}ol{padding:0 0 1em 1.3em}li{line-height:1.5em;padding:0 0 .5em 0}p{padding:0 0 1em 0}h1,h2,h3,h4,h5{padding:0 0 1em 0}h1,h2{font-size:1.3em}h3{font-size:1.1em}h4,h5,table{font-size:1em}sup,sub{font-size:.7em}input,select,textarea,option{font-family:inherit;font-size:inherit}.g-doc,.g-doc-1024,.g-doc-800{font-size:130%}.g-doc{width:100%;text-align:left}.g-section{width:100%;vertical-align:top;display:inline-block}*:first-child+html .g-section{display:block}* html .g-section{overflow:hidden}@-moz-document url-prefix(''){.g-section{overflow:hidden}}@-moz-document url-prefix(''){.g-section,tt:default{overflow:visible}}.g-section,.g-unit{zoom:1}.g-split .g-unit{text-align:right}.g-split .g-first{text-align:left}.g-doc-1024{width:73.074em;min-width:950px;margin:0 auto;text-align:left}* html .g-doc-1024{width:71.313em}*+html .g-doc-1024{width:71.313em}.g-doc-800{width:57.69em;min-width:750px;margin:0 auto;text-align:left}* html .g-doc-800{width:56.3em}*+html .g-doc-800{width:56.3em}.g-tpl-160 .g-unit,.g-unit .g-tpl-160 .g-unit,.g-unit .g-unit .g-tpl-160 .g-unit,.g-unit .g-unit .g-unit .g-tpl-160 .g-unit{margin:0 0 0 160px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-160 .g-first,.g-unit .g-unit .g-tpl-160 .g-first,.g-unit .g-tpl-160 .g-first,.g-tpl-160 .g-first{margin:0;width:160px;float:left}.g-tpl-160-alt .g-unit,.g-unit .g-tpl-160-alt .g-unit,.g-unit .g-unit .g-tpl-160-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-160-alt .g-unit{margin:0 160px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-160-alt .g-first,.g-unit .g-unit .g-tpl-160-alt .g-first,.g-unit .g-tpl-160-alt .g-first,.g-tpl-160-alt .g-first{margin:0;width:160px;float:right}.g-tpl-180 .g-unit,.g-unit .g-tpl-180 .g-unit,.g-unit .g-unit .g-tpl-180 .g-unit,.g-unit .g-unit .g-unit .g-tpl-180 .g-unit{margin:0 0 0 180px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-180 .g-first,.g-unit .g-unit .g-tpl-180 .g-first,.g-unit .g-tpl-180 .g-first,.g-tpl-180 .g-first{margin:0;width:180px;float:left}.g-tpl-180-alt .g-unit,.g-unit .g-tpl-180-alt .g-unit,.g-unit .g-unit .g-tpl-180-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-180-alt .g-unit{margin:0 180px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-180-alt .g-first,.g-unit .g-unit .g-tpl-180-alt .g-first,.g-unit .g-tpl-180-alt .g-first,.g-tpl-180-alt .g-first{margin:0;width:180px;float:right}.g-tpl-300 .g-unit,.g-unit .g-tpl-300 .g-unit,.g-unit .g-unit .g-tpl-300 .g-unit,.g-unit .g-unit .g-unit .g-tpl-300 .g-unit{margin:0 0 0 300px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-300 .g-first,.g-unit .g-unit .g-tpl-300 .g-first,.g-unit .g-tpl-300 .g-first,.g-tpl-300 .g-first{margin:0;width:300px;float:left}.g-tpl-300-alt .g-unit,.g-unit .g-tpl-300-alt .g-unit,.g-unit .g-unit .g-tpl-300-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-300-alt .g-unit{margin:0 300px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-300-alt .g-first,.g-unit .g-unit .g-tpl-300-alt .g-first,.g-unit .g-tpl-300-alt .g-first,.g-tpl-300-alt .g-first{margin:0;width:300px;float:right}.g-tpl-25-75 .g-unit,.g-unit .g-tpl-25-75 .g-unit,.g-unit .g-unit .g-tpl-25-75 .g-unit,.g-unit .g-unit .g-unit .g-tpl-25-75 .g-unit{width:74.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-25-75 .g-first,.g-unit .g-unit .g-tpl-25-75 .g-first,.g-unit .g-tpl-25-75 .g-first,.g-tpl-25-75 .g-first{width:24.999%;float:left;margin:0}.g-tpl-25-75-alt .g-unit,.g-unit .g-tpl-25-75-alt .g-unit,.g-unit .g-unit .g-tpl-25-75-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-25-75-alt .g-unit{width:24.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-25-75-alt .g-first,.g-unit .g-unit .g-tpl-25-75-alt .g-first,.g-unit .g-tpl-25-75-alt .g-first,.g-tpl-25-75-alt .g-first{width:74.999%;float:right;margin:0}.g-tpl-75-25 .g-unit,.g-unit .g-tpl-75-25 .g-unit,.g-unit .g-unit .g-tpl-75-25 .g-unit,.g-unit .g-unit .g-unit .g-tpl-75-25 .g-unit{width:24.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-75-25 .g-first,.g-unit .g-unit .g-tpl-75-25 .g-first,.g-unit .g-tpl-75-25 .g-first,.g-tpl-75-25 .g-first{width:74.999%;float:left;margin:0}.g-tpl-75-25-alt .g-unit,.g-unit .g-tpl-75-25-alt .g-unit,.g-unit .g-unit .g-tpl-75-25-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-75-25-alt .g-unit{width:74.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-75-25-alt .g-first,.g-unit .g-unit .g-tpl-75-25-alt .g-first,.g-unit .g-tpl-75-25-alt .g-first,.g-tpl-75-25-alt .g-first{width:24.999%;float:right;margin:0}.g-tpl-33-67 .g-unit,.g-unit .g-tpl-33-67 .g-unit,.g-unit .g-unit .g-tpl-33-67 .g-unit,.g-unit .g-unit .g-unit .g-tpl-33-67 .g-unit{width:66.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-33-67 .g-first,.g-unit .g-unit .g-tpl-33-67 .g-first,.g-unit .g-tpl-33-67 .g-first,.g-tpl-33-67 .g-first{width:32.999%;float:left;margin:0}.g-tpl-33-67-alt .g-unit,.g-unit .g-tpl-33-67-alt .g-unit,.g-unit .g-unit .g-tpl-33-67-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-33-67-alt .g-unit{width:32.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-33-67-alt .g-first,.g-unit .g-unit .g-tpl-33-67-alt .g-first,.g-unit .g-tpl-33-67-alt .g-first,.g-tpl-33-67-alt .g-first{width:66.999%;float:right;margin:0}.g-tpl-67-33 .g-unit,.g-unit .g-tpl-67-33 .g-unit,.g-unit .g-unit .g-tpl-67-33 .g-unit,.g-unit .g-unit .g-unit .g-tpl-67-33 .g-unit{width:32.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-67-33 .g-first,.g-unit .g-unit .g-tpl-67-33 .g-first,.g-unit .g-tpl-67-33 .g-first,.g-tpl-67-33 .g-first{width:66.999%;float:left;margin:0}.g-tpl-67-33-alt .g-unit,.g-unit .g-tpl-67-33-alt .g-unit,.g-unit .g-unit .g-tpl-67-33-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-67-33-alt .g-unit{width:66.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-67-33-alt .g-first,.g-unit .g-unit .g-tpl-67-33-alt .g-first,.g-unit .g-tpl-67-33-alt .g-first,.g-tpl-67-33-alt .g-first{width:32.999%;float:right;margin:0}.g-tpl-50-50 .g-unit,.g-unit .g-tpl-50-50 .g-unit,.g-unit .g-unit .g-tpl-50-50 .g-unit,.g-unit .g-unit .g-unit .g-tpl-50-50 .g-unit{width:49.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-50-50 .g-first,.g-unit .g-unit .g-tpl-50-50 .g-first,.g-unit .g-tpl-50-50 .g-first,.g-tpl-50-50 .g-first{width:49.999%;float:left;margin:0}.g-tpl-50-50-alt .g-unit,.g-unit .g-tpl-50-50-alt .g-unit,.g-unit .g-unit .g-tpl-50-50-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-50-50-alt .g-unit{width:49.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-50-50-alt .g-first,.g-unit .g-unit .g-tpl-50-50-alt .g-first,.g-unit .g-tpl-50-50-alt .g-first,.g-tpl-50-50-alt .g-first{width:49.999%;float:right;margin:0}.g-tpl-nest{width:auto}.g-tpl-nest .g-section{display:inline}.g-tpl-nest .g-unit,.g-unit .g-tpl-nest .g-unit,.g-unit .g-unit .g-tpl-nest .g-unit,.g-unit .g-unit .g-unit .g-tpl-nest .g-unit{float:left;width:auto;margin:0}.g-tpl-nest-alt .g-unit,.g-unit .g-tpl-nest-alt .g-unit,.g-unit .g-unit .g-tpl-nest-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-nest-alt .g-unit{float:right;width:auto;margin:0}html>body .goog-inline-block{display:-moz-inline-box;display:inline-block;}.goog-inline-block{position:relative;display:inline-block}* html .goog-inline-block{display:inline}*:first-child+html .goog-inline-block{display:inline}.goog-tab{position:relative;border:1px solid #8ac;padding:4px 9px;color:#000;background:#e5ecf9;border-top-left-radius:2px;border-top-right-radius:2px;-moz-border-radius-topleft:2px;-webkit-border-top-left-radius:2px;-moz-border-radius-topright:2px;-webkit-border-top-right-radius:2px}.goog-tab-bar-top .goog-tab{margin:1px 4px 0 0;border-bottom:0;float:left}.goog-tab-bar-bottom .goog-tab{margin:0 4px 1px 0;border-top:0;float:left}.goog-tab-bar-start .goog-tab{margin:0 0 4px 1px;border-right:0}.goog-tab-bar-end .goog-tab{margin:0 1px 4px 0;border-left:0}.goog-tab-hover{text-decoration:underline;cursor:pointer}.goog-tab-disabled{color:#fff;background:#ccc;border-color:#ccc}.goog-tab-selected{background:#fff!important;color:black;font-weight:bold}.goog-tab-bar-top .goog-tab-selected{top:1px;margin-top:0;padding-bottom:5px}.goog-tab-bar-bottom .goog-tab-selected{top:-1px;margin-bottom:0;padding-top:5px}.goog-tab-bar-start .goog-tab-selected{left:1px;margin-left:0;padding-right:9px}.goog-tab-bar-end .goog-tab-selected{left:-1px;margin-right:0;padding-left:9px}.goog-tab-content{padding:.1em .8em .8em .8em;border:1px solid #8ac;border-top:none}.goog-tab-bar{position:relative;margin:0 0 0 5px;border:0;padding:0;list-style:none;cursor:default;outline:none}.goog-tab-bar-clear{border-top:1px solid #8ac;clear:both;height:0;overflow:hidden}.goog-tab-bar-start{float:left}.goog-tab-bar-end{float:right}* html .goog-tab-bar-start{margin-right:-3px}* html .goog-tab-bar-end{margin-left:-3px}.ae-table-plain{border-collapse:collapse;width:100%}.ae-table{border:1px solid #c5d7ef;border-collapse:collapse;width:100%}#bd h2.ae-table-title{background:#e5ecf9;margin:0;color:#000;font-size:1em;padding:3px 0 3px 5px;border-left:1px solid #c5d7ef;border-right:1px solid #c5d7ef;border-top:1px solid #c5d7ef}.ae-table-caption,.ae-table caption{border:1px solid #c5d7ef;background:#e5ecf9;-moz-margin-start:-1px}.ae-table caption{padding:3px 5px;text-align:left}.ae-table th,.ae-table td{background-color:#fff;padding:.35em 1em .25em .35em;margin:0}.ae-table thead th{font-weight:bold;text-align:left;background:#c5d7ef;vertical-align:bottom}.ae-table thead th .ae-no-bold{font-weight:normal}.ae-table tfoot tr td{border-top:1px solid #c5d7ef;background-color:#e5ecf9}.ae-table td{border-top:1px solid #c5d7ef;border-bottom:1px solid #c5d7ef}.ae-even>td,.ae-even th,.ae-even-top td,.ae-even-tween td,.ae-even-bottom td,ol.ae-even{background-color:#e9e9e9;border-top:1px solid #c5d7ef;border-bottom:1px solid #c5d7ef}.ae-even-top td{border-bottom:0}.ae-even-bottom td{border-top:0}.ae-even-tween td{border:0}.ae-table .ae-tween td{border:0}.ae-table .ae-tween-top td{border-bottom:0}.ae-table .ae-tween-bottom td{border-top:0}#bd .ae-table .cbc{width:1.5em;padding-right:0}.ae-table #ae-live td{background-color:#ffeac0}.ae-table-fixed{table-layout:fixed}.ae-table-fixed td,.ae-table-nowrap{overflow:hidden;white-space:nowrap}.ae-paginate strong{margin:0 .5em}tfoot .ae-paginate{text-align:right}.ae-table-caption .ae-paginate,.ae-table-caption .ae-orderby{padding:2px 5px}.g-doc{width:auto;margin:8px 10px 0 10px}#ae-logo{margin-bottom:0}#ae-appbar-lrg{margin:0 0 1.25em 0;padding:.2em .6em;background-color:#e5ecf9;border-top:1px solid #6b90da}#ae-appbar-lrg h1{font-size:1em;margin:0;padding:0}#ft p{text-align:center;margin-top:2.5em;padding-top:.5em;border-top:2px solid #c3d9ff}#bd h3{font-weight:bold;font-size:1.4em}#bd p{padding:0 0 1em 0}#ae-content{padding-left:1em;border-left:1px solid #6b90da;min-height:200px}.ae-table .ae-pager{background-color:#c5d7ef}#ae-nav ul{list-style-type:none;margin:0;padding:1em 0}#ae-nav ul li{padding:.1em 0 .1em .5em;margin-bottom:.3em}#ae-nav .ae-nav-selected{color:#44464a;display:block;font-weight:bold;background-color:#e5ecf9;border-bottom:1px solid #cedff2}a.ae-nav-selected{color:#44464a;text-decoration:none}#ae-nav ul li span.ae-nav-disabled{color:#666}#ae-nav ul ul{margin:0;padding:0 0 0 .5em}#ae-nav ul ul li{padding-left:.5em}#ae-nav ul li a,#ae-nav ul li span,#ae-nav ul ul li a{padding-left:.5em}#ae-nav li a:link,#ae-nav li a:visited{color:#00c}#ae-nav li a:link.ae-nav-selected,#ae-nav li a:visited.ae-nav-selected{color:#000;text-decoration:none}.ae-nav-group{padding:.5em;margin:0 .75em 0 0;background-color:#fffbe8;border:1px solid #fff1a9}.ae-nav-group h4{font-weight:bold;padding:auto auto .5em .5em;padding-left:.4em;margin-bottom:.5em;padding-bottom:0}.ae-nav-group ul{margin:0 0 .5em 0;padding:0 0 0 1.3em;list-style-type:none}.ae-nav-group ul li{padding-bottom:.5em}.ae-nav-group li a:link,.ae-nav-group li a:visited{color:#00c}.ae-nav-group li a:hover{color:#00c}#datastore_search{margin-bottom:1em}#hint{background-color:#f6f9ff;border:1px solid #e5ecf9;margin-bottom:1em;padding:0.5em 1em}#message{color:red;position:relative;bottom:6px}#pagetotal{float:right}#pagetotal .count{font-weight:bold}table.entities{border:1px solid #c5d7ef;border-collapse:collapse;width:100%;margin-bottom:0}table.entities th,table.entities td{padding:.25em 1.5em .5em .5em}table.entities th{font-weight:bold;text-align:left;background:#e5ecf9;white-space:nowrap}table.entities th a,table.entities th a:visited{color:black;text-decoration:none}table.entities td{background-color:#fff;text-align:left;vertical-align:top;cursor:pointer}table.entities tr.even td{background-color:#f9f9f9}div.entities{background-color:#c5d7ef;margin-top:0}#entities-pager,#entities-control{padding:.3em 1em .4em 1em}#entities-pager{text-align:right}.ae-page-number{margin:0 0.5em}.ae-page-selected{font-weight:bold}#ae-stats-hd span{font-weight:normal}#ae-rpc-label-col{width:85%}#ae-rpc-stats-col{width:15%}#ae-path-label-col{width:45%}#ae-path-reqs-col{width:10%}#ae-path-rpcs-col{width:10%}#ae-path-stats-col{width:35%}#ae-stats-refresh{margin-bottom:1em}.ae-table-wrapper-left{margin-right:.5em}.ae-table-wrapper-right{margin-left:.5em}#ae-req-history,#ae-rpc-traces{margin-top:1em}.ae-zippy,.ae-zippy-all{position:relative;top:1px;height:12px;width:12px}.goog-zippy-collapsed{background:transparent url('./plus.gif') no-repeat}.goog-zippy-expanded{background:transparent url('./minus.gif') no-repeat}td.ae-hanging-indent{padding-left:20px;text-indent:-20px}.ae-stats-request-link{text-decoration:none}.ae-table td.rpc-req{padding-left:20px;width:20em}#bd div.ae-table-title{background:#e5ecf9;margin:0;color:#000;padding:3px 0 3px 5px;border-left:1px solid #c5d7ef;border-right:1px solid #c5d7ef;border-top:1px solid #c5d7ef}#bd div.ae-table-title h2{font-size:1em;margin:0;padding:0}#bd div.ae-table-title h2.ae-zippy{padding-left:16px;text-decoration:underline;color:#00c}#ae-head-glance span,#ae-rpc-expand-all span,#ae-path-expand-all span,#ae-request-expand-all span{padding-right:.5em}.ae-action{color:#00c;text-decoration:underline;cursor:pointer}.ae-toggle{padding-left:16px;background-position:left center;background-repeat:no-repeat;cursor:pointer}.ae-minus{background-image:url('./minus.gif')}.ae-plus{background-image:url('./plus.gif')}#ae-stats-summary{margin-bottom:1em}#ae-stats-summary dt{float:left;text-align:right;margin-right:1em;font-weight:bold}.ae-stats-date{color:#666}.ae-stats-response-200{color:green}#ae-stats-summary dd{float:left}table.ae-stats-gantt-table{width:95%;border:1px solid #999}div.ae-stats-gantt-container{position:relative;width:100%;height:1em;background-color:#eeeeff}img.ae-stats-gantt-bar{border:0;height:1em;background-color:#7777ff;position:absolute;top:0}img.ae-stats-gantt-extra{border:0;height:0.5em;background-color:#ff6666;position:absolute;top:25%}span.ae-stats-gantt-inline{font-size:80%;position:absolute;top:0.1em;white-space:nowrap;overflow:hidden}a.ae-stats-gantt-link{text-decoration:none}div.ae-stats-gantt-axis{position:relative;width:100%;height:1em}img.ae-stats-gantt-tick{width:1px;height:1em;position:absolute;background-color:gray}span.ae-stats-gantt-scale{position:absolute}") 6 | 7 | var appstats_js_js = []byte("/* Copyright 2008-10 Google Inc. All Rights Reserved. */ (function(){function e(a){throw a;}\nvar h=void 0,k=!0,l=null,p=!1,r,s=this,aa=function(){},ba=function(a){a.M=function(){return a.Db?a.Db:a.Db=new a}},ca=function(a){var b=typeof a;if(\"object\"==b)if(a){if(a instanceof Array)return\"array\";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(\"[object Window]\"==c)return\"object\";if(\"[object Array]\"==c||\"number\"==typeof a.length&&\"undefined\"!=typeof a.splice&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"splice\"))return\"array\";if(\"[object Function]\"==\nc||\"undefined\"!=typeof a.call&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"call\"))return\"function\"}else return\"null\";else if(\"function\"==b&&\"undefined\"==typeof a.call)return\"object\";return b},da=function(a){return\"array\"==ca(a)},u=function(a){return\"string\"==typeof a},w=function(a){return\"function\"==ca(a)},ea=function(a){var b=typeof a;return\"object\"==b&&a!=l||\"function\"==b},x=function(a){return a[fa]||(a[fa]=++ga)},fa=\"closure_uid_\"+Math.floor(2147483648*Math.random()).toString(36),\nga=0,ha=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=Array.prototype.slice.call(arguments);b.unshift.apply(b,c);return a.apply(this,b)}},ia=function(a,b){var c=a.split(\".\"),d=s;!(c[0]in d)&&d.execScript&&d.execScript(\"var \"+c[0]);for(var g;c.length&&(g=c.shift());)!c.length&&b!==h?d[g]=b:d=d[g]?d[g]:d[g]={}},y=function(a,b){function c(){}c.prototype=b.prototype;a.e=b.prototype;a.prototype=new c;a.prototype.constructor=a};var ja=function(a){Error.captureStackTrace?Error.captureStackTrace(this,ja):this.stack=Error().stack||\"\";a&&(this.message=String(a))};y(ja,Error);ja.prototype.name=\"CustomError\";var ka=function(a,b){for(var c=1;c\")&&(a=a.replace(pa,\">\"));-1!=a.indexOf('\"')&&(a=a.replace(qa,\""\"));return a},na=/&/g,oa=//g,qa=/\\\"/g,ma=/[&<>\\\"]/;var sa=function(a,b){b.unshift(a);ja.call(this,ka.apply(l,b));b.shift()};y(sa,ja);sa.prototype.name=\"AssertionError\";var ta=function(a,b,c){var d=\"Assertion failed\";if(b)var d=d+(\": \"+b),g=c;else a&&(d+=\": \"+a,g=l);e(new sa(\"\"+d,g||[]))},z=function(a,b,c){a||ta(\"\",b,Array.prototype.slice.call(arguments,2))},ua=function(a,b,c,d){a instanceof b||ta(\"instanceof check failed.\",c,Array.prototype.slice.call(arguments,3))};var A=Array.prototype,va=A.indexOf?function(a,b,c){z(a.length!=l);return A.indexOf.call(a,b,c)}:function(a,b,c){c=c==l?0:0>c?Math.max(0,a.length+c):c;if(u(a))return!u(b)||1!=b.length?-1:a.indexOf(b,c);for(;c=arguments.length?A.slice.call(a,b):A.slice.call(a,b,c)};var Ea=function(a,b,c){b in a&&e(Error('The object already contains the key \"'+b+'\"'));a[b]=c},Fa=function(a){var b={},c;for(c in a)b[a[c]]=c;return b},Ga=\"constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf\".split(\" \"),Ha=function(a,b){for(var c,d,g=1;gparseFloat(Ua)){Ta=String(Ya);break a}}Ta=Ua}\nvar Za=Ta,$a={},F=function(a){var b;if(!(b=$a[a])){b=0;for(var c=la(String(Za)).split(\".\"),d=la(String(a)).split(\".\"),g=Math.max(c.length,d.length),f=0;0==b&&f(0==v[1].length?0:parseInt(v[1],10))?1:\n0)||((0==t[2].length)<(0==v[2].length)?-1:(0==t[2].length)>(0==v[2].length)?1:0)||(t[2]v[2]?1:0)}while(0==b)}b=$a[a]=0<=b}return b},ab=s.document,bb=!ab||!B?h:Sa()||(\"CSS1Compat\"==ab.compatMode?parseInt(Za,10):5);var cb,db=!B||B&&9<=bb;!C&&!B||B&&B&&9<=bb||C&&F(\"1.9.1\");var eb=B&&!F(\"9\");var fb=function(a){a=a.className;return u(a)&&a.match(/\\S+/g)||[]},G=function(a,b){for(var c=fb(a),d=Ca(arguments,1),g=c.length+d.length,f=c,j=0;j\");c=c.join(\"\")}var f=a.createElement(c);if(d)if(u(d))f.className=d;else if(da(d))G.apply(l,[f].concat(d));else{var c=function(a,b){\"style\"==b?f.style.cssText=a:\"class\"==b?f.className=a:\"for\"==b?f.htmlFor=a:b in mb?f.setAttribute(mb[b],a):0==b.lastIndexOf(\"aria-\",0)||0==b.lastIndexOf(\"data-\",0)?f.setAttribute(b,a):f[b]=a},j;for(j in d)c.call(h,d[j],j)}if(2a):p},vb=function(a,b,c){if(!(a.nodeName in sb))if(3==a.nodeType)c?b.push(String(a.nodeValue).replace(/(\\r\\n|\\r|\\n)/g,\"\")):b.push(a.nodeValue);else if(a.nodeName in tb)b.push(tb[a.nodeName]);else for(a=a.firstChild;a;)vb(a,b,c),a=a.nextSibling},hb=function(a){this.B=a||s.document||document};r=hb.prototype;r.Fa=jb;r.a=function(a){return u(a)?this.B.getElementById(a):a};\nr.k=function(a,b,c){return nb(this.B,arguments)};r.createElement=function(a){return this.B.createElement(a)};r.createTextNode=function(a){return this.B.createTextNode(a)};r.appendChild=function(a,b){a.appendChild(b)};r.contains=qb;var wb=function(a){wb[\" \"](a);return a};wb[\" \"]=aa;var xb=!B||B&&9<=bb,yb=!B||B&&9<=bb,zb=B&&!F(\"9\");!D||F(\"528\");C&&F(\"1.9b\")||B&&F(\"8\")||Qa&&F(\"9.5\")||D&&F(\"528\");C&&!F(\"8\")||B&&F(\"9\");var Ab=function(){};Ab.prototype.Rb=p;var H=function(a,b){this.type=a;this.currentTarget=this.target=b};r=H.prototype;r.Z=p;r.defaultPrevented=p;r.Ia=k;r.stopPropagation=function(){this.Z=k};r.preventDefault=function(){this.defaultPrevented=k;this.Ia=p};var I=function(a,b){a&&this.sa(a,b)};y(I,H);var Bb=[1,4,2];r=I.prototype;r.target=l;r.relatedTarget=l;r.offsetX=0;r.offsetY=0;r.clientX=0;r.clientY=0;r.screenX=0;r.screenY=0;r.button=0;r.keyCode=0;r.charCode=0;r.ctrlKey=p;r.altKey=p;r.shiftKey=p;r.metaKey=p;r.Ya=p;r.P=l;\nr.sa=function(a,b){var c=this.type=a.type;H.call(this,c);this.target=a.target||a.srcElement;this.currentTarget=b;var d=a.relatedTarget;if(d){if(C){var g;a:{try{wb(d.nodeName);g=k;break a}catch(f){}g=p}g||(d=l)}}else\"mouseover\"==c?d=a.fromElement:\"mouseout\"==c&&(d=a.toElement);this.relatedTarget=d;this.offsetX=D||a.offsetX!==h?a.offsetX:a.layerX;this.offsetY=D||a.offsetY!==h?a.offsetY:a.layerY;this.clientX=a.clientX!==h?a.clientX:a.pageX;this.clientY=a.clientY!==h?a.clientY:a.pageY;this.screenX=a.screenX||\n0;this.screenY=a.screenY||0;this.button=a.button;this.keyCode=a.keyCode||0;this.charCode=a.charCode||(\"keypress\"==c?a.keyCode:0);this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey=a.metaKey;this.Ya=E?a.metaKey:a.ctrlKey;this.state=a.state;this.P=a;a.defaultPrevented&&this.preventDefault();delete this.Z};var Cb=function(a){return xb?0==a.P.button:\"click\"==a.type?k:!!(a.P.button&Bb[0])};\nI.prototype.stopPropagation=function(){I.e.stopPropagation.call(this);this.P.stopPropagation?this.P.stopPropagation():this.P.cancelBubble=k};I.prototype.preventDefault=function(){I.e.preventDefault.call(this);var a=this.P;if(a.preventDefault)a.preventDefault();else if(a.returnValue=p,zb)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};var Db=function(){},Eb=0;r=Db.prototype;r.key=0;r.Y=p;r.Cb=p;r.sa=function(a,b,c,d,g,f){w(a)?this.Ab=k:a&&a.handleEvent&&w(a.handleEvent)?this.Ab=p:e(Error(\"Invalid listener argument\"));this.ea=a;this.tb=b;this.src=c;this.type=d;this.capture=!!g;this.Aa=f;this.Cb=p;this.key=++Eb;this.Y=p};r.handleEvent=function(a){return this.Ab?this.ea.call(this.Aa||this.src,a):this.ea.handleEvent.call(this.ea,a)};var Fb={},J={},Gb={},Hb={},K=function(a,b,c,d,g){if(b){if(da(b)){for(var f=0;ff.keyCode||f.returnValue!=h)return k;a:{var q=p;if(0==f.keyCode)try{f.keyCode=-1;break a}catch(t){q=k}if(q||f.returnValue==h)f.returnValue=k}}q=new I;q.sa(f,this);f=k;try{if(m){for(var v=[],Pa=q.currentTarget;Pa;Pa=Pa.parentNode)v.push(Pa);j=g[k];j.v=j.C;for(var S=v.length-1;!q.Z&&0<=S&&j.v;S--)q.currentTarget=v[S],f&=Nb(j,v[S],d,k,q);if(n){j=g[p];j.v=j.C;for(S=0;!q.Z&&Sb||b>bc(this))&&e(Error(\"Child component index out of bounds\"));if(!this.h||!this.r)this.h={},this.r=[];if(a.getParent()==this){var d=Xb(a);this.h[d]=a;Aa(this.r,a)}else Ea(this.h,Xb(a),a);$b(a,this);Da(this.r,b,0,a);a.d&&this.d&&a.getParent()==this?(c=this.z(),c.insertBefore(a.a(),c.childNodes[b]||l)):c?(this.c||this.k(),c=P(this,b+1),b=this.z(),c=c?c.c:l,a.d&&e(Error(\"Component already rendered\")),a.c||a.k(),b?b.insertBefore(a.c,\nc||l):a.m.B.body.appendChild(a.c),(!a.n||a.n.d)&&a.t()):this.d&&(!a.d&&a.c&&a.c.parentNode&&1==a.c.parentNode.nodeType)&&a.t()};r.z=function(){return this.c};\nvar cc=function(a){if(a.pa==l){var b;a:{b=a.d?a.c:a.m.B.body;var c=ib(b);if(c.defaultView&&c.defaultView.getComputedStyle&&(b=c.defaultView.getComputedStyle(b,l))){b=b.direction||b.getPropertyValue(\"direction\")||\"\";break a}b=\"\"}a.pa=\"rtl\"==(b||((a.d?a.c:a.m.B.body).currentStyle?(a.d?a.c:a.m.B.body).currentStyle.direction:l)||(a.d?a.c:a.m.B.body).style&&(a.d?a.c:a.m.B.body).style.direction)}return a.pa};O.prototype.na=function(a){this.d&&e(Error(\"Component already rendered\"));this.pa=a};\nvar bc=function(a){return a.r?a.r.length:0},P=function(a,b){return a.r?a.r[b]||l:l},ac=function(a,b,c){a.r&&wa(a.r,b,c)},dc=function(a,b){return a.r&&b?va(a.r,b):-1};O.prototype.removeChild=function(a,b){if(a){var c=u(a)?a:Xb(a);a=this.h&&c?(c in this.h?this.h[c]:h)||l:l;if(c&&a){var d=this.h;c in d&&delete d[c];Aa(this.r,a);b&&(a.$(),a.c&&pb(a.c));$b(a,l)}}a||e(Error(\"Child is not in parent component\"));return a};var fc=function(a,b,c,d,g){if(!B&&(!D||!F(\"525\")))return k;if(E&&g)return ec(a);if(g&&!d||!c&&(17==b||18==b||E&&91==b))return p;if(D&&d&&c)switch(a){case 220:case 219:case 221:case 192:case 186:case 189:case 187:case 188:case 190:case 191:case 192:case 222:return p}if(B&&d&&b==a)return p;switch(a){case 13:return!(B&&B&&9<=bb);case 27:return!D}return ec(a)},ec=function(a){if(48<=a&&57>=a||96<=a&&106>=a||65<=a&&90>=a||D&&0==a)return k;switch(a){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return k;\ndefault:return p}},gc=function(a){switch(a){case 61:return 187;case 59:return 186;case 224:return 91;case 0:return 224;default:return a}};var Q=function(a,b){a&&hc(this,a,b)};y(Q,Qb);r=Q.prototype;r.c=l;r.Ba=l;r.Sa=l;r.Ca=l;r.s=-1;r.N=-1;r.gb=p;\nvar ic={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},jc={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,\"U+007F\":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},kc=B||D&&F(\"525\"),lc=E&&C;\nQ.prototype.Pb=function(a){if(D&&(17==this.s&&!a.ctrlKey||18==this.s&&!a.altKey||E&&91==this.s&&!a.metaKey))this.N=this.s=-1;-1==this.s&&(a.ctrlKey&&17!=a.keyCode?this.s=17:a.altKey&&18!=a.keyCode?this.s=18:a.metaKey&&91!=a.keyCode&&(this.s=91));kc&&!fc(a.keyCode,this.s,a.shiftKey,a.ctrlKey,a.altKey)?this.handleEvent(a):(this.N=C?gc(a.keyCode):a.keyCode,lc&&(this.gb=a.altKey))};Q.prototype.Qb=function(a){this.N=this.s=-1;this.gb=a.altKey};\nQ.prototype.handleEvent=function(a){var b=a.P,c,d,g=b.altKey;B&&\"keypress\"==a.type?(c=this.N,d=13!=c&&27!=c?b.keyCode:0):D&&\"keypress\"==a.type?(c=this.N,d=0<=b.charCode&&63232>b.charCode&&ec(c)?b.charCode:0):Qa?(c=this.N,d=ec(c)?b.keyCode:0):(c=b.keyCode||this.N,d=b.charCode||0,lc&&(g=this.gb),E&&(63==d&&224==c)&&(c=191));var f=c,j=b.keyIdentifier;c?63232<=c&&c in ic?f=ic[c]:25==c&&a.shiftKey&&(f=9):j&&j in jc&&(f=jc[j]);a=f==this.s;this.s=f;b=new mc(f,d,a,b);b.altKey=g;this.dispatchEvent(b)};\nQ.prototype.a=function(){return this.c};var hc=function(a,b,c){a.Ca&&a.detach();a.c=b;a.Ba=K(a.c,\"keypress\",a,c);a.Sa=K(a.c,\"keydown\",a.Pb,c,a);a.Ca=K(a.c,\"keyup\",a.Qb,c,a)};Q.prototype.detach=function(){this.Ba&&(L(this.Ba),L(this.Sa),L(this.Ca),this.Ca=this.Sa=this.Ba=l);this.c=l;this.N=this.s=-1};var mc=function(a,b,c,d){d&&this.sa(d,h);this.type=\"key\";this.keyCode=a;this.charCode=b;this.repeat=c};y(mc,I);var oc=function(a,b){a||e(Error(\"Invalid class name \"+a));w(b)||e(Error(\"Invalid decorator function \"+b));nc[a]=b},pc={},nc={};var R=function(){},qc;ba(R);r=R.prototype;r.I=function(){};r.k=function(a){var b=a.Fa().k(\"div\",this.ra(a).join(\" \"),a.wa);rc(a,b);return b};r.z=function(a){return a};r.oa=function(a,b,c){if(a=a.a?a.a():a)if(B&&!F(\"7\")){var d=sc(fb(a),b);d.push(b);ha(c?G:gb,a).apply(l,d)}else c?G(a,b):gb(a,b)};r.X=function(){return k};\nr.H=function(a,b){b.id&&Yb(a,b.id);var c=this.z(b);c&&c.firstChild?(c=c.firstChild.nextSibling?Ba(c.childNodes):c.firstChild,a.wa=c):a.wa=l;var d=0,g=this.o(),f=this.o(),j=p,m=p,c=p,n=fb(b);wa(n,function(a){if(!j&&a==g)j=k,f==g&&(m=k);else if(!m&&a==f)m=k;else{var b=d;this.nb||(this.Da||tc(this),this.nb=Fa(this.Da));a=parseInt(this.nb[a],10);d=b|(isNaN(a)?0:a)}},this);a.f=d;j||(n.push(g),f==g&&(m=k));m||n.push(f);var q=a.F;q&&n.push.apply(n,q);if(B&&!F(\"7\")){var t=sc(n);0a?b-1:a},0);break;case 38:if(\"vertical\"==this.K)Kc(this);else return p;break;case 37:if(\"horizontal\"==this.K)cc(this)?Lc(this):\nKc(this);else return p;break;case 40:if(\"vertical\"==this.K)Lc(this);else return p;break;case 39:if(\"horizontal\"==this.K)cc(this)?Kc(this):Lc(this);else return p;break;default:return p}return k};var Gc=function(a,b){var c=b.a(),c=c.id||(c.id=Xb(b));a.L||(a.L={});a.L[c]=b};W.prototype.za=function(a,b){ua(a,T,\"The child of a container must be a control\");W.e.za.call(this,a,b)};\nW.prototype.Oa=function(a,b,c){a.S|=2;a.S|=64;(this.R()||!this.Ob)&&zc(a,32,p);a.Ka(p);W.e.Oa.call(this,a,b,c);a.d&&this.d&&Gc(this,a);b<=this.i&&this.i++};W.prototype.removeChild=function(a,b){if(a=u(a)?this.h&&a?(a in this.h?this.h[a]:h)||l:l:a){var c=dc(this,a);-1!=c&&(c==this.i?a.A(p):ca?c-1:a},a.i)},Jc=function(a,b,c){c=0>c?dc(a,a.g):c;var d=bc(a);c=b.call(a,c,d);for(var g=0;g<=d;){var f=P(a,c);if(f&&f.D()&&f.isEnabled()&&f.p&2){a.Qa(c);break}g++;c=b.call(a,c,d)}};W.prototype.Qa=function(a){Ic(this,a)};var Mc=function(){};y(Mc,R);ba(Mc);r=Mc.prototype;r.o=function(){return\"goog-tab\"};r.I=function(){return\"tab\"};r.k=function(a){var b=Mc.e.k.call(this,a);(a=a.Na())&&this.Pa(b,a);return b};r.H=function(a,b){b=Mc.e.H.call(this,a,b);var c=this.Na(b);c&&(a.mb=c);if(a.f&8&&(c=a.getParent())&&w(c.V))a.u(8,p),c.V(a);return b};r.Na=function(a){return a.title||\"\"};r.Pa=function(a,b){a&&(a.title=b||\"\")};var Nc=function(a,b,c){T.call(this,a,b||Mc.M(),c);zc(this,8,k);this.S|=9};y(Nc,T);Nc.prototype.Na=function(){return this.mb};Nc.prototype.Pa=function(a){this.vb().Pa(this.a(),a);this.mb=a};oc(\"goog-tab\",function(){return new Nc(l)});var X=function(){};y(X,Ec);ba(X);X.prototype.o=function(){return\"goog-tab-bar\"};X.prototype.I=function(){return\"tablist\"};X.prototype.Ma=function(a,b,c){this.wb||(this.Ea||Oc(this),this.wb=Fa(this.Ea));var d=this.wb[b];d?(Fc(a,Pc(d)),a.pb=d):X.e.Ma.call(this,a,b,c)};X.prototype.ra=function(a){var b=X.e.ra.call(this,a);this.Ea||Oc(this);b.push(this.Ea[a.pb]);return b};var Oc=function(a){var b=a.o();a.Ea={top:b+\"-top\",bottom:b+\"-bottom\",start:b+\"-start\",end:b+\"-end\"}};var Y=function(a,b,c){a=a||\"top\";Fc(this,Pc(a));this.pb=a;W.call(this,this.K,b||X.M(),c);Qc(this)};y(Y,W);r=Y.prototype;r.Ub=k;r.G=l;r.t=function(){Y.e.t.call(this);Qc(this)};r.removeChild=function(a,b){Rc(this,a);return Y.e.removeChild.call(this,a,b)};r.Qa=function(a){Y.e.Qa.call(this,a);this.Ub&&this.V(P(this,a))};r.V=function(a){a?xc(a,k):this.G&&xc(this.G,p)};\nvar Rc=function(a,b){if(b&&b==a.G){for(var c=dc(a,b),d=c-1;b=P(a,d);d--)if(b.D()&&b.isEnabled()){a.V(b);return}for(c+=1;b=P(a,c);c++)if(b.D()&&b.isEnabled()){a.V(b);return}a.V(l)}};r=Y.prototype;r.ac=function(a){this.G&&this.G!=a.target&&xc(this.G,p);this.G=a.target};r.bc=function(a){a.target==this.G&&(this.G=l)};r.Zb=function(a){Rc(this,a.target)};r.$b=function(a){Rc(this,a.target)};r.ma=function(){P(this,this.i)||this.A(this.G||P(this,0))};\nvar Qc=function(a){M(M(M(M(Zb(a),a,\"select\",a.ac),a,\"unselect\",a.bc),a,\"disable\",a.Zb),a,\"hide\",a.$b)},Pc=function(a){return\"start\"==a||\"end\"==a?\"vertical\":\"horizontal\"};oc(\"goog-tab-bar\",function(){return new Y});var Z=function(a,b,c,d,g){function f(a){a&&(a.tabIndex=0,a.setAttribute(\"role\",j.I()),G(a,\"goog-zippy-header\"),Sc(j,a),a&&M(j.Lb,a,\"keydown\",j.Mb))}this.m=g||jb();this.T=this.m.a(a)||l;this.xa=this.m.a(d||l);this.ba=(this.La=w(b)?b:l)||!b?l:this.m.a(b);this.l=c==k;this.Lb=new Ob(this);this.kb=new Ob(this);var j=this;f(this.T);f(this.xa);this.W(this.l)};y(Z,Qb);r=Z.prototype;r.fa=k;r.ic=k;r.I=function(){return\"tab\"};r.z=function(){return this.ba};r.toggle=function(){this.W(!this.l)};\nr.W=function(a){this.ba?Rb(this.ba,a):a&&this.La&&(this.ba=this.La());this.ba&&G(this.ba,\"goog-zippy-content\");if(this.xa)Rb(this.T,!a),Rb(this.xa,a);else if(this.T){var b=this.T;a?G(b,\"goog-zippy-expanded\"):gb(b,\"goog-zippy-expanded\");b=this.T;!a?G(b,\"goog-zippy-collapsed\"):gb(b,\"goog-zippy-collapsed\");this.T.setAttribute(\"aria-expanded\",a)}this.l=a;this.dispatchEvent(new Tc(\"toggle\",this))};r.lb=function(){return this.ic};\nr.Ka=function(a){this.fa!=a&&((this.fa=a)?(Sc(this,this.T),Sc(this,this.xa)):(a=this.kb,wa(a.da,L),a.da.length=0))};var Sc=function(a,b){b&&M(a.kb,b,\"click\",a.dc)};Z.prototype.Mb=function(a){if(13==a.keyCode||32==a.keyCode)this.toggle(),this.dispatchEvent(new H(\"action\",this)),a.preventDefault(),a.stopPropagation()};Z.prototype.dc=function(){this.toggle();this.dispatchEvent(new H(\"action\",this))};var Tc=function(a,b){H.call(this,a,b)};y(Tc,H);var Vc=function(a,b){this.ib=[];for(var c=kb(a),c=lb(\"span\",\"ae-zippy\",c),d=0,g;g=c[d];d++){var f;if(g.parentNode.parentNode.parentNode.nextElementSibling!=h)f=g.parentNode.parentNode.parentNode.nextElementSibling;else for(f=g.parentNode.parentNode.parentNode.nextSibling;f&&1!=f.nodeType;)f=f.nextSibling;g=new Z(g,f,p);this.ib.push(g)}this.cc=new Uc(this.ib,kb(b))};Vc.prototype.gc=function(){return this.cc};Vc.prototype.hc=function(){return this.ib};\nvar Uc=function(a,b){this.ta=a;if(this.ta.length)for(var c=0,d;d=this.ta[c];c++)K(d,\"toggle\",this.Tb,p,this);this.Ha=0;this.l=p;c=\"ae-toggle ae-plus ae-action\";this.ta.length||(c+=\" ae-disabled\");this.Q=ob(\"span\",{className:c},\"Expand All\");K(this.Q,\"click\",this.Sb,p,this);b&&b.appendChild(this.Q)};Uc.prototype.Sb=function(){this.ta.length&&this.W(!this.l)};\nUc.prototype.Tb=function(a){a=a.currentTarget;this.Ha=a.l?this.Ha+1:this.Ha-1;a.l!=this.l&&(a.l?(this.l=k,Wc(this,k)):0==this.Ha&&(this.l=p,Wc(this,p)))};Uc.prototype.W=function(a){this.l=a;a=0;for(var b;b=this.ta[a];a++)b.l!=this.l&&b.W(this.l);Wc(this)};\nvar Wc=function(a,b){(b!==h?b:a.l)?(gb(a.Q,\"ae-plus\"),G(a.Q,\"ae-minus\"),rb(a.Q,\"Collapse All\")):(gb(a.Q,\"ae-minus\"),G(a.Q,\"ae-plus\"),rb(a.Q,\"Expand All\"))},Xc=function(a){this.Vb=a;this.Bb={};var b,c=ob(\"div\",{},b=ob(\"div\",{id:\"ae-stats-details-tabs\",className:\"goog-tab-bar goog-tab-bar-top\"}),ob(\"div\",{className:\"goog-tab-bar-clear\"}),a=ob(\"div\",{id:\"ae-stats-details-tabs-content\",className:\"goog-tab-content\"})),d=new Y;d.H(b);K(d,\"select\",this.yb,p,this);K(d,\"unselect\",this.yb,p,this);b=0;for(var g;g=\nthis.Vb[b];b++)if(g=kb(\"ae-stats-details-\"+g)){var f=lb(\"h2\",l,g)[0],j;j=f;var m=h;eb&&\"innerText\"in j?m=j.innerText.replace(/(\\r\\n|\\r|\\n)/g,\"\\n\"):(m=[],vb(j,m,k),m=m.join(\"\"));m=m.replace(/ \\xAD /g,\" \").replace(/\\xAD/g,\"\");m=m.replace(/\\u200B/g,\"\");eb||(m=m.replace(/ +/g,\" \"));\" \"!=m&&(m=m.replace(/^\\s*/,\"\"));j=m;pb(f);f=new Nc(j);this.Bb[x(f)]=g;d.za(f,k);a.appendChild(g);0==b?d.V(f):Rb(g,p)}kb(\"bd\").appendChild(c)};Xc.prototype.yb=function(a){var b=this.Bb[x(a.target)];Rb(b,\"select\"==a.type)};\nia(\"ae.Stats.Details.Tabs\",Xc);ia(\"goog.ui.Zippy\",Z);Z.prototype.setExpanded=Z.prototype.W;ia(\"ae.Stats.MakeZippys\",Vc);Vc.prototype.getExpandCollapse=Vc.prototype.gc;Vc.prototype.getZippys=Vc.prototype.hc;Uc.prototype.setExpanded=Uc.prototype.W;var $=function(){this.Za=[];this.cb=[]},Yc=[[5,0.2,1],[6,0.2,1.2],[5,0.25,1.25],[6,0.25,1.5],[4,0.5,2],[5,0.5,2.5],[6,0.5,3],[4,1,4],[5,1,5],[6,1,6],[4,2,8],[5,2,10]],Zc=function(a){if(0>=a)return[2,0.5,1];for(var b=1;1>a;)a*=10,b/=10;for(;10<=a;)a/=10,b*=10;for(var c=0;c');a.write('
');for(var g=0;g<=b;g++)a.write('\"\"'),a.write(''),a.write(\" \"+g*c+\"\");a.write(\"
\\n\")};\n$.prototype.fc=function(){this.cb=[];var a=Zc(this.ab),b=a[0],c=a[1],a=100/a[2];this.write('\\n');$c(this,b,c,a);for(var d=0;d\\n\\n\")}$c(this,b,c,a);this.write(\"
');0'),this.write(g.label),0\"));this.write(\"\");this.write('
');0');this.write('\"\"');0'));0 '),this.write(g.ub),this.write(\"\"));0\");this.write(\"
\\n\");return this.cb.join(\"\")};$.prototype.ec=function(a,b,c,d,g,f){this.ab=Math.max(this.ab,Math.max(b+c,b+d));this.Za.push({label:a,start:b,duration:c,$a:d,ub:g,ga:f})};ia(\"Gantt\",$);$.prototype.add_bar=$.prototype.ec;$.prototype.draw=$.prototype.fc;})();\n") 8 | 9 | var gantt_js = []byte("// Copyright 2009 Google Inc. All Rights Reserved.\n\n/**\n * Defines a class that can render a simple Gantt chart.\n *\n * @author guido@google.com (Guido van Rossum)\n * @author schefflerjens@google.com (Jens Scheffler)\n */\n\n/**\n * @constructor\n */\nvar Gantt = function() {\n /**\n * @type {Array}\n */\n this.bars = [];\n\n /**\n * @type {Array}\n */\n this.output = [];\n};\n\n\n/**\n * Internal fields used to render the chart.\n * Should not be modified.\n * @type {Array.}\n */\nGantt.SCALES = [[5, 0.2, 1.0],\n [6, 0.2, 1.2],\n [5, 0.25, 1.25],\n [6, 0.25, 1.5],\n [4, 0.5, 2.0],\n [5, 0.5, 2.5],\n [6, 0.5, 3.0],\n [4, 1.0, 4.0],\n [5, 1.0, 5.0],\n [6, 1.0, 6.0],\n [4, 2.0, 8.0],\n [5, 2.0, 10.0]];\n\n\n/**\n * Helper to compute the proper X axis scale.\n * Args:\n * highest: the highest value in the data series.\n *\n * Returns:\n * A tuple (howmany, spacing, limit) where howmany is the number of\n * increments, spacing is the increment to be used between successive\n * axis labels, and limit is the rounded-up highest value of the\n * axis. Within float precision, howmany * spacing == highest will\n * hold.\n *\n * The axis is assumed to always start at zero.\n */\nGantt.compute_scale = function(highest) {\n if (highest <= 0) {\n return [2, 0.5, 1.0] // Special-case if there's no data.\n }\n var scale = 1.0\n while (highest < 1.0) {\n highest *= 10.0\n scale /= 10.0\n }\n while (highest >= 10.0) {\n highest /= 10.0\n scale *= 10.0\n }\n // Now 1 <= highest < 10\n for (var i = 0; i < Gantt.SCALES.length; i++) {\n if (highest <= Gantt.SCALES[i][2]) {\n return [Gantt.SCALES[i][0], Gantt.SCALES[i][1] * scale,\n Gantt.SCALES[i][2] * scale];\n }\n }\n // Avoid the need for \"assert False\". Not actually reachable.\n return [5, 2.0 * scale, 10.0 * scale];\n};\n\n\n/**\n * URL of a transparent 1x1 GIF.\n * @type {string}\n */\nGantt.prototype.PIX = 'stats/static/pix.gif';\n\n\n/**\n * CSS class name prefix.\n * @type {string}\n */\nGantt.prototype.PREFIX = 'ae-stats-gantt-';\n\n\n/**\n * Height of one bar.\n * @type {string}\n */\nGantt.prototype.HEIGHT = '1em';\n\n\n/**\n * Height of the extra bar.\n * @type {string}\n */\nGantt.prototype.EXTRA_HEIGHT = '0.5em';\n\n\n/**\n * Background color for the bar.\n * @type {string}\n */\nGantt.prototype.BG_COLOR = '#eeeeff';\n\n\n/**\n * Color of the main bar.\n * @type {string}\n */\nGantt.prototype.COLOR = '#7777ff';\n\n\n/**\n * Color of the extra bar.\n * @type {string}\n */\nGantt.prototype.EXTRA_COLOR = '#ff6666';\n\n\n/**\n * Font size of inline_label.\n * @type {string}\n */\nGantt.prototype.INLINE_FONT_SIZE = '80%';\n\n\n/**\n * Top of inline label text.\n * @type {string}\n */\nGantt.prototype.INLINE_TOP = '0.1em';\n\n\n/**\n * Color for ticks.\n * @type {string}\n */\nGantt.prototype.TICK_COLOR = 'grey';\n\n\n/**\n * @type {number}\n */\nGantt.prototype.highest_duration = 0;\n\n\n/*\n * Appends text to the output array.\n * @param {string} text The text to append to the output.\n */\nGantt.prototype.write = function(text) {\n this.output.push(text);\n};\n\n\n/*\n * Internal helper to draw a table row showing the scale.\n * @param {number} howmany\n * @param {number} spacing\n * @param {number} scale\n */\nGantt.prototype.draw_scale = function(howmany, spacing, scale) {\n this.write('' +\n '');\n this.write('
');\n for (var i = 0; i <= howmany; i++) {\n this.write('\"\"');\n this.write('');\n this.write(' ' + (i * spacing) + ''); // TODO: number format %4g\n }\n this.write('
\\n');\n};\n\n\n/**\n * Draw the bar chart as HTML.\n */\nGantt.prototype.draw = function() {\n this.output = [];\n var scale = Gantt.compute_scale(this.highest_duration);\n var howmany = scale[0];\n var spacing = scale[1];\n var limit = scale[2];\n scale = 100.0 / limit;\n this.write('\\n');\n this.draw_scale(howmany, spacing, scale);\n for (var i = 0; i < this.bars.length; i++) {\n var bar = this.bars[i];\n this.write('\\n\\n');\n\n }\n this.draw_scale(howmany, spacing, scale);\n this.write('
');\n if (bar.label.length > 0) {\n if (bar.link_target.length > 0) {\n this.write('');\n }\n this.write(bar.label);\n if (bar.link_target.length > 0) {\n this.write('');\n }\n }\n this.write('');\n this.write('
\\n');\n\n var html = this.output.join('');\n return html;\n};\n\n\n/**\n * Add a bar to the chart.\n * All arguments representing times or durations should be integers\n * or floats expressed in seconds. The scale drawn is always\n * expressed in seconds (with limited precision).\n * @param {string} label Valid HTML or HTML-escaped text for the left column.\n * @param {number} start Start time for the event.\n * @param {number} duration Duration for the event.\n * @param {number} extra_duration Duration for the second bar; use 0 to\n * suppress.\n * @param {string} inline_label Valid HTML or HTML-escaped text drawn after the\n * bars; use '' to suppress.\n * @param {string} link_target HTML-escaped link where clicking on any element\n * will take you; use '' for no linking.\n */\nGantt.prototype.add_bar = function(label, start, duration, extra_duration,\n inline_label, link_target) {\n this.highest_duration = Math.max(\n this.highest_duration, Math.max(start + duration,\n start + extra_duration));\n this.bars.push({label: label, start: start, duration: duration,\n extra_duration: extra_duration, inline_label: inline_label,\n link_target: link_target});\n};\n\n\ngoog.exportSymbol('Gantt', Gantt);\ngoog.exportProperty(Gantt.prototype, 'add_bar', Gantt.prototype.add_bar);\ngoog.exportProperty(Gantt.prototype, 'draw', Gantt.prototype.draw);\n") 10 | 11 | var minus_gif = []byte("GIF89a\f\x00\f\x00\xc4\x1f\x00\xcf\xd1\xd8\xf1\xf3\xf7\xc9\xcc\xd3\xef\xf1\xf5\xd9\xdb\xe1\xd7\xd9\xdfp\u007f\xba\xee\xf0\xf4\xd8\xda\xe0\xcc\xce\xd5\xfc\xfc\xfc\xfd\xfd\xfd\xf3\xf5\xf7\xe0\xe2\xe7\xf8\xf8\xf8\xe5\xe7\xeb\xd6\xd9\xde\xe8\xe9\xed\xe8\xe9\xee\xdc\xdf\xe5\xcb\xce\xd5\xf0\xf1\xf3\xf7\xf8\xfa\xd5\xd8\xdd\xe2\xe4\xe8\xec\xee\xf0\xe7\xe9훥\xcfUf\xad\x00\x00\xcc\xff\xff\xff\xff\xff\xff!\xf9\x04\x01\x00\x00\x1f\x00,\x00\x00\x00\x00\f\x00\f\x00\x00\x05D\xe0\xb7qdIn\"\xe7\xad\xec\xca\x19j+\x93r\xbbе\xa78\xa4\xd5\xfd\xc0_\x85\xc4\b\x063\xa4\xc3`\x19h\x1e2\x11\x92f:\x95h\x1e\x98\x06iB\xe8\"\x10\x05ȥ\xc0\x19\x01\xce\x14\x81:\xf1J\x99J\xa8\x10\x00;") 12 | 13 | var pix_gif = []byte("GIF89a\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;") 14 | 15 | var plus_gif = []byte("GIF89a\f\x00\f\x00\xc4\x1f\x00\xee\xf0\xf4p\u007f\xba\xd8\xda\xe0\xd7\xd9\xdf\xeb\xed\xf1\xe8\xe9\xee\xec\xee\xf0\xe2\xe4\xe8\xe0\xe2\xe7\xd9\xdb\xe1\xf3\xf5\xf7\xe8\xe9\xed\xf0\xf1\xf3\xf8\xf8\xf8\xcb\xce\xd5\xf7\xf8\xfa\xfd\xfd\xfd\xd6\xd9\xde\xe5\xe7\xeb\xdc\xdf\xe5\xcc\xce\xd5\xcf\xd1\xd8\xd5\xd8\xdd\xef\xf1\xf5\xf1\xf3\xf7\xe7\xe9\xed\xc9\xccӛ\xa5\xcf\x00\x00\xcc\xff\xff\xffUf\xad\xff\xff\xff!\xf9\x04\x01\x00\x00\x1f\x00,\x00\x00\x00\x00\f\x00\f\x00\x00\x05H\xe0\xb7ydIn\xa2\u05ed\xec\xea\x05*\xcbq-\xd9\xce,d\xcbt\a5\xa4\xc7l8d\x90\x14D\xa2\x81\x04\xb8`03\f\x80\xb0 e\xae\x85YFr@\x90&\t\x81`6\x88X\x06\x9eQši\xb7)\xaf\x94\xa9\x84\n\x01\x00;") --------------------------------------------------------------------------------