java程序性能分析

java程序性能分析

java-analyse-go

env

CentOS Linux release 7.9.2009 (Core)
go version go1.20.12 linux/amd64
openjdk version “1.8.0_412”

输入: openjdk的java进程id
输出: top3cpu线程,top3mem线程

历次优化说明:

  1. 线程信息添加堆栈信息,便于分析
  2. 内存单位展示优化,大于1G按GB显示,小于1G展示MB
  3. 内存取数逻辑优化

code

package main

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

// ThreadInfo 存储线程的 CPU、内存和堆栈信息
type ThreadInfo struct {
    TID    string
    CPU    float64
    Memory float64
    Stack  string
}

// GetProcessThreads 获取指定进程的所有线程 ID
func GetProcessThreads(pid string) ([]string, error) {
    threadsPath := filepath.Join("/proc", pid, "task")
    entries, err := os.ReadDir(threadsPath)
    if err != nil {
        return nil, fmt.Errorf("failed to read threads directory: %v", err)
    }

    var threads []string
    for _, entry := range entries {
        if entry.IsDir() {
            threads = append(threads, entry.Name())
        }
    }
    return threads, nil
}

// ReadThreadStat 获取线程的 CPU 使用率
func ReadThreadStat(pid, tid string) (float64, error) {
    statPath := filepath.Join("/proc", pid, "task", tid, "stat")
    file, err := os.Open(statPath)
    if err != nil {
        return 0, fmt.Errorf("failed to open stat file: %v", err)
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    if scanner.Scan() {
        fields := strings.Fields(scanner.Text())
        if len(fields) < 14 {
            return 0, fmt.Errorf("stat file format invalid")
        }

        utime, err := strconv.ParseFloat(fields[13], 64) // 用户态时间
        if err != nil {
            return 0, fmt.Errorf("failed to parse utime: %v", err)
        }

        stime, err := strconv.ParseFloat(fields[14], 64) // 内核态时间
        if err != nil {
            return 0, fmt.Errorf("failed to parse stime: %v", err)
        }

        // 返回 CPU 使用时间
        return utime + stime, nil
    }
    return 0, fmt.Errorf("failed to read stat file")
}

// ReadThreadMemory 获取线程的实际驻留内存(RSS),以 GB 为单位
func ReadThreadMemory(pid, tid string) (float64, error) {
    statusPath := filepath.Join("/proc", pid, "task", tid, "status")
    file, err := os.Open(statusPath)
    if err != nil {
        return 0, fmt.Errorf("failed to open status file: %v", err)
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        if strings.HasPrefix(line, "VmRSS:") {
            fields := strings.Fields(line)
            if len(fields) < 2 {
                return 0, fmt.Errorf("invalid VmRSS line format")
            }

            // 将 VmRSS 值转换为浮点数(单位:kB)
            rssKb, err := strconv.ParseFloat(fields[1], 64)
            if err != nil {
                return 0, fmt.Errorf("failed to parse VmRSS value: %v", err)
            }

            // 转换为 GB
            return rssKb / (1024 * 1024), nil
        }
    }
    return 0, fmt.Errorf("VmRSS not found in status file")
}

// ReadThreadStack 获取线程的堆栈信息
func ReadThreadStack(pid, tid string) (string, error) {
    stackPath := filepath.Join("/proc", pid, "task", tid, "stack")
    file, err := os.Open(stackPath)
    if err != nil {
        return "", fmt.Errorf("failed to open stack file: %v", err)
    }
    defer file.Close()

    stackData, err := io.ReadAll(file)
    if err != nil {
        return "", fmt.Errorf("failed to read stack file: %v", err)
    }

    return string(stackData), nil
}

// FormatMemory 格式化内存大小,根据大小自动选择单位(GB 或 MB)
func FormatMemory(memory float64) string {
    if memory >= 1 {
        return fmt.Sprintf("%.2fGB", memory)
    }
    return fmt.Sprintf("%.2fMB", memory*1024)
}

// min 返回两个整数中的最小值
func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

func main() {
    if len(os.Args) != 2 {
        fmt.Println("Usage: go run main.go <pid>")
        return
    }

    pid := os.Args[1]

    // 获取线程信息
    threads, err := GetProcessThreads(pid)
    if err != nil {
        fmt.Printf("Error getting threads: %v\n", err)
        return
    }

    var threadInfos []ThreadInfo
    for _, tid := range threads {
        cpu, err := ReadThreadStat(pid, tid)
        if err != nil {
            fmt.Printf("Error reading CPU info for thread %s: %v\n", tid, err)
            continue
        }

        mem, err := ReadThreadMemory(pid, tid)
        if err != nil {
            fmt.Printf("Error reading memory info for thread %s: %v\n", tid, err)
            continue
        }

        stack, err := ReadThreadStack(pid, tid)
        if err != nil {
            fmt.Printf("Error reading stack info for thread %s: %v\n", tid, err)
            continue
        }

        threadInfos = append(threadInfos, ThreadInfo{
            TID:    tid,
            CPU:    cpu,
            Memory: mem,
            Stack:  stack,
        })
    }

    // 按 CPU 排序
    sort.Slice(threadInfos, func(i, j int) bool {
        return threadInfos[i].CPU > threadInfos[j].CPU
    })
    fmt.Println("Top 3 threads by CPU usage:")
    for i, info := range threadInfos[:min(3, len(threadInfos))] {
        fmt.Printf("Rank %d: TID=%s, CPU=%.2f%%, Stack:\n%s\n",
            i+1, info.TID, info.CPU, info.Stack)
    }

    // 按内存排序
    sort.Slice(threadInfos, func(i, j int) bool {
        return threadInfos[i].Memory > threadInfos[j].Memory
    })
    fmt.Println("Top 3 threads by Memory usage:")
    for i, info := range threadInfos[:min(3, len(threadInfos))] {
        fmt.Printf("Rank %d: TID=%s, Memory=%s, Stack:\n%s\n",
            i+1, info.TID, FormatMemory(info.Memory), info.Stack)
    }
}
Avatar photo
igoZhang

互联网应用,虚拟化,容器

评论已关闭。