package utils
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/urfave/cli/v2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubernetes2 "k8s.io/client-go/kubernetes"
"openfuyao.com/npu-feature-discovery/internal/kubernetes"
"openfuyao.com/npu-feature-discovery/internal/lm/common"
)
const (
unknown = "UNKNOWN"
classAccelerator = "0x1200"
classBridge = "0x0604"
arrLength = 4
)
var SysPCIDevicesPath = "/sys/bus/pci/devices"
func GetEnvVar(name string) (string, error) {
value := os.Getenv(name)
if value == "" {
return "", fmt.Errorf("%s environment variable is not set", name)
}
return value, nil
}
func GetNode(c *cli.Context) (kubernetes2.Interface, *corev1.Node, error) {
client, err := kubernetes.GetKubernetesClient()
if err != nil {
return nil, nil, fmt.Errorf("failed to get Kubernetes client: %v", err)
}
nodeName, err := GetEnvVar("NODE_NAME")
if err != nil {
return nil, nil, err
}
node, err := client.CoreV1().Nodes().Get(c.Context, nodeName, metav1.GetOptions{})
if err != nil {
return nil, nil, fmt.Errorf("failed to get node %s: %w", nodeName, err)
}
return client, node, nil
}
func HasNPUDeviceLabels(labels map[string]string) bool {
for key, value := range labels {
_, ok := common.NpuDeviceTypes[key]
if ok && value == common.NpuDeviceLabelValue {
return true
}
}
return false
}
func GetCardInfo(node *corev1.Node) (string, error) {
tmpValue := unknown
entries, err := os.ReadDir(SysPCIDevicesPath)
if err != nil {
return unknown, err
}
archKey := getArchKey(node)
for _, entry := range entries {
fullDir := filepath.Join(SysPCIDevicesPath, entry.Name())
classFile := filepath.Join(fullDir, "class")
content, err := os.ReadFile(classFile)
if err != nil {
continue
}
classID := strings.TrimSpace(string(content))
isAccelerator := strings.HasPrefix(classID, classAccelerator)
isBridge := strings.HasPrefix(classID, classBridge)
if !isAccelerator && !isBridge {
continue
}
item, err := parseItem(fullDir)
if err != nil {
continue
}
m, ok := common.CardServerMap[item]
if !ok {
continue
}
cardName := resolveCardName(m, archKey)
if cardName == "" {
continue
}
if isBridge {
tmpValue = cardName
continue
}
return cardName, nil
}
return tmpValue, nil
}
func getArchKey(node *corev1.Node) string {
arch, ok := node.Labels["kubernetes.io/arch"]
if !ok {
return unknown
}
switch arch {
case "arm64":
return "aarch64"
default:
return "x86_64"
}
}
func resolveCardName(m map[string]string, archKey string) string {
if archKey == unknown {
return ""
}
if v := m[archKey]; v != "" {
return v
}
return m["*"]
}
func parseItem(dirPath string) (common.PCIIdentity, error) {
ids := [arrLength]string{}
files := []string{"vendor", "device", "subsystem_vendor", "subsystem_device"}
for i, name := range files {
content, err := os.ReadFile(filepath.Join(dirPath, name))
if err != nil {
return common.PCIIdentity{}, err
}
ids[i] = strings.TrimSpace(string(content))
}
return common.PCIIdentity{
Vendor: ids[0],
Device: ids[1],
SubsystemVendor: ids[2],
SubsystemDevice: ids[3],
}, nil
}
func ExecuteCommand(cmd string) (string, error) {
cmdArgs := []string{
"-i/proc/1/ns/ipc",
"-m/proc/1/ns/mnt",
"-n/proc/1/ns/net",
}
cmdParts := strings.Fields(cmd)
cmdArgs = append(cmdArgs, cmdParts...)
cmdOut := exec.Command("nsenter", cmdArgs...)
var out bytes.Buffer
cmdOut.Stdout = &out
cmdOut.Stderr = &out
err := cmdOut.Run()
if err != nil {
return "", fmt.Errorf("error executing command: %w\nOutput: %s", err, out.String())
}
return out.String(), nil
}