1 // Copyright 2020 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 "chromeos/components/diagnostics_ui/backend/system_routine_controller.h"
6
7 #include "base/bind.h"
8 #include "base/logging.h"
9 #include "base/time/time.h"
10 #include "base/timer/timer.h"
11 #include "chromeos/components/diagnostics_ui/backend/cros_healthd_helpers.h"
12 #include "chromeos/services/cros_healthd/public/cpp/service_connection.h"
13
14 namespace chromeos {
15 namespace diagnostics {
16 namespace {
17
18 namespace healthd = cros_healthd::mojom;
19
20 constexpr uint32_t kCpuCacheDurationInSeconds = 10;
21 constexpr uint32_t kCpuFloatingPointDurationInSeconds = 10;
22 constexpr uint32_t kCpuPrimeDurationInSeconds = 10;
23 constexpr uint64_t kCpuPrimeMaxNumber = 1000000;
24 constexpr uint32_t kCpuStressDurationInSeconds = 10;
25 constexpr uint32_t kExpectedMemoryDurationInSeconds = 1000;
26 constexpr uint32_t kRoutineResultRefreshIntervalInSeconds = 1;
27
ConstructStandardRoutineResultInfoPtr(mojom::RoutineType type,mojom::StandardRoutineResult result)28 mojom::RoutineResultInfoPtr ConstructStandardRoutineResultInfoPtr(
29 mojom::RoutineType type,
30 mojom::StandardRoutineResult result) {
31 auto routine_result = mojom::RoutineResult::NewSimpleResult(result);
32 return mojom::RoutineResultInfo::New(type, std::move(routine_result));
33 }
34
35 // Converts a cros_healthd::mojom::DiagnosticRoutineStatusEnum to a
36 // mojom::StandardRoutineResult. Should only be called to construct the final
37 // response. Should not be called for in-progess statuses.
TestStatusToResult(healthd::DiagnosticRoutineStatusEnum status)38 mojom::StandardRoutineResult TestStatusToResult(
39 healthd::DiagnosticRoutineStatusEnum status) {
40 switch (status) {
41 case healthd::DiagnosticRoutineStatusEnum::kPassed:
42 return mojom::StandardRoutineResult::kTestPassed;
43 case healthd::DiagnosticRoutineStatusEnum::kFailed:
44 return mojom::StandardRoutineResult::kTestFailed;
45 case healthd::DiagnosticRoutineStatusEnum::kCancelled:
46 case healthd::DiagnosticRoutineStatusEnum::kError:
47 return mojom::StandardRoutineResult::kExecutionError;
48 case healthd::DiagnosticRoutineStatusEnum::kFailedToStart:
49 case healthd::DiagnosticRoutineStatusEnum::kUnsupported:
50 return mojom::StandardRoutineResult::kUnableToRun;
51 case healthd::DiagnosticRoutineStatusEnum::kReady:
52 case healthd::DiagnosticRoutineStatusEnum::kRunning:
53 case healthd::DiagnosticRoutineStatusEnum::kWaiting:
54 case healthd::DiagnosticRoutineStatusEnum::kRemoved:
55 case healthd::DiagnosticRoutineStatusEnum::kCancelling:
56 NOTREACHED();
57 return mojom::StandardRoutineResult::kExecutionError;
58 }
59 }
60
GetExpectedRoutineDurationInSeconds(mojom::RoutineType routine_type)61 uint32_t GetExpectedRoutineDurationInSeconds(mojom::RoutineType routine_type) {
62 switch (routine_type) {
63 case mojom::RoutineType::kCpuCache:
64 return kCpuCacheDurationInSeconds;
65 case mojom::RoutineType::kCpuFloatingPoint:
66 return kCpuFloatingPointDurationInSeconds;
67 case mojom::RoutineType::kCpuPrime:
68 return kCpuPrimeDurationInSeconds;
69 case mojom::RoutineType::kCpuStress:
70 return kCpuCacheDurationInSeconds;
71 case mojom::RoutineType::kMemory:
72 return kExpectedMemoryDurationInSeconds;
73 }
74 }
75
76 } // namespace
77
SystemRoutineController()78 SystemRoutineController::SystemRoutineController() {
79 inflight_routine_timer_ = std::make_unique<base::OneShotTimer>();
80 }
81
82 SystemRoutineController::~SystemRoutineController() = default;
83
RunRoutine(mojom::RoutineType type,mojo::PendingRemote<mojom::RoutineRunner> runner)84 void SystemRoutineController::RunRoutine(
85 mojom::RoutineType type,
86 mojo::PendingRemote<mojom::RoutineRunner> runner) {
87 if (IsRoutineRunning()) {
88 // If a routine is already running, alert the caller that we were unable
89 // to start the routine.
90 mojo::Remote<mojom::RoutineRunner> routine_runner(std::move(runner));
91 auto result = ConstructStandardRoutineResultInfoPtr(
92 type, mojom::StandardRoutineResult::kUnableToRun);
93 routine_runner->OnRoutineResult(std::move(result));
94 return;
95 }
96
97 inflight_routine_runner_ =
98 mojo::Remote<mojom::RoutineRunner>(std::move(runner));
99 ExecuteRoutine(type);
100 }
101
ExecuteRoutine(mojom::RoutineType routine_type)102 void SystemRoutineController::ExecuteRoutine(mojom::RoutineType routine_type) {
103 BindCrosHealthdDiagnosticsServiceIfNeccessary();
104
105 switch (routine_type) {
106 case mojom::RoutineType::kCpuCache:
107 diagnostics_service_->RunCpuCacheRoutine(
108 kCpuCacheDurationInSeconds,
109 base::BindOnce(&SystemRoutineController::OnRoutineStarted,
110 base::Unretained(this), routine_type));
111 return;
112 case mojom::RoutineType::kCpuFloatingPoint:
113 diagnostics_service_->RunFloatingPointAccuracyRoutine(
114 kCpuFloatingPointDurationInSeconds,
115 base::BindOnce(&SystemRoutineController::OnRoutineStarted,
116 base::Unretained(this), routine_type));
117 return;
118 case mojom::RoutineType::kCpuPrime:
119 diagnostics_service_->RunPrimeSearchRoutine(
120 kCpuPrimeDurationInSeconds, kCpuPrimeMaxNumber,
121 base::BindOnce(&SystemRoutineController::OnRoutineStarted,
122 base::Unretained(this), routine_type));
123 return;
124 case mojom::RoutineType::kCpuStress:
125 diagnostics_service_->RunCpuStressRoutine(
126 kCpuStressDurationInSeconds,
127 base::BindOnce(&SystemRoutineController::OnRoutineStarted,
128 base::Unretained(this), routine_type));
129 return;
130 case mojom::RoutineType::kMemory:
131 diagnostics_service_->RunMemoryRoutine(
132 base::BindOnce(&SystemRoutineController::OnRoutineStarted,
133 base::Unretained(this), routine_type));
134 return;
135 }
136 }
137
OnRoutineStarted(mojom::RoutineType routine_type,healthd::RunRoutineResponsePtr response_ptr)138 void SystemRoutineController::OnRoutineStarted(
139 mojom::RoutineType routine_type,
140 healthd::RunRoutineResponsePtr response_ptr) {
141 // Check for error conditions.
142 // TODO(baileyberro): Handle additional statuses.
143 if (response_ptr->status ==
144 healthd::DiagnosticRoutineStatusEnum::kFailedToStart ||
145 response_ptr->id == healthd::kFailedToStartId) {
146 OnStandardRoutineResult(mojom::RoutineType::kCpuStress,
147 TestStatusToResult(response_ptr->status));
148 return;
149 }
150 DCHECK_EQ(healthd::DiagnosticRoutineStatusEnum::kRunning,
151 response_ptr->status);
152
153 const int32_t id = response_ptr->id;
154
155 // Sleep for the length of the test using a one-shot timer, then start
156 // querying again for status.
157 ScheduleCheckRoutineStatus(GetExpectedRoutineDurationInSeconds(routine_type),
158 routine_type, id);
159 }
160
CheckRoutineStatus(mojom::RoutineType routine_type,int32_t id)161 void SystemRoutineController::CheckRoutineStatus(
162 mojom::RoutineType routine_type,
163 int32_t id) {
164 BindCrosHealthdDiagnosticsServiceIfNeccessary();
165 diagnostics_service_->GetRoutineUpdate(
166 id, healthd::DiagnosticRoutineCommandEnum::kGetStatus,
167 /*include_output=*/false,
168 base::BindOnce(&SystemRoutineController::OnRoutineStatusUpdated,
169 base::Unretained(this), routine_type, id));
170 }
171
OnRoutineStatusUpdated(mojom::RoutineType routine_type,int32_t id,healthd::RoutineUpdatePtr update_ptr)172 void SystemRoutineController::OnRoutineStatusUpdated(
173 mojom::RoutineType routine_type,
174 int32_t id,
175 healthd::RoutineUpdatePtr update_ptr) {
176 const healthd::NonInteractiveRoutineUpdate* update =
177 GetNonInteractiveRoutineUpdate(*update_ptr);
178
179 if (!update) {
180 DVLOG(2) << "Invalid routine update";
181 OnStandardRoutineResult(routine_type,
182 mojom::StandardRoutineResult::kExecutionError);
183 return;
184 }
185
186 const healthd::DiagnosticRoutineStatusEnum status = update->status;
187
188 // If still running, continue to repoll until it is finished.
189 // TODO(baileyberro): Consider adding a timeout mechanism.
190 if (status == healthd::DiagnosticRoutineStatusEnum::kRunning) {
191 ScheduleCheckRoutineStatus(kRoutineResultRefreshIntervalInSeconds,
192 routine_type, id);
193 return;
194 }
195
196 // If test passed, report result.
197 if (status == healthd::DiagnosticRoutineStatusEnum::kPassed) {
198 OnStandardRoutineResult(routine_type, TestStatusToResult(status));
199 return;
200 }
201
202 // If test failed, report result.
203 if (status == healthd::DiagnosticRoutineStatusEnum::kFailed) {
204 OnStandardRoutineResult(routine_type, TestStatusToResult(status));
205 return;
206 }
207
208 // Any other reason, report failure.
209 DVLOG(2) << "Routine failed: " << update->status_message;
210 OnStandardRoutineResult(routine_type, TestStatusToResult(status));
211 }
212
IsRoutineRunning() const213 bool SystemRoutineController::IsRoutineRunning() const {
214 return inflight_routine_runner_.is_bound();
215 }
216
ScheduleCheckRoutineStatus(uint32_t duration_in_seconds,mojom::RoutineType routine_type,int32_t id)217 void SystemRoutineController::ScheduleCheckRoutineStatus(
218 uint32_t duration_in_seconds,
219 mojom::RoutineType routine_type,
220 int32_t id) {
221 inflight_routine_timer_->Start(
222 FROM_HERE, base::TimeDelta::FromSeconds(duration_in_seconds),
223 base::BindOnce(&SystemRoutineController::CheckRoutineStatus,
224 base::Unretained(this), routine_type, id));
225 }
226
OnStandardRoutineResult(mojom::RoutineType routine_type,mojom::StandardRoutineResult result)227 void SystemRoutineController::OnStandardRoutineResult(
228 mojom::RoutineType routine_type,
229 mojom::StandardRoutineResult result) {
230 DCHECK(IsRoutineRunning());
231 auto result_info =
232 ConstructStandardRoutineResultInfoPtr(routine_type, result);
233 inflight_routine_runner_->OnRoutineResult(std::move(result_info));
234 inflight_routine_runner_.reset();
235 }
236
BindCrosHealthdDiagnosticsServiceIfNeccessary()237 void SystemRoutineController::BindCrosHealthdDiagnosticsServiceIfNeccessary() {
238 if (!diagnostics_service_ || !diagnostics_service_.is_connected()) {
239 cros_healthd::ServiceConnection::GetInstance()->GetDiagnosticsService(
240 diagnostics_service_.BindNewPipeAndPassReceiver());
241 diagnostics_service_.set_disconnect_handler(base::BindOnce(
242 &SystemRoutineController::OnDiagnosticsServiceDisconnected,
243 base::Unretained(this)));
244 }
245 }
246
OnDiagnosticsServiceDisconnected()247 void SystemRoutineController::OnDiagnosticsServiceDisconnected() {
248 diagnostics_service_.reset();
249 }
250
OnInflightRoutineRunnerDisconnected()251 void SystemRoutineController::OnInflightRoutineRunnerDisconnected() {
252 inflight_routine_runner_.reset();
253 // TODO(baileyberro): Implement routine cancellation.
254 }
255
256 } // namespace diagnostics
257 } // namespace chromeos
258