diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
commit | f6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch) | |
tree | 7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/cmd/covdata/metamerge.go | |
parent | Initial commit. (diff) | |
download | golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip |
Adding upstream version 1.22.1.upstream/1.22.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/covdata/metamerge.go')
-rw-r--r-- | src/cmd/covdata/metamerge.go | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/src/cmd/covdata/metamerge.go b/src/cmd/covdata/metamerge.go new file mode 100644 index 0000000..6c68e0c --- /dev/null +++ b/src/cmd/covdata/metamerge.go @@ -0,0 +1,422 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This file contains functions and apis that support merging of +// meta-data information. It helps implement the "merge", "subtract", +// and "intersect" subcommands. + +import ( + "crypto/md5" + "fmt" + "internal/coverage" + "internal/coverage/calloc" + "internal/coverage/cmerge" + "internal/coverage/decodecounter" + "internal/coverage/decodemeta" + "internal/coverage/encodecounter" + "internal/coverage/encodemeta" + "internal/coverage/slicewriter" + "io" + "os" + "path/filepath" + "sort" + "time" + "unsafe" +) + +// metaMerge provides state and methods to help manage the process +// of selecting or merging meta data files. There are three cases +// of interest here: the "-pcombine" flag provided by merge, the +// "-pkg" option provided by all merge/subtract/intersect, and +// a regular vanilla merge with no package selection +// +// In the -pcombine case, we're essentially glomming together all the +// meta-data for all packages and all functions, meaning that +// everything we see in a given package needs to be added into the +// meta-data file builder; we emit a single meta-data file at the end +// of the run. +// +// In the -pkg case, we will typically emit a single meta-data file +// per input pod, where that new meta-data file contains entries for +// just the selected packages. +// +// In the third case (vanilla merge with no combining or package +// selection) we can carry over meta-data files without touching them +// at all (only counter data files will be merged). +type metaMerge struct { + calloc.BatchCounterAlloc + cmerge.Merger + // maps package import path to package state + pkm map[string]*pkstate + // list of packages + pkgs []*pkstate + // current package state + p *pkstate + // current pod state + pod *podstate + // counter data file osargs/goos/goarch state + astate *argstate +} + +// pkstate +type pkstate struct { + // index of package within meta-data file. + pkgIdx uint32 + // this maps function index within the package to counter data payload + ctab map[uint32]decodecounter.FuncPayload + // pointer to meta-data blob for package + mdblob []byte + // filled in only for -pcombine merges + *pcombinestate +} + +type podstate struct { + pmm map[pkfunc]decodecounter.FuncPayload + mdf string + mfr *decodemeta.CoverageMetaFileReader + fileHash [16]byte +} + +type pkfunc struct { + pk, fcn uint32 +} + +// pcombinestate +type pcombinestate struct { + // Meta-data builder for the package. + cmdb *encodemeta.CoverageMetaDataBuilder + // Maps function meta-data hash to new function index in the + // new version of the package we're building. + ftab map[[16]byte]uint32 +} + +func newMetaMerge() *metaMerge { + return &metaMerge{ + pkm: make(map[string]*pkstate), + astate: &argstate{}, + } +} + +func (mm *metaMerge) visitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader) { + dbgtrace(2, "visitMetaDataFile(mdf=%s)", mdf) + + // Record meta-data file name. + mm.pod.mdf = mdf + // Keep a pointer to the file-level reader. + mm.pod.mfr = mfr + // Record file hash. + mm.pod.fileHash = mfr.FileHash() + // Counter mode and granularity -- detect and record clashes here. + newgran := mfr.CounterGranularity() + newmode := mfr.CounterMode() + if err := mm.SetModeAndGranularity(mdf, newmode, newgran); err != nil { + fatal("%v", err) + } +} + +func (mm *metaMerge) beginCounterDataFile(cdr *decodecounter.CounterDataReader) { + state := argvalues{ + osargs: cdr.OsArgs(), + goos: cdr.Goos(), + goarch: cdr.Goarch(), + } + mm.astate.Merge(state) +} + +func copyMetaDataFile(inpath, outpath string) { + inf, err := os.Open(inpath) + if err != nil { + fatal("opening input meta-data file %s: %v", inpath, err) + } + defer inf.Close() + + fi, err := inf.Stat() + if err != nil { + fatal("accessing input meta-data file %s: %v", inpath, err) + } + + outf, err := os.OpenFile(outpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode()) + if err != nil { + fatal("opening output meta-data file %s: %v", outpath, err) + } + + _, err = io.Copy(outf, inf) + outf.Close() + if err != nil { + fatal("writing output meta-data file %s: %v", outpath, err) + } +} + +func (mm *metaMerge) beginPod() { + mm.pod = &podstate{ + pmm: make(map[pkfunc]decodecounter.FuncPayload), + } +} + +// metaEndPod handles actions needed when we're done visiting all of +// the things in a pod -- counter files and meta-data file. There are +// three cases of interest here: +// +// Case 1: in an unconditional merge (we're not selecting a specific set of +// packages using "-pkg", and the "-pcombine" option is not in use), +// we can simply copy over the meta-data file from input to output. +// +// Case 2: if this is a select merge (-pkg is in effect), then at +// this point we write out a new smaller meta-data file that includes +// only the packages of interest. At this point we also emit a merged +// counter data file as well. +// +// Case 3: if "-pcombine" is in effect, we don't write anything at +// this point (all writes will happen at the end of the run). +func (mm *metaMerge) endPod(pcombine bool) { + if pcombine { + // Just clear out the pod data, we'll do all the + // heavy lifting at the end. + mm.pod = nil + return + } + + finalHash := mm.pod.fileHash + if matchpkg != nil { + // Emit modified meta-data file for this pod. + finalHash = mm.emitMeta(*outdirflag, pcombine) + } else { + // Copy meta-data file for this pod to the output directory. + inpath := mm.pod.mdf + mdfbase := filepath.Base(mm.pod.mdf) + outpath := filepath.Join(*outdirflag, mdfbase) + copyMetaDataFile(inpath, outpath) + } + + // Emit acccumulated counter data for this pod. + mm.emitCounters(*outdirflag, finalHash) + + // Reset package state. + mm.pkm = make(map[string]*pkstate) + mm.pkgs = nil + mm.pod = nil + + // Reset counter mode and granularity + mm.ResetModeAndGranularity() +} + +// emitMeta encodes and writes out a new coverage meta-data file as +// part of a merge operation, specifically a merge with the +// "-pcombine" flag. +func (mm *metaMerge) emitMeta(outdir string, pcombine bool) [16]byte { + fh := md5.New() + blobs := [][]byte{} + tlen := uint64(unsafe.Sizeof(coverage.MetaFileHeader{})) + for _, p := range mm.pkgs { + var blob []byte + if pcombine { + mdw := &slicewriter.WriteSeeker{} + p.cmdb.Emit(mdw) + blob = mdw.BytesWritten() + } else { + blob = p.mdblob + } + ph := md5.Sum(blob) + blobs = append(blobs, blob) + if _, err := fh.Write(ph[:]); err != nil { + panic(fmt.Sprintf("internal error: md5 sum failed: %v", err)) + } + tlen += uint64(len(blob)) + } + var finalHash [16]byte + fhh := fh.Sum(nil) + copy(finalHash[:], fhh) + + // Open meta-file for writing. + fn := fmt.Sprintf("%s.%x", coverage.MetaFilePref, finalHash) + fpath := filepath.Join(outdir, fn) + mf, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + fatal("unable to open output meta-data file %s: %v", fpath, err) + } + + // Encode and write. + mfw := encodemeta.NewCoverageMetaFileWriter(fpath, mf) + err = mfw.Write(finalHash, blobs, mm.Mode(), mm.Granularity()) + if err != nil { + fatal("error writing %s: %v\n", fpath, err) + } + return finalHash +} + +func (mm *metaMerge) emitCounters(outdir string, metaHash [16]byte) { + // Open output file. The file naming scheme is intended to mimic + // that used when running a coverage-instrumented binary, for + // consistency (however the process ID is not meaningful here, so + // use a value of zero). + var dummyPID int + fn := fmt.Sprintf(coverage.CounterFileTempl, coverage.CounterFilePref, metaHash, dummyPID, time.Now().UnixNano()) + fpath := filepath.Join(outdir, fn) + cf, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + fatal("opening counter data file %s: %v", fpath, err) + } + defer func() { + if err := cf.Close(); err != nil { + fatal("error closing output meta-data file %s: %v", fpath, err) + } + }() + + args := mm.astate.ArgsSummary() + cfw := encodecounter.NewCoverageDataWriter(cf, coverage.CtrULeb128) + if err := cfw.Write(metaHash, args, mm); err != nil { + fatal("counter file write failed: %v", err) + } + mm.astate = &argstate{} +} + +// VisitFuncs is used while writing the counter data files; it +// implements the 'VisitFuncs' method required by the interface +// internal/coverage/encodecounter/CounterVisitor. +func (mm *metaMerge) VisitFuncs(f encodecounter.CounterVisitorFn) error { + if *verbflag >= 4 { + fmt.Printf("counterVisitor invoked\n") + } + // For each package, for each function, construct counter + // array and then call "f" on it. + for pidx, p := range mm.pkgs { + fids := make([]int, 0, len(p.ctab)) + for fid := range p.ctab { + fids = append(fids, int(fid)) + } + sort.Ints(fids) + if *verbflag >= 4 { + fmt.Printf("fids for pk=%d: %+v\n", pidx, fids) + } + for _, fid := range fids { + fp := p.ctab[uint32(fid)] + if *verbflag >= 4 { + fmt.Printf("counter write for pk=%d fid=%d len(ctrs)=%d\n", pidx, fid, len(fp.Counters)) + } + if err := f(uint32(pidx), uint32(fid), fp.Counters); err != nil { + return err + } + } + } + return nil +} + +func (mm *metaMerge) visitPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32, pcombine bool) { + p, ok := mm.pkm[pd.PackagePath()] + if !ok { + p = &pkstate{ + pkgIdx: uint32(len(mm.pkgs)), + } + mm.pkgs = append(mm.pkgs, p) + mm.pkm[pd.PackagePath()] = p + if pcombine { + p.pcombinestate = new(pcombinestate) + cmdb, err := encodemeta.NewCoverageMetaDataBuilder(pd.PackagePath(), pd.PackageName(), pd.ModulePath()) + if err != nil { + fatal("fatal error creating meta-data builder: %v", err) + } + dbgtrace(2, "install new pkm entry for package %s pk=%d", pd.PackagePath(), pkgIdx) + p.cmdb = cmdb + p.ftab = make(map[[16]byte]uint32) + } else { + var err error + p.mdblob, err = mm.pod.mfr.GetPackagePayload(pkgIdx, nil) + if err != nil { + fatal("error extracting package %d payload from %s: %v", + pkgIdx, mm.pod.mdf, err) + } + } + p.ctab = make(map[uint32]decodecounter.FuncPayload) + } + mm.p = p +} + +func (mm *metaMerge) visitFuncCounterData(data decodecounter.FuncPayload) { + key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx} + val := mm.pod.pmm[key] + // FIXME: in theory either A) len(val.Counters) is zero, or B) + // the two lengths are equal. Assert if not? Of course, we could + // see odd stuff if there is source file skew. + if *verbflag > 4 { + fmt.Printf("visit pk=%d fid=%d len(counters)=%d\n", data.PkgIdx, data.FuncIdx, len(data.Counters)) + } + if len(val.Counters) < len(data.Counters) { + t := val.Counters + val.Counters = mm.AllocateCounters(len(data.Counters)) + copy(val.Counters, t) + } + err, overflow := mm.MergeCounters(val.Counters, data.Counters) + if err != nil { + fatal("%v", err) + } + if overflow { + warn("uint32 overflow during counter merge") + } + mm.pod.pmm[key] = val +} + +func (mm *metaMerge) visitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc, verb string, pcombine bool) { + if *verbflag >= 3 { + fmt.Printf("visit pk=%d fid=%d func %s\n", pkgIdx, fnIdx, fd.Funcname) + } + + var counters []uint32 + key := pkfunc{pk: pkgIdx, fcn: fnIdx} + v, haveCounters := mm.pod.pmm[key] + if haveCounters { + counters = v.Counters + } + + if pcombine { + // If the merge is running in "combine programs" mode, then hash + // the function and look it up in the package ftab to see if we've + // encountered it before. If we haven't, then register it with the + // meta-data builder. + fnhash := encodemeta.HashFuncDesc(fd) + gfidx, ok := mm.p.ftab[fnhash] + if !ok { + // We haven't seen this function before, need to add it to + // the meta data. + gfidx = uint32(mm.p.cmdb.AddFunc(*fd)) + mm.p.ftab[fnhash] = gfidx + if *verbflag >= 3 { + fmt.Printf("new meta entry for fn %s fid=%d\n", fd.Funcname, gfidx) + } + } + fnIdx = gfidx + } + if !haveCounters { + return + } + + // Install counters in package ctab. + gfp, ok := mm.p.ctab[fnIdx] + if ok { + if verb == "subtract" || verb == "intersect" { + panic("should never see this for intersect/subtract") + } + if *verbflag >= 3 { + fmt.Printf("counter merge for %s fidx=%d\n", fd.Funcname, fnIdx) + } + // Merge. + err, overflow := mm.MergeCounters(gfp.Counters, counters) + if err != nil { + fatal("%v", err) + } + if overflow { + warn("uint32 overflow during counter merge") + } + mm.p.ctab[fnIdx] = gfp + } else { + if *verbflag >= 3 { + fmt.Printf("null merge for %s fidx %d\n", fd.Funcname, fnIdx) + } + gfp := v + gfp.PkgIdx = mm.p.pkgIdx + gfp.FuncIdx = fnIdx + mm.p.ctab[fnIdx] = gfp + } +} |