├── .gitignore ├── LICENSE ├── README.md ├── limits ├── limits.go ├── limits_test.go └── testfiles │ └── limits ├── meminfo.go ├── meminfo_test.go ├── process.go ├── process_test.go ├── procfs.go ├── procfs_test.go ├── stat ├── stat.go ├── stat_test.go └── testfiles │ └── stat ├── statm ├── statm.go ├── statm_test.go └── testfiles │ └── statm ├── status ├── status.go ├── status_test.go └── testfiles │ └── status ├── testfiles ├── cmdline └── meminfo └── util ├── structparser.go ├── util.go └── util_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Source: https://raw.github.com/github/gitignore/master/Go.gitignore 2 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 3 | *.o 4 | *.a 5 | *.so 6 | 7 | # Folders 8 | _obj 9 | _test 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | 25 | *.sw* 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Jen Andre (jandre@gmail.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software (except Dustin Willis Webber) and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Procfs 2 | 3 | Procfs is a parser for the /proc virtual filesystem on Linux written in the Go programming lanugage. 4 | 5 | Not all /proc interfaces are currently supported; but pull requests are welcome! 6 | 7 | ## Installation 8 | 9 | go get github.com/jandre/procfs 10 | 11 | ## Examples 12 | See the `*_test` files for usage, but in short: 13 | 14 | ```go 15 | import ( 16 | "github.com/jandre/procfs" 17 | ) 18 | 19 | // fetch all processes from /proc 20 | // returns a map of pid -> *Process 21 | processes, err := procfs.Processes(); 22 | 23 | ``` 24 | 25 | ## Documentation 26 | 27 | Documentation can be found at: http://godoc.org/github.com/jandre/procfs 28 | -------------------------------------------------------------------------------- /limits/limits.go: -------------------------------------------------------------------------------- 1 | // 2 | // limits.Limits describes data in /proc//limits. 3 | // 4 | // Use limits.New() to create a new limits.Limits object 5 | // from data in a limits file. 6 | // 7 | package limits 8 | 9 | // Copyright Jen Andre (jandre@gmail.com) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining 12 | // a copy of this software and associated documentation files (the 13 | // "Software"), to deal in the Software without restriction, including 14 | // without limitation the rights to use, copy, modify, merge, publish, 15 | // distribute, sublicense, and/or sell copies of the Software, and to 16 | // permit persons to whom the Software is furnished to do so, subject to 17 | // the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be 20 | // included in all copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 26 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 27 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 28 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | import ( 31 | "bytes" 32 | "io/ioutil" 33 | "log" 34 | "regexp" 35 | "strconv" 36 | "strings" 37 | ) 38 | 39 | const UNLIMITED = -1 40 | 41 | type Unit string 42 | 43 | const ( 44 | Bytes Unit = "bytes" 45 | Seconds = "seconds" 46 | Processes = "processes" 47 | Files = "files" 48 | Signals = "signals" 49 | Locks = "locks" 50 | Us = "us" 51 | Unknown = "unknown" 52 | ) 53 | 54 | type Limit struct { 55 | SoftValue int 56 | HardValue int 57 | units Unit 58 | } 59 | 60 | func makeUnit(str string) Unit { 61 | switch str { 62 | case "bytes": 63 | return Bytes 64 | case "seconds": 65 | return Seconds 66 | case "processes": 67 | return Processes 68 | case "signals": 69 | return Signals 70 | case "files": 71 | return Files 72 | case "us": 73 | return Us 74 | } 75 | return Unknown 76 | } 77 | 78 | // 79 | // parseLimit parses a limit value from a string 80 | // if the value is 'unlimited', it will be set to 81 | // UNLIMITED (-1) 82 | func parseLimit(str string) (int, error) { 83 | if str == "unlimited" { 84 | return UNLIMITED, nil 85 | } 86 | return strconv.Atoi(str) 87 | } 88 | 89 | var splitBy2Whitespace = regexp.MustCompile("\\s\\s+") 90 | 91 | // logic taken from: https://github.com/etgryphon/stringUp 92 | // no license is given, but i hope this is ok :( 93 | var camelingRegex = regexp.MustCompile("[0-9A-Za-z]+") 94 | 95 | // 96 | // toCamelCase converts a spaced string to CamelCase 97 | // 98 | // e.g., "my string is" becomes "MyStringIs" 99 | // 100 | func toCamelCase(str string) string { 101 | byteSrc := []byte(str) 102 | chunks := camelingRegex.FindAll(byteSrc, -1) 103 | for idx, val := range chunks { 104 | if idx >= 0 { 105 | chunks[idx] = bytes.Title(val) 106 | } 107 | } 108 | return string(bytes.Join(chunks, nil)) 109 | } 110 | 111 | // 112 | // linesToLimits turns every line in in a /proc/pid/limits file 113 | // to a Limit object. 114 | // 115 | func linesToLimits(lines []string) (map[string]*Limit, error) { 116 | var result map[string]*Limit = make(map[string]*Limit) 117 | var units Unit 118 | 119 | // first line is the header 120 | for i := 1; i < len(lines); i++ { 121 | 122 | lines[i] = strings.TrimSpace(lines[i]) 123 | 124 | if len(lines[i]) == 0 { 125 | // it's empty 126 | continue 127 | } 128 | 129 | parts := splitBy2Whitespace.Split(lines[i], UNLIMITED) 130 | 131 | if len(parts) < 3 { 132 | log.Println("malformed line, expected 4 parts but only got:", len(parts), "line:", lines[i]) 133 | continue 134 | } 135 | 136 | title := strings.Replace(parts[0], "Max ", "", UNLIMITED) 137 | 138 | title = toCamelCase(title) 139 | 140 | soft, err := parseLimit(parts[1]) 141 | if err != nil { 142 | return nil, err 143 | } 144 | hard, err := parseLimit(parts[2]) 145 | if err != nil { 146 | return nil, err 147 | } 148 | 149 | if len(parts) > 3 { 150 | units = makeUnit(parts[3]) 151 | } else { 152 | units = Unknown 153 | } 154 | 155 | result[title] = &Limit{ 156 | SoftValue: soft, 157 | HardValue: hard, 158 | units: units, 159 | } 160 | } 161 | return result, nil 162 | } 163 | 164 | // 165 | // Limits is abstraction for /proc//limit 166 | // 167 | // Each Limit pointer pointers to a Limit object, 168 | // which has the HardValue, SoftValue, and Units. 169 | // 170 | // If no limit is found, it will be nil. 171 | // 172 | type Limits struct { 173 | CpuTime *Limit 174 | FileSize *Limit 175 | DataSize *Limit 176 | StackSize *Limit 177 | CoreFileSize *Limit 178 | ResidentSet *Limit 179 | Processes *Limit 180 | OpenFiles *Limit 181 | LockedMemory *Limit 182 | AddressSpace *Limit 183 | FileLocks *Limit 184 | PendingSignals *Limit 185 | MsgqueueSize *Limit 186 | NicePriority *Limit 187 | RealtimePriority *Limit 188 | RealtimeTimeout *Limit 189 | } 190 | 191 | // 192 | // Create a Limit instance from a /proc//limits path 193 | // 194 | func New(path string) (*Limits, error) { 195 | 196 | buf, err := ioutil.ReadFile(path) 197 | if err != nil { 198 | return nil, err 199 | } 200 | 201 | lines := strings.Split(string(buf), "\n") 202 | limits, err := linesToLimits(lines) 203 | if err != nil { 204 | return nil, err 205 | } 206 | 207 | limit := &Limits{} 208 | 209 | limit.CpuTime = limits["CpuTime"] 210 | limit.FileSize = limits["FileSize"] 211 | limit.DataSize = limits["DataSize"] 212 | limit.StackSize = limits["StackSize"] 213 | limit.CoreFileSize = limits["CoreFileSize"] 214 | limit.ResidentSet = limits["ResidentSet"] 215 | limit.Processes = limits["Processes"] 216 | limit.OpenFiles = limits["OpenFiles"] 217 | limit.LockedMemory = limits["LockedMemory"] 218 | limit.AddressSpace = limits["AddressSpace"] 219 | limit.FileLocks = limits["FileLocks"] 220 | limit.PendingSignals = limits["PendingSignals"] 221 | limit.MsgqueueSize = limits["MsgqueueSize"] 222 | limit.NicePriority = limits["NicePriority"] 223 | limit.RealtimePriority = limits["RealtimePriority"] 224 | limit.RealtimeTimeout = limits["RealtimeTimeout"] 225 | 226 | return limit, nil 227 | } 228 | -------------------------------------------------------------------------------- /limits/limits_test.go: -------------------------------------------------------------------------------- 1 | package limits 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestParsingLimit(t *testing.T) { 8 | s, err := New("./testfiles/limits") 9 | 10 | if err != nil { 11 | t.Fatal("Got error", err) 12 | } 13 | 14 | if s == nil { 15 | t.Fatal("stat is missing") 16 | } 17 | 18 | if s.NicePriority == nil || s.NicePriority.SoftValue != 0 || s.NicePriority.HardValue != 0 { 19 | t.Fatal("Wrong values for NicePriority, expected soft=0 and hard=4096", s.NicePriority) 20 | } 21 | 22 | if s.OpenFiles == nil || s.OpenFiles.SoftValue != 1024 || s.OpenFiles.HardValue != 4096 { 23 | t.Fatal("Wrong values for OpenFiles, expected soft=1024 and hard=4096", s.OpenFiles) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /limits/testfiles/limits: -------------------------------------------------------------------------------- 1 | Limit Soft Limit Hard Limit Units 2 | Max cpu time unlimited unlimited seconds 3 | Max file size unlimited unlimited bytes 4 | Max data size unlimited unlimited bytes 5 | Max stack size 8388608 unlimited bytes 6 | Max core file size unlimited unlimited bytes 7 | Max resident set unlimited unlimited bytes 8 | Max processes 7760 7760 processes 9 | Max open files 1024 4096 files 10 | Max locked memory 65536 65536 bytes 11 | Max address space unlimited unlimited bytes 12 | Max file locks unlimited unlimited locks 13 | Max pending signals 7760 7760 signals 14 | Max msgqueue size 819200 819200 bytes 15 | Max nice priority 0 0 16 | Max realtime priority 0 0 17 | Max realtime timeout unlimited unlimited us 18 | -------------------------------------------------------------------------------- /meminfo.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | 3 | // Copyright Jen Andre (jandre@gmail.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | import ( 25 | "io/ioutil" 26 | "log" 27 | "strconv" 28 | "strings" 29 | ) 30 | 31 | // 32 | // Meminfo is a parser for /proc/meminfo. 33 | // 34 | // 35 | type Meminfo struct { 36 | MemTotal int64 37 | MemFree int64 38 | Buffers int64 39 | Cached int64 40 | SwapCached int64 41 | Active int64 42 | Inactive int64 43 | HighTotal int64 44 | HighFree int64 45 | LowTotal int64 46 | LowFree int64 47 | SwapTotal int64 48 | SwapFree int64 49 | Dirty int64 50 | Writeback int64 51 | AnonPages int64 52 | Mapped int64 53 | Slab int64 54 | SReclaimable int64 55 | SUnreclaim int64 56 | PageTables int64 57 | NFS_Unstable int64 58 | Bounce int64 59 | WritebackTmp int64 60 | CommitLimit int64 61 | Committed_AS int64 62 | VmallocTotal int64 63 | VmallocUsed int64 64 | VmallocChunk int64 65 | AnonHugePages int64 66 | } 67 | 68 | func linesToMeminfo(lines []string) (map[string]int64, error) { 69 | var result map[string]int64 = make(map[string]int64) 70 | 71 | // first line is the header 72 | for i := 0; i < len(lines); i++ { 73 | 74 | lines[i] = strings.TrimSpace(lines[i]) 75 | 76 | if len(lines[i]) == 0 { 77 | // it's empty 78 | continue 79 | } 80 | 81 | parts := strings.Fields(lines[i]) 82 | 83 | if len(parts) < 2 { 84 | log.Println("malformed line, expected 2 parts but only got:", len(parts), "line:", lines[i]) 85 | continue 86 | } 87 | 88 | title := strings.Replace(parts[0], ":", "", -1) 89 | val, err := strconv.ParseInt(parts[1], 10, 64) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | result[title] = val 95 | } 96 | return result, nil 97 | } 98 | 99 | // 100 | // ParseMeminfo() loads a Meminfo object from a supplied path string. 101 | // 102 | // If the path cannot be found, it will return an error. 103 | // 104 | func ParseMeminfo(path string) (*Meminfo, error) { 105 | 106 | buf, err := ioutil.ReadFile(path) 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | lines := strings.Split(string(buf), "\n") 112 | meminfos, err := linesToMeminfo(lines) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | meminfo := &Meminfo{} 118 | meminfo.MemTotal = meminfos["MemTotal"] 119 | meminfo.MemFree = meminfos["MemFree"] 120 | meminfo.Buffers = meminfos["Buffers"] 121 | meminfo.Cached = meminfos["Cached"] 122 | meminfo.SwapCached = meminfos["SwapCached"] 123 | meminfo.Active = meminfos["Active"] 124 | meminfo.Inactive = meminfos["Inactive"] 125 | meminfo.HighTotal = meminfos["HighTotal"] 126 | meminfo.HighFree = meminfos["HighFree"] 127 | meminfo.LowTotal = meminfos["LowTotal"] 128 | meminfo.LowFree = meminfos["LowFree"] 129 | meminfo.SwapTotal = meminfos["SwapTotal"] 130 | meminfo.SwapFree = meminfos["SwapFree"] 131 | meminfo.Dirty = meminfos["Dirty"] 132 | meminfo.Writeback = meminfos["Writeback"] 133 | meminfo.AnonPages = meminfos["AnonPages"] 134 | meminfo.Mapped = meminfos["Mapped"] 135 | meminfo.Slab = meminfos["Slab"] 136 | meminfo.SReclaimable = meminfos["SReclaimable"] 137 | meminfo.SUnreclaim = meminfos["SUnreclaim"] 138 | meminfo.PageTables = meminfos["PageTables"] 139 | meminfo.NFS_Unstable = meminfos["NFS_Unstable"] 140 | meminfo.Bounce = meminfos["Bounce"] 141 | meminfo.WritebackTmp = meminfos["WritebackTmp"] 142 | meminfo.CommitLimit = meminfos["CommitLimit"] 143 | meminfo.Committed_AS = meminfos["Committed_AS"] 144 | meminfo.VmallocTotal = meminfos["VmallocTotal"] 145 | meminfo.VmallocUsed = meminfos["VmallocUsed"] 146 | meminfo.VmallocChunk = meminfos["VmallocChunk"] 147 | meminfo.AnonHugePages = meminfos["AnonHugePages"] 148 | 149 | return meminfo, nil 150 | 151 | } 152 | 153 | // 154 | // NewMeminfo() creates a new Meminfo object that loads from /proc/meminfo. 155 | // 156 | // 157 | func NewMeminfo() (*Meminfo, error) { 158 | return ParseMeminfo("/proc/meminfo") 159 | } 160 | -------------------------------------------------------------------------------- /meminfo_test.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestParseMeminfo(t *testing.T) { 8 | // set the GLOBAL_SYSTEM_START 9 | meminfo, err := ParseMeminfo("./testfiles/meminfo") 10 | 11 | if err != nil { 12 | t.Fatal("Got error", err) 13 | } 14 | 15 | if meminfo == nil { 16 | t.Fatal("meminfo is missing") 17 | } 18 | 19 | if meminfo.MemTotal != 1011932 { 20 | t.Fatal("Expected 1011932 from MemTotal") 21 | } 22 | 23 | if meminfo.PageTables != 8340 { 24 | t.Fatal("Expected 8340 from PageTables") 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /process.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | 3 | // Copyright Jen Andre (jandre@gmail.com) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | import ( 25 | "io/ioutil" 26 | "log" 27 | "os" 28 | "path" 29 | "strconv" 30 | "strings" 31 | 32 | "github.com/jandre/procfs/limits" 33 | "github.com/jandre/procfs/stat" 34 | "github.com/jandre/procfs/statm" 35 | "github.com/jandre/procfs/status" 36 | ) 37 | 38 | // 39 | // Process describes a /proc/ entry 40 | // 41 | type Process struct { 42 | Pid int // Process ID 43 | Environ map[string]string // Environment variables 44 | Cmdline []string // Command line of process (argv array) 45 | Cwd string // Process current working directory 46 | Exe string // Symlink to executed command. 47 | Root string // Per-process root (e.g. chroot) 48 | prefix string // directory path, e.g. /proc/ 49 | stat *stat.Stat // Status information from /proc/pid/stat - see Stat() 50 | statm *statm.Statm // Memory Status information from /proc/pid/statm - see Statm() 51 | status *status.Status //Status information from /proc/pid/status 52 | limits *limits.Limits // Per process rlimit settings from /proc/pid/limits - see Limits() 53 | loginuid *int // Maybe loginuid from /proc/pid/loginuid - see Loginuid() 54 | sessionid *int // Maybe sessionid from /proc/pid/sessionid- see Sessionid() 55 | 56 | } 57 | 58 | // 59 | // Read /proc information for `pid` 60 | // 61 | func NewProcess(pid int, lazy bool) (*Process, error) { 62 | prefix := path.Join("/proc", strconv.Itoa(pid)) 63 | return NewProcessFromPath(pid, prefix, lazy) 64 | } 65 | 66 | // 67 | // Read a process entry from a directory path 68 | // 69 | // if lazy = true, then preload the stat, limits, and other files. 70 | // 71 | func NewProcessFromPath(pid int, prefix string, lazy bool) (*Process, error) { 72 | var err error 73 | 74 | if _, err = os.Stat(prefix); err != nil { 75 | // error reading pid 76 | return nil, err 77 | } 78 | 79 | process := &Process{prefix: prefix, Pid: pid} 80 | if process.Cmdline, err = readCmdLine(prefix); err != nil { 81 | return nil, err 82 | } 83 | 84 | if process.Exe, err = readLink(prefix, "exe"); err != nil { 85 | process.Exe = "" 86 | } 87 | 88 | if process.Cwd, err = readLink(prefix, "cwd"); err != nil { 89 | return nil, err 90 | } 91 | 92 | if process.Root, err = readLink(prefix, "root"); err != nil { 93 | return nil, err 94 | } 95 | 96 | process.readEnviron() 97 | 98 | if !lazy { 99 | // preload all of the subdirs 100 | process.Stat() 101 | process.Statm() 102 | process.Status() 103 | process.Limits() 104 | process.Loginuid() 105 | process.Sessionid() 106 | } 107 | 108 | return process, nil 109 | } 110 | 111 | const NO_VALUE = 4294967295 112 | 113 | func clearEmpty(strings []string) []string { 114 | var filtered []string 115 | for _, v := range strings { 116 | if len(v) != 0 { 117 | filtered = append(filtered, v) 118 | } 119 | } 120 | return filtered 121 | } 122 | 123 | // 124 | // read integer from file 125 | // 126 | func readFileInteger(prefix string, file string) (*int, error) { 127 | filename := path.Join(prefix, file) 128 | str, err := ioutil.ReadFile(filename) 129 | if err != nil { 130 | return nil, err 131 | } 132 | if val, err := strconv.Atoi(string(str)); err != nil { 133 | return nil, err 134 | } else { 135 | return &val, nil 136 | } 137 | } 138 | 139 | // 140 | 141 | // 142 | // read link 143 | // 144 | func readLink(prefix string, file string) (string, error) { 145 | filename := path.Join(prefix, file) 146 | str, err := os.Readlink(filename) 147 | if err != nil { 148 | return "", err 149 | } 150 | return string(str), nil 151 | } 152 | 153 | // 154 | // Read a /proc//cmdline file and break up into array 155 | // of argv 156 | // 157 | func readCmdLine(prefix string) ([]string, error) { 158 | filename := path.Join(prefix, "cmdline") 159 | str, err := ioutil.ReadFile(filename) 160 | if err != nil { 161 | return nil, err 162 | } 163 | return clearEmpty(strings.Split(string(str), "\x00")), nil 164 | } 165 | 166 | // 167 | // Parser for /proc//stat 168 | // 169 | func (p *Process) Stat() (*stat.Stat, error) { 170 | var err error 171 | if p.stat == nil { 172 | if p.stat, err = stat.New(path.Join(p.prefix, "stat")); err != nil { 173 | return nil, err 174 | } 175 | } 176 | 177 | return p.stat, nil 178 | } 179 | 180 | // 181 | // Parser for /proc//statm 182 | // 183 | func (p *Process) Statm() (*statm.Statm, error) { 184 | var err error 185 | if p.statm == nil { 186 | if p.statm, err = statm.New(path.Join(p.prefix, "statm")); err != nil { 187 | return nil, err 188 | } 189 | } 190 | 191 | return p.statm, nil 192 | } 193 | 194 | // 195 | // Parser for /proc//status 196 | // 197 | func (p *Process) Status() (*status.Status, error) { 198 | var err error 199 | if p.status == nil { 200 | if p.status, err = status.New(path.Join(p.prefix, "status")); err != nil { 201 | return nil, err 202 | } 203 | } 204 | 205 | return p.status, nil 206 | } 207 | 208 | // 209 | // Parser for /proc//limits 210 | // 211 | func (p *Process) Limits() (*limits.Limits, error) { 212 | var err error 213 | if p.limits == nil { 214 | if p.limits, err = limits.New(path.Join(p.prefix, "limits")); err != nil { 215 | return nil, err 216 | } 217 | } 218 | 219 | return p.limits, nil 220 | } 221 | 222 | // 223 | // Parses contents of /proc/pid/loginuid (if present) 224 | // 225 | func (p *Process) Loginuid() int { 226 | var err error 227 | if p.loginuid == nil { 228 | p.loginuid, err = readFileInteger(p.prefix, "loginuid") 229 | if err != nil { 230 | log.Println("Warning: unable to read loginuid so returning nil:", err) 231 | } 232 | 233 | } 234 | 235 | if p.loginuid != nil { 236 | return *p.loginuid 237 | } 238 | return NO_VALUE 239 | 240 | } 241 | 242 | // 243 | // Parses contents of /proc/pid/sessionid (if present) 244 | // 245 | func (p *Process) Sessionid() int { 246 | var err error 247 | if p.sessionid == nil { 248 | p.sessionid, err = readFileInteger(p.prefix, "sessionid") 249 | if err != nil { 250 | log.Println("Warning: unable to read sessionid so returning nil:", err) 251 | } 252 | 253 | } 254 | 255 | if p.sessionid != nil { 256 | return *p.sessionid 257 | } 258 | return NO_VALUE 259 | } 260 | 261 | func (p *Process) readEnviron() { 262 | p.Environ = make(map[string]string, 0) 263 | bytes, err := ioutil.ReadFile(path.Join(p.prefix, "environ")) 264 | if err != nil { 265 | return 266 | } 267 | for _, item := range strings.Split(string(bytes), "\x00") { 268 | fields := strings.SplitN(item, "=", 2) 269 | if len(fields) == 2 { 270 | p.Environ[fields[0]] = fields[1] 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /process_test.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestParsingInit(t *testing.T) { 9 | // set the GLOBAL_SYSTEM_START 10 | process, err := NewProcess(1, false) 11 | 12 | if err != nil { 13 | t.Fatal("Got error", err) 14 | } 15 | 16 | if process == nil { 17 | t.Fatal("process is missing") 18 | } 19 | 20 | if process.Cwd != "/" { 21 | t.Fatal("Expected / for cwd") 22 | } 23 | } 24 | 25 | func TestParsingCmdline(t *testing.T) { 26 | prefix := "./testfiles/" 27 | cmd, err := readCmdLine(prefix) 28 | 29 | if err != nil { 30 | t.Fatal("Got error", err) 31 | } 32 | 33 | log.Println("Got cmdline:", cmd, len(cmd)) 34 | if len(cmd) != 9 { 35 | t.Fatal("Expected string length to be 9 for cmdline, got", len(cmd)) 36 | } 37 | 38 | if cmd[0] != "dhclient3" { 39 | t.Fatal("Expected dhclient3 for cmdline argv0, got", cmd[0]) 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /procfs.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | // 3 | // License information: 4 | // 5 | // Copyright Jen Andre (jandre@gmail.com) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining 8 | // a copy of this software and associated documentation files (the 9 | // "Software"), to deal in the Software without restriction, including 10 | // without limitation the rights to use, copy, modify, merge, publish, 11 | // distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to 13 | // the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be 16 | // included in all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | import ( 27 | "io/ioutil" 28 | "log" 29 | "os" 30 | "strconv" 31 | ) 32 | 33 | // 34 | // Take a list of FileInfo from /proc listing and only return 35 | // pid directories 36 | // 37 | func filterByPidDirectories(files []os.FileInfo) []int { 38 | var result []int 39 | 40 | for _, file := range files { 41 | if file.IsDir() { 42 | if pid, err := strconv.Atoi(file.Name()); err == nil { 43 | result = append(result, pid) 44 | } 45 | } 46 | } 47 | return result 48 | } 49 | 50 | // 51 | // Load all processes from /proc 52 | // 53 | // If lazy = true, do not load ancillary information (/proc//stat, 54 | // /proc//statm, etc) immediately - load it on demand 55 | // 56 | func Processes(lazy bool) (map[int]*Process, error) { 57 | processes := make(map[int]*Process) 58 | done := make(chan *Process) 59 | files, err := ioutil.ReadDir("/proc") 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | pids := filterByPidDirectories(files) 65 | 66 | fetch := func(pid int) { 67 | proc, err := NewProcess(pid, lazy) 68 | if err != nil { 69 | // TODO: bubble errors up if requested 70 | log.Println("Failure loading process pid: ", pid, err) 71 | done <- nil 72 | } else { 73 | done <- proc 74 | } 75 | } 76 | 77 | todo := len(pids) 78 | 79 | // create a goroutine that asynchronously processes all /proc/ entries 80 | for _, pid := range pids { 81 | go fetch(pid) 82 | } 83 | 84 | // 85 | // fetch all processes until we're done 86 | // 87 | for ;todo > 0; { 88 | proc := <-done 89 | todo-- 90 | if proc != nil { 91 | processes[proc.Pid] = proc 92 | } 93 | } 94 | 95 | return processes, nil 96 | } 97 | -------------------------------------------------------------------------------- /procfs_test.go: -------------------------------------------------------------------------------- 1 | package procfs 2 | 3 | import ( 4 | "testing" 5 | "log" 6 | "fmt" 7 | ) 8 | 9 | func TestAllProc(t * testing.T) { 10 | procs, err := Processes(false) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | if len(procs) <= 0 { 15 | t.Fatal("procs length must be > 0") 16 | } 17 | 18 | log.Println("Pid 1", procs[0]) 19 | for i, p := range procs { 20 | fmt.Printf("%d PID: %d - CMDLINE: %s - CWD: %s - EXE: %s\n", i, p.Pid, p.Cmdline, p.Cwd, p.Exe) 21 | // noop 22 | } 23 | 24 | // for i := 0; i < len(procs); i++ { 25 | // p := procs[i]; 26 | // log.Println("%d PID: %d - CMDLINE: %s - CWD: %s - EXE: %s\n", p.Pid, p.Cmdline, p.Cwd, p.Exe) 27 | // } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /stat/stat.go: -------------------------------------------------------------------------------- 1 | // 2 | // stat.Stat describes data in /proc//stat. 3 | // 4 | // Use stat.New() to create a new stat.Stat object 5 | // from data in a path. 6 | // 7 | package stat 8 | 9 | import ( 10 | "io/ioutil" 11 | "strings" 12 | "time" 13 | 14 | "github.com/jandre/procfs/util" 15 | ) 16 | 17 | // 18 | // Stat is a data structure that maps to /proc//stat. 19 | // 20 | type Stat struct { 21 | Pid int // process id 22 | Comm string // filename of the executable 23 | State string // state (R is running, S is sleeping, D is sleeping in an uninterruptible wait, Z is zombie, T is traced or stopped) 24 | Ppid int // process id of the parent process 25 | Pgrp int // pgrp of the process 26 | Session int // session id 27 | TtyNr int // tty the process uses 28 | Tpgid int // pgrp of the tty 29 | Flags int64 // task flags 30 | Minflt int64 // number of minor faults 31 | Cminflt int64 // number of minor faults with child's 32 | Majflt int64 // number of major faults 33 | Cmajflt int64 // number of major faults with child's 34 | Utime time.Duration // user mode in nanoseconds 35 | Stime time.Duration // kernel mode in nanoseconds 36 | Cutime time.Duration // user mode in nanoseconds with child's 37 | Cstime time.Duration // kernel mode in nanoseconds with child's 38 | Priority int64 // priority level 39 | Nice int64 // nice level 40 | NumThreads int64 // number of threads 41 | Itrealvalue int64 // (obsolete, always 0) 42 | Starttime time.Time // time the process started after system boot 43 | Vsize int64 // virtual memory size 44 | Rss int64 // resident set memory size 45 | Rlim uint64 // current limit in bytes on the rss 46 | Startcode int64 // address above which program text can run 47 | Endcode int64 // address below which program text can run 48 | Startstack int64 // address of the start of the main process stack 49 | Kstkesp int64 // current value of ESP 50 | Kstkeip int64 // current value of EIP 51 | Signal int64 // bitmap of pending signals 52 | Blocked int64 // bitmap of blocked signals 53 | Sigignore int64 // bitmap of ignored signals 54 | Sigcatch int64 // bitmap of catched signals 55 | Wchan uint64 // address where process went to sleep 56 | Nswap int64 // (place holder) 57 | Cnswap int64 // (place holder) 58 | ExitSignal int // signal to send to parent thread on exit 59 | Processor int // which CPU the task is scheduled on 60 | RtPriority int64 // realtime priority 61 | Policy int64 // scheduling policy (man sched_setscheduler) 62 | DelayacctBlkioTicks int64 // time spent waiting for block IO 63 | } 64 | 65 | // 66 | // stat.New creates a new /proc//stat from a path. 67 | // 68 | // An error is returned if the data is malformed, or the path does not exist. 69 | // 70 | func New(path string) (*Stat, error) { 71 | 72 | buf, err := ioutil.ReadFile(path) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | lines := strings.Split(string(buf), " ") 78 | stat := &Stat{} 79 | err = util.ParseStringsIntoStruct(stat, lines) 80 | return stat, err 81 | } 82 | -------------------------------------------------------------------------------- /stat/stat_test.go: -------------------------------------------------------------------------------- 1 | package stat 2 | 3 | import ( 4 | "github.com/jandre/procfs/util" 5 | "testing" 6 | ) 7 | 8 | 9 | func TestParsingStat(t *testing.T) { 10 | // set the GLOBAL_SYSTEM_START 11 | util.GLOBAL_SYSTEM_START = 1388417200 12 | s, err := New("./testfiles/stat") 13 | 14 | if err != nil { 15 | t.Fatal("Got error", err) 16 | } 17 | 18 | if s == nil { 19 | t.Fatal("stat is missing") 20 | } 21 | 22 | // if s.Starttime.seconds() != 1388604586 { 23 | // t.Fatal("Start time is wrong, expected: 1388604586", s.Starttime.EpochSeconds) 24 | // } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /stat/testfiles/stat: -------------------------------------------------------------------------------- 1 | 21577 (bash) S 6111 21577 21577 34830 21683 4202496 4482 26913 0 0 9 3 1 0 20 0 1 0 18738669 24788992 1371 18446744073709551615 4194304 5111460 140735842674736 140735842673312 139863804222606 0 65536 3686404 1266761467 18446744071579207508 0 0 17 0 0 0 0 0 0 7212552 7248528 40173568 140735842683137 140735842683143 140735842683143 140735842684910 0 2 | -------------------------------------------------------------------------------- /statm/statm.go: -------------------------------------------------------------------------------- 1 | // 2 | // statm.Statm describes data in /proc//statm. 3 | // 4 | // Use statm.New() to create a new stat.Statm object 5 | // from data in a path. 6 | // 7 | package statm 8 | 9 | // 10 | // Copyright Jen Andre (jandre@gmail.com) 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining 13 | // a copy of this software and associated documentation files (the 14 | // "Software"), to deal in the Software without restriction, including 15 | // without limitation the rights to use, copy, modify, merge, publish, 16 | // distribute, sublicense, and/or sell copies of the Software, and to 17 | // permit persons to whom the Software is furnished to do so, subject to 18 | // the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be 21 | // included in all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | // 31 | 32 | import ( 33 | "github.com/jandre/procfs/util" 34 | "io/ioutil" 35 | "strings" 36 | ) 37 | 38 | // 39 | // Abstraction for /proc//stat 40 | // 41 | type Statm struct { 42 | Size int64 // total program size (pages)(same as VmSize in status) 43 | Resident int64 //size of memory portions (pages)(same as VmRSS in status) 44 | Shared int // number of pages that are shared(i.e. backed by a file) 45 | Trs int // number of pages that are 'code'(not including libs; broken, includes data segment) 46 | Lrs int //number of pages of library(always 0 on 2.6) 47 | Drs int //number of pages of data/stack(including libs; broken, includes library text) 48 | Dt int //number of dirty pages(always 0 on 2.6) 49 | } 50 | 51 | func New(path string) (*Statm, error) { 52 | 53 | buf, err := ioutil.ReadFile(path) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | lines := strings.Split(string(buf), " ") 59 | stat := &Statm{} 60 | err = util.ParseStringsIntoStruct(stat, lines) 61 | return stat, err 62 | } 63 | -------------------------------------------------------------------------------- /statm/statm_test.go: -------------------------------------------------------------------------------- 1 | package statm 2 | 3 | import ( 4 | // "procfs/util" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestParsingStatm(t *testing.T) { 10 | // set the GLOBAL_SYSTEM_START 11 | s, err := New("./testfiles/statm") 12 | 13 | if err != nil { 14 | t.Fatal("Got error", err) 15 | } 16 | 17 | if s == nil { 18 | t.Fatal("statm is missing") 19 | } 20 | log.Println("statm", s) 21 | 22 | if s.Size != 134008 { 23 | t.Fatal("Expected size to be 134008") 24 | } 25 | if s.Resident != 72921 { 26 | t.Fatal("Expected Resident to be 72921") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /statm/testfiles/statm: -------------------------------------------------------------------------------- 1 | 134008 72921 239 877 0 130820 0 2 | -------------------------------------------------------------------------------- /status/status.go: -------------------------------------------------------------------------------- 1 | // 2 | // status.Status describes select data in /proc//status. 3 | // 4 | // Since most of this data is also available in /proc//stat 5 | // and parsable via stat.Stat, we only include the uid/gid 6 | // and the context switches information from /proc//status. 7 | // 8 | // Use status.New() to create a new status.Status object 9 | // from data in a path. 10 | // 11 | package status 12 | 13 | import ( 14 | "fmt" 15 | "io/ioutil" 16 | "strconv" 17 | "strings" 18 | ) 19 | 20 | // 21 | // Status is a data structure that describes the uid 22 | // information from /proc//status. 23 | // 24 | type Status struct { 25 | Uid int // Real user ID 26 | Euid int // Effective user ID 27 | Suid int // Saved usesr ID 28 | Fsuid int // FS user ID 29 | Gid int // Real group IDusesr 30 | Egid int // Effective group ID 31 | Sgid int // Saved group ItrealvalueD 32 | Fsgid int // FS group ID 33 | Vcswitch int64 //voluntary context switches 34 | NVcswitch int64 //nonvoluntary context switches 35 | } 36 | 37 | // 38 | // status.New creates a new /proc//status from a path. 39 | // 40 | // An error is returned if the data is malformed, or the path does not exist. 41 | // 42 | func New(path string) (*Status, error) { 43 | var err error 44 | 45 | buf, err := ioutil.ReadFile(path) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | lines := strings.Split(string(buf), "\n") 51 | status := &Status{} 52 | 53 | for i := 1; i < len(lines); i++ { 54 | lines[i] = strings.TrimSpace(lines[i]) 55 | if strings.HasPrefix(lines[i], "Uid:") { 56 | fields := strings.Fields(lines[i]) 57 | if len(fields) >= 5 { 58 | 59 | if status.Uid, err = strconv.Atoi(fields[1]); err != nil { 60 | return nil, fmt.Errorf("Unable to parse Uid %s: %v", fields[1], err) 61 | } 62 | if status.Euid, err = strconv.Atoi(fields[2]); err != nil { 63 | return nil, fmt.Errorf("Unable to parse Euid %s: %v", fields[2], err) 64 | } 65 | if status.Suid, err = strconv.Atoi(fields[3]); err != nil { 66 | return nil, fmt.Errorf("Unable to parse Suid %s: %v", fields[3], err) 67 | } 68 | if status.Fsuid, err = strconv.Atoi(fields[4]); err != nil { 69 | return nil, fmt.Errorf("Unable to parse Fsuid %s: %v", fields[4], err) 70 | } 71 | } else { 72 | return nil, fmt.Errorf("Malformed Uid: line %s", lines[i]) 73 | } 74 | } else if strings.HasPrefix(lines[i], "Gid:") { 75 | fields := strings.Fields(lines[i]) 76 | if len(fields) >= 5 { 77 | 78 | if status.Gid, err = strconv.Atoi(fields[1]); err != nil { 79 | return nil, fmt.Errorf("Unable to parse Gid %s: %v", fields[1], err) 80 | } 81 | if status.Egid, err = strconv.Atoi(fields[2]); err != nil { 82 | return nil, fmt.Errorf("Unable to parse Egid %s: %v", fields[2], err) 83 | } 84 | if status.Sgid, err = strconv.Atoi(fields[3]); err != nil { 85 | return nil, fmt.Errorf("Unable to parse Sgid %s: %v", fields[3], err) 86 | } 87 | if status.Fsgid, err = strconv.Atoi(fields[4]); err != nil { 88 | return nil, fmt.Errorf("Unable to parse Fsgid %s: %v", fields[4], err) 89 | } 90 | } else { 91 | return nil, fmt.Errorf("Malformed Gid: line %s", lines[i]) 92 | } 93 | } else if strings.HasPrefix(lines[i], "voluntary_ctxt_switches:") { 94 | fields := strings.Fields(lines[i]) 95 | if status.Vcswitch, err = strconv.ParseInt(fields[1], 10, 64); err != nil { 96 | return nil, fmt.Errorf("Unable to parse voluntary_ctxt_switches %s: %v", fields[1], err) 97 | } 98 | } else if strings.HasPrefix(lines[i], "nonvoluntary_ctxt_switches:") { 99 | fields := strings.Fields(lines[i]) 100 | if status.NVcswitch, err = strconv.ParseInt(fields[1], 10, 64); err != nil { 101 | return nil, fmt.Errorf("Unable to parse nonvoluntary_ctxt_switches %s: %v", fields[1], err) 102 | } 103 | } 104 | } 105 | 106 | return status, err 107 | } 108 | -------------------------------------------------------------------------------- /status/status_test.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jandre/procfs/util" 7 | ) 8 | 9 | func TestParsingStatus(t *testing.T) { 10 | // set the GLOBAL_SYSTEM_START 11 | util.GLOBAL_SYSTEM_START = 1388417200 12 | s, err := New("./testfiles/status") 13 | 14 | if err != nil { 15 | t.Fatal("Got error", err) 16 | } 17 | 18 | if s == nil { 19 | t.Fatal("stat is missing") 20 | } 21 | 22 | if s.Uid != 0 { 23 | t.Fatal("Uid should be 0, instead:", s.Uid) 24 | } 25 | 26 | if s.Gid != 1001 { 27 | t.Fatal("Gid should be 1001, instead:", s.Gid) 28 | } 29 | if s.Vcswitch != 72058 { 30 | t.Fatal("Vcswitch should be 75958, instead,", s.Vcswitch) 31 | } 32 | if s.NVcswitch != 3109 { 33 | t.Fatal("NVcswitch should be 75958, instead,", s.NVcswitch) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /status/testfiles/status: -------------------------------------------------------------------------------- 1 | Name: ossec-logcollec 2 | State: S (sleeping) 3 | Tgid: 11784 4 | Pid: 11784 5 | PPid: 1 6 | TracerPid: 0 7 | Uid: 0 0 0 0 8 | Gid: 1001 1001 1001 1001 9 | FDSize: 64 10 | Groups: 0 11 | VmPeak: 4524 kB 12 | VmSize: 4520 kB 13 | VmLck: 0 kB 14 | VmPin: 0 kB 15 | VmHWM: 832 kB 16 | VmRSS: 832 kB 17 | VmData: 184 kB 18 | VmStk: 136 kB 19 | VmExe: 228 kB 20 | VmLib: 1884 kB 21 | VmPTE: 32 kB 22 | VmSwap: 0 kB 23 | Threads: 1 24 | SigQ: 0/7760 25 | SigPnd: 0000000000000000 26 | ShdPnd: 0000000000000000 27 | SigBlk: 0000000000000000 28 | SigIgn: 0000000000000001 29 | SigCgt: 0000000000007006 30 | CapInh: 0000000000000000 31 | CapPrm: ffffffffffffffff 32 | CapEff: ffffffffffffffff 33 | CapBnd: ffffffffffffffff 34 | Cpus_allowed: ffffffff,ffffffff 35 | Cpus_allowed_list: 0-63 36 | Mems_allowed: 00000000,00000001 37 | Mems_allowed_list: 0 38 | voluntary_ctxt_switches: 72058 39 | nonvoluntary_ctxt_switches: 3109 40 | -------------------------------------------------------------------------------- /testfiles/cmdline: -------------------------------------------------------------------------------- 1 | dhclient3-eIF_METRIC=100-pf/var/run/dhclient.eth0.pid-lf/var/lib/dhcp/dhclient.eth0.leases-1eth0 -------------------------------------------------------------------------------- /testfiles/meminfo: -------------------------------------------------------------------------------- 1 | MemTotal: 1011932 kB 2 | MemFree: 228784 kB 3 | Buffers: 43304 kB 4 | Cached: 117304 kB 5 | SwapCached: 11440 kB 6 | Active: 294296 kB 7 | Inactive: 402400 kB 8 | Active(anon): 206796 kB 9 | Inactive(anon): 329340 kB 10 | Active(file): 87500 kB 11 | Inactive(file): 73060 kB 12 | Unevictable: 0 kB 13 | Mlocked: 0 kB 14 | SwapTotal: 1046524 kB 15 | SwapFree: 987348 kB 16 | Dirty: 180 kB 17 | Writeback: 0 kB 18 | AnonPages: 528664 kB 19 | Mapped: 19532 kB 20 | Shmem: 32 kB 21 | Slab: 56632 kB 22 | SReclaimable: 38616 kB 23 | SUnreclaim: 18016 kB 24 | KernelStack: 1712 kB 25 | PageTables: 8340 kB 26 | NFS_Unstable: 0 kB 27 | Bounce: 0 kB 28 | WritebackTmp: 0 kB 29 | CommitLimit: 1552488 kB 30 | Committed_AS: 819540 kB 31 | VmallocTotal: 34359738367 kB 32 | VmallocUsed: 278144 kB 33 | VmallocChunk: 34359457276 kB 34 | HardwareCorrupted: 0 kB 35 | AnonHugePages: 0 kB 36 | HugePages_Total: 0 37 | HugePages_Free: 0 38 | HugePages_Rsvd: 0 39 | HugePages_Surp: 0 40 | Hugepagesize: 2048 kB 41 | DirectMap4k: 38912 kB 42 | DirectMap2M: 1009664 kB 43 | -------------------------------------------------------------------------------- /util/structparser.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // 4 | // Copyright Jen Andre (jandre@gmail.com) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the 8 | // "Software"), to deal in the Software without restriction, including 9 | // without limitation the rights to use, copy, modify, merge, publish, 10 | // distribute, sublicense, and/or sell copies of the Software, and to 11 | // permit persons to whom the Software is furnished to do so, subject to 12 | // the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be 15 | // included in all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | // WITH THE SOFTWARE OR THE USE OR O 24 | 25 | import ( 26 | "fmt" 27 | "reflect" 28 | "strconv" 29 | "strings" 30 | "time" 31 | ) 32 | 33 | // 34 | // ParseStringsIntoStruct expects a pointer to a struct as its 35 | // first argument. It assigns each element from the lines slice 36 | // sequentially to the struct members, parsing each according to 37 | // type. It currently accepts fields of type int, int64, string 38 | // and time.Time (it assumes that values of the latter kind 39 | // are formatted as a clock-tick count since the system start). 40 | // 41 | // Extra lines are ignored. 42 | // 43 | func ParseStringsIntoStruct(vi interface{}, strs []string) error { 44 | v := reflect.ValueOf(vi).Elem() 45 | typeOf := v.Type() 46 | 47 | for i := 0; i < v.NumField(); i++ { 48 | if i > len(strs) { 49 | break 50 | } 51 | str := strings.TrimSpace(strs[i]) 52 | interf := v.Field(i).Addr().Interface() 53 | if err := parseField(interf, str); err != nil { 54 | return fmt.Errorf("cannot parse field %s=%q: %v", typeOf.Field(i).Name, str, err) 55 | } 56 | } 57 | return nil 58 | } 59 | 60 | func parseField(field interface{}, line string) error { 61 | switch field := field.(type) { 62 | case *int: 63 | val, err := strconv.Atoi(line) 64 | if err != nil { 65 | return err 66 | } 67 | *field = val 68 | case *int64: 69 | val, err := strconv.ParseInt(line, 10, 64) 70 | if err != nil { 71 | return err 72 | } 73 | *field = val 74 | case *uint64: 75 | val, err := strconv.ParseUint(line, 10, 64) 76 | if err != nil { 77 | return err 78 | } 79 | *field = val 80 | case *string: 81 | *field = line 82 | case *time.Time: 83 | jiffies, err := strconv.ParseInt(line, 10, 64) 84 | if err != nil { 85 | return err 86 | } 87 | *field = jiffiesToTime(jiffies) 88 | case *time.Duration: 89 | jiffies, err := strconv.ParseInt(line, 10, 64) 90 | if err != nil { 91 | return nil 92 | } 93 | *field = jiffiesToDuration(jiffies) 94 | default: 95 | return fmt.Errorf("unsupported field type %T", field) 96 | } 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // 4 | // Copyright Jen Andre (jandre@gmail.com) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the 8 | // "Software"), to deal in the Software without restriction, including 9 | // without limitation the rights to use, copy, modify, merge, publish, 10 | // distribute, sublicense, and/or sell copies of the Software, and to 11 | // permit persons to whom the Software is furnished to do so, subject to 12 | // the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be 15 | // included in all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | import ( 26 | "io/ioutil" 27 | "log" 28 | "strconv" 29 | "strings" 30 | "time" 31 | ) 32 | 33 | /* 34 | #include 35 | */ 36 | import "C" 37 | 38 | // 39 | // systemStart() attempts to detect the system start time. 40 | // 41 | // It will attempt to fetch the system start time by parsing /proc/stat 42 | // look for the btime line, and converting that to an int64 value (the start 43 | // time in Epoch seconds). 44 | // 45 | // This value must be used for certain /proc time values that are specified 46 | // in 'jiffies', and is used with the _SC_CLK_TCK retrieved from sysconf(3) 47 | // to calculate epoch timestamps. 48 | // 49 | // 50 | func systemStart() int64 { 51 | str, err := ioutil.ReadFile("/proc/stat") 52 | if err != nil { 53 | log.Fatal("Unable to read btime from /proc/stat - is this Linux?", err) 54 | } 55 | lines := strings.Split(string(str), "\n") 56 | for _, line := range lines { 57 | if strings.HasPrefix(line, "btime") { 58 | parts := strings.Split(line, " ") 59 | // format is btime 1388417200 60 | val, err := strconv.ParseInt(parts[1], 10, 32) 61 | if err != nil { 62 | log.Fatal("Unable to convert btime value in /proc/stat to int64", parts[1], err) 63 | } 64 | return int64(val) 65 | } 66 | } 67 | 68 | log.Fatal("No btime found in /proc/stat. This value is needed to calculate timestamps") 69 | 70 | return 0 71 | } 72 | 73 | var GLOBAL_SYSTEM_START int64 = systemStart() 74 | 75 | // 76 | // jiffiesToTime converts jiffies to a Time object 77 | // using the GLOBAL_SYSTEM_START time and the value of the 78 | // _SC_CLK_TICK value obtained from sysconf(3). 79 | // 80 | // To get the # of seconds elapsed since system start, we do jiffies / _SC_CLK_TCK. 81 | // 82 | // We then add the system start time (GLOBAL_SYSTEM_START) to get the epoch 83 | // timestamp in seconds. 84 | // 85 | func jiffiesToTime(jiffies int64) time.Time { 86 | ticks := C.sysconf(C._SC_CLK_TCK) 87 | return time.Unix(GLOBAL_SYSTEM_START+jiffies/int64(ticks), 0) 88 | } 89 | 90 | func jiffiesToDuration(jiffies int64) time.Duration { 91 | ticks := C.sysconf(C._SC_CLK_TCK) 92 | return time.Duration(jiffies / int64(ticks)) 93 | 94 | } 95 | -------------------------------------------------------------------------------- /util/util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSystemStart(t *testing.T) { 8 | systemStart() 9 | } 10 | 11 | func TestJiffiesToEpoch(t * testing.T) { 12 | val := JiffiesToEpoch(17350497) 13 | if (val < GLOBAL_SYSTEM_START) { 14 | t.Error("error", val) 15 | } 16 | } 17 | --------------------------------------------------------------------------------