MINOR: activity: add configuration and CLI support for "profiling.tasks"

This is a new global setting which enables or disables CPU profiling
per task. For now it only sets/resets the variable based on the global
option "profiling.tasks" and supports showing it as well as setting it
from the CLI using "show profiling" and "set profiling". The option will
be used by a future commit. It was done in a way which should ease future
addition of profiling options.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 7552352..180bfc8 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -619,6 +619,7 @@
    - nosplice
    - nogetaddrinfo
    - noreuseport
+   - profiling.tasks
    - spread-checks
    - server-state-base
    - server-state-file
@@ -1434,6 +1435,16 @@
   Disables the use of SO_REUSEPORT - see socket(7). It is equivalent to the
   command line argument "-dR".
 
+profiling.tasks { on | off }
+  Enables ('on') or disables ('off') per-task CPU profiling. CPU profiling per
+  task can be very convenient to report where the time is spent and which
+  requests have what effect on which other request. It is not enabled by
+  default as it may consume a little bit extra CPU. This requires a system
+  supporting the clock_gettime(2) syscall with clock identifiers
+  CLOCK_MONOTONIC and CLOCK_THREAD_CPUTIME_ID, otherwise the reported time will
+  be zero. This option may be changed at run time using "set profiling" on the
+  CLI.
+
 spread-checks <0..50, in percent>
   Sometimes it is desirable to avoid sending agent and health checks to
   servers at exact intervals, for instance when many logical servers are
diff --git a/doc/management.txt b/doc/management.txt
index 885ff2c..c8f8c65 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -1649,6 +1649,11 @@
   delayed until the threshold is reached. A value of zero restores the initial
   setting.
 
+set profiling { tasks } { on | off }
+  Enables or disables CPU profiling for the indicated subsystem. This is
+  equivalent to setting or clearing the "profiling" settings in the "global"
+  section of the configuration file. Please also see "show profiling".
+
 set rate-limit connections global <value>
   Change the process-wide connection rate limit, which is set by the global
   'maxconnrate' setting. A value of zero disables the limitation. This limit
@@ -2045,6 +2050,10 @@
   as the SIGQUIT when running in foreground except that it does not flush
   the pools.
 
+show profiling
+  Dumps the current profiling settings, one per line, as well as the command
+  needed to change them.
+
 show servers state [<backend>]
   Dump the state of the servers found in the running configuration. A backend
   name or identifier may be provided to limit the output to this backend only.
diff --git a/include/proto/activity.h b/include/proto/activity.h
index 717d667..098206e 100644
--- a/include/proto/activity.h
+++ b/include/proto/activity.h
@@ -28,6 +28,10 @@
 #include <types/activity.h>
 #include <proto/freq_ctr.h>
 
+/* bit fields for "profiling" */
+#define HA_PROF_TASKS       0x00000001     /* enable per-task CPU profiling */
+
+extern unsigned int profiling;
 extern struct activity activity[MAX_THREADS];
 
 
diff --git a/src/activity.c b/src/activity.c
index c843b47..9460a8a 100644
--- a/src/activity.c
+++ b/src/activity.c
@@ -10,11 +10,19 @@
  *
  */
 
+#include <common/cfgparse.h>
 #include <common/config.h>
 #include <common/standard.h>
 #include <common/hathreads.h>
 #include <types/activity.h>
+#include <proto/channel.h>
+#include <proto/cli.h>
 #include <proto/freq_ctr.h>
+#include <proto/stream_interface.h>
+
+
+/* bit field of profiling options. Beware, may be modified at runtime! */
+unsigned int profiling;
 
 /* One struct per thread containing all collected measurements */
 struct activity activity[MAX_THREADS] __attribute__((aligned(64))) = { };
@@ -29,3 +37,95 @@
 	update_freq_ctr(&activity[tid].cpust_1s, stolen);
 	update_freq_ctr_period(&activity[tid].cpust_15s, 15000, stolen);
 }
+
+/* config parser for global "profiling.tasks", accepts "on" or "off" */
+static int cfg_parse_prof_tasks(char **args, int section_type, struct proxy *curpx,
+                                struct proxy *defpx, const char *file, int line,
+                                char **err)
+{
+	if (too_many_args(1, args, err, NULL))
+		return -1;
+
+	if (strcmp(args[1], "on") == 0)
+		profiling |=  HA_PROF_TASKS;
+	else if (strcmp(args[1], "off") == 0)
+		profiling &= ~HA_PROF_TASKS;
+	else {
+		memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]);
+		return -1;
+	}
+	return 0;
+}
+
+/* parse a "set profiling" command. It always returns 1. */
+static int cli_parse_set_profiling(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	unsigned int bit = 0;
+
+	if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+		return 1;
+
+	if (strcmp(args[2], "tasks") == 0)
+		bit = HA_PROF_TASKS;
+	else {
+		appctx->ctx.cli.severity = LOG_ERR;
+		appctx->ctx.cli.msg = "Expects 'tasks'.\n";
+		appctx->st0 = CLI_ST_PRINT;
+		return 1;
+	}
+
+	if (strcmp(args[3], "on") == 0)
+		HA_ATOMIC_OR(&profiling, bit);
+	else if (strcmp(args[3], "off") == 0)
+		HA_ATOMIC_AND(&profiling, ~bit);
+	else {
+		appctx->ctx.cli.severity = LOG_ERR;
+		appctx->ctx.cli.msg = "Expects either 'on' or 'off'.\n";
+		appctx->st0 = CLI_ST_PRINT;
+		return 1;
+	}
+	return 1;
+}
+
+/* This function dumps all profiling settings. It returns 0 if the output
+ * buffer is full and it needs to be called again, otherwise non-zero.
+ */
+static int cli_io_handler_show_profiling(struct appctx *appctx)
+{
+	struct stream_interface *si = appctx->owner;
+
+	if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
+		return 1;
+
+	chunk_reset(&trash);
+
+	chunk_printf(&trash, "Per-task CPU profiling              : %s      # set profiling tasks {on|off}\n",
+		     (profiling & HA_PROF_TASKS) ? "on" : "off");
+
+	if (ci_putchk(si_ic(si), &trash) == -1) {
+		/* failed, try again */
+		si_rx_room_blk(si);
+		return 0;
+	}
+	return 1;
+}
+
+/* config keyword parsers */
+static struct cfg_kw_list cfg_kws = {ILH, {
+	{ CFG_GLOBAL, "profiling.tasks",      cfg_parse_prof_tasks      },
+	{ 0, NULL, NULL }
+}};
+
+/* register cli keywords */
+static struct cli_kw_list cli_kws = {{ },{
+	{ { "show", "profiling", NULL }, "show profiling : show CPU profiling options",   NULL, cli_io_handler_show_profiling, NULL },
+	{ { "set",  "profiling", NULL }, "set  profiling : enable/disable CPU profiling", cli_parse_set_profiling,  NULL },
+	{{},}
+}};
+
+__attribute__((constructor))
+static void __activity_init(void)
+{
+	cfg_register_keywords(&cfg_kws);
+	cli_register_kw(&cli_kws);
+}