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线程
历次优化说明:
- 线程信息添加堆栈信息,便于分析
- 内存单位展示优化,大于1G按GB显示,小于1G展示MB
- 内存取数逻辑优化
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)
}
}