You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by ma...@apache.org on 2017/03/06 21:44:18 UTC

[18/41] incubator-mynewt-newt git commit: newt: More detailed size report command

newt: More detailed size report command

This patch improves the output of the size command. New flags
were added for this purpose:

Flags:
  -F, --flash   Print FLASH statistics
  -R, --ram     Print RAM statistics

The size statistics are broken down into a tree-like structure, where
the leaves are symbols and branches are folders and files. For
each tree element there its size in bytes and percentage contribution
to the total size of the memory region.

Path                           Size       %
=============================================
(...)
libc                             32     0.49%
  baselibc                       32     0.49%
    src                          32     0.49%
      malloc.c                   32     0.49%
        __malloc_head            24     0.37%
        malloc_lock               4     0.06%
        malloc_unlock             4     0.06%
(...)

Moreover, there is more memory region info available after specifying
verbose flag(-v):

Mem FLASH: 0x00008000-0x00042000
Mem RAM:   0x20000000-0x20010000

Mem: FLASH
Name                       Size
.ARM.extab                    0
.ARM.exidx                   24
.imghdr                      32
.text                     18624
Total                     18680

Mem: RAM
Name                       Size
.bss                       5656
.stack_dummy                432
.vector_relocation          216
.data                       256
.bssnz                        0
Total                      6560


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/commit/6493620b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/tree/6493620b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/diff/6493620b

Branch: refs/heads/mynewt_1_0_0
Commit: 6493620b088701568d663fae65f67e9b418259f9
Parents: ffdbc54
Author: Micha\u0142 Narajowski <mi...@codecoup.pl>
Authored: Tue Feb 7 12:34:37 2017 +0100
Committer: Marko Kiiskila <ma...@runtime.io>
Committed: Mon Mar 6 13:36:01 2017 -0800

----------------------------------------------------------------------
 newt/builder/size.go        |  34 +++-
 newt/builder/size_report.go | 332 +++++++++++++++++++++++++++++++++++++++
 newt/builder/symbol_tree.go | 194 +++++++++++++++++++++++
 newt/cli/build_cmds.go      |  18 ++-
 4 files changed, 574 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/6493620b/newt/builder/size.go
