2 # ignore-tidy-linelength
4 # This is a small script that we use on CI to collect CPU usage statistics of
5 # our builders. By seeing graphs of CPU usage over time we hope to correlate
6 # that with possible improvements to Rust's own build system, ideally diagnosing
7 # that either builders are always fully using their CPU resources or they're
8 # idle for long stretches of time.
10 # This script is relatively simple, but it's platform specific. Each platform
11 # (OSX/Windows/Linux) has a different way of calculating the current state of
12 # CPU at a point in time. We then compare two captured states to determine the
13 # percentage of time spent in one state versus another. The state capturing is
14 # all platform-specific but the loop at the bottom is the cross platform part
15 # that executes everywhere.
17 # # Viewing statistics
19 # All builders will upload their CPU statistics as CSV files to our S3 buckets.
20 # These URLS look like:
22 # https://$bucket.s3.amazonaws.com/rustc-builds/$commit/cpu-$builder.csv
26 # https://rust-lang-ci2.s3.amazonaws.com/rustc-builds/68baada19cd5340f05f0db15a3e16d6671609bcc/cpu-x86_64-apple.csv
28 # Each CSV file has two columns. The first is the timestamp of the measurement
29 # and the second column is the % of idle cpu time in that time slice. Ideally
30 # the second column is always zero.
32 # Once you've downloaded a file there's various ways to plot it and visualize
33 # it. For command line usage you use the `src/etc/cpu-usage-over-time-plot.sh`
34 # script in this repository.
40 # Python 3.3 changed the value of `sys.platform` on Linux from "linux2" to just
41 # "linux". We check here with `.startswith` to keep compatibility with older
42 # Python versions (especially Python 2.7).
43 if sys.platform.startswith('linux'):
46 with open('/proc/stat', 'r') as file:
47 data = file.readline().split()
49 raise Exception('did not start with "cpu"')
50 self.user = int(data[1])
51 self.nice = int(data[2])
52 self.system = int(data[3])
53 self.idle = int(data[4])
54 self.iowait = int(data[5])
55 self.irq = int(data[6])
56 self.softirq = int(data[7])
57 self.steal = int(data[8])
58 self.guest = int(data[9])
59 self.guest_nice = int(data[10])
61 def idle_since(self, prev):
62 user = self.user - prev.user
63 nice = self.nice - prev.nice
64 system = self.system - prev.system
65 idle = self.idle - prev.idle
66 iowait = self.iowait - prev.iowait
67 irq = self.irq - prev.irq
68 softirq = self.softirq - prev.softirq
69 steal = self.steal - prev.steal
70 guest = self.guest - prev.guest
71 guest_nice = self.guest_nice - prev.guest_nice
72 total = user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice
73 return float(idle) / float(total) * 100
75 elif sys.platform == 'win32':
76 from ctypes.wintypes import DWORD
77 from ctypes import Structure, windll, WinError, GetLastError, byref
79 class FILETIME(Structure):
81 ("dwLowDateTime", DWORD),
82 ("dwHighDateTime", DWORD),
87 idle, kernel, user = FILETIME(), FILETIME(), FILETIME()
89 success = windll.kernel32.GetSystemTimes(
95 assert success, WinError(GetLastError())[1]
97 self.idle = (idle.dwHighDateTime << 32) | idle.dwLowDateTime
98 self.kernel = (kernel.dwHighDateTime << 32) | kernel.dwLowDateTime
99 self.user = (user.dwHighDateTime << 32) | user.dwLowDateTime
101 def idle_since(self, prev):
102 idle = self.idle - prev.idle
103 user = self.user - prev.user
104 kernel = self.kernel - prev.kernel
105 return float(idle) / float(user + kernel) * 100
107 elif sys.platform == 'darwin':
109 libc = cdll.LoadLibrary('/usr/lib/libc.dylib')
111 class host_cpu_load_info_data_t(Structure):
112 _fields_ = [("cpu_ticks", c_uint * 4)]
114 host_statistics = libc.host_statistics
115 host_statistics.argtypes = [
118 POINTER(host_cpu_load_info_data_t),
121 host_statistics.restype = c_int
129 stats = host_cpu_load_info_data_t()
130 count = c_int(4) # HOST_CPU_LOAD_INFO_COUNT
131 err = libc.host_statistics(
132 libc.mach_host_self(),
133 c_int(3), # HOST_CPU_LOAD_INFO
138 self.system = stats.cpu_ticks[CPU_STATE_SYSTEM]
139 self.user = stats.cpu_ticks[CPU_STATE_USER]
140 self.idle = stats.cpu_ticks[CPU_STATE_IDLE]
141 self.nice = stats.cpu_ticks[CPU_STATE_NICE]
143 def idle_since(self, prev):
144 user = self.user - prev.user
145 system = self.system - prev.system
146 idle = self.idle - prev.idle
147 nice = self.nice - prev.nice
148 return float(idle) / float(user + system + idle + nice) * 100.0
151 print('unknown platform', sys.platform)
159 now = datetime.datetime.utcnow().isoformat()
160 idle = next_state.idle_since(cur_state)
161 print("%s,%s" % (now, idle))
163 cur_state = next_state