[pve-devel] [PATCH v3 qemu-server 04/12] Add QEMU CPU flag querying helpers

Stefan Reiter s.reiter at proxmox.com
Tue Oct 15 16:12:26 CEST 2019


* query_understood_cpu_flags returns all flags that QEMU/KVM knows about
* query_supported_cpu_flags returns all flags that QEMU/KVM can use on
  this particular host.

To get supported flags, a temporary VM is started with QEMU, so we can
issue the "query-cpu-model-expansion" QMP command. This is how libvirt
queries supported flags for its "host-passthrough" CPU type.
query_supported_cpu_flags is thus rather slow and shouldn't be called
unnecessarily.

Note that KVM and TCG accelerators provide different expansions for the
"host" CPU type, so we need to query both.

Currently only supports x86_64, because QEMU-aarch64 doesn't provide the
necessary querying functions.

Signed-off-by: Stefan Reiter <s.reiter at proxmox.com>
---

query_understood_cpu_flags is currently not used, but will be very useful for
the later UI part. I think it thematically fits best with this patch though.

v2 -> v3:
* support tcg accelerator as well as KVM (only query TCG on hosts without KVM)
* change query_understood_cpu_flags to read the static file generated in 02/11
* simplify s/\.|-/_/g and dedup code
* $vmid -> $fakevmid
* better comments

v1 -> v2:
* Change order of functions and add a single, more useful comment on usage
* Do s/\.|-/_/g directly in query_understood_cpu_flags, since the other format
  is useless anyway
* Add "die" and FIXME for aarch64, since it doesn't support querying atm
  (still, use get_host_arch()/get_basic_machine_info() for now, so once QEMU
  supports it, we theoretically just have to remove the "die")
* Do QMP in extra eval, so we don't die before calling vm_stop


 PVE/QemuServer.pm | 117 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 116 insertions(+), 1 deletion(-)

diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index 8376260..4447afc 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -24,7 +24,7 @@ use Storable qw(dclone);
 use MIME::Base64;
 use PVE::Exception qw(raise raise_param_exc);
 use PVE::Storage;
-use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach $IPV6RE);
+use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline file_get_contents dir_glob_foreach $IPV6RE);
 use PVE::JSONSchema qw(get_standard_option);
 use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
 use PVE::INotify;
@@ -3534,6 +3534,121 @@ sub get_command_for_arch($) {
     return $cmd;
 }
 
+# To use query_supported_cpu_flags and query_understood_cpu_flags to get flags
+# to use in a QEMU command line (-cpu element), first array_intersect the result
+# of query_supported_ with query_understood_. This is necessary because:
+#
+# a) query_understood_ returns flags the host cannot use and
+# b) query_supported_ (rather the QMP call) doesn't actually return CPU
+#    flags, but CPU settings - with most of them being flags. Those settings
+#    (and some flags, curiously) cannot be specified as a "-cpu" argument.
+#
+# query_supported_ needs to start up to 2 temporary VMs and is therefore rather
+# expensive. If you need the value returned from this, you can get it much
+# cheaper from pmxcfs using PVE::Cluster::get_node_kv('cpuflags').
+#
+# pvestatd calls this function on startup and whenever the QEMU/KVM version
+# changes, automatically populating pmxcfs.
+#
+# Returns: { kvm => [ flagX, flagY, ... ], tcg => [ flag1, flag2, ... ] }
+# since kvm and tcg machines support different flags
+#
+sub query_supported_cpu_flags {
+    my $flags = {};
+
+    my ($arch, $default_machine) = get_basic_machine_info();
+
+    # FIXME: Once this is merged, the code below should work for ARM as well:
+    # https://lists.nongnu.org/archive/html/qemu-devel/2019-06/msg04947.html
+    die "QEMU/KVM cannot detect CPU flags on ARM (aarch64)\n" if
+	$arch eq "aarch64";
+
+    my $kvm_supported = defined(kvm_version());
+    my $qemu_cmd = get_command_for_arch($arch);
+    my $fakevmid = -1;
+    my $pidfile = pidfile_name($fakevmid);
+
+    # Start a temporary (frozen) VM with vmid -1 to allow sending a QMP command
+    my $query_supported_run_qemu = sub {
+	my ($kvm) = @_;
+
+	my $flags = {};
+	my $cmd = [
+	    $qemu_cmd,
+	    '-machine', $default_machine,
+	    '-display', 'none',
+	    '-chardev', "socket,id=qmp,path=/var/run/qemu-server/$fakevmid.qmp,server,nowait",
+	    '-mon', 'chardev=qmp,mode=control',
+	    '-pidfile', $pidfile,
+	    '-S', '-daemonize'
+	];
+
+	if (!$kvm) {
+	    push @$cmd, '-accel', 'tcg';
+	}
+
+	my $rc = run_command($cmd, noerr => 1, quiet => 0);
+	die "QEMU flag querying VM exited with code " . $rc if $rc;
+
+	eval {
+	    my $cmd_result = vm_mon_cmd_nocheck(
+		$fakevmid,
+		'query-cpu-model-expansion',
+		type => 'full',
+		model => { name => 'host' }
+	    );
+
+	    my $props = $cmd_result->{model}->{props};
+	    foreach my $prop (keys %$props) {
+		next if $props->{$prop} ne '1';
+		# QEMU returns some flags multiple times, with '_', '.' or '-'
+		# (e.g. lahf_lm and lahf-lm; sse4.2, sse4-2 and sse4_2; ...).
+		# We only keep those with underscores, to match /proc/cpuinfo
+		$prop =~ s/\.|-/_/g;
+		$flags->{$prop} = 1;
+	    }
+	};
+	my $err = $@;
+
+	# force stop with 10 sec timeout and 'nocheck'
+	# always stop, even if QMP failed
+	vm_stop(undef, $fakevmid, 1, 1, 10, 0, 1);
+
+	die $err if $err;
+
+	return [ keys %$flags ];
+    };
+
+    # We need to query QEMU twice, since KVM and TCG have different supported flags
+    PVE::QemuConfig->lock_config($fakevmid, sub {
+	$flags->{tcg} = eval { $query_supported_run_qemu->(0) };
+	warn "warning: failed querying supported tcg flags: $@\n" if $@;
+
+	if ($kvm_supported) {
+	    $flags->{kvm} = eval { $query_supported_run_qemu->(1) };
+	    warn "warning: failed querying supported kvm flags: $@\n" if $@;
+	}
+    });
+
+    return $flags;
+}
+
+# Understood CPU flags are written to a file at 'pve-qemu' compile time
+my $understood_cpu_flag_dir = "/usr/share/kvm";
+sub query_understood_cpu_flags {
+    my $arch = get_host_arch();
+    my $filepath = "$understood_cpu_flag_dir/cpu-flags-understood-$arch";
+
+    die "Cannot query understood QEMU CPU flags for architecture: $arch (file not found)\n"
+	if ! -e $filepath;
+
+    my $raw = file_get_contents($filepath);
+    $raw =~ s/^\s+|\s+$//g;
+    my @flags = split(/\s+/, $raw);
+
+    return \@flags;
+}
+
 sub get_cpu_options {
     my ($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough) = @_;
 
-- 
2.20.1





More information about the pve-devel mailing list