commit 45e6aebfbf15b6443ccb96ff8f3ea9ddb0802359 Author: Harald Eilertsen Date: Fri Jun 20 13:21:43 2025 +0200 A simple app to get run times from a thread Just a quick app to test and demonstrate how to get run times from a specific thread on FreeBSD. diff --git a/LICENSES/BSD-2-Clause.txt b/LICENSES/BSD-2-Clause.txt new file mode 100644 index 0000000..5f662b3 --- /dev/null +++ b/LICENSES/BSD-2-Clause.txt @@ -0,0 +1,9 @@ +Copyright (c) + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6610a6d --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri +# SPDX-FileCopyrightText: 2025 The FreeBSD Foundation +# SPDX-FileContributor: Harald Eilertsen +# +# SPDX-License-Identifier: BSD-2-Clause + +CXXFLAGS += -Wall -Werror -pedantic -g -pthread +LDFLAGS += -lprocstat -g + +all: test + +test: main.o fast_cpu_time.o procstat_cpu_time.o sysctl_cpu_time.o + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $> + +clean: + @rm *.o test > /dev/null 2>&1 diff --git a/fast_cpu_time.cpp b/fast_cpu_time.cpp new file mode 100644 index 0000000..5e46420 --- /dev/null +++ b/fast_cpu_time.cpp @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri + * SPDX-FileCopyrightText: 2025 The FreeBSD Foundation + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fast_cpu_time.hpp" +#include "helpers.hpp" +#include + +namespace { + clockid_t get_thread_clock_id(pthread_t thread) { + clockid_t clock_id; + + auto rc = pthread_getcpuclockid(thread, &clock_id); + assert(rc == 0); + + return clock_id; + } +} + +unsigned long fast_thread_cpu_time(pthread_t thread) { + auto clock_id = get_thread_clock_id(thread); + + struct timespec time; + auto rc = clock_gettime(clock_id, &time); + assert(rc == 0); + + return time.tv_sec * 1000000 + time.tv_nsec; +} diff --git a/fast_cpu_time.hpp b/fast_cpu_time.hpp new file mode 100644 index 0000000..0980c24 --- /dev/null +++ b/fast_cpu_time.hpp @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri + * SPDX-FileCopyrightText: 2025 The FreeBSD Foundation + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef __FAST_CPU_TIME_HPP +#define __FAST_CPU_TIME_HPP + +#include + +/** + * Return the combined (sys+user) runtime of the given thread. + * + * This uses the clock_gettime(2) system call with the clock id obtained from + * the given thread via pthread_getcpuclockid(2). + * + * It gives the total runtime of the thread in nanoseconds. This is presumably + * the faster way to get the runtime of a specific thread, but we do not get + * the user and sys times separate. + */ +unsigned long fast_thread_cpu_time(pthread_t thread); + +#endif diff --git a/helpers.hpp b/helpers.hpp new file mode 100644 index 0000000..e40caa1 --- /dev/null +++ b/helpers.hpp @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri + * SPDX-FileCopyrightText: 2025 The FreeBSD Foundation + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef __HELPERS_HPP +#define __HELPERS_HPP + +#include +#include + +extern pid_t ktid; + +/* + * Simple helper to turn struct timeval into unsigned long. + */ +inline unsigned long timeval_to_long(struct timeval time) { + return (time.tv_sec * 1000 + time.tv_usec) * 1000; +} + +#endif diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..c3c6138 --- /dev/null +++ b/main.cpp @@ -0,0 +1,98 @@ +/* + * SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri + * SPDX-FileCopyrightText: 2025 The FreeBSD Foundation + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "helpers.hpp" +#include "fast_cpu_time.hpp" +#include "procstat_cpu_time.hpp" +#include "sysctl_cpu_time.hpp" + +#include +#include +#include +#include +#include +#include + +pid_t ktid; // Kernel thread id, + +/* + * A simple thread. + * + * As we're only interested in observing the thread from another thread, it's + * not important what it does, only that it stays around for long enough that + * we can make our observations. + */ +void * thread_start(void * arg) { + // Get the kernel thread id for the thread and save it + // so it's accessible to the main thread. + ktid = pthread_getthreadid_np(); + + for (auto i = 0; i < 5; ++i) { + sleep(1); + std::cout << "."; + } + + /* + * We can use getrusage(2) to quickly obtain the running time for + * the current thread. This does not seem to be exposed for other + * threads. + */ + auto ru = new struct rusage{}; + getrusage(RUSAGE_THREAD, ru); + + std::cout << " done!" << std::endl; + return ru; +} + + +int main(int argc, char * argv[]) { + std::cout << "Hello Victims!" << std::endl; + + pthread_t thread; + + auto res = pthread_create(&thread, nullptr, thread_start, nullptr); + assert(res == 0); + +#ifndef NDEBUG + std::cout << "[*] thread = " << thread << ", " + << "kernel tid = " << ktid + << std::endl; +#endif + + sleep(2); + + auto fast_time = fast_thread_cpu_time(thread); + + struct timeval procstat_user_time; + struct timeval procstat_sys_time; + procstat_cpu_time(thread, &procstat_user_time, &procstat_sys_time); + + struct timeval sysctl_user_time; + struct timeval sysctl_sys_time; + sysctl_cpu_time(thread, &sysctl_user_time, &sysctl_sys_time); + + void * thread_res; + auto join_res = pthread_join(thread, &thread_res); + assert(join_res == 0); + + std::cout << "Fast time: " << fast_time << "\n"; + + std::cout << "Procstat:" + << " User time: " << timeval_to_long(procstat_user_time) << ", " + << " Sys time : " << timeval_to_long(procstat_sys_time) << "\n"; + + std::cout << "Sysctl :" + << " User time: " << timeval_to_long(sysctl_user_time) << ", " + << " Sys time : " << timeval_to_long(sysctl_sys_time) << "\n"; + + auto ru = reinterpret_cast(thread_res); + + std::cout << "Rusage :" + << " User time: " << timeval_to_long(ru->ru_utime) << ", " + << " Sys time : " << timeval_to_long(ru->ru_stime) << "\n"; +} diff --git a/procstat_cpu_time.cpp b/procstat_cpu_time.cpp new file mode 100644 index 0000000..3507488 --- /dev/null +++ b/procstat_cpu_time.cpp @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri + * SPDX-FileCopyrightText: 2025 The FreeBSD Foundation + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "procstat_cpu_time.hpp" +#include "helpers.hpp" +#include +#include + +// For procstat funcs +#include +#include +#include +#include +#include +#include + +void procstat_cpu_time(pthread_t thread, struct timeval * utime, struct timeval * stime) { + auto ps = procstat_open_sysctl(); + unsigned int count = 0; + + auto procinfo = procstat_getprocs(ps, KERN_PROC_PID|KERN_PROC_INC_THREAD, getpid(), &count); + +#ifndef NDEBUG + std::cout << "[*] Received procinfo @ " << procinfo + << ", with " << count << " entries" << std::endl; +#endif + + for (auto i = 0U; i < count; ++i) { + auto tinfo = procinfo + i; + +#ifndef NDEBUG + std::cout << "[*] " << i + << ": tid = " << tinfo->ki_tid + << ", runtime = " << tinfo->ki_runtime + << ", user time = " << timeval_to_long(tinfo->ki_rusage.ru_utime) + << ", sys time = " << timeval_to_long(tinfo->ki_rusage.ru_stime) + << std::endl; +#endif + + if (tinfo->ki_tid == ktid) { + *utime = tinfo->ki_rusage.ru_utime; + *stime = tinfo->ki_rusage.ru_stime; + break; + } + } + + procstat_freeprocs(ps, procinfo); +} diff --git a/procstat_cpu_time.hpp b/procstat_cpu_time.hpp new file mode 100644 index 0000000..d5e427d --- /dev/null +++ b/procstat_cpu_time.hpp @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri + * SPDX-FileCopyrightText: 2025 The FreeBSD Foundation + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef __PROCSTAT_CPU_TIME_HPP +#define __PROCSTAT_CPU_TIME_HPP + +#include + +/** + * Helper function to get the kernel thread id from a pthread_t handle. + */ +long ktid_from_pthread(pthread_t thread); + +/** + * Get the user and system runtime for a given thread by using libprocstat. + * + * Possibly slower than the fast_cpu_time method, as we have to iterate over the + * thread info (kinfo_proc) structs for every thread in the process until we find + * the correct one. + * + * Also the returned struct is dynamically allocated by the procstat_getprocs function, + * and has to be freed by procstat_freeprocs after it's no longer needed. + * + * While this may be ok when retreiving the info for one struct, it would be way more + * efficient to check multiple threads at once using the same returned array of structs. + */ +void procstat_cpu_time(pthread_t thread, struct timeval * utime, struct timeval * stime); + +#endif diff --git a/sysctl_cpu_time.cpp b/sysctl_cpu_time.cpp new file mode 100644 index 0000000..bca12af --- /dev/null +++ b/sysctl_cpu_time.cpp @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri + * SPDX-FileCopyrightText: 2025 The FreeBSD Foundation + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "helpers.hpp" +#include +#include +#include +#include +#include + +void sysctl_cpu_time(pthread_t thread, struct timeval * utime, struct timeval * stime) { + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID|KERN_PROC_INC_THREAD, getpid() }; + + auto miblen = sizeof mib / sizeof mib[0]; + size_t bufsize = 0; + + auto rc = sysctl(mib, miblen, nullptr, &bufsize, nullptr, 0); + // for some reason it seems sysctl sometimes return 0 (Ok) instead of ENOMEM when + // passed a nullptr as oldp. This is not according to documentation. + assert(rc == ENOMEM || rc == 0); + + auto procinfo_buf = malloc(bufsize); + rc = sysctl(mib, 4U, procinfo_buf, &bufsize, nullptr, 0); + assert(rc == 0); + + auto count = bufsize / sizeof(struct kinfo_proc); + +#ifndef NDEBUG + std::cout << "[*] Received procinfo @ " << procinfo_buf + << ", with " << count << " entries" << std::endl; +#endif + + for (auto i = 0U; i < count; ++i) { + auto tinfo = (struct kinfo_proc *)procinfo_buf + i; + +#ifndef NDEBUG + std::cout << "[*] " << i + << ": tid = " << tinfo->ki_tid + << ", runtime = " << tinfo->ki_runtime + << ", user time = " << timeval_to_long(tinfo->ki_rusage.ru_utime) + << ", sys time = " << timeval_to_long(tinfo->ki_rusage.ru_stime) + << std::endl; +#endif + + if (tinfo->ki_tid == ktid) { + *utime = tinfo->ki_rusage.ru_utime; + *stime = tinfo->ki_rusage.ru_stime; + break; + } + } + + free(procinfo_buf); +} diff --git a/sysctl_cpu_time.hpp b/sysctl_cpu_time.hpp new file mode 100644 index 0000000..0d59332 --- /dev/null +++ b/sysctl_cpu_time.hpp @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri + * SPDX-FileCopyrightText: 2025 The FreeBSD Foundation + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef __SYSCTL_CPU_TIME_HPP +#define __SYSCTL_CPU_TIME_HPP + +#include + +void sysctl_cpu_time(pthread_t thread, struct timeval * utime, struct timeval * stime); + +#endif