----------------------------------------------------------------------
diff --git a/newt/builder/size.go b/newt/builder/size.go
index 970db2d..5ce2939 100644
--- a/newt/builder/size.go
+++ b/newt/builder/size.go
@@ -106,8 +106,8 @@ func MakePkgSize(name string, memSections map[string]*MemSection) *PkgSize {
 /*
  * Go through GCC generated mapfile, and collect info about symbol sizes
  */
-func ParseMapFileSizes(fileName string) (map[string]*PkgSize, map[string]*MemSection,
-	error) {
+func ParseMapFileSizes(fileName string) (map[string]*PkgSize,
+	map[string]*MemSection, error) {
 	var state int = 0
 
 	file, err := os.Open(fileName)
@@ -375,3 +375,33 @@ func (b *Builder) Size() error {
 
 	return nil
 }
+
+func (t *TargetBuilder) SizeReport(ram, flash bool) error {
+
+	err := t.PrepBuild()
+
+	if err != nil {
+		return err
+	}
+
+	fmt.Printf("Size of Application Image: %s\n", t.AppBuilder.buildName)
+	err = t.AppBuilder.SizeReport(ram, flash)
+
+	if err == nil {
+		if t.LoaderBuilder != nil {
+			fmt.Printf("Size of Loader Image: %s\n", t.LoaderBuilder.buildName)
+			err = t.LoaderBuilder.SizeReport(ram, flash)
+		}
+	}
+
+	return err
+}
+
+func (b *Builder) SizeReport(ram, flash bool) error {
+	srcBase := b.targetBuilder.GetTarget().App().Repo().Path() + "/"
+	err := SizeReport(b.AppElfPath(), srcBase, ram, flash)
+	if err != nil {
+		return util.NewNewtError(err.Error())
+	}
+	return nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/6493620b/newt/builder/size_report.go
----------------------------------------------------------------------
diff --git a/newt/builder/size_report.go b/newt/builder/size_report.go
new file mode 100644
index 0000000..5c9fa02
--- /dev/null
+++ b/newt/builder/size_report.go
@@ -0,0 +1,332 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package builder
+
+import (
+	"bufio"
+	"fmt"
+	"mynewt.apache.org/newt/util"
+	"os"
+	"os/exec"
+	"strconv"
+	"strings"
+)
+
+func runNmCommand(elfFilePath string) ([]byte, error) {
+	var (
+		cmdOut []byte
+		err    error
+	)
+	cmdName := "arm-none-eabi-nm"
+	cmdArgs := []string{elfFilePath, "-S", "-l", "--size-sort", "--radix=d"}
+
+	if cmdOut, err = exec.Command(cmdName, cmdArgs...).Output(); err != nil {
+		fmt.Fprintln(os.Stderr, "There was an error running nm command: ", err)
+		os.Exit(1)
+	}
+
+	return cmdOut, err
+}
+
+func runObjdumpCommand(elfFilePath string, params string) ([]byte, error) {
+	var (
+		cmdOut []byte
+		err    error
+	)
+	cmdName := "arm-none-eabi-objdump"
+	cmdArgs := []string{params, elfFilePath}
+	if cmdOut, err = exec.Command(cmdName, cmdArgs...).Output(); err != nil {
+		fmt.Fprintln(os.Stderr, "There was an error running objdump command: ",
+			err)
+		os.Exit(1)
+	}
+
+	return cmdOut, err
+}
+
+func loadSymbolsAndPaths(elfFilePath, pathToStrip string) (map[string]string,
+	error) {
+	symbolsPath := make(map[string]string)
+
+	nmOut, err := runNmCommand(elfFilePath)
+	if err != nil {
+		return nil, err
+	}
+
+	lines := strings.Split(string(nmOut), "\n")
+
+	for _, line := range lines {
+		fields := strings.Fields(strings.Replace(line, "\t", " ", -1))
+		if len(fields) < 4 {
+			continue
+		}
+		var path string
+
+		if len(fields) < 5 {
+			path = "(other)"
+		} else {
+			path = strings.Split(fields[4], ":")[0]
+		}
+		if pathToStrip != "" {
+			if strings.Contains(path, pathToStrip) {
+				path = strings.Replace(path, pathToStrip, "", -1)
+			} else {
+				path = "(other)"
+			}
+		}
+		symbolsPath[fields[3]] = path
+	}
+	return symbolsPath, nil
+}
+
+func MakeSymbol(name string, section string, size uint64) *Symbol {
+	symbol := &Symbol{
+		name,
+		section,
+		size,
+	}
+	return symbol
+}
+
+type MemoryRegion struct {
+	Name         string
+	Offset       uint64
+	EndOff       uint64
+	TotalSize    uint64
+	SectionNames map[string]struct{}
+	NamesSizes   map[string]uint64
+}
+
+func MakeMemoryRegion() *MemoryRegion {
+	section := &MemoryRegion{
+		"", 0, 0, 0,
+		make(map[string]struct{}),
+		make(map[string]uint64),
+	}
+	return section
+}
+
+func (m *MemoryRegion) PartOf(addr uint64) bool {
+	return addr >= m.Offset && addr < m.EndOff
+}
+
+func loadSymbolsAndSections(elfFilePath string) (map[string]*Symbol, error) {
+	objdumpOut, err := runObjdumpCommand(elfFilePath, "-tw")
+	if err != nil {
+		return nil, err
+	}
+
+	lines := strings.Split(string(objdumpOut), "\n")
+	symbols := make(map[string]*Symbol)
+	for _, line := range lines {
+		fields := strings.Fields(strings.Replace(line, "\t", " ", -1))
+
+		if len(fields) == 5 {
+			size, err := strconv.ParseUint(fields[3], 16, 64)
+			if err != nil {
+				continue
+			}
+			symbols[fields[4]] = MakeSymbol(fields[4], fields[2], size)
+		} else if len(fields) == 6 {
+			size, err := strconv.ParseUint(fields[4], 16, 64)
+			if err != nil {
+				continue
+			}
+			symbols[fields[5]] = MakeSymbol(fields[5], fields[3], size)
+		}
+
+	}
+
+	return symbols, nil
+}
+
+func generateMemoryRegions(elfFilePath string) (*MemoryRegion, *MemoryRegion,
+	error) {
+
+	mapFile := elfFilePath + ".map"
+	flashRegion, ramRegion, err := parseMapFileRegions(mapFile)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	objdumpOut, err := runObjdumpCommand(elfFilePath, "-hw")
+	if err != nil {
+		return nil, nil, err
+	}
+
+	lines := strings.Split(string(objdumpOut), "\n")
+	for _, line := range lines {
+		fields := strings.Fields(line)
+		if len(fields) < 7 {
+			continue
+		}
+		size, err := strconv.ParseUint(fields[2], 16, 64)
+		if err != nil {
+			continue
+		}
+		address, err := strconv.ParseUint(fields[3], 16, 64)
+		if err != nil {
+			continue
+		}
+
+		if flashRegion.PartOf(address) {
+			flashRegion.TotalSize += size
+			flashRegion.SectionNames[fields[1]] = struct{}{}
+			flashRegion.NamesSizes[fields[1]] = size
+			continue
+		}
+
+		if ramRegion.PartOf(address) {
+			ramRegion.TotalSize += size
+			ramRegion.SectionNames[fields[1]] = struct{}{}
+			ramRegion.NamesSizes[fields[1]] = size
+			continue
+		}
+	}
+
+	return flashRegion, ramRegion, nil
+}
+
+/*
+ * Go through GCC generated mapfile, and collect info about symbol sizes
+ */
+func parseMapFileRegions(fileName string) (*MemoryRegion, *MemoryRegion,
+	error) {
+	var state int = 0
+
+	file, err := os.Open(fileName)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	flashRegion := MakeMemoryRegion()
+	ramRegion := MakeMemoryRegion()
+
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		switch state {
+		case 0:
+			if strings.Contains(scanner.Text(), "Memory Configuration") {
+				state = 1
+			}
+		case 1:
+			if strings.Contains(scanner.Text(), "Origin") {
+				state = 2
+			}
+		case 2:
+			if strings.Contains(scanner.Text(), "*default*") {
+				state = 3
+				continue
+			}
+			array := strings.Fields(scanner.Text())
+			offset, err := strconv.ParseUint(array[1], 0, 64)
+			if err != nil {
+				continue
+			}
+			size, err := strconv.ParseUint(array[2], 0, 64)
+			if err != nil {
+				continue
+			}
+			if strings.EqualFold(array[0], "flash") {
+				flashRegion.Name = array[0]
+				flashRegion.Offset = offset
+				flashRegion.EndOff = offset + size
+			} else if strings.EqualFold(array[0], "ram") {
+				ramRegion.Name = array[0]
+				ramRegion.Offset = offset
+				ramRegion.EndOff = offset + size
+			}
+		case 3:
+			fallthrough
+		default:
+			return flashRegion, ramRegion, nil
+		}
+
+	}
+	return flashRegion, flashRegion, nil
+}
+
+func logMemoryRegionStats(flashRegion, ramRegion *MemoryRegion) {
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "%-10s 0x%08x-0x%08x\n",
+		"Mem FLASH:", flashRegion.Offset, flashRegion.EndOff)
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "%-10s 0x%08x-0x%08x\n",
+		"Mem RAM:", ramRegion.Offset, ramRegion.EndOff)
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "\n")
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "Mem: FLASH\n")
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "%-20s %10s\n", "Name", "Size")
+	for sectionName, size := range flashRegion.NamesSizes {
+		util.StatusMessage(util.VERBOSITY_VERBOSE, "%-20s %10d\n",
+			sectionName, size)
+	}
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "%-20s %10d\n", "Total",
+		flashRegion.TotalSize)
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "\n")
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "Mem: RAM\n")
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "%-20s %10s\n", "Name", "Size")
+	for sectionName, size := range ramRegion.NamesSizes {
+		util.StatusMessage(util.VERBOSITY_VERBOSE, "%-20s %10d\n",
+			sectionName, size)
+	}
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "%-20s %10d\n", "Total",
+		ramRegion.TotalSize)
+	util.StatusMessage(util.VERBOSITY_VERBOSE, "\n")
+}
+
+func SizeReport(elfFilePath, srcBase string, ram bool, flash bool) error {
+	symbolsPath, err := loadSymbolsAndPaths(elfFilePath, srcBase)
+	if err != nil {
+		return err
+	}
+	loadedSectionSizes, err := loadSymbolsAndSections(elfFilePath)
+	if err != nil {
+		return err
+	}
+	flashRegion, ramRegion, err := generateMemoryRegions(elfFilePath)
+	if err != nil {
+		return err
+	}
+
+	logMemoryRegionStats(flashRegion, ramRegion)
+
+	startPath := "."
+
+	if flash {
+		flashNodes := newFolder(startPath)
+		for _, symbol := range loadedSectionSizes {
+			if _, ok := flashRegion.SectionNames[symbol.Section]; ok {
+				flashNodes.addSymbol(symbol, symbolsPath[symbol.Name])
+			}
+		}
+		fmt.Println("FLASH report:")
+		fmt.Printf("%v", flashNodes.ToString(flashRegion.TotalSize))
+	}
+
+	if ram {
+		ramNodes := newFolder(startPath)
+		for _, symbol := range loadedSectionSizes {
+			if _, ok := ramRegion.SectionNames[symbol.Section]; ok {
+				ramNodes.addSymbol(symbol, symbolsPath[symbol.Name])
+			}
+		}
+		fmt.Println("RAM report:")
+		fmt.Printf("%v", ramNodes.ToString(ramRegion.TotalSize))
+	}
+	return nil
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/6493620b/newt/builder/symbol_tree.go
----------------------------------------------------------------------
diff --git a/newt/builder/symbol_tree.go b/newt/builder/symbol_tree.go
new file mode 100644
index 0000000..ea6202b
--- /dev/null
+++ b/newt/builder/symbol_tree.go
@@ -0,0 +1,194 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package builder
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+)
+
+type Symbol struct {
+	Name    string
+	Section string
+	Size    uint64
+}
+
+type File struct {
+	Name    string
+	Symbols map[string]*Symbol
+}
+
+type Folder struct {
+	Name    string
+	Files   map[string]*File
+	Folders map[string]*Folder
+}
+
+func newFolder(name string) *Folder {
+	return &Folder{name, make(map[string]*File), make(map[string]*Folder)}
+}
+
+func newFile(name string) *File {
+	return &File{name, make(map[string]*Symbol)}
+}
+
+func (f *File) sumSize() uint64 {
+	var sum uint64
+	for _, symbol := range f.Symbols {
+		sum += symbol.Size
+	}
+	return sum
+}
+
+func (f *Folder) sumSize() uint64 {
+	var sum uint64
+	for _, folder := range f.Folders {
+		sum += folder.sumSize()
+	}
+
+	for _, file := range f.Files {
+		sum += file.sumSize()
+	}
+	return sum
+}
+
+func (f *Folder) getFolder(name string) *Folder {
+	if nextF, ok := f.Folders[name]; ok {
+		return nextF
+	} else {
+		f.Folders[name] = newFolder(name)
+		return f.Folders[name]
+	}
+	return &Folder{} // cannot happen
+}
+
+func (f *Folder) getFile(name string) *File {
+	if nextF, ok := f.Files[name]; ok {
+		return nextF
+	} else {
+		f.Files[name] = newFile(name)
+		return f.Files[name]
+	}
+	return &File{} // cannot happen
+}
+
+func (f *File) getSymbol(name string) *Symbol {
+	if nextF, ok := f.Symbols[name]; ok {
+		return nextF
+	} else {
+		f.Symbols[name] = &Symbol{name, "", 0}
+		return f.Symbols[name]
+	}
+	return &Symbol{} // cannot happen
+}
+
+func (f *Folder) addFolder(path []string) *Folder {
+	if len(path) == 1 {
+		// last segment == new folder
+		return f.getFolder(path[0])
+	} else {
+		return f.getFolder(path[0]).addFolder(path[1:])
+	}
+}
+
+func (f *Folder) addFile(path []string) *File {
+	if len(path) == 1 {
+		// last segment == file
+		return f.getFile(path[0])
+	} else {
+		return f.getFolder(path[0]).addFile(path[1:])
+	}
+}
+
+func (f *Folder) addSymbol(symbol *Symbol, path string) *Symbol {
+	segments := strings.Split(path, "/")
+	file := f.addFile(segments)
+	sym := file.getSymbol(symbol.Name)
+	sym.Section = symbol.Section
+	sym.Size += symbol.Size
+	return sym
+}
+
+func (f *File) String(indent string, level int, total uint64) string {
+	var str string
+	if f.sumSize() <= 0 {
+		return ""
+	}
+	size := f.sumSize()
+	percent := 100 * float64(size) / float64(total)
+	str += fmt.Sprintf("%-80s %20d %8.2f%%\n", strings.Repeat(indent, level)+
+		f.Name, size, percent)
+
+	var sorted []string
+	for symName := range f.Symbols {
+		sorted = append(sorted, symName)
+	}
+	sort.Strings(sorted)
+	for _, sym := range sorted {
+		size := f.Symbols[sym].Size
+		percent := 100 * float64(size) / float64(total)
+		if f.Symbols[sym].Size > 0 {
+			str += fmt.Sprintf("%-80s %20d %8.2f%%\n",
+				strings.Repeat(indent, level+1)+
+					f.Symbols[sym].Name,
+				size, percent)
+		}
+	}
+	return str
+}
+
+func (f *Folder) StringRec(indent string, level int, total uint64) string {
+	var str string
+
+	var sorted []string
+	for folderName := range f.Folders {
+		sorted = append(sorted, folderName)
+	}
+	for fileName := range f.Files {
+		sorted = append(sorted, fileName)
+	}
+	sort.Strings(sorted)
+
+	for _, name := range sorted {
+		if folder, ok := f.Folders[name]; ok {
+			size := folder.sumSize()
+			percent := 100 * float64(size) / float64(total)
+			str += fmt.Sprintf("%-80s %20d %8.2f%%\n",
+				strings.Repeat(indent, level)+folder.Name, size, percent)
+			str += folder.StringRec(indent, level+1, total)
+		} else {
+			str += f.Files[name].String(indent, level, total)
+		}
+	}
+	return str
+}
+
+func (f *Folder) ToString(total uint64) string {
+	indent := "  "
+	var str string
+	str += fmt.Sprintf("%-90s %10s %9s\n", "Path", "Size", "%")
+	str += strings.Repeat("=", 111) + "\n"
+	str += f.StringRec(indent, 0, total)
+	str += strings.Repeat("=", 111) + "\n"
+	str += fmt.Sprintf("%90s %10d\n",
+		"Total symbol size (i.e. excluding padding, etc.)", f.sumSize())
+	return str
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/6493620b/newt/cli/build_cmds.go
----------------------------------------------------------------------
diff --git a/newt/cli/build_cmds.go b/newt/cli/build_cmds.go
index 43cf47e..f0789db 100644
--- a/newt/cli/build_cmds.go
+++ b/newt/cli/build_cmds.go
@@ -358,7 +358,7 @@ func debugRunCmd(cmd *cobra.Command, args []string) {
 	}
 }
 
