From 1130aab85ec72354f3cd96247a4cfee5d4b551c8 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Wed, 19 Sep 2018 10:09:46 +0800 Subject: [PATCH] qmp: add "query-cpus" support Add "query-cpus" and "query-cpus-fast" to query CPU information from qemu Signed-off-by: Wei Zhang --- qemu/qmp.go | 72 +++++++++++++++++++++++++++++++++++++++++++++- qemu/qmp_test.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) diff --git a/qemu/qmp.go b/qemu/qmp.go index 9f764f1236..ea3e97cb10 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -144,7 +144,7 @@ type QMPVersion struct { Capabilities []string } -// CPUProperties contains the properties to be used for hotplugging a CPU instance +// CPUProperties contains the properties of a CPU instance type CPUProperties struct { Node int `json:"node-id"` Socket int `json:"socket-id"` @@ -178,6 +178,28 @@ type MemoryDevices struct { Type string `json:"type"` } +// CPUInfo represents information about each virtual CPU +type CPUInfo struct { + CPU int `json:"CPU"` + Current bool `json:"current"` + Halted bool `json:"halted"` + QomPath string `json:"qom_path"` + Arch string `json:"arch"` + Pc int `json:"pc"` + ThreadID int `json:"thread_id"` + Props CPUProperties `json:"props"` +} + +// CPUInfoFast represents information about each virtual CPU +type CPUInfoFast struct { + CPUIndex int `json:"cpu-index"` + QomPath string `json:"qom-path"` + Arch string `json:"arch"` + ThreadID int `json:"thread-id"` + Target string `json:"target"` + Props CPUProperties `json:"props"` +} + func (q *QMP) readLoop(fromVMCh chan<- []byte) { scanner := bufio.NewScanner(q.conn) for scanner.Scan() { @@ -1033,6 +1055,54 @@ func (q *QMP) ExecQueryMemoryDevices(ctx context.Context) ([]MemoryDevices, erro return memoryDevices, nil } +// ExecQueryCpus returns a slice with the list of `CpuInfo` +// Since qemu 2.12, we have `query-cpus-fast` as a better choice in production +// we can still choose `ExecQueryCpus` for compatibility though not recommended. +func (q *QMP) ExecQueryCpus(ctx context.Context) ([]CPUInfo, error) { + response, err := q.executeCommandWithResponse(ctx, "query-cpus", nil, nil, nil) + if err != nil { + return nil, err + } + + // convert response to json + data, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("Unable to extract memory devices information: %v", err) + } + + var cpuInfo []CPUInfo + // convert json to []CPUInfo + if err = json.Unmarshal(data, &cpuInfo); err != nil { + return nil, fmt.Errorf("unable to convert json to CPUInfo: %v", err) + } + + return cpuInfo, nil +} + +// ExecQueryCpusFast returns a slice with the list of `CpuInfoFast` +// This is introduced since 2.12, it does not incur a performance penalty and +// should be used in production instead of query-cpus. +func (q *QMP) ExecQueryCpusFast(ctx context.Context) ([]CPUInfoFast, error) { + response, err := q.executeCommandWithResponse(ctx, "query-cpus-fast", nil, nil, nil) + if err != nil { + return nil, err + } + + // convert response to json + data, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("Unable to extract memory devices information: %v", err) + } + + var cpuInfoFast []CPUInfoFast + // convert json to []CPUInfoFast + if err = json.Unmarshal(data, &cpuInfoFast); err != nil { + return nil, fmt.Errorf("unable to convert json to CPUInfoFast: %v", err) + } + + return cpuInfoFast, nil +} + // ExecHotplugMemory adds size of MiB memory to the guest func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int) error { args := map[string]interface{}{ diff --git a/qemu/qmp_test.go b/qemu/qmp_test.go index a1831a863b..8014cbe0c6 100644 --- a/qemu/qmp_test.go +++ b/qemu/qmp_test.go @@ -1096,6 +1096,80 @@ func TestQMPExecuteQueryMemoryDevices(t *testing.T) { <-disconnectedCh } +// Checks that cpus are listed correctly +func TestQMPExecuteQueryCpus(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + cpuInfo := CPUInfo{ + CPU: 1, + Current: false, + Halted: false, + Arch: "x86_64", + QomPath: "/tmp/testQom", + Pc: 123456, + ThreadID: 123457, + Props: CPUProperties{ + Node: 0, + Socket: 1, + Core: 1, + Thread: 1966, + }, + } + buf.AddCommand("query-cpus", nil, "return", []interface{}{cpuInfo}) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + cpus, err := q.ExecQueryCpus(context.Background()) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + if len(cpus) != 1 { + t.Fatalf("Expected memory devices length equals to 1\n") + } + if reflect.DeepEqual(cpus[0], cpuInfo) == false { + t.Fatalf("Expected %v equals to %v\n", cpus[0], cpuInfo) + } + q.Shutdown() + <-disconnectedCh +} + +// Checks that cpus are listed correctly +func TestQMPExecuteQueryCpusFast(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + cpuInfoFast := CPUInfoFast{ + CPUIndex: 1, + Arch: "x86", + Target: "x86_64", + QomPath: "/tmp/testQom", + ThreadID: 123457, + Props: CPUProperties{ + Node: 0, + Socket: 1, + Core: 1, + Thread: 1966, + }, + } + buf.AddCommand("query-cpus-fast", nil, "return", []interface{}{cpuInfoFast}) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + cpus, err := q.ExecQueryCpusFast(context.Background()) + if err != nil { + t.Fatalf("Unexpected error: %v\n", err) + } + if len(cpus) != 1 { + t.Fatalf("Expected memory devices length equals to 1\n") + } + if reflect.DeepEqual(cpus[0], cpuInfoFast) == false { + t.Fatalf("Expected %v equals to %v\n", cpus[0], cpuInfoFast) + } + q.Shutdown() + <-disconnectedCh +} + // Checks that migrate capabilities can be set func TestExecSetMigrationCaps(t *testing.T) { connectedCh := make(chan *QMPVersion)