...

Source file src/cmd/covdata/dump.go

Documentation: cmd/covdata

     1  // Copyright 2022 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  package main
     6  
     7  // This file contains functions and apis to support the "go tool
     8  // covdata" sub-commands that relate to dumping text format summaries
     9  // and reports: "pkglist", "func",  "debugdump", "percent", and
    10  // "textfmt".
    11  
    12  import (
    13  	"flag"
    14  	"fmt"
    15  	"internal/coverage"
    16  	"internal/coverage/calloc"
    17  	"internal/coverage/cformat"
    18  	"internal/coverage/cmerge"
    19  	"internal/coverage/decodecounter"
    20  	"internal/coverage/decodemeta"
    21  	"internal/coverage/pods"
    22  	"os"
    23  	"sort"
    24  	"strings"
    25  )
    26  
    27  var textfmtoutflag *string
    28  var liveflag *bool
    29  
    30  func makeDumpOp(cmd string) covOperation {
    31  	if cmd == textfmtMode || cmd == percentMode {
    32  		textfmtoutflag = flag.String("o", "", "Output text format to file")
    33  	}
    34  	if cmd == debugDumpMode {
    35  		liveflag = flag.Bool("live", false, "Select only live (executed) functions for dump output.")
    36  	}
    37  	d := &dstate{
    38  		cmd: cmd,
    39  		cm:  &cmerge.Merger{},
    40  	}
    41  	// For these modes (percent, pkglist, func, etc), use a relaxed
    42  	// policy when it comes to counter mode clashes. For a percent
    43  	// report, for example, we only care whether a given line is
    44  	// executed at least once, so it's ok to (effectively) merge
    45  	// together runs derived from different counter modes.
    46  	if d.cmd == percentMode || d.cmd == funcMode || d.cmd == pkglistMode {
    47  		d.cm.SetModeMergePolicy(cmerge.ModeMergeRelaxed)
    48  	}
    49  	if d.cmd == pkglistMode {
    50  		d.pkgpaths = make(map[string]struct{})
    51  	}
    52  	return d
    53  }
    54  
    55  // dstate encapsulates state and provides methods for implementing
    56  // various dump operations. Specifically, dstate implements the
    57  // CovDataVisitor interface, and is designed to be used in
    58  // concert with the CovDataReader utility, which abstracts away most
    59  // of the grubby details of reading coverage data files.
    60  type dstate struct {
    61  	// for batch allocation of counter arrays
    62  	calloc.BatchCounterAlloc
    63  
    64  	// counter merging state + methods
    65  	cm *cmerge.Merger
    66  
    67  	// counter data formatting helper
    68  	format *cformat.Formatter
    69  
    70  	// 'mm' stores values read from a counter data file; the pkfunc key
    71  	// is a pkgid/funcid pair that uniquely identifies a function in
    72  	// instrumented application.
    73  	mm map[pkfunc]decodecounter.FuncPayload
    74  
    75  	// pkm maps package ID to the number of functions in the package
    76  	// with that ID. It is used to report inconsistencies in counter
    77  	// data (for example, a counter data entry with pkgid=N funcid=10
    78  	// where package N only has 3 functions).
    79  	pkm map[uint32]uint32
    80  
    81  	// pkgpaths records all package import paths encountered while
    82  	// visiting coverage data files (used to implement the "pkglist"
    83  	// subcommand).
    84  	pkgpaths map[string]struct{}
    85  
    86  	// Current package name and import path.
    87  	pkgName       string
    88  	pkgImportPath string
    89  
    90  	// Module path for current package (may be empty).
    91  	modulePath string
    92  
    93  	// Dump subcommand (ex: "textfmt", "debugdump", etc).
    94  	cmd string
    95  
    96  	// File to which we will write text format output, if enabled.
    97  	textfmtoutf *os.File
    98  
    99  	// Total and covered statements (used by "debugdump" subcommand).
   100  	totalStmts, coveredStmts int
   101  
   102  	// Records whether preamble has been emitted for current pkg
   103  	// (used when in "debugdump" mode)
   104  	preambleEmitted bool
   105  }
   106  
   107  func (d *dstate) Usage(msg string) {
   108  	if len(msg) > 0 {
   109  		fmt.Fprintf(os.Stderr, "error: %s\n", msg)
   110  	}
   111  	fmt.Fprintf(os.Stderr, "usage: go tool covdata %s -i=<directories>\n\n", d.cmd)
   112  	flag.PrintDefaults()
   113  	fmt.Fprintf(os.Stderr, "\nExamples:\n\n")
   114  	switch d.cmd {
   115  	case pkglistMode:
   116  		fmt.Fprintf(os.Stderr, "  go tool covdata pkglist -i=dir1,dir2\n\n")
   117  		fmt.Fprintf(os.Stderr, "  \treads coverage data files from dir1+dirs2\n")
   118  		fmt.Fprintf(os.Stderr, "  \tand writes out a list of the import paths\n")
   119  		fmt.Fprintf(os.Stderr, "  \tof all compiled packages.\n")
   120  	case textfmtMode:
   121  		fmt.Fprintf(os.Stderr, "  go tool covdata textfmt -i=dir1,dir2 -o=out.txt\n\n")
   122  		fmt.Fprintf(os.Stderr, "  \tmerges data from input directories dir1+dir2\n")
   123  		fmt.Fprintf(os.Stderr, "  \tand emits text format into file 'out.txt'\n")
   124  	case percentMode:
   125  		fmt.Fprintf(os.Stderr, "  go tool covdata percent -i=dir1,dir2\n\n")
   126  		fmt.Fprintf(os.Stderr, "  \tmerges data from input directories dir1+dir2\n")
   127  		fmt.Fprintf(os.Stderr, "  \tand emits percentage of statements covered\n\n")
   128  	case funcMode:
   129  		fmt.Fprintf(os.Stderr, "  go tool covdata func -i=dir1,dir2\n\n")
   130  		fmt.Fprintf(os.Stderr, "  \treads coverage data files from dir1+dirs2\n")
   131  		fmt.Fprintf(os.Stderr, "  \tand writes out coverage profile data for\n")
   132  		fmt.Fprintf(os.Stderr, "  \teach function.\n")
   133  	case debugDumpMode:
   134  		fmt.Fprintf(os.Stderr, "  go tool covdata debugdump [flags] -i=dir1,dir2\n\n")
   135  		fmt.Fprintf(os.Stderr, "  \treads coverage data from dir1+dir2 and dumps\n")
   136  		fmt.Fprintf(os.Stderr, "  \tcontents in human-readable form to stdout, for\n")
   137  		fmt.Fprintf(os.Stderr, "  \tdebugging purposes.\n")
   138  	default:
   139  		panic("unexpected")
   140  	}
   141  	Exit(2)
   142  }
   143  
   144  // Setup is called once at program startup time to vet flag values
   145  // and do any necessary setup operations.
   146  func (d *dstate) Setup() {
   147  	if *indirsflag == "" {
   148  		d.Usage("select input directories with '-i' option")
   149  	}
   150  	if d.cmd == textfmtMode || (d.cmd == percentMode && *textfmtoutflag != "") {
   151  		if *textfmtoutflag == "" {
   152  			d.Usage("select output file name with '-o' option")
   153  		}
   154  		var err error
   155  		d.textfmtoutf, err = os.Create(*textfmtoutflag)
   156  		if err != nil {
   157  			d.Usage(fmt.Sprintf("unable to open textfmt output file %q: %v", *textfmtoutflag, err))
   158  		}
   159  	}
   160  	if d.cmd == debugDumpMode {
   161  		fmt.Printf("/* WARNING: the format of this dump is not stable and is\n")
   162  		fmt.Printf(" * expected to change from one Go release to the next.\n")
   163  		fmt.Printf(" *\n")
   164  		fmt.Printf(" * produced by:\n")
   165  		args := append([]string{os.Args[0]}, debugDumpMode)
   166  		args = append(args, os.Args[1:]...)
   167  		fmt.Printf(" *\t%s\n", strings.Join(args, " "))
   168  		fmt.Printf(" */\n")
   169  	}
   170  }
   171  
   172  func (d *dstate) BeginPod(p pods.Pod) {
   173  	d.mm = make(map[pkfunc]decodecounter.FuncPayload)
   174  }
   175  
   176  func (d *dstate) EndPod(p pods.Pod) {
   177  	if d.cmd == debugDumpMode {
   178  		d.cm.ResetModeAndGranularity()
   179  	}
   180  }
   181  
   182  func (d *dstate) BeginCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) {
   183  	dbgtrace(2, "visit counter data file %s dirIdx %d", cdf, dirIdx)
   184  	if d.cmd == debugDumpMode {
   185  		fmt.Printf("data file %s", cdf)
   186  		if cdr.Goos() != "" {
   187  			fmt.Printf(" GOOS=%s", cdr.Goos())
   188  		}
   189  		if cdr.Goarch() != "" {
   190  			fmt.Printf(" GOARCH=%s", cdr.Goarch())
   191  		}
   192  		if len(cdr.OsArgs()) != 0 {
   193  			fmt.Printf("  program args: %+v\n", cdr.OsArgs())
   194  		}
   195  		fmt.Printf("\n")
   196  	}
   197  }
   198  
   199  func (d *dstate) EndCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) {
   200  }
   201  
   202  func (d *dstate) VisitFuncCounterData(data decodecounter.FuncPayload) {
   203  	if nf, ok := d.pkm[data.PkgIdx]; !ok || data.FuncIdx > nf {
   204  		warn("func payload inconsistency: id [p=%d,f=%d] nf=%d len(ctrs)=%d in VisitFuncCounterData, ignored", data.PkgIdx, data.FuncIdx, nf, len(data.Counters))
   205  		return
   206  	}
   207  	key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx}
   208  	val, found := d.mm[key]
   209  
   210  	dbgtrace(5, "ctr visit pk=%d fid=%d found=%v len(val.ctrs)=%d len(data.ctrs)=%d", data.PkgIdx, data.FuncIdx, found, len(val.Counters), len(data.Counters))
   211  
   212  	if len(val.Counters) < len(data.Counters) {
   213  		t := val.Counters
   214  		val.Counters = d.AllocateCounters(len(data.Counters))
   215  		copy(val.Counters, t)
   216  	}
   217  	err, overflow := d.cm.MergeCounters(val.Counters, data.Counters)
   218  	if err != nil {
   219  		fatal("%v", err)
   220  	}
   221  	if overflow {
   222  		warn("uint32 overflow during counter merge")
   223  	}
   224  	d.mm[key] = val
   225  }
   226  
   227  func (d *dstate) EndCounters() {
   228  }
   229  
   230  func (d *dstate) VisitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader) {
   231  	newgran := mfr.CounterGranularity()
   232  	newmode := mfr.CounterMode()
   233  	if err := d.cm.SetModeAndGranularity(mdf, newmode, newgran); err != nil {
   234  		fatal("%v", err)
   235  	}
   236  	if d.cmd == debugDumpMode {
   237  		fmt.Printf("Cover mode: %s\n", newmode.String())
   238  		fmt.Printf("Cover granularity: %s\n", newgran.String())
   239  	}
   240  	if d.format == nil {
   241  		d.format = cformat.NewFormatter(mfr.CounterMode())
   242  	}
   243  
   244  	// To provide an additional layer of checking when reading counter
   245  	// data, walk the meta-data file to determine the set of legal
   246  	// package/function combinations. This will help catch bugs in the
   247  	// counter file reader.
   248  	d.pkm = make(map[uint32]uint32)
   249  	np := uint32(mfr.NumPackages())
   250  	payload := []byte{}
   251  	for pkIdx := uint32(0); pkIdx < np; pkIdx++ {
   252  		var pd *decodemeta.CoverageMetaDataDecoder
   253  		var err error
   254  		pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload)
   255  		if err != nil {
   256  			fatal("reading pkg %d from meta-file %s: %s", pkIdx, mdf, err)
   257  		}
   258  		d.pkm[pkIdx] = pd.NumFuncs()
   259  	}
   260  }
   261  
   262  func (d *dstate) BeginPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) {
   263  	d.preambleEmitted = false
   264  	d.pkgImportPath = pd.PackagePath()
   265  	d.pkgName = pd.PackageName()
   266  	d.modulePath = pd.ModulePath()
   267  	if d.cmd == pkglistMode {
   268  		d.pkgpaths[d.pkgImportPath] = struct{}{}
   269  	}
   270  	d.format.SetPackage(pd.PackagePath())
   271  }
   272  
   273  func (d *dstate) EndPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) {
   274  }
   275  
   276  func (d *dstate) VisitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc) {
   277  	var counters []uint32
   278  	key := pkfunc{pk: pkgIdx, fcn: fnIdx}
   279  	v, haveCounters := d.mm[key]
   280  
   281  	dbgtrace(5, "meta visit pk=%d fid=%d fname=%s file=%s found=%v len(val.ctrs)=%d", pkgIdx, fnIdx, fd.Funcname, fd.Srcfile, haveCounters, len(v.Counters))
   282  
   283  	suppressOutput := false
   284  	if haveCounters {
   285  		counters = v.Counters
   286  	} else if d.cmd == debugDumpMode && *liveflag {
   287  		suppressOutput = true
   288  	}
   289  
   290  	if d.cmd == debugDumpMode && !suppressOutput {
   291  		if !d.preambleEmitted {
   292  			fmt.Printf("\nPackage path: %s\n", d.pkgImportPath)
   293  			fmt.Printf("Package name: %s\n", d.pkgName)
   294  			fmt.Printf("Module path: %s\n", d.modulePath)
   295  			d.preambleEmitted = true
   296  		}
   297  		fmt.Printf("\nFunc: %s\n", fd.Funcname)
   298  		fmt.Printf("Srcfile: %s\n", fd.Srcfile)
   299  		fmt.Printf("Literal: %v\n", fd.Lit)
   300  	}
   301  	for i := 0; i < len(fd.Units); i++ {
   302  		u := fd.Units[i]
   303  		var count uint32
   304  		if counters != nil {
   305  			count = counters[i]
   306  		}
   307  		d.format.AddUnit(fd.Srcfile, fd.Funcname, fd.Lit, u, count)
   308  		if d.cmd == debugDumpMode && !suppressOutput {
   309  			fmt.Printf("%d: L%d:C%d -- L%d:C%d ",
   310  				i, u.StLine, u.StCol, u.EnLine, u.EnCol)
   311  			if u.Parent != 0 {
   312  				fmt.Printf("Parent:%d = %d\n", u.Parent, count)
   313  			} else {
   314  				fmt.Printf("NS=%d = %d\n", u.NxStmts, count)
   315  			}
   316  		}
   317  		d.totalStmts += int(u.NxStmts)
   318  		if count != 0 {
   319  			d.coveredStmts += int(u.NxStmts)
   320  		}
   321  	}
   322  }
   323  
   324  func (d *dstate) Finish() {
   325  	// d.format maybe nil here if the specified input dir was empty.
   326  	if d.format != nil {
   327  		if d.cmd == percentMode {
   328  			d.format.EmitPercent(os.Stdout, nil, "", false, false)
   329  		}
   330  		if d.cmd == funcMode {
   331  			d.format.EmitFuncs(os.Stdout)
   332  		}
   333  		if d.textfmtoutf != nil {
   334  			if err := d.format.EmitTextual(d.textfmtoutf); err != nil {
   335  				fatal("writing to %s: %v", *textfmtoutflag, err)
   336  			}
   337  		}
   338  	}
   339  	if d.textfmtoutf != nil {
   340  		if err := d.textfmtoutf.Close(); err != nil {
   341  			fatal("closing textfmt output file %s: %v", *textfmtoutflag, err)
   342  		}
   343  	}
   344  	if d.cmd == debugDumpMode {
   345  		fmt.Printf("totalStmts: %d coveredStmts: %d\n", d.totalStmts, d.coveredStmts)
   346  	}
   347  	if d.cmd == pkglistMode {
   348  		pkgs := make([]string, 0, len(d.pkgpaths))
   349  		for p := range d.pkgpaths {
   350  			pkgs = append(pkgs, p)
   351  		}
   352  		sort.Strings(pkgs)
   353  		for _, p := range pkgs {
   354  			fmt.Printf("%s\n", p)
   355  		}
   356  	}
   357  }
   358  

View as plain text