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