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.
This commit is contained in:
commit
45e6aebfbf
10 changed files with 366 additions and 0 deletions
9
LICENSES/BSD-2-Clause.txt
Normal file
9
LICENSES/BSD-2-Clause.txt
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Copyright (c) <year> <owner>
|
||||||
|
|
||||||
|
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.
|
16
Makefile
Normal file
16
Makefile
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri
|
||||||
|
# SPDX-FileCopyrightText: 2025 The FreeBSD Foundation
|
||||||
|
# SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
|
||||||
|
#
|
||||||
|
# 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
|
32
fast_cpu_time.cpp
Normal file
32
fast_cpu_time.cpp
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri
|
||||||
|
* SPDX-FileCopyrightText: 2025 The FreeBSD Foundation
|
||||||
|
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fast_cpu_time.hpp"
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
26
fast_cpu_time.hpp
Normal file
26
fast_cpu_time.hpp
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri
|
||||||
|
* SPDX-FileCopyrightText: 2025 The FreeBSD Foundation
|
||||||
|
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __FAST_CPU_TIME_HPP
|
||||||
|
#define __FAST_CPU_TIME_HPP
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
24
helpers.hpp
Normal file
24
helpers.hpp
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri
|
||||||
|
* SPDX-FileCopyrightText: 2025 The FreeBSD Foundation
|
||||||
|
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __HELPERS_HPP
|
||||||
|
#define __HELPERS_HPP
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
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
|
98
main.cpp
Normal file
98
main.cpp
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri
|
||||||
|
* SPDX-FileCopyrightText: 2025 The FreeBSD Foundation
|
||||||
|
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
|
||||||
|
*
|
||||||
|
* 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 <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <pthread_np.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
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<struct rusage *>(thread_res);
|
||||||
|
|
||||||
|
std::cout << "Rusage :"
|
||||||
|
<< " User time: " << timeval_to_long(ru->ru_utime) << ", "
|
||||||
|
<< " Sys time : " << timeval_to_long(ru->ru_stime) << "\n";
|
||||||
|
}
|
53
procstat_cpu_time.cpp
Normal file
53
procstat_cpu_time.cpp
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri
|
||||||
|
* SPDX-FileCopyrightText: 2025 The FreeBSD Foundation
|
||||||
|
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "procstat_cpu_time.hpp"
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
// For procstat funcs
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/queue.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/sysctl.h>
|
||||||
|
#include <sys/user.h>
|
||||||
|
#include <libprocstat.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
34
procstat_cpu_time.hpp
Normal file
34
procstat_cpu_time.hpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri
|
||||||
|
* SPDX-FileCopyrightText: 2025 The FreeBSD Foundation
|
||||||
|
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __PROCSTAT_CPU_TIME_HPP
|
||||||
|
#define __PROCSTAT_CPU_TIME_HPP
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
58
sysctl_cpu_time.cpp
Normal file
58
sysctl_cpu_time.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri
|
||||||
|
* SPDX-FileCopyrightText: 2025 The FreeBSD Foundation
|
||||||
|
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sys/sysctl.h>
|
||||||
|
#include <sys/user.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
16
sysctl_cpu_time.hpp
Normal file
16
sysctl_cpu_time.hpp
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Eilertsens Kodeknekkeri
|
||||||
|
* SPDX-FileCopyrightText: 2025 The FreeBSD Foundation
|
||||||
|
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __SYSCTL_CPU_TIME_HPP
|
||||||
|
#define __SYSCTL_CPU_TIME_HPP
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
void sysctl_cpu_time(pthread_t thread, struct timeval * utime, struct timeval * stime);
|
||||||
|
|
||||||
|
#endif
|
Loading…
Add table
Add a link
Reference in a new issue