1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/trace_event/thread_instruction_count.h"
6
7 #include "base/base_switches.h"
8 #include "base/command_line.h"
9 #include "base/logging.h"
10 #include "base/no_destructor.h"
11 #include "base/threading/thread_local_storage.h"
12 #include "build/build_config.h"
13
14 #if defined(OS_LINUX)
15 #include <linux/perf_event.h>
16 #include <sys/syscall.h>
17 #include <unistd.h>
18 #endif // defined(OS_LINUX)
19
20 namespace base {
21 namespace trace_event {
22
23 namespace {
24
25 #if defined(OS_LINUX)
26
27 // Special constants used for counter FD states.
28 constexpr int kPerfFdDisabled = -2;
29 constexpr int kPerfFdOpenFailed = -1;
30 constexpr int kPerfFdUninitialized = 0;
31
InstructionCounterFdSlot()32 ThreadLocalStorage::Slot& InstructionCounterFdSlot() {
33 static NoDestructor<ThreadLocalStorage::Slot> fd_slot([](void* fd_ptr) {
34 int fd = reinterpret_cast<intptr_t>(fd_ptr);
35 if (fd > kPerfFdUninitialized)
36 close(fd);
37 });
38 return *fd_slot;
39 }
40
41 // Opens a new file descriptor that emits the value of
42 // PERF_COUNT_HW_INSTRUCTIONS in userspace (excluding kernel and hypervisor
43 // instructions) for the given |thread_id|, or 0 for the calling thread.
44 //
45 // Returns kPerfFdOpenFailed if opening the file descriptor failed, or
46 // kPerfFdDisabled if performance counters are disabled in the calling process.
OpenInstructionCounterFdForThread(int thread_id)47 int OpenInstructionCounterFdForThread(int thread_id) {
48 // This switch is only propagated for processes that are unaffected by the
49 // BPF sandbox, such as the browser process or renderers with --no-sandbox.
50 const base::CommandLine& command_line =
51 *base::CommandLine::ForCurrentProcess();
52 if (!command_line.HasSwitch(switches::kEnableThreadInstructionCount))
53 return kPerfFdDisabled;
54
55 struct perf_event_attr pe = {0};
56 pe.type = PERF_TYPE_HARDWARE;
57 pe.size = sizeof(struct perf_event_attr);
58 pe.config = PERF_COUNT_HW_INSTRUCTIONS;
59 pe.exclude_kernel = 1;
60 pe.exclude_hv = 1;
61
62 int fd = syscall(__NR_perf_event_open, &pe, thread_id, /* cpu */ -1,
63 /* group_fd */ -1, /* flags */ 0);
64 if (fd < 0) {
65 LOG(ERROR) << "perf_event_open failed, omitting instruction counters";
66 return kPerfFdOpenFailed;
67 }
68 return fd;
69 }
70
71 // Retrieves the active perf counter FD for the current thread, performing
72 // lazy-initialization if necessary.
InstructionCounterFdForCurrentThread()73 int InstructionCounterFdForCurrentThread() {
74 auto& slot = InstructionCounterFdSlot();
75 int fd = reinterpret_cast<intptr_t>(slot.Get());
76 if (fd == kPerfFdUninitialized) {
77 fd = OpenInstructionCounterFdForThread(0);
78 slot.Set(reinterpret_cast<void*>(fd));
79 }
80 return fd;
81 }
82
83 #endif // defined(OS_LINUX)
84
85 } // namespace
86
IsSupported()87 bool ThreadInstructionCount::IsSupported() {
88 #if defined(OS_LINUX)
89 // If we can't initialize the counter FD, mark as disabled.
90 int counter_fd = InstructionCounterFdForCurrentThread();
91 if (counter_fd <= 0)
92 return false;
93
94 return true;
95 #endif // defined(OS_LINUX)
96 return false;
97 }
98
Now()99 ThreadInstructionCount ThreadInstructionCount::Now() {
100 DCHECK(IsSupported());
101 #if defined(OS_LINUX)
102 int fd = InstructionCounterFdForCurrentThread();
103 if (fd <= 0)
104 return ThreadInstructionCount();
105
106 uint64_t instructions = 0;
107 ssize_t bytes_read = read(fd, &instructions, sizeof(instructions));
108 CHECK_EQ(bytes_read, static_cast<ssize_t>(sizeof(instructions)))
109 << "Short reads of small size from kernel memory is not expected. If "
110 "this fails, use HANDLE_EINTR.";
111 return ThreadInstructionCount(instructions);
112 #else
113 return ThreadInstructionCount();
114 #endif // defined(OS_LINUX)
115 }
116
117 } // namespace trace_event
118 } // namespace base
119