Skip to content

Commit bbe8772

Browse files
committed
By default, prefer to record on P-cores, but provide an option to disable that preference.
This cleans up the CPU-binding API by making `BindCPU` a proper type with an enum for the various cases we support.
1 parent efa2b2f commit bbe8772

18 files changed

Lines changed: 250 additions & 156 deletions

src/CPUs.cc

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,113 @@
55
#include <sched.h>
66
#include <unistd.h>
77

8+
#include <filesystem>
9+
#include <fstream>
10+
#include <istream>
11+
#include <sstream>
12+
#include <string>
13+
814
#include "log.h"
915

1016
using namespace std;
1117

1218
namespace rr {
1319

20+
static bool hybrid_cpu_arch_parse_cpus(const filesystem::path& dir,
21+
CPUGroup* group) {
22+
ifstream file(dir / "cpus");
23+
if (!file.good()) {
24+
LOG(warn) << "File " << dir.string() << "/cpus not found";
25+
return false;
26+
}
27+
ostringstream sstr;
28+
sstr << file.rdbuf();
29+
string s = sstr.str();
30+
while (!s.empty() && s[s.size() - 1] == '\n') {
31+
s.resize(s.size() - 1);
32+
}
33+
size_t dash = s.find('-');
34+
if (dash == string::npos) {
35+
size_t end;
36+
group->start_cpu = stoi(s, &end);
37+
if (end != s.size()) {
38+
LOG(warn) << "Bad CPU index";
39+
return false;
40+
}
41+
group->end_cpu = group->start_cpu + 1;
42+
return true;
43+
}
44+
size_t end;
45+
group->start_cpu = stoi(s.substr(0, dash), &end);
46+
if (end != dash) {
47+
LOG(warn) << "Bad CPU index";
48+
return false;
49+
}
50+
int last_cpu = stoi(s.substr(dash + 1), &end);
51+
if (end != s.size() - (dash + 1)) {
52+
LOG(warn) << "Bad end CPU index";
53+
return false;
54+
}
55+
group->end_cpu = last_cpu + 1;
56+
return true;
57+
}
58+
59+
static bool hybrid_cpu_arch_parse_type(const filesystem::path& dir,
60+
CPUGroup* group) {
61+
// See https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/intel-hybrid.txt
62+
ifstream file(dir / "type");
63+
if (!file.good()) {
64+
LOG(warn) << "File " << dir.string() << "/type not found";
65+
return false;
66+
}
67+
ostringstream sstr;
68+
sstr << file.rdbuf();
69+
string s = sstr.str();
70+
while (!s.empty() && s[s.size() - 1] == '\n') {
71+
s.resize(s.size() - 1);
72+
}
73+
size_t end;
74+
group->type = stoi(s, &end);
75+
if (end != s.size()) {
76+
LOG(warn) << "Bad type";
77+
return false;
78+
}
79+
return true;
80+
}
81+
82+
// Returns an empty list on many (all?) systems that aren't using hybrid cores.
83+
// In that case, assume all CPUs have the same microarch.
84+
static vector<CPUGroup> hybrid_cpu_arch_groups() {
85+
vector<CPUGroup> result;
86+
filesystem::path dir_path = "/sys/devices";
87+
if (!filesystem::is_directory(dir_path)) {
88+
return result;
89+
}
90+
for (const auto& entry : filesystem::directory_iterator(dir_path)) {
91+
auto file_name = entry.path().filename().string();
92+
if (file_name.find("cpu_") != 0) {
93+
continue;
94+
}
95+
CPUGroup group;
96+
group.name = file_name.substr(4);
97+
if (!hybrid_cpu_arch_parse_cpus(entry.path(), &group)) {
98+
continue;
99+
}
100+
if (!hybrid_cpu_arch_parse_type(entry.path(), &group)) {
101+
continue;
102+
}
103+
if (group.name == "core") {
104+
group.kind = CPUGroup::P_CORE;
105+
} else if (group.name == "atom" || group.name == "lowpower") {
106+
group.kind = CPUGroup::E_CORE;
107+
} else {
108+
group.kind = CPUGroup::UNKNOWN;
109+
}
110+
result.push_back(std::move(group));
111+
}
112+
return result;
113+
}
114+
14115
const CPUs& CPUs::get() {
15116
static CPUs singleton;
16117
return singleton;
@@ -56,6 +157,7 @@ CPUs::CPUs() {
56157
if (ret < 0) {
57158
FATAL() << "failed to get initial affinity";
58159
}
160+
cpu_groups_ = hybrid_cpu_arch_groups();
59161
}
60162

61163
}

src/CPUs.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,44 @@
55

66
#include <sched.h>
77

8+
#include <string>
89
#include <vector>
910

1011
namespace rr {
1112

13+
struct BindCPU {
14+
enum Mode {
15+
// Bind to any core.
16+
ANY,
17+
// Perfer high-performance core if CPU affinity settings allow.
18+
PREFER_PERF_CORE,
19+
// Bind to a specified core unconditionally.
20+
SPECIFIED_CORE,
21+
// Don't bind.
22+
UNBOUND,
23+
};
24+
25+
explicit BindCPU(Mode mode) : mode(mode) {}
26+
explicit BindCPU(int specified_core)
27+
: mode(SPECIFIED_CORE), specified_core(specified_core) {}
28+
29+
Mode mode;
30+
int specified_core;
31+
};
32+
33+
struct CPUGroup {
34+
enum Kind { P_CORE, E_CORE, UNKNOWN };
35+
36+
int start_cpu;
37+
// Exclusive
38+
int end_cpu;
39+
std::string name;
40+
// PERF_TYPE_RAW or something else usable in the perf-attr
41+
// event_type field.
42+
int type;
43+
Kind kind;
44+
};
45+
1246
class CPUs {
1347
public:
1448
static const CPUs& get();
@@ -20,10 +54,15 @@ class CPUs {
2054
// If unspecified, defaults to this thread.
2155
void restore_initial_affinity(pid_t tid = 0) const;
2256

57+
// Returns the CPU group list. May be empty on systems where that
58+
// information is not available or relevant.
59+
const std::vector<CPUGroup>& cpu_groups() const { return cpu_groups_; }
60+
2361
private:
2462
CPUs();
2563

2664
cpu_set_t initial_affinity_;
65+
std::vector<CPUGroup> cpu_groups_;
2766
};
2867

2968
}

src/DiversionSession.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ using namespace std;
1414

1515
namespace rr {
1616

17-
DiversionSession::DiversionSession(int cpu_binding) :
17+
DiversionSession::DiversionSession(BindCPU cpu_binding) :
1818
emu_fs(EmuFs::create()), fake_timer_counter(uint64_t(1) << 60), cpu_binding_(cpu_binding) {}
1919

2020
DiversionSession::~DiversionSession() {

src/DiversionSession.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ReplaySession;
2929
*/
3030
class DiversionSession final : public Session {
3131
public:
32-
DiversionSession(int cpu_binding);
32+
DiversionSession(BindCPU cpu_binding);
3333

3434
typedef std::shared_ptr<DiversionSession> shr_ptr;
3535

@@ -54,7 +54,7 @@ class DiversionSession final : public Session {
5454
int signal_to_deliver = 0);
5555

5656
virtual DiversionSession* as_diversion() override { return this; }
57-
virtual int cpu_binding() const override { return cpu_binding_; }
57+
virtual BindCPU cpu_binding() const override { return cpu_binding_; }
5858

5959
void set_tracee_fd_number(int fd_number) { tracee_socket_fd_number = fd_number; }
6060
void on_create(Task *t) override { this->Session::on_create(t); }
@@ -66,7 +66,7 @@ class DiversionSession final : public Session {
6666

6767
std::shared_ptr<EmuFs> emu_fs;
6868
uint64_t fake_timer_counter;
69-
int cpu_binding_;
69+
BindCPU cpu_binding_;
7070
};
7171

7272
} // namespace rr

src/PerfCounters.cc

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323
#endif
2424

2525
#include <algorithm>
26-
#include <filesystem>
2726
#include <fstream>
28-
#include <sstream>
2927
#include <limits>
3028
#include <regex>
3129
#include <string>
@@ -316,24 +314,28 @@ static string lowercase(const string& s) {
316314
// The index of the PMU we are using within perf_attrs.
317315
// This is always 0 if we detected a single PMU type
318316
// and will be the same as the CPU index if we detected multiple PMU types.
319-
static int get_pmu_index(int cpu_binding) {
320-
if (cpu_binding < 0) {
317+
static int get_pmu_index(BindCPU cpu_binding) {
318+
if (cpu_binding.mode == BindCPU::UNBOUND) {
321319
if (perf_attrs.size() > 1) {
322320
CLEAN_FATAL() << "\nMultiple PMU types detected. Unbinding CPU is not supported.";
323321
}
324322
return 0;
325323
}
326-
if (!PerfCounters::support_cpu(cpu_binding)) {
327-
CLEAN_FATAL() << "\nPMU on cpu " << cpu_binding << " is not supported.";
324+
if (cpu_binding.mode != BindCPU::SPECIFIED_CORE) {
325+
FATAL() << "Only specified-core mode supported at this point";
326+
}
327+
int cpu = cpu_binding.specified_core;
328+
if (!PerfCounters::support_cpu(cpu)) {
329+
CLEAN_FATAL() << "\nPMU on cpu " << cpu << " is not supported.";
328330
}
329331
if (perf_attrs.size() == 1) {
330332
// Single PMU type.
331333
return 0;
332334
}
333-
if ((size_t)cpu_binding >= perf_attrs.size()) {
334-
CLEAN_FATAL() << "\nUnable to find PMU type for CPU " << cpu_binding;
335+
if ((size_t)cpu >= perf_attrs.size()) {
336+
CLEAN_FATAL() << "\nUnable to find PMU type for CPU " << cpu;
335337
}
336-
return cpu_binding;
338+
return cpu;
337339
}
338340

339341
static void init_perf_event_attr(struct perf_event_attr* attr,
@@ -755,7 +757,7 @@ uint32_t PerfCounters::skid_size() {
755757
perf_attrs[pmu_index].ticks_min_period;
756758
}
757759

758-
PerfCounters::PerfCounters(pid_t tid, int cpu_binding,
760+
PerfCounters::PerfCounters(pid_t tid, BindCPU cpu_binding,
759761
TicksSemantics ticks_semantics, Enabled enabled,
760762
IntelPTEnabled enable_pt)
761763
: tid(tid), pmu_index(get_pmu_index(cpu_binding)), ticks_semantics_(ticks_semantics),

src/PerfCounters.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <utility>
1717
#include <vector>
1818

19+
#include "CPUs.h"
1920
#include "PerfCounterBuffers.h"
2021
#include "ScopedFd.h"
2122
#include "Ticks.h"
@@ -64,7 +65,8 @@ class PerfCounters {
6465
PT_DISABLE,
6566
PT_ENABLE
6667
};
67-
PerfCounters(pid_t tid, int cpu_binding, TicksSemantics ticks_semantics,
68+
// `cpu_binding` must be `UNBOUND` or `SPECIFIED_CORE`.
69+
PerfCounters(pid_t tid, BindCPU cpu_binding, TicksSemantics ticks_semantics,
6870
Enabled enabled, IntelPTEnabled enable_pt);
6971
~PerfCounters() { close(); }
7072

src/PerfCounters_x86.h

Lines changed: 1 addition & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -176,105 +176,9 @@ static CpuMicroarch compute_cpu_microarch() {
176176
return UnknownCpu; // not reached
177177
}
178178

179-
struct CPUGroup {
180-
int start_cpu;
181-
// Exclusive
182-
int end_cpu;
183-
string name;
184-
int type;
185-
};
186-
187-
static bool hybrid_cpu_arch_parse_cpus(const filesystem::path& dir,
188-
CPUGroup* group) {
189-
ifstream file(dir / "cpus");
190-
if (!file.good()) {
191-
LOG(warn) << "File " << dir.string() << "/cpus not found";
192-
return false;
193-
}
194-
ostringstream sstr;
195-
sstr << file.rdbuf();
196-
string s = sstr.str();
197-
while (!s.empty() && s[s.size() - 1] == '\n') {
198-
s.resize(s.size() - 1);
199-
}
200-
size_t dash = s.find('-');
201-
if (dash == string::npos) {
202-
size_t end;
203-
group->start_cpu = stoi(s, &end);
204-
if (end != s.size()) {
205-
LOG(warn) << "Bad CPU index";
206-
return false;
207-
}
208-
group->end_cpu = group->start_cpu + 1;
209-
return true;
210-
}
211-
size_t end;
212-
group->start_cpu = stoi(s.substr(0, dash), &end);
213-
if (end != dash) {
214-
LOG(warn) << "Bad CPU index";
215-
return false;
216-
}
217-
int last_cpu = stoi(s.substr(dash + 1), &end);
218-
if (end != s.size() - (dash + 1)) {
219-
LOG(warn) << "Bad end CPU index";
220-
return false;
221-
}
222-
group->end_cpu = last_cpu + 1;
223-
return true;
224-
}
225-
226-
static bool hybrid_cpu_arch_parse_type(const filesystem::path& dir,
227-
CPUGroup* group) {
228-
// See https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/intel-hybrid.txt
229-
ifstream file(dir / "type");
230-
if (!file.good()) {
231-
LOG(warn) << "File " << dir.string() << "/type not found";
232-
return false;
233-
}
234-
ostringstream sstr;
235-
sstr << file.rdbuf();
236-
string s = sstr.str();
237-
while (!s.empty() && s[s.size() - 1] == '\n') {
238-
s.resize(s.size() - 1);
239-
}
240-
size_t end;
241-
group->type = stoi(s, &end);
242-
if (end != s.size()) {
243-
LOG(warn) << "Bad type";
244-
return false;
245-
}
246-
return true;
247-
}
248-
249-
// Returns an empty list on many (all?) systems that aren't using hybrid cores.
250-
// In that case, assume all CPUs have the same microarch.
251-
static vector<CPUGroup> hybrid_cpu_arch_groups() {
252-
vector<CPUGroup> result;
253-
filesystem::path dir_path = "/sys/devices";
254-
if (!filesystem::is_directory(dir_path)) {
255-
return result;
256-
}
257-
for (const auto& entry : filesystem::directory_iterator(dir_path)) {
258-
auto file_name = entry.path().filename().string();
259-
if (file_name.find("cpu_") != 0) {
260-
continue;
261-
}
262-
CPUGroup group;
263-
group.name = file_name.substr(4);
264-
if (!hybrid_cpu_arch_parse_cpus(entry.path(), &group)) {
265-
continue;
266-
}
267-
if (!hybrid_cpu_arch_parse_type(entry.path(), &group)) {
268-
continue;
269-
}
270-
result.push_back(std::move(group));
271-
}
272-
return result;
273-
}
274-
275179
static vector<CPUInfo> compute_cpus_info() {
276180
vector<CPUInfo> result;
277-
auto groups = hybrid_cpu_arch_groups();
181+
auto groups = CPUs::get().cpu_groups();
278182
if (groups.empty()) {
279183
// Only one kind of CPU core.
280184
result.push_back({compute_cpu_microarch(), PERF_TYPE_RAW});

0 commit comments

Comments
 (0)