gopkg.in/essentialkaos/ek.v3@v3.5.1/system/info.go (about) 1 // +build !windows 2 3 // Package system provides methods for working with system data (metrics/users) 4 package system 5 6 // ////////////////////////////////////////////////////////////////////////////////// // 7 // // 8 // Copyright (c) 2009-2016 Essential Kaos // 9 // Essential Kaos Open Source License <http://essentialkaos.com/ekol?en> // 10 // // 11 // ////////////////////////////////////////////////////////////////////////////////// // 12 13 import ( 14 "errors" 15 "io/ioutil" 16 "path/filepath" 17 "strconv" 18 "strings" 19 "syscall" 20 "time" 21 ) 22 23 // ////////////////////////////////////////////////////////////////////////////////// // 24 25 const _Hz = 100.0 26 27 const ( 28 _PROC_UPTIME = "/proc/uptime" 29 _PROC_LOADAVG = "/proc/loadavg" 30 _PROC_MEMINFO = "/proc/meminfo" 31 _PROC_CPUINFO = "/proc/stat" 32 _PROC_NET = "/proc/net/dev" 33 _PROC_DISCSTATS = "/proc/diskstats" 34 _MTAB_FILE = "/etc/mtab" 35 ) 36 37 // ////////////////////////////////////////////////////////////////////////////////// // 38 39 // LoadAvg contains information about average system load 40 type LoadAvg struct { 41 Min1 float64 `json:"min1"` // LA in last 1 minute 42 Min5 float64 `json:"min5"` // LA in last 5 minutes 43 Min15 float64 `json:"min15"` // LA in last 15 minutes 44 RProc int `json:"rproc"` // Number of currently runnable kernel scheduling entities 45 TProc int `json:"tproc"` // Number of kernel scheduling entities that currently exist on the system 46 } 47 48 // MemInfo contains info about system memory 49 type MemInfo struct { 50 MemTotal uint64 `json:"total"` // Total usable ram (i.e. physical ram minus a few reserved bits and the kernel binary code) 51 MemFree uint64 `json:"free"` // The sum of MemFree - (Buffers + Cached) 52 MemUsed uint64 `json:"used"` // MemTotal - MemFree 53 Buffers uint64 `json:"buffers"` // Relatively temporary storage for raw disk blocks shouldn't get tremendously large (20MB or so) 54 Cached uint64 `json:"cached"` // In-memory cache for files read from the disk (the pagecache). Doesn't include SwapCached 55 Active uint64 `json:"active"` // Memory that has been used more recently and usually not reclaimed unless absolutely necessary 56 Inactive uint64 `json:"inactive"` // Memory which has been less recently used. It is more eligible to be reclaimed for other purposes 57 SwapTotal uint64 `json:"swap_total"` // Total amount of swap space available 58 SwapFree uint64 `json:"swap_free"` // Memory which has been evicted from RAM, and is temporarily on the disk still also is in the swapfile 59 SwapUsed uint64 `json:"swap_used"` // SwapTotal - SwapFree 60 SwapCached uint64 `json:"spaw_cached"` // Memory that once was swapped out, is swapped back in but 61 Dirty uint64 `json:"dirty"` // Memory which is waiting to get written back to the disk 62 Slab uint64 `json:"slab"` // In-kernel data structures cache 63 } 64 65 // CPUInfo contains info about CPU usage 66 type CPUInfo struct { 67 User float64 `json:"user"` // Normal processes executing in user mode 68 System float64 `json:"system"` // Processes executing in kernel mode 69 Nice float64 `json:"nice"` // Niced processes executing in user mode 70 Idle float64 `json:"idle"` // Twiddling thumbs 71 Wait float64 `json:"wait"` // Waiting for I/O to complete 72 Count int `json:"count"` // Number of CPU cores 73 } 74 75 // FSInfo contains info about fs usage 76 type FSInfo struct { 77 Type string `json:"type"` // FS type (ext4/ntfs/etc...) 78 Device string `json:"device"` // Device spec 79 Used uint64 `json:"used"` // Used space 80 Free uint64 `json:"free"` // Free space 81 Total uint64 `json:"total"` // Total space 82 IOStats *IOStats `json:"iostats"` // IO statistics 83 } 84 85 // IOStats contains inforamtion about I/O 86 type IOStats struct { 87 ReadComplete uint64 `json:"read_complete"` // Reads completed successfully 88 ReadMerged uint64 `json:"read_merged"` // Reads merged 89 ReadSectors uint64 `json:"read_sectors"` // Sectors read 90 ReadMs uint64 `json:"read_ms"` // Time spent reading (ms) 91 WriteComplete uint64 `json:"write_complete"` // Writes completed 92 WriteMerged uint64 `json:"write_merged"` // Writes merged 93 WriteSectors uint64 `json:"write_sectors"` // Sectors written 94 WriteMs uint64 `json:"write_ms"` // Time spent writing (ms) 95 IOPending uint64 `json:"io_pending"` // I/Os currently in progress 96 IOMs uint64 `json:"io_ms"` // Time spent doing I/Os (ms) 97 IOQueueMs uint64 `json:"io_queue_ms"` // Weighted time spent doing I/Os (ms) 98 } 99 100 // SystemInfo contains info about system (hostname, OS, arch...) 101 type SystemInfo struct { 102 Hostname string `json:"hostname"` // Hostname 103 OS string `json:"os"` // OS name 104 Kernel string `json:"kernel"` // Kernel version 105 Arch string `json:"arch"` // System architecture (i386/i686/x86_64/etc...) 106 } 107 108 // InterfaceInfo contains info about network interfaces 109 type InterfaceInfo struct { 110 ReceivedBytes uint64 `json:"received_bytes"` 111 ReceivedPackets uint64 `json:"received_packets"` 112 TransmittedBytes uint64 `json:"transmitted_bytes"` 113 TransmittedPackets uint64 `json:"transmitted_packets"` 114 } 115 116 // ////////////////////////////////////////////////////////////////////////////////// // 117 118 // GetUptime return uptime in seconds from 1/1/1970 119 func GetUptime() (uint64, error) { 120 content, err := readFileContent(_PROC_UPTIME) 121 122 if err != nil { 123 return 0, err 124 } 125 126 ca := strings.Split(content[0], " ") 127 128 if len(ca) != 2 { 129 return 0, errors.New("Can't parse file " + _PROC_UPTIME + ".") 130 } 131 132 up, _ := strconv.ParseFloat(ca[0], 64) 133 134 return uint64(up), nil 135 } 136 137 // GetLA return loadavg 138 func GetLA() (*LoadAvg, error) { 139 result := &LoadAvg{} 140 content, err := readFileContent(_PROC_LOADAVG) 141 142 if err != nil { 143 return result, err 144 } 145 146 contentSlice := strings.Split(content[0], " ") 147 148 if len(contentSlice) != 5 { 149 return result, errors.New("Can't parse file " + _PROC_LOADAVG + ".") 150 } 151 152 procSlice := strings.Split(contentSlice[3], "/") 153 154 result.Min1, _ = strconv.ParseFloat(contentSlice[0], 64) 155 result.Min5, _ = strconv.ParseFloat(contentSlice[1], 64) 156 result.Min15, _ = strconv.ParseFloat(contentSlice[2], 64) 157 result.RProc, _ = strconv.Atoi(procSlice[0]) 158 result.TProc, _ = strconv.Atoi(procSlice[1]) 159 160 return result, nil 161 } 162 163 // GetMemInfo return memory info 164 func GetMemInfo() (*MemInfo, error) { 165 var props = map[string]bool{ 166 "MemTotal": true, 167 "MemFree": true, 168 "Buffers": true, 169 "Cached": true, 170 "SwapCached": true, 171 "Active": true, 172 "Inactive": true, 173 "SwapTotal": true, 174 "SwapFree": true, 175 "Dirty": true, 176 "Slab": true, 177 } 178 179 result := &MemInfo{} 180 content, err := readFileContent(_PROC_MEMINFO) 181 182 if err != nil { 183 return result, err 184 } 185 186 for _, line := range content { 187 if line == "" { 188 continue 189 } 190 191 lineSlice := strings.Split(line, ":") 192 193 if len(lineSlice) != 2 { 194 return result, errors.New("Can't parse file " + _PROC_MEMINFO + ".") 195 } 196 197 if props[lineSlice[0]] != true { 198 continue 199 } 200 201 strValue := strings.TrimRight(lineSlice[1], " kB") 202 strValue = strings.Replace(strValue, " ", "", -1) 203 uintValue, err := strconv.ParseUint(strValue, 10, 64) 204 205 if err != nil { 206 return result, err 207 } 208 209 switch lineSlice[0] { 210 case "MemTotal": 211 result.MemTotal = uintValue * 1024 212 case "MemFree": 213 result.MemFree = uintValue * 1024 214 case "Buffers": 215 result.Buffers = uintValue * 1024 216 case "Cached": 217 result.Cached = uintValue * 1024 218 case "SwapCached": 219 result.SwapCached = uintValue * 1024 220 case "Active": 221 result.Active = uintValue * 1024 222 case "Inactive": 223 result.Inactive = uintValue * 1024 224 case "SwapTotal": 225 result.SwapTotal = uintValue * 1024 226 case "SwapFree": 227 result.SwapFree = uintValue * 1024 228 case "Dirty": 229 result.Dirty = uintValue * 1024 230 case "Slab": 231 result.Slab = uintValue * 1024 232 } 233 } 234 235 result.MemFree += result.Cached + result.Buffers 236 result.MemUsed = result.MemTotal - result.MemFree 237 result.SwapUsed = result.SwapTotal - result.SwapFree 238 239 return result, nil 240 } 241 242 // GetCPUInfo return info about CPU usage 243 func GetCPUInfo() (*CPUInfo, error) { 244 result := &CPUInfo{} 245 246 user, system, nice, idle, wait, total, count, err := getCPUStats() 247 248 if err != nil { 249 return result, err 250 } 251 252 result.System = (float64(system) / float64(total)) * 100 253 result.User = (float64(user) / float64(total)) * 100 254 result.Nice = (float64(nice) / float64(total)) * 100 255 result.Wait = (float64(wait) / float64(total)) * 100 256 result.Idle = (float64(idle) / float64(total)) * 100 257 result.Count = count 258 259 return result, nil 260 } 261 262 // GetFSInfo return info about mounted filesystems 263 func GetFSInfo() (map[string]*FSInfo, error) { 264 result := make(map[string]*FSInfo) 265 266 content, err := readFileContent(_MTAB_FILE) 267 268 if err != nil { 269 return result, err 270 } 271 272 ios, err := GetIOStats() 273 274 if err != nil { 275 return result, err 276 } 277 278 for _, line := range content { 279 if line == "" || line[0:1] == "#" || line[0:1] != "/" { 280 continue 281 } 282 283 values := strings.Split(line, " ") 284 285 if len(values) < 4 { 286 return result, errors.New("Can't parse file " + _MTAB_FILE) 287 } 288 289 path := values[1] 290 fsInfo := &FSInfo{Type: values[2]} 291 stats := &syscall.Statfs_t{} 292 293 err = syscall.Statfs(path, stats) 294 295 if err != nil { 296 return result, err 297 } 298 299 fsDevice, err := filepath.EvalSymlinks(values[0]) 300 301 if err == nil { 302 fsInfo.Device = fsDevice 303 } else { 304 fsInfo.Device = values[0] 305 } 306 307 fsInfo.Total = stats.Blocks * uint64(stats.Bsize) 308 fsInfo.Free = uint64(stats.Bavail) * uint64(stats.Bsize) 309 fsInfo.Used = fsInfo.Total - (stats.Bfree * uint64(stats.Bsize)) 310 fsInfo.IOStats = ios[strings.Replace(fsInfo.Device, "/dev/", "", 1)] 311 312 result[path] = fsInfo 313 } 314 315 return result, nil 316 } 317 318 // GetIOStats return I/O stats 319 func GetIOStats() (map[string]*IOStats, error) { 320 result := make(map[string]*IOStats) 321 322 content, err := readFileContent(_PROC_DISCSTATS) 323 324 if err != nil { 325 return result, err 326 } 327 328 for _, line := range content { 329 if line == "" { 330 continue 331 } 332 333 values := cleanSlice(strings.Split(line, " ")) 334 335 if len(values) != 14 { 336 return result, errors.New("Can't parse file " + _PROC_DISCSTATS) 337 } 338 339 device := values[2] 340 341 if device[0:3] == "ram" || device[0:3] == "loo" { 342 continue 343 } 344 345 metrics := stringSliceToUintSlice(values[3:]) 346 347 result[device] = &IOStats{ 348 ReadComplete: metrics[0], 349 ReadMerged: metrics[1], 350 ReadSectors: metrics[2], 351 ReadMs: metrics[3], 352 WriteComplete: metrics[4], 353 WriteMerged: metrics[5], 354 WriteSectors: metrics[6], 355 WriteMs: metrics[7], 356 IOPending: metrics[8], 357 IOMs: metrics[9], 358 IOQueueMs: metrics[10], 359 } 360 } 361 362 return result, nil 363 } 364 365 // GetInterfacesInfo return info about network interfaces 366 func GetInterfacesInfo() (map[string]*InterfaceInfo, error) { 367 result := make(map[string]*InterfaceInfo) 368 369 content, err := readFileContent(_PROC_NET) 370 371 if err != nil { 372 return result, err 373 } 374 375 if len(content) <= 2 { 376 return result, nil 377 } 378 379 for _, line := range content[2:] { 380 lineSlice := strings.Split(line, ":") 381 382 if len(lineSlice) != 2 { 383 continue 384 } 385 386 metrics := cleanSlice(strings.Split(lineSlice[1], " ")) 387 name := strings.TrimLeft(lineSlice[0], " ") 388 receivedBytes, _ := strconv.ParseUint(metrics[0], 10, 64) 389 receivedPackets, _ := strconv.ParseUint(metrics[1], 10, 64) 390 transmittedBytes, _ := strconv.ParseUint(metrics[8], 10, 64) 391 transmittedPackets, _ := strconv.ParseUint(metrics[9], 10, 64) 392 393 result[name] = &InterfaceInfo{ 394 receivedBytes, 395 receivedPackets, 396 transmittedBytes, 397 transmittedPackets, 398 } 399 } 400 401 return result, nil 402 } 403 404 // GetNetworkSpeed return input/output speed in bytes per second 405 func GetNetworkSpeed() (uint64, uint64, error) { 406 intInfo1, err := GetInterfacesInfo() 407 408 if err != nil { 409 return 0, 0, err 410 } 411 412 time.Sleep(time.Second) 413 414 intInfo2, err := GetInterfacesInfo() 415 416 if err != nil { 417 return 0, 0, err 418 } 419 420 rb1, tb1 := getActiveInterfacesBytes(intInfo1) 421 rb2, tb2 := getActiveInterfacesBytes(intInfo2) 422 423 if rb1+tb1 == 0 || rb2+tb2 == 0 { 424 return 0, 0, nil 425 } 426 427 return rb2 - rb1, tb2 - tb1, nil 428 } 429 430 // GetIOUtil return IO utilization 431 func GetIOUtil() (map[string]float64, error) { 432 result := make(map[string]float64) 433 434 fsInfoPrev, err := GetFSInfo() 435 436 if err != nil { 437 return result, err 438 } 439 440 userPrev, systemPrev, _, idlePrev, waitPrev, _, count, err := getCPUStats() 441 442 if err != nil { 443 return result, err 444 } 445 446 time.Sleep(time.Second) 447 448 fsInfoCur, err := GetFSInfo() 449 450 if err != nil { 451 return result, err 452 } 453 454 userCur, systemCur, _, idleCur, waitCur, _, _, err := getCPUStats() 455 456 if err != nil { 457 return result, err 458 } 459 460 deltams := 1000.0 * (float64(userCur+systemCur+idleCur+waitCur) - float64(userPrev+systemPrev+idlePrev+waitPrev)) / float64(count) / _Hz 461 462 for n, f := range fsInfoPrev { 463 if fsInfoPrev[n].IOStats != nil && fsInfoCur[n].IOStats != nil { 464 ticks := float64(fsInfoCur[n].IOStats.IOQueueMs - fsInfoPrev[n].IOStats.IOQueueMs) 465 util := 100.0 * ticks / deltams 466 467 if util > 100.0 { 468 util = 100.0 469 } 470 471 result[f.Device] = util 472 } 473 } 474 475 return result, nil 476 } 477 478 // ////////////////////////////////////////////////////////////////////////////////// // 479 480 func readFileContent(file string) ([]string, error) { 481 result := []string{} 482 483 content, err := ioutil.ReadFile(file) 484 485 if err != nil { 486 return result, err 487 } 488 489 result = strings.Split(string(content), "\n") 490 491 if len(result) == 0 { 492 return result, errors.New("File " + file + " is empty") 493 } 494 495 return result, nil 496 } 497 498 func cleanSlice(s []string) []string { 499 var result []string 500 501 for _, item := range s { 502 if item != "" { 503 result = append(result, item) 504 } 505 } 506 507 return result 508 } 509 510 func byteSliceToString(s [65]int8) string { 511 result := "" 512 513 for _, r := range s { 514 if r == 0 { 515 break 516 } 517 518 result += string(r) 519 } 520 521 return result 522 } 523 524 func stringSliceToUintSlice(s []string) []uint64 { 525 var result []uint64 526 527 for _, i := range s { 528 iu, _ := strconv.ParseUint(i, 10, 64) 529 result = append(result, iu) 530 } 531 532 return result 533 } 534 535 func getActiveInterfacesBytes(is map[string]*InterfaceInfo) (uint64, uint64) { 536 var ( 537 received uint64 538 transmitted uint64 539 ) 540 541 for name, info := range is { 542 if len(name) < 3 || name[0:3] != "eth" { 543 continue 544 } 545 546 if info.ReceivedBytes == 0 && info.TransmittedBytes == 0 { 547 continue 548 } 549 550 received += info.ReceivedBytes 551 transmitted += info.TransmittedBytes 552 } 553 554 return received, transmitted 555 } 556 557 func getCPUStats() (uint64, uint64, uint64, uint64, uint64, uint64, int, error) { 558 content, err := readFileContent(_PROC_CPUINFO) 559 560 if err != nil { 561 return 0, 0, 0, 0, 0, 0, 0, errors.New("Can't parse file " + _PROC_CPUINFO + ".") 562 } 563 564 if len(content) <= 1 { 565 return 0, 0, 0, 0, 0, 0, 0, err 566 } 567 568 var count int 569 570 for _, line := range content { 571 if line != "" && line[0:3] == "cpu" && line[0:4] != "cpu " { 572 count++ 573 } 574 } 575 576 cpu := strings.Replace(content[0], "cpu ", "", -1) 577 cpua := strings.Split(cpu, " ") 578 579 var user, system, nice, idle, wait, irq, srq, steal, total uint64 580 581 user, _ = strconv.ParseUint(cpua[0], 10, 64) 582 nice, _ = strconv.ParseUint(cpua[1], 10, 64) 583 system, _ = strconv.ParseUint(cpua[2], 10, 64) 584 idle, _ = strconv.ParseUint(cpua[3], 10, 64) 585 wait, _ = strconv.ParseUint(cpua[4], 10, 64) 586 irq, _ = strconv.ParseUint(cpua[5], 10, 64) 587 srq, _ = strconv.ParseUint(cpua[6], 10, 64) 588 steal, _ = strconv.ParseUint(cpua[7], 10, 64) 589 590 total = user + system + nice + idle + wait + irq + srq + steal 591 592 return user, system, nice, idle, wait, total, count, nil 593 }