-func sizeRunCmd(cmd *cobra.Command, args []string) {
+func sizeRunCmd(cmd *cobra.Command, args []string, ram bool, flash bool) {
 	if len(args) < 1 {
 		NewtUsage(cmd, util.NewNewtError("Must specify target"))
 	}
@@ -375,6 +375,13 @@ func sizeRunCmd(cmd *cobra.Command, args []string) {
 		NewtUsage(nil, err)
 	}
 
+	if ram || flash {
+		if err := b.SizeReport(ram, flash); err != nil {
+			NewtUsage(cmd, err)
+		}
+		return
+	}
+
 	if err := b.Size(); err != nil {
 		NewtUsage(cmd, err)
 	}
@@ -452,13 +459,20 @@ func AddBuildCommands(cmd *cobra.Command) {
 	sizeHelpText := "Calculate the size of target components specified by " +
 		"<target-name>."
 
+	var ram, flash bool
 	sizeCmd := &cobra.Command{
 		Use:   "size <target-name>",
 		Short: "Size of target components",
 		Long:  sizeHelpText,
-		Run:   sizeRunCmd,
+		Run: func(cmd *cobra.Command, args []string) {
+			sizeRunCmd(cmd, args, ram, flash)
+		},
 	}
 
+	sizeCmd.Flags().BoolVarP(&ram, "ram", "R", false, "Print RAM statistics")
+	sizeCmd.Flags().BoolVarP(&flash, "flash", "F", false,
+		"Print FLASH statistics")
+
 	cmd.AddCommand(sizeCmd)
 	AddTabCompleteFn(sizeCmd, targetList)
 }