Skip to content

Commit

Permalink
pyroscope.java: bump async-profiler binaries (#2341)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleks-p authored Jan 7, 2025
1 parent 0b63535 commit 11d2d05
Show file tree
Hide file tree
Showing 18 changed files with 208 additions and 230 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ Main (unreleased)

- Update `prometheus.write.queue` library for performance increases in cpu. (@mattdurham)

- Update `async-profiler` binaries for `pyroscope.java` to 3.0-fa937db (@aleks-p)

### Bugfixes

- Fixed issue with automemlimit logging bad messages and trying to access cgroup on non-linux builds (@dehaansa)
Expand Down
199 changes: 26 additions & 173 deletions internal/component/pyroscope/java/asprof/asprof.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build linux && (amd64 || arm64)
//go:build (linux || darwin) && (amd64 || arm64)

package asprof

Expand All @@ -12,51 +12,25 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"

"github.com/prometheus/procfs"
)

var fsMutex sync.Mutex

// separte dirs for glibc & musl
type Distribution struct {
extractedDir string
version int
}

func (d *Distribution) binaryLauncher() bool {
return d.version >= 210
}

func (d *Distribution) LibPath() string {
if d.binaryLauncher() {
return filepath.Join(d.extractedDir, "lib/libasyncProfiler.so")
}
return filepath.Join(d.extractedDir, "build/libasyncProfiler.so")
}

func (d *Distribution) JattachPath() string {
if d.binaryLauncher() {
return ""
}
return filepath.Join(d.extractedDir, "build/jattach")
}

func (d *Distribution) LauncherPath() string {
if d.binaryLauncher() {
return filepath.Join(d.extractedDir, "bin/asprof")
}
return filepath.Join(d.extractedDir, "profiler.sh")
return filepath.Join(d.extractedDir, "bin/asprof")
}

