bilibili-backup/app/admin/main/apm/service/pprof.go
2019-04-22 02:59:20 +00:00

250 lines
7.1 KiB
Go

package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
"go-common/app/admin/main/apm/conf"
"go-common/app/admin/main/apm/model/pprof"
"go-common/library/log"
xtime "go-common/library/time"
)
var (
port = "2333"
cpuURL = "%s/x/admin/apm/pprof/svg?name=%s&uri=%s&hostname=%s"
msg = "<a href=\"%s\">点击查看详情</a>(注:生成时间有延迟,如未显示,稍后重试)"
)
// kind .
const (
CPUPerformace = 1 // CPU性能图
CPUFrame = 2 // CPU火焰图
HeapPerformance = 3 // 内存性能图
HeapFrame = 4 // 内存火焰图
)
// Pprof ...
func (s *Service) Pprof(url, uri, svgName, hostName string, time int64, sType int8) (err error) {
var (
out bytes.Buffer
errOut bytes.Buffer
)
goPath := "go"
if len(conf.Conf.Pprof.GoPath) > 0 {
goPath = conf.Conf.Pprof.GoPath
}
f, err := exec.LookPath(goPath)
if err != nil {
log.Error("pprof go error(%v) goPath=(%v)", err, goPath)
fmt.Printf("pprof=(%v)", err)
return
}
cmd := exec.Command(f, "tool", "pprof", "--seconds="+strconv.FormatInt(time, 10), "--svg", "--output="+conf.Conf.Pprof.Dir+"/"+svgName+"_"+hostName+"_"+uri+".svg", url+"/debug/pprof/"+uri)
cmd.Stdout = &out
cmd.Stderr = &errOut
// 执行命令
if sType == 1 { //串行
if err = cmd.Run(); err != nil {
log.Error("pprof Run stdout=(%s) stderr=(%s) error(%v)", out.String(), errOut.String(), err)
}
} else { //阻塞
if err = cmd.Start(); err != nil {
log.Error("pprof Start stdout=(%s) stderr=(%s) error(%v)", out.String(), errOut.String(), err)
}
if err = cmd.Wait(); err != nil {
log.Error("s.Pprof cmd.Wait() error(%v)", err)
}
}
return
}
// Torch ...
func (s *Service) Torch(c context.Context, url, uri, svgName, hostName string, time int64, sType int8) (err error) {
goPath := "go-torch"
// if len(conf.Conf.Pprof.GoPath) > 0 {
// goPath = conf.Conf.Pprof.GoPath
// }
f, err := exec.LookPath(goPath)
if err != nil {
log.Error("go-torch error(%v) goPath=(%v)", err, goPath)
fmt.Printf("go-torch=(%v)", err)
return
}
cmd := exec.Command(f, "--url="+url, "--suffix=/debug/pprof/"+uri, "--seconds="+strconv.FormatInt(time, 10), "-f="+conf.Conf.Pprof.Dir+"/"+svgName+"_"+hostName+"_"+uri+"_flame.svg")
var (
out bytes.Buffer
cmdErr bytes.Buffer
)
cmd.Stdout = &out
cmd.Stderr = &cmdErr
// 执行命令
if sType == 1 { //串行
if err = cmd.Run(); err != nil {
log.Error("go-torch Run stdout=(%s) stderr=(%s) error(%v)", out.String(), cmdErr.String(), err)
}
} else { //阻塞
if err = cmd.Start(); err != nil {
log.Error("go-torch Start stdout=(%s) stderr=(%s) error(%v)", out.String(), cmdErr.String(), err)
}
if err = cmd.Wait(); err != nil {
log.Error("cmd.Wait() error(%v)", err)
}
}
return
}
// ActiveWarning active
func (s *Service) ActiveWarning(c context.Context, text string) (err error) {
var (
ins *pprof.Ins
title = "【%s】性能告警抓取通知"
warn = &pprof.Warning{}
pws = make([]*pprof.Warn, 0)
times = time.Now().Unix()
curTime = xtime.Time(times)
reg = regexp.MustCompile(`[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}`)
)
if err = json.Unmarshal([]byte(text), warn); err != nil {
log.Error("s.ActiveWarning json.Unmarshal data error(%v)", err)
return
}
if warn.Tags.App == "" {
log.Info("s.ActiveWarning skipped(AppName is empty)")
return
}
title = fmt.Sprintf(title, warn.Tags.App)
if ins, err = s.dao.Instances(c, warn.Tags.App); err != nil {
log.Error("s.ActiveWaring get instances error(%v)", err)
return
}
for _, instance := range ins.Instances {
var (
ip string
addrs = instance.Addrs
strs = strings.Split(instance.Hostname, "-")
hostName = fmt.Sprintf("%s-%d", strs[len(strs)-1], times)
pprofWarn = &pprof.Warn{}
)
if len(addrs) < 1 {
log.Info("s.ActiveWarning not found adds")
continue
}
ip = reg.FindString(addrs[0])
host := fmt.Sprintf("http://%s:%s", ip, port)
pprofWarn.IP = ip
pprofWarn.AppID = warn.Tags.App
pprofWarn.SvgName = hostName
pprofWarn.Ctime = curTime
pprofWarn.Mtime = curTime
if err = s.Torch(c, host, "profile", warn.Tags.App, hostName, 30, 2); err == nil {
pprofWarn.Kind = CPUPerformace
pws = append(pws, packing(pprofWarn))
}
if err = s.Pprof(host, "profile", warn.Tags.App, hostName, 30, 2); err == nil {
pprofWarn.Kind = CPUFrame
pws = append(pws, packing(pprofWarn))
}
if err = s.Torch(c, host, "heap", warn.Tags.App, hostName, 30, 2); err == nil {
pprofWarn.Kind = HeapPerformance
pws = append(pws, packing(pprofWarn))
}
if err = s.Pprof(host, "heap", warn.Tags.App, hostName, 30, 2); err == nil {
pprofWarn.Kind = HeapFrame
pws = append(pws, packing(pprofWarn))
}
}
if len(pws) == 0 {
return
}
if err = s.AddPprofWarn(c, pws); err != nil {
return
}
return s.SendWeChat(c, title, fmt.Sprintf(msg, s.c.Host.SVENCo), warn.Tags.App, strings.Join(s.c.WeChat.Users, ","))
}
// packing .
func packing(pw *pprof.Warn) (pprofWarn *pprof.Warn) {
pprofWarn = &pprof.Warn{
AppID: pw.AppID,
SvgName: pw.SvgName,
IP: pw.IP,
Kind: pw.Kind,
Mtime: pw.Mtime,
Ctime: pw.Ctime,
}
return
}
// AddPprofWarn .
func (s *Service) AddPprofWarn(c context.Context, pws []*pprof.Warn) (err error) {
var (
sql = "INSERT INTO `pprof_warn`(`app_id`, `svg_name`, `ip`, `kind`, `ctime`, `mtime`) VALUES %s"
key = make([]string, 0)
value = make([]interface{}, 0)
)
for _, pw := range pws {
key = append(key, "(?,?,?,?,?,?)")
value = append(value, pw.AppID, pw.SvgName, pw.IP, pw.Kind, pw.Ctime, pw.Mtime)
}
if err = s.DB.Exec(fmt.Sprintf(sql, strings.Join(key, ",")), value...).Error; err != nil {
log.Error("s.AddPprofWarn error(%v)", err)
}
return
}
// PprofWarn .
func (s *Service) PprofWarn(c context.Context, req *pprof.Params) (pws []*pprof.Warn, err error) {
var (
query = s.DB.Where("1=1")
)
pws = make([]*pprof.Warn, 0)
if req.AppID == "" && req.IP == "" && req.SvgName == "" && req.Kind == 0 && req.StartTime == 0 && req.EndTime == 0 {
return
}
if req.AppID != "" {
query = query.Where("app_id=?", req.AppID)
}
if req.SvgName != "" {
query = query.Where("svg_name=?", req.SvgName)
}
if req.IP != "" {
query = query.Where("ip=?", req.IP)
}
if req.Kind != 0 {
query = query.Where("kind=?", req.Kind)
}
if req.StartTime != 0 && req.EndTime != 0 {
query = query.Where("mtime between ? and ?", req.StartTime, req.EndTime)
}
if err = query.Order("mtime desc").Find(&pws).Error; err != nil {
log.Error("s.PprofWarn query error(%v)", err)
}
s.setSvgURL(pws)
return
}
// setSvgURL .
func (s *Service) setSvgURL(pws []*pprof.Warn) {
for _, pw := range pws {
switch {
case pw.Kind == CPUPerformace:
pw.URL = fmt.Sprintf(cpuURL, s.c.Host.SVENCo, pw.AppID, "profile", pw.SvgName)
case pw.Kind == CPUFrame:
pw.URL = fmt.Sprintf(cpuURL, s.c.Host.SVENCo, pw.AppID, "profile_flame", pw.SvgName)
case pw.Kind == HeapPerformance:
pw.URL = fmt.Sprintf(cpuURL, s.c.Host.SVENCo, pw.AppID, "heap", pw.SvgName)
case pw.Kind == HeapFrame:
pw.URL = fmt.Sprintf(cpuURL, s.c.Host.SVENCo, pw.AppID, "heap_flame", pw.SvgName)
default:
}
}
}