-
Notifications
You must be signed in to change notification settings - Fork 154
/
main.go
150 lines (129 loc) · 3.86 KB
/
main.go
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package main
/*
* Copyright 2014-2017 A. Tobey <[email protected]> @AlTobey
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* pcstat.go - page cache stat
*
* uses the mincore(2) syscall to find out which pages (almost always 4k)
* of a file are currently cached in memory
*
*/
import (
"bufio"
"flag"
"fmt"
"log"
"os"
"path"
"sort"
"strings"
"github.com/tobert/pcstat/pkg"
)
var (
pidFlag int
terseFlag, nohdrFlag, jsonFlag, unicodeFlag bool
plainFlag, ppsFlag, histoFlag, bnameFlag bool
sortFlag bool
)
func init() {
// TODO: error on useless/broken combinations
flag.IntVar(&pidFlag, "pid", 0, "show all open maps for the given pid")
flag.BoolVar(&terseFlag, "terse", false, "show terse output")
flag.BoolVar(&nohdrFlag, "nohdr", false, "omit the header from terse & text output")
flag.BoolVar(&jsonFlag, "json", false, "return data in JSON format")
flag.BoolVar(&unicodeFlag, "unicode", false, "return data with unicode box characters")
flag.BoolVar(&plainFlag, "plain", false, "return data with no box characters")
flag.BoolVar(&ppsFlag, "pps", false, "include the per-page status in JSON output")
flag.BoolVar(&histoFlag, "histo", false, "print a simple histogram instead of raw data")
flag.BoolVar(&bnameFlag, "bname", false, "convert paths to basename to narrow the output")
flag.BoolVar(&sortFlag, "sort", false, "sort output by cached pages desc")
}
func main() {
flag.Parse()
files := flag.Args()
if pidFlag != 0 {
pcstat.SwitchMountNs(pidFlag)
maps := getPidMaps(pidFlag)
files = append(files, maps...)
}
// all non-flag arguments are considered to be filenames
// this works well with shell globbing
// file order is preserved throughout this program
if len(files) == 0 {
flag.Usage()
os.Exit(1)
}
stats := make(PcStatusList, 0, len(files))
for _, fname := range files {
status, err := pcstat.GetPcStatus(fname)
if err != nil {
log.Printf("skipping %q: %v", fname, err)
continue
}
// convert long paths to their basename with the -bname flag
// this overwrites the original filename in pcs but it doesn't matter since
// it's not used to access the file again -- and should not be!
if bnameFlag {
status.Name = path.Base(fname)
}
stats = append(stats, status)
}
if sortFlag {
sort.Slice(stats, func(i, j int) bool {
return stats[i].Cached > stats[j].Cached
})
}
if jsonFlag {
stats.formatJson(!ppsFlag)
} else if terseFlag {
stats.formatTerse()
} else if histoFlag {
stats.formatHistogram()
} else if unicodeFlag {
stats.formatUnicode()
} else if plainFlag {
stats.formatPlain()
} else {
stats.formatText()
}
}
func getPidMaps(pid int) []string {
fname := fmt.Sprintf("/proc/%d/maps", pid)
f, err := os.Open(fname)
if err != nil {
log.Fatalf("could not open '%s' for read: %v", fname, err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
// use a map to help avoid duplicates
maps := make(map[string]bool)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) == 6 && strings.HasPrefix(parts[5], "/") {
// found something that looks like a file
maps[parts[5]] = true
}
}
if err := scanner.Err(); err != nil {
log.Fatalf("reading '%s' failed: %s", fname, err)
}
// convert back to a list
out := make([]string, 0, len(maps))
for key := range maps {
out = append(out, key)
}
return out
}