All of lore.kernel.org
 help / color / mirror / Atom feed
From: Namhyung Kim <namhyung@kernel.org>
To: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Ingo Molnar <mingo@kernel.org>,
	Thomas Gleixner <tglx@linutronix.de>,
	James Clark <james.clark@linaro.org>,
	Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
	Adrian Hunter <adrian.hunter@intel.com>,
	Clark Williams <williams@redhat.com>,
	linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org,
	Arnaldo Carvalho de Melo <acme@redhat.com>
Subject: Re: [PATCH v6 1/5] perf config: Move perf_config__set_variable() to util/config.c
Date: Thu, 1 Oct 2026 00:18:55 -0700	[thread overview]
Message-ID: <ar4JX79-r_4gCNfG@z2> (raw)
In-Reply-To: <20260930213716.2633750-2-acme@kernel.org>

On Wed, Sep 30, 2026 at 11:37:12PM +0200, Arnaldo Carvalho de Melo wrote:
> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> Move perf_config__set_variable() out of the 'perf config' builtin so
> that opt-in features can persist their choice from outside it, e.g.
> util/debuginfo.c writing core.debuginfod=false when the user disables
> debuginfod for the rest of the session.  The
> set_config() body becomes perf_config_set__write(), with the
> system_config choice as an argument, as the builtin's
> use_system_config/use_user_config statics are not available outside it.
> 
> All config file access shares the static parser state and can now run
> on more than one thread, with a feature writing the config while perf
> top's display thread reads it, so serialize parsing and
> rewriting with a mutex, and the whole read-modify-write of
> perf_config__set_variable() against itself.
> 
> perf_config_set__write() checked fopen() but none of the fprintf()s or
> fclose(), so a write failure after truncating the file was reported as
> success.  Harmless for the interactive 'perf config' this came from,
> but this makes it an entry point a background feature can call with no
> other feedback, so propagate those errors too.
> 
> perf_etc_perfconfig() caches what system_path() returns, and that
> allocates, so on failure every caller dereferenced NULL, the
> pre-existing ones in perf_config_from_file() and in the daemon
> included.  Pre-existing, not introduced here, but this rewrites the
> accessor anyway, so make it total: the unresolved path is the same
> string for the usual absolute ETC_PERFCONFIG.

I think this patch does many things.  Can we split them? 

