Skip to content

Commit fec63ad

Browse files
committed
Add command to get the current memory usage
Signed-off-by: Anders F Björklund <anders.f.bjorklund@gmail.com>
1 parent e34ad35 commit fec63ad

File tree

11 files changed

+164
-3
lines changed

11 files changed

+164
-3
lines changed

cmd/limactl/memory.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ func memoryGetAction(cmd *cobra.Command, args []string) error {
5151
return err
5252
}
5353

54-
_ = inst
55-
mem := 0 // TODO: implement
54+
mem, err := memory.GetCurrent(ctx, inst)
55+
if err != nil {
56+
return err
57+
}
5658
fmt.Fprintf(cmd.OutOrStdout(), "%d\n", mem>>20)
5759
return nil
5860
}

pkg/driver/driver.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ type Driver interface {
8989

9090
SSHAddress(ctx context.Context) (string, error)
9191

92+
GetCurrentMemory() (int64, error)
93+
9294
SetTargetMemory(memory int64) error
9395
}
9496

pkg/driver/external/client/methods.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,10 @@ func (d *DriverClient) BootScripts() (map[string][]byte, error) {
324324
return resp.Scripts, nil
325325
}
326326

327+
func (d *DriverClient) GetCurrentMemory() (int64, error) {
328+
return 0, errors.New("unavailable")
329+
}
330+
327331
func (d *DriverClient) SetTargetMemory(_ int64) error {
328332
return errors.New("unavailable")
329333
}

pkg/driver/qemu/qemu_driver.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,25 @@ func (l *LimaQemuDriver) ForwardGuestAgent() bool {
722722
return l.vSockPort == 0 && l.virtioPort == ""
723723
}
724724

