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
5package main
6
7import (
8	"cmd/internal/cov"
9	"cmd/internal/pkgpattern"
10	"cmd/internal/telemetry/counter"
11	"flag"
12	"fmt"
13	"os"
14	"runtime"
15	"runtime/pprof"
16	"strings"
17)
18
19var verbflag = flag.Int("v", 0, "Verbose trace output level")
20var hflag = flag.Bool("h", false, "Panic on fatal errors (for stack trace)")
21var hwflag = flag.Bool("hw", false, "Panic on warnings (for stack trace)")
22var indirsflag = flag.String("i", "", "Input dirs to examine (comma separated)")
23var pkgpatflag = flag.String("pkg", "", "Restrict output to package(s) matching specified package pattern.")
24var cpuprofileflag = flag.String("cpuprofile", "", "Write CPU profile to specified file")
25var memprofileflag = flag.String("memprofile", "", "Write memory profile to specified file")
26var memprofilerateflag = flag.Int("memprofilerate", 0, "Set memprofile sampling rate to value")
27
28var matchpkg func(name string) bool
29
30var atExitFuncs []func()
31
32func atExit(f func()) {
33	atExitFuncs = append(atExitFuncs, f)
34}
35
36func Exit(code int) {
37	for i := len(atExitFuncs) - 1; i >= 0; i-- {
38		f := atExitFuncs[i]
39		atExitFuncs = atExitFuncs[:i]
40		f()
41	}
42	os.Exit(code)
43}
44
45func dbgtrace(vlevel int, s string, a ...interface{}) {
46	if *verbflag >= vlevel {
47		fmt.Printf(s, a...)
48		fmt.Printf("\n")
49	}
50}
51
52func warn(s string, a ...interface{}) {
53	fmt.Fprintf(os.Stderr, "warning: ")
54	fmt.Fprintf(os.Stderr, s, a...)
55	fmt.Fprintf(os.Stderr, "\n")
56	if *hwflag {
57		panic("unexpected warning")
58	}
59}
60
61func fatal(s string, a ...interface{}) {
62	fmt.Fprintf(os.Stderr, "error: ")
63	fmt.Fprintf(os.Stderr, s, a...)
64	fmt.Fprintf(os.Stderr, "\n")
65	if *hflag {
66		panic("fatal error")
67	}
68	Exit(1)
69}
70
71func usage(msg string) {
72	if len(msg) > 0 {
73		fmt.Fprintf(os.Stderr, "error: %s\n", msg)
74	}
75	fmt.Fprintf(os.Stderr, "usage: go tool covdata [command]\n")
76	fmt.Fprintf(os.Stderr, `
77Commands are:
78
79textfmt     convert coverage data to textual format
80percent     output total percentage of statements covered
81pkglist     output list of package import paths
82func        output coverage profile information for each function
83merge       merge data files together
84subtract    subtract one set of data files from another set
85intersect   generate intersection of two sets of data files
86debugdump   dump data in human-readable format for debugging purposes
87`)
88	fmt.Fprintf(os.Stderr, "\nFor help on a specific subcommand, try:\n")
89	fmt.Fprintf(os.Stderr, "\ngo tool covdata <cmd> -help\n")
90	Exit(2)
91}
92
93type covOperation interface {
94	cov.CovDataVisitor
95	Setup()
96	Usage(string)
97}
98
99// Modes of operation.
100const (
101	funcMode      = "func"
102	mergeMode     = "merge"
103	intersectMode = "intersect"
104	subtractMode  = "subtract"
105	percentMode   = "percent"
106	pkglistMode   = "pkglist"
107	textfmtMode   = "textfmt"
108	debugDumpMode = "debugdump"
109)
110
111func main() {
112	counter.Open()
113
114	// First argument should be mode/subcommand.
115	if len(os.Args) < 2 {
116		usage("missing command selector")
117	}
118
119	// Select mode
120	var op covOperation
121	cmd := os.Args[1]
122	switch cmd {
123	case mergeMode:
124		op = makeMergeOp()
125	case debugDumpMode:
126		op = makeDumpOp(debugDumpMode)
127	case textfmtMode:
128		op = makeDumpOp(textfmtMode)
129	case percentMode:
130		op = makeDumpOp(percentMode)
131	case funcMode:
132		op = makeDumpOp(funcMode)
133	case pkglistMode:
134		op = makeDumpOp(pkglistMode)
135	case subtractMode:
136		op = makeSubtractIntersectOp(subtractMode)
137	case intersectMode:
138		op = makeSubtractIntersectOp(intersectMode)
139	default:
140		usage(fmt.Sprintf("unknown command selector %q", cmd))
141	}
142
143	// Edit out command selector, then parse flags.
144	os.Args = append(os.Args[:1], os.Args[2:]...)
145	flag.Usage = func() {
146		op.Usage("")
147	}
148	flag.Parse()
149	counter.Inc("covdata/invocations")
150	counter.CountFlags("covdata/flag:", *flag.CommandLine)
151
152	// Mode-independent flag setup
153	dbgtrace(1, "starting mode-independent setup")
154	if flag.NArg() != 0 {
155		op.Usage("unknown extra arguments")
156	}
157	if *pkgpatflag != "" {
158		pats := strings.Split(*pkgpatflag, ",")
159		matchers := []func(name string) bool{}
160		for _, p := range pats {
161			if p == "" {
162				continue
163			}
164			f := pkgpattern.MatchSimplePattern(p)
165			matchers = append(matchers, f)
166		}
167		matchpkg = func(name string) bool {
168			for _, f := range matchers {
169				if f(name) {
170					return true
171				}
172			}
173			return false
174		}
175	}
176	if *cpuprofileflag != "" {
177		f, err := os.Create(*cpuprofileflag)
178		if err != nil {
179			fatal("%v", err)
180		}
181		if err := pprof.StartCPUProfile(f); err != nil {
182			fatal("%v", err)
183		}
184		atExit(func() {
185			pprof.StopCPUProfile()
186			if err = f.Close(); err != nil {
187				fatal("error closing cpu profile: %v", err)
188			}
189		})
190	}
191	if *memprofileflag != "" {
192		if *memprofilerateflag != 0 {
193			runtime.MemProfileRate = *memprofilerateflag
194		}
195		f, err := os.Create(*memprofileflag)
196		if err != nil {
197			fatal("%v", err)
198		}
199		atExit(func() {
200			runtime.GC()
201			const writeLegacyFormat = 1
202			if err := pprof.Lookup("heap").WriteTo(f, writeLegacyFormat); err != nil {
203				fatal("%v", err)
204			}
205			if err = f.Close(); err != nil {
206				fatal("error closing memory profile: %v", err)
207			}
208		})
209	} else {
210		// Not doing memory profiling; disable it entirely.
211		runtime.MemProfileRate = 0
212	}
213
214	// Mode-dependent setup.
215	op.Setup()
216
217	// ... off and running now.
218	dbgtrace(1, "starting perform")
219
220	indirs := strings.Split(*indirsflag, ",")
221	vis := cov.CovDataVisitor(op)
222	var flags cov.CovDataReaderFlags
223	if *hflag {
224		flags |= cov.PanicOnError
225	}
226	if *hwflag {
227		flags |= cov.PanicOnWarning
228	}
229	reader := cov.MakeCovDataReader(vis, indirs, *verbflag, flags, matchpkg)
230	st := 0
231	if err := reader.Visit(); err != nil {
232		fmt.Fprintf(os.Stderr, "error: %v\n", err)
233		st = 1
234	}
235	dbgtrace(1, "leaving main")
236	Exit(st)
237}
238