1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3""" 4Copyright (c) 2024 Huawei Device Co., Ltd. 5Licensed under the Apache License, Version 2.0 (the "License"); 6you may not use this file except in compliance with the License. 7You may obtain a copy of the License at 8 9 http://www.apache.org/licenses/LICENSE-2.0 10 11Unless required by applicable law or agreed to in writing, software 12distributed under the License is distributed on an "AS IS" BASIS, 13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14See the License for the specific language governing permissions and 15limitations under the License. 16 17Description: Utils for action words. 18""" 19 20import asyncio 21import os 22import re 23import subprocess 24import threading 25from typing import Union 26 27from hypium import SystemUI, BY, MatchPattern 28 29from fport import Fport 30from taskpool import TaskPool 31from toolchain_websocket import ToolchainWebSocket 32 33 34class TimeRecord: 35 def __init__(self, expected_time, actual_time): 36 self.expected_time = expected_time 37 self.actual_times = [actual_time] 38 39 def avg_actual_time(self): 40 return sum(self.actual_times) / len(self.actual_times) 41 42 def max_actual_time(self): 43 return max(self.actual_times) 44 45 def min_actual_time(self): 46 return min(self.actual_times) 47 48 def add_actual_time(self, actual_time): 49 self.actual_times.append(actual_time) 50 51 def avg_proportion(self): 52 return self.avg_actual_time * 100 / self.expected_time 53 54 def rounds(self): 55 return len(self.actual_times) 56 57 def mid_actual_time(self): 58 self.actual_times.sort() 59 return self.actual_times[len(self.actual_times) // 2] 60 61 62class CommonUtils(object): 63 def __init__(self, driver): 64 self.driver = driver 65 66 @staticmethod 67 async def communicate_with_debugger_server(websocket, connection, command, message_id): 68 ''' 69 Assembles and send the commands, then return the response. 70 Send message to the debugger server corresponding to the to_send_queue. 71 Return the response from the received_queue. 72 ''' 73 command['id'] = message_id 74 await websocket.send_msg_to_debugger_server(connection.instance_id, connection.send_msg_queue, command) 75 response = await websocket.recv_msg_of_debugger_server(connection.instance_id, 76 connection.received_msg_message) 77 return response 78 79 @staticmethod 80 def get_custom_protocols(): 81 protocols = ["removeBreakpointsByUrl", 82 "setMixedDebugEnabled", 83 "replyNativeCalling", 84 "getPossibleAndSetBreakpointByUrl", 85 "dropFrame", 86 "setNativeRange", 87 "resetSingleStepper", 88 "callFunctionOn", 89 "smartStepInto", 90 "saveAllPossibleBreakpoints"] 91 return protocols 92 93 @staticmethod 94 def message_id_generator(): 95 message_id = 1 96 while True: 97 yield message_id 98 message_id += 1 99 100 @staticmethod 101 def assert_equal(actual, expected): 102 ''' 103 判断actual和expected是否相同,若不相同,则抛出异常 104 ''' 105 assert actual == expected, f"\nExpected: {expected}\nActual: {actual}" 106 107 @staticmethod 108 def get_variables_from_properties(properties, prefix_name): 109 ''' 110 properties是Runtime.getProperties协议返回的变量信息 111 该方法会根据prefix_name前缀匹配properties中的变量名,符合的变量会以字典形式返回变量名和相关描述 112 描述首先采用description中的内容,但会删掉其中的哈希值,没有description则会采用subtype或type中的内容 113 ''' 114 variables = {} 115 for var in properties: 116 if not var['name'].startswith(prefix_name): 117 continue 118 name = var['name'] 119 value = var['value'] 120 description = value.get('description') 121 if description is not None: 122 index_of_at = description.find('@') 123 if index_of_at == -1: 124 variables[name] = description 125 continue 126 variables[name] = description[:index_of_at] 127 index_of_bracket = description.find('[', index_of_at + 1) 128 if index_of_bracket != -1: 129 variables[name] += description[index_of_bracket:] 130 else: 131 subtype = value.get('subtype') 132 variables[name] = subtype if subtype is not None else value.get('type') 133 return variables 134 135 def get_pid(self, bundle_name): 136 ''' 137 获取bundle_name对应的pid 138 ''' 139 pid = self.driver.shell("pidof " + bundle_name).strip() 140 if not pid.isdigit(): 141 return 0 142 self.driver.log_info(f'pid of {bundle_name}: ' + pid) 143 return int(pid) 144 145 def attach(self, bundle_name): 146 ''' 147 通过bundle_name使指定应用进入调试模式 148 ''' 149 attach_result = self.driver.shell(f"aa attach b {bundle_name}").strip() 150 self.driver.log_info(attach_result) 151 self.assert_equal(attach_result, 'attach app debug successfully.') 152 153 def connect_server(self, config): 154 ''' 155 根据config连接ConnectServer 156 ''' 157 fport = Fport(self.driver) 158 fport.clear_fport() 159 connect_server_port = fport.fport_connect_server(config['connect_server_port'], config['pid'], 160 config['bundle_name']) 161 assert connect_server_port > 0, 'Failed to fport connect server for 3 times, the port is very likely occupied' 162 config['connect_server_port'] = connect_server_port 163 config['websocket'] = ToolchainWebSocket(self.driver, config['connect_server_port'], 164 config['debugger_server_port'], config.get('print_protocol', True)) 165 config['taskpool'] = TaskPool() 166 167 def hot_reload(self, hqf_path: Union[str, list]): 168 ''' 169 根据hqf_path路径对应用进行热重载 170 ''' 171 assert isinstance(hqf_path, (str, list)), 'Tyep of hqf_path is NOT string or list!' 172 if isinstance(hqf_path, str): 173 cmd = f'bm quickfix -a -f {hqf_path} -d' 174 elif isinstance(hqf_path, list): 175 cmd = f'bm quickfix -a -f {" ".join(hqf_path)} -d' 176 self.driver.log_info('hot reload: ' + cmd) 177 result = self.driver.shell(cmd).strip() 178 self.driver.log_info(result) 179 self.assert_equal(result, 'apply quickfic succeed.') 180 181 async def async_wait_timeout(self, task, timeout=3): 182 ''' 183 在timeout内执行task异步任务,若执行超时则抛出异常 184 ''' 185 try: 186 result = await asyncio.wait_for(task, timeout) 187 return result 188 except asyncio.TimeoutError: 189 self.driver.log_error('await timeout!') 190 191 def hdc_target_mount(self): 192 ''' 193 挂载设备文件系统 194 ''' 195 cmd = 'target mount' 196 self.driver.log_info('Mount finish: ' + cmd) 197 result = self.driver.hdc(cmd).strip() 198 self.driver.log_info(result) 199 self.assert_equal(result, 'Mount finish') 200 201 def hdc_file_send(self, source, sink): 202 ''' 203 将source中的文件发送到设备的sink路径下 204 ''' 205 cmd = f'file send {source} {sink}' 206 self.driver.log_info(cmd) 207 result = self.driver.hdc(cmd) 208 self.driver.log_info(result) 209 assert 'FileTransfer finish' in result, result 210 211 def clear_fault_log(self): 212 ''' 213 清楚故障日志 214 ''' 215 cmd = 'rm /data/log/faultlog/faultlogger/*' 216 self.driver.log_info(cmd) 217 result = self.driver.shell(cmd) 218 self.driver.log_info(result) 219 assert 'successfully' in result, result 220 221 def save_fault_log(self, log_path): 222 ''' 223 保存故障日志到log_path 224 ''' 225 if not os.path.exists(log_path): 226 os.makedirs(log_path) 227 228 cmd = f'file recv /data/log/faultlog/faultlogger/ {log_path}' 229 self.driver.log_info(cmd) 230 result = self.driver.hdc(cmd) 231 self.driver.log_info(result) 232 assert 'successfully' in result, result 233 234 235class UiUtils(object): 236 def __init__(self, driver): 237 self.driver = driver 238 239 def get_screen_size(self): 240 ''' 241 获取屏幕大小 242 ''' 243 screen_info = self.driver.shell(f"hidumper -s RenderService -a screen") 244 match = re.search(r'physical screen resolution: (\d+)x(\d+)', screen_info) 245 # 新版本镜像中screen_info信息格式有变,需要用下方正则表达式获取 246 if match is None: 247 match = re.search(r'physical resolution=(\d+)x(\d+)', screen_info) 248 assert match is not None, f"screen_info is incorrect: {screen_info}" 249 return int(match.group(1)), int(match.group(2)) 250 251 def click(self, x, y): 252 ''' 253 点击屏幕指定坐标位置 254 ''' 255 click_result = self.driver.shell(f"uinput -T -c {x} {y}") 256 self.driver.log_info(click_result) 257 assert "click coordinate" in click_result, f"click_result is incorrect: {click_result}" 258 259 def click_on_middle(self): 260 ''' 261 点击屏幕中心 262 ''' 263 width, height = self.get_screen_size() 264 middle_x = width // 2 265 middle_y = height // 2 266 self.click(middle_x, middle_y) 267 268 def back(self): 269 ''' 270 返回上一步 271 ''' 272 cmd = 'uitest uiInput keyEvent Back' 273 self.driver.log_info('click the back button: ' + cmd) 274 result = self.driver.shell(cmd).strip() 275 self.driver.log_info(result) 276 CommonUtils.assert_equal(result, 'No Error') 277 278 def keep_awake(self): 279 ''' 280 保持屏幕常亮,要在屏幕亮起时使用该方法才有效 281 ''' 282 cmd = 'power-shell wakeup' 283 result = self.driver.shell(cmd).strip() 284 assert "WakeupDevice is called" in result, result 285 cmd = 'power-shell timeout -o 214748647' 286 result = self.driver.shell(cmd).strip() 287 assert "Override screen off time to 214748647" in result, result 288 cmd = 'power-shell setmode 602' 289 result = self.driver.shell(cmd).strip() 290 assert "Set Mode Success!" in result, result 291 self.driver.log_info('Keep the screen awake Success!') 292 293 def open_control_center(self): 294 ''' 295 滑动屏幕顶部右侧打开控制中心 296 ''' 297 width, height = self.get_screen_size() 298 start = (int(width * 0.75), 20) 299 end = (int(width * 0.75), int(height * 0.3)) 300 cmd = f"uinput -T -m {start[0]} {start[1]} {end[0]} {end[1]} 500" 301 self.driver.log_info('open control center') 302 result = self.driver.shell(cmd) 303 self.driver.log_info(result) 304 305 def click_location_component(self): 306 ''' 307 点击控制中心中的位置控件 308 ''' 309 self.open_control_center() 310 self.driver.wait(1) 311 width, height = self.get_screen_size() 312 self.click(int(width * 0.2), int(height * 0.95)) 313 self.back() 314 315 def disable_location(self): 316 ''' 317 关闭位置信息(GPS),不能在异步任务中使用 318 ''' 319 self.driver.wait(1) 320 SystemUI.open_control_center(self.driver) 321 comp = self.driver.find_component(BY.key("ToggleBaseComponent_Image_location", MatchPattern.CONTAINS)) 322 result = comp.getAllProperties() 323 try: 324 bg = result.backgroudColor 325 value = int("0x" + bg[3:], base=16) 326 # 读取背景颜色判断开启或关闭 327 if value < 0xffffff: 328 comp.click() 329 self.driver.wait(1) 330 self.driver.press_back() 331 except Exception as e: 332 raise RuntimeError("Fail to disable location") 333 334 def enable_location(self): 335 ''' 336 开启位置信息(GPS),不能在异步任务中使用 337 ''' 338 self.driver.wait(1) 339 SystemUI.open_control_center(self.driver) 340 comp = self.driver.find_component(BY.key("ToggleBaseComponent_Image_location", MatchPattern.CONTAINS)) 341 result = comp.getAllProperties() 342 try: 343 bg = result.backgroudColor 344 value = int("0x" + bg[3:], base=16) 345 # 读取背景颜色判断开启或关闭 346 if value == 0xffffff: 347 comp.click() 348 self.driver.wait(1) 349 self.driver.press_back() 350 except Exception as e: 351 raise RuntimeError("Fail to disable location") 352 353 354class PerformanceUtils(object): 355 def __init__(self, driver): 356 self.driver = driver 357 self.time_data = {} 358 self.file_path_hwpower_genie_engine = "/system/app" 359 self.file_name_hwpower_genie_engine = "HwPowerGenieEngine3" 360 self.cpu_num_list = [0] * 3 361 self.cpu_start_id = [0] * 3 362 self.total_cpu_num = 0 363 self.cpu_cluster_num = 0 364 self.prev_mode = 0 365 self.board_ipa_support = False 366 self.perfg_version = "0.0" 367 368 def show_performace_data_in_html(self): 369 ''' 370 将性能数据以html格式展示出来 371 ''' 372 header = ['测试用例', '执行次数', '期望执行时间(ms)', '最大执行时间(ms)', 373 '最小执行时间(ms)', '执行时间中位数(ms)', '平均执行时间(ms)', 374 '平均执行时间/期望执行时间(%)'] 375 content = [] 376 failed_cases = [] 377 for key, value in self.time_data.items(): 378 if value.avg_proportion() >= 130: 379 failed_cases.append(key) 380 content.append([key, value.rounds(), value.expected_time, value.max_actual_time(), 381 value.min_actual_time(), value.mid_actual_time(), 382 f'{value.avg_actual_time()}:.2f', 383 f'{value.avg_proportion()}:.2f']) 384 table_html = '<table border="1" cellpadding="5" cellspacing="0"' 385 header_html = '<thead>' 386 for i in header: 387 header_html += f'<th>{i}</th>' 388 header_html += '</thead>' 389 content_html = 'tbody' 390 for row in content: 391 row_html = '<tr style="color: red;">' if row[0] in failed_cases else '<tr>' 392 for i in row: 393 row_html += f'<td>{i}</td>' 394 row_html += '</tr>' 395 content_html += row_html 396 table_html += header_html 397 table_html += content_html 398 table_html += '</table>' 399 self.driver.lg_info(table_html) 400 assert len(failed_cases) == 0, "The following cases have obvious deterioration: " + " ".join(failed_cases) 401 402 def add_time_data(self, case_name: str, expected_time: int, actual_time: int): 403 ''' 404 添加指定性能用例对应的耗时 405 ''' 406 if case_name in self.time_data: 407 self.time_data[case_name].add_actual_time(actual_time) 408 else: 409 self.time_data[case_name] = TimeRecord(expected_time, actual_time) 410 411 def get_perf_data_from_hilog(self): 412 ''' 413 从hilog日志中获取性能数据 414 ''' 415 # config the hilog 416 cmd = 'hilog -r' 417 self.driver.log_info(cmd) 418 result = self.driver.shell(cmd) 419 assert 'successfully' in result, result 420 421 cmd = 'hilog -G 16M' 422 self.driver.log_info(cmd) 423 result = self.driver.shell(cmd) 424 assert 'successfully' in result, result 425 426 cmd = 'hilog -Q pidoff' 427 self.driver.log_info(cmd) 428 result = self.driver.shell(cmd) 429 assert 'successfully' in result, result 430 431 cmd = 'hilog -Q domainoff' 432 self.driver.log_info(cmd) 433 result = self.driver.shell(cmd) 434 assert 'successfully' in result, result 435 436 cmd = 'hilog -b INFO' 437 self.driver.log_info(cmd) 438 result = self.driver.shell(cmd) 439 assert 'successfully' in result, result 440 441 # create a sub-process to receive the hilog 442 hilog_process = subprocess.Popen(['hdc', 'shell', 'hilog'], 443 stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 444 445 def get_time_from_records(records): 446 # 解析records的数据 447 pattern = r"\((.*?)\) Expected Time = (\d+), Actual Time = (\d+)" 448 for record in records: 449 match = re.search(pattern, record) 450 if match: 451 expected_time = int(match.group(2)) 452 actual_time = int(match.group(3)) 453 case_name = match.group(1) 454 if case_name in self.time_data: 455 self.time_data[case_name].add_actual_time(actual_time) 456 else: 457 self.time_data[case_name] = TimeRecord(expected_time, actual_time) 458 459 def get_perf_records(): 460 records = [] 461 try: 462 for line in iter(hilog_process.stdout.readline, b''): 463 decode_line = line.decode('utf-8') 464 if '[ArkCompilerPerfTest]' in decode_line: 465 records.append(decode_line) 466 except ValueError: 467 self.driver.log_info('hilog stream is closed.') 468 finally: 469 get_time_from_records(records) 470 471 perf_records_thread = threading.Thread(target=get_perf_records) 472 perf_records_thread.start() 473 474 return hilog_process, perf_records_thread 475 476 def lock_for_performance(self): 477 ''' 478 执行性能用例前先进行锁频锁核操作 479 ''' 480 if self._check_if_already_locked(): 481 self.driver.log_info("The device has locked the frequency and core.") 482 return True 483 # 获取系统参数 484 self._check_if_support_ipa() 485 self._check_perf_genius_version() 486 if not self._get_core_number(): 487 self.driver.log_info("Get core number failed.") 488 return False 489 # 取消性能限制 490 self._disable_perf_limitation() 491 # 锁频 492 self._lock_frequency() 493 self._lock_ddr_frequency() 494 # 锁中核和大核 495 self._lock_core_by_id(self.cpu_start_id[1], self.total_cpu_num - 1) 496 return True 497 498 def _check_if_support_ipa(self): 499 ''' 500 壳温IPA支持情况检查,检查结果存在self.board_ipa_support中 501 ''' 502 result = self.driver.shell("cat /sys/class/thermal/thermal_zone1/type").strip() 503 self.board_ipa_support = (result == 'board_thermal') 504 self.driver.log_info('If support IPA: ' + str(self.board_ipa_support)) 505 506 def _check_perf_genius_version(self): 507 ''' 508 perfGenius版本号检查 509 ''' 510 result = self.driver.shell("ps -ef | grep perfg") 511 idx = result.find('perfgenius@') 512 if idx != -1: 513 start_idx = idx + len('perfgenius') + 1 514 self.perfg_version = result[start_idx:start_idx + 4] 515 self.driver.log_info('PerfGenius Version: ' + self.perfg_version) 516 517 def _get_core_number(self): 518 ''' 519 从设备上读取大中小核个数 520 ''' 521 # 获取CPU总个数 522 result = self.driver.shell("cat /sys/devices/system/cpu/possible").strip() 523 idx = result.find('-') 524 if idx == -1 or result[idx + 1:] == '0': 525 self.driver.log_info('Get total cpu num failed') 526 return False 527 self.total_cpu_num = int(result[idx + 1:]) + 1 528 self.driver.log_info('total_cpu_num = ' + str(self.total_cpu_num)) 529 # 获取CPU小核个数 530 result = self.driver.shell("cat /sys/devices/system/cpu/cpu0/topology/core_siblings_list").strip() 531 idx = result.find('-') 532 if idx == -1 or result[idx + 1:] == '0': 533 self.driver.log_info('Get small-core cpu num failed') 534 return False 535 self.cpu_start_id[1] = int(result[idx + 1:]) + 1 536 self.driver.log_info('cpu_start_id[1] = ' + str(self.cpu_start_id[1])) 537 # 获取CPU中核个数 538 result = self.driver.shell( 539 f"cat /sys/devices/system/cpu/cpu{self.cpu_start_id[1]}/topology/core_siblings_list").strip() 540 idx = result.find('-') 541 if idx == -1 or result[idx + 1:] == '0': 542 self.driver.log_info('Get medium-core cpu num failed') 543 return False 544 self.cpu_start_id[2] = int(result[idx + 1:]) + 1 545 if self.cpu_start_id[2] == self.total_cpu_num: 546 self.cpu_start_id[2] = self.cpu_start_id[1] 547 return True 548 self.driver.log_info('cpu_start_id[2] = ' + str(self.cpu_start_id[2])) 549 # 获取CPU大核个数 550 result = self.driver.shell( 551 f"cat /sys/devices/system/cpu/cpu{self.cpu_start_id[2]}/topology/core_siblings_list").strip() 552 if result == '': 553 self.driver.log_info('Get large-core cpu num failed') 554 return False 555 idx = result.find('-') 556 tmp_total_cpu_num = (int(result) if idx == -1 else int(result[idx + 1])) + 1 557 if tmp_total_cpu_num != self.total_cpu_num: 558 self.driver.log_info('Get large-core cpu num failed') 559 return False 560 self.cpu_num_list[0] = self.cpu_start_id[1] 561 self.cpu_num_list[1] = self.cpu_start_id[2] - self.cpu_start_id[1] 562 self.cpu_num_list[2] = self.total_cpu_num - self.cpu_start_id[2] 563 self.driver.log_info(f'Small-core cpu number: {self.cpu_num_list[0]};' 564 f'Medium-core cpu number: {self.cpu_num_list[1]};' 565 f'Large-core cpu number: {self.cpu_num_list[2]};') 566 return True 567 568 def _disable_perf_limitation(self): 569 ''' 570 查杀省电精灵并关闭IPA和PerfGenius 571 ''' 572 # 如果存在省电精灵,强制删除并重启手机 573 result = self.driver.shell("find /system/app -name HwPowerGenieEngine3").strip() 574 if result != '': 575 self.driver.shell("rm -rf /system/app/HwPowerGenieEngine3") 576 self.driver.System.reboot() 577 self.driver.System.wait_for_boot_complete() 578 579 # 关闭IPA 580 self.driver.shell("echo disabled > /sys/class/thermal/thermal_zone0/mode") 581 if self.board_ipa_support: 582 self.driver.shell("echo disabled > /sys/class/thermal/thermal_zone0/mode") 583 self.driver.shell("echo 10000 > /sys/class/thermal/thermal_zone0/sustainable_power") 584 # 关闭perfGenius, 使用时需要手动修改下方bundle_name 585 bundle_name = '' 586 if self.perfg_version == '0.0': 587 self.driver.shell("dumpsys perfhub --perfhub_dis") 588 else: 589 self.driver.shell(f"lshal debug {bundle_name}@{self.perfg_version}" 590 f"::IPerfGenius/perfgenius --perfgenius_dis") 591 592 def _lock_frequency_by_start_id(self, start_id): 593 ''' 594 锁id为start_id的CPU核的频率 595 ''' 596 self.driver.log_info(f"Lock frequency, start_id = {start_id}") 597 result = self.driver.shell( 598 f"cat /sys/devices/system/cpu/cpu{start_id}/cpufreq/scaling_available_frequencies").strip() 599 freq_list = list(map(int, result.strip())) 600 max_freq = max(freq_list) 601 self.driver.shell(f"echo {max_freq} > cat /sys/devices/system/cpu/cpu{start_id}/cpufreq/scaling_max_freq") 602 self.driver.shell(f"echo {max_freq} > cat /sys/devices/system/cpu/cpu{start_id}/cpufreq/scaling_min_freq") 603 self.driver.shell(f"echo {max_freq} > cat /sys/devices/system/cpu/cpu{start_id}/cpufreq/scaling_max_freq") 604 605 def _lock_ddr_frequency(self): 606 ''' 607 DDR锁频,锁最接近749000000的频率 608 ''' 609 std_freq = 749000000 610 self.driver.log_info("Lock ddr frequency") 611 result = self.driver.shell("cat /sys/class/devfreq/ddrfreq/available_frequencies").strip() 612 freq_list = list(map(int, result.strip())) 613 freq = min(freq_list, key=lambda x: abs(x - std_freq)) 614 self.driver.shell(f"echo {freq} > /sys/class/devfreq/ddrfreq/max_freq") 615 self.driver.shell(f"echo {freq} > /sys/class/devfreq/ddrfreq/min_freq") 616 self.driver.shell(f"echo {freq} > /sys/class/devfreq/ddrfreq/max_freq") 617 self.driver.shell(f"echo {freq} > /sys/class/devfreq/ddrfreq_up_threshold/max_freq") 618 self.driver.shell(f"echo {freq} > /sys/class/devfreq/ddrfreq_up_threshold/min_freq") 619 self.driver.shell(f"echo {freq} > /sys/class/devfreq/ddrfreq_up_threshold/max_freq") 620 621 def _lock_frequency(self): 622 ''' 623 锁CPU的所有核的频率 624 ''' 625 # 小核锁频 626 self._lock_frequency_by_start_id(self.cpu_start_id[0]) 627 # 中核锁频 628 if self.cpu_num_list[1] != 0: 629 self._lock_frequency_by_start_id(self.cpu_start_id[1]) 630 # 大核锁频 631 self._lock_frequency_by_start_id(self.cpu_start_id[2]) 632 633 def _lock_core_by_id(self, start_id, end_id): 634 ''' 635 锁start_id到end_id的CPU核 636 ''' 637 for i in range(start_id, end_id + 1): 638 self.driver.shell(f"echo 0 > sys/devices/system/cpu/cpu{i}/online") 639 self.driver.shell("echo test > sys/power/wake_unlock") 640 641 def _check_if_already_locked(self): 642 ''' 643 根据DDR频率来判断是否已经锁频锁核 644 ''' 645 result = self.driver.shell("cat /sys/class/devfreq/ddrfreq/cur_freq").strip() 646 return result == "749000000" 647