Add time getters to the process interface for requesting the elapsed

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.

llvm-svn: 171551
This commit is contained in:
Chandler Carruth 2013-01-04 23:19:55 +00:00
parent 18021a45aa
commit ef7f968e09
9 changed files with 244 additions and 37 deletions

View File

@ -1250,6 +1250,10 @@ AC_SEARCH_LIBS(dlopen,dl,AC_DEFINE([HAVE_DLOPEN],[1],
[Define if dlopen() is available on this platform.]), [Define if dlopen() is available on this platform.]),
AC_MSG_WARN([dlopen() not found - disabling plugin support])) 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 dnl libffi is optional; used to call external functions from the interpreter
if test "$llvm_cv_enable_libffi" = "yes" ; then if test "$llvm_cv_enable_libffi" = "yes" ; then
AC_SEARCH_LIBS(ffi_call,ffi,AC_DEFINE([HAVE_FFI_CALL],[1], AC_SEARCH_LIBS(ffi_call,ffi,AC_DEFINE([HAVE_FFI_CALL],[1],

View File

@ -99,6 +99,7 @@ if( NOT PURE_WINDOWS )
endif() endif()
endif() endif()
check_library_exists(dl dlopen "" HAVE_LIBDL) check_library_exists(dl dlopen "" HAVE_LIBDL)
check_library_exists(rt clock_gettime "" HAVE_LIBRT)
endif() endif()
# function checks # function checks

View File

@ -4,6 +4,9 @@ function(get_system_libs return_var)
if( MINGW ) if( MINGW )
set(system_libs ${system_libs} imagehlp psapi) set(system_libs ${system_libs} imagehlp psapi)
elseif( CMAKE_HOST_UNIX ) elseif( CMAKE_HOST_UNIX )
if( HAVE_LIBRT )
set(system_libs ${system_libs} rt)
endif()
if( HAVE_LIBDL ) if( HAVE_LIBDL )
set(system_libs ${system_libs} ${CMAKE_DL_LIBS}) set(system_libs ${system_libs} ${CMAKE_DL_LIBS})
endif() endif()

104
llvm/configure vendored
View File

@ -12517,6 +12517,110 @@ echo "$as_me: WARNING: dlopen() not found - disabling plugin support" >&2;}
fi 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 if test "$llvm_cv_enable_libffi" = "yes" ; then
{ echo "$as_me:$LINENO: checking for library containing ffi_call" >&5 { echo "$as_me:$LINENO: checking for library containing ffi_call" >&5
echo $ECHO_N "checking for library containing ffi_call... $ECHO_C" >&6; } echo $ECHO_N "checking for library containing ffi_call... $ECHO_C" >&6; }

View File

@ -64,6 +64,23 @@ public:
/// \brief Get the operating system specific identifier for this process. /// \brief Get the operating system specific identifier for this process.
virtual id_type get_id() = 0; 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. /// \name Static factory routines for processes.
/// @{ /// @{
@ -88,6 +105,9 @@ class self_process : public process {
public: public:
virtual id_type get_id(); 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) /// \name Process configuration (sysconf on POSIX)
/// @{ /// @{

View File

@ -50,6 +50,31 @@ self_process::~self_process() {
llvm_unreachable("This destructor must never be executed!"); 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) #if defined(_MSC_VER)
#pragma warning(pop) #pragma warning(pop)
#endif #endif

View File

@ -49,6 +49,43 @@ process::id_type self_process::get_id() {
return getpid(); 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() { static unsigned getPageSize() {
#if defined(__CYGWIN__) #if defined(__CYGWIN__)
// On Cygwin, getpagesize() returns 64k but the page size for the purposes of // On Cygwin, getpagesize() returns 64k but the page size for the purposes of
@ -95,29 +132,10 @@ size_t Process::GetMallocUsage() {
#endif #endif
} }
void void Process::GetTimeUsage(TimeValue &elapsed, TimeValue &user_time,
Process::GetTimeUsage(TimeValue& elapsed, TimeValue& user_time, TimeValue &sys_time) {
TimeValue& sys_time)
{
elapsed = TimeValue::now(); elapsed = TimeValue::now();
#if defined(HAVE_GETRUSAGE) llvm::tie(user_time, sys_time) = getRUsageTimes();
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
} }
int Process::GetCurrentUserId() { int Process::GetCurrentUserId() {

View File

@ -43,6 +43,36 @@ process::id_type self_process::get_id() {
return GetCurrentProcess(); 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 // 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. // solely so it can be called once to initialize the self_process member below.
static unsigned getPageSize() { static unsigned getPageSize() {
@ -76,22 +106,17 @@ Process::GetMallocUsage()
return size; return size;
} }
void void Process::GetTimeUsage(TimeValue &elapsed, TimeValue &user_time,
Process::GetTimeUsage( TimeValue &sys_time) {
TimeValue& elapsed, TimeValue& user_time, TimeValue& sys_time)
{
elapsed = TimeValue::now(); elapsed = TimeValue::now();
uint64_t ProcCreate, ProcExit, KernelTime, UserTime; FILETIME ProcCreate, ProcExit, KernelTime, UserTime;
GetProcessTimes(GetCurrentProcess(), (FILETIME*)&ProcCreate, if (GetProcessTimes(GetCurrentProcess(), &ProcCreate, &ProcExit, &KernelTime,
(FILETIME*)&ProcExit, (FILETIME*)&KernelTime, &UserTime) == 0)
(FILETIME*)&UserTime); return;
// FILETIME's are # of 100 nanosecond ticks (1/10th of a microsecond) user_time = getTimeValueFromFILETIME(UserTime);
user_time.seconds( UserTime / 10000000 ); sys_time = getTimeValueFromFILETIME(SystemTime);
user_time.nanoseconds( unsigned(UserTime % 10000000) * 100 );
sys_time.seconds( KernelTime / 10000000 );
sys_time.nanoseconds( unsigned(KernelTime % 10000000) * 100 );
} }
int Process::GetCurrentUserId() int Process::GetCurrentUserId()

View File

@ -30,6 +30,13 @@ TEST(ProcessTest, SelfProcess) {
#endif #endif
EXPECT_LT(1u, process::get_self()->page_size()); 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 } // end anonymous namespace