func printFunctionClosing(w io.Writer) {
	fmt.Fprintln(w, "</pre>")
}
printFunctionClosing prints the end of a function in a weblist report.
package report
import (
	"bufio"
	"fmt"
	"html/template"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"github.com/google/pprof/internal/graph"
	"github.com/google/pprof/internal/measurement"
	"github.com/google/pprof/internal/plugin"
	"github.com/google/pprof/profile"
)
func printSource(w io.Writer, rpt *Report) error {
	o := rpt.options
	g := rpt.newGraph(nil)
	var functions graph.Nodes
	functionNodes := make(map[string]graph.Nodes)
	for _, n := range g.Nodes {
		if !o.Symbol.MatchString(n.Info.Name) {
			continue
		}
		if functionNodes[n.Info.Name] == nil {
			functions = append(functions, n)
		}
		functionNodes[n.Info.Name] = append(functionNodes[n.Info.Name], n)
	}
	functions.Sort(graph.NameOrder)
	if len(functionNodes) == 0 {
		return fmt.Errorf("no matches found for regexp: %s", o.Symbol)
	}
	sourcePath := o.SourcePath
	if sourcePath == "" {
		wd, err := os.Getwd()
		if err != nil {
			return fmt.Errorf("could not stat current dir: %v", err)
		}
		sourcePath = wd
	}
	reader := newSourceReader(sourcePath, o.TrimPath)
	fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
	for _, fn := range functions {
		name := fn.Info.Name
		var sourceFiles graph.Nodes
		fileNodes := make(map[string]graph.Nodes)
		for _, n := range functionNodes[name] {
			if n.Info.File == "" {
				continue
			}
			if fileNodes[n.Info.File] == nil {
				sourceFiles = append(sourceFiles, n)
			}
			fileNodes[n.Info.File] = append(fileNodes[n.Info.File], n)
		}
		if len(sourceFiles) == 0 {
			fmt.Fprintf(w, "No source information for %s\n", name)
			continue
		}
		sourceFiles.Sort(graph.FileOrder)
		for _, fl := range sourceFiles {
			filename := fl.Info.File
			fns := fileNodes[filename]
			flatSum, cumSum := fns.Sum()
			fnodes, _, err := getSourceFromFile(filename, reader, fns, 0, 0)
			fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, filename)
			fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
				rpt.formatValue(flatSum), rpt.formatValue(cumSum),
				measurement.Percentage(cumSum, rpt.total))
			if err != nil {
				fmt.Fprintf(w, " Error: %v\n", err)
				continue
			}
			for _, fn := range fnodes {
				fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt), fn.Info.Lineno, fn.Info.Name)
			}
		}
	}
	return nil
}
func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
	printHeader(w, rpt)
	if err := PrintWebList(w, rpt, obj, -1); err != nil {
		return err
	}
	printPageClosing(w)
	return nil
}
type sourcePrinter struct {
	reader     *sourceReader
	synth      *synthCode
	objectTool plugin.ObjTool
	objects    map[string]plugin.ObjFile   
	sym        *regexp.Regexp              
	files      map[string]*sourceFile      
	insts      map[uint64]instructionInfo  
	interest map[string]bool
	prettyNames map[string]string
}
type addrInfo struct {
	loc *profile.Location  
	obj plugin.ObjFile     
}
type instructionInfo struct {
	objAddr   uint64  
	length    int     
	disasm    string  
	file      string  
	line      int     
	flat, cum int64   
}
type sourceFile struct {
	fname    string
	cum      int64
	flat     int64
	lines    map[int][]sourceInst  
	funcName map[int]string        
}
type sourceInst struct {
	addr  uint64
	stack []callID  
}
type sourceFunction struct {
	name       string
	begin, end int  
	flat, cum  int64
}
type addressRange struct {
	begin, end uint64
	obj        plugin.ObjFile
	mapping    *profile.Mapping
	score      int64  
}
func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) error {
	sourcePath := rpt.options.SourcePath
	if sourcePath == "" {
		wd, err := os.Getwd()
		if err != nil {
			return fmt.Errorf("could not stat current dir: %v", err)
		}
		sourcePath = wd
	}
	sp := newSourcePrinter(rpt, obj, sourcePath)
	if len(sp.interest) == 0 {
		return fmt.Errorf("no matches found for regexp: %s", rpt.options.Symbol)
	}
	sp.print(w, maxFiles, rpt)
	sp.close()
	return nil
}
func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourcePrinter {
	sp := &sourcePrinter{
		reader:      newSourceReader(sourcePath, rpt.options.TrimPath),
		synth:       newSynthCode(rpt.prof.Mapping),
		objectTool:  obj,
		objects:     map[string]plugin.ObjFile{},
		sym:         rpt.options.Symbol,
		files:       map[string]*sourceFile{},
		insts:       map[uint64]instructionInfo{},
		prettyNames: map[string]string{},
		interest:    map[string]bool{},
	}
	var address *uint64
	if sp.sym != nil {
		if hex, err := strconv.ParseUint(sp.sym.String(), 0, 64); err == nil {
			address = &hex
		}
	}
	addrs := map[uint64]addrInfo{}
	flat := map[uint64]int64{}
	cum := map[uint64]int64{}
	markInterest := func(addr uint64, loc *profile.Location, index int) {
		fn := loc.Line[index]
		if fn.Function == nil {
			return
		}
		sp.interest[fn.Function.Name] = true
		sp.interest[fn.Function.SystemName] = true
		if _, ok := addrs[addr]; !ok {
			addrs[addr] = addrInfo{loc, sp.objectFile(loc.Mapping)}
		}
	}
	matches := func(line profile.Line) bool {
		if line.Function == nil {
			return false
		}
		return sp.sym.MatchString(line.Function.Name) ||
			sp.sym.MatchString(line.Function.SystemName) ||
			sp.sym.MatchString(line.Function.Filename)
	}
	for _, sample := range rpt.prof.Sample {
		value := rpt.options.SampleValue(sample.Value)
		if rpt.options.SampleMeanDivisor != nil {
			div := rpt.options.SampleMeanDivisor(sample.Value)
			if div != 0 {
				value /= div
			}
		}
		for i := len(sample.Location) - 1; i >= 0; i-- {
			loc := sample.Location[i]
			for _, line := range loc.Line {
				if line.Function == nil {
					continue
				}
				sp.prettyNames[line.Function.SystemName] = line.Function.Name
			}
			addr := loc.Address
			if addr == 0 {
				addr = sp.synth.address(loc)
			}
			cum[addr] += value
			if i == 0 {
				flat[addr] += value
			}
			if sp.sym == nil || (address != nil && addr == *address) {
				if len(loc.Line) > 0 {
					markInterest(addr, loc, len(loc.Line)-1)
				}
				continue
			}
			matchFile := (loc.Mapping != nil && sp.sym.MatchString(loc.Mapping.File))
			for j, line := range loc.Line {
				if (j == 0 && matchFile) || matches(line) {
					markInterest(addr, loc, j)
				}
			}
		}
	}
	sp.expandAddresses(rpt, addrs, flat)
	sp.initSamples(flat, cum)
	return sp
}
func (sp *sourcePrinter) close() {
	for _, objFile := range sp.objects {
		if objFile != nil {
			objFile.Close()
		}
	}
}
func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]addrInfo, flat map[uint64]int64) {
	ranges, unprocessed := sp.splitIntoRanges(rpt.prof, addrs, flat)
	sp.handleUnprocessed(addrs, unprocessed)
	const maxRanges = 25
	sort.Slice(ranges, func(i, j int) bool {
		return ranges[i].score > ranges[j].score
	})
	if len(ranges) > maxRanges {
		ranges = ranges[:maxRanges]
	}
	for _, r := range ranges {
		objBegin, err := r.obj.ObjAddr(r.begin)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range start %x: %v\n", r.begin, err)
			continue
		}
		objEnd, err := r.obj.ObjAddr(r.end)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range end %x: %v\n", r.end, err)
			continue
		}
		base := r.begin - objBegin
		insts, err := sp.objectTool.Disasm(r.mapping.File, objBegin, objEnd, rpt.options.IntelSyntax)
		if err != nil {
			continue
		}
		var lastFrames []plugin.Frame
		var lastAddr, maxAddr uint64
		for i, inst := range insts {
			addr := inst.Addr + base
			if addr <= maxAddr {
				continue
			}
			maxAddr = addr
			length := 1
			if i+1 < len(insts) && insts[i+1].Addr > inst.Addr {
				length = int(insts[i+1].Addr - inst.Addr)
			}
			frames, err := r.obj.SourceLine(addr)
			if err != nil {
				frames = []plugin.Frame{{Func: inst.Function, File: inst.File, Line: inst.Line}}
			}
			x := instructionInfo{objAddr: inst.Addr, length: length, disasm: inst.Text}
			if len(frames) > 0 {
				index := 0  
				x.file = frames[index].File
				x.line = frames[index].Line
			}
			sp.insts[addr] = x
			const neighborhood = 32
			if len(frames) > 0 && frames[0].Line != 0 {
				lastFrames = frames
				lastAddr = addr
			} else if (addr-lastAddr <= neighborhood) && lastFrames != nil {
				frames = lastFrames
			}
			sp.addStack(addr, frames)
		}
	}
}
func (sp *sourcePrinter) addStack(addr uint64, frames []plugin.Frame) {
	for i, f := range frames {
		if !sp.interest[f.Func] {
			continue
		}
		fname := canonicalizeFileName(f.File)
		file := sp.files[fname]
		if file == nil {
			file = &sourceFile{
				fname:    fname,
				lines:    map[int][]sourceInst{},
				funcName: map[int]string{},
			}
			sp.files[fname] = file
		}
		callees := frames[:i]
		stack := make([]callID, 0, len(callees))
		for j := len(callees) - 1; j >= 0; j-- {  
			stack = append(stack, callID{
				file: callees[j].File,
				line: callees[j].Line,
			})
		}
		file.lines[f.Line] = append(file.lines[f.Line], sourceInst{addr, stack})
		if _, ok := file.funcName[f.Line]; !ok {
			file.funcName[f.Line] = f.Func
		}
	}
}
const synthAsm = ""
func (sp *sourcePrinter) handleUnprocessed(addrs map[uint64]addrInfo, unprocessed []uint64) {
	makeFrames := func(addr uint64) []plugin.Frame {
		loc := addrs[addr].loc
		stack := make([]plugin.Frame, 0, len(loc.Line))
		for _, line := range loc.Line {
			fn := line.Function
			if fn == nil {
				continue
			}
			stack = append(stack, plugin.Frame{
				Func: fn.Name,
				File: fn.Filename,
				Line: int(line.Line),
			})
		}
		return stack
	}
	for _, addr := range unprocessed {
		frames := makeFrames(addr)
		x := instructionInfo{
			objAddr: addr,
			length:  1,
			disasm:  synthAsm,
		}
		if len(frames) > 0 {
			x.file = frames[0].File
			x.line = frames[0].Line
		}
		sp.insts[addr] = x
		sp.addStack(addr, frames)
	}
}
func (sp *sourcePrinter) splitIntoRanges(prof *profile.Profile, addrMap map[uint64]addrInfo, flat map[uint64]int64) ([]addressRange, []uint64) {
	var addrs, unprocessed []uint64
	for addr, info := range addrMap {
		if info.obj != nil {
			addrs = append(addrs, addr)
		} else {
			unprocessed = append(unprocessed, addr)
		}
	}
	sort.Slice(addrs, func(i, j int) bool { return addrs[i] < addrs[j] })
	const expand = 500  
	var result []addressRange
	for i, n := 0, len(addrs); i < n; {
		begin, end := addrs[i], addrs[i]
		sum := flat[begin]
		i++
		info := addrMap[begin]
		m := info.loc.Mapping
		obj := info.obj  
		for i < n && addrs[i] <= end+2*expand && addrs[i] < m.Limit {
			end = addrs[i]
			sum += flat[end]
			i++
		}
		if m.Start-begin >= expand {
			begin -= expand
		} else {
			begin = m.Start
		}
		if m.Limit-end >= expand {
			end += expand
		} else {
			end = m.Limit
		}
		result = append(result, addressRange{begin, end, obj, m, sum})
	}
	return result, unprocessed
}
func (sp *sourcePrinter) initSamples(flat, cum map[uint64]int64) {
	for addr, inst := range sp.insts {
		instEnd := addr + uint64(inst.length)
		for p := addr; p < instEnd; p++ {
			inst.flat += flat[p]
			inst.cum += cum[p]
		}
		sp.insts[addr] = inst
	}
}
func (sp *sourcePrinter) print(w io.Writer, maxFiles int, rpt *Report) {
	for _, file := range sp.files {
		seen := map[uint64]bool{}
		for _, line := range file.lines {
			for _, x := range line {
				if seen[x.addr] {
					continue
				}
				seen[x.addr] = true
				inst := sp.insts[x.addr]
				file.cum += inst.cum
				file.flat += inst.flat
			}
		}
	}
	var files []*sourceFile
	for _, f := range sp.files {
		files = append(files, f)
	}
	order := func(i, j int) bool { return files[i].flat > files[j].flat }
	if maxFiles < 0 {
		order = func(i, j int) bool { return files[i].fname < files[j].fname }
		maxFiles = len(files)
	}
	sort.Slice(files, order)
	for i, f := range files {
		if i < maxFiles {
			sp.printFile(w, f, rpt)
		}
	}
}
func (sp *sourcePrinter) printFile(w io.Writer, f *sourceFile, rpt *Report) {
	for _, fn := range sp.functions(f) {
		if fn.cum == 0 {
			continue
		}
		printFunctionHeader(w, fn.name, f.fname, fn.flat, fn.cum, rpt)
		var asm []assemblyInstruction
		for l := fn.begin; l < fn.end; l++ {
			lineContents, ok := sp.reader.line(f.fname, l)
			if !ok {
				if len(f.lines[l]) == 0 {
					continue
				}
				if l == 0 {
					lineContents = "<instructions with unknown line numbers>"
				} else {
					lineContents = "???"
				}
			}
			asm = asm[:0]
			var flatSum, cumSum int64
			var lastAddr uint64
			for _, inst := range f.lines[l] {
				addr := inst.addr
				x := sp.insts[addr]
				flatSum += x.flat
				cumSum += x.cum
				startsBlock := (addr != lastAddr+uint64(sp.insts[lastAddr].length))
				lastAddr = addr
				asm = append(asm, assemblyInstruction{
					address:     x.objAddr,
					instruction: x.disasm,
					function:    fn.name,
					file:        x.file,
					line:        x.line,
					flat:        x.flat,
					cum:         x.cum,
					startsBlock: startsBlock,
					inlineCalls: inst.stack,
				})
			}
			printFunctionSourceLine(w, l, flatSum, cumSum, lineContents, asm, sp.reader, rpt)
		}
		printFunctionClosing(w)
	}
}
func (sp *sourcePrinter) functions(f *sourceFile) []sourceFunction {
	var funcs []sourceFunction
	lines := make([]int, 0, len(f.lines))
	for l := range f.lines {
		lines = append(lines, l)
	}
	sort.Ints(lines)
	const mergeLimit = 20
	for _, l := range lines {
		name := f.funcName[l]
		if pretty, ok := sp.prettyNames[name]; ok {
			name = pretty
		}
		fn := sourceFunction{name: name, begin: l, end: l + 1}
		for _, x := range f.lines[l] {
			inst := sp.insts[x.addr]
			fn.flat += inst.flat
			fn.cum += inst.cum
		}
		if len(funcs) > 0 {
			last := funcs[len(funcs)-1]
			if l-last.end < mergeLimit && last.name == name {
				last.end = l + 1
				last.flat += fn.flat
				last.cum += fn.cum
				funcs[len(funcs)-1] = last
				continue
			}
		}
		funcs = append(funcs, fn)
	}
	const expand = 5
	for i, f := range funcs {
		if i == 0 {
			if f.begin > expand {
				f.begin -= expand
			} else if f.begin > 1 {
				f.begin = 1
			}
		} else {
			halfGap := (f.begin - funcs[i-1].end) / 2
			if halfGap > expand {
				halfGap = expand
			}
			funcs[i-1].end += halfGap
			f.begin -= halfGap
		}
		funcs[i] = f
	}
	if len(funcs) > 0 {
		funcs[len(funcs)-1].end += expand
	}
	return funcs
}
func (sp *sourcePrinter) objectFile(m *profile.Mapping) plugin.ObjFile {
	if m == nil {
		return nil
	}
	if object, ok := sp.objects[m.File]; ok {
		return object  
	}
	object, err := sp.objectTool.Open(m.File, m.Start, m.Limit, m.Offset)
	if err != nil {
		object = nil
	}
	sp.objects[m.File] = object  
	return object
}
func printHeader(w io.Writer, rpt *Report) {
	fmt.Fprintln(w, `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Pprof listing</title>`)
	fmt.Fprintln(w, weblistPageCSS)
	fmt.Fprintln(w, weblistPageScript)
	fmt.Fprint(w, "</head>\n<body>\n\n")
	var labels []string
	for _, l := range ProfileLabels(rpt) {
		labels = append(labels, template.HTMLEscapeString(l))
	}
	fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
		strings.Join(labels, "<br>\n"),
		rpt.formatValue(rpt.total),
	)
}
func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
	fmt.Fprintf(w, `<h2>%s</h2><p class="filename">%s</p>
<pre onClick="pprof_toggle_asm(event)">
  Total:  %10s %10s (flat, cum) %s
`,
		template.HTMLEscapeString(name), template.HTMLEscapeString(path),
		rpt.formatValue(flatSum), rpt.formatValue(cumSum),
		measurement.Percentage(cumSum, rpt.total))
}
func printFunctionSourceLine(w io.Writer, lineNo int, flat, cum int64, lineContents string,
	assembly []assemblyInstruction, reader *sourceReader, rpt *Report) {
	if len(assembly) == 0 {
		fmt.Fprintf(w,
			"<span class=line> %6d</span> <span class=nop>  %10s %10s %8s  %s </span>\n",
			lineNo,
			valueOrDot(flat, rpt), valueOrDot(cum, rpt),
			"", template.HTMLEscapeString(lineContents))
		return
	}
	nestedInfo := false
	cl := "deadsrc"
	for _, an := range assembly {
		if len(an.inlineCalls) > 0 || an.instruction != synthAsm {
			nestedInfo = true
			cl = "livesrc"
		}
	}
	fmt.Fprintf(w,
		"<span class=line> %6d</span> <span class=%s>  %10s %10s %8s  %s </span>",
		lineNo, cl,
		valueOrDot(flat, rpt), valueOrDot(cum, rpt),
		"", template.HTMLEscapeString(lineContents))
	if nestedInfo {
		srcIndent := indentation(lineContents)
		printNested(w, srcIndent, assembly, reader, rpt)
	}
	fmt.Fprintln(w)
}
func printNested(w io.Writer, srcIndent int, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) {
	fmt.Fprint(w, "<span class=asm>")
	var curCalls []callID
	for i, an := range assembly {
		if an.startsBlock && i != 0 {
			fmt.Fprintf(w, " %8s %28s\n", "", "⋮")
		}
		var fileline string
		if an.file != "" {
			fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(filepath.Base(an.file)), an.line)
		}
		flat, cum := an.flat, an.cum
		for j, c := range an.inlineCalls {
			if j < len(curCalls) && curCalls[j] == c {
				continue
			}
			curCalls = nil
			fline, ok := reader.line(c.file, c.line)
			if !ok {
				fline = ""
			}
			text := strings.Repeat(" ", srcIndent+4+4*j) + strings.TrimSpace(fline)
			fmt.Fprintf(w, " %8s %10s %10s %8s  <span class=inlinesrc>%s</span> <span class=unimportant>%s:%d</span>\n",
				"", "", "", "",
				template.HTMLEscapeString(rightPad(text, 80)),
				template.HTMLEscapeString(filepath.Base(c.file)), c.line)
		}
		curCalls = an.inlineCalls
		if an.instruction == synthAsm {
			continue
		}
		text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction
		fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=unimportant>%s</span>\n",
			"", valueOrDot(flat, rpt), valueOrDot(cum, rpt), an.address,
			template.HTMLEscapeString(rightPad(text, 80)),
			fileline)
	}
	fmt.Fprint(w, "</span>")
}