1#!/usr/bin/env python3

2

3# Copyright (C) 2020 Canonical Ltd.

4#

5# Authors

6# Adrian Lane <adrian.lane@canonical.com>

7#

8# This program is free software: you can redistribute it and/or modify

9# it under the terms of the GNU General Public License version 3,

10# as published by the Free Software Foundation.

11#

12# This program is distributed in the hope that it will be useful,

13# but WITHOUT ANY WARRANTY; without even the implied warranty of

14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

15# GNU General Public License for more details.

16#

17# You should have received a copy of the GNU General Public License

18# along with this program. If not, see <http://www.gnu.org/licenses/>.

19

20"""Test and validate SUT CPU scaling capabilities via CPUFreq."""

21

22

23 from os import path, geteuid

24 import multiprocessing

25 import collections

26 import threading

27 import argparse

28 import logging

29 import pprint

30 import random

31 import signal

32 import copy

33 import math

34 import time

35 import sys

36 import psutil

37

38

39 class CpuFreqTestError(Exception):

40 """Exception handling."""

41

42 def __init__(self, message):

43 super().__init__()

44 # warn and exit if cpufreq scaling non-supported

45 if "scaling_driver" in message:

46 logging.warning("## Warning: scaling via CpuFreq non-supported ##")

47 sys.exit()

48 # exempt systems unable to change intel_pstate driver mode

49 elif "intel_pstate/status" in message:

50 pass

51 else:

52 logging.error(message)

53

54

55 class CpuFreqTest:

56 """Test cpufreq scaling capabilities."""

57

58 # duration to stay at frequency (sec) (gt observe_interval)

59 scale_duration = 8

60 # frequency sampling interval (sec) (lt scale_duration)

61 observe_interval = 0.4

62 # max, min percentage of avg freq allowed to pass

63 # values relative to target freq

64 # ex: max = 110, min = 90 is 20% passing tolerance

65 # LP 1963650: lowered min to 85 to fine tune failures

66 max_freq_pct = 150

67 min_freq_pct = 85

68

69 def __init__(self):

70 def append_max_min():

71 """Create scaling table from max_freq,

72 min_freq cpufreq files.

73 """

74 freq_table = []

75 path_max = path.join("cpu0", "cpufreq", "scaling_max_freq")

76 path_min = path.join("cpu0", "cpufreq", "scaling_min_freq")

77 freq_table.append(self._read_sysfs(path_max).rstrip("\n"))

78 freq_table.append(self._read_sysfs(path_min).rstrip("\n"))

79 return freq_table

80

81 self.fail_count = 0

82 self.path_root = "/sys/devices/system/cpu"

83 self.__proc_list = [] # track spawned processes

84 # catalog known cpufreq driver types

85 # used to determine logic flow control

86 self.driver_types = ("-cpufreq", "cpufreq-", "arm-big-little")

87 # chainmap object for dict of dicts

88 self.freq_chainmap = collections.ChainMap()

89 # cpufreq driver

90 path_scaling_driver = path.join("cpu0", "cpufreq", "scaling_driver")

91 self.scaling_driver = self._read_sysfs(path_scaling_driver).rstrip(

92 "\n"

93 )

94 path_scaling_gvrnrs = path.join(

95 "cpu0", "cpufreq", "scaling_available_governors"

96 )

97 path_startup_governor = path.join(

98 "cpu0", "cpufreq", "scaling_governor"

99 )

100 self.scaling_gvrnrs = (

101 self._read_sysfs(path_scaling_gvrnrs).rstrip("\n").split()

102 )

103 self.startup_governor = self._read_sysfs(path_startup_governor).rstrip(

104 "\n"

105 )

106

107 # ensure the correct freq table is populated

108 if any(drvr in self.scaling_driver for drvr in self.driver_types):

109 path_scaling_freqs = path.join(

110 "cpu0", "cpufreq", "scaling_available_frequencies"

111 )

112 scaling_freqs = (

113 self._read_sysfs(path_scaling_freqs).rstrip("\n").split()

114 )

115 self.scaling_freqs = list(map(int, scaling_freqs))

116 # test freqs in ascending order

117 self.scaling_freqs.sort()

118 else:

119 # setup path and status for intel pstate directives

120 if "intel_" in self.scaling_driver:

121 # /sys/devices/system/cpu/intel_pstate/status

122 self.path_ipst_status = path.join("intel_pstate", "status")

123 self.startup_ipst_status = self._read_sysfs(

124 self.path_ipst_status

125 ).rstrip("\n")

