Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
tasks.go477 linesDownload Raw Back to trace
1// Copyright 2023 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.4 5package main6 7import (8	"bytes"9	"cmp"10	"fmt"11	"html/template"12	"internal/trace"13	"internal/trace/traceviewer"14	"log"15	"net/http"16	"slices"17	"strings"18	"time"19)20 21// UserTasksHandlerFunc returns a HandlerFunc that reports all tasks found in the trace.22func UserTasksHandlerFunc(t *parsedTrace) http.HandlerFunc {23	return func(w http.ResponseWriter, r *http.Request) {24		tasks := t.summary.Tasks25 26		// Summarize groups of tasks with the same name.27		summary := make(map[string]taskStats)28		for _, task := range tasks {29			stats, ok := summary[task.Name]30			if !ok {31				stats.Type = task.Name32			}33			stats.add(task)34			summary[task.Name] = stats35		}36 37		// Sort tasks by type.38		userTasks := make([]taskStats, 0, len(summary))39		for _, stats := range summary {40			userTasks = append(userTasks, stats)41		}42		slices.SortFunc(userTasks, func(a, b taskStats) int {43			return cmp.Compare(a.Type, b.Type)44		})45 46		// Emit table.47		err := templUserTaskTypes.Execute(w, userTasks)48		if err != nil {49			http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)50			return51		}52	}53}54 55type taskStats struct {56	Type      string57	Count     int                       // Complete + incomplete tasks58	Histogram traceviewer.TimeHistogram // Complete tasks only59}60 61func (s *taskStats) UserTaskURL(complete bool) func(min, max time.Duration) string {62	return func(min, max time.Duration) string {63		return fmt.Sprintf("/usertask?type=%s&complete=%v&latmin=%v&latmax=%v", template.URLQueryEscaper(s.Type), template.URLQueryEscaper(complete), template.URLQueryEscaper(min), template.URLQueryEscaper(max))64	}65}66 67func (s *taskStats) add(task *trace.UserTaskSummary) {68	s.Count++69	if task.Complete() {70		s.Histogram.Add(task.End.Time().Sub(task.Start.Time()))71	}72}73 74var templUserTaskTypes = template.Must(template.New("").Parse(`75<!DOCTYPE html>76<title>Tasks</title>77<style>` + traceviewer.CommonStyle + `78.histoTime {79  width: 20%;80  white-space:nowrap;81}82th {83  background-color: #050505;84  color: #fff;85}86table {87  border-collapse: collapse;88}89td,90th {91  padding-left: 8px;92  padding-right: 8px;93  padding-top: 4px;94  padding-bottom: 4px;95}96</style>97<body>98Search log text: <form action="/usertask"><input name="logtext" type="text"><input type="submit"></form><br>99<table border="1" sortable="1">100<tr>101<th>Task type</th>102<th>Count</th>103<th>Duration distribution (complete tasks)</th>104</tr>105{{range $}}106  <tr>107    <td>{{.Type}}</td>108    <td><a href="/usertask?type={{.Type}}">{{.Count}}</a></td>109    <td>{{.Histogram.ToHTML (.UserTaskURL true)}}</td>110  </tr>111{{end}}112</table>113</body>114</html>115`))116 117// UserTaskHandlerFunc returns a HandlerFunc that presents the details of the selected tasks.118func UserTaskHandlerFunc(t *parsedTrace) http.HandlerFunc {119	return func(w http.ResponseWriter, r *http.Request) {120		filter, err := newTaskFilter(r)121		if err != nil {122			http.Error(w, err.Error(), http.StatusBadRequest)123			return124		}125		type event struct {126			WhenString string127			Elapsed    time.Duration128			Goroutine  trace.GoID129			What       string130			// TODO: include stack trace of creation time131		}132		type task struct {133			WhenString string134			ID         trace.TaskID135			Duration   time.Duration136			Complete   bool137			Events     []event138			Start, End time.Duration // Time since the beginning of the trace139			GCTime     time.Duration140		}141		var tasks []task142		for _, summary := range t.summary.Tasks {143			if !filter.match(t, summary) {144				continue145			}146 147			// Collect all the events for the task.148			var rawEvents []*trace.Event149			if summary.Start != nil {150				rawEvents = append(rawEvents, summary.Start)151			}152			if summary.End != nil {153				rawEvents = append(rawEvents, summary.End)154			}155			rawEvents = append(rawEvents, summary.Logs...)156			for _, r := range summary.Regions {157				if r.Start != nil {158					rawEvents = append(rawEvents, r.Start)159				}160				if r.End != nil {161					rawEvents = append(rawEvents, r.End)162				}163			}164 165			// Sort them.166			slices.SortStableFunc(rawEvents, func(a, b *trace.Event) int {167				return cmp.Compare(a.Time(), b.Time())168			})169 170			// Summarize them.171			var events []event172			last := t.startTime()173			for _, ev := range rawEvents {174				what := describeEvent(ev)175				if what == "" {176					continue177				}178				sinceStart := ev.Time().Sub(t.startTime())179				events = append(events, event{180					WhenString: fmt.Sprintf("%2.9f", sinceStart.Seconds()),181					Elapsed:    ev.Time().Sub(last),182					What:       what,183					Goroutine:  primaryGoroutine(ev),184				})185				last = ev.Time()186			}187			taskSpan := taskInterval(t, summary)188			taskStart := taskSpan.start.Sub(t.startTime())189 190			// Produce the task summary.191			tasks = append(tasks, task{192				WhenString: fmt.Sprintf("%2.9fs", taskStart.Seconds()),193				Duration:   taskSpan.duration(),194				ID:         summary.ID,195				Complete:   summary.Complete(),196				Events:     events,197				Start:      taskStart,198				End:        taskStart + taskSpan.duration(),199			})200		}201		// Sort the tasks by duration.202		slices.SortFunc(tasks, func(a, b task) int {203			return cmp.Compare(a.Duration, b.Duration)204		})205 206		// Emit table.207		err = templUserTaskType.Execute(w, struct {208			Name  string209			Tasks []task210		}{211			Name:  filter.name,212			Tasks: tasks,213		})214		if err != nil {215			log.Printf("failed to execute template: %v", err)216			http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)217			return218		}219	}220}221 222var templUserTaskType = template.Must(template.New("userTask").Funcs(template.FuncMap{223	"elapsed":       elapsed,224	"asMillisecond": asMillisecond,225	"trimSpace":     strings.TrimSpace,226}).Parse(`227<!DOCTYPE html>228<title>Tasks: {{.Name}}</title>229<style>` + traceviewer.CommonStyle + `230body {231  font-family: sans-serif;232}233table#req-status td.family {234  padding-right: 2em;235}236table#req-status td.active {237  padding-right: 1em;238}239table#req-status td.empty {240  color: #aaa;241}242table#reqs {243  margin-top: 1em;244  border-collapse: collapse;245}246table#reqs tr.first {247  font-weight: bold;248}249table#reqs td {250  font-family: monospace;251}252table#reqs td.when {253  text-align: right;254  white-space: nowrap;255}256table#reqs td.elapsed {257  padding: 0 0.5em;258  text-align: right;259  white-space: pre;260  width: 10em;261}262address {263  font-size: smaller;264  margin-top: 5em;265}266</style>267<body>268 269<h2>User Task: {{.Name}}</h2>270 271Search log text: <form onsubmit="window.location.search+='&logtext='+window.logtextinput.value; return false">272<input name="logtext" id="logtextinput" type="text"><input type="submit">273</form><br>274 275<table id="reqs">276	<tr>277		<th>When</th>278		<th>Elapsed</th>279		<th>Goroutine</th>280		<th>Events</th>281	</tr>282	{{range $el := $.Tasks}}283	<tr class="first">284		<td class="when">{{$el.WhenString}}</td>285		<td class="elapsed">{{$el.Duration}}</td>286		<td></td>287		<td>288			<a href="/trace?focustask={{$el.ID}}#{{asMillisecond $el.Start}}:{{asMillisecond $el.End}}">Task {{$el.ID}}</a>289			<a href="/trace?taskid={{$el.ID}}#{{asMillisecond $el.Start}}:{{asMillisecond $el.End}}">(goroutine view)</a>290			({{if .Complete}}complete{{else}}incomplete{{end}})291		</td>292	</tr>293	{{range $el.Events}}294	<tr>295		<td class="when">{{.WhenString}}</td>296		<td class="elapsed">{{elapsed .Elapsed}}</td>297		<td class="goid">{{.Goroutine}}</td>298		<td>{{.What}}</td>299	</tr>300	{{end}}301    {{end}}302</body>303</html>304`))305 306// taskFilter represents a task filter specified by a user of cmd/trace.307type taskFilter struct {308	name string309	cond []func(*parsedTrace, *trace.UserTaskSummary) bool310}311 312// match returns true if a task, described by its ID and summary, matches313// the filter.314func (f *taskFilter) match(t *parsedTrace, task *trace.UserTaskSummary) bool {315	if t == nil {316		return false317	}318	for _, c := range f.cond {319		if !c(t, task) {320			return false321		}322	}323	return true324}325 326// newTaskFilter creates a new task filter from URL query variables.327func newTaskFilter(r *http.Request) (*taskFilter, error) {328	if err := r.ParseForm(); err != nil {329		return nil, err330	}331 332	var name []string333	var conditions []func(*parsedTrace, *trace.UserTaskSummary) bool334 335	param := r.Form336	if typ, ok := param["type"]; ok && len(typ) > 0 {337		name = append(name, fmt.Sprintf("%q", typ[0]))338		conditions = append(conditions, func(_ *parsedTrace, task *trace.UserTaskSummary) bool {339			return task.Name == typ[0]340		})341	}342	if complete := r.FormValue("complete"); complete == "1" {343		name = append(name, "complete")344		conditions = append(conditions, func(_ *parsedTrace, task *trace.UserTaskSummary) bool {345			return task.Complete()346		})347	} else if complete == "0" {348		name = append(name, "incomplete")349		conditions = append(conditions, func(_ *parsedTrace, task *trace.UserTaskSummary) bool {350			return !task.Complete()351		})352	}353	if lat, err := time.ParseDuration(r.FormValue("latmin")); err == nil {354		name = append(name, fmt.Sprintf("latency >= %s", lat))355		conditions = append(conditions, func(t *parsedTrace, task *trace.UserTaskSummary) bool {356			return task.Complete() && taskInterval(t, task).duration() >= lat357		})358	}359	if lat, err := time.ParseDuration(r.FormValue("latmax")); err == nil {360		name = append(name, fmt.Sprintf("latency <= %s", lat))361		conditions = append(conditions, func(t *parsedTrace, task *trace.UserTaskSummary) bool {362			return task.Complete() && taskInterval(t, task).duration() <= lat363		})364	}365	if text := r.FormValue("logtext"); text != "" {366		name = append(name, fmt.Sprintf("log contains %q", text))367		conditions = append(conditions, func(_ *parsedTrace, task *trace.UserTaskSummary) bool {368			return taskMatches(task, text)369		})370	}371 372	return &taskFilter{name: strings.Join(name, ","), cond: conditions}, nil373}374 375func taskInterval(t *parsedTrace, s *trace.UserTaskSummary) interval {376	var i interval377	if s.Start != nil {378		i.start = s.Start.Time()379	} else {380		i.start = t.startTime()381	}382	if s.End != nil {383		i.end = s.End.Time()384	} else {385		i.end = t.endTime()386	}387	return i388}389 390func taskMatches(t *trace.UserTaskSummary, text string) bool {391	matches := func(s string) bool {392		return strings.Contains(s, text)393	}394	if matches(t.Name) {395		return true396	}397	for _, r := range t.Regions {398		if matches(r.Name) {399			return true400		}401	}402	for _, ev := range t.Logs {403		log := ev.Log()404		if matches(log.Category) {405			return true406		}407		if matches(log.Message) {408			return true409		}410	}411	return false412}413 414func describeEvent(ev *trace.Event) string {415	switch ev.Kind() {416	case trace.EventStateTransition:417		st := ev.StateTransition()418		if st.Resource.Kind != trace.ResourceGoroutine {419			return ""420		}421		old, new := st.Goroutine()422		return fmt.Sprintf("%s -> %s", old, new)423	case trace.EventRegionBegin:424		return fmt.Sprintf("region %q begin", ev.Region().Type)425	case trace.EventRegionEnd:426		return fmt.Sprintf("region %q end", ev.Region().Type)427	case trace.EventTaskBegin:428		t := ev.Task()429		return fmt.Sprintf("task %q (D %d, parent %d) begin", t.Type, t.ID, t.Parent)430	case trace.EventTaskEnd:431		return "task end"432	case trace.EventLog:433		log := ev.Log()434		if log.Category != "" {435			return fmt.Sprintf("log %q", log.Message)436		}437		return fmt.Sprintf("log (category: %s): %q", log.Category, log.Message)438	}439	return ""440}441 442func primaryGoroutine(ev *trace.Event) trace.GoID {443	if ev.Kind() != trace.EventStateTransition {444		return ev.Goroutine()445	}446	st := ev.StateTransition()447	if st.Resource.Kind != trace.ResourceGoroutine {448		return trace.NoGoroutine449	}450	return st.Resource.Goroutine()451}452 453func elapsed(d time.Duration) string {454	b := fmt.Appendf(nil, "%.9f", d.Seconds())455 456	// For subsecond durations, blank all zeros before decimal point,457	// and all zeros between the decimal point and the first non-zero digit.458	if d < time.Second {459		dot := bytes.IndexByte(b, '.')460		for i := 0; i < dot; i++ {461			b[i] = ' '462		}463		for i := dot + 1; i < len(b); i++ {464			if b[i] == '0' {465				b[i] = ' '466			} else {467				break468			}469		}470	}471	return string(b)472}473 474func asMillisecond(d time.Duration) float64 {475	return float64(d.Nanoseconds()) / float64(time.Millisecond)476}477 
codekingpro/portable-devtools · Team Ai