Home > Software engineering >  How to check the size of packages linked into my Go code
How to check the size of packages linked into my Go code

Time:01-20

Following up with How do I check the size of a Go project?

The conclusion was:

in order to get a true sense of how much extra weight importing certain packages, one has to look at all of the pkg's sub-dependencies as well.

That's totally understandable. My question is,

Is there anyway that I can know how much space each component is taking in my compiled binary, the Go runtime, the dependencies and sub-dependencies packages, and my own code.

I vaguely remember reading something like this before (when go enhanced its linker maybe).
If there has never been such discussion before, then is there any way the go or even c linker can look into the my compiled binary, and reveal something that I can further parse myself?

CodePudding user response:

The binary will contain debug symbols which we can use to figure out how many space each package takes up.

I wrote a basic program to do this since I don't know of any tool that does this:

package main

import (
    "debug/elf"
    "fmt"
    "os"
    "runtime"
    "sort"
    "strings"

    "github.com/go-delve/delve/pkg/proc"
)

func main() {
    // Use delve to decode the DWARF section
    binInfo := proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH)
    err := binInfo.AddImage(os.Args[1], 0)
    if err != nil {
        panic(err)
    }

    // Make a list of unique packages
    pkgs := make([]string, 0, len(binInfo.PackageMap))
    for _, fullPkgs := range binInfo.PackageMap {
        for _, fullPkg := range fullPkgs {
            exists := false
            for _, pkg := range pkgs {
                if fullPkg == pkg {
                    exists = true
                    break
                }
            }
            if !exists {
                pkgs = append(pkgs, fullPkg)
            }
        }
    }
    // Sort them for a nice output
    sort.Strings(pkgs)

    // Parse the ELF file ourselfs
    elfFile, err := elf.Open(os.Args[1])
    if err != nil {
        panic(err)
    }

    // Get the symbol table
    symbols, err := elfFile.Symbols()
    if err != nil {
        panic(err)
    }

    usage := make(map[string]map[string]int)

    for _, sym := range symbols {
        if sym.Section == elf.SHN_UNDEF || sym.Section >= elf.SectionIndex(len(elfFile.Sections)) {
            continue
        }

        sectionName := elfFile.Sections[sym.Section].Name

        symPkg := ""
        for _, pkg := range pkgs {
            if strings.HasPrefix(sym.Name, pkg) {
                symPkg = pkg
                break
            }
        }
        // Symbol doesn't belong to a known package
        if symPkg == "" {
            continue
        }

        pkgStats := usage[symPkg]
        if pkgStats == nil {
            pkgStats = make(map[string]int)
        }

        pkgStats[sectionName]  = int(sym.Size)
        usage[symPkg] = pkgStats
    }

    for _, pkg := range pkgs {
        sections, exists := usage[pkg]
        if !exists {
            continue
        }

        fmt.Printf("%s:\n", pkg)
        for section, size := range sections {
            fmt.Printf("s:            
  •  Tags:  
  • Related