* [Intel-gfx] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default
2021-02-10 9:37 ` [Intel-gfx] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default Tvrtko Ursulin
@ 2021-02-10 9:44 ` Tvrtko Ursulin
2021-02-10 10:35 ` Chris Wilson
` (2 subsequent siblings)
3 siblings, 0 replies; 20+ messages in thread
From: Tvrtko Ursulin @ 2021-02-10 9:44 UTC (permalink / raw)
To: igt-dev; +Cc: Intel-gfx
From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Implement a default view where clients are aggregated by their PID.
Toggled by pressing 'H' similar to top(1).
v2:
* Fix memory leak.
Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
man/intel_gpu_top.rst | 1 +
tools/intel_gpu_top.c | 121 +++++++++++++++++++++++++++++++++++++-----
2 files changed, 109 insertions(+), 13 deletions(-)
diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst
index b145d85c0440..20658e291db0 100644
--- a/man/intel_gpu_top.rst
+++ b/man/intel_gpu_top.rst
@@ -58,6 +58,7 @@ Supported keys:
'n' Toggle display of numeric client busyness overlay.
's' Toggle between sort modes (runtime, total runtime, pid, client id).
'i' Toggle display of clients which used no GPU time.
+ 'H' Toggle between per PID aggregation and individual clients.
DEVICE SELECTION
================
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index 63ef77056341..525fb2cd539b 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -979,17 +979,18 @@ static int client_pid_cmp(const void *_a, const void *_b)
static int (*client_cmp)(const void *, const void *) = client_last_cmp;
-static void sort_clients(struct clients *clients)
+static struct clients *sort_clients(struct clients *clients,
+ int (*cmp)(const void *, const void *))
{
unsigned int active, free;
struct client *c;
int tmp;
if (!clients)
- return;
+ return clients;
qsort(clients->client, clients->num_clients, sizeof(*clients->client),
- client_cmp);
+ cmp);
/* Trim excessive array space. */
active = 0;
@@ -1011,9 +1012,88 @@ static void sort_clients(struct clients *clients)
sizeof(*c));
}
}
+
+ return clients;
+}
+
+static struct clients *aggregated_clients(struct clients *clients)
+{
+ struct client *ac, *c, *cp = NULL;
+ struct clients *aggregated;
+ int tmp, num = 0;
+
+ /* Sort by pid first to make it easy to aggregate while walking. */
+ sort_clients(clients, client_pid_cmp);
+
+ aggregated = calloc(1, sizeof(*clients));
+ assert(aggregated);
+
+ ac = calloc(clients->num_clients, sizeof(*c));
+ assert(ac);
+
+ aggregated->num_classes = clients->num_classes;
+ aggregated->class = clients->class;
+ aggregated->client = ac;
+
+ for_each_client(clients, c, tmp) {
+ unsigned int i;
+
+ if (c->status == FREE)
+ break;
+
+ assert(c->status == ALIVE);
+
+ if ((cp && c->pid != cp->pid) || !cp) {
+ ac = &aggregated->client[num];
+
+ /* New pid. */
+ ac->clients = aggregated;
+ ac->status = ALIVE;
+ ac->id = ++num;
+ ac->pid = c->pid;
+ strcpy(ac->name, c->name);
+ strcpy(ac->print_name, c->print_name);
+ ac->engines = c->engines;
+ ac->val = calloc(clients->num_classes,
+ sizeof(ac->val[0]));
+ assert(ac->val);
+ ac->samples = 1;
+ }
+
+ cp = c;
+
+ if (c->samples < 2)
+ continue;
+
+ ac->samples = 2; /* All what matters for display. */
+ ac->total_runtime += c->total_runtime;
+ ac->last_runtime += c->last_runtime;
+
+ for (i = 0; i < clients->num_classes; i++)
+ ac->val[i] += c->val[i];
+ }
+
+ aggregated->num_clients = num;
+ aggregated->active_clients = num;
+
+ return sort_clients(aggregated, client_cmp);
}
-static void scan_clients(struct clients *clients)
+static void free_clients(struct clients *clients)
+{
+ struct client *c;
+ unsigned int tmp;
+
+ for_each_client(clients, c, tmp)
+ free(c->val);
+
+ free(clients->client);
+ free(clients);
+}
+
+static bool aggregate_pids = true;
+
+static struct clients *scan_clients(struct clients *clients)
{
struct dirent *dent;
struct client *c;
@@ -1022,7 +1102,7 @@ static void scan_clients(struct clients *clients)
DIR *d;
if (!clients)
- return;
+ return clients;
for_each_client(clients, c, tmp) {
assert(c->status != PROBE);
@@ -1034,7 +1114,7 @@ static void scan_clients(struct clients *clients)
d = opendir(clients->sysfs_root);
if (!d)
- return;
+ return clients;
while ((dent = readdir(d)) != NULL) {
char name[24], pid[24];
@@ -1077,7 +1157,10 @@ static void scan_clients(struct clients *clients)
break;
}
- sort_clients(clients);
+ if (aggregate_pids)
+ return aggregated_clients(clients);
+ else
+ return sort_clients(clients, client_cmp);
}
static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
@@ -2227,6 +2310,13 @@ static void process_stdin(unsigned int timeout_us)
case 's':
select_client_sort();
break;
+ case 'H':
+ aggregate_pids ^= true;
+ if (aggregate_pids)
+ header_msg = "Aggregating clients.";
+ else
+ header_msg = "Showing individual clients.";
+ break;
};
}
}
@@ -2378,6 +2468,7 @@ int main(int argc, char **argv)
codename = igt_device_get_pretty_name(&card, false);
while (!stop_top) {
+ struct clients *disp_clients;
bool consumed = false;
int j, lines = 0;
struct winsize ws;
@@ -2400,7 +2491,7 @@ int main(int argc, char **argv)
pmu_sample(engines);
t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
- scan_clients(clients);
+ disp_clients = scan_clients(clients);
if (stop_top)
break;
@@ -2416,14 +2507,14 @@ int main(int argc, char **argv)
lines = print_engines(engines, t, lines, con_w, con_h);
- if (clients) {
+ if (disp_clients) {
int class_w;
- lines = print_clients_header(clients, lines,
+ lines = print_clients_header(disp_clients, lines,
con_w, con_h,
&class_w);
- for_each_client(clients, c, j) {
+ for_each_client(disp_clients, c, j) {
assert(c->status != PROBE);
if (c->status != ALIVE)
break; /* Active clients are first in the array. */
@@ -2437,8 +2528,9 @@ int main(int argc, char **argv)
&class_w);
}
- lines = print_clients_footer(clients, t, lines,
- con_w, con_h);
+ lines = print_clients_footer(disp_clients, t,
+ lines, con_w,
+ con_h);
}
pops->close_struct();
@@ -2447,6 +2539,9 @@ int main(int argc, char **argv)
if (stop_top)
break;
+ if (disp_clients != clients)
+ free_clients(disp_clients);
+
if (output_mode == INTERACTIVE)
process_stdin(period_us);
else
--
2.27.0
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [Intel-gfx] [igt-dev] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default
2021-02-10 9:37 ` [Intel-gfx] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default Tvrtko Ursulin
@ 2021-02-10 10:35 ` Chris Wilson
2021-02-10 10:35 ` Chris Wilson
` (2 subsequent siblings)
3 siblings, 0 replies; 20+ messages in thread
From: Chris Wilson @ 2021-02-10 10:35 UTC (permalink / raw)
To: Tvrtko Ursulin, igt-dev; +Cc: Intel-gfx
Quoting Tvrtko Ursulin (2021-02-10 09:37:55)
> +static struct clients *aggregated_clients(struct clients *clients)
> +{
> + struct client *ac, *c, *cp = NULL;
> + struct clients *aggregated;
> + int tmp, num = 0;
> +
> + /* Sort by pid first to make it easy to aggregate while walking. */
> + sort_clients(clients, client_pid_cmp);
You could eliminate this tiny bit of duplication by always calling
aggregated_clients() and returning here for !aggregate_pids.
> + aggregated = calloc(1, sizeof(*clients));
> + assert(aggregated);
> +
> + ac = calloc(clients->num_clients, sizeof(*c));
> + assert(ac);
> +
> + aggregated->num_classes = clients->num_classes;
> + aggregated->class = clients->class;
> + aggregated->client = ac;
> +
> + for_each_client(clients, c, tmp) {
> + unsigned int i;
> +
> + if (c->status == FREE)
> + break;
> +
> + assert(c->status == ALIVE);
> +
> + if ((cp && c->pid != cp->pid) || !cp) {
> + ac = &aggregated->client[num];
> +
> + /* New pid. */
> + ac->clients = aggregated;
> + ac->status = ALIVE;
> + ac->id = ++num;
> + ac->pid = c->pid;
> + strcpy(ac->name, c->name);
> + strcpy(ac->print_name, c->print_name);
> + ac->engines = c->engines;
> + ac->val = calloc(clients->num_classes,
> + sizeof(ac->val[0]));
> + assert(ac->val);
> + ac->samples = 1;
> + }
> +
> + cp = c;
> +
> + if (c->samples < 2)
> + continue;
> +
> + ac->samples = 2; /* All what matters for display. */
> + ac->total_runtime += c->total_runtime;
> + ac->last_runtime += c->last_runtime;
> +
> + for (i = 0; i < clients->num_classes; i++)
> + ac->val[i] += c->val[i];
> + }
> +
> + aggregated->num_clients = num;
> + aggregated->active_clients = num;
> +
> + return sort_clients(aggregated, client_cmp);
> }
Ok, that works very well. Hmm. The sort order does seem a little jumpy
though. May I suggest ac->id = -c->pid; instead of num;
-Chris
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [igt-dev] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default
@ 2021-02-10 10:35 ` Chris Wilson
0 siblings, 0 replies; 20+ messages in thread
From: Chris Wilson @ 2021-02-10 10:35 UTC (permalink / raw)
To: Tvrtko Ursulin, igt-dev; +Cc: Intel-gfx, Tvrtko Ursulin
Quoting Tvrtko Ursulin (2021-02-10 09:37:55)
> +static struct clients *aggregated_clients(struct clients *clients)
> +{
> + struct client *ac, *c, *cp = NULL;
> + struct clients *aggregated;
> + int tmp, num = 0;
> +
> + /* Sort by pid first to make it easy to aggregate while walking. */
> + sort_clients(clients, client_pid_cmp);
You could eliminate this tiny bit of duplication by always calling
aggregated_clients() and returning here for !aggregate_pids.
> + aggregated = calloc(1, sizeof(*clients));
> + assert(aggregated);
> +
> + ac = calloc(clients->num_clients, sizeof(*c));
> + assert(ac);
> +
> + aggregated->num_classes = clients->num_classes;
> + aggregated->class = clients->class;
> + aggregated->client = ac;
> +
> + for_each_client(clients, c, tmp) {
> + unsigned int i;
> +
> + if (c->status == FREE)
> + break;
> +
> + assert(c->status == ALIVE);
> +
> + if ((cp && c->pid != cp->pid) || !cp) {
> + ac = &aggregated->client[num];
> +
> + /* New pid. */
> + ac->clients = aggregated;
> + ac->status = ALIVE;
> + ac->id = ++num;
> + ac->pid = c->pid;
> + strcpy(ac->name, c->name);
> + strcpy(ac->print_name, c->print_name);
> + ac->engines = c->engines;
> + ac->val = calloc(clients->num_classes,
> + sizeof(ac->val[0]));
> + assert(ac->val);
> + ac->samples = 1;
> + }
> +
> + cp = c;
> +
> + if (c->samples < 2)
> + continue;
> +
> + ac->samples = 2; /* All what matters for display. */
> + ac->total_runtime += c->total_runtime;
> + ac->last_runtime += c->last_runtime;
> +
> + for (i = 0; i < clients->num_classes; i++)
> + ac->val[i] += c->val[i];
> + }
> +
> + aggregated->num_clients = num;
> + aggregated->active_clients = num;
> +
> + return sort_clients(aggregated, client_cmp);
> }
Ok, that works very well. Hmm. The sort order does seem a little jumpy
though. May I suggest ac->id = -c->pid; instead of num;
-Chris
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [Intel-gfx] [igt-dev] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default
2021-02-10 10:35 ` Chris Wilson
@ 2021-02-10 10:55 ` Tvrtko Ursulin
-1 siblings, 0 replies; 20+ messages in thread
From: Tvrtko Ursulin @ 2021-02-10 10:55 UTC (permalink / raw)
To: Chris Wilson, igt-dev; +Cc: Intel-gfx
On 10/02/2021 10:35, Chris Wilson wrote:
> Quoting Tvrtko Ursulin (2021-02-10 09:37:55)
>> +static struct clients *aggregated_clients(struct clients *clients)
>> +{
>> + struct client *ac, *c, *cp = NULL;
>> + struct clients *aggregated;
>> + int tmp, num = 0;
>> +
>> + /* Sort by pid first to make it easy to aggregate while walking. */
>> + sort_clients(clients, client_pid_cmp);
>
> You could eliminate this tiny bit of duplication by always calling
> aggregated_clients() and returning here for !aggregate_pids.
Okay, I did something like that.
>> + aggregated = calloc(1, sizeof(*clients));
>> + assert(aggregated);
>> +
>> + ac = calloc(clients->num_clients, sizeof(*c));
>> + assert(ac);
>> +
>> + aggregated->num_classes = clients->num_classes;
>> + aggregated->class = clients->class;
>> + aggregated->client = ac;
>> +
>> + for_each_client(clients, c, tmp) {
>> + unsigned int i;
>> +
>> + if (c->status == FREE)
>> + break;
>> +
>> + assert(c->status == ALIVE);
>> +
>> + if ((cp && c->pid != cp->pid) || !cp) {
>> + ac = &aggregated->client[num];
>> +
>> + /* New pid. */
>> + ac->clients = aggregated;
>> + ac->status = ALIVE;
>> + ac->id = ++num;
>> + ac->pid = c->pid;
>> + strcpy(ac->name, c->name);
>> + strcpy(ac->print_name, c->print_name);
>> + ac->engines = c->engines;
>> + ac->val = calloc(clients->num_classes,
>> + sizeof(ac->val[0]));
>> + assert(ac->val);
>> + ac->samples = 1;
>> + }
>> +
>> + cp = c;
>> +
>> + if (c->samples < 2)
>> + continue;
>> +
>> + ac->samples = 2; /* All what matters for display. */
>> + ac->total_runtime += c->total_runtime;
>> + ac->last_runtime += c->last_runtime;
>> +
>> + for (i = 0; i < clients->num_classes; i++)
>> + ac->val[i] += c->val[i];
>> + }
>> +
>> + aggregated->num_clients = num;
>> + aggregated->active_clients = num;
>> +
>> + return sort_clients(aggregated, client_cmp);
>> }
>
> Ok, that works very well. Hmm. The sort order does seem a little jumpy
> though. May I suggest ac->id = -c->pid; instead of num;
Done it although I thought 1st pass being sort by pid already, num as id
would follow a stable order. I guess your point was inversion to
preserve order when cycling sort modes?
Regards,
Tvrtko
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [igt-dev] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default
@ 2021-02-10 10:55 ` Tvrtko Ursulin
0 siblings, 0 replies; 20+ messages in thread
From: Tvrtko Ursulin @ 2021-02-10 10:55 UTC (permalink / raw)
To: Chris Wilson, igt-dev; +Cc: Intel-gfx, Tvrtko Ursulin
On 10/02/2021 10:35, Chris Wilson wrote:
> Quoting Tvrtko Ursulin (2021-02-10 09:37:55)
>> +static struct clients *aggregated_clients(struct clients *clients)
>> +{
>> + struct client *ac, *c, *cp = NULL;
>> + struct clients *aggregated;
>> + int tmp, num = 0;
>> +
>> + /* Sort by pid first to make it easy to aggregate while walking. */
>> + sort_clients(clients, client_pid_cmp);
>
> You could eliminate this tiny bit of duplication by always calling
> aggregated_clients() and returning here for !aggregate_pids.
Okay, I did something like that.
>> + aggregated = calloc(1, sizeof(*clients));
>> + assert(aggregated);
>> +
>> + ac = calloc(clients->num_clients, sizeof(*c));
>> + assert(ac);
>> +
>> + aggregated->num_classes = clients->num_classes;
>> + aggregated->class = clients->class;
>> + aggregated->client = ac;
>> +
>> + for_each_client(clients, c, tmp) {
>> + unsigned int i;
>> +
>> + if (c->status == FREE)
>> + break;
>> +
>> + assert(c->status == ALIVE);
>> +
>> + if ((cp && c->pid != cp->pid) || !cp) {
>> + ac = &aggregated->client[num];
>> +
>> + /* New pid. */
>> + ac->clients = aggregated;
>> + ac->status = ALIVE;
>> + ac->id = ++num;
>> + ac->pid = c->pid;
>> + strcpy(ac->name, c->name);
>> + strcpy(ac->print_name, c->print_name);
>> + ac->engines = c->engines;
>> + ac->val = calloc(clients->num_classes,
>> + sizeof(ac->val[0]));
>> + assert(ac->val);
>> + ac->samples = 1;
>> + }
>> +
>> + cp = c;
>> +
>> + if (c->samples < 2)
>> + continue;
>> +
>> + ac->samples = 2; /* All what matters for display. */
>> + ac->total_runtime += c->total_runtime;
>> + ac->last_runtime += c->last_runtime;
>> +
>> + for (i = 0; i < clients->num_classes; i++)
>> + ac->val[i] += c->val[i];
>> + }
>> +
>> + aggregated->num_clients = num;
>> + aggregated->active_clients = num;
>> +
>> + return sort_clients(aggregated, client_cmp);
>> }
>
> Ok, that works very well. Hmm. The sort order does seem a little jumpy
> though. May I suggest ac->id = -c->pid; instead of num;
Done it although I thought 1st pass being sort by pid already, num as id
would follow a stable order. I guess your point was inversion to
preserve order when cycling sort modes?
Regards,
Tvrtko
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [Intel-gfx] [igt-dev] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default
2021-02-10 10:55 ` Tvrtko Ursulin
(?)
@ 2021-02-10 11:03 ` Chris Wilson
-1 siblings, 0 replies; 20+ messages in thread
From: Chris Wilson @ 2021-02-10 11:03 UTC (permalink / raw)
To: Tvrtko Ursulin, igt-dev; +Cc: Intel-gfx
Quoting Tvrtko Ursulin (2021-02-10 10:55:44)
>
> On 10/02/2021 10:35, Chris Wilson wrote:
> > Quoting Tvrtko Ursulin (2021-02-10 09:37:55)
> > Ok, that works very well. Hmm. The sort order does seem a little jumpy
> > though. May I suggest ac->id = -c->pid; instead of num;
>
> Done it although I thought 1st pass being sort by pid already, num as id
> would follow a stable order. I guess your point was inversion to
> preserve order when cycling sort modes?
I thought that makes more sense for 'aggregate-by-pid', that it should
either be in ascending/descending pid order for idle.
It just felt very jumpy; it may have been tiny amount of %busy, but I
suspected it may have been slightly unstable sorting with changing
clients.
-Chris
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx
^ permalink raw reply [flat|nested] 20+ messages in thread
* [Intel-gfx] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default
2021-02-10 9:37 ` [Intel-gfx] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default Tvrtko Ursulin
2021-02-10 9:44 ` Tvrtko Ursulin
2021-02-10 10:35 ` Chris Wilson
@ 2021-02-10 10:53 ` Tvrtko Ursulin
2021-02-10 11:06 ` [igt-dev] " Chris Wilson
2021-02-10 11:12 ` [igt-dev] " Tvrtko Ursulin
3 siblings, 1 reply; 20+ messages in thread
From: Tvrtko Ursulin @ 2021-02-10 10:53 UTC (permalink / raw)
To: igt-dev; +Cc: Intel-gfx
From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Implement a default view where clients are aggregated by their PID.
Toggled by pressing 'H' similar to top(1).
v2:
* Fix memory leak.
v3:
* Do not allow sort by client id in aggregated mode.
* Tweak sort criteria and sorting decisions. (Chris)
Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
man/intel_gpu_top.rst | 1 +
tools/intel_gpu_top.c | 130 +++++++++++++++++++++++++++++++++++++-----
2 files changed, 118 insertions(+), 13 deletions(-)
diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst
index b145d85c0440..20658e291db0 100644
--- a/man/intel_gpu_top.rst
+++ b/man/intel_gpu_top.rst
@@ -58,6 +58,7 @@ Supported keys:
'n' Toggle display of numeric client busyness overlay.
's' Toggle between sort modes (runtime, total runtime, pid, client id).
'i' Toggle display of clients which used no GPU time.
+ 'H' Toggle between per PID aggregation and individual clients.
DEVICE SELECTION
================
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index 63ef77056341..695a8cb397ae 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -979,17 +979,18 @@ static int client_pid_cmp(const void *_a, const void *_b)
static int (*client_cmp)(const void *, const void *) = client_last_cmp;
-static void sort_clients(struct clients *clients)
+static struct clients *sort_clients(struct clients *clients,
+ int (*cmp)(const void *, const void *))
{
unsigned int active, free;
struct client *c;
int tmp;
if (!clients)
- return;
+ return clients;
qsort(clients->client, clients->num_clients, sizeof(*clients->client),
- client_cmp);
+ cmp);
/* Trim excessive array space. */
active = 0;
@@ -1011,9 +1012,95 @@ static void sort_clients(struct clients *clients)
sizeof(*c));
}
}
+
+ return clients;
}
-static void scan_clients(struct clients *clients)
+static bool aggregate_pids = true;
+
+static struct clients *display_clients(struct clients *clients)
+{
+ struct client *ac, *c, *cp = NULL;
+ struct clients *aggregated;
+ int tmp, num = 0;
+
+ if (!aggregate_pids)
+ return sort_clients(clients, client_cmp);
+
+ /* Sort by pid first to make it easy to aggregate while walking. */
+ sort_clients(clients, client_pid_cmp);
+
+ aggregated = calloc(1, sizeof(*clients));
+ assert(aggregated);
+
+ ac = calloc(clients->num_clients, sizeof(*c));
+ assert(ac);
+
+ aggregated->num_classes = clients->num_classes;
+ aggregated->class = clients->class;
+ aggregated->client = ac;
+
+ for_each_client(clients, c, tmp) {
+ unsigned int i;
+
+ if (c->status == FREE)
+ break;
+
+ assert(c->status == ALIVE);
+
+ if ((cp && c->pid != cp->pid) || !cp) {
+ ac = &aggregated->client[num++];
+
+ /* New pid. */
+ ac->clients = aggregated;
+ ac->status = ALIVE;
+ ac->id = -c->pid;
+ ac->pid = c->pid;
+ ac->busy_root = -1;
+ ac->sysfs_root = -1;
+ strcpy(ac->name, c->name);
+ strcpy(ac->print_name, c->print_name);
+ ac->engines = c->engines;
+ ac->val = calloc(clients->num_classes,
+ sizeof(ac->val[0]));
+ assert(ac->val);
+ ac->samples = 1;
+ }
+
+ cp = c;
+
+ if (c->samples < 2)
+ continue;
+
+ ac->samples = 2; /* All what matters for display. */
+ ac->total_runtime += c->total_runtime;
+ ac->last_runtime += c->last_runtime;
+
+ for (i = 0; i < clients->num_classes; i++)
+ ac->val[i] += c->val[i];
+ }
+
+ aggregated->num_clients = num;
+ aggregated->active_clients = num;
+
+ return sort_clients(aggregated, client_cmp);
+}
+
+static void free_clients(struct clients *clients)
+{
+ struct client *c;
+ unsigned int tmp;
+
+ for_each_client(clients, c, tmp) {
+ free(c->val);
+ free(c->last);
+ }
+
+ free(clients->client);
+ free(clients);
+}
+
+static struct clients *scan_clients(struct clients *clients)
{
struct dirent *dent;
struct client *c;
@@ -1022,7 +1109,7 @@ static void scan_clients(struct clients *clients)
DIR *d;
if (!clients)
- return;
+ return clients;
for_each_client(clients, c, tmp) {
assert(c->status != PROBE);
@@ -1034,7 +1121,7 @@ static void scan_clients(struct clients *clients)
d = opendir(clients->sysfs_root);
if (!d)
- return;
+ return clients;
while ((dent = readdir(d)) != NULL) {
char name[24], pid[24];
@@ -1077,7 +1164,7 @@ static void scan_clients(struct clients *clients)
break;
}
- sort_clients(clients);
+ return display_clients(clients);
}
static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
@@ -2177,11 +2264,16 @@ static void select_client_sort(void)
};
static unsigned int client_sort;
+bump:
if (++client_sort >= ARRAY_SIZE(cmp))
client_sort = 0;
client_cmp = cmp[client_sort].cmp;
header_msg = cmp[client_sort].msg;
+
+ /* Sort by client id makes no sense with pid aggregation. */
+ if (aggregate_pids && client_cmp == client_id_cmp)
+ goto bump;
}
static void process_stdin(unsigned int timeout_us)
@@ -2227,6 +2319,13 @@ static void process_stdin(unsigned int timeout_us)
case 's':
select_client_sort();
break;
+ case 'H':
+ aggregate_pids ^= true;
+ if (aggregate_pids)
+ header_msg = "Aggregating clients.";
+ else
+ header_msg = "Showing individual clients.";
+ break;
};
}
}
@@ -2378,6 +2477,7 @@ int main(int argc, char **argv)
codename = igt_device_get_pretty_name(&card, false);
while (!stop_top) {
+ struct clients *disp_clients;
bool consumed = false;
int j, lines = 0;
struct winsize ws;
@@ -2400,7 +2500,7 @@ int main(int argc, char **argv)
pmu_sample(engines);
t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
- scan_clients(clients);
+ disp_clients = scan_clients(clients);
if (stop_top)
break;
@@ -2416,14 +2516,14 @@ int main(int argc, char **argv)
lines = print_engines(engines, t, lines, con_w, con_h);
- if (clients) {
+ if (disp_clients) {
int class_w;
- lines = print_clients_header(clients, lines,
+ lines = print_clients_header(disp_clients, lines,
con_w, con_h,
&class_w);
- for_each_client(clients, c, j) {
+ for_each_client(disp_clients, c, j) {
assert(c->status != PROBE);
if (c->status != ALIVE)
break; /* Active clients are first in the array. */
@@ -2437,8 +2537,9 @@ int main(int argc, char **argv)
&class_w);
}
- lines = print_clients_footer(clients, t, lines,
- con_w, con_h);
+ lines = print_clients_footer(disp_clients, t,
+ lines, con_w,
+ con_h);
}
pops->close_struct();
@@ -2447,6 +2548,9 @@ int main(int argc, char **argv)
if (stop_top)
break;
+ if (disp_clients != clients)
+ free_clients(disp_clients);
+
if (output_mode == INTERACTIVE)
process_stdin(period_us);
else
--
2.27.0
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [Intel-gfx] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default
2021-02-10 10:53 ` [Intel-gfx] " Tvrtko Ursulin
@ 2021-02-10 11:06 ` Chris Wilson
0 siblings, 0 replies; 20+ messages in thread
From: Chris Wilson @ 2021-02-10 11:06 UTC (permalink / raw)
To: Tvrtko Ursulin, igt-dev; +Cc: Intel-gfx
Quoting Tvrtko Ursulin (2021-02-10 10:53:43)
> +static struct clients *display_clients(struct clients *clients)
> +{
> + struct client *ac, *c, *cp = NULL;
> + struct clients *aggregated;
> + int tmp, num = 0;
> +
> + if (!aggregate_pids)
> + return sort_clients(clients, client_cmp);
Still two calls to return sort_clients(foo, client_cmp) in this function
:) [a clients = aggregated; after processing would merge the two paths].
Reviewed-by: Chris Wilson <chris@chris-wilson.co.uk>
-Chris
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [igt-dev] [Intel-gfx] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default
@ 2021-02-10 11:06 ` Chris Wilson
0 siblings, 0 replies; 20+ messages in thread
From: Chris Wilson @ 2021-02-10 11:06 UTC (permalink / raw)
To: Tvrtko Ursulin, igt-dev; +Cc: Intel-gfx
Quoting Tvrtko Ursulin (2021-02-10 10:53:43)
> +static struct clients *display_clients(struct clients *clients)
> +{
> + struct client *ac, *c, *cp = NULL;
> + struct clients *aggregated;
> + int tmp, num = 0;
> +
> + if (!aggregate_pids)
> + return sort_clients(clients, client_cmp);
Still two calls to return sort_clients(foo, client_cmp) in this function
:) [a clients = aggregated; after processing would merge the two paths].
Reviewed-by: Chris Wilson <chris@chris-wilson.co.uk>
-Chris
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev
^ permalink raw reply [flat|nested] 20+ messages in thread
* [Intel-gfx] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default
2021-02-10 9:37 ` [Intel-gfx] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default Tvrtko Ursulin
@ 2021-02-10 11:12 ` Tvrtko Ursulin
2021-02-10 10:35 ` Chris Wilson
` (2 subsequent siblings)
3 siblings, 0 replies; 20+ messages in thread
From: Tvrtko Ursulin @ 2021-02-10 11:12 UTC (permalink / raw)
To: igt-dev; +Cc: Intel-gfx
From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Implement a default view where clients are aggregated by their PID.
Toggled by pressing 'H' similar to top(1).
v2:
* Fix memory leak.
v3:
* Do not allow sort by client id in aggregated mode.
* Tweak sort criteria and sorting decisions. (Chris)
v4:
* More tweaks to code flow. (Chris)
Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
man/intel_gpu_top.rst | 1 +
tools/intel_gpu_top.c | 133 +++++++++++++++++++++++++++++++++++++-----
2 files changed, 121 insertions(+), 13 deletions(-)
diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst
index b145d85c0440..20658e291db0 100644
--- a/man/intel_gpu_top.rst
+++ b/man/intel_gpu_top.rst
@@ -58,6 +58,7 @@ Supported keys:
'n' Toggle display of numeric client busyness overlay.
's' Toggle between sort modes (runtime, total runtime, pid, client id).
'i' Toggle display of clients which used no GPU time.
+ 'H' Toggle between per PID aggregation and individual clients.
DEVICE SELECTION
================
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index 63ef77056341..e7f696110be8 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -979,17 +979,18 @@ static int client_pid_cmp(const void *_a, const void *_b)
static int (*client_cmp)(const void *, const void *) = client_last_cmp;
-static void sort_clients(struct clients *clients)
+static struct clients *sort_clients(struct clients *clients,
+ int (*cmp)(const void *, const void *))
{
unsigned int active, free;
struct client *c;
int tmp;
if (!clients)
- return;
+ return clients;
qsort(clients->client, clients->num_clients, sizeof(*clients->client),
- client_cmp);
+ cmp);
/* Trim excessive array space. */
active = 0;
@@ -1011,9 +1012,98 @@ static void sort_clients(struct clients *clients)
sizeof(*c));
}
}
+
+ return clients;
+}
+
+static bool aggregate_pids = true;
+
+static struct clients *display_clients(struct clients *clients)
+{
+ struct client *ac, *c, *cp = NULL;
+ struct clients *aggregated;
+ int tmp, num = 0;
+
+ if (!aggregate_pids)
+ goto out;
+
+ /* Sort by pid first to make it easy to aggregate while walking. */
+ sort_clients(clients, client_pid_cmp);
+
+ aggregated = calloc(1, sizeof(*clients));
+ assert(aggregated);
+
+ ac = calloc(clients->num_clients, sizeof(*c));
+ assert(ac);
+
+ aggregated->num_classes = clients->num_classes;
+ aggregated->class = clients->class;
+ aggregated->client = ac;
+
+ for_each_client(clients, c, tmp) {
+ unsigned int i;
+
+ if (c->status == FREE)
+ break;
+
+ assert(c->status == ALIVE);
+
+ if ((cp && c->pid != cp->pid) || !cp) {
+ ac = &aggregated->client[num++];
+
+ /* New pid. */
+ ac->clients = aggregated;
+ ac->status = ALIVE;
+ ac->id = -c->pid;
+ ac->pid = c->pid;
+ ac->busy_root = -1;
+ ac->sysfs_root = -1;
+ strcpy(ac->name, c->name);
+ strcpy(ac->print_name, c->print_name);
+ ac->engines = c->engines;
+ ac->val = calloc(clients->num_classes,
+ sizeof(ac->val[0]));
+ assert(ac->val);
+ ac->samples = 1;
+ }
+
+ cp = c;
+
+ if (c->samples < 2)
+ continue;
+
+ ac->samples = 2; /* All what matters for display. */
+ ac->total_runtime += c->total_runtime;
+ ac->last_runtime += c->last_runtime;
+
+ for (i = 0; i < clients->num_classes; i++)
+ ac->val[i] += c->val[i];
+ }
+
+ aggregated->num_clients = num;
+ aggregated->active_clients = num;
+
+ clients = aggregated;
+
+out:
+ return sort_clients(clients, client_cmp);
}
-static void scan_clients(struct clients *clients)
+static void free_clients(struct clients *clients)
+{
+ struct client *c;
+ unsigned int tmp;
+
+ for_each_client(clients, c, tmp) {
+ free(c->val);
+ free(c->last);
+ }
+
+ free(clients->client);
+ free(clients);
+}
+
+static struct clients *scan_clients(struct clients *clients)
{
struct dirent *dent;
struct client *c;
@@ -1022,7 +1112,7 @@ static void scan_clients(struct clients *clients)
DIR *d;
if (!clients)
- return;
+ return clients;
for_each_client(clients, c, tmp) {
assert(c->status != PROBE);
@@ -1034,7 +1124,7 @@ static void scan_clients(struct clients *clients)
d = opendir(clients->sysfs_root);
if (!d)
- return;
+ return clients;
while ((dent = readdir(d)) != NULL) {
char name[24], pid[24];
@@ -1077,7 +1167,7 @@ static void scan_clients(struct clients *clients)
break;
}
- sort_clients(clients);
+ return display_clients(clients);
}
static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
@@ -2177,11 +2267,16 @@ static void select_client_sort(void)
};
static unsigned int client_sort;
+bump:
if (++client_sort >= ARRAY_SIZE(cmp))
client_sort = 0;
client_cmp = cmp[client_sort].cmp;
header_msg = cmp[client_sort].msg;
+
+ /* Sort by client id makes no sense with pid aggregation. */
+ if (aggregate_pids && client_cmp == client_id_cmp)
+ goto bump;
}
static void process_stdin(unsigned int timeout_us)
@@ -2227,6 +2322,13 @@ static void process_stdin(unsigned int timeout_us)
case 's':
select_client_sort();
break;
+ case 'H':
+ aggregate_pids ^= true;
+ if (aggregate_pids)
+ header_msg = "Aggregating clients.";
+ else
+ header_msg = "Showing individual clients.";
+ break;
};
}
}
@@ -2378,6 +2480,7 @@ int main(int argc, char **argv)
codename = igt_device_get_pretty_name(&card, false);
while (!stop_top) {
+ struct clients *disp_clients;
bool consumed = false;
int j, lines = 0;
struct winsize ws;
@@ -2400,7 +2503,7 @@ int main(int argc, char **argv)
pmu_sample(engines);
t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
- scan_clients(clients);
+ disp_clients = scan_clients(clients);
if (stop_top)
break;
@@ -2416,14 +2519,14 @@ int main(int argc, char **argv)
lines = print_engines(engines, t, lines, con_w, con_h);
- if (clients) {
+ if (disp_clients) {
int class_w;
- lines = print_clients_header(clients, lines,
+ lines = print_clients_header(disp_clients, lines,
con_w, con_h,
&class_w);
- for_each_client(clients, c, j) {
+ for_each_client(disp_clients, c, j) {
assert(c->status != PROBE);
if (c->status != ALIVE)
break; /* Active clients are first in the array. */
@@ -2437,8 +2540,9 @@ int main(int argc, char **argv)
&class_w);
}
- lines = print_clients_footer(clients, t, lines,
- con_w, con_h);
+ lines = print_clients_footer(disp_clients, t,
+ lines, con_w,
+ con_h);
}
pops->close_struct();
@@ -2447,6 +2551,9 @@ int main(int argc, char **argv)
if (stop_top)
break;
+ if (disp_clients != clients)
+ free_clients(disp_clients);
+
if (output_mode == INTERACTIVE)
process_stdin(period_us);
else
--
2.27.0
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [igt-dev] [PATCH i-g-t 2/3] intel_gpu_top: Aggregate clients by PID by default
@ 2021-02-10 11:12 ` Tvrtko Ursulin
0 siblings, 0 replies; 20+ messages in thread
From: Tvrtko Ursulin @ 2021-02-10 11:12 UTC (permalink / raw)
To: igt-dev; +Cc: Intel-gfx, Tvrtko Ursulin
From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Implement a default view where clients are aggregated by their PID.
Toggled by pressing 'H' similar to top(1).
v2:
* Fix memory leak.
v3:
* Do not allow sort by client id in aggregated mode.
* Tweak sort criteria and sorting decisions. (Chris)
v4:
* More tweaks to code flow. (Chris)
Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
man/intel_gpu_top.rst | 1 +
tools/intel_gpu_top.c | 133 +++++++++++++++++++++++++++++++++++++-----
2 files changed, 121 insertions(+), 13 deletions(-)
diff --git a/man/intel_gpu_top.rst b/man/intel_gpu_top.rst
index b145d85c0440..20658e291db0 100644
--- a/man/intel_gpu_top.rst
+++ b/man/intel_gpu_top.rst
@@ -58,6 +58,7 @@ Supported keys:
'n' Toggle display of numeric client busyness overlay.
's' Toggle between sort modes (runtime, total runtime, pid, client id).
'i' Toggle display of clients which used no GPU time.
+ 'H' Toggle between per PID aggregation and individual clients.
DEVICE SELECTION
================
diff --git a/tools/intel_gpu_top.c b/tools/intel_gpu_top.c
index 63ef77056341..e7f696110be8 100644
--- a/tools/intel_gpu_top.c
+++ b/tools/intel_gpu_top.c
@@ -979,17 +979,18 @@ static int client_pid_cmp(const void *_a, const void *_b)
static int (*client_cmp)(const void *, const void *) = client_last_cmp;
-static void sort_clients(struct clients *clients)
+static struct clients *sort_clients(struct clients *clients,
+ int (*cmp)(const void *, const void *))
{
unsigned int active, free;
struct client *c;
int tmp;
if (!clients)
- return;
+ return clients;
qsort(clients->client, clients->num_clients, sizeof(*clients->client),
- client_cmp);
+ cmp);
/* Trim excessive array space. */
active = 0;
@@ -1011,9 +1012,98 @@ static void sort_clients(struct clients *clients)
sizeof(*c));
}
}
+
+ return clients;
+}
+
+static bool aggregate_pids = true;
+
+static struct clients *display_clients(struct clients *clients)
+{
+ struct client *ac, *c, *cp = NULL;
+ struct clients *aggregated;
+ int tmp, num = 0;
+
+ if (!aggregate_pids)
+ goto out;
+
+ /* Sort by pid first to make it easy to aggregate while walking. */
+ sort_clients(clients, client_pid_cmp);
+
+ aggregated = calloc(1, sizeof(*clients));
+ assert(aggregated);
+
+ ac = calloc(clients->num_clients, sizeof(*c));
+ assert(ac);
+
+ aggregated->num_classes = clients->num_classes;
+ aggregated->class = clients->class;
+ aggregated->client = ac;
+
+ for_each_client(clients, c, tmp) {
+ unsigned int i;
+
+ if (c->status == FREE)
+ break;
+
+ assert(c->status == ALIVE);
+
+ if ((cp && c->pid != cp->pid) || !cp) {
+ ac = &aggregated->client[num++];
+
+ /* New pid. */
+ ac->clients = aggregated;
+ ac->status = ALIVE;
+ ac->id = -c->pid;
+ ac->pid = c->pid;
+ ac->busy_root = -1;
+ ac->sysfs_root = -1;
+ strcpy(ac->name, c->name);
+ strcpy(ac->print_name, c->print_name);
+ ac->engines = c->engines;
+ ac->val = calloc(clients->num_classes,
+ sizeof(ac->val[0]));
+ assert(ac->val);
+ ac->samples = 1;
+ }
+
+ cp = c;
+
+ if (c->samples < 2)
+ continue;
+
+ ac->samples = 2; /* All what matters for display. */
+ ac->total_runtime += c->total_runtime;
+ ac->last_runtime += c->last_runtime;
+
+ for (i = 0; i < clients->num_classes; i++)
+ ac->val[i] += c->val[i];
+ }
+
+ aggregated->num_clients = num;
+ aggregated->active_clients = num;
+
+ clients = aggregated;
+
+out:
+ return sort_clients(clients, client_cmp);
}
-static void scan_clients(struct clients *clients)
+static void free_clients(struct clients *clients)
+{
+ struct client *c;
+ unsigned int tmp;
+
+ for_each_client(clients, c, tmp) {
+ free(c->val);
+ free(c->last);
+ }
+
+ free(clients->client);
+ free(clients);
+}
+
+static struct clients *scan_clients(struct clients *clients)
{
struct dirent *dent;
struct client *c;
@@ -1022,7 +1112,7 @@ static void scan_clients(struct clients *clients)
DIR *d;
if (!clients)
- return;
+ return clients;
for_each_client(clients, c, tmp) {
assert(c->status != PROBE);
@@ -1034,7 +1124,7 @@ static void scan_clients(struct clients *clients)
d = opendir(clients->sysfs_root);
if (!d)
- return;
+ return clients;
while ((dent = readdir(d)) != NULL) {
char name[24], pid[24];
@@ -1077,7 +1167,7 @@ static void scan_clients(struct clients *clients)
break;
}
- sort_clients(clients);
+ return display_clients(clients);
}
static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
@@ -2177,11 +2267,16 @@ static void select_client_sort(void)
};
static unsigned int client_sort;
+bump:
if (++client_sort >= ARRAY_SIZE(cmp))
client_sort = 0;
client_cmp = cmp[client_sort].cmp;
header_msg = cmp[client_sort].msg;
+
+ /* Sort by client id makes no sense with pid aggregation. */
+ if (aggregate_pids && client_cmp == client_id_cmp)
+ goto bump;
}
static void process_stdin(unsigned int timeout_us)
@@ -2227,6 +2322,13 @@ static void process_stdin(unsigned int timeout_us)
case 's':
select_client_sort();
break;
+ case 'H':
+ aggregate_pids ^= true;
+ if (aggregate_pids)
+ header_msg = "Aggregating clients.";
+ else
+ header_msg = "Showing individual clients.";
+ break;
};
}
}
@@ -2378,6 +2480,7 @@ int main(int argc, char **argv)
codename = igt_device_get_pretty_name(&card, false);
while (!stop_top) {
+ struct clients *disp_clients;
bool consumed = false;
int j, lines = 0;
struct winsize ws;
@@ -2400,7 +2503,7 @@ int main(int argc, char **argv)
pmu_sample(engines);
t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
- scan_clients(clients);
+ disp_clients = scan_clients(clients);
if (stop_top)
break;
@@ -2416,14 +2519,14 @@ int main(int argc, char **argv)
lines = print_engines(engines, t, lines, con_w, con_h);
- if (clients) {
+ if (disp_clients) {
int class_w;
- lines = print_clients_header(clients, lines,
+ lines = print_clients_header(disp_clients, lines,
con_w, con_h,
&class_w);
- for_each_client(clients, c, j) {
+ for_each_client(disp_clients, c, j) {
assert(c->status != PROBE);
if (c->status != ALIVE)
break; /* Active clients are first in the array. */
@@ -2437,8 +2540,9 @@ int main(int argc, char **argv)
&class_w);
}
- lines = print_clients_footer(clients, t, lines,
- con_w, con_h);
+ lines = print_clients_footer(disp_clients, t,
+ lines, con_w,
+ con_h);
}
pops->close_struct();
@@ -2447,6 +2551,9 @@ int main(int argc, char **argv)
if (stop_top)
break;
+ if (disp_clients != clients)
+ free_clients(disp_clients);
+
if (output_mode == INTERACTIVE)
process_stdin(period_us);
else
--
2.27.0
_______________________________________________
igt-dev mailing list
igt-dev@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/igt-dev
^ permalink raw reply related [flat|nested] 20+ messages in thread