//go:build !windows // +build !windows /* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ package vfsfs import ( "bufio" "io" "os" "strings" "golang.org/x/sys/unix" "golang.zabbix.com/sdk/errs" "golang.zabbix.com/sdk/plugin" ) func init() { err := plugin.RegisterMetrics( &impl, "VfsFs", "vfs.fs.discovery", "List of mounted filesystems. Used for low-level discovery.", "vfs.fs.get", "List of mounted filesystems with statistics.", "vfs.fs.size", "Disk space in bytes or in percentage from total.", "vfs.fs.inode", "Disk space in bytes or in percentage from total.", ) if err != nil { panic(errs.Wrap(err, "failed to register metrics")) } } func (p *Plugin) getFsInfoStats() (data []*FsInfoNew, err error) { allData, err := p.getFsInfo() if err != nil { return nil, err } fsmap := make(map[string]*FsInfoNew) fsStatCaller := p.newFSCaller(getFsStats, len(allData)) fsInodeCaller := p.newFSCaller(getFsInode, len(allData)) for _, info := range allData { bytes, err := fsStatCaller.run(*info.FsName) if err != nil { p.Debugf(`cannot discern stats for the mount %s: %s`, *info.FsName, err.Error()) continue } inodes, err := fsInodeCaller.run(*info.FsName) if err != nil { p.Debugf(`cannot discern inode for the mount %s: %s`, *info.FsName, err.Error()) continue } fsmap[*info.FsName+*info.FsType] = &FsInfoNew{info.FsName, info.FsType, nil, nil, bytes, inodes, info.FsOptions} } allData, err = p.getFsInfo() if err != nil { return nil, err } for _, info := range allData { if fsInfo, ok := fsmap[*info.FsName+*info.FsType]; ok { data = append(data, fsInfo) } } return } func (p *Plugin) readMounts(file io.Reader) (data []*FsInfo, err error) { scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() mnt := strings.Split(line, " ") if len(mnt) < 4 { p.Debugf(`cannot discern the mount in given line: %s`, line) continue } data = append(data, &FsInfo{FsName: &mnt[1], FsType: &mnt[2], FsOptions: &mnt[3]}) } if err = scanner.Err(); err != nil { return nil, err } return } func (p *Plugin) getFsInfo() (data []*FsInfo, err error) { file, err := os.Open("/proc/mounts") if err != nil { return nil, err } defer file.Close() data, err = p.readMounts(file) if err != nil { return nil, err } return data, nil } func getFsStats(path string) (stats *FsStats, err error) { var pused float64 fs := unix.Statfs_t{} err = unix.Statfs(path, &fs) if err != nil { return nil, err } var available uint64 if fs.Bavail > 0 { available = fs.Bavail } total := fs.Blocks * uint64(fs.Bsize) free := available * uint64(fs.Bsize) used := (fs.Blocks - fs.Bfree) * uint64(fs.Bsize) pfree := float64(fs.Blocks - fs.Bfree + fs.Bavail) if pfree > 0 { pfree = 100.00 * float64(available) / pfree pused = 100 - pfree } else { pfree = 0 pused = 0 } stats = &FsStats{ Total: total, Free: free, Used: used, PFree: pfree, PUsed: pused, } return } func getFsInode(path string) (stats *FsStats, err error) { var pfree, pused float64 fs := unix.Statfs_t{} err = unix.Statfs(path, &fs) if err != nil { return nil, err } total := fs.Files free := fs.Ffree used := fs.Files - fs.Ffree if 0 < total { pfree = 100 * float64(free) / float64(total) pused = 100 * float64(total-free) / float64(total) } else { pfree = 100.0 pused = 0.0 } stats = &FsStats{ Total: total, Free: free, Used: used, PFree: pfree, PUsed: pused, } return }