Skip to content

Commit aa28d53

Browse files
hhellyertobespc
authored andcommitted
Add event loop thread cpu usage (#476)
* Add the percentage CPU time used by the event loop in the last LOOP_INTERVAL so we can see if a Node.js process is CPU bound. * Add thread cpu times for Mac. Update README.md
1 parent 763f90c commit aa28d53

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,12 +247,14 @@ Emitted when a garbage collection (GC) cycle occurs in the underlying V8 runtime
247247
Emitted when all possible environment variables have been collected. Use `appmetrics.monitor.getEnvironment()` to access the available environment variables.
248248

249249
### Event: 'loop'
250-
Emitted every 60 seconds, summarising event tick information in time interval
250+
Emitted every 5 seconds, summarising event tick information in time interval
251251
* `data` (Object) the data from the event loop sample:
252252
* `count` (Number) the number of event loop ticks in the last interval.
253253
* `minimum` (Number) the shortest (i.e. fastest) tick in milliseconds.
254254
* `maximum` (Number) the longest (slowest) tick in milliseconds.
255255
* `average` (Number) the average tick time in milliseconds.
256+
* `cpu_user` (Number) the percentage of 1 CPU used by the event loop thread in user code the last interval. This is a value between 0.0 and 1.0.
257+
* `cpu_system` (Number) the percentage of 1 CPU used by the event loop thread in system code in the last interval. This is a value between 0.0 and 1.0.
256258

257259
### Event: 'memory'
258260
Emitted when a memory monitoring sample is taken.

appmetrics-api.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ function API(agent, appmetrics) {
218218
maximum: parseFloat(values[2]),
219219
count: parseInt(values[3]),
220220
average: parseFloat(values[4]),
221+
cpu_user: parseFloat(values[5]),
222+
cpu_system: parseFloat(values[6]),
221223
};
222224
that.emit('loop', loop);
223225
});

src/plugins/node/loop/nodeloopplugin.cpp

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <ctime>
2626
#else
2727
#include <sys/time.h>
28+
#include <sys/resource.h>
2829
#endif
2930

3031
#define DEFAULT_CAPACITY 10240
@@ -58,23 +59,66 @@ uint64_t max = 0;
5859
uint64_t num = 0;
5960
uint64_t sum = 0;
6061

62+
uint64_t last_cpu_user = 0;
63+
uint64_t last_cpu_sys = 0;
64+
uint64_t last_cpu_ts = 0;
65+
66+
void getThreadCPUTime(uint64_t* cpu_user, uint64_t* cpu_sys) {
67+
// Get the CPU time for this thread
68+
#ifdef RUSAGE_THREAD
69+
struct rusage stats;
70+
if (getrusage(RUSAGE_THREAD, &stats) == 0) {
71+
#if defined(__APPLE__) || defined(_AIX)
72+
*cpu_user = (uint64_t)(stats.ru_utime.tv_sec * 1000) + (uint64_t)(stats.ru_utime.tv_usec / 1000);
73+
*cpu_sys = (uint64_t)(stats.ru_stime.tv_sec * 1000) + (uint64_t)(stats.ru_stime.tv_usec / 1000);
74+
#else
75+
*cpu_user = (uint64_t)(stats.ru_utime.tv_sec * 1000) + (uint64_t)(stats.ru_utime.tv_usec / 1000);
76+
*cpu_sys = (uint64_t)(stats.ru_stime.tv_sec * 1000) + (uint64_t)(stats.ru_stime.tv_usec / 1000);
77+
#endif
78+
}
79+
#elif defined(__APPLE__)
80+
mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
81+
mach_port_t thread = pthread_mach_thread_np(pthread_self());
82+
thread_basic_info thr_info;
83+
84+
kern_return_t rc = thread_info(thread, THREAD_BASIC_INFO,
85+
(thread_info_t) &thr_info, &count);
86+
87+
if (rc == KERN_SUCCESS) {
88+
*cpu_user = (thr_info.user_time.seconds * 1000) + (thr_info.user_time.microseconds / 1000);
89+
*cpu_sys = (thr_info.system_time.seconds * 1000) + (thr_info.system_time.microseconds / 1000);
90+
}
91+
#else
92+
*cpu_user = 0;
93+
*cpu_sys = 0;
94+
#endif
95+
}
6196

6297
#if NODE_VERSION_AT_LEAST(0, 11, 0) // > v0.11+
6398
static void GetLoopInformation(uv_timer_s *data) {
6499
#else
65100
static void GetLoopInformation(uv_timer_s *data, int status) {
66101
#endif
67102
if (num != 0) {
68-
// Convert from nanoseconds to milliseconds.
103+
104+
uint64_t cpu_user = 0;
105+
uint64_t cpu_sys = 0;
106+
uint64_t cpu_ts = uv_hrtime() / (1000*1000);
107+
getThreadCPUTime(&cpu_user, &cpu_sys);
108+
109+
// Convert from nanoseconds to milliseconds.
69110

70111
double mean = (sum / 1e6) / num;
112+
double cpu_duration = (double)(cpu_ts - last_cpu_ts);
71113

72114
std::stringstream contentss;
73115
contentss << "NodeLoopData";
74116
contentss << "," << (min / 1e6);
75117
contentss << "," << (max / 1e6);
76118
contentss << "," << num;
77119
contentss << "," << mean;
120+
contentss << "," << (double)(((double)(cpu_user - last_cpu_user)) / cpu_duration);
121+
contentss << "," << (double)(((double)(cpu_sys - last_cpu_sys)) / cpu_duration);
78122
contentss << '\n';
79123

80124
std::string content = contentss.str();
@@ -83,6 +127,9 @@ static void GetLoopInformation(uv_timer_s *data, int status) {
83127
max = 0;
84128
num = 0;
85129
sum = 0;
130+
last_cpu_user = cpu_user;
131+
last_cpu_sys = cpu_sys;
132+
last_cpu_ts = cpu_ts;
86133

87134

88135
// Send data
@@ -160,6 +207,9 @@ extern "C" {
160207
NODELOOPPLUGIN_DECL int ibmras_monitoring_plugin_start() {
161208
plugin::api.logMessage(fine, "[loop_node] Starting");
162209

210+
last_cpu_ts = uv_hrtime() / (1000*1000);
211+
getThreadCPUTime(&last_cpu_user, &last_cpu_sys);
212+
163213
uv_prepare_start(&prepare_handle, OnPrepare);
164214
uv_check_start(&check_handle, OnCheck);
165215
uv_timer_start(plugin::timer, GetLoopInformation, LOOP_INTERVAL, LOOP_INTERVAL);

0 commit comments

Comments
 (0)