Skip to content

Commit

Permalink
Add time getters to the process interface for requesting the elapsed
Browse files Browse the repository at this point in the history
wall time, user time, and system time since a process started.

For walltime, we currently use TimeValue's interface and a global
initializer to compute a close approximation of total process runtime.

For user time, this adds support for an somewhat more precise timing
mechanism -- clock_gettime with the CLOCK_PROCESS_CPUTIME_ID clock
selected.

For system time, we have to do a full getrusage call to extract the
system time from the OS. This is expensive but unavoidable.

In passing, clean up the implementation of the old APIs and fix some
latent bugs in the Windows code. This might have manifested on Windows
ARM systems or other systems with strange 64-bit integer behavior.

The old API for this both user time and system time simultaneously from
a single getrusage call. While this results in fewer system calls, it
also results in a lower precision user time and if only user time is
desired, it introduces a higher overhead. It may be worthwhile to switch
some of the pass timers to not track system time and directly track user
and wall time. The old API also tracked walltime in a confusing way --
it just set it to the current walltime rather than providing any measure
of wall time since the process started the way buth user and system time
are tracked. The new API is more consistent here.

The plan is to eventually implement these methods for a *child* process
by using the wait3(2) system call to populate an rusage struct
representing the whole subprocess execution. That way, after waiting on
a child process its stats will become accurate and cheap to query.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@171551 91177308-0d34-0410-b5e6-96231b3b80d8
  • Loading branch information
chandlerc committed Jan 4, 2013
1 parent 57fa912 commit 73c35d8
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 37 deletions.
4 changes: 4 additions & 0 deletions autoconf/configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,10 @@ AC_SEARCH_LIBS(dlopen,dl,AC_DEFINE([HAVE_DLOPEN],[1],
[Define if dlopen() is available on this platform.]),
AC_MSG_WARN([dlopen() not found - disabling plugin support]))

dnl clock_gettime() is required for modern timing support.
AC_SEARCH_LIBS(clock_gettime,rt,[],
AC_MSG_ERROR([clock_gettime not found and is required for modern POSIX timing uspport]))

