allow monitoring remote mounts (#178) and handle I/O edge case (#183)

This commit is contained in:
Henry Dollman
2024-09-26 18:01:52 -04:00
parent f96f04f876
commit cec9339f6d
3 changed files with 101 additions and 79 deletions

View File

@@ -385,7 +385,6 @@ func (a *Agent) Run() {
} }
a.initializeDiskInfo() a.initializeDiskInfo()
a.initializeDiskIoStats()
a.initializeNetIoStats() a.initializeNetIoStats()
a.startServer() a.startServer()

View File

@@ -13,105 +13,131 @@ import (
"github.com/shirou/gopsutil/v4/disk" "github.com/shirou/gopsutil/v4/disk"
) )
// problem: device is in partitions, but not in io counters
// solution: if filesystem exists, always use for io counters, even if root is
// Sets up the filesystems to monitor for disk usage and I/O. // Sets up the filesystems to monitor for disk usage and I/O.
func (a *Agent) initializeDiskInfo() error { func (a *Agent) initializeDiskInfo() {
filesystem := os.Getenv("FILESYSTEM") filesystem := os.Getenv("FILESYSTEM")
efPath := "/extra-filesystems"
hasRoot := false hasRoot := false
// add values from EXTRA_FILESYSTEMS env var to fsStats
if extraFilesystems, exists := os.LookupEnv("EXTRA_FILESYSTEMS"); exists {
for _, filesystem := range strings.Split(extraFilesystems, ",") {
a.fsStats[filepath.Base(filesystem)] = &system.FsStats{}
}
}
partitions, err := disk.Partitions(false) partitions, err := disk.Partitions(false)
if err != nil { if err != nil {
return err log.Println("Error getting disk partitions:", err.Error())
} }
// if FILESYSTEM env var is set, use it to find root filesystem // ioContext := context.WithValue(a.sensorsContext,
// common.EnvKey, common.EnvMap{common.HostProcEnvKey: "/tmp/testproc"},
// )
// diskIoCounters, err := disk.IOCountersWithContext(ioContext)
diskIoCounters, err := disk.IOCounters()
if err != nil {
log.Println("Error getting diskstats:", err.Error())
}
// Helper function to add a filesystem to fsStats if it doesn't exist
addFsStat := func(device, mountpoint string, root bool) {
key := filepath.Base(device)
if _, exists := a.fsStats[key]; !exists {
if root {
log.Println("Detected root fs:", key)
// check if root device is in /proc/diskstats, use fallback if not
if _, exists := diskIoCounters[key]; !exists {
log.Printf("%s not found in diskstats\n", key)
key = findFallbackIoDevice(filesystem, diskIoCounters)
log.Printf("Using %s for I/O\n", key)
}
}
a.fsStats[key] = &system.FsStats{Root: root, Mountpoint: mountpoint}
}
}
// Use FILESYSTEM env var to find root filesystem
if filesystem != "" { if filesystem != "" {
for _, v := range partitions { for _, p := range partitions {
// use filesystem env var if matching partition is found if strings.HasSuffix(p.Device, filesystem) || p.Mountpoint == filesystem {
if strings.HasSuffix(v.Device, filesystem) || v.Mountpoint == filesystem { addFsStat(p.Device, p.Mountpoint, true)
a.fsStats[filepath.Base(v.Device)] = &system.FsStats{Root: true, Mountpoint: v.Mountpoint}
hasRoot = true hasRoot = true
break break
} }
} }
if !hasRoot { if !hasRoot {
// if no match, log available partition details log.Printf("Partition details not found for %s\n", filesystem)
log.Printf("Partition details not found for %s:\n", filesystem) for _, p := range partitions {
for _, v := range partitions { fmt.Printf("%+v\n", p)
fmt.Printf("%+v\n", v)
} }
} }
} }
for _, v := range partitions { // Add EXTRA_FILESYSTEMS env var values to fsStats
// binary root fallback - use root mountpoint if extraFilesystems, exists := os.LookupEnv("EXTRA_FILESYSTEMS"); exists {
if !hasRoot && v.Mountpoint == "/" { for _, fs := range strings.Split(extraFilesystems, ",") {
a.fsStats[filepath.Base(v.Device)] = &system.FsStats{Root: true, Mountpoint: "/"} found := false
for _, p := range partitions {
if strings.HasSuffix(p.Device, fs) || p.Mountpoint == fs {
addFsStat(p.Device, p.Mountpoint, false)
found = true
break
}
}
// if not in partitions, test if we can get disk usage
if !found {
if _, err := disk.Usage(fs); err == nil {
addFsStat(filepath.Base(fs), fs, false)
} else {
log.Println(err, fs)
}
}
}
}
// Process partitions for various mount points
for _, p := range partitions {
// fmt.Println(p.Device, p.Mountpoint)
// Binary root fallback or docker root fallback
if !hasRoot && (p.Mountpoint == "/" || (p.Mountpoint == "/etc/hosts" && strings.HasPrefix(p.Device, "/dev") && !strings.Contains(p.Device, "mapper"))) {
addFsStat(p.Device, "/", true)
hasRoot = true hasRoot = true
} }
// docker root fallback - use /etc/hosts device if not mapped
if !hasRoot && v.Mountpoint == "/etc/hosts" && strings.HasPrefix(v.Device, "/dev") && !strings.Contains(v.Device, "mapper") { // Check if device is in /extra-filesystems
a.fsStats[filepath.Base(v.Device)] = &system.FsStats{Root: true, Mountpoint: "/"} if strings.HasPrefix(p.Mountpoint, efPath) {
hasRoot = true addFsStat(p.Device, p.Mountpoint, false)
} }
// check if device is in /extra-filesystem }
if strings.HasPrefix(v.Mountpoint, "/extra-filesystem") {
// add to fsStats if not already there // Check all folders in /extra-filesystems and add them if not already present
if _, exists := a.fsStats[filepath.Base(v.Device)]; !exists { if folders, err := os.ReadDir(efPath); err == nil {
a.fsStats[filepath.Base(v.Device)] = &system.FsStats{Mountpoint: v.Mountpoint} // log.Printf("Found %d extra filesystems in %s\n", len(folders), efPath)
} existingMountpoints := make(map[string]bool)
continue for _, stats := range a.fsStats {
existingMountpoints[stats.Mountpoint] = true
} }
// set mountpoints for extra filesystems if passed in via env var for _, folder := range folders {
for name, stats := range a.fsStats { if folder.IsDir() {
if strings.HasSuffix(v.Device, name) { mountpoint := filepath.Join(efPath, folder.Name())
stats.Mountpoint = v.Mountpoint if !existingMountpoints[mountpoint] {
break a.fsStats[folder.Name()] = &system.FsStats{Mountpoint: mountpoint}
}
} }
} }
} }
// remove extra filesystems that don't have a mountpoint // If no root filesystem set, use fallback
for name, stats := range a.fsStats {
if stats.Root {
log.Println("Detected root fs:", name)
}
if stats.Mountpoint == "" {
log.Printf("Ignoring %s. No mountpoint found.\n", name)
delete(a.fsStats, name)
}
}
// if no root filesystem set, use most read device in /proc/diskstats
if !hasRoot { if !hasRoot {
rootDevice := findFallbackIoDevice(filepath.Base(filesystem)) rootDevice := findFallbackIoDevice(filepath.Base(filesystem), diskIoCounters)
log.Printf("Using / as mountpoint and %s for I/O\n", rootDevice) log.Printf("Using / as mountpoint and %s for I/O\n", rootDevice)
a.fsStats[rootDevice] = &system.FsStats{Root: true, Mountpoint: "/"} a.fsStats[rootDevice] = &system.FsStats{Root: true, Mountpoint: "/"}
} }
return nil a.initializeDiskIoStats(diskIoCounters)
} }
// Returns the device with the most reads in /proc/diskstats, // Returns the device with the most reads in /proc/diskstats,
// or the device specified by the filesystem argument if it exists // or the device specified by the filesystem argument if it exists
// (fallback in case the root device is not supplied or detected) func findFallbackIoDevice(filesystem string, diskIoCounters map[string]disk.IOCountersStat) string {
func findFallbackIoDevice(filesystem string) string {
var maxReadBytes uint64 var maxReadBytes uint64
maxReadDevice := "/" maxReadDevice := "/"
counters, err := disk.IOCounters() for _, d := range diskIoCounters {
if err != nil {
return maxReadDevice
}
for _, d := range counters {
if d.Name == filesystem { if d.Name == filesystem {
return d.Name return d.Name
} }
@@ -124,21 +150,19 @@ func findFallbackIoDevice(filesystem string) string {
} }
// Sets start values for disk I/O stats. // Sets start values for disk I/O stats.
func (a *Agent) initializeDiskIoStats() { func (a *Agent) initializeDiskIoStats(diskIoCounters map[string]disk.IOCountersStat) {
// create slice of fs names to pass to disk.IOCounters for device, stats := range a.fsStats {
a.fsNames = make([]string, 0, len(a.fsStats)) // skip if not in diskIoCounters
for name := range a.fsStats { d, exists := diskIoCounters[device]
a.fsNames = append(a.fsNames, name) if !exists {
} log.Println(device, "not found in diskstats")
continue
if ioCounters, err := disk.IOCounters(a.fsNames...); err == nil {
for _, d := range ioCounters {
if a.fsStats[d.Name] == nil {
continue
}
a.fsStats[d.Name].Time = time.Now()
a.fsStats[d.Name].TotalRead = d.ReadBytes
a.fsStats[d.Name].TotalWrite = d.WriteBytes
} }
// populate initial values
stats.Time = time.Now()
stats.TotalRead = d.ReadBytes
stats.TotalWrite = d.WriteBytes
// add to list of valid io device names
a.fsNames = append(a.fsNames, device)
} }
} }

View File

@@ -26,7 +26,6 @@ type Stats struct {
type FsStats struct { type FsStats struct {
Time time.Time `json:"-"` Time time.Time `json:"-"`
Device string `json:"-"`
Root bool `json:"-"` Root bool `json:"-"`
Mountpoint string `json:"-"` Mountpoint string `json:"-"`
DiskTotal float64 `json:"d"` DiskTotal float64 `json:"d"`