type Profiler struct {
tmpDir string
extractOnce sync.Once
glibcDist *Distribution
muslDist *Distribution
dist *Distribution
extractError error
tmpDirMarker any
archiveHash string
Expand All @@ -66,15 +40,20 @@ type Profiler struct {
type Archive struct {
data []byte
version int
format int
}

const (
ArchiveFormatTarGz = iota
ArchiveFormatZip
)

func NewProfiler(tmpDir string, archive Archive) *Profiler {
res := &Profiler{tmpDir: tmpDir, glibcDist: new(Distribution), muslDist: new(Distribution), tmpDirMarker: "alloy-asprof"}
res := &Profiler{tmpDir: tmpDir, dist: new(Distribution), tmpDirMarker: "alloy-asprof"}
sum := sha1.Sum(archive.data)
hexSum := hex.EncodeToString(sum[:])
res.archiveHash = hexSum
res.glibcDist.version = archive.version
res.muslDist.version = archive.version
res.dist.version = archive.version
res.archive = archive
return res
}
Expand All @@ -99,96 +78,8 @@ func (p *Profiler) Execute(dist *Distribution, argv []string) (string, string, e
return stdout.String(), stderr.String(), nil
}

func (p *Profiler) CopyLib(dist *Distribution, pid int) error {
fsMutex.Lock()
defer fsMutex.Unlock()
libData, err := os.ReadFile(dist.LibPath())
if err != nil {
return err
}
launcherData, err := os.ReadFile(dist.LauncherPath())
if err != nil {
return err
}
procRoot := ProcessPath("/", pid)
procRootFile, err := os.Open(procRoot)
if err != nil {
return fmt.Errorf("failed to open proc root %s: %w", procRoot, err)
}
defer procRootFile.Close()
dstLibPath := strings.TrimPrefix(dist.LibPath(), "/")
dstLauncherPath := strings.TrimPrefix(dist.LauncherPath(), "/")
if err = writeFile(procRootFile, dstLibPath, libData, false); err != nil {
return err
}
// this is to create bin directory, we dont actually need to write anything there, and we dont execute launcher there
if err = writeFile(procRootFile, dstLauncherPath, launcherData, false); err != nil {
return err
}
return nil
}

func (p *Profiler) DistributionForProcess(pid int) (*Distribution, error) {
proc, err := procfs.NewProc(pid)
if err != nil {
return nil, fmt.Errorf("failed to select dist for pid %d %w", pid, err)
}
maps, err := proc.ProcMaps()
if err != nil {
return nil, fmt.Errorf("failed to select dist for pid %d %w", pid, err)
}
musl := false
glibc := false
for _, m := range maps {
if isMuslMapping(m) {
musl = true
}
if isGlibcMapping(m) {
glibc = true
}
}
if musl && glibc {
return nil, fmt.Errorf("failed to select dist for pid %d: both musl and glibc found", pid)
}
if musl {
return p.muslDist, nil
}
if glibc {
return p.glibcDist, nil
}
if _, err := os.Stat(ProcessPath("/lib/ld-musl-x86_64.so.1", pid)); err == nil {
return p.muslDist, nil
}
if _, err := os.Stat(ProcessPath("/lib/ld-musl-aarch64.so.1", pid)); err == nil {
return p.muslDist, nil
}
if _, err := os.Stat(ProcessPath("/lib64/ld-linux-x86-64.so.2", pid)); err == nil {
return p.glibcDist, nil
}
return nil, fmt.Errorf("failed to select dist for pid %d: neither musl nor glibc found", pid)
}

func isMuslMapping(m *procfs.ProcMap) bool {
if strings.Contains(m.Pathname, "/lib/ld-musl-x86_64.so.1") {
return true
}
if strings.Contains(m.Pathname, "/lib/ld-musl-aarch64.so.1") {
return true
}
return false
}

func isGlibcMapping(m *procfs.ProcMap) bool {
if strings.HasSuffix(m.Pathname, "/libc.so.6") {
return true
}
if strings.Contains(m.Pathname, "x86_64-linux-gnu/libc-") {
return true
}
if strings.Contains(m.Pathname, "aarch64-linux-gnu/libc-") {
return true
}
return false
func (p *Profiler) Distribution() *Distribution {
return p.dist
}

func (p *Profiler) ExtractDistributions() error {
Expand All @@ -201,45 +92,28 @@ func (p *Profiler) ExtractDistributions() error {
func (p *Profiler) extractDistributions() error {
fsMutex.Lock()
defer fsMutex.Unlock()
muslDistName, glibcDistName := p.getDistNames()
distName := p.getDistName()

var launcher, jattach, glibc, musl []byte
err := readTarGZ(p.archive.data, func(name string, fi fs.FileInfo, data []byte) error {
if name == "profiler.sh" || name == "asprof" {
var launcher, dist []byte
err := readArchive(p.archive.data, p.archive.format, func(name string, fi fs.FileInfo, data []byte) error {
if strings.Contains(name, "asprof") {
launcher = data
}
if name == "jattach" {
jattach = data
}
if strings.Contains(name, "glibc/libasyncProfiler.so") {
glibc = data
}
if strings.Contains(name, "musl/libasyncProfiler.so") {
musl = data
if strings.Contains(name, "libasyncProfiler") {
dist = data
}
return nil
})
if err != nil {
return err
}
if launcher == nil || glibc == nil || musl == nil {
return fmt.Errorf("failed to find libasyncProfiler in tar.gz")
}
if !p.glibcDist.binaryLauncher() {
if jattach == nil {
return fmt.Errorf("failed to find jattach in tar.gz")
}
if launcher == nil || dist == nil {
return fmt.Errorf("failed to find libasyncProfiler in archive %s", distName)
}

fileMap := map[string][]byte{}
fileMap[filepath.Join(glibcDistName, p.glibcDist.LauncherPath())] = launcher
fileMap[filepath.Join(glibcDistName, p.glibcDist.LibPath())] = glibc
fileMap[filepath.Join(muslDistName, p.muslDist.LauncherPath())] = launcher
fileMap[filepath.Join(muslDistName, p.muslDist.LibPath())] = musl
if !p.glibcDist.binaryLauncher() {
fileMap[filepath.Join(glibcDistName, p.glibcDist.JattachPath())] = jattach
fileMap[filepath.Join(muslDistName, p.muslDist.JattachPath())] = jattach
}
fileMap[filepath.Join(distName, p.dist.LauncherPath())] = launcher
fileMap[filepath.Join(distName, p.dist.LibPath())] = dist
tmpDirFile, err := os.Open(p.tmpDir)
if err != nil {
return fmt.Errorf("failed to open tmp dir %s: %w", p.tmpDir, err)
Expand All @@ -255,31 +129,10 @@ func (p *Profiler) extractDistributions() error {
return err
}
}
p.glibcDist.extractedDir = filepath.Join(p.tmpDir, glibcDistName)
p.muslDist.extractedDir = filepath.Join(p.tmpDir, muslDistName)
p.dist.extractedDir = filepath.Join(p.tmpDir, distName)
return nil
}

func (p *Profiler) getDistNames() (string, string) {
muslDistName := fmt.Sprintf("%s-%s-%s", p.tmpDirMarker,
"musl",
p.archiveHash)
glibcDistName := fmt.Sprintf("%s-%s-%s", p.tmpDirMarker,
"glibc",
p.archiveHash)
return muslDistName, glibcDistName
}

func ProcessPath(path string, pid int) string {
f := ProcFile{path, pid}
return f.ProcRootPath()
}

type ProcFile struct {
Path string
PID int
}

func (f *ProcFile) ProcRootPath() string {
return filepath.Join("/proc", strconv.Itoa(f.PID), "root", f.Path)
func (p *Profiler) getDistName() string {
return fmt.Sprintf("%s-%s", p.tmpDirMarker, p.archiveHash)
}
30 changes: 30 additions & 0 deletions internal/component/pyroscope/java/asprof/asprof_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//go:build darwin

package asprof

import (
_ "embed"
"path/filepath"
)

//go:embed async-profiler-3.0-fa937db-macos.zip
var embeddedArchiveData []byte

// bin/asprof
// lib/libasyncProfiler.dylib

var embeddedArchiveVersion = 300

var EmbeddedArchive = Archive{data: embeddedArchiveData, version: embeddedArchiveVersion, format: ArchiveFormatZip}

func (d *Distribution) LibPath() string {
return filepath.Join(d.extractedDir, "lib/libasyncProfiler.dylib")
}

func (p *Profiler) CopyLib(dist *Distribution, pid int) error {
return nil
}

func ProcessPath(path string, pid int) string {
return path
}
62 changes: 62 additions & 0 deletions internal/component/pyroscope/java/asprof/asprof_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//go:build linux && (amd64 || arm64)

package asprof

import (
"fmt"
"os"
"strconv"
"strings"
"path/filepath"
)

var embeddedArchiveVersion = 300

var EmbeddedArchive = Archive{data: embeddedArchiveData, version: embeddedArchiveVersion, format: ArchiveFormatTarGz}

func (d *Distribution) LibPath() string {
return filepath.Join(d.extractedDir, "lib/libasyncProfiler.so")
}

func (p *Profiler) CopyLib(dist *Distribution, pid int) error {
fsMutex.Lock()
defer fsMutex.Unlock()
libData, err := os.ReadFile(dist.LibPath())
if err != nil {
return err
}
launcherData, err := os.ReadFile(dist.LauncherPath())
if err != nil {
return err
}
procRoot := ProcessPath("/", pid)
procRootFile, err := os.Open(procRoot)
if err != nil {
return fmt.Errorf("failed to open proc root %s: %w", procRoot, err)
}
defer procRootFile.Close()
dstLibPath := strings.TrimPrefix(dist.LibPath(), "/")
dstLauncherPath := strings.TrimPrefix(dist.LauncherPath(), "/")
if err = writeFile(procRootFile, dstLibPath, libData, false); err != nil {
return err
}
// this is to create a bin directory, we don't actually need to write anything there, and we don't execute the launcher there
if err = writeFile(procRootFile, dstLauncherPath, launcherData, false); err != nil {
return err
}
return nil
}

func ProcessPath(path string, pid int) string {
f := procFile{path, pid}
return f.procRootPath()
}

type procFile struct {
path string
pid int
}

func (f *procFile) procRootPath() string {
return filepath.Join("/proc", strconv.Itoa(f.pid), "root", f.path)
}
13 changes: 4 additions & 9 deletions internal/component/pyroscope/java/asprof/asprof_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,8 @@ import (
_ "embed"
)

//go:embed async-profiler-3.0-linux-x64.tar.gz
var embededArchiveData []byte
//go:embed async-profiler-3.0-fa937db-linux-x64.tar.gz
var embeddedArchiveData []byte

// asprof
// glibc / libasyncProfiler.so
// musl / libasyncProfiler.so

var embededArchiveVersion = 300

var EmbeddedArchive = Archive{data: embededArchiveData, version: embededArchiveVersion}
// bin/asprof
// lib/libasyncProfiler.so
Loading

0 comments on commit 11d2d05

Please sign in to comment.