nptl: New test nptl/tst-stack-usage

Message ID 87mupg9et1.fsf@oldenburg2.str.redhat.com
State New, archived
Headers

Commit Message

Florian Weimer Dec. 8, 2018, 4:14 p.m. UTC
  Results for x86-64 with AVX2 (kernel-4.19.5-300.fc29):

info: stack size: 524288
info: PTHREAD_STACK_MIN: 16384
info: MINSIGSTKSZ: 2048
info: SIGSTKSZ: 8192
info: measuring stack usage, no signal delivery
  lowest modification offset ranges from 519904 to 519920
  highest modification offset ranges from 523675 to 524288
  minimum modified range: 3755
  maximum modified range: 4384
info: measuring stack usage, with signal delivery
  lowest modification offset ranges from 517944 to 517945
  highest modification offset ranges from 523675 to 524288
  minimum modified range: 5731
  maximum modified range: 6344
info: estimated signal delivery stack overhead: 1960 bytes
info: measuring stack usage, no-cancel test
  lowest modification offset ranges from 519720 to 519736
  highest modification offset ranges from 523675 to 524288
  minimum modified range: 3939
  maximum modified range: 4568
info: measuring stack usage, cancellation test
  lowest modification offset ranges from 516048 to 516049
  highest modification offset ranges from 523675 to 524288
  minimum modified range: 7627
  maximum modified range: 8240
info: estimated cancellation stack overhead: 3672 bytes

Results for x86-64 with AVX-512F (kernel-4.18.0-48.el8):

info: stack size: 524288
info: PTHREAD_STACK_MIN: 16384
info: MINSIGSTKSZ: 2048
info: SIGSTKSZ: 8192
info: measuring stack usage, no signal delivery
  lowest modification offset ranges from 519920 to 519936
  highest modification offset ranges from 523675 to 524288
  minimum modified range: 3739
  maximum modified range: 4368
info: measuring stack usage, with signal delivery
  lowest modification offset ranges from 516472 to 516473
  highest modification offset ranges from 523675 to 524288
  minimum modified range: 7202
  maximum modified range: 7816
info: estimated signal delivery stack overhead: 3448 bytes
info: measuring stack usage, no-cancel test
  lowest modification offset ranges from 519736 to 519752
  highest modification offset ranges from 523675 to 524288
  minimum modified range: 3923
  maximum modified range: 4552
info: measuring stack usage, cancellation test
  lowest modification offset ranges from 514448 to 518120
  highest modification offset ranges from 523675 to 524288
  minimum modified range: 6168
  maximum modified range: 9840
info: estimated cancellation stack overhead: 5288 bytes

Results for POWER8 BE (kernel-3.10.0-957.1.3.el7):

info: stack size: 524288
info: PTHREAD_STACK_MIN: 131072
info: MINSIGSTKSZ: 4096
info: SIGSTKSZ: 16384
info: measuring stack usage, no signal delivery
  lowest modification offset ranges from 517824 to 517826
  highest modification offset ranges from 522528 to 522624
  minimum modified range: 4702
  maximum modified range: 4800
info: measuring stack usage, with signal delivery
  lowest modification offset ranges from 513232 to 513234
  highest modification offset ranges from 522528 to 522624
  minimum modified range: 9294
  maximum modified range: 9392
info: estimated signal delivery stack overhead: 4592 bytes
info: measuring stack usage, no-cancel test
  lowest modification offset ranges from 516976 to 516978
  highest modification offset ranges from 522528 to 522624
  minimum modified range: 5550
  maximum modified range: 5648
info: measuring stack usage, cancellation test
  lowest modification offset ranges from 503600 to 503602
  highest modification offset ranges from 522528 to 522624
  minimum modified range: 18926
  maximum modified range: 19024
info: estimated cancellation stack overhead: 13376 bytes

Results for POWER9 LE (kernel-4.18.0-48.el8):

info: stack size: 524288
info: PTHREAD_STACK_MIN: 131072
info: MINSIGSTKSZ: 4096
info: SIGSTKSZ: 16384
info: measuring stack usage, no signal delivery
  lowest modification offset ranges from 517984 to 517985
  highest modification offset ranges from 522526 to 522624
  minimum modified range: 4542
  maximum modified range: 4640
