@@ -38,7 +38,7 @@ import (
38
38
"github.com/google/cadvisor/summary"
39
39
"github.com/google/cadvisor/utils/cpuload"
40
40
41
- units "github.com/docker/go-units"
41
+ "github.com/docker/go-units"
42
42
"k8s.io/klog/v2"
43
43
"k8s.io/utils/clock"
44
44
)
@@ -47,9 +47,14 @@ import (
47
47
var enableLoadReader = flag .Bool ("enable_load_reader" , false , "Whether to enable cpu load reader" )
48
48
var HousekeepingInterval = flag .Duration ("housekeeping_interval" , 1 * time .Second , "Interval between container housekeepings" )
49
49
50
+ // TODO: replace regular expressions with something simpler, such as strings.Split().
50
51
// cgroup type chosen to fetch the cgroup path of a process.
51
- // Memory has been chosen, as it is one of the default cgroups that is enabled for most containers.
52
- var cgroupPathRegExp = regexp .MustCompile (`memory[^:]*:(.*?)[,;$]` )
52
+ // Memory has been chosen, as it is one of the default cgroups that is enabled for most containers...
53
+ var cgroupMemoryPathRegExp = regexp .MustCompile (`memory[^:]*:(.*?)[,;$]` )
54
+
55
+ // ... but there are systems (e.g. Raspberry Pi 4) where memory cgroup controller is disabled by default.
56
+ // We should check cpu cgroup then.
57
+ var cgroupCPUPathRegExp = regexp .MustCompile (`cpu[^:]*:(.*?)[,;$]` )
53
58
54
59
type containerInfo struct {
55
60
info.ContainerReference
@@ -198,20 +203,28 @@ func (cd *containerData) DerivedStats() (v2.DerivedStats, error) {
198
203
return cd .summaryReader .DerivedStats ()
199
204
}
200
205
201
- func (cd * containerData ) getCgroupPath (cgroups string ) ( string , error ) {
206
+ func (cd * containerData ) getCgroupPath (cgroups string ) string {
202
207
if cgroups == "-" {
203
- return "/" , nil
208
+ return "/"
204
209
}
205
210
if strings .HasPrefix (cgroups , "0::" ) {
206
- return cgroups [3 :], nil
211
+ return cgroups [3 :]
207
212
}
208
- matches := cgroupPathRegExp .FindSubmatch ([]byte (cgroups ))
213
+ matches := cgroupMemoryPathRegExp .FindSubmatch ([]byte (cgroups ))
209
214
if len (matches ) != 2 {
210
- klog .V (3 ).Infof ("failed to get memory cgroup path from %q" , cgroups )
211
- // return root in case of failures - memory hierarchy might not be enabled.
212
- return "/" , nil
213
- }
214
- return string (matches [1 ]), nil
215
+ klog .V (3 ).Infof (
216
+ "failed to get memory cgroup path from %q, will try to get cpu cgroup path" ,
217
+ cgroups ,
218
+ )
219
+ // On some systems (e.g. Raspberry PI 4) cgroup memory controlled is disabled by default.
220
+ matches = cgroupCPUPathRegExp .FindSubmatch ([]byte (cgroups ))
221
+ if len (matches ) != 2 {
222
+ klog .V (3 ).Infof ("failed to get cpu cgroup path from %q; assuming root cgroup" , cgroups )
223
+ // return root in case of failures - memory hierarchy might not be enabled.
224
+ return "/"
225
+ }
226
+ }
227
+ return string (matches [1 ])
215
228
}
216
229
217
230
// Returns contents of a file inside the container root.
@@ -274,10 +287,7 @@ func (cd *containerData) getContainerPids(inHostNamespace bool) ([]string, error
274
287
return nil , fmt .Errorf ("expected at least %d fields, found %d: output: %q" , expectedFields , len (fields ), line )
275
288
}
276
289
pid := fields [0 ]
277
- cgroup , err := cd .getCgroupPath (fields [1 ])
278
- if err != nil {
279
- return nil , fmt .Errorf ("could not parse cgroup path from %q: %v" , fields [1 ], err )
280
- }
290
+ cgroup := cd .getCgroupPath (fields [1 ])
281
291
if cd .info .Name == cgroup {
282
292
pids = append (pids , pid )
283
293
}
@@ -286,106 +296,130 @@ func (cd *containerData) getContainerPids(inHostNamespace bool) ([]string, error
286
296
}
287
297
288
298
func (cd * containerData ) GetProcessList (cadvisorContainer string , inHostNamespace bool ) ([]v2.ProcessInfo , error ) {
289
- // report all processes for root.
290
- isRoot := cd .info .Name == "/"
291
- rootfs := "/"
292
- if ! inHostNamespace {
293
- rootfs = "/rootfs"
294
- }
295
299
format := "user,pid,ppid,stime,pcpu,pmem,rss,vsz,stat,time,comm,psr,cgroup"
296
300
out , err := cd .getPsOutput (inHostNamespace , format )
297
301
if err != nil {
298
302
return nil , err
299
303
}
300
- expectedFields := 13
304
+ return cd .parseProcessList (cadvisorContainer , inHostNamespace , out )
305
+ }
306
+
307
+ func (cd * containerData ) parseProcessList (cadvisorContainer string , inHostNamespace bool , out []byte ) ([]v2.ProcessInfo , error ) {
308
+ rootfs := "/"
309
+ if ! inHostNamespace {
310
+ rootfs = "/rootfs"
311
+ }
301
312
processes := []v2.ProcessInfo {}
302
313
lines := strings .Split (string (out ), "\n " )
303
314
for _ , line := range lines [1 :] {
304
- if len (line ) == 0 {
305
- continue
306
- }
307
- fields := strings .Fields (line )
308
- if len (fields ) < expectedFields {
309
- return nil , fmt .Errorf ("expected at least %d fields, found %d: output: %q" , expectedFields , len (fields ), line )
310
- }
311
- pid , err := strconv .Atoi (fields [1 ])
312
- if err != nil {
313
- return nil , fmt .Errorf ("invalid pid %q: %v" , fields [1 ], err )
314
- }
315
- ppid , err := strconv .Atoi (fields [2 ])
316
- if err != nil {
317
- return nil , fmt .Errorf ("invalid ppid %q: %v" , fields [2 ], err )
318
- }
319
- percentCPU , err := strconv .ParseFloat (fields [4 ], 32 )
320
- if err != nil {
321
- return nil , fmt .Errorf ("invalid cpu percent %q: %v" , fields [4 ], err )
322
- }
323
- percentMem , err := strconv .ParseFloat (fields [5 ], 32 )
324
- if err != nil {
325
- return nil , fmt .Errorf ("invalid memory percent %q: %v" , fields [5 ], err )
326
- }
327
- rss , err := strconv .ParseUint (fields [6 ], 0 , 64 )
328
- if err != nil {
329
- return nil , fmt .Errorf ("invalid rss %q: %v" , fields [6 ], err )
330
- }
331
- // convert to bytes
332
- rss *= 1024
333
- vs , err := strconv .ParseUint (fields [7 ], 0 , 64 )
334
- if err != nil {
335
- return nil , fmt .Errorf ("invalid virtual size %q: %v" , fields [7 ], err )
336
- }
337
- // convert to bytes
338
- vs *= 1024
339
- psr , err := strconv .Atoi (fields [11 ])
340
- if err != nil {
341
- return nil , fmt .Errorf ("invalid pid %q: %v" , fields [1 ], err )
342
- }
343
-
344
- cgroup , err := cd .getCgroupPath (fields [12 ])
315
+ processInfo , err := cd .parsePsLine (line , cadvisorContainer , inHostNamespace )
345
316
if err != nil {
346
- return nil , fmt .Errorf ("could not parse cgroup path from %q : %v" , fields [ 11 ] , err )
317
+ return nil , fmt .Errorf ("could not parse line %s : %v" , line , err )
347
318
}
348
- // Remove the ps command we just ran from cadvisor container.
349
- // Not necessary, but makes the cadvisor page look cleaner.
350
- if ! inHostNamespace && cadvisorContainer == cgroup && fields [10 ] == "ps" {
319
+ if processInfo == nil {
351
320
continue
352
321
}
353
- var cgroupPath string
354
- if isRoot {
355
- cgroupPath = cgroup
356
- }
357
322
358
323
var fdCount int
359
- dirPath := path .Join (rootfs , "/proc" , strconv .Itoa (pid ), "fd" )
324
+ dirPath := path .Join (rootfs , "/proc" , strconv .Itoa (processInfo . Pid ), "fd" )
360
325
fds , err := ioutil .ReadDir (dirPath )
361
326
if err != nil {
362
327
klog .V (4 ).Infof ("error while listing directory %q to measure fd count: %v" , dirPath , err )
363
328
continue
364
329
}
365
330
fdCount = len (fds )
331
+ processInfo .FdCount = fdCount
366
332
367
- if isRoot || cd .info .Name == cgroup {
368
- processes = append (processes , v2.ProcessInfo {
369
- User : fields [0 ],
370
- Pid : pid ,
371
- Ppid : ppid ,
372
- StartTime : fields [3 ],
373
- PercentCpu : float32 (percentCPU ),
374
- PercentMemory : float32 (percentMem ),
375
- RSS : rss ,
376
- VirtualSize : vs ,
377
- Status : fields [8 ],
378
- RunningTime : fields [9 ],
379
- Cmd : fields [10 ],
380
- CgroupPath : cgroupPath ,
381
- FdCount : fdCount ,
382
- Psr : psr ,
383
- })
384
- }
333
+ processes = append (processes , * processInfo )
385
334
}
386
335
return processes , nil
387
336
}
388
337
338
+ func (cd * containerData ) isRoot () bool {
339
+ return cd .info .Name == "/"
340
+ }
341
+
342
+ func (cd * containerData ) parsePsLine (line , cadvisorContainer string , inHostNamespace bool ) (* v2.ProcessInfo , error ) {
343
+ const expectedFields = 13
344
+ if len (line ) == 0 {
345
+ return nil , nil
346
+ }
347
+
348
+ info := v2.ProcessInfo {}
349
+ var err error
350
+
351
+ fields := strings .Fields (line )
352
+ if len (fields ) < expectedFields {
353
+ return nil , fmt .Errorf ("expected at least %d fields, found %d: output: %q" , expectedFields , len (fields ), line )
354
+ }
355
+ info .User = fields [0 ]
356
+ info .StartTime = fields [3 ]
357
+ info .Status = fields [8 ]
358
+ info .RunningTime = fields [9 ]
359
+
360
+ info .Pid , err = strconv .Atoi (fields [1 ])
361
+ if err != nil {
362
+ return nil , fmt .Errorf ("invalid pid %q: %v" , fields [1 ], err )
363
+ }
364
+ info .Ppid , err = strconv .Atoi (fields [2 ])
365
+ if err != nil {
366
+ return nil , fmt .Errorf ("invalid ppid %q: %v" , fields [2 ], err )
367
+ }
368
+
369
+ percentCPU , err := strconv .ParseFloat (fields [4 ], 32 )
370
+ if err != nil {
371
+ return nil , fmt .Errorf ("invalid cpu percent %q: %v" , fields [4 ], err )
372
+ }
373
+ info .PercentCpu = float32 (percentCPU )
374
+ percentMem , err := strconv .ParseFloat (fields [5 ], 32 )
375
+ if err != nil {
376
+ return nil , fmt .Errorf ("invalid memory percent %q: %v" , fields [5 ], err )
377
+ }
378
+ info .PercentMemory = float32 (percentMem )
379
+
380
+ info .RSS , err = strconv .ParseUint (fields [6 ], 0 , 64 )
381
+ if err != nil {
382
+ return nil , fmt .Errorf ("invalid rss %q: %v" , fields [6 ], err )
383
+ }
384
+ info .VirtualSize , err = strconv .ParseUint (fields [7 ], 0 , 64 )
385
+ if err != nil {
386
+ return nil , fmt .Errorf ("invalid virtual size %q: %v" , fields [7 ], err )
387
+ }
388
+ // convert to bytes
389
+ info .RSS *= 1024
390
+ info .VirtualSize *= 1024
391
+
392
+ // According to `man ps`: The following user-defined format specifiers may contain spaces: args, cmd, comm, command,
393
+ // fname, ucmd, ucomm, lstart, bsdstart, start.
394
+ // Therefore we need to be able to parse comm that consists of multiple space-separated parts.
395
+ info .Cmd = strings .Join (fields [10 :len (fields )- 2 ], " " )
396
+
397
+ // These are last two parts of the line. We create a subslice of `fields` to handle comm that includes spaces.
398
+ lastTwoFields := fields [len (fields )- 2 :]
399
+ info .Psr , err = strconv .Atoi (lastTwoFields [0 ])
400
+ if err != nil {
401
+ return nil , fmt .Errorf ("invalid psr %q: %v" , lastTwoFields [0 ], err )
402
+ }
403
+ info .CgroupPath = cd .getCgroupPath (lastTwoFields [1 ])
404
+
405
+ // Remove the ps command we just ran from cadvisor container.
406
+ // Not necessary, but makes the cadvisor page look cleaner.
407
+ if ! inHostNamespace && cadvisorContainer == info .CgroupPath && info .Cmd == "ps" {
408
+ return nil , nil
409
+ }
410
+
411
+ // Do not report processes from other containers when non-root container requested.
412
+ if ! cd .isRoot () && info .CgroupPath != cd .info .Name {
413
+ return nil , nil
414
+ }
415
+
416
+ // Remove cgroup information when non-root container requested.
417
+ if ! cd .isRoot () {
418
+ info .CgroupPath = ""
419
+ }
420
+ return & info , nil
421
+ }
422
+
389
423
func newContainerData (containerName string , memoryCache * memory.InMemoryCache , handler container.ContainerHandler , logUsage bool , collectorManager collector.CollectorManager , maxHousekeepingInterval time.Duration , allowDynamicHousekeeping bool , clock clock.Clock ) (* containerData , error ) {
390
424
if memoryCache == nil {
391
425
return nil , fmt .Errorf ("nil memory storage" )
@@ -519,7 +553,7 @@ func (cd *containerData) housekeeping() {
519
553
usageCPUNs := uint64 (0 )
520
554
for i := range stats {
521
555
if i > 0 {
522
- usageCPUNs += ( stats [i ].Cpu .Usage .Total - stats [i - 1 ].Cpu .Usage .Total )
556
+ usageCPUNs += stats [i ].Cpu .Usage .Total - stats [i - 1 ].Cpu .Usage .Total
523
557
}
524
558
}
525
559
usageMemory := stats [numSamples - 1 ].Memory .Usage
0 commit comments