public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH cluster v8 1/4] add CFS_IPC_GET_GUEST_CONFIG_PROPERTIES method
Date: Tue, 18 Oct 2022 16:02:07 +0200	[thread overview]
Message-ID: <20221018140226.598710-2-d.csapak@proxmox.com> (raw)
In-Reply-To: <20221018140226.598710-1-d.csapak@proxmox.com>

for getting multiple properties from the in memory config of the
guests in one go. Keep the existing IPC call as is for backward
compatibility and add this as separate, new one.

It basically behaves the same as
CFS_IPC_GET_GUEST_CONFIG_PROPERTY, but takes a list of properties
instead and returns multiple properties per guest.

The old way of getting a single property is now also done by
the new function, since it basically performs identically.

Here a short benchmark:

Setup:
PVE in a VM with cpu type host (12700k) and 4 cores
10000 typical configs with both 'lock' and 'tags' set at the end
and fairly long tags ('test-tag1,test-tag2,test-tag3')
(normal vm with a snapshot, ~1KiB)

i let it run 100 times each, times in ms

old code:

total time  time per iteration
1054.2      10.2

new code:

num props  total time  time per iter  function
2          1332.2      13.2           get_properties
1          1051.2      10.2           get_properties
2          2082.2      20.2           get_property (2 calls to get_property)
1          1034.2      10.2           get_property

so a call with the new code for one property is the same as with the
old code, and adding a second property only adds a bit of additional
time (in this case ~30%)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 data/src/cfs-ipc-ops.h |   2 +
 data/src/server.c      |  64 +++++++++++++++
 data/src/status.c      | 177 ++++++++++++++++++++++++++++-------------
 data/src/status.h      |   3 +
 4 files changed, 190 insertions(+), 56 deletions(-)

diff --git a/data/src/cfs-ipc-ops.h b/data/src/cfs-ipc-ops.h
index 003e233..249308d 100644
--- a/data/src/cfs-ipc-ops.h
+++ b/data/src/cfs-ipc-ops.h
@@ -43,4 +43,6 @@
 
 #define CFS_IPC_VERIFY_TOKEN 12
 
+#define CFS_IPC_GET_GUEST_CONFIG_PROPERTIES 13
+
 #endif
diff --git a/data/src/server.c b/data/src/server.c
index 549788a..ced9cfc 100644
--- a/data/src/server.c
+++ b/data/src/server.c
@@ -89,6 +89,13 @@ typedef struct {
 	char property[];
 } cfs_guest_config_propery_get_request_header_t;
 
+typedef struct {
+	struct qb_ipc_request_header req_header;
+	uint32_t vmid;
+	uint8_t num_props;
+	char props[]; /* list of \0 terminated properties */
+} cfs_guest_config_properties_get_request_header_t;
+
 typedef struct {
 	struct qb_ipc_request_header req_header;
 	char token[];
@@ -348,6 +355,63 @@ static int32_t s1_msg_process_fn(
 
 			result = cfs_create_guest_conf_property_msg(outbuf, memdb, rh->property, rh->vmid);
 		}
+	} else if (request_id == CFS_IPC_GET_GUEST_CONFIG_PROPERTIES) {
+
+		cfs_guest_config_properties_get_request_header_t *rh =
+			(cfs_guest_config_properties_get_request_header_t *) data;
+
+		size_t remaining = request_size - G_STRUCT_OFFSET(cfs_guest_config_properties_get_request_header_t, props);
+
+		result = 0;
+		if (rh->vmid < 100 && rh->vmid != 0) {
+			cfs_debug("vmid out of range %u", rh->vmid);
+			result = -EINVAL;
+		} else if (rh->vmid >= 100 && !vmlist_vm_exists(rh->vmid)) {
+			result = -ENOENT;
+		} else if (rh->num_props == 0) {
+			cfs_debug("num_props == 0");
+			result = -EINVAL;
+		} else if (remaining <= 1) {
+			cfs_debug("property length <= 1, %ld", remaining);
+			result = -EINVAL;
+		} else {
+			const char **properties = malloc(sizeof(char*) * rh->num_props);
+			char *current = (rh->props);
+			for (uint8_t i = 0; i < rh->num_props; i++) {
+			    size_t proplen = strnlen(current, remaining);
+			    if (proplen == 0) {
+				cfs_debug("property length 0");
+				result = -EINVAL;
+				break;
+			    }
+			    if (proplen == remaining) {
+				cfs_debug("property not \\0 terminated");
+				result = -EINVAL;
+				break;
+			    }
+			    if (current[0] < 'a' || current[0] > 'z') {
+				cfs_debug("property does not start with [a-z]");
+				result = -EINVAL;
+				break;
+			    }
+			    properties[i] = current;
+			    current[proplen] = '\0'; // ensure property is 0 terminated
+			    remaining -= (proplen + 1);
+			    current += proplen + 1;
+			}
+
+			if (remaining != 0) {
+			    cfs_debug("leftover data after parsing %u properties", rh->num_props);
+			    result = -EINVAL;
+			}
+
+			if (result == 0) {
+			    cfs_debug("cfs_get_guest_config_properties: basic valid checked, do request");
+			    result = cfs_create_guest_conf_properties_msg(outbuf, memdb, properties, rh->num_props, rh->vmid);
+			}
+
+			free(properties);
+		}
 	} else if (request_id == CFS_IPC_VERIFY_TOKEN) {
 
 		cfs_verify_token_request_header_t *rh = (cfs_verify_token_request_header_t *) data;
diff --git a/data/src/status.c b/data/src/status.c
index 9bceaeb..e2bedaa 100644
--- a/data/src/status.c
+++ b/data/src/status.c
@@ -804,25 +804,52 @@ cfs_create_vmlist_msg(GString *str)
 	return 0;
 }
 
