This commit is contained in:
illustris
2026-03-11 15:01:20 +05:30
commit d8f4a77657
30 changed files with 3164 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
package sysfs
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
)
// SysReader abstracts /sys access for testability.
type SysReader interface {
ReadInterfaceStats(ifname string) (map[string]int64, error)
GetBlockDeviceSize(devPath string) (int64, error)
}
// RealSysReader reads from the actual /sys filesystem.
type RealSysReader struct {
SysPath string // default "/sys"
}
func NewRealSysReader() *RealSysReader {
return &RealSysReader{SysPath: "/sys"}
}
// ReadInterfaceStats reads all statistics files from /sys/class/net/{ifname}/statistics/.
func (r *RealSysReader) ReadInterfaceStats(ifname string) (map[string]int64, error) {
dir := filepath.Join(r.SysPath, "class", "net", ifname, "statistics")
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
stats := make(map[string]int64)
for _, e := range entries {
if e.IsDir() {
continue
}
data, err := os.ReadFile(filepath.Join(dir, e.Name()))
if err != nil {
continue
}
val, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
if err != nil {
continue
}
stats[e.Name()] = val
}
return stats, nil
}
// GetBlockDeviceSize returns the size in bytes of a block device.
// For symlinks (e.g., /dev/zvol/...), resolves to the real device first.
// Reads size from /sys/block/{dev}/size (in 512-byte sectors).
func (r *RealSysReader) GetBlockDeviceSize(devPath string) (int64, error) {
// Resolve symlinks
resolved, err := filepath.EvalSymlinks(devPath)
if err != nil {
return 0, fmt.Errorf("resolve symlink %s: %w", devPath, err)
}
// Extract device name from /dev/XXX
devName := filepath.Base(resolved)
// Try /sys/block/{devName}/size
sizeFile := filepath.Join(r.SysPath, "block", devName, "size")
data, err := os.ReadFile(sizeFile)
if err != nil {
// For partition devices like dm-0, try without partition suffix
return 0, fmt.Errorf("read size %s: %w", sizeFile, err)
}
sectors, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
if err != nil {
return 0, fmt.Errorf("parse size: %w", err)
}
return sectors * 512, nil
}
// GetDeviceSymlinkTarget resolves a device symlink and returns the target path.
func GetDeviceSymlinkTarget(devPath string) (string, error) {
resolved, err := filepath.EvalSymlinks(devPath)
if err != nil {
return "", err
}
return resolved, nil
}

View File

@@ -0,0 +1,69 @@
package sysfs
import (
"os"
"path/filepath"
"testing"
)
func TestReadInterfaceStats(t *testing.T) {
// Create temp sysfs-like structure
tmpDir := t.TempDir()
statsDir := filepath.Join(tmpDir, "class", "net", "tap100i0", "statistics")
os.MkdirAll(statsDir, 0755)
os.WriteFile(filepath.Join(statsDir, "rx_bytes"), []byte("123456\n"), 0644)
os.WriteFile(filepath.Join(statsDir, "tx_bytes"), []byte("789012\n"), 0644)
os.WriteFile(filepath.Join(statsDir, "rx_packets"), []byte("100\n"), 0644)
reader := &RealSysReader{SysPath: tmpDir}
stats, err := reader.ReadInterfaceStats("tap100i0")
if err != nil {
t.Fatal(err)
}
if stats["rx_bytes"] != 123456 {
t.Errorf("rx_bytes = %d", stats["rx_bytes"])
}
if stats["tx_bytes"] != 789012 {
t.Errorf("tx_bytes = %d", stats["tx_bytes"])
}
if stats["rx_packets"] != 100 {
t.Errorf("rx_packets = %d", stats["rx_packets"])
}
}
func TestReadInterfaceStats_NotFound(t *testing.T) {
reader := &RealSysReader{SysPath: t.TempDir()}
_, err := reader.ReadInterfaceStats("nonexistent")
if err == nil {
t.Fatal("expected error for nonexistent interface")
}
}
func TestGetBlockDeviceSize(t *testing.T) {
tmpDir := t.TempDir()
// Create /sys/block/dm-0/size
blockDir := filepath.Join(tmpDir, "block", "dm-0")
os.MkdirAll(blockDir, 0755)
// 1GB = 2097152 sectors of 512 bytes
os.WriteFile(filepath.Join(blockDir, "size"), []byte("2097152\n"), 0644)
// Create a "device" symlink that points to dm-0
devDir := filepath.Join(tmpDir, "dev")
os.MkdirAll(devDir, 0755)
os.Symlink(filepath.Join(devDir, "dm-0"), filepath.Join(devDir, "mydev"))
// Create the actual "device" file so symlink resolves
os.WriteFile(filepath.Join(devDir, "dm-0"), []byte{}, 0644)
reader := &RealSysReader{SysPath: tmpDir}
size, err := reader.GetBlockDeviceSize(filepath.Join(devDir, "dm-0"))
if err != nil {
t.Fatal(err)
}
expected := int64(2097152 * 512)
if size != expected {
t.Errorf("size = %d, want %d", size, expected)
}
}