Skip to content

Commit 4339433

Browse files
committed
fix: Translate PIDs to container namespace in docker top
Docker top was showing root namespace PIDs (e.g., PID 783 for init) instead of container namespace PIDs (e.g., PID 1 for init). Implementation: - Two-pass algorithm in listContainerProcesses() - First pass: Build pidTranslation map by reading NSpid from /proc/[pid]/status - Second pass: Translate all PIDs and PPIDs to container namespace - NSpid format: "NSpid: 783 1" (root NS PID, then container NS PID) - PPIDs outside container namespace translate to "0" Result: docker top now shows correct container-perspective PIDs
1 parent b8d8345 commit 4339433

File tree

2 files changed

+72
-16
lines changed

2 files changed

+72
-16
lines changed
0 Bytes
Binary file not shown.

vminitd/extensions/process-control/server.go

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,60 @@ func listContainerProcesses() ([]*pb.ProcessInfo, error) {
155155
return nil, fmt.Errorf("failed to read /proc: %w", err)
156156
}
157157

158+
// First pass: build a map of root namespace PID -> container namespace PID
159+
// This is needed to translate PPIDs correctly
160+
pidTranslation := make(map[string]string)
161+
162+
for _, entry := range entries {
163+
if !entry.IsDir() {
164+
continue
165+
}
166+
167+
pidStr := entry.Name()
168+
pid, err := strconv.Atoi(pidStr)
169+
if err != nil {
170+
continue
171+
}
172+
173+
// Check if this process is in the root namespace
174+
procNS, err := os.Readlink(fmt.Sprintf("/proc/%d/ns/pid", pid))
175+
if err != nil {
176+
continue
177+
}
178+
179+
// Skip root namespace processes
180+
if procNS == rootNS {
181+
continue
182+
}
183+
184+
// Read NSpid from status to get container-namespace PID
185+
statusPath := fmt.Sprintf("/proc/%d/status", pid)
186+
statusData, err := os.ReadFile(statusPath)
187+
if err != nil {
188+
continue
189+
}
190+
191+
for _, line := range strings.Split(string(statusData), "\n") {
192+
if strings.HasPrefix(line, "NSpid:") {
193+
fields := strings.Fields(line)
194+
if len(fields) >= 3 {
195+
rootPID := fields[1] // Root namespace PID
196+
containerPID := fields[len(fields)-1] // Container namespace PID
197+
pidTranslation[rootPID] = containerPID
198+
}
199+
break
200+
}
201+
}
202+
}
203+
204+
// Second pass: collect process information
158205
var processes []*pb.ProcessInfo
159206

160207
for _, entry := range entries {
161208
if !entry.IsDir() {
162209
continue
163210
}
164211

165-
// Check if directory name is a number (PID)
166212
pidStr := entry.Name()
167213
pid, err := strconv.Atoi(pidStr)
168214
if err != nil {
@@ -172,18 +218,16 @@ func listContainerProcesses() ([]*pb.ProcessInfo, error) {
172218
// Check if this process is in the root namespace
173219
procNS, err := os.Readlink(fmt.Sprintf("/proc/%d/ns/pid", pid))
174220
if err != nil {
175-
// Process may have exited, skip it
176221
continue
177222
}
178223

179-
// Skip processes in the root namespace (vminitd, services, kernel threads)
180-
// Only include processes NOT in root namespace (container processes)
224+
// Skip processes in the root namespace
181225
if procNS == rootNS {
182226
continue
183227
}
184228

185-
// Read process information
186-
procInfo, err := readProcessInfo(pidStr)
229+
// Read process information with PID translation
230+
procInfo, err := readProcessInfo(pidStr, pidTranslation)
187231
if err != nil {
188232
// Process may have exited or unreadable, skip it
189233
continue
@@ -198,7 +242,8 @@ func listContainerProcesses() ([]*pb.ProcessInfo, error) {
198242
}
199243

200244
// readProcessInfo reads process information from /proc/[pid]
201-
func readProcessInfo(pid string) ([]string, error) {
245+
// pidTranslation maps root namespace PIDs to container namespace PIDs
246+
func readProcessInfo(pid string, pidTranslation map[string]string) ([]string, error) {
202247
// Read /proc/[pid]/stat for basic process info
203248
statPath := filepath.Join("/proc", pid, "stat")
204249
statData, err := os.ReadFile(statPath)
@@ -222,7 +267,7 @@ func readProcessInfo(pid string) ([]string, error) {
222267
return nil, fmt.Errorf("insufficient stat fields")
223268
}
224269

225-
ppid := afterComm[0]
270+
ppid := afterComm[0] // PPID in root namespace
226271
// state := afterComm[0] // We could use this if needed
227272
utime := afterComm[11]
228273
stime := afterComm[12]
@@ -257,6 +302,17 @@ func readProcessInfo(pid string) ([]string, error) {
257302
}
258303
}
259304

305+
// Translate PIDs from root namespace to container namespace
306+
containerPID := pidTranslation[pid]
307+
if containerPID == "" {
308+
containerPID = pid // fallback to root namespace PID
309+
}
310+
311+
containerPPID := pidTranslation[ppid]
312+
if containerPPID == "" {
313+
containerPPID = "0" // Parent not in container namespace (e.g., init's parent)
314+
}
315+
260316
// Calculate total CPU time (utime + stime)
261317
// These are in clock ticks, convert to seconds (assuming 100 ticks/second)
262318
utimeInt, _ := strconv.ParseInt(utime, 10, 64)
@@ -271,13 +327,13 @@ func readProcessInfo(pid string) ([]string, error) {
271327
// Build ps -ef format output
272328
// UID PID PPID C STIME TTY TIME CMD
273329
return []string{
274-
uid, // UID
275-
pid, // PID
276-
ppid, // PPID
277-
"0", // C (CPU utilization - we could calculate this from stat)
278-
"?", // STIME (start time - we could read from /proc/[pid]/stat field 21)
279-
"?", // TTY
280-
timeStr, // TIME
281-
cmdline, // CMD
330+
uid, // UID
331+
containerPID, // PID (container namespace)
332+
containerPPID, // PPID (container namespace)
333+
"0", // C (CPU utilization - we could calculate this from stat)
334+
"?", // STIME (start time - we could read from /proc/[pid]/stat field 21)
335+
"?", // TTY
336+
timeStr, // TIME
337+
cmdline, // CMD
282338
}, nil
283339
}

0 commit comments

Comments
 (0)