Repository: aclements/go-perf Branch: master Commit: 7b712e5ff658 Files: 130 Total size: 1.0 MB Directory structure: gitextract_kmdeuzjx/ ├── .github/ │ └── workflows/ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── cmd/ │ ├── bitstringer/ │ │ └── main.go │ ├── branchstats/ │ │ └── main.go │ ├── memanim/ │ │ ├── .gitignore │ │ ├── hilbert_test.go │ │ └── main.go │ ├── memheat/ │ │ ├── draw.go │ │ ├── main.go │ │ └── svg.go │ ├── memlat/ │ │ ├── database.go │ │ ├── main.go │ │ └── static/ │ │ ├── bower.json │ │ ├── bower_components/ │ │ │ ├── font-roboto/ │ │ │ │ ├── .bower.json │ │ │ │ ├── README.md │ │ │ │ ├── bower.json │ │ │ │ └── roboto.html │ │ │ ├── paper-styles/ │ │ │ │ ├── .bower.json │ │ │ │ ├── README.md │ │ │ │ ├── bower.json │ │ │ │ ├── classes/ │ │ │ │ │ ├── global.html │ │ │ │ │ ├── shadow-layout.html │ │ │ │ │ ├── shadow.html │ │ │ │ │ └── typography.html │ │ │ │ ├── color.html │ │ │ │ ├── default-theme.html │ │ │ │ ├── demo/ │ │ │ │ │ └── index.html │ │ │ │ ├── demo-pages.html │ │ │ │ ├── demo.css │ │ │ │ ├── paper-styles-classes.html │ │ │ │ ├── paper-styles.html │ │ │ │ ├── shadow.html │ │ │ │ └── typography.html │ │ │ ├── polymer/ │ │ │ │ ├── .bower.json │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── bower.json │ │ │ │ ├── build.log │ │ │ │ ├── polymer-micro.html │ │ │ │ ├── polymer-mini.html │ │ │ │ └── polymer.html │ │ │ ├── promise-polyfill/ │ │ │ │ ├── .bower.json │ │ │ │ ├── Gruntfile.js │ │ │ │ ├── LICENSE │ │ │ │ ├── Promise-Statics.js │ │ │ │ ├── Promise.js │ │ │ │ ├── README.md │ │ │ │ ├── bower.json │ │ │ │ ├── package.json │ │ │ │ ├── promise-polyfill-lite.html │ │ │ │ └── promise-polyfill.html │ │ │ └── webcomponentsjs/ │ │ │ ├── .bower.json │ │ │ ├── CustomElements.js │ │ │ ├── HTMLImports.js │ │ │ ├── MutationObserver.js │ │ │ ├── README.md │ │ │ ├── ShadowDOM.js │ │ │ ├── bower.json │ │ │ ├── build.log │ │ │ ├── package.json │ │ │ ├── webcomponents-lite.js │ │ │ └── webcomponents.js │ │ ├── index.html │ │ └── memlat-browser.html │ ├── perfdump/ │ │ └── main.go │ └── prologuer/ │ └── main.go ├── fmt_test.go ├── go.mod ├── go.sum ├── internal/ │ ├── cparse/ │ │ ├── enums.go │ │ ├── enums_test.go │ │ ├── lex.go │ │ ├── lex_test.go │ │ ├── pp.go │ │ ├── pp_test.go │ │ └── vals.go │ └── gendefs/ │ ├── edit.go │ └── main.go ├── perffile/ │ ├── auxflags_string.go │ ├── auxpmuformat_string.go │ ├── bpfeventtype_string.go │ ├── branchflags_string.go │ ├── branchsampletype_string.go │ ├── breakpointop_string.go │ ├── buf.go │ ├── bufdecoder.go │ ├── cpumode_string.go │ ├── cpuset.go │ ├── datasrcblock_string.go │ ├── datasrchops_string.go │ ├── datasrclevel_string.go │ ├── datasrclevelnum_string.go │ ├── datasrclock_string.go │ ├── datasrcop_string.go │ ├── datasrcsnoop_string.go │ ├── datasrctlb_string.go │ ├── doc_test.go │ ├── eventflags_string.go │ ├── eventhardwareid_string.go │ ├── eventprecision_string.go │ ├── events.go │ ├── eventtype_string.go │ ├── format.go │ ├── gendefs.sh │ ├── ksymbolflags_string.go │ ├── ksymboltype_string.go │ ├── meta.go │ ├── package.go │ ├── reader.go │ ├── readformat_string.go │ ├── records.go │ ├── recordsorder_string.go │ ├── recordtype_string.go │ ├── sampleformat_string.go │ ├── sampleregsabi_string.go │ └── transaction_string.go ├── perfsession/ │ ├── package.go │ ├── ranges.go │ ├── session.go │ └── symbolize.go ├── scale/ │ ├── interface.go │ ├── linear.go │ ├── log.go │ ├── output.go │ ├── power.go │ └── util.go └── scripts/ ├── membw ├── memload.py └── topdown.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/test.yml ================================================ on: [push, pull_request] name: Test jobs: test: strategy: matrix: go-version: [1.17.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - uses: actions/checkout@v3 - run: go install golang.org/x/tools/cmd/stringer@latest - run: go test ./... ================================================ FILE: .gitignore ================================================ /cmd/bitstringer/bitstringer /cmd/branchstats/branchstats /cmd/memanim/memanim /cmd/memheat/memheat /cmd/memlat/memlat /cmd/perfdump/perfdump /cmd/prologuer/prologuer ================================================ FILE: LICENSE ================================================ Copyright (c) 2015 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ go-perf is a set of tools for working with Linux perf.data profiles, as well as a set of Go packages for parsing and interpreting such profiles. memlat ------ memlat is a web-based interactive browser for memory load latency profiles. Such profiles give deep and detailed insight in to the sources of memory stalls and conflicts, but are difficult to interpret using traditional profiling tools. See the [detailed documentation on godoc](http://godoc.org/github.com/aclements/go-perf/cmd/memlat). There is also a predecessor of memlat in `cmd/memheat`. This tool generates static SVG files summarizing memory load latency distributions by function and source line. This may be removed in the future. dump ---- dump prints the detailed decoded contents of a perf.data profile. It's similar to `perf report -D`, but is somewhat less mysterious. It's particularly useful when developing with the perffile library because it prints everything in terms of perffile structures. Libraries --------- This repository also contains two Go packages for parsing and interpreting perf.data files. [perffile](http://godoc.org/github.com/aclements/go-perf/perffile) provides a parser for perf.data files. It can interpret all current record types and almost all metadata fields. [perfsession](http://godoc.org/github.com/aclements/go-perf/perfsession) provides utilities for tracking session state while processing a perf.data file. Its API is still evolving and should be considered unstable. ================================================ FILE: cmd/bitstringer/main.go ================================================ // Copyright 2016 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. // Command bitstringer generates String methods for bit-mask types. // // bitstringer is like the stringer tool, but for bit-mask types. See // go doc stringer for details. // // bitstringer adds one flag, -strip, which specifies a prefix to // strip from stringified-constants. For bit-mask types in particular, // this can make the string representation much shorter, at the // expense of not being unambiguous and syntactically valid Go code. package main import ( "flag" "fmt" "go/ast" "go/build" "go/constant" "go/importer" "go/parser" "go/token" "go/types" "io" "log" "os" "path/filepath" "strings" ) func main() { flagTypes := flag.String("type", "", "comma-separated list of `types` to generate Stringers for") flagStrip := flag.String("strip", "", "strip `prefix` from constant names") flag.Parse() if flag.NArg() != 0 { flag.PrintDefaults() os.Exit(2) } // Find source files. wd, err := os.Getwd() if err != nil { log.Fatal(err) } pkg, err := build.ImportDir(wd, 0) if err != nil { log.Fatalf("importing %s: %v", wd, err) } paths := prefixDirectory(pkg.Dir, pkg.GoFiles) // Parse source files. fset := token.NewFileSet() var files []*ast.File for _, path := range paths { f, err := parser.ParseFile(fset, path, nil, 0) if err != nil { log.Fatalf("parsing file %s: %v", path, err) } files = append(files, f) } // Type check. conf := types.Config{Importer: importer.Default(), FakeImportC: true} info := &types.Info{ Defs: make(map[*ast.Ident]types.Object), } typesPkg, err := conf.Check(pkg.ImportPath, fset, files, info) if err != nil { log.Fatalf("checking package: %v", err) } scope := typesPkg.Scope() // Find the requested Types. names := strings.Split(*flagTypes, ",") name2Type := map[string]types.Type{} consts := map[types.Type][]*types.Const{} for _, name := range names { tname, ok := scope.Lookup(name).(*types.TypeName) if !ok { log.Fatalf("unknown type %q", name) } // Check that it's integral. utype := tname.Type().Underlying() if utype, ok := utype.(*types.Basic); !ok || utype.Info()&types.IsInteger == 0 { log.Fatalf("type %q is not an integer type", name) } name2Type[name] = tname.Type() consts[tname.Type()] = nil } // Find all constants with each Type. for _, name := range scope.Names() { obj := scope.Lookup(name) cobj, ok := obj.(*types.Const) if !ok { continue } constList, ok := consts[cobj.Type()] if !ok { continue } constList = append(constList, cobj) consts[cobj.Type()] = constList } // Construct String methods. for _, name := range names { fname := strings.ToLower(name) + "_string.go" f, err := os.Create(fname) if err != nil { log.Fatalf("error creating %s: %v", fname, err) } typ := name2Type[name] writeStringer(f, pkg.Name, name, *flagStrip, consts[typ]) if err := f.Close(); err != nil { log.Fatalf("error writing %s: %v", fname, err) } } } func prefixDirectory(dir string, names []string) []string { if dir == "." { return names } out := make([]string, len(names)) for i, name := range names { out[i] = filepath.Join(dir, name) } return out } func writeStringer(w io.Writer, pkg, tname, prefix string, consts []*types.Const) { if len(consts) == 0 { fmt.Fprintf(os.Stderr, "warning: no consts for type %q\n", tname) } fmt.Fprintf(w, `// Code generated by "bitstringer -type=%s"; DO NOT EDIT package %s import "strconv" func (i %s) String() string { `, tname, pkg, tname) strip := func(s string) string { return strings.TrimPrefix(s, prefix) } // Find and format any zero value. zero := constant.MakeInt64(0) zlabel := "0" for _, c := range consts { val := c.Val() if constant.Compare(val, token.EQL, zero) { // Format it. zlabel = strip(c.Name()) break } } fmt.Fprintf(w, "\tif i == 0 {\n\t\treturn %q\n\t}\n", zlabel) // Create bit value formatters. fmt.Fprintf(w, "\ts := \"\"\n") have := constant.MakeInt64(0) for _, c := range consts { // Does this contribute to the bit set? have2 := constant.BinaryOp(have, token.OR, c.Val()) if constant.Compare(have, token.EQL, have2) { // Nope. continue } have = have2 // Format it. fmt.Fprintf(w, "\tif i&%s != 0 {\n\t\ts += %q\n\t}\n", c.Name(), strip(c.Name())+"|") } // Handle any left-over bits. fmt.Fprintf(w, ` i &^= %s if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } `, have.ExactString()) } ================================================ FILE: cmd/branchstats/main.go ================================================ // Copyright 2015 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. // Command branchstats analyzes branch profiles for branch mispredict // rates. // // branchstats expects a perf.data collected with // // perf record -e branches -j any -c 400009 // // To collect only branches in user-space code, use // // perf record -e branches:u -j any,u -c 400009 // // The output is a table like // // comm PC branches mispredicts // bench scanner.go:258 419609441 309206957 (73.7%) // 257 func isLetter(ch rune) bool { // 258 return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) // 259 } // // bench mgcmark.go:1000 1967244262 236405319 (12.0%) // 999 } // 1000 if bits&bitPointer == 0 { // 1001 continue // not a pointer // // Each row shows a branch at a particular location and gives the // estimated number of times that branch executed, the estimated // number of mispredicts, and the mispredict rate. The table is sorted // by the number of mispredicts. package main import ( "bufio" "flag" "fmt" "log" "os" "path/filepath" "sort" "github.com/aclements/go-perf/perffile" "github.com/aclements/go-perf/perfsession" ) type PC struct { PC uint64 Comm string } type Agg struct { Mmap *perfsession.Mmap Events uint64 Predicted int64 Mispredicted int64 } type pair struct { PC Agg rate float64 } func main() { var ( flagInput = flag.String("i", "perf.data", "input perf.data `file`") ) flag.Parse() if flag.NArg() > 0 { flag.Usage() os.Exit(1) } f, err := perffile.Open(*flagInput) if err != nil { log.Fatal(err) } defer f.Close() s := perfsession.New(f) agg := make(map[PC]Agg) const requiredFormat = perffile.SampleFormatTID | perffile.SampleFormatBranchStack rs := f.Records(perffile.RecordsCausalOrder) for rs.Next() { r := rs.Record s.Update(r) switch r := r.(type) { case *perffile.RecordSample: if r.Format&requiredFormat != requiredFormat { break } pidinfo := s.LookupPID(r.PID) comm := "" var mmap *perfsession.Mmap if pidinfo != nil { comm = pidinfo.Comm mmap = pidinfo.LookupMmap(r.BranchStack[0].From) } // We ignore the location of the sample // because it's often not a branch (even in // precise mode). Instead, we take the most // recent branch record as an unbiased, // precise sampling of branches. Similarly, we // only take the prediction information from // the most recent branch. (Too bad there's no // way to tell perf we only want one branch // record.) br := r.BranchStack[0] pc := PC{br.From, comm} var events uint64 if r.Format&perffile.SampleFormatPeriod != 0 { events = r.Period } else if r.EventAttr.Flags&perffile.EventFlagFreq == 0 { events = r.EventAttr.SamplePeriod } else { log.Fatalf("sample %+v has no period", r) } a := agg[pc] a.Events += events a.Mmap = mmap if br.Flags&perffile.BranchFlagMispredicted != 0 { a.Mispredicted++ } if br.Flags&perffile.BranchFlagPredicted != 0 { a.Predicted++ } agg[pc] = a } } if err := rs.Err(); err != nil { log.Fatal(err) } // Rescale and sort records. pairs := make([]pair, 0) for pc, a := range agg { if a.Events == 0 { continue } rate := float64(a.Mispredicted) / float64(a.Predicted+a.Mispredicted) a.Mispredicted = int64(rate * float64(a.Events)) a.Predicted = int64(a.Events) - a.Mispredicted pairs = append(pairs, pair{pc, a, rate}) } sort.Sort(sort.Reverse(pairSorter(pairs))) // Print summary information. var total Agg for _, a := range pairs { total.Events += a.Events total.Mispredicted += a.Mispredicted total.Predicted += a.Predicted } fmt.Printf("# Total branches: %d\n", total.Events) fmt.Printf("# Total mispredicts: %d (%2.1f%% of all branches)\n", total.Mispredicted, 100*float64(total.Mispredicted)/float64(total.Events)) fmt.Printf("\n") // Print branch details. var sym perfsession.Symbolic fmt.Printf("%-8s %-24s %16s %s\n", "comm", "PC", "branches", "mispredicts") for _, pair := range pairs { var pos string var lines []string if pair.Mmap != nil && perfsession.Symbolize(s, pair.Mmap, pair.PC.PC, &sym) && sym.Line.File != nil { pos = fmt.Sprintf("%s:%d", filepath.Base(sym.Line.File.Name), sym.Line.Line) lines, _ = getLines(sym.Line.File.Name, sym.Line.Line-1, sym.Line.Line+1) } else { pos = fmt.Sprintf("%#-24x", pair.PC.PC) lines = nil } fmt.Printf("%-8.8s %-24s %16d %d (%2.1f%%)\n", pair.Comm, pos, pair.Events, pair.Mispredicted, 100*pair.rate) trim := stringCommon(lines) for i, line := range lines { fmt.Printf("%7d %s\n", i+sym.Line.Line-1, line[trim:]) } fmt.Printf("\n") } } type pairSorter []pair func (p pairSorter) Len() int { return len(p) } func (p pairSorter) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p pairSorter) Less(i, j int) bool { if p[i].Mispredicted != p[j].Mispredicted { return p[i].Mispredicted < p[j].Mispredicted } if p[i].Events != p[j].Events { return p[i].Events < p[j].Events } if p[i].Comm != p[j].Comm { return p[i].Comm < p[j].Comm } return p[i].PC.PC < p[j].PC.PC } func getLines(path string, minLine, maxLine int) ([]string, error) { // TODO: Make a nice line cache API. This isn't the only place // I've needed this. lines := make([]string, maxLine-minLine+1) file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() // Skip to minLine. scanner := bufio.NewScanner(file) for i := 0; i < minLine && scanner.Scan(); i++ { // Do nothing } for line := minLine; line <= maxLine && scanner.Err() == nil; line++ { lines[line-minLine] = scanner.Text() scanner.Scan() } if err := scanner.Err(); err != nil { return nil, err } return lines, nil } func stringCommon(strs []string) int { if len(strs) == 0 { return 0 } for i := 0; i < len(strs[0]); i++ { c := strs[0][i] for _, s := range strs { if i == len(s) || s[i] != c { return i } } } return len(strs[0]) } ================================================ FILE: cmd/memanim/.gitignore ================================================ /addr.png /f*.png /out.mp4 /memanim ================================================ FILE: cmd/memanim/hilbert_test.go ================================================ // Copyright 2015 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 import "testing" func TestHilbert(t *testing.T) { const n = 64 x0, y0 := hilbert(n, 0) have := make([]bool, n*n) have[x0+y0*n] = true for d := 1; d < n*n; d++ { x1, y1 := hilbert(n, d) if !(x0 == x1 && abs(y0-y1) == 1 || y0 == y1 && abs(x0-x1) == 1) { t.Fatalf("moved by more than 1: (%d,%d) -> (%d,%d)", x0, y0, x1, y1) } if have[x1+y1*n] { t.Fatalf("repeated point (%d,%d)", x1, y1) } have[x1+y1*n] = true x0, y0 = x1, y1 } } func abs(n int) int { if n < 0 { return -n } return n } ================================================ FILE: cmd/memanim/main.go ================================================ // Copyright 2015 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. // Command memanim creates an animation of memory accesses over time // from a "perf mem record" profile. In the animation, the address // space is compacted to remove pages that have no recorded references // and then mapped on to Hilbert curve so that nearby accesses appear // nearby in 2-D space. It is then broken in to panels showing all // accesses, L2-and-up accesses, etc. // // The simplest way to record a memory load profile is "perf mem // record ". // // To record only load latency events over a threshold number of // cycles, use the following command on Sandy Bridge or later: // // perf record -W -d -e cpu/event=0xcd,umask=0x1,ldlat=/pp // // The minimum (and default) latency threshold is 3 cycles. // // At a reasonably high latency threshold, such as 50 cycles, it's // possible to crank up to recording every single load with, e.g., // --count 1 -m 1024. // // To collect only user-space loads, change pp to ppu. package main import ( "flag" "fmt" "image" "image/color" "image/draw" "image/png" "io/ioutil" "log" "math" "os" "runtime/pprof" "sort" "github.com/aclements/go-perf/perffile" "github.com/golang/freetype" ) const pageBytes = 4096 func main() { var ( flagInput = flag.String("i", "perf.data", "read memory latency profile from `file`") flagBy = flag.String("by", "address", "`layout` by \"address\" or \"pc\"") flagFPS = flag.Int("fps", 24, "frames per second") flagDilation = flag.Float64("dilation", 1, "time dilation factor") flagWidth = flag.Int("w", 512, "output width/height; must be a power of 2") flagCpuProfile = flag.String("cpuprofile", "", "write cpu profile to file") ) flag.Parse() if flag.NArg() > 0 { flag.Usage() os.Exit(1) } if *flagWidth <= 0 || *flagWidth&(*flagWidth-1) != 0 { fmt.Fprintln(os.Stderr, "width must be a power of two") os.Exit(1) } if !(*flagBy == "address" || *flagBy == "pc") { fmt.Fprintln(os.Stderr, "-by must be address or pc") os.Exit(1) } if *flagCpuProfile != "" { f, err := os.Create(*flagCpuProfile) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } // TODO: Do something better with weight? I tried blue/red // coloring, but it really isn't obvious. Fade it out at a // certain rate? Shade? Ripples? events := parsePerf(*flagInput, *flagBy) // Canonicalize the events. imgSize := *flagWidth mapper := newAddrMapper(events, uint64(imgSize*imgSize-1)) normalizeWeight(events) zeroTime(events) lastTime := events[len(events)-1].time // Load font. // // TODO Don't hard-code it's location. Unfortunately, there's // no fontconfig equivalent for Go that I can find. fontCtx := freetype.NewContext() fontData, err := ioutil.ReadFile("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf") if err != nil { log.Fatal(err) } font, err := freetype.ParseFont(fontData) if err != nil { log.Fatal(err) } fontCtx.SetFontSize(12) fontCtx.SetSrc(image.Black) fontCtx.SetFont(font) fontBounds := font.Bounds(fontCtx.PointToFixed(12)) labelHeight := int((fontBounds.Max.Y - fontBounds.Min.Y) >> 6) // Create image. img := image.NewNRGBA(image.Rect(0, 0, (imgSize+1)*numPanels-1, imgSize+labelHeight)) draw.Draw(img, img.Bounds(), image.White, image.ZP, draw.Over) fontCtx.SetDst(img) fontCtx.SetClip(img.Bounds()) // Construct sub-images for panels and draw framing elements. levelImgs := make([]*image.NRGBA, numPanels) for i := range levelImgs { left := (imgSize + 1) * i levelImgs[i] = img.SubImage(image.Rect(left, labelHeight, left+imgSize, imgSize+labelHeight)).(*image.NRGBA) levelImgs[i].Rect = levelImgs[i].Rect.Sub(levelImgs[i].Rect.Min) if i > 0 { for y := img.Rect.Min.Y; y < img.Rect.Max.Y; y++ { img.Set(left-1, y, color.Black) } } fontCtx.DrawString(">= "+panelLevels[i].String(), freetype.Pt(left+2, 12)) } // Create address space reference image. // // TODO: This isn't very useful, it turns out. I could simply // print out the map from coordinate to address. Visually, I // could underlay boundaries between very distinct parts of // the address space (say, find points that have a >1GB break // and mark the boundary between all pixels before that break // and after that break). if false { addrStep := int(math.Floor(1 / mapper.normFactor)) for pfn := range mapper.pageBase { for offset := 0; offset < pageBytes; offset += addrStep { addr := pfn*pageBytes + uint64(offset) x, y := hilbert(imgSize, int(mapper.mapAddr(addr))) naddr := float64(addr%(1<<48)) / (1 << 48) * 2 * math.Pi cb, cr := math.Cos(naddr), -math.Sin(naddr) //fmt.Println(fmt.Sprintf("%016x", addr), int(mapper.mapAddr(addr)), naddr, cb, cr) r, g, b := color.YCbCrToRGB(127, uint8((cb+1)*127), uint8((cr+1)*127)) img.SetNRGBA(x, y, color.NRGBA{r, g, b, 255}) } } writePNG("addr.png", img) } nsPerFrame := int(1000000000 / (float64(*flagFPS) * *flagDilation)) lastIndex := 0 for frame := 0; ; frame++ { t0 := uint64(frame * nsPerFrame) t1 := uint64((frame + 1) * nsPerFrame) if t0 > lastTime { break } log.Println("frame", frame) // Fade the frame. // // TODO: The fade rate should be proportional to FPS. for _, levelImg := range levelImgs { for y := 0; y < levelImg.Rect.Dy(); y++ { scan := levelImg.Pix[y*levelImg.Stride : y*levelImg.Stride+levelImg.Rect.Dx()*4] for i, p := range scan { scan[i] = uint8(255 - (int(255-p) * 3 / 4)) } } } // Draw the events. for evIndex, ev := range events[lastIndex:] { if ev.time < t0 { panic("time went backwards") } if t1 <= ev.time { lastIndex += evIndex break } addr := mapper.mapAddr(ev.addr) x, y := hilbert(imgSize, int(addr)) //color := color.NRGBA{R: uint8(ev.weight), G: 0, B: 255 - uint8(ev.weight), A: 255} color := color.NRGBA{0, 0, 0, 255} for level := 0; level <= ev.level; level++ { levelImgs[level].SetNRGBA(x, y, color) } } // Write the frame out. writePNG(fmt.Sprintf("f%08d.png", frame), img) } fmt.Printf("%g bytes/pixel\n", 1/mapper.normFactor) fmt.Printf("%g pixels/page\n", mapper.normFactor*pageBytes) fmt.Printf("To combine frames:\n mencoder 'mf://f*.png' -mf fps=%d -nosound -of lavf -lavfopts format=mp4 -ovc x264 -o out.mp4\n", *flagFPS) } type event struct { time uint64 addr uint64 weight uint64 level int } // parsePerf parses a perf.data profile and returns the cache miss // events. func parsePerf(fileName, by string) []event { f, err := perffile.Open(fileName) if err != nil { fmt.Fprintf(os.Stderr, "error loading profile: %s\n", err) os.Exit(1) } defer f.Close() byPC := by == "pc" const requiredFormat = perffile.SampleFormatTime | perffile.SampleFormatAddr | perffile.SampleFormatWeight | perffile.SampleFormatDataSrc events := make([]event, 0) rs := f.Records(perffile.RecordsTimeOrder) for rs.Next() { r := rs.Record switch r := r.(type) { case *perffile.RecordSample: if r.Format&requiredFormat != requiredFormat { break } level := r.DataSrc.Level if r.DataSrc.Miss { level <<= 1 } addr := r.Addr if byPC { addr = r.IP } events = append(events, event{r.Time, addr, r.Weight, levelToPanel[level]}) } } return events } type addrMapper struct { pageBase map[uint64]uint64 normMax uint64 normFactor float64 // pixels/byte } // newAddrMapper returns an addrMapper that maps addresses in events // to a compacted space in the range [0, normMax]. func newAddrMapper(events []event, normMax uint64) *addrMapper { am := &addrMapper{normMax: normMax} // Find all distinct pages and max address. pages := make([]uint64, 0) pageSet := make(map[uint64]bool) maxAddr := uint64(0) for _, ev := range events { page := ev.addr / pageBytes if pageSet[page] { continue } pageSet[page] = true pages = append(pages, page) if ev.addr > maxAddr { maxAddr = ev.addr } } sort.Sort(uint64Slice(pages)) // Map pages to a compact sequence. am.pageBase = make(map[uint64]uint64, len(pages)) for i, page := range pages { am.pageBase[page] = uint64(i) * pageBytes } // Compute normalization factor. compactMax := am.pageBase[maxAddr/pageBytes] + maxAddr%pageBytes if compactMax <= normMax { am.normFactor = 1 } else { am.normFactor = float64(normMax) / float64(compactMax) } return am } func (am *addrMapper) mapAddr(addr uint64) uint64 { compact := am.pageBase[addr/pageBytes] + addr%pageBytes norm := uint64(float64(compact) * am.normFactor) if norm > am.normMax { norm = am.normMax } return norm } func normalizeWeight(events []event) { // Find the maximum weight. maxW := uint64(0) for _, ev := range events { if ev.weight > maxW { maxW = ev.weight } } // TODO: Log scale? // Normalize [0, maxW] to [0, 255]. factor := float64(255) / float64(maxW) for i, ev := range events { w := uint64(float64(ev.weight) * factor) if w > 255 { w = 255 } events[i].weight = w } } func zeroTime(events []event) { if len(events) == 0 { return } t0 := events[0].time for i := range events { events[i].time -= t0 } } type uint64Slice []uint64 func (s uint64Slice) Len() int { return len(s) } func (s uint64Slice) Less(i, j int) bool { return s[i] < s[j] } func (s uint64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // hilbert converts a 1-D point d to a coordinate (x, y) in an n×n // Hilbert space. func hilbert(n, d int) (x, y int) { // Based on Wikipedia. rot := func(s, x, y, rx, ry int) (int, int) { if ry == 0 { if rx == 1 { x = s - 1 - x y = s - 1 - y } x, y = y, x } return x, y } for s := 1; s < n; s *= 2 { rx := 1 & (d / 2) ry := 1 & (d ^ rx) x, y = rot(s, x, y, rx, ry) x += s * rx y += s * ry d /= 4 } return } func writePNG(path string, img image.Image) { f, err := os.Create(path) if err != nil { log.Fatal(err) } enc := png.Encoder{CompressionLevel: png.BestSpeed} if err := enc.Encode(f, img); err != nil { log.Fatal(err) } if err := f.Close(); err != nil { log.Fatal(err) } } // panelLevels maps from panel number to data source level. var panelLevels = [...]perffile.DataSrcLevel{ perffile.DataSrcLevelL1, perffile.DataSrcLevelL2, perffile.DataSrcLevelL3, perffile.DataSrcLevelLocalRAM, perffile.DataSrcLevelRemoteRAM1, } const numPanels = len(panelLevels) // levelToPanel maps from a data source level to a panel number. var levelToPanel = map[perffile.DataSrcLevel]int{ perffile.DataSrcLevelNA: 0, } func init() { for panel, level := range panelLevels { levelToPanel[level] = panel } var l int for i := perffile.DataSrcLevelL1; i <= perffile.DataSrcLevelUncached; i++ { if l2, ok := levelToPanel[i]; ok { l = l2 } else { levelToPanel[i] = l } } } ================================================ FILE: cmd/memheat/draw.go ================================================ // Copyright 2015 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 import ( "fmt" "image/color" "github.com/aclements/go-perf/scale" ) type TicksFormat struct { tickLen, minorTickLen, textSep float64 tickColor, labelColor color.Color labelFormat string } func (f *TicksFormat) HTicks(svg *SVG, scale scale.Interface, x scale.OutputScale, y float64) { x.Crop() major, minor := scale.Ticks(5) // Draw ticks if f.tickColor == nil { svg.SetStroke(color.Black) } else { svg.SetStroke(f.tickColor) } svg.NewPath() for _, sx := range major { if x, ok := x.Of(scale.Of(sx)); ok { svg.MoveTo(x, y) svg.LineToRel(0, -f.tickLen) } } for _, sx := range minor { if x, ok := x.Of(scale.Of(sx)); ok { svg.MoveTo(x, y) svg.LineToRel(0, -f.minorTickLen) } } svg.Stroke() svg.SetStroke(nil) // Draw labels lOpts := TextOpts{Anchor: AnchorMiddle} if f.labelFormat != "" { if f.labelColor == nil { svg.SetFill(color.Black) } else { svg.SetFill(f.labelColor) } for _, sx := range major { if x, ok := x.Of(scale.Of(sx)); ok { l := fmt.Sprintf(f.labelFormat, sx) svg.Text(x, y-f.tickLen-f.textSep, lOpts, l) } } svg.SetFill(nil) } } ================================================ FILE: cmd/memheat/main.go ================================================ // Copyright 2015 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 import ( "bufio" "debug/dwarf" "flag" "fmt" "image/color" "log" "os" "path" "sort" "github.com/aclements/go-perf/perffile" "github.com/aclements/go-perf/perfsession" "github.com/aclements/go-perf/scale" ) type lineStat struct { ip uint64 totalWeight uint64 weights []uint64 fn string src *dwarf.LineEntry yCoord float64 histogram []int } func main() { var ( flagInput = flag.String("i", "perf.data", "input perf.data file") flagLimit = flag.Int("limit", 30, "output top N functions") ) flag.Parse() if flag.NArg() > 0 { flag.Usage() os.Exit(1) } f, err := perffile.Open(*flagInput) if err != nil { log.Fatal(err) } defer f.Close() s := perfsession.New(f) // Collect samples by IP (TODO: by (comm, ip) or something) ipToInfo := map[uint64]*lineStat{} rs := f.Records(perffile.RecordsCausalOrder) for rs.Next() { r := rs.Record s.Update(r) switch r := r.(type) { case *perffile.RecordSample: mmap := s.LookupPID(r.PID).LookupMmap(r.IP) if mmap == nil { break } line, ok := ipToInfo[r.IP] if !ok { var symb perfsession.Symbolic if !perfsession.Symbolize(s, mmap, r.IP, &symb) { break } line = &lineStat{ ip: r.IP, fn: symb.FuncName, src: &symb.Line, } ipToInfo[r.IP] = line } weight := r.Weight if weight == 0 { weight = uint64(r.Weights.Var1) } line.weights = append(line.weights, weight) line.totalWeight += weight } } // Compute total function weight fnWeight := map[string]uint64{} for _, ls := range ipToInfo { fnWeight[ls.fn] += ls.totalWeight } // Sort stats by function weight, line number stats := make([]*lineStat, 0, len(ipToInfo)) for _, ls := range ipToInfo { stats = append(stats, ls) } sort.Sort(lineStatSorter{stats, fnWeight}) // Limit to the top N functions if *flagLimit > 0 { stats = limitFuncs(stats, *flagLimit) } // Find max weight maxWeight := uint64(0) for _, stat := range stats { for _, w := range stat.weights { if w > maxWeight { maxWeight = w } } } wscale := scale.NewPower([]float64{0, float64(maxWeight)}, 1/2.0) // Compute histograms and find max bar height const buckets = 50 lscale := scale.NewLog([]float64{1, float64(maxWeight + 1)}, 10) lscale.Nice(5) maxHeight := 0 for _, stat := range stats { stat.histogram = make([]int, buckets) for _, w := range stat.weights { bucket := int(lscale.Of(float64(w)) * buckets) stat.histogram[bucket] += int(w) } for _, height := range stat.histogram { if height > maxHeight { maxHeight = height } } } // Assign Y coordinates const ( marginTop = 45 cellHeight = 10 cellWidth = 10 fnGap = 10 lineLabelWidth = 30 groupWidth = 20 groupGap = 5 marginLeft = groupWidth*2 + groupGap marginRight = 300 sourceLeft = marginLeft + buckets*cellWidth ) y := marginTop // lastLine := -1 for i, stat := range stats { if i != 0 && (stat.fn != stats[i-1].fn || stat.src.File.Name != stats[i-1].src.File.Name) { y += fnGap // lastLine = -1 } // if lastLine != -1 { // y += cellHeight * (stat.src.Line - lastLine) // } stat.yCoord = float64(y) // lastLine = stat.src.Line y += cellHeight } // Emit SVG svg := NewSVG(os.Stdout, sourceLeft+marginRight, y) var ticks = TicksFormat{ tickLen: 5, minorTickLen: 3, textSep: 5, labelFormat: "%g", } // TODO: Show command line and hostname { lOpts := TextOpts{Anchor: AnchorMiddle} svg.SetFill(color.Black) y := float64(marginTop - ticks.tickLen - ticks.textSep) svg.Text(marginLeft+cellWidth*buckets/2, y-20, lOpts, "memory load latency (cycles)") svg.SetFill(nil) } // TODO: Draw color key // Label lines svg.NewPath() lastLineY := -1.0 for _, idxs := range sections(len(stats), func(i int) bool { return stats[i].src.Line != stats[i-1].src.Line }) { first, last := stats[idxs[0]], stats[idxs[1]-1] top := first.yCoord bot := last.yCoord + cellHeight if lastLineY != top { svg.MoveTo(sourceLeft, top) svg.LineToRel(ticks.tickLen, 0) } svg.MoveTo(sourceLeft, bot) svg.LineToRel(ticks.tickLen, 0) lastLineY = bot lOpts := TextOpts{Anchor: AnchorStart, Baseline: BaselineMiddle, FontSize: 10} svg.Text(sourceLeft+ticks.tickLen, (top+bot)/2, lOpts, fmt.Sprintf("%d", first.src.Line)) svg.Text(sourceLeft+lineLabelWidth, (top+bot)/2, lOpts, getLine(first.src.File.Name, first.src.Line)) } svg.SetStroke(color.Black) svg.Stroke() svg.SetStroke(nil) groupX := float64(marginLeft) // Label function groups for _, idxs := range sections(len(stats), func(i int) bool { return stats[i].fn != stats[i-1].fn }) { first, last := stats[idxs[0]], stats[idxs[1]-1] top := first.yCoord bot := last.yCoord + cellHeight // Ticks at the top of each function ticks.HTicks(svg, lscale, scale.NewOutputScale(marginLeft, marginLeft+cellWidth*buckets), top) ticks.labelFormat = "" // Function label lOpts := TextOpts{Anchor: AnchorMiddle, Rotate: -90} svg.SetFill(color.Gray{192}) svg.Rect(groupX-groupWidth, top, groupWidth, bot-top).FillPreserve().Clip() svg.SetFill(color.Black) svg.Text(groupX-5, (top+bot)/2, lOpts, first.fn) svg.ResetClip() } groupX -= groupWidth + groupGap // Label file name groups for _, idxs := range sections(len(stats), func(i int) bool { return stats[i].src.File.Name != stats[i-1].src.File.Name }) { lOpts := TextOpts{Anchor: AnchorMiddle, Rotate: -90} svg.SetFill(color.Gray{192}) top := stats[idxs[0]].yCoord bot := stats[idxs[1]-1].yCoord + cellHeight svg.Rect(groupX-groupWidth, top, groupWidth, bot-top).FillPreserve().Clip() svg.SetFill(color.Black) fileName := path.Base(stats[idxs[0]].src.File.Name) svg.Text(groupX-5, (top+bot)/2, lOpts, fileName) svg.ResetClip() } groupX -= groupWidth + groupGap // Draw heat map for _, stat := range stats { for x, height := range stat.histogram { if height == 0 { continue } shade := wscale.Of(float64(height)) svg.SetFill(color.NRGBA{255, 0, 0, uint8(255 * shade)}) svg.Rect(float64(marginLeft+x*cellWidth), stat.yCoord, cellWidth, cellHeight).Fill() } // Tooltip for raw IP svg.Rect(marginLeft, stat.yCoord, cellWidth*buckets, cellHeight).TooltipHighlight(fmt.Sprintf("IP: %#x", stat.ip)) } svg.SetFill(nil) svg.Done() } type lineStatSorter struct { lines []*lineStat fnWeight map[string]uint64 } func (s lineStatSorter) Len() int { return len(s.lines) } func (s lineStatSorter) Swap(i, j int) { s.lines[i], s.lines[j] = s.lines[j], s.lines[i] } func (s lineStatSorter) Less(i, j int) bool { // Sort by function weight first fni, fnj := s.lines[i].fn, s.lines[j].fn if s.fnWeight[fni] != s.fnWeight[fnj] { return s.fnWeight[fni] > s.fnWeight[fnj] } // Sort by file:line li, lj := s.lines[i].src, s.lines[j].src if li != nil || lj != nil { if li == nil || lj == nil { // Unknown line info comes first return li == nil } if li.File.Name != lj.File.Name { return li.File.Name < lj.File.Name } if li.Line != lj.Line { return li.Line < lj.Line } } // Finally, sort by IP return s.lines[i].ip < s.lines[j].ip } func limitFuncs(stats []*lineStat, limit int) []*lineStat { seen := 0 for i, stat := range stats { if i == 0 || stat.fn != stats[i-1].fn { if seen == limit { return stats[:i] } seen++ } } return stats } func sections(count int, newGroup func(int) bool) [][2]int { sections := make([][2]int, 0) if count == 0 { return sections } start := 0 for i := 1; i < count; i++ { if newGroup(i) { sections = append(sections, [2]int{start, i}) start = i } } sections = append(sections, [2]int{start, count}) return sections } func getLine(path string, line int) string { // TODO: Cache parsing file, err := os.Open(path) if err != nil { log.Println(err) return "" } defer file.Close() scanner := bufio.NewScanner(file) for i := 0; i < line && scanner.Scan(); i++ { // Do nothing } if err := scanner.Err(); err != nil { log.Fatal(err) } return scanner.Text() } ================================================ FILE: cmd/memheat/svg.go ================================================ // Copyright 2015 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 import ( "encoding/xml" "fmt" "image/color" "io" "strconv" "strings" ) type SVG struct { w io.Writer err error fill, stroke string lineWidth string clipPath string id int path []string } func NewSVG(w io.Writer, width, height int) *SVG { s := &SVG{w: w} s.fprintf("\n", width, height) s.fprintf("\n") s.NewPath() return s } type svglen float64 func (v svglen) String() string { return strconv.FormatFloat(float64(v), 'f', -1, 32) } func colorToCSS(c color.Color) string { cc := color.NRGBAModel.Convert(c).(color.NRGBA) if cc.A == 0xff { return fmt.Sprintf("rgb(%d,%d,%d)", cc.R, cc.G, cc.B) } return fmt.Sprintf("rgba(%d,%d,%d,%f)", cc.R, cc.G, cc.B, float64(cc.A)/0xff) } func (s *SVG) fprintf(format string, a ...interface{}) { if s.err != nil { return } _, s.err = fmt.Fprintf(s.w, format, a...) } func (s *SVG) SetFill(c color.Color) { if c == nil { s.fill = "" } else { s.fill = "fill:" + colorToCSS(c) } } func (s *SVG) SetStroke(c color.Color) { if c == nil { s.stroke = "" } else { s.stroke = "stroke:" + colorToCSS(c) } } func (s *SVG) SetLineWidth(lw float64) { s.lineWidth = fmt.Sprintf("stroke-width:%v", svglen(lw)) } func (s *SVG) style(parts ...string) string { val, sep := "", "" for _, part := range parts { if part != "" { val += sep + part sep = ";" } } if val != "" { return " style=\"" + val + "\"" } else { return "" } } func (s *SVG) NewPath() *SVG { s.path = []string{} return s } func (s *SVG) MoveTo(x, y float64) *SVG { s.path = append(s.path, fmt.Sprintf("M%v %v", svglen(x), svglen(y))) return s } func (s *SVG) LineToRel(xd, yd float64) *SVG { var op string if xd == 0 { op = fmt.Sprintf("v%v", svglen(yd)) } else if yd == 0 { op = fmt.Sprintf("h%v", svglen(xd)) } else { op = fmt.Sprintf("l%v %v", svglen(xd), svglen(yd)) } s.path = append(s.path, op) return s } func (s *SVG) Rect(x, y, w, h float64) *SVG { return s.MoveTo(x, y).LineToRel(w, 0).LineToRel(0, h).LineToRel(-w, 0).ClosePath() } func (s *SVG) ClosePath() *SVG { s.path = append(s.path, "z") return s } func (s *SVG) pathData() string { return strings.Join(s.path, "") } func (s *SVG) Stroke() *SVG { s.fprintf("\n", s.pathData(), s.style(s.stroke, s.lineWidth, s.clipPath)) return s.NewPath() } func (s *SVG) FillPreserve() *SVG { s.fprintf("\n", s.pathData(), s.style(s.fill, s.clipPath)) return s } func (s *SVG) Fill() *SVG { return s.FillPreserve().NewPath() } func (s *SVG) FillStroke() *SVG { s.fprintf("\n", s.pathData(), s.style(s.fill, s.stroke, s.lineWidth, s.clipPath)) return s.NewPath() } func (s *SVG) Clip() *SVG { s.fprintf("", s.id, s.pathData()) s.clipPath = fmt.Sprintf("clip-path:url(#i%d)", s.id) s.id++ return s.NewPath() } func (s *SVG) ResetClip() *SVG { s.clipPath = "" return s } func (s *SVG) Tooltip(text string) *SVG { s.fprintf("", s.pathData()) if s.err == nil { s.err = xml.EscapeText(s.w, []byte(text)) } s.fprintf("\n") return s.NewPath() } func (s *SVG) TooltipHighlight(text string) *SVG { s.fprintf("", s.pathData()) if s.err == nil { s.err = xml.EscapeText(s.w, []byte(text)) } s.fprintf("\n") return s.NewPath() } type Anchor int const ( AnchorStart Anchor = iota AnchorMiddle AnchorEnd ) type Baseline int const ( BaselineAuto Baseline = iota BaselineBaseline BaselineMiddle ) type TextOpts struct { Anchor Anchor Baseline Baseline Rotate float64 FontSize float64 } func (s *SVG) Text(x, y float64, opts TextOpts, text string) { astr := map[Anchor]string{ AnchorStart: "", AnchorMiddle: " text-anchor=\"middle\"", AnchorEnd: " text-anchor=\"end\"", }[opts.Anchor] bstr := map[Baseline]string{ BaselineAuto: "", BaselineBaseline: " dominant-baseline=\"baseline\"", BaselineMiddle: " dominant-baseline=\"middle\"", }[opts.Baseline] rstr := "" if opts.Rotate != 0 { rstr = fmt.Sprintf(" transform=\"rotate(%v,%v,%v)\"", svglen(opts.Rotate), svglen(x), svglen(y)) } fstr := "" if opts.FontSize != 0 { fstr = fmt.Sprintf(" font-size=\"%v\"", svglen(opts.FontSize)) } close := "" if s.clipPath != "" { // Don't apply rotation to clip path s.fprintf("", s.style(s.clipPath)) close = "" } s.fprintf("", svglen(x), svglen(y), astr, bstr, rstr, fstr, s.style(s.fill)) if s.err == nil { s.err = xml.EscapeText(s.w, []byte(text)) } s.fprintf("%s\n", close) } func (s *SVG) Done() error { s.fprintf("") return s.err } ================================================ FILE: cmd/memlat/database.go ================================================ // Copyright 2015 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 import ( "fmt" "os" "github.com/aclements/go-perf/perffile" "github.com/aclements/go-perf/perfsession" ) type database struct { // procs maps from PID to information and records for a // process. procs map[int]*proc // dataSrcs maps dataSrcIDs to full DataSrc information. // There's a lot of information in a DataSrc, but in practice // a given architecture will generate a small subset of the // possibilities. Hence, rather than storing a whole DataSrc // in every record, we canonicalize it to a small identifier. dataSrcs []perffile.DataSrc // maxLatency is the maximum latency value across all records // in this database. maxLatency uint32 // metadata records metadata fields from the profile. metadata Metadata } type proc struct { pid int comm string records []record ipInfo map[uint64]ipInfo } type record struct { ip uint64 address uint64 latency uint32 dataSrc dataSrcID } type ipInfo struct { funcName string fileName string line int } // dataSrcID is a small integer identifying a perffile.DataSrc. type dataSrcID uint32 type Metadata struct { Hostname string Arch string CPUDesc string `json:"CPU"` CmdLine []string `json:"Command line"` } // parsePerf parses a perf.data profile into a database. func parsePerf(fileName string) *database { f, err := perffile.Open(fileName) if os.IsNotExist(err) && fileName == "perf.data" { // Give a friendly error for first-time users. fmt.Fprintf(os.Stderr, "%s.\nTo record a profile, use\n perf mem record \nor specify an alternate profile path with -i.\n", err) os.Exit(1) } else if err != nil { fmt.Fprintf(os.Stderr, "error loading profile: %s\n", err) os.Exit(1) } defer f.Close() db := &database{ procs: make(map[int]*proc), } db.metadata.Hostname = f.Meta.Hostname db.metadata.Arch = f.Meta.Arch db.metadata.CPUDesc = f.Meta.CPUDesc db.metadata.CmdLine = f.Meta.CmdLine dataSrc2ID := make(map[perffile.DataSrc]dataSrcID) s := perfsession.New(f) numSamples := 0 droppedMmaps := 0 droppedSymbols := 0 const requiredFormat = perffile.SampleFormatIP | perffile.SampleFormatAddr | perffile.SampleFormatDataSrc rs := f.Records(perffile.RecordsCausalOrder) for rs.Next() { r := rs.Record s.Update(r) switch r := r.(type) { case *perffile.RecordComm: // Comm events usually happen after the first // few samples from this PID. p := db.procs[r.PID] if p != nil { p.comm = r.Comm } case *perffile.RecordSample: if r.Format&requiredFormat != requiredFormat { break } // Either Weight or WeightStruct is required. if r.Format&(perffile.SampleFormatWeight|perffile.SampleFormatWeightStruct) == 0 { break } numSamples++ pidInfo := s.LookupPID(r.PID) mmap := pidInfo.LookupMmap(r.IP) if mmap == nil { droppedMmaps++ break } // Find proc for r.PID. p, ok := db.procs[r.PID] if !ok { p = &proc{ pid: r.PID, comm: pidInfo.Comm, ipInfo: make(map[uint64]ipInfo), } db.procs[r.PID] = p } // Canonicalize data source. dsID, ok := dataSrc2ID[r.DataSrc] if !ok { dsID = dataSrcID(len(db.dataSrcs)) dataSrc2ID[r.DataSrc] = dsID db.dataSrcs = append(db.dataSrcs, r.DataSrc) } // Create the record. p.records = append(p.records, record{ ip: r.IP, address: r.Addr, latency: uint32(r.Weight), dataSrc: dsID, }) // Update database stats. if uint32(r.Weight) > db.maxLatency { db.maxLatency = uint32(r.Weight) } // Symbolize IP. if _, ok := p.ipInfo[r.IP]; !ok { // TODO: Intern strings var symb perfsession.Symbolic if !perfsession.Symbolize(s, mmap, r.IP, &symb) { droppedSymbols++ } if symb.FuncName == "" { symb.FuncName = "[unknown]" } fileName := "[unknown]" if symb.Line.File != nil && symb.Line.File.Name != "" { fileName = symb.Line.File.Name } p.ipInfo[r.IP] = ipInfo{ funcName: symb.FuncName, fileName: fileName, line: symb.Line.Line, } } } } if numSamples == 0 { fmt.Printf("no memory latency samples in %s (did you use \"perf mem record\"?)\n", fileName) os.Exit(1) } if droppedMmaps > 0 { fmt.Printf("warning: %d sample IPs (%d%%) occurred in unmapped memory regions\n", droppedMmaps, droppedMmaps*100/numSamples) } if droppedSymbols > 0 { fmt.Printf("warning: failed to symbolize %d samples (%d%%)\n", droppedSymbols, droppedSymbols*100/numSamples) } return db } // filter specifies a set of field values to filter records on. The // zero value of each field means not to filter on that field. type filter struct { pid int funcName string fileName string line int // Requires fileName. address uint64 dataSrc perffile.DataSrc } // filter invokes cb for every record matching f. func (db *database) filter(f *filter, cb func(*proc, *record)) { dsFilter := f.dataSrc != perffile.DataSrc{} filterProc := func(proc *proc) { var ds perffile.DataSrc // TODO: Consider creating indexes for some or all of // these. Then just do a list merge of the record // indexes. for i := range proc.records { // Avoid heap-allocating for passing rec to cb. rec := &proc.records[i] if f.address != 0 && f.address != rec.address { continue } ipi := proc.ipInfo[rec.ip] if f.funcName != "" && f.funcName != ipi.funcName { continue } if f.fileName != "" && f.fileName != ipi.fileName { continue } if f.line != 0 && f.line != ipi.line { continue } if !dsFilter { // Short-circuit dataSrc checking. goto good } ds = db.dataSrcs[rec.dataSrc] if f.dataSrc.Op != 0 && f.dataSrc.Op != ds.Op { continue } if f.dataSrc.Level != 0 && (f.dataSrc.Level != ds.Level || f.dataSrc.Miss != ds.Miss) { continue } if f.dataSrc.Snoop != 0 && f.dataSrc.Snoop != ds.Snoop { continue } if f.dataSrc.Locked != 0 && f.dataSrc.Locked != ds.Locked { continue } if f.dataSrc.TLB != 0 && f.dataSrc.TLB != ds.TLB { continue } good: cb(proc, rec) } } if f.pid == 0 { for _, proc := range db.procs { filterProc(proc) } } else { proc := db.procs[f.pid] if proc != nil { filterProc(proc) } } } ================================================ FILE: cmd/memlat/main.go ================================================ // Copyright 2015 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. // Command memlat is a web-based browser for memory load latency profiles. // // Memory stalls and conflicts are increasingly important for software // performance. Memory load latency profiles can give deep insights in // to these problems; however, the richness of these profiles makes // them difficult to interpret using traditional profiling tools and // techniques. // // memlat is a profile browser built for understanding and // interpreting memory load latency profiles. The central concept is a // "latency distribution", which is a statistical distribution of the // number of cycles spent in memory load or store operations. For // example, if there are 10 loads that take 10 cycles and 2 loads that // takes 100 cycles, the latency distribution consists of a 100 cycle // spike at 10 cycles and a 200 cycle spike at 100 cycles. The total // weight of this distribution accounts for the total cycles spent // waiting on memory loads or stores. // // memlat presents a profile as a multidimensional latency // distribution and provides a tools for viewing and filtering this // distribution on each dimension, such as by function, by source // line, by data source (L1 hit, TLB miss, etc), by address, etc. Each // tab in the UI browses the profile on a different dimension and // clicking on a row filters the profile down to just that function, // source line, etc. An active filters can be removed by clicking on // it in the filter bar at the top. // // For example, suppose we want to understand the primary source of // memory latency in a profile. Select the "By source line" tab and // click on the top row to filter to the source line that contributed // the most total memory latency. You can select the "Source // annotation" tab to see the text of this line. To drill down to a // particular memory address, select the "By address" tab to see the // memory addresses touched by this source line. Click the top one to // further filter to the hottest address touched by this source line. // Then, click the source line filter in the filter bar at the top to // remove the source line filter. Finally, select the "Source // annotation" tab to see the other source code lines that touch this // hot address. // // Note that the latency reported by the hardware is the time from // instruction issue to retire. Hence, a "fast load" (say, an L1 hit) // that happens immediately after a "slow load" (say, an LLC miss), // will have a high reported latency because it has to wait for the // slow load, even though the actual memory operation for the fast // load is fast. // // Usage // // To download and install memlat, run // // go get github.com/aclements/go-perf/cmd/memlat // // memlat works with the memory load latency profiles recorded by the // Linux perf tool. This requires hardware support that has been // available since Intel Nehalem. To record a memory latency profile, // use perf's "mem" subcommand. For example, // // perf mem record # Record a memory profile for command // perf mem record -a # Record a system-wide memory profile // // This will write the profile to a file called perf.data. Then, // simply start memlat with // // memlat // // memlat will parse and symbolize the profile and start a web server // listening by default on localhost;8001. package main import ( "bufio" "embed" "encoding/json" "flag" "fmt" "io/fs" "log" "net/http" "net/url" "os" "sort" "strconv" "github.com/aclements/go-moremath/scale" "github.com/aclements/go-moremath/vec" "github.com/aclements/go-perf/perffile" ) //go:embed static var staticFiles embed.FS // TODO: Open a browser automatically. Maybe bind to any address in // this mode. // TODO: Does this correctly handle the dynamic sampling rate that // perf mem record uses by default? func main() { var ( flagInput = flag.String("i", "perf.data", "read memory latency profile from `file`") flagHttp = flag.String("http", "localhost:8001", "serve HTTP on `address`") flagDocRoot = flag.String("docroot", "", "alternate `path` to static web resources") ) flag.Parse() if flag.NArg() > 0 { flag.Usage() os.Exit(1) } fmt.Fprintln(os.Stderr, "loading profile...") db := parsePerf(*flagInput) fmt.Fprintln(os.Stderr, "profile loaded") mux := http.NewServeMux() if *flagDocRoot == "" { // Use the embedded static assets. sub, _ := fs.Sub(staticFiles, "static") mux.Handle("/", http.FileServer(http.FS(sub))) } else { // Use assets from the file system. mux.Handle("/", http.FileServer(http.Dir(*flagDocRoot))) } mux.Handle("/h", &heatMapHandler{db}) mux.Handle("/metadata", &metadataHandler{*flagInput, db.metadata}) fmt.Fprintf(os.Stderr, "serving on %s\n", *flagHttp) if err := http.ListenAndServe(*flagHttp, mux); err != nil { log.Fatal(err) } } type heatMapHandler struct { db *database } func (h *heatMapHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // TOOD: Include a signature for this profile in the request // and mark the response as cacheable. // TODO: Compress the output. // Request includes filter, group by. Response: map from group // by to histograms. qs, err := url.ParseQuery(req.URL.RawQuery) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } atoi := func(s string) int { x, _ := strconv.Atoi(s) return x } f := filter{ pid: atoi(qs.Get("pid")), funcName: qs.Get("funcName"), fileName: qs.Get("fileName"), line: atoi(qs.Get("line")), address: uint64(atoi(qs.Get("address"))), dataSrc: perffile.DataSrc{ Op: perffile.DataSrcOp(atoi(qs.Get("op"))), Miss: qs.Get("miss") == "miss", Level: perffile.DataSrcLevel(atoi(qs.Get("level"))), Snoop: perffile.DataSrcSnoop(atoi(qs.Get("snoop"))), Locked: perffile.DataSrcLock(atoi(qs.Get("locked"))), TLB: perffile.DataSrcTLB(atoi(qs.Get("tlb"))), }, } groupBy := qs.Get("groupBy") limit := atoi(qs.Get("limit")) // Compute the scale for this histogram set. const useLocalScale = false var maxLatency uint32 = 1 if useLocalScale { h.db.filter(&f, func(p *proc, rec *record) { if rec.latency > maxLatency { maxLatency = rec.latency } }) } else { maxLatency = h.db.maxLatency } scaler, err := scale.NewLog(1, float64(maxLatency), 10) if err != nil { log.Fatal(err) } scaler.Nice(scale.TickOptions{Max: 6}) var histograms []*latencyHistogram newHist := func() *latencyHistogram { hist := newLatencyHistogram(&scaler) histograms = append(histograms, hist) return hist } // Create aggregation function. var agg func(*proc, *record) switch groupBy { default: http.Error(w, fmt.Sprintf("unknown groupby %q", groupBy), http.StatusBadRequest) return case "all": hist := newHist() agg = func(p *proc, r *record) { hist.update(r) } case "pid": groups := make(map[*proc]*latencyHistogram) agg = func(p *proc, r *record) { hist, ok := groups[p] if !ok { hist = newHist() hist.PID = p.pid hist.Comm = p.comm groups[p] = hist } hist.update(r) } case "funcName": groups := make(map[string]*latencyHistogram) agg = func(p *proc, r *record) { funcName := p.ipInfo[r.ip].funcName hist, ok := groups[funcName] if !ok { hist = newHist() hist.FuncName = funcName groups[funcName] = hist } hist.update(r) } case "annotation", "line": groups := make(map[ipInfo]*latencyHistogram) agg = func(p *proc, r *record) { ipInfo := p.ipInfo[r.ip] hist, ok := groups[ipInfo] if !ok { hist = newHist() hist.FileName = ipInfo.fileName hist.Line = ipInfo.line groups[ipInfo] = hist } hist.update(r) } case "address": groups := make(map[uint64]*latencyHistogram) agg = func(p *proc, r *record) { hist, ok := groups[r.address] if !ok { hist = newHist() hist.Address = r.address groups[r.address] = hist } hist.update(r) } case "dataSrc": groups := make(map[perffile.DataSrc]*latencyHistogram) add1 := func(r *record, key perffile.DataSrc, label, group string) { hist, ok := groups[key] if !ok { hist = newHist() hist.group = group hist.Op = key.Op hist.Miss = key.Miss hist.Level = key.Level hist.Snoop = key.Snoop hist.Locked = key.Locked hist.TLB = key.TLB hist.DataSrcLabel = label groups[key] = hist } hist.update(r) } agg = func(p *proc, r *record) { ds := h.db.dataSrcs[r.dataSrc] if ds.Op != 0 { add1(r, perffile.DataSrc{Op: ds.Op}, ds.Op.String(), "Operation") } if ds.Level != 0 { miss := " hit" if ds.Miss { miss = " miss" } add1(r, perffile.DataSrc{Miss: ds.Miss, Level: ds.Level}, ds.Level.String()+miss, "Cache level") } if ds.Snoop != 0 { add1(r, perffile.DataSrc{Snoop: ds.Snoop}, ds.Snoop.String(), "Snoop") } if ds.Locked != 0 { add1(r, perffile.DataSrc{Locked: ds.Locked}, ds.Locked.String()[11:], "Locked") } if ds.TLB != 0 { add1(r, perffile.DataSrc{TLB: ds.TLB}, ds.TLB.String(), "TLB") } } } h.db.filter(&f, agg) // Sort histograms by weight. sort.Sort(sort.Reverse(weightSorter(histograms))) // Take the top N histograms. if groupBy == "dataSrc" { limit = 0 } if limit != 0 && limit < len(histograms) { histograms = histograms[:limit] } // Special processing for some grouping types. switch groupBy { case "dataSrc": // Add group headers for i := 0; i < len(histograms); i++ { if i == 0 || histograms[i-1].group != histograms[i].group { histograms = append(histograms, nil) copy(histograms[i+1:], histograms[i:]) histograms[i] = &latencyHistogram{ Text: histograms[i+1].group, IsHeader: true, } i++ } } case "annotation": if len(histograms) == 0 { break } // TODO: When loading profile, check for out-of-date // source files and warn. ranges := []sourceRange{} histMap := map[ipInfo]*latencyHistogram{} for _, hist := range histograms { ranges = append(ranges, sourceRange{hist.FileName, hist.Line, hist.Line + 1, hist.weight}) histMap[ipInfo{"", hist.FileName, hist.Line}] = hist } ranges = expandSourceRanges(ranges, 5) // Sort ranges by max weight histogram. sort.Slice(ranges, func(i, j int) bool { return ranges[i].maxWeight > ranges[j].maxWeight }) // Collect lines from ranges. histograms = []*latencyHistogram{} for _, r := range ranges { lines, r, err := getLines(r) if err != nil { log.Println(err) continue } if len(lines) == 0 { continue } // Add a header for this range of source. header := newLatencyHistogram(&scaler) header.Bins = nil header.Text = r.file header.IsHeader = true histograms = append(histograms, header) for i, text := range lines { hist := histMap[ipInfo{"", r.file, r.start + i}] if hist == nil { hist = newLatencyHistogram(&scaler) hist.FileName = r.file hist.Line = r.start + i hist.Bins = nil } hist.Text = text histograms = append(histograms, hist) } } } // Compute maximum bin size for bin scaling. maxBin := 0 for _, hist := range histograms { max := hist.max() if max > maxBin { maxBin = max } } // Construct JSON reply. major, minor := scaler.Ticks(scale.TickOptions{Max: 6}) majorX, minorX := vec.Map(scaler.Map, major), vec.Map(scaler.Map, minor) err = json.NewEncoder(w).Encode(struct { Histograms []*latencyHistogram MaxBin int MajorTicks, MajorTicksX []float64 MinorTicksX []float64 }{histograms, maxBin, major, majorX, minorX}) if err != nil { log.Print(err) http.Error(w, err.Error(), http.StatusInternalServerError) } } const latencyHistogramBins = 60 // TODO: There's a lot of code for transforming between filters in the // query string, latencyHistogram, and the filter struct. Unify these // better. type latencyHistogram struct { scale scale.Quantitative Bins []int `json:",omitempty"` weight int group string // Filter specification. PID int `json:"pid,omitempty"` Comm string `json:"comm,omitempty"` FuncName string `json:"funcName,omitempty"` FileName string `json:"fileName,omitempty"` Line int `json:"line,omitempty"` Address uint64 `json:"address,omitempty"` // Data source filter specification. Op perffile.DataSrcOp `json:"op,omitempty"` Miss bool `json:"miss,omitempty"` Level perffile.DataSrcLevel `json:"level,omitempty"` Snoop perffile.DataSrcSnoop `json:"snoop,omitempty"` Locked perffile.DataSrcLock `json:"locked,omitempty"` TLB perffile.DataSrcTLB `json:"tlb,omitempty"` // Presentation. Text string `json:"text,omitempty"` DataSrcLabel string `json:"dataSrcLabel,omitempty"` IsHeader bool `json:"isHeader,omitempty"` } func newLatencyHistogram(scale scale.Quantitative) *latencyHistogram { return &latencyHistogram{ scale: scale, Bins: make([]int, latencyHistogramBins), weight: 0, } } func (h *latencyHistogram) update(r *record) { bin := int(h.scale.Map(float64(r.latency)) * latencyHistogramBins) if bin < 0 { bin = 0 } if bin >= latencyHistogramBins { bin = latencyHistogramBins } h.Bins[bin] += int(r.latency) h.weight += int(r.latency) } func (h *latencyHistogram) max() int { out := 0 for _, count := range h.Bins { if count > out { out = count } } return out } type weightSorter []*latencyHistogram func (w weightSorter) Len() int { return len(w) } func (w weightSorter) Less(i, j int) bool { if w[i].group != w[j].group { return w[i].group > w[j].group } return w[i].weight < w[j].weight } func (w weightSorter) Swap(i, j int) { w[i], w[j] = w[j], w[i] } type sourceRange struct { file string start, end int maxWeight int } func expandSourceRanges(r []sourceRange, by int) []sourceRange { sort.Slice(r, func(i, j int) bool { if r[i].file != r[j].file { return r[i].file < r[j].file } return r[i].start < r[j].start }) // Expand ranges. for i := range r { r[i].start -= by r[i].end += by } // Merge ranges. i := 0 for j := 1; j < len(r); j++ { if r[i].file == r[j].file && r[i].end >= r[j].start { if r[j].end > r[i].end { r[i].end = r[j].end } if r[j].maxWeight > r[i].maxWeight { r[i].maxWeight = r[j].maxWeight } } else { i++ r[i] = r[j] } } return r[:i+1] } func getLines(r sourceRange) ([]string, sourceRange, error) { lines := []string{} file, err := os.Open(r.file) if err != nil { return nil, r, err } defer file.Close() // Skip to start. if r.start < 0 { r.start = 0 } scanner := bufio.NewScanner(file) for i := 0; i < r.start && scanner.Scan(); i++ { // Do nothing } for i := 0; i < r.end-r.start && scanner.Err() == nil; i++ { lines = append(lines, scanner.Text()) scanner.Scan() } if err := scanner.Err(); err != nil { return nil, r, err } return lines, r, nil } type metadataHandler struct { Filename string Metadata } func (h *metadataHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { err := json.NewEncoder(w).Encode(h) if err != nil { log.Print(err) http.Error(w, err.Error(), http.StatusInternalServerError) } } ================================================ FILE: cmd/memlat/static/bower.json ================================================ { "name": "memlat", "version": "0.0.0", "homepage": "https://github.com/aclements/go-perf", "authors": [ "Austin Clements <(none)>" ], "license": "MIT", "ignore": [ "**/.*", "node_modules", "bower_components", "test", "tests" ], "dependencies": { "polymer": "Polymer/polymer#^1.0.0", "paper-button": "PolymerElements/paper-button#~1.0.3", "paper-tabs": "PolymerElements/paper-tabs#~1.0.2", "paper-styles": "PolymerElements/paper-styles#~1.0.11", "iron-pages": "PolymerElements/iron-pages#~1.0.3", "iron-ajax": "PolymerElements/iron-ajax#~1.0.4", "paper-card": "PolymerElements/paper-card#~1.0.3", "paper-header-panel": "PolymerElements/paper-header-panel#~1.0.4", "paper-toolbar": "PolymerElements/paper-toolbar#~1.0.4", "iron-icon": "PolymerElements/iron-icon#~1.0.3", "iron-icons": "PolymerElements/iron-icons#~1.0.3" } } ================================================ FILE: cmd/memlat/static/bower_components/font-roboto/.bower.json ================================================ { "name": "font-roboto", "version": "1.0.1", "description": "An HTML import for Roboto", "authors": [ "The Polymer Authors" ], "keywords": [ "font", "roboto" ], "repository": { "type": "git", "url": "git://github.com/PolymerElements/font-roboto.git" }, "main": "roboto.html", "license": "http://polymer.github.io/LICENSE.txt", "homepage": "https://github.com/PolymerElements/font-roboto/", "ignore": [ "/.*" ], "_release": "1.0.1", "_resolution": { "type": "version", "tag": "v1.0.1", "commit": "21ce9b51a417fa9995cf6606e886aba0728f70a1" }, "_source": "git://github.com/PolymerElements/font-roboto.git", "_target": "^1.0.1", "_originalSource": "PolymerElements/font-roboto" } ================================================ FILE: cmd/memlat/static/bower_components/font-roboto/README.md ================================================ # font-roboto ================================================ FILE: cmd/memlat/static/bower_components/font-roboto/bower.json ================================================ { "name": "font-roboto", "version": "1.0.1", "description": "An HTML import for Roboto", "authors": [ "The Polymer Authors" ], "keywords": [ "font", "roboto" ], "repository": { "type": "git", "url": "git://github.com/PolymerElements/font-roboto.git" }, "main": "roboto.html", "license": "http://polymer.github.io/LICENSE.txt", "homepage": "https://github.com/PolymerElements/font-roboto/", "ignore": [ "/.*" ] } ================================================ FILE: cmd/memlat/static/bower_components/font-roboto/roboto.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/.bower.json ================================================ { "name": "paper-styles", "version": "1.0.11", "description": "Common (global) styles for Material Design elements.", "authors": [ "The Polymer Authors" ], "keywords": [ "web-component", "polymer", "style" ], "repository": { "type": "git", "url": "git://github.com/PolymerElements/paper-styles.git" }, "main": "paper-styles.html", "license": "http://polymer.github.io/LICENSE.txt", "homepage": "https://github.com/polymerelements/paper-styles/", "ignore": [ "/.*" ], "dependencies": { "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.0", "font-roboto": "PolymerElements/font-roboto#^1.0.1", "polymer": "Polymer/polymer#^1.0.0" }, "devDependencies": { "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0" }, "_release": "1.0.11", "_resolution": { "type": "version", "tag": "v1.0.11", "commit": "347542e9ebe3e6e5f0830ee10e1c20c12956ff2c" }, "_source": "git://github.com/polymerelements/paper-styles.git", "_target": "^1.0.0", "_originalSource": "polymerelements/paper-styles" } ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/README.md ================================================ # paper-styles Material design CSS styles. ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/bower.json ================================================ { "name": "paper-styles", "version": "1.0.11", "description": "Common (global) styles for Material Design elements.", "authors": [ "The Polymer Authors" ], "keywords": [ "web-component", "polymer", "style" ], "repository": { "type": "git", "url": "git://github.com/PolymerElements/paper-styles.git" }, "main": "paper-styles.html", "license": "http://polymer.github.io/LICENSE.txt", "homepage": "https://github.com/polymerelements/paper-styles/", "ignore": [ "/.*" ], "dependencies": { "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.0", "font-roboto": "PolymerElements/font-roboto#^1.0.1", "polymer": "Polymer/polymer#^1.0.0" }, "devDependencies": { "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0" } } ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/classes/global.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/classes/shadow-layout.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/classes/shadow.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/classes/typography.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/color.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/default-theme.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/demo/index.html ================================================ paper-styles demo

demo-pages.html

Horizontal sections

Column 1

Oxygen
Carbon
Hydrogen
Nitrogen
Calcium

Column 2

Oxygen
Carbon
Hydrogen
Nitrogen
Calcium

Column 3

Oxygen
Carbon
Hydrogen
Nitrogen
Calcium

Vertical sections

Section 1

Oxygen
Carbon
Hydrogen
Nitrogen
Calcium

Section 2 (centered)

Oxygen
Carbon
Hydrogen
Nitrogen
Calcium
================================================ FILE: cmd/memlat/static/bower_components/paper-styles/demo-pages.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/demo.css ================================================ /** @license Copyright (c) 2015 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ body { font-family: 'Roboto', 'Noto', sans-serif; font-size: 14px; margin: 0; padding: 24px; } section { padding: 20px 0; } section > div { padding: 14px; font-size: 16px; } ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/paper-styles-classes.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/paper-styles.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/shadow.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/paper-styles/typography.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/polymer/.bower.json ================================================ { "name": "polymer", "version": "1.1.0", "main": [ "polymer.html" ], "license": "http://polymer.github.io/LICENSE.txt", "ignore": [ "/.*", "/test/" ], "authors": [ "The Polymer Authors (http://polymer.github.io/AUTHORS.txt)" ], "repository": { "type": "git", "url": "https://github.com/Polymer/polymer.git" }, "dependencies": { "webcomponentsjs": "^0.7.2" }, "devDependencies": { "web-component-tester": "*" }, "private": true, "homepage": "https://github.com/Polymer/polymer", "_release": "1.1.0", "_resolution": { "type": "version", "tag": "v1.1.0", "commit": "67fb2f85fd66d8556fc07cf1dec41ff5273fa68a" }, "_source": "git://github.com/Polymer/polymer.git", "_target": "^1.0.0", "_originalSource": "Polymer/polymer", "_direct": true } ================================================ FILE: cmd/memlat/static/bower_components/polymer/LICENSE.txt ================================================ // Copyright (c) 2014 The Polymer Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: cmd/memlat/static/bower_components/polymer/bower.json ================================================ { "name": "polymer", "version": "1.1.0", "main": [ "polymer.html" ], "license": "http://polymer.github.io/LICENSE.txt", "ignore": [ "/.*", "/test/" ], "authors": [ "The Polymer Authors (http://polymer.github.io/AUTHORS.txt)" ], "repository": { "type": "git", "url": "https://github.com/Polymer/polymer.git" }, "dependencies": { "webcomponentsjs": "^0.7.2" }, "devDependencies": { "web-component-tester": "*" }, "private": true } ================================================ FILE: cmd/memlat/static/bower_components/polymer/build.log ================================================ BUILD LOG --------- Build Time: 2015-08-13T16:56:33-0700 NODEJS INFORMATION ================== nodejs: v0.12.7 del: 1.2.0 gulp: 3.9.0 gulp-audit: 1.0.0 gulp-rename: 1.2.2 gulp-replace: 0.5.3 gulp-vulcanize: 6.0.1 lazypipe: 0.2.4 polyclean: 1.2.0 run-sequence: 1.1.1 REPO REVISIONS ============== polymer: a42ca09c3b99749b1407d5fa68cd957c2eaf5ca6 BUILD HASHES ============ polymer-mini.html: b40016f458e85bb815c898378b7bcd5c8abe5661 polymer-micro.html: ef8ebb2dc40697c845c2b8ec64ee69838d0d7bfc polymer.html: 2f874995a3a3ada9e87da48a01d10c6d3ee297bb ================================================ FILE: cmd/memlat/static/bower_components/polymer/polymer-micro.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/polymer/polymer-mini.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/polymer/polymer.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/promise-polyfill/.bower.json ================================================ { "name": "promise-polyfill", "version": "1.0.0", "homepage": "https://github.com/taylorhakes/promise-polyfill", "authors": [ "Taylor Hakes" ], "description": "Lightweight promise polyfill for the browser and node. A+ Compliant.", "main": "Promise.js", "moduleType": [ "globals", "node" ], "keywords": [ "promise", "es6", "polyfill", "html5" ], "license": "MIT", "ignore": [ "**/.*", "node_modules", "bower_components", "test", "tests" ], "dependencies": { "polymer": "polymer/polymer#^1.0.0" }, "_release": "1.0.0", "_resolution": { "type": "version", "tag": "v1.0.0", "commit": "2ef7dada161cae30e69ffff918485c57121d4b88" }, "_source": "git://github.com/polymerlabs/promise-polyfill.git", "_target": "^1.0.0", "_originalSource": "polymerlabs/promise-polyfill" } ================================================ FILE: cmd/memlat/static/bower_components/promise-polyfill/Gruntfile.js ================================================ module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), uglify: { options: { banner: '/*! <%= pkg.name %> <%= pkg.version %> */\n' }, dist: { files: { 'Promise.min.uglify.js': ['Promise.js'] } } }, closurecompiler: { options: { compilation_level: 'ADVANCED_OPTIMIZATIONS', }, dist: { files: { 'Promise.min.js': ['Promise.js'] } } }, bytesize: { dist: { src: ['Promise*.js'] } } }); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-closurecompiler'); grunt.loadNpmTasks('grunt-bytesize'); grunt.registerTask('build', ['closurecompiler', 'bytesize']); }; ================================================ FILE: cmd/memlat/static/bower_components/promise-polyfill/LICENSE ================================================ Copyright (c) 2014 Taylor Hakes Copyright (c) 2014 Forbes Lindesay Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: cmd/memlat/static/bower_components/promise-polyfill/Promise-Statics.js ================================================ Promise.all = Promise.all || function () { var args = Array.prototype.slice.call(arguments.length === 1 && Array.isArray(arguments[0]) ? arguments[0] : arguments); return new Promise(function (resolve, reject) { if (args.length === 0) return resolve([]); var remaining = args.length; function res(i, val) { try { if (val && (typeof val === 'object' || typeof val === 'function')) { var then = val.then; if (typeof then === 'function') { then.call(val, function (val) { res(i, val) }, reject); return; } } args[i] = val; if (--remaining === 0) { resolve(args); } } catch (ex) { reject(ex); } } for (var i = 0; i < args.length; i++) { res(i, args[i]); } }); }; Promise.race = Promise.race || function (values) { return new Promise(function (resolve, reject) { for(var i = 0, len = values.length; i < len; i++) { values[i].then(resolve, reject); } }); }; ================================================ FILE: cmd/memlat/static/bower_components/promise-polyfill/Promise.js ================================================ function MakePromise (asap) { function Promise(fn) { if (typeof this !== 'object' || typeof fn !== 'function') throw new TypeError(); this._state = null; this._value = null; this._deferreds = [] doResolve(fn, resolve.bind(this), reject.bind(this)); } function handle(deferred) { var me = this; if (this._state === null) { this._deferreds.push(deferred); return } asap(function() { var cb = me._state ? deferred.onFulfilled : deferred.onRejected if (typeof cb !== 'function') { (me._state ? deferred.resolve : deferred.reject)(me._value); return; } var ret; try { ret = cb(me._value); } catch (e) { deferred.reject(e); return; } deferred.resolve(ret); }) } function resolve(newValue) { try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure if (newValue === this) throw new TypeError(); if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { var then = newValue.then; if (typeof then === 'function') { doResolve(then.bind(newValue), resolve.bind(this), reject.bind(this)); return; } } this._state = true; this._value = newValue; finale.call(this); } catch (e) { reject.call(this, e); } } function reject(newValue) { this._state = false; this._value = newValue; finale.call(this); } function finale() { for (var i = 0, len = this._deferreds.length; i < len; i++) { handle.call(this, this._deferreds[i]); } this._deferreds = null; } /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. */ function doResolve(fn, onFulfilled, onRejected) { var done = false; try { fn(function (value) { if (done) return; done = true; onFulfilled(value); }, function (reason) { if (done) return; done = true; onRejected(reason); }) } catch (ex) { if (done) return; done = true; onRejected(ex); } } Promise.prototype['catch'] = function (onRejected) { return this.then(null, onRejected); }; Promise.prototype.then = function(onFulfilled, onRejected) { var me = this; return new Promise(function(resolve, reject) { handle.call(me, { onFulfilled: onFulfilled, onRejected: onRejected, resolve: resolve, reject: reject }); }) }; Promise.resolve = function (value) { if (value && typeof value === 'object' && value.constructor === Promise) { return value; } return new Promise(function (resolve) { resolve(value); }); }; Promise.reject = function (value) { return new Promise(function (resolve, reject) { reject(value); }); }; return Promise; } if (typeof module !== 'undefined') { module.exports = MakePromise; } ================================================ FILE: cmd/memlat/static/bower_components/promise-polyfill/README.md ================================================ # Promise Polyfill Note: this is an unsolicited fork of [taylorhakes/promise-polyfill](https://github.com/taylorhakes/promise-polyfill) and should be considered experimental and unstable compared to upstream. ## Testing ``` npm install npm test ``` ## License MIT ================================================ FILE: cmd/memlat/static/bower_components/promise-polyfill/bower.json ================================================ { "name": "promise-polyfill", "version": "1.0.0", "homepage": "https://github.com/taylorhakes/promise-polyfill", "authors": [ "Taylor Hakes" ], "description": "Lightweight promise polyfill for the browser and node. A+ Compliant.", "main": "Promise.js", "moduleType": [ "globals", "node" ], "keywords": [ "promise", "es6", "polyfill", "html5" ], "license": "MIT", "ignore": [ "**/.*", "node_modules", "bower_components", "test", "tests" ], "dependencies": { "polymer": "polymer/polymer#^1.0.0" } } ================================================ FILE: cmd/memlat/static/bower_components/promise-polyfill/package.json ================================================ { "name": "promise-polyfill", "version": "2.0.0", "description": "Lightweight promise polyfill. A+ compliant", "main": "Promise.js", "scripts": { "test": "./node_modules/.bin/promises-aplus-tests tests/adapter.js; ./node_modules/.bin/promises-es6-tests tests/adapter.js" }, "repository": { "type": "git", "url": "https://taylorhakes@github.com/taylorhakes/promise-polyfill.git" }, "author": "Taylor Hakes", "license": "MIT", "bugs": { "url": "https://github.com/taylorhakes/promise-polyfill/issues" }, "homepage": "https://github.com/taylorhakes/promise-polyfill", "devDependencies": { "grunt": "^0.4.5", "grunt-bytesize": "^0.1.1", "grunt-closurecompiler": "^0.9.9", "grunt-contrib-uglify": "^0.4.0", "mocha": "^2.2.1", "promises-aplus-tests": "*", "promises-es6-tests": "^0.5.0" }, "keywords": [ "promise", "promise-polyfill", "ES6", "promises-aplus" ], "dependencies": {} } ================================================ FILE: cmd/memlat/static/bower_components/promise-polyfill/promise-polyfill-lite.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/promise-polyfill/promise-polyfill.html ================================================ ================================================ FILE: cmd/memlat/static/bower_components/webcomponentsjs/.bower.json ================================================ { "name": "webcomponentsjs", "main": "webcomponents.js", "version": "0.7.11", "homepage": "http://webcomponents.org", "authors": [ "The Polymer Authors" ], "repository": { "type": "git", "url": "https://github.com/webcomponents/webcomponentsjs.git" }, "keywords": [ "webcomponents" ], "license": "BSD", "ignore": [], "_release": "0.7.11", "_resolution": { "type": "version", "tag": "v0.7.11", "commit": "ce6321507de6161ec52b43f82a6c36eda614d750" }, "_source": "git://github.com/Polymer/webcomponentsjs.git", "_target": "^0.7.2", "_originalSource": "webcomponentsjs" } ================================================ FILE: cmd/memlat/static/bower_components/webcomponentsjs/CustomElements.js ================================================ /** * @license * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ // @version 0.7.11 if (typeof WeakMap === "undefined") { (function() { var defineProperty = Object.defineProperty; var counter = Date.now() % 1e9; var WeakMap = function() { this.name = "__st" + (Math.random() * 1e9 >>> 0) + (counter++ + "__"); }; WeakMap.prototype = { set: function(key, value) { var entry = key[this.name]; if (entry && entry[0] === key) entry[1] = value; else defineProperty(key, this.name, { value: [ key, value ], writable: true }); return this; }, get: function(key) { var entry; return (entry = key[this.name]) && entry[0] === key ? entry[1] : undefined; }, "delete": function(key) { var entry = key[this.name]; if (!entry || entry[0] !== key) return false; entry[0] = entry[1] = undefined; return true; }, has: function(key) { var entry = key[this.name]; if (!entry) return false; return entry[0] === key; } }; window.WeakMap = WeakMap; })(); } (function(global) { var registrationsTable = new WeakMap(); var setImmediate; if (/Trident|Edge/.test(navigator.userAgent)) { setImmediate = setTimeout; } else if (window.setImmediate) { setImmediate = window.setImmediate; } else { var setImmediateQueue = []; var sentinel = String(Math.random()); window.addEventListener("message", function(e) { if (e.data === sentinel) { var queue = setImmediateQueue; setImmediateQueue = []; queue.forEach(function(func) { func(); }); } }); setImmediate = function(func) { setImmediateQueue.push(func); window.postMessage(sentinel, "*"); }; } var isScheduled = false; var scheduledObservers = []; function scheduleCallback(observer) { scheduledObservers.push(observer); if (!isScheduled) { isScheduled = true; setImmediate(dispatchCallbacks); } } function wrapIfNeeded(node) { return window.ShadowDOMPolyfill && window.ShadowDOMPolyfill.wrapIfNeeded(node) || node; } function dispatchCallbacks() { isScheduled = false; var observers = scheduledObservers; scheduledObservers = []; observers.sort(function(o1, o2) { return o1.uid_ - o2.uid_; }); var anyNonEmpty = false; observers.forEach(function(observer) { var queue = observer.takeRecords(); removeTransientObserversFor(observer); if (queue.length) { observer.callback_(queue, observer); anyNonEmpty = true; } }); if (anyNonEmpty) dispatchCallbacks(); } function removeTransientObserversFor(observer) { observer.nodes_.forEach(function(node) { var registrations = registrationsTable.get(node); if (!registrations) return; registrations.forEach(function(registration) { if (registration.observer === observer) registration.removeTransientObservers(); }); }); } function forEachAncestorAndObserverEnqueueRecord(target, callback) { for (var node = target; node; node = node.parentNode) { var registrations = registrationsTable.get(node); if (registrations) { for (var j = 0; j < registrations.length; j++) { var registration = registrations[j]; var options = registration.options; if (node !== target && !options.subtree) continue; var record = callback(options); if (record) registration.enqueue(record); } } } } var uidCounter = 0; function JsMutationObserver(callback) { this.callback_ = callback; this.nodes_ = []; this.records_ = []; this.uid_ = ++uidCounter; } JsMutationObserver.prototype = { observe: function(target, options) { target = wrapIfNeeded(target); if (!options.childList && !options.attributes && !options.characterData || options.attributeOldValue && !options.attributes || options.attributeFilter && options.attributeFilter.length && !options.attributes || options.characterDataOldValue && !options.characterData) { throw new SyntaxError(); } var registrations = registrationsTable.get(target); if (!registrations) registrationsTable.set(target, registrations = []); var registration; for (var i = 0; i < registrations.length; i++) { if (registrations[i].observer === this) { registration = registrations[i]; registration.removeListeners(); registration.options = options; break; } } if (!registration) { registration = new Registration(this, target, options); registrations.push(registration); this.nodes_.push(target); } registration.addListeners(); }, disconnect: function() { this.nodes_.forEach(function(node) { var registrations = registrationsTable.get(node); for (var i = 0; i < registrations.length; i++) { var registration = registrations[i]; if (registration.observer === this) { registration.removeListeners(); registrations.splice(i, 1); break; } } }, this); this.records_ = []; }, takeRecords: function() { var copyOfRecords = this.records_; this.records_ = []; return copyOfRecords; } }; function MutationRecord(type, target) { this.type = type; this.target = target; this.addedNodes = []; this.removedNodes = []; this.previousSibling = null; this.nextSibling = null; this.attributeName = null; this.attributeNamespace = null; this.oldValue = null; } function copyMutationRecord(original) { var record = new MutationRecord(original.type, original.target); record.addedNodes = original.addedNodes.slice(); record.removedNodes = original.removedNodes.slice(); record.previousSibling = original.previousSibling; record.nextSibling = original.nextSibling; record.attributeName = original.attributeName; record.attributeNamespace = original.attributeNamespace; record.oldValue = original.oldValue; return record; } var currentRecord, recordWithOldValue; function getRecord(type, target) { return currentRecord = new MutationRecord(type, target); } function getRecordWithOldValue(oldValue) { if (recordWithOldValue) return recordWithOldValue; recordWithOldValue = copyMutationRecord(currentRecord); recordWithOldValue.oldValue = oldValue; return recordWithOldValue; } function clearRecords() { currentRecord = recordWithOldValue = undefined; } function recordRepresentsCurrentMutation(record) { return record === recordWithOldValue || record === currentRecord; } function selectRecord(lastRecord, newRecord) { if (lastRecord === newRecord) return lastRecord; if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) return recordWithOldValue; return null; } function Registration(observer, target, options) { this.observer = observer; this.target = target; this.options = options; this.transientObservedNodes = []; } Registration.prototype = { enqueue: function(record) { var records = this.observer.records_; var length = records.length; if (records.length > 0) { var lastRecord = records[length - 1]; var recordToReplaceLast = selectRecord(lastRecord, record); if (recordToReplaceLast) { records[length - 1] = recordToReplaceLast; return; } } else { scheduleCallback(this.observer); } records[length] = record; }, addListeners: function() { this.addListeners_(this.target); }, addListeners_: function(node) { var options = this.options; if (options.attributes) node.addEventListener("DOMAttrModified", this, true); if (options.characterData) node.addEventListener("DOMCharacterDataModified", this, true); if (options.childList) node.addEventListener("DOMNodeInserted", this, true); if (options.childList || options.subtree) node.addEventListener("DOMNodeRemoved", this, true); }, removeListeners: function() { this.removeListeners_(this.target); }, removeListeners_: function(node) { var options = this.options; if (options.attributes) node.removeEventListener("DOMAttrModified", this, true); if (options.characterData) node.removeEventListener("DOMCharacterDataModified", this, true); if (options.childList) node.removeEventListener("DOMNodeInserted", this, true); if (options.childList || options.subtree) node.removeEventListener("DOMNodeRemoved", this, true); }, addTransientObserver: function(node) { if (node === this.target) return; this.addListeners_(node); this.transientObservedNodes.push(node); var registrations = registrationsTable.get(node); if (!registrations) registrationsTable.set(node, registrations = []); registrations.push(this); }, removeTransientObservers: function() { var transientObservedNodes = this.transientObservedNodes; this.transientObservedNodes = []; transientObservedNodes.forEach(function(node) { this.removeListeners_(node); var registrations = registrationsTable.get(node); for (var i = 0; i < registrations.length; i++) { if (registrations[i] === this) { registrations.splice(i, 1); break; } } }, this); }, handleEvent: function(e) { e.stopImmediatePropagation(); switch (e.type) { case "DOMAttrModified": var name = e.attrName; var namespace = e.relatedNode.namespaceURI; var target = e.target; var record = new getRecord("attributes", target); record.attributeName = name; record.attributeNamespace = namespace; var oldValue = e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; forEachAncestorAndObserverEnqueueRecord(target, function(options) { if (!options.attributes) return; if (options.attributeFilter && options.attributeFilter.length && options.attributeFilter.indexOf(name) === -1 && options.attributeFilter.indexOf(namespace) === -1) { return; } if (options.attributeOldValue) return getRecordWithOldValue(oldValue); return record; }); break; case "DOMCharacterDataModified": var target = e.target; var record = getRecord("characterData", target); var oldValue = e.prevValue; forEachAncestorAndObserverEnqueueRecord(target, function(options) { if (!options.characterData) return; if (options.characterDataOldValue) return getRecordWithOldValue(oldValue); return record; }); break; case "DOMNodeRemoved": this.addTransientObserver(e.target); case "DOMNodeInserted": var changedNode = e.target; var addedNodes, removedNodes; if (e.type === "DOMNodeInserted") { addedNodes = [ changedNode ]; removedNodes = []; } else { addedNodes = []; removedNodes = [ changedNode ]; } var previousSibling = changedNode.previousSibling; var nextSibling = changedNode.nextSibling; var record = getRecord("childList", e.target.parentNode); record.addedNodes = addedNodes; record.removedNodes = removedNodes; record.previousSibling = previousSibling; record.nextSibling = nextSibling; forEachAncestorAndObserverEnqueueRecord(e.relatedNode, function(options) { if (!options.childList) return; return record; }); } clearRecords(); } }; global.JsMutationObserver = JsMutationObserver; if (!global.MutationObserver) global.MutationObserver = JsMutationObserver; })(this); window.CustomElements = window.CustomElements || { flags: {} }; (function(scope) { var flags = scope.flags; var modules = []; var addModule = function(module) { modules.push(module); }; var initializeModules = function() { modules.forEach(function(module) { module(scope); }); }; scope.addModule = addModule; scope.initializeModules = initializeModules; scope.hasNative = Boolean(document.registerElement); scope.useNative = !flags.register && scope.hasNative && !window.ShadowDOMPolyfill && (!window.HTMLImports || window.HTMLImports.useNative); })(window.CustomElements); window.CustomElements.addModule(function(scope) { var IMPORT_LINK_TYPE = window.HTMLImports ? window.HTMLImports.IMPORT_LINK_TYPE : "none"; function forSubtree(node, cb) { findAllElements(node, function(e) { if (cb(e)) { return true; } forRoots(e, cb); }); forRoots(node, cb); } function findAllElements(node, find, data) { var e = node.firstElementChild; if (!e) { e = node.firstChild; while (e && e.nodeType !== Node.ELEMENT_NODE) { e = e.nextSibling; } } while (e) { if (find(e, data) !== true) { findAllElements(e, find, data); } e = e.nextElementSibling; } return null; } function forRoots(node, cb) { var root = node.shadowRoot; while (root) { forSubtree(root, cb); root = root.olderShadowRoot; } } function forDocumentTree(doc, cb) { _forDocumentTree(doc, cb, []); } function _forDocumentTree(doc, cb, processingDocuments) { doc = window.wrap(doc); if (processingDocuments.indexOf(doc) >= 0) { return; } processingDocuments.push(doc); var imports = doc.querySelectorAll("link[rel=" + IMPORT_LINK_TYPE + "]"); for (var i = 0, l = imports.length, n; i < l && (n = imports[i]); i++) { if (n.import) { _forDocumentTree(n.import, cb, processingDocuments); } } cb(doc); } scope.forDocumentTree = forDocumentTree; scope.forSubtree = forSubtree; }); window.CustomElements.addModule(function(scope) { var flags = scope.flags; var forSubtree = scope.forSubtree; var forDocumentTree = scope.forDocumentTree; function addedNode(node, isAttached) { return added(node, isAttached) || addedSubtree(node, isAttached); } function added(node, isAttached) { if (scope.upgrade(node, isAttached)) { return true; } if (isAttached) { attached(node); } } function addedSubtree(node, isAttached) { forSubtree(node, function(e) { if (added(e, isAttached)) { return true; } }); } var hasPolyfillMutations = !window.MutationObserver || window.MutationObserver === window.JsMutationObserver; scope.hasPolyfillMutations = hasPolyfillMutations; var isPendingMutations = false; var pendingMutations = []; function deferMutation(fn) { pendingMutations.push(fn); if (!isPendingMutations) { isPendingMutations = true; setTimeout(takeMutations); } } function takeMutations() { isPendingMutations = false; var $p = pendingMutations; for (var i = 0, l = $p.length, p; i < l && (p = $p[i]); i++) { p(); } pendingMutations = []; } function attached(element) { if (hasPolyfillMutations) { deferMutation(function() { _attached(element); }); } else { _attached(element); } } function _attached(element) { if (element.__upgraded__ && !element.__attached) { element.__attached = true; if (element.attachedCallback) { element.attachedCallback(); } } } function detachedNode(node) { detached(node); forSubtree(node, function(e) { detached(e); }); } function detached(element) { if (hasPolyfillMutations) { deferMutation(function() { _detached(element); }); } else { _detached(element); } } function _detached(element) { if (element.__upgraded__ && element.__attached) { element.__attached = false; if (element.detachedCallback) { element.detachedCallback(); } } } function inDocument(element) { var p = element; var doc = window.wrap(document); while (p) { if (p == doc) { return true; } p = p.parentNode || p.nodeType === Node.DOCUMENT_FRAGMENT_NODE && p.host; } } function watchShadow(node) { if (node.shadowRoot && !node.shadowRoot.__watched) { flags.dom && console.log("watching shadow-root for: ", node.localName); var root = node.shadowRoot; while (root) { observe(root); root = root.olderShadowRoot; } } } function handler(root, mutations) { if (flags.dom) { var mx = mutations[0]; if (mx && mx.type === "childList" && mx.addedNodes) { if (mx.addedNodes) { var d = mx.addedNodes[0]; while (d && d !== document && !d.host) { d = d.parentNode; } var u = d && (d.URL || d._URL || d.host && d.host.localName) || ""; u = u.split("/?").shift().split("/").pop(); } } console.group("mutations (%d) [%s]", mutations.length, u || ""); } var isAttached = inDocument(root); mutations.forEach(function(mx) { if (mx.type === "childList") { forEach(mx.addedNodes, function(n) { if (!n.localName) { return; } addedNode(n, isAttached); }); forEach(mx.removedNodes, function(n) { if (!n.localName) { return; } detachedNode(n); }); } }); flags.dom && console.groupEnd(); } function takeRecords(node) { node = window.wrap(node); if (!node) { node = window.wrap(document); } while (node.parentNode) { node = node.parentNode; } var observer = node.__observer; if (observer) { handler(node, observer.takeRecords()); takeMutations(); } } var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); function observe(inRoot) { if (inRoot.__observer) { return; } var observer = new MutationObserver(handler.bind(this, inRoot)); observer.observe(inRoot, { childList: true, subtree: true }); inRoot.__observer = observer; } function upgradeDocument(doc) { doc = window.wrap(doc); flags.dom && console.group("upgradeDocument: ", doc.baseURI.split("/").pop()); var isMainDocument = doc === window.wrap(document); addedNode(doc, isMainDocument); observe(doc); flags.dom && console.groupEnd(); } function upgradeDocumentTree(doc) { forDocumentTree(doc, upgradeDocument); } var originalCreateShadowRoot = Element.prototype.createShadowRoot; if (originalCreateShadowRoot) { Element.prototype.createShadowRoot = function() { var root = originalCreateShadowRoot.call(this); window.CustomElements.watchShadow(this); return root; }; } scope.watchShadow = watchShadow; scope.upgradeDocumentTree = upgradeDocumentTree; scope.upgradeDocument = upgradeDocument; scope.upgradeSubtree = addedSubtree; scope.upgradeAll = addedNode; scope.attached = attached; scope.takeRecords = takeRecords; }); window.CustomElements.addModule(function(scope) { var flags = scope.flags; function upgrade(node, isAttached) { if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) { var is = node.getAttribute("is"); var definition = scope.getRegisteredDefinition(node.localName) || scope.getRegisteredDefinition(is); if (definition) { if (is && definition.tag == node.localName || !is && !definition.extends) { return upgradeWithDefinition(node, definition, isAttached); } } } } function upgradeWithDefinition(element, definition, isAttached) { flags.upgrade && console.group("upgrade:", element.localName); if (definition.is) { element.setAttribute("is", definition.is); } implementPrototype(element, definition); element.__upgraded__ = true; created(element); if (isAttached) { scope.attached(element); } scope.upgradeSubtree(element, isAttached); flags.upgrade && console.groupEnd(); return element; } function implementPrototype(element, definition) { if (Object.__proto__) { element.__proto__ = definition.prototype; } else { customMixin(element, definition.prototype, definition.native); element.__proto__ = definition.prototype; } } function customMixin(inTarget, inSrc, inNative) { var used = {}; var p = inSrc; while (p !== inNative && p !== HTMLElement.prototype) { var keys = Object.getOwnPropertyNames(p); for (var i = 0, k; k = keys[i]; i++) { if (!used[k]) { Object.defineProperty(inTarget, k, Object.getOwnPropertyDescriptor(p, k)); used[k] = 1; } } p = Object.getPrototypeOf(p); } } function created(element) { if (element.createdCallback) { element.createdCallback(); } } scope.upgrade = upgrade; scope.upgradeWithDefinition = upgradeWithDefinition; scope.implementPrototype = implementPrototype; }); window.CustomElements.addModule(function(scope) { var isIE11OrOlder = scope.isIE11OrOlder; var upgradeDocumentTree = scope.upgradeDocumentTree; var upgradeAll = scope.upgradeAll; var upgradeWithDefinition = scope.upgradeWithDefinition; var implementPrototype = scope.implementPrototype; var useNative = scope.useNative; function register(name, options) { var definition = options || {}; if (!name) { throw new Error("document.registerElement: first argument `name` must not be empty"); } if (name.indexOf("-") < 0) { throw new Error("document.registerElement: first argument ('name') must contain a dash ('-'). Argument provided was '" + String(name) + "'."); } if (isReservedTag(name)) { throw new Error("Failed to execute 'registerElement' on 'Document': Registration failed for type '" + String(name) + "'. The type name is invalid."); } if (getRegisteredDefinition(name)) { throw new Error("DuplicateDefinitionError: a type with name '" + String(name) + "' is already registered"); } if (!definition.prototype) { definition.prototype = Object.create(HTMLElement.prototype); } definition.__name = name.toLowerCase(); definition.lifecycle = definition.lifecycle || {}; definition.ancestry = ancestry(definition.extends); resolveTagName(definition); resolvePrototypeChain(definition); overrideAttributeApi(definition.prototype); registerDefinition(definition.__name, definition); definition.ctor = generateConstructor(definition); definition.ctor.prototype = definition.prototype; definition.prototype.constructor = definition.ctor; if (scope.ready) { upgradeDocumentTree(document); } return definition.ctor; } function overrideAttributeApi(prototype) { if (prototype.setAttribute._polyfilled) { return; } var setAttribute = prototype.setAttribute; prototype.setAttribute = function(name, value) { changeAttribute.call(this, name, value, setAttribute); }; var removeAttribute = prototype.removeAttribute; prototype.removeAttribute = function(name) { changeAttribute.call(this, name, null, removeAttribute); }; prototype.setAttribute._polyfilled = true; } function changeAttribute(name, value, operation) { name = name.toLowerCase(); var oldValue = this.getAttribute(name); operation.apply(this, arguments); var newValue = this.getAttribute(name); if (this.attributeChangedCallback && newValue !== oldValue) { this.attributeChangedCallback(name, oldValue, newValue); } } function isReservedTag(name) { for (var i = 0; i < reservedTagList.length; i++) { if (name === reservedTagList[i]) { return true; } } } var reservedTagList = [ "annotation-xml", "color-profile", "font-face", "font-face-src", "font-face-uri", "font-face-format", "font-face-name", "missing-glyph" ]; function ancestry(extnds) { var extendee = getRegisteredDefinition(extnds); if (extendee) { return ancestry(extendee.extends).concat([ extendee ]); } return []; } function resolveTagName(definition) { var baseTag = definition.extends; for (var i = 0, a; a = definition.ancestry[i]; i++) { baseTag = a.is && a.tag; } definition.tag = baseTag || definition.__name; if (baseTag) { definition.is = definition.__name; } } function resolvePrototypeChain(definition) { if (!Object.__proto__) { var nativePrototype = HTMLElement.prototype; if (definition.is) { var inst = document.createElement(definition.tag); nativePrototype = Object.getPrototypeOf(inst); } var proto = definition.prototype, ancestor; var foundPrototype = false; while (proto) { if (proto == nativePrototype) { foundPrototype = true; } ancestor = Object.getPrototypeOf(proto); if (ancestor) { proto.__proto__ = ancestor; } proto = ancestor; } if (!foundPrototype) { console.warn(definition.tag + " prototype not found in prototype chain for " + definition.is); } definition.native = nativePrototype; } } function instantiate(definition) { return upgradeWithDefinition(domCreateElement(definition.tag), definition); } var registry = {}; function getRegisteredDefinition(name) { if (name) { return registry[name.toLowerCase()]; } } function registerDefinition(name, definition) { registry[name] = definition; } function generateConstructor(definition) { return function() { return instantiate(definition); }; } var HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; function createElementNS(namespace, tag, typeExtension) { if (namespace === HTML_NAMESPACE) { return createElement(tag, typeExtension); } else { return domCreateElementNS(namespace, tag); } } function createElement(tag, typeExtension) { if (tag) { tag = tag.toLowerCase(); } if (typeExtension) { typeExtension = typeExtension.toLowerCase(); } var definition = getRegisteredDefinition(typeExtension || tag); if (definition) { if (tag == definition.tag && typeExtension == definition.is) { return new definition.ctor(); } if (!typeExtension && !definition.is) { return new definition.ctor(); } } var element; if (typeExtension) { element = createElement(tag); element.setAttribute("is", typeExtension); return element; } element = domCreateElement(tag); if (tag.indexOf("-") >= 0) { implementPrototype(element, HTMLElement); } return element; } var domCreateElement = document.createElement.bind(document); var domCreateElementNS = document.createElementNS.bind(document); var isInstance; if (!Object.__proto__ && !useNative) { isInstance = function(obj, ctor) { if (obj instanceof ctor) { return true; } var p = obj; while (p) { if (p === ctor.prototype) { return true; } p = p.__proto__; } return false; }; } else { isInstance = function(obj, base) { return obj instanceof base; }; } function wrapDomMethodToForceUpgrade(obj, methodName) { var orig = obj[methodName]; obj[methodName] = function() { var n = orig.apply(this, arguments); upgradeAll(n); return n; }; } wrapDomMethodToForceUpgrade(Node.prototype, "cloneNode"); wrapDomMethodToForceUpgrade(document, "importNode"); if (isIE11OrOlder) { (function() { var importNode = document.importNode; document.importNode = function() { var n = importNode.apply(document, arguments); if (n.nodeType == n.DOCUMENT_FRAGMENT_NODE) { var f = document.createDocumentFragment(); f.appendChild(n); return f; } else { return n; } }; })(); } document.registerElement = register; document.createElement = createElement; document.createElementNS = createElementNS; scope.registry = registry; scope.instanceof = isInstance; scope.reservedTagList = reservedTagList; scope.getRegisteredDefinition = getRegisteredDefinition; document.register = document.registerElement; }); (function(scope) { var useNative = scope.useNative; var initializeModules = scope.initializeModules; var isIE11OrOlder = /Trident/.test(navigator.userAgent); if (useNative) { var nop = function() {}; scope.watchShadow = nop; scope.upgrade = nop; scope.upgradeAll = nop; scope.upgradeDocumentTree = nop; scope.upgradeSubtree = nop; scope.takeRecords = nop; scope.instanceof = function(obj, base) { return obj instanceof base; }; } else { initializeModules(); } var upgradeDocumentTree = scope.upgradeDocumentTree; var upgradeDocument = scope.upgradeDocument; if (!window.wrap) { if (window.ShadowDOMPolyfill) { window.wrap = window.ShadowDOMPolyfill.wrapIfNeeded; window.unwrap = window.ShadowDOMPolyfill.unwrapIfNeeded; } else { window.wrap = window.unwrap = function(node) { return node; }; } } if (window.HTMLImports) { window.HTMLImports.__importsParsingHook = function(elt) { if (elt.import) { upgradeDocument(wrap(elt.import)); } }; } function bootstrap() { upgradeDocumentTree(window.wrap(document)); window.CustomElements.ready = true; requestAnimationFrame(function() { setTimeout(function() { window.CustomElements.readyTime = Date.now(); if (window.HTMLImports) { window.CustomElements.elapsed = window.CustomElements.readyTime - window.HTMLImports.readyTime; } document.dispatchEvent(new CustomEvent("WebComponentsReady", { bubbles: true })); }); }); } if (isIE11OrOlder && typeof window.CustomEvent !== "function") { window.CustomEvent = function(inType, params) { params = params || {}; var e = document.createEvent("CustomEvent"); e.initCustomEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable), params.detail); e.preventDefault = function() { Object.defineProperty(this, "defaultPrevented", { get: function() { return true; } }); }; return e; }; window.CustomEvent.prototype = window.Event.prototype; } if (document.readyState === "complete" || scope.flags.eager) { bootstrap(); } else if (document.readyState === "interactive" && !window.attachEvent && (!window.HTMLImports || window.HTMLImports.ready)) { bootstrap(); } else { var loadEvent = window.HTMLImports && !window.HTMLImports.ready ? "HTMLImportsLoaded" : "DOMContentLoaded"; window.addEventListener(loadEvent, bootstrap); } scope.isIE11OrOlder = isIE11OrOlder; })(window.CustomElements); ================================================ FILE: cmd/memlat/static/bower_components/webcomponentsjs/HTMLImports.js ================================================ /** * @license * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ // @version 0.7.11 if (typeof WeakMap === "undefined") { (function() { var defineProperty = Object.defineProperty; var counter = Date.now() % 1e9; var WeakMap = function() { this.name = "__st" + (Math.random() * 1e9 >>> 0) + (counter++ + "__"); }; WeakMap.prototype = { set: function(key, value) { var entry = key[this.name]; if (entry && entry[0] === key) entry[1] = value; else defineProperty(key, this.name, { value: [ key, value ], writable: true }); return this; }, get: function(key) { var entry; return (entry = key[this.name]) && entry[0] === key ? entry[1] : undefined; }, "delete": function(key) { var entry = key[this.name]; if (!entry || entry[0] !== key) return false; entry[0] = entry[1] = undefined; return true; }, has: function(key) { var entry = key[this.name]; if (!entry) return false; return entry[0] === key; } }; window.WeakMap = WeakMap; })(); } (function(global) { var registrationsTable = new WeakMap(); var setImmediate; if (/Trident|Edge/.test(navigator.userAgent)) { setImmediate = setTimeout; } else if (window.setImmediate) { setImmediate = window.setImmediate; } else { var setImmediateQueue = []; var sentinel = String(Math.random()); window.addEventListener("message", function(e) { if (e.data === sentinel) { var queue = setImmediateQueue; setImmediateQueue = []; queue.forEach(function(func) { func(); }); } }); setImmediate = function(func) { setImmediateQueue.push(func); window.postMessage(sentinel, "*"); }; } var isScheduled = false; var scheduledObservers = []; function scheduleCallback(observer) { scheduledObservers.push(observer); if (!isScheduled) { isScheduled = true; setImmediate(dispatchCallbacks); } } function wrapIfNeeded(node) { return window.ShadowDOMPolyfill && window.ShadowDOMPolyfill.wrapIfNeeded(node) || node; } function dispatchCallbacks() { isScheduled = false; var observers = scheduledObservers; scheduledObservers = []; observers.sort(function(o1, o2) { return o1.uid_ - o2.uid_; }); var anyNonEmpty = false; observers.forEach(function(observer) { var queue = observer.takeRecords(); removeTransientObserversFor(observer); if (queue.length) { observer.callback_(queue, observer); anyNonEmpty = true; } }); if (anyNonEmpty) dispatchCallbacks(); } function removeTransientObserversFor(observer) { observer.nodes_.forEach(function(node) { var registrations = registrationsTable.get(node); if (!registrations) return; registrations.forEach(function(registration) { if (registration.observer === observer) registration.removeTransientObservers(); }); }); } function forEachAncestorAndObserverEnqueueRecord(target, callback) { for (var node = target; node; node = node.parentNode) { var registrations = registrationsTable.get(node); if (registrations) { for (var j = 0; j < registrations.length; j++) { var registration = registrations[j]; var options = registration.options; if (node !== target && !options.subtree) continue; var record = callback(options); if (record) registration.enqueue(record); } } } } var uidCounter = 0; function JsMutationObserver(callback) { this.callback_ = callback; this.nodes_ = []; this.records_ = []; this.uid_ = ++uidCounter; } JsMutationObserver.prototype = { observe: function(target, options) { target = wrapIfNeeded(target); if (!options.childList && !options.attributes && !options.characterData || options.attributeOldValue && !options.attributes || options.attributeFilter && options.attributeFilter.length && !options.attributes || options.characterDataOldValue && !options.characterData) { throw new SyntaxError(); } var registrations = registrationsTable.get(target); if (!registrations) registrationsTable.set(target, registrations = []); var registration; for (var i = 0; i < registrations.length; i++) { if (registrations[i].observer === this) { registration = registrations[i]; registration.removeListeners(); registration.options = options; break; } } if (!registration) { registration = new Registration(this, target, options); registrations.push(registration); this.nodes_.push(target); } registration.addListeners(); }, disconnect: function() { this.nodes_.forEach(function(node) { var registrations = registrationsTable.get(node); for (var i = 0; i < registrations.length; i++) { var registration = registrations[i]; if (registration.observer === this) { registration.removeListeners(); registrations.splice(i, 1); break; } } }, this); this.records_ = []; }, takeRecords: function() { var copyOfRecords = this.records_; this.records_ = []; return copyOfRecords; } }; function MutationRecord(type, target) { this.type = type; this.target = target; this.addedNodes = []; this.removedNodes = []; this.previousSibling = null; this.nextSibling = null; this.attributeName = null; this.attributeNamespace = null; this.oldValue = null; } function copyMutationRecord(original) { var record = new MutationRecord(original.type, original.target); record.addedNodes = original.addedNodes.slice(); record.removedNodes = original.removedNodes.slice(); record.previousSibling = original.previousSibling; record.nextSibling = original.nextSibling; record.attributeName = original.attributeName; record.attributeNamespace = original.attributeNamespace; record.oldValue = original.oldValue; return record; } var currentRecord, recordWithOldValue; function getRecord(type, target) { return currentRecord = new MutationRecord(type, target); } function getRecordWithOldValue(oldValue) { if (recordWithOldValue) return recordWithOldValue; recordWithOldValue = copyMutationRecord(currentRecord); recordWithOldValue.oldValue = oldValue; return recordWithOldValue; } function clearRecords() { currentRecord = recordWithOldValue = undefined; } function recordRepresentsCurrentMutation(record) { return record === recordWithOldValue || record === currentRecord; } function selectRecord(lastRecord, newRecord) { if (lastRecord === newRecord) return lastRecord; if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) return recordWithOldValue; return null; } function Registration(observer, target, options) { this.observer = observer; this.target = target; this.options = options; this.transientObservedNodes = []; } Registration.prototype = { enqueue: function(record) { var records = this.observer.records_; var length = records.length; if (records.length > 0) { var lastRecord = records[length - 1]; var recordToReplaceLast = selectRecord(lastRecord, record); if (recordToReplaceLast) { records[length - 1] = recordToReplaceLast; return; } } else { scheduleCallback(this.observer); } records[length] = record; }, addListeners: function() { this.addListeners_(this.target); }, addListeners_: function(node) { var options = this.options; if (options.attributes) node.addEventListener("DOMAttrModified", this, true); if (options.characterData) node.addEventListener("DOMCharacterDataModified", this, true); if (options.childList) node.addEventListener("DOMNodeInserted", this, true); if (options.childList || options.subtree) node.addEventListener("DOMNodeRemoved", this, true); }, removeListeners: function() { this.removeListeners_(this.target); }, removeListeners_: function(node) { var options = this.options; if (options.attributes) node.removeEventListener("DOMAttrModified", this, true); if (options.characterData) node.removeEventListener("DOMCharacterDataModified", this, true); if (options.childList) node.removeEventListener("DOMNodeInserted", this, true); if (options.childList || options.subtree) node.removeEventListener("DOMNodeRemoved", this, true); }, addTransientObserver: function(node) { if (node === this.target) return; this.addListeners_(node); this.transientObservedNodes.push(node); var registrations = registrationsTable.get(node); if (!registrations) registrationsTable.set(node, registrations = []); registrations.push(this); }, removeTransientObservers: function() { var transientObservedNodes = this.transientObservedNodes; this.transientObservedNodes = []; transientObservedNodes.forEach(function(node) { this.removeListeners_(node); var registrations = registrationsTable.get(node); for (var i = 0; i < registrations.length; i++) { if (registrations[i] === this) { registrations.splice(i, 1); break; } } }, this); }, handleEvent: function(e) { e.stopImmediatePropagation(); switch (e.type) { case "DOMAttrModified": var name = e.attrName; var namespace = e.relatedNode.namespaceURI; var target = e.target; var record = new getRecord("attributes", target); record.attributeName = name; record.attributeNamespace = namespace; var oldValue = e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; forEachAncestorAndObserverEnqueueRecord(target, function(options) { if (!options.attributes) return; if (options.attributeFilter && options.attributeFilter.length && options.attributeFilter.indexOf(name) === -1 && options.attributeFilter.indexOf(namespace) === -1) { return; } if (options.attributeOldValue) return getRecordWithOldValue(oldValue); return record; }); break; case "DOMCharacterDataModified": var target = e.target; var record = getRecord("characterData", target); var oldValue = e.prevValue; forEachAncestorAndObserverEnqueueRecord(target, function(options) { if (!options.characterData) return; if (options.characterDataOldValue) return getRecordWithOldValue(oldValue); return record; }); break; case "DOMNodeRemoved": this.addTransientObserver(e.target); case "DOMNodeInserted": var changedNode = e.target; var addedNodes, removedNodes; if (e.type === "DOMNodeInserted") { addedNodes = [ changedNode ]; removedNodes = []; } else { addedNodes = []; removedNodes = [ changedNode ]; } var previousSibling = changedNode.previousSibling; var nextSibling = changedNode.nextSibling; var record = getRecord("childList", e.target.parentNode); record.addedNodes = addedNodes; record.removedNodes = removedNodes; record.previousSibling = previousSibling; record.nextSibling = nextSibling; forEachAncestorAndObserverEnqueueRecord(e.relatedNode, function(options) { if (!options.childList) return; return record; }); } clearRecords(); } }; global.JsMutationObserver = JsMutationObserver; if (!global.MutationObserver) global.MutationObserver = JsMutationObserver; })(this); window.HTMLImports = window.HTMLImports || { flags: {} }; (function(scope) { var IMPORT_LINK_TYPE = "import"; var useNative = Boolean(IMPORT_LINK_TYPE in document.createElement("link")); var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill); var wrap = function(node) { return hasShadowDOMPolyfill ? window.ShadowDOMPolyfill.wrapIfNeeded(node) : node; }; var rootDocument = wrap(document); var currentScriptDescriptor = { get: function() { var script = window.HTMLImports.currentScript || document.currentScript || (document.readyState !== "complete" ? document.scripts[document.scripts.length - 1] : null); return wrap(script); }, configurable: true }; Object.defineProperty(document, "_currentScript", currentScriptDescriptor); Object.defineProperty(rootDocument, "_currentScript", currentScriptDescriptor); var isIE = /Trident/.test(navigator.userAgent); function whenReady(callback, doc) { doc = doc || rootDocument; whenDocumentReady(function() { watchImportsLoad(callback, doc); }, doc); } var requiredReadyState = isIE ? "complete" : "interactive"; var READY_EVENT = "readystatechange"; function isDocumentReady(doc) { return doc.readyState === "complete" || doc.readyState === requiredReadyState; } function whenDocumentReady(callback, doc) { if (!isDocumentReady(doc)) { var checkReady = function() { if (doc.readyState === "complete" || doc.readyState === requiredReadyState) { doc.removeEventListener(READY_EVENT, checkReady); whenDocumentReady(callback, doc); } }; doc.addEventListener(READY_EVENT, checkReady); } else if (callback) { callback(); } } function markTargetLoaded(event) { event.target.__loaded = true; } function watchImportsLoad(callback, doc) { var imports = doc.querySelectorAll("link[rel=import]"); var parsedCount = 0, importCount = imports.length, newImports = [], errorImports = []; function checkDone() { if (parsedCount == importCount && callback) { callback({ allImports: imports, loadedImports: newImports, errorImports: errorImports }); } } function loadedImport(e) { markTargetLoaded(e); newImports.push(this); parsedCount++; checkDone(); } function errorLoadingImport(e) { errorImports.push(this); parsedCount++; checkDone(); } if (importCount) { for (var i = 0, imp; i < importCount && (imp = imports[i]); i++) { if (isImportLoaded(imp)) { parsedCount++; checkDone(); } else { imp.addEventListener("load", loadedImport); imp.addEventListener("error", errorLoadingImport); } } } else { checkDone(); } } function isImportLoaded(link) { return useNative ? link.__loaded || link.import && link.import.readyState !== "loading" : link.__importParsed; } if (useNative) { new MutationObserver(function(mxns) { for (var i = 0, l = mxns.length, m; i < l && (m = mxns[i]); i++) { if (m.addedNodes) { handleImports(m.addedNodes); } } }).observe(document.head, { childList: true }); function handleImports(nodes) { for (var i = 0, l = nodes.length, n; i < l && (n = nodes[i]); i++) { if (isImport(n)) { handleImport(n); } } } function isImport(element) { return element.localName === "link" && element.rel === "import"; } function handleImport(element) { var loaded = element.import; if (loaded) { markTargetLoaded({ target: element }); } else { element.addEventListener("load", markTargetLoaded); element.addEventListener("error", markTargetLoaded); } } (function() { if (document.readyState === "loading") { var imports = document.querySelectorAll("link[rel=import]"); for (var i = 0, l = imports.length, imp; i < l && (imp = imports[i]); i++) { handleImport(imp); } } })(); } whenReady(function(detail) { window.HTMLImports.ready = true; window.HTMLImports.readyTime = new Date().getTime(); var evt = rootDocument.createEvent("CustomEvent"); evt.initCustomEvent("HTMLImportsLoaded", true, true, detail); rootDocument.dispatchEvent(evt); }); scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; scope.useNative = useNative; scope.rootDocument = rootDocument; scope.whenReady = whenReady; scope.isIE = isIE; })(window.HTMLImports); (function(scope) { var modules = []; var addModule = function(module) { modules.push(module); }; var initializeModules = function() { modules.forEach(function(module) { module(scope); }); }; scope.addModule = addModule; scope.initializeModules = initializeModules; })(window.HTMLImports); window.HTMLImports.addModule(function(scope) { var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; var path = { resolveUrlsInStyle: function(style, linkUrl) { var doc = style.ownerDocument; var resolver = doc.createElement("a"); style.textContent = this.resolveUrlsInCssText(style.textContent, linkUrl, resolver); return style; }, resolveUrlsInCssText: function(cssText, linkUrl, urlObj) { var r = this.replaceUrls(cssText, urlObj, linkUrl, CSS_URL_REGEXP); r = this.replaceUrls(r, urlObj, linkUrl, CSS_IMPORT_REGEXP); return r; }, replaceUrls: function(text, urlObj, linkUrl, regexp) { return text.replace(regexp, function(m, pre, url, post) { var urlPath = url.replace(/["']/g, ""); if (linkUrl) { urlPath = new URL(urlPath, linkUrl).href; } urlObj.href = urlPath; urlPath = urlObj.href; return pre + "'" + urlPath + "'" + post; }); } }; scope.path = path; }); window.HTMLImports.addModule(function(scope) { var xhr = { async: true, ok: function(request) { return request.status >= 200 && request.status < 300 || request.status === 304 || request.status === 0; }, load: function(url, next, nextContext) { var request = new XMLHttpRequest(); if (scope.flags.debug || scope.flags.bust) { url += "?" + Math.random(); } request.open("GET", url, xhr.async); request.addEventListener("readystatechange", function(e) { if (request.readyState === 4) { var locationHeader = request.getResponseHeader("Location"); var redirectedUrl = null; if (locationHeader) { var redirectedUrl = locationHeader.substr(0, 1) === "/" ? location.origin + locationHeader : locationHeader; } next.call(nextContext, !xhr.ok(request) && request, request.response || request.responseText, redirectedUrl); } }); request.send(); return request; }, loadDocument: function(url, next, nextContext) { this.load(url, next, nextContext).responseType = "document"; } }; scope.xhr = xhr; }); window.HTMLImports.addModule(function(scope) { var xhr = scope.xhr; var flags = scope.flags; var Loader = function(onLoad, onComplete) { this.cache = {}; this.onload = onLoad; this.oncomplete = onComplete; this.inflight = 0; this.pending = {}; }; Loader.prototype = { addNodes: function(nodes) { this.inflight += nodes.length; for (var i = 0, l = nodes.length, n; i < l && (n = nodes[i]); i++) { this.require(n); } this.checkDone(); }, addNode: function(node) { this.inflight++; this.require(node); this.checkDone(); }, require: function(elt) { var url = elt.src || elt.href; elt.__nodeUrl = url; if (!this.dedupe(url, elt)) { this.fetch(url, elt); } }, dedupe: function(url, elt) { if (this.pending[url]) { this.pending[url].push(elt); return true; } var resource; if (this.cache[url]) { this.onload(url, elt, this.cache[url]); this.tail(); return true; } this.pending[url] = [ elt ]; return false; }, fetch: function(url, elt) { flags.load && console.log("fetch", url, elt); if (!url) { setTimeout(function() { this.receive(url, elt, { error: "href must be specified" }, null); }.bind(this), 0); } else if (url.match(/^data:/)) { var pieces = url.split(","); var header = pieces[0]; var body = pieces[1]; if (header.indexOf(";base64") > -1) { body = atob(body); } else { body = decodeURIComponent(body); } setTimeout(function() { this.receive(url, elt, null, body); }.bind(this), 0); } else { var receiveXhr = function(err, resource, redirectedUrl) { this.receive(url, elt, err, resource, redirectedUrl); }.bind(this); xhr.load(url, receiveXhr); } }, receive: function(url, elt, err, resource, redirectedUrl) { this.cache[url] = resource; var $p = this.pending[url]; for (var i = 0, l = $p.length, p; i < l && (p = $p[i]); i++) { this.onload(url, p, resource, err, redirectedUrl); this.tail(); } this.pending[url] = null; }, tail: function() { --this.inflight; this.checkDone(); }, checkDone: function() { if (!this.inflight) { this.oncomplete(); } } }; scope.Loader = Loader; }); window.HTMLImports.addModule(function(scope) { var Observer = function(addCallback) { this.addCallback = addCallback; this.mo = new MutationObserver(this.handler.bind(this)); }; Observer.prototype = { handler: function(mutations) { for (var i = 0, l = mutations.length, m; i < l && (m = mutations[i]); i++) { if (m.type === "childList" && m.addedNodes.length) { this.addedNodes(m.addedNodes); } } }, addedNodes: function(nodes) { if (this.addCallback) { this.addCallback(nodes); } for (var i = 0, l = nodes.length, n, loading; i < l && (n = nodes[i]); i++) { if (n.children && n.children.length) { this.addedNodes(n.children); } } }, observe: function(root) { this.mo.observe(root, { childList: true, subtree: true }); } }; scope.Observer = Observer; }); window.HTMLImports.addModule(function(scope) { var path = scope.path; var rootDocument = scope.rootDocument; var flags = scope.flags; var isIE = scope.isIE; var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE; var IMPORT_SELECTOR = "link[rel=" + IMPORT_LINK_TYPE + "]"; var importParser = { documentSelectors: IMPORT_SELECTOR, importsSelectors: [ IMPORT_SELECTOR, "link[rel=stylesheet]:not([type])", "style:not([type])", "script:not([type])", 'script[type="application/javascript"]', 'script[type="text/javascript"]' ].join(","), map: { link: "parseLink", script: "parseScript", style: "parseStyle" }, dynamicElements: [], parseNext: function() { var next = this.nextToParse(); if (next) { this.parse(next); } }, parse: function(elt) { if (this.isParsed(elt)) { flags.parse && console.log("[%s] is already parsed", elt.localName); return; } var fn = this[this.map[elt.localName]]; if (fn) { this.markParsing(elt); fn.call(this, elt); } }, parseDynamic: function(elt, quiet) { this.dynamicElements.push(elt); if (!quiet) { this.parseNext(); } }, markParsing: function(elt) { flags.parse && console.log("parsing", elt); this.parsingElement = elt; }, markParsingComplete: function(elt) { elt.__importParsed = true; this.markDynamicParsingComplete(elt); if (elt.__importElement) { elt.__importElement.__importParsed = true; this.markDynamicParsingComplete(elt.__importElement); } this.parsingElement = null; flags.parse && console.log("completed", elt); }, markDynamicParsingComplete: function(elt) { var i = this.dynamicElements.indexOf(elt); if (i >= 0) { this.dynamicElements.splice(i, 1); } }, parseImport: function(elt) { elt.import = elt.__doc; if (window.HTMLImports.__importsParsingHook) { window.HTMLImports.__importsParsingHook(elt); } if (elt.import) { elt.import.__importParsed = true; } this.markParsingComplete(elt); if (elt.__resource && !elt.__error) { elt.dispatchEvent(new CustomEvent("load", { bubbles: false })); } else { elt.dispatchEvent(new CustomEvent("error", { bubbles: false })); } if (elt.__pending) { var fn; while (elt.__pending.length) { fn = elt.__pending.shift(); if (fn) { fn({ target: elt }); } } } this.parseNext(); }, parseLink: function(linkElt) { if (nodeIsImport(linkElt)) { this.parseImport(linkElt); } else { linkElt.href = linkElt.href; this.parseGeneric(linkElt); } }, parseStyle: function(elt) { var src = elt; elt = cloneStyle(elt); src.__appliedElement = elt; elt.__importElement = src; this.parseGeneric(elt); }, parseGeneric: function(elt) { this.trackElement(elt); this.addElementToDocument(elt); }, rootImportForElement: function(elt) { var n = elt; while (n.ownerDocument.__importLink) { n = n.ownerDocument.__importLink; } return n; }, addElementToDocument: function(elt) { var port = this.rootImportForElement(elt.__importElement || elt); port.parentNode.insertBefore(elt, port); }, trackElement: function(elt, callback) { var self = this; var done = function(e) { elt.removeEventListener("load", done); elt.removeEventListener("error", done); if (callback) { callback(e); } self.markParsingComplete(elt); self.parseNext(); }; elt.addEventListener("load", done); elt.addEventListener("error", done); if (isIE && elt.localName === "style") { var fakeLoad = false; if (elt.textContent.indexOf("@import") == -1) { fakeLoad = true; } else if (elt.sheet) { fakeLoad = true; var csr = elt.sheet.cssRules; var len = csr ? csr.length : 0; for (var i = 0, r; i < len && (r = csr[i]); i++) { if (r.type === CSSRule.IMPORT_RULE) { fakeLoad = fakeLoad && Boolean(r.styleSheet); } } } if (fakeLoad) { setTimeout(function() { elt.dispatchEvent(new CustomEvent("load", { bubbles: false })); }); } } }, parseScript: function(scriptElt) { var script = document.createElement("script"); script.__importElement = scriptElt; script.src = scriptElt.src ? scriptElt.src : generateScriptDataUrl(scriptElt); scope.currentScript = scriptElt; this.trackElement(script, function(e) { if (script.parentNode) { script.parentNode.removeChild(script); } scope.currentScript = null; }); this.addElementToDocument(script); }, nextToParse: function() { this._mayParse = []; return !this.parsingElement && (this.nextToParseInDoc(rootDocument) || this.nextToParseDynamic()); }, nextToParseInDoc: function(doc, link) { if (doc && this._mayParse.indexOf(doc) < 0) { this._mayParse.push(doc); var nodes = doc.querySelectorAll(this.parseSelectorsForNode(doc)); for (var i = 0, l = nodes.length, p = 0, n; i < l && (n = nodes[i]); i++) { if (!this.isParsed(n)) { if (this.hasResource(n)) { return nodeIsImport(n) ? this.nextToParseInDoc(n.__doc, n) : n; } else { return; } } } } return link; }, nextToParseDynamic: function() { return this.dynamicElements[0]; }, parseSelectorsForNode: function(node) { var doc = node.ownerDocument || node; return doc === rootDocument ? this.documentSelectors : this.importsSelectors; }, isParsed: function(node) { return node.__importParsed; }, needsDynamicParsing: function(elt) { return this.dynamicElements.indexOf(elt) >= 0; }, hasResource: function(node) { if (nodeIsImport(node) && node.__doc === undefined) { return false; } return true; } }; function nodeIsImport(elt) { return elt.localName === "link" && elt.rel === IMPORT_LINK_TYPE; } function generateScriptDataUrl(script) { var scriptContent = generateScriptContent(script); return "data:text/javascript;charset=utf-8," + encodeURIComponent(scriptContent); } function generateScriptContent(script) { return script.textContent + generateSourceMapHint(script); } function generateSourceMapHint(script) { var owner = script.ownerDocument; owner.__importedScripts = owner.__importedScripts || 0; var moniker = script.ownerDocument.baseURI; var num = owner.__importedScripts ? "-" + owner.__importedScripts : ""; owner.__importedScripts++; return "\n//# sourceURL=" + moniker + num + ".js\n"; } function cloneStyle(style) { var clone = style.ownerDocument.createElement("style"); clone.textContent = style.textContent; path.resolveUrlsInStyle(clone); return clone; } scope.parser = importParser; scope.IMPORT_SELECTOR = IMPORT_SELECTOR; }); window.HTMLImports.addModule(function(scope) { var flags = scope.flags; var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE; var IMPORT_SELECTOR = scope.IMPORT_SELECTOR; var rootDocument = scope.rootDocument; var Loader = scope.Loader; var Observer = scope.Observer; var parser = scope.parser; var importer = { documents: {}, documentPreloadSelectors: IMPORT_SELECTOR, importsPreloadSelectors: [ IMPORT_SELECTOR ].join(","), loadNode: function(node) { importLoader.addNode(node); }, loadSubtree: function(parent) { var nodes = this.marshalNodes(parent); importLoader.addNodes(nodes); }, marshalNodes: function(parent) { return parent.querySelectorAll(this.loadSelectorsForNode(parent)); }, loadSelectorsForNode: function(node) { var doc = node.ownerDocument || node; return doc === rootDocument ? this.documentPreloadSelectors : this.importsPreloadSelectors; }, loaded: function(url, elt, resource, err, redirectedUrl) { flags.load && console.log("loaded", url, elt); elt.__resource = resource; elt.__error = err; if (isImportLink(elt)) { var doc = this.documents[url]; if (doc === undefined) { doc = err ? null : makeDocument(resource, redirectedUrl || url); if (doc) { doc.__importLink = elt; this.bootDocument(doc); } this.documents[url] = doc; } elt.__doc = doc; } parser.parseNext(); }, bootDocument: function(doc) { this.loadSubtree(doc); this.observer.observe(doc); parser.parseNext(); }, loadedAll: function() { parser.parseNext(); } }; var importLoader = new Loader(importer.loaded.bind(importer), importer.loadedAll.bind(importer)); importer.observer = new Observer(); function isImportLink(elt) { return isLinkRel(elt, IMPORT_LINK_TYPE); } function isLinkRel(elt, rel) { return elt.localName === "link" && elt.getAttribute("rel") === rel; } function hasBaseURIAccessor(doc) { return !!Object.getOwnPropertyDescriptor(doc, "baseURI"); } function makeDocument(resource, url) { var doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE); doc._URL = url; var base = doc.createElement("base"); base.setAttribute("href", url); if (!doc.baseURI && !hasBaseURIAccessor(doc)) { Object.defineProperty(doc, "baseURI", { value: url }); } var meta = doc.createElement("meta"); meta.setAttribute("charset", "utf-8"); doc.head.appendChild(meta); doc.head.appendChild(base); doc.body.innerHTML = resource; if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) { HTMLTemplateElement.bootstrap(doc); } return doc; } if (!document.baseURI) { var baseURIDescriptor = { get: function() { var base = document.querySelector("base"); return base ? base.href : window.location.href; }, configurable: true }; Object.defineProperty(document, "baseURI", baseURIDescriptor); Object.defineProperty(rootDocument, "baseURI", baseURIDescriptor); } scope.importer = importer; scope.importLoader = importLoader; }); window.HTMLImports.addModule(function(scope) { var parser = scope.parser; var importer = scope.importer; var dynamic = { added: function(nodes) { var owner, parsed, loading; for (var i = 0, l = nodes.length, n; i < l && (n = nodes[i]); i++) { if (!owner) { owner = n.ownerDocument; parsed = parser.isParsed(owner); } loading = this.shouldLoadNode(n); if (loading) { importer.loadNode(n); } if (this.shouldParseNode(n) && parsed) { parser.parseDynamic(n, loading); } } }, shouldLoadNode: function(node) { return node.nodeType === 1 && matches.call(node, importer.loadSelectorsForNode(node)); }, shouldParseNode: function(node) { return node.nodeType === 1 && matches.call(node, parser.parseSelectorsForNode(node)); } }; importer.observer.addCallback = dynamic.added.bind(dynamic); var matches = HTMLElement.prototype.matches || HTMLElement.prototype.matchesSelector || HTMLElement.prototype.webkitMatchesSelector || HTMLElement.prototype.mozMatchesSelector || HTMLElement.prototype.msMatchesSelector; }); (function(scope) { var initializeModules = scope.initializeModules; var isIE = scope.isIE; if (scope.useNative) { return; } if (isIE && typeof window.CustomEvent !== "function") { window.CustomEvent = function(inType, params) { params = params || {}; var e = document.createEvent("CustomEvent"); e.initCustomEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable), params.detail); e.preventDefault = function() { Object.defineProperty(this, "defaultPrevented", { get: function() { return true; } }); }; return e; }; window.CustomEvent.prototype = window.Event.prototype; } initializeModules(); var rootDocument = scope.rootDocument; function bootstrap() { window.HTMLImports.importer.bootDocument(rootDocument); } if (document.readyState === "complete" || document.readyState === "interactive" && !window.attachEvent) { bootstrap(); } else { document.addEventListener("DOMContentLoaded", bootstrap); } })(window.HTMLImports); ================================================ FILE: cmd/memlat/static/bower_components/webcomponentsjs/MutationObserver.js ================================================ /** * @license * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ // @version 0.7.11 if (typeof WeakMap === "undefined") { (function() { var defineProperty = Object.defineProperty; var counter = Date.now() % 1e9; var WeakMap = function() { this.name = "__st" + (Math.random() * 1e9 >>> 0) + (counter++ + "__"); }; WeakMap.prototype = { set: function(key, value) { var entry = key[this.name]; if (entry && entry[0] === key) entry[1] = value; else defineProperty(key, this.name, { value: [ key, value ], writable: true }); return this; }, get: function(key) { var entry; return (entry = key[this.name]) && entry[0] === key ? entry[1] : undefined; }, "delete": function(key) { var entry = key[this.name]; if (!entry || entry[0] !== key) return false; entry[0] = entry[1] = undefined; return true; }, has: function(key) { var entry = key[this.name]; if (!entry) return false; return entry[0] === key; } }; window.WeakMap = WeakMap; })(); } (function(global) { var registrationsTable = new WeakMap(); var setImmediate; if (/Trident|Edge/.test(navigator.userAgent)) { setImmediate = setTimeout; } else if (window.setImmediate) { setImmediate = window.setImmediate; } else { var setImmediateQueue = []; var sentinel = String(Math.random()); window.addEventListener("message", function(e) { if (e.data === sentinel) { var queue = setImmediateQueue; setImmediateQueue = []; queue.forEach(function(func) { func(); }); } }); setImmediate = function(func) { setImmediateQueue.push(func); window.postMessage(sentinel, "*"); }; } var isScheduled = false; var scheduledObservers = []; function scheduleCallback(observer) { scheduledObservers.push(observer); if (!isScheduled) { isScheduled = true; setImmediate(dispatchCallbacks); } } function wrapIfNeeded(node) { return window.ShadowDOMPolyfill && window.ShadowDOMPolyfill.wrapIfNeeded(node) || node; } function dispatchCallbacks() { isScheduled = false; var observers = scheduledObservers; scheduledObservers = []; observers.sort(function(o1, o2) { return o1.uid_ - o2.uid_; }); var anyNonEmpty = false; observers.forEach(function(observer) { var queue = observer.takeRecords(); removeTransientObserversFor(observer); if (queue.length) { observer.callback_(queue, observer); anyNonEmpty = true; } }); if (anyNonEmpty) dispatchCallbacks(); } function removeTransientObserversFor(observer) { observer.nodes_.forEach(function(node) { var registrations = registrationsTable.get(node); if (!registrations) return; registrations.forEach(function(registration) { if (registration.observer === observer) registration.removeTransientObservers(); }); }); } function forEachAncestorAndObserverEnqueueRecord(target, callback) { for (var node = target; node; node = node.parentNode) { var registrations = registrationsTable.get(node); if (registrations) { for (var j = 0; j < registrations.length; j++) { var registration = registrations[j]; var options = registration.options; if (node !== target && !options.subtree) continue; var record = callback(options); if (record) registration.enqueue(record); } } } } var uidCounter = 0; function JsMutationObserver(callback) { this.callback_ = callback; this.nodes_ = []; this.records_ = []; this.uid_ = ++uidCounter; } JsMutationObserver.prototype = { observe: function(target, options) { target = wrapIfNeeded(target); if (!options.childList && !options.attributes && !options.characterData || options.attributeOldValue && !options.attributes || options.attributeFilter && options.attributeFilter.length && !options.attributes || options.characterDataOldValue && !options.characterData) { throw new SyntaxError(); } var registrations = registrationsTable.get(target); if (!registrations) registrationsTable.set(target, registrations = []); var registration; for (var i = 0; i < registrations.length; i++) { if (registrations[i].observer === this) { registration = registrations[i]; registration.removeListeners(); registration.options = options; break; } } if (!registration) { registration = new Registration(this, target, options); registrations.push(registration); this.nodes_.push(target); } registration.addListeners(); }, disconnect: function() { this.nodes_.forEach(function(node) { var registrations = registrationsTable.get(node); for (var i = 0; i < registrations.length; i++) { var registration = registrations[i]; if (registration.observer === this) { registration.removeListeners(); registrations.splice(i, 1); break; } } }, this); this.records_ = []; }, takeRecords: function() { var copyOfRecords = this.records_; this.records_ = []; return copyOfRecords; } }; function MutationRecord(type, target) { this.type = type; this.target = target; this.addedNodes = []; this.removedNodes = []; this.previousSibling = null; this.nextSibling = null; this.attributeName = null; this.attributeNamespace = null; this.oldValue = null; } function copyMutationRecord(original) { var record = new MutationRecord(original.type, original.target); record.addedNodes = original.addedNodes.slice(); record.removedNodes = original.removedNodes.slice(); record.previousSibling = original.previousSibling; record.nextSibling = original.nextSibling; record.attributeName = original.attributeName; record.attributeNamespace = original.attributeNamespace; record.oldValue = original.oldValue; return record; } var currentRecord, recordWithOldValue; function getRecord(type, target) { return currentRecord = new MutationRecord(type, target); } function getRecordWithOldValue(oldValue) { if (recordWithOldValue) return recordWithOldValue; recordWithOldValue = copyMutationRecord(currentRecord); recordWithOldValue.oldValue = oldValue; return recordWithOldValue; } function clearRecords() { currentRecord = recordWithOldValue = undefined; } function recordRepresentsCurrentMutation(record) { return record === recordWithOldValue || record === currentRecord; } function selectRecord(lastRecord, newRecord) { if (lastRecord === newRecord) return lastRecord; if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) return recordWithOldValue; return null; } function Registration(observer, target, options) { this.observer = observer; this.target = target; this.options = options; this.transientObservedNodes = []; } Registration.prototype = { enqueue: function(record) { var records = this.observer.records_; var length = records.length; if (records.length > 0) { var lastRecord = records[length - 1]; var recordToReplaceLast = selectRecord(lastRecord, record); if (recordToReplaceLast) { records[length - 1] = recordToReplaceLast; return; } } else { scheduleCallback(this.observer); } records[length] = record; }, addListeners: function() { this.addListeners_(this.target); }, addListeners_: function(node) { var options = this.options; if (options.attributes) node.addEventListener("DOMAttrModified", this, true); if (options.characterData) node.addEventListener("DOMCharacterDataModified", this, true); if (options.childList) node.addEventListener("DOMNodeInserted", this, true); if (options.childList || options.subtree) node.addEventListener("DOMNodeRemoved", this, true); }, removeListeners: function() { this.removeListeners_(this.target); }, removeListeners_: function(node) { var options = this.options; if (options.attributes) node.removeEventListener("DOMAttrModified", this, true); if (options.characterData) node.removeEventListener("DOMCharacterDataModified", this, true); if (options.childList) node.removeEventListener("DOMNodeInserted", this, true); if (options.childList || options.subtree) node.removeEventListener("DOMNodeRemoved", this, true); }, addTransientObserver: function(node) { if (node === this.target) return; this.addListeners_(node); this.transientObservedNodes.push(node); var registrations = registrationsTable.get(node); if (!registrations) registrationsTable.set(node, registrations = []); registrations.push(this); }, removeTransientObservers: function() { var transientObservedNodes = this.transientObservedNodes; this.transientObservedNodes = []; transientObservedNodes.forEach(function(node) { this.removeListeners_(node); var registrations = registrationsTable.get(node); for (var i = 0; i < registrations.length; i++) { if (registrations[i] === this) { registrations.splice(i, 1); break; } } }, this); }, handleEvent: function(e) { e.stopImmediatePropagation(); switch (e.type) { case "DOMAttrModified": var name = e.attrName; var namespace = e.relatedNode.namespaceURI; var target = e.target; var record = new getRecord("attributes", target); record.attributeName = name; record.attributeNamespace = namespace; var oldValue = e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; forEachAncestorAndObserverEnqueueRecord(target, function(options) { if (!options.attributes) return; if (options.attributeFilter && options.attributeFilter.length && options.attributeFilter.indexOf(name) === -1 && options.attributeFilter.indexOf(namespace) === -1) { return; } if (options.attributeOldValue) return getRecordWithOldValue(oldValue); return record; }); break; case "DOMCharacterDataModified": var target = e.target; var record = getRecord("characterData", target); var oldValue = e.prevValue; forEachAncestorAndObserverEnqueueRecord(target, function(options) { if (!options.characterData) return; if (options.characterDataOldValue) return getRecordWithOldValue(oldValue); return record; }); break; case "DOMNodeRemoved": this.addTransientObserver(e.target); case "DOMNodeInserted": var changedNode = e.target; var addedNodes, removedNodes; if (e.type === "DOMNodeInserted") { addedNodes = [ changedNode ]; removedNodes = []; } else { addedNodes = []; removedNodes = [ changedNode ]; } var previousSibling = changedNode.previousSibling; var nextSibling = changedNode.nextSibling; var record = getRecord("childList", e.target.parentNode); record.addedNodes = addedNodes; record.removedNodes = removedNodes; record.previousSibling = previousSibling; record.nextSibling = nextSibling; forEachAncestorAndObserverEnqueueRecord(e.relatedNode, function(options) { if (!options.childList) return; return record; }); } clearRecords(); } }; global.JsMutationObserver = JsMutationObserver; if (!global.MutationObserver) global.MutationObserver = JsMutationObserver; })(this); ================================================ FILE: cmd/memlat/static/bower_components/webcomponentsjs/README.md ================================================ webcomponents.js ================ [![Join the chat at https://gitter.im/webcomponents/webcomponentsjs](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/webcomponents/webcomponentsjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) A suite of polyfills supporting the [Web Components](http://webcomponents.org) specs: **Custom Elements**: allows authors to define their own custom tags ([spec](https://w3c.github.io/webcomponents/spec/custom/)). **HTML Imports**: a way to include and reuse HTML documents via other HTML documents ([spec](https://w3c.github.io/webcomponents/spec/imports/)). **Shadow DOM**: provides encapsulation by hiding DOM subtrees under shadow roots ([spec](https://w3c.github.io/webcomponents/spec/shadow/)). This also folds in polyfills for `MutationObserver` and `WeakMap`. ## Releases Pre-built (concatenated & minified) versions of the polyfills are maintained in the [tagged versions](https://github.com/webcomponents/webcomponentsjs/releases) of this repo. There are two variants: `webcomponents.js` includes all of the polyfills. `webcomponents-lite.js` includes all polyfills except for shadow DOM. ## Browser Support Our polyfills are intended to work in the latest versions of evergreen browsers. See below for our complete browser support matrix: | Polyfill | IE10 | IE11+ | Chrome* | Firefox* | Safari 7+* | Chrome Android* | Mobile Safari* | | ---------- |:----:|:-----:|:-------:|:--------:|:----------:|:---------------:|:--------------:| | Custom Elements | ~ | ✓ | ✓ | ✓ | ✓ | ✓| ✓ | | HTML Imports | ~ | ✓ | ✓ | ✓ | ✓| ✓| ✓ | | Shadow DOM | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Templates | ✓ | ✓ | ✓ | ✓| ✓ | ✓ | ✓ | *Indicates the current version of the browser ~Indicates support may be flaky. If using Custom Elements or HTML Imports with Shadow DOM, you will get the non-flaky Mutation Observer polyfill that Shadow DOM includes. The polyfills may work in older browsers, however require additional polyfills (such as classList) to be used. We cannot guarantee support for browsers outside of our compatibility matrix. ### Manually Building If you wish to build the polyfills yourself, you'll need `node` and `gulp` on your system: * install [node.js](http://nodejs.org/) using the instructions on their website * use `npm` to install [gulp.js](http://gulpjs.com/): `npm install -g gulp` Now you are ready to build the polyfills with: # install dependencies npm install # build gulp build The builds will be placed into the `dist/` directory. ## Contribute See the [contributing guide](CONTRIBUTING.md) ## License Everything in this repository is BSD style license unless otherwise specified. Copyright (c) 2015 The Polymer Authors. All rights reserved. ## Helper utilities ### `WebComponentsReady` Under native HTML Imports, ` ================================================ FILE: cmd/memlat/static/memlat-browser.html ================================================ ================================================ FILE: cmd/perfdump/main.go ================================================ // Copyright 2015 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. // Command perfdump prints the raw contents of a perf.data profile. package main import ( "flag" "fmt" "log" "os" "reflect" "github.com/aclements/go-perf/perffile" ) func main() { var ( flagInput = flag.String("i", "perf.data", "input perf.data `file`") flagOrder = flag.String("order", "time", "sort `order`; one of: file, time, causal") ) flag.Parse() order, ok := parseOrder(*flagOrder) if flag.NArg() > 0 || !ok { flag.Usage() os.Exit(1) } f, err := perffile.Open(*flagInput) if err != nil { log.Fatal(err) } defer f.Close() fmt.Printf("events:\n") for _, event := range f.Events { fmt.Printf(" %p=%+v\n", event, *event) } if f.Meta.BuildIDs != nil { fmt.Printf("build IDs:\n") for _, bid := range f.Meta.BuildIDs { fmt.Printf(" %v\n", bid) } } for _, hdr := range []struct { label string val interface{} }{ //{"build IDs", &f.Meta.BuildIDs}, {"hostname", f.Meta.Hostname}, {"OS release", f.Meta.OSRelease}, {"version", f.Meta.Version}, {"arch", f.Meta.Arch}, {"CPUs online", f.Meta.CPUsOnline}, {"CPUs available", f.Meta.CPUsAvail}, {"CPU desc", f.Meta.CPUDesc}, {"CPUID", f.Meta.CPUID}, {"total memory", f.Meta.TotalMem}, {"cmdline", f.Meta.CmdLine}, {"core groups", f.Meta.CoreGroups}, {"thread groups", f.Meta.ThreadGroups}, {"NUMA nodes", f.Meta.NUMANodes}, {"PMU mappings", f.Meta.PMUMappings}, {"groups", f.Meta.Groups}, } { if hdr.val == reflect.Zero(reflect.ValueOf(hdr.val).Type()) { continue } fmt.Printf("%s: %v\n", hdr.label, hdr.val) } fmt.Println() rs := f.Records(order) for rs.Next() { fmt.Printf("%v{\n", rs.Record.Type()) switch r := rs.Record.(type) { case *perffile.RecordSample: v := reflect.ValueOf(r).Elem() for _, n := range r.Fields() { f := v.FieldByName(n) fmt.Printf("\t%s,\n", fmtVal(n, f)) } default: printFields(reflect.ValueOf(r)) } fmt.Printf("}\n") //fmt.Printf("%v %+v\n", rs.Record.Type(), rs.Record) } if err := rs.Err(); err != nil { log.Fatal(err) } } func parseOrder(order string) (perffile.RecordsOrder, bool) { switch order { case "file": return perffile.RecordsFileOrder, true case "time": return perffile.RecordsTimeOrder, true case "causal": return perffile.RecordsCausalOrder, true } return 0, false } func printFields(v reflect.Value) { for v.Kind() == reflect.Ptr { v = v.Elem() } t := v.Type() for i := 0; i < t.NumField(); i++ { info := t.Field(i) f := v.Field(i) if info.Anonymous { printFields(f) } else if (f.Kind() == reflect.Ptr || f.Kind() == reflect.Slice) && f.IsNil() { // Skip } else { fmt.Printf("\t%s,\n", fmtVal(info.Name, f)) } } } func fmtVal(name string, v reflect.Value) string { if v.Kind() == reflect.Ptr { return fmt.Sprintf("%-14s %p", name+":", v.Interface()) } switch name { case "IP", "Addr", "Callchain": return fmt.Sprintf("%-14s %#x", name+":", v.Interface()) } return fmt.Sprintf("%-14s %+v", name+":", v.Interface()) } ================================================ FILE: cmd/prologuer/main.go ================================================ // Copyright 2019 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. // Command prologuer reports what fraction of samples from a profile // are from IPs marked as function prologue in DWARF line tables. // // In Go binaries (as of Go 1.12), this counts the stack bounds check // up to and including the conditional branch to morestack. It does // not the instructions that call morestack or the instructions that // create a function's stack frame. // // This is best used with precise profile samples, such as // // perf record -e cycles:ppp -- package main import ( "debug/dwarf" "debug/elf" "flag" "fmt" "io" "log" "os" "sort" "github.com/aclements/go-perf/perffile" "github.com/aclements/go-perf/perfsession" ) func main() { var flagInput = flag.String("i", "perf.data", "input perf.data `file`") flag.Parse() if flag.NArg() > 0 { flag.Usage() os.Exit(1) } f, err := perffile.Open(*flagInput) if err != nil { log.Fatal(err) } defer f.Close() s := perfsession.New(f) var samples, prologueSamples uint64 rs := f.Records(perffile.RecordsFileOrder) for rs.Next() { s.Update(rs.Record) switch r := rs.Record.(type) { case *perffile.RecordSample: pidInfo := s.LookupPID(r.PID) mmap := pidInfo.LookupMmap(r.IP) if mmap == nil { break } samples++ pr := prologueRanges(s, mmap) if _, _, _, ok := pr.Get(r.IP); ok { prologueSamples++ } } } fmt.Printf("%d of %d samples (%.2f%%) in prologue\n", prologueSamples, samples, float64(prologueSamples)*100/float64(samples)) } var prologueRangesKey = perfsession.NewExtraKey("prologuer.prologueRanges") func prologueRanges(session *perfsession.Session, mmap *perfsession.Mmap) *perfsession.Ranges { fileTab, ok := session.Extra[prologueRangesKey].(map[string]*perfsession.Ranges) if !ok { fileTab = make(map[string]*perfsession.Ranges) session.Extra[prologueRangesKey] = fileTab } filename := mmap.Filename if r, ok := fileTab[filename]; ok { return r } if len(filename) > 0 && filename[0] == '[' { // Not a real file. return nil } r := new(perfsession.Ranges) fileTab[filename] = r elff, err := elf.Open(filename) if err != nil { log.Printf("error loading ELF file %s: %s", filename, err) return r } defer elff.Close() if elff.Section(".debug_info") == nil && elff.Section(".zdebug_info") == nil { // No DWARF info. return r } dwarff, err := elff.DWARF() if err != nil { log.Printf("error loading DWARF from %s: %s", filename, err) return r } pb, cb := fillRanges(r, dwarff) fmt.Printf("%s: %d of %d bytes (%.2f%%) in prologue\n", filename, pb, cb, float64(pb)*100/float64(cb)) return r } func fillRanges(r *perfsession.Ranges, dwarff *dwarf.Data) (prologueBytes, codeBytes uint64) { dr := dwarff.Reader() for { ent, err := dr.Next() if ent == nil || err != nil { break } switch ent.Tag { default: dr.SkipChildren() case dwarf.TagModule, dwarf.TagNamespace: break case dwarf.TagCompileUnit: pend := prologueEndPCs(dwarff, ent) // Process functions in this CU. var funcRanges [][2]uint64 cu: for { ent, err = dr.Next() if ent == nil || err != nil { break } switch ent.Tag { case 0: // End of children of the CU. break cu default: dr.SkipChildren() case dwarf.TagSubprogram: dr.SkipChildren() fr, err := dwarff.Ranges(ent) if err != nil { log.Fatal(err) } funcRanges = append(funcRanges, fr...) } } for _, fr := range funcRanges { codeBytes += fr[1] - fr[0] } // Match function ranges against prologue ends. sort.Slice(funcRanges, func(i, j int) bool { return funcRanges[i][0] < funcRanges[j][0] }) for len(pend) > 0 && len(funcRanges) > 0 { if pend[0] < funcRanges[0][0] { pend = pend[1:] } else if pend[0] >= funcRanges[0][1] { funcRanges = funcRanges[1:] } else { r.Add(funcRanges[0][0], pend[0], nil) prologueBytes += pend[0] - funcRanges[0][0] pend = pend[1:] funcRanges = funcRanges[1:] } } } } return } func prologueEndPCs(dwarff *dwarf.Data, cu *dwarf.Entry) []uint64 { // Decode CU's line table to find prologue end // PCs var pend []uint64 lr, err := dwarff.LineReader(cu) if err != nil { log.Fatal(err) } else if lr == nil { return nil } for { var lent dwarf.LineEntry err := lr.Next(&lent) if err != nil { if err == io.EOF { break } log.Fatal(err) } if lent.PrologueEnd { pend = append(pend, lent.Address) } } return pend } ================================================ FILE: fmt_test.go ================================================ package main import ( "fmt" "io" "io/fs" "os" "os/exec" "path/filepath" "strings" "testing" ) // TestGofmt tests that all files are formatted. func TestGofmt(t *testing.T) { root, fileMap := copyTree(t) gofmt := exec.Command("gofmt", "-w", ".") gofmt.Dir = root gofmt.Stdout, gofmt.Stderr = os.Stdout, os.Stderr if err := gofmt.Run(); err != nil { t.Fatalf("gofmt failed: %v", err) } // Diff the trees. if diffFiles(t, fileMap) { t.Errorf("Files are not gofmt clean. Please run gofmt.") } } // TestGenerated tests that all generated files are up-to-date. func TestGenerated(t *testing.T) { root, fileMap := copyTree(t) // Build bitstringer build := exec.Command("go", "build") build.Dir = filepath.Join(root, "cmd/bitstringer") build.Stdout, build.Stderr = os.Stdout, os.Stderr if err := build.Run(); err != nil { t.Fatalf("go build failed: %v", err) } gen := exec.Command("go", "generate", "./...") gen.Dir = root gen.Stdout, gen.Stderr = os.Stdout, os.Stderr if err := gen.Run(); err != nil { t.Fatalf("go generate failed: %v", err) } // Diff the trees. if diffFiles(t, fileMap) { t.Errorf("Please run go generate.") } } func copyTree(t *testing.T) (string, map[string]string) { src, err := os.Getwd() if err != nil { t.Fatalf("getting working directory: %v", err) } dst := t.TempDir() // Ensure src ends with "/" src = fmt.Sprintf("%s%c", filepath.Clean(src), filepath.Separator) fileMap := make(map[string]string) err = filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error { if !strings.HasPrefix(path, src) { panic(fmt.Sprintf("WalkDir path %q does not start with source path %q", path, src)) } rel := path[len(src):] if d.Name() == ".git" { return filepath.SkipDir } if d.IsDir() { if rel == "" { // This is the root of the tree, so // the destination already exists. return nil } return os.Mkdir(filepath.Join(dst, rel), 0777) } // Only copy .go and related files. if n := d.Name(); !(filepath.Ext(n) == ".go" || n == "go.mod" || n == "go.sum") { return nil } // Copy file. fileMap[path] = filepath.Join(dst, rel) return copyFile(path, filepath.Join(dst, rel)) }) if err != nil { t.Fatalf("error copying source tree: %v", err) } t.Logf("copied source tree to %s", dst) return dst, fileMap } func copyFile(src, dst string) error { out, err := os.Create(dst) if err != nil { return err } defer out.Close() in, err := os.Open(src) if err != nil { return err } defer in.Close() _, err = io.Copy(out, in) if err != nil { return err } return out.Close() } func diffFiles(t *testing.T, fileMap map[string]string) bool { diffs := 0 for orig, new := range fileMap { // --strip-trailing-cr is important on Windows where // git and gofmt may disagree on newline conventions. diff := exec.Command("diff", "-u", "--strip-trailing-cr", orig, new) diff.Stdout = os.Stdout diff.Stderr = os.Stderr if err := diff.Run(); err != nil { switch err := err.(type) { case *exec.ExitError: if err.ExitCode() == 1 { diffs++ continue } } t.Errorf("diff failed: %v", err) } } return diffs != 0 } ================================================ FILE: go.mod ================================================ module github.com/aclements/go-perf go 1.18 require ( github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/ianlancetaylor/demangle v0.0.0-20220517205856-0058ec4f073c ) require golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect ================================================ FILE: go.sum ================================================ github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 h1:xlwdaKcTNVW4PtpQb8aKA4Pjy0CdJHEqvFbAnvR5m2g= github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794/go.mod h1:7e+I0LQFUI9AXWxOfsQROs9xPhoJtbsyWcjJqDd4KPY= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/ianlancetaylor/demangle v0.0.0-20220517205856-0058ec4f073c h1:rwmN+hgiyp8QyBqzdEX43lTjKAxaqCrYHaU5op5P9J8= github.com/ianlancetaylor/demangle v0.0.0-20220517205856-0058ec4f073c/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE= golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= ================================================ FILE: internal/cparse/enums.go ================================================ // Copyright 2018 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 cparse import ( "fmt" ) type Enum struct { Tag Tok Ident Tok } // FindEnums finds top-level enumeration constants in toks. func FindEnums(tokens []Tok) ([]Enum, error) { t := toks(tokens) var enums []Enum for len(t) > 0 { switch { case t.Try(TokOp, "{"): t.SkipBalanced("}") case t.Try(TokOp, "("): t.SkipBalanced(")") case t.Try(TokOp, "["): t.SkipBalanced("]") case t.Try(TokKeyword, "enum"): // Found an enum. Skip tag, if any. tag, _ := t.TryIdent() if t.Try(TokOp, "{") { for { if t.Try(TokOp, "}") { break } id, ok := t.TryIdent() if !ok { return nil, fmt.Errorf("expected identifier") } enums = append(enums, Enum{tag, id}) // Consume initializer. if t.Try(TokOp, "=") { t.SkipBalanced(",", "}") } t.Try(TokOp, ",") } } default: t.Skip(1) } } return enums, nil } ================================================ FILE: internal/cparse/enums_test.go ================================================ // Copyright 2018 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 cparse import ( "testing" ) func TestFindEnums(t *testing.T) { needCC(t) pp := preprocess(t, "int foo(); enum tag { A, B = 2 + (C, D), E };") toks, err := Tokenize(pp) if err != nil { t.Fatal(err) } enums, err := FindEnums(toks) if err != nil { t.Fatal(err) } if len(enums) != 3 { t.Fatalf("expected 3 enums, got %d", len(enums)) } for i, name := range []string{"A", "B", "E"} { if enums[i].Tag.Text != "tag" || enums[i].Ident.Text != name { t.Errorf("expected enum tag.%s, got %v", name, enums[i]) } } } ================================================ FILE: internal/cparse/lex.go ================================================ // Copyright 2018 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 cparse import ( "bytes" "fmt" "strconv" ) type Toks struct { Toks []Tok } type Tok struct { Kind TokKind Text string } type TokKind uint8 const ( TokKeyword TokKind = 1 + iota TokIdent TokNumber TokString TokChar TokOp TokEOF ) func (t Tok) Int() int { if t.Kind != TokNumber { panic("token is not a number") } v, err := strconv.Atoi(t.Text) if err != nil { panic(err) } return v } func (t Tok) Match(kind TokKind, text string) bool { return t.Kind == kind && t.Text == text } var tokTab, puncTab, keyTab = mkTokTab([]string{ "[", "]", "(", ")", "{", "}", ".", "->", "++", "--", "&", "*", "+", "-", "~", "!", "/", "%", "<<", ">>", "<", ">", "<=", ">=", "!=", "^", "|", "&&", "||", "?", ":", ";", "...", "=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|=", ",", "#", "##", }, []string{"auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Bool", "_Complex", "_Imaginary", }) var escTab = map[byte]byte{ '\'': '\'', '"': '"', '?': '?', '\\': '\\', 'a': '\a', 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', 'v': '\v'} type chProps uint8 const ( chNonDigit chProps = 1 << iota chDigit chOct chHex chChars // Character constant or string literal chPunct ) func mkTokTab(punct, keywords []string) (tab [256]chProps, ptab map[string]bool, ktab map[string]bool) { for i := range tab { if i == '_' || 'a' <= i && i <= 'z' || 'A' <= i && i <= 'Z' { tab[i] |= chNonDigit if 'a' <= i && i <= 'f' || 'A' <= i && i <= 'F' { tab[i] |= chHex } } else if '0' <= i && i <= '9' { tab[i] |= chDigit | chHex if i <= '7' { tab[i] |= chOct } } else { switch i { case '\'', '"': tab[i] |= chChars } } } ptab = make(map[string]bool) for _, p := range punct { tab[p[0]] |= chPunct ptab[p] = true } ktab = make(map[string]bool) for _, k := range keywords { ktab[k] = true } return } type pos struct { path string line, col int } func (p pos) error(f string, args ...interface{}) error { msg := fmt.Sprintf(f, args...) return fmt.Errorf("%s:%d:%d: %s", p.path, p.line, p.col, msg) } // charReader implements translation phases 1 and 2: mapping to the // source character set, and deleting "\\\n". It doesn't implement // trigraph sequences, so phase 1 is trivial. type charReader struct { src []byte pos pos } func (r *charReader) ReadByte() byte { // Fold "\\\n". for len(r.src) >= 2 && r.src[0] == '\\' && r.src[1] == '\n' { r.pos.line++ r.pos.col = 0 r.src = r.src[2:] } if len(r.src) == 0 { return 0 } next := r.src[0] r.src = r.src[1:] r.pos.col++ if next == '\n' { r.pos.line++ r.pos.col = 0 } return next } func (r *charReader) EOF() bool { return len(r.src) == 0 } // Tokenize parses src into C tokens. src must already be // pre-processed. func Tokenize(src []byte) ([]Tok, error) { toks := []Tok{} cr := charReader{src: src, pos: pos{"", 1, 0}} var buf []byte var ch byte haveCh := false inLine, lineStart := false, 0 // Processing line directive for { if !haveCh { ch = cr.ReadByte() } haveCh = false if inLine && (ch == '\n' || cr.EOF()) { // Line directive complete. inLine = false lineToks := toks[lineStart:] toks = toks[:lineStart] cr.pos = pos{lineToks[2].Text, lineToks[1].Int(), 0} } if cr.EOF() { return toks, nil } // Skip whitespace. if bytes.IndexByte([]byte(" \t\n\v\f"), ch) >= 0 { continue } // Consume token. buf = append(buf[:0], ch) start := cr.pos switch { case tokTab[ch]&chNonDigit != 0: // Identifier or keyword for { ch = cr.ReadByte() if tokTab[ch]&(chNonDigit|chDigit) == 0 { haveCh = true break } buf = append(buf, ch) } if keyTab[string(buf)] { toks = append(toks, Tok{TokKeyword, string(buf)}) } else { toks = append(toks, Tok{TokIdent, string(buf)}) } case tokTab[ch]&chDigit != 0: // Number // // TODO: Floating-point constants. hex, oct := false, false for i := 0; ; i++ { ch = cr.ReadByte() if i == 0 && ch == 'x' { hex = true } else if i == 0 && ch == '0' { oct = true } else if hex && tokTab[ch]&chHex != 0 || oct && tokTab[ch]&chOct != 0 || tokTab[ch]&chDigit != 0 { // Consume } else { haveCh = true break } buf = append(buf, ch) } toks = append(toks, Tok{TokNumber, string(buf)}) case tokTab[ch]&chChars != 0: // Character constant or string literal term := ch kind := "character constant" if ch == '"' { kind = "string literal" } buf = buf[:0] var escPos pos esc, hex, oct := false, 0, 0 val := uint8(0) for { ch = cr.ReadByte() unread: if cr.EOF() { return nil, start.error("unterminated " + kind) } if esc { esc = false if rep, ok := escTab[ch]; ok { // Simple escape sequence buf = append(buf, rep) } else if ch == 'x' { // Hex escape sequence hex, val = 1, 0 } else if tokTab[ch]&chOct != 0 { oct = 1 val = ch - '0' } else { return nil, escPos.error("bad escape sequence") } } else if hex > 0 { if tokTab[ch]&chHex != 0 { hex++ val = (val << 4) | hexVal(ch) } else if hex == 1 { return nil, escPos.error("bad escape sequence") } else { buf = append(buf, val) hex = 0 goto unread } } else if oct > 0 { if oct <= 3 && tokTab[ch]&chOct != 0 { oct++ val = (val << 3) | (ch - '0') } else { buf = append(buf, val) oct = 0 goto unread } } else if ch == term { break } else if ch == '\\' { esc = true escPos = cr.pos } else if ch == '\n' { return nil, start.error("newline in " + kind) } else { buf = append(buf, ch) } } if term == '"' { toks = append(toks, Tok{TokString, string(buf)}) } else { toks = append(toks, Tok{TokChar, string(buf)}) } case tokTab[ch]&chPunct != 0: // Punctuation or line directive sol := cr.pos.col == 1 for { ch = cr.ReadByte() buf = append(buf, ch) if !puncTab[string(buf)] { buf = buf[:len(buf)-1] haveCh = true break } } text := string(buf) if sol && text == "#" { // Line directive. lineStart, inLine = len(toks), true } toks = append(toks, Tok{TokOp, text}) default: return nil, start.error("unexpected character %q", string(ch)) } } } func hexVal(ch byte) uint8 { switch { case '0' <= ch && ch <= '9': return ch - '0' case 'a' <= ch && ch <= 'f': return ch - 'a' + 10 case 'A' <= ch && ch <= 'F': return ch - 'A' + 10 } panic("not a hex digit") } type toks []Tok func (s toks) Next() Tok { if len(s) > 0 { return s[0] } return Tok{Kind: TokEOF, Text: "EOF"} } func (s toks) Peek(kind TokKind, text string) bool { return len(s) > 0 && s[0].Match(kind, text) } func (s *toks) Try(kind TokKind, text string) bool { if s.Peek(kind, text) { *s = (*s)[1:] return true } return false } func (s *toks) TryIdent() (Tok, bool) { tok := s.Next() if tok.Kind == TokIdent { s.Skip(1) return tok, true } return Tok{}, false } func (s *toks) Skip(n int) { if n > len(*s) { n = len(*s) } (*s) = (*s)[n:] } func (s *toks) SkipBalanced(until ...string) { level := 0 for len(*s) != 0 { next := s.Next() if level == 0 { // Are we at a terminator? for _, u := range until { if next.Match(TokOp, u) { return } } } s.Skip(1) if next.Kind == TokOp { switch next.Text { case "{", "(", "[": level++ case "]", ")", "}": level-- } } } } ================================================ FILE: internal/cparse/lex_test.go ================================================ // Copyright 2018 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 cparse import ( "testing" ) func TestTokenize(t *testing.T) { needCC(t) pp := preprocess(t, "#include ") toks, err := Tokenize(pp) if err != nil { t.Fatal(err) } // Look for "int fprintf(FILE *". subSeq := []Tok{{TokKeyword, "int"}, {TokIdent, "fprintf"}, {TokOp, "("}, {TokIdent, "FILE"}, {TokOp, "*"}} outer: for start := range toks { if len(toks)-start < len(subSeq) { t.Fatal("didn't find fprintf declaration in token stream") } for i, tok := range subSeq { if toks[start+i] != tok { continue outer } } // Found the subsequence. break } } ================================================ FILE: internal/cparse/pp.go ================================================ // Copyright 2018 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 cparse import ( "fmt" "io" "os" "os/exec" "regexp" "strings" ) type BuildEnv struct { CCArgs []string } var macroRe = regexp.MustCompile(`^#define ([_a-zA-Z][_a-zA-Z0-9]*)`) // FindMacros returns the names of all macros defined by the C source // in r. func FindMacros(env *BuildEnv, r io.Reader) ([]string, error) { ccArgs := append([]string(nil), env.CCArgs...) ccArgs = append(ccArgs, "-x", "c", "-E", "-dM", "-") cc := exec.Command("cc", ccArgs...) cc.Stdin = r cc.Stderr = os.Stderr out, err := cc.Output() if err != nil { return nil, err } var macros []string lines := strings.Split(string(out), "\n") for _, line := range lines { if line == "" { continue } m := macroRe.FindStringSubmatch(line) if m == nil { return nil, fmt.Errorf("failed to parse macro %q", line) } macros = append(macros, m[1]) } return macros, nil } // Preprocess invokes the C preprocessor to pre-process the C source // in r. func Preprocess(env *BuildEnv, r io.Reader) ([]byte, error) { // Invoke C compiler for pre-processing. ccArgs := append([]string(nil), env.CCArgs...) ccArgs = append(ccArgs, "-x", "c", "-E", "-") cc := exec.Command("cc", ccArgs...) cc.Stdin = r cc.Stderr = os.Stderr return cc.Output() } ================================================ FILE: internal/cparse/pp_test.go ================================================ // Copyright 2018 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 cparse import ( "bytes" "os/exec" "testing" ) var defaultEnv = BuildEnv{} func TestMacros(t *testing.T) { needCC(t) src := bytes.NewBufferString("#define TEST 123\n#define EMPTY") macros, err := FindMacros(&defaultEnv, src) if err != nil { t.Fatal(err) } outer: for _, want := range []string{"EMPTY", "TEST", "__STDC__"} { for _, have := range macros { if have == want { continue outer } } t.Errorf("%q is not defined", want) } } func needCC(t *testing.T) { t.Helper() const bin = "cc" if _, err := exec.LookPath(bin); err != nil { t.Skipf("need %s binary in PATH", bin) } } func preprocess(t *testing.T, src string) []byte { t.Helper() sb := bytes.NewBufferString(src) pp, err := Preprocess(&defaultEnv, sb) if err != nil { t.Fatal(err) } return pp } ================================================ FILE: internal/cparse/vals.go ================================================ // Copyright 2018 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 cparse import ( "bytes" "fmt" "io/ioutil" "os" "os/exec" "path/filepath" "strconv" "strings" ) type Extractor struct { Prologue string Names []string Vals map[string]interface{} } func (e *Extractor) Extract(env *BuildEnv) error { // Other ways to do this: // // __typeof__(x) xxx = x; // // Or generate inline assembly. // // Extract from object file (requires different object format // readers) or asm. // Construct printer program. src := bytes.NewBufferString(e.Prologue) src.WriteString(` #include #include #define __CPARSE_PR(x) _Generic((x), \ int : __cparse_pr_int, \ long : __cparse_pr_int, \ long long : __cparse_pr_int, \ unsigned int : __cparse_pr_uint, \ unsigned long : __cparse_pr_uint, \ unsigned long long : __cparse_pr_uint, \ char* : __cparse_pr_str)(x) void __cparse_pr_int(long long x) { printf("int %lld\n", x); } void __cparse_pr_uint(unsigned long long x) { printf("uint %llu\n", x); } void __cparse_pr_str(const char *x) { printf("str %zu %s\n", strlen(x), x); } int main(int argc, char **argv) { `) for _, n := range e.Names { fmt.Fprintf(src, "__CPARSE_PR(%s);\n", n) } src.WriteString("return 0;\n}\n") // Compiler printer. tdir, err := ioutil.TempDir("", "cparse-") if err != nil { return err } defer os.RemoveAll(tdir) prPath := filepath.Join(tdir, "cparse-pr.exe") ccArgs := append([]string(nil), env.CCArgs...) ccArgs = append(ccArgs, "-Wformat", "-Werror", "-std=c11", "-x", "c", "-o", prPath, "-") cc := exec.Command("cc", ccArgs...) cc.Stdin = src cc.Stderr = os.Stderr if _, err := cc.Output(); err != nil { fmt.Fprintf(os.Stderr, "compiling source:\n%s", src) return err } // Run printer. pr := exec.Command(prPath) outb, err := pr.Output() if err != nil { return err } out := string(outb) // Parse printer output. e.Vals = make(map[string]interface{}) for i := 0; len(out) > 0; i++ { sep := strings.Index(out, " ") typ := out[:sep] out = out[sep+1:] switch typ { case "int": sep = strings.Index(out, "\n") val, err := strconv.Atoi(out[:sep]) if err != nil { panic(err) } out = out[sep+1:] e.Vals[e.Names[i]] = val case "uint": sep = strings.Index(out, "\n") val, err := strconv.ParseUint(out[:sep], 10, 0) if err != nil { panic(err) } out = out[sep+1:] e.Vals[e.Names[i]] = uint(val) case "str": panic("not implemented: str") default: panic("unexpected type " + typ) } } return nil } ================================================ FILE: internal/gendefs/edit.go ================================================ // Copyright 2018 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 import "sort" type Edit struct { Pos int // Byte offset of edit Del int // Number of bytes to delete at Pos Insert []byte // Bytes to insert at Pos } func DoEdit(src []byte, edits []Edit) []byte { // Sort the edits in order. sort.SliceStable(edits, func(i, j int) bool { return edits[i].Pos < edits[j].Pos }) // Check for overlapping edits. for i := 1; i < len(edits); i++ { if edits[i-1].Pos+edits[i-1].Del > edits[i].Pos { panic("overlapping edits") } } // Process edits. out := []byte{} srcPos := 0 for _, edit := range edits { out = append(out, src[srcPos:edit.Pos]...) out = append(out, edit.Insert...) srcPos = edit.Pos + edit.Del } out = append(out, src[srcPos:]...) return out } ================================================ FILE: internal/gendefs/main.go ================================================ // Copyright 2018 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 import ( "bytes" "flag" "fmt" "go/ast" "go/parser" "go/printer" "go/token" "io/ioutil" "log" "os" "strings" "unicode" "github.com/aclements/go-perf/internal/cparse" ) var ccflags = flag.String("ccflags", "", "space-separated list of flags to pass to cc") func main() { flag.Parse() // Process each Go source file. for _, path := range flag.Args() { process(path) } } func process(path string) { src, err := ioutil.ReadFile(path) if err != nil { log.Fatal(err) } fset := token.NewFileSet() f, err := parser.ParseFile(fset, path, src, parser.ParseComments) if err != nil { log.Fatal(err) } // Extract directives. var defs []*def csrc := new(bytes.Buffer) for _, cg := range f.Comments { for _, c := range cg.List { sp := strings.IndexAny(c.Text, " \r\n\t\v\f") if sp < 0 { continue } cmd := c.Text[2:sp] if cmd == "gendefs:C" { fmt.Fprintf(csrc, "%s\n", c.Text[sp:len(c.Text)-2]) } else if cmd == "gendefs" { defs = append(defs, parseDef(c)) } } } // Attach declaration blocks to def directives. defsTodo := defs for _, decl := range f.Decls { // Skip decls before the next def. if len(defsTodo) == 0 { break } if decl.Pos() < defsTodo[0].Pos { continue } // Attach this decl. if decl, ok := decl.(*ast.GenDecl); !ok || decl.Tok != token.CONST { log.Fatalf("%s: def must be applied to const", fset.Position(defsTodo[0].Pos)) } defsTodo[0].Decl = decl.(*ast.GenDecl) defsTodo = defsTodo[1:] // Check for more than one def per decl. if len(defsTodo) > 0 && defsTodo[0].Pos < decl.Pos() { log.Fatalf("%s: multiple defs for declaration", fset.Position(decl.Pos())) } } if len(defsTodo) > 0 { log.Fatalf("%s: def without a declaration", fset.Position(defsTodo[0].Pos)) } // Get identifier names from C code. env := cparse.BuildEnv{CCArgs: strings.Fields(*ccflags)} pp, err := cparse.Preprocess(&env, bytes.NewBuffer(csrc.Bytes())) if err != nil { log.Fatal(err) } toks, err := cparse.Tokenize(pp) if err != nil { log.Fatal(err) } // TODO: Macros aren't in source order. :( Maybe I need to // sort them by value? Or do my own pre-processor scan just to // get the names (and hope that ignoring other pre-processor // directives is okay)? macros, err := cparse.FindMacros(&env, bytes.NewBuffer(csrc.Bytes())) if err != nil { log.Fatal(err) } consts, err := cparse.FindEnums(toks) if err != nil { log.Fatal(err) } for _, m := range macros { consts = append(consts, cparse.Enum{Ident: cparse.Tok{Text: m}}) } // Extract values of defs. ex := cparse.Extractor{Prologue: csrc.String()} for _, def := range defs { for i, c := range consts { id := c.Ident.Text if def.OmitMax && i == len(consts)-1 && strings.HasSuffix(id, "_MAX") { continue } if def.Omit[id] { continue } _, ok1 := def.CTag(c.Tag.Text) _, ok2 := def.CIdent(id) if ok1 && ok2 { def.Names = append(def.Names, id) ex.Names = append(ex.Names, id) } } if def.OmitMax && len(def.Names) > 0 { if last := def.Names[len(def.Names)-1]; !strings.HasSuffix(last, "_MAX") { log.Fatalf("%s: final name is %s, expected *_MAX", fset.Position(def.Pos), last) } def.Names = def.Names[:len(def.Names)-1] ex.Names = ex.Names[:len(ex.Names)-1] } if len(def.Names) == 0 { log.Fatalf("%s: no constants match C name", fset.Position(def.Pos)) } } if err := ex.Extract(&env); err != nil { log.Fatal(err) } // Replace decls. var edits []Edit filePos := func(pos token.Pos) int { return fset.Position(pos).Offset } for _, def := range defs { // Delete the const block. lparen := filePos(def.Decl.Lparen) rparen := filePos(def.Decl.Rparen) edit := Edit{Pos: lparen + 1, Del: rparen - lparen - 1} insert := new(bytes.Buffer) // Translate values to expressions. var vals []interface{} for _, name := range def.Names { vals = append(vals, ex.Vals[name]) } // Clean up value sequence. valExprs := cleanVals(vals) // Collect comments on existing values. We do this // straight from the text to avoid depending on how // package ast attaches comments to nodes. This // maintains interstitial comments and other // formatting. docText := map[string][]byte{} lineText := map[string][]byte{} prevEnd := lparen + 2 // Skip newline for _, spec := range def.Decl.Specs { spec := spec.(*ast.ValueSpec) docEnd := filePos(spec.Pos()) // Back up to the end of the previous line, // where the comment ended. for src[docEnd] != '\n' { docEnd-- } docEnd++ // Extract the doc text from before the spec. if prevEnd < docEnd { docText[spec.Names[0].Name] = src[prevEnd:docEnd] } // Extract the line text from after the spec. end := filePos(spec.End()) lineLen := bytes.IndexByte(src[end:], '\n') if lineLen > 0 { lineText[spec.Names[0].Name] = src[end : end+lineLen] } prevEnd = end + lineLen + 1 // Skip newline } var prevType string for i, name := range def.Names { suff, _ := def.CIdent(name) goName := def.GoPrefix + cNameToGo(suff) // Emit doc comment. if text, ok := docText[goName]; ok { fmt.Fprintf(insert, "\n%s", text) } // Emit name. fmt.Fprintf(insert, "\n\t%s", goName) // Emit type. if def.GoType != prevType { fmt.Fprintf(insert, " %s", def.GoType) prevType = def.GoType } // Emit value. if valExprs[i] != nil { fmt.Fprintf(insert, "=") printer.Fprint(insert, fset, valExprs[i]) } // Emit line comment. if text, ok := lineText[goName]; ok { fmt.Fprintf(insert, "%s", text) } } fmt.Fprintf(insert, "\n") edit.Insert = insert.Bytes() edits = append(edits, edit) } fmt.Printf("%s", format(DoEdit(src, edits))) } type def struct { CTag func(string) (string, bool) CIdent func(string) (string, bool) GoPrefix string GoType string Omit map[string]bool OmitMax bool Pos token.Pos Decl *ast.GenDecl Names []string } func parseDef(c *ast.Comment) *def { var d def args := strings.Fields(c.Text)[1:] // Extract flags, leaving only positional args. var pos []string for len(args) > 0 { arg := args[0] args = args[1:] switch { case arg == "-omit": if len(args) < 1 { log.Fatalf("missing argument: -no") } if d.Omit == nil { d.Omit = make(map[string]bool) } d.Omit[args[0]] = true args = args[1:] case arg == "-omit-max": d.OmitMax = true case arg[0] == '-': log.Fatalf("unknown directive flag %s", arg) default: pos = append(pos, arg) } } if len(pos) < 2 || len(pos) > 3 { log.Fatalf("wrong number of directive arguments; expected 2 or 3") } if i := strings.Index(pos[0], "."); i < 0 { d.CTag = func(x string) (string, bool) { return x, true } d.CIdent = compileGlob(pos[0]) } else { d.CTag = compileGlob(pos[0][:i]) d.CIdent = compileGlob(pos[0][i+1:]) } d.GoPrefix = pos[1] if len(pos) == 2 { d.GoType = pos[1] } else { d.GoType = pos[2] } d.Pos = c.Slash return &d } func compileGlob(glob string) func(string) (suff string, match bool) { if !strings.HasSuffix(glob, "*") { return func(x string) (string, bool) { return "", glob == x } } pfx := glob[:len(glob)-1] return func(x string) (string, bool) { if strings.HasPrefix(x, pfx) { return x[len(pfx):], true } return "", false } } func cleanVals(lits []interface{}) []ast.Expr { // Look for sequential patterns. isOffset := make([]bool, len(lits)) offsets := make([]int, len(lits)) isShift := make([]bool, len(lits)) for i, lit := range lits { switch val := lit.(type) { case int: isOffset[i] = true offsets[i] = val - i isShift[i] = val == (1 << uint(i)) case uint: default: log.Fatalf("unhandled constant type %T", lit) } } // Extract longest runs and create exprs. runLen := func(len int, fn func(int) bool) int { for i := 0; i < len; i++ { if !fn(i) { return i } } return len } intLit := func(v int) ast.Expr { return &ast.BasicLit{ Kind: token.INT, Value: fmt.Sprint(v), } } exprs := make([]ast.Expr, len(lits)) iota := ast.NewIdent("iota") for i := 0; i < len(lits); { offsetRun := runLen(len(offsets)-i, func(j int) bool { return isOffset[i+j] && offsets[i+j] == offsets[i] }) shiftRun := runLen(len(isShift)-i, func(j int) bool { return isShift[i+j] }) if offsetRun > 1 && offsetRun >= shiftRun { // Run of iota offsets. if offsets[i] == 0 { exprs[i] = iota } else { exprs[i] = &ast.BinaryExpr{X: iota, Op: token.ADD, Y: intLit(offsets[i])} } i += offsetRun } else if shiftRun > 1 { // Run of iota shifts. exprs[i] = &ast.BinaryExpr{X: intLit(1), Op: token.SHL, Y: iota} i += shiftRun } else { // Singleton. switch val := lits[i].(type) { case int: exprs[i] = intLit(val) case uint: exprs[i] = &ast.BasicLit{ Kind: token.INT, Value: fmt.Sprintf("%#x", val), } } i++ } } return exprs } var words = map[string]string{ "CPU": "CPU", "PMU": "PMU", "TSC": "TSC", "MTC": "MTC", "CTC": "CTC", "ID": "ID", "PID": "PID", "TID": "TID", "IP": "IP", "L1D": "L1D", "L1I": "L1I", "LL": "LL", "DTLB": "DTLB", "ITLB": "ITLB", "BPU": "BPU", // Branch prediction unit? "HW": "HW", // Hardware "TX": "TX", // Transaction "HV": "HV", // Hypervisor "CGROUP": "CGroup", "CPUMODE": "CPUMode", } func cNameToGo(c string) string { var out []rune parts := strings.Split(c, "_") for _, part := range parts { if w, ok := words[part]; ok { out = append(out, []rune(w)...) continue } for i, r := range part { if i == 0 { out = append(out, unicode.ToTitle(r)) } else { out = append(out, unicode.ToLower(r)) } } } return string(out) } func format(src []byte) []byte { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err != nil { fmt.Fprintf(os.Stderr, "%s", src) log.Fatal(err) } cfg := printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8} buf := new(bytes.Buffer) cfg.Fprint(buf, fset, f) return buf.Bytes() } ================================================ FILE: perffile/auxflags_string.go ================================================ // Code generated by "bitstringer -type=AuxFlags"; DO NOT EDIT package perffile import "strconv" func (i AuxFlags) String() string { if i == 0 { return "0" } s := "" if i&AuxFlagCollision != 0 { s += "Collision|" } if i&AuxFlagOverwrite != 0 { s += "Overwrite|" } if i&AuxFlagPartial != 0 { s += "Partial|" } if i&AuxFlagTruncated != 0 { s += "Truncated|" } i &^= 15 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/auxpmuformat_string.go ================================================ // Code generated by "stringer -type=AuxPMUFormat"; DO NOT EDIT. package perffile import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[AuxPMUFormatCoresightCoresight-0] _ = x[AuxPMUFormatCoresightRaw-1] _ = x[AuxPMUFormatDefault-0] } const _AuxPMUFormat_name = "AuxPMUFormatCoresightCoresightAuxPMUFormatCoresightRaw" var _AuxPMUFormat_index = [...]uint8{0, 30, 54} func (i AuxPMUFormat) String() string { if i >= AuxPMUFormat(len(_AuxPMUFormat_index)-1) { return "AuxPMUFormat(" + strconv.FormatInt(int64(i), 10) + ")" } return _AuxPMUFormat_name[_AuxPMUFormat_index[i]:_AuxPMUFormat_index[i+1]] } ================================================ FILE: perffile/bpfeventtype_string.go ================================================ // Code generated by "bitstringer -type=BPFEventType"; DO NOT EDIT package perffile import "strconv" func (i BPFEventType) String() string { if i == 0 { return "Unknown" } s := "" if i&BPFEventTypeProgLoad != 0 { s += "ProgLoad|" } if i&BPFEventTypeProgUnload != 0 { s += "ProgUnload|" } i &^= 3 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/branchflags_string.go ================================================ // Code generated by "bitstringer -type=BranchFlags"; DO NOT EDIT package perffile import "strconv" func (i BranchFlags) String() string { if i == 0 { return "0" } s := "" if i&BranchFlagAbort != 0 { s += "Abort|" } if i&BranchFlagInTransaction != 0 { s += "InTransaction|" } if i&BranchFlagMispredicted != 0 { s += "Mispredicted|" } if i&BranchFlagPredicted != 0 { s += "Predicted|" } i &^= 15 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/branchsampletype_string.go ================================================ // Code generated by "bitstringer -type=BranchSampleType"; DO NOT EDIT package perffile import "strconv" func (i BranchSampleType) String() string { if i == 0 { return "0" } s := "" if i&BranchSampleAbortTX != 0 { s += "AbortTX|" } if i&BranchSampleAny != 0 { s += "Any|" } if i&BranchSampleAnyCall != 0 { s += "AnyCall|" } if i&BranchSampleAnyReturn != 0 { s += "AnyReturn|" } if i&BranchSampleCall != 0 { s += "Call|" } if i&BranchSampleCallStack != 0 { s += "CallStack|" } if i&BranchSampleCond != 0 { s += "Cond|" } if i&BranchSampleHV != 0 { s += "HV|" } if i&BranchSampleHWIndex != 0 { s += "HWIndex|" } if i&BranchSampleInTX != 0 { s += "InTX|" } if i&BranchSampleIndCall != 0 { s += "IndCall|" } if i&BranchSampleIndJump != 0 { s += "IndJump|" } if i&BranchSampleKernel != 0 { s += "Kernel|" } if i&BranchSampleNoCycles != 0 { s += "NoCycles|" } if i&BranchSampleNoFlags != 0 { s += "NoFlags|" } if i&BranchSampleNoTX != 0 { s += "NoTX|" } if i&BranchSampleTypeSave != 0 { s += "TypeSave|" } if i&BranchSampleUser != 0 { s += "User|" } i &^= 262143 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/breakpointop_string.go ================================================ // Code generated by "bitstringer -type=BreakpointOp"; DO NOT EDIT package perffile import "strconv" func (i BreakpointOp) String() string { if i == 0 { return "0" } s := "" if i&BreakpointOpR != 0 { s += "R|" } if i&BreakpointOpRW != 0 { s += "RW|" } i &^= 3 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/buf.go ================================================ // Copyright 2015 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 perffile import ( "errors" "io" ) // bufferedSectionReader is a buffered io.SectionReader with offset // tracking. // // This is based on bufio.Reader. This could apply to an arbitrary // io.Reader, but it's specialized for our one current use so the // linker can statically resolve the method calls. type bufferedSectionReader struct { buf []byte rd *io.SectionReader r, w int // buf read and write positions err error pos int64 // file position of read } func newBufferedSectionReader(rd *io.SectionReader) *bufferedSectionReader { pos, err := rd.Seek(0, 1) return &bufferedSectionReader{ buf: make([]byte, 16<<10), rd: rd, pos: pos, err: err, } } var errNegativeRead = errors.New("reader returned negative count from Read") func (b *bufferedSectionReader) readErr() error { err := b.err b.err = nil return err } func (b *bufferedSectionReader) Seek(offset int64, whence int) (int64, error) { if whence == 0 && offset == b.pos || whence == 1 && offset == 0 { return b.pos, nil } var err error b.pos, err = b.rd.Seek(offset, whence) if err == nil { b.r, b.w = 0, 0 } return b.pos, err } func (b *bufferedSectionReader) Read(p []byte) (n int, err error) { n = len(p) if n == 0 { return 0, b.readErr() } if b.r == b.w { if b.err != nil { return 0, b.readErr() } if len(p) >= len(b.buf) { // Large read, empty buffer. // Read directly into p to avoid copy. n, b.err = b.rd.Read(p) if n < 0 { panic(errNegativeRead) } b.pos += int64(n) return n, b.readErr() } b.fill() // buffer is empty if b.r == b.w { return 0, b.readErr() } } // copy as much as we can n = copy(p, b.buf[b.r:b.w]) b.r += n b.pos += int64(n) return n, nil } // fill reads a new chunk into the buffer. func (b *bufferedSectionReader) fill() { // Slide existing data to beginning. if b.r > 0 { copy(b.buf, b.buf[b.r:b.w]) b.w -= b.r b.r = 0 } if b.w >= len(b.buf) { panic("tried to fill full buffer") } // Read new data: try a limited number of times. for i := 0; i < 100; i++ { n, err := b.rd.Read(b.buf[b.w:]) if n < 0 { panic(errNegativeRead) } b.w += n if err != nil { b.err = err return } if n > 0 { return } } b.err = io.ErrNoProgress } ================================================ FILE: perffile/bufdecoder.go ================================================ // Copyright 2015 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 perffile import "encoding/binary" type bufDecoder struct { buf []byte order binary.ByteOrder } func (b *bufDecoder) skip(n int) { b.buf = b.buf[n:] } func (b *bufDecoder) bytes(x []byte) { copy(x, b.buf) b.buf = b.buf[len(x):] } func (b *bufDecoder) u8() uint8 { x := b.buf[0] b.buf = b.buf[1:] return x } func (b *bufDecoder) u16() uint16 { x := b.order.Uint16(b.buf) b.buf = b.buf[2:] return x } func (b *bufDecoder) u32() uint32 { x := b.order.Uint32(b.buf) b.buf = b.buf[4:] return x } func (b *bufDecoder) i32() int32 { x := int32(b.order.Uint32(b.buf)) b.buf = b.buf[4:] return x } func (b *bufDecoder) u64() uint64 { x := b.order.Uint64(b.buf) b.buf = b.buf[8:] return x } func (b *bufDecoder) i64() int64 { x := int64(b.order.Uint64(b.buf)) b.buf = b.buf[8:] return x } func (b *bufDecoder) u64s(x []uint64) { for i := range x { x[i] = b.order.Uint64(b.buf[i*8:]) } b.buf = b.buf[len(x)*8:] } func (b *bufDecoder) u32If(cond bool) uint32 { if cond { return b.u32() } return 0 } func (b *bufDecoder) i32If(cond bool) int32 { if cond { return b.i32() } return 0 } func (b *bufDecoder) u64If(cond bool) uint64 { if cond { return b.u64() } return 0 } func (b *bufDecoder) i64If(cond bool) int64 { if cond { return b.i64() } return 0 } func (b *bufDecoder) cstring() string { for i, c := range b.buf { if c == 0 { x := string(b.buf[:i]) b.buf = b.buf[i+1:] return x } } // TODO: Error? x := string(b.buf) b.buf = b.buf[:1] return x } func (b *bufDecoder) lenString() string { l := b.u32() if l > uint32(len(b.buf)) { // TODO: Error? l = uint32(len(b.buf)) } str := (&bufDecoder{b.buf[:l], nil}).cstring() b.buf = b.buf[l:] return str } func (b *bufDecoder) stringList() []string { out := []string{} count := b.u32() for i := uint32(0); i < count; i++ { out = append(out, b.lenString()) } return out } ================================================ FILE: perffile/cpumode_string.go ================================================ // Code generated by "stringer -type=CPUMode"; DO NOT EDIT. package perffile import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[CPUModeUnknown-0] _ = x[CPUModeKernel-1] _ = x[CPUModeUser-2] _ = x[CPUModeHypervisor-3] _ = x[CPUModeGuestKernel-4] _ = x[CPUModeGuestUser-5] } const _CPUMode_name = "CPUModeUnknownCPUModeKernelCPUModeUserCPUModeHypervisorCPUModeGuestKernelCPUModeGuestUser" var _CPUMode_index = [...]uint8{0, 14, 27, 38, 55, 73, 89} func (i CPUMode) String() string { if i >= CPUMode(len(_CPUMode_index)-1) { return "CPUMode(" + strconv.FormatInt(int64(i), 10) + ")" } return _CPUMode_name[_CPUMode_index[i]:_CPUMode_index[i+1]] } ================================================ FILE: perffile/cpuset.go ================================================ // Copyright 2015 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 perffile import ( "fmt" "sort" "strconv" "strings" ) // A CPUSet represents a set of CPUs by CPU index. type CPUSet []int func parseCPUSet(str string) (CPUSet, error) { var err error out := CPUSet{} for _, r := range strings.Split(str, ",") { var lo, hi int dash := strings.Index(r, "-") if dash == -1 { lo, err = strconv.Atoi(r) if err != nil { return nil, err } hi = lo } else { lo, err = strconv.Atoi(r[:dash]) if err != nil { return nil, err } hi, err = strconv.Atoi(r[dash+1:]) if err != nil { return nil, err } } for cpu := lo; cpu <= hi; cpu++ { out = append(out, cpu) } } sort.Ints(out) i, j := 0, 0 for ; i < len(out); i++ { if i != j && out[i] == out[j] { continue } out[j] = out[i] j++ } return out, nil } func (c CPUSet) String() string { if len(c) == 0 { return "" } out := "" lo, hi := c[0], c[0]-1 flush := func() { if lo == hi { out = fmt.Sprintf("%s,%d", out, lo) } else { out = fmt.Sprintf("%s,%d-%d", out, lo, hi) } } for _, cpu := range c { if cpu == hi+1 { hi = cpu } else { flush() lo, hi = cpu, cpu } } flush() return out[1:] } ================================================ FILE: perffile/datasrcblock_string.go ================================================ // Code generated by "bitstringer -type=DataSrcBlock"; DO NOT EDIT package perffile import "strconv" func (i DataSrcBlock) String() string { if i == 0 { return "NA" } s := "" if i&DataSrcBlockAddr != 0 { s += "Addr|" } if i&DataSrcBlockData != 0 { s += "Data|" } i &^= 3 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/datasrchops_string.go ================================================ // Code generated by "stringer -type=DataSrcHops"; DO NOT EDIT. package perffile import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[DataSrcHopsCore-1] _ = x[DataSrcHopsNode-3] _ = x[DataSrcHopsSocket-3] _ = x[DataSrcHopesBoard-4] _ = x[DataSrcHopsNA-0] } const ( _DataSrcHops_name_0 = "DataSrcHopsNADataSrcHopsCore" _DataSrcHops_name_1 = "DataSrcHopsNodeDataSrcHopesBoard" ) var ( _DataSrcHops_index_0 = [...]uint8{0, 13, 28} _DataSrcHops_index_1 = [...]uint8{0, 15, 32} ) func (i DataSrcHops) String() string { switch { case 0 <= i && i <= 1: return _DataSrcHops_name_0[_DataSrcHops_index_0[i]:_DataSrcHops_index_0[i+1]] case 3 <= i && i <= 4: i -= 3 return _DataSrcHops_name_1[_DataSrcHops_index_1[i]:_DataSrcHops_index_1[i+1]] default: return "DataSrcHops(" + strconv.FormatInt(int64(i), 10) + ")" } } ================================================ FILE: perffile/datasrclevel_string.go ================================================ // Code generated by "bitstringer -type=DataSrcLevel"; DO NOT EDIT package perffile import "strconv" func (i DataSrcLevel) String() string { if i == 0 { return "NA" } s := "" if i&DataSrcLevelIO != 0 { s += "IO|" } if i&DataSrcLevelL1 != 0 { s += "L1|" } if i&DataSrcLevelL2 != 0 { s += "L2|" } if i&DataSrcLevelL3 != 0 { s += "L3|" } if i&DataSrcLevelLFB != 0 { s += "LFB|" } if i&DataSrcLevelLocalRAM != 0 { s += "LocalRAM|" } if i&DataSrcLevelRemoteCache1 != 0 { s += "RemoteCache1|" } if i&DataSrcLevelRemoteCache2 != 0 { s += "RemoteCache2|" } if i&DataSrcLevelRemoteRAM1 != 0 { s += "RemoteRAM1|" } if i&DataSrcLevelRemoteRAM2 != 0 { s += "RemoteRAM2|" } if i&DataSrcLevelUncached != 0 { s += "Uncached|" } i &^= 2047 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/datasrclevelnum_string.go ================================================ // Code generated by "stringer -type=DataSrcLevelNum"; DO NOT EDIT. package perffile import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[DataSrcLevelNumL1-1] _ = x[DataSrcLevelNumL2-2] _ = x[DataSrcLevelNumL3-3] _ = x[DataSrcLevelNumL4-4] _ = x[DataSrcLevelNumAnyCache-11] _ = x[DataSrcLevelNumLFB-12] _ = x[DataSrcLevelNumRAM-13] _ = x[DataSrcLevelNumPMEM-14] _ = x[DataSrcLevelNumNA-15] } const ( _DataSrcLevelNum_name_0 = "DataSrcLevelNumL1DataSrcLevelNumL2DataSrcLevelNumL3DataSrcLevelNumL4" _DataSrcLevelNum_name_1 = "DataSrcLevelNumAnyCacheDataSrcLevelNumLFBDataSrcLevelNumRAMDataSrcLevelNumPMEMDataSrcLevelNumNA" ) var ( _DataSrcLevelNum_index_0 = [...]uint8{0, 17, 34, 51, 68} _DataSrcLevelNum_index_1 = [...]uint8{0, 23, 41, 59, 78, 95} ) func (i DataSrcLevelNum) String() string { switch { case 1 <= i && i <= 4: i -= 1 return _DataSrcLevelNum_name_0[_DataSrcLevelNum_index_0[i]:_DataSrcLevelNum_index_0[i+1]] case 11 <= i && i <= 15: i -= 11 return _DataSrcLevelNum_name_1[_DataSrcLevelNum_index_1[i]:_DataSrcLevelNum_index_1[i+1]] default: return "DataSrcLevelNum(" + strconv.FormatInt(int64(i), 10) + ")" } } ================================================ FILE: perffile/datasrclock_string.go ================================================ // Code generated by "stringer -type=DataSrcLock"; DO NOT EDIT. package perffile import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[DataSrcLockNA-0] _ = x[DataSrcLockUnlocked-1] _ = x[DataSrcLockLocked-2] } const _DataSrcLock_name = "DataSrcLockNADataSrcLockUnlockedDataSrcLockLocked" var _DataSrcLock_index = [...]uint8{0, 13, 32, 49} func (i DataSrcLock) String() string { if i < 0 || i >= DataSrcLock(len(_DataSrcLock_index)-1) { return "DataSrcLock(" + strconv.FormatInt(int64(i), 10) + ")" } return _DataSrcLock_name[_DataSrcLock_index[i]:_DataSrcLock_index[i+1]] } ================================================ FILE: perffile/datasrcop_string.go ================================================ // Code generated by "bitstringer -type=DataSrcOp"; DO NOT EDIT package perffile import "strconv" func (i DataSrcOp) String() string { if i == 0 { return "NA" } s := "" if i&DataSrcOpExec != 0 { s += "Exec|" } if i&DataSrcOpLoad != 0 { s += "Load|" } if i&DataSrcOpPrefetch != 0 { s += "Prefetch|" } if i&DataSrcOpStore != 0 { s += "Store|" } i &^= 15 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/datasrcsnoop_string.go ================================================ // Code generated by "bitstringer -type=DataSrcSnoop"; DO NOT EDIT package perffile import "strconv" func (i DataSrcSnoop) String() string { if i == 0 { return "NA" } s := "" if i&DataSrcSnoopFwd != 0 { s += "Fwd|" } if i&DataSrcSnoopHit != 0 { s += "Hit|" } if i&DataSrcSnoopHitM != 0 { s += "HitM|" } if i&DataSrcSnoopMiss != 0 { s += "Miss|" } if i&DataSrcSnoopNone != 0 { s += "None|" } i &^= 31 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/datasrctlb_string.go ================================================ // Code generated by "bitstringer -type=DataSrcTLB"; DO NOT EDIT package perffile import "strconv" func (i DataSrcTLB) String() string { if i == 0 { return "NA" } s := "" if i&DataSrcTLBHardwareWalker != 0 { s += "HardwareWalker|" } if i&DataSrcTLBHit != 0 { s += "Hit|" } if i&DataSrcTLBL1 != 0 { s += "L1|" } if i&DataSrcTLBL2 != 0 { s += "L2|" } if i&DataSrcTLBMiss != 0 { s += "Miss|" } if i&DataSrcTLBOSFaultHandler != 0 { s += "OSFaultHandler|" } i &^= 63 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/doc_test.go ================================================ // Copyright 2015 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 perffile import ( "fmt" "log" ) func Example() { f, err := Open("perf.data") if err != nil { log.Fatal(err) } defer f.Close() rs := f.Records(RecordsTimeOrder) for rs.Next() { switch r := rs.Record.(type) { case *RecordSample: fmt.Printf("sample: %+v\n", r) } } if err := rs.Err(); err != nil { log.Fatal(err) } } ================================================ FILE: perffile/eventflags_string.go ================================================ // Code generated by "bitstringer -type=EventFlags"; DO NOT EDIT package perffile import "strconv" func (i EventFlags) String() string { if i == 0 { return "0" } s := "" if i&EventFlagAuxOutput != 0 { s += "AuxOutput|" } if i&EventFlagBuildID != 0 { s += "BuildID|" } if i&EventFlagCGroup != 0 { s += "CGroup|" } if i&EventFlagClockID != 0 { s += "ClockID|" } if i&EventFlagComm != 0 { s += "Comm|" } if i&EventFlagCommExec != 0 { s += "CommExec|" } if i&EventFlagContextSwitch != 0 { s += "ContextSwitch|" } if i&EventFlagDisabled != 0 { s += "Disabled|" } if i&EventFlagEnableOnExec != 0 { s += "EnableOnExec|" } if i&EventFlagExcludeCallchainKernel != 0 { s += "ExcludeCallchainKernel|" } if i&EventFlagExcludeCallchainUser != 0 { s += "ExcludeCallchainUser|" } if i&EventFlagExcludeGuest != 0 { s += "ExcludeGuest|" } if i&EventFlagExcludeHost != 0 { s += "ExcludeHost|" } if i&EventFlagExcludeHypervisor != 0 { s += "ExcludeHypervisor|" } if i&EventFlagExcludeIdle != 0 { s += "ExcludeIdle|" } if i&EventFlagExcludeKernel != 0 { s += "ExcludeKernel|" } if i&EventFlagExcludeUser != 0 { s += "ExcludeUser|" } if i&EventFlagExclusive != 0 { s += "Exclusive|" } if i&EventFlagFreq != 0 { s += "Freq|" } if i&EventFlagInherit != 0 { s += "Inherit|" } if i&EventFlagInheritStat != 0 { s += "InheritStat|" } if i&EventFlagInheritThread != 0 { s += "InheritThread|" } if i&EventFlagKsymbol != 0 { s += "Ksymbol|" } if i&EventFlagMmap != 0 { s += "Mmap|" } if i&EventFlagMmapData != 0 { s += "MmapData|" } if i&EventFlagMmapInodeData != 0 { s += "MmapInodeData|" } if i&EventFlagNamespaces != 0 { s += "Namespaces|" } if i&EventFlagPinned != 0 { s += "Pinned|" } if i&EventFlagRemoveOnExec != 0 { s += "RemoveOnExec|" } if i&EventFlagSampleIDAll != 0 { s += "SampleIDAll|" } if i&EventFlagSigtrap != 0 { s += "Sigtrap|" } if i&EventFlagTask != 0 { s += "Task|" } if i&EventFlagTextPoke != 0 { s += "TextPoke|" } if i&EventFlagWakeupWatermark != 0 { s += "WakeupWatermark|" } if i&EventFlagWriteBackward != 0 { s += "WriteBackward|" } i &^= 137438855167 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/eventhardwareid_string.go ================================================ // Code generated by "stringer -type=EventHardwareID,EventSoftware,HWCache,HWCacheOp,HWCacheResult"; DO NOT EDIT. package perffile import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[EventHardwareIDCPUCycles-0] _ = x[EventHardwareIDInstructions-1] _ = x[EventHardwareIDCacheReferences-2] _ = x[EventHardwareIDCacheMisses-3] _ = x[EventHardwareIDBranchInstructions-4] _ = x[EventHardwareIDBranchMisses-5] _ = x[EventHardwareIDBusCycles-6] _ = x[EventHardwareIDStalledCyclesFrontend-7] _ = x[EventHardwareIDStalledCyclesBackend-8] _ = x[EventHardwareIDRefCPUCycles-9] } const _EventHardwareID_name = "EventHardwareIDCPUCyclesEventHardwareIDInstructionsEventHardwareIDCacheReferencesEventHardwareIDCacheMissesEventHardwareIDBranchInstructionsEventHardwareIDBranchMissesEventHardwareIDBusCyclesEventHardwareIDStalledCyclesFrontendEventHardwareIDStalledCyclesBackendEventHardwareIDRefCPUCycles" var _EventHardwareID_index = [...]uint16{0, 24, 51, 81, 107, 140, 167, 191, 227, 262, 289} func (i EventHardwareID) String() string { if i >= EventHardwareID(len(_EventHardwareID_index)-1) { return "EventHardwareID(" + strconv.FormatInt(int64(i), 10) + ")" } return _EventHardwareID_name[_EventHardwareID_index[i]:_EventHardwareID_index[i+1]] } func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[EventSoftwareCPUClock-0] _ = x[EventSoftwareTaskClock-1] _ = x[EventSoftwarePageFaults-2] _ = x[EventSoftwareContextSwitches-3] _ = x[EventSoftwareCPUMigrations-4] _ = x[EventSoftwarePageFaultsMin-5] _ = x[EventSoftwarePageFaultsMaj-6] _ = x[EventSoftwareAlignmentFaults-7] _ = x[EventSoftwareEmulationFaults-8] _ = x[EventSoftwareDummy-9] _ = x[EventSoftwareBpfOutput-10] _ = x[EventSoftwareCGroupSwitches-11] } const _EventSoftware_name = "EventSoftwareCPUClockEventSoftwareTaskClockEventSoftwarePageFaultsEventSoftwareContextSwitchesEventSoftwareCPUMigrationsEventSoftwarePageFaultsMinEventSoftwarePageFaultsMajEventSoftwareAlignmentFaultsEventSoftwareEmulationFaultsEventSoftwareDummyEventSoftwareBpfOutputEventSoftwareCGroupSwitches" var _EventSoftware_index = [...]uint16{0, 21, 43, 66, 94, 120, 146, 172, 200, 228, 246, 268, 295} func (i EventSoftware) String() string { if i >= EventSoftware(len(_EventSoftware_index)-1) { return "EventSoftware(" + strconv.FormatInt(int64(i), 10) + ")" } return _EventSoftware_name[_EventSoftware_index[i]:_EventSoftware_index[i+1]] } func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[HWCacheL1D-0] _ = x[HWCacheL1I-1] _ = x[HWCacheLL-2] _ = x[HWCacheDTLB-3] _ = x[HWCacheITLB-4] _ = x[HWCacheBPU-5] _ = x[HWCacheNode-6] } const _HWCache_name = "HWCacheL1DHWCacheL1IHWCacheLLHWCacheDTLBHWCacheITLBHWCacheBPUHWCacheNode" var _HWCache_index = [...]uint8{0, 10, 20, 29, 40, 51, 61, 72} func (i HWCache) String() string { if i >= HWCache(len(_HWCache_index)-1) { return "HWCache(" + strconv.FormatInt(int64(i), 10) + ")" } return _HWCache_name[_HWCache_index[i]:_HWCache_index[i+1]] } func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[HWCacheOpRead-0] _ = x[HWCacheOpWrite-1] _ = x[HWCacheOpPrefetch-2] } const _HWCacheOp_name = "HWCacheOpReadHWCacheOpWriteHWCacheOpPrefetch" var _HWCacheOp_index = [...]uint8{0, 13, 27, 44} func (i HWCacheOp) String() string { if i >= HWCacheOp(len(_HWCacheOp_index)-1) { return "HWCacheOp(" + strconv.FormatInt(int64(i), 10) + ")" } return _HWCacheOp_name[_HWCacheOp_index[i]:_HWCacheOp_index[i+1]] } func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[HWCacheResultAccess-0] _ = x[HWCacheResultMiss-1] } const _HWCacheResult_name = "HWCacheResultAccessHWCacheResultMiss" var _HWCacheResult_index = [...]uint8{0, 19, 36} func (i HWCacheResult) String() string { if i >= HWCacheResult(len(_HWCacheResult_index)-1) { return "HWCacheResult(" + strconv.FormatInt(int64(i), 10) + ")" } return _HWCacheResult_name[_HWCacheResult_index[i]:_HWCacheResult_index[i+1]] } ================================================ FILE: perffile/eventprecision_string.go ================================================ // Code generated by "stringer -type=EventPrecision"; DO NOT EDIT. package perffile import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[EventPrecisionArbitrarySkid-0] _ = x[EventPrecisionConstantSkid-1] _ = x[EventPrecisionTryZeroSkid-2] _ = x[EventPrecisionZeroSkip-3] } const _EventPrecision_name = "EventPrecisionArbitrarySkidEventPrecisionConstantSkidEventPrecisionTryZeroSkidEventPrecisionZeroSkip" var _EventPrecision_index = [...]uint8{0, 27, 53, 78, 100} func (i EventPrecision) String() string { if i < 0 || i >= EventPrecision(len(_EventPrecision_index)-1) { return "EventPrecision(" + strconv.FormatInt(int64(i), 10) + ")" } return _EventPrecision_name[_EventPrecision_index[i]:_EventPrecision_index[i+1]] } ================================================ FILE: perffile/events.go ================================================ package perffile /*gendefs:C #include */ //go:generate -command bitstringer ../cmd/bitstringer/bitstringer //go:generate stringer -type=EventHardwareID,EventSoftware,HWCache,HWCacheOp,HWCacheResult //go:generate bitstringer -type=BreakpointOp -strip=BreakpointOp // EventGeneric is a generic representation of a performance event. // // Any perf event can be represented by EventGeneric, but some // encoding is generally necessary. Hence, EventGeneric can be // translated back and forth between specific Event* types. type EventGeneric struct { // Type specifies the major type of this event, such as // hardware event, software event, or tracepoint. Type EventType // ID is the specific event within the class described by // Type. // // In perf_event_attr, this corresponds to either // perf_event_attr.config or, if Type == EventTypeBreakpoint, // perf_event_attr.bp_type. ID uint64 // Config gives additional configuration specific to the event // described by Type and ID. // // In perf_event_attr, this corresponds to // perf_event_attr.config1 and config2. Config []uint64 } // Decode decodes a generic event g into a specific event type. func (g *EventGeneric) Decode() Event { switch g.Type { case EventTypeHardware: return EventHardware{ ID: EventHardwareID(g.ID), PMUTypeID: PMUTypeID(g.ID >> 32), } case EventTypeSoftware: return EventSoftware(g.ID) case EventTypeTracepoint: return EventTracepoint(g.ID) case EventTypeHWCache: return EventHWCache{ Level: HWCache(g.ID), Op: HWCacheOp(g.ID >> 8), Result: HWCacheResult(g.ID >> 16), PMUTypeID: uint32(g.ID >> 32), } case EventTypeRaw: return EventRaw(g.ID) case EventTypeBreakpoint: return EventBreakpoint{ BreakpointOp(g.ID), g.Config[0], g.Config[1], } } return eventUnknown{*g} } type eventUnknown struct { g EventGeneric } func (e eventUnknown) Generic() EventGeneric { return e.g } // EventHardware represents a hardware event. type EventHardware struct { ID EventHardwareID PMUTypeID PMUTypeID // Map back to name with FileMeta.PMUMappings } func (e EventHardware) Generic() EventGeneric { id := uint64(e.ID) | uint64(e.PMUTypeID)<<32 return EventGeneric{Type: EventTypeHardware, ID: id} } // EventHardwareID represents a hardware event type. // // This corresponds to the perf_hw_id enum from // include/uapi/linux/perf_event.h type EventHardwareID uint8 //gendefs perf_hw_id.PERF_COUNT_HW_* EventHardwareID -omit-max const ( EventHardwareIDCPUCycles EventHardwareID = iota EventHardwareIDInstructions EventHardwareIDCacheReferences EventHardwareIDCacheMisses EventHardwareIDBranchInstructions EventHardwareIDBranchMisses EventHardwareIDBusCycles EventHardwareIDStalledCyclesFrontend EventHardwareIDStalledCyclesBackend EventHardwareIDRefCPUCycles ) // PMUTypeID is a kernel-assigned type id assigned on PMU registration, visible // at /sys/bus/event_source/devices/$PMU/type. type PMUTypeID uint32 // EventSoftware represents a software event. // // This corresponds to the perf_sw_ids enum from // include/uapi/linux/perf_event.h type EventSoftware uint64 //gendefs perf_sw_ids.PERF_COUNT_SW_* EventSoftware -omit-max const ( EventSoftwareCPUClock EventSoftware = iota EventSoftwareTaskClock EventSoftwarePageFaults EventSoftwareContextSwitches EventSoftwareCPUMigrations EventSoftwarePageFaultsMin EventSoftwarePageFaultsMaj EventSoftwareAlignmentFaults EventSoftwareEmulationFaults EventSoftwareDummy EventSoftwareBpfOutput EventSoftwareCGroupSwitches ) func (e EventSoftware) Generic() EventGeneric { return EventGeneric{Type: EventTypeSoftware, ID: uint64(e)} } // EventTracepoint represents a kernel tracepoint event. // // The IDs of the tracepoint events are given by the // tracing/events/*/*/id files under debugfs. type EventTracepoint uint64 func (e EventTracepoint) Generic() EventGeneric { return EventGeneric{Type: EventTypeTracepoint, ID: uint64(e)} } // EventHWCache represents a hardware cache event. type EventHWCache struct { Level HWCache Op HWCacheOp Result HWCacheResult PMUTypeID uint32 } func (e EventHWCache) Generic() EventGeneric { id := uint64(e.Level) | uint64(e.Op)<<8 | uint64(e.Result)<<16 | uint64(e.PMUTypeID)<<32 return EventGeneric{Type: EventTypeHWCache, ID: id} } // HWCache represents a level in the hardware cache. // // This corresponds to the perf_hw_cache_id enum from // include/uapi/linux/perf_event.h type HWCache uint8 //gendefs perf_hw_cache_id.PERF_COUNT_HW_CACHE_* HWCache -omit-max const ( HWCacheL1D HWCache = iota HWCacheL1I HWCacheLL HWCacheDTLB HWCacheITLB HWCacheBPU HWCacheNode ) // HWCacheOp represents a type of access to a hardware cache. // // This corresponds to the perf_hw_cache_op_id enum from // include/uapi/linux/perf_event.h type HWCacheOp uint8 //gendefs perf_hw_cache_op_id.PERF_COUNT_HW_CACHE_OP_* HWCacheOp -omit-max const ( HWCacheOpRead HWCacheOp = iota HWCacheOpWrite HWCacheOpPrefetch ) // HWCacheResult represents the result of a accessing a hardware // cache. // // This corresponds to the perf_hw_cache_op_result_id enum from // include/uapi/linux/perf_event.h type HWCacheResult uint8 //gendefs perf_hw_cache_op_result_id.PERF_COUNT_HW_CACHE_RESULT_* HWCacheResult -omit-max const ( HWCacheResultAccess HWCacheResult = iota HWCacheResultMiss ) // EventRaw represents a "raw" hardware PMU event in a CPU-specific // format. type EventRaw uint64 func (e EventRaw) Generic() EventGeneric { return EventGeneric{Type: EventTypeRaw, ID: uint64(e)} } // EventBreakpoint represents a breakpoint event. // // Breakpoint events are triggered by a specific type of access to an // address in memory. type EventBreakpoint struct { // Op specifies what type of access to Addr should trigger // this event. Op BreakpointOp // Addr is the address to watch for operation Op. Addr uint64 // Len is the number of bytes to watch at Addr. What sizes are // supported depends on the hardware, but it generally must be // a small power of 2. Len uint64 } func (e EventBreakpoint) Generic() EventGeneric { return EventGeneric{Type: EventTypeBreakpoint, ID: uint64(e.Op), Config: []uint64{e.Addr, e.Len}} } // BreakpointOp is a type of memory access that can trigger a // breakpoint event. // // This corresponds to the HW_BREAKPOINT_* constants from // include/uapi/linux/hw_breakpoint.h type BreakpointOp uint32 const ( BreakpointOpR BreakpointOp = 1 BreakpointOpW = 2 BreakpointOpRW = BreakpointOpR | BreakpointOpW BreakpointOpX = 4 ) ================================================ FILE: perffile/eventtype_string.go ================================================ // Code generated by "stringer -type=EventType"; DO NOT EDIT. package perffile import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[EventTypeHardware-0] _ = x[EventTypeSoftware-1] _ = x[EventTypeTracepoint-2] _ = x[EventTypeHWCache-3] _ = x[EventTypeRaw-4] _ = x[EventTypeBreakpoint-5] } const _EventType_name = "EventTypeHardwareEventTypeSoftwareEventTypeTracepointEventTypeHWCacheEventTypeRawEventTypeBreakpoint" var _EventType_index = [...]uint8{0, 17, 34, 53, 69, 81, 100} func (i EventType) String() string { if i >= EventType(len(_EventType_index)-1) { return "EventType(" + strconv.FormatInt(int64(i), 10) + ")" } return _EventType_name[_EventType_index[i]:_EventType_index[i+1]] } ================================================ FILE: perffile/format.go ================================================ // Copyright 2015 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 perffile import ( "fmt" "io" ) /*gendefs:C #include */ //go:generate -command bitstringer ../cmd/bitstringer/bitstringer const numFeatureBits = 256 // perf_file_header from tools/perf/util/header.h type fileHeader struct { Magic [8]byte Size uint64 // Size of fileHeader on disk AttrSize uint64 // Size of fileAttr on disk Attrs fileSection // Array of fileAttr Data fileSection // Alternating recordHeader and record _ fileSection // event_types; ignored in v2 Features [numFeatureBits / 64]uint64 // Bitmap of feature } func (h *fileHeader) hasFeature(f feature) bool { return h.Features[f/64]&(1<<(uint(f)%64)) != 0 } // perf_file_section from tools/perf/util/header.h type fileSection struct { Offset, Size uint64 } func (s fileSection) sectionReader(r io.ReaderAt) *io.SectionReader { return io.NewSectionReader(r, int64(s.Offset), int64(s.Size)) } func (s fileSection) data(r io.ReaderAt) ([]byte, error) { out := make([]byte, s.Size) n, err := r.ReadAt(out, int64(s.Offset)) if n == len(out) { return out, nil } return nil, err } // HEADER_* enum from tools/perf/util/header.h type feature int // TODO: gendefs HEADER_* feature -omit HEADER_FIRST_FEATURE -omit HEADER_FEAT_BITS // Tricky because tools/perf/util/header.h pulls in all sorts of other junk. const ( featureReserved feature = iota // always cleared featureTracingData featureBuildID featureHostname featureOSRelease featureVersion featureArch featureNrCpus featureCPUDesc featureCPUID featureTotalMem featureCmdline featureEventDesc featureCPUTopology featureNUMATopology featureBranchStack featurePMUMappings featureGroupDesc ) // perf_file_attr from tools/perf/util/header.c type fileAttr struct { Attr EventAttr IDs fileSection // array of attrID, one per core/thread } // eventAttrV0 is on-disk version 0 of the perf_event_attr structure. // Later versions extended this with additional fields, but the header // is always the same. type eventAttrV0 struct { Type EventType Size uint32 Config uint64 SamplePeriodOrFreq uint64 SampleFormat SampleFormat ReadFormat ReadFormat Flags EventFlags WakeupEventsOrWatermark uint32 BPType uint32 // BPAddrOrConfig1 can also contain kprobe_func or uprobe_path, // but these are just pointers to strings used by the // perf_event_open API, so are not meaningful in perf files. BPAddrOrConfig1 uint64 } // eventAttrVN is the on-disk latest version of the perf_event_attr // structure (currently version 7). type eventAttrVN struct { eventAttrV0 // ABI v1 // // BPLenOrConfig2 can also contain kprobe_addr or // probe_offset, which are used in conjunction with // kprobe_func and uprobe_path (above). BPLenOrConfig2 uint64 // ABI v2 BranchSampleType BranchSampleType // ABI v3 SampleRegsUser uint64 SampleStackUser uint32 ClockID int32 // ABI v4 SampleRegsIntr uint64 // ABI v5 AuxWatermark uint32 SampleMaxStack uint16 // Max number of frame pointers in a callchain; should be < /proc/sys/kernel/perf_event_max_stack Pad uint16 // Align to uint64 // ABI v6 AuxSampleSize uint32 // Size of aux samples to include in SampleFormatAux. Pad2 uint32 // Align to uint64 // ABI v7 SigData uint64 // User-provided data passed in sigcontext to SIGTRAP. } // TODO: Make public type attrID uint64 // Event describes a specific performance monitoring event. // // Events are quite general. They can be hardware events such as // cycles or cache misses. They can be kernel software events such as // page faults. They can be user or kernel trace-points, or many other // things. All events happen at some instant and can be counted. type Event interface { // Generic returns the generic representation of this Event. Generic() EventGeneric } // An EventType is a general class of performance event. // // This corresponds to the perf_type_id enum from // include/uapi/linux/perf_event.h type EventType uint32 //gendefs perf_type_id.PERF_TYPE_* EventType -omit-max //go:generate stringer -type=EventType const ( EventTypeHardware EventType = iota EventTypeSoftware EventTypeTracepoint EventTypeHWCache EventTypeRaw EventTypeBreakpoint ) // An EventID combined with an EventType describes a specific event. type EventID uint64 // EventAttr describes an event and how that event should be recorded. // // This corresponds to the perf_event_attr struct from // include/uapi/linux/perf_event.h type EventAttr struct { // Event describes the event that will be (or was) counted or // sampled. Event Event // SamplePeriod, if non-zero, is the approximate number of // events between each sample. // // For a sampled event, SamplePeriod will be set if // Flags&EventFlagsFreq == 0. See also SampleFreq. SamplePeriod uint64 // SampleFreq, if non-zero, is the approximate number of // samples to record per second per core. This is approximated // by dynamically adjusting the event sampling period (see // perf_calculate_period) and thus is not particularly // accurate (and even less accurate for events that don't // happen at a regular rate). If SampleFormat includes // SampleFormatPeriod, each sample includes the number of // events until the next sample on the same CPU. // // For a sampled event, SampleFreq will be set if // Flags&EventFlagsFreq != 0. See also SamplePeriod. SampleFreq uint64 // The format of RecordSamples SampleFormat SampleFormat // The format of SampleRead ReadFormat ReadFormat Flags EventFlags // Precise indicates the precision of instruction pointers // recorded by this event. Precise EventPrecision // WakeupEvents specifies to wake up every WakeupEvents // events. Either this or WakeupWatermark will be non-zero, // depending on Flags&EventFlagWakeupWatermark. WakeupEvents uint32 // WakeupWatermark specifies to wake up every WakeupWatermark // bytes. WakeupWatermark uint32 // BranchSampleType specifies the types of branches to record // in the branch stack if SampleFormat&SampleFormatBranchStack // is set, as well as what information to record about each // branch. BranchSampleType BranchSampleType // SampleRegsUser is a bitmask of user-space registers // captured at each sample in RecordSample.RegsUser. The // hardware register corresponding to each bit depends on the // register ABI. SampleRegsUser uint64 // Size of user stack to dump on samples SampleStackUser uint32 // SampleRegsIntr is a bitmask of registers captured at each // sample in RecordSample.RegsIntr. If Precise == // EventPrecisionArbitrarySkid, these registers are captured // at the PMU interrupt. Otherwise, these registers are // captured by the hardware when it samples an instruction. SampleRegsIntr uint64 // AuxWatermark is the watermark for the AUX area in bytes at // which user space is woken up to collect the AUX area. AuxWatermark uint32 // SampleMaxStack is the maximum number of frame pointers in a // callchain. Should be < /proc/sys/kernel/perf_event_max_stack. SampleMaxStack uint16 } // A SampleFormat is a bitmask of the fields recorded by a sample. // // This corresponds to the perf_event_sample_format enum from // include/uapi/linux/perf_event.h type SampleFormat uint64 //gendefs perf_event_sample_format.PERF_SAMPLE_* SampleFormat -omit-max //go:generate bitstringer -type=SampleFormat -strip=SampleFormat const ( SampleFormatIP SampleFormat = 1 << iota SampleFormatTID SampleFormatTime SampleFormatAddr SampleFormatRead SampleFormatCallchain SampleFormatID SampleFormatCPU SampleFormatPeriod SampleFormatStreamID SampleFormatRaw SampleFormatBranchStack SampleFormatRegsUser SampleFormatStackUser SampleFormatWeight SampleFormatDataSrc SampleFormatIdentifier SampleFormatTransaction SampleFormatRegsIntr SampleFormatPhysAddr SampleFormatAux SampleFormatCGroup SampleFormatDataPageSize SampleFormatCodePageSize SampleFormatWeightStruct ) // sampleIDOffset returns the byte offset of the ID field within an // on-disk sample record with this sample format. If there is no ID // field, it returns -1. func (s SampleFormat) sampleIDOffset() int { // See __perf_evsel__calc_id_pos in tools/perf/util/evsel.c. if s&SampleFormatIdentifier != 0 { return 0 } if s&SampleFormatID == 0 { return -1 } off := 0 if s&SampleFormatIP != 0 { off += 8 } if s&SampleFormatTID != 0 { off += 8 } if s&SampleFormatTime != 0 { off += 8 } if s&SampleFormatAddr != 0 { off += 8 } return off } // recordIDOffset returns the byte offset of the ID field of // non-sample records relative to the end of the on-disk sample. If // there is no ID field, it returns -1. func (s SampleFormat) recordIDOffset() int { // See __perf_evsel__calc_is_pos in tools/perf/util/evsel.c. if s&SampleFormatIdentifier != 0 { return -8 } if s&SampleFormatID == 0 { return -1 } off := 0 if s&SampleFormatCPU != 0 { off -= 8 } if s&SampleFormatStreamID != 0 { off -= 8 } return off - 8 } // trailerBytes returns the length in the sample_id trailer for // non-sample records. func (s SampleFormat) trailerBytes() int { s &= SampleFormatTID | SampleFormatTime | SampleFormatID | SampleFormatStreamID | SampleFormatCPU | SampleFormatIdentifier return 8 * weight(uint64(s)) } // ReadFormat is a bitmask of the fields recorded in the SampleRead // field(s) of a sample. // // This corresponds to the perf_event_read_format enum from // include/uapi/linux/perf_event.h type ReadFormat uint64 //gendefs perf_event_read_format.PERF_FORMAT_* ReadFormat -omit-max //go:generate bitstringer -type=ReadFormat -strip=ReadFormat const ( ReadFormatTotalTimeEnabled ReadFormat = 1 << iota ReadFormatTotalTimeRunning ReadFormatID ReadFormatGroup ) // EventFlags is a bitmask of boolean properties of an event. // // This corresponds to the perf_event_attr enum from // include/uapi/linux/perf_event.h type EventFlags uint64 // TODO: gendefs (need to understand skip in the middle) //go:generate bitstringer -type=EventFlags -strip=EventFlag const ( // Event is disabled by default EventFlagDisabled EventFlags = 1 << iota // Children inherit this event EventFlagInherit // Event must always be on the PMU EventFlagPinned // Event is only group on PMU EventFlagExclusive // Don't count events in user/kernel/hypervisor/when idle EventFlagExcludeUser EventFlagExcludeKernel EventFlagExcludeHypervisor EventFlagExcludeIdle // Include mmap data EventFlagMmap // Include comm data EventFlagComm // Use frequency, not period EventFlagFreq // Per task counts EventFlagInheritStat // Next exec enables this event EventFlagEnableOnExec // Trace fork/exit EventFlagTask // WakeupWatermark is set rather than WakeupEvents. EventFlagWakeupWatermark // Skip two bits here for EventFlagPreciseIPMask // Non-exec mmap data EventFlagMmapData EventFlags = 1 << (2 + iota) // All events have SampleField fields EventFlagSampleIDAll // Don't count events in host/guest EventFlagExcludeHost EventFlagExcludeGuest // Don't include kernel/user callchains EventFlagExcludeCallchainKernel EventFlagExcludeCallchainUser // Include inode data in mmap events EventFlagMmapInodeData // Flag comm events that are due to an exec EventFlagCommExec // Use clock specified by clockid for time fields EventFlagClockID // Record context switch data. Enables RecordTypeSwitch and // RecordTypeSwitchCPUWide events. EventFlagContextSwitch // Write ring buffer from end to beginning. EventFlagWriteBackward // Include namespaces data. EventFlagNamespaces // Include ksymbol events. EventFlagKsymbol // Generate aux records instead of events. EventFlagAuxOutput // Include cgroup events. EventFlagCGroup // Include text poke events. EventFlagTextPoke // Use build ID in mmap2 events instead of inode. EventFlagBuildID // Children only inherit if cloned with CLONE_THREAD. EventFlagInheritThread // Event is removed from task on exec. EventFlagRemoveOnExec // Send synchronous SIGTRAP on event. EventFlagSigtrap eventFlagPreciseShift = 15 eventFlagPreciseMask = 0x3 << eventFlagPreciseShift ) // An EventPrecision indicates the precision of instruction pointers // recorded by an event. This can vary depending on the exact method // used to capture IPs. type EventPrecision int //go:generate stringer -type=EventPrecision const ( EventPrecisionArbitrarySkid EventPrecision = iota EventPrecisionConstantSkid EventPrecisionTryZeroSkid EventPrecisionZeroSkip ) // BranchSampleType is a bit-field of the types of branches to record // in the branch stack. // // This can include privilege levels to record, which can be different // from the privilege levels of the event being sampled. If none of // the privilege level bits are set, it defaults to the privilege // levels of the event. // // This corresponds to the perf_branch_sample_type enum from // include/uapi/linux/perf_event.h type BranchSampleType uint64 //gendefs perf_branch_sample_type.PERF_SAMPLE_BRANCH_* BranchSample BranchSampleType -omit-max //go:generate bitstringer -type=BranchSampleType -strip=BranchSample const ( BranchSampleUser BranchSampleType = 1 << iota // User branches BranchSampleKernel // Kernel branches BranchSampleHV // Hypervisor branches BranchSampleAny // Any branch types BranchSampleAnyCall // Any call branch BranchSampleAnyReturn // Any return branch BranchSampleIndCall // Indirect calls BranchSampleAbortTX // Transaction aborts BranchSampleInTX // In transaction BranchSampleNoTX // Not in transaction BranchSampleCond // Conditional branches BranchSampleCallStack // Call/ret stack BranchSampleIndJump // Indirect jumps BranchSampleCall // Direct call BranchSampleNoFlags // Don't set BranchRecord.Flags BranchSampleNoCycles // Don't set BranchRecord.Cycles BranchSampleTypeSave // Do set BranchRecord.Type BranchSampleHWIndex // Do set RecordSample.BranchHWIndex ) // perf_event_header from include/uapi/linux/perf_event.h type recordHeader struct { Type RecordType Misc recordMisc Size uint16 } // A RecordType indicates the type of a record in a profile. A record // can either be a profiling sample or give information about changes // to system state, such as a process calling mmap. type RecordType uint32 // TODO gendefs (mix of exported and unexported) //go:generate stringer -type=RecordType const ( RecordTypeMmap RecordType = 1 + iota RecordTypeLost RecordTypeComm RecordTypeExit RecordTypeThrottle RecordTypeUnthrottle RecordTypeFork RecordTypeRead RecordTypeSample recordTypeMmap2 // internal extended RecordTypeMmap RecordTypeAux RecordTypeItraceStart RecordTypeLostSamples // TODO: How does this differ from RecordTypeLost? RecordTypeSwitch RecordTypeSwitchCPUWide RecordTypeNamespaces RecordTypeKsymbol RecordTypeBPFEvent RecordTypeCGroup RecordTypeTextPoke RecordTypeAuxOutputHardwareID recordTypeUserStart RecordType = 64 ) // perf_user_event_type in tools/perf/util/event.h // // TODO: Figure out what to do with these. Some of these are only to // direct parsing so they should never escape the API. Some of these // are only for perf.data pipes. const ( recordTypeAttr RecordType = recordTypeUserStart + iota recordTypeEventType // deprecated recordTypeTracingData recordTypeBuildID recordTypeFinishedRound recordTypeIDIndex RecordTypeAuxtraceInfo // TODO RecordTypeAuxtrace RecordTypeAuxtraceError // TODO recordTypeThreadMap recordTypeCPUMap recordTypeStatConfig recordTypeStat recordTypeStatRound recordTypeEventUpdate recordTypeTimeConv recordTypeHeaderFeature ) // PERF_RECORD_MISC_* from include/uapi/linux/perf_event.h type recordMisc uint16 // TODO gendefs PERF_RECORD_MISC_* recordMisc -omit PERF_RECORD_MISC_CPUMODE_UNKNOWN -omit PERF_RECORD_MISC_KERNEL -omit PERF_RECORD_MISC_USER -omit PERF_RECORD_MISC_HYPERVISOR -omit PERF_RECORD_MISC_GUEST_KERNEL -omit PERF_RECORD_MISC_GUEST_USER // (macros) const ( recordMiscCPUModeMask recordMisc = 7 recordMiscProcMapParseTimeout = 1 << 12 // /proc/PID/maps parsing was truncated by a time-out (TODO: What record is this set on?) recordMiscMmapData = 1 << 13 // RecordTypeMmap* events recordMiscCommExec = 1 << 13 // RecordTypeComm events recordMiscForkExec = 1 << 13 // RecordTypeFork events (perf tool internal) recordMiscSwitchOut = 1 << 13 // RecordTypeSwitch* events // recordMiscExactIP applies to RecordTypeSample records. It // indicates that the sample IP points to the actual // instruction that triggered the event. recordMiscExactIP = 1 << 14 // recordMiscSwitchOutPreempt applies to RecordTypeSwitch* // records. It indicates that the thread was preempted in a // TASK_RUNNING state. recordMiscSwitchOutPreempt = 1 << 14 // recordMiscMmapBuildID applies to recordTypeMmap2 records. It // indicates that the event contain build ID data rather than inode // data. recordMiscMmapBuildID = 1 << 14 ) // Record is the common interface implemented by all profile record // types. type Record interface { Type() RecordType Common() *RecordCommon } // RecordCommon stores fields that are common to all record types, as // well as additional metadata. It is not itself a Record. // // Many fields are optional and their presence is determined by the // bitmask EventAttr.SampleFormat. Some record types guarantee that // some of these fields will be filled. type RecordCommon struct { // Offset is the byte offset of this event in the perf.data // file. Offset int64 // Format is a bit mask of SampleFormat* values that indicate // which optional fields of this record are valid. Format SampleFormat // EventAttr is the event, if any, associated with this record. EventAttr *EventAttr PID, TID int // if SampleFormatTID Time uint64 // if SampleFormatTime ID attrID // if SampleFormatID or SampleFormatIdentifier StreamID uint64 // if SampleFormatStreamID CPU, Res uint32 // if SampleFormatCPU } func (r *RecordCommon) Common() *RecordCommon { return r } // A RecordUnknown is a Record of unknown or unimplemented type. type RecordUnknown struct { recordHeader RecordCommon Data []byte } func (r *RecordUnknown) Type() RecordType { return RecordType(r.recordHeader.Type) } // A RecordMmap records when a process being profiled called mmap. // RecordMmaps can also occur at the beginning of a profile to // describe the existing memory layout. type RecordMmap struct { // RecordCommon.PID and .TID will always be filled RecordCommon Data bool // from header.misc // Addr and Len are the virtual address of the start of this // mapping and its length in bytes. Addr, Len uint64 // FileOffset is the byte offset in the mapped file of the // beginning of this mapping. FileOffset uint64 Major, Minor uint32 // if !EventFlagBuildID Ino, InoGeneration uint64 // if !EventFlagBuildID BuildID []byte // if EventFlagBuildID Prot, Flags uint32 Filename string } func (r *RecordMmap) Type() RecordType { return RecordTypeMmap } // A RecordLost records that profiling events were lost because of a // buffer overflow. type RecordLost struct { // RecordCommon.ID and .EventAttr will always be filled RecordCommon NumLost uint64 } func (r *RecordLost) Type() RecordType { return RecordTypeLost } // A RecordComm records that a process being profiled called exec. // RecordComms can also occur at the beginning of a profile to // describe the existing set of processes. type RecordComm struct { // RecordCommon.PID and .TID will always be filled RecordCommon Exec bool // from header.misc Comm string } func (r *RecordComm) Type() RecordType { return RecordTypeComm } // A RecordExit records that a process or thread exited. type RecordExit struct { // RecordCommon.PID, .TID, and .Time will always be filled RecordCommon PPID, PTID int } func (r *RecordExit) Type() RecordType { return RecordTypeExit } // A RecordThrottle records that interrupt throttling was enabled or // disabled. type RecordThrottle struct { // RecordCommon.Time, .ID, and .StreamID, and .EventAttr will // always be filled RecordCommon Enable bool } func (r *RecordThrottle) Type() RecordType { return RecordTypeThrottle } // A RecordFork records that a process called clone to either fork the // process or create a new thread. type RecordFork struct { // RecordCommon.PID, .TID, and .Time will always be filled RecordCommon PPID, PTID int } func (r *RecordFork) Type() RecordType { return RecordTypeFork } // A RecordAux records the data was added to the AUX buffer. type RecordAux struct { RecordCommon Offset, Size uint64 Flags AuxFlags PMUFormat AuxPMUFormat } func (r *RecordAux) Type() RecordType { return RecordTypeAux } // AuxFlags gives flags for an RecordAux event. type AuxFlags uint64 //TODO gendefs PERF_AUX_FLAG_* AuxFlag AuxFlags (macros) //go:generate bitstringer -type=AuxFlags -strip=AuxFlag const ( // Record was truncated to fit in the ring buffer. AuxFlagTruncated AuxFlags = 1 << iota // AUX data was collected in overwrite mode, so the AUX buffer // was treated as a circular ring buffer. AuxFlagOverwrite // Record contains gaps. AuxFlagPartial // Sample collided with another. AuxFlagCollision ) // AuxPMUFormat is the PMU specific trace format type. Values are architecture dependent. type AuxPMUFormat uint8 //go:generate stringer -type=AuxPMUFormat const ( // ARM AuxPMUFormatCoresightCoresight AuxPMUFormat = 0 // ARM Coresight format CORESIGHT. AuxPMUFormatCoresightRaw AuxPMUFormat = 1 // ARM Coresight format RAW. AuxPMUFormatDefault AuxPMUFormat = 0 ) // A RecordItraceStart indicates that an instruction trace started. type RecordItraceStart struct { // PID and TID will always be filled in. RecordCommon } func (r *RecordItraceStart) Type() RecordType { return RecordTypeItraceStart } // A RecordLostSamples records the number of dropped or lost samples. type RecordLostSamples struct { RecordCommon Lost uint64 } func (r *RecordLostSamples) Type() RecordType { return RecordTypeLostSamples } // A RecordSwitch records a context switch in or out of the monitored // process. See also RecordSwitchCPUWide. type RecordSwitch struct { RecordCommon // Out indicates this is a switch out. Otherwise, this is a // switch in. Out bool } func (r *RecordSwitch) Type() RecordType { return RecordTypeSwitch } // RecordSwitchCPUWide is a CPU-wide version of RecordSwitch. type RecordSwitchCPUWide struct { RecordCommon // Out indicates this is a switch out. Otherwise, this is a // switch in. Out bool // Preempt indicates that the preempted thread was in // TASK_RUNNING state. That is, this was an involuntary // preemption. Preempt bool // SwitchPID and SwitchTID are the PID and TID of the process // being switched in or switched out. SwitchPID, SwitchTID int } func (r *RecordSwitchCPUWide) Type() RecordType { return RecordTypeSwitchCPUWide } type RecordNamespaces struct { // PID and TID are always filled in. RecordCommon Namespaces []Namespace } func (r *RecordNamespaces) Type() RecordType { return RecordTypeNamespaces } type Namespace struct { Dev, Inode uint64 } // RecordKsymbol record kernel symbol register/unregister information, for // dynamically loaded or JITed kernel functions. type RecordKsymbol struct { RecordCommon Addr uint64 Len uint32 KsymType KsymbolType Flags KsymbolFlags Name string } func (r *RecordKsymbol) Type() RecordType { return RecordTypeKsymbol } type KsymbolType uint16 //gendefs perf_record_ksymbol_type.PERF_RECORD_KSYMBOL_TYPE_* KsymbolType -omit-max //go:generate bitstringer -type=KsymbolType -strip=KsymbolType const ( KsymbolTypeUnknown KsymbolType = iota KsymbolTypeBpf KsymbolTypeOol ) // KsymbolFlags gives flags for a RecordKsymbol event. type KsymbolFlags uint64 // TODO gendefs PERF_RECORD_KSYMBOL_FLAGS_* KsymbolFlag KsymbolFlags (macros) //go:generate bitstringer -type=KsymbolFlags -strip=KsymbolFlag const ( // Ksymbol was unregistered. KsymbolFlagUnregister KsymbolFlags = iota ) // RecordBPFEvent records BPF program load/unload information. type RecordBPFEvent struct { RecordCommon EventType BPFEventType Flags BPFEventFlags ID uint32 Tag uint64 } func (r *RecordBPFEvent) Type() RecordType { return RecordTypeBPFEvent } type BPFEventType uint16 // gendefs perf_bpf_event_type.PERF_BPF_EVENT_* BPFEventType -omit-max //go:generate bitstringer -type=BPFEventType -strip=BPFEventType const ( BPFEventTypeUnknown BPFEventType = iota BPFEventTypeProgLoad BPFEventTypeProgUnload ) type BPFEventFlags uint16 // No BPFEvent flags are defined yet. // RecordCGroup records the assosciation between a cgroup id and path. type RecordCGroup struct { RecordCommon ID uint32 Path string } func (r *RecordCGroup) Type() RecordType { return RecordTypeCGroup } // RecordTextPoke records single instruction changes to the kernel text. This // event records the address modified and the old and new code. type RecordTextPoke struct { RecordCommon Addr uint64 Old []byte New []byte } func (r *RecordTextPoke) Type() RecordType { return RecordTypeTextPoke } // RecordAuxOutputHardwareID records an archtecture-specific hardware ID // assosciated with the aux data for this event ID. // // e.g., this is used to disambiguate different PEBS event types from each // other when using PEBS-via-PT. type RecordAuxOutputHardwareID struct { RecordCommon ID uint64 } func (r *RecordAuxOutputHardwareID) Type() RecordType { return RecordTypeAuxOutputHardwareID } type RecordAuxtraceInfo struct { RecordCommon Kind uint32 Priv []uint64 } func (r *RecordAuxtraceInfo) Type() RecordType { return RecordTypeAuxtraceInfo } type RecordAuxtrace struct { // TID and CPU are always filled in. RecordCommon // Offset is the byte offset of the aux data in the aux mmap. // Not meaningful in perf data files. Offset uint64 // Ref is a unique identifier for this auxtrace block. // // TODO: What's the point of this? Is it cross-referenced // against something? Ref uint64 // Idx is the index of the aux mmap region of this data. // Not meaningful in perf data files. Idx uint32 // Data is the raw auxiliary data. The encoding of this // depends on the latest RecordAuxtraceInfo. Data []byte } func (r *RecordAuxtrace) Type() RecordType { return RecordTypeAuxtrace } // A RecordSample records a profiling sample event. // // Typically only a subset of the fields are used. Which fields are // set can be determined from the bitmask // RecordSample.EventAttr.SampleFormat. type RecordSample struct { // RecordCommon.EventAttr will always be filled. // RecordCommon.Format descibes the optional fields in this // structure, as well as the optional common fields. RecordCommon CPUMode CPUMode // from header.misc ExactIP bool // from header.misc IP uint64 // if SampleFormatIP Addr uint64 // if SampleFormatAddr // Period is the number of events on this CPU until the next // sample. In frequency sampling mode, this is adjusted // dynamically based on the rate of recent events. In period // sampling mode, this is fixed. Period uint64 // if SampleFormatPeriod // SampleRead records raw event counter values. If this is an // event group, this slice will have more than one element; // otherwise, it will have one element. SampleRead []Count // if SampleFormatRead // Callchain gives the call stack of the sampled instruction, // starting from the sampled instruction itself. The call // chain may span several types of stacks (e.g., it may start // in a kernel stack, then transition to a user stack). Before // the first IP from each stack there will be a Callchain* // constant indicating the stack type for the following IPs. Callchain []uint64 // if SampleFormatCallchain // BranchHWIndex is the low level index of the raw hardware branch // record (e.g., LBR) for BranchStack[0]. // // BranchStack is an abstraction of the raw hardware branch records, // and the index of the raw entry can be very useful for stitching the // stacks of multiple samples to reconstruct the call stack. // // The value is between -1 (unknown) and the max depth from // /sys/devices/cpu/caps/branches. BranchHWIndex int64 // if BranchSampleHWIndex BranchStack []BranchRecord // if SampleFormatBranchStack // RegsUserABI and RegsUser record the ABI and values of // user-space registers as of this sample. Note that these are // the current user-space registers even if this sample // occurred at a kernel PC. RegsUser[i] records the value of // the register indicated by the i-th set bit of // EventAttr.SampleRegsUser. RegsUserABI SampleRegsABI // if SampleFormatRegsUser RegsUser []uint64 // if SampleFormatRegsUser // RegsIntrABI And RegsIntr record the ABI and values of // registers as of this sample. Unlike RegsUser, these can be // kernel-space registers if this sample occurs in the kernel. // RegsIntr[i] records the value of the register indicated by // the i-th set bit of EventAttr.SampleRegsIntr. RegsIntrABI SampleRegsABI // if SampleFormatRegsIntr RegsIntr []uint64 // if SampleFormatRegsIntr StackUser []byte // if SampleFormatStackUser StackUserDynSize uint64 // if SampleFormatStackUser Weight uint64 // if SampleFormatWeight or SampleFormatWeightStruct Weights Weights // if SampleFormatWeightStruct DataSrc DataSrc // if SampleFormatDataSrc Transaction Transaction // if SampleFormatTransaction AbortCode uint32 // if SampleFormatTransaction PhysAddr uint64 // if SampleFormatPhysAddr CGroup uint64 // if SampleFormatCGroup DataPageSize uint64 // if SampleFormatDataPageSize CodePageSize uint64 // if SampleFormatCodePageSize Aux []byte // if SampleFormatAux } func (r *RecordSample) Type() RecordType { return RecordTypeSample } func (r *RecordSample) String() string { // TODO: Stringers for other record types f := r.Format s := fmt.Sprintf("{Offset:%v Format:%v EventAttr:%p CPUMode:%v ExactIP:%v", r.Offset, r.Format, r.EventAttr, r.CPUMode, r.ExactIP) if f&(SampleFormatID|SampleFormatIdentifier) != 0 { s += fmt.Sprintf(" ID:%d", r.ID) } if f&SampleFormatIP != 0 { s += fmt.Sprintf(" IP:%#x", r.IP) } if f&SampleFormatTID != 0 { s += fmt.Sprintf(" PID:%d TID:%d", r.PID, r.TID) } if f&SampleFormatTime != 0 { s += fmt.Sprintf(" Time:%d", r.Time) } if f&SampleFormatAddr != 0 { s += fmt.Sprintf(" Addr:%#x", r.Addr) } if f&SampleFormatStreamID != 0 { s += fmt.Sprintf(" StreamID:%d", r.StreamID) } if f&SampleFormatCPU != 0 { s += fmt.Sprintf(" CPU:%d Res:%d", r.CPU, r.Res) } if f&SampleFormatPeriod != 0 { s += fmt.Sprintf(" Period:%d", r.Period) } if f&SampleFormatRead != 0 { s += fmt.Sprintf(" SampleRead:%v", r.SampleRead) } if f&SampleFormatCallchain != 0 { s += fmt.Sprintf(" Callchain:%#x", r.Callchain) } if f&SampleFormatBranchStack != 0 { s += fmt.Sprintf(" BranchStack:%v", r.BranchStack) } if f&SampleFormatRegsUser != 0 { s += fmt.Sprintf(" RegsUserABI:%v RegsUser:%v", r.RegsUserABI, r.RegsUser) } if f&SampleFormatRegsIntr != 0 { s += fmt.Sprintf(" RegsIntrABI:%v RegsIntr:%v", r.RegsIntrABI, r.RegsIntr) } if f&SampleFormatStackUser != 0 { s += fmt.Sprintf(" StackUser:[...] StackUserDynSize:%d", r.StackUserDynSize) } if f&SampleFormatWeight != 0 { s += fmt.Sprintf(" Weight:%d", r.Weight) } if f&SampleFormatDataSrc != 0 { s += fmt.Sprintf(" DataSrc:%+v", r.DataSrc) } if f&SampleFormatTransaction != 0 { s += fmt.Sprintf(" Transaction:%v AbortCode:%d", r.Transaction, r.AbortCode) } if f&SampleFormatPhysAddr != 0 { s += fmt.Sprintf(" PhysAddr:%#x", r.PhysAddr) } if f&SampleFormatAux != 0 { s += fmt.Sprintf(" Aux:%v", r.Aux) } if f&SampleFormatCGroup != 0 { s += fmt.Sprintf(" CGroup:%d", r.CGroup) } if f&SampleFormatDataPageSize != 0 { s += fmt.Sprintf(" DataPageSize:%#x", r.DataPageSize) } if f&SampleFormatCodePageSize != 0 { s += fmt.Sprintf(" CodePageSize:%#x", r.CodePageSize) } if f&SampleFormatWeightStruct != 0 { s += fmt.Sprintf(" Weights:%v", r.Weights) } return s + "}" } // Fields returns the list of names of valid fields in r based on // r.Format. This is useful for writing custom printing functions. func (r *RecordSample) Fields() []string { f := r.Format fs := []string{"Offset", "Format", "EventAttr", "CPUMode", "ExactIP"} if f&(SampleFormatID|SampleFormatIdentifier) != 0 { fs = append(fs, "ID") } if f&SampleFormatIP != 0 { fs = append(fs, "IP") } if f&SampleFormatTID != 0 { fs = append(fs, "PID", "TID") } if f&SampleFormatTime != 0 { fs = append(fs, "Time") } if f&SampleFormatAddr != 0 { fs = append(fs, "Addr") } if f&SampleFormatStreamID != 0 { fs = append(fs, "StreamID") } if f&SampleFormatCPU != 0 { fs = append(fs, "CPU", "Res") } if f&SampleFormatPeriod != 0 { fs = append(fs, "Period") } if f&SampleFormatRead != 0 { fs = append(fs, "SampleRead") } if f&SampleFormatCallchain != 0 { fs = append(fs, "Callchain") } if f&SampleFormatBranchStack != 0 { fs = append(fs, "BranchStack") } if f&SampleFormatRegsUser != 0 { fs = append(fs, "RegsUserABI", "RegsUser") } if f&SampleFormatRegsIntr != 0 { fs = append(fs, "RegsIntrABI", "RegsIntr") } if f&SampleFormatStackUser != 0 { fs = append(fs, "StackUser", "StackUserDynSize") } if f&SampleFormatWeight != 0 { fs = append(fs, "Weight") } if f&SampleFormatDataSrc != 0 { fs = append(fs, "DataSrc") } if f&SampleFormatTransaction != 0 { fs = append(fs, "Transaction", "AbortCode") } if f&SampleFormatPhysAddr != 0 { fs = append(fs, "PhysAddr") } if f&SampleFormatAux != 0 { fs = append(fs, "Aux") } if f&SampleFormatCGroup != 0 { fs = append(fs, "CGroup") } if f&SampleFormatDataPageSize != 0 { fs = append(fs, "DataPageSize") } if f&SampleFormatCodePageSize != 0 { fs = append(fs, "CodePageSize") } if f&SampleFormatWeightStruct != 0 { fs = append(fs, "Weights") } return fs } // A CPUMode indicates the privilege level of a sample or event. // // This corresponds to PERF_RECORD_MISC_CPUMODE from // include/uapi/linux/perf_event.h type CPUMode uint16 // TODO: gendefs (need to extract from PERF_RECORD_MISC_* flags) //go:generate stringer -type=CPUMode const ( CPUModeUnknown CPUMode = iota CPUModeKernel CPUModeUser CPUModeHypervisor CPUModeGuestKernel CPUModeGuestUser ) // A Count records the raw value of an event counter. // // Typically only a subset of the fields are used. Which fields are // set can be determined from the bitmask in the sample's // EventAttr.ReadFormat. // // This corresponds to perf_event_read_format from // include/uapi/linux/perf_event.h type Count struct { Value uint64 // Event counter value TimeEnabled uint64 // if ReadFormatTotalTimeEnabled TimeRunning uint64 // if ReadFormatTotalTimeRunning EventAttr *EventAttr // if ReadFormatID } // A BranchRecord records a single branching event in a sample. type BranchRecord struct { From, To uint64 Flags BranchFlags Cycles uint16 // Cycle count to last branch (or 0) // Type is the type of branch instruction that caused this // branch. If supported, this is set by the kernel by // disassembling the branch instruction, since the binary // itself may not be available at decoding time. This is only // set if EventAttr.BranchSampleType&BranchSampleTypeSave is // set in the event. Type BranchType } type BranchFlags uint64 //go:generate bitstringer -type=BranchFlags -strip=BranchFlag const ( // BranchFlagMispredicted indicates branch target was mispredicted. BranchFlagMispredicted BranchFlags = 1 << iota // BranchFlagPredicted indicates branch target was predicted. // In case predicted/mispredicted information is unavailable, // both flags will be unset. BranchFlagPredicted // BranchFlagInTransaction indicates the branch occurred in a // transaction. BranchFlagInTransaction // BranchFlagAbort indicates the branch is a transaction abort. BranchFlagAbort ) type BranchType uint8 //gendefs PERF_BR_* BranchType -omit-max const ( BranchTypeUnknown BranchType = iota // unknown BranchTypeCond // conditional BranchTypeUncond // unconditional BranchTypeInd // indirect BranchTypeCall // function call BranchTypeIndCall // indirect function call BranchTypeRet // function return BranchTypeSyscall // syscall BranchTypeSysret // syscall return BranchTypeCondCall // conditional function call BranchTypeCondRet // conditional function return BranchTypeEret // exception return BranchTypeIrq // interrupt ) //gendefs perf_callchain_context.PERF_CONTEXT_* Callchain uint64 -omit-max // Special markers used in RecordSample.Callchain to mark boundaries // between types of stacks. // // These correspond to PERF_CONTEXT_* from // include/uapi/linux/perf_event.h const ( CallchainHV uint64 = 0xffffffffffffffe0 // -32 CallchainKernel = 0xffffffffffffff80 // -128 CallchainUser = 0xfffffffffffffe00 // -512 CallchainGuest = 0xfffffffffffff800 // -2048 CallchainGuestKernel = 0xfffffffffffff780 // -2176 CallchainGuestUser = 0xfffffffffffff600 // -2560 ) // SampleRegsABI indicates the register ABI of a given sample for // architectures that support multiple ABIs. // // This corresponds to the perf_sample_regs_abi enum from // include/uapi/linux/perf_event.h type SampleRegsABI uint64 //gendefs perf_sample_regs_abi.PERF_SAMPLE_REGS_ABI_* SampleRegsABI //go:generate stringer -type=SampleRegsABI const ( SampleRegsABINone SampleRegsABI = iota SampleRegsABI32 SampleRegsABI64 ) type DataSrc struct { Op DataSrcOp Miss bool // if true, Level specifies miss, rather than hit Level DataSrcLevel Snoop DataSrcSnoop Locked DataSrcLock TLB DataSrcTLB LevelNum DataSrcLevelNum Remote bool Block DataSrcBlock Hops DataSrcHops } type DataSrcOp int //go:generate bitstringer -type=DataSrcOp -strip=DataSrcOp const ( DataSrcOpLoad DataSrcOp = 1 << iota DataSrcOpStore DataSrcOpPrefetch DataSrcOpExec DataSrcOpNA DataSrcOp = 0 ) type DataSrcLevel int //go:generate bitstringer -type=DataSrcLevel -strip=DataSrcLevel const ( DataSrcLevelL1 DataSrcLevel = 1 << iota DataSrcLevelLFB // Line fill buffer DataSrcLevelL2 DataSrcLevelL3 DataSrcLevelLocalRAM // Local DRAM DataSrcLevelRemoteRAM1 // Remote DRAM (1 hop) DataSrcLevelRemoteRAM2 // Remote DRAM (2 hops) DataSrcLevelRemoteCache1 // Remote cache (1 hop) DataSrcLevelRemoteCache2 // Remote cache (2 hops) DataSrcLevelIO // I/O memory DataSrcLevelUncached DataSrcLevelNA DataSrcLevel = 0 ) type DataSrcSnoop int //go:generate bitstringer -type=DataSrcSnoop -strip=DataSrcSnoop const ( DataSrcSnoopNone DataSrcSnoop = 1 << iota DataSrcSnoopHit DataSrcSnoopMiss DataSrcSnoopHitM // Snoop hit modified DataSrcSnoopFwd DataSrcSnoopNA DataSrcSnoop = 0 ) type DataSrcLock int //go:generate stringer -type=DataSrcLock const ( DataSrcLockNA DataSrcLock = iota DataSrcLockUnlocked DataSrcLockLocked ) type DataSrcTLB int //go:generate bitstringer -type=DataSrcTLB -strip=DataSrcTLB const ( DataSrcTLBHit DataSrcTLB = 1 << iota DataSrcTLBMiss DataSrcTLBL1 DataSrcTLBL2 DataSrcTLBHardwareWalker DataSrcTLBOSFaultHandler DataSrcTLBNA DataSrcTLB = 0 ) type DataSrcLevelNum int // TODO gendefs (macros) //go:generate stringer -type=DataSrcLevelNum const ( DataSrcLevelNumL1 DataSrcLevelNum = 0x01 // L1 DataSrcLevelNumL2 DataSrcLevelNum = 0x02 // L2 DataSrcLevelNumL3 DataSrcLevelNum = 0x03 // L3 DataSrcLevelNumL4 DataSrcLevelNum = 0x04 // L4 DataSrcLevelNumAnyCache DataSrcLevelNum = 0x0b // Any cache DataSrcLevelNumLFB DataSrcLevelNum = 0x0c // LFB DataSrcLevelNumRAM DataSrcLevelNum = 0x0d // RAM DataSrcLevelNumPMEM DataSrcLevelNum = 0x0e // PMEM DataSrcLevelNumNA DataSrcLevelNum = 0x0f // N/A ) type DataSrcBlock int //go:generate bitstringer -type=DataSrcBlock -strip=DataSrcBlock const ( DataSrcBlockData DataSrcBlock = 1 << iota // Data could not be forwarded DataSrcBlockAddr // Address conflict DataSrcBlockNA DataSrcBlock = 0 ) type DataSrcHops int //go:generate stringer -type=DataSrcHops const ( DataSrcHopsCore DataSrcHops = 1 // Remote core, same node DataSrcHopsNode DataSrcHops = 3 // Remote node, same socket DataSrcHopsSocket DataSrcHops = 3 // Remote socket, same board DataSrcHopesBoard DataSrcHops = 4 // Remote board DataSrcHopsNA DataSrcHops = 0 ) type Transaction int // TODO: Handle abort code mask //gendefs PERF_TXN_* Transaction -omit-max -omit PERF_TXN_ABORT_MASK -omit PERF_TXN_ABORT_SHIFT //go:generate bitstringer -type=Transaction -strip=Transaction const ( TransactionElision Transaction = 1 << iota // From elision TransactionTransaction // From transaction TransactionSync // Instruction is related TransactionAsync // Instruction is not related TransactionRetry // Retry possible TransactionConflict // Conflict abort TransactionCapacityWrite // Capactiy write abort TransactionCapacityRead // Capactiy read abort ) type Weights struct { Var1 uint32 Var2 uint16 Var3 uint16 } ================================================ FILE: perffile/gendefs.sh ================================================ #!/bin/sh set -e if [ "$1" = -u ]; then update=1 shift fi if [ "$#" != 1 ]; then echo "Usage: $0 [-u] " 2>&1 exit 2 fi linux="$1" go build ../internal/gendefs process() { ./gendefs -ccflags "-I $linux" $1 > .$1.tmp if [ -z "$update" ]; then diff -u $1 .$1.tmp || true rm .$1.tmp else mv .$1.tmp $1 fi } process events.go process format.go rm gendefs ================================================ FILE: perffile/ksymbolflags_string.go ================================================ // Code generated by "bitstringer -type=KsymbolFlags"; DO NOT EDIT package perffile import "strconv" func (i KsymbolFlags) String() string { if i == 0 { return "Unregister" } s := "" i &^= 0 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/ksymboltype_string.go ================================================ // Code generated by "bitstringer -type=KsymbolType"; DO NOT EDIT package perffile import "strconv" func (i KsymbolType) String() string { if i == 0 { return "Unknown" } s := "" if i&KsymbolTypeBpf != 0 { s += "Bpf|" } if i&KsymbolTypeOol != 0 { s += "Ool|" } i &^= 3 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/meta.go ================================================ // Copyright 2015 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 perffile import ( "encoding/binary" "fmt" "io" "reflect" ) type FileMeta struct { // BuildIDs is the list of build IDs for processes and // libraries in this profile, or nil if unknown. Note that in // "live mode" (e.g., a file written by perf inject), it's // possible for build IDs to be introduced in the sample // stream itself. BuildIDs []BuildIDInfo // Hostname is the hostname of the machine that recorded this // profile, or "" if unknown. Hostname string // OSRelease is the OS release of the machine that recorded // this profile such as "3.13.0-62", or "" if unknown. OSRelease string // Version is the perf version that recorded this profile such // as "3.13.11", or "" if unknown. Version string // Arch is the host architecture of the machine that recorded // this profile such as "x86_64", or "" if unknown. Arch string // CPUsOnline and CPUsAvail are the number of online and // available CPUs of the machine that recorded this profile, // or 0, 0 if unknown. CPUsOnline, CPUsAvail int // CPUDesc describes the CPU of the machine that recorded this // profile such as "Intel(R) Core(TM) i7-4600U CPU @ 2.10GHz", // or "" if unknown. CPUDesc string // CPUID describes the CPU type of the machine that recorded // this profile, or "" if unknown. The exact format of this // varies between architectures. On x86 architectures, it is a // comma-separated list of vendor, family, model, and // stepping, such as "GenuineIntel,6,69,1". CPUID string // TotalMem is the total memory in bytes of the machine that // recorded this profile, or 0 if unknown. TotalMem int64 // CmdLine is the list of command line arguments perf was // invoked with, or nil if unknown. CmdLine []string // CoreGroups and ThreadGroups describe the CPU topology of // the machine that recorded this profile. Each CPUSet in // CoreGroups is a set of CPUs in the same package, and each // CPUSet in ThreadGroups is a set of hardware threads in the // same core. These will be nil if unkneon. CoreGroups, ThreadGroups []CPUSet // NUMANodes is the set of NUMA nodes in the NUMA topology of // the machine that recorded this profile, or nil if unknown. NUMANodes []NUMANode // PMUMappings is a map from numerical PMUTypeID to name for // event classes supported by the machine that recorded this // profile, or nil if unknown. PMUMappings map[PMUTypeID]string // Groups is the descriptions of each perf event group in this // profile, or nil if unknown. Groups []GroupDesc } // A BuildIDInfo records the mapping between a single build ID and the // path of an executable with that build ID. type BuildIDInfo struct { CPUMode CPUMode PID int // Usually -1; for VM kernels BuildID BuildID Filename string } type BuildID []byte func (b BuildID) String() string { return fmt.Sprintf("%x", []byte(b)) } // A NUMANode represents a single hardware NUMA node. type NUMANode struct { // Node is the system identifier of this NUMA node. Node int // MemTotal and MemFree are the total and free number of bytes // of memory in this NUMA node. MemTotal, MemFree int64 // CPUs is the set of CPUs in this NUMA node. CPUs CPUSet } // A GroupDesc describes a group of PMU events that are scheduled // together. // // TODO: Are Leader and NumMembers attribute IDs? If so, we should // probably map them to *EventAttrs to make this useful. type GroupDesc struct { Name string Leader int NumMembers int } var featureParsers = map[feature]func(*FileMeta, bufDecoder) error{ featureBuildID: (*FileMeta).parseBuildID, featureHostname: stringFeature("Hostname"), featureOSRelease: stringFeature("OSRelease"), featureVersion: stringFeature("Version"), featureArch: stringFeature("Arch"), featureNrCpus: (*FileMeta).parseNrCPUs, featureCPUDesc: stringFeature("CPUDesc"), featureCPUID: stringFeature("CPUID"), featureTotalMem: (*FileMeta).parseTotalMem, featureCmdline: (*FileMeta).parseCmdLine, featureCPUTopology: (*FileMeta).parseCPUTopology, featureNUMATopology: (*FileMeta).parseNUMATopology, featurePMUMappings: (*FileMeta).parsePMUMappings, featureGroupDesc: (*FileMeta).parseGroupDesc, } func (m *FileMeta) parse(f feature, sec fileSection, r io.ReaderAt) error { parser := featureParsers[f] if parser == nil { return nil } // Load the section. data, err := sec.data(r) if err != nil { return err } bd := bufDecoder{data, binary.LittleEndian} // Parse the section. return parser(m, bd) } func stringFeature(name string) func(*FileMeta, bufDecoder) error { return func(m *FileMeta, bd bufDecoder) error { bd.u32() // Ignore length; string is \0-terminated str := bd.cstring() reflect.ValueOf(m).Elem().FieldByName(name).SetString(str) return nil } } func (m *FileMeta) parseBuildID(bd bufDecoder) error { m.BuildIDs = make([]BuildIDInfo, 0) for len(bd.buf) > 0 { var bid BuildIDInfo start := bd.buf // This starts with a recordHeader. _ = bd.u32() // type, unused bid.CPUMode = CPUMode(bd.u16() & uint16(recordMiscCPUModeMask)) size := bd.u16() bid.PID = int(bd.i32()) // The build ID is 20 bytes, but padded to 8 bytes. buildID := make([]byte, 24) bd.bytes(buildID) bid.BuildID = BuildID(buildID[:20]) bid.Filename = bd.cstring() m.BuildIDs = append(m.BuildIDs, bid) bd.buf = start[size:] } return nil } func (m *FileMeta) parseNrCPUs(bd bufDecoder) error { m.CPUsOnline, m.CPUsAvail = int(bd.u32()), int(bd.u32()) return nil } func (m *FileMeta) parseTotalMem(bd bufDecoder) error { m.TotalMem = int64(bd.u64()) * 1024 return nil } func (m *FileMeta) parseCmdLine(bd bufDecoder) error { m.CmdLine = bd.stringList() return nil } // TODO: Implement featureEventDesc. This isn't useful unless we also // expose attribute IDs or something to make it possible to match up // the event descriptions with the samples. Probably we should hide // this as a feature section and just expose the set of events in the // file, augmented with the string names from this section if // available. As far as I can tell, the string name is the *only* // thing this section adds over the EventAttrs in the file header. func (m *FileMeta) parseCPUTopology(bd bufDecoder) error { var err error cores, threads := bd.stringList(), bd.stringList() m.CoreGroups = make([]CPUSet, len(cores)) for i, str := range cores { m.CoreGroups[i], err = parseCPUSet(str) if err != nil { return err } } m.ThreadGroups = make([]CPUSet, len(threads)) for i, str := range threads { m.ThreadGroups[i], err = parseCPUSet(str) if err != nil { return err } } return nil } func (m *FileMeta) parseNUMATopology(bd bufDecoder) error { var err error count := bd.u32() m.NUMANodes = []NUMANode{} for i := uint32(0); i < count; i++ { node := NUMANode{ Node: int(bd.u32()), MemTotal: int64(bd.u64()) * 1024, MemFree: int64(bd.u64()) * 1024, } node.CPUs, err = parseCPUSet(bd.lenString()) if err != nil { return err } m.NUMANodes = append(m.NUMANodes, node) } return nil } func (m *FileMeta) parsePMUMappings(bd bufDecoder) error { count := bd.u32() m.PMUMappings = map[PMUTypeID]string{} for i := uint32(0); i < count; i++ { m.PMUMappings[PMUTypeID(bd.u32())] = bd.lenString() } return nil } func (m *FileMeta) parseGroupDesc(bd bufDecoder) error { count := bd.u32() m.Groups = []GroupDesc{} for i := uint32(0); i < count; i++ { m.Groups = append(m.Groups, GroupDesc{ Name: bd.lenString(), Leader: int(bd.u32()), NumMembers: int(bd.u32()), }) } return nil } ================================================ FILE: perffile/package.go ================================================ // Copyright 2015 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 perffile is a parser for Linux perf.data profiles. // // Parsing a perf.data profile starts with a call to New or Open to // open a perf.data file. A perf.data file consists of a sequence of // records, which can be retrieved with File.Records, as well as // several metadata fields, which can be retrieved with other methods // of File. package perffile // import "github.com/aclements/go-perf/perffile" ================================================ FILE: perffile/reader.go ================================================ // Copyright 2015 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 perffile import ( "encoding/binary" "fmt" "io" "os" "reflect" "sort" ) // TODO: Type for file format errors. // A File is a perf.data file. It consists of a sequence of records, // which can be retrieved with the Records method, as well as several // optional metadata fields. type File struct { // Meta contains the metadata for this profile, such as // information about the hardware. Meta FileMeta // Events lists all events that may appear in this profile. Events []*EventAttr r io.ReaderAt closer io.Closer hdr fileHeader attrs []fileAttr idToAttr map[attrID]*EventAttr sampleIDOffset int // byte offset of AttrID in sample sampleIDAll bool // non-samples have sample_id trailer recordIDOffset int // byte offset of AttrID in non-sample, from end } // New reads a "perf.data" file from r. // // The caller must keep r open as long as it is using the returned // *File. func New(r io.ReaderAt) (*File, error) { // See perf_session__open in tools/perf/util/session.c. file := &File{r: r, Events: make([]*EventAttr, 0)} // Read and process the file header. // // See perf_session__read_header in tools/perf/util/header.c sr := io.NewSectionReader(r, 0, 1024) if err := binary.Read(sr, binary.LittleEndian, &file.hdr); err != nil { return nil, err } switch string(file.hdr.Magic[:]) { case "PERFILE2": // Version 2, little endian. break case "2ELIFREP": // Version 2, big endian. // // TODO: Support big endian profiles. return nil, fmt.Errorf("big endian profiles not supported") case "PERFFILE": // Version 1 file. return nil, fmt.Errorf("version 1 profiles not supported") default: return nil, fmt.Errorf("bad or unsupported file magic %q", string(file.hdr.Magic[:])) } if file.hdr.Size != uint64(binary.Size(&file.hdr)) { return nil, fmt.Errorf("bad header size %d", file.hdr.Size) } // hdr.Data.Size is the last thing written out by perf, so if // it's zero, we're working with a partial file. if file.hdr.Data.Size == 0 { return nil, fmt.Errorf("truncated data file; was 'perf record' properly terminated?") } // Read EventAttrs. Note that the attr size is represented in // both the file header and in each individual attr, but perf // doesn't validate the file-level attr size. if file.hdr.AttrSize == 0 { return nil, fmt.Errorf("bad attr size 0") } nAttrs := int(file.hdr.Attrs.Size / file.hdr.AttrSize) if nAttrs == 0 { return nil, fmt.Errorf("no event types") } else if nAttrs > 64*1024 { return nil, fmt.Errorf("too many attrs or bad attr size") } file.attrs = make([]fileAttr, nAttrs) attrSR := file.hdr.Attrs.sectionReader(r) for i := 0; i < nAttrs; i++ { if err := readFileAttr(attrSR, &file.attrs[i]); err != nil { return nil, err } file.Events = append(file.Events, &file.attrs[i].Attr) } // Read EventAttr IDs and create ID -> EventAttr map file.idToAttr = make(map[attrID]*EventAttr) for _, attr := range file.attrs { var ids []attrID if err := readSlice(attr.IDs.sectionReader(r), &ids); err != nil { return nil, err } for _, id := range ids { file.idToAttr[id] = &attr.Attr } } // Check that sample formats are consistent across all event // types and record cross-event sample format information. firstEvent := &file.attrs[0].Attr file.sampleIDOffset = firstEvent.SampleFormat.sampleIDOffset() file.recordIDOffset = firstEvent.SampleFormat.recordIDOffset() file.sampleIDAll = firstEvent.Flags&EventFlagSampleIDAll != 0 if len(file.attrs) > 1 { if len(file.idToAttr) == 0 { return nil, fmt.Errorf("file has multiple EventAttrs, but no IDs") } for _, attr := range file.attrs { // See perf_evlist__valid_sample_type. x := attr.Attr.SampleFormat.sampleIDOffset() if x == -1 { return nil, fmt.Errorf("multiple events, but samples have no event ID field") } else if file.sampleIDOffset != x { return nil, fmt.Errorf("events have incompatible ID offsets %d and %d", file.sampleIDOffset, x) } x = attr.Attr.SampleFormat.recordIDOffset() if x == -1 { return nil, fmt.Errorf("multiple events, but records have no event ID field") } else if file.recordIDOffset != x { return nil, fmt.Errorf("records have incompatible ID offsets %d and %d", file.recordIDOffset, x) } // See perf_evlist__valid_sample_id_all. idAll := attr.Attr.Flags&EventFlagSampleIDAll != 0 if file.sampleIDAll != idAll { return nil, fmt.Errorf("events have incompatible SampleIDAll flags") } // See perf_evlist__valid_read_format. if firstEvent.ReadFormat != attr.Attr.ReadFormat { return nil, fmt.Errorf("events have incompatible read formats") } } if firstEvent.SampleFormat&SampleFormatRead != 0 && firstEvent.ReadFormat&ReadFormatID == 0 { return nil, fmt.Errorf("bad event read format") } } // Load feature sections. sr = io.NewSectionReader(r, int64(file.hdr.Data.Offset+file.hdr.Data.Size), int64(numFeatureBits*binary.Size(fileSection{}))) for bit := feature(0); bit < feature(numFeatureBits); bit++ { if !file.hdr.hasFeature(bit) { continue } sec := fileSection{} if err := binary.Read(sr, binary.LittleEndian, &sec); err != nil { return nil, err } file.Meta.parse(bit, sec, file.r) } return file, nil } // Open opens the named "perf.data" file using os.Open. // // The caller must call f.Close() on the returned file when it is // done. func Open(name string) (*File, error) { f, err := os.Open(name) if err != nil { return nil, err } ff, err := New(f) if err != nil { f.Close() return nil, err } ff.closer = f return ff, nil } func readFileAttr(sr *io.SectionReader, fa *fileAttr) error { // See read_attr in tools/perf/util/header.c. // Read the common prefix of all event attr versions. var attr eventAttrVN if err := binary.Read(sr, binary.LittleEndian, &attr.eventAttrV0); err != nil { return err } if attr.Size == 0 { // Assume ABI v0 attr.Size = 64 } else if attr.Size > uint32(binary.Size(&attr)) { return fmt.Errorf("event attr size %d too large; more recent and unsupported format", attr.Size) } else { // Read whatever's left. There are specific versions // of this structure, but perf doesn't try to // distinguish them, so neither do we. left := int(attr.Size) - binary.Size(&attr.eventAttrV0) rattr := reflect.ValueOf(&attr).Elem() for i := 1; i < rattr.NumField() && left > 0; i++ { field := rattr.Field(i).Addr().Interface() err := binary.Read(sr, binary.LittleEndian, field) if err != nil { return err } left -= binary.Size(field) } } // Convert on-disk perf_event_attr in to EventAttr. var ev EventGeneric ev.Type = attr.Type ev.ID = attr.Config if attr.Flags&EventFlagFreq == 0 { fa.Attr.SamplePeriod = attr.SamplePeriodOrFreq } else { fa.Attr.SampleFreq = attr.SamplePeriodOrFreq } fa.Attr.SampleFormat = attr.SampleFormat fa.Attr.ReadFormat = attr.ReadFormat fa.Attr.Flags = attr.Flags &^ eventFlagPreciseMask fa.Attr.Precise = EventPrecision((attr.Flags & eventFlagPreciseMask) >> eventFlagPreciseShift) if attr.Flags&EventFlagWakeupWatermark == 0 { fa.Attr.WakeupEvents = attr.WakeupEventsOrWatermark } else { fa.Attr.WakeupWatermark = attr.WakeupEventsOrWatermark } if attr.Type == EventTypeBreakpoint { // For EventTypeBreakpoint, attr.Config is 0 and the // breakpoint type is described in BPType. We merge // the two. ev.ID = uint64(attr.BPType) } ev.Config = make([]uint64, 2) ev.Config[0] = attr.BPAddrOrConfig1 ev.Config[1] = attr.BPLenOrConfig2 fa.Attr.SampleRegsUser = attr.SampleRegsUser fa.Attr.SampleStackUser = attr.SampleStackUser fa.Attr.AuxWatermark = attr.AuxWatermark fa.Attr.SampleMaxStack = attr.SampleMaxStack fa.Attr.Event = ev.Decode() // Finally, read IDs fileSection, which follows the eventAttr. return binary.Read(sr, binary.LittleEndian, &fa.IDs) } // Close closes the File. // // If the File was created using New directly instead of Open, Close // has no effect. func (f *File) Close() error { var err error if f.closer != nil { err = f.closer.Close() f.closer = nil } return err } // readSlice reads an entire section into a slice. v must be a // pointer to a slice; the slice itself may be nil. The section size // must be an exact multiple of the size of the element type of v. func readSlice(sr *io.SectionReader, v interface{}) error { // Figure out slice value size vt := reflect.TypeOf(v) if vt.Kind() != reflect.Ptr || vt.Elem().Kind() != reflect.Slice { panic("v must be a pointer to a slice") } et := vt.Elem().Elem() esize := binary.Size(reflect.Zero(et).Interface()) nelem := int(sr.Size() / int64(esize)) if sr.Size()%int64(esize) != 0 { return fmt.Errorf("section size %d is not a multiple of element size %d", sr.Size(), esize) } // Create slice reflect.ValueOf(v).Elem().Set(reflect.MakeSlice(vt.Elem(), nelem, nelem)) // Read in to slice return binary.Read(sr, binary.LittleEndian, v) } //go:generate stringer -type=RecordsOrder type RecordsOrder int const ( // RecordsFileOrder requests records in file order. This is // efficient because it allows streaming the records directly // from the file, but the records may not be in time-stamp or // even causal order. RecordsFileOrder RecordsOrder = iota // RecordsCausalOrder requests records in causal order. This // is weakly time-ordered: any two records will be in // time-stamp order *unless* those records are both // RecordSamples. This is potentially more efficient than // RecordsTimeOrder, though currently the implementation does // not distinguish. RecordsCausalOrder // RecordsTimeOrder requests records in time-stamp order. This // is the most expensive iteration order because it requires // buffering and/or re-reading potentially large sections of // the input file in order to sort the records. RecordsTimeOrder ) // Records returns an iterator over the records in the profile. The // order argument specifies the order for iterating through the // records in this File. Callers should choose the least // resource-intensive iteration order that satisfies their needs. func (f *File) Records(order RecordsOrder) *Records { if order == RecordsCausalOrder || order == RecordsTimeOrder { // Sort the records by making two passes: first record // the offsets and time-stamps of all records, then // sort this by time-stamp and re-read in the new // offset order. // // See process_finished_round in session.c for how // perf does this. process_finished_round uses a // special flush event; however, I've never actually // observed in a perf.data file, so I think perf may // be reading and sorting the whole file looking for a // flush. // TODO: Optimize the first pass to decode only the // record length and time-stamp. // TODO: Optimize IO on the second pass by keeping // track of the non-monotonic boundaries and // performing separately buffered reads of each // sub-stream. rs := f.Records(RecordsFileOrder) pos, ts := make([]int64, 0), make([]uint64, 0) for rs.Next() { c := rs.Record.Common() pos = append(pos, c.Offset) ts = append(ts, c.Time) } if rs.Err() != nil { return &Records{err: rs.Err()} } sort.Stable(&timeSorter{pos, ts}) return &Records{f: f, sr: newBufferedSectionReader(f.hdr.Data.sectionReader(f.r)), order: pos} } return &Records{f: f, sr: newBufferedSectionReader(f.hdr.Data.sectionReader(f.r))} } type timeSorter struct { pos []int64 ts []uint64 } func (s *timeSorter) Len() int { return len(s.pos) } func (s *timeSorter) Less(i, j int) bool { return s.ts[i] < s.ts[j] } func (s *timeSorter) Swap(i, j int) { s.pos[i], s.pos[j] = s.pos[j], s.pos[i] s.ts[i], s.ts[j] = s.ts[j], s.ts[i] } ================================================ FILE: perffile/readformat_string.go ================================================ // Code generated by "bitstringer -type=ReadFormat"; DO NOT EDIT package perffile import "strconv" func (i ReadFormat) String() string { if i == 0 { return "0" } s := "" if i&ReadFormatGroup != 0 { s += "Group|" } if i&ReadFormatID != 0 { s += "ID|" } if i&ReadFormatTotalTimeEnabled != 0 { s += "TotalTimeEnabled|" } if i&ReadFormatTotalTimeRunning != 0 { s += "TotalTimeRunning|" } i &^= 15 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/records.go ================================================ // Copyright 2015 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 perffile import ( "encoding/binary" "fmt" "io" ) // A Records is an iterator over the records in a "perf.data" file. // Each record will be one of the Record* types. // // Typical usage is // // rs := file.Records() // for rs.Next() { // switch r := rs.Record.(type) { // case *perffile.RecordSample: // ... // } // } // if rs.Err() { ... } type Records struct { // The current record. The concrete type of this will be one // of the Record* types. Determine which type of record this // is using a type switch. Record Record f *File sr *bufferedSectionReader // or *io.SectionReader err error // order specifies the seek order to read records in. If nil, // records are read in file order until EOF. If non-nil, // records are read in this order. order []int64 // Read buffer. Reused (and resized) by Next. buf []byte // Cache for common record types recordMmap RecordMmap recordComm RecordComm recordExit RecordExit recordFork RecordFork recordSample RecordSample recordAux RecordAux recordSwitch RecordSwitch recordSwitchCPUWide RecordSwitchCPUWide } // Err returns the first error encountered by Records. func (r *Records) Err() error { return r.err } // Next fetches the next record into r.Record. It returns true if // successful, and false if it reaches the end of the record stream or // encounters an error. // // The record stored in r.Record may be reused by later invocations of // Next, so if the caller may need the record after another call to // Next, it must make its own copy. func (r *Records) Next() bool { // See perf_evsel__parse_sample if r.err != nil { return false } if r.order != nil { if len(r.order) == 0 { return false } pos := r.order[0] r.order = r.order[1:] _, r.err = r.sr.Seek(pos-int64(r.f.hdr.Data.Offset), 0) if r.err != nil { return false } } var common RecordCommon offset, _ := r.sr.Seek(0, 1) common.Offset = offset + int64(r.f.hdr.Data.Offset) // Read record header var hdr recordHeader if err := binary.Read(r.sr, binary.LittleEndian, &hdr); err != nil { if err != io.EOF { r.err = err } return false } // Read record data rlen := int(hdr.Size - 8) if rlen > len(r.buf) { r.buf = make([]byte, rlen) } var bd = &bufDecoder{r.buf[:rlen], binary.LittleEndian} if _, err := io.ReadFull(r.sr, bd.buf); err != nil { r.err = err return false } // Parse common sample_id fields if r.f.sampleIDAll && hdr.Type != RecordTypeSample && hdr.Type < recordTypeUserStart { // mmap records in the prologue don't have eventAttrs // in recent perf versions, but that's okay. // // TODO: When is perf okay with missing eventAttrs? r.parseCommon(bd, &common, hdr.Type == RecordTypeMmap) } // Parse record // TODO: Don't array out-of-bounds on short records switch hdr.Type { default: // As far as I can tell, RecordTypeRead can never // appear in a perf.data file. r.Record = &RecordUnknown{hdr, common, bd.buf} case RecordTypeMmap: r.Record = r.parseMmap(bd, &hdr, &common, false) case RecordTypeLost: r.Record = r.parseLost(bd, &hdr, &common) case RecordTypeComm: r.Record = r.parseComm(bd, &hdr, &common) case RecordTypeExit: r.Record = r.parseExit(bd, &hdr, &common) case RecordTypeThrottle: r.Record = r.parseThrottle(bd, &hdr, &common, true) case RecordTypeUnthrottle: r.Record = r.parseThrottle(bd, &hdr, &common, false) case RecordTypeFork: r.Record = r.parseFork(bd, &hdr, &common) case RecordTypeSample: r.Record = r.parseSample(bd, &hdr, &common) case recordTypeMmap2: r.Record = r.parseMmap(bd, &hdr, &common, true) case RecordTypeAux: r.Record = r.parseAux(bd, &hdr, &common) case RecordTypeItraceStart: r.Record = r.parseItraceStart(bd, &hdr, &common) case RecordTypeLostSamples: r.Record = r.parseLostSamples(bd, &hdr, &common) case RecordTypeSwitch: r.Record = r.parseSwitch(bd, &hdr, &common) case RecordTypeSwitchCPUWide: r.Record = r.parseSwitchCPUWide(bd, &hdr, &common) case RecordTypeNamespaces: r.Record = r.parseNamespaces(bd, &hdr, &common) case RecordTypeKsymbol: r.Record = r.parseKsymbol(bd, &hdr, &common) case RecordTypeBPFEvent: r.Record = r.parseBPFEvent(bd, &hdr, &common) case RecordTypeCGroup: r.Record = r.parseCGroup(bd, &hdr, &common) case RecordTypeTextPoke: r.Record = r.parseTextPoke(bd, &hdr, &common) case RecordTypeAuxOutputHardwareID: r.Record = r.parseAuxOutputHardwareID(bd, &hdr, &common) case RecordTypeAuxtraceInfo: r.Record = r.parseAuxtraceInfo(bd, &hdr, &common) case RecordTypeAuxtrace: // Note: This appears to be the only record type that // has additional payload data following it that isn't // included in the header size. r.Record = r.parseAuxtrace(bd, &hdr, &common) } if r.err != nil { return false } return true } func (r *Records) getAttr(id attrID, nilOk bool) *EventAttr { // See perf_evlist__id2evsel in tools/perf/util/evlist.c. // If there's only one event, all records implicitly use it. if len(r.f.attrs) == 1 || id == 0 { return &r.f.attrs[0].Attr } // Otherwise, look up the event by ID. if attr, ok := r.f.idToAttr[id]; ok { return attr } if !nilOk { r.err = fmt.Errorf("event has unknown eventAttr ID %d", id) } return nil } // parseCommon parses the common sample_id structure in the trailer of // non-sample records. func (r *Records) parseCommon(bd *bufDecoder, o *RecordCommon, missingOk bool) bool { // Get EventAttr ID if r.f.recordIDOffset == -1 { o.ID = 0 } else { o.ID = attrID(bd.order.Uint64(bd.buf[len(bd.buf)+r.f.recordIDOffset:])) } o.EventAttr = r.getAttr(o.ID, missingOk && o.ID == 0) if o.EventAttr == nil { return false } // Narrow decoder to the trailer commonLen := o.EventAttr.SampleFormat.trailerBytes() bd = &bufDecoder{bd.buf[len(bd.buf)-commonLen:], bd.order} // Decode trailer t := o.EventAttr.SampleFormat o.Format = t o.PID = int(bd.i32If(t&SampleFormatTID != 0)) o.TID = int(bd.i32If(t&SampleFormatTID != 0)) o.Time = bd.u64If(t&SampleFormatTime != 0) bd.u64If(t&SampleFormatID != 0) o.StreamID = bd.u64If(t&SampleFormatStreamID != 0) o.CPU = bd.u32If(t&SampleFormatCPU != 0) o.Res = bd.u32If(t&SampleFormatCPU != 0) return true } func (r *Records) parseMmap(bd *bufDecoder, hdr *recordHeader, common *RecordCommon, v2 bool) Record { o := &r.recordMmap o.RecordCommon = *common o.Format |= SampleFormatTID // Decode hdr.Misc o.Data = (hdr.Misc&recordMiscMmapData != 0) // Decode fields. Note that perf calls the file offset // "pgoff", but it's actually a byte offset. o.PID, o.TID = int(bd.i32()), int(bd.i32()) o.Addr, o.Len, o.FileOffset = bd.u64(), bd.u64(), bd.u64() if v2 { buildID := (hdr.Misc&recordMiscMmapBuildID != 0) if buildID { buildIDLen := int(bd.u8()) if o.BuildID == nil || cap(o.BuildID) < buildIDLen { o.BuildID = make([]byte, buildIDLen) } else { o.BuildID = o.BuildID[:buildIDLen] } bd.skip(3) bd.bytes(o.BuildID) o.Major, o.Minor = 0, 0 o.Ino, o.InoGeneration = 0, 0 } else { o.Major, o.Minor = bd.u32(), bd.u32() o.Ino, o.InoGeneration = bd.u64(), bd.u64() o.BuildID = nil } o.Prot, o.Flags = bd.u32(), bd.u32() } o.Filename = bd.cstring() return o } func (r *Records) parseLost(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &RecordLost{RecordCommon: *common} o.Format |= SampleFormatID o.ID = attrID(bd.u64()) o.EventAttr = r.getAttr(o.ID, false) o.NumLost = bd.u64() return o } func (r *Records) parseComm(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &r.recordComm o.RecordCommon = *common o.Format |= SampleFormatTID // Decode hdr.Misc o.Exec = (hdr.Misc&recordMiscCommExec != 0) // Decode fields o.PID, o.TID = int(bd.i32()), int(bd.i32()) o.Comm = bd.cstring() return o } func (r *Records) parseExit(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &r.recordExit o.RecordCommon = *common o.Format |= SampleFormatTID | SampleFormatTime o.PID, o.PPID = int(bd.i32()), int(bd.i32()) o.TID, o.PTID = int(bd.i32()), int(bd.i32()) o.Time = bd.u64() return o } func (r *Records) parseThrottle(bd *bufDecoder, hdr *recordHeader, common *RecordCommon, enable bool) Record { o := &RecordThrottle{RecordCommon: *common, Enable: enable} o.Format |= SampleFormatTime | SampleFormatID | SampleFormatStreamID o.Time = bd.u64() // Throttle events always have an event attr ID, even if the // IDs aren't recorded. So if we see an unknown attr ID, just // assume it's the default event. id := attrID(bd.u64()) if r.f.idToAttr[id] == nil && r.f.idToAttr[0] != nil { o.EventAttr = r.f.idToAttr[0] } else { o.EventAttr = r.getAttr(id, false) } o.StreamID = bd.u64() return o } func (r *Records) parseFork(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &r.recordFork o.RecordCommon = *common o.Format |= SampleFormatTID | SampleFormatTime o.PID, o.PPID = int(bd.i32()), int(bd.i32()) o.TID, o.PTID = int(bd.i32()), int(bd.i32()) o.Time = bd.u64() return o } func (r *Records) parseAux(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &r.recordAux o.RecordCommon = *common o.Offset, o.Size = bd.u64(), bd.u64() flags := bd.u64() format := (flags & 0xff00) >> 8 flags &^= 0xff00 o.Flags = AuxFlags(flags) o.PMUFormat = AuxPMUFormat(format) return o } func (r *Records) parseItraceStart(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &RecordItraceStart{RecordCommon: *common} o.Format |= SampleFormatTID o.PID, o.TID = int(bd.i32()), int(bd.i32()) return o } func (r *Records) parseLostSamples(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &RecordLostSamples{RecordCommon: *common} o.Lost = bd.u64() return o } func (r *Records) parseSwitch(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &r.recordSwitch o.RecordCommon = *common o.Out = hdr.Misc&recordMiscSwitchOut != 0 return o } func (r *Records) parseSwitchCPUWide(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &r.recordSwitchCPUWide o.RecordCommon = *common o.SwitchPID, o.SwitchTID = int(bd.i32()), int(bd.i32()) o.Out = hdr.Misc&recordMiscSwitchOut != 0 o.Preempt = hdr.Misc&recordMiscSwitchOutPreempt != 0 return o } func (r *Records) parseNamespaces(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &RecordNamespaces{RecordCommon: *common} o.Format |= SampleFormatTID o.PID, o.TID = int(bd.i32()), int(bd.i32()) n := bd.u64() o.Namespaces = make([]Namespace, n) for i := range o.Namespaces { o.Namespaces[i] = Namespace{bd.u64(), bd.u64()} } return o } func (r *Records) parseKsymbol(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &RecordKsymbol{RecordCommon: *common} o.Addr, o.Len = bd.u64(), bd.u32() o.KsymType = KsymbolType(bd.u16()) o.Flags = KsymbolFlags(bd.u64()) o.Name = bd.cstring() return o } func (r *Records) parseBPFEvent(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &RecordBPFEvent{RecordCommon: *common} o.EventType = BPFEventType(bd.u16()) o.Flags = BPFEventFlags(bd.u16()) o.ID = bd.u32() o.Tag = bd.u64() return o } func (r *Records) parseCGroup(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &RecordCGroup{RecordCommon: *common} o.ID = bd.u32() o.Path = bd.cstring() return o } func (r *Records) parseTextPoke(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &RecordTextPoke{RecordCommon: *common} o.Addr = bd.u64() oldLen, newLen := bd.u16(), bd.u16() o.Old = make([]byte, oldLen) bd.bytes(o.Old) o.New = make([]byte, newLen) bd.bytes(o.New) return o } func (r *Records) parseAuxOutputHardwareID(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &RecordAuxOutputHardwareID{RecordCommon: *common} o.ID = bd.u64() return o } func (r *Records) parseAuxtraceInfo(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &RecordAuxtraceInfo{RecordCommon: *common} o.Kind = bd.u32() bd.u32() // Alignment // TODO: Decode remainder according to Kind o.Priv = make([]uint64, len(bd.buf)/8) bd.u64s(o.Priv) return o } func (r *Records) parseAuxtrace(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &RecordAuxtrace{RecordCommon: *common} size := bd.u64() o.Offset, o.Ref = bd.u64(), bd.u64() o.Idx, o.TID, o.CPU = bd.u32(), int(bd.u32()), bd.u32() o.Data = make([]byte, size) if _, err := io.ReadFull(r.sr, o.Data); err != nil { r.err = err return nil } return o } func (r *Records) parseSample(bd *bufDecoder, hdr *recordHeader, common *RecordCommon) Record { o := &r.recordSample o.RecordCommon = *common // Get sample EventAttr ID if r.f.sampleIDOffset == -1 { o.ID = 0 } else { o.ID = attrID(bd.order.Uint64(bd.buf[r.f.sampleIDOffset:])) } o.EventAttr = r.getAttr(o.ID, false) if o.EventAttr == nil { return nil } // Decode hdr.Misc o.CPUMode = CPUMode(hdr.Misc & recordMiscCPUModeMask) o.ExactIP = (hdr.Misc&recordMiscExactIP != 0) // Decode the rest of the sample t := o.EventAttr.SampleFormat o.Format = t bd.u64If(t&SampleFormatIdentifier != 0) o.IP = bd.u64If(t&SampleFormatIP != 0) o.PID = int(bd.i32If(t&SampleFormatTID != 0)) o.TID = int(bd.i32If(t&SampleFormatTID != 0)) o.Time = bd.u64If(t&SampleFormatTime != 0) o.Addr = bd.u64If(t&SampleFormatAddr != 0) bd.u64If(t&SampleFormatID != 0) o.StreamID = bd.u64If(t&SampleFormatStreamID != 0) o.CPU = bd.u32If(t&SampleFormatCPU != 0) o.Res = bd.u32If(t&SampleFormatCPU != 0) o.Period = bd.u64If(t&SampleFormatPeriod != 0) if t&SampleFormatRead != 0 { r.parseReadFormat(bd, o.EventAttr.ReadFormat, &o.SampleRead) } if t&SampleFormatCallchain != 0 { callchainLen := int(bd.u64()) if o.Callchain == nil || cap(o.Callchain) < callchainLen { o.Callchain = make([]uint64, callchainLen) } else { o.Callchain = o.Callchain[:callchainLen] } bd.u64s(o.Callchain) } else { o.Callchain = nil } rawSize := bd.u32If(t&SampleFormatRaw != 0) bd.skip(int(rawSize)) o.BranchHWIndex = bd.i64If(o.EventAttr.BranchSampleType&BranchSampleHWIndex != 0) if t&SampleFormatBranchStack != 0 { count := int(bd.u64()) if o.BranchStack == nil || cap(o.BranchStack) < count { o.BranchStack = make([]BranchRecord, count) } else { o.BranchStack = o.BranchStack[:count] } for i := range o.BranchStack { br := &o.BranchStack[i] br.From = bd.u64() br.To = bd.u64() flags := bd.u64() // First 4 bits are flags br.Flags = BranchFlags(flags & 0x0f) // Next 16 bits are cycles br.Cycles = uint16(flags >> 4) // Next 4 bits are type br.Type = BranchType((flags >> 20) & 0x0f) } } if t&SampleFormatRegsUser != 0 { o.RegsUserABI = SampleRegsABI(bd.u64()) count := weight(o.EventAttr.SampleRegsUser) if o.RegsUser == nil || cap(o.RegsUser) < count { o.RegsUser = make([]uint64, count) } else { o.RegsUser = o.RegsUser[:count] } if o.RegsUserABI == SampleRegsABINone { o.RegsUser = o.RegsUser[:0:0] } else { bd.u64s(o.RegsUser) } } if t&SampleFormatStackUser != 0 { size := int(bd.u64()) if o.StackUser == nil || cap(o.StackUser) < size { o.StackUser = make([]byte, size) } else { o.StackUser = o.StackUser[:size] } bd.bytes(o.StackUser) o.StackUserDynSize = bd.u64() } else { o.StackUser = nil o.StackUserDynSize = 0 } if t&SampleFormatWeight != 0 { o.Weight = bd.u64() o.Weights = Weights{} } else if t&SampleFormatWeightStruct != 0 { // N.B. the kernel memcpys the 64-bit union value regardless of // format, so on big endian systems the fields appear in the // opposite order. Read as a 64-bit value and extract the // fields to handle both little and big endian. weight := bd.u64() o.Weights.Var1 = uint32(weight) o.Weights.Var2 = uint16(weight >> 32) o.Weights.Var3 = uint16(weight >> 48) // For ease of use, also put Var1 in Weight. If you // only care about one weight, that's the one. o.Weight = uint64(o.Weights.Var1) } if t&SampleFormatDataSrc != 0 { o.DataSrc = decodeDataSrc(bd.u64()) } transaction := bd.u64If(t&SampleFormatTransaction != 0) o.Transaction = Transaction(transaction & 0xffffffff) o.AbortCode = uint32(transaction >> 32) if t&SampleFormatRegsIntr != 0 { o.RegsIntrABI = SampleRegsABI(bd.u64()) count := weight(o.EventAttr.SampleRegsIntr) if o.RegsIntr == nil || cap(o.RegsIntr) < count { o.RegsIntr = make([]uint64, count) } else { o.RegsIntr = o.RegsIntr[:count] } if o.RegsIntrABI == SampleRegsABINone { o.RegsIntr = o.RegsIntr[:0:0] } else { bd.u64s(o.RegsIntr) } } if t&SampleFormatPhysAddr != 0 { o.PhysAddr = bd.u64() } if t&SampleFormatCGroup != 0 { o.CGroup = bd.u64() } if t&SampleFormatDataPageSize != 0 { o.DataPageSize = bd.u64() } if t&SampleFormatCodePageSize != 0 { o.CodePageSize = bd.u64() } if t&SampleFormatAux != 0 { auxLen := int(bd.u64()) if o.Aux == nil || cap(o.Aux) < auxLen { o.Aux = make([]byte, auxLen) } else { o.Aux = o.Aux[:auxLen] } bd.bytes(o.Aux) } else { o.Aux = nil } return o } func (r *Records) parseReadFormat(bd *bufDecoder, f ReadFormat, out *[]Count) { n := 1 if f&ReadFormatGroup != 0 { n = int(bd.u64()) } if *out == nil || cap(*out) < n { *out = make([]Count, n) } else { *out = (*out)[:n] } if f&ReadFormatGroup == 0 { o := &(*out)[0] o.Value = bd.u64() o.TimeEnabled = bd.u64If(f&ReadFormatTotalTimeEnabled != 0) o.TimeRunning = bd.u64If(f&ReadFormatTotalTimeRunning != 0) if f&ReadFormatID != 0 { o.EventAttr = r.getAttr(attrID(bd.u64()), false) } else { o.EventAttr = nil } } else { for i := range *out { o := &(*out)[i] o.TimeEnabled = bd.u64If(f&ReadFormatTotalTimeEnabled != 0) o.TimeRunning = bd.u64If(f&ReadFormatTotalTimeRunning != 0) o.Value = bd.u64() if f&ReadFormatID != 0 { o.EventAttr = r.getAttr(attrID(bd.u64()), false) } else { o.EventAttr = nil } } } } func decodeDataSrc(d uint64) (out DataSrc) { // See perf_mem_data_src in include/uapi/linux/perf_event.h op := (d >> 0) & 0x1f lvl := (d >> 5) & 0x3fff snoop := (d >> 19) & 0x1f lock := (d >> 24) & 0x3 dtlb := (d >> 26) & 0x7f levelNum := (d >> 33) & 0xf remote := (d >> 37) & 0x1 snoopX := (d >> 38) & 0x3 // two bit extension of snoop blk := (d >> 40) & 0x7 hops := (d >> 43) & 0x7 if op&0x1 != 0 { out.Op = DataSrcOpNA } else { out.Op = DataSrcOp(op >> 1) } if lvl&0x1 != 0 { out.Miss, out.Level = false, DataSrcLevelNA } else { out.Miss = (lvl & 0x4) != 0 out.Level = DataSrcLevel(lvl >> 3) } if snoop&0x1 != 0 { out.Snoop = DataSrcSnoopNA } else { out.Snoop = DataSrcSnoop(snoop >> 1) if snoopX&0x1 != 0 { out.Snoop |= DataSrcSnoopFwd } } if lock&0x1 != 0 { out.Locked = DataSrcLockNA } else if lock&0x02 != 0 { out.Locked = DataSrcLockLocked } else { out.Locked = DataSrcLockUnlocked } if dtlb&0x1 != 0 { out.TLB = DataSrcTLBNA } else { out.TLB = DataSrcTLB(dtlb >> 1) } out.LevelNum = DataSrcLevelNum(levelNum) out.Remote = remote != 0 if blk&0x1 != 0 { out.Block = DataSrcBlockNA } else { out.Block = DataSrcBlock(blk >> 1) } out.Hops = DataSrcHops(hops) return } func weight(x uint64) int { x -= (x >> 1) & 0x5555555555555555 x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333) x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f return int((x * 0x0101010101010101) >> 56) } ================================================ FILE: perffile/recordsorder_string.go ================================================ // Code generated by "stringer -type=RecordsOrder"; DO NOT EDIT. package perffile import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[RecordsFileOrder-0] _ = x[RecordsCausalOrder-1] _ = x[RecordsTimeOrder-2] } const _RecordsOrder_name = "RecordsFileOrderRecordsCausalOrderRecordsTimeOrder" var _RecordsOrder_index = [...]uint8{0, 16, 34, 50} func (i RecordsOrder) String() string { if i < 0 || i >= RecordsOrder(len(_RecordsOrder_index)-1) { return "RecordsOrder(" + strconv.FormatInt(int64(i), 10) + ")" } return _RecordsOrder_name[_RecordsOrder_index[i]:_RecordsOrder_index[i+1]] } ================================================ FILE: perffile/recordtype_string.go ================================================ // Code generated by "stringer -type=RecordType"; DO NOT EDIT. package perffile import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[RecordTypeMmap-1] _ = x[RecordTypeLost-2] _ = x[RecordTypeComm-3] _ = x[RecordTypeExit-4] _ = x[RecordTypeThrottle-5] _ = x[RecordTypeUnthrottle-6] _ = x[RecordTypeFork-7] _ = x[RecordTypeRead-8] _ = x[RecordTypeSample-9] _ = x[recordTypeMmap2-10] _ = x[RecordTypeAux-11] _ = x[RecordTypeItraceStart-12] _ = x[RecordTypeLostSamples-13] _ = x[RecordTypeSwitch-14] _ = x[RecordTypeSwitchCPUWide-15] _ = x[RecordTypeNamespaces-16] _ = x[RecordTypeKsymbol-17] _ = x[RecordTypeBPFEvent-18] _ = x[RecordTypeCGroup-19] _ = x[RecordTypeTextPoke-20] _ = x[RecordTypeAuxOutputHardwareID-21] _ = x[recordTypeUserStart-64] _ = x[recordTypeAttr-64] _ = x[recordTypeEventType-65] _ = x[recordTypeTracingData-66] _ = x[recordTypeBuildID-67] _ = x[recordTypeFinishedRound-68] _ = x[recordTypeIDIndex-69] _ = x[RecordTypeAuxtraceInfo-70] _ = x[RecordTypeAuxtrace-71] _ = x[RecordTypeAuxtraceError-72] _ = x[recordTypeThreadMap-73] _ = x[recordTypeCPUMap-74] _ = x[recordTypeStatConfig-75] _ = x[recordTypeStat-76] _ = x[recordTypeStatRound-77] _ = x[recordTypeEventUpdate-78] _ = x[recordTypeTimeConv-79] _ = x[recordTypeHeaderFeature-80] } const ( _RecordType_name_0 = "RecordTypeMmapRecordTypeLostRecordTypeCommRecordTypeExitRecordTypeThrottleRecordTypeUnthrottleRecordTypeForkRecordTypeReadRecordTypeSamplerecordTypeMmap2RecordTypeAuxRecordTypeItraceStartRecordTypeLostSamplesRecordTypeSwitchRecordTypeSwitchCPUWideRecordTypeNamespacesRecordTypeKsymbolRecordTypeBPFEventRecordTypeCGroupRecordTypeTextPokeRecordTypeAuxOutputHardwareID" _RecordType_name_1 = "recordTypeUserStartrecordTypeEventTyperecordTypeTracingDatarecordTypeBuildIDrecordTypeFinishedRoundrecordTypeIDIndexRecordTypeAuxtraceInfoRecordTypeAuxtraceRecordTypeAuxtraceErrorrecordTypeThreadMaprecordTypeCPUMaprecordTypeStatConfigrecordTypeStatrecordTypeStatRoundrecordTypeEventUpdaterecordTypeTimeConvrecordTypeHeaderFeature" ) var ( _RecordType_index_0 = [...]uint16{0, 14, 28, 42, 56, 74, 94, 108, 122, 138, 153, 166, 187, 208, 224, 247, 267, 284, 302, 318, 336, 365} _RecordType_index_1 = [...]uint16{0, 19, 38, 59, 76, 99, 116, 138, 156, 179, 198, 214, 234, 248, 267, 288, 306, 329} ) func (i RecordType) String() string { switch { case 1 <= i && i <= 21: i -= 1 return _RecordType_name_0[_RecordType_index_0[i]:_RecordType_index_0[i+1]] case 64 <= i && i <= 80: i -= 64 return _RecordType_name_1[_RecordType_index_1[i]:_RecordType_index_1[i+1]] default: return "RecordType(" + strconv.FormatInt(int64(i), 10) + ")" } } ================================================ FILE: perffile/sampleformat_string.go ================================================ // Code generated by "bitstringer -type=SampleFormat"; DO NOT EDIT package perffile import "strconv" func (i SampleFormat) String() string { if i == 0 { return "0" } s := "" if i&SampleFormatAddr != 0 { s += "Addr|" } if i&SampleFormatAux != 0 { s += "Aux|" } if i&SampleFormatBranchStack != 0 { s += "BranchStack|" } if i&SampleFormatCGroup != 0 { s += "CGroup|" } if i&SampleFormatCPU != 0 { s += "CPU|" } if i&SampleFormatCallchain != 0 { s += "Callchain|" } if i&SampleFormatCodePageSize != 0 { s += "CodePageSize|" } if i&SampleFormatDataPageSize != 0 { s += "DataPageSize|" } if i&SampleFormatDataSrc != 0 { s += "DataSrc|" } if i&SampleFormatID != 0 { s += "ID|" } if i&SampleFormatIP != 0 { s += "IP|" } if i&SampleFormatIdentifier != 0 { s += "Identifier|" } if i&SampleFormatPeriod != 0 { s += "Period|" } if i&SampleFormatPhysAddr != 0 { s += "PhysAddr|" } if i&SampleFormatRaw != 0 { s += "Raw|" } if i&SampleFormatRead != 0 { s += "Read|" } if i&SampleFormatRegsIntr != 0 { s += "RegsIntr|" } if i&SampleFormatRegsUser != 0 { s += "RegsUser|" } if i&SampleFormatStackUser != 0 { s += "StackUser|" } if i&SampleFormatStreamID != 0 { s += "StreamID|" } if i&SampleFormatTID != 0 { s += "TID|" } if i&SampleFormatTime != 0 { s += "Time|" } if i&SampleFormatTransaction != 0 { s += "Transaction|" } if i&SampleFormatWeight != 0 { s += "Weight|" } if i&SampleFormatWeightStruct != 0 { s += "WeightStruct|" } i &^= 33554431 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perffile/sampleregsabi_string.go ================================================ // Code generated by "stringer -type=SampleRegsABI"; DO NOT EDIT. package perffile import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[SampleRegsABINone-0] _ = x[SampleRegsABI32-1] _ = x[SampleRegsABI64-2] } const _SampleRegsABI_name = "SampleRegsABINoneSampleRegsABI32SampleRegsABI64" var _SampleRegsABI_index = [...]uint8{0, 17, 32, 47} func (i SampleRegsABI) String() string { if i >= SampleRegsABI(len(_SampleRegsABI_index)-1) { return "SampleRegsABI(" + strconv.FormatInt(int64(i), 10) + ")" } return _SampleRegsABI_name[_SampleRegsABI_index[i]:_SampleRegsABI_index[i+1]] } ================================================ FILE: perffile/transaction_string.go ================================================ // Code generated by "bitstringer -type=Transaction"; DO NOT EDIT package perffile import "strconv" func (i Transaction) String() string { if i == 0 { return "0" } s := "" if i&TransactionAsync != 0 { s += "Async|" } if i&TransactionCapacityRead != 0 { s += "CapacityRead|" } if i&TransactionCapacityWrite != 0 { s += "CapacityWrite|" } if i&TransactionConflict != 0 { s += "Conflict|" } if i&TransactionElision != 0 { s += "Elision|" } if i&TransactionRetry != 0 { s += "Retry|" } if i&TransactionSync != 0 { s += "Sync|" } if i&TransactionTransaction != 0 { s += "Transaction|" } i &^= 255 if i == 0 { return s[:len(s)-1] } return s + "0x" + strconv.FormatUint(uint64(i), 16) } ================================================ FILE: perfsession/package.go ================================================ // Copyright 2015 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 perfsession provides utilities for tracking session state // while processing a perf.data profile. // // The API of perfsession should be considered unstable at this point. package perfsession // import "github.com/aclements/go-perf/perfsession" ================================================ FILE: perfsession/ranges.go ================================================ // Copyright 2019 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 perfsession import "sort" // Ranges stores data associated with ranges of uint64 values and // supports efficient lookup. type Ranges struct { rs []rangeEnt sorted bool } type rangeEnt struct { lo, hi uint64 val interface{} } // Add inserts val for range [lo, hi). // // Add is undefined if [lo, hi) overlaps a range already in r. func (r *Ranges) Add(lo, hi uint64, val interface{}) { r.rs = append(r.rs, rangeEnt{lo, hi, val}) r.sorted = false } // Get returns the range and the value for the range containing idx. func (r *Ranges) Get(idx uint64) (lo, hi uint64, val interface{}, ok bool) { if r == nil { return 0, 0, nil, false } rs := r.rs if !r.sorted { sort.Slice(rs, func(i, j int) bool { return rs[i].lo < rs[j].lo }) r.sorted = true } i := sort.Search(len(rs), func(i int) bool { return idx < rs[i].hi }) if i < len(rs) && rs[i].lo <= idx && idx < rs[i].hi { return rs[i].lo, rs[i].hi, rs[i].val, true } return 0, 0, nil, false } ================================================ FILE: perfsession/session.go ================================================ // Copyright 2015 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 perfsession import "github.com/aclements/go-perf/perffile" // TODO: Per-TID state. type Session struct { kernel *PIDInfo pidInfo map[int]*PIDInfo File *perffile.File Extra map[ExtraKey]interface{} } func New(f *perffile.File) *Session { kernel := &PIDInfo{ Comm: "[kernel]", Extra: make(ForkableExtra), } return &Session{ kernel: kernel, pidInfo: map[int]*PIDInfo{ // The kernel is implicitly PID -1 -1: kernel, }, File: f, Extra: make(map[ExtraKey]interface{}), } } func (s *Session) Update(r perffile.Record) { ensurePID := func(pid int) *PIDInfo { pidInfo, ok := s.pidInfo[pid] if !ok { pidInfo = &PIDInfo{ kernel: s.kernel, Extra: make(ForkableExtra), } s.pidInfo[pid] = pidInfo } return pidInfo } switch r := r.(type) { case *perffile.RecordComm: ensurePID(r.PID).Comm = r.Comm case *perffile.RecordExit: if r.PID == r.TID { delete(s.pidInfo, r.PID) } // Otherwise this is thread exit case *perffile.RecordFork: if r.PID == r.TID { s.pidInfo[r.PID] = ensurePID(r.PPID).fork(r.PID) } // Otherwise this is thread creation case *perffile.RecordMmap: info := ensurePID(r.PID) info.munmap(r.Addr, r.Len) info.maps = append(info.maps, &Mmap{make(ForkableExtra), *r}) case *perffile.RecordSample: // Sometimes (particularly early in sample files), we // see kernel samples before the RecordComm. ensurePID(r.PID) } } func (s *Session) LookupPID(pid int) *PIDInfo { return s.pidInfo[pid] } type PIDInfo struct { Extra ForkableExtra Comm string kernel *PIDInfo maps []*Mmap } func (p *PIDInfo) fork(pid int) *PIDInfo { maps := make([]*Mmap, len(p.maps)) for i, mmap := range p.maps { maps[i] = mmap.fork(pid) } return &PIDInfo{p.Extra.Fork(pid).(ForkableExtra), p.Comm, p.kernel, maps} } func (p *PIDInfo) munmap(addr, mlen uint64) { end := addr + mlen removed := false nmaps := p.maps for i, mmap := range p.maps { if addr <= mmap.Addr { if end >= mmap.Addr+mmap.Len { p.maps[i] = nil removed = true } else if end > mmap.Addr { // Remove beginning of mmap mmap.Len -= (end - mmap.Addr) mmap.Addr = end } } else if addr < mmap.Addr+mmap.Len { if end >= mmap.Addr+mmap.Len { // Remove end of mmap mmap.Len = addr - mmap.Addr } else { // Split mmap in two nmmap := *mmap nmmap.Len = end - (mmap.Addr + mmap.Len) nmaps = append(nmaps, &nmmap) mmap.Len = addr - mmap.Addr } } } // Fill holes if removed { d := 0 for s := 0; s < len(nmaps); s++ { if nmaps[d] == nil { nmaps[d] = nmaps[s] } if nmaps[d] != nil { d++ } } nmaps = nmaps[:d] } p.maps = nmaps } func (p *PIDInfo) mapFind(addr uint64) *Mmap { for _, mmap := range p.maps { if mmap.Addr <= addr && addr < mmap.Addr+mmap.Len { return mmap } } return nil } func (p *PIDInfo) LookupMmap(addr uint64) *Mmap { m := p.mapFind(addr) if m == nil && p.kernel != nil { m = p.kernel.mapFind(addr) } return m } type Mmap struct { Extra ForkableExtra perffile.RecordMmap } func (m *Mmap) fork(pid int) *Mmap { return &Mmap{m.Extra.Fork(pid).(ForkableExtra), m.RecordMmap} } type Forkable interface { Fork(pid int) Forkable } type ExtraKey *struct { private struct{} Name string } func NewExtraKey(name string) ExtraKey { return ExtraKey(&struct { private struct{} Name string }{Name: name}) } type ForkableExtra map[ExtraKey]Forkable func (f ForkableExtra) Fork(pid int) Forkable { f2 := make(ForkableExtra, len(f)) for k, v := range f { f2[k] = v.Fork(pid) } return f2 } ================================================ FILE: perfsession/symbolize.go ================================================ // Copyright 2015 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 perfsession import ( "bufio" "debug/dwarf" "debug/elf" "fmt" "io" "log" "os" "os/user" "regexp" "sort" "strconv" "strings" "github.com/ianlancetaylor/demangle" ) type Symbolic struct { FuncName string Line dwarf.LineEntry } // TODO: Take a PID and look up the mmap. func Symbolize(session *Session, mmap *Mmap, ip uint64, out *Symbolic) bool { s := getSymbolicExtra(session, mmap.Filename) if s == nil { return false } f, l := s.findIP(mmap, ip) if f == nil { out.FuncName = "" } else { out.FuncName = f.name } if l == nil { out.Line = dwarf.LineEntry{} } else { out.Line = *l } return true } var symbolicExtraKey = NewExtraKey("perfsession.symbolicExtra") var buildIDDir = (func() string { // See set_buildid_dir in tools/perf/util/config.c. u, err := user.Current() if err != nil { return ".debug" } return fmt.Sprintf("%s/.debug", u.HomeDir) })() func getSymbolicExtra(session *Session, filename string) *symbolicExtra { var err error tables, ok := session.Extra[symbolicExtraKey].(map[string]*symbolicExtra) if !ok { tables = make(map[string]*symbolicExtra) session.Extra[symbolicExtraKey] = tables } // For some reason, the filename for the kernel mapping looks // like "[kernel.kallsyms]_text", but the build ID file name // is just "[kernel.kallsyms]". Match them up. // // TODO: There may be a reason the file name is different that // I don't understand. perf doesn't seem to have any special // treatment of "_text". // // TODO: perf works a lot harder to find kernel symbols. See // dso__find_kallsyms in tools/perf/util/symbol.c. isKallsyms := false if strings.HasPrefix(filename, "[kernel.kallsyms]") { isKallsyms = true filename = "[kernel.kallsyms]" } extra, ok := tables[filename] if ok { return extra } tables[filename] = (*symbolicExtra)(nil) // See dso__data_fd in toosl/perf/util/dso.c. // Try build ID cache first. // // TODO: Cache filename to build ID mapping. for _, bid := range session.File.Meta.BuildIDs { if bid.Filename == filename { nfilename := fmt.Sprintf("%s/.build-id/%.2s/%s", buildIDDir, bid.BuildID, bid.BuildID.String()[2:]) if isKallsyms { extra, err = newKallsyms(nfilename) } else { extra, err = newSymbolicExtra(nfilename) } if err == nil { break } } } // Try original path. if extra == nil { extra, err = newSymbolicExtra(filename) if err != nil { log.Println(err) } } tables[filename] = extra return extra } func newSymbolicExtra(filename string) (*symbolicExtra, error) { // Load ELF elff, err := elf.Open(filename) if err != nil { return nil, fmt.Errorf("error loading ELF file %s: %s", filename, err) } defer elff.Close() extra := &symbolicExtra{} // Load DWARF // // TODO: Support build IDs and debug links // // TODO: Support DWARF for relocatable objects if elff.Type == elf.ET_EXEC && (elff.Section(".debug_info") != nil || elff.Section(".zdebug_info") != nil) { dwarff, err := elff.DWARF() if err != nil { return nil, fmt.Errorf("error loading DWARF from %s: %s", filename, err) } extra.functab = dwarfFuncTable(dwarff) extra.linetab = dwarfLineTable(dwarff) return &symbolicExtra{ dwarfFuncTable(dwarff), dwarfLineTable(dwarff), false, }, nil } if extra.functab == nil { // Make do with the ELF symbols. extra.functab, extra.isReloc = elfFuncTable(filename, elff) } return extra, nil } var kallsymsRe = regexp.MustCompile("^([0-9a-fA-F]*) +(.) (.*)") func newKallsyms(filename string) (*symbolicExtra, error) { f, err := os.Open(filename) if err != nil { return nil, fmt.Errorf("error loading kallsyms from %s: %s", filename, err) } defer f.Close() // This file is a nm-style object list. See kallsyms__parse in // tools/lib/symbol/kallsyms.c. functab := make([]funcRange, 0) scanner := bufio.NewScanner(f) for scanner.Scan() { subs := kallsymsRe.FindStringSubmatch(scanner.Text()) if subs == nil { continue } typ, name := subs[2][0], subs[3] if !(typ == 't' || typ == 'T') { continue } addr, _ := strconv.ParseUint(subs[1], 16, 64) functab = append(functab, funcRange{name, addr, addr, true}) } if err := scanner.Err(); err != nil { return nil, err } sort.Sort(funcRangeSorter(functab)) setFuncHighPCs(functab) return &symbolicExtra{functab, nil, false}, nil } type symbolicExtra struct { functab []funcRange linetab []dwarf.LineEntry // isReloc indicates that lowpc/highpc in functab are ELF file // offsets rather than virtual addresses. isReloc bool } func (s *symbolicExtra) findIP(mmap *Mmap, ip uint64) (f *funcRange, l *dwarf.LineEntry) { if s.functab != nil { if s.isReloc { // functab is indexed by file offset. ip = ip - mmap.Addr + mmap.FileOffset } i := sort.Search(len(s.functab), func(i int) bool { return ip < s.functab[i].highpc }) if i < len(s.functab) && s.functab[i].lowpc <= ip && ip < s.functab[i].highpc { f = &s.functab[i] if !f.demangled { f.name = demangle.Filter(f.name) f.demangled = true } } } if s.linetab != nil { i := sort.Search(len(s.linetab), func(i int) bool { return ip < s.linetab[i].Address }) if i != 0 && !s.linetab[i-1].EndSequence { l = &s.linetab[i-1] } } return } type funcRange struct { name string lowpc, highpc uint64 demangled bool } func dwarfFuncTable(dwarff *dwarf.Data) []funcRange { // Walk DWARF for functions // TODO: Use .debug_pubnames (not supported by dwarf package) r := dwarff.Reader() out := make([]funcRange, 0) for { ent, err := r.Next() if ent == nil || err != nil { break } // TODO: We should process TagInlinedSubroutine, but // apparently gc doesn't produce these. // // TODO: Support DW_AT_ranges. tag: switch ent.Tag { case dwarf.TagSubprogram: r.SkipChildren() const AttrLinkageName dwarf.Attr = 0x6e name, ok := ent.Val(AttrLinkageName).(string) demangled := true if !ok { name, ok = ent.Val(dwarf.AttrName).(string) demangled = false if !ok { break } } lowpc, ok := ent.Val(dwarf.AttrLowpc).(uint64) if !ok { break } var highpc uint64 switch highpcx := ent.Val(dwarf.AttrHighpc).(type) { case uint64: highpc = highpcx case int64: highpc = lowpc + uint64(highpcx) default: break tag } out = append(out, funcRange{name, lowpc, highpc, demangled}) case dwarf.TagCompileUnit, dwarf.TagModule, dwarf.TagNamespace: break default: r.SkipChildren() } } sort.Sort(funcRangeSorter(out)) if len(out) == 0 { return nil } return out } func elfFuncTable(filename string, elff *elf.File) (out []funcRange, isReloc bool) { switch elff.Type { case elf.ET_EXEC: // Symbol values are virtual addresses. isReloc = false case elf.ET_DYN: // Symbol values are section-relative offsets. This // will resolve them to file offsets. isReloc = true default: return nil, false } out = make([]funcRange, 0) syms, err := elff.Symbols() if err != nil { if err != elf.ErrNoSymbols { log.Fatalf("%s: %s", filename, err) } return nil, false } for _, sym := range syms { if elf.SymType(sym.Info&0xF) != elf.STT_FUNC || sym.Section == elf.SHN_UNDEF { continue } lowpc := sym.Value if isReloc { // lowpc is a section-relative offset. // Translate it to a file offset. if int(sym.Section) >= len(elff.Sections) { continue } sec := elff.Sections[sym.Section] lowpc = lowpc - sec.Addr + sec.Offset } out = append(out, funcRange{sym.Name, lowpc, lowpc + sym.Size, false}) } sort.Sort(funcRangeSorter(out)) setFuncHighPCs(out) return } type funcRangeSorter []funcRange func (s funcRangeSorter) Len() int { return len(s) } func (s funcRangeSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s funcRangeSorter) Less(i, j int) bool { return s[i].lowpc < s[j].lowpc } // setFuncHighPCs fills in missing highpc values in functab. functab // must be sorted. func setFuncHighPCs(functab []funcRange) { // Assign symbols highpcs if they don't have them. for i := range functab { if functab[i].highpc == functab[i].lowpc { if i == len(functab)-1 { functab[i].highpc++ } else { functab[i].highpc = functab[i+1].lowpc } } } } func dwarfLineTable(dwarff *dwarf.Data) []dwarf.LineEntry { out := make([]dwarf.LineEntry, 0) // Iterate over compilation units dr := dwarff.Reader() for { ent, err := dr.Next() if ent == nil || err != nil { break } if ent.Tag != dwarf.TagCompileUnit { dr.SkipChildren() continue } // Decode CU's line table lr, err := dwarff.LineReader(ent) if err != nil { log.Fatal(err) } else if lr == nil { continue } for { var lent dwarf.LineEntry err := lr.Next(&lent) if err != nil { if err == io.EOF { break } log.Fatal(err) } out = append(out, lent) } } sort.Slice(out, func(i, j int) bool { return out[i].Address < out[j].Address }) return out } ================================================ FILE: scale/interface.go ================================================ // Copyright 2015 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 scale // A scale satisfies Interface if it maps from some input range to an // output interval [0, 1]. type Interface interface { Of(x float64) float64 Ticks(n int) (major, minor []float64) } ================================================ FILE: scale/linear.go ================================================ // Copyright 2015 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 scale type Linear struct { min, width float64 } // NewLinear returns a new linear scale. func NewLinear(input []float64) Linear { min, max := minmax(input) return Linear{min, max - min} } func (s Linear) Of(x float64) float64 { return (x - s.min) / s.width } func (s Linear) Ticks(n int) (major, minor []float64) { major, minor = make([]float64, n), []float64{} // TODO: Pick good ticks for i := range major { major[i] = float64(i)*s.width/float64(n) + s.min } return } ================================================ FILE: scale/log.go ================================================ // Copyright 2015 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 scale import "math" type Log struct { min, max, base float64 logMin, denom float64 } // NewLog returns a new logarithmic scale. // // base has no effect on the scaling. It is only used for computing // tick marks. func NewLog(input []float64, base float64) *Log { min, max := minmax(input) s := &Log{min: min, max: max, base: base} s.precompute() return s } func (s *Log) precompute() { s.logMin = math.Log(s.min) s.denom = math.Log(s.max) - s.logMin } func (s *Log) Of(x float64) float64 { return (math.Log(x) - s.logMin) / s.denom } // Nice expands the domain of s to "nice" values of the scale, which // will translate into major tick marks. // // n is the maximum number of major ticks. n must be >= 2. func (s *Log) Nice(n int) { if n < 2 { panic("n must be >= 2") } // Increase the effective base until there are <= n major ticks for ebase := s.base; ; ebase *= s.base { // TODO: Allow for some round-off error // Compute major tick below s.min and above s.max lo := math.Pow(ebase, math.Floor(math.Log(s.min)/math.Log(ebase))) hi := math.Pow(ebase, math.Ceil(math.Log(s.max)/math.Log(ebase))) // Compute number of ticks between lo and hi nticks := 1 + (math.Log(hi)-math.Log(lo))/math.Log(ebase) if nticks <= float64(n) { // Found it s.min, s.max = lo, hi s.precompute() break } } } func (s *Log) Ticks(n int) (major, minor []float64) { if n < 2 { panic("n must be >= 2") } major, minor = []float64{}, []float64{} // Increase the effective base until there are <= n major ticks ebase := s.base for ; ; ebase *= s.base { // Compute number of ticks between lo and hi nticks := 1 + (math.Log(s.max)-math.Log(s.min))/math.Log(ebase) if nticks <= float64(n) { // Found it break } } // Start at the major tick below s.min x := math.Pow(ebase, math.Floor(math.Log(s.min)/math.Log(ebase))) for x <= s.max { for step := 0.0; step < ebase; step += ebase / s.base { x2 := x + step*x if x2 < s.min { continue } else if x2 > s.max { break } if step == 0 { major = append(major, x2) } else { minor = append(minor, x2) } } x *= ebase } return } ================================================ FILE: scale/output.go ================================================ // Copyright 2015 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 scale type OutputScale struct { min, max float64 clamp int } const ( clampCrop = iota clampNone clampClamp ) func NewOutputScale(min, max float64) OutputScale { return OutputScale{min, max, clampCrop} } func (s *OutputScale) Crop() { s.clamp = clampCrop } func (s *OutputScale) Unclamp() { s.clamp = clampNone } func (s *OutputScale) Clamp() { s.clamp = clampClamp } func (s OutputScale) Of(x float64) (float64, bool) { if s.clamp == clampCrop { if x < 0 || x > 1 { return 0, false } } else if s.clamp == clampClamp { if x < 0 { x = 0 } else if x > 1 { x = 1 } } return x*(s.max-s.min) + s.min, true } ================================================ FILE: scale/power.go ================================================ // Copyright 2015 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 scale import "math" type Power struct { lin Linear exp float64 } // NewPower returns a new power scale. func NewPower(input []float64, exp float64) Power { return Power{NewLinear(input), exp} } func (s Power) Of(x float64) float64 { return math.Pow(s.lin.Of(x), s.exp) } func (s Power) Ticks(n int) (major, minor []float64) { return s.lin.Ticks(n) } ================================================ FILE: scale/util.go ================================================ // Copyright 2015 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 scale func minmax(xs []float64) (min float64, max float64) { min, max = xs[0], xs[0] for _, x := range xs { if x < min { min = x } if x > max { max = x } } return } ================================================ FILE: scripts/membw ================================================ #!/bin/zsh # Measure read/write memory bandwidth at memory controllers. # # Based on "Intel® Xeon® Processor E5 v2 and E7 v2 Product Families # Uncore Performance Monitoring Reference Manual". set -e family=$(grep '^cpu family' /proc/cpuinfo | awk '{print $4; exit}') model=$(grep '^model' /proc/cpuinfo | awk '{print $3; exit}') fm=$(printf "%02x" $family)_$(printf "%02x" $model) # TODO: This works for all E5 v2 and E7 v2. There are probably other # CPUIDs that apply. if [[ $fm != 06_3e ]]; then echo "Unsupported family/model $fm" >&2 exit 1 fi # Get iMC uncore boxes. In the E5 v2 there are four. imcs=($(cd -q /sys/bus/event_source/devices/; echo uncore_imc_*)) # Construct the event list. events=() for imc in $imcs; do events=($events -e $imc/event=0x04,umask=0x03/ -e $imc/event=0x04,umask=0x0c/) done # Uncore events must be monitored system-wide. output=$(perf stat -a $events $* 2>&1) echo $output echo $output | awk ' /uncore_imc_.*umask=0x03/ {read += $1} /uncore_imc_.*umask=0x0c/ {write += $1} /time elapsed/ {time = $1} END { print (64 * read / time / 1024 / 1024) " MiB read per second" print (64 * write / time / 1024 / 1024) " MiB written per second" }' ================================================ FILE: scripts/memload.py ================================================ #!/usr/bin/python3 import os import sys import subprocess import operator def gather(counters): counters = list(frozenset(counters)) r, w = os.pipe() args = ["ocperf.py", "stat", "-x;", "--log-fd", str(w)] for counter in counters: args.extend(["-e", counter]) args.extend(sys.argv[1:]) perf = subprocess.Popen(args, pass_fds=[w]) os.close(w) r = os.fdopen(r) output = r.read() r.close() res = perf.wait() if res: sys.exit(res) #print(output, file=sys.stderr) # Debugging # Parse output. Annoyingly, column 3 is the names, but ocperf # generated names trim off modifiers, so we can't distinguish # cmasks and such. Hence, we just match them up by order. cmap = {} for line in output.splitlines(): line = line.split("#", 1)[0].strip() if not line: continue row = line.split(";") # TODO: What's column 2? Column 3 is name. Column 4 is run # time of the counter. Column 5 is fraction of time counter # was running. cmap[counters[len(cmap)]] = int(row[0]) return cmap def cpu_family_model(): family = model = None for l in open("/proc/cpuinfo").readlines(): if l.startswith("cpu family\t"): family = int(l.split(":")[1]) if l.startswith("model\t"): model = int(l.split(":")[1]) if family != None and model != None: return family, model print("failed to get CPU family and model from /proc/cpuinfo", file=sys.stderr) sys.exit(1) def main(): # Only Ivy Bridge for now. family, model = cpu_family_model() if (family, model) not in [(0x06, 0x3a), (0x06, 0x3e)]: print("unsupported CPU model %02x_%02xH" % (family, model), file=sys.stderr) sys.exit(1) events = ["OFFCORE_REQUESTS_OUTSTANDING.DEMAND_DATA_RD:cmask=%d" % cmask for cmask in range(1, 18)] ctx = gather(["CPU_CLK_UNHALTED.THREAD"] + events) # Print PDF clocks = ctx["CPU_CLK_UNHALTED.THREAD"] for i, (ev1, ev2) in enumerate(zip(events, events[1:])): print(i + 1, 100 * (ctx[ev1] - ctx[ev2]) / clocks) if ctx[events[-1]] != 0: print("Warning: Highest cmask has non-zero event count %d" % ctx[events[-1]], file=sys.stderr) if __name__ == "__main__": main() ================================================ FILE: scripts/topdown.py ================================================ #!/usr/bin/python3 import os import sys import subprocess import operator # TODO: Improve the quality of computed metrics using event groups. def gather(counters): counters = list(frozenset(counters)) r, w = os.pipe() args = ["ocperf.py", "stat", "-x;", "--log-fd", str(w)] for counter in counters: args.extend(["-e", counter]) args.extend(sys.argv[1:]) perf = subprocess.Popen(args, pass_fds=[w]) os.close(w) r = os.fdopen(r) output = r.read() r.close() res = perf.wait() if res: sys.exit(res) #print(output, file=sys.stderr) # Debugging # Parse output. Annoyingly, column 3 is the names, but ocperf # generated names trim off modifiers, so we can't distinguish # cmasks and such. Hence, we just match them up by order. cmap = {} for line in output.splitlines(): line = line.split("#", 1)[0].strip() if not line: continue row = line.split(";") # TODO: What's column 2? Column 3 is name. Column 4 is run # time of the counter. Column 5 is fraction of time counter # was running. cmap[counters[len(cmap)]] = int(row[0]) return cmap class Formula: def __init__(self, children): self.children = children def eval(self, ctx): raise NotImplementedError("eval is abstract") def events(self): raise NotImplementedError("events is abstract") def __add__(self, o): return FormulaOp(operator.add, self, o) def __radd__(self, o): return FormulaOp(operator.add, o, self) def __sub__(self, o): return FormulaOp(operator.sub, self, o) def __rsub__(self, o): return FormulaOp(operator.sub, o, self) def __mul__(self, o): return FormulaOp(operator.mul, self, o) def __rmul__(self, o): return FormulaOp(operator.mul, o, self) def __truediv__(self, o): return FormulaOp(operator.truediv, self, o) def __rtruediv__(self, o): return FormulaOp(operator.truediv, o, self) class FormulaOp(Formula): def __init__(self, op, *args): super().__init__(args) self.op = op def eval(self, ctx): args = [c.eval(ctx) if isinstance(c, Formula) else c for c in self.children] return self.op(*args) def events(self): events = [] for child in self.children: if isinstance(child, Formula): events.extend(child.events()) return events class E(Formula): def __init__(self, event): super().__init__([]) self.event = event def eval(self, ctx): return ctx[self.event] def events(self): return [self.event] # Top-Down bottleneck formulas. These are mostly from Yasin 2014, "A # Top-Down Method for Performance Analysis and Counters Architecture" # with some more detailed metrics from pmu-tools' toplev.py. clocks = E("CPU_CLK_UNHALTED.THREAD") issueWidth = 4 slots = issueWidth * clocks frontendBound = E("IDQ_UOPS_NOT_DELIVERED.CORE") / slots fetchLatencyBound = E("IDQ_UOPS_NOT_DELIVERED.CORE:cmask=4") / clocks fetchBandwidthBound = frontendBound - fetchLatencyBound # Fetch bandwidth breakdown from toplev.py. NOTE: These are just # fractions of all cycles. They don't add up to fetchBandwidthBound, # since they're not gated on the front end being bottlenecked. fe_MITE = (E("IDQ.ALL_MITE_CYCLES_ANY_UOPS") - E("IDQ.ALL_MITE_CYCLES_4_UOPS")) / clocks fe_DSB = (E("IDQ.ALL_DSB_CYCLES_ANY_UOPS") - E("IDQ.ALL_DSB_CYCLES_4_UOPS")) / clocks fe_LSD = (E("LSD.CYCLES_ACTIVE") - E("LSD.CYCLES_4_UOPS")) / clocks badSpeculation = (E("UOPS_ISSUED.ANY") - E("UOPS_RETIRED.RETIRE_SLOTS") + 4 * E("INT_MISC.RECOVERY_CYCLES")) / slots retiring = E("UOPS_RETIRED.RETIRE_SLOTS") / slots memoryBound = (E("CYCLE_ACTIVITY.STALLS_MEM_ANY") + E("RESOURCE_STALLS.SB")) / clocks numExecutionStalls = (E("CYCLE_ACTIVITY.CYCLES_NO_EXECUTE") - E("RS_EVENTS.EMPTY_CYCLES") + E("UOPS_EXECUTED.THREAD:cmask=1") - E("UOPS_EXECUTED.THREAD:cmask=2")) / clocks coreBound = numExecutionStalls - memoryBound # See https://software.intel.com/en-us/node/544440 for detailed # explanations of L1 bound reasons. l1Bound = (E("CYCLE_ACTIVITY.STALLS_MEM_ANY") - E("CYCLE_ACTIVITY.STALLS_L1D_MISS")) / clocks l1_STLBHitCost = 7 l1_DTLBMiss = (l1_STLBHitCost * E("DTLB_LOAD_MISSES.STLB_HIT") + E("DTLB_LOAD_MISSES.WALK_DURATION")) / clocks l1_SFBCost = 13 l1_StoreForwardBlocked = l1_SFBCost * E("LD_BLOCKS.STORE_FORWARD") / clocks l1_LockStoreFraction = E("MEM_UOPS_RETIRED.LOCK_LOADS") / E("MEM_UOPS_RETIRED.ALL_STORES") oroDemandRFOC1 = FormulaOp(min, E("CPU_CLK_UNHALTED.THREAD"), E("OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DEMAND_RFO")) l1_LockLatency = l1_LockStoreFraction * oroDemandRFOC1 / clocks # Load spans two cache lines. l1_SplitLoads = 13 * E("LD_BLOCKS.NO_SR") / clocks # Earlier load conflicts with later store on bottom 12 bits. l1_4KAliasCost = 7 l1_4KAliasing = (l1_4KAliasCost * E("LD_BLOCKS_PARTIAL.ADDRESS_ALIAS")) / clocks # L1D fill buffer limits further demand loads. l1_LoadMissRealLatency = E("L1D_PEND_MISS.PENDING") / (E("MEM_LOAD_UOPS_RETIRED.L1_MISS") + E("MEM_LOAD_UOPS_RETIRED.HIT_LFB")) l1_FBFull = l1_LoadMissRealLatency * E("L1D_PEND_MISS.FB_FULL:cmask=1") / clocks l2Bound = (E("CYCLE_ACTIVITY.STALLS_L1D_MISS") - E("CYCLE_ACTIVITY.STALLS_L2_MISS")) / clocks # toplev.py gives the following alternate formula, but the results # don't seem any different. #l2Bound = (E("CYCLE_ACTIVITY.STALLS_L1D_PENDING") - E("CYCLE_ACTIVITY.STALLS_L2_PENDING")) / clocks l3HitFraction = E("MEM_LOAD_UOPS_RETIRED.LLC_HIT") / (E("MEM_LOAD_UOPS_RETIRED.LLC_HIT") + 7*E("MEM_LOAD_UOPS_RETIRED.LLC_MISS")) l3Bound = l3HitFraction * E("CYCLE_ACTIVITY.STALLS_L2_MISS") / clocks extMemoryBound = (1 - l3HitFraction) * E("CYCLE_ACTIVITY.STALLS_L2_MISS") / clocks # These formulas come from toplev.py. They're very different from the # Yasin paper, but are based on the same idea, and don't depend on # undocumented uncore counters. extMem_BandwidthBound = E("OFFCORE_REQUESTS_OUTSTANDING.DEMAND_DATA_RD:cmask=6") / clocks extMem_LatencyBound = (E("OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DEMAND_DATA_RD") - E("OFFCORE_REQUESTS_OUTSTANDING.DEMAND_DATA_RD:cmask=6")) / clocks class Node: def __init__(self, label, value, *children): self.label, self.value, self.children = label, value, children def events(self): counters = [] if self.value is not None: counters.extend(self.value.events()) for child in self.children: counters.extend(child.events()) return counters def eval(self, ctx): if self.value is None: return sum(child.eval(ctx) for child in self.children) return self.value.eval(ctx) def show(self, ctx, indent=0): value = self.eval(ctx) print("%-30s %6.2f%%" % (" " * indent + self.label, 100*value)) for child in self.children: child.show(ctx, indent+1) tree = Node("All slots", None, Node("µop issued", None, Node("Retires", retiring), Node("Bad speculation", badSpeculation)), Node("No µop issued", None, Node("Front end bound", frontendBound, Node("Fetch latency bound", fetchLatencyBound), Node("Fetch bandwidth bound", fetchBandwidthBound, Node("MITE *", fe_MITE), Node("DSB *", fe_DSB), Node("LSD *", fe_LSD))), Node("Back end bound", None, Node("Core bound", coreBound), Node("Memory bound", memoryBound, Node("L1 bound", l1Bound, Node("DTLB load miss", l1_DTLBMiss), Node("Load blocked by store forwarding", l1_StoreForwardBlocked), Node("Lock latency", l1_LockLatency), Node("Split loads", l1_SplitLoads), Node("4K aliasing", l1_4KAliasing), Node("Fill buffer full", l1_FBFull)), Node("L2 bound", l2Bound), Node("L3 bound", l3Bound), Node("Ext mem bound", extMemoryBound, Node("Bandwidth *", extMem_BandwidthBound), Node("Latency *", extMem_LatencyBound)))))) def cpu_family_model(): family = model = None for l in open("/proc/cpuinfo").readlines(): if l.startswith("cpu family\t"): family = int(l.split(":")[1]) if l.startswith("model\t"): model = int(l.split(":")[1]) if family != None and model != None: return family, model print("failed to get CPU family and model from /proc/cpuinfo", file=sys.stderr) sys.exit(1) def main(): # Only Ivy Bridge for now. family, model = cpu_family_model() if (family, model) not in [(0x06, 0x3a), (0x06, 0x3e)]: print("unsupported CPU model %02x_%02xH" % (family, model), file=sys.stderr) sys.exit(1) events = tree.events() ctx = gather(events) tree.show(ctx) if __name__ == "__main__": main()