github.com/google/cadvisor@v0.49.1/container/libcontainer/handler.go (about) 1 // Copyright 2018 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package libcontainer 16 17 import ( 18 "bufio" 19 "bytes" 20 "encoding/json" 21 "flag" 22 "fmt" 23 "io" 24 "os" 25 "path" 26 "regexp" 27 "strconv" 28 "strings" 29 "time" 30 31 "github.com/opencontainers/runc/libcontainer" 32 "github.com/opencontainers/runc/libcontainer/cgroups" 33 "github.com/opencontainers/runc/libcontainer/cgroups/fs2" 34 "k8s.io/klog/v2" 35 36 "github.com/google/cadvisor/container" 37 "github.com/google/cadvisor/container/common" 38 info "github.com/google/cadvisor/info/v1" 39 ) 40 41 var ( 42 referencedResetInterval = flag.Uint64("referenced_reset_interval", 0, 43 "Reset interval for referenced bytes (container_referenced_bytes metric), number of measurement cycles after which referenced bytes are cleared, if set to 0 referenced bytes are never cleared (default: 0)") 44 45 smapsFilePathPattern = "/proc/%d/smaps" 46 clearRefsFilePathPattern = "/proc/%d/clear_refs" 47 48 referencedRegexp = regexp.MustCompile(`Referenced:\s*([0-9]+)\s*kB`) 49 ) 50 51 type Handler struct { 52 cgroupManager cgroups.Manager 53 rootFs string 54 pid int 55 includedMetrics container.MetricSet 56 // pidMetricsCache holds CPU scheduler stats for existing processes (map key is PID) between calls to schedulerStatsFromProcs. 57 pidMetricsCache map[int]*info.CpuSchedstat 58 // pidMetricsSaved holds accumulated CPU scheduler stats for processes that no longer exist. 59 pidMetricsSaved info.CpuSchedstat 60 cycles uint64 61 } 62 63 func NewHandler(cgroupManager cgroups.Manager, rootFs string, pid int, includedMetrics container.MetricSet) *Handler { 64 return &Handler{ 65 cgroupManager: cgroupManager, 66 rootFs: rootFs, 67 pid: pid, 68 includedMetrics: includedMetrics, 69 pidMetricsCache: make(map[int]*info.CpuSchedstat), 70 } 71 } 72 73 // Get cgroup and networking stats of the specified container 74 func (h *Handler) GetStats() (*info.ContainerStats, error) { 75 ignoreStatsError := false 76 if cgroups.IsCgroup2UnifiedMode() { 77 // On cgroup v2 the root cgroup stats have been introduced in recent kernel versions, 78 // so not all kernel versions have all the data. This means that stat fetching can fail 79 // due to lacking cgroup stat files, but that some data is provided. 80 if h.cgroupManager.Path("") == fs2.UnifiedMountpoint { 81 ignoreStatsError = true 82 } 83 } 84 85 cgroupStats, err := h.cgroupManager.GetStats() 86 if err != nil { 87 if !ignoreStatsError { 88 return nil, err 89 } 90 klog.V(4).Infof("Ignoring errors when gathering stats for root cgroup since some controllers don't have stats on the root cgroup: %v", err) 91 } 92 libcontainerStats := &libcontainer.Stats{ 93 CgroupStats: cgroupStats, 94 } 95 stats := newContainerStats(libcontainerStats, h.includedMetrics) 96 97 if h.includedMetrics.Has(container.ProcessSchedulerMetrics) { 98 stats.Cpu.Schedstat, err = h.schedulerStatsFromProcs() 99 if err != nil { 100 klog.V(4).Infof("Unable to get Process Scheduler Stats: %v", err) 101 } 102 } 103 104 if h.includedMetrics.Has(container.ReferencedMemoryMetrics) { 105 h.cycles++ 106 pids, err := h.cgroupManager.GetPids() 107 if err != nil { 108 klog.V(4).Infof("Could not get PIDs for container %d: %v", h.pid, err) 109 } else { 110 stats.ReferencedMemory, err = referencedBytesStat(pids, h.cycles, *referencedResetInterval) 111 if err != nil { 112 klog.V(4).Infof("Unable to get referenced bytes: %v", err) 113 } 114 } 115 } 116 117 // If we know the pid then get network stats from /proc/<pid>/net/dev 118 if h.pid > 0 { 119 if h.includedMetrics.Has(container.NetworkUsageMetrics) { 120 netStats, err := networkStatsFromProc(h.rootFs, h.pid) 121 if err != nil { 122 klog.V(4).Infof("Unable to get network stats from pid %d: %v", h.pid, err) 123 } else { 124 stats.Network.Interfaces = append(stats.Network.Interfaces, netStats...) 125 } 126 } 127 if h.includedMetrics.Has(container.NetworkTcpUsageMetrics) { 128 t, err := tcpStatsFromProc(h.rootFs, h.pid, "net/tcp") 129 if err != nil { 130 klog.V(4).Infof("Unable to get tcp stats from pid %d: %v", h.pid, err) 131 } else { 132 stats.Network.Tcp = t 133 } 134 135 t6, err := tcpStatsFromProc(h.rootFs, h.pid, "net/tcp6") 136 if err != nil { 137 klog.V(4).Infof("Unable to get tcp6 stats from pid %d: %v", h.pid, err) 138 } else { 139 stats.Network.Tcp6 = t6 140 } 141 142 } 143 if h.includedMetrics.Has(container.NetworkAdvancedTcpUsageMetrics) { 144 ta, err := advancedTCPStatsFromProc(h.rootFs, h.pid, "net/netstat", "net/snmp") 145 if err != nil { 146 klog.V(4).Infof("Unable to get advanced tcp stats from pid %d: %v", h.pid, err) 147 } else { 148 stats.Network.TcpAdvanced = ta 149 } 150 } 151 if h.includedMetrics.Has(container.NetworkUdpUsageMetrics) { 152 u, err := udpStatsFromProc(h.rootFs, h.pid, "net/udp") 153 if err != nil { 154 klog.V(4).Infof("Unable to get udp stats from pid %d: %v", h.pid, err) 155 } else { 156 stats.Network.Udp = u 157 } 158 159 u6, err := udpStatsFromProc(h.rootFs, h.pid, "net/udp6") 160 if err != nil { 161 klog.V(4).Infof("Unable to get udp6 stats from pid %d: %v", h.pid, err) 162 } else { 163 stats.Network.Udp6 = u6 164 } 165 } 166 } 167 // some process metrics are per container ( number of processes, number of 168 // file descriptors etc.) and not required a proper container's 169 // root PID (systemd services don't have the root PID atm) 170 if h.includedMetrics.Has(container.ProcessMetrics) { 171 path, ok := common.GetControllerPath(h.cgroupManager.GetPaths(), "cpu", cgroups.IsCgroup2UnifiedMode()) 172 if !ok { 173 klog.V(4).Infof("Could not find cgroups CPU for container %d", h.pid) 174 } else { 175 stats.Processes, err = processStatsFromProcs(h.rootFs, path, h.pid) 176 if err != nil { 177 klog.V(4).Infof("Unable to get Process Stats: %v", err) 178 } 179 } 180 181 // if include processes metrics, just set threads metrics if exist, and has no relationship with cpu path 182 setThreadsStats(cgroupStats, stats) 183 } 184 185 // For backwards compatibility. 186 if len(stats.Network.Interfaces) > 0 { 187 stats.Network.InterfaceStats = stats.Network.Interfaces[0] 188 } 189 190 return stats, nil 191 } 192 193 func parseUlimit(value string) (int64, error) { 194 num, err := strconv.ParseInt(value, 10, 64) 195 if err != nil { 196 if strings.EqualFold(value, "unlimited") { 197 // -1 implies unlimited except for priority and nice; man limits.conf 198 num = -1 199 } else { 200 // Value is not a number or "unlimited"; return an error 201 return 0, fmt.Errorf("unable to parse limit: %s", value) 202 } 203 } 204 return num, nil 205 } 206 207 func processLimitsFile(fileData string) []info.UlimitSpec { 208 const maxOpenFilesLinePrefix = "Max open files" 209 210 limits := strings.Split(fileData, "\n") 211 ulimits := make([]info.UlimitSpec, 0, len(limits)) 212 for _, lim := range limits { 213 // Skip any headers/footers 214 if strings.HasPrefix(lim, "Max open files") { 215 // Remove line prefix 216 ulimit, err := processMaxOpenFileLimitLine( 217 "max_open_files", 218 lim[len(maxOpenFilesLinePrefix):], 219 ) 220 if err == nil { 221 ulimits = append(ulimits, ulimit) 222 } 223 } 224 } 225 return ulimits 226 } 227 228 // Any caller of processMaxOpenFileLimitLine must ensure that the name prefix is already removed from the limit line. 229 // with the "Max open files" prefix. 230 func processMaxOpenFileLimitLine(name, line string) (info.UlimitSpec, error) { 231 // Remove any leading whitespace 232 line = strings.TrimSpace(line) 233 // Split on whitespace 234 fields := strings.Fields(line) 235 if len(fields) != 3 { 236 return info.UlimitSpec{}, fmt.Errorf("unable to parse max open files line: %s", line) 237 } 238 // The first field is the soft limit, the second is the hard limit 239 soft, err := parseUlimit(fields[0]) 240 if err != nil { 241 return info.UlimitSpec{}, err 242 } 243 hard, err := parseUlimit(fields[1]) 244 if err != nil { 245 return info.UlimitSpec{}, err 246 } 247 return info.UlimitSpec{ 248 Name: name, 249 SoftLimit: soft, 250 HardLimit: hard, 251 }, nil 252 } 253 254 func processRootProcUlimits(rootFs string, rootPid int) []info.UlimitSpec { 255 filePath := path.Join(rootFs, "/proc", strconv.Itoa(rootPid), "limits") 256 out, err := os.ReadFile(filePath) 257 if err != nil { 258 klog.V(4).Infof("error while listing directory %q to read ulimits: %v", filePath, err) 259 return []info.UlimitSpec{} 260 } 261 return processLimitsFile(string(out)) 262 } 263 264 func processStatsFromProcs(rootFs string, cgroupPath string, rootPid int) (info.ProcessStats, error) { 265 var fdCount, socketCount uint64 266 filePath := path.Join(cgroupPath, "cgroup.procs") 267 out, err := os.ReadFile(filePath) 268 if err != nil { 269 return info.ProcessStats{}, fmt.Errorf("couldn't open cpu cgroup procs file %v : %v", filePath, err) 270 } 271 272 pids := strings.Split(string(out), "\n") 273 274 // EOL is also treated as a new line while reading "cgroup.procs" file with os.ReadFile. 275 // The last value is an empty string "". Ex: pids = ["22", "1223", ""] 276 // Trim the last value 277 if len(pids) != 0 && pids[len(pids)-1] == "" { 278 pids = pids[:len(pids)-1] 279 } 280 281 for _, pid := range pids { 282 dirPath := path.Join(rootFs, "/proc", pid, "fd") 283 fds, err := os.ReadDir(dirPath) 284 if err != nil { 285 klog.V(4).Infof("error while listing directory %q to measure fd count: %v", dirPath, err) 286 continue 287 } 288 fdCount += uint64(len(fds)) 289 for _, fd := range fds { 290 fdPath := path.Join(dirPath, fd.Name()) 291 linkName, err := os.Readlink(fdPath) 292 if err != nil { 293 klog.V(4).Infof("error while reading %q link: %v", fdPath, err) 294 continue 295 } 296 if strings.HasPrefix(linkName, "socket") { 297 socketCount++ 298 } 299 } 300 } 301 302 processStats := info.ProcessStats{ 303 ProcessCount: uint64(len(pids)), 304 FdCount: fdCount, 305 SocketCount: socketCount, 306 } 307 308 if rootPid > 0 { 309 processStats.Ulimits = processRootProcUlimits(rootFs, rootPid) 310 } 311 312 return processStats, nil 313 } 314 315 func (h *Handler) schedulerStatsFromProcs() (info.CpuSchedstat, error) { 316 pids, err := h.cgroupManager.GetAllPids() 317 if err != nil { 318 return info.CpuSchedstat{}, fmt.Errorf("Could not get PIDs for container %d: %w", h.pid, err) 319 } 320 alivePids := make(map[int]struct{}, len(pids)) 321 for _, pid := range pids { 322 f, err := os.Open(path.Join(h.rootFs, "proc", strconv.Itoa(pid), "schedstat")) 323 if err != nil { 324 return info.CpuSchedstat{}, fmt.Errorf("couldn't open scheduler statistics for process %d: %v", pid, err) 325 } 326 defer f.Close() 327 contents, err := io.ReadAll(f) 328 if err != nil { 329 return info.CpuSchedstat{}, fmt.Errorf("couldn't read scheduler statistics for process %d: %v", pid, err) 330 } 331 alivePids[pid] = struct{}{} 332 rawMetrics := bytes.Split(bytes.TrimRight(contents, "\n"), []byte(" ")) 333 if len(rawMetrics) != 3 { 334 return info.CpuSchedstat{}, fmt.Errorf("unexpected number of metrics in schedstat file for process %d", pid) 335 } 336 cacheEntry, ok := h.pidMetricsCache[pid] 337 if !ok { 338 cacheEntry = &info.CpuSchedstat{} 339 h.pidMetricsCache[pid] = cacheEntry 340 } 341 for i, rawMetric := range rawMetrics { 342 metric, err := strconv.ParseUint(string(rawMetric), 10, 64) 343 if err != nil { 344 return info.CpuSchedstat{}, fmt.Errorf("parsing error while reading scheduler statistics for process: %d: %v", pid, err) 345 } 346 switch i { 347 case 0: 348 cacheEntry.RunTime = metric 349 case 1: 350 cacheEntry.RunqueueTime = metric 351 case 2: 352 cacheEntry.RunPeriods = metric 353 } 354 } 355 } 356 schedstats := h.pidMetricsSaved // copy 357 for p, v := range h.pidMetricsCache { 358 schedstats.RunPeriods += v.RunPeriods 359 schedstats.RunqueueTime += v.RunqueueTime 360 schedstats.RunTime += v.RunTime 361 if _, alive := alivePids[p]; !alive { 362 // PID p is gone: accumulate its stats ... 363 h.pidMetricsSaved.RunPeriods += v.RunPeriods 364 h.pidMetricsSaved.RunqueueTime += v.RunqueueTime 365 h.pidMetricsSaved.RunTime += v.RunTime 366 // ... and remove its cache entry, to prevent 367 // pidMetricsCache from growing. 368 delete(h.pidMetricsCache, p) 369 } 370 } 371 return schedstats, nil 372 } 373 374 // referencedBytesStat gets and clears referenced bytes 375 // see: https://github.com/brendangregg/wss#wsspl-referenced-page-flag 376 func referencedBytesStat(pids []int, cycles uint64, resetInterval uint64) (uint64, error) { 377 referencedKBytes, err := getReferencedKBytes(pids) 378 if err != nil { 379 return uint64(0), err 380 } 381 382 err = clearReferencedBytes(pids, cycles, resetInterval) 383 if err != nil { 384 return uint64(0), err 385 } 386 return referencedKBytes * 1024, nil 387 } 388 389 func getReferencedKBytes(pids []int) (uint64, error) { 390 referencedKBytes := uint64(0) 391 readSmapsContent := false 392 foundMatch := false 393 for _, pid := range pids { 394 smapsFilePath := fmt.Sprintf(smapsFilePathPattern, pid) 395 smapsContent, err := os.ReadFile(smapsFilePath) 396 if err != nil { 397 klog.V(5).Infof("Cannot read %s file, err: %s", smapsFilePath, err) 398 if os.IsNotExist(err) { 399 continue // smaps file does not exists for all PIDs 400 } 401 return 0, err 402 } 403 readSmapsContent = true 404 405 allMatches := referencedRegexp.FindAllSubmatch(smapsContent, -1) 406 if len(allMatches) == 0 { 407 klog.V(5).Infof("Not found any information about referenced bytes in %s file", smapsFilePath) 408 continue // referenced bytes may not exist in smaps file 409 } 410 411 for _, matches := range allMatches { 412 if len(matches) != 2 { 413 return 0, fmt.Errorf("failed to match regexp in output: %s", string(smapsContent)) 414 } 415 foundMatch = true 416 referenced, err := strconv.ParseUint(string(matches[1]), 10, 64) 417 if err != nil { 418 return 0, err 419 } 420 referencedKBytes += referenced 421 } 422 } 423 424 if len(pids) != 0 { 425 if !readSmapsContent { 426 klog.Warningf("Cannot read smaps files for any PID from %s", "CONTAINER") 427 } else if !foundMatch { 428 klog.Warningf("Not found any information about referenced bytes in smaps files for any PID from %s", "CONTAINER") 429 } 430 } 431 return referencedKBytes, nil 432 } 433 434 func clearReferencedBytes(pids []int, cycles uint64, resetInterval uint64) error { 435 if resetInterval == 0 { 436 return nil 437 } 438 439 if cycles%resetInterval == 0 { 440 for _, pid := range pids { 441 clearRefsFilePath := fmt.Sprintf(clearRefsFilePathPattern, pid) 442 clerRefsFile, err := os.OpenFile(clearRefsFilePath, os.O_WRONLY, 0o644) 443 if err != nil { 444 // clear_refs file may not exist for all PIDs 445 continue 446 } 447 _, err = clerRefsFile.WriteString("1\n") 448 if err != nil { 449 return err 450 } 451 err = clerRefsFile.Close() 452 if err != nil { 453 return err 454 } 455 } 456 } 457 return nil 458 } 459 460 func networkStatsFromProc(rootFs string, pid int) ([]info.InterfaceStats, error) { 461 netStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), "/net/dev") 462 463 ifaceStats, err := scanInterfaceStats(netStatsFile) 464 if err != nil { 465 return []info.InterfaceStats{}, fmt.Errorf("couldn't read network stats: %v", err) 466 } 467 468 return ifaceStats, nil 469 } 470 471 var ignoredDevicePrefixes = []string{"lo", "veth", "docker", "nerdctl"} 472 473 func isIgnoredDevice(ifName string) bool { 474 for _, prefix := range ignoredDevicePrefixes { 475 if strings.HasPrefix(strings.ToLower(ifName), prefix) { 476 return true 477 } 478 } 479 return false 480 } 481 482 func scanInterfaceStats(netStatsFile string) ([]info.InterfaceStats, error) { 483 file, err := os.Open(netStatsFile) 484 if err != nil { 485 return nil, fmt.Errorf("failure opening %s: %v", netStatsFile, err) 486 } 487 defer file.Close() 488 489 scanner := bufio.NewScanner(file) 490 491 // Discard header lines 492 for i := 0; i < 2; i++ { 493 if b := scanner.Scan(); !b { 494 return nil, scanner.Err() 495 } 496 } 497 498 stats := []info.InterfaceStats{} 499 for scanner.Scan() { 500 line := scanner.Text() 501 line = strings.Replace(line, ":", "", -1) 502 503 fields := strings.Fields(line) 504 // If the format of the line is invalid then don't trust any of the stats 505 // in this file. 506 if len(fields) != 17 { 507 return nil, fmt.Errorf("invalid interface stats line: %v", line) 508 } 509 510 devName := fields[0] 511 if isIgnoredDevice(devName) { 512 continue 513 } 514 515 i := info.InterfaceStats{ 516 Name: devName, 517 } 518 519 statFields := append(fields[1:5], fields[9:13]...) 520 statPointers := []*uint64{ 521 &i.RxBytes, &i.RxPackets, &i.RxErrors, &i.RxDropped, 522 &i.TxBytes, &i.TxPackets, &i.TxErrors, &i.TxDropped, 523 } 524 525 err := setInterfaceStatValues(statFields, statPointers) 526 if err != nil { 527 return nil, fmt.Errorf("cannot parse interface stats (%v): %v", err, line) 528 } 529 530 stats = append(stats, i) 531 } 532 533 return stats, nil 534 } 535 536 func setInterfaceStatValues(fields []string, pointers []*uint64) error { 537 for i, v := range fields { 538 val, err := strconv.ParseUint(v, 10, 64) 539 if err != nil { 540 return err 541 } 542 *pointers[i] = val 543 } 544 return nil 545 } 546 547 func tcpStatsFromProc(rootFs string, pid int, file string) (info.TcpStat, error) { 548 tcpStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file) 549 550 tcpStats, err := scanTCPStats(tcpStatsFile) 551 if err != nil { 552 return tcpStats, fmt.Errorf("couldn't read tcp stats: %v", err) 553 } 554 555 return tcpStats, nil 556 } 557 558 func advancedTCPStatsFromProc(rootFs string, pid int, file1, file2 string) (info.TcpAdvancedStat, error) { 559 var advancedStats info.TcpAdvancedStat 560 var err error 561 562 netstatFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file1) 563 err = scanAdvancedTCPStats(&advancedStats, netstatFile) 564 if err != nil { 565 return advancedStats, err 566 } 567 568 snmpFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file2) 569 err = scanAdvancedTCPStats(&advancedStats, snmpFile) 570 if err != nil { 571 return advancedStats, err 572 } 573 574 return advancedStats, nil 575 } 576 577 func scanAdvancedTCPStats(advancedStats *info.TcpAdvancedStat, advancedTCPStatsFile string) error { 578 data, err := os.ReadFile(advancedTCPStatsFile) 579 if err != nil { 580 return fmt.Errorf("failure opening %s: %v", advancedTCPStatsFile, err) 581 } 582 583 reader := strings.NewReader(string(data)) 584 scanner := bufio.NewScanner(reader) 585 scanner.Split(bufio.ScanLines) 586 587 advancedTCPStats := make(map[string]interface{}) 588 for scanner.Scan() { 589 nameParts := strings.Split(scanner.Text(), " ") 590 scanner.Scan() 591 valueParts := strings.Split(scanner.Text(), " ") 592 // Remove trailing :. and ignore non-tcp 593 protocol := nameParts[0][:len(nameParts[0])-1] 594 if protocol != "TcpExt" && protocol != "Tcp" { 595 continue 596 } 597 if len(nameParts) != len(valueParts) { 598 return fmt.Errorf("mismatch field count mismatch in %s: %s", 599 advancedTCPStatsFile, protocol) 600 } 601 for i := 1; i < len(nameParts); i++ { 602 if strings.Contains(valueParts[i], "-") { 603 vInt64, err := strconv.ParseInt(valueParts[i], 10, 64) 604 if err != nil { 605 return fmt.Errorf("decode value: %s to int64 error: %s", valueParts[i], err) 606 } 607 advancedTCPStats[nameParts[i]] = vInt64 608 } else { 609 vUint64, err := strconv.ParseUint(valueParts[i], 10, 64) 610 if err != nil { 611 return fmt.Errorf("decode value: %s to uint64 error: %s", valueParts[i], err) 612 } 613 advancedTCPStats[nameParts[i]] = vUint64 614 } 615 } 616 } 617 618 b, err := json.Marshal(advancedTCPStats) 619 if err != nil { 620 return err 621 } 622 623 err = json.Unmarshal(b, advancedStats) 624 if err != nil { 625 return err 626 } 627 628 return scanner.Err() 629 } 630 631 func scanTCPStats(tcpStatsFile string) (info.TcpStat, error) { 632 var stats info.TcpStat 633 634 data, err := os.ReadFile(tcpStatsFile) 635 if err != nil { 636 return stats, fmt.Errorf("failure opening %s: %v", tcpStatsFile, err) 637 } 638 639 tcpStateMap := map[string]uint64{ 640 "01": 0, // ESTABLISHED 641 "02": 0, // SYN_SENT 642 "03": 0, // SYN_RECV 643 "04": 0, // FIN_WAIT1 644 "05": 0, // FIN_WAIT2 645 "06": 0, // TIME_WAIT 646 "07": 0, // CLOSE 647 "08": 0, // CLOSE_WAIT 648 "09": 0, // LAST_ACK 649 "0A": 0, // LISTEN 650 "0B": 0, // CLOSING 651 } 652 653 reader := strings.NewReader(string(data)) 654 scanner := bufio.NewScanner(reader) 655 656 scanner.Split(bufio.ScanLines) 657 658 // Discard header line 659 if b := scanner.Scan(); !b { 660 return stats, scanner.Err() 661 } 662 663 for scanner.Scan() { 664 line := scanner.Text() 665 666 state := strings.Fields(line) 667 // TCP state is the 4th field. 668 // Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 669 tcpState := state[3] 670 _, ok := tcpStateMap[tcpState] 671 if !ok { 672 return stats, fmt.Errorf("invalid TCP stats line: %v", line) 673 } 674 tcpStateMap[tcpState]++ 675 } 676 677 stats = info.TcpStat{ 678 Established: tcpStateMap["01"], 679 SynSent: tcpStateMap["02"], 680 SynRecv: tcpStateMap["03"], 681 FinWait1: tcpStateMap["04"], 682 FinWait2: tcpStateMap["05"], 683 TimeWait: tcpStateMap["06"], 684 Close: tcpStateMap["07"], 685 CloseWait: tcpStateMap["08"], 686 LastAck: tcpStateMap["09"], 687 Listen: tcpStateMap["0A"], 688 Closing: tcpStateMap["0B"], 689 } 690 691 return stats, nil 692 } 693 694 func udpStatsFromProc(rootFs string, pid int, file string) (info.UdpStat, error) { 695 var err error 696 var udpStats info.UdpStat 697 698 udpStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file) 699 700 r, err := os.Open(udpStatsFile) 701 if err != nil { 702 return udpStats, fmt.Errorf("failure opening %s: %v", udpStatsFile, err) 703 } 704 705 udpStats, err = scanUDPStats(r) 706 if err != nil { 707 return udpStats, fmt.Errorf("couldn't read udp stats: %v", err) 708 } 709 710 return udpStats, nil 711 } 712 713 func scanUDPStats(r io.Reader) (info.UdpStat, error) { 714 var stats info.UdpStat 715 716 scanner := bufio.NewScanner(r) 717 scanner.Split(bufio.ScanLines) 718 719 // Discard header line 720 if b := scanner.Scan(); !b { 721 return stats, scanner.Err() 722 } 723 724 listening := uint64(0) 725 dropped := uint64(0) 726 rxQueued := uint64(0) 727 txQueued := uint64(0) 728 729 for scanner.Scan() { 730 line := scanner.Text() 731 // Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops 732 733 listening++ 734 735 fs := strings.Fields(line) 736 if len(fs) != 13 { 737 continue 738 } 739 740 rx, tx := uint64(0), uint64(0) 741 fmt.Sscanf(fs[4], "%X:%X", &rx, &tx) 742 rxQueued += rx 743 txQueued += tx 744 745 d, err := strconv.Atoi(string(fs[12])) 746 if err != nil { 747 continue 748 } 749 dropped += uint64(d) 750 } 751 752 stats = info.UdpStat{ 753 Listen: listening, 754 Dropped: dropped, 755 RxQueued: rxQueued, 756 TxQueued: txQueued, 757 } 758 759 return stats, nil 760 } 761 762 func (h *Handler) GetProcesses() ([]int, error) { 763 pids, err := h.cgroupManager.GetPids() 764 if err != nil { 765 return nil, err 766 } 767 return pids, nil 768 } 769 770 // Convert libcontainer stats to info.ContainerStats. 771 func setCPUStats(s *cgroups.Stats, ret *info.ContainerStats, withPerCPU bool) { 772 ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode 773 ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode 774 ret.Cpu.Usage.Total = s.CpuStats.CpuUsage.TotalUsage 775 ret.Cpu.CFS.Periods = s.CpuStats.ThrottlingData.Periods 776 ret.Cpu.CFS.ThrottledPeriods = s.CpuStats.ThrottlingData.ThrottledPeriods 777 ret.Cpu.CFS.ThrottledTime = s.CpuStats.ThrottlingData.ThrottledTime 778 779 if !withPerCPU { 780 return 781 } 782 if len(s.CpuStats.CpuUsage.PercpuUsage) == 0 { 783 // libcontainer's 'GetStats' can leave 'PercpuUsage' nil if it skipped the 784 // cpuacct subsystem. 785 return 786 } 787 ret.Cpu.Usage.PerCpu = s.CpuStats.CpuUsage.PercpuUsage 788 } 789 790 func setDiskIoStats(s *cgroups.Stats, ret *info.ContainerStats) { 791 ret.DiskIo.IoServiceBytes = diskStatsCopy(s.BlkioStats.IoServiceBytesRecursive) 792 ret.DiskIo.IoServiced = diskStatsCopy(s.BlkioStats.IoServicedRecursive) 793 ret.DiskIo.IoQueued = diskStatsCopy(s.BlkioStats.IoQueuedRecursive) 794 ret.DiskIo.Sectors = diskStatsCopy(s.BlkioStats.SectorsRecursive) 795 ret.DiskIo.IoServiceTime = diskStatsCopy(s.BlkioStats.IoServiceTimeRecursive) 796 ret.DiskIo.IoWaitTime = diskStatsCopy(s.BlkioStats.IoWaitTimeRecursive) 797 ret.DiskIo.IoMerged = diskStatsCopy(s.BlkioStats.IoMergedRecursive) 798 ret.DiskIo.IoTime = diskStatsCopy(s.BlkioStats.IoTimeRecursive) 799 } 800 801 func setMemoryStats(s *cgroups.Stats, ret *info.ContainerStats) { 802 ret.Memory.Usage = s.MemoryStats.Usage.Usage 803 ret.Memory.MaxUsage = s.MemoryStats.Usage.MaxUsage 804 ret.Memory.Failcnt = s.MemoryStats.Usage.Failcnt 805 ret.Memory.KernelUsage = s.MemoryStats.KernelUsage.Usage 806 807 if cgroups.IsCgroup2UnifiedMode() { 808 ret.Memory.Cache = s.MemoryStats.Stats["file"] 809 ret.Memory.RSS = s.MemoryStats.Stats["anon"] 810 ret.Memory.Swap = s.MemoryStats.SwapUsage.Usage - s.MemoryStats.Usage.Usage 811 ret.Memory.MappedFile = s.MemoryStats.Stats["file_mapped"] 812 } else if s.MemoryStats.UseHierarchy { 813 ret.Memory.Cache = s.MemoryStats.Stats["total_cache"] 814 ret.Memory.RSS = s.MemoryStats.Stats["total_rss"] 815 ret.Memory.Swap = s.MemoryStats.Stats["total_swap"] 816 ret.Memory.MappedFile = s.MemoryStats.Stats["total_mapped_file"] 817 } else { 818 ret.Memory.Cache = s.MemoryStats.Stats["cache"] 819 ret.Memory.RSS = s.MemoryStats.Stats["rss"] 820 ret.Memory.Swap = s.MemoryStats.Stats["swap"] 821 ret.Memory.MappedFile = s.MemoryStats.Stats["mapped_file"] 822 } 823 if v, ok := s.MemoryStats.Stats["pgfault"]; ok { 824 ret.Memory.ContainerData.Pgfault = v 825 ret.Memory.HierarchicalData.Pgfault = v 826 } 827 if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok { 828 ret.Memory.ContainerData.Pgmajfault = v 829 ret.Memory.HierarchicalData.Pgmajfault = v 830 } 831 832 inactiveFileKeyName := "total_inactive_file" 833 if cgroups.IsCgroup2UnifiedMode() { 834 inactiveFileKeyName = "inactive_file" 835 } 836 837 workingSet := ret.Memory.Usage 838 if v, ok := s.MemoryStats.Stats[inactiveFileKeyName]; ok { 839 if workingSet < v { 840 workingSet = 0 841 } else { 842 workingSet -= v 843 } 844 } 845 ret.Memory.WorkingSet = workingSet 846 } 847 848 func setCPUSetStats(s *cgroups.Stats, ret *info.ContainerStats) { 849 ret.CpuSet.MemoryMigrate = s.CPUSetStats.MemoryMigrate 850 } 851 852 func getNumaStats(memoryStats map[uint8]uint64) map[uint8]uint64 { 853 stats := make(map[uint8]uint64, len(memoryStats)) 854 for node, usage := range memoryStats { 855 stats[node] = usage 856 } 857 return stats 858 } 859 860 func setMemoryNumaStats(s *cgroups.Stats, ret *info.ContainerStats) { 861 ret.Memory.ContainerData.NumaStats.File = getNumaStats(s.MemoryStats.PageUsageByNUMA.File.Nodes) 862 ret.Memory.ContainerData.NumaStats.Anon = getNumaStats(s.MemoryStats.PageUsageByNUMA.Anon.Nodes) 863 ret.Memory.ContainerData.NumaStats.Unevictable = getNumaStats(s.MemoryStats.PageUsageByNUMA.Unevictable.Nodes) 864 865 ret.Memory.HierarchicalData.NumaStats.File = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.File.Nodes) 866 ret.Memory.HierarchicalData.NumaStats.Anon = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.Anon.Nodes) 867 ret.Memory.HierarchicalData.NumaStats.Unevictable = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.Unevictable.Nodes) 868 } 869 870 func setHugepageStats(s *cgroups.Stats, ret *info.ContainerStats) { 871 ret.Hugetlb = make(map[string]info.HugetlbStats) 872 for k, v := range s.HugetlbStats { 873 ret.Hugetlb[k] = info.HugetlbStats{ 874 Usage: v.Usage, 875 MaxUsage: v.MaxUsage, 876 Failcnt: v.Failcnt, 877 } 878 } 879 } 880 881 func setNetworkStats(libcontainerStats *libcontainer.Stats, ret *info.ContainerStats) { 882 ret.Network.Interfaces = make([]info.InterfaceStats, len(libcontainerStats.Interfaces)) 883 for i := range libcontainerStats.Interfaces { 884 ret.Network.Interfaces[i] = info.InterfaceStats{ 885 Name: libcontainerStats.Interfaces[i].Name, 886 RxBytes: libcontainerStats.Interfaces[i].RxBytes, 887 RxPackets: libcontainerStats.Interfaces[i].RxPackets, 888 RxErrors: libcontainerStats.Interfaces[i].RxErrors, 889 RxDropped: libcontainerStats.Interfaces[i].RxDropped, 890 TxBytes: libcontainerStats.Interfaces[i].TxBytes, 891 TxPackets: libcontainerStats.Interfaces[i].TxPackets, 892 TxErrors: libcontainerStats.Interfaces[i].TxErrors, 893 TxDropped: libcontainerStats.Interfaces[i].TxDropped, 894 } 895 } 896 897 // Add to base struct for backwards compatibility. 898 if len(ret.Network.Interfaces) > 0 { 899 ret.Network.InterfaceStats = ret.Network.Interfaces[0] 900 } 901 } 902 903 // read from pids path not cpu 904 func setThreadsStats(s *cgroups.Stats, ret *info.ContainerStats) { 905 if s != nil { 906 ret.Processes.ThreadsCurrent = s.PidsStats.Current 907 ret.Processes.ThreadsMax = s.PidsStats.Limit 908 } 909 } 910 911 func newContainerStats(libcontainerStats *libcontainer.Stats, includedMetrics container.MetricSet) *info.ContainerStats { 912 ret := &info.ContainerStats{ 913 Timestamp: time.Now(), 914 } 915 916 if s := libcontainerStats.CgroupStats; s != nil { 917 setCPUStats(s, ret, includedMetrics.Has(container.PerCpuUsageMetrics)) 918 if includedMetrics.Has(container.DiskIOMetrics) { 919 setDiskIoStats(s, ret) 920 } 921 setMemoryStats(s, ret) 922 if includedMetrics.Has(container.MemoryNumaMetrics) { 923 setMemoryNumaStats(s, ret) 924 } 925 if includedMetrics.Has(container.HugetlbUsageMetrics) { 926 setHugepageStats(s, ret) 927 } 928 if includedMetrics.Has(container.CPUSetMetrics) { 929 setCPUSetStats(s, ret) 930 } 931 } 932 if len(libcontainerStats.Interfaces) > 0 { 933 setNetworkStats(libcontainerStats, ret) 934 } 935 return ret 936 }