2020-12-12 15:01:29 +03:00
#include "util/types.hpp"
2021-02-23 13:52:07 +03:00
#include "util/cpu_stats.hpp"
#include "util/sysinfo.hpp"
2022-03-11 21:08:44 +01:00
#include "util/logs.hpp"
#include "Utilities/StrUtil.h"
2023-05-18 00:10:49 +02:00
2021-02-23 13:52:07 +03:00
#include <algorithm>
2018-05-24 22:20:13 +03:00
#ifdef _WIN32
2025-02-11 03:00:37 +01:00
#include "util/asm.hpp"
2018-05-24 22:20:13 +03:00
#include "windows.h"
#include "tlhelp32.h"
2023-07-11 20:40:30 +02:00
#ifdef _MSC_VER
2022-03-11 21:08:44 +01:00
#pragma comment(lib, "pdh.lib")
2023-07-11 20:40:30 +02:00
#endif
2018-05-24 22:20:13 +03:00
#else
2022-03-11 21:08:44 +01:00
#include "fstream"
#include "sstream"
2018-05-24 22:20:13 +03:00
#include "stdlib.h"
#include "sys/times.h"
2018-05-24 15:33:23 +00:00
#endif
#ifdef __APPLE__
# include <mach/mach_init.h>
# include <mach/task.h>
# include <mach/vm_map.h>
#endif
#ifdef __linux__
# include <dirent.h>
#endif
#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
# include <sys/sysctl.h>
2025-02-12 17:56:13 +01:00
# include <unistd.h>
2018-05-24 15:33:23 +00:00
# if defined(__DragonFly__) || defined(__FreeBSD__)
# include <sys/user.h>
# endif
# if defined(__NetBSD__)
# undef KERN_PROC
# define KERN_PROC KERN_PROC2
# define kinfo_proc kinfo_proc2
# endif
# if defined(__DragonFly__)
# define KP_NLWP(kp) (kp.kp_nthreads)
# elif defined(__FreeBSD__)
# define KP_NLWP(kp) (kp.ki_numthreads)
# elif defined(__NetBSD__)
# define KP_NLWP(kp) (kp.p_nlwps)
# endif
2018-05-24 22:20:13 +03:00
#endif
2022-03-11 21:08:44 +01:00
LOG_CHANNEL ( perf_log , "PERF" );
2021-02-23 13:52:07 +03:00
namespace utils
2018-05-24 22:20:13 +03:00
{
2022-03-11 21:08:44 +01:00
#ifdef _WIN32
2023-03-04 11:05:32 +01:00
fmt :: win_error pdh_error ( PDH_STATUS status )
2022-03-11 21:08:44 +01:00
{
2023-03-04 11:05:32 +01:00
return fmt :: win_error { static_cast < unsigned long > ( status ), LoadLibrary ( L "pdh.dll" )};
2022-03-11 21:08:44 +01:00
}
#endif
2021-02-23 13:52:07 +03:00
cpu_stats :: cpu_stats ()
2018-05-24 22:20:13 +03:00
{
#ifdef _WIN32
FILETIME ftime , fsys , fuser ;
GetSystemTimeAsFileTime ( & ftime );
memcpy ( & m_last_cpu , & ftime , sizeof ( FILETIME ));
2021-02-23 13:52:07 +03:00
GetProcessTimes ( GetCurrentProcess (), & ftime , & ftime , & fsys , & fuser );
2018-05-24 22:20:13 +03:00
memcpy ( & m_sys_cpu , & fsys , sizeof ( FILETIME ));
memcpy ( & m_usr_cpu , & fuser , sizeof ( FILETIME ));
#else
struct tms timeSample ;
m_last_cpu = times ( & timeSample );
m_sys_cpu = timeSample . tms_stime ;
m_usr_cpu = timeSample . tms_utime ;
#endif
}
2023-05-18 00:10:49 +02:00
cpu_stats ::~ cpu_stats ()
{
#ifdef _WIN32
if ( m_cpu_query )
{
PDH_STATUS status = PdhCloseQuery ( m_cpu_query );
if ( ERROR_SUCCESS != status )
{
perf_log . error ( "Failed to close cpu query of per core cpu usage: %s" , pdh_error ( status ));
}
}
#endif
}
2022-03-11 21:08:44 +01:00
void cpu_stats :: init_cpu_query ()
{
#ifdef _WIN32
2023-07-11 20:40:30 +02:00
PDH_STATUS status = PdhOpenQuery ( NULL , 0 , & m_cpu_query );
2022-03-11 21:08:44 +01:00
if ( ERROR_SUCCESS != status )
{
perf_log . error ( "Failed to open cpu query for per core cpu usage: %s" , pdh_error ( status ));
return ;
}
2023-07-11 20:40:30 +02:00
status = PdhAddEnglishCounter ( m_cpu_query , L " \\ Processor(*) \\ % Processor Time" , 0 , & m_cpu_cores );
2022-03-11 21:08:44 +01:00
if ( ERROR_SUCCESS != status )
{
perf_log . error ( "Failed to add processor time counter for per core cpu usage: %s" , pdh_error ( status ));
return ;
}
status = PdhCollectQueryData ( m_cpu_query );
if ( ERROR_SUCCESS != status )
{
perf_log . error ( "Failed to collect per core cpu usage: %s" , pdh_error ( status ));
return ;
}
#endif
}
void cpu_stats :: get_per_core_usage ( std :: vector < double >& per_core_usage , double & total_usage )
{
total_usage = 0.0 ;
per_core_usage . resize ( utils :: get_thread_count ());
std :: fill ( per_core_usage . begin (), per_core_usage . end (), 0.0 );
#ifdef _WIN32
2022-03-18 00:16:31 +01:00
if ( ! m_cpu_cores || ! m_cpu_query )
{
perf_log . warning ( "Can not collect per core cpu usage: The required API is not initialized." );
return ;
}
2022-03-11 21:08:44 +01:00
PDH_STATUS status = PdhCollectQueryData ( m_cpu_query );
if ( ERROR_SUCCESS != status )
{
perf_log . error ( "Failed to collect per core cpu usage: %s" , pdh_error ( status ));
return ;
}
2023-05-18 00:10:49 +02:00
DWORD dwBufferSize = 0 ; // Size of the items buffer
DWORD dwItemCount = 0 ; // Number of items in the items buffer
2022-03-11 21:08:44 +01:00
2023-05-18 00:10:49 +02:00
status = PdhGetFormattedCounterArray ( m_cpu_cores , PDH_FMT_DOUBLE , & dwBufferSize , & dwItemCount , nullptr );
2023-07-11 20:40:30 +02:00
if ( static_cast < PDH_STATUS > ( PDH_MORE_DATA ) == status )
2022-03-11 21:08:44 +01:00
{
2023-05-18 00:10:49 +02:00
std :: vector < PDH_FMT_COUNTERVALUE_ITEM > items ( utils :: aligned_div ( dwBufferSize , sizeof ( PDH_FMT_COUNTERVALUE_ITEM )));
if ( items . size () >= dwItemCount )
2022-03-11 21:08:44 +01:00
{
2023-05-18 00:10:49 +02:00
status = PdhGetFormattedCounterArray ( m_cpu_cores , PDH_FMT_DOUBLE , & dwBufferSize , & dwItemCount , items . data ());
2022-03-11 21:08:44 +01:00
if ( ERROR_SUCCESS == status )
{
2023-05-18 00:10:49 +02:00
ensure ( dwItemCount == per_core_usage . size () + 1 ); // Plus one for _Total
2022-03-11 21:08:44 +01:00
// Loop through the array and get the instance name and percentage.
2023-05-18 00:10:49 +02:00
for ( usz i = 0 ; i < dwItemCount ; i ++ )
2022-03-11 21:08:44 +01:00
{
2023-05-18 00:10:49 +02:00
const PDH_FMT_COUNTERVALUE_ITEM & item = items [ i ];
const std :: string token = wchar_to_utf8 ( item . szName );
2022-03-11 21:08:44 +01:00
if ( const std :: string lower = fmt :: to_lower ( token ); lower . find ( "total" ) != umax )
{
2023-05-18 00:10:49 +02:00
total_usage = item . FmtValue . doubleValue ;
2022-03-11 21:08:44 +01:00
continue ;
}
if ( const auto [ success , cpu_index ] = string_to_number ( token ); success && cpu_index < dwItemCount )
{
2023-05-18 00:10:49 +02:00
per_core_usage [ cpu_index ] = item . FmtValue . doubleValue ;
2022-03-11 21:08:44 +01:00
}
else if ( ! success )
{
perf_log . error ( "Can not convert string to cpu index for per core cpu usage. (token='%s')" , token );
}
else
{
perf_log . error ( "Invalid cpu index for per core cpu usage. (token='%s', cpu_index=%d, cores=%d)" , token , cpu_index , dwItemCount );
}
}
}
2023-07-11 20:40:30 +02:00
else if ( static_cast < PDH_STATUS > ( PDH_CALC_NEGATIVE_DENOMINATOR ) == status ) // Apparently this is a common uncritical error
2023-06-16 19:14:11 +02:00
{
perf_log . notice ( "Failed to get per core cpu usage: %s" , pdh_error ( status ));
}
2022-03-11 21:08:44 +01:00
else
{
perf_log . error ( "Failed to get per core cpu usage: %s" , pdh_error ( status ));
}
}
else
{
2023-05-18 00:10:49 +02:00
perf_log . error ( "Failed to allocate buffer for per core cpu usage. (size=%d, dwItemCount=%d)" , items . size (), dwItemCount );
2022-03-11 21:08:44 +01:00
}
}
#elif __linux__
2025-02-28 21:25:57 +03:00
#ifndef ANDROID
2022-03-11 21:08:44 +01:00
m_previous_idle_times_per_cpu . resize ( utils :: get_thread_count (), 0.0 );
m_previous_total_times_per_cpu . resize ( utils :: get_thread_count (), 0.0 );
if ( std :: ifstream proc_stat ( "/proc/stat" ); proc_stat . good ())
{
std :: stringstream content ;
content << proc_stat . rdbuf ();
proc_stat . close ();
2026-06-16 00:38:47 +02:00
const std :: string content_str = content . str ();
const std :: vector < std :: string_view > lines = fmt :: split_sv ( content_str , { " \n " });
2022-03-11 21:08:44 +01:00
if ( lines . empty ())
{
perf_log . error ( "/proc/stat is empty" );
return ;
}
2026-06-16 00:38:47 +02:00
for ( const std :: string_view & line : lines )
2022-03-11 21:08:44 +01:00
{
2026-06-16 00:38:47 +02:00
const std :: vector < std :: string_view > tokens = fmt :: split_sv ( line , { " " });
2022-03-11 21:08:44 +01:00
if ( tokens . size () < 5 )
{
return ;
}
2026-06-16 00:38:47 +02:00
const std :: string_view token = tokens [ 0 ];
2022-03-11 21:08:44 +01:00
if ( ! token . starts_with ( "cpu" ))
{
return ;
}
// Get CPU index
int cpu_index = - 1 ; // -1 for total
constexpr size_t size_of_cpu = 3 ;
if ( token . size () > size_of_cpu )
{
if ( const auto [ success , val ] = string_to_number ( token . substr ( size_of_cpu )); success && val < per_core_usage . size ())
{
cpu_index = val ;
}
else if ( ! success )
{
perf_log . error ( "Can not convert string to cpu index for per core cpu usage. (token='%s', line='%s')" , token , line );
continue ;
}
else
{
perf_log . error ( "Invalid cpu index for per core cpu usage. (cpu_index=%d, cores=%d, token='%s', line='%s')" , cpu_index , per_core_usage . size (), token , line );
continue ;
}
}
size_t idle_time = 0 ;
size_t total_time = 0 ;
for ( size_t i = 1 ; i < tokens . size (); i ++ )
{
if ( const auto [ success , val ] = string_to_number ( tokens [ i ]); success )
{
if ( i == 4 )
{
idle_time = val ;
}
total_time += val ;
}
else
{
perf_log . error ( "Can not convert string to time for per core cpu usage. (i=%d, token='%s', line='%s')" , i , tokens [ i ], line );
}
}
if ( cpu_index < 0 )
{
const double idle_time_delta = idle_time - std :: exchange ( m_previous_idle_time_total , idle_time );
const double total_time_delta = total_time - std :: exchange ( m_previous_total_time_total , total_time );
total_usage = 100.0 * ( 1.0 - idle_time_delta / total_time_delta );
}
else
{
const double idle_time_delta = idle_time - std :: exchange ( m_previous_idle_times_per_cpu [ cpu_index ], idle_time );
const double total_time_delta = total_time - std :: exchange ( m_previous_total_times_per_cpu [ cpu_index ], total_time );
per_core_usage [ cpu_index ] = 100.0 * ( 1.0 - idle_time_delta / total_time_delta );
}
}
}
else
{
perf_log . error ( "Failed to open /proc/stat (%s)" , strerror ( errno ));
}
2026-08-09 02:56:50 -04:00
#else
// Android has no per-core numbers to report: an app is not allowed to read the
// per-cpu lines of /proc/stat, which is why this branch was excluded in the first
// place. But excluding it left total_usage at 0.0 and per_core_usage full of the
// zeros filled in above, so the monitor did not go quiet -- it reported an idle
// machine. Observed on device logging "CPU Usage: Total: 0.0%, Cores: 0.0%, 0.0%,
// ..." while three emulator threads were pegged at 100%, which actively hides the
// class of bug this monitor exists to surface.
//
// times() is process-wide, POSIX, and readable by our own process, so report that
// and leave the per-core vector empty. perf_monitor only prints the "Cores:" list
// when it is non-empty, so nothing fabricates a per-core figure we cannot measure.
per_core_usage . clear ();
total_usage = get_usage ();
2025-02-28 21:25:57 +03:00
#endif
2022-03-11 21:08:44 +01:00
#else
total_usage = get_usage ();
#endif
}
2021-02-23 13:52:07 +03:00
double cpu_stats :: get_usage ()
2018-05-24 22:20:13 +03:00
{
#ifdef _WIN32
FILETIME ftime , fsys , fusr ;
ULARGE_INTEGER now , sys , usr ;
2021-02-24 23:20:32 +01:00
double percent ;
2018-05-24 22:20:13 +03:00
GetSystemTimeAsFileTime ( & ftime );
memcpy ( & now , & ftime , sizeof ( FILETIME ));
2021-02-23 13:52:07 +03:00
GetProcessTimes ( GetCurrentProcess (), & ftime , & ftime , & fsys , & fusr );
2018-05-24 22:20:13 +03:00
memcpy ( & sys , & fsys , sizeof ( FILETIME ));
memcpy ( & usr , & fusr , sizeof ( FILETIME ));
2021-02-24 23:20:32 +01:00
if ( now . QuadPart <= m_last_cpu || sys . QuadPart < m_sys_cpu || usr . QuadPart < m_usr_cpu )
{
// Overflow detection. Just skip this value.
percent = 0.0 ;
}
else
{
2023-12-29 18:33:29 +01:00
percent = static_cast < double > (( sys . QuadPart - m_sys_cpu ) + ( usr . QuadPart - m_usr_cpu ));
2021-02-24 23:20:32 +01:00
percent /= ( now . QuadPart - m_last_cpu );
percent /= utils :: get_thread_count (); // Let's assume this is at least 1
percent *= 100 ;
}
2018-05-24 22:20:13 +03:00
2021-02-23 13:52:07 +03:00
m_last_cpu = now . QuadPart ;
m_usr_cpu = usr . QuadPart ;
m_sys_cpu = sys . QuadPart ;
2018-05-24 22:20:13 +03:00
2021-02-24 23:20:32 +01:00
return std :: clamp ( percent , 0.0 , 100.0 );
2018-05-24 22:20:13 +03:00
#else
struct tms timeSample ;
2021-02-24 23:20:32 +01:00
clock_t now = times ( & timeSample );
2018-05-24 22:20:13 +03:00
double percent ;
2021-02-23 13:52:07 +03:00
if ( now <= static_cast < clock_t > ( m_last_cpu ) || timeSample . tms_stime < static_cast < clock_t > ( m_sys_cpu ) || timeSample . tms_utime < static_cast < clock_t > ( m_usr_cpu ))
2018-05-24 22:20:13 +03:00
{
// Overflow detection. Just skip this value.
2021-02-24 23:20:32 +01:00
percent = 0.0 ;
2018-05-24 22:20:13 +03:00
}
else
{
percent = ( timeSample . tms_stime - m_sys_cpu ) + ( timeSample . tms_utime - m_usr_cpu );
percent /= ( now - m_last_cpu );
2021-02-23 13:52:07 +03:00
percent /= utils :: get_thread_count ();
2018-05-24 22:20:13 +03:00
percent *= 100 ;
}
m_last_cpu = now ;
m_sys_cpu = timeSample . tms_stime ;
m_usr_cpu = timeSample . tms_utime ;
2021-02-24 23:20:32 +01:00
return std :: clamp ( percent , 0.0 , 100.0 );
2018-05-24 22:20:13 +03:00
#endif
}
2022-03-11 21:08:44 +01:00
u32 cpu_stats :: get_current_thread_count () // static
2018-05-24 22:20:13 +03:00
{
#ifdef _WIN32
// first determine the id of the current process
2021-04-09 21:12:47 +02:00
const DWORD id = GetCurrentProcessId ();
2018-05-24 22:20:13 +03:00
// then get a process list snapshot.
2021-04-09 21:12:47 +02:00
const HANDLE snapshot = CreateToolhelp32Snapshot ( TH32CS_SNAPPROCESS , 0 );
2018-05-24 22:20:13 +03:00
// initialize the process entry structure.
PROCESSENTRY32 entry = { 0 };
entry . dwSize = sizeof ( entry );
// get the first process info.
2023-05-18 00:10:49 +02:00
BOOL ret = Process32First ( snapshot , & entry );
2018-05-24 22:20:13 +03:00
while ( ret && entry . th32ProcessID != id )
{
ret = Process32Next ( snapshot , & entry );
}
CloseHandle ( snapshot );
return ret ? entry . cntThreads : 0 ;
2018-05-24 15:33:23 +00:00
#elif defined(__APPLE__)
const task_t task = mach_task_self ();
mach_msg_type_number_t thread_count ;
thread_act_array_t thread_list ;
if ( task_threads ( task , & thread_list , & thread_count ) != KERN_SUCCESS )
{
return 0 ;
}
vm_deallocate ( task , reinterpret_cast < vm_address_t > ( thread_list ),
sizeof ( thread_t ) * thread_count );
return static_cast < u32 > ( thread_count );
#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
int mib [] = {
CTL_KERN ,
KERN_PROC ,
KERN_PROC_PID ,
getpid (),
#if defined(__NetBSD__)
sizeof ( struct kinfo_proc ),
1 ,
#endif
};
2018-09-05 22:52:31 +02:00
u_int miblen = std :: size ( mib );
2018-05-24 15:33:23 +00:00
struct kinfo_proc info ;
2020-12-18 10:39:54 +03:00
usz size = sizeof ( info );
2018-05-24 15:33:23 +00:00
if ( sysctl ( mib , miblen , & info , & size , NULL , 0 ))
{
return 0 ;
}
return KP_NLWP ( info );
#elif defined(__OpenBSD__)
int mib [] = {
CTL_KERN ,
KERN_PROC ,
KERN_PROC_PID | KERN_PROC_SHOW_THREADS ,
getpid (),
sizeof ( struct kinfo_proc ),
0 ,
};
2018-09-05 22:52:31 +02:00
u_int miblen = std :: size ( mib );
2018-05-24 15:33:23 +00:00
// get number of structs
2020-12-18 10:39:54 +03:00
usz size ;
2018-05-24 15:33:23 +00:00
if ( sysctl ( mib , miblen , NULL , & size , NULL , 0 ))
{
return 0 ;
}
mib [ 5 ] = size / mib [ 4 ];
// populate array of structs
struct kinfo_proc info [ mib [ 5 ]];
if ( sysctl ( mib , miblen , & info , & size , NULL , 0 ))
{
return 0 ;
}
// exclude empty members
u32 thread_count { 0 };
for ( int i = 0 ; i < size / mib [ 4 ]; i ++ )
{
if ( info [ i ]. p_tid != - 1 )
++ thread_count ;
}
return thread_count ;
#elif defined(__linux__)
2018-05-24 22:20:13 +03:00
u32 thread_count { 0 };
DIR * proc_dir = opendir ( "/proc/self/task" );
if ( proc_dir )
{
// proc available, iterate through tasks and count them
2025-06-01 20:45:05 +02:00
const struct dirent * entry ;
2018-05-24 22:20:13 +03:00
while (( entry = readdir ( proc_dir )) != NULL )
{
if ( entry -> d_name [ 0 ] == '.' )
continue ;
++ thread_count ;
}
closedir ( proc_dir );
}
return thread_count ;
2018-05-24 15:33:23 +00:00
#else
// unimplemented
return 0 ;
2018-05-24 22:20:13 +03:00
#endif
}
2021-02-23 13:52:07 +03:00
}