126 # use max, min freq for scaling table

127 self.scaling_freqs = list(map(int, append_max_min()))

128 self.scaling_freqs.sort()

129 self.startup_max_freq = self.scaling_freqs[1]

130 self.startup_min_freq = self.scaling_freqs[0]

131

132 def _read_sysfs(self, fpath):

133 """Read sysfs/cpufreq file."""

134 abs_path = path.join(self.path_root, fpath)

135 try:

136 with open(abs_path, "r") as _file:

137 data = _file.read()

138 except OSError:

139 raise CpuFreqTestError("Unable to read file: %s" % abs_path)

140 return data

141

142 def _write_sysfs(self, fpath, data):

143 """Write sysfs/cpufreq file, data type agnostic."""

144

145 def return_bytes_utf(_data):

146 """Data type conversion to bytes utf."""

147 try:

148 data_enc = _data.encode()

149 except (AttributeError, TypeError):

150 data_enc = str(_data).encode()

151 return bytes(data_enc)

152

153 if not isinstance(data, bytes):

154 data_utf = return_bytes_utf(data)

155 else:

156 # do not convert bytes()

157 data_utf = data

158

159 abs_path = path.join(self.path_root, fpath)

160 try:

161 with open(abs_path, "wb") as _file:

162 _file.write(data_utf)

163 except OSError:

164 raise CpuFreqTestError("Unable to write file: %s" % abs_path)

165

166 def _get_cores(self, fpath):

167 """Get various core ranges, convert to list."""

168

169 def list_core_range(_core_range):

170 """Method to convert core range to list prior

171 to iteration.

172 """

173 _core_list = []

174 # allow iteration over range: rng

175 for core in _core_range.split(","):

176 first_last = core.split("-")

177 if len(first_last) == 2:

178 _core_list += list(

179 range(int(first_last[0]), int(first_last[1]) + 1)

180 )

181 else:

182 _core_list += [int(first_last[0])]

183 return _core_list

184

185 core_range = self._read_sysfs(fpath).strip("\n").strip()

186 core_list = list_core_range(core_range)

187 return core_list

188

189 def _process_results(self):

190 """Process results from CpuFreqCoreTest."""

191

192 def comp_freq_dict(_inner_key, _inner_val):

193 """Transpose and append results from subclass."""

194 if _inner_val:

195 # calc freq_median/freq_target %

196 result_pct = int((_inner_val / _inner_key) * 100)

197 if (

198 CpuFreqTest.min_freq_pct

199 <= result_pct

200 <= (CpuFreqTest.max_freq_pct)

201 ):

202 # append result pass/fail

203 new_inner_val = [str(result_pct) + "%", "Pass"]

204 else:

205 new_inner_val = [str(result_pct) + "%", "Fail"]

206 # increment fail bit

207 self.fail_count += 1

208 # append raw freq_median value

209 new_inner_val.append(int(_inner_val))

210 else:

211 new_inner_val = ["<=0%", "Fail", _inner_val]

212 self.fail_count += 1

213 return new_inner_val

214

215 # create master result table with dict comprehension

216 freq_result_map = {

217 outer_key: {

218 inner_key: comp_freq_dict(inner_key, inner_val)

219 for inner_key, inner_val in outer_val.items()

220 }

221 for outer_key, outer_val in self.freq_chainmap.items()

222 }

223 return freq_result_map

224

225 def disable_thread_siblings(self):

226 """Disable thread_siblings (aka hyperthreading)

227 on all cores.

228 """

229

230 def get_thread_siblings():

231 """Get hyperthread cores to offline."""

232 thread_siblings = []

233 online_cores = self._get_cores("online")

234 for _core in online_cores:

235 _fpath = path.join(

236 "cpu%i" % _core, "topology", "thread_siblings_list"

237 )

238 # second core is sibling

239 thread_siblings += self._get_cores(_fpath)[1:]

240

241 if thread_siblings:

242 _to_disable = set(thread_siblings) & set(online_cores)

243 logging.info("* disabling thread siblings (hyperthreading):")

244 logging.info(" - disabling cores: %s", _to_disable)

245 else:

246 _to_disable = False

247 return _to_disable

248

249 to_disable = get_thread_siblings()

250 if to_disable:

251 for core in to_disable:

252 fpath = path.join("cpu%i" % core, "online")

253 self._write_sysfs(fpath, 0)

254

255 def set_governors(self, governor):

256 """Set/change CpuFreq scaling governor; global on all cores."""

