// Copyright (C) 2017 Nexedi SA and Contributors. // Kirill Smelkov <kirr@nexedi.com> // // This program is free software: you can Use, Study, Modify and Redistribute // it under the terms of the GNU General Public License version 3, or (at your // option) any later version, as published by the Free Software Foundation. // // You can also Link and Combine this program with other software covered by // the terms of any of the Free Software licenses or any of the Open Source // Initiative approved licenses and Convey the resulting work. Corresponding // source of such a combination shall include the source code for all other // software used. // // This program is distributed WITHOUT ANY WARRANTY; without even the implied // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // // See COPYING file for full licensing terms. // See https://www.nexedi.com/licensing for rationale and options. /* Gotrace is a program to support and interact with go tracing subsystem. Gotrace is a common entry to tracing and provides several subcommands: gen generate code according to tracing annotations and imports list lists tracepoints defined by a package See package lab.nexedi.com/kirr/go123/tracing documentation on how to define and use trace events in programs. TODO automatically turn every trace:event into an USDT probe so that they can be traced from outside of the process too. See e.g. https://github.com/iovisor/bcc/issues/327 for context. FIXME build tags not taken into account */ package main import ( "bufio" "crypto/sha1" "flag" "fmt" "go/ast" "go/build" "go/parser" "go/token" "go/types" "io" "io/ioutil" "log" "os" "path/filepath" "sort" "strconv" "strings" "text/template" "golang.org/x/tools/go/loader" "lab.nexedi.com/kirr/go123/xerr" zt "lab.nexedi.com/kirr/neo/go/zodb/zodbtools" ) // traceEvent represents 1 trace:event declaration type traceEvent struct { Pos token.Position Pkgt *Package // package this trace event is part of // declaration of function to signal the event // the declaration is constructed on the fly via converting e.g. // // //trace:event traceConnRecv(c *Conn, msg Msg) // // into // // func traceConnRecv(c *Conn, msg Msg) // // when trace:event is parsed the func declaration is not added // anywhere in the sources - just its AST + package is virtually // constructed. // // See parseTraceEvent for details. *ast.FuncDecl } // traceImport represents 1 trace:import directive type traceImport struct { Pos token.Position PkgName string // "" if import name was not explicitly specified PkgPath string } // traceImported represents 1 imported trace:event type traceImported struct { *traceEvent // imported event ImportSpec *traceImport // imported via this spec ImporterPkg *types.Package // from this package ImportedAs map[string]string // in context where some packages are imported as named (pkgpath -> pkgname) } // Package represents tracing-related information about a package type Package struct { Pkgi *loader.PackageInfo // original non-augmented package Eventv []*traceEvent // trace events this package defines Importv []*traceImport // trace imports of other packages // original package is augmented with tracing code // information about tracing code is below: traceFilev []*ast.File // files for added trace:event funcs traceFset *token.FileSet // fset for ^^^ traceChecker *types.Checker // to typecheck ^^^ tracePkg *types.Package // original package augmented with ^^^ traceTypeInfo *types.Info // typeinfo for ^^^ } // parseTraceEvent parses trace event definition into traceEvent. // // text is text argument after "//trace:event ". func (p *Package) parseTraceEvent(srcfile *ast.File, pos token.Position, text string) (*traceEvent, error) { posErr := func(format string, argv ...interface{}) error { return fmt.Errorf("%v: "+format, append([]interface{}{pos}, argv...)...) } if !strings.HasPrefix(text, "trace") { return nil, posErr("trace event must start with \"trace\"") } // prepare artificial package with trace event definition as func declaration buf := &Buffer{} buf.emit("package %s", p.Pkgi.Pkg.Name()) // add all imports from original source file // so that inside it all looks like as if it was in original source context buf.emit("\nimport (") for _, imp := range srcfile.Imports { impline := "" if imp.Name != nil { impline += imp.Name.Name + " " } impline += imp.Path.Value buf.emit("\t%s", impline) } buf.emit(")") // func itself buf.emit("\nfunc " + text) // now parse/typecheck filename := fmt.Sprintf("%v:%v+trace:event %v", pos.Filename, pos.Line, text) //println("---- 8< ----", filename) //println(buf.String()) //println("---- 8< ----") tf, err := parser.ParseFile(p.traceFset, filename, buf.String(), 0) if err != nil { return nil, err // already has pos' as prefix } p.traceFilev = append(p.traceFilev, tf) // must be: // GenDecl{IMPORT} // FuncDecl if len(tf.Decls) != 2 { return nil, posErr("trace event must be func-like") } declf, ok := tf.Decls[1].(*ast.FuncDecl) if !ok { return nil, posErr("trace event must be func-like, not %v", tf.Decls[0]) } // XXX ok to allow methods (declf.Recv != nil) ? if declf.Type.Results != nil { return nil, posErr("trace event must not return results") } // typecheck prepared file to get trace func argument types // (type information lands into p.traceTypeInfo) err = p.traceChecker.Files([]*ast.File{tf}) if err != nil { return nil, err // already has pos' as prefix } return &traceEvent{Pos: pos, Pkgt: p, FuncDecl: declf}, nil } // parseTraceImport parses trace import directive into traceImport. // // text is text argument after "//trace:import ". func (p *Package) parseTraceImport(pos token.Position, text string) (*traceImport, error) { // //trace:import "path/to/pkg" // //trace:import name "path/to/pkg" if len(text) == 0 { return nil, fmt.Errorf("%v: empty trace-import spec", pos) } pkgname := "" pkgqpath := text if !(text[0] == '"' || text[0] == '\'') { textv := strings.SplitN(text, " ", 2) if len(textv) != 2 { return nil, fmt.Errorf("%v: invalid trace-import spec %v", pos, text) } pkgname = textv[0] pkgqpath = textv[1] } // Unquote pkgqpath as regular import does pkgpath, err := strconv.Unquote(pkgqpath) if err != nil || pkgpath == "" || pkgpath[0] == '\'' { return nil, fmt.Errorf("%v: invalid trace-import path %v", pos, pkgqpath) } // reject duplicate imports for _, imported := range p.Importv { if pkgpath == imported.PkgPath { return nil, fmt.Errorf("%v: duplicate trace import of %v (previous at %v)", pos, pkgpath, imported.Pos) } } return &traceImport{Pos: pos, PkgName: pkgname, PkgPath: pkgpath}, nil } // progImporter is types.Importer that imports packages from loaded loader.Program type progImporter struct { prog *loader.Program } func (pi *progImporter) Import(path string) (*types.Package, error) { pkgi := pi.prog.Package(path) if pkgi == nil { return nil, fmt.Errorf("package %q not found", path) } return pkgi.Pkg, nil } // packageTrace returns tracing information about a package func packageTrace(prog *loader.Program, pkgi *loader.PackageInfo) (*Package, error) { // prepare Package with typechecker ready to typecheck trace files // (to get trace func argument types) tconf := &types.Config{ Importer: &progImporter{prog}, // to ignore traceXXX() calls from original package code IgnoreFuncBodies: true, // we take imports from original source file verbatim, // but most of them probably won't be used. DisableUnusedImportCheck: true, } // tfset := token.NewFileSet() // XXX ok to separate or use original package fset? tfset := prog.Fset tpkg := types.NewPackage(pkgi.Pkg.Path(), pkgi.Pkg.Name()) tinfo := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)} p := &Package{ Pkgi: pkgi, // XXX vvv do we need separate field for traceFset if it is = prog.Fset? traceFset: tfset, traceChecker: types.NewChecker(tconf, tfset, tpkg, tinfo), tracePkg: tpkg, traceTypeInfo: tinfo, } // preload original package files into tracing package err := p.traceChecker.Files(p.Pkgi.Files) if err != nil { // must not happen panic(fmt.Errorf("%v: error rechecking original package: %v", pkgi.Pkg.Path(), err)) } // go through files of the original package and process //trace: directives // // FIXME we currently don't process cgo files as go/loader passes to us // already preprocessed results with comments stripped, not original source. // Maybe in some time it will be possible to have AST of original source: // https://golang.org/issues/16623 for _, file := range pkgi.Files { // ast.File for _, commgroup := range file.Comments { // ast.CommentGroup for _, comment := range commgroup.List { // ast.Comment pos := prog.Fset.Position(comment.Slash) //fmt.Printf("%v %q\n", pos, comment.Text) // only directives starting from beginning of line if pos.Column != 1 { continue } if !strings.HasPrefix(comment.Text, "//trace:") { continue } textv := strings.SplitN(comment.Text, " ", 2) if len(textv) != 2 { return nil, fmt.Errorf("%v: invalid directive format", pos) } directive, arg := textv[0], textv[1] switch directive { case "//trace:event": //fmt.Println("*", textv) event, err := p.parseTraceEvent(file, pos, arg) if err != nil { return nil, err } // XXX needed here? - better append in parseTraceEvent p.Eventv = append(p.Eventv, event) case "//trace:import": imported, err := p.parseTraceImport(pos, arg) if err != nil { return nil, err } // XXX needed here? - better append in parseTraceImport p.Importv = append(p.Importv, imported) default: return nil, fmt.Errorf("%v: unknown tracing directive %q", pos, directive) } } } } // events and imports go in canonical order sort.Sort(byEventName(p.Eventv)) sort.Sort(byPkgPath(p.Importv)) return p, nil } // byEventName provides []*traceEvent ordering by event name type byEventName []*traceEvent func (v byEventName) Less(i, j int) bool { return v[i].Name.Name < v[j].Name.Name } func (v byEventName) Swap(i, j int) { v[i], v[j] = v[j], v[i] } func (v byEventName) Len() int { return len(v) } // byPkgPath provides []*traceImport ordering by package path type byPkgPath []*traceImport func (v byPkgPath) Less(i, j int) bool { return v[i].PkgPath < v[j].PkgPath } func (v byPkgPath) Swap(i, j int) { v[i], v[j] = v[j], v[i] } func (v byPkgPath) Len() int { return len(v) } // SplitTests splits package into main and test parts, each covering trace-related things accordingly func (p *Package) SplitTests() (testPkg *Package) { __ := *p testPkg = &__ // relevant for tracing are only: .Eventv & .Importv eventv := p.Eventv importv := p.Importv p.Eventv = nil p.Importv = nil testPkg.Eventv = nil testPkg.Importv = nil for _, e := range eventv { if strings.HasSuffix(e.Pos.Filename, "_test.go") { testPkg.Eventv = append(testPkg.Eventv, e) } else { p.Eventv = append(p.Eventv, e) } } for _, i := range importv { if strings.HasSuffix(i.Pos.Filename, "_test.go") { testPkg.Importv = append(testPkg.Importv, i) } else { p.Importv = append(p.Importv, i) } } return testPkg } // ---------------------------------------- // Argv returns comma-separated argument-list func (te *traceEvent) Argv() string { argv := []string{} for _, field := range te.FuncDecl.Type.Params.List { for _, name := range field.Names { argv = append(argv, name.Name) } } return strings.Join(argv, ", ") } // ArgvTyped returns argument list with types. // // types are qualified relative to original package func (te *traceEvent) ArgvTyped() string { return te.ArgvTypedRelativeTo(te.Pkgt.tracePkg, nil) } // ArgvTypedRelativeTo returns argument list with types qualified relative to specified package. // // importedAs specifies under which name a package was imported, if name was explicitly set func (te *traceEvent) ArgvTypedRelativeTo(pkg *types.Package, importedAs map[string]string /*pkgpath -> pkgname*/) string { argv := []string{} // default qualifier - relative to original package qf := func(p *types.Package) string { // specified package - unqualified if p == pkg { return "" } // qualify as explicitly named pkgname := importedAs[p.Path()] if pkgname != "" { return pkgname } // default qualification return p.Name() } for _, field := range te.FuncDecl.Type.Params.List { namev := []string{} for _, name := range field.Names { namev = append(namev, name.Name) } arg := strings.Join(namev, ", ") typ := te.Pkgt.traceTypeInfo.Types[field.Type].Type arg += " " + types.TypeString(typ, qf) argv = append(argv, arg) } return strings.Join(argv, ", ") } // NeedPkgv returns packages that are needed for argument types func (te *traceEvent) NeedPkgv() []string { pkgset := StrSet{ /*pkgpath*/ } qf := func(pkg *types.Package) string { // if we are called - pkg is used pkgset.Add(pkg.Path()) return "" // don't care } for _, field := range te.FuncDecl.Type.Params.List { typ := te.Pkgt.traceTypeInfo.Types[field.Type].Type _ = types.TypeString(typ, qf) } return pkgset.Itemv() } // ImportSpec returns string representation of import spec func (ti *traceImport) ImportSpec() string { t := ti.PkgName if t != "" { t += " " } t += fmt.Sprintf("%q", ti.PkgPath) return t } // traceEventCodeTmpl is code template generated for one trace event var traceEventCodeTmpl = template.Must(template.New("traceevent").Parse(` // traceevent: {{.Name}}({{.ArgvTyped}}) {{/* probe type for this trace event */ -}} type _t_{{.Name}} struct { tracing.Probe probefunc func({{.ArgvTyped}}) } {{/* list of probes attached (nil if nothing) */ -}} var _{{.Name}} *_t_{{.Name}} {{/* function which event producer calls to notify about the event * * after https://github.com/golang/go/issues/19348 is done this separate * checking function will be inlined and tracepoint won't cost a function * call when it is disabled */ -}} func {{.Name}}({{.ArgvTyped}}) { if _{{.Name}} != nil { _{{.Name}}_run({{.Argv}}) } } {{/* function to notify attached probes */ -}} func _{{.Name}}_run({{.ArgvTyped}}) { for p := _{{.Name}}; p != nil; p = (*_t_{{.Name}})(unsafe.Pointer(p.Next())) { p.probefunc({{.Argv}}) } } {{/* function to attach a probe to tracepoint */ -}} func {{.Name}}_Attach(pg *tracing.ProbeGroup, probe func({{.ArgvTyped}})) *tracing.Probe { p := _t_{{.Name}}{probefunc: probe} tracing.AttachProbe(pg, (**tracing.Probe)(unsafe.Pointer(&_{{.Name}})), &p.Probe) return &p.Probe } `)) // traceEventImportTmpl is code template generated for importing one trace event var traceEventImportTmpl = template.Must(template.New("traceimport").Parse(` {{/* function to attach a probe to tracepoint imported via go:linkname */ -}} //go:linkname {{.ImportSpec.PkgName}}_{{.Name}}_Attach {{.ImportSpec.PkgPath}}.{{.Name}}_Attach func {{.ImportSpec.PkgName}}_{{.Name}}_Attach(*tracing.ProbeGroup, func({{.ArgvTypedRelativeTo .ImporterPkg .ImportedAs}})) *tracing.Probe `)) // traceEventImportCheckTmpl is code template generated to check consistency with one imported package var traceEventImportCheckTmpl = template.Must(template.New("traceimportcheck").Parse(` {{/* linking will fail if trace import code becomes out of sync wrt imported package */ -}} // rerun "gotrace gen" if you see link failure ↓↓↓ //go:linkname {{.ImportSpec.PkgName}}_trace_exporthash {{.ImportSpec.PkgPath}}._trace_exporthash_{{.ExportHash}} func {{.ImportSpec.PkgName}}_trace_exporthash() func init() { {{.ImportSpec.PkgName}}_trace_exporthash() } `)) // magic begins all files generated by gotrace const magic = "// Code generated by lab.nexedi.com/kirr/go123/tracing/cmd/gotrace; DO NOT EDIT.\n" // checkCanWrite checks whether it is safe to write to file at path. // // it is safe to write when either // - the file does not exist, or // - it exits but was previously generated by us func checkCanWrite(path string) error { f, err := os.Open(path) if e, ok := err.(*os.PathError); ok && os.IsNotExist(e.Err) { return nil } defer f.Close() bf := bufio.NewReader(f) headline, err := bf.ReadString('\n') if err != nil || headline != magic { return fmt.Errorf("refusing to make output: %v exists but was not generated by gotrace", path) } return nil } // writeFile writes data to a file at path after checking it is safe to write there func writeFile(path string, data []byte) error { err := checkCanWrite(path) if err != nil { return err } return ioutil.WriteFile(path, data, 0666) } // removeFile make sure there is no file at path after checking it is safe to write to that file func removeFile(path string) error { err := checkCanWrite(path) if err != nil { return err } err = os.Remove(path) if e, ok := err.(*os.PathError); ok && os.IsNotExist(e.Err) { err = nil } return err } // Program represents loaded program for tracepoint analysis. // // It is generalization of loader.Program due to loader not allowing to // construct programs incrementally. type Program struct { // list of loader.Programs in use // // We generally need to have several programs because a package can // trace:import another package which is not otherwise imported by // original program. // // Since go/loader does not support incrementally augmenting loaded // program with more packages, we work-around it with having several // progs. progv []*loader.Program // config for loading programs loaderConf *loader.Config } // NewProgram constructs new empty Program ready to load packages according to specified build context func NewProgram(ctxt *build.Context, cwd string) *Program { // adjust build context to filter-out ztrace* files when discovering packages // // we don't load what should be generated by us for 2 reasons: // - code generated could be wrong with older version of the // tool - it should not prevent from regenerating. // - generated code imports packages which might be not there // yet in gopath (lab.nexedi.com/kirr/go123/tracing) ctxtReadDir := ctxt.ReadDir if ctxtReadDir == nil { ctxtReadDir = ioutil.ReadDir } ctxtNoZTrace := *ctxt ctxtNoZTrace.ReadDir = func(dir string) ([]os.FileInfo, error) { fv, err := ctxtReadDir(dir) okv := fv[:0] for _, f := range fv { if !strings.HasPrefix(f.Name(), "ztrace") { okv = append(okv, f) } } return okv, err } p := &Program{} p.loaderConf = &loader.Config{ ParserMode: parser.ParseComments, TypeCheckFuncBodies: func(path string) bool { return false }, Build: &ctxtNoZTrace, Cwd: cwd, } return p } // Import imports a package and returns associated package info and program // under which it was loaded. func (p *Program) Import(pkgpath string) (prog *loader.Program, pkgi *loader.PackageInfo, err error) { // let's see - maybe it is already there for _, prog := range p.progv { pkgi := prog.Package(pkgpath) if pkgi != nil { return prog, pkgi, nil } } // not found - we have to load new program rooted at pkgpath p.loaderConf.ImportPkgs = nil p.loaderConf.Import(pkgpath) prog, err = p.loaderConf.Load() if err != nil { return nil, nil, err } if !(len(prog.Created) == 0 && len(prog.Imported) == 1) { panic("import") } p.progv = append(p.progv, prog) pkgi = prog.InitialPackages()[0] return prog, pkgi, nil } // ImportWithTests imports a package augmented with code from _test.go files + // imports external test package (if present). func (p *Program) ImportWithTests(pkgpath string) (prog *loader.Program, pkgi *loader.PackageInfo, xtestPkgi *loader.PackageInfo, err error) { // NOTE always reimporting not to interfere with regular imports p.loaderConf.ImportPkgs = nil p.loaderConf.ImportWithTests(pkgpath) prog, err = p.loaderConf.Load() if err != nil { return nil, nil, nil, err } if len(prog.Imported) != 1 { panic("import with tests") } if len(prog.Created) > 0 { xtestPkgi = prog.Created[0] } for _, pkgi = range prog.Imported { } return prog, pkgi, xtestPkgi, nil } // ---- `gotrace gen` ---- // tracegen generates code according to tracing directives in a package @ pkgpath. // // ctxt is build context for discovering packages // cwd is "current" directory for resolving local imports (e.g. packages like "./some/package") func tracegen(pkgpath string, ctxt *build.Context, cwd string) error { P := NewProgram(ctxt, cwd) lprog, pkgi, xtestPkgi, err := P.ImportWithTests(pkgpath) if err != nil { return err } // determine package directory if len(pkgi.Files) == 0 { return fmt.Errorf("package %s is empty", pkgi.Pkg.Path) } pkgdir := filepath.Dir(lprog.Fset.File(pkgi.Files[0].Pos()).Name()) // tracing info for this specified package tpkg, err := packageTrace(lprog, pkgi) if err != nil { return err // XXX err ctx } // split everything related to tracing into plain and test (not xtest) packages testTpkg := tpkg.SplitTests() err1 := tracegen1(P, tpkg, pkgdir, "") err2 := tracegen1(P, testTpkg, pkgdir, "_test") // also handle xtest package xtestTpkg := &Package{} // dummy package with empty .Eventv & .Importv if xtestPkgi != nil { xtestTpkg, err = packageTrace(lprog, xtestPkgi) if err != nil { return err // XXX err ctx } } err3 := tracegen1(P, xtestTpkg, pkgdir, "_x_test") return xerr.Merge(err1, err2, err3) } // tracegen1 generates code according to tracing directives for a (sub)package @pkgpath. // // subpackage is either original package, testing code, or external test package func tracegen1(P *Program, tpkg *Package, pkgdir string, kind string) error { var err error // write ztrace.go with code generated for trace events and imports ztrace_go := filepath.Join(pkgdir, "ztrace"+kind+".go") if len(tpkg.Eventv) == 0 && len(tpkg.Importv) == 0 { err = removeFile(ztrace_go) if err != nil { return err } } else { // prologue prologue := &Buffer{} prologue.WriteString(magic) prologue.emit("\npackage %v", tpkg.Pkgi.Pkg.Name()) prologue.emit("// code generated for tracepoints") prologue.emit("\nimport (") prologue.emit("\t%q", "lab.nexedi.com/kirr/neo/go/xcommon/tracing") // pkgpaths of all packages needed for used types needPkg := StrSet{} // some packages are imported with explicit name importedAs := map[string]string{} // pkgpath -> pkgname text := &Buffer{} // code for trace:event definitions for _, event := range tpkg.Eventv { needPkg.Add("unsafe") // used in tr needPkg.Add(event.NeedPkgv()...) err = traceEventCodeTmpl.Execute(text, event) if err != nil { panic(err) } } // export hash symbol so that if importing package is out of // sync - it will have it different and linking will fail. if len(tpkg.Eventv) > 0 { text.emit("\n// trace export signature") //text.emit("---- 8< ----") //fmt.Fprintf(text, "%s", traceExport(tpkg, kind)) //text.emit("---- 8< ----") text.emit("func _trace_exporthash_%s() {}", traceExportHash(tpkg, kind)) } // code for trace:import imports for _, timport := range tpkg.Importv { text.emit("\n// traceimport: %s", timport.ImportSpec()) impProg, impPkgi, err := P.Import(timport.PkgPath) if err != nil { return fmt.Errorf("%v: error trace-importing %s: %v", timport.Pos, timport.PkgPath, err) } // set name of the package if it was not explicitly specified if timport.PkgName == "" { timport.PkgName = impPkgi.Pkg.Name() } else { importedAs[timport.PkgPath] = timport.PkgName } impPkg, err := packageTrace(impProg, impPkgi) if err != nil { return err // XXX err ctx } if len(impPkg.Eventv) == 0 { return fmt.Errorf("%v: package %v does not export anything trace-related", timport.Pos, timport.PkgPath) } // verify export hash so link fails if it gets out of sync with imported package err = traceEventImportCheckTmpl.Execute(text, struct { ImportSpec *traceImport ExportHash string }{ timport, traceExportHash(impPkg, "" /*regular package*/)}) text.emit("") // import individual events for _, event := range impPkg.Eventv { needPkg.Add(event.NeedPkgv()...) importedEvent := traceImported{ traceEvent: event, ImportSpec: timport, ImporterPkg: tpkg.Pkgi.Pkg, ImportedAs: importedAs, } err = traceEventImportTmpl.Execute(text, importedEvent) if err != nil { panic(err) } } } // finish prologue with needed imports if !needPkg.Has("unsafe") { // we need it anyway because go:linkname is not allowed without unsafe prologue.emit("\t_ %q", "unsafe") } else { prologue.emit("\t%q", "unsafe") needPkg.Delete("unsafe") } needPkg.Delete(tpkg.Pkgi.Pkg.Path()) // our pkg - no need to import needPkgv := needPkg.Itemv() if len(needPkgv) > 0 { prologue.emit("") } for _, needpkg := range needPkgv { pkgname := importedAs[needpkg] if pkgname != "" { pkgname += " " } prologue.emit("\t%s%q", pkgname, needpkg) } prologue.emit(")") // write output to ztrace.go fulltext := append(prologue.Bytes(), text.Bytes()...) err = writeFile(ztrace_go, fulltext) if err != nil { return err } } // write empty ztrace.s so go:linkname works, if there are trace imports ztrace_s := filepath.Join(pkgdir, "ztrace"+kind+".s") if len(tpkg.Importv) == 0 { err = removeFile(ztrace_s) } else { text := &Buffer{} text.WriteString(magic) text.emit("// empty .s so `go build` does not use -complete for go:linkname to work") err = writeFile(ztrace_s, text.Bytes()) } if err != nil { return err } return nil } // traceExport returns signatures of all tracing-related exports of a package // in canonical order as would be seen from universe scope. func traceExport(tpkg *Package, kind string) []byte { pkgpath := tpkg.Pkgi.Pkg.Path() pkgname := tpkg.Pkgi.Pkg.Name() exported := &Buffer{} exported.emit("%q %q", pkgpath, kind) for _, event := range tpkg.Eventv { importedEvent := traceImported{ traceEvent: event, ImportSpec: &traceImport{PkgName: pkgname, PkgPath: pkgpath}, ImporterPkg: nil, // from nowhere ImportedAs: nil, // no naming for imports } err := traceEventImportTmpl.Execute(exported, importedEvent) if err != nil { panic(err) } } return exported.Bytes() } // traceExportHash computes signature of tracing-related exports of a package // implementation note: it is sha1 of associated header + importing code as // if it was executed from universe scope. func traceExportHash(tpkg *Package, kind string) string { return fmt.Sprintf("%x", sha1.Sum(traceExport(tpkg, kind))) } const genSummary = "generate code according to tracing annotations and imports" func genUsage(w io.Writer) { fmt.Fprintf(w, `Usage: gotrace gen <package> Generate code according to tracing annotations and imports options: -h --help this help text. `) } func genMain(argv []string) { flags := flag.FlagSet{Usage: func() { genUsage(os.Stderr) }} flags.Init("", flag.ExitOnError) flags.Parse(argv[1:]) argv = flags.Args() if len(argv) < 1 { flags.Usage() zt.Exit(2) } pkgpath := argv[0] cwd, err := os.Getwd() if err != nil { zt.Fatal(err) } err = tracegen(pkgpath, &build.Default, cwd) if err != nil { zt.Fatal(err) } } // ---- `gotrace list` ---- // tracelist lists trace-events defined by a package @ pkgpath. // // ctxt and cwd are tunables for discovering packages. See tracegen for details. // // TODO support listing by pkgspec (e.g. "./...") func tracelist(w io.Writer, pkgpath string, ctxt *build.Context, cwd string) error { P := NewProgram(ctxt, cwd) // NOTE only listing trace-events provided by main package, not tests or xtest lprog, pkgi, err := P.Import(pkgpath) if err != nil { return err } tpkg, err := packageTrace(lprog, pkgi) if err != nil { return err // XXX err ctx } for _, event := range tpkg.Eventv { _, err = fmt.Fprintf(w, "%s:%s\n", event.Pkgt.Pkgi.Pkg.Path(), event.Name) if err != nil { return err } } return nil } const listSummary = "lists tracepoints defined by a package" func listUsage(w io.Writer) { fmt.Fprintf(w, `Usage: gotrace list <package> List tracepoints defined by a package options: -h --help this help text. `) } func listMain(argv []string) { flags := flag.FlagSet{Usage: func() { genUsage(os.Stderr) }} flags.Init("", flag.ExitOnError) flags.Parse(argv[1:]) argv = flags.Args() if len(argv) < 1 { flags.Usage() zt.Exit(2) } pkgpath := argv[0] cwd, err := os.Getwd() if err != nil { zt.Fatal(err) } err = tracelist(os.Stdout, pkgpath, &build.Default, cwd) if err != nil { zt.Fatal(err) } } // ---- main driver ---- var commands = zt.CommandRegistry{ {"gen", genSummary, genUsage, genMain}, {"list", listSummary, listUsage, listMain}, } var helpTopics = zt.HelpRegistry{ // XXX for now empty } var prog = zt.MainProg{ Name: "gotrace", Summary: "Gotrace is a program to support and interact with go tracing subsystem", Commands: commands, HelpTopics: helpTopics, } func main() { log.SetFlags(0) log.SetPrefix("gotrace: ") prog.Main() }