Add gathering per-thread CPU usage to ddsrt

Signed-off-by: Erik Boasson <eb@ilities.com>
This commit is contained in:
Erik Boasson 2019-08-02 08:56:01 +02:00 committed by eboasson
parent 9b1920862e
commit f9808c7656
10 changed files with 589 additions and 109 deletions

View file

@ -22,12 +22,15 @@
# else
# define DDSRT_HAVE_RUSAGE 0
#endif
#else
#elif defined (_WIN32) || defined (__linux) || defined (__APPLE__)
# define DDSRT_HAVE_RUSAGE 1
#else
# define DDSRT_HAVE_RUSAGE 0
#endif
#include "dds/ddsrt/time.h"
#include "dds/ddsrt/retcode.h"
#include "dds/ddsrt/threads.h"
#if defined (__cplusplus)
extern "C" {
@ -43,8 +46,10 @@ typedef struct {
size_t nivcsw; /* Involuntary context switches. Not maintained on Windows. */
} ddsrt_rusage_t;
#define DDSRT_RUSAGE_SELF (0)
#define DDSRT_RUSAGE_THREAD (1)
enum ddsrt_getrusage_who {
DDSRT_RUSAGE_SELF,
DDSRT_RUSAGE_THREAD
};
/**
* @brief Get resource usage for the current thread or process.
@ -61,7 +66,26 @@ typedef struct {
* @retval DDS_RETCODE_ERROR
* An unidentified error occurred.
*/
DDS_EXPORT dds_return_t ddsrt_getrusage(int who, ddsrt_rusage_t *usage);
DDS_EXPORT dds_return_t ddsrt_getrusage(enum ddsrt_getrusage_who who, ddsrt_rusage_t *usage);
#if DDSRT_HAVE_THREAD_LIST
/**
* @brief Get resource usage for some thread.
*
* @param[in] tid id of the thread of to get the resource usage for
* @param[in] usage Structure where resource usage is returned.
*
* @returns A dds_return_t indicating success or failure.
*
* @retval DDS_RETCODE_OK
* Resource usage successfully returned in @usage.
* @retval DDS_RETCODE_OUT_OF_RESOURCES
* There were not enough resources to get resource usage.
* @retval DDS_RETCODE_ERROR
* An unidentified error occurred.
*/
DDS_EXPORT dds_return_t ddsrt_getrusage_anythread (ddsrt_thread_list_id_t tid, ddsrt_rusage_t * __restrict usage);
#endif
#if defined (__cplusplus)
}

View file

@ -214,6 +214,53 @@ ddsrt_thread_setname(
const char *__restrict name);
#endif
#if DDSRT_HAVE_THREAD_LIST
/**
* @brief Get a list of threads in the calling process
*
* @param[out] tids Array of size elements to be filled with thread
* identifiers, may be NULL if size is 0
* @param[in] size The size of the tids array; 0 is allowed
*
* @returns A dds_return_t indicating the number of threads in the process
* or an error code on failure.
*
* @retval > 0
* Number of threads in the process, may be larger than size
* tids[0 .. (return - 1)] are valid
* @retval DDS_RETCODE_ERROR
* Something went wrong, contents of tids is undefined
* @retval DDS_RETCODE_UNSUPPORTED
* Not supported on the platform
*/
DDS_EXPORT dds_return_t ddsrt_thread_list (ddsrt_thread_list_id_t * __restrict tids, size_t size);
/**
* @brief Get the name of the specified thread (in the calling process)
*
* @param[in] tid Thread identifier for which the name is sought
* @param[out] name Filled with the thread name (or a synthesized one)
* on successful return; name is silently truncated
* if the actual name is longer than name can hold;
* always 0-terminated if size > 0
* @param[in] size Number of bytes of name that may be assigned, size
* is 0 is allowed, though somewhat useless
*
* @returns A dds_return_t indicating success or failure.
*
* @retval DDS_RETCODE_OK
* Possibly truncated name is returned as a null-terminated
* string in name (provided size > 0).
* @retval DDS_RETCODE_NOT_FOUND
* Thread not found; the contents of name is unchanged
* @retval DDS_RETCODE_ERROR
* Unspecified failure, the contents of name is undefined
* @retval DDS_RETCODE_UNSUPPORTED
* Not supported on the platform
*/
DDS_EXPORT dds_return_t ddsrt_thread_getname_anythread (ddsrt_thread_list_id_t tid, char *__restrict name, size_t size);
#endif
/**
* @brief Push cleanup handler onto the cleanup stack
*

View file

@ -16,6 +16,7 @@
#include <task.h>
#define DDSRT_HAVE_THREAD_SETNAME (0)
#define DDSRT_HAVE_THREAD_LIST (0)
#if defined(__cplusplus)
extern "C" {

View file

@ -19,6 +19,11 @@
#else
#define DDSRT_HAVE_THREAD_SETNAME (1)
#endif
#if defined (__linux) || defined (__APPLE__)
#define DDSRT_HAVE_THREAD_LIST (1)
#else
#define DDSRT_HAVE_THREAD_LIST (0)
#endif
#if defined (__cplusplus)
extern "C" {
@ -27,6 +32,7 @@ extern "C" {
#if defined(__linux)
typedef long int ddsrt_tid_t;
#define PRIdTID "ld"
typedef long int ddsrt_thread_list_id_t;
/* __linux */
#elif defined(__FreeBSD__) && (__FreeBSD_version >= 900031)
/* FreeBSD >= 9.0 */
@ -38,6 +44,8 @@ typedef int ddsrt_tid_t;
/* macOS X >= 10.6 */
typedef uint64_t ddsrt_tid_t;
#define PRIdTID PRIu64
/* ddsrt_thread_list_id_t is actually a mach_port_t */
typedef uint32_t ddsrt_thread_list_id_t;
/* __APPLE__ */
#elif defined(__VXWORKS__)
/* TODO: Verify taskIdSelf is the right function to use on VxWorks */

View file

