فهرست منبع

Feature #TASK_QT-18250 Python nodejs 进程适配

Feature #TASK_QT-18250 pythont

Feature #TASK_QT-18250 nodejs
Carl 8 ماه پیش
والد
کامیت
afcbbd29ba
5فایلهای تغییر یافته به همراه226 افزوده شده و 2 حذف شده
  1. 1 1
      containers/apm_register_app.go
  2. 49 0
      containers/container.go
  3. 3 1
      containers/container_apm.go
  4. 2 0
      containers/process.go
  5. 171 0
      containers/util.go

+ 1 - 1
containers/apm_register_app.go

@@ -14,7 +14,7 @@ func (c *Container) RegisterAppInfo(r *Registry, pid uint32) error {
 		c.AppInfo.AppName = c.WhiteSettingInfo.AppName
 		c.AppInfo.AppIdHash.IntVal, _ = utils.BuildInt64ID(c.WhiteSettingInfo.AppName).ToInt64()
 		c.AppInfo.AppIdHash.HashtVal = utils.BuildInt64ID(c.WhiteSettingInfo.AppName).ToHashByte()
-		c.AppInfo.CodeType = CodeTypeJava
+		c.AppInfo.CodeType = c.GetCodeTypeFromCache(pid)
 		c.AppInfo.SetAppSuccess()
 		return nil
 	}

+ 49 - 0
containers/container.go

@@ -1446,6 +1446,13 @@ func (c *Container) AttachUprobes(tracer *ebpftracer.Tracer, pid uint32, _type s
 		err = c.attachTlsUprobes(tracer, pid)
 	case CodeTypeNetCoreAot:
 		err = c.attachNetCoreUprobes(tracer, pid)
+	case CodeTypePython:
+		err = c.attachPythonUprobes(tracer, pid)
+	case CodeTypeNode:
+		err = c.attachNodejsUprobes(tracer, pid)
+	default:
+		klog.Warningf("[attach] Not Supported codeType: %s", codeType.String())
+		return fmt.Errorf("[attach] Not Supported codeType: %s", codeType.String())
 	}
 	if err != nil {
 		klog.WithField("pid", pid).Errorf("[attach] error  %v :", err)
@@ -1656,6 +1663,48 @@ func (c *Container) attachNetCoreUprobes(tracer *ebpftracer.Tracer, pid uint32)
 	return nil
 }
 
