Coverage for /builds/ase/ase/ase/utils/timing.py: 85.71%

112 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-08-02 00:12 +0000

1# Copyright (C) 2003 CAMP 

2# Please see the accompanying LICENSE file for further information. 

3 

4 

5import functools 

6import inspect 

7import sys 

8import time 

9 

10 

11def function_timer(func, *args, **kwargs): 

12 out = kwargs.pop('timeout', sys.stdout) 

13 t1 = time.time() 

14 r = func(*args, **kwargs) 

15 t2 = time.time() 

16 print(t2 - t1, file=out) 

17 return r 

18 

19 

20class Timer: 

21 """Timer object. 

22 

23 Use like this:: 

24 

25 timer = Timer() 

26 timer.start('description') 

27 # do something 

28 timer.stop() 

29 

30 or:: 

31 

32 with timer('description'): 

33 # do something 

34 

35 To get a summary call:: 

36 

37 timer.write() 

38 

39 """ 

40 

41 def __init__(self, print_levels=1000): 

42 self.timers = {} 

43 self.t0 = time.time() 

44 self.running = [] 

45 self.print_levels = print_levels 

46 

47 def print_info(self, calc): 

48 """Override to get to write info during calculator's initialize().""" 

49 

50 def start(self, name): 

51 names = tuple(self.running + [name]) 

52 self.timers[names] = self.timers.get(names, 0.0) - time.time() 

53 self.running.append(name) 

54 

55 def stop(self, name=None): 

56 if name is None: 

57 name = self.running[-1] 

58 names = tuple(self.running) 

59 running = self.running.pop() 

60 if name != running: 

61 raise RuntimeError( 

62 'Must stop timers by stack order. ' 

63 'Requested stopping of %s but topmost is %s' % (name, running) 

64 ) 

65 self.timers[names] += time.time() 

66 return names 

67 

68 def __call__(self, name): 

69 """Context manager for timing a block of code. 

70 

71 Example (t is a timer object):: 

72 

73 with t('Add two numbers'): 

74 x = 2 + 2 

75 

76 # same as this: 

77 t.start('Add two numbers') 

78 x = 2 + 2 

79 t.stop() 

80 """ 

81 self.start(name) 

82 return self 

83 

84 def __enter__(self): 

85 pass 

86 

87 def __exit__(self, *args): 

88 self.stop() 

89 

90 def get_time(self, *names): 

91 return self.timers[names] 

92 

93 def write(self, out=sys.stdout): 

94 were_running = list(self.running) 

95 while self.running: 

96 self.stop() 

97 if len(self.timers) == 0: 

98 return 

99 

100 t0 = time.time() 

101 tot = t0 - self.t0 

102 

103 n = max(len(names[-1]) + len(names) for names in self.timers) + 1 

104 line = '-' * (n + 26) + '\n' 

105 out.write('%-*s incl. excl.\n' % (n, 'Timing:')) 

106 out.write(line) 

107 tother = tot 

108 

109 inclusive = self.timers.copy() 

110 exclusive = self.timers.copy() 

111 keys = sorted(exclusive.keys()) 

112 for names in keys: 

113 t = exclusive[names] 

114 if len(names) > 1: 

115 if len(names) < self.print_levels + 1: 

116 exclusive[names[:-1]] -= t 

117 else: 

118 tother -= t 

119 exclusive[('Other',)] = tother 

120 inclusive[('Other',)] = tother 

121 keys.append(('Other',)) 

122 for names in keys: 

123 t = exclusive[names] 

124 tinclusive = inclusive[names] 

125 r = t / tot 

126 p = 100 * r 

127 i = int(40 * r + 0.5) 

128 if i == 0: 

129 bar = '|' 

130 else: 

131 bar = '|%s|' % ('-' * (i - 1)) 

132 level = len(names) 

133 if level > self.print_levels: 

134 continue 

135 name = (level - 1) * ' ' + names[-1] + ':' 

136 out.write( 

137 '%-*s%9.3f %9.3f %5.1f%% %s\n' 

138 % (n, name, tinclusive, t, p, bar) 

139 ) 

140 out.write(line) 

141 out.write('%-*s%9.3f %5.1f%%\n\n' % (n + 10, 'Total:', tot, 100.0)) 

142 

143 for name in were_running: 

144 self.start(name) 

145 

146 def add(self, timer): 

147 for name, t in timer.timers.items(): 

148 self.timers[name] = self.timers.get(name, 0.0) + t 

149 

150 

151class timer: 

152 """Decorator for timing a method call. 

153 

154 Example:: 

155 

156 from ase.utils.timing import timer, Timer 

157 

158 class A: 

159 def __init__(self): 

160 self.timer = Timer() 

161 

162 @timer('Add two numbers') 

163 def add(self, x, y): 

164 return x + y 

165 

166 """ 

167 

168 def __init__(self, name): 

169 self.name = name 

170 

171 def __call__(self, method): 

172 if inspect.isgeneratorfunction(method): 

173 

174 @functools.wraps(method) 

175 def new_method(slf, *args, **kwargs): 

176 gen = method(slf, *args, **kwargs) 

177 while True: 

178 slf.timer.start(self.name) 

179 try: 

180 x = next(gen) 

181 except StopIteration: 

182 break 

183 finally: 

184 slf.timer.stop() 

185 yield x 

186 else: 

187 

188 @functools.wraps(method) 

189 def new_method(slf, *args, **kwargs): 

190 slf.timer.start(self.name) 

191 x = method(slf, *args, **kwargs) 

192 try: 

193 slf.timer.stop() 

194 except IndexError: 

195 pass 

196 return x 

197 

198 return new_method