diff options
| -rw-r--r-- | mutrace.c | 752 | ||||
| -rwxr-xr-x | mutrace.in | 48 | 
2 files changed, 716 insertions, 84 deletions
| @@ -105,13 +105,49 @@ struct mutex_info {          struct mutex_info *next;  }; +struct cond_info { +        pthread_cond_t *cond; + +        bool broken:1; +        bool realtime:1; +        bool dead:1; + +        unsigned n_wait_level; +        pthread_mutex_t *mutex; + +        unsigned n_wait; +        unsigned n_signal; +        unsigned n_broadcast; + +        unsigned n_wait_contended; /* number of wait() calls made while another +                                    * thread is already waiting on the cond */ +        unsigned n_signal_contended; /* number of signal() or broadcast() calls +                                      * made while no thread is waiting */ + +        uint64_t nsec_wait_total; +        uint64_t nsec_wait_max; + +        uint64_t nsec_wait_contended_total; +        uint64_t nsec_wait_contended_max; + +        uint64_t nsec_timestamp; +        struct stacktrace_info stacktrace; + +        unsigned id; + +        struct cond_info *next; +}; +  static unsigned hash_size = 3371; /* probably a good idea to pick a prime here */  static unsigned frames_max = 16; -static volatile unsigned n_broken = 0; +static volatile unsigned n_broken_mutexes = 0; +static volatile unsigned n_broken_conds = 0; +  static volatile unsigned n_collisions = 0;  static volatile unsigned n_self_contended = 0; +static unsigned show_n_wait_min = 1;  static unsigned show_n_locked_min = 1;  static unsigned show_n_read_locked_min = 0;  static unsigned show_n_contended_min = 0; @@ -128,6 +164,10 @@ static int (*real_pthread_mutex_lock)(pthread_mutex_t *mutex) = NULL;  static int (*real_pthread_mutex_trylock)(pthread_mutex_t *mutex) = NULL;  static int (*real_pthread_mutex_timedlock)(pthread_mutex_t *mutex, const struct timespec *abstime) = NULL;  static int (*real_pthread_mutex_unlock)(pthread_mutex_t *mutex) = NULL; +static int (*real_pthread_cond_init)(pthread_cond_t *cond, const pthread_condattr_t *attr) = NULL; +static int (*real_pthread_cond_destroy)(pthread_cond_t *cond) = NULL; +static int (*real_pthread_cond_signal)(pthread_cond_t *cond) = NULL; +static int (*real_pthread_cond_broadcast)(pthread_cond_t *cond) = NULL;  static int (*real_pthread_cond_wait)(pthread_cond_t *cond, pthread_mutex_t *mutex) = NULL;  static int (*real_pthread_cond_timedwait)(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) = NULL;  static int (*real_pthread_create)(pthread_t *newthread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) = NULL; @@ -150,6 +190,9 @@ static void (*real_backtrace_symbols_fd)(void *const *array, int size, int fd) =  static struct mutex_info **alive_mutexes = NULL, **dead_mutexes = NULL;  static pthread_mutex_t *mutexes_lock = NULL; +static struct cond_info **alive_conds = NULL, **dead_conds = NULL; +static pthread_mutex_t *conds_lock = NULL; +  static __thread bool recursive = false;  static volatile bool initialized = false; @@ -157,34 +200,34 @@ static volatile bool threads_existing = false;  static uint64_t nsec_timestamp_setup; -/* Must be kept in sync with summary_order_details. */ -typedef enum { -        ORDER_ID = 0, -        ORDER_N_LOCKED, -        ORDER_N_READ_LOCKED, -        ORDER_N_CONTENDED, -        ORDER_N_READ_CONTENDED, -        ORDER_N_OWNER_CHANGED, -        ORDER_NSEC_LOCKED_TOTAL, -        ORDER_NSEC_LOCKED_MAX, -        ORDER_NSEC_LOCKED_AVG, -        ORDER_NSEC_READ_LOCKED_TOTAL, -        ORDER_NSEC_READ_LOCKED_MAX, -        ORDER_NSEC_READ_LOCKED_AVG, -        ORDER_NSEC_CONTENDED_TOTAL, -        ORDER_NSEC_CONTENDED_AVG, -        ORDER_NSEC_READ_CONTENDED_TOTAL, -        ORDER_NSEC_READ_CONTENDED_AVG, -} SummaryOrder; -#define ORDER_INVALID ORDER_NSEC_READ_CONTENDED_AVG + 1 /* first invalid order */ -  typedef struct {          const char *command; /* as passed to --order command line argument */          const char *ui_string; /* as displayed by show_summary() */  } SummaryOrderDetails; -/* Must be kept in sync with SummaryOrder. */ -static const SummaryOrderDetails summary_order_details[] = { +/* Must be kept in sync with summary_mutex_order_details. */ +typedef enum { +        MUTEX_ORDER_ID = 0, +        MUTEX_ORDER_N_LOCKED, +        MUTEX_ORDER_N_READ_LOCKED, +        MUTEX_ORDER_N_CONTENDED, +        MUTEX_ORDER_N_READ_CONTENDED, +        MUTEX_ORDER_N_OWNER_CHANGED, +        MUTEX_ORDER_NSEC_LOCKED_TOTAL, +        MUTEX_ORDER_NSEC_LOCKED_MAX, +        MUTEX_ORDER_NSEC_LOCKED_AVG, +        MUTEX_ORDER_NSEC_READ_LOCKED_TOTAL, +        MUTEX_ORDER_NSEC_READ_LOCKED_MAX, +        MUTEX_ORDER_NSEC_READ_LOCKED_AVG, +        MUTEX_ORDER_NSEC_CONTENDED_TOTAL, +        MUTEX_ORDER_NSEC_CONTENDED_AVG, +        MUTEX_ORDER_NSEC_READ_CONTENDED_TOTAL, +        MUTEX_ORDER_NSEC_READ_CONTENDED_AVG, +} SummaryMutexOrder; +#define MUTEX_ORDER_INVALID MUTEX_ORDER_NSEC_READ_CONTENDED_AVG + 1 /* first invalid order */ + +/* Must be kept in sync with SummaryMutexOrder. */ +static const SummaryOrderDetails summary_mutex_order_details[] = {          { "id", "mutex number" },          { "n-locked", "(write) lock count" },          { "n-read-locked", "(read) lock count" }, @@ -203,9 +246,46 @@ static const SummaryOrderDetails summary_order_details[] = {          { "nsec-read-contended-avg", "average time (read) contended" },  }; -static SummaryOrder summary_order = ORDER_N_CONTENDED; +static SummaryMutexOrder summary_mutex_order = MUTEX_ORDER_N_CONTENDED; + +static SummaryMutexOrder summary_mutex_order_from_command(const char *command); + +/* Must be kept in sync with summary_cond_order_details. */ +typedef enum { +        COND_ORDER_ID = 0, +        COND_ORDER_N_WAIT, +        COND_ORDER_N_SIGNAL, +        COND_ORDER_N_BROADCAST, +        COND_ORDER_N_WAIT_CONTENDED, +        COND_ORDER_N_SIGNAL_CONTENDED, +        COND_ORDER_NSEC_WAIT_TOTAL, +        COND_ORDER_NSEC_WAIT_MAX, +        COND_ORDER_NSEC_WAIT_AVG, +        COND_ORDER_NSEC_WAIT_CONTENDED_TOTAL, +        COND_ORDER_NSEC_WAIT_CONTENDED_MAX, +        COND_ORDER_NSEC_WAIT_CONTENDED_AVG, +} SummaryCondOrder; +#define COND_ORDER_INVALID COND_ORDER_NSEC_WAIT_CONTENDED_AVG + 1 /* first invalid order */ + +/* Must be kept in sync with SummaryCondOrder. */ +static const SummaryOrderDetails summary_cond_order_details[] = { +        { "id", "condition variable number" }, +        { "n-wait", "wait count" }, +        { "n-signal", "signal count" }, +        { "n-broadcast", "broadcast count" }, +        { "n-wait-contended", "wait contention count" }, +        { "n-signal-contended", "signal contention count" }, +        { "nsec-wait-total", "total wait time" }, +        { "nsec-wait-max", "maximum wait time" }, +        { "nsec-wait-avg", "average wait time" }, +        { "nsec-wait-contended-total", "total contended wait time" }, +        { "nsec-wait-contended-max", "maximum contended wait time" }, +        { "nsec-wait-contended-avg", "average contended wait time" }, +}; + +static SummaryCondOrder summary_cond_order = COND_ORDER_N_WAIT_CONTENDED; -static SummaryOrder summary_order_from_command(const char *command); +static SummaryCondOrder summary_cond_order_from_command(const char *command);  static void setup(void) __attribute ((constructor));  static void shutdown(void) __attribute ((destructor)); @@ -321,6 +401,10 @@ static void load_functions(void) {          /* There's some kind of weird incompatibility problem causing           * pthread_cond_timedwait() to freeze if we don't ask for this           * explicit version of these functions */ +        LOAD_FUNC_VERSIONED(pthread_cond_init, "GLIBC_2.3.2"); +        LOAD_FUNC_VERSIONED(pthread_cond_destroy, "GLIBC_2.3.2"); +        LOAD_FUNC_VERSIONED(pthread_cond_signal, "GLIBC_2.3.2"); +        LOAD_FUNC_VERSIONED(pthread_cond_broadcast, "GLIBC_2.3.2");          LOAD_FUNC_VERSIONED(pthread_cond_wait, "GLIBC_2.3.2");          LOAD_FUNC_VERSIONED(pthread_cond_timedwait, "GLIBC_2.3.2"); @@ -388,6 +472,12 @@ static void setup(void) {          else                  frames_max = t; +        t = show_n_wait_min; +        if (parse_env("MUTRACE_WAIT_MIN", &t) < 0) +                fprintf(stderr, "mutrace: WARNING: Failed to parse $MUTRACE_WAIT_MIN.\n"); +        else +                show_n_wait_min = t; +          t = show_n_locked_min;          if (parse_env("MUTRACE_LOCKED_MIN", &t) < 0)                  fprintf(stderr, "mutrace: WARNING: Failed to parse $MUTRACE_LOCKED_MIN.\n"); @@ -424,13 +514,22 @@ static void setup(void) {          else                  show_n_max = t; -        s = getenv("MUTRACE_SUMMARY_ORDER"); +        s = getenv("MUTRACE_SUMMARY_MUTEX_ORDER"); +        if (s != NULL) { +                t = summary_mutex_order_from_command(s); +                if (t == MUTEX_ORDER_INVALID) +                        fprintf(stderr, "mutrace: WARNING: Failed to parse $MUTRACE_SUMMARY_MUTEX_ORDER.\n"); +                else +                        summary_mutex_order = t; +        } + +        s = getenv("MUTRACE_SUMMARY_COND_ORDER");          if (s != NULL) { -                t = summary_order_from_command(s); -                if (t == ORDER_INVALID) -                        fprintf(stderr, "mutrace: WARNING: Failed to parse $MUTRACE_SUMMARY_ORDER.\n"); +                t = summary_cond_order_from_command(s); +                if (t == COND_ORDER_INVALID) +                        fprintf(stderr, "mutrace: WARNING: Failed to parse $MUTRACE_SUMMARY_COND_ORDER.\n");                  else -                        summary_order = t; +                        summary_cond_order = t;          }          if (getenv("MUTRACE_TRAP")) @@ -439,6 +538,7 @@ static void setup(void) {          if (getenv("MUTRACE_TRACK_RT"))                  track_rt = true; +        /* Set up the mutex hash table. */          alive_mutexes = calloc(hash_size, sizeof(struct mutex_info*));          assert(alive_mutexes); @@ -454,6 +554,22 @@ static void setup(void) {                  assert(r == 0);          } +        /* Set up the cond hash table. */ +        alive_conds = calloc(hash_size, sizeof(struct cond_info*)); +        assert(alive_conds); + +        dead_conds = calloc(hash_size, sizeof(struct cond_info*)); +        assert(dead_conds); + +        conds_lock = malloc(hash_size * sizeof(pthread_mutex_t)); +        assert(conds_lock); + +        for (m = conds_lock, last = conds_lock+hash_size; m < last; m++) { +                r = real_pthread_mutex_init(m, NULL); + +                assert(r == 0); +        } +          /* Listen for SIGUSR1 and print out a summary of what's happened so far           * when we receive it. */          sigusr_action.sa_handler = sigusr1_cb; @@ -486,26 +602,58 @@ static unsigned long rwlock_hash(pthread_rwlock_t *rwlock) {          return u % hash_size;  } -static void lock_hash_mutex(unsigned u) { +static unsigned long cond_hash(pthread_cond_t *cond) { +        unsigned long u; + +        u = (unsigned long) cond; +        u /= sizeof(void*); + +        return u % hash_size; +} + +static void lock_hash(pthread_mutex_t *lock_array, unsigned u) {          int r; -        r = real_pthread_mutex_trylock(mutexes_lock + u); +        r = real_pthread_mutex_trylock(lock_array + u);          if (UNLIKELY(r == EBUSY)) {                  __sync_fetch_and_add(&n_self_contended, 1); -                r = real_pthread_mutex_lock(mutexes_lock + u); +                r = real_pthread_mutex_lock(lock_array + u);          }          assert(r == 0);  } -static void unlock_hash_mutex(unsigned u) { +static void unlock_hash(pthread_mutex_t *lock_array, unsigned u) {          int r; -        r = real_pthread_mutex_unlock(mutexes_lock + u); +        r = real_pthread_mutex_unlock(lock_array + u);          assert(r == 0);  } +#define lock_hash_mutex(u) lock_hash(mutexes_lock, u) +#define unlock_hash_mutex(u) unlock_hash(mutexes_lock, u) + +#define lock_hash_cond(u) lock_hash(conds_lock, u) +#define unlock_hash_cond(u) unlock_hash(conds_lock, u) + +#define _ORDER_CASE(TYPE, UCASE, lcase) \ +case TYPE##_ORDER_##UCASE: \ +        if (a->lcase != b->lcase) \ +                return a->lcase - b->lcase; \ +        break; +#define _ORDER_CASE_AVG(TYPE, UCASE, lcase, divisor) \ +case TYPE##_ORDER_##UCASE: { \ +        double a_avg = a->lcase / a->divisor, \ +               b_avg = b->lcase / b->divisor; \ +        if (!doubles_equal(a_avg, b_avg)) \ +                return ((a_avg - b_avg) < 0.0) ? -1 : 1; \ +        break; \ +} +#define STATIC_ORDER(lcase) \ +if (a->lcase != b->lcase) \ +        return a->lcase - b->lcase +  static int mutex_info_compare(const void *_a, const void *_b) {          const struct mutex_info                  *a = *(const struct mutex_info**) _a, @@ -513,20 +661,9 @@ static int mutex_info_compare(const void *_a, const void *_b) {          /* Order by the user's chosen ordering first, then fall back to a static           * ordering. */ -        switch (summary_order) { -                #define ORDER_CASE(UCASE, lcase) \ -                case ORDER_##UCASE: \ -                        if (a->lcase != b->lcase) \ -                                return a->lcase - b->lcase; \ -                        break; -                #define ORDER_CASE_AVG(UCASE, lcase, divisor) \ -                case ORDER_##UCASE: { \ -                        double a_avg = a->lcase / a->divisor, \ -                               b_avg = b->lcase / b->divisor; \ -                        if (!doubles_equal(a_avg, b_avg)) \ -                                return ((a_avg - b_avg) < 0.0) ? -1 : 1; \ -                        break; \ -                } +        switch (summary_mutex_order) { +                #define ORDER_CASE(UCASE, lcase) _ORDER_CASE(MUTEX, UCASE, lcase) +                #define ORDER_CASE_AVG(UCASE, lcase, divisor) _ORDER_CASE_AVG(MUTEX, UCASE, lcase, divisor)                  ORDER_CASE(ID, id)                  ORDER_CASE(N_LOCKED, n_locked[WRITE]) @@ -553,21 +690,60 @@ static int mutex_info_compare(const void *_a, const void *_b) {          }          /* Fall back to a static ordering. */ -        #define STATIC_ORDER(lcase) \ -        if (a->lcase != b->lcase) \ -                return a->lcase - b->lcase -          STATIC_ORDER(n_contended[WRITE]);          STATIC_ORDER(n_owner_changed);          STATIC_ORDER(n_locked[WRITE]);          STATIC_ORDER(nsec_locked_max[WRITE]); -        #undef STATIC_ORDER +        /* Let's make the output deterministic */ +        return a - b; +} + +static int cond_info_compare(const void *_a, const void *_b) { +        const struct cond_info +                *a = *(const struct cond_info**) _a, +                *b = *(const struct cond_info**) _b; + +        /* Order by the user's chosen ordering first, then fall back to a static +         * ordering. */ +        switch (summary_cond_order) { +                #define ORDER_CASE(UCASE, lcase) _ORDER_CASE(COND, UCASE, lcase) +                #define ORDER_CASE_AVG(UCASE, lcase, divisor) _ORDER_CASE_AVG(COND, UCASE, lcase, divisor) + +                ORDER_CASE(ID, id) +                ORDER_CASE(N_WAIT, n_wait) +                ORDER_CASE(N_SIGNAL, n_signal) +                ORDER_CASE(N_BROADCAST, n_broadcast) +                ORDER_CASE(N_WAIT_CONTENDED, n_wait_contended) +                ORDER_CASE(N_SIGNAL_CONTENDED, n_signal_contended) +                ORDER_CASE(NSEC_WAIT_TOTAL, nsec_wait_total) +                ORDER_CASE(NSEC_WAIT_MAX, nsec_wait_max) +                ORDER_CASE_AVG(NSEC_WAIT_AVG, nsec_wait_total, n_wait) +                ORDER_CASE(NSEC_WAIT_CONTENDED_TOTAL, nsec_wait_contended_total) +                ORDER_CASE(NSEC_WAIT_CONTENDED_MAX, nsec_wait_contended_max) +                ORDER_CASE_AVG(NSEC_WAIT_CONTENDED_AVG, nsec_wait_contended_total, n_wait_contended) +                default: +                        /* Should never be reached. */ +                        assert(0); + +                #undef ORDER_CASE_AVG +                #undef ORDER_CASE +        } + +        /* Fall back to a static ordering. */ +        STATIC_ORDER(n_wait_contended); +        STATIC_ORDER(n_wait); +        STATIC_ORDER(nsec_wait_total); +        STATIC_ORDER(n_signal);          /* Let's make the output deterministic */          return a - b;  } +#undef STATIC_ORDER +#undef ORDER_CASE_AVG +#undef ORDER_CASE +  static bool mutex_info_show(struct mutex_info *mi) {          /* Mutexes used by real-time code are always noteworthy */ @@ -592,6 +768,17 @@ static bool mutex_info_show(struct mutex_info *mi) {          return true;  } +static bool cond_info_show(struct cond_info *ci) { +        /* Condition variables used by real-time code are always noteworthy */ +        if (ci->realtime) +                return true; + +        if (ci->n_wait < show_n_wait_min) +                return false; + +        return true; +} +  static bool mutex_info_dump(struct mutex_info *mi) {          char *stacktrace_str; @@ -609,6 +796,23 @@ static bool mutex_info_dump(struct mutex_info *mi) {          return true;  } +static bool cond_info_dump(struct cond_info *ci) { +        char *stacktrace_str; + +        if (!cond_info_show(ci)) +                return false; + +        stacktrace_str = stacktrace_to_string(ci->stacktrace); + +        fprintf(stderr, +                "\nCondvar #%u (0x%p) first referenced by:\n" +                "%s", ci->id, ci->cond, stacktrace_str); + +        free(stacktrace_str); + +        return true; +} +  static char mutex_type_name(int type) {          switch (type) { @@ -699,20 +903,52 @@ static bool mutex_info_stat(struct mutex_info *mi) {          return true;  } -static SummaryOrder summary_order_from_command(const char *command) { +static bool cond_info_stat(struct cond_info *ci) { +        if (!cond_info_show(ci)) +                return false; + +        fprintf(stderr, +                "%8u %8u %8u %8u %12.3f %13.3f %12.3f     %c%c\n", +                ci->id, +                ci->n_wait, +                ci->n_signal + ci->n_broadcast, +                ci->n_wait_contended, +                (double) ci->nsec_wait_total / 1000000.0, +                (double) ci->nsec_wait_contended_total / 1000000.0, +                (double) ci->nsec_wait_contended_total / ci->n_wait / 1000000.0, +                ci->broken ? '!' : (ci->dead ? 'x' : '-'), +                track_rt ? (ci->realtime ? 'R' : '-') : '.'); + +        return true; +} + +static SummaryMutexOrder summary_mutex_order_from_command(const char *command) { +        unsigned int i; + +        for (i = 0; i < MUTEX_ORDER_INVALID; i++) { +                if (strcmp(command, summary_mutex_order_details[i].command) == 0) { +                        return i; +                } +        } + +        return MUTEX_ORDER_INVALID; +} + +static SummaryCondOrder summary_cond_order_from_command(const char *command) {          unsigned int i; -        for (i = 0; i < ORDER_INVALID; i++) { -                if (strcmp(command, summary_order_details[i].command) == 0) { -                        return ORDER_ID; +        for (i = 0; i < COND_ORDER_INVALID; i++) { +                if (strcmp(command, summary_cond_order_details[i].command) == 0) { +                        return i;                  }          } -        return ORDER_INVALID; +        return COND_ORDER_INVALID;  }  static void show_summary_internal(void) { -        struct mutex_info *mi, **table; +        struct mutex_info *mi, **mutex_table; +        struct cond_info *ci, **cond_table;          unsigned n, u, i, m;          uint64_t t;          long n_cpus; @@ -723,6 +959,7 @@ static void show_summary_internal(void) {                  "\n"                  "mutrace: Showing statistics for process %s (PID: %lu).\n", get_prname(), (unsigned long) getpid()); +        /* Mutexes. */          n = 0;          for (u = 0; u < hash_size; u++) {                  lock_hash_mutex(u); @@ -743,26 +980,26 @@ static void show_summary_internal(void) {          fprintf(stderr,                  "mutrace: %u mutexes used.\n", n); -        table = malloc(sizeof(struct mutex_info*) * n); +        mutex_table = malloc(sizeof(struct mutex_info*) * n);          i = 0;          for (u = 0; u < hash_size; u++) {                  for (mi = alive_mutexes[u]; mi; mi = mi->next) {                          mi->id = i; -                        table[i++] = mi; +                        mutex_table[i++] = mi;                  }                  for (mi = dead_mutexes[u]; mi; mi = mi->next) {                          mi->id = i; -                        table[i++] = mi; +                        mutex_table[i++] = mi;                  }          }          assert(i == n); -        qsort(table, n, sizeof(table[0]), mutex_info_compare); +        qsort(mutex_table, n, sizeof(mutex_table[0]), mutex_info_compare);          for (i = n, m = 0; i > 0 && (show_n_max <= 0 || m < show_n_max); i--) -                m += mutex_info_dump(table[i - 1]) ? 1 : 0; +                m += mutex_info_dump(mutex_table[i - 1]) ? 1 : 0;          if (m > 0) {                  fprintf(stderr, @@ -770,10 +1007,10 @@ static void show_summary_internal(void) {                          "mutrace: Showing %u mutexes in order of %s:\n"                          "\n"                          " Mutex #   Locked  Changed    Cont. cont.Time[ms] tot.Time[ms] avg.Time[ms] Flags\n", -                        m, summary_order_details[summary_order].ui_string); +                        m, summary_mutex_order_details[summary_mutex_order].ui_string);                  for (i = n, m = 0; i > 0 && (show_n_max <= 0 || m < show_n_max); i--) -                        m += mutex_info_stat(table[i - 1]) ? 1 : 0; +                        m += mutex_info_stat(mutex_table[i - 1]) ? 1 : 0;                  if (i < n) @@ -797,18 +1034,100 @@ static void show_summary_internal(void) {                  if (!track_rt)                          fprintf(stderr,                                  "\n" -                                "mutrace: Note that the flags column R is only valid in --track-rt mode!\n"); +                                "mutrace: Note that the flags column R is only valid in --track-rt mode!\n\n");          } else                  fprintf(stderr,                          "\n" -                        "mutrace: No mutex contended according to filtering parameters.\n"); +                        "mutrace: No mutex contended according to filtering parameters.\n\n"); -        free(table); +        free(mutex_table);          for (u = 0; u < hash_size; u++)                  unlock_hash_mutex(u); +        /* Condition variables. */ +        n = 0; +        for (u = 0; u < hash_size; u++) { +                lock_hash_cond(u); + +                for (ci = alive_conds[u]; ci; ci = ci->next) +                        n++; + +                for (ci = dead_conds[u]; ci; ci = ci->next) +                        n++; +        } + +        if (n <= 0) { +                fprintf(stderr, +                        "mutrace: No condition variables used.\n"); +                return; +        } + +        fprintf(stderr, +                "mutrace: %u condition variables used.\n", n); + +        cond_table = malloc(sizeof(struct cond_info*) * n); + +        i = 0; +        for (u = 0; u < hash_size; u++) { +                for (ci = alive_conds[u]; ci; ci = ci->next) { +                        ci->id = i; +                        cond_table[i++] = ci; +                } + +                for (ci = dead_conds[u]; ci; ci = ci->next) { +                        ci->id = i; +                        cond_table[i++] = ci; +                } +        } +        assert(i == n); + +        qsort(cond_table, n, sizeof(cond_table[0]), cond_info_compare); + +        for (i = n, m = 0; i > 0 && (show_n_max <= 0 || m < show_n_max); i--) +                m += cond_info_dump(cond_table[i - 1]) ? 1 : 0; + +        if (m > 0) { +                fprintf(stderr, +                        "\n" +                        "mutrace: Showing %u condition variables in order of %s:\n" +                        "\n" +                        "  Cond #    Waits  Signals    Cont. tot.Time[ms] cont.Time[ms] avg.Time[ms] Flags\n", +                        m, summary_cond_order_details[summary_cond_order].ui_string); + +                for (i = n, m = 0; i > 0 && (show_n_max <= 0 || m < show_n_max); i--) +                        m += cond_info_stat(cond_table[i - 1]) ? 1 : 0; + + +                if (i < n) +                        fprintf(stderr, +                                "     ...      ...      ...      ...          ...           ...          ...     ||\n"); +                else +                        fprintf(stderr, +                                "                                                                                ||\n"); + +                fprintf(stderr, +                        "                                                                                /|\n" +                        "           State:                                     x = dead, ! = inconsistent /\n" +                        "             Use:                                     R = used in realtime thread \n"); + +                if (!track_rt) +                        fprintf(stderr, +                                "\n" +                                "mutrace: Note that the flags column R is only valid in --track-rt mode!\n"); + +        } else +                fprintf(stderr, +                        "\n" +                        "mutrace: No condition variable contended according to filtering parameters.\n"); + +        free(cond_table); + +        for (u = 0; u < hash_size; u++) +                unlock_hash_cond(u); + +        /* Footer. */          fprintf(stderr,                  "\n"                  "mutrace: Total runtime is %0.3f ms.\n", (double) t / 1000000.0); @@ -826,11 +1145,17 @@ static void show_summary_internal(void) {                          "\n"                          "mutrace: Results for SMP with %li processors.\n", n_cpus); -        if (n_broken > 0) +        if (n_broken_mutexes > 0)                  fprintf(stderr,                          "\n"                          "mutrace: WARNING: %u inconsistent mutex uses detected. Results might not be reliable.\n" -                        "mutrace:          Fix your program first!\n", n_broken); +                        "mutrace:          Fix your program first!\n", n_broken_mutexes); + +        if (n_broken_conds > 0) +                fprintf(stderr, +                        "\n" +                        "mutrace: WARNING: %u inconsistent condition variable uses detected. Results might not be reliable.\n" +                        "mutrace:          Fix your program first!\n", n_broken_conds);          if (n_collisions > 0)                  fprintf(stderr, @@ -1161,7 +1486,7 @@ static void mutex_lock(pthread_mutex_t *mutex, bool busy, uint64_t nsec_contende          mi = mutex_info_acquire(mutex);          if (mi->n_lock_level > 0 && mi->type != PTHREAD_MUTEX_RECURSIVE) { -                __sync_fetch_and_add(&n_broken, 1); +                __sync_fetch_and_add(&n_broken_mutexes, 1);                  mi->broken = true;                  if (raise_trap) @@ -1293,7 +1618,7 @@ static void mutex_unlock(pthread_mutex_t *mutex) {          mi = mutex_info_acquire(mutex);          if (mi->n_lock_level <= 0) { -                __sync_fetch_and_add(&n_broken, 1); +                __sync_fetch_and_add(&n_broken_mutexes, 1);                  mi->broken = true;                  if (raise_trap) @@ -1326,33 +1651,306 @@ int pthread_mutex_unlock(pthread_mutex_t *mutex) {          return real_pthread_mutex_unlock(mutex);  } +static struct cond_info *cond_info_add(unsigned long u, pthread_cond_t *cond) { +        struct cond_info *ci; + +        /* Needs external locking */ + +        if (alive_conds[u]) +                __sync_fetch_and_add(&n_collisions, 1); + +        ci = calloc(1, sizeof(struct cond_info)); +        assert(ci); + +        ci->cond = cond; +        ci->stacktrace = generate_stacktrace(); + +        ci->next = alive_conds[u]; +        alive_conds[u] = ci; + +        return ci; +} + +static void cond_info_remove(unsigned u, pthread_cond_t *cond) { +        struct cond_info *ci, *p; + +        /* Needs external locking */ + +        for (ci = alive_conds[u], p = NULL; ci; p = ci, ci = ci->next) +                if (ci->cond == cond) +                        break; + +        if (!ci) +                return; + +        if (p) +                p->next = ci->next; +        else +                alive_conds[u] = ci->next; + +        ci->dead = true; +        ci->next = dead_conds[u]; +        dead_conds[u] = ci; +} + +static struct cond_info *cond_info_acquire(pthread_cond_t *cond) { +        unsigned long u; +        struct cond_info *ci; + +        u = cond_hash(cond); +        lock_hash_cond(u); + +        for (ci = alive_conds[u]; ci; ci = ci->next) +                if (ci->cond == cond) +                        return ci; + +        return cond_info_add(u, cond); +} + +static void cond_info_release(pthread_cond_t *cond) { +        unsigned long u; + +        u = cond_hash(cond); +        unlock_hash_cond(u); +} + +int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr) { +        int r; + +        if (UNLIKELY(!initialized && recursive)) { +                static const pthread_cond_t template = PTHREAD_COND_INITIALIZER; +                /* Now this is incredibly ugly. */ + +                memcpy(cond, &template, sizeof(pthread_cond_t)); +                return 0; +        } + +        load_functions(); + +        r = real_pthread_cond_init(cond, attr); +        if (r != 0) +                return r; + +        if (LIKELY(initialized && !recursive)) { +                unsigned long u; + +                recursive = true; +                u = cond_hash(cond); +                lock_hash_cond(u); + +                cond_info_remove(u, cond); +                cond_info_add(u, cond); + +                unlock_hash_cond(u); +                recursive = false; +        } + +        return r; +} + +int pthread_cond_destroy(pthread_cond_t *cond) { +        assert(initialized || !recursive); + +        load_functions(); + +        if (LIKELY(initialized && !recursive)) { +                unsigned long u; + +                recursive = true; +                u = cond_hash(cond); +                lock_hash_cond(u); + +                cond_info_remove(u, cond); + +                unlock_hash_cond(u); +                recursive = false; +        } + +        return real_pthread_cond_destroy(cond); +} + +static void cond_signal(pthread_cond_t *cond, bool is_broadcast) { +        struct cond_info *ci; + +        if (UNLIKELY(!initialized || recursive)) +                return; + +        recursive = true; +        ci = cond_info_acquire(cond); + +        if (ci->n_wait_level == 0) { +                ci->n_signal_contended++; +        } else { +                /* If we're signalling the second-last thread which is waiting +                 * on the cond, calculate the total contention time (from the +                 * time the second thread first started waiting on the cond) and +                 * add it to the total. +                 * +                 * However, if we're broadcasting, don't sum up the contention +                 * time, since we assume that the threads will all now have +                 * useful work to do. */ +                if (ci->n_wait_level == 2 && !is_broadcast) { +                        uint64_t contention_time = nsec_now() - ci->nsec_timestamp; +                        ci->nsec_wait_contended_total += contention_time; + +                        if (contention_time > ci->nsec_wait_contended_max) +                                ci->nsec_wait_contended_max = contention_time; +                } +        } + +        if (is_broadcast) +                ci->n_broadcast++; +        else +                ci->n_signal++; + +        if (track_rt && !ci->realtime && is_realtime()) +                ci->realtime = true; + +        cond_info_release(cond); +        recursive = false; +} + +int pthread_cond_signal(pthread_cond_t *cond) { +        int r; + +        if (UNLIKELY(!initialized && recursive)) { +                assert(!threads_existing); +                return 0; +        } + +        load_functions(); + +        r = real_pthread_cond_signal(cond); +        if (UNLIKELY(r != 0)) +                return r; + +        cond_signal(cond, false); + +        return r; +} + +int pthread_cond_broadcast(pthread_cond_t *cond) { +        int r; + +        if (UNLIKELY(!initialized && recursive)) { +                assert(!threads_existing); +                return 0; +        } + +        load_functions(); + +        r = real_pthread_cond_broadcast(cond); +        if (UNLIKELY(r != 0)) +                return r; + +        cond_signal(cond, true); + +        return r; +} + +static void cond_wait_start(pthread_cond_t *cond, pthread_mutex_t *mutex) { +        struct cond_info *ci; + +        if (UNLIKELY(!initialized || recursive)) +                return; + +        recursive = true; +        ci = cond_info_acquire(cond); + +        /* Check the cond is always used with the same mutex. pthreads +         * technically allows for different mutexes to be used sequentially +         * with a given cond, but this seems error prone and probably always +         * A Bad Idea. */ +        if (ci->mutex == NULL) { +                ci->mutex = mutex; +        } else if (ci->mutex != mutex) { +                __sync_fetch_and_add(&n_broken_conds, 1); +                ci->broken = true; + +                if (raise_trap) +                        DEBUG_TRAP; +        } + +        if (ci->n_wait_level > 0) +                ci->n_wait_contended++; + +        /* Iff we're the second thread to concurrently wait on this cond, start +         * the contention timer. This timer will continue running until the +         * second-last thread to be waiting on the cond is signalled. */ +        if (ci->n_wait_level == 1) +                ci->nsec_timestamp = nsec_now(); + +        ci->n_wait_level++; +        ci->n_wait++; + +        if (track_rt && !ci->realtime && is_realtime()) +                ci->realtime = true; + +        cond_info_release(cond); +        recursive = false; +} + +static void cond_wait_finish(pthread_cond_t *cond, pthread_mutex_t *mutex, uint64_t wait_time) { +        struct cond_info *ci; + +        if (UNLIKELY(!initialized || recursive)) +                return; + +        recursive = true; +        ci = cond_info_acquire(cond); + +        ci->nsec_wait_total += wait_time; +        if (wait_time > ci->nsec_wait_max) +                ci->nsec_wait_max = wait_time; + +        ci->n_wait_level--; + +        cond_info_release(cond); +        recursive = false; +} +  int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) {          int r; +        uint64_t start_time, wait_time = 0;          assert(initialized || !recursive);          load_functions(); +        cond_wait_start(cond, mutex);          mutex_unlock(mutex); + +        start_time = nsec_now();          r = real_pthread_cond_wait(cond, mutex); +        wait_time = nsec_now() - start_time;          /* Unfortunately we cannot distuingish mutex contention and           * the condition not being signalled here. */          mutex_lock(mutex, false, 0); +        cond_wait_finish(cond, mutex, wait_time);          return r;  }  int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) {          int r; +        uint64_t start_time, wait_time = 0;          assert(initialized || !recursive);          load_functions(); +        cond_wait_start(cond, mutex);          mutex_unlock(mutex); + +        start_time = nsec_now();          r = real_pthread_cond_timedwait(cond, mutex, abstime); +        wait_time = nsec_now() - start_time; + +        /* Unfortunately we cannot distuingish mutex contention and +         * the condition not being signalled here. */          mutex_lock(mutex, false, 0); +        cond_wait_finish(cond, mutex, wait_time);          return r;  } @@ -1551,7 +2149,7 @@ static void rwlock_lock(pthread_rwlock_t *rwlock, LockType lock_type, bool busy,          mi = rwlock_info_acquire(rwlock);          if (mi->n_lock_level > 0 && lock_type == WRITE) { -                __sync_fetch_and_add(&n_broken, 1); +                __sync_fetch_and_add(&n_broken_mutexes, 1);                  mi->broken = true;                  if (raise_trap) @@ -1770,7 +2368,7 @@ static void rwlock_unlock(pthread_rwlock_t *rwlock) {          mi = rwlock_info_acquire(rwlock);          if (mi->n_lock_level <= 0) { -                __sync_fetch_and_add(&n_broken, 1); +                __sync_fetch_and_add(&n_broken_mutexes, 1);                  mi->broken = true;                  if (raise_trap) @@ -17,7 +17,7 @@  # You should have received a copy of the GNU Lesser General Public  # License along with mutrace. If not, see <http://www.gnu.org/licenses/>. -if ! TEMP=`getopt -o +ardh --long hash-size:,frames:,locked-min:,owner-changed-min:,contended-min:,max:,order:,trap,help,all,debug-info -n mutrace -- "$@"` ; then +if ! TEMP=`getopt -o +ardh --long hash-size:,frames:,locked-min:,owner-changed-min:,contended-min:,max:,mutex-order:,cond-order:,trap,help,all,debug-info -n mutrace -- "$@"` ; then      exit 1  fi @@ -57,8 +57,13 @@ while : ; do                          shift 2                          ;; -                --order) -                        export MUTRACE_SUMMARY_ORDER="$2" +                --mutex-order) +                        export MUTRACE_SUMMARY_MUTEX_ORDER="$2" +                        shift 2 +                        ;; + +                --cond-order) +                        export MUTRACE_SUMMARY_COND_ORDER="$2"                          shift 2                          ;; @@ -99,9 +104,14 @@ OPTIONS:        --frames=INTEGER            Set number of frames to show in stack traces    -d, --debug-info                Make use of debug information in stack traces        --max=INTEGER               Show this many mutexes at maximum -      --order=STRING              Order the summary table by this column (see -                                  below for valid column names) - +      --mutex-order=STRING        Order the summary table of mutexes by this +                                  column (see below for valid column names) +      --cond-order=STRING         Order the summary table of condition variables +                                  by this column (see below for valid column +                                  names) + +      --wait-min=INTEGER          Only show condition variables that have been +                                  waited on at least this often        --locked-min=INTEGER        Only show mutexes that have been locked at                                    least this often        --owner-changed-min=INTEGER Only show mutexes whose owning thread changed @@ -117,7 +127,7 @@ OPTIONS:                                    inconsistency is detected (for use in                                    conjunction with gdb) -ORDER COLUMN NAMES: +MUTEX ORDER COLUMN NAMES:    id                              Mutex number    n-locked                        Total number of times mutex was locked for                                    writing @@ -144,6 +154,30 @@ ORDER COLUMN NAMES:    nsec-read-contended-total       Total time mutex was contended for reading    nsec-read-contended-avg         Average time mutex was continuously contended                                    for reading + +CONDITION VARIABLE ORDER COLUMN NAMES: +  id                              Condition variable number +  n-wait                          Total number of times condition variable was +                                  waited on (inc. timed waits) +  n-signal                        Total number of times condition variable was +                                  signalled +  n-broadcast                     Total number of times condition variable was +                                  broadcasted +  n-wait-contended                Total number of times condition variable was +                                  concurrently waited on by multiple threads +  n-signal-contended              Total number of times condition variable was +                                  signalled with no threads waiting +  nsec-wait-total                 Total time condition variable was waited on +  nsec-wait-max                   Maximum time condition variable was +                                  continuously waited on +  nsec-wait-avg                   Average time condition variable was +                                  continuously waited on +  nsec-wait-contended-total       Total time condition variable was waited on +                                  by multiple threads before being signalled +  nsec-wait-contended-max         Maximum time condition variable was waited on +                                  by multiple threads +  nsec-wait-contended-avg         Average time condition variable was waited on +                                  by multiple threads  EOF                          exit 0                          ;; | 