+func (c *Container) attachPythonUprobes(tracer *ebpftracer.Tracer, pid uint32) error {
+	if common.IsOpenFilter() && !common.IsFilterPid(pid) {
+		return nil
+	}
+	p := c.processes[pid]
+	if p == nil {
+		return nil
+	}
+	if !p.pyAttachOnce {
+		p.pyAttachOnce = true
+		err := tracer.InitKProcInfo(pid, &c.AppInfo)
+		if err != nil {
+			klog.Error(err)
+			return err
+		}
+		klog.Infof("[attach] python proc succeed! pid:[%d]", pid)
+		c.l7AttachSuccess()
+	}
+	return nil
+}
+
+func (c *Container) attachNodejsUprobes(tracer *ebpftracer.Tracer, pid uint32) error {
+	if common.IsOpenFilter() && !common.IsFilterPid(pid) {
+		return nil
+	}
+	p := c.processes[pid]
+	if p == nil {
+		return nil
+	}
+	if !p.nodejsAttachOnce {
+		p.nodejsAttachOnce = true
+		err := tracer.InitKProcInfo(pid, &c.AppInfo)
+		if err != nil {
+			klog.Error(err)
+			return err
+		}
+		klog.Infof("[attach] nodejs proc succeed! pid:[%d]", pid)
+		c.l7AttachSuccess()
+	}
+	return nil
+}
+
 func resolveFd(pid uint32, fd uint64) (mntId string, logPath string) {
 	info := proc.GetFdInfo(pid, fd)
 	if info == nil {

+ 3 - 1
containers/container_apm.go

@@ -139,10 +139,11 @@ func (c *Container) onL7RequestApm(pid uint32, fd uint64, timestamp uint64, r *l
 		}
 	}
 	if r.Protocol == l7.ProtocolHTTP {
+		fmt.Println("ProtocolHTTP-----", r.TraceId)
 		if c.l7Attach && c.valuableTrace(r.TraceId) {
 			method, requestURI, sn, sport := l7.ParseHttpHost(r.Payload)
 			apmTrace, err := c.getOrInitTrace(r.TraceId)
-			//fmt.Println("ProtocolHTTP-----", r.TraceId, err)
+			fmt.Println("ProtocolHTTP-----", r.TraceId, err)
 			if err == nil {
 				apmTrace.HttpTraceRequestEvent(method, requestURI, sn, sport, r)
 				c.SendEvent(apmTrace, r.TraceId)
@@ -208,6 +209,7 @@ func (c *Container) onL7RequestApm(pid uint32, fd uint64, timestamp uint64, r *l
 			}
 		}
 	case l7.ProtocolMysql:
+		fmt.Println("mysql r.TraceId:", r.TraceId)
 		if r.Method != l7.MethodStatementClose {
 			stats.observe(r.Status.String(), "", r.Duration)
 		}

+ 2 - 0
containers/process.go

@@ -36,6 +36,8 @@ type Process struct {
 	openSslUprobesChecked bool
 
 	jvmAttachOnce    bool
+	pyAttachOnce     bool
+	nodejsAttachOnce bool
 	stackAttachOnce  bool
 	stackStatus      StackStatus
 	versionFailed    bool

+ 171 - 0
containers/util.go

@@ -1,16 +1,22 @@
 package containers
 
 import (
+	"bufio"
+	"bytes"
 	"debug/elf"
+	"errors"
 	"fmt"
 	"github.com/coroot/coroot-node-agent/flags"
 	. "github.com/coroot/coroot-node-agent/utils/modelse"
 	klog "github.com/sirupsen/logrus"
+	"io"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"regexp"
 	"runtime"
 	"strings"
+	"unicode"
 )
 
 var libjvmRegex = regexp.MustCompile(`.*/libjvm\.so`)
@@ -42,6 +48,10 @@ func GetExeType(pid uint32, rootfs string) CodeType {
 	} else if isNetCoreProcess(pid, rootfs) {
 		//	fmt.Println("is netcore process")
 		return CodeTypeNetCoreAot
+	} else if isPythonProcess(pid, rootfs) {
+		return CodeTypePython
+	} else if isNodeProcess(pid, rootfs) {
+		return CodeTypeNode
 	}
 	return CodeTypeUnknown
 }
@@ -153,6 +163,167 @@ func isGoProcess(pid uint32, rootfs string) bool {
 	return false
 }
 
+func isPythonProcess(pid uint32, rootfs string) bool {
+	pathPrefix := rootfs
+	if pathPrefix == "" && *flags.RunInContainer {
+		pathPrefix = *flags.HostDirPathPrefix
+	}
+
+	procMapsPath := fmt.Sprintf("%s/proc/%d/maps", pathPrefix, pid)
+	f, err := os.Open(procMapsPath)
+	if err != nil {
+		klog.Error(err)
+		return false
+	}
+	defer f.Close()
+
+	// 读取 maps(限制读取上限,防止非常大的文件)
+	const maxRead = 256 * 1024 // 256 KB should be plenty for maps
+	buf := make([]byte, maxRead)
+	n, err := f.Read(buf)
+	if err != nil && err != io.EOF {
+		return false
+	}
+	data := buf[:n]
+	lower := bytes.ToLower(data)
+
+	// 快速子串预筛(避免不必要的正则)
+	if !(bytes.Contains(lower, []byte("python")) ||
+		bytes.Contains(lower, []byte("cpython")) ||
+		bytes.Contains(lower, []byte("libpython"))) {
+		return false
+	}
+
+	// 精确正则匹配:若任一匹配成功,则认定为 Python 进程
+	var patterns = []*regexp.Regexp{
+		// 可执行 /usr/bin/python 或 /usr/local/bin/python 等
+		regexp.MustCompile(`(?i)/[a-z0-9_\-/.]*\bpython[0-9]*(\.[0-9]+)*\b`),
+		// libpythonX.Y.so 或 libpython3.8.so.1.0 之类
+		regexp.MustCompile(`(?i)libpython[0-9]*(\.[0-9]+)*\.so(\.[0-9]+)*`),
+		// cpython-310 / cpython-39 等扩展模块名
+		regexp.MustCompile(`(?i)cpython-[0-9]+`),
+		// python3.10 的路径片段(更精确匹配版本)
+		regexp.MustCompile(`(?i)python[0-9]+\.[0-9]+`),
+	}
+
+	for _, re := range patterns {
+		if re.Find(lower) != nil {
+			return true
+		}
+	}
+
+	// 若没有任何精确模式匹配,则认为不是 Python(或无法确定)
+	return false
+}
+
+func isNodeProcess(pid uint32, rootfs string) bool {
+
+	pathPrefix := rootfs
+	if pathPrefix == "" && *flags.RunInContainer {
+		pathPrefix = *flags.HostDirPathPrefix
+	}
+
+	procExe := fmt.Sprintf("%s/proc/%d/exe", pathPrefix, pid)
+
+	// 1) readlink exe
+	exePath, err := os.Readlink(procExe)
+	if err != nil {
+		return false
+	}
+
+	// 2) 判断 exe 名称/路径是否为 node
+	base := filepath.Base(exePath)
+	lowBase := strings.ToLower(base)
+	lowPath := strings.ToLower(exePath)
+	if !(lowBase == "node" || lowBase == "nodejs" || strings.Contains(lowPath, "/node") || strings.Contains(lowPath, "nodejs")) {
+		return false
+	}
+
+	// 3) 打开可执行文件并进行 strings 风格扫描
+	f, err := os.Open(exePath)
+	if err != nil {
+		// 无法打开二进制,视为未找到关键字
+		return false
+	}
+	defer f.Close()
+
+	// 要匹配的关键字正则(严格、降低误报)
+	patterns := []*regexp.Regexp{
+		regexp.MustCompile(`(?i)nodejsVersion`),
+		regexp.MustCompile(`(?i)node_version`),
+		regexp.MustCompile(`(?i)node[^\w]?js`),
+	}
+
+	checkMatch := func(s string) bool {
+		for _, p := range patterns {
+			if p.FindStringIndex(s) != nil {
+				return true
+			}
+		}
+		return false
+	}
+
+	const bufSize = 64 * 1024
+	reader := bufio.NewReaderSize(f, bufSize)
+	const minRunLen = 4
+	var leftover []byte
+
+	for {
+		chunk := make([]byte, bufSize)
+		n, rerr := reader.Read(chunk)
+		if n > 0 {
+			data := chunk[:n]
+			if len(leftover) > 0 {
+				data = append(leftover, data...)
+				leftover = nil
+			}
+			start := -1
+			for i, b := range data {
+				if (b >= 32 && b < 127) || b == '\n' || b == '\t' || b == '\r' {
+					if start == -1 {
+						start = i
+					}
+				} else {
+					if start != -1 {
+						if i-start >= minRunLen {
+							s := string(data[start:i])
+							s = strings.TrimFunc(s, func(r rune) bool { return !unicode.IsPrint(r) })
+							if checkMatch(s) {
+								return true
+							}
+						}
+						start = -1
+					}
+				}
+			}
+			if start != -1 {
+				// 保存块尾部可打印残留
+				leftover = append([]byte{}, data[start:]...)
+				if len(leftover) > 1<<20 {
+					leftover = leftover[len(leftover)-(1<<20):]
+				}
+			}
+		}
+
+		if rerr != nil {
+			if errors.Is(rerr, io.EOF) {
+				// 检查 leftover
+				if len(leftover) >= minRunLen {
+					s := string(leftover)
+					s = strings.TrimFunc(s, func(r rune) bool { return !unicode.IsPrint(r) })
+					if checkMatch(s) {
+						return true
+					}
+				}
+				// 读完未找到
+				return false
+			}
+			// 读错误,返回 false
+			return false
+		}
+	}
+}
+
 func getProcessPath(pid uint32) (string, error) {
 	switch runtime.GOOS {
 	case "linux":