-// checks the conf for a line starting with '$prop:' and returns the value
-// afterwards, whitout initial whitespace(s), we only deal with the format
-// restricion imposed by our perl VM config parser, main reference is
+// checks if a config line starts with the given prop. if yes, writes a '\0'
+// at the end of the value, and returns the pointer where the value starts
+char *
+_get_property_value_from_line(char *line, int line_len, const char *prop, int prop_len)
+{
+	if (line_len <= prop_len + 1) return NULL;
+
+	if (line[prop_len] == ':' && memcmp(line, prop, prop_len) == 0) { // found
+		char *v_start = &line[prop_len + 1];
+
+		// drop initial value whitespaces here already
+		while (*v_start && isspace(*v_start)) v_start++;
+
+		if (!*v_start) return NULL;
+
+		char *v_end = &line[line_len - 1];
+		while (v_end > v_start && isspace(*v_end)) v_end--;
+		v_end[1] = '\0';
+
+		return v_start;
+	}
+
+	return NULL;
+}
+
+// checks the conf for lines starting with the given props and
+// writes the pointers into the correct positions into the 'found' array
+// afterwards, without initial whitespace(s), we only deal with the format
+// restriction imposed by our perl VM config parser, main reference is
 // PVE::QemuServer::parse_vm_config this allows to be very fast and still
 // relatively simple
-// main restrictions used for our advantage is the properties match reges:
+// main restrictions used for our advantage is the properties match regex:
 // ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) from parse_vm_config
 // currently we only look at the current configuration in place, i.e., *no*
-// snapshort and *no* pending changes
-static char *
-_get_property_value(char *conf, int conf_size, const char *prop, int prop_len)
+// snapshot and *no* pending changes
+void
+_get_property_values(char **found, char *conf, int conf_size, const char **props, uint8_t num_props, char min, char max)
 {
 	const char *const conf_end = conf + conf_size;
 	char *line = conf;
-	size_t remaining_size;
+	size_t remaining_size = conf_size;
+	int count = 0;
 
 	char *next_newline = memchr(conf, '\n', conf_size);
 	if (next_newline == NULL) {
-		return NULL; // valid property lines end with \n, but none in the config
+		return; // valid property lines end with \n, but none in the config
 	}
 	*next_newline = '\0';
 
@@ -830,41 +857,32 @@ _get_property_value(char *conf, int conf_size, const char *prop, int prop_len)
 		if (!line[0]) goto next;
 
 		// snapshot or pending section start, but nothing found yet -> not found
-		if (line[0] == '[') return NULL;
-		// properties start with /^[a-z]/, so continue early if not
-		if (line[0] < 'a' || line[0] > 'z') goto next;
+		if (line[0] == '[') return;
+		// continue early if line does not begin with the min/max char of the properties
+		if (line[0] < min || line[0] > max) goto next;
 
 		int line_len = strlen(line);
-		if (line_len <= prop_len + 1) goto next;
-
-		if (line[prop_len] == ':' && memcmp(line, prop, prop_len) == 0) { // found
-			char *v_start = &line[prop_len + 1];
-
-			// drop initial value whitespaces here already
-			while (*v_start && isspace(*v_start)) v_start++;
-
-			if (!*v_start) return NULL;
-
-			char *v_end = &line[line_len - 1];
-			while (v_end > v_start && isspace(*v_end)) v_end--;
-			v_end[1] = '\0';
-
-			return v_start;
+		for (uint8_t i = 0; i < num_props; i++) {
+			char * value = _get_property_value_from_line(line, line_len, props[i], strlen(props[i]));
+			if (value != NULL) {
+				count += (found[i] != NULL) & 0x1; // count newly found lines
+				found[i] = value;
+			}
+		}
+		if (count == num_props) {
+			return; // found all
 		}
 next:
 		line = next_newline + 1;
 		remaining_size = conf_end - line;
-		if (remaining_size <= prop_len) {
-			return NULL;
-		}
 		next_newline = memchr(line, '\n', remaining_size);
 		if (next_newline == NULL) {
-			return NULL; // valid property lines end with \n, but none in the config
+			return; // valid property lines end with \n, but none in the config
 		}
 		*next_newline = '\0';
 	}
 
-	return NULL; // not found
+	return;
 }
 
 static void
