blob: 27d0354ddfa8f6885ab3309e3b4870385b7c32fc [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 bloaty
import (
"bufio"
"bytes"
"fmt"
"os"
"os/exec"
"sort"
"strings"
"sync"
)
type bloatyOutput struct {
data row
file string
err error
}
// TODO(jakehehrlich): Add reading ids.txt to elflib, since there are now three
// different tools that each need to read it for mostly unrelated reasons
func getFiles(idsPath string) ([]string, error) {
out := []string{}
idsFile, err := os.Open(idsPath)
if err != nil {
return nil, err
}
defer idsFile.Close()
scanner := bufio.NewScanner(idsFile)
for line := 1; scanner.Scan(); line++ {
parts := strings.SplitN(scanner.Text(), " ", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid ids.txt: error on line %d", line)
}
out = append(out, parts[1])
}
return out, nil
}
func run(bloatyPath, file string, out chan<- bloatyOutput) {
args := []string{
"-d", "segments,compileunits,symbols",
"-s", "file",
"--tsv",
"-n", "0",
file,
}
cmd := exec.Command(bloatyPath, args...)
fmt.Printf("running: %s\n", file)
stdout, err := cmd.StdoutPipe()
if err != nil {
out <- bloatyOutput{err: fmt.Errorf("pipe: %s: %s", file, err)}
return
}
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Start(); err != nil {
out <- bloatyOutput{err: fmt.Errorf("start (%s): %s: %s\n", err, file, cmd.Stderr)}
stdout.Close()
return
}
err = ReadCSV(stdout, out, file)
if err != nil {
out <- bloatyOutput{err: fmt.Errorf("csv: %s: %s", file, err)}
stdout.Close()
return
}
if err := cmd.Wait(); err != nil {
out <- bloatyOutput{err: fmt.Errorf("wait (%s): %s: %s\n", err, file, cmd.Stderr)}
return
}
}
func updateSymbol(newSym *row, file string, sym *Symbol) {
sym.Name = newSym.Symbol
sym.Vmsz += newSym.Vmsz
sym.Filesz += newSym.Filesz
sym.Binaries = append(sym.Binaries, file)
}
func addRowToOutput(r *row, file string, output map[string]*Segment) {
if _, ok := output[r.Seg]; !ok {
output[r.Seg] = &Segment{make(map[string]*File)}
}
seg := output[r.Seg]
if _, ok := seg.Files[r.File]; !ok {
seg.Files[r.File] = &File{Symbols: make(map[string]*Symbol)}
}
f := seg.Files[r.File]
if _, ok := f.Symbols[r.Symbol]; !ok {
f.Symbols[r.Symbol] = &Symbol{}
}
updateSymbol(r, file, f.Symbols[r.Symbol])
seg.Files[r.File] = f
output[r.Seg] = seg
}
func getTopN(fileSizes map[string]uint64, topFiles, topSyms uint64, output *map[string]*Segment) {
// If both topFiles and topSyms are 0, bail early because we're returning everything.
if topFiles == 0 && topSyms == 0 {
return
}
type sortedFile struct {
name string
size uint64
}
smallFiles := make(map[string]uint64)
if topFiles > 0 && topFiles < uint64(len(fileSizes)) {
var all []struct {
name string
size uint64
}
for name, size := range fileSizes {
all = append(all, sortedFile{name, size})
}
sort.Slice(all, func(i, j int) bool {
return all[i].size < all[j].size
})
for _, d := range all[:uint64(len(all))-topFiles] {
smallFiles[d.name] = d.size
}
}
for _, segData := range *output {
smallFilesSize := uint64(0)
for file, fileData := range segData.Files {
smallSyms := Symbol{Name: "all small syms"}
// If the file labeled a small file, add to small files size and delete the sym data.
if size, exists := smallFiles[file]; exists {
smallFilesSize += size
delete(segData.Files, file)
} else if topSyms > 0 && topSyms < uint64(len(fileData.Symbols)) {
var all []*Symbol
for _, sym := range fileData.Symbols {
all = append(all, sym)
}
sort.Slice(all, func(i, j int) bool {
return all[i].Filesz < all[j].Filesz
})
for _, d := range all[:uint64(len(all))-topSyms] {
if sym, exists := fileData.Symbols[d.Name]; exists {
smallSyms.Vmsz += sym.Vmsz
smallSyms.Filesz += sym.Filesz
delete(fileData.Symbols, d.Name)
}
}
}
if topSyms > 0 {
fileData.Symbols["all small syms"] = &smallSyms
}
}
if topFiles > 0 {
segData.Files["all small files"] = &File{TotalFilesz: smallFilesSize}
}
}
}
// RunBloaty runs bloaty on all files in ids.txt, and returns a mapping of the
// symbols and files by segment.
func RunBloaty(bloatyPath, idsPath string, topFiles, topSyms uint64, jobs int) (map[string]*Segment, error) {
files, err := getFiles(idsPath)
if err != nil {
return nil, err
}
var wg sync.WaitGroup
data := make(chan bloatyOutput)
output := make(map[string]*Segment)
fileSizes := make(map[string]uint64)
// Start up the data collection process.
dataComplete := make(chan struct{}, 1)
go func() {
for d := range data {
if d.err != nil {
fmt.Printf("%v", d.err)
continue
}
addRowToOutput(&d.data, d.file, output)
fileSizes[d.data.File] += d.data.Filesz
}
dataComplete <- struct{}{}
}()
// Only allow up to max jobs concurrent executions.
sem := make(chan struct{}, jobs)
// Start a bloaty run on each file.
for _, file := range files {
wg.Add(1)
sem <- struct{}{}
go func(file string) {
defer wg.Done()
run(bloatyPath, file, data)
<-sem
}(file)
}
wg.Wait()
close(data)
<-dataComplete
getTopN(fileSizes, topFiles, topSyms, &output)
return output, nil
}