@@ -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