@@ -883,24 +901,77 @@ _g_str_append_kv_jsonescaped(GString *str, const char *k, const char *v)
 }
 
 int
-cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *prop, uint32_t vmid)
+_print_found_properties(
+	GString *str,
+	gpointer conf,
+	int size,
+	const char **props,
+	uint8_t num_props,
+	uint32_t vmid,
+	char **values,
+	char min,
+	char max,
+	int first)
+{
+	_get_property_values(values, conf, size, props, num_props, min, max);
+
+	uint8_t found = 0;
+	for (uint8_t i = 0; i < num_props; i++) {
+		if (values[i] == NULL) {
+			continue;
+		}
+		if (found) {
+			g_string_append_c(str, ',');
+		} else {
+			if (!first) {
+				g_string_append_printf(str, ",\n");
+			} else {
+				first = 0;
+			}
+			g_string_append_printf(str, "\"%u\":{", vmid);
+			found = 1;
+		}
+		_g_str_append_kv_jsonescaped(str, props[i], values[i]);
+	}
+
+	if (found) {
+		g_string_append_c(str, '}');
+	}
+
+	return first;
+}
+
+int
+cfs_create_guest_conf_properties_msg(GString *str, memdb_t *memdb, const char **props, uint8_t num_props, uint32_t vmid)
 {
 	g_return_val_if_fail(cfs_status.vmlist != NULL, -EINVAL);
 	g_return_val_if_fail(str != NULL, -EINVAL);
 
-	int prop_len = strlen(prop);
-	int res = 0;
-	GString *path = NULL;
-
 	// Prelock &memdb->mutex in order to enable the usage of memdb_read_nolock
 	// to prevent Deadlocks as in #2553
 	g_mutex_lock (&memdb->mutex);
 	g_mutex_lock (&mutex);
 
-	g_string_printf(str,"{\n");
+	g_string_printf(str, "{\n");
 
 	GHashTable *ht = cfs_status.vmlist;
+
+	int res = 0;
+	GString *path = NULL;
 	gpointer tmp = NULL;
+	char **values = calloc(num_props, sizeof(char*));
+	char min = 'z', max = 'a';
+
+	for (uint8_t i = 0; i < num_props; i++) {
+		if (props[i][0] > max) {
+			max = props[i][0];
+		}
+
+		if (props[i][0] < min) {
+			min = props[i][0];
+		}
+	}
+
 	if (!g_hash_table_size(ht)) {
 		goto ret;
 	}
@@ -919,15 +990,8 @@ cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *pro
 		// use memdb_read_nolock because lock is handled here
 		int size = memdb_read_nolock(memdb, path->str, &tmp);
 		if (tmp == NULL) goto err;
-		if (size <= prop_len) goto ret;
-
-		char *val = _get_property_value(tmp, size, prop, prop_len);
-		if (val == NULL) goto ret;
-
-		g_string_append_printf(str, "\"%u\":{", vmid);
-		_g_str_append_kv_jsonescaped(str, prop, val);
-		g_string_append_c(str, '}');
 
+		_print_found_properties(str, tmp, size, props, num_props, vmid, values, min, max, 1);
 	} else {
 		GHashTableIter iter;
 		g_hash_table_iter_init (&iter, ht);
@@ -943,21 +1007,16 @@ cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *pro
 			tmp = NULL;
 			// use memdb_read_nolock because lock is handled here
 			int size = memdb_read_nolock(memdb, path->str, &tmp);
-			if (tmp == NULL || size <= prop_len) continue;
+			if (tmp == NULL) continue;
 
-			char *val = _get_property_value(tmp, size, prop, prop_len);
-			if (val == NULL) continue;
-
-			if (!first) g_string_append_printf(str, ",\n");
-			else first = 0;
-
-			g_string_append_printf(str, "\"%u\":{", vminfo->vmid);
-			_g_str_append_kv_jsonescaped(str, prop, val);
-			g_string_append_c(str, '}');
+			memset(values, 0, sizeof(char*)*num_props); // reset array
+			first = _print_found_properties(str, tmp, size, props, num_props,
+					vminfo->vmid, values, min, max, first);
 		}
 	}
 ret:
 	g_free(tmp);