257 logging.info(" - setting governor: %s", governor)

258 online_cores = self._get_cores("online")

259 for core in online_cores:

260 fpath = path.join("cpu%i" % core, "cpufreq", "scaling_governor")

261 self._write_sysfs(fpath, governor)

262

263 def reset(self):

264 """Enable all offline cpus,

265 and reset max and min frequencies files.

266 """

267

268 def reset_intel_driver():

269 """Reset fn for pstate driver."""

270 try:

271 self._write_sysfs(self.path_ipst_status, "off")

272 # if kernel/bios limitations present

273 except CpuFreqTestError:

274 # then reset via max, min freq files

275 set_max_min()

276 return

277

278 logging.info("* resetting intel p_state cpufreq driver")

279 # wait 300ms between setting driver modes

280 time.sleep(0.3)

281 logging.info(

282 " - setting driver mode: %s", self.startup_ipst_status

283 )

284 self._write_sysfs(self.path_ipst_status, self.startup_ipst_status)

285

286 def enable_off_cores():

287 """Enable all present and offline cores."""

288 present_cores = self._get_cores("present")

289 try:

290 offline_cores = self._get_cores("offline")

291 # for -r (reset) arg invokation

292 except ValueError:

293 return

294

295 to_enable = set(present_cores) & set(offline_cores)

296 logging.info("* enabling thread siblings/hyperthreading:")

297 logging.info(" - enabling cores: %s", to_enable)

298 for core in to_enable:

299 fpath = path.join("cpu%i" % core, "online")

300 self._write_sysfs(fpath, 1)

301

302 def set_max_min():

303 """Set max_frequency and min_frequency cpufreq files."""

304 logging.info("* restoring max, min freq files")

305 present_cores = self._get_cores("present")

306 for core in present_cores:

307 path_max = path.join(

308 "cpu%i" % core, "cpufreq", "scaling_max_freq"

309 )

310 path_min = path.join(

311 "cpu%i" % core, "cpufreq", "scaling_min_freq"

312 )

313 # reset max freq

314 self._write_sysfs(path_max, self.startup_max_freq)

315 # reset min freq

316 self._write_sysfs(path_min, self.startup_min_freq)

317

318 logging.info("* restoring startup governor:")

319 self.set_governors(self.startup_governor)

320

321 # enable offline cores

322 enable_off_cores()

323

324 # reset sysfs for non-acpi_cpufreq systems

325 if not any(drvr in self.scaling_driver for drvr in self.driver_types):

326 if "intel_" in self.scaling_driver:

327 reset_intel_driver()

328 else:

329 set_max_min()

330

331 def execute_test(self):

332 """Execute cpufreq test, process results and return

333 appropriate exit code.

334 """

335

336 def init_intel_driver():

337 """Initialize Intel driver for testing.

338 Some modes unavailable for certain processor:kernel/bios configs.

339 """

340 try:

341 self._write_sysfs(self.path_ipst_status, "off")

342 # exempt systems unable to change intel_pstate driver mode

343 except CpuFreqTestError:

344 return

345

346 logging.info("* initializing intel_cpufreq driver:")

347 # wait 300ms between changing driver modes

348 time.sleep(0.3)

349 # prefer the intel_cpufreq driver (passive mode)

350 self._write_sysfs(self.path_ipst_status, "passive")

351 cur_ipst_status = self._read_sysfs(self.path_ipst_status).rstrip(

352 "\n"

353 )

354 logging.info(" - driver mode: %s", cur_ipst_status)

355

356 logging.info(

357 "---------------------\n"

358 "| CpuFreqTest Begin |\n"

359 "---------------------"

360 )

361 start_time = time.time()

362 # disable hyperthreading

363 self.disable_thread_siblings()

364

365 # if intel, reset and set best compatible driver

366 if "intel_" in self.scaling_driver:

367 init_intel_driver()

368

369 logging.info("* configuring cpu governors:")

370 # userspace governor required for scaling_setspeed

371 if any(drvr in self.scaling_driver for drvr in self.driver_types):

372 self.set_governors("userspace")

373 else:

374 self.set_governors("performance")

375

376 # spawn core_tests concurrently

377 logging.info("---------------------")

378 self.spawn_core_test()

379 # wrap up test

380 logging.info(

381 "\n-----------------\n" "| Test Complete |\n" "-----------------\n"

382 )

383 # reset state and cleanup

384 logging.info("[Reset & Cleanup]")

385 self.reset()

386

387 # facilitate house cleaning

388 if self.__proc_list:

389 logging.info("* terminating dangling pids")

390 for proc in self.__proc_list:

391 # terminate dangling processes

392 proc.terminate()

393 # prove that we are single-threaded again

394 logging.info("* active threads: %i\n", threading.active_count())

395

396 # process, then display results

397 results = self._process_results()

398 # provide time under test for debug/verbose output

399 end_time = time.time() - start_time

400

401 print("[CpuFreqTest Results]")

402 logging.debug("[Test Took: %.3fs]", end_time)

403 logging.info(

404 " - legend:\n"

405 " {core: {target_freq:"

406 "[sampled_med_%, P/F, sampled_median],:.\n"

407 )

408

409 if self.fail_count:

410 print(pprint.pformat(results))

411 print("\n[Test Failed]\n" "* core fail_count =", self.fail_count)

412 return 1

413

414 logging.info(pprint.pformat(results))

415 print("\n[Test Passed]")

416

417 return 0

418

419 def spawn_core_test(self):

420 """Spawn concurrent scale testing on all online cores."""

421

422 def run_worker_process(_result_queue, affinity):

423 """Subclass instantiation & constructor for

424 individual core.

425 """

426 _worker = psutil.Process()

427 # assign affinity, pin to core

428 _worker.cpu_affinity(affinity)

429 # intantiate core_test

430 cpu_freq_ctest = CpuFreqCoreTest(affinity[0], _worker.pid)

431 # execute freq scaling

432 cpu_freq_ctest.scale_all_freq()

433 # get results

434 res_freq_map = cpu_freq_ctest.__call__()

435 # place in result_queue

436 _result_queue.put(res_freq_map)

437

438 def process_rqueue(queue_depth, _result_queue):

439 """Get and process core_test result_queue."""

440 # get queued core_test results

441 for _ in range(queue_depth):

442 # pipe results from core_test

443 worker_queue = _result_queue.get()

444 # append to chainmap object

445 self.freq_chainmap = self.freq_chainmap.new_child(worker_queue)

446 # signal processing complete

447 _result_queue.task_done()

448 logging.info("----------------------------")

449 logging.info("* joining and closing queues")

450 # nicely join and close queue

451 try:

452 _result_queue.join()

453 finally:

454 _result_queue.close()

455

456 worker_list = [] # track spawned multiproc processes

457 pid_list = [] # track spawned multiproc pids

458 online_cores = self._get_cores("online")

459 # delegate & spawn tests on other cores first

460 # then run core 0 last (main() thread)

461 online_cores.append(online_cores.pop(0))

462 # create queue for piping results

463 result_queue = multiprocessing.JoinableQueue()

464

465 # assign affinity and spawn core_test

466 for core in online_cores:

467 affinity = [int(core)]

468 affinity_dict = dict(affinity=affinity)

469 worker = multiprocessing.Process(

470 target=run_worker_process,

471 args=(result_queue,),

472 kwargs=affinity_dict,

473 )

474 # start core_test

475 worker.start()

476 worker_list.append(worker)

477 # track and log active child pids

478 pid_list.append(worker.pid)

479

480 # get, process queues

481 process_rqueue(len(worker_list), result_queue)

482

483 # cleanup core_test pids

484 logging.info("* joining worker processes:")

485 for idx, worker in enumerate(worker_list):

486 # join worker processes

487 worker_return = worker.join()

488 time.sleep(0.1)

489 if worker_return is None:

490 logging.info(" - PID %s joined parent", pid_list[idx])

491 else:

492 # can cleanup in reset subroutine

493 continue

494 # update attribute for a 2nd pass terminate

495 self.__proc_list = worker_list

496

497

498 class CpuFreqCoreTest(CpuFreqTest):

499 """Subclass to facilitate concurrent frequency scaling."""

500

501 class ObserveFreq:

502 """Class for instantiating observation thread.

503 Non-blocking and locked to system time to prevent

504 linear timer drift as frequency scaling ramps up.

505 """

506

507 __slots__ = (

508 "interval",

509 "callback",

510 "thread_timer",

511 "timer_running",

512 "next_call",

513 )

514

515 def __init__(self, interval, callback):

516 """Execute start_timer on class instantiation."""

517 self.interval = interval

518 self.callback = callback

519 self.thread_timer = None

520 self.timer_running = False

521 self.next_call = time.time()

522 # start event loop

523 self.start_timer()

524

525 def start_timer(self):

526 """Facilitate callbacks at specified interval,

527 accounts and corrects for drift.

528 """

529 if not self.timer_running:

530 # offset interval

531 self.next_call += self.interval

532 # create time delta for consistent timing

533 time_delta = self.next_call - time.time()

534 # call self.observe() at end of time_delta

535 self.thread_timer = threading.Timer(time_delta, self.observe)

536 # cleanup spawned timer threads on exit

537 self.thread_timer.daemon = True

538 self.thread_timer.start()

539 self.timer_running = True

540

541 def observe(self):

542 """Trigger callback to sample frequency."""

543 # reset timer_running

544 self.timer_running = False

545 # callback to outer scope

546 self.callback()

547 # start another tt cycle

548 self.start_timer()

549

550 def stop(self):

551 """Called when frequency scaling completed."""

552 if self.thread_timer:

553 # event loop end

554 self.thread_timer.cancel()

555 # logic reinforcement

556 self.timer_running = False

557

558 # as we may instantiate many instances

559 __slots__ = (

560 "core",

561 "pid",

562 "__instance_core",

563 "__instance_cpu",

564 "__instance_pid",

565 "__stop_scaling",

566 "__observed_freqs",

567 "__observed_freqs_dict",

568 "__read_sysfs",

569 "__write_sysfs",

570 )

571

572 def __init__(self, core, pid):

573 # perform base class inheritance

574 super().__init__()

575 # mangle instance attributes

576 self.__instance_core = int(core)

577 self.__instance_cpu = "cpu%i" % core # future call speedup

578 self.__instance_pid = pid # worker pid

579 self.__stop_scaling = False # signal.alarm semaphore

580 self.__observed_freqs = [] # recorded freqs

581 self.__observed_freqs_dict = {} # core: recorded freqs

582 # private _r/_w_sysfs methods for concurrent access w/o locks

583 self.__read_sysfs = copy.deepcopy(self._read_sysfs)

584 self.__write_sysfs = copy.deepcopy(self._write_sysfs)

585

586 def __call__(self):

587 """Have subclass return dict '{core: {trgt_f: med_f,}}'

588 when called.

589 """

590 freq_map = {self.__instance_core: self.__observed_freqs_dict}

591 return freq_map

592

593 def _observefreq_callback(self):

594 """Callback method to sample frequency."""

595

596 def get_cur_freq():

597 """Get current frequency."""

598 fpath = path.join(

599 self.__instance_cpu, "cpufreq", "scaling_cur_freq"

600 )

601 freqs = self.__read_sysfs(fpath).rstrip("\n").split()[0]

602 return int(freqs)

603

604 self.__observed_freqs.append(get_cur_freq())

605 # matrix mode

606 logging.debug(self.__observed_freqs)

607

608 def scale_all_freq(self):

609 """Primary method to scale full range of freqs."""

610

611 def calc_freq_median(obs_freqs):

612 """Calculate the median value of observed freqs."""

613 n_samples = len(obs_freqs)

614 c_index = n_samples // 2

615 # odd number of samples

616 if n_samples % 2:

617 freq_median = sorted(obs_freqs)[c_index]

618 # even number of samples

619 else:

620 freq_median = (

621 sum(sorted(obs_freqs)[(c_index - 1) : (c_index + 1)]) / 2

622 )

623 return freq_median

624

625 def map_observed_freqs(target_freq):

626 """Align freq key/values and split result lists

627 for grouping.

628 """

629 # get median of observed freqs

630 freq_median = calc_freq_median(self.__observed_freqs)

631 # target_freq = key, freq_median = value

632 self.__observed_freqs_dict.update({target_freq: freq_median})

633

634 def handle_alarm(*args):

635 """Alarm trigger callback, unload core."""

636 # *args req to call signal.signal()

637 del args # args unused

638 # stop workload loop

639 self.__stop_scaling = True

640

641 def execute_workload(workload_n):

642 """Perform maths to load core."""

643 # compartmentalized for future development

644 while not self.__stop_scaling:

645 math.factorial(workload_n)

646

647 def log_freq_scaling(_freq, workload_n):

648 """Provide feedback via logging."""

649 logging.info(

650 "* testing: %s || target freq: %i ||"

651 " work: fact(%i) || worker pid: %i",

652 self.__instance_cpu,

653 _freq,

654 workload_n,

655 self.__instance_pid,

656 )

657

658 def load_observe_map(_freq):

659 """Proxy fn to scale core to freq."""

660 # gen randint for workload factorial calcs

661 workload_n = random.randint(37512, 39845)

662 # setup async alarm to kill load gen

663 signal.signal(signal.SIGALRM, handle_alarm)

664 # time to gen load

665 signal.alarm(CpuFreqTest.scale_duration)

666 # instantiate ObserveFreq and start data sampling

667 observe_freq = self.ObserveFreq(

668 interval=CpuFreqTest.observe_interval,

669 callback=self._observefreq_callback,

670 )

671 # provide feedback on test status

672 log_freq_scaling(_freq, workload_n)

673 # start loading core

674 execute_workload(workload_n)

675 # stop sampling

676 observe_freq.stop()

677 # map freq results to core

678 map_observed_freqs(_freq)

679

680 # cpufreq class driver (non-intel) supports full freq table scaling

681 if any(drvr in self.scaling_driver for drvr in self.driver_types):

682 fpath = path.join(

683 self.__instance_cpu, "cpufreq", "scaling_setspeed"

684 )

685 # others support max, min freq scaling

686 else:

687 fpath = path.join(

688 self.__instance_cpu, "cpufreq", "scaling_max_freq"

689 )

690

691 # iterate over supported frequency scaling table

692 for idx, freq in enumerate(self.scaling_freqs):

693 # re-init some attributes after 1st pass

694 if idx:

695 # time buffer ensure all prior freq intervals processed

696 time.sleep(1)

697 # reset freq list

698 self.__observed_freqs = []

699 # reset signal.signal() event loop bit

700 self.__stop_scaling = False

701

702 self.__write_sysfs(fpath, freq)

703 # load core, observe freqs, map to obs_freq_dict

704 load_observe_map(freq)

705

706

707 def parse_arg_logging():

708 """Ingest arguments and init logging."""

709

710 def init_logging(_user_arg):

711 """Pass user arg and configure logging module."""

712 # logging optimizations; per python logging lib docs

713 logging._srcfile = None # pylint: disable=protected-access

714 # "%(processName)s prefix

715 logging.logMultiprocessing = False

716 # "%(process)d" prefix

717 logging.logProcesses = False

718 # "%(thread)d" & "%(threadName)s" prefixes

719 logging.logThreads = False

720

721 # log to stdout for argparsed logging lvls

722 stdout_handler = logging.StreamHandler(sys.stdout)

723 stdout_handler.setLevel(_user_arg.log_level)

724

725 # log to stderr for exceptions

726 stderr_formatter = logging.Formatter("%(levelname)s: %(message)s")

727 stderr_handler = logging.StreamHandler(sys.stderr)

728 stderr_handler.setLevel(logging.ERROR)

729 stderr_handler.setFormatter(stderr_formatter)

730

731 # setup base/root logger

732 root_logger = logging.getLogger()

733 # set root logging level

734 root_logger.setLevel(logging.NOTSET)

735 # add handlers for out, err

736 root_logger.addHandler(stdout_handler)

737 root_logger.addHandler(stderr_handler)

738

739 parser = argparse.ArgumentParser()

740 # only allow one arg to be passed

741 parser_mutex_grp = parser.add_mutually_exclusive_group()

742 parser_mutex_grp.add_argument(

743 "-d",

744 "-D",

745 "--debug",

746 dest="log_level",

747 action="store_const",

748 const=logging.DEBUG,

749 # default logging level

750 default=logging.INFO,

751 help="debug/verbose output",

752 )

753 parser_mutex_grp.add_argument(

754 "-q",

755 "-Q",

756 "--quiet",

757 dest="log_level",

758 action="store_const",

759 # allow visible warnings in quiet mode

760 const=logging.WARNING,

761 help="suppress output",

762 )

763 parser_mutex_grp.add_argument(

764 "-r",

765 "-R",

766 "--reset",

767 action="store_true",

768 help="reset cpufreq sysfs parameters (all cores):"

769 " (governor, thread siblings, max/min freqs, pstate)",

770 )

771 user_arg = parser.parse_args()

772 init_logging(user_arg)

773 return user_arg

774

775

776 def main():

777 # configure and start logging

778 user_arg = parse_arg_logging()

779 # Make sure we're running with root permissions

780 if geteuid() != 0:

781 logging.error("You must be root to run this script")

782 return 1

783 # instantiate CpuFreqTest as cpu_freq_test

784 cpu_freq_test = CpuFreqTest()

785 # provide access to reset() method

786 if user_arg.reset:

787 print("[Reset CpuFreq Sysfs]")

788 return cpu_freq_test.reset()

789

790 return cpu_freq_test.execute_test()

791

792

793 if __name__ == "__main__":

794 sys.exit(main())