github.com/elastic/gosigar@v0.14.3/sigar_linux_common.go (about) 1 // Copyright (c) 2012 VMware, Inc. 2 3 // +build freebsd linux 4 5 package gosigar 6 7 import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "os" 14 "os/user" 15 "path/filepath" 16 "strconv" 17 "strings" 18 "syscall" 19 ) 20 21 var system struct { 22 ticks uint64 23 btime uint64 24 } 25 26 var Procd string 27 28 func getLinuxBootTime() { 29 // grab system boot time 30 readFile(Procd+"/stat", func(line string) bool { 31 if strings.HasPrefix(line, "btime") { 32 system.btime, _ = strtoull(line[6:]) 33 return false // stop reading 34 } 35 return true 36 }) 37 } 38 39 func (self *LoadAverage) Get() error { 40 line, err := ioutil.ReadFile(Procd + "/loadavg") 41 if err != nil { 42 return nil 43 } 44 45 fields := strings.Fields(string(line)) 46 47 self.One, _ = strconv.ParseFloat(fields[0], 64) 48 self.Five, _ = strconv.ParseFloat(fields[1], 64) 49 self.Fifteen, _ = strconv.ParseFloat(fields[2], 64) 50 51 return nil 52 } 53 54 func (self *Swap) Get() error { 55 56 table, err := parseMeminfo() 57 if err != nil { 58 return err 59 } 60 self.Total, _ = table["SwapTotal"] 61 self.Free, _ = table["SwapFree"] 62 63 self.Used = self.Total - self.Free 64 return nil 65 } 66 67 func (self *Cpu) Get() error { 68 return readFile(Procd+"/stat", func(line string) bool { 69 if len(line) > 4 && line[0:4] == "cpu " { 70 parseCpuStat(self, line) 71 return false 72 } 73 return true 74 75 }) 76 } 77 78 func (self *CpuList) Get() error { 79 capacity := len(self.List) 80 if capacity == 0 { 81 capacity = 4 82 } 83 list := make([]Cpu, 0, capacity) 84 85 err := readFile(Procd+"/stat", func(line string) bool { 86 if len(line) > 3 && line[0:3] == "cpu" && line[3] != ' ' { 87 cpu := Cpu{} 88 parseCpuStat(&cpu, line) 89 list = append(list, cpu) 90 } 91 return true 92 }) 93 94 self.List = list 95 96 return err 97 } 98 99 func (self *FileSystemList) Get() error { 100 capacity := len(self.List) 101 if capacity == 0 { 102 capacity = 10 103 } 104 fslist := make([]FileSystem, 0, capacity) 105 106 err := readFile(getMountTableFileName(), func(line string) bool { 107 fields := strings.Fields(line) 108 109 fs := FileSystem{} 110 fs.DevName = fields[0] 111 fs.DirName = fields[1] 112 fs.SysTypeName = fields[2] 113 fs.Options = fields[3] 114 115 fslist = append(fslist, fs) 116 117 return true 118 }) 119 120 self.List = fslist 121 122 return err 123 } 124 125 func (self *ProcList) Get() error { 126 dir, err := os.Open(Procd) 127 if err != nil { 128 return err 129 } 130 defer dir.Close() 131 132 const readAllDirnames = -1 // see os.File.Readdirnames doc 133 134 names, err := dir.Readdirnames(readAllDirnames) 135 if err != nil { 136 return err 137 } 138 139 capacity := len(names) 140 list := make([]int, 0, capacity) 141 142 for _, name := range names { 143 if name[0] < '0' || name[0] > '9' { 144 continue 145 } 146 pid, err := strconv.Atoi(name) 147 if err == nil { 148 list = append(list, pid) 149 } 150 } 151 152 self.List = list 153 154 return nil 155 } 156 157 func (self *ProcState) Get(pid int) error { 158 data, err := readProcFile(pid, "stat") 159 if err != nil { 160 return err 161 } 162 163 // Extract the comm value with is surrounded by parentheses. 164 lIdx := bytes.Index(data, []byte("(")) 165 rIdx := bytes.LastIndex(data, []byte(")")) 166 if lIdx < 0 || rIdx < 0 || lIdx >= rIdx || rIdx+2 >= len(data) { 167 return fmt.Errorf("failed to extract comm for pid %d from '%v'", pid, string(data)) 168 } 169 self.Name = string(data[lIdx+1 : rIdx]) 170 171 // Extract the rest of the fields that we are interested in. 172 fields := bytes.Fields(data[rIdx+2:]) 173 if len(fields) <= 36 { 174 return fmt.Errorf("expected more stat fields for pid %d from '%v'", pid, string(data)) 175 } 176 177 interests := bytes.Join([][]byte{ 178 fields[0], // state 179 fields[1], // ppid 180 fields[2], // pgrp 181 fields[4], // tty_nr 182 fields[15], // priority 183 fields[16], // nice 184 fields[36], // processor (last processor executed on) 185 }, []byte(" ")) 186 187 var state string 188 _, err = fmt.Fscan(bytes.NewBuffer(interests), 189 &state, 190 &self.Ppid, 191 &self.Pgid, 192 &self.Tty, 193 &self.Priority, 194 &self.Nice, 195 &self.Processor, 196 ) 197 if err != nil { 198 return fmt.Errorf("failed to parse stat fields for pid %d from '%v': %v", pid, string(data), err) 199 } 200 self.State = RunState(state[0]) 201 202 // Read /proc/[pid]/status to get the uid, then lookup uid to get username. 203 status, err := getProcStatus(pid) 204 if err != nil { 205 return fmt.Errorf("failed to read process status for pid %d: %v", pid, err) 206 } 207 uids, err := getUIDs(status) 208 if err != nil { 209 return fmt.Errorf("failed to read process status for pid %d: %v", pid, err) 210 } 211 user, err := user.LookupId(uids[0]) 212 if err == nil { 213 self.Username = user.Username 214 } else { 215 self.Username = uids[0] 216 } 217 218 return nil 219 } 220 221 func (self *ProcMem) Get(pid int) error { 222 contents, err := readProcFile(pid, "statm") 223 if err != nil { 224 return err 225 } 226 227 fields := strings.Fields(string(contents)) 228 229 size, _ := strtoull(fields[0]) 230 self.Size = size << 12 231 232 rss, _ := strtoull(fields[1]) 233 self.Resident = rss << 12 234 235 share, _ := strtoull(fields[2]) 236 self.Share = share << 12 237 238 contents, err = readProcFile(pid, "stat") 239 if err != nil { 240 return err 241 } 242 243 fields = strings.Fields(string(contents)) 244 245 self.MinorFaults, _ = strtoull(fields[10]) 246 self.MajorFaults, _ = strtoull(fields[12]) 247 self.PageFaults = self.MinorFaults + self.MajorFaults 248 249 return nil 250 } 251 252 func (self *ProcTime) Get(pid int) error { 253 contents, err := readProcFile(pid, "stat") 254 if err != nil { 255 return err 256 } 257 258 fields := strings.Fields(string(contents)) 259 260 user, _ := strtoull(fields[13]) 261 sys, _ := strtoull(fields[14]) 262 // convert to millis 263 self.User = user * (1000 / system.ticks) 264 self.Sys = sys * (1000 / system.ticks) 265 self.Total = self.User + self.Sys 266 267 // convert to millis 268 self.StartTime, _ = strtoull(fields[21]) 269 self.StartTime /= system.ticks 270 self.StartTime += system.btime 271 self.StartTime *= 1000 272 273 return nil 274 } 275 276 func (self *ProcArgs) Get(pid int) error { 277 contents, err := readProcFile(pid, "cmdline") 278 if err != nil { 279 return err 280 } 281 282 bbuf := bytes.NewBuffer(contents) 283 284 var args []string 285 286 for { 287 arg, err := bbuf.ReadBytes(0) 288 if err == io.EOF { 289 break 290 } 291 args = append(args, string(chop(arg))) 292 } 293 294 self.List = args 295 296 return nil 297 } 298 299 func (self *ProcEnv) Get(pid int) error { 300 contents, err := readProcFile(pid, "environ") 301 if err != nil { 302 return err 303 } 304 305 if self.Vars == nil { 306 self.Vars = map[string]string{} 307 } 308 309 pairs := bytes.Split(contents, []byte{0}) 310 for _, kv := range pairs { 311 parts := bytes.SplitN(kv, []byte{'='}, 2) 312 if len(parts) != 2 { 313 continue 314 } 315 316 key := string(bytes.TrimSpace(parts[0])) 317 if key == "" { 318 continue 319 } 320 321 self.Vars[key] = string(bytes.TrimSpace(parts[1])) 322 } 323 324 return nil 325 } 326 327 func (self *ProcExe) Get(pid int) error { 328 fields := map[string]*string{ 329 "exe": &self.Name, 330 "cwd": &self.Cwd, 331 "root": &self.Root, 332 } 333 334 for name, field := range fields { 335 val, err := os.Readlink(procFileName(pid, name)) 336 337 if err != nil { 338 return err 339 } 340 341 *field = val 342 } 343 344 return nil 345 } 346 347 func parseMeminfo() (map[string]uint64, error) { 348 table := map[string]uint64{} 349 350 err := readFile(Procd+"/meminfo", func(line string) bool { 351 fields := strings.Split(line, ":") 352 353 if len(fields) != 2 { 354 return true // skip on errors 355 } 356 357 valueUnit := strings.Fields(fields[1]) 358 value, err := strtoull(valueUnit[0]) 359 if err != nil { 360 return true // skip on errors 361 } 362 363 if len(valueUnit) > 1 && valueUnit[1] == "kB" { 364 value *= 1024 365 } 366 table[fields[0]] = value 367 368 return true 369 }) 370 return table, err 371 } 372 373 func readFile(file string, handler func(string) bool) error { 374 contents, err := ioutil.ReadFile(file) 375 if err != nil { 376 return err 377 } 378 379 reader := bufio.NewReader(bytes.NewBuffer(contents)) 380 381 for { 382 line, _, err := reader.ReadLine() 383 if err == io.EOF { 384 break 385 } 386 if !handler(string(line)) { 387 break 388 } 389 } 390 391 return nil 392 } 393 394 func strtoull(val string) (uint64, error) { 395 return strconv.ParseUint(val, 10, 64) 396 } 397 398 func procFileName(pid int, name string) string { 399 return Procd + "/" + strconv.Itoa(pid) + "/" + name 400 } 401 402 func readProcFile(pid int, name string) (content []byte, err error) { 403 path := procFileName(pid, name) 404 405 // Panics have been reported when reading proc files, let's recover and 406 // report the path if this happens 407 // See https://github.com/elastic/beats/issues/6692 408 defer func() { 409 if r := recover(); r != nil { 410 content = nil 411 err = fmt.Errorf("recovered panic when reading proc file '%s': %v", path, r) 412 } 413 }() 414 contents, err := ioutil.ReadFile(path) 415 416 if err != nil { 417 if perr, ok := err.(*os.PathError); ok { 418 if perr.Err == syscall.ENOENT { 419 return nil, syscall.ESRCH 420 } 421 } 422 } 423 424 return contents, err 425 } 426 427 // getProcStatus reads /proc/[pid]/status which contains process status 428 // information in human readable form. 429 func getProcStatus(pid int) (map[string]string, error) { 430 status := make(map[string]string, 42) 431 path := filepath.Join(Procd, strconv.Itoa(pid), "status") 432 err := readFile(path, func(line string) bool { 433 fields := strings.SplitN(line, ":", 2) 434 if len(fields) == 2 { 435 status[fields[0]] = strings.TrimSpace(fields[1]) 436 } 437 438 return true 439 }) 440 return status, err 441 } 442 443 // getUIDs reads the "Uid" value from status and splits it into four values -- 444 // real, effective, saved set, and file system UIDs. 445 func getUIDs(status map[string]string) ([]string, error) { 446 uidLine, ok := status["Uid"] 447 if !ok { 448 return nil, fmt.Errorf("Uid not found in proc status") 449 } 450 451 uidStrs := strings.Fields(uidLine) 452 if len(uidStrs) != 4 { 453 return nil, fmt.Errorf("Uid line ('%s') did not contain four values", uidLine) 454 } 455 456 return uidStrs, nil 457 }