forked from wrenn/wrenn
Migrate the entire VM layer from Firecracker to Cloud Hypervisor (CH). CH provides native snapshot/restore via its HTTP API, eliminating the need for custom UFFD handling, memfile processing, and snapshot header management that Firecracker required. Key changes: - Remove fc.go, jailer.go (FC process management) - Remove internal/uffd/ package (userfaultfd lazy page loading) - Remove snapshot/header.go, mapping.go, memfile.go (FC snapshot format) - Add ch.go (CH HTTP API client over Unix socket) - Add process.go (CH process lifecycle with unshare+netns) - Add chversion.go (CH version detection) - Refactor sandbox manager: remove UFFD socket tracking, snapshot parent/diff chaining, FC-specific balloon logic; add crash watcher - Simplify snapshot/local.go to CH's native snapshot format - Update VM config: FirecrackerBin → VMMBin, new CH-specific fields - Update envdclient, devicemapper, network for CH compatibility
97 lines
2.7 KiB
Go
97 lines
2.7 KiB
Go
package sandbox
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"syscall"
|
||
|
||
"git.omukk.dev/wrenn/wrenn/internal/envdclient"
|
||
)
|
||
|
||
// cpuStat holds raw CPU jiffies read from /proc/{pid}/stat.
|
||
type cpuStat struct {
|
||
utime uint64
|
||
stime uint64
|
||
}
|
||
|
||
// readCPUStat reads user and system CPU jiffies from /proc/{pid}/stat.
|
||
// Fields 14 (utime) and 15 (stime) are 1-indexed in the man page;
|
||
// after splitting on space, they are at indices 13 and 14.
|
||
func readCPUStat(pid int) (cpuStat, error) {
|
||
path := fmt.Sprintf("/proc/%d/stat", pid)
|
||
data, err := os.ReadFile(path)
|
||
if err != nil {
|
||
return cpuStat{}, fmt.Errorf("read stat: %w", err)
|
||
}
|
||
|
||
content := string(data)
|
||
idx := strings.LastIndex(content, ")")
|
||
if idx < 0 {
|
||
return cpuStat{}, fmt.Errorf("malformed /proc/%d/stat: no closing paren", pid)
|
||
}
|
||
fields := strings.Fields(content[idx+2:])
|
||
if len(fields) < 13 {
|
||
return cpuStat{}, fmt.Errorf("malformed /proc/%d/stat: too few fields (%d)", pid, len(fields))
|
||
}
|
||
utime, err := strconv.ParseUint(fields[11], 10, 64)
|
||
if err != nil {
|
||
return cpuStat{}, fmt.Errorf("parse utime: %w", err)
|
||
}
|
||
stime, err := strconv.ParseUint(fields[12], 10, 64)
|
||
if err != nil {
|
||
return cpuStat{}, fmt.Errorf("parse stime: %w", err)
|
||
}
|
||
return cpuStat{utime: utime, stime: stime}, nil
|
||
}
|
||
|
||
// readEnvdMemUsed fetches mem_used from envd's /metrics endpoint. Returns
|
||
// guest-side total - MemAvailable (actual process memory, excluding reclaimable
|
||
// page cache). VmRSS of the VMM process includes guest page cache and
|
||
// never decreases, so this is the accurate metric for dashboard display.
|
||
func readEnvdMemUsed(ctx context.Context, client *envdclient.Client) (int64, error) {
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, client.BaseURL()+"/metrics", nil)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("build metrics request: %w", err)
|
||
}
|
||
|
||
resp, err := client.HTTPClient().Do(req)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("fetch envd metrics: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != 200 {
|
||
return 0, fmt.Errorf("envd metrics: status %d", resp.StatusCode)
|
||
}
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("read envd metrics body: %w", err)
|
||
}
|
||
|
||
var m struct {
|
||
MemUsed int64 `json:"mem_used"`
|
||
}
|
||
if err := json.Unmarshal(body, &m); err != nil {
|
||
return 0, fmt.Errorf("decode envd metrics: %w", err)
|
||
}
|
||
|
||
return m.MemUsed, nil
|
||
}
|
||
|
||
// readDiskAllocated returns the actual allocated bytes (not apparent size)
|
||
// of the file at path. This uses stat's block count × 512.
|
||
func readDiskAllocated(path string) (int64, error) {
|
||
var stat syscall.Stat_t
|
||
if err := syscall.Stat(path, &stat); err != nil {
|
||
return 0, fmt.Errorf("stat %s: %w", path, err)
|
||
}
|
||
return stat.Blocks * 512, nil
|
||
}
|