blob: 39234bb4e600b2488fca54fb427a6d0fca3ff5d0 [file] [log] [blame]
package plugin
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import (
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/apache/trafficcontrol/grove/web"
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-rfc"
"code.cloudfoundry.org/bytefmt"
)
func init() {
AddPlugin(10000, Funcs{onRequest: cacheinspect})
}
// CacheStatsEndpoint is our reserved path
const CacheStatsEndpoint = "/_cacheinspect"
func writeHTMLPageHeader(w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Grove Cache Inspector</title>
</head>
<body>
<pre>
`))
}
func writeHTMLPageFooter(w http.ResponseWriter) {
w.Write([]byte(`
</pre>
</body>
</html>
`))
}
func cacheinspect(icfg interface{}, d OnRequestData) bool {
if !strings.HasPrefix(d.R.URL.Path, CacheStatsEndpoint) {
log.Debugf("plugin onrequest http_cacheinspect returning, not in path '" + d.R.URL.Path + "'\n")
return false
}
log.Debugf("plugin onrequest http_cacheinspect calling\n")
reqTime := time.Now()
w := d.W
req := d.R
// TODO access log? Stats byte count?
ip, err := web.GetIP(req)
if err != nil {
code := http.StatusInternalServerError
w.WriteHeader(code)
w.Write([]byte(http.StatusText(code)))
log.Errorln("statHandler ServeHTTP failed to get IP: " + ip.String())
return true
}
if !d.StatRules.Allowed(ip) {
code := http.StatusForbidden
w.WriteHeader(code)
w.Write([]byte(http.StatusText(code)))
log.Debugln("statHandler.ServeHTTP IP " + ip.String() + " FORBIDDEN") // TODO event?
return true
}
respCode := http.StatusOK
w.WriteHeader(respCode)
writeHTMLPageHeader(w)
qstringOptions := req.URL.Query()
// The default cache = "", which is the default memcache
cacheToDisplay := ""
showSelectCache := false
if cNameArr, cNamePresent := qstringOptions["cache"]; cNamePresent {
showSelectCache = true
cacheToDisplay = cNameArr[0]
}
if keyArr, showKey := qstringOptions["key"]; showKey {
hLine := fmt.Sprintf("Key: %s cache: \"%s\"\n\n", keyArr[0], cacheToDisplay)
w.Write([]byte(hLine))
if cacheObject, ok := d.Stats.CachePeek(keyArr[0], cacheToDisplay); ok {
for k, v := range cacheObject.ReqHeaders {
w.Write([]byte(fmt.Sprintf(" > %s: %s\n", k, strings.Join(v, ","))))
}
w.Write([]byte("\n"))
for k, v := range cacheObject.RespHeaders {
w.Write([]byte(fmt.Sprintf(" < %s: %s\n", k, strings.Join(v, ","))))
}
w.Write([]byte("\n"))
w.Write([]byte(fmt.Sprintf(" Code: %d\n", cacheObject.Code)))
w.Write([]byte(fmt.Sprintf(" OriginCode: %d\n", cacheObject.OriginCode)))
w.Write([]byte(fmt.Sprintf(" ProxyURL: %s\n", cacheObject.ProxyURL)))
w.Write([]byte(fmt.Sprintf(" ReqTime: %v\n", cacheObject.ReqTime)))
w.Write([]byte(fmt.Sprintf(" ReqRespTime: %v\n", cacheObject.ReqRespTime)))
w.Write([]byte(fmt.Sprintf(" RespRespTime: %v\n", cacheObject.RespRespTime)))
w.Write([]byte(fmt.Sprintf(" LastModified: %v\n", cacheObject.LastModified)))
w.Write([]byte(fmt.Sprintf(" HitCount: %v\n", cacheObject.HitCount)))
} else {
w.Write([]byte("Not Found"))
}
} else {
searchArr, doSearch := qstringOptions["search"]
cacheNames := d.Stats.CacheNames()
sort.Strings(cacheNames)
w.Write([]byte(fmt.Sprintf("Jump to:")))
for _, cName := range cacheNames {
w.Write([]byte(fmt.Sprintf("<a href=#%s>%s</a> ", cName, cName)))
}
w.Write([]byte(fmt.Sprintf("\n")))
for _, cName := range cacheNames {
if showSelectCache && cName != cacheToDisplay {
continue
}
w.Write([]byte(fmt.Sprintf("<a name=%s></a>", cName)))
w.Write([]byte(fmt.Sprintf("\n\n<b>*** Cache \"%s\" ***</b>\n", cName)))
keys := d.Stats.CacheKeys(cName)
size, _ := d.Stats.CacheSizeByName(cName)
capacity, _ := d.Stats.CacheCapacityByName(cName)
w.Write([]byte(fmt.Sprintf("\n * Size of in use cache: %s \n", bytefmt.ByteSize(size))))
w.Write([]byte(fmt.Sprintf(" * Cache capacity: %s \n", bytefmt.ByteSize(capacity))))
w.Write([]byte(fmt.Sprintf(" * Number of elements in LRU: %d\n", len(keys))))
// tail is how much from the top of the LRU to display, top of the LRU is most recently used. head is the other side.
head := 100
tail := 100
tailStr, ok := qstringOptions["tail"]
if ok {
tail, err = strconv.Atoi(tailStr[0])
if err != nil {
log.Errorf("Error converting tail value to int: %v", err)
}
}
headStr, ok := qstringOptions["head"]
if ok {
head, err = strconv.Atoi(headStr[0])
if err != nil {
log.Errorf("Error converting head value to int: %v", err)
head = 100
}
}
w.Write([]byte(fmt.Sprintf(" * Objects in cache sorted by Least Recently Used on top, ")))
if doSearch {
w.Write([]byte(fmt.Sprintf("showing only strings matching %s:\n", searchArr[0])))
} else {
w.Write([]byte(fmt.Sprintf("showing only first %d and last %d:\n\n", head, tail)))
}
w.Write([]byte(fmt.Sprintf("<b> # Code Size Age FreshFor HitCount Key</b>\n")))
for i, key := range keys {
if (doSearch && !strings.Contains(key, searchArr[0])) || !doSearch && (i >= tail && i < len(keys)-head) {
continue
}
cacheObject, _ := d.Stats.CachePeek(key, cName)
age := time.Now().Sub(cacheObject.ReqRespTime)
freshFor := rfc.FreshFor(cacheObject.RespHeaders, cacheObject.RespCacheControl, cacheObject.ReqRespTime, cacheObject.RespRespTime)
w.Write([]byte(fmt.Sprintf(" %8d%8d%10s%22v%22v%12d <a href=\"http://%s%s?key=%s&cache=%s\">%s</a>\n",
i, cacheObject.Code, bytefmt.ByteSize(cacheObject.Size), age, freshFor, cacheObject.HitCount, req.Host, CacheStatsEndpoint, url.QueryEscape(key), cName, key)))
}
}
}
writeHTMLPageFooter(w)
clientIP, _ := web.GetClientIPPort(req)
now := time.Now()
// TODO add eventId?
log.EventRaw(atsEventLogStr(now, clientIP, d.Hostname, req.Host, d.Port, "-", d.Scheme, req.URL.String(), req.Method, req.Proto, respCode, now.Sub(reqTime), 0, 0, 0, true, true, getCacheHitStr(true, false), "-", "-", req.UserAgent(), req.Header.Get("X-Money-Trace"), 1))
return true
}