725+
func (l *LimaQemuDriver) GetCurrentMemory() (int64, error) {
726+
qmpSockPath := filepath.Join(l.Instance.Dir, filenames.QMPSock)
727+
qmpClient, err := qmp.NewSocketMonitor("unix", qmpSockPath, 5*time.Second)
728+
if err != nil {
729+
return 0, err
730+
}
731+
if err := qmpClient.Connect(); err != nil {
732+
return 0, err
733+
}
734+
defer func() { _ = qmpClient.Disconnect() }()
735+
rawClient := raw.NewMonitor(qmpClient)
736+
info, err := rawClient.QueryBalloon()
737+
if err != nil {
738+
return 0, err
739+
}
740+
logrus.Infof("Balloon actual size: %s", units.BytesSize(float64(info.Actual)))
741+
return info.Actual, nil
742+
}
743+
725744
func (l *LimaQemuDriver) SetTargetMemory(memory int64) error {
726745
qmpSockPath := filepath.Join(l.Instance.Dir, filenames.QMPSock)
727746
qmpClient, err := qmp.NewSocketMonitor("unix", qmpSockPath, 5*time.Second)

pkg/driver/vz/vz_driver_darwin.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,24 @@ func (l *LimaVzDriver) ForwardGuestAgent() bool {
438438
return l.vSockPort == 0 && l.virtioPort == ""
439439
}
440440

441+
func (l *LimaVzDriver) GetCurrentMemory() (int64, error) {
442+
if l.machine == nil {
443+
return 0, errors.New("no machine")
444+
}
445+
balloons := l.machine.MemoryBalloonDevices()
446+
if len(balloons) != 1 {
447+
return 0, fmt.Errorf("unexpected number of devices: %d", len(balloons))
448+
}
449+
balloon := vz.AsVirtioTraditionalMemoryBalloonDevice(balloons[0])
450+
if balloon == nil {
451+
return 0, errors.New("unexpected type of balloon")
452+
}
453+
// avoid segfault, when trying to Release
454+
runtime.SetFinalizer(balloon, nil)
455+
memory := balloon.GetTargetVirtualMachineMemorySize()
456+
return int64(memory), nil
457+
}
458+
441459
func (l *LimaVzDriver) SetTargetMemory(memory int64) error {
442460
if l.machine == nil {
443461
return errors.New("no machine")

pkg/driver/wsl2/wsl_driver_windows.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,10 @@ func (l *LimaWslDriver) ForwardGuestAgent() bool {
358358
return l.vSockPort == 0 && l.virtioPort == ""
359359
}
360360

361+
func (l *LimaWslDriver) GetCurrentMemory() (int64, error) {
362+
return 0, errUnimplemented
363+
}
364+
361365
func (l *LimaWslDriver) SetTargetMemory(memory int64) error {
362366
return errUnimplemented
363367
}

pkg/hostagent/api/client/client.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"context"
1111
"encoding/json"
1212
"fmt"
13+
"io"
1314
"net/http"
1415
"strconv"
1516
"strings"
@@ -21,6 +22,7 @@ import (
2122
type HostAgentClient interface {
2223
HTTPClient() *http.Client
2324
Info(context.Context) (*api.Info, error)
25+
GetCurrentMemory(context.Context) (int64, error)
2426
SetTargetMemory(context.Context, int64) error
2527
}
2628

@@ -69,6 +71,24 @@ func (c *client) Info(ctx context.Context) (*api.Info, error) {
6971
return &info, nil
7072
}
7173

74+
func (c *client) GetCurrentMemory(ctx context.Context) (int64, error) {
75+
u := fmt.Sprintf("http://%s/%s/memory", c.dummyHost, c.version)
76+
resp, err := httpclientutil.Get(ctx, c.HTTPClient(), u)
77+
if err != nil {
78+
return 0, err
79+
}
80+
defer resp.Body.Close()
81+
body, err := io.ReadAll(resp.Body)
82+
if err != nil {
83+
return 0, err
84+
}
85+
memory, err := strconv.ParseInt(string(body), 10, 64)
86+
if err != nil {
87+
return 0, err
88+
}
89+
return memory, nil
90+
}
91+
7292
func (c *client) SetTargetMemory(ctx context.Context, memory int64) error {
7393
u := fmt.Sprintf("http://%s/%s/memory", c.dummyHost, c.version)
7494
body := strconv.FormatInt(memory, 10)

pkg/hostagent/api/server/server.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,25 @@ func (b *Backend) GetInfo(w http.ResponseWriter, r *http.Request) {
5555
_, _ = w.Write(m)
5656
}
5757

58+
// GetMemory is the handler for GET /v1/memory.
59+
func (b *Backend) GetMemory(w http.ResponseWriter, r *http.Request) {
60+
if r.Method != http.MethodGet {
61+
w.WriteHeader(http.StatusMethodNotAllowed)
62+
return
63+
}
64+
65+
memory, err := b.Agent.GetCurrentMemory()
66+
if err != nil {
67+
b.onError(w, err, http.StatusInternalServerError)
68+
return
69+
}
70+
s := strconv.FormatInt(memory, 10)
71+
72+
w.Header().Set("Content-Type", "text/plain")
73+
w.WriteHeader(http.StatusOK)
74+
_, _ = w.Write([]byte(s))
75+
}
76+
5877
// SetMemory is the handler for PUT /v1/memory.
5978
func (b *Backend) SetMemory(w http.ResponseWriter, r *http.Request) {
6079
if r.Method != http.MethodPut {
@@ -83,5 +102,6 @@ func (b *Backend) SetMemory(w http.ResponseWriter, r *http.Request) {
83102

84103
func AddRoutes(r *http.ServeMux, b *Backend) {
85104
r.Handle("/v1/info", http.HandlerFunc(b.GetInfo))
86-
r.Handle("/v1/memory", http.HandlerFunc(b.SetMemory))
105+
r.Handle("GET /v1/memory", http.HandlerFunc(b.GetMemory))
106+
r.Handle("PUT /v1/memory", http.HandlerFunc(b.SetMemory))
87107
}

pkg/hostagent/hostagent.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,10 @@ func (a *HostAgent) Run(ctx context.Context) error {
445445
return a.startRoutinesAndWait(ctx, errCh)
446446
}
447447

448+
func (a *HostAgent) GetCurrentMemory() (int64, error) {
449+
return a.driver.GetCurrentMemory()
450+
}
451+
448452
func (a *HostAgent) SetTargetMemory(memory int64) error {
449453
return a.driver.SetTargetMemory(memory)
450454
}

pkg/memory/memory.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,82 @@ package memory
55

66
import (
77
"context"
8+
"fmt"
9+
"os/exec"
810
"path/filepath"
11+
"strconv"
12+
"strings"
913
"time"
1014

1115
hostagentclient "github.com/lima-vm/lima/v2/pkg/hostagent/api/client"
1216
"github.com/lima-vm/lima/v2/pkg/limatype"
1317
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
18+
"github.com/lima-vm/lima/v2/pkg/sshutil"
1419
"github.com/lima-vm/lima/v2/pkg/store"
1520
)
1621

22+
func GetCurrent(ctx context.Context, inst *limatype.Instance) (int64, error) {
23+
var memory int64
24+
hostAgentPID, err := store.ReadPIDFile(filepath.Join(inst.Dir, filenames.HostAgentPID))
25+
if err != nil {
26+
return 0, err
27+
}
28+
if hostAgentPID != 0 {
29+
haSock := filepath.Join(inst.Dir, filenames.HostAgentSock)
30+
haClient, err := hostagentclient.NewHostAgentClient(haSock)
31+
if err != nil {
32+
return 0, err
33+
}
34+
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
35+
defer cancel()
36+
memory, err = haClient.GetCurrentMemory(ctx)
37+
if err != nil {
38+
return 0, err
39+
}
40+
}
41+
42+
sshExe, err := sshutil.NewSSHExe()
43+
if err != nil {
44+
return 0, err
45+
}
46+
sshOpts, err := sshutil.CommonOpts(ctx, sshExe, false)
47+
if err != nil {
48+
return 0, err
49+
}
50+
sshArgs := append(sshutil.SSHArgsFromOpts(sshOpts),
51+
"-p", fmt.Sprintf("%d", inst.SSHLocalPort),
52+
fmt.Sprintf("%s@%s", *inst.Config.User.Name, inst.SSHAddress),
53+
)
54+
55+
args := []string{"cat", "/proc/meminfo"}
56+
sshCmd := exec.CommandContext(ctx, sshExe.Exe, append(sshArgs, args...)...)
57+
out, err := sshCmd.Output()
58+
if err != nil {
59+
return 0, err
60+
}
61+
62+
var available int64
63+
for _, line := range strings.Split(string(out), "\n") {
64+
if !strings.HasPrefix(line, "MemAvailable: ") {
65+
continue
66+
}
67+
fields := strings.Fields(line)
68+
if len(fields) < 3 {
69+
return 0, fmt.Errorf("unexpected line: %s", line)
70+
}
71+
num, err := strconv.ParseInt(fields[1], 10, 64)
72+
if err != nil {
73+
return 0, err
74+
}
75+
if fields[2] == "kB" {
76+
num *= 1024
77+
}
78+
available = num
79+
}
80+
81+
return memory - available, nil
82+
}
83+
1784
func SetTarget(ctx context.Context, inst *limatype.Instance, memory int64) error {
1885
hostAgentPID, err := store.ReadPIDFile(filepath.Join(inst.Dir, filenames.HostAgentPID))
1986
if err != nil {

0 commit comments

Comments
 (0)