Classify and enrich guest services
This commit is contained in:
112
agent.go
112
agent.go
@@ -58,10 +58,18 @@ type AgentInterface struct {
|
||||
}
|
||||
|
||||
type AgentService struct {
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"`
|
||||
SubState string `json:"subState"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"`
|
||||
SubState string `json:"subState"`
|
||||
Description string `json:"description"`
|
||||
Origin string `json:"origin"`
|
||||
UnitPath string `json:"unitPath,omitempty"`
|
||||
Package string `json:"package,omitempty"`
|
||||
Restarts uint64 `json:"restarts"`
|
||||
ExitCode int `json:"exitCode"`
|
||||
StartedAt int64 `json:"startedAt,omitempty"`
|
||||
MemoryBytes uint64 `json:"memoryBytes,omitempty"`
|
||||
CPUSeconds float64 `json:"cpuSeconds,omitempty"`
|
||||
}
|
||||
|
||||
type AgentIssue struct {
|
||||
@@ -595,7 +603,7 @@ func runAgentCommand(timeout time.Duration, name string, args ...string) string
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
output, err := exec.CommandContext(ctx, name, args...).Output()
|
||||
if err != nil {
|
||||
if err != nil && len(output) == 0 {
|
||||
return ""
|
||||
}
|
||||
return string(output)
|
||||
@@ -622,7 +630,99 @@ func collectAgentServices() []AgentService {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
return enrichAgentServices(result)
|
||||
}
|
||||
|
||||
func enrichAgentServices(services []AgentService) []AgentService {
|
||||
if len(services) == 0 {
|
||||
return services
|
||||
}
|
||||
args := []string{"show", "--no-pager", "--property=Id", "--property=FragmentPath", "--property=NRestarts", "--property=ExecMainStatus", "--property=ActiveEnterTimestampUSec", "--property=MemoryCurrent", "--property=CPUUsageNSec"}
|
||||
for _, service := range services {
|
||||
args = append(args, service.Name)
|
||||
}
|
||||
show := runAgentCommand(8*time.Second, "systemctl", args...)
|
||||
properties := map[string]map[string]string{}
|
||||
paths := map[string]string{}
|
||||
for _, record := range strings.Split(show, "\n\n") {
|
||||
values := map[string]string{}
|
||||
for _, line := range strings.Split(record, "\n") {
|
||||
key, value, ok := strings.Cut(line, "=")
|
||||
if ok {
|
||||
values[key] = value
|
||||
}
|
||||
}
|
||||
if id := values["Id"]; id != "" {
|
||||
properties[id] = values
|
||||
paths[id] = values["FragmentPath"]
|
||||
}
|
||||
}
|
||||
pathArgs := []string{"-S"}
|
||||
for _, service := range services {
|
||||
if path := paths[service.Name]; path != "" {
|
||||
pathArgs = append(pathArgs, path)
|
||||
}
|
||||
}
|
||||
owners := map[string]string{}
|
||||
packages := []string{}
|
||||
if len(pathArgs) > 1 {
|
||||
output := runAgentCommand(8*time.Second, "dpkg-query", pathArgs...)
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
left, path, ok := strings.Cut(line, ": ")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
pkg := strings.TrimSpace(strings.Split(left, ",")[0])
|
||||
path = strings.TrimSpace(path)
|
||||
if pkg != "" && path != "" {
|
||||
owners[path] = pkg
|
||||
packages = append(packages, pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
priorities := map[string]string{}
|
||||
if len(packages) > 0 {
|
||||
packageArgs := []string{"-W", "-f=${binary:Package}\t${Priority}\n"}
|
||||
packageArgs = append(packageArgs, packages...)
|
||||
output := runAgentCommand(8*time.Second, "dpkg-query", packageArgs...)
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 2 {
|
||||
priorities[fields[0]] = fields[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := range services {
|
||||
service := &services[i]
|
||||
service.UnitPath = paths[service.Name]
|
||||
service.Package = owners[service.UnitPath]
|
||||
service.Origin = classifyAgentService(service.UnitPath, service.Package, priorities[service.Package])
|
||||
values := properties[service.Name]
|
||||
service.Restarts, _ = strconv.ParseUint(values["NRestarts"], 10, 64)
|
||||
exit, _ := strconv.ParseInt(values["ExecMainStatus"], 10, 32)
|
||||
service.ExitCode = int(exit)
|
||||
started, _ := strconv.ParseInt(values["ActiveEnterTimestampUSec"], 10, 64)
|
||||
service.StartedAt = started / 1_000_000
|
||||
service.MemoryBytes, _ = strconv.ParseUint(values["MemoryCurrent"], 10, 64)
|
||||
cpu, _ := strconv.ParseUint(values["CPUUsageNSec"], 10, 64)
|
||||
service.CPUSeconds = float64(cpu) / 1_000_000_000
|
||||
}
|
||||
return services
|
||||
}
|
||||
|
||||
func classifyAgentService(path, pkg, priority string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(path, "/etc/systemd/") || strings.HasPrefix(path, "/usr/local/"):
|
||||
return "local"
|
||||
case priority == "required" || priority == "important" || priority == "standard":
|
||||
return "system"
|
||||
case pkg != "":
|
||||
return "added"
|
||||
case strings.HasPrefix(path, "/run/systemd/"):
|
||||
return "runtime"
|
||||
default:
|
||||
return "system"
|
||||
}
|
||||
}
|
||||
|
||||
func collectAgentIssues() []AgentIssue {
|
||||
|
||||
Reference in New Issue
Block a user