> 
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
> ---
>  tools/perf/builtin-config.c |  70 +-----------
>  tools/perf/util/config.c    | 219 ++++++++++++++++++++++++++++++++----
>  tools/perf/util/config.h    |   2 +
>  3 files changed, 201 insertions(+), 90 deletions(-)
> 
> diff --git a/tools/perf/builtin-config.c b/tools/perf/builtin-config.c
> index cefd042e4f853466..3b074aca8d344539 100644
> --- a/tools/perf/builtin-config.c
> +++ b/tools/perf/builtin-config.c
> @@ -41,37 +41,7 @@ static struct option config_options[] = {
>  
>  static int set_config(struct perf_config_set *set, const char *file_name)
>  {
> -	struct perf_config_section *section = NULL;
> -	struct perf_config_item *item = NULL;
> -	const char *first_line = "# this file is auto-generated.";
> -	FILE *fp;
> -
> -	if (set == NULL)
> -		return -1;
> -
> -	fp = fopen(file_name, "w");
> -	if (!fp)
> -		return -1;
> -
> -	fprintf(fp, "%s\n", first_line);
> -
> -	/* overwrite configvariables */
> -	perf_config_items__for_each_entry(&set->sections, section) {
> -		if (!use_system_config && section->from_system_config)
> -			continue;
> -		fprintf(fp, "[%s]\n", section->name);
> -
> -		perf_config_items__for_each_entry(&section->items, item) {
> -			if (!use_system_config && item->from_system_config)
> -				continue;
> -			if (item->value)
> -				fprintf(fp, "\t%s = %s\n",
> -					item->name, item->value);
> -		}
> -	}
> -	fclose(fp);
> -
> -	return 0;
> +	return perf_config_set__write(set, file_name, use_system_config);
>  }
>  
>  static int show_spec_config(struct perf_config_set *set, const char *var)
> @@ -158,44 +128,6 @@ static int parse_config_arg(char *arg, char **var, char **value)
>  	return 0;
>  }
>  
> -int perf_config__set_variable(const char *var, const char *value)
> -{
> -	char path[PATH_MAX];
> -	char *user_config = mkpath(path, sizeof(path), "%s/.perfconfig", getenv("HOME"));
> -	const char *config_filename;
> -	struct perf_config_set *set;
> -	int ret = -1;
> -
> -	if (use_system_config)
> -		config_exclusive_filename = perf_etc_perfconfig();
> -	else if (use_user_config)
> -		config_exclusive_filename = user_config;
> -
> -	if (!config_exclusive_filename)
> -		config_filename = user_config;
> -	else
> -		config_filename = config_exclusive_filename;
> -
> -	set = perf_config_set__new();
> -	if (!set)
> -		goto out_err;
> -
> -	if (perf_config_set__collect(set, config_filename, var, value) < 0) {
> -		pr_err("Failed to add '%s=%s'\n", var, value);
> -		goto out_err;
> -	}
> -
> -	if (set_config(set, config_filename) < 0) {
> -		pr_err("Failed to set the configs on %s\n", config_filename);
> -		goto out_err;
> -	}
> -
> -	ret = 0;
> -out_err:
> -	perf_config_set__delete(set);
> -	return ret;
> -}
> -
>  int cmd_config(int argc, const char **argv)
>  {
>  	int i, ret = -1;
> diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c
> index 8fe43b032e9af88a..3b6a45569ae3d2eb 100644
> --- a/tools/perf/util/config.c
> +++ b/tools/perf/util/config.c
> @@ -12,6 +12,8 @@
>  #include "config.h"
>  
>  #include <errno.h>
> +#include <limits.h>
> +#include <pthread.h>
>  #include <stdbool.h>
>  #include <stdio.h>
>  #include <stdlib.h>
> @@ -370,10 +372,15 @@ static int perf_parse_long(const char *value, long *ret)
>  
>  static void bad_config(const char *name)
>  {
> -	if (config_file_name)
> -		pr_warning("bad config value for '%s' in %s, ignoring...\n", name, config_file_name);
> -	else
> -		pr_warning("bad config value for '%s', ignoring...\n", name);
> +	/*
> +	 * No file name here: config_file_name is set and cleared by
> +	 * whichever thread has a config file in flight, under config_mutex,
> +	 * while this runs on the thread dispatching the collected values.
> +	 * Reading it would race with that thread: NULL between the check
> +	 * and the use, or the file it is parsing, not the one the bad value
> +	 * came from.
> +	 */
> +	pr_warning("bad config value for '%s', ignoring...\n", name);
>  }
>  
>  int perf_config_u64(u64 *dest, const char *name, const char *value)
> @@ -549,11 +556,26 @@ int perf_default_config(const char *var, const char *value,
>  	return 0;
>  }
>  
> +/*
> + * Serialize config file access: parsing and rewriting share the static
> + * parser state and can run on more than one thread, the debuginfod
> + * fetch writing core.debuginfod=false while perf top reads it.
> + */
> +static pthread_mutex_t config_mutex = PTHREAD_MUTEX_INITIALIZER;

Nit: perf has its own mutex type and helper functions.  But I suspect it
doesn't have the static initializer yet.

Thanks,
Namhyung


> +
> +/*
> + * perf_config__set_variable() is a read-modify-write of a config file,
> + * and config_mutex serializes just each of its steps: take it for the
> + * whole update so callers can't write over each other's changes.
> + */
> +static pthread_mutex_t config_update_mutex = PTHREAD_MUTEX_INITIALIZER;
> +
>  static int perf_config_from_file(config_fn_t fn, const char *filename, void *data)
>  {
>  	int ret;
>  	FILE *f = fopen(filename, "r");
>  
> +	pthread_mutex_lock(&config_mutex);
>  	ret = -1;
>  	if (f) {
>  		config_file = f;
> @@ -564,15 +586,34 @@ static int perf_config_from_file(config_fn_t fn, const char *filename, void *dat
>  		fclose(f);
>  		config_file_name = NULL;
>  	}
> +	pthread_mutex_unlock(&config_mutex);
>  	return ret;
>  }
>  
> +/*
> + * Computed once: system_path() allocates, a lazy init racing on two
> + * threads would leak all but one of the strings.
> + */
> +static const char *etc_perfconfig;
> +
> +static void perf_etc_perfconfig__init(void)
> +{
> +	etc_perfconfig = system_path(ETC_PERFCONFIG);
> +	/*
> +	 * None of the callers check for NULL, so an allocation failure
> +	 * here leaves all of them dereferencing it.  The unresolved path
> +	 * is the same string when ETC_PERFCONFIG is absolute anyway.
> +	 */
> +	if (!etc_perfconfig)
> +		etc_perfconfig = ETC_PERFCONFIG;
> +}
> +
>  const char *perf_etc_perfconfig(void)
>  {
> -	static const char *system_wide;
> -	if (!system_wide)
> -		system_wide = system_path(ETC_PERFCONFIG);
> -	return system_wide;
> +	static pthread_once_t once = PTHREAD_ONCE_INIT;
> +
> +	pthread_once(&once, perf_etc_perfconfig__init);
> +	return etc_perfconfig;
>  }
>  
>  static int perf_env_bool(const char *k, int def)
> @@ -630,19 +671,25 @@ static char *home_perfconfig(void)
>  	return NULL;
>  }
>  
> -const char *perf_home_perfconfig(void)
> -{
> -	static const char *config;
> -	static bool failed;
> +/*
> + * Computed once for the same reason as perf_etc_perfconfig() above:
> + * home_perfconfig() allocates, a lazy init racing on two threads would
> + * leak all but one of the strings, and the warnings it may print would
> + * come out more than once.
> + */
> +static const char *home_config;
>  
> -	if (failed || config)
> -		return config;
> +static void perf_home_perfconfig__init(void)
> +{
> +	home_config = home_perfconfig();
> +}
>  
> -	config = home_perfconfig();
> -	if (!config)
> -		failed = true;
> +const char *perf_home_perfconfig(void)
> +{
> +	static pthread_once_t once = PTHREAD_ONCE_INIT;
>  
> -	return config;
> +	pthread_once(&once, perf_home_perfconfig__init);
> +	return home_config;
>  }
>  
>  static struct perf_config_section *find_section(struct list_head *sections,
> @@ -783,8 +830,18 @@ static int collect_config(const char *var, const char *value,
>  int perf_config_set__collect(struct perf_config_set *set, const char *file_name,
>  			     const char *var, const char *value)
>  {
> +	int ret;
> +
> +	pthread_mutex_lock(&config_mutex);
>  	config_file_name = file_name;
> -	return collect_config(var, value, set);
> +	ret = collect_config(var, value, set);
> +	/*
> +	 * Don't leave the static parser state pointing at the caller's
> +	 * buffer.
> +	 */
> +	config_file_name = NULL;
> +	pthread_mutex_unlock(&config_mutex);
> +	return ret;
>  }
>  
>  static int perf_config_set__init(struct perf_config_set *set)
> @@ -831,6 +888,16 @@ struct perf_config_set *perf_config_set__load_file(const char *file)
>  	return set;
>  }
>  
> +/*
> + * The global config_set is built lazily: two threads in perf_config() at
> + * once would both build one and leak all but the last, and config_set must
> + * not be read while another thread swaps it, so take it one at a time.  Not
> + * with config_mutex: building the set parses the config files, which takes
> + * that one.
> + */
> +static pthread_mutex_t config_set_mutex = PTHREAD_MUTEX_INITIALIZER;
> +
> +/* Called with config_set_mutex held. */
>  static int perf_config__init(void)
>  {
>  	if (config_set == NULL)
> @@ -871,16 +938,126 @@ int perf_config_set(struct perf_config_set *set,
>  
>  int perf_config(config_fn_t fn, void *data)
>  {
> -	if (config_set == NULL && perf_config__init())
> +	struct perf_config_set *set;
> +
> +	/*
> +	 * The mutex is taken just for the lazy init and for the pointer:
> +	 * the dispatch below only reads the set, so a callback that called
> +	 * perf_config() again would find it unlocked, and only
> +	 * perf_config__exit() replaces the set.
> +	 */
> +	pthread_mutex_lock(&config_set_mutex);
> +	if (perf_config__init()) {
> +		pthread_mutex_unlock(&config_set_mutex);
>  		return -1;
> +	}
> +	set = config_set;
> +	pthread_mutex_unlock(&config_set_mutex);
>  
> -	return perf_config_set(config_set, fn, data);
> +	return perf_config_set(set, fn, data);
>  }
>  
>  void perf_config__exit(void)
>  {
> +	pthread_mutex_lock(&config_set_mutex);
>  	perf_config_set__delete(config_set);
>  	config_set = NULL;
> +	pthread_mutex_unlock(&config_set_mutex);
> +}
> +
> +int perf_config_set__write(struct perf_config_set *set,
> +			   const char *file_name, bool system_config)
> +{
> +	struct perf_config_section *section = NULL;
> +	struct perf_config_item *item = NULL;
> +	int ret = 0;
> +	FILE *fp;
> +
> +	pthread_mutex_lock(&config_mutex);
> +	fp = fopen(file_name, "w");
> +	if (!fp) {
> +		pthread_mutex_unlock(&config_mutex);
> +		return -1;
> +	}
> +
> +	if (fprintf(fp, "# this file is auto-generated.\n") < 0)
> +		ret = -1;
> +
> +	/* overwrite configvariables */
> +	perf_config_sections__for_each_entry(&set->sections, section) {
> +		if (!system_config && section->from_system_config)
> +			continue;
> +		if (fprintf(fp, "[%s]\n", section->name) < 0)
> +			ret = -1;
> +
> +		perf_config_items__for_each_entry(&section->items, item) {
> +			if (!system_config && item->from_system_config)
> +				continue;
> +			if (item->value &&
> +			    fprintf(fp, "\t%s = %s\n", item->name, item->value) < 0)
> +				ret = -1;
> +		}
> +	}
> +	if (fclose(fp) != 0)
> +		ret = -1;
> +	pthread_mutex_unlock(&config_mutex);
> +
> +	return ret;
> +}
> +
> +/*
> + * Set @var=@value in the configuration file perf is using: ~/.perfconfig
> + * or the file named by PERF_CONFIG, which makes perf read only that
> + * file.  The rewrite is the same 'perf config' does, comments are not
> + * preserved.
> + */
> +int perf_config__set_variable(const char *var, const char *value)
> +{
> +	const char *config_filename;
> +	bool system_config;
> +	struct perf_config_set *set = NULL;
> +	int ret = -1;
> +
> +	pthread_mutex_lock(&config_update_mutex);
> +
> +	/*
> +	 * Not on the stack: the parser publishes this buffer as
> +	 * config_file_name, which another thread may still be reading.  It is
> +	 * shared by every caller, so it is formatted under the lock above.
> +	 */
> +	{
> +		static char path[PATH_MAX];
> +		char *user_config = mkpath(path, sizeof(path), "%s/.perfconfig", getenv("HOME"));
> +
> +		config_filename = config_exclusive_filename ?: user_config;
> +	}
> +
> +	/*
> +	 * When rewriting the system wide file all entries are marked as coming
> +	 * from it and must be kept, or it would be truncated down to its
> +	 * header.
> +	 */
> +	system_config = strcmp(config_filename, perf_etc_perfconfig()) == 0;
> +
> +	set = perf_config_set__new();
> +	if (!set)
> +		goto out_err;
> +
> +	if (perf_config_set__collect(set, config_filename, var, value) < 0) {
> +		pr_err("Failed to add '%s=%s'\n", var, value);
> +		goto out_err;
> +	}
> +
> +	if (perf_config_set__write(set, config_filename, system_config) < 0) {
> +		pr_err("Failed to set the configs on %s\n", config_filename);
> +		goto out_err;
> +	}
> +
> +	ret = 0;
> +out_err:
> +	perf_config_set__delete(set);
> +	pthread_mutex_unlock(&config_update_mutex);
> +	return ret;
>  }
>  
>  static void perf_config_item__delete(struct perf_config_item *item)
> diff --git a/tools/perf/util/config.h b/tools/perf/util/config.h
> index 987b47cf54c350ba..9098f8a045850c97 100644
> --- a/tools/perf/util/config.h
> +++ b/tools/perf/util/config.h
> @@ -33,6 +33,8 @@ int perf_config_scan(const char *name, const char *fmt, ...) __scanf(2, 3);
>  const char *perf_config_get(const char *name);
>  int perf_config_set(struct perf_config_set *set,
>  		    config_fn_t fn, void *data);
> +int perf_config_set__write(struct perf_config_set *set,
> +			   const char *file_name, bool system_config);
>  int perf_config_int(int *dest, const char *, const char *);
>  int perf_config_u8(u8 *dest, const char *name, const char *value);
>  int perf_config_u64(u64 *dest, const char *, const char *);
> -- 
> 2.55.0
> 

  parent reply	other threads:[~2026-10-01  7:18 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-30 21:37 [PATCH v6 0/5] perf tools: Add progress diagnostics and a false-sharing workload Arnaldo Carvalho de Melo
2026-09-30 21:37 ` [PATCH v6 1/5] perf config: Move perf_config__set_variable() to util/config.c Arnaldo Carvalho de Melo
2026-09-30 21:48   ` sashiko-bot
2026-10-01  7:18   ` Namhyung Kim [this message]
2026-10-01  9:19     ` Arnaldo Carvalho de Melo
2026-09-30 21:37 ` [PATCH v6 2/5] perf report: Add --progress option Arnaldo Carvalho de Melo
2026-09-30 21:52   ` sashiko-bot
2026-09-30 21:37 ` [PATCH v6 3/5] perf report: Add --no-progress option Arnaldo Carvalho de Melo
2026-09-30 21:58   ` sashiko-bot
2026-10-01  7:01   ` Namhyung Kim
2026-10-01  9:20     ` Arnaldo Carvalho de Melo
2026-09-30 21:37 ` [PATCH v6 4/5] perf scripts: Add perf-stuck, to tell where a running perf is stuck Arnaldo Carvalho de Melo
2026-09-30 22:01   ` sashiko-bot
2026-10-01  7:24   ` Namhyung Kim
2026-10-01  9:19     ` Arnaldo Carvalho de Melo
2026-09-30 21:37 ` [PATCH v6 5/5] perf test: Add false_sharing workload exhibiting cross-CPU false sharing Arnaldo Carvalho de Melo
2026-09-30 22:05   ` sashiko-bot
2026-10-01  7:28   ` Namhyung Kim
2026-10-01  9:18     ` Arnaldo Carvalho de Melo
  -- strict thread matches above, loose matches on Subject: below --
2026-09-30 11:24 [PATCH v6 0/5] perf tools: Add progress diagnostics and a false-sharing workload Arnaldo Carvalho de Melo
2026-09-30 11:24 ` [PATCH v6 1/5] perf config: Move perf_config__set_variable() to util/config.c Arnaldo Carvalho de Melo
2026-09-30 11:37   ` sashiko-bot

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=ar4JX79-r_4gCNfG@z2 \
    --to=namhyung@kernel.org \
    --cc=acme@kernel.org \
    --cc=acme@redhat.com \
    --cc=adrian.hunter@intel.com \
    --cc=irogers@google.com \
    --cc=james.clark@linaro.org \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=mingo@kernel.org \
    --cc=tglx@linutronix.de \
    --cc=williams@redhat.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.