dnl libffi is optional; used to call external functions from the interpreter
if test "$llvm_cv_enable_libffi" = "yes" ; then
AC_SEARCH_LIBS(ffi_call,ffi,AC_DEFINE([HAVE_FFI_CALL],[1],
Expand Down
1 change: 1 addition & 0 deletions cmake/config-ix.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ if( NOT PURE_WINDOWS )
endif()
endif()
check_library_exists(dl dlopen "" HAVE_LIBDL)
check_library_exists(rt clock_gettime "" HAVE_LIBRT)
endif()

# function checks
Expand Down
7 changes: 5 additions & 2 deletions cmake/modules/LLVM-Config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ function(get_system_libs return_var)
if( MINGW )
set(system_libs ${system_libs} imagehlp psapi)
elseif( CMAKE_HOST_UNIX )
if( HAVE_LIBRT )
set(system_libs ${system_libs} rt)
endif()
if( HAVE_LIBDL )
set(system_libs ${system_libs} ${CMAKE_DL_LIBS})
set(system_libs ${system_libs} ${CMAKE_DL_LIBS})
endif()
if( LLVM_ENABLE_THREADS AND HAVE_LIBPTHREAD )
set(system_libs ${system_libs} pthread)
set(system_libs ${system_libs} pthread)
endif()
endif( MINGW )
endif( NOT MSVC )
Expand Down
104 changes: 104 additions & 0 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -12517,6 +12517,110 @@ echo "$as_me: WARNING: dlopen() not found - disabling plugin support" >&2;}
fi


{ echo "$as_me:$LINENO: checking for library containing clock_gettime" >&5
echo $ECHO_N "checking for library containing clock_gettime... $ECHO_C" >&6; }
if test "${ac_cv_search_clock_gettime+set}" = set; then
echo $ECHO_N "(cached) $ECHO_C" >&6
else
ac_func_search_save_LIBS=$LIBS
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */

/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char clock_gettime ();
int
main ()
{
return clock_gettime ();
;
return 0;
}
_ACEOF
for ac_lib in '' rt; do
if test -z "$ac_lib"; then
ac_res="none required"
else
ac_res=-l$ac_lib
LIBS="-l$ac_lib $ac_func_search_save_LIBS"
fi
rm -f conftest.$ac_objext conftest$ac_exeext
if { (ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_link") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } &&
{ ac_try='test -z "$ac_c_werror_flag" || test ! -s conftest.err'
{ (case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_try") 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); }; } &&
{ ac_try='test -s conftest$ac_exeext'
{ (case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_try") 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); }; }; then
ac_cv_search_clock_gettime=$ac_res
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5


fi

rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext
if test "${ac_cv_search_clock_gettime+set}" = set; then
break
fi
done
if test "${ac_cv_search_clock_gettime+set}" = set; then
:
else
ac_cv_search_clock_gettime=no
fi
rm conftest.$ac_ext
LIBS=$ac_func_search_save_LIBS
fi
{ echo "$as_me:$LINENO: result: $ac_cv_search_clock_gettime" >&5
echo "${ECHO_T}$ac_cv_search_clock_gettime" >&6; }
ac_res=$ac_cv_search_clock_gettime
if test "$ac_res" != no; then
test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"

else
{ { echo "$as_me:$LINENO: error: clock_gettime not found and is required for modern POSIX timing uspport" >&5
echo "$as_me: error: clock_gettime not found and is required for modern POSIX timing uspport" >&2;}
{ (exit 1); exit 1; }; }
fi


if test "$llvm_cv_enable_libffi" = "yes" ; then
{ echo "$as_me:$LINENO: checking for library containing ffi_call" >&5
echo $ECHO_N "checking for library containing ffi_call... $ECHO_C" >&6; }
Expand Down
20 changes: 20 additions & 0 deletions include/llvm/Support/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ class process {
/// \brief Get the operating system specific identifier for this process.
virtual id_type get_id() = 0;

/// \brief Get the user time consumed by this process.
///
/// Note that this is often an approximation and may be zero on platforms
/// where we don't have good support for the functionality.
virtual TimeValue get_user_time() const = 0;

/// \brief Get the system time consumed by this process.
///
/// Note that this is often an approximation and may be zero on platforms
/// where we don't have good support for the functionality.
virtual TimeValue get_system_time() const = 0;

/// \brief Get the wall time consumed by this process.
///
/// Note that this is often an approximation and may be zero on platforms
/// where we don't have good support for the functionality.
virtual TimeValue get_wall_time() const = 0;

/// \name Static factory routines for processes.
/// @{
Expand All @@ -88,6 +105,9 @@ class self_process : public process {

public:
virtual id_type get_id();
virtual TimeValue get_user_time() const;
virtual TimeValue get_system_time() const;
virtual TimeValue get_wall_time() const;

/// \name Process configuration (sysconf on POSIX)
/// @{
Expand Down
25 changes: 25 additions & 0 deletions lib/Support/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,31 @@ self_process::~self_process() {
llvm_unreachable("This destructor must never be executed!");
}

/// \brief A helper function to compute the elapsed wall-time since the program
/// started.
///
/// Note that this routine actually computes the elapsed wall time since the
/// first time it was called. However, we arrange to have it called during the
/// startup of the process to get approximately correct results.
static TimeValue getElapsedWallTime() {
static TimeValue &StartTime = *new TimeValue(TimeValue::now());
return TimeValue::now() - StartTime;
}

/// \brief A special global variable to ensure we call \c getElapsedWallTime
/// during global initialization of the program.
///
/// Note that this variable is never referenced elsewhere. Doing so could
/// create race conditions during program startup or shutdown.
static volatile TimeValue DummyTimeValue = getElapsedWallTime();

// Implement this routine by using the static helpers above. They're already
// portable.
TimeValue self_process::get_wall_time() const {
return getElapsedWallTime();
}


#if defined(_MSC_VER)
#pragma warning(pop)
#endif
Expand Down
62 changes: 40 additions & 22 deletions lib/Support/Unix/Process.inc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,43 @@ process::id_type self_process::get_id() {
return getpid();
}

static std::pair<TimeValue, TimeValue> getRUsageTimes() {
#if defined(HAVE_GETRUSAGE)
struct rusage RU;
::getrusage(RUSAGE_SELF, &RU);
return std::make_pair(
TimeValue(
static_cast<TimeValue::SecondsType>(RU.ru_utime.tv_sec),
static_cast<TimeValue::NanoSecondsType>(
RU.ru_utime.tv_usec * TimeValue::NANOSECONDS_PER_MICROSECOND)),
TimeValue(
static_cast<TimeValue::SecondsType>(RU.ru_stime.tv_sec),
static_cast<TimeValue::NanoSecondsType>(
RU.ru_stime.tv_usec * TimeValue::NANOSECONDS_PER_MICROSECOND)));
#else
#warning Cannot get usage times on this platform
return std::make_pair(TimeValue(), TimeValue());
#endif
}

TimeValue self_process::get_user_time() const {
#ifdef _POSIX_CPUTIME
// Try to get a high resolution CPU timer.
struct timespec TS;
if (::clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &TS) == 0)
return TimeValue(static_cast<TimeValue::SecondsType>(TS.tv_sec),
static_cast<TimeValue::NanoSecondsType>(TS.tv_nsec));
#endif

// Otherwise fall back to rusage based timing.
return getRUsageTimes().first;
}

TimeValue self_process::get_system_time() const {
// We can only collect system time by inspecting the results of getrusage.
return getRUsageTimes().second;
}

static unsigned getPageSize() {
#if defined(__CYGWIN__)
// On Cygwin, getpagesize() returns 64k but the page size for the purposes of
Expand Down Expand Up @@ -95,29 +132,10 @@ size_t Process::GetMallocUsage() {
#endif
}

void
Process::GetTimeUsage(TimeValue& elapsed, TimeValue& user_time,
TimeValue& sys_time)
{
void Process::GetTimeUsage(TimeValue &elapsed, TimeValue &user_time,
TimeValue &sys_time) {
elapsed = TimeValue::now();
#if defined(HAVE_GETRUSAGE)
struct rusage usage;
::getrusage(RUSAGE_SELF, &usage);
user_time = TimeValue(
static_cast<TimeValue::SecondsType>( usage.ru_utime.tv_sec ),
static_cast<TimeValue::NanoSecondsType>( usage.ru_utime.tv_usec *
TimeValue::NANOSECONDS_PER_MICROSECOND ) );
sys_time = TimeValue(
static_cast<TimeValue::SecondsType>( usage.ru_stime.tv_sec ),
static_cast<TimeValue::NanoSecondsType>( usage.ru_stime.tv_usec *
TimeValue::NANOSECONDS_PER_MICROSECOND ) );
#else
#warning Cannot get usage times on this platform
user_time.seconds(0);
user_time.microseconds(0);
sys_time.seconds(0);
sys_time.microseconds(0);
#endif
llvm::tie(user_time, sys_time) = getRUsageTimes();
}

int Process::GetCurrentUserId() {
Expand Down
51 changes: 38 additions & 13 deletions lib/Support/Windows/Process.inc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,36 @@ process::id_type self_process::get_id() {
return GetCurrentProcess();
}

static TimeValue getTimeValueFromFILETIME(FILETIME Time) {
ULARGE_INTEGER TimeInteger;
TimeInteger.LowPart = Time.dwLowDateTime;
TimeInteger.HighPart = Time.dwHighDateTime;

// FILETIME's are # of 100 nanosecond ticks (1/10th of a microsecond)
return TimeValue(
static_cast<TimeValue::SecondsType>(TimeInteger.QuadPart / 10000000),
static_cast<TimeValue::NanoSecondsType>(
(TimeInteger.QuadPart % 10000000) * 100));
}

TimeValue self_process::get_user_time() const {
FILETIME ProcCreate, ProcExit, KernelTime, UserTime;
if (GetProcessTimes(GetCurrentProcess(), &ProcCreate, &ProcExit, &KernelTime,
&UserTime) == 0)
return TimeValue();

return getTimeValueFromFILETIME(UserTime);
}

TimeValue self_process::get_system_time() const {
FILETIME ProcCreate, ProcExit, KernelTime, UserTime;
if (GetProcessTimes(GetCurrentProcess(), &ProcCreate, &ProcExit, &KernelTime,
&UserTime) == 0)
return TimeValue();

return getTimeValueFromFILETIME(KernelTime);
}

// This function retrieves the page size using GetSystemInfo and is present
// solely so it can be called once to initialize the self_process member below.
static unsigned getPageSize() {
Expand Down Expand Up @@ -76,22 +106,17 @@ Process::GetMallocUsage()
return size;
}

void
Process::GetTimeUsage(
TimeValue& elapsed, TimeValue& user_time, TimeValue& sys_time)
{
void Process::GetTimeUsage(TimeValue &elapsed, TimeValue &user_time,
TimeValue &sys_time) {
elapsed = TimeValue::now();

uint64_t ProcCreate, ProcExit, KernelTime, UserTime;
GetProcessTimes(GetCurrentProcess(), (FILETIME*)&ProcCreate,
(FILETIME*)&ProcExit, (FILETIME*)&KernelTime,
(FILETIME*)&UserTime);
FILETIME ProcCreate, ProcExit, KernelTime, UserTime;
if (GetProcessTimes(GetCurrentProcess(), &ProcCreate, &ProcExit, &KernelTime,
&UserTime) == 0)
return;

// FILETIME's are # of 100 nanosecond ticks (1/10th of a microsecond)
user_time.seconds( UserTime / 10000000 );
user_time.nanoseconds( unsigned(UserTime % 10000000) * 100 );
sys_time.seconds( KernelTime / 10000000 );
sys_time.nanoseconds( unsigned(KernelTime % 10000000) * 100 );
user_time = getTimeValueFromFILETIME(UserTime);
sys_time = getTimeValueFromFILETIME(SystemTime);
}

int Process::GetCurrentUserId()
Expand Down
7 changes: 7 additions & 0 deletions unittests/Support/ProcessTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ TEST(ProcessTest, SelfProcess) {
#endif

EXPECT_LT(1u, process::get_self()->page_size());

EXPECT_LT(TimeValue::MinTime, process::get_self()->get_user_time());
EXPECT_GT(TimeValue::MaxTime, process::get_self()->get_user_time());
EXPECT_LT(TimeValue::MinTime, process::get_self()->get_system_time());
EXPECT_GT(TimeValue::MaxTime, process::get_self()->get_system_time());
EXPECT_LT(TimeValue::MinTime, process::get_self()->get_wall_time());
EXPECT_GT(TimeValue::MaxTime, process::get_self()->get_wall_time());
}

} // end anonymous namespace

0 comments on commit 73c35d8

Please sign in to comment.