info: measuring stack usage, with signal delivery
  lowest modification offset ranges from 513472 to 513473
  highest modification offset ranges from 522526 to 522624
  minimum modified range: 9054
  maximum modified range: 9152
info: estimated signal delivery stack overhead: 4512 bytes
info: measuring stack usage, no-cancel test
  lowest modification offset ranges from 517296 to 517297
  highest modification offset ranges from 522526 to 522624
  minimum modified range: 5230
  maximum modified range: 5328
info: measuring stack usage, cancellation test
  lowest modification offset ranges from 506144 to 506145
  highest modification offset ranges from 522526 to 522624
  minimum modified range: 16381
  maximum modified range: 16480
info: estimated cancellation stack overhead: 11152 bytes

2018-12-08  Florian Weimer  <fweimer@redhat.com>

	* nptl/tst-stack-usage.c: New file.
	* nptl/Makefile (tests): Add tst-stack-usage.
	(tst-stack-usage): Link with -z now.
	* support/Makefile (libsupport-routines): Add
	xpthread_attr_setstack.
	* support/xthread.h (xpthread_attr_setstack): Declare.
	* support/xpthread_attr_setstack.c: New file.
  

Patch

diff --git a/nptl/Makefile b/nptl/Makefile
index 34ae830276..f994db59cc 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -318,7 +318,8 @@  tests = tst-attr1 tst-attr2 tst-attr3 tst-default-attr \
 	tst-minstack-throw \
 	tst-cnd-basic tst-mtx-trylock tst-cnd-broadcast \
 	tst-cnd-timedwait tst-thrd-detach tst-mtx-basic tst-thrd-sleep \
-	tst-mtx-recursive tst-tss-basic tst-call-once tst-mtx-timedlock
+	tst-mtx-recursive tst-tss-basic tst-call-once tst-mtx-timedlock \
+	tst-stack-usage
 
 tests-internal := tst-rwlock19 tst-rwlock20 \
 		  tst-sem11 tst-sem12 tst-sem13 \
@@ -722,6 +723,9 @@  $(objpfx)tst-audit-threads: $(objpfx)tst-audit-threads-mod2.so
 $(objpfx)tst-audit-threads.out: $(objpfx)tst-audit-threads-mod1.so
 tst-audit-threads-ENV = LD_AUDIT=$(objpfx)tst-audit-threads-mod1.so
 
+# Disable lazy binding to avoid measuring ld.so stack overhead.
+LDFLAGS-tst-stack-usage = -Wl,-z,now
+
 # The tests here better do not run in parallel
 ifneq ($(filter %tests,$(MAKECMDGOALS)),)
 .NOTPARALLEL:
