Skip to content

Commit

Permalink
iostat, pressure: QEMU support
Browse files Browse the repository at this point in the history
Cleanup qemu-related code to cgroup/fs.go, and add qemu support for iostat
  • Loading branch information
taoky committed Mar 2, 2024
1 parent 31ddcb6 commit 02a9b7d
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 55 deletions.
35 changes: 22 additions & 13 deletions cmd/iostat/iostat.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ func (l IOSingle) Diff(r IOSingle) IOSingle {

type IOStat map[string]IOSingle

func GetIOStat(id string) (IOStat, error) {
f, err := cgroup.OpenLXC(id, "io.stat")
func GetIOStat(vmid cgroup.VMID) (IOStat, error) {
f, err := cgroup.OpenVM(vmid, "io.stat")
if err != nil {
return nil, fmt.Errorf("open io.stat for %s: %w", id, err)
return nil, fmt.Errorf("open io.stat for %s: %w", vmid, err)
}
defer f.Close()
stats := make(IOStat)
Expand Down Expand Up @@ -94,27 +94,36 @@ func iostatMain(diskPath string) error {
major, minor := util.GetDeviceNumbers(stat.Rdev)
matchString := fmt.Sprintf("%d:%d", major, minor)

cachedStats := make(map[string]IOSingle)
// Enable IO subtree for qemu, if exists
if err := cgroup.EnableIOForQemu(); err != nil {
if os.IsNotExist(err) {
log.Println("qemu.slice not found (not running any KVM), skipping")
} else {
log.Printf("EnableIOForQemu error: %v", err)
}
}

cachedStats := make(map[cgroup.VMID]IOSingle)
for t := range time.NewTicker(1 * time.Second).C {
ids, err := cgroup.ListLXC()
vmids, err := cgroup.ListVM()
if err != nil {
log.Fatal(err)
}
lines := []string{"ID | Rios | Wios | Rbytes | Wbytes"}
newStats := make(map[string]IOSingle)
for _, id := range ids {
stats, err := GetIOStat(id)
lines := []string{"ID | Type | Rios | Wios | Rbytes | Wbytes"}
newStats := make(map[cgroup.VMID]IOSingle)
for _, vmid := range vmids {
stats, err := GetIOStat(vmid)
if err != nil {
log.Printf("GetIOStat error for %s: %v", id, err)
log.Printf("GetIOStat error for %s: %v", vmid, err)
continue
}
stat := stats[matchString]
newStats[id] = stat
oldStat, ok := cachedStats[id]
newStats[vmid] = stat
oldStat, ok := cachedStats[vmid]
if ok {
diff := stat.Diff(oldStat)
if !diff.Zero() {
line := fmt.Sprintf("%s | %d | %d | %s | %s", id,
line := fmt.Sprintf("%s | %s | %d | %d | %s | %s", vmid.Id, vmid.Type,
diff.Rios, diff.Wios, util.FormatSize(diff.Rbytes), util.FormatSize(diff.Wbytes))
lines = append(lines, line)
}
Expand Down
54 changes: 16 additions & 38 deletions cmd/pressure/pressure.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bufio"
"fmt"
"log"
"os"
"sort"
"strings"

Expand All @@ -19,11 +18,6 @@ const (
MEMORY = "memory.pressure"
)

const (
LXC = "LXC"
QEMU = "Qemu"
)

const pressureLineFormat = "avg10=%f avg60=%f avg300=%f total=%d"

type PSILine struct {
Expand All @@ -38,16 +32,10 @@ type PSIStats struct {
Full *PSILine
}

func GetPressure(id, typ string, filename string) (*PSIStats, error) {
var f *os.File
var err error
if typ == LXC {
f, err = cgroup.OpenLXC(id, filename)
} else {
f, err = cgroup.OpenQemu(id, filename)
}
func GetPressure(vmid cgroup.VMID, filename string) (*PSIStats, error) {
f, err := cgroup.OpenVM(vmid, filename)
if err != nil {
return nil, fmt.Errorf("open %s for %s (%s): %w", filename, id, typ, err)
return nil, fmt.Errorf("open %s for %s: %w", filename, vmid, err)
}
defer f.Close()
stats := &PSIStats{
Expand Down Expand Up @@ -75,37 +63,27 @@ func GetPressure(id, typ string, filename string) (*PSIStats, error) {
return stats, nil
}

type idAndPressure struct {
id string
typ string
type vmidAndPressure struct {
vmid cgroup.VMID
pressure *PSIStats
}

func listPressures(filename string, topN int) error {
lxcIds, err := cgroup.ListLXC()
if err != nil {
log.Printf("ListLXC error (may not have LXC?): %v", err)
lxcIds = []string{}
}
qemuIds, err := cgroup.ListQemu()
vmids, err := cgroup.ListVM()
if err != nil {
log.Printf("ListQemu error (may not have Qemu?): %v", err)
qemuIds = []string{}
return err
}

pressures := make([]idAndPressure, 0, len(lxcIds)+len(qemuIds))
appendToPressure := func(ids []string, typ string) {
for _, id := range ids {
pressure, err := GetPressure(id, typ, filename)
if err != nil {
log.Printf("GetPressure error for %s: %v", id, err)
continue
}
pressures = append(pressures, idAndPressure{id, typ, pressure})
pressures := make([]vmidAndPressure, 0, len(vmids))
for _, vmid := range vmids {
pressure, err := GetPressure(vmid, filename)
if err != nil {
log.Printf("GetPressure error for %s: %v", vmid, err)
continue
}
pressures = append(pressures, vmidAndPressure{vmid, pressure})
}
appendToPressure(lxcIds, LXC)
appendToPressure(qemuIds, QEMU)

sort.Slice(pressures, func(i, j int) bool {
return pressures[i].pressure.Some.Avg10 > pressures[j].pressure.Some.Avg10
})
Expand All @@ -115,7 +93,7 @@ func listPressures(filename string, topN int) error {
if i >= topN {
break
}
line := fmt.Sprintf("%s | %s | %.1f | %.1f | %.1f", p.id, p.typ, p.pressure.Some.Avg10, p.pressure.Some.Avg60, p.pressure.Some.Avg300)
line := fmt.Sprintf("%s | %s | %.1f | %.1f | %.1f", p.vmid.Id, p.vmid.Type, p.pressure.Some.Avg10, p.pressure.Some.Avg60, p.pressure.Some.Avg300)
lines = append(lines, line)
}
fmt.Printf("Top %d containers/VMs with %s\n", topN, filename)
Expand Down
74 changes: 70 additions & 4 deletions pkg/cgroup/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,29 @@ import (

const BaseDir = "/sys/fs/cgroup"

func Open(filename string) (*os.File, error) {
return os.Open(filepath.Join(BaseDir, filename))
type VMID struct {
Id string
Type string
}

func (v VMID) String() string {
return fmt.Sprintf("%s (%s)", v.Id, v.Type)
}

const (
LXC = "LXC"
QEMU = "Qemu"
)

func OpenVM(vmid VMID, filename string) (*os.File, error) {
switch vmid.Type {
case LXC:
return OpenLXC(vmid.Id, filename)
case QEMU:
return OpenQemu(vmid.Id, filename)
default:
return nil, fmt.Errorf("unknown vm type %s", vmid.Type)
}
}

func OpenLXC(id string, filename string) (*os.File, error) {
Expand All @@ -21,6 +42,17 @@ func OpenQemu(id string, filename string) (*os.File, error) {
return os.Open(GetFilenameQemu(id, filename))
}

func GetFilenameVM(vmid VMID, filename string) string {
switch vmid.Type {
case LXC:
return GetFilenameLXC(vmid.Id, filename)
case QEMU:
return GetFilenameQemu(vmid.Id, filename)
default:
panic("unknown vm type " + vmid.Type)
}
}

func GetFilenameLXC(id string, filename string) string {
return filepath.Join(BaseDir, "lxc", id, filename)
}
Expand All @@ -29,9 +61,41 @@ func GetFilenameQemu(id string, filename string) string {
return filepath.Join(BaseDir, "qemu.slice", id+".scope", filename)
}

func EnableIOForQemu() error {
subtreeControlFilename := filepath.Join(BaseDir, "qemu.slice", "cgroup.subtree_control")
subtreeControl, err := os.OpenFile(subtreeControlFilename, os.O_WRONLY, 0)
if err != nil {
return err
}
defer subtreeControl.Close()
_, err = subtreeControl.WriteString("+io")
return err
}

func ListVM() ([]VMID, error) {
lxc, err := ListLXC()
if err != nil {
return nil, err
}
qemu, err := ListQemu()
if err != nil {
return nil, err
}
ids := make([]VMID, 0, len(lxc)+len(qemu))
for _, id := range lxc {
ids = append(ids, VMID{id, LXC})
}
for _, id := range qemu {
ids = append(ids, VMID{id, QEMU})
}
return ids, nil
}

func ListLXC() ([]string, error) {
entries, err := os.ReadDir(filepath.Join(BaseDir, "lxc"))
if err != nil {
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, err
}
ids := make([]string, 0, len(entries))
Expand All @@ -45,7 +109,9 @@ func ListLXC() ([]string, error) {

func ListQemu() ([]string, error) {
entries, err := os.ReadDir(filepath.Join(BaseDir, "qemu.slice"))
if err != nil {
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, err
}
ids := make([]string, 0, len(entries))
Expand Down

0 comments on commit 02a9b7d

Please sign in to comment.