Skip to content

Add --pids and --max-depth to flame command and use Go implementation of stackcollapse-perf to eliminate Perl dependency #332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bddbd30
Use Go implementation of stackcollapse-perf to eliminate Perl depende…
harp-intel May 6, 2025
e6cb128
Add go.mod file for stackcollapse-perf module
harp-intel May 6, 2025
64bd60f
spelling
harp-intel May 6, 2025
9c0bff0
Remove stackcollapse-perf reset command from Makefile
harp-intel May 6, 2025
dbe2716
use go stackcollapse
harp-intel May 6, 2025
22da023
Use Go implementation of stackcollapse-perf to eliminate Perl depende…
harp-intel May 6, 2025
b29f02d
Add go.mod file for stackcollapse-perf module
harp-intel May 6, 2025
14317ba
spelling
harp-intel May 6, 2025
94b6662
Remove stackcollapse-perf reset command from Makefile
harp-intel May 6, 2025
4145397
use go stackcollapse
harp-intel May 6, 2025
61aa706
resolve conflict
harp-intel May 7, 2025
7c72161
fix out of range panic
harp-intel May 7, 2025
d41eb46
add maximum depth of flame graph rendering
harp-intel May 7, 2025
775255c
refactor and add unit tests
harp-intel May 8, 2025
ff19d06
Use Go implementation of stackcollapse-perf to eliminate Perl depende…
harp-intel May 6, 2025
811d8a6
Add go.mod file for stackcollapse-perf module
harp-intel May 6, 2025
556d58a
spelling
harp-intel May 6, 2025
d788587
Remove stackcollapse-perf reset command from Makefile
harp-intel May 6, 2025
c6c2797
use go stackcollapse
harp-intel May 6, 2025
d21319c
Use Go implementation of stackcollapse-perf to eliminate Perl depende…
harp-intel May 6, 2025
cfdb78c
Add go.mod file for stackcollapse-perf module
harp-intel May 6, 2025
cf72f78
Remove stackcollapse-perf reset command from Makefile
harp-intel May 6, 2025
bbf2669
fix out of range panic
harp-intel May 7, 2025
e4d4674
add maximum depth of flame graph rendering
harp-intel May 7, 2025
e3454b2
refactor and add unit tests
harp-intel May 8, 2025
08d4a5c
Merge branch 'stackcollapse' of github.com:intel/PerfSpect into stack…
harp-intel May 8, 2025
139f652
support multiple pids
harp-intel May 9, 2025
96adc0e
set cwd to temp dir in script
harp-intel May 9, 2025
f1db0a9
Merge branch 'main' into stackcollapse
harp-intel May 9, 2025
52c4a8f
more refactoring
harp-intel May 11, 2025
9203d3f
replace log with slog
harp-intel May 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use Go implementation of stackcollapse-perf to eliminate Perl depende…
…ncy.

Signed-off-by: Harper, Jason M <[email protected]>
  • Loading branch information
