mirror of
https://github.com/weaveworks/scope.git
synced 2026-05-08 18:26:58 +00:00
Upgraded from 99c19923, branch release-3.0. This required fetching or upgrading the following: * k8s.io/api to kubernetes-1.9.1 * k8s.io/apimachinery to kubernetes-1.9.1 * github.com/juju/ratelimit to 1.0.1 * github.com/spf13/pflag to 4c012f6d Also, update Scope's imports/function calls to be compatible with the new client.
355 lines
8.1 KiB
Go
355 lines
8.1 KiB
Go
/*
|
|
* Copyright (c) 2013 Matt Jibson <matt.jibson@gmail.com>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
package appstats
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"fmt"
|
|
"html/template"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
"google.golang.org/appengine"
|
|
"google.golang.org/appengine/memcache"
|
|
"google.golang.org/appengine/user"
|
|
)
|
|
|
|
var templates *template.Template
|
|
var staticFiles map[string][]byte
|
|
|
|
func init() {
|
|
templates = template.New("appstats").Funcs(funcs)
|
|
templates.Parse(htmlBase)
|
|
templates.Parse(htmlMain)
|
|
templates.Parse(htmlDetails)
|
|
templates.Parse(htmlFile)
|
|
|
|
staticFiles = map[string][]byte{
|
|
"app_engine_logo_sm.gif": app_engine_logo_sm_gif,
|
|
"appstats_css.css": appstats_css_css,
|
|
"appstats_js.js": appstats_js_js,
|
|
"gantt.js": gantt_js,
|
|
"minus.gif": minus_gif,
|
|
"pix.gif": pix_gif,
|
|
"plus.gif": plus_gif,
|
|
}
|
|
}
|
|
|
|
func serveError(w http.ResponseWriter, err error) {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
func appstatsHandler(w http.ResponseWriter, r *http.Request) {
|
|
c := storeContext(appengine.NewContext(r))
|
|
if appengine.IsDevAppServer() {
|
|
// noop
|
|
} else if u := user.Current(c); u == nil {
|
|
if loginURL, err := user.LoginURL(c, r.URL.String()); err == nil {
|
|
http.Redirect(w, r, loginURL, http.StatusTemporaryRedirect)
|
|
} else {
|
|
serveError(w, err)
|
|
}
|
|
return
|
|
} else if !u.Admin {
|
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
if detailsURL == r.URL.Path {
|
|
details(c, w, r)
|
|
} else if fileURL == r.URL.Path {
|
|
file(c, w, r)
|
|
} else if strings.HasPrefix(r.URL.Path, staticURL) {
|
|
static(w, r)
|
|
} else {
|
|
index(c, w, r)
|
|
}
|
|
}
|
|
|
|
func index(c context.Context, w http.ResponseWriter, r *http.Request) {
|
|
keys := make([]string, modulus)
|
|
for i := range keys {
|
|
keys[i] = fmt.Sprintf(keyPart, i*distance)
|
|
}
|
|
|
|
items, err := memcache.GetMulti(c, keys)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
ars := allrequestStats{}
|
|
for _, v := range items {
|
|
t := stats_part{}
|
|
err := gob.NewDecoder(bytes.NewBuffer(v.Value)).Decode(&t)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
r := requestStats(t)
|
|
ars = append(ars, &r)
|
|
}
|
|
sort.Sort(reverse{ars})
|
|
|
|
requestById := make(map[int]*requestStats, len(ars))
|
|
idByRequest := make(map[*requestStats]int, len(ars))
|
|
requests := make(map[int]*statByName)
|
|
byRequest := make(map[int]map[string]cVal)
|
|
for i, v := range ars {
|
|
idx := i + 1
|
|
requestById[idx] = v
|
|
idByRequest[v] = idx
|
|
requests[idx] = &statByName{
|
|
RequestStats: v,
|
|
}
|
|
byRequest[idx] = make(map[string]cVal)
|
|
}
|
|
|
|
requestByPath := make(map[string][]int)
|
|
byCount := make(map[string]cVal)
|
|
byRPC := make(map[skey]cVal)
|
|
for _, t := range ars {
|
|
id := idByRequest[t]
|
|
|
|
requestByPath[t.Path] = append(requestByPath[t.Path], id)
|
|
|
|
for _, r := range t.RPCStats {
|
|
rpc := r.Name()
|
|
|
|
v := byRequest[id][rpc]
|
|
v.count++
|
|
v.cost += r.Cost
|
|
byRequest[id][rpc] = v
|
|
|
|
v = byCount[rpc]
|
|
v.count++
|
|
v.cost += r.Cost
|
|
byCount[rpc] = v
|
|
|
|
v = byRPC[skey{rpc, t.Path}]
|
|
v.count++
|
|
v.cost += r.Cost
|
|
byRPC[skey{rpc, t.Path}] = v
|
|
}
|
|
}
|
|
|
|
for k, v := range byRequest {
|
|
stats := statsByName{}
|
|
for rpc, s := range v {
|
|
stats = append(stats, &statByName{
|
|
Name: rpc,
|
|
Count: s.count,
|
|
Cost: s.cost,
|
|
})
|
|
}
|
|
sort.Sort(reverse{stats})
|
|
requests[k].SubStats = stats
|
|
}
|
|
|
|
statsByRPC := make(map[string]statsByName)
|
|
pathStats := make(map[string]statsByName)
|
|
for k, v := range byRPC {
|
|
statsByRPC[k.a] = append(statsByRPC[k.a], &statByName{
|
|
Name: k.b,
|
|
Count: v.count,
|
|
Cost: v.cost,
|
|
})
|
|
pathStats[k.b] = append(pathStats[k.b], &statByName{
|
|
Name: k.a,
|
|
Count: v.count,
|
|
Cost: v.cost,
|
|
})
|
|
}
|
|
for k, v := range statsByRPC {
|
|
sort.Sort(reverse{v})
|
|
statsByRPC[k] = v
|
|
}
|
|
|
|
pathStatsByCount := statsByName{}
|
|
for k, v := range pathStats {
|
|
total := 0
|
|
var cost int64
|
|
for _, stat := range v {
|
|
total += stat.Count
|
|
cost += stat.Cost
|
|
}
|
|
sort.Sort(reverse{v})
|
|
|
|
pathStatsByCount = append(pathStatsByCount, &statByName{
|
|
Name: k,
|
|
Count: total,
|
|
Cost: cost,
|
|
SubStats: v,
|
|
Requests: len(requestByPath[k]),
|
|
RecentReqs: requestByPath[k],
|
|
})
|
|
}
|
|
sort.Sort(reverse{pathStatsByCount})
|
|
|
|
allStatsByCount := statsByName{}
|
|
for k, v := range byCount {
|
|
allStatsByCount = append(allStatsByCount, &statByName{
|
|
Name: k,
|
|
Count: v.count,
|
|
Cost: v.cost,
|
|
SubStats: statsByRPC[k],
|
|
})
|
|
}
|
|
sort.Sort(reverse{allStatsByCount})
|
|
|
|
v := struct {
|
|
Env map[string]string
|
|
Requests map[int]*statByName
|
|
RequestStatsByCount map[int]*statByName
|
|
AllStatsByCount statsByName
|
|
PathStatsByCount statsByName
|
|
}{
|
|
Env: map[string]string{
|
|
"APPLICATION_ID": appengine.AppID(c),
|
|
},
|
|
Requests: requests,
|
|
AllStatsByCount: allStatsByCount,
|
|
PathStatsByCount: pathStatsByCount,
|
|
}
|
|
|
|
_ = templates.ExecuteTemplate(w, "main", v)
|
|
}
|
|
|
|
func details(c context.Context, w http.ResponseWriter, r *http.Request) {
|
|
i, _ := strconv.Atoi(r.FormValue("time"))
|
|
qtime := roundTime(i)
|
|
key := fmt.Sprintf(keyFull, qtime)
|
|
|
|
v := struct {
|
|
Env map[string]string
|
|
Record *requestStats
|
|
Header http.Header
|
|
AllStatsByCount statsByName
|
|
Real time.Duration
|
|
}{
|
|
Env: map[string]string{
|
|
"APPLICATION_ID": appengine.AppID(c),
|
|
},
|
|
}
|
|
|
|
item, err := memcache.Get(c, key)
|
|
if err != nil {
|
|
templates.ExecuteTemplate(w, "details", v)
|
|
return
|
|
}
|
|
|
|
full := stats_full{}
|
|
err = gob.NewDecoder(bytes.NewBuffer(item.Value)).Decode(&full)
|
|
if err != nil {
|
|
templates.ExecuteTemplate(w, "details", v)
|
|
return
|
|
}
|
|
|
|
byCount := make(map[string]cVal)
|
|
durationCount := make(map[string]time.Duration)
|
|
var _real time.Duration
|
|
for _, r := range full.Stats.RPCStats {
|
|
rpc := r.Name()
|
|
|
|
// byCount
|
|
if _, present := byCount[rpc]; !present {
|
|
durationCount[rpc] = 0
|
|
}
|
|
v := byCount[rpc]
|
|
v.count++
|
|
v.cost += r.Cost
|
|
byCount[rpc] = v
|
|
durationCount[rpc] += r.Duration
|
|
_real += r.Duration
|
|
}
|
|
|
|
allStatsByCount := statsByName{}
|
|
for k, v := range byCount {
|
|
allStatsByCount = append(allStatsByCount, &statByName{
|
|
Name: k,
|
|
Count: v.count,
|
|
Cost: v.cost,
|
|
Duration: durationCount[k],
|
|
})
|
|
}
|
|
sort.Sort(allStatsByCount)
|
|
|
|
v.Record = full.Stats
|
|
v.Header = full.Header
|
|
v.AllStatsByCount = allStatsByCount
|
|
v.Real = _real
|
|
|
|
_ = templates.ExecuteTemplate(w, "details", v)
|
|
}
|
|
|
|
func file(c context.Context, w http.ResponseWriter, r *http.Request) {
|
|
fname := r.URL.Query().Get("f")
|
|
n := r.URL.Query().Get("n")
|
|
lineno, _ := strconv.Atoi(n)
|
|
|
|
f, err := ioutil.ReadFile(fname)
|
|
if err != nil {
|
|
serveError(w, err)
|
|
return
|
|
}
|
|
|
|
fp := make(map[int]string)
|
|
for k, v := range strings.Split(string(f), "\n") {
|
|
fp[k+1] = v
|
|
}
|
|
|
|
v := struct {
|
|
Env map[string]string
|
|
Filename string
|
|
Lineno int
|
|
Fp map[int]string
|
|
}{
|
|
Env: map[string]string{
|
|
"APPLICATION_ID": appengine.AppID(c),
|
|
},
|
|
Filename: fname,
|
|
Lineno: lineno,
|
|
Fp: fp,
|
|
}
|
|
|
|
_ = templates.ExecuteTemplate(w, "file", v)
|
|
}
|
|
|
|
func static(w http.ResponseWriter, r *http.Request) {
|
|
fname := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
|
|
if v, present := staticFiles[fname]; present {
|
|
h := w.Header()
|
|
|
|
if strings.HasSuffix(r.URL.Path, ".css") {
|
|
h.Set("Content-type", "text/css")
|
|
} else if strings.HasSuffix(r.URL.Path, ".js") {
|
|
h.Set("Content-type", "text/javascript")
|
|
}
|
|
|
|
h.Set("Cache-Control", "public, max-age=expiry")
|
|
expires := time.Now().Add(time.Hour)
|
|
h.Set("Expires", expires.Format(time.RFC1123))
|
|
|
|
w.Write(v)
|
|
}
|
|
}
|