Source file src/cmd/trace/goroutines.go

     1  // Copyright 2014 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  // Goroutine-related profiles.
     6  
     7  package main
     8  
     9  import (
    10  	"fmt"
    11  	"html/template"
    12  	"internal/trace"
    13  	"log"
    14  	"net/http"
    15  	"reflect"
    16  	"sort"
    17  	"strconv"
    18  	"sync"
    19  	"time"
    20  )
    21  
    22  func init() {
    23  	http.HandleFunc("/goroutines", httpGoroutines)
    24  	http.HandleFunc("/goroutine", httpGoroutine)
    25  }
    26  
    27  // gtype describes a group of goroutines grouped by start PC.
    28  type gtype struct {
    29  	ID       uint64 // Unique identifier (PC).
    30  	Name     string // Start function.
    31  	N        int    // Total number of goroutines in this group.
    32  	ExecTime int64  // Total execution time of all goroutines in this group.
    33  }
    34  
    35  var (
    36  	gsInit sync.Once
    37  	gs     map[uint64]*trace.GDesc
    38  )
    39  
    40  // analyzeGoroutines generates statistics about execution of all goroutines and stores them in gs.
    41  func analyzeGoroutines(events []*trace.Event) {
    42  	gsInit.Do(func() {
    43  		gs = trace.GoroutineStats(events)
    44  	})
    45  }
    46  
    47  // httpGoroutines serves list of goroutine groups.
    48  func httpGoroutines(w http.ResponseWriter, r *http.Request) {
    49  	events, err := parseEvents()
    50  	if err != nil {
    51  		http.Error(w, err.Error(), http.StatusInternalServerError)
    52  		return
    53  	}
    54  	analyzeGoroutines(events)
    55  	gss := make(map[uint64]gtype)
    56  	for _, g := range gs {
    57  		gs1 := gss[g.PC]
    58  		gs1.ID = g.PC
    59  		gs1.Name = g.Name
    60  		gs1.N++
    61  		gs1.ExecTime += g.ExecTime
    62  		gss[g.PC] = gs1
    63  	}
    64  	var glist []gtype
    65  	for k, v := range gss {
    66  		v.ID = k
    67  		glist = append(glist, v)
    68  	}
    69  	sort.Slice(glist, func(i, j int) bool { return glist[i].ExecTime > glist[j].ExecTime })
    70  	w.Header().Set("Content-Type", "text/html;charset=utf-8")
    71  	if err := templGoroutines.Execute(w, glist); err != nil {
    72  		log.Printf("failed to execute template: %v", err)
    73  		return
    74  	}
    75  }
    76  
    77  var templGoroutines = template.Must(template.New("").Parse(`
    78  <html>
    79  <body>
    80  Goroutines: <br>
    81  {{range $}}
    82    <a href="/goroutine?id={{.ID}}">{{.Name}}</a> N={{.N}} <br>
    83  {{end}}
    84  </body>
    85  </html>
    86  `))
    87  
    88  // httpGoroutine serves list of goroutines in a particular group.
    89  func httpGoroutine(w http.ResponseWriter, r *http.Request) {
    90  	// TODO(hyangah): support format=csv (raw data)
    91  
    92  	events, err := parseEvents()
    93  	if err != nil {
    94  		http.Error(w, err.Error(), http.StatusInternalServerError)
    95  		return
    96  	}
    97  
    98  	pc, err := strconv.ParseUint(r.FormValue("id"), 10, 64)
    99  	if err != nil {
   100  		http.Error(w, fmt.Sprintf("failed to parse id parameter '%v': %v", r.FormValue("id"), err), http.StatusInternalServerError)
   101  		return
   102  	}
   103  	analyzeGoroutines(events)
   104  	var (
   105  		glist                   []*trace.GDesc
   106  		name                    string
   107  		totalExecTime, execTime int64
   108  		maxTotalTime            int64
   109  	)
   110  
   111  	for _, g := range gs {
   112  		totalExecTime += g.ExecTime
   113  
   114  		if g.PC != pc {
   115  			continue
   116  		}
   117  		glist = append(glist, g)
   118  		name = g.Name
   119  		execTime += g.ExecTime
   120  		if maxTotalTime < g.TotalTime {
   121  			maxTotalTime = g.TotalTime
   122  		}
   123  	}
   124  
   125  	execTimePercent := ""
   126  	if totalExecTime > 0 {
   127  		execTimePercent = fmt.Sprintf("%.2f%%", float64(execTime)/float64(totalExecTime)*100)
   128  	}
   129  
   130  	sortby := r.FormValue("sortby")
   131  	_, ok := reflect.TypeOf(trace.GDesc{}).FieldByNameFunc(func(s string) bool {
   132  		return s == sortby
   133  	})
   134  	if !ok {
   135  		sortby = "TotalTime"
   136  	}
   137  
   138  	sort.Slice(glist, func(i, j int) bool {
   139  		ival := reflect.ValueOf(glist[i]).Elem().FieldByName(sortby).Int()
   140  		jval := reflect.ValueOf(glist[j]).Elem().FieldByName(sortby).Int()
   141  		return ival > jval
   142  	})
   143  
   144  	err = templGoroutine.Execute(w, struct {
   145  		Name            string
   146  		PC              uint64
   147  		N               int
   148  		ExecTimePercent string
   149  		MaxTotal        int64
   150  		GList           []*trace.GDesc
   151  	}{
   152  		Name:            name,
   153  		PC:              pc,
   154  		N:               len(glist),
   155  		ExecTimePercent: execTimePercent,
   156  		MaxTotal:        maxTotalTime,
   157  		GList:           glist})
   158  	if err != nil {
   159  		http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
   160  		return
   161  	}
   162  }
   163  
   164  var templGoroutine = template.Must(template.New("").Funcs(template.FuncMap{
   165  	"prettyDuration": func(nsec int64) template.HTML {
   166  		d := time.Duration(nsec) * time.Nanosecond
   167  		return template.HTML(niceDuration(d))
   168  	},
   169  	"percent": func(dividend, divisor int64) template.HTML {
   170  		if divisor == 0 {
   171  			return ""
   172  		}
   173  		return template.HTML(fmt.Sprintf("(%.1f%%)", float64(dividend)/float64(divisor)*100))
   174  	},
   175  	"barLen": func(dividend, divisor int64) template.HTML {
   176  		if divisor == 0 {
   177  			return "0"
   178  		}
   179  		return template.HTML(fmt.Sprintf("%.2f%%", float64(dividend)/float64(divisor)*100))
   180  	},
   181  	"unknownTime": func(desc *trace.GDesc) int64 {
   182  		sum := desc.ExecTime + desc.IOTime + desc.BlockTime + desc.SyscallTime + desc.SchedWaitTime
   183  		if sum < desc.TotalTime {
   184  			return desc.TotalTime - sum
   185  		}
   186  		return 0
   187  	},
   188  }).Parse(`
   189  <!DOCTYPE html>
   190  <title>Goroutine {{.Name}}</title>
   191  <style>
   192  th {
   193    background-color: #050505;
   194    color: #fff;
   195  }
   196  th.total-time,
   197  th.exec-time,
   198  th.io-time,
   199  th.block-time,
   200  th.syscall-time,
   201  th.sched-time,
   202  th.sweep-time,
   203  th.pause-time {
   204    cursor: pointer;
   205  }
   206  table {
   207    border-collapse: collapse;
   208  }
   209  .details tr:hover {
   210    background-color: #f2f2f2;
   211  }
   212  .details td {
   213    text-align: right;
   214    border: 1px solid black;
   215  }
   216  .details td.id {
   217    text-align: left;
   218  }
   219  .stacked-bar-graph {
   220    width: 300px;
   221    height: 10px;
   222    color: #414042;
   223    white-space: nowrap;
   224    font-size: 5px;
   225  }
   226  .stacked-bar-graph span {
   227    display: inline-block;
   228    width: 100%;
   229    height: 100%;
   230    box-sizing: border-box;
   231    float: left;
   232    padding: 0;
   233  }
   234  .unknown-time { background-color: #636363; }
   235  .exec-time { background-color: #d7191c; }
   236  .io-time { background-color: #fdae61; }
   237  .block-time { background-color: #d01c8b; }
   238  .syscall-time { background-color: #7b3294; }
   239  .sched-time { background-color: #2c7bb6; }
   240  </style>
   241  
   242  <script>
   243  function reloadTable(key, value) {
   244    let params = new URLSearchParams(window.location.search);
   245    params.set(key, value);
   246    window.location.search = params.toString();
   247  }
   248  </script>
   249  
   250  <table class="summary">
   251  	<tr><td>Goroutine Name:</td><td>{{.Name}}</td></tr>
   252  	<tr><td>Number of Goroutines:</td><td>{{.N}}</td></tr>
   253  	<tr><td>Execution Time:</td><td>{{.ExecTimePercent}} of total program execution time </td> </tr>
   254  	<tr><td>Network Wait Time:</td><td> <a href="/io?id={{.PC}}">graph</a><a href="/io?id={{.PC}}&raw=1" download="io.profile">(download)</a></td></tr>
   255  	<tr><td>Sync Block Time:</td><td> <a href="/block?id={{.PC}}">graph</a><a href="/block?id={{.PC}}&raw=1" download="block.profile">(download)</a></td></tr>
   256  	<tr><td>Blocking Syscall Time:</td><td> <a href="/syscall?id={{.PC}}">graph</a><a href="/syscall?id={{.PC}}&raw=1" download="syscall.profile">(download)</a></td></tr>
   257  	<tr><td>Scheduler Wait Time:</td><td> <a href="/sched?id={{.PC}}">graph</a><a href="/sched?id={{.PC}}&raw=1" download="sched.profile">(download)</a></td></tr>
   258  </table>
   259  <p>
   260  <table class="details">
   261  <tr>
   262  <th> Goroutine</th>
   263  <th onclick="reloadTable('sortby', 'TotalTime')" class="total-time"> Total</th>
   264  <th></th>
   265  <th onclick="reloadTable('sortby', 'ExecTime')" class="exec-time"> Execution</th>
   266  <th onclick="reloadTable('sortby', 'IOTime')" class="io-time"> Network wait</th>
   267  <th onclick="reloadTable('sortby', 'BlockTime')" class="block-time"> Sync block </th>
   268  <th onclick="reloadTable('sortby', 'SyscallTime')" class="syscall-time"> Blocking syscall</th>
   269  <th onclick="reloadTable('sortby', 'SchedWaitTime')" class="sched-time"> Scheduler wait</th>
   270  <th onclick="reloadTable('sortby', 'SweepTime')" class="sweep-time"> GC sweeping</th>
   271  <th onclick="reloadTable('sortby', 'GCTime')" class="pause-time"> GC pause</th>
   272  </tr>
   273  {{range .GList}}
   274    <tr>
   275      <td> <a href="/trace?goid={{.ID}}">{{.ID}}</a> </td>
   276      <td> {{prettyDuration .TotalTime}} </td>
   277      <td>
   278  	<div class="stacked-bar-graph">
   279  	  {{if unknownTime .}}<span style="width:{{barLen (unknownTime .) $.MaxTotal}}" class="unknown-time">&nbsp;</span>{{end}}
   280            {{if .ExecTime}}<span style="width:{{barLen .ExecTime $.MaxTotal}}" class="exec-time">&nbsp;</span>{{end}}
   281            {{if .IOTime}}<span style="width:{{barLen .IOTime $.MaxTotal}}" class="io-time">&nbsp;</span>{{end}}
   282            {{if .BlockTime}}<span style="width:{{barLen .BlockTime $.MaxTotal}}" class="block-time">&nbsp;</span>{{end}}
   283            {{if .SyscallTime}}<span style="width:{{barLen .SyscallTime $.MaxTotal}}" class="syscall-time">&nbsp;</span>{{end}}
   284            {{if .SchedWaitTime}}<span style="width:{{barLen .SchedWaitTime $.MaxTotal}}" class="sched-time">&nbsp;</span>{{end}}
   285          </div>
   286      </td>
   287      <td> {{prettyDuration .ExecTime}}</td>
   288      <td> {{prettyDuration .IOTime}}</td>
   289      <td> {{prettyDuration .BlockTime}}</td>
   290      <td> {{prettyDuration .SyscallTime}}</td>
   291      <td> {{prettyDuration .SchedWaitTime}}</td>
   292      <td> {{prettyDuration .SweepTime}} {{percent .SweepTime .TotalTime}}</td>
   293      <td> {{prettyDuration .GCTime}} {{percent .GCTime .TotalTime}}</td>
   294    </tr>
   295  {{end}}
   296  </table>
   297  `))
   298  

View as plain text