• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# ignore-tidy-linelength
3
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.
9#
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.
16#
17# # Viewing statistics
18#
19# All builders will upload their CPU statistics as CSV files to our S3 buckets.
20# These URLS look like:
21#
22#   https://$bucket.s3.amazonaws.com/rustc-builds/$commit/cpu-$builder.csv
23#
24# for example
25#
26#   https://rust-lang-ci2.s3.amazonaws.com/rustc-builds/68baada19cd5340f05f0db15a3e16d6671609bcc/cpu-x86_64-apple.csv
27#
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.
31#
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.
35
36import datetime
37import sys
38import time
39
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).
43if sys.platform.startswith('linux'):
44    class State:
45        def __init__(self):
46            with open('/proc/stat', 'r') as file:
47                data = file.readline().split()
48            if data[0] != 'cpu':
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])
60
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
74
75elif sys.platform == 'win32':
76    from ctypes.wintypes import DWORD
77    from ctypes import Structure, windll, WinError, GetLastError, byref
78
79    class FILETIME(Structure):
80        _fields_ = [
81            ("dwLowDateTime", DWORD),
82            ("dwHighDateTime", DWORD),
83        ]
84
85    class State:
86        def __init__(self):
87            idle, kernel, user = FILETIME(), FILETIME(), FILETIME()
88
89            success = windll.kernel32.GetSystemTimes(
90                byref(idle),
91                byref(kernel),
92                byref(user),
93            )
94
95            assert success, WinError(GetLastError())[1]
96
97            self.idle = (idle.dwHighDateTime << 32) | idle.dwLowDateTime
98            self.kernel = (kernel.dwHighDateTime << 32) | kernel.dwLowDateTime
99            self.user = (user.dwHighDateTime << 32) | user.dwLowDateTime
100
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
106
107elif sys.platform == 'darwin':
108    from ctypes import *
109    libc = cdll.LoadLibrary('/usr/lib/libc.dylib')
110
111    class host_cpu_load_info_data_t(Structure):
112        _fields_ = [("cpu_ticks", c_uint * 4)]
113
114    host_statistics = libc.host_statistics
115    host_statistics.argtypes = [
116        c_uint,
117        c_int,
118        POINTER(host_cpu_load_info_data_t),
119        POINTER(c_int)
120    ]
121    host_statistics.restype = c_int
122
123    CPU_STATE_USER = 0
124    CPU_STATE_SYSTEM = 1
125    CPU_STATE_IDLE = 2
126    CPU_STATE_NICE = 3
127    class State:
128        def __init__(self):
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
134                byref(stats),
135                byref(count),
136            )
137            assert err == 0
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]
142
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
149
150else:
151    print('unknown platform', sys.platform)
152    sys.exit(1)
153
154cur_state = State()
155print("Time,Idle")
156while True:
157    time.sleep(1)
158    next_state = State()
159    now = datetime.datetime.utcnow().isoformat()
160    idle = next_state.idle_since(cur_state)
161    print("%s,%s" % (now, idle))
162    sys.stdout.flush()
163    cur_state = next_state
164