123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- // Copyright 2017, OpenCensus Authors
- //
- // 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.
- //
- package zpages
- import (
- "fmt"
- "io"
- "log"
- "math"
- "net/http"
- "sort"
- "sync"
- "text/tabwriter"
- "time"
- "go.opencensus.io/plugin/ocgrpc"
- "go.opencensus.io/stats/view"
- )
- const bytesPerKb = 1024
- var (
- programStartTime = time.Now()
- mu sync.Mutex // protects snaps
- snaps = make(map[methodKey]*statSnapshot)
- // viewType lists the views we are interested in for RPC stats.
- // A view's map value indicates whether that view contains data for received
- // RPCs.
- viewType = map[*view.View]bool{
- ocgrpc.ClientCompletedRPCsView: false,
- ocgrpc.ClientSentBytesPerRPCView: false,
- ocgrpc.ClientSentMessagesPerRPCView: false,
- ocgrpc.ClientReceivedBytesPerRPCView: false,
- ocgrpc.ClientReceivedMessagesPerRPCView: false,
- ocgrpc.ClientRoundtripLatencyView: false,
- ocgrpc.ServerCompletedRPCsView: true,
- ocgrpc.ServerReceivedBytesPerRPCView: true,
- ocgrpc.ServerReceivedMessagesPerRPCView: true,
- ocgrpc.ServerSentBytesPerRPCView: true,
- ocgrpc.ServerSentMessagesPerRPCView: true,
- ocgrpc.ServerLatencyView: true,
- }
- )
- func registerRPCViews() {
- views := make([]*view.View, 0, len(viewType))
- for v := range viewType {
- views = append(views, v)
- }
- if err := view.Register(views...); err != nil {
- log.Printf("error subscribing to views: %v", err)
- }
- view.RegisterExporter(snapExporter{})
- }
- func rpczHandler(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- WriteHTMLRpczPage(w)
- }
- // WriteHTMLRpczPage writes an HTML document to w containing per-method RPC stats.
- func WriteHTMLRpczPage(w io.Writer) {
- if err := headerTemplate.Execute(w, headerData{Title: "RPC Stats"}); err != nil {
- log.Printf("zpages: executing template: %v", err)
- }
- WriteHTMLRpczSummary(w)
- if err := footerTemplate.Execute(w, nil); err != nil {
- log.Printf("zpages: executing template: %v", err)
- }
- }
- // WriteHTMLRpczSummary writes HTML to w containing per-method RPC stats.
- //
- // It includes neither a header nor footer, so you can embed this data in other pages.
- func WriteHTMLRpczSummary(w io.Writer) {
- mu.Lock()
- if err := statsTemplate.Execute(w, getStatsPage()); err != nil {
- log.Printf("zpages: executing template: %v", err)
- }
- mu.Unlock()
- }
- // WriteTextRpczPage writes formatted text to w containing per-method RPC stats.
- func WriteTextRpczPage(w io.Writer) {
- mu.Lock()
- defer mu.Unlock()
- page := getStatsPage()
- for i, sg := range page.StatGroups {
- switch i {
- case 0:
- fmt.Fprint(w, "Sent:\n")
- case 1:
- fmt.Fprint(w, "\nReceived:\n")
- }
- tw := tabwriter.NewWriter(w, 6, 8, 1, ' ', 0)
- fmt.Fprint(tw, "Method\tCount\t\t\tAvgLat\t\t\tMaxLat\t\t\tRate\t\t\tIn (MiB/s)\t\t\tOut (MiB/s)\t\t\tErrors\t\t\n")
- fmt.Fprint(tw, "\tMin\tHr\tTot\tMin\tHr\tTot\tMin\tHr\tTot\tMin\tHr\tTot\tMin\tHr\tTot\tMin\tHr\tTot\tMin\tHr\tTot\n")
- for _, s := range sg.Snapshots {
- fmt.Fprintf(tw, "%s\t%d\t%d\t%d\t%v\t%v\t%v\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%d\t%d\t%d\n",
- s.Method,
- s.CountMinute,
- s.CountHour,
- s.CountTotal,
- s.AvgLatencyMinute,
- s.AvgLatencyHour,
- s.AvgLatencyTotal,
- s.RPCRateMinute,
- s.RPCRateHour,
- s.RPCRateTotal,
- s.InputRateMinute/bytesPerKb,
- s.InputRateHour/bytesPerKb,
- s.InputRateTotal/bytesPerKb,
- s.OutputRateMinute/bytesPerKb,
- s.OutputRateHour/bytesPerKb,
- s.OutputRateTotal/bytesPerKb,
- s.ErrorsMinute,
- s.ErrorsHour,
- s.ErrorsTotal)
- }
- tw.Flush()
- }
- }
- // headerData contains data for the header template.
- type headerData struct {
- Title string
- }
- // statsPage aggregates stats on the page for 'sent' and 'received' categories
- type statsPage struct {
- StatGroups []*statGroup
- }
- // statGroup aggregates snapshots for a directional category
- type statGroup struct {
- Direction string
- Snapshots []*statSnapshot
- }
- func (s *statGroup) Len() int {
- return len(s.Snapshots)
- }
- func (s *statGroup) Swap(i, j int) {
- s.Snapshots[i], s.Snapshots[j] = s.Snapshots[j], s.Snapshots[i]
- }
- func (s *statGroup) Less(i, j int) bool {
- return s.Snapshots[i].Method < s.Snapshots[j].Method
- }
- // statSnapshot holds the data items that are presented in a single row of RPC
- // stat information.
- type statSnapshot struct {
- // TODO: compute hour/minute values from cumulative
- Method string
- Received bool
- CountMinute uint64
- CountHour uint64
- CountTotal uint64
- AvgLatencyMinute time.Duration
- AvgLatencyHour time.Duration
- AvgLatencyTotal time.Duration
- RPCRateMinute float64
- RPCRateHour float64
- RPCRateTotal float64
- InputRateMinute float64
- InputRateHour float64
- InputRateTotal float64
- OutputRateMinute float64
- OutputRateHour float64
- OutputRateTotal float64
- ErrorsMinute uint64
- ErrorsHour uint64
- ErrorsTotal uint64
- }
- type methodKey struct {
- method string
- received bool
- }
- type snapExporter struct{}
- func (s snapExporter) ExportView(vd *view.Data) {
- received, ok := viewType[vd.View]
- if !ok {
- return
- }
- if len(vd.Rows) == 0 {
- return
- }
- ageSec := float64(time.Now().Sub(programStartTime)) / float64(time.Second)
- computeRate := func(maxSec, x float64) float64 {
- dur := ageSec
- if maxSec > 0 && dur > maxSec {
- dur = maxSec
- }
- return x / dur
- }
- convertTime := func(ms float64) time.Duration {
- if math.IsInf(ms, 0) || math.IsNaN(ms) {
- return 0
- }
- return time.Duration(float64(time.Millisecond) * ms)
- }
- haveResetErrors := make(map[string]struct{})
- mu.Lock()
- defer mu.Unlock()
- for _, row := range vd.Rows {
- var method string
- for _, tag := range row.Tags {
- if tag.Key == ocgrpc.KeyClientMethod || tag.Key == ocgrpc.KeyServerMethod {
- method = tag.Value
- break
- }
- }
- key := methodKey{method: method, received: received}
- s := snaps[key]
- if s == nil {
- s = &statSnapshot{Method: method, Received: received}
- snaps[key] = s
- }
- var (
- sum float64
- count float64
- )
- switch v := row.Data.(type) {
- case *view.CountData:
- sum = float64(v.Value)
- count = float64(v.Value)
- case *view.DistributionData:
- sum = v.Sum()
- count = float64(v.Count)
- case *view.SumData:
- sum = v.Value
- count = v.Value
- }
- // Update field of s corresponding to the view.
- switch vd.View {
- case ocgrpc.ClientCompletedRPCsView:
- if _, ok := haveResetErrors[method]; !ok {
- haveResetErrors[method] = struct{}{}
- s.ErrorsTotal = 0
- }
- for _, tag := range row.Tags {
- if tag.Key == ocgrpc.KeyClientStatus && tag.Value != "OK" {
- s.ErrorsTotal += uint64(count)
- }
- }
- case ocgrpc.ClientRoundtripLatencyView:
- s.AvgLatencyTotal = convertTime(sum / count)
- case ocgrpc.ClientSentBytesPerRPCView:
- s.OutputRateTotal = computeRate(0, sum)
- case ocgrpc.ClientReceivedBytesPerRPCView:
- s.InputRateTotal = computeRate(0, sum)
- case ocgrpc.ClientSentMessagesPerRPCView:
- s.CountTotal = uint64(count)
- s.RPCRateTotal = computeRate(0, count)
- case ocgrpc.ClientReceivedMessagesPerRPCView:
- // currently unused
- case ocgrpc.ServerCompletedRPCsView:
- if _, ok := haveResetErrors[method]; !ok {
- haveResetErrors[method] = struct{}{}
- s.ErrorsTotal = 0
- }
- for _, tag := range row.Tags {
- if tag.Key == ocgrpc.KeyServerStatus && tag.Value != "OK" {
- s.ErrorsTotal += uint64(count)
- }
- }
- case ocgrpc.ServerLatencyView:
- s.AvgLatencyTotal = convertTime(sum / count)
- case ocgrpc.ServerSentBytesPerRPCView:
- s.OutputRateTotal = computeRate(0, sum)
- case ocgrpc.ServerReceivedMessagesPerRPCView:
- s.CountTotal = uint64(count)
- s.RPCRateTotal = computeRate(0, count)
- case ocgrpc.ServerSentMessagesPerRPCView:
- // currently unused
- }
- }
- }
- func getStatsPage() *statsPage {
- sentStats := statGroup{Direction: "Sent"}
- receivedStats := statGroup{Direction: "Received"}
- for key, sg := range snaps {
- if key.received {
- receivedStats.Snapshots = append(receivedStats.Snapshots, sg)
- } else {
- sentStats.Snapshots = append(sentStats.Snapshots, sg)
- }
- }
- sort.Sort(&sentStats)
- sort.Sort(&receivedStats)
- return &statsPage{
- StatGroups: []*statGroup{&sentStats, &receivedStats},
- }
- }
|