diff --git a/nptl/tst-stack-usage.c b/nptl/tst-stack-usage.c
new file mode 100644
index 0000000000..80f853f595
--- /dev/null
+++ b/nptl/tst-stack-usage.c
@@ -0,0 +1,238 @@ 
+/* Measure the stack size used by signal delivery and thread cancellation.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/xsignal.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+#include <sys/mman.h>
+
+/* Initialized by do_test below.  */
+static unsigned char *stack_base;
+static size_t stack_size;
+static pthread_attr_t stack_attr;
+static sigset_t sigusr1_set;
+
+/* Capture the range of a variable.  */
+struct range_statistics
+{
+  size_t min;
+  size_t max;
+};
+
+/* Initialize a struct range_statistics object.  */
+static inline struct range_statistics
+range_statistics_create (void)
+{
+  return (struct range_statistics) { .min = -1, .max = 0 };
+}
+
+/* Update the statistics object with the value.  */
+static inline void
+range_statistics_update (struct range_statistics *stats, size_t value)
+{
+  if (value < stats->min)
+    stats->min = value;
+  if (value > stats->max)
+    stats->max = value;
+}
+
+/* Used to capture stack usage information.  */
+static int canary_value;
+struct range_statistics untouched_low_stats;
+struct range_statistics untouched_high_stats;
+struct range_statistics touched_range_stats;
+
+
+/* Examine the stack at stack_base for traces of usage.  This must be
+   called from the thread start routine because during thread
+   destruction, madvise is called on the user-supplied stack with
+   MADV_DONTNEED.  */
+static void *
+capture_stack_usage (void)
+{
+  /* Computed the untouched poritions of the stack.  */
+  size_t untouched_low = 0;
+  while (untouched_low < stack_size
+         && stack_base[untouched_low] == canary_value)
+    ++untouched_low;
+  size_t untouched_high = stack_size;
+  while (untouched_high > 0
+         && stack_base[untouched_high - 1] == canary_value)
+    --untouched_high;
+  TEST_VERIFY (untouched_high > untouched_low);
+
+  range_statistics_update (&untouched_low_stats, untouched_low);
+  range_statistics_update (&untouched_high_stats, untouched_high);
+  size_t used = untouched_high - untouched_low;
+  range_statistics_update (&touched_range_stats, used);
+  return NULL;
+}
+
+/* Wrapper for capture_stack_usage for use from a cancellation
+   handler.  */
+static void
+capture_on_cancel (void *closure)
+{
+  capture_stack_usage ();
+}
+
+/* Attempt to determine stack usage by running THREADFUNC.  Print
+   statistics and return the best estimate for the stack usage.  If
+   OPERATE is not NULL, call it on the thread in question before
+   joining it.  */
+static size_t
+measure_stack (const char *what, void *(*threadfunc) (void *),
+               void (*operate) (pthread_t))
+{
+  untouched_low_stats = range_statistics_create ();
+  untouched_high_stats = range_statistics_create ();
+  touched_range_stats = range_statistics_create ();
+
+  /* Use different canary values to cover usage scenarios which write
+     the same value as the canary.  */
+  for (canary_value = 0; canary_value <= 255; ++canary_value)
+    {
+      memset (stack_base, canary_value, stack_size);
+      pthread_t thr = xpthread_create (&stack_attr, threadfunc, operate);
+      if (operate != NULL)
+        operate (thr);
+      xpthread_join (thr);
+    }
+
+  printf ("info: measuring stack usage, %s\n", what);
+  printf ("  lowest modification offset ranges from %zu to %zu\n",
+         untouched_low_stats.min, untouched_low_stats.max);
+  printf ("  highest modification offset ranges from %zu to %zu\n",
+         untouched_high_stats.min, untouched_high_stats.max);
+  printf ("  minimum modified range: %zu\n", touched_range_stats.min);
+  printf ("  maximum modified range: %zu\n", touched_range_stats.max);
+
+  return touched_range_stats.max;
+}
+
+static void
+noop_signal_handler (int signo)
+{
+}
+
+/* Used as the SIGUSR1 signal handler.  */
+static void *
+noop_threadfunc (void *closure)
+{
+  return capture_stack_usage ();
+}
+
+/* Thread start routine for signal handler test.  */
+static void *
+signal_threadfunc (void *closure)
+{
+  xpthread_sigmask (SIG_UNBLOCK, &sigusr1_set, NULL);
+  /* Delivery is synchronous, to this thread, because the signal is
+     blocked on all other threads.  */
+  raise (SIGUSR1);
+  return capture_stack_usage ();
+}
+
+/* Used for synchronization in the cancellation thread.  */
+static pthread_barrier_t barrier;
+
+/* Cause cancel_threadfunc to return normally.  */
+static void
+operate_no_cancel (pthread_t thr)
+{
+  xpthread_barrier_wait (&barrier);
+}
+
+/* Cancel the cancel_threadfunc thread.  */
+static void
+operate_cancel (pthread_t thr)
+{
+  xpthread_barrier_wait (&barrier);
+  xpthread_cancel (thr);
+}
+
+/* Thread start routine for cancellation tests.  */
+static void *
+cancel_threadfunc (void *closure)
+{
+  /* Synchronization here prevents cancellation in the thread
+     initialization.  */
+  xpthread_barrier_wait (&barrier);
+  pthread_cleanup_push (capture_on_cancel, NULL);
+  if (closure == operate_cancel)
+    /* Wait for cancellation.  */
+    pause ();
+  pthread_cleanup_pop (1);
+  return NULL;
+}
+
+static int
+do_test (void)
+{
+  /* This should be large enough for all architectures.  */
+  stack_size = 512 * 1024;
+  printf ("info: stack size: %zu\n", stack_size);
+  printf ("info: PTHREAD_STACK_MIN: %zu\n", (size_t) PTHREAD_STACK_MIN);
+  printf ("info: MINSIGSTKSZ: %zu\n", (size_t) MINSIGSTKSZ);
+  printf ("info: SIGSTKSZ: %zu\n", (size_t) SIGSTKSZ);
+  TEST_VERIFY (stack_size >= PTHREAD_STACK_MIN);
+
+  stack_base = xmmap (NULL, stack_size, PROT_READ | PROT_WRITE,
+                      MAP_PRIVATE | MAP_ANONYMOUS, -1);
+  xpthread_attr_init (&stack_attr);
+  xpthread_attr_setstack (&stack_attr, stack_base, stack_size);
+
+  /* Block the signal on all threads, so that we can force delivery on
+     a specific thread.  */
+  sigemptyset (&sigusr1_set);
+  sigaddset (&sigusr1_set, SIGUSR1);
+  xpthread_sigmask (SIG_BLOCK, &sigusr1_set, NULL);
+  xsignal (SIGUSR1, noop_signal_handler);
+
+  size_t noop = measure_stack ("no signal delivery", noop_threadfunc, NULL);
+  TEST_VERIFY (noop <= PTHREAD_STACK_MIN);
+  size_t with_signal
+    = measure_stack ("with signal delivery", signal_threadfunc, NULL);
+  TEST_VERIFY (with_signal <= PTHREAD_STACK_MIN);
+  TEST_VERIFY_EXIT (with_signal >= noop);
+  printf ("info: estimated signal delivery stack overhead: %zu bytes\n",
+          with_signal - noop);
+
+  xpthread_barrier_init (&barrier, NULL, 2);
+  noop = measure_stack ("no-cancel test", cancel_threadfunc,
+                        operate_no_cancel);
+  TEST_VERIFY (noop <= PTHREAD_STACK_MIN);
+  size_t with_cancel = measure_stack ("cancellation test", cancel_threadfunc,
+                                      operate_cancel);
+  TEST_VERIFY (with_cancel <= PTHREAD_STACK_MIN);
+  xpthread_barrier_destroy (&barrier);
+  TEST_VERIFY_EXIT (with_cancel >= noop);
+  printf ("info: estimated cancellation stack overhead: %zu bytes\n",
+          with_cancel - noop);
+
+  xpthread_attr_destroy (&stack_attr);
+  xmunmap (stack_base, stack_size);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/support/Makefile b/support/Makefile
index 93a5143016..c322c5efac 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -103,6 +103,7 @@  libsupport-routines = \
   xpthread_attr_init \
   xpthread_attr_setdetachstate \
   xpthread_attr_setguardsize \
+  xpthread_attr_setstack \
   xpthread_attr_setstacksize \
   xpthread_barrier_destroy \
   xpthread_barrier_init \
diff --git a/support/xpthread_attr_setstack.c b/support/xpthread_attr_setstack.c
new file mode 100644
index 0000000000..8b7f6fe5f2
--- /dev/null
+++ b/support/xpthread_attr_setstack.c
@@ -0,0 +1,26 @@ 
+/* pthread_attr_setstack with error checking.
+   Copyright (C) 2017-2018 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <support/xthread.h>
+
+void
+xpthread_attr_setstack (pthread_attr_t *attr, void *addr, size_t size)
+{
+  xpthread_check_return ("pthread_attr_setstack",
+			 pthread_attr_setstack (attr, addr, size));
+}
diff --git a/support/xthread.h b/support/xthread.h
index 623f5ad0ac..209b41c2be 100644
--- a/support/xthread.h
+++ b/support/xthread.h
@@ -70,6 +70,7 @@  void xpthread_attr_setdetachstate (pthread_attr_t *attr,
 				   int detachstate);
 void xpthread_attr_setstacksize (pthread_attr_t *attr,
 				 size_t stacksize);
+void xpthread_attr_setstack (pthread_attr_t *attr, void *, size_t);
 void xpthread_attr_setguardsize (pthread_attr_t *attr,
 				 size_t guardsize);