Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cmd/lima-guestagent/daemon_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/lima-vm/lima/v2/pkg/guestagent"
"github.com/lima-vm/lima/v2/pkg/guestagent/api/server"
"github.com/lima-vm/lima/v2/pkg/guestagent/metrics"
"github.com/lima-vm/lima/v2/pkg/guestagent/serialport"
"github.com/lima-vm/lima/v2/pkg/guestagent/ticker"
"github.com/lima-vm/lima/v2/pkg/portfwdserver"
Expand Down Expand Up @@ -145,5 +146,6 @@ func daemonAction(cmd *cobra.Command, _ []string) error {
logrus.Infof("serving the guest agent on %q", socket)
}
defer logrus.Debug("exiting lima-guestagent daemon")
return server.StartServer(ctx, l, &server.GuestServer{Agent: agent, TunnelS: portfwdserver.NewTunnelServer()})
collector := metrics.NewCollector()
return server.StartServer(ctx, l, &server.GuestServer{Agent: agent, TunnelS: portfwdserver.NewTunnelServer(), Collector: collector})
}
6 changes: 6 additions & 0 deletions pkg/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ type GuestAgent interface {
GuestAgentConn(_ context.Context) (net.Conn, string, error)
}

// Ballooner is an optional interface for drivers that support memory ballooning.
// Use type assertion to check if a driver implements this interface.
type Ballooner interface {
SetBalloonTarget(targetBytes uint64) error
}

// Driver interface is used by hostagent for managing vm.
type Driver interface {
Lifecycle
Expand Down
14 changes: 12 additions & 2 deletions pkg/driver/vz/vm_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ const diskImageCachingMode = vz.DiskImageCachingModeCached

type virtualMachineWrapper struct {
*vz.VirtualMachine
mu sync.Mutex
stopped bool
mu sync.Mutex
stopped bool
balloonDevice *vz.VirtioTraditionalMemoryBalloonDevice
}

// Hold all *os.File created via socketpair() so that they won't get garbage collected. f.FD() gets invalid if f gets garbage collected.
Expand All @@ -72,6 +73,15 @@ func startVM(ctx context.Context, inst *limatype.Instance, sshLocalPort int, onV
}

wrapper := &virtualMachineWrapper{VirtualMachine: machine, stopped: false}

// Capture the balloon device reference for runtime memory control.
for _, dev := range machine.MemoryBalloonDevices() {
if bd := vz.AsVirtioTraditionalMemoryBalloonDevice(dev); bd != nil {
wrapper.balloonDevice = bd
break
}
}

notifySSHLocalPortAccessible := make(chan any)
sendErrCh := make(chan error)

Expand Down
41 changes: 41 additions & 0 deletions pkg/driver/vz/vz_driver_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,32 @@ func (l *LimaVzDriver) FillConfig(ctx context.Context, cfg *limatype.LimaYAML, _
}
}

// Memory balloon defaults — only set when enabled.
if vzOpts.MemoryBalloon.Enabled == nil {
vzOpts.MemoryBalloon.Enabled = ptr.Of(false)
}
if *vzOpts.MemoryBalloon.Enabled {
if vzOpts.MemoryBalloon.Min == nil {
// 25% of configured memory.
if cfg.Memory != nil {
if memBytes, err := units.RAMInBytes(*cfg.Memory); err == nil {
vzOpts.MemoryBalloon.Min = ptr.Of(units.BytesSize(float64(memBytes) * 0.25))
}
}
}
if vzOpts.MemoryBalloon.IdleTarget == nil {
// 33% of configured memory.
if cfg.Memory != nil {
if memBytes, err := units.RAMInBytes(*cfg.Memory); err == nil {
vzOpts.MemoryBalloon.IdleTarget = ptr.Of(units.BytesSize(float64(memBytes) * 0.33))
}
}
}
if vzOpts.MemoryBalloon.Cooldown == nil {
vzOpts.MemoryBalloon.Cooldown = ptr.Of("30s")
}
}

var opts any
if err := limayaml.Convert(vzOpts, &opts, ""); err != nil {
logrus.WithError(err).Warnf("Couldn't convert %+v", vzOpts)
Expand Down Expand Up @@ -536,6 +562,21 @@ func (l *LimaVzDriver) GuestAgentConn(_ context.Context) (net.Conn, string, erro
return nil, "", errors.New("unable to connect to guest agent via vsock port 2222")
}

// SetBalloonTarget adjusts the balloon device to set the target memory size in bytes.
// The balloon inflates or deflates to control memory available to the guest.
func (l *LimaVzDriver) SetBalloonTarget(targetBytes uint64) error {
if l.machine == nil {
return errors.New("vz: VM is not running")
}
l.machine.mu.Lock()
defer l.machine.mu.Unlock()
if l.machine.balloonDevice == nil {
return errors.New("vz: no balloon device available")
}
l.machine.balloonDevice.SetTargetVirtualMachineMemorySize(targetBytes)
return nil
}

func (l *LimaVzDriver) Info() driver.Info {
var info driver.Info

Expand Down
5 changes: 5 additions & 0 deletions pkg/guestagent/api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,8 @@ func (c *GuestAgentClient) SyncTime(ctx context.Context, hostTime time.Time) (*a
}
return c.cli.SyncTime(ctx, req)
}

// GetMemoryMetrics retrieves guest memory statistics for the balloon controller.
func (c *GuestAgentClient) GetMemoryMetrics(ctx context.Context) (*api.MemoryMetrics, error) {
return c.cli.GetMemoryMetrics(ctx, &emptypb.Empty{})
}
26 changes: 23 additions & 3 deletions pkg/guestagent/api/guestservice.pb.desc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

ã
©
guestservice.protogoogle/protobuf/empty.protogoogle/protobuf/timestamp.proto"0
Info(
local_ports ( 2.IPPortR
Expand Down Expand Up @@ -29,10 +29,30 @@ guest_addr ( R guestAddr&
TimeSyncResponse
adjusted (Radjusted
drift_ms (RdriftMs
error ( Rerror2ù
error ( Rerror"‡
MemoryMetrics&
mem_total_bytes (RmemTotalBytes.
mem_available_bytes (RmemAvailableBytes(
mem_cached_bytes (RmemCachedBytes(
swap_total_bytes (RswapTotalBytes&
swap_free_bytes (RswapFreeBytes$
anon_rss_bytes (R anonRssBytes+
psi_memory_some_10 (RpsiMemorySome10+
psi_memory_full_10 (RpsiMemoryFull100
swap_in_bytes_per_sec (RswapInBytesPerSec2
swap_out_bytes_per_sec
(RswapOutBytesPerSec&
page_fault_rate (RpageFaultRate'
container_count (RcontainerCount2
container_cpu_percent (RcontainerCpuPercent:
container_io_bytes_per_sec (RcontainerIoBytesPerSec!
oom_detected (R oomDetected+
psi_memory_some_60 (RpsiMemorySome60+
psi_memory_full_60 (RpsiMemoryFull602µ
GuestService(
GetInfo.google.protobuf.Empty.Info-
GetEvents.google.protobuf.Empty.Event01
PostInotify.Inotify.google.protobuf.Empty(,
Tunnel.TunnelMessage.TunnelMessage(0/
SyncTime.TimeSyncRequest.TimeSyncResponseB/Z-github.com/lima-vm/lima/v2/pkg/guestagent/apibproto3
SyncTime.TimeSyncRequest.TimeSyncResponse:
GetMemoryMetrics.google.protobuf.Empty.MemoryMetricsB/Z-github.com/lima-vm/lima/v2/pkg/guestagent/apibproto3
Expand Down
Loading
Loading