summaryrefslogtreecommitdiff
path: root/cgswap.go
blob: 6fab8aecc71c8e5eecb32d058ec34e23c7dac8a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Copyright (C) 2017 Luke Shumaker <lukeshu@sbcglobal.net>

// Command cgswap displays which cgroups are using swap space.
package main

import (
	"bufio"
	"fmt"
	"os"
	"sort"
	"strconv"
	"strings"
	"sync"
)

// You may be wondering why this program is so parallel.  Needless
// complexity, right?  Well, because processes may be very short
// lived, we want to read each process's info as soon as possible,
// before it goes away.

type cginfo struct {
	Cgroup string
	VmSwap int
}

type cginfos []cginfo

func (l cginfos) Len() int           { return len(l) }
func (l cginfos) Less(i, j int) bool { return l[i].VmSwap < l[j].VmSwap }
func (l cginfos) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }

func in_array(needle string, haystack []string) bool {
	for _, straw := range haystack {
		if needle == straw {
			return true
		}
	}
	return false
}

func main() {
	dir, err := os.Open("/proc")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fileinfos, err := dir.Readdir(-1)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	ch := make(chan cginfo)
	var infos cginfos
	cgroup2idx := make(map[string]int)
	var consumers sync.WaitGroup
	consumers.Add(1)
	go func() {
		for info := range ch {
			idx, ok := cgroup2idx[info.Cgroup]
			if ok {
				infos[idx].VmSwap += info.VmSwap
			} else {
				cgroup2idx[info.Cgroup] = len(infos)
				infos = append(infos, info)
			}
		}
		consumers.Done()
	}()

	var producers sync.WaitGroup
	for _, fileinfo := range fileinfos {
		if pid, err := strconv.Atoi(fileinfo.Name()); fileinfo.IsDir() && err == nil {
			producers.Add(1)
			go func(pid int) {
				defer producers.Done()
				statusFile, err := os.Open(fmt.Sprintf("/proc/%d/status", pid))
				if err != nil {
					return
				}
				cgroupFile, err := os.Open(fmt.Sprintf("/proc/%d/cgroup", pid))

				buf := bufio.NewScanner(statusFile)
				for buf.Scan() {
					line := buf.Text()
					if strings.HasPrefix(line, "VmSwap:") {
						swap, err := strconv.Atoi(strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "VmSwap:"), "kB")))
						if err != nil || swap == 0 {
							return
						}
						buf := bufio.NewScanner(cgroupFile)
						for buf.Scan() {
							parts := strings.SplitN(buf.Text(), ":", 3)
							if len(parts) != 3 {
								continue
							}
							heir := parts[0]
							controllers := strings.Split(parts[1], ",")
							cgroup := parts[2]
							if heir == "0" || in_array("name=systemd", controllers) {
								ch <- cginfo{VmSwap: swap, Cgroup: cgroup}
								return
							}
						}
						return
					}
				}
			}(pid)
		}
	}
	producers.Wait()
	close(ch)
	consumers.Wait()

	sort.Sort(infos)

	total := 0
	for _, info := range infos {
		total += info.VmSwap
	}
	vmswap_width := len(strconv.Itoa(total))

	for _, info := range infos {
		fmt.Printf("%[1]*d kB  %s\n", vmswap_width, info.VmSwap, info.Cgroup)
	}
	fmt.Printf("%[1]*d kB  %s\n", vmswap_width, total, "total")
}