Skip to content

Commit c4e5fa9

Browse files
yaoz08facebook-github-bot
authored andcommitted
move TaskStatsData to OSS dynolog
Summary: TaskStatsData.h contains most of the data models used by ThreadMonitor. This is an essential step In order to finally create an OSS ThreadMonitor. Differential Revision: D86358725
1 parent 5ca37d1 commit c4e5fa9

1 file changed

Lines changed: 259 additions & 0 deletions

File tree

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <stdint.h>
11+
#include <sys/types.h>
12+
#include <algorithm>
13+
#include <chrono>
14+
#include <string>
15+
16+
namespace dynolog {
17+
18+
using timestamp = std::chrono::time_point<std::chrono::steady_clock>;
19+
20+
struct TaskStat {
21+
time_t t = 0;
22+
timestamp ts;
23+
uint32_t seq = 0;
24+
25+
pid_t tid = 0;
26+
pid_t ppid = 0;
27+
std::string comm;
28+
29+
std::string tcomm;
30+
31+
uint64_t cpuCount = 0; /* number of schedules */
32+
uint64_t cpuDelayTotalNs = 0; /* ns waiting to be scheduled */
33+
uint64_t cpuRunRealTotalNs = 0; /* total real cpu running time */
34+
uint64_t acMinflt = 0; /* Minor Page Fault Count - copy on write */
35+
uint64_t acMajflt = 0; /* Major Page Fault Count - virtual memory */
36+
/* Delay waiting for page fault I/O (swap in only) */
37+
uint64_t swapinCount = 0;
38+
uint64_t swapinDelayTotal = 0;
39+
uint64_t voluntaryCSW = 0; /* Voluntary Context Switches */
40+
uint64_t involuntaryCSW = 0; /* Involuntary Context Switches */
41+
uint64_t etime = 0; /* elapsed time in us */
42+
uint64_t utime = 0; /* User cpu time in us */
43+
uint64_t stime = 0; /* System cpu time in us */
44+
int64_t memdelayUs = 0; /* type waiting for memory page */
45+
uint64_t io_read_bytes = 0; /* bytes of read i/o */
46+
uint64_t io_write_bytes = 0; /* bytes of write i/o */
47+
/* High watermark of RSS usage in duration of a task, in KBytes. */
48+
uint64_t hiwaterRssKb = 0; /* High-watermark of RSS usage in KBytes */
49+
};
50+
51+
// calculate difference assuming possible 32-bit counter overflow
52+
static inline uint64_t deltaWrap32(uint64_t cur, uint64_t prev) {
53+
return cur < prev ? (UINT32_MAX - prev) + cur : cur - prev;
54+
}
55+
56+
struct TaskStatsDelta {
57+
uint64_t cpuCount = 0;
58+
uint64_t cpuDelayUs = 0; /* time waiting to be scheduled */
59+
uint64_t cpuRunRealUs = 0; /* total real cpu running time */
60+
uint64_t acMinflt = 0;
61+
uint64_t acMajflt = 0;
62+
uint64_t swapinCount = 0;
63+
uint64_t swapinDelayUs = 0;
64+
uint64_t voluntaryCSW = 0; /* Voluntary Context Switches */
65+
uint64_t involuntaryCSW = 0; /* Involuntary Context Switches */
66+
uint64_t utimeUs = 0; /* user cpu time in us */
67+
uint64_t stimeUs = 0; /* time spent in System */
68+
uint64_t etimeUs = 0; /* time elapsed */
69+
int64_t memdelayUs = 0; /* time waiting for memory page */
70+
uint64_t io_read_bytes = 0; /* bytes of read i/o */
71+
uint64_t io_write_bytes = 0; /* bytes of write i/o */
72+
uint64_t hiwaterRssKb = 0;
73+
74+
explicit TaskStatsDelta(const TaskStat& prev, const TaskStat& cur) {
75+
// It's quite annoying, but even though taskstats return 64-bit counters,
76+
// kernel actually keeps track of some of them in 32-bit counters, some in
77+
// 64-bit counters. To add to that confusion, taskstats has additional logic
78+
// to not roll-over counters on overflow and truncate it to zero (but that
79+
// will be relevant only to per-process stats, which can accumulate large
80+
// enough value across many threads, not per-thread stats, because for
81+
// single thread signed 64-bit overflow can happen only after 292 years). We
82+
// carefully take all that into account here when calculating deltas.
83+
84+
// cpuCount is unsigned 64-bit counter (good for 584 years)
85+
cpuCount = cur.cpuCount - prev.cpuCount;
86+
87+
// cpuDelay is signed 64-bit counter (good for 292 years)
88+
cpuDelayUs = (cur.cpuDelayTotalNs - prev.cpuDelayTotalNs) / 1000;
89+
90+
// cpuRunReal is signed 64-bit counter (good for 292 years)
91+
cpuRunRealUs = (cur.cpuRunRealTotalNs - prev.cpuRunRealTotalNs) / 1000;
92+
93+
// swapinCount is 32-bit counter
94+
swapinCount = deltaWrap32(cur.swapinCount, prev.swapinCount);
95+
96+
// swapinDelay is unsigned 64-bit counter (good for 584 years)
97+
swapinDelayUs = (cur.swapinDelayTotal - prev.swapinDelayTotal) / 1000;
98+
99+
// faults and context switches are unsigned 64-bit counters
100+
acMinflt = cur.acMinflt - prev.acMinflt;
101+
acMajflt = cur.acMajflt - prev.acMajflt;
102+
voluntaryCSW = cur.voluntaryCSW - prev.voluntaryCSW;
103+
involuntaryCSW = cur.involuntaryCSW - prev.involuntaryCSW;
104+
105+
// etime is 64-bit counter always, we are good for at least 584 years
106+
etimeUs = cur.etime - prev.etime;
107+
108+
// utime/stime are unsigned 64-bit counters tracked as nanoseconds.
109+
utimeUs = cur.utime - prev.utime;
110+
stimeUs = cur.stime - prev.stime;
111+
112+
memdelayUs = prev.memdelayUs >= 0 && cur.memdelayUs >= prev.memdelayUs
113+
? cur.memdelayUs - prev.memdelayUs
114+
: 0;
115+
116+
// io reads and writes are unsigned 64-bit counters
117+
io_read_bytes = cur.io_read_bytes - prev.io_read_bytes;
118+
io_write_bytes = cur.io_write_bytes - prev.io_write_bytes;
119+
120+
// at least is all straightforward with memory high watermark
121+
hiwaterRssKb = cur.hiwaterRssKb > prev.hiwaterRssKb ? cur.hiwaterRssKb
122+
: prev.hiwaterRssKb;
123+
}
124+
};
125+
126+
// aggregated thread stats
127+
struct ThreadSummaryStats {
128+
uint32_t activeThreads = 0; /* number of live threads */
129+
uint32_t exitedThreads = 0; /* number of exited threads */
130+
uint64_t schedThreads = 0; /* number of threads scheduled to be run on CPU */
131+
132+
uint64_t utimeUs = 0; /* time spent in User */
133+
uint64_t stimeUs = 0; /* time spent in System */
134+
uint64_t etimeUs = 0; /* time elapsed */
135+
uint64_t utimeMaxUs = 0; /* max utimeUs of all threads */
136+
uint64_t stimeMaxUs = 0; /* max stimeUs of all threads */
137+
uint64_t cpuTimeMaxUs = 0; /* max total cpu time of all threads */
138+
139+
uint64_t hiwaterRssKb = 0; /* maximum RSS in KB */
140+
141+
uint64_t schedDelayUs = 0; /* time waiting to be scheduled */
142+
uint64_t schedCount = 0; /* number of times thread was scheduled */
143+
uint64_t voluntaryCSW = 0; /* voluntary context switches */
144+
uint64_t involuntaryCSW = 0; /* involuntary context switches */
145+
uint64_t acMinflt = 0; /* minor faults */
146+
uint64_t acMajflt = 0; /* major faults */
147+
uint64_t swapinCount = 0; /* number of memory swap ins */
148+
uint64_t swapinDelayUs = 0; /* memory swap in elapsed time */
149+
int64_t memdelayUs = 0; /* amount of time thread waited to get memory */
150+
151+
uint64_t io_read_bytes = 0; /* bytes of read i/o */
152+
uint64_t io_write_bytes = 0; /* bytes of write i/o */
153+
154+
void aggregate(const TaskStatsDelta& d, bool exited) {
155+
activeThreads += exited ? 0 : 1;
156+
exitedThreads += exited ? 1 : 0;
157+
schedThreads += d.cpuCount > 0 ? 1 : 0;
158+
159+
utimeUs += d.utimeUs;
160+
stimeUs += d.stimeUs;
161+
etimeUs += d.etimeUs;
162+
utimeMaxUs = std::max(utimeMaxUs, d.utimeUs);
163+
stimeMaxUs = std::max(stimeMaxUs, d.stimeUs);
164+
cpuTimeMaxUs = std::max(cpuTimeMaxUs, d.utimeUs + d.stimeUs);
165+
166+
hiwaterRssKb =
167+
hiwaterRssKb > d.hiwaterRssKb ? hiwaterRssKb : d.hiwaterRssKb;
168+
169+
schedDelayUs += d.cpuDelayUs;
170+
schedCount += d.cpuCount;
171+
voluntaryCSW += d.voluntaryCSW;
172+
involuntaryCSW += d.involuntaryCSW;
173+
acMinflt += d.acMinflt;
174+
acMajflt += d.acMajflt;
175+
swapinCount += d.swapinCount;
176+
swapinDelayUs += d.swapinDelayUs;
177+
memdelayUs += d.memdelayUs;
178+
179+
io_read_bytes += d.io_read_bytes;
180+
io_write_bytes += d.io_write_bytes;
181+
}
182+
183+
void aggregate(const ThreadSummaryStats& s) {
184+
activeThreads += s.activeThreads;
185+
exitedThreads += s.exitedThreads;
186+
schedThreads += s.schedThreads;
187+
188+
utimeUs += s.utimeUs;
189+
stimeUs += s.stimeUs;
190+
etimeUs += s.etimeUs;
191+
utimeMaxUs = std::max(utimeMaxUs, s.utimeMaxUs);
192+
stimeMaxUs = std::max(stimeMaxUs, s.stimeMaxUs);
193+
cpuTimeMaxUs = std::max(cpuTimeMaxUs, s.cpuTimeMaxUs);
194+
195+
hiwaterRssKb =
196+
hiwaterRssKb > s.hiwaterRssKb ? hiwaterRssKb : s.hiwaterRssKb;
197+
198+
schedDelayUs += s.schedDelayUs;
199+
schedCount += s.schedCount;
200+
voluntaryCSW += s.voluntaryCSW;
201+
involuntaryCSW += s.involuntaryCSW;
202+
acMinflt += s.acMinflt;
203+
acMajflt += s.acMajflt;
204+
swapinCount += s.swapinCount;
205+
swapinDelayUs += s.swapinDelayUs;
206+
memdelayUs += s.memdelayUs;
207+
208+
io_read_bytes += s.io_read_bytes;
209+
io_write_bytes += s.io_write_bytes;
210+
}
211+
};
212+
213+
// counters maintained by ThreadMonitor
214+
// calculated from aggregating thread-level data
215+
struct ThreadAggCounters {
216+
uint64_t sumEtimeUs = 0;
217+
uint64_t countEtimeUs = 0;
218+
uint8_t maxThreadCpuUtil = 0;
219+
unsigned int swapinCount = 0;
220+
unsigned int swapinDelayUs = 0;
221+
unsigned int taskCount = 0;
222+
unsigned int taskActiveCount = 0;
223+
uint64_t majorFaultCount = 0;
224+
uint64_t minorFaultCount = 0;
225+
uint64_t voluntaryCSW = 0;
226+
uint64_t involuntaryCSW = 0;
227+
uint64_t cpuDelayUs = 0;
228+
uint64_t cpuRunRealUs = 0;
229+
230+
void updateThreadAggCounters(const TaskStatsDelta& delta) {
231+
sumEtimeUs += delta.etimeUs;
232+
countEtimeUs++;
233+
234+
float cpuUtil = 0.0;
235+
if (delta.etimeUs > 0) {
236+
auto util =
237+
100.0 * (delta.utimeUs + delta.stimeUs) / (delta.etimeUs * 1.0);
238+
// utime and stime may be rounded to the closest ms while etime remains
239+
// accurate, this could result in a cpu util above 100%, for now no clear
240+
// way to avoid this in the bfp probe so clamp down the value to 100%
241+
cpuUtil = (util > 100.0) ? 100.0 : util;
242+
}
243+
maxThreadCpuUtil = std::max(maxThreadCpuUtil, (uint8_t)cpuUtil);
244+
swapinCount += delta.swapinCount;
245+
swapinDelayUs += delta.swapinCount;
246+
majorFaultCount += delta.acMajflt;
247+
minorFaultCount += delta.acMinflt;
248+
voluntaryCSW += delta.voluntaryCSW;
249+
involuntaryCSW += delta.involuntaryCSW;
250+
cpuDelayUs += delta.cpuDelayUs;
251+
cpuRunRealUs += delta.cpuRunRealUs;
252+
taskCount++;
253+
if (delta.cpuCount) {
254+
taskActiveCount++;
255+
}
256+
}
257+
};
258+
259+
} // namespace dynolog

0 commit comments

Comments
 (0)