1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/trace_processor/importers/ftrace/thread_state_tracker.h"
18
19 #include <algorithm>
20
21 #include "src/trace_processor/importers/common/args_tracker.h"
22 #include "src/trace_processor/importers/common/global_args_tracker.h"
23 #include "src/trace_processor/types/trace_processor_context.h"
24 #include "test/gtest_and_gmock.h"
25
26 namespace perfetto {
27 namespace trace_processor {
28 namespace {
29
30 constexpr uint32_t CPU_A = 0;
31 constexpr uint32_t CPU_B = 1;
32 constexpr UniqueTid IDLE_THREAD = 0;
33 constexpr UniqueTid THREAD_A = 1;
34 constexpr UniqueTid THREAD_B = 2;
35 constexpr UniqueTid THREAD_C = 3;
36 static constexpr char kRunning[] = "Running";
37 static constexpr char kRunnable[] = "R";
38 static constexpr char kBlockedFunction[] = "blocked1";
39
40 class ThreadStateTrackerUnittest : public testing::Test {
41 public:
ThreadStateTrackerUnittest()42 ThreadStateTrackerUnittest() {
43 context_.storage.reset(new TraceStorage());
44 context_.global_args_tracker.reset(
45 new GlobalArgsTracker(context_.storage.get()));
46 context_.args_tracker.reset(new ArgsTracker(&context_));
47 tracker_.reset(new ThreadStateTracker(context_.storage.get()));
48 }
49
StringIdOf(const char * s)50 StringId StringIdOf(const char* s) {
51 return context_.storage->InternString(s);
52 }
53
ThreadStateIterator()54 tables::ThreadStateTable::ConstIterator ThreadStateIterator() {
55 return context_.storage->thread_state_table().FilterToIterator({});
56 }
57
VerifyThreadState(const tables::ThreadStateTable::ConstIterator & it,int64_t from,std::optional<int64_t> to,UniqueTid utid,const char * state,std::optional<bool> io_wait=std::nullopt,std::optional<StringId> blocked_function=std::nullopt,std::optional<UniqueTid> waker_utid=std::nullopt,std::optional<int64_t> cpu=std::nullopt)58 void VerifyThreadState(
59 const tables::ThreadStateTable::ConstIterator& it,
60 int64_t from,
61 std::optional<int64_t> to,
62 UniqueTid utid,
63 const char* state,
64 std::optional<bool> io_wait = std::nullopt,
65 std::optional<StringId> blocked_function = std::nullopt,
66 std::optional<UniqueTid> waker_utid = std::nullopt,
67 std::optional<int64_t> cpu = std::nullopt) {
68 ASSERT_EQ(it.ts(), from);
69 ASSERT_EQ(it.dur(), to ? *to - from : -1);
70 ASSERT_EQ(it.utid(), utid);
71 if (state == kRunning) {
72 if (cpu.has_value()) {
73 ASSERT_EQ(it.cpu(), cpu);
74 } else {
75 ASSERT_EQ(it.cpu(), CPU_A);
76 }
77 } else {
78 ASSERT_EQ(it.cpu(), std::nullopt);
79 }
80 ASSERT_STREQ(context_.storage->GetString(it.state()).c_str(), state);
81 ASSERT_EQ(it.io_wait(), io_wait);
82 ASSERT_EQ(it.blocked_function(), blocked_function);
83 ASSERT_EQ(it.waker_utid(), waker_utid);
84 }
85
86 protected:
87 std::unique_ptr<ThreadStateTracker> tracker_;
88 TraceProcessorContext context_;
89 StringId running_string_id_;
90 StringId runnable_string_id_;
91 StringId sched_blocked_reason_id_;
92 };
93
TEST_F(ThreadStateTrackerUnittest,BasicPushSchedSwitchEvent)94 TEST_F(ThreadStateTrackerUnittest, BasicPushSchedSwitchEvent) {
95 tracker_->PushSchedSwitchEvent(10, CPU_A, THREAD_A, StringIdOf("S"),
96 THREAD_B);
97
98 ASSERT_EQ(context_.storage->thread_state_table().row_count(), 2ul);
99 auto rows_it = ThreadStateIterator();
100 VerifyThreadState(rows_it, 10, std::nullopt, THREAD_A, "S");
101 VerifyThreadState(++rows_it, 10, std::nullopt, THREAD_B, kRunning);
102 }
103
TEST_F(ThreadStateTrackerUnittest,StartWithWakingEvent)104 TEST_F(ThreadStateTrackerUnittest, StartWithWakingEvent) {
105 tracker_->PushWakingEvent(10, THREAD_A, THREAD_C);
106 ASSERT_EQ(context_.storage->thread_state_table().row_count(), 0ul);
107 }
108
TEST_F(ThreadStateTrackerUnittest,BasicWakingEvent)109 TEST_F(ThreadStateTrackerUnittest, BasicWakingEvent) {
110 tracker_->PushSchedSwitchEvent(10, CPU_A, THREAD_A, StringIdOf("S"),
111 THREAD_B);
112 tracker_->PushWakingEvent(20, THREAD_A, THREAD_C);
113
114 ASSERT_EQ(context_.storage->thread_state_table().row_count(), 3ul);
115 auto row_it = ThreadStateIterator();
116 VerifyThreadState(row_it, 10, 20, THREAD_A, "S");
117 VerifyThreadState(++row_it, 10, std::nullopt, THREAD_B, kRunning);
118 VerifyThreadState(++row_it, 20, std::nullopt, THREAD_A, kRunnable,
119 std::nullopt, std::nullopt, THREAD_C);
120 }
121
TEST_F(ThreadStateTrackerUnittest,BasicPushBlockedReason)122 TEST_F(ThreadStateTrackerUnittest, BasicPushBlockedReason) {
123 tracker_->PushSchedSwitchEvent(10, CPU_A, THREAD_A, StringIdOf("S"),
124 THREAD_B);
125 tracker_->PushBlockedReason(THREAD_A, true, StringIdOf(kBlockedFunction));
126
127 auto rows_it = ThreadStateIterator();
128 VerifyThreadState(rows_it, 10, std::nullopt, THREAD_A, "S", true,
129 StringIdOf(kBlockedFunction));
130 }
131
TEST_F(ThreadStateTrackerUnittest,CloseState)132 TEST_F(ThreadStateTrackerUnittest, CloseState) {
133 // Add a new runnable state of THREAD_A at ts=10.
134 tracker_->PushSchedSwitchEvent(10, CPU_A, THREAD_A, StringIdOf(kRunnable),
135 THREAD_B);
136
137 // Close the runnable state of THREAD_A at ts=20 and make it run on the CPU.
138 tracker_->PushSchedSwitchEvent(20, CPU_A, THREAD_B, StringIdOf("S"),
139 THREAD_A);
140
141 auto rows_it = ThreadStateIterator();
142 VerifyThreadState(rows_it, 10, 20, THREAD_A, kRunnable);
143 VerifyThreadState(++rows_it, 10, 20, THREAD_B, kRunning);
144 }
145
TEST_F(ThreadStateTrackerUnittest,PushIdleThread)146 TEST_F(ThreadStateTrackerUnittest, PushIdleThread) {
147 tracker_->PushSchedSwitchEvent(10, CPU_A, IDLE_THREAD, StringIdOf(kRunnable),
148 THREAD_A);
149 auto rows_it = ThreadStateIterator();
150
151 // The opening of idle_thred should be discarded so the first row will be
152 // for the THREAD_A.
153 VerifyThreadState(rows_it, 10, std::nullopt, THREAD_A, kRunning);
154 }
155
TEST_F(ThreadStateTrackerUnittest,SchedBlockedReasonWithIdleThread)156 TEST_F(ThreadStateTrackerUnittest, SchedBlockedReasonWithIdleThread) {
157 tracker_->PushSchedSwitchEvent(1, CPU_A, IDLE_THREAD, StringIdOf("D"),
158 THREAD_A);
159 tracker_->PushSchedSwitchEvent(2, CPU_A, THREAD_A, StringIdOf("D"),
160 IDLE_THREAD);
161 tracker_->PushBlockedReason(THREAD_A, IDLE_THREAD, std::nullopt);
162 tracker_->PushSchedSwitchEvent(3, CPU_A, IDLE_THREAD, StringIdOf("D"),
163 THREAD_B);
164 tracker_->PushSchedSwitchEvent(4, CPU_A, THREAD_B, StringIdOf("D"),
165 IDLE_THREAD);
166 tracker_->PushBlockedReason(THREAD_B, 1, std::nullopt);
167
168 auto rows_it = ThreadStateIterator();
169
170 VerifyThreadState(rows_it, 1, 2, THREAD_A, kRunning);
171 VerifyThreadState(++rows_it, 2, std::nullopt, THREAD_A, "D", 0);
172 VerifyThreadState(++rows_it, 3, 4, THREAD_B, kRunning);
173 VerifyThreadState(++rows_it, 4, std::nullopt, THREAD_B, "D", 1);
174 }
175
TEST_F(ThreadStateTrackerUnittest,SchedSwitchForcedMigration)176 TEST_F(ThreadStateTrackerUnittest, SchedSwitchForcedMigration) {
177 tracker_->PushSchedSwitchEvent(1, CPU_A, THREAD_A, StringIdOf("S"), THREAD_B);
178 tracker_->PushSchedSwitchEvent(2, CPU_A, THREAD_A, StringIdOf("S"), THREAD_B);
179
180 auto rows_it = ThreadStateIterator();
181 VerifyThreadState(rows_it, 1, std::nullopt, THREAD_A, "S");
182 VerifyThreadState(++rows_it, 1, 2, THREAD_B, kRunning);
183 }
184
TEST_F(ThreadStateTrackerUnittest,SchedWakingBigTest)185 TEST_F(ThreadStateTrackerUnittest, SchedWakingBigTest) {
186 tracker_->PushWakingEvent(1, 8, 11);
187 tracker_->PushSchedSwitchEvent(2, CPU_A, 11, StringIdOf("S"), 0);
188 tracker_->PushSchedSwitchEvent(3, CPU_A, 8, StringIdOf("S"), 0);
189 tracker_->PushSchedSwitchEvent(4, CPU_A, 17771, StringIdOf("S"), 17772);
190 tracker_->PushSchedSwitchEvent(5, CPU_A, 17772, StringIdOf("S"), 0);
191 tracker_->PushWakingEvent(6, 18, 0);
192 tracker_->PushSchedSwitchEvent(7, CPU_A, 0, StringIdOf(kRunnable), 18);
193
194 auto rows_it = ThreadStateIterator();
195 VerifyThreadState(rows_it, 2, std::nullopt, 11, "S");
196 VerifyThreadState(++rows_it, 3, std::nullopt, 8, "S");
197 VerifyThreadState(++rows_it, 4, std::nullopt, 17771, "S");
198 VerifyThreadState(++rows_it, 4, 5, 17772, kRunning);
199 VerifyThreadState(++rows_it, 5, std::nullopt, 17772, "S");
200 VerifyThreadState(++rows_it, 7, std::nullopt, 18, kRunning);
201 }
202
TEST_F(ThreadStateTrackerUnittest,RunningOnMultipleCPUsForcedMigration)203 TEST_F(ThreadStateTrackerUnittest, RunningOnMultipleCPUsForcedMigration) {
204 // Thread A was running on multiple CPUs
205 tracker_->PushSchedSwitchEvent(1, CPU_A, THREAD_C, StringIdOf("S"), THREAD_A);
206 tracker_->PushSchedSwitchEvent(2, CPU_B, THREAD_B, StringIdOf("S"), THREAD_A);
207
208 auto rows_it = ThreadStateIterator();
209 VerifyThreadState(rows_it, 1, std::nullopt, THREAD_C, "S");
210 VerifyThreadState(++rows_it, 1, 2, THREAD_A, kRunning);
211 VerifyThreadState(++rows_it, 2, std::nullopt, THREAD_B, "S");
212 VerifyThreadState(++rows_it, 2, std::nullopt, THREAD_A, kRunning,
213 std::nullopt, std::nullopt, std::nullopt, CPU_B);
214 }
215
216 } // namespace
217 } // namespace trace_processor
218 } // namespace perfetto
219