@ -15,6 +15,7 @@
#include "dds/ddsrt/types.h"
#define DDSRT_HAVE_THREAD_SETNAME (1)
#define DDSRT_HAVE_THREAD_LIST (1)
#if defined (__cplusplus)
extern "C" {
@ -28,6 +29,8 @@ typedef struct {
typedef DWORD ddsrt_tid_t;
#define PRIdTID "u"
typedef HANDLE ddsrt_thread_list_id_t;
#if defined (__cplusplus)
}
#endif

View file

@ -78,8 +78,18 @@ rusage_thread(ddsrt_rusage_t *usage)
return DDS_RETCODE_OK;
}
#if ! DDSRT_HAVE_THREAD_LIST
static
#endif
dds_return_t
ddsrt_getrusage(int who, ddsrt_rusage_t *usage)
ddsrt_getrusage_anythread(ddsrt_thread_list_id_t tid, ddsrt_rusage_t *__restrict usage)
{
assert(usage != NULL);
return rusage_thread(tid, usage);
}
dds_return_t
ddsrt_getrusage(enum ddsrt_getrusage_who who, ddsrt_rusage_t *usage)
{
dds_return_t rc;
@ -87,7 +97,7 @@ ddsrt_getrusage(int who, ddsrt_rusage_t *usage)
assert(usage != NULL);
if (who == DDSRT_RUSAGE_THREAD) {
rc = rusage_thread(usage);
rc = rusage_thread_anythread(xTaskGetCurrentTaskHandle(), usage);
} else {
rc = rusage_self(usage);
}

View file

@ -15,87 +15,212 @@
#include <string.h>
#include <sys/resource.h>
#if defined(__APPLE__)
#include "dds/ddsrt/rusage.h"
#if defined __linux
#include <stdio.h>
#include <dirent.h>
dds_return_t
ddsrt_getrusage_anythread (
ddsrt_thread_list_id_t tid,
ddsrt_rusage_t * __restrict usage)
{
/* Linux' man pages happily state that the second field is the process/task name
in parentheses, and that %s is the correct scanf conversion. As it turns out
the process name itself can contain spaces and parentheses ... so %s is not a
good choice for the general case. The others are spec'd as a character or a
number, which suggests the correct procedure is to have the 2nd field start at
the first ( and end at the last ) ...
RSS is per-process, so no point in populating that one
field 14, 15: utime, stime (field 1 is first)
Voluntary and involuntary context switches can be found in .../status, but
not in stat; and .../status does not give the time. Crazy. */
const double hz = (double) sysconf (_SC_CLK_TCK);
char file[100];
FILE *fp;
int pos;
pos = snprintf (file, sizeof (file), "/proc/self/task/%lu/stat", (unsigned long) tid);
if (pos < 0 || pos >= (int) sizeof (file))
return DDS_RETCODE_ERROR;
if ((fp = fopen (file, "r")) == NULL)
return DDS_RETCODE_NOT_FOUND;
/* max 2 64-bit ints plus some whitespace; need 1 extra for detecting something
went wrong and we ended up gobbling up garbage; 64 will do */
char save[64];
size_t savepos = 0;
int prevc, c;
int field = 1;
for (prevc = 0; (c = fgetc (fp)) != EOF; prevc = c)
{
if (field == 1)
{
if (c == '(')
field = 2;
}
else if (field >= 2)
{
/* each close paren resets the field counter to 3 (the first is common,
further ones are rare and occur only if the thread name contains a
closing parenthesis), as well as the save space for fields 14 & 15
that we care about. */
if (c == ')')
{
field = 2;
savepos = 0;
}
else
{
/* next field on transition of whitespace to non-whitespace */
if (c != ' ' && prevc == ' ')
field++;
/* save fields 14 & 15 while continuing scanning to EOF on the off-chance
that 14&15 initially appear to be in what ultimately turns out to be
task name */
if (field == 14 || field == 15)
{
if (savepos < sizeof (save) - 1)
save[savepos++] = (char) c;
}
}
}
}
fclose (fp);
assert (savepos < sizeof (save));
save[savepos] = 0;
if (savepos == sizeof (save) - 1)
return DDS_RETCODE_ERROR;
/* it's really integer, but the conversion from an unknown HZ value is much
less tricky in floating-point */
double user, sys;
if (sscanf (save, "%lf %lf%n", &user, &sys, &pos) != 2 || (save[pos] != 0 && save[pos] != ' '))
return DDS_RETCODE_ERROR;
usage->utime = (dds_time_t) (1e9 * user / hz);
usage->stime = (dds_time_t) (1e9 * sys / hz);
usage->idrss = 0;
usage->maxrss = 0;
usage->nvcsw = 0;
usage->nivcsw = 0;
pos = snprintf (file, sizeof (file), "/proc/self/task/%lu/status", (unsigned long) tid);
if (pos < 0 || pos >= (int) sizeof (file))
return DDS_RETCODE_ERROR;
if ((fp = fopen (file, "r")) == NULL)
return DDS_RETCODE_NOT_FOUND;
enum { ERROR, READ_HEADING, SKIP_TO_EOL, READ_VCSW, READ_IVCSW } state = READ_HEADING;
savepos = 0;
while (state != ERROR && (c = fgetc (fp)) != EOF)
{
switch (state)
{
case READ_HEADING:
if (savepos < sizeof (save) - 1)
save[savepos++] = (char) c;
if (c == ':')
{
save[savepos] = 0;
savepos = 0;
if (strcmp (save, "voluntary_ctxt_switches:") == 0)
state = READ_VCSW;
else if (strcmp (save, "nonvoluntary_ctxt_switches:") == 0)
state = READ_IVCSW;
else
state = SKIP_TO_EOL;
}
break;
case SKIP_TO_EOL:
if (c == '\n')
state = READ_HEADING;
break;
case READ_VCSW:
case READ_IVCSW:
if (fscanf (fp, "%zu", (state == READ_VCSW) ? &usage->nvcsw : &usage->nivcsw) != 1)
state = ERROR;
else
state = SKIP_TO_EOL;
break;
case ERROR:
break;
}
}
fclose (fp);
return (state == ERROR) ? DDS_RETCODE_ERROR : DDS_RETCODE_OK;
}
dds_return_t
ddsrt_getrusage (enum ddsrt_getrusage_who who, ddsrt_rusage_t *usage)
{
struct rusage buf;
assert (who == DDSRT_RUSAGE_SELF || who == DDSRT_RUSAGE_THREAD);
assert (usage != NULL);
memset (&buf, 0, sizeof(buf));
if (getrusage ((who == DDSRT_RUSAGE_SELF) ? RUSAGE_SELF : RUSAGE_THREAD, &buf) == -1)
return DDS_RETCODE_ERROR;
usage->utime = (buf.ru_utime.tv_sec * DDS_NSECS_IN_SEC) + (buf.ru_utime.tv_usec * DDS_NSECS_IN_USEC);
usage->stime = (buf.ru_stime.tv_sec * DDS_NSECS_IN_SEC) + (buf.ru_stime.tv_usec * DDS_NSECS_IN_USEC);
usage->maxrss = 1024 * (size_t) buf.ru_maxrss;
usage->idrss = (size_t) buf.ru_idrss;
usage->nvcsw = (size_t) buf.ru_nvcsw;
usage->nivcsw = (size_t) buf.ru_nivcsw;
return DDS_RETCODE_OK;
}
#elif defined (__APPLE__)
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/thread_act.h>
#endif
#include "dds/ddsrt/rusage.h"
dds_return_t
ddsrt_getrusage(int who, ddsrt_rusage_t *usage)
ddsrt_getrusage_anythread (
ddsrt_thread_list_id_t tid,
ddsrt_rusage_t * __restrict usage)
{
mach_msg_type_number_t cnt;
thread_basic_info_data_t info;
cnt = THREAD_BASIC_INFO_COUNT;
if (thread_info ((mach_port_t) tid, THREAD_BASIC_INFO, (thread_info_t) &info, &cnt) != KERN_SUCCESS)
return DDS_RETCODE_ERROR;
/* Don't see an (easy) way to get context switch counts */
usage->utime = info.user_time.seconds * DDS_NSECS_IN_SEC + info.user_time.microseconds * DDS_NSECS_IN_USEC;
usage->stime = info.system_time.seconds * DDS_NSECS_IN_SEC + info.system_time.microseconds * DDS_NSECS_IN_USEC;
usage->idrss = 0;
usage->maxrss = 0;
usage->nivcsw = 0;
usage->nvcsw = 0;
return DDS_RETCODE_OK;
}
dds_return_t
ddsrt_getrusage (enum ddsrt_getrusage_who who, ddsrt_rusage_t *usage)
{
int err = 0;
struct rusage buf;
dds_return_t rc;
assert(who == DDSRT_RUSAGE_SELF || who == DDSRT_RUSAGE_THREAD);
assert(usage != NULL);
assert (usage != NULL);
memset(&buf, 0, sizeof(buf));
memset (&buf, 0, sizeof(buf));
if (getrusage (RUSAGE_SELF, &buf) == -1)
return DDS_RETCODE_ERROR;
#if defined(__linux)
if ((who == DDSRT_RUSAGE_SELF && getrusage(RUSAGE_SELF, &buf) == -1) ||
(who == DDSRT_RUSAGE_THREAD && getrusage(RUSAGE_THREAD, &buf) == -1))
{
err = errno;
} else {
buf.ru_maxrss *= 1024;
switch (who) {
case DDSRT_RUSAGE_THREAD:
if ((rc = ddsrt_getrusage_anythread (pthread_mach_thread_np (pthread_self()), usage)) < 0)
return rc;
break;
case DDSRT_RUSAGE_SELF:
usage->utime = (buf.ru_utime.tv_sec * DDS_NSECS_IN_SEC) + (buf.ru_utime.tv_usec * DDS_NSECS_IN_USEC);
usage->stime = (buf.ru_stime.tv_sec * DDS_NSECS_IN_SEC) + (buf.ru_stime.tv_usec * DDS_NSECS_IN_USEC);
usage->nvcsw = (size_t) buf.ru_nvcsw;
usage->nivcsw = (size_t) buf.ru_nivcsw;
break;
}
#else
if (getrusage(RUSAGE_SELF, &buf) == -1) {
err = errno;
} else if (who == DDSRT_RUSAGE_THREAD) {
memset(&buf.ru_utime, 0, sizeof(buf.ru_utime));
memset(&buf.ru_stime, 0, sizeof(buf.ru_stime));
buf.ru_nvcsw = 0;
buf.ru_nivcsw = 0;
#if defined(__APPLE__)
kern_return_t ret;
mach_port_t thr;
mach_msg_type_number_t cnt;
thread_basic_info_data_t info;
thr = mach_thread_self();
assert(thr != MACH_PORT_DEAD);
if (thr == MACH_PORT_NULL) {
/* Resource shortage prevented reception of send right. */
err = ENOMEM;
} else {
cnt = THREAD_BASIC_INFO_COUNT;
ret = thread_info(
thr, THREAD_BASIC_INFO, (thread_info_t)&info, &cnt);
assert(ret != KERN_INVALID_ARGUMENT);
/* Assume MIG_ARRAY_TOO_LARGE will not happen. */
buf.ru_utime.tv_sec = info.user_time.seconds;
buf.ru_utime.tv_usec = info.user_time.microseconds;
buf.ru_stime.tv_sec = info.system_time.seconds;
buf.ru_stime.tv_usec = info.system_time.microseconds;
mach_port_deallocate(mach_task_self(), thr);
}
#endif /* __APPLE__ */
}
#endif /* __linux */
if (err == 0) {
rc = DDS_RETCODE_OK;
usage->utime =
(buf.ru_utime.tv_sec * DDS_NSECS_IN_SEC) +
(buf.ru_utime.tv_usec * DDS_NSECS_IN_USEC);
usage->stime =
(buf.ru_stime.tv_sec * DDS_NSECS_IN_SEC) +
(buf.ru_stime.tv_usec * DDS_NSECS_IN_USEC);
usage->maxrss = (size_t)buf.ru_maxrss;
usage->idrss = (size_t)buf.ru_idrss;
usage->nvcsw = (size_t)buf.ru_nvcsw;
usage->nivcsw = (size_t)buf.ru_nivcsw;
} else if (err == ENOMEM) {
rc = DDS_RETCODE_OUT_OF_RESOURCES;
} else {
rc = DDS_RETCODE_ERROR;
}
return rc;
usage->maxrss = (size_t) buf.ru_maxrss;
usage->idrss = (size_t) buf.ru_idrss;
return DDS_RETCODE_OK;
}
#endif

View file

@ -18,36 +18,60 @@
#include <psapi.h>
dds_time_t
filetime_to_time(const FILETIME *ft)
filetime_to_time (const FILETIME *ft)
{
/* FILETIME structures express times in 100-nanosecond time units. */
return ((ft->dwHighDateTime << 31) + (ft->dwLowDateTime)) * 100;
return (dds_time_t) ((((uint64_t) ft->dwHighDateTime << 31) + (ft->dwLowDateTime)) * 100);
}
dds_return_t
ddsrt_getrusage(int who, ddsrt_rusage_t *usage)
ddsrt_getrusage_anythread (ddsrt_thread_list_id_t tid, ddsrt_rusage_t * __restrict usage)
{
FILETIME stime, utime, ctime, etime;
PROCESS_MEMORY_COUNTERS pmctrs;
assert(who == DDSRT_RUSAGE_SELF || who == DDSRT_RUSAGE_THREAD);
assert(usage != NULL);
assert (usage != NULL);
/* Memory counters are per process, but populate them if thread resource
usage is requested to keep in sync with Linux. */
if ((!GetProcessMemoryInfo(GetCurrentProcess(), &pmctrs, sizeof(pmctrs)))
|| (who == DDSRT_RUSAGE_SELF &&
!GetProcessTimes(GetCurrentProcess(), &ctime, &etime, &stime, &utime))
|| (who == DDSRT_RUSAGE_THREAD &&
!GetThreadTimes(GetCurrentThread(), &ctime, &etime, &stime, &utime)))
{
return GetLastError();
}
memset(usage, 0, sizeof(*usage));
usage->stime = filetime_to_time(&stime);
usage->utime = filetime_to_time(&utime);
usage->maxrss = pmctrs.PeakWorkingSetSize;
if (!GetThreadTimes (tid, &ctime, &etime, &stime, &utime))
return DDS_RETCODE_ERROR;
memset (usage, 0, sizeof (*usage));
usage->stime = filetime_to_time (&stime);
usage->utime = filetime_to_time (&utime);
return DDS_RETCODE_OK;
}
dds_return_t
ddsrt_getrusage (enum ddsrt_getrusage_who who, ddsrt_rusage_t *usage)
{
PROCESS_MEMORY_COUNTERS pmctrs;
assert (who == DDSRT_RUSAGE_SELF || who == DDSRT_RUSAGE_THREAD);
assert (usage != NULL);
/* Memory counters are per process, but populate them if thread resource
usage is requested to keep in sync with Linux. */
if (!GetProcessMemoryInfo (GetCurrentProcess (), &pmctrs, sizeof (pmctrs)))
return DDS_RETCODE_ERROR;
if (who == DDSRT_RUSAGE_THREAD)
{
dds_return_t rc;
if ((rc = ddsrt_getrusage_anythread (GetCurrentThread (), usage)) < 0)
return rc;
}
else
{
FILETIME stime, utime, ctime, etime;
if (!GetProcessTimes (GetCurrentProcess (), &ctime, &etime, &stime, &utime))
return DDS_RETCODE_ERROR;
memset(usage, 0, sizeof(*usage));
usage->stime = filetime_to_time(&stime);
usage->utime = filetime_to_time(&utime);
}
usage->maxrss = pmctrs.PeakWorkingSetSize;
return DDS_RETCODE_OK;
}

View file

@ -30,6 +30,7 @@
#include "dds/ddsrt/string.h"
#include "dds/ddsrt/threads_priv.h"
#include "dds/ddsrt/types.h"
#include "dds/ddsrt/static_assert.h"
typedef struct {
char *name;
@ -39,9 +40,14 @@ typedef struct {
#if defined(__linux)
#include <sys/syscall.h>
#include <dirent.h>
#define MAXTHREADNAMESIZE (15) /* 16 bytes including null-terminating byte. */
#elif defined(__APPLE__)
#include <mach/mach_init.h>
#include <mach/thread_info.h> /* MAXTHREADNAMESIZE */
#include <mach/task.h>
#include <mach/task_info.h>
#include <mach/vm_map.h>
#elif defined(__sun)
#define MAXTHREADNAMESIZE (31)
#elif defined(__FreeBSD__)
@ -363,7 +369,7 @@ ddsrt_thread_join(ddsrt_thread_t thread, uint32_t *thread_result)
if ((err = pthread_join (thread.v, &vthread_result)) != 0)
{
DDS_TRACE ("pthread_join(0x%"PRIxMAX") failed with error %d\n", (uintmax_t)((uintptr_t)thread.v), err);
DDS_ERROR ("pthread_join(0x%"PRIxMAX") failed with error %d\n", (uintmax_t)((uintptr_t)thread.v), err);
return DDS_RETCODE_ERROR;
}
@ -372,6 +378,104 @@ ddsrt_thread_join(ddsrt_thread_t thread, uint32_t *thread_result)
return DDS_RETCODE_OK;
}
#if defined __linux
dds_return_t
ddsrt_thread_list (
ddsrt_thread_list_id_t * __restrict tids,
size_t size)
{
DIR *dir;
struct dirent *de;
if ((dir = opendir ("/proc/self/task")) == NULL)
return DDS_RETCODE_ERROR;
dds_return_t n = 0;
while ((de = readdir (dir)) != NULL)
{
if (de->d_name[0] == '.' && (de->d_name[1] == 0 || (de->d_name[1] == '.' && de->d_name[2] == 0)))
continue;
int pos;
long tid;
if (sscanf (de->d_name, "%ld%n", &tid, &pos) != 1 || de->d_name[pos] != 0)
{
n = DDS_RETCODE_ERROR;
break;
}
if ((size_t) n < size)
tids[n] = (ddsrt_thread_list_id_t) tid;
n++;
}
closedir (dir);
/* If there were no threads, something must've gone badly wrong */
return (n == 0) ? DDS_RETCODE_ERROR : n;
}
dds_return_t
ddsrt_thread_getname_anythread (
ddsrt_thread_list_id_t tid,
char *__restrict name,
size_t size)
{
char file[100];
FILE *fp;
int pos;
pos = snprintf (file, sizeof (file), "/proc/self/task/%lu/stat", (unsigned long) tid);
if (pos < 0 || pos >= (int) sizeof (file))
return DDS_RETCODE_ERROR;
if ((fp = fopen (file, "r")) == NULL)
return DDS_RETCODE_NOT_FOUND;
int c;
size_t namelen = 0, namepos = 0;
while ((c = fgetc (fp)) != EOF)
if (c == '(')
break;
while ((c = fgetc (fp)) != EOF)
{
if (c == ')')
namelen = namepos;
if (namepos + 1 < size)
name[namepos++] = (char) c;
}
fclose (fp);
assert (size == 0 || namelen < size);
if (size > 0)
name[namelen] = 0;
return DDS_RETCODE_OK;
}
#elif defined __APPLE__
DDSRT_STATIC_ASSERT (sizeof (ddsrt_thread_list_id_t) == sizeof (mach_port_t));
dds_return_t
ddsrt_thread_list (
ddsrt_thread_list_id_t * __restrict tids,
size_t size)
{
thread_act_array_t tasks;
mach_msg_type_number_t count;
if (task_threads (mach_task_self (), &tasks, &count) != KERN_SUCCESS)
return DDS_RETCODE_ERROR;
for (mach_msg_type_number_t i = 0; i < count && (size_t) i < size; i++)
tids[i] = (ddsrt_thread_list_id_t) tasks[i];
vm_deallocate (mach_task_self (), (vm_address_t) tasks, count * sizeof (thread_act_t));
return (dds_return_t) count;
}
dds_return_t
ddsrt_thread_getname_anythread (
ddsrt_thread_list_id_t tid,
char *__restrict name,
size_t size)
{
if (size > 0)
{
pthread_t pt = pthread_from_mach_thread_np ((mach_port_t) tid);
name[0] = '\0';
if (pt == NULL || pthread_getname_np (pt, name, size) != 0 || name[0] == 0)
snprintf (name, size, "task%"PRIu64, (uint64_t) tid);
}
return DDS_RETCODE_OK;
}
#endif
static pthread_key_t thread_cleanup_key;
static pthread_once_t thread_once = PTHREAD_ONCE_INIT;

View file

@ -16,6 +16,60 @@
#include "dds/ddsrt/string.h"
#include "dds/ddsrt/threads_priv.h"
/* tlhelp32 for ddsrt_thread_list */
#include <stdio.h>
#include <stdlib.h>
#include <tlhelp32.h>
/* {Get,Set}ThreadDescription is the Windows 10 interface for dealing with thread names, but it at
least in some setups the linker can't find the symbols in kernel32.lib, even though kernel32.dll
exports them. (Perhaps it is just a broken installation, who knows ...) Looking them up
dynamically works fine. */
typedef HRESULT (WINAPI *SetThreadDescription_t) (HANDLE hThread, PCWSTR lpThreadDescription);
typedef HRESULT (WINAPI *GetThreadDescription_t) (HANDLE hThread, PWSTR *ppszThreadDescription);
static volatile SetThreadDescription_t SetThreadDescription_ptr = 0;
static volatile GetThreadDescription_t GetThreadDescription_ptr = 0;
static HRESULT WINAPI SetThreadDescription_dummy (HANDLE hThread, PCWSTR lpThreadDescription)
{
(void) hThread;
(void) lpThreadDescription;
return E_FAIL;
}
static HRESULT WINAPI GetThreadDescription_dummy (HANDLE hThread, PWSTR *ppszThreadDescription)
{
(void) hThread;
return E_FAIL;
}
static void getset_threaddescription_addresses (void)
{
/* Rely on MSVC's interpretation of the meaning of volatile
to order checking & setting the pointers */
if (GetThreadDescription_ptr == 0)
{
HMODULE mod;
FARPROC p;
if (!GetModuleHandleExA (GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, "kernel32.dll", &mod))
{
SetThreadDescription_ptr = SetThreadDescription_dummy;
GetThreadDescription_ptr = GetThreadDescription_dummy;
}
else
{
if ((p = GetProcAddress (mod, "SetThreadDescription")) != 0)
SetThreadDescription_ptr = (SetThreadDescription_t) p;
else
SetThreadDescription_ptr = SetThreadDescription_dummy;
if ((p = GetProcAddress (mod, "GetThreadDescription")) != 0)
GetThreadDescription_ptr = (GetThreadDescription_t) p;
else
GetThreadDescription_ptr = GetThreadDescription_dummy;
}
}
}
typedef struct {
char *name;
ddsrt_thread_routine_t routine;
@ -176,7 +230,10 @@ ddsrt_thread_join(
}
/* Thread names on Linux are limited to 16 bytes, no reason to provide
more storage than that as internal threads must adhere to that limit. */
more storage than that as internal threads must adhere to that limit.
Use the thread-local variable instead of relying on GetThreadDescription
to avoid the dynamic memory allocation, as the thread name is used by
the logging code and the overhead there matters. */
static ddsrt_thread_local char thread_name[16] = "";
size_t
@ -197,6 +254,16 @@ ddsrt_thread_getname(
return cnt;
}
/** \brief Set thread name for debugging and system monitoring
*
* Windows 10 introduced the SetThreadDescription function, which is
* obviously the sane interface. For reasons unknown to me, the
* linker claims to have no knowledge of the function, even though
* they appear present, and so it seems to sensible to retain the
* old exception-based trick as a fall-back mechanism. At least
* until the reason for {Get,Set}Description's absence from the
* regular libraries.
*/
static const DWORD MS_VC_EXCEPTION=0x406D1388;
#pragma pack(push,8)
@ -209,19 +276,23 @@ typedef struct tagTHREADNAME_INFO
} THREADNAME_INFO;
#pragma pack(pop)
/** \brief Wrap thread start routine
*
* \b os_startRoutineWrapper wraps a threads starting routine.
* before calling the user routine. It tries to set a thread name
* that will be visible if the process is running under the MS
* debugger.
*/
void
ddsrt_thread_setname(
const char *__restrict name)
{
assert(name != NULL);
assert (name != NULL);
getset_threaddescription_addresses ();
if (SetThreadDescription_ptr != SetThreadDescription_dummy)
{
size_t size = strlen (name) + 1;
wchar_t *wname = malloc (size * sizeof (*wname));
size_t cnt = 0;
mbstowcs_s (&cnt, wname, size, name, _TRUNCATE);
SetThreadDescription_ptr (GetCurrentThread (), wname);
free (wname);
}
else
{
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = name;
@ -241,10 +312,73 @@ ddsrt_thread_setname(
/* Suppress warnings. */
}
#pragma warning(pop)
ddsrt_strlcpy(thread_name, name, sizeof(thread_name));
}
ddsrt_strlcpy (thread_name, name, sizeof (thread_name));
}
dds_return_t
ddsrt_thread_list (
ddsrt_thread_list_id_t * __restrict tids,
size_t size)
{
HANDLE hThreadSnap;
THREADENTRY32 te32;
const DWORD pid = GetCurrentProcessId ();
int32_t n = 0;
if ((hThreadSnap = CreateToolhelp32Snapshot (TH32CS_SNAPTHREAD, 0)) == INVALID_HANDLE_VALUE)
return 0;
memset (&te32, 0, sizeof (te32));
te32.dwSize = sizeof (THREADENTRY32);
if (!Thread32First (hThreadSnap, &te32))
{
CloseHandle (hThreadSnap);
return 0;
}
do {
if (te32.th32OwnerProcessID != pid)
continue;
if ((size_t) n < size)
{
/* get a handle to the thread, not counting the thread the thread if no such
handle is obtainable */
if ((tids[n] = OpenThread (THREAD_QUERY_INFORMATION, FALSE, te32.th32ThreadID)) == NULL)
continue;
}
n++;
} while (Thread32Next (hThreadSnap, &te32));
CloseHandle (hThreadSnap);
return n;
}
dds_return_t
ddsrt_thread_getname_anythread (
ddsrt_thread_list_id_t tid,
char * __restrict name,
size_t size)
{
getset_threaddescription_addresses ();
if (size > 0)
{
PWSTR data;
HRESULT hr = GetThreadDescription_ptr (tid, &data);
if (! SUCCEEDED (hr))
name[0] = 0;
else
{
size_t cnt;
wcstombs_s (&cnt, name, size, data, _TRUNCATE);
LocalFree (data);
}
if (name[0] == 0)
{
snprintf (name, sizeof (name), "%"PRIdTID, GetThreadId (tid));
}
}
return DDS_RETCODE_OK;
}
static ddsrt_thread_local thread_cleanup_t *thread_cleanup = NULL;