+	free(values);
 	if (path != NULL) {
 		g_string_free(path, TRUE);
 	}
@@ -973,6 +1032,12 @@ enoent:
 	goto ret;
 }
 
+int
+cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *prop, uint32_t vmid)
+{
+	return cfs_create_guest_conf_properties_msg(str, memdb, &prop, 1, vmid);
+}
+
 void
 record_memdb_change(const char *path)
 {
diff --git a/data/src/status.h b/data/src/status.h
index bbf0948..041cb34 100644
--- a/data/src/status.h
+++ b/data/src/status.h
@@ -163,4 +163,7 @@ cfs_create_memberlist_msg(
 int
 cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *prop, uint32_t vmid);
 
+int
+cfs_create_guest_conf_properties_msg(GString *str, memdb_t *memdb, const char **props, uint8_t num_props, uint32_t vmid);
+
 #endif /* _PVE_STATUS_H_ */
-- 
2.30.2





  reply	other threads:[~2022-10-18 14:03 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-10-18 14:02 [pve-devel] [PATCH cluster/qemu-server/container/wt/manager v8] add tags to ui Dominik Csapak
2022-10-18 14:02 ` Dominik Csapak [this message]
2022-10-18 14:02 ` [pve-devel] [PATCH cluster v8 2/4] Cluster: add get_guest_config_properties Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH cluster v8 3/4] datacenter.cfg: add option for tag-style Dominik Csapak
2022-11-10  9:54   ` Thomas Lamprecht
2022-10-18 14:02 ` [pve-devel] [PATCH cluster v8 4/4] DataCenterConfig: add tag rights control to the datacenter config Dominik Csapak
2022-11-09 14:42   ` Aaron Lauterer
2022-11-10 10:09   ` Thomas Lamprecht
2022-10-18 14:02 ` [pve-devel] [PATCH qemu-server v8] api: update: improve tag privilege check Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH container v8] check_ct_modify_config_perm: " Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH widget-toolkit v8 1/2] add tag related helpers Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH widget-toolkit v8 2/2] Toolkit: add override for Ext.dd.DragDropManager Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 01/12] api: /cluster/resources: add tags to returned properties Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 02/12] api: add /ui-options api call Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 03/12] ui: call '/ui-options' and save the result in PVE.UIOptions Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 04/12] ui: parse and save tag color overrides from /ui-options Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 05/12] ui: tree/ResourceTree: collect tags on update Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 06/12] ui: add form/TagColorGrid Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 07/12] ui: dc/OptionView: add editors for tag settings Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 08/12] ui: add form/Tag Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 09/12] ui: add form/TagEdit.js Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 10/12] ui: {lxc, qemu}/Config: show Tags and make them editable Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 11/12] ui: tree/ResourceTree: show Tags in tree Dominik Csapak
2022-10-18 14:02 ` [pve-devel] [PATCH manager v8 12/12] ui: add tags to ResourceGrid and GlobalSearchField Dominik Csapak
2022-11-07 14:56 ` [pve-devel] [PATCH manager] ui: add missing tag classes Dominik Csapak
2022-11-09 13:11 ` [pve-devel] [PATCH cluster/qemu-server/container/wt/manager v8] add tags to ui Aaron Lauterer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20221018140226.598710-2-d.csapak@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal