Coverage for /pythoncovmergedfiles/medio/medio/usr/lib/python3.9/pstats.py: 14%

545 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:05 +0000

1"""Class for printing reports on profiled python code.""" 

2 

3# Written by James Roskind 

4# Based on prior profile module by Sjoerd Mullender... 

5# which was hacked somewhat by: Guido van Rossum 

6 

7# Copyright Disney Enterprises, Inc. All Rights Reserved. 

8# Licensed to PSF under a Contributor Agreement 

9# 

10# Licensed under the Apache License, Version 2.0 (the "License"); 

11# you may not use this file except in compliance with the License. 

12# You may obtain a copy of the License at 

13# 

14# http://www.apache.org/licenses/LICENSE-2.0 

15# 

16# Unless required by applicable law or agreed to in writing, software 

17# distributed under the License is distributed on an "AS IS" BASIS, 

18# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 

19# either express or implied. See the License for the specific language 

20# governing permissions and limitations under the License. 

21 

22 

23import sys 

24import os 

25import time 

26import marshal 

27import re 

28 

29from enum import Enum 

30from functools import cmp_to_key 

31from dataclasses import dataclass 

32from typing import Dict 

33 

34__all__ = ["Stats", "SortKey", "FunctionProfile", "StatsProfile"] 

35 

36class SortKey(str, Enum): 

37 CALLS = 'calls', 'ncalls' 

38 CUMULATIVE = 'cumulative', 'cumtime' 

39 FILENAME = 'filename', 'module' 

40 LINE = 'line' 

41 NAME = 'name' 

42 NFL = 'nfl' 

43 PCALLS = 'pcalls' 

44 STDNAME = 'stdname' 

45 TIME = 'time', 'tottime' 

46 

47 def __new__(cls, *values): 

48 value = values[0] 

49 obj = str.__new__(cls, value) 

50 obj._value_ = value 

51 for other_value in values[1:]: 

52 cls._value2member_map_[other_value] = obj 

53 obj._all_values = values 

54 return obj 

55 

56 

57@dataclass(unsafe_hash=True) 

58class FunctionProfile: 

59 ncalls: int 

60 tottime: float 

61 percall_tottime: float 

62 cumtime: float 

63 percall_cumtime: float 

64 file_name: str 

65 line_number: int 

66 

67@dataclass(unsafe_hash=True) 

68class StatsProfile: 

69 '''Class for keeping track of an item in inventory.''' 

70 total_tt: float 

71 func_profiles: Dict[str, FunctionProfile] 

72 

73class Stats: 

74 """This class is used for creating reports from data generated by the 

75 Profile class. It is a "friend" of that class, and imports data either 

76 by direct access to members of Profile class, or by reading in a dictionary 

77 that was emitted (via marshal) from the Profile class. 

78 

79 The big change from the previous Profiler (in terms of raw functionality) 

80 is that an "add()" method has been provided to combine Stats from 

81 several distinct profile runs. Both the constructor and the add() 

82 method now take arbitrarily many file names as arguments. 

83 

84 All the print methods now take an argument that indicates how many lines 

85 to print. If the arg is a floating point number between 0 and 1.0, then 

86 it is taken as a decimal percentage of the available lines to be printed 

87 (e.g., .1 means print 10% of all available lines). If it is an integer, 

88 it is taken to mean the number of lines of data that you wish to have 

89 printed. 

90 

91 The sort_stats() method now processes some additional options (i.e., in 

92 addition to the old -1, 0, 1, or 2 that are respectively interpreted as 

93 'stdname', 'calls', 'time', and 'cumulative'). It takes either an 

94 arbitrary number of quoted strings or SortKey enum to select the sort 

95 order. 

96 

97 For example sort_stats('time', 'name') or sort_stats(SortKey.TIME, 

98 SortKey.NAME) sorts on the major key of 'internal function time', and on 

99 the minor key of 'the name of the function'. Look at the two tables in 

100 sort_stats() and get_sort_arg_defs(self) for more examples. 

101 

102 All methods return self, so you can string together commands like: 

103 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\ 

104 print_stats(5).print_callers(5) 

105 """ 

106 

107 def __init__(self, *args, stream=None): 

108 self.stream = stream or sys.stdout 

109 if not len(args): 

110 arg = None 

111 else: 

112 arg = args[0] 

113 args = args[1:] 

114 self.init(arg) 

115 self.add(*args) 

116 

117 def init(self, arg): 

118 self.all_callees = None # calc only if needed 

119 self.files = [] 

120 self.fcn_list = None 

121 self.total_tt = 0 

122 self.total_calls = 0 

123 self.prim_calls = 0 

124 self.max_name_len = 0 

125 self.top_level = set() 

126 self.stats = {} 

127 self.sort_arg_dict = {} 

128 self.load_stats(arg) 

129 try: 

130 self.get_top_level_stats() 

131 except Exception: 

132 print("Invalid timing data %s" % 

133 (self.files[-1] if self.files else ''), file=self.stream) 

134 raise 

135 

136 def load_stats(self, arg): 

137 if arg is None: 

138 self.stats = {} 

139 return 

140 elif isinstance(arg, str): 

141 with open(arg, 'rb') as f: 

142 self.stats = marshal.load(f) 

143 try: 

144 file_stats = os.stat(arg) 

145 arg = time.ctime(file_stats.st_mtime) + " " + arg 

146 except: # in case this is not unix 

147 pass 

148 self.files = [arg] 

149 elif hasattr(arg, 'create_stats'): 

150 arg.create_stats() 

151 self.stats = arg.stats 

152 arg.stats = {} 

153 if not self.stats: 

154 raise TypeError("Cannot create or construct a %r object from %r" 

155 % (self.__class__, arg)) 

156 return 

157 

158 def get_top_level_stats(self): 

159 for func, (cc, nc, tt, ct, callers) in self.stats.items(): 

160 self.total_calls += nc 

161 self.prim_calls += cc 

162 self.total_tt += tt 

163 if ("jprofile", 0, "profiler") in callers: 

164 self.top_level.add(func) 

165 if len(func_std_string(func)) > self.max_name_len: 

166 self.max_name_len = len(func_std_string(func)) 

167 

168 def add(self, *arg_list): 

169 if not arg_list: 

170 return self 

171 for item in reversed(arg_list): 

172 if type(self) != type(item): 

173 item = Stats(item) 

174 self.files += item.files 

175 self.total_calls += item.total_calls 

176 self.prim_calls += item.prim_calls 

177 self.total_tt += item.total_tt 

178 for func in item.top_level: 

179 self.top_level.add(func) 

180 

181 if self.max_name_len < item.max_name_len: 

182 self.max_name_len = item.max_name_len 

183 

184 self.fcn_list = None 

185 

186 for func, stat in item.stats.items(): 

187 if func in self.stats: 

188 old_func_stat = self.stats[func] 

189 else: 

190 old_func_stat = (0, 0, 0, 0, {},) 

191 self.stats[func] = add_func_stats(old_func_stat, stat) 

192 return self 

193 

194 def dump_stats(self, filename): 

195 """Write the profile data to a file we know how to load back.""" 

196 with open(filename, 'wb') as f: 

197 marshal.dump(self.stats, f) 

198 

199 # list the tuple indices and directions for sorting, 

200 # along with some printable description 

201 sort_arg_dict_default = { 

202 "calls" : (((1,-1), ), "call count"), 

203 "ncalls" : (((1,-1), ), "call count"), 

204 "cumtime" : (((3,-1), ), "cumulative time"), 

205 "cumulative": (((3,-1), ), "cumulative time"), 

206 "filename" : (((4, 1), ), "file name"), 

207 "line" : (((5, 1), ), "line number"), 

208 "module" : (((4, 1), ), "file name"), 

209 "name" : (((6, 1), ), "function name"), 

210 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"), 

211 "pcalls" : (((0,-1), ), "primitive call count"), 

212 "stdname" : (((7, 1), ), "standard name"), 

213 "time" : (((2,-1), ), "internal time"), 

214 "tottime" : (((2,-1), ), "internal time"), 

215 } 

216 

217 def get_sort_arg_defs(self): 

218 """Expand all abbreviations that are unique.""" 

219 if not self.sort_arg_dict: 

220 self.sort_arg_dict = dict = {} 

221 bad_list = {} 

222 for word, tup in self.sort_arg_dict_default.items(): 

223 fragment = word 

224 while fragment: 

225 if not fragment: 

226 break 

227 if fragment in dict: 

228 bad_list[fragment] = 0 

229 break 

230 dict[fragment] = tup 

231 fragment = fragment[:-1] 

232 for word in bad_list: 

233 del dict[word] 

234 return self.sort_arg_dict 

235 

236 def sort_stats(self, *field): 

237 if not field: 

238 self.fcn_list = 0 

239 return self 

240 if len(field) == 1 and isinstance(field[0], int): 

241 # Be compatible with old profiler 

242 field = [ {-1: "stdname", 

243 0: "calls", 

244 1: "time", 

245 2: "cumulative"}[field[0]] ] 

246 elif len(field) >= 2: 

247 for arg in field[1:]: 

248 if type(arg) != type(field[0]): 

249 raise TypeError("Can't have mixed argument type") 

250 

251 sort_arg_defs = self.get_sort_arg_defs() 

252 

253 sort_tuple = () 

254 self.sort_type = "" 

255 connector = "" 

256 for word in field: 

257 if isinstance(word, SortKey): 

258 word = word.value 

259 sort_tuple = sort_tuple + sort_arg_defs[word][0] 

260 self.sort_type += connector + sort_arg_defs[word][1] 

261 connector = ", " 

262 

263 stats_list = [] 

264 for func, (cc, nc, tt, ct, callers) in self.stats.items(): 

265 stats_list.append((cc, nc, tt, ct) + func + 

266 (func_std_string(func), func)) 

267 

268 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare)) 

269 

270 self.fcn_list = fcn_list = [] 

271 for tuple in stats_list: 

272 fcn_list.append(tuple[-1]) 

273 return self 

274 

275 def reverse_order(self): 

276 if self.fcn_list: 

277 self.fcn_list.reverse() 

278 return self 

279 

280 def strip_dirs(self): 

281 oldstats = self.stats 

282 self.stats = newstats = {} 

283 max_name_len = 0 

284 for func, (cc, nc, tt, ct, callers) in oldstats.items(): 

285 newfunc = func_strip_path(func) 

286 if len(func_std_string(newfunc)) > max_name_len: 

287 max_name_len = len(func_std_string(newfunc)) 

288 newcallers = {} 

289 for func2, caller in callers.items(): 

290 newcallers[func_strip_path(func2)] = caller 

291 

292 if newfunc in newstats: 

293 newstats[newfunc] = add_func_stats( 

294 newstats[newfunc], 

295 (cc, nc, tt, ct, newcallers)) 

296 else: 

297 newstats[newfunc] = (cc, nc, tt, ct, newcallers) 

298 old_top = self.top_level 

299 self.top_level = new_top = set() 

300 for func in old_top: 

301 new_top.add(func_strip_path(func)) 

302 

303 self.max_name_len = max_name_len 

304 

305 self.fcn_list = None 

306 self.all_callees = None 

307 return self 

308 

309 def calc_callees(self): 

310 if self.all_callees: 

311 return 

312 self.all_callees = all_callees = {} 

313 for func, (cc, nc, tt, ct, callers) in self.stats.items(): 

314 if not func in all_callees: 

315 all_callees[func] = {} 

316 for func2, caller in callers.items(): 

317 if not func2 in all_callees: 

318 all_callees[func2] = {} 

319 all_callees[func2][func] = caller 

320 return 

321 

322 #****************************************************************** 

323 # The following functions support actual printing of reports 

324 #****************************************************************** 

325 

326 # Optional "amount" is either a line count, or a percentage of lines. 

327 

328 def eval_print_amount(self, sel, list, msg): 

329 new_list = list 

330 if isinstance(sel, str): 

331 try: 

332 rex = re.compile(sel) 

333 except re.error: 

334 msg += " <Invalid regular expression %r>\n" % sel 

335 return new_list, msg 

336 new_list = [] 

337 for func in list: 

338 if rex.search(func_std_string(func)): 

339 new_list.append(func) 

340 else: 

341 count = len(list) 

342 if isinstance(sel, float) and 0.0 <= sel < 1.0: 

343 count = int(count * sel + .5) 

344 new_list = list[:count] 

345 elif isinstance(sel, int) and 0 <= sel < count: 

346 count = sel 

347 new_list = list[:count] 

348 if len(list) != len(new_list): 

349 msg += " List reduced from %r to %r due to restriction <%r>\n" % ( 

350 len(list), len(new_list), sel) 

351 

352 return new_list, msg 

353 

354 def get_stats_profile(self): 

355 """This method returns an instance of StatsProfile, which contains a mapping 

356 of function names to instances of FunctionProfile. Each FunctionProfile 

357 instance holds information related to the function's profile such as how 

358 long the function took to run, how many times it was called, etc... 

359 """ 

360 func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys()) 

361 if not func_list: 

362 return StatsProfile(0, {}) 

363 

364 total_tt = float(f8(self.total_tt)) 

365 func_profiles = {} 

366 stats_profile = StatsProfile(total_tt, func_profiles) 

367 

368 for func in func_list: 

369 cc, nc, tt, ct, callers = self.stats[func] 

370 file_name, line_number, func_name = func 

371 ncalls = str(nc) if nc == cc else (str(nc) + '/' + str(cc)) 

372 tottime = float(f8(tt)) 

373 percall_tottime = -1 if nc == 0 else float(f8(tt/nc)) 

374 cumtime = float(f8(ct)) 

375 percall_cumtime = -1 if cc == 0 else float(f8(ct/cc)) 

376 func_profile = FunctionProfile( 

377 ncalls, 

378 tottime, # time spent in this function alone 

379 percall_tottime, 

380 cumtime, # time spent in the function plus all functions that this function called, 

381 percall_cumtime, 

382 file_name, 

383 line_number 

384 ) 

385 func_profiles[func_name] = func_profile 

386 

387 return stats_profile 

388 

389 def get_print_list(self, sel_list): 

390 width = self.max_name_len 

391 if self.fcn_list: 

392 stat_list = self.fcn_list[:] 

393 msg = " Ordered by: " + self.sort_type + '\n' 

394 else: 

395 stat_list = list(self.stats.keys()) 

396 msg = " Random listing order was used\n" 

397 

398 for selection in sel_list: 

399 stat_list, msg = self.eval_print_amount(selection, stat_list, msg) 

400 

401 count = len(stat_list) 

402 

403 if not stat_list: 

404 return 0, stat_list 

405 print(msg, file=self.stream) 

406 if count < len(self.stats): 

407 width = 0 

408 for func in stat_list: 

409 if len(func_std_string(func)) > width: 

410 width = len(func_std_string(func)) 

411 return width+2, stat_list 

412 

413 def print_stats(self, *amount): 

414 for filename in self.files: 

415 print(filename, file=self.stream) 

416 if self.files: 

417 print(file=self.stream) 

418 indent = ' ' * 8 

419 for func in self.top_level: 

420 print(indent, func_get_function_name(func), file=self.stream) 

421 

422 print(indent, self.total_calls, "function calls", end=' ', file=self.stream) 

423 if self.total_calls != self.prim_calls: 

424 print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream) 

425 print("in %.3f seconds" % self.total_tt, file=self.stream) 

426 print(file=self.stream) 

427 width, list = self.get_print_list(amount) 

428 if list: 

429 self.print_title() 

430 for func in list: 

431 self.print_line(func) 

432 print(file=self.stream) 

433 print(file=self.stream) 

434 return self 

435 

436 def print_callees(self, *amount): 

437 width, list = self.get_print_list(amount) 

438 if list: 

439 self.calc_callees() 

440 

441 self.print_call_heading(width, "called...") 

442 for func in list: 

443 if func in self.all_callees: 

444 self.print_call_line(width, func, self.all_callees[func]) 

445 else: 

446 self.print_call_line(width, func, {}) 

447 print(file=self.stream) 

448 print(file=self.stream) 

449 return self 

450 

451 def print_callers(self, *amount): 

452 width, list = self.get_print_list(amount) 

453 if list: 

454 self.print_call_heading(width, "was called by...") 

455 for func in list: 

456 cc, nc, tt, ct, callers = self.stats[func] 

457 self.print_call_line(width, func, callers, "<-") 

458 print(file=self.stream) 

459 print(file=self.stream) 

460 return self 

461 

462 def print_call_heading(self, name_size, column_title): 

463 print("Function ".ljust(name_size) + column_title, file=self.stream) 

464 # print sub-header only if we have new-style callers 

465 subheader = False 

466 for cc, nc, tt, ct, callers in self.stats.values(): 

467 if callers: 

468 value = next(iter(callers.values())) 

469 subheader = isinstance(value, tuple) 

470 break 

471 if subheader: 

472 print(" "*name_size + " ncalls tottime cumtime", file=self.stream) 

473 

474 def print_call_line(self, name_size, source, call_dict, arrow="->"): 

475 print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream) 

476 if not call_dict: 

477 print(file=self.stream) 

478 return 

479 clist = sorted(call_dict.keys()) 

480 indent = "" 

481 for func in clist: 

482 name = func_std_string(func) 

483 value = call_dict[func] 

484 if isinstance(value, tuple): 

485 nc, cc, tt, ct = value 

486 if nc != cc: 

487 substats = '%d/%d' % (nc, cc) 

488 else: 

489 substats = '%d' % (nc,) 

490 substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)), 

491 f8(tt), f8(ct), name) 

492 left_width = name_size + 1 

493 else: 

494 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3])) 

495 left_width = name_size + 3 

496 print(indent*left_width + substats, file=self.stream) 

497 indent = " " 

498 

499 def print_title(self): 

500 print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream) 

501 print('filename:lineno(function)', file=self.stream) 

502 

503 def print_line(self, func): # hack: should print percentages 

504 cc, nc, tt, ct, callers = self.stats[func] 

505 c = str(nc) 

506 if nc != cc: 

507 c = c + '/' + str(cc) 

508 print(c.rjust(9), end=' ', file=self.stream) 

509 print(f8(tt), end=' ', file=self.stream) 

510 if nc == 0: 

511 print(' '*8, end=' ', file=self.stream) 

512 else: 

513 print(f8(tt/nc), end=' ', file=self.stream) 

514 print(f8(ct), end=' ', file=self.stream) 

515 if cc == 0: 

516 print(' '*8, end=' ', file=self.stream) 

517 else: 

518 print(f8(ct/cc), end=' ', file=self.stream) 

519 print(func_std_string(func), file=self.stream) 

520 

521class TupleComp: 

522 """This class provides a generic function for comparing any two tuples. 

523 Each instance records a list of tuple-indices (from most significant 

524 to least significant), and sort direction (ascending or decending) for 

525 each tuple-index. The compare functions can then be used as the function 

526 argument to the system sort() function when a list of tuples need to be 

527 sorted in the instances order.""" 

528 

529 def __init__(self, comp_select_list): 

530 self.comp_select_list = comp_select_list 

531 

532 def compare (self, left, right): 

533 for index, direction in self.comp_select_list: 

534 l = left[index] 

535 r = right[index] 

536 if l < r: 

537 return -direction 

538 if l > r: 

539 return direction 

540 return 0 

541 

542 

543#************************************************************************** 

544# func_name is a triple (file:string, line:int, name:string) 

545 

546def func_strip_path(func_name): 

