diff packages/kernel/current/tests/stress_threads.c @ 2:443894e2e912 ecos-v1_2_1-release

Block commit of eCos version 1.2.1
author jlarmour
date Tue, 11 May 1999 12:24:34 +0000
parents
children 1d7f19c9e4d1
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/packages/kernel/current/tests/stress_threads.c
@@ -0,0 +1,559 @@
+//==========================================================================
+//
+//        stress_threads.cxx
+//
+//        Basic thread stress test
+//
+//==========================================================================
+//####COPYRIGHTBEGIN####
+//
+// -------------------------------------------
+// The contents of this file are subject to the Cygnus eCos Public License
+// Version 1.0 (the "License"); you may not use this file except in
+// compliance with the License.  You may obtain a copy of the License at
+// http://sourceware.cygnus.com/ecos
+// 
+// Software distributed under the License is distributed on an "AS IS"
+// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
+// License for the specific language governing rights and limitations under
+// the License.
+// 
+// The Original Code is eCos - Embedded Cygnus Operating System, released
+// September 30, 1998.
+// 
+// The Initial Developer of the Original Code is Cygnus.  Portions created
+// by Cygnus are Copyright (C) 1998,1999 Cygnus Solutions.  All Rights Reserved.
+// -------------------------------------------
+//
+//####COPYRIGHTEND####
+//==========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author(s):     rosalia
+// Contributors:  rosalia
+// Date:          1999-04-13
+// Description:   Very simple thread stress test, with some memory
+//                allocation and alarm handling.
+//####DESCRIPTIONEND####
+
+#include <pkgconf/system.h>
+#include <cyg/infra/testcase.h>
+
+#include <cyg/hal/hal_arch.h>
+
+#if defined(CYGPKG_KERNEL) && defined(CYGPKG_IO) && defined(CYGPKG_LIBC)
+
+#include <pkgconf/kernel.h>
+#include <pkgconf/libc.h>
+
+#if defined(CYGFUN_KERNEL_API_C)
+
+#include <cyg/kernel/kapi.h>
+
+#ifdef CYGPKG_LIBC_STDIO
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(CYGPKG_LIBM)
+
+#include <math.h>
+#include <assert.h>
+
+#if defined(CYGFUN_KERNEL_THREADS_TIMER)
+#if defined(CYGPKG_LIBC_MALLOC)
+
+/* if TIME_LIMIT is defined, it represents the number of seconds this
+   test should last; if it is undefined the test will go forever */
+#define DEATH_TIME_LIMIT 15
+/*  #undef DEATH_TIME_LIMIT */
+
+#define STACK_SIZE (CYGNUM_HAL_STACK_SIZE_TYPICAL)
+#define STACK_SIZE2 (8*1024 + CYGNUM_HAL_STACK_SIZE_TYPICAL)
+
+#define N_CLIENTS 4
+#define N_LISTENERS 4
+#define MAX_HANDLERS 19
+
+#if (CYGNUM_KERNEL_SCHED_PRIORITIES < (N_CLIENTS+N_LISTENERS+MAX_HANDLERS))
+# error "not enough priorities available"
+#endif
+
+/* if we use the bitmap scheduler we must make sure we don't use the
+   same priority more than once, so we must store those already in use */
+static char priority_in_use[N_CLIENTS+N_LISTENERS+MAX_HANDLERS];
+
+/* now declare (and allocate space for) some kernel objects, like the
+   threads we will use */
+cyg_thread client_thread_s[N_CLIENTS];
+cyg_thread listener_thread_s[N_LISTENERS];
+cyg_thread handler_thread_s[MAX_HANDLERS];
+
+/* space for stacks for all threads */
+char client_stack[N_CLIENTS][STACK_SIZE];
+char listener_stack[N_LISTENERS][STACK_SIZE];
+char handler_stack[MAX_HANDLERS][STACK_SIZE2];
+
+/* now the handles for the threads */
+cyg_handle_t clientH[N_CLIENTS];
+cyg_handle_t listenerH[N_LISTENERS];
+cyg_handle_t handlerH[MAX_HANDLERS];
+
+#ifdef DEATH_TIME_LIMIT
+/* how many client threads have been killed by the death handler */
+int n_clients_killed = 0;
+#endif /* DEATH_TIME_LIMIT */
+
+/* and now variables for the procedure which is the thread */
+cyg_thread_entry_t client_program, listener_program, handler_program;
+
+/* a few mutexes used in the code */
+cyg_mutex_t client_request_lock, handler_slot_lock, statistics_print_lock;
+
+/* a global variable with which the client and server coordinate */
+int client_makes_request = 0;
+
+/* indicates that it's time to print out a report */
+int time_to_report = 0;
+
+/*** now application-specific variables ***/
+/* an array that stores whether the handler threads are in use */
+int handler_thread_in_use[MAX_HANDLERS];
+
+/***** statistics-gathering variables *****/
+struct s_statistics {
+  /* store the number of times each handler has been invoked */
+  unsigned long handler_invocation_histogram[MAX_HANDLERS];
+
+  /* store how many times malloc has been attempted and how many times
+     it has failed */
+  unsigned long malloc_tries, malloc_failures;
+
+  /* how many threads have been created */
+  unsigned long thread_creations, thread_exits;
+};
+
+struct s_statistics statistics;
+
+/* some function prototypes; those with the sc_ prefix are
+   "statistics-collecting" versions of the cyg_ primitives */
+void sc_thread_create(
+    cyg_addrword_t        sched_info,           /* scheduling info (eg pri)  */
+    cyg_thread_entry_t  *entry,                 /* entry point function      */
+    cyg_addrword_t        entry_data,           /* entry data                */
+    char                *name,                  /* optional thread name      */
+    void                *stack_base,            /* stack base, NULL = alloc  */
+    cyg_ucount32        stack_size,             /* stack size, 0 = default   */
+    cyg_handle_t        *handle,                /* returned thread handle    */
+    cyg_thread          *thread                 /* put thread here           */
+);
+void sc_thread_exit(void);
+
+int get_handler_slot(cyg_handle_t current_threadH);
+void perform_stressful_tasks(void);
+void permute_array(char a[], int size, int seed);
+void setup_death_alarm(cyg_addrword_t data, cyg_handle_t *deathHp,
+                       cyg_alarm *death_alarm_p, int *killed_p);
+void handle_death(cyg_handle_t deathH, cyg_handle_t alarmH);
+void print_statistics(void);
+
+/* we need to declare the alarm handling function (which is defined
+   below), so that we can pass it to cyg_alarm_initialize() */
+cyg_alarm_t report_alarm_func, death_alarm_func;
+
+/* handle and alarm for the report alarm */
+cyg_handle_t report_alarmH, counterH, system_clockH;
+cyg_alarm report_alarm;
+
+/* we install our own startup routine which sets up threads */
+void cyg_user_start(void)
+{
+  int i;
+
+  CYG_TEST_INIT();
+  CYG_TEST_INFO("# Entering stress's cyg_user_start() function");
+
+  cyg_mutex_init(&client_request_lock);
+  cyg_mutex_init(&statistics_print_lock);
+
+  /* initialize statistics */
+  memset(&statistics, 0, sizeof(statistics));
+
+  /* initialize all handler threads to not be in use */
+  for (i = 0; i < MAX_HANDLERS; ++i) {
+    handler_thread_in_use[i] = 0;
+  }
+  for (i = 0; i < N_CLIENTS; ++i) {
+    int prio;
+    char thread_name[20];
+    sprintf(thread_name, "client-%02d", i);
+    prio = i;
+    sc_thread_create(prio, client_program, (cyg_addrword_t) i,
+                      thread_name, (void *) client_stack[i], STACK_SIZE,
+                      &(clientH[i]), &client_thread_s[i]);
+    priority_in_use[prio] = 1;
+  }
+  for (i = 0; i < N_LISTENERS; ++i) {
+    int prio;
+    char thread_name[20];
+    sprintf(thread_name, "listener-%02d", i);
+    prio = N_CLIENTS + i;
+    sc_thread_create(prio, listener_program, (cyg_addrword_t) i,
+                      thread_name, (void *) listener_stack[i], STACK_SIZE,
+                      &listenerH[i], &listener_thread_s[i]);
+    priority_in_use[prio] = 1;
+  }
+
+  for (i = 0; i < N_CLIENTS; ++i) {
+    cyg_thread_resume(clientH[i]);
+  }
+  for (i = 0; i < N_LISTENERS; ++i) {
+    cyg_thread_resume(listenerH[i]);
+  }
+
+  /* set up the alarm which gives periodic wakeups to say "time to
+     print a report */
+  system_clockH = cyg_real_time_clock();
+  cyg_clock_to_counter(system_clockH, &counterH);
+
+  cyg_alarm_create(counterH, report_alarm_func,
+                   (cyg_addrword_t) 4000,
+                   &report_alarmH, &report_alarm);
+  if (cyg_test_is_simulator) {
+    cyg_alarm_initialize(report_alarmH, cyg_current_time()+300, 400);
+  } else {
+    cyg_alarm_initialize(report_alarmH, cyg_current_time()+300, 4000);
+  }
+
+}
+
+/* client_program() -- an obnoxious client which makes a lot of requests */
+void client_program(cyg_addrword_t data)
+{
+  int delay;
+
+  cyg_handle_t counterH, deathH, system_clockH;
+  cyg_alarm death_alarm;
+  int is_dead = 0;
+
+  setup_death_alarm(data, &deathH, &death_alarm, &is_dead);
+
+  printf("# Starting client-%d\n", (int) data);
+
+  system_clockH = cyg_real_time_clock();
+  cyg_clock_to_counter(system_clockH, &counterH);
+
+  for (;;) {
+    delay = (rand() % 3);
+
+    /* now send a request to the server */
+    cyg_mutex_lock(&client_request_lock); {
+      ++client_makes_request;
+/*        printf("client_makes_request        %d\n", client_makes_request); */
+    } cyg_mutex_unlock(&client_request_lock);
+
+    cyg_thread_delay(10+delay);
+/*      cyg_thread_delay(0); */
+#ifdef DEATH_TIME_LIMIT
+    if (is_dead) {
+      handle_death(deathH, report_alarmH);
+    }
+#endif /* DEATH_TIME_LIMIT */
+  }
+}
+
+/* listener_program() -- listens for a request and spawns a handler to
+   take care of the request */
+void listener_program(cyg_addrword_t data)
+{
+/*   int message = (int) data; */
+  int handler_slot;
+
+  printf("# Beginning execution; thread data is %d\n", (int) data);
+
+  for (;;) {
+#ifdef DEATH_TIME_LIMIT
+    /* as an extra task, the listener sees if all clients have been
+       killed off, so it can report that the test is over */
+    if (n_clients_killed == N_CLIENTS) {
+      n_clients_killed = -1;    /* so we don't call this again */
+      CYG_TEST_PASS_FINISH("Kernel thread stress test OK");
+    }
+#endif /* DEATH_TIME_LIMIT */
+    if (client_makes_request > 0) {
+      int prio;
+      /*          printf("just got a request from a client (count = %d)\n", */
+      /*                 client_makes_request); */
+      cyg_mutex_lock(&client_request_lock); {
+        --client_makes_request;
+      } cyg_mutex_unlock(&client_request_lock);
+
+      handler_slot = get_handler_slot(listenerH[(int) data]);
+      prio = N_CLIENTS+N_LISTENERS+handler_slot;
+      priority_in_use[prio] = 1;
+      sc_thread_create(prio, handler_program,
+                       (cyg_addrword_t) handler_slot,
+                       "handler", (void *) handler_stack[handler_slot],
+                       STACK_SIZE2, &handlerH[handler_slot],
+                       &handler_thread_s[handler_slot]);
+      cyg_thread_resume(handlerH[handler_slot]);
+      ++statistics.handler_invocation_histogram[handler_slot];
+    }
+    cyg_thread_delay(1);
+  }
+}
+
+/* handler_program() -- is spawned to handle each incoming request */
+void handler_program(cyg_addrword_t data)
+{
+  /* here is where we perform specific stressful tasks */
+  perform_stressful_tasks();
+
+  if (time_to_report) {
+    time_to_report = 0;
+    print_statistics();
+  }
+
+  cyg_thread_delay(4 + (int) (0.5*log(1.0 + fabs((rand() % 1000000)))));
+/*    cyg_thread_delay(0); */
+
+  /* lock the scheduler before we declare this thread slot available
+     and quit; note that cyg_thread_exit() will unlock the scheduler
+     as many times as necessary */
+  cyg_mutex_lock(&handler_slot_lock); {
+    handler_thread_in_use[data] = 0;
+    priority_in_use[N_CLIENTS + N_LISTENERS + (int) data] = 0;
+  } cyg_mutex_unlock(&handler_slot_lock);
+  /* FIXME: could there be a race condition right here? I unlock the
+     scheduler, so I could get pre-empted out, but meanwhile I have
+     declared this thread available again.  must fix it. */
+  sc_thread_exit();
+}
+
+/* look for an available handler thread */
+int get_handler_slot(cyg_handle_t current_threadH)
+{
+  int i;
+  int found = 0;
+
+  while (!found) {
+    for (i = 0; i < MAX_HANDLERS; ++i) {
+      cyg_mutex_lock(&handler_slot_lock); {
+        if (!handler_thread_in_use[i]) {
+          found = 1;
+          handler_thread_in_use[i] = 1;
+        }
+      } cyg_mutex_unlock(&handler_slot_lock);
+      if (found) {
+        break;
+      }
+#ifdef DEATH_TIME_LIMIT
+      /* must do a check here to see if all clients have been killed,
+         since otherwise we might end up in an infinite loop */
+      if (n_clients_killed == N_CLIENTS) {
+	n_clients_killed = -1;    /* so we don't call this again */
+	CYG_TEST_PASS_FINISH("Kernel thread stress test OK");
+      }
+#endif
+    }
+    cyg_thread_delay(1);
+  }
+  return i;
+}
+
+/* do things which will stress the system */
+void perform_stressful_tasks()
+{
+#define MAX_MALLOCED_SPACES 100  /* do this many mallocs at most */
+#define MALLOCED_BASE_SIZE 1    /* basic size in bytes */
+  char *spaces[MAX_MALLOCED_SPACES];
+  unsigned int i;
+
+  cyg_mutex_t tmp_lock;
+
+  cyg_uint8 pool_space[10][100];
+  cyg_handle_t mempool_handles[10];
+  cyg_mempool_fix mempool_objects[10];
+
+  cyg_mutex_init(&tmp_lock);
+
+  /* here I use malloc, which uses the kernel's variable memory pools.
+     note that malloc/free is a bit simple-minded here: it does not
+     try to really fragment things, and it does not try to make the
+     allocation/deallocation concurrent with other thread execution
+     (although I'm about to throw in a yield()) */
+  for (i = 0; i < MAX_MALLOCED_SPACES; ++i) {
+    ++statistics.malloc_tries;
+/*      spaces[i] = (char *) malloc(((int)(sqrt(i*2.0))+1)*MALLOCED_BASE_SIZE); */
+    spaces[i] = (char *) malloc(((int)i*2.0+1)*MALLOCED_BASE_SIZE);
+    if (i % 100 == 0) {
+      cyg_thread_yield();
+    }
+  }
+
+  /* now free it all up */
+  for (i = 0; i < MAX_MALLOCED_SPACES; ++i) {
+    if (spaces[i] != NULL) {
+      unsigned int j;
+      for (j = 0; j < (i*2+1)*MALLOCED_BASE_SIZE; ++j) {
+	spaces[i][j] = 0xAA;	/* write a bit pattern */
+      }
+      free(spaces[i]);
+    } else {
+      ++statistics.malloc_failures;
+    }
+  }
+  /* now allocate and then free some fixed-size memory pools; for
+     now this is simple-minded because it does not have many threads
+     sharing the memory pools and racing for memory. */
+  for (i = 0; i < 10; ++i) {
+    cyg_mempool_fix_create(pool_space[i], 100, (i+1)*3,
+                           &mempool_handles[i], &mempool_objects[i]);
+  }
+
+  for (i = 0; i < 10; ++i) {
+    spaces[i] = cyg_mempool_fix_try_alloc(mempool_handles[i]);
+  }
+
+  for (i = 0; i < 10; ++i) {
+    if (spaces[i]) {
+      cyg_mempool_fix_delete(mempool_handles[i]);
+    }
+  }
+
+  cyg_mutex_destroy(&tmp_lock);
+}
+
+/* report_alarm_func() is invoked as an alarm handler, so it should be
+   quick and simple.  in this case it sets a global flag which is
+   checked by threads. */
+void report_alarm_func(cyg_handle_t alarmH, cyg_addrword_t data)
+{
+    time_to_report = 1;
+}
+
+/* this sets up death alarms. it gets the handle and alarm from the
+   caller, since they must persist for the life of the alarm */
+void setup_death_alarm(cyg_addrword_t data, cyg_handle_t *deathHp,
+                       cyg_alarm *death_alarm_p, int *killed_p)
+{
+#ifdef DEATH_TIME_LIMIT
+  cyg_handle_t system_clockH, counterH;
+  cyg_resolution_t rtc_res;
+
+  system_clockH = cyg_real_time_clock();
+  cyg_clock_to_counter(system_clockH, &counterH);
+
+  cyg_alarm_create(counterH, death_alarm_func,
+                   (cyg_addrword_t) killed_p,
+                   deathHp, death_alarm_p);
+  rtc_res = cyg_clock_get_resolution(system_clockH);
+  {
+    cyg_tick_count_t tick_delay;
+    tick_delay = (long long)
+      ((1000000000.0*rtc_res.divisor)
+       *((double)DEATH_TIME_LIMIT)/((double)rtc_res.dividend));
+    if ( cyg_test_is_simulator )
+        tick_delay /= 10;
+    cyg_alarm_initialize(*deathHp, cyg_current_time() + tick_delay, 0);
+  }
+#endif /* DEATH_TIME_LIMIT */
+}
+
+/* death_alarm_func() is the alarm handler that kills the current
+   thread after a specified timeout. It does so by setting a flag the
+   thread is constantly checking. */
+void death_alarm_func(cyg_handle_t alarmH, cyg_addrword_t data)
+{
+  int *killed_p;
+  killed_p = (int *) data;
+  *killed_p = 1;
+}
+
+#ifdef DEATH_TIME_LIMIT
+/* handle_death is called by a client thread when it dies; it kills
+   off the alarm */
+void handle_death(cyg_handle_t deathH, cyg_handle_t alarmH)
+{
+  ++n_clients_killed;
+  cyg_alarm_delete(deathH);
+  cyg_alarm_delete(alarmH);
+  cyg_thread_exit();
+}
+#endif /* DEATH_TIME_LIMIT */
+
+/* now I write the sc_ versions of the cyg_functions */
+void sc_thread_create(
+    cyg_addrword_t        sched_info,          /* scheduling info (eg pri)  */
+    cyg_thread_entry_t  *entry,                /* entry point function      */
+    cyg_addrword_t        entry_data,          /* entry data                */
+    char                *name,                 /* optional thread name      */
+    void                *stack_base,           /* stack base, NULL = alloc  */
+    cyg_ucount32        stack_size,            /* stack size, 0 = default   */
+    cyg_handle_t        *handle,               /* returned thread handle    */
+    cyg_thread          *thread                /* put thread here           */
+)
+{
+/*printf("Creating a thread -- priority is %lu\n", (unsigned long) sched_info);*/
+/*    fflush(stdout); */
+  ++statistics.thread_creations;
+  cyg_thread_create(sched_info, entry, entry_data, name,
+                    stack_base, stack_size, handle, thread);
+}
+
+void sc_thread_exit()
+{
+/*    printf("exiting\n"); */
+/*    fflush(stdout); */
+  ++statistics.thread_exits;
+  cyg_thread_exit();
+}
+
+void print_statistics(void)
+{
+  int i;
+
+  cyg_mutex_lock(&statistics_print_lock); {
+    printf("Handler-invocations: ");
+    for (i = 0; i < MAX_HANDLERS; ++i) {
+      printf("%4lu ", statistics.handler_invocation_histogram[i]);
+    }
+    printf("\n");
+    printf("malloc()-tries/failures: -- %7lu %7lu\n",
+           statistics.malloc_tries, statistics.malloc_failures);
+    printf("client_makes_request:       %d\n", client_makes_request);
+  } cyg_mutex_unlock(&statistics_print_lock);
+}
+
+#else /* CYGSEM_LIBC_MALLOC */
+# define N_A_MSG "this test needs malloc"
+#endif /* CYGSEM_LIBC_MALLOC */
+
+#else /* CYGFUN_KERNEL_THREADS_TIMER */
+# define N_A_MSG "this test needs kernel threads timer"
+#endif /* CYGFUN_KERNEL_THREADS_TIMER */
+
+#else /* CYGPKG_LIBM */
+# define N_A_MSG "this test needs libm"
+#endif /* CYGPKG_LIBM */
+
+#else /* CYGSEM_LIBC_STDIO */
+# define N_A_MSG "this test needs stdio"
+#endif /* CYGSEM_LIBC_STDIO */
+
+#else // def CYGFUN_KERNEL_API_C
+# define N_A_MSG "this test needs Kernel C API"
+#endif
+
+#else // def CYGPKG_KERNEL && CYGPKG_IO && CYGPKG_LIBC
+# define N_A_MSG "this tests needs Kernel, libc and IO"
+#endif
+
+#ifdef N_A_MSG
+externC void
+cyg_start( void )
+{
+    CYG_TEST_INIT();
+    CYG_TEST_NA( N_A_MSG);
+}
+#endif // N_A_MSG