harp-intel committed May 8, 2025
commit ff19d06c7318fdff9b56b4b730072041153d750c
2 changes: 1 addition & 1 deletion internal/script/script_defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1410,7 +1410,7 @@ fi
rm -f "$perf_fp_data" "$perf_dwarf_data" "$perf_dwarf_folded" "$perf_fp_folded"
`,
Superuser: true,
Depends: []string{"perf", "stackcollapse-perf.pl"},
Depends: []string{"perf", "stackcollapse-perf"},
},
// lock analysis scripts
ProfileKernelLockScriptName: {
Expand Down
21 changes: 8 additions & 13 deletions tools/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
#

default: tools
.PHONY: default tools async-profiler avx-turbo cpuid dmidecode ethtool fio flamegraph ipmitool lshw lspci msr-tools pcm perf processwatch spectre-meltdown-checker sshpass stress-ng sysstat tsc turbostat
.PHONY: default tools async-profiler avx-turbo cpuid dmidecode ethtool fio ipmitool lshw lspci msr-tools pcm perf processwatch spectre-meltdown-checker sshpass stackcollapse-perf stress-ng sysstat tsc turbostat

tools: async-profiler avx-turbo cpuid dmidecode ethtool fio flamegraph ipmitool lshw lspci msr-tools pcm spectre-meltdown-checker sshpass stress-ng sysstat tsc turbostat
tools: async-profiler avx-turbo cpuid dmidecode ethtool fio ipmitool lshw lspci msr-tools pcm spectre-meltdown-checker sshpass stackcollapse-perf stress-ng sysstat tsc turbostat
mkdir -p bin
cp -R async-profiler bin/
cp avx-turbo/avx-turbo bin/
cp cpuid/cpuid bin/
cp dmidecode/dmidecode bin/
cp ethtool/ethtool bin/
cp fio/fio bin/
cp flamegraph/stackcollapse-perf.pl bin/
cp stackcollapse-perf/stackcollapse-perf bin/
cp ipmitool/src/ipmitool.static bin/ipmitool
cp lshw/src/lshw-static bin/lshw
cp lspci/lspci bin/
Expand Down Expand Up @@ -103,14 +103,6 @@ ifeq ("$(wildcard fio/config.log)","")
endif
cd fio && make

FLAMEGRAPH_VERSION := "master"
flamegraph:
ifeq ("$(wildcard flamegraph)","")
git clone https://github.com/brendangregg/FlameGraph.git flamegraph
# small modification to script to include module name in output
cd flamegraph && sed -i '382 a \\t\t\t\t$$func = \$$func."'" "'".\$$mod;\t# add module name' stackcollapse-perf.pl
endif

IPMITOOL_VERSION := "IPMITOOL_1_8_19"
ipmitool:
ifeq ("$(wildcard ipmitool)","")
Expand Down Expand Up @@ -213,6 +205,9 @@ ifeq ("$(wildcard sshpass)","")
endif
cd sshpass && make

stackcollapse-perf:
cd stackcollapse-perf && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build

STRESS_NG_VERSION := "V0.13.08"
stress-ng:
ifeq ("$(wildcard stress-ng)","")
Expand Down Expand Up @@ -252,14 +247,14 @@ reset:
cd dmidecode && git clean -fdx && git reset --hard
cd ethtool && git clean -fdx && git reset --hard
cd fio && git clean -fdx && git reset --hard
cd flamegraph && git clean -fdx && git reset --hard
cd ipmitool && git clean -fdx && git reset --hard
cd lshw && git clean -fdx && git reset --hard
cd lspci && git clean -fdx && git reset --hard
cd pcm && git clean -fdx && git reset --hard
cd msr-tools && git clean -fdx && git reset --hard
cd spectre-meltdown-checker
cd sshpass && make clean
cd stackcollapse-perf && git clean -fdx && git reset --hard
cd stress-ng && git clean -fdx && git reset --hard
cd sysstat && git clean -fdx && git reset --hard
cd tsc && rm -f tsc
Expand All @@ -275,5 +270,5 @@ libcrypt.tar.gz:
libs: glibc-2.19.tar.bz2 zlib.tar.gz libcrypt.tar.gz

oss-source: reset libs
tar --exclude-vcs -czf oss_source.tgz async-profiler/ cpuid/ dmidecode/ ethtool/ fio/ flamegraph/ ipmitool/ lshw/ lspci/ msr-tools/ pcm/ spectre-meltdown-checker/ sshpass/ stress-ng/ sysstat/ linux_turbostat/tools/power/x86/turbostat glibc-2.19.tar.bz2 zlib.tar.gz libcrypt.tar.gz
tar --exclude-vcs -czf oss_source.tgz async-profiler/ cpuid/ dmidecode/ ethtool/ fio/ ipmitool/ lshw/ lspci/ msr-tools/ pcm/ spectre-meltdown-checker/ sshpass/ stress-ng/ sysstat/ linux_turbostat/tools/power/x86/turbostat glibc-2.19.tar.bz2 zlib.tar.gz libcrypt.tar.gz
md5sum oss_source.tgz > oss_source.tgz.md5
269 changes: 269 additions & 0 deletions tools/stackcollapse-perf/stackcollapse-perf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
package main

// Copyright (C) 2021-2025 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause

// This code is a port of the Perl script stackcollapse-perf.pl from Brendan
// Gregg's Flamegraph project -- github.com/brendangregg/FlameGraph.
// All credit to Brendan Gregg for the original implementation and the flamegraph concept.

import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
)

// In the original Perl code, the following
// are command line arguments. For our use, we don't need to set them as flags,
// but we can keep them for compatibility with the original
var annotateKernel = false
var annotateJit = false

// var annotateAll = false
var includePname = true
var includePid = false
var includeTid = false
var includeAddrs = false
var tidyJava = true
var tidyGeneric = true

// var targetPname = ""
var eventFilter = ""

// var showInline = false
// var showContext = false
// var srcLineInInput = false

type StackAggregator struct {
collapsed map[string]int
}

func NewStackAggregator() *StackAggregator {
return &StackAggregator{collapsed: make(map[string]int)}
}

func (sa *StackAggregator) RememberStack(stack string, count int) {
sa.collapsed[stack] += count
}

func main() {
aggregator := NewStackAggregator()

var input *os.File
var err error

// Check if a file path is provided as a command-line argument
if len(os.Args) > 1 {
input, err = os.Open(os.Args[1]) // Open the file
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening file: %s\n", err)
os.Exit(1)
}
defer input.Close()
} else {
input = os.Stdin // Default to standard input
}
scanner := bufio.NewScanner(input)

var stack []string
var pname string
var mPeriod int
var mTid string
var mPid string

eventRegex := regexp.MustCompile(`^(\S.+?)\s+(\d+)\/*(\d+)*\s+`)
eventTypeRegex := regexp.MustCompile(`:\s*(\d+)*\s+(\S+):\s*$`)
stackLineRegex := regexp.MustCompile(`^\s*(\w+)\s*(.+) \((.*)\)`)
// inlineRegex := regexp.MustCompile(`(perf-\d+.map|kernel\.|\[[^\]]+\])`)
stripSymbolsRegex := regexp.MustCompile(`\+0x[\da-f]+$`)
stripIdRegex := regexp.MustCompile(`\.\(.*\)\.`)
stripAnonymousRegex := regexp.MustCompile(`\([^a]*anonymous namespace[^)]*\)`)
jitRegex := regexp.MustCompile(`/tmp/perf-\d+\.map`)

var eventDefaulted bool
var eventWarning bool

// main loop, read lines from stdin
for scanner.Scan() {
line := scanner.Text()

if strings.HasPrefix(line, "# cmdline") {
// loop through the command line arguments in reverse order
for i := len(os.Args) - 1; i > 0; i-- {
if !strings.HasPrefix(os.Args[i], "-") {
// not used -> target_pname = filepath.Base(os.Args[i])
break
}
}
}

// Skip remaining comments
if strings.HasPrefix(line, "#") {
continue
}

// End of stack
if line == "" {
if pname == "" {
continue
}
if includePname {
// prepend the process name to the stack
stack = append([]string{pname}, stack...)
}

if stack != nil {
aggregator.RememberStack(strings.Join(stack, ";"), mPeriod)
}
stack = nil
pname = ""
continue
}

// Event record start
if matches := eventRegex.FindStringSubmatch(line); matches != nil {
comm, pid, tid, period := matches[1], matches[2], matches[3], ""
if tid == "" {
tid = pid
pid = "?"
}

if eventMatches := eventTypeRegex.FindStringSubmatch(line); eventMatches != nil {
period = eventMatches[1]
event := eventMatches[2]

if eventFilter == "" {
eventFilter = event
eventDefaulted = true
} else if event != eventFilter {
if eventDefaulted && !eventWarning {
fmt.Fprintf(os.Stderr, "Filtering for events of type: %s\n", event)
eventWarning = true
}
continue
}
}

if period == "" {
period = "1"
}
periodInt, err := strconv.Atoi(period)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing period: %s\n", err)
continue
}
mPid, mTid, mPeriod = pid, tid, periodInt

if includeTid {
pname = fmt.Sprintf("%s-%s/%s", comm, mPid, mTid)
} else if includePid {
pname = fmt.Sprintf("%s-%s", comm, mPid)
} else {
pname = comm
}
pname = strings.ReplaceAll(pname, " ", "_")
continue
// Stack line
} else if matches := stackLineRegex.FindStringSubmatch(line); matches != nil {
if pname == "" {
continue
}
pc, rawFunc, mod := matches[1], matches[2], matches[3]

// skip for now as showInline is always false
// if showInline && !inlineRegex.MatchString(mod) {
// inlineRes := inline(pc, rawFunc, mod)
// if inlineRes != "" && inlineRes != "??" && inlineRes != "??:??:0" {
// // prepend the inline result to the stack
// stack = append([]string{inlineRes}, stack...)
// continue
// }
//}

// strip symbol offsets from rawFunc
// symbol offsets match this regex: \+0x[\da-f]+$
rawFunc = stripSymbolsRegex.ReplaceAllString(rawFunc, "")

// skip process names
if strings.HasPrefix(rawFunc, "(") {
continue
}
// var isUnknown bool
var inline []string
for funcname := range strings.SplitSeq(rawFunc, "->") {
if funcname == "[unknown]" { // use module name instead, if known
if mod != "[unknown]" {
funcname = filepath.Base(mod)
} else {
funcname = "unknown"
// isUnknown = true
}

if includeAddrs {
funcname = fmt.Sprintf("[%s <%s>]", funcname, pc)
} else {
funcname = fmt.Sprintf("[%s]", funcname)
}
}
if tidyGeneric {
funcname = strings.ReplaceAll(funcname, ";", ":")
if matches := stripIdRegex.FindStringSubmatch(funcname); matches != nil {
index := stripAnonymousRegex.FindStringIndex(funcname)
funcname = funcname[0:index[0]]
}
funcname = strings.ReplaceAll(funcname, "\"", "")
funcname = strings.ReplaceAll(funcname, "'", "")
}
if tidyJava {
if strings.Contains(funcname, "/") {
// strip the leading L
funcname = strings.TrimPrefix(funcname, "L")
}
}
// annotations
if len(inline) > 0 {
if !strings.Contains(funcname, "_[i]") {
funcname = fmt.Sprintf("%s_[i]", funcname)
} else if annotateKernel && (strings.HasPrefix(funcname, "[") || strings.HasSuffix(funcname, "vmlinux")) && !strings.Contains(mod, "unknown") {
funcname = fmt.Sprintf("%s_[k]", funcname)
} else if annotateJit && jitRegex.MatchString(funcname) {
if !strings.Contains(funcname, "_[j]") {
funcname = fmt.Sprintf("%s_[j]", funcname)
}
}
}

// source lines
// skip for now since srcLineInInput is always false
// if srcLineInInput && !isUnknown {
// }

inline = append(inline, funcname)
}

// prepend inline array to the stack array
if len(inline) > 0 {
stack = append(inline, stack...)
}

} else {
fmt.Fprintf(os.Stderr, "Unknown line format: %s\n", line)
}
}

// Output results
keys := make([]string, 0, len(aggregator.collapsed))
for k := range aggregator.collapsed {
keys = append(keys, k)
}
sort.Strings(keys)

for _, k := range keys {
fmt.Printf("%s %d\n", k, aggregator.collapsed[k])
}
}