547 filename, line, name = func_name 

548 return os.path.basename(filename), line, name 

549 

550def func_get_function_name(func): 

551 return func[2] 

552 

553def func_std_string(func_name): # match what old profile produced 

554 if func_name[:2] == ('~', 0): 

555 # special case for built-in functions 

556 name = func_name[2] 

557 if name.startswith('<') and name.endswith('>'): 

558 return '{%s}' % name[1:-1] 

559 else: 

560 return name 

561 else: 

562 return "%s:%d(%s)" % func_name 

563 

564#************************************************************************** 

565# The following functions combine statistics for pairs functions. 

566# The bulk of the processing involves correctly handling "call" lists, 

567# such as callers and callees. 

568#************************************************************************** 

569 

570def add_func_stats(target, source): 

571 """Add together all the stats for two profile entries.""" 

572 cc, nc, tt, ct, callers = source 

573 t_cc, t_nc, t_tt, t_ct, t_callers = target 

574 return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct, 

575 add_callers(t_callers, callers)) 

576 

577def add_callers(target, source): 

578 """Combine two caller lists in a single list.""" 

579 new_callers = {} 

580 for func, caller in target.items(): 

581 new_callers[func] = caller 

582 for func, caller in source.items(): 

583 if func in new_callers: 

584 if isinstance(caller, tuple): 

585 # format used by cProfile 

586 new_callers[func] = tuple(i + j for i, j in zip(caller, new_callers[func])) 

587 else: 

588 # format used by profile 

589 new_callers[func] += caller 

590 else: 

591 new_callers[func] = caller 

592 return new_callers 

593 

594def count_calls(callers): 

595 """Sum the caller statistics to get total number of calls received.""" 

596 nc = 0 

597 for calls in callers.values(): 

598 nc += calls 

599 return nc 

600 

601#************************************************************************** 

602# The following functions support printing of reports 

603#************************************************************************** 

604 

605def f8(x): 

606 return "%8.3f" % x 

607 

608#************************************************************************** 

609# Statistics browser added by ESR, April 2001 

610#************************************************************************** 

611 

612if __name__ == '__main__': 

613 import cmd 

614 try: 

615 import readline 

616 except ImportError: 

617 pass 

618 

619 class ProfileBrowser(cmd.Cmd): 

620 def __init__(self, profile=None): 

621 cmd.Cmd.__init__(self) 

622 self.prompt = "% " 

623 self.stats = None 

624 self.stream = sys.stdout 

625 if profile is not None: 

626 self.do_read(profile) 

627 

628 def generic(self, fn, line): 

629 args = line.split() 

630 processed = [] 

631 for term in args: 

632 try: 

633 processed.append(int(term)) 

634 continue 

635 except ValueError: 

636 pass 

637 try: 

638 frac = float(term) 

639 if frac > 1 or frac < 0: 

640 print("Fraction argument must be in [0, 1]", file=self.stream) 

641 continue 

642 processed.append(frac) 

643 continue 

644 except ValueError: 

645 pass 

646 processed.append(term) 

647 if self.stats: 

648 getattr(self.stats, fn)(*processed) 

649 else: 

650 print("No statistics object is loaded.", file=self.stream) 

651 return 0 

652 def generic_help(self): 

653 print("Arguments may be:", file=self.stream) 

654 print("* An integer maximum number of entries to print.", file=self.stream) 

655 print("* A decimal fractional number between 0 and 1, controlling", file=self.stream) 

656 print(" what fraction of selected entries to print.", file=self.stream) 

657 print("* A regular expression; only entries with function names", file=self.stream) 

658 print(" that match it are printed.", file=self.stream) 

659 

660 def do_add(self, line): 

661 if self.stats: 

662 try: 

663 self.stats.add(line) 

664 except OSError as e: 

665 print("Failed to load statistics for %s: %s" % (line, e), file=self.stream) 

666 else: 

667 print("No statistics object is loaded.", file=self.stream) 

668 return 0 

669 def help_add(self): 

670 print("Add profile info from given file to current statistics object.", file=self.stream) 

671 

672 def do_callees(self, line): 

673 return self.generic('print_callees', line) 

674 def help_callees(self): 

675 print("Print callees statistics from the current stat object.", file=self.stream) 

676 self.generic_help() 

677 

678 def do_callers(self, line): 

679 return self.generic('print_callers', line) 

680 def help_callers(self): 

681 print("Print callers statistics from the current stat object.", file=self.stream) 

682 self.generic_help() 

683 

684 def do_EOF(self, line): 

685 print("", file=self.stream) 

686 return 1 

687 def help_EOF(self): 

688 print("Leave the profile browser.", file=self.stream) 

689 

690 def do_quit(self, line): 

691 return 1 

692 def help_quit(self): 

693 print("Leave the profile browser.", file=self.stream) 

694 

695 def do_read(self, line): 

696 if line: 

697 try: 

698 self.stats = Stats(line) 

699 except OSError as err: 

700 print(err.args[1], file=self.stream) 

701 return 

702 except Exception as err: 

703 print(err.__class__.__name__ + ':', err, file=self.stream) 

704 return 

705 self.prompt = line + "% " 

706 elif len(self.prompt) > 2: 

707 line = self.prompt[:-2] 

708 self.do_read(line) 

709 else: 

710 print("No statistics object is current -- cannot reload.", file=self.stream) 

711 return 0 

712 def help_read(self): 

713 print("Read in profile data from a specified file.", file=self.stream) 

714 print("Without argument, reload the current file.", file=self.stream) 

715 

716 def do_reverse(self, line): 

717 if self.stats: 

718 self.stats.reverse_order() 

719 else: 

720 print("No statistics object is loaded.", file=self.stream) 

721 return 0 

722 def help_reverse(self): 

723 print("Reverse the sort order of the profiling report.", file=self.stream) 

724 

725 def do_sort(self, line): 

726 if not self.stats: 

727 print("No statistics object is loaded.", file=self.stream) 

728 return 

729 abbrevs = self.stats.get_sort_arg_defs() 

730 if line and all((x in abbrevs) for x in line.split()): 

731 self.stats.sort_stats(*line.split()) 

732 else: 

733 print("Valid sort keys (unique prefixes are accepted):", file=self.stream) 

734 for (key, value) in Stats.sort_arg_dict_default.items(): 

735 print("%s -- %s" % (key, value[1]), file=self.stream) 

736 return 0 

737 def help_sort(self): 

738 print("Sort profile data according to specified keys.", file=self.stream) 

739 print("(Typing `sort' without arguments lists valid keys.)", file=self.stream) 

740 def complete_sort(self, text, *args): 

741 return [a for a in Stats.sort_arg_dict_default if a.startswith(text)] 

742 

743 def do_stats(self, line): 

744 return self.generic('print_stats', line) 

745 def help_stats(self): 

746 print("Print statistics from the current stat object.", file=self.stream) 

747 self.generic_help() 

748 

749 def do_strip(self, line): 

750 if self.stats: 

751 self.stats.strip_dirs() 

752 else: 

753 print("No statistics object is loaded.", file=self.stream) 

754 def help_strip(self): 

755 print("Strip leading path information from filenames in the report.", file=self.stream) 

756 

757 def help_help(self): 

758 print("Show help for a given command.", file=self.stream) 

759 

760 def postcmd(self, stop, line): 

761 if stop: 

762 return stop 

763 return None 

764 

765 if len(sys.argv) > 1: 

766 initprofile = sys.argv[1] 

767 else: 

768 initprofile = None 

769 try: 

770 browser = ProfileBrowser(initprofile) 

771 for profile in sys.argv[2:]: 

772 browser.do_add(profile) 

773 print("Welcome to the profile statistics browser.", file=browser.stream) 

774 browser.cmdloop() 

775 print("Goodbye.", file=browser.stream) 

776 except KeyboardInterrupt: 

777 pass 

778 

779# That's all, folks.