forked from OSchip/llvm-project
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:
parent
18021a45aa
commit
ef7f968e09
|
@ -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],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -64,6 +64,23 @@ public:
|
|||
/// \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.
|
||||
/// @{
|
||||
|
@ -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)
|
||||
/// @{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue