PageRenderTime 62ms CodeModel.GetById 5ms RepoModel.GetById 0ms app.codeStats 1ms

/pypy/module/_lsprof/interp_lsprof.py

https://bitbucket.org/pypy/pypy/
Python | 448 lines | 374 code | 56 blank | 18 comment | 77 complexity | 94cc0cb4865a9726c74bcb5bd47584e1 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. import py
  2. from pypy.interpreter.baseobjspace import W_Root
  3. from pypy.interpreter.error import OperationError, oefmt
  4. from pypy.interpreter.function import Method, Function
  5. from pypy.interpreter.gateway import interp2app, unwrap_spec
  6. from pypy.interpreter.typedef import (TypeDef, GetSetProperty,
  7. interp_attrproperty)
  8. from rpython.rlib import jit
  9. from rpython.rlib.objectmodel import we_are_translated
  10. from rpython.rlib.rtimer import read_timestamp, _is_64_bit
  11. from rpython.rtyper.lltypesystem import rffi, lltype
  12. from rpython.translator.tool.cbuild import ExternalCompilationInfo
  13. from rpython.translator import cdir
  14. from rpython.rlib.rarithmetic import r_longlong
  15. import time, sys
  16. # cpu affinity settings
  17. srcdir = py.path.local(cdir).join('src')
  18. eci = ExternalCompilationInfo(
  19. include_dirs = [cdir],
  20. separate_module_files = [srcdir.join('profiling.c')])
  21. c_setup_profiling = rffi.llexternal('pypy_setup_profiling',
  22. [], lltype.Void,
  23. compilation_info = eci)
  24. c_teardown_profiling = rffi.llexternal('pypy_teardown_profiling',
  25. [], lltype.Void,
  26. compilation_info = eci)
  27. if _is_64_bit:
  28. timer_size_int = int
  29. else:
  30. timer_size_int = r_longlong
  31. class W_StatsEntry(W_Root):
  32. def __init__(self, space, frame, callcount, reccallcount, tt, it,
  33. w_sublist):
  34. self.frame = frame
  35. self.callcount = callcount
  36. self.reccallcount = reccallcount
  37. self.it = it
  38. self.tt = tt
  39. self.w_calls = w_sublist
  40. def get_calls(self, space):
  41. return self.w_calls
  42. def repr(self, space):
  43. frame_repr = space.str_w(space.repr(self.frame))
  44. if not self.w_calls:
  45. calls_repr = "None"
  46. else:
  47. calls_repr = space.str_w(space.repr(self.w_calls))
  48. return space.wrap('("%s", %d, %d, %f, %f, %s)' % (
  49. frame_repr, self.callcount, self.reccallcount,
  50. self.tt, self.it, calls_repr))
  51. def get_code(self, space):
  52. return returns_code(space, self.frame)
  53. W_StatsEntry.typedef = TypeDef(
  54. 'StatsEntry',
  55. code = GetSetProperty(W_StatsEntry.get_code),
  56. callcount = interp_attrproperty('callcount', W_StatsEntry),
  57. reccallcount = interp_attrproperty('reccallcount', W_StatsEntry),
  58. inlinetime = interp_attrproperty('it', W_StatsEntry),
  59. totaltime = interp_attrproperty('tt', W_StatsEntry),
  60. calls = GetSetProperty(W_StatsEntry.get_calls),
  61. __repr__ = interp2app(W_StatsEntry.repr),
  62. )
  63. class W_StatsSubEntry(W_Root):
  64. def __init__(self, space, frame, callcount, reccallcount, tt, it):
  65. self.frame = frame
  66. self.callcount = callcount
  67. self.reccallcount = reccallcount
  68. self.it = it
  69. self.tt = tt
  70. def repr(self, space):
  71. frame_repr = space.str_w(space.repr(self.frame))
  72. return space.wrap('("%s", %d, %d, %f, %f)' % (
  73. frame_repr, self.callcount, self.reccallcount, self.tt, self.it))
  74. def get_code(self, space):
  75. return returns_code(space, self.frame)
  76. W_StatsSubEntry.typedef = TypeDef(
  77. 'SubStatsEntry',
  78. code = GetSetProperty(W_StatsSubEntry.get_code),
  79. callcount = interp_attrproperty('callcount', W_StatsSubEntry),
  80. reccallcount = interp_attrproperty('reccallcount', W_StatsSubEntry),
  81. inlinetime = interp_attrproperty('it', W_StatsSubEntry),
  82. totaltime = interp_attrproperty('tt', W_StatsSubEntry),
  83. __repr__ = interp2app(W_StatsSubEntry.repr),
  84. )
  85. def stats(space, values, factor):
  86. l_w = []
  87. for v in values:
  88. if v.callcount != 0:
  89. l_w.append(v.stats(space, None, factor))
  90. return space.newlist(l_w)
  91. class ProfilerSubEntry(object):
  92. def __init__(self, frame):
  93. self.frame = frame
  94. self.ll_tt = r_longlong(0)
  95. self.ll_it = r_longlong(0)
  96. self.callcount = 0
  97. self.recursivecallcount = 0
  98. self.recursionLevel = 0
  99. def stats(self, space, parent, factor):
  100. w_sse = W_StatsSubEntry(space, self.frame,
  101. self.callcount, self.recursivecallcount,
  102. factor * float(self.ll_tt),
  103. factor * float(self.ll_it))
  104. return space.wrap(w_sse)
  105. def _stop(self, tt, it):
  106. if not we_are_translated():
  107. assert type(tt) is timer_size_int
  108. assert type(it) is timer_size_int
  109. self.recursionLevel -= 1
  110. if self.recursionLevel == 0:
  111. self.ll_tt += tt
  112. else:
  113. self.recursivecallcount += 1
  114. self.ll_it += it
  115. self.callcount += 1
  116. class ProfilerEntry(ProfilerSubEntry):
  117. def __init__(self, frame):
  118. ProfilerSubEntry.__init__(self, frame)
  119. self.calls = {}
  120. def stats(self, space, dummy, factor):
  121. if self.calls:
  122. w_sublist = space.newlist([sub_entry.stats(space, self, factor)
  123. for sub_entry in self.calls.values()])
  124. else:
  125. w_sublist = space.w_None
  126. w_se = W_StatsEntry(space, self.frame, self.callcount,
  127. self.recursivecallcount,
  128. factor * float(self.ll_tt),
  129. factor * float(self.ll_it), w_sublist)
  130. return space.wrap(w_se)
  131. @jit.elidable
  132. def _get_or_make_subentry(self, entry, make=True):
  133. try:
  134. return self.calls[entry]
  135. except KeyError:
  136. if make:
  137. subentry = ProfilerSubEntry(entry.frame)
  138. self.calls[entry] = subentry
  139. return subentry
  140. raise
  141. class ProfilerContext(object):
  142. def __init__(self, profobj, entry):
  143. self.entry = entry
  144. self.ll_subt = timer_size_int(0)
  145. self.previous = profobj.current_context
  146. entry.recursionLevel += 1
  147. if profobj.subcalls and self.previous:
  148. caller = jit.promote(self.previous.entry)
  149. subentry = caller._get_or_make_subentry(entry)
  150. subentry.recursionLevel += 1
  151. self.ll_t0 = profobj.ll_timer()
  152. def _stop(self, profobj, entry):
  153. tt = profobj.ll_timer() - self.ll_t0
  154. it = tt - self.ll_subt
  155. if self.previous:
  156. self.previous.ll_subt += tt
  157. entry._stop(tt, it)
  158. if profobj.subcalls and self.previous:
  159. caller = jit.promote(self.previous.entry)
  160. try:
  161. subentry = caller._get_or_make_subentry(entry, False)
  162. except KeyError:
  163. pass
  164. else:
  165. subentry._stop(tt, it)
  166. def create_spec_for_method(space, w_function, w_type):
  167. class_name = None
  168. if isinstance(w_function, Function):
  169. name = w_function.name
  170. # try to get the real class that defines the method,
  171. # which is a superclass of the class of the instance
  172. from pypy.objspace.std.typeobject import W_TypeObject # xxx
  173. if isinstance(w_type, W_TypeObject):
  174. w_realclass, _ = space.lookup_in_type_where(w_type, name)
  175. if isinstance(w_realclass, W_TypeObject):
  176. class_name = w_realclass.name
  177. else:
  178. name = '?'
  179. if class_name is None:
  180. class_name = w_type.getname(space) # if the rest doesn't work
  181. return "<method '%s' of '%s' objects>" % (name, class_name)
  182. def create_spec_for_function(space, w_func):
  183. assert isinstance(w_func, Function)
  184. if w_func.w_module is not None:
  185. module = space.str_w(w_func.w_module)
  186. if module != '__builtin__':
  187. return '<%s.%s>' % (module, w_func.name)
  188. return '<%s>' % w_func.name
  189. def create_spec_for_object(space, w_type):
  190. class_name = w_type.getname(space)
  191. return "<'%s' object>" % (class_name,)
  192. class W_DelayedBuiltinStr(W_Root):
  193. # This class should not be seen at app-level, but is useful to
  194. # contain a (w_func, w_type) pair returned by prepare_spec().
  195. # Turning this pair into a string cannot be done eagerly in
  196. # an @elidable function because of space.str_w(), but it can
  197. # be done lazily when we really want it.
  198. _immutable_fields_ = ['w_func', 'w_type']
  199. def __init__(self, w_func, w_type):
  200. self.w_func = w_func
  201. self.w_type = w_type
  202. self.w_string = None
  203. def wrap_string(self, space):
  204. if self.w_string is None:
  205. if self.w_type is None:
  206. s = create_spec_for_function(space, self.w_func)
  207. elif self.w_func is None:
  208. s = create_spec_for_object(space, self.w_type)
  209. else:
  210. s = create_spec_for_method(space, self.w_func, self.w_type)
  211. self.w_string = space.wrap(s)
  212. return self.w_string
  213. W_DelayedBuiltinStr.typedef = TypeDef(
  214. 'DelayedBuiltinStr',
  215. __str__ = interp2app(W_DelayedBuiltinStr.wrap_string),
  216. )
  217. def returns_code(space, w_frame):
  218. if isinstance(w_frame, W_DelayedBuiltinStr):
  219. return w_frame.wrap_string(space)
  220. return w_frame # actually a PyCode object
  221. def prepare_spec(space, w_arg):
  222. if isinstance(w_arg, Method):
  223. return (w_arg.w_function, w_arg.w_class)
  224. elif isinstance(w_arg, Function):
  225. return (w_arg, None)
  226. else:
  227. return (None, space.type(w_arg))
  228. prepare_spec._always_inline_ = True
  229. def lsprof_call(space, w_self, frame, event, w_arg):
  230. assert isinstance(w_self, W_Profiler)
  231. if event == 'call':
  232. code = frame.getcode()
  233. w_self._enter_call(code)
  234. elif event == 'return':
  235. code = frame.getcode()
  236. w_self._enter_return(code)
  237. elif event == 'c_call':
  238. if w_self.builtins:
  239. w_self._enter_builtin_call(w_arg)
  240. elif event == 'c_return' or event == 'c_exception':
  241. if w_self.builtins:
  242. w_self._enter_builtin_return(w_arg)
  243. else:
  244. # ignore or raise an exception???
  245. pass
  246. class W_Profiler(W_Root):
  247. def __init__(self, space, w_callable, time_unit, subcalls, builtins):
  248. self.subcalls = subcalls
  249. self.builtins = builtins
  250. self.current_context = None
  251. self.w_callable = w_callable
  252. self.time_unit = time_unit
  253. self.data = {}
  254. self.builtin_data = {}
  255. self.space = space
  256. self.is_enabled = False
  257. self.total_timestamp = r_longlong(0)
  258. self.total_real_time = 0.0
  259. def ll_timer(self):
  260. if self.w_callable:
  261. space = self.space
  262. try:
  263. if _is_64_bit:
  264. return space.int_w(space.call_function(self.w_callable))
  265. else:
  266. return space.r_longlong_w(space.call_function(self.w_callable))
  267. except OperationError as e:
  268. e.write_unraisable(space, "timer function ",
  269. self.w_callable)
  270. return timer_size_int(0)
  271. return read_timestamp()
  272. def enable(self, space, w_subcalls=None,
  273. w_builtins=None):
  274. if self.is_enabled:
  275. return # ignored
  276. if w_subcalls is not None:
  277. self.subcalls = space.bool_w(w_subcalls)
  278. if w_builtins is not None:
  279. self.builtins = space.bool_w(w_builtins)
  280. # We want total_real_time and total_timestamp to end up containing
  281. # (endtime - starttime). Now we are at the start, so we first
  282. # have to subtract the current time.
  283. self.is_enabled = True
  284. self.total_real_time -= time.time()
  285. self.total_timestamp -= read_timestamp()
  286. # set profiler hook
  287. c_setup_profiling()
  288. space.getexecutioncontext().setllprofile(lsprof_call, space.wrap(self))
  289. @jit.elidable
  290. def _get_or_make_entry(self, f_code, make=True):
  291. try:
  292. return self.data[f_code]
  293. except KeyError:
  294. if make:
  295. entry = ProfilerEntry(f_code)
  296. self.data[f_code] = entry
  297. return entry
  298. raise
  299. @jit.elidable_promote()
  300. def _get_or_make_builtin_entry(self, w_func, w_type, make):
  301. key = (w_func, w_type)
  302. try:
  303. return self.builtin_data[key]
  304. except KeyError:
  305. if make:
  306. entry = ProfilerEntry(W_DelayedBuiltinStr(w_func, w_type))
  307. self.builtin_data[key] = entry
  308. return entry
  309. raise
  310. def _enter_call(self, f_code):
  311. # we have a superb gc, no point in freelist :)
  312. self = jit.promote(self)
  313. entry = self._get_or_make_entry(f_code)
  314. self.current_context = ProfilerContext(self, entry)
  315. def _enter_return(self, f_code):
  316. context = self.current_context
  317. if context is None:
  318. return
  319. self = jit.promote(self)
  320. try:
  321. entry = self._get_or_make_entry(f_code, False)
  322. except KeyError:
  323. pass
  324. else:
  325. context._stop(self, entry)
  326. self.current_context = context.previous
  327. def _enter_builtin_call(self, w_arg):
  328. w_func, w_type = prepare_spec(self.space, w_arg)
  329. entry = self._get_or_make_builtin_entry(w_func, w_type, True)
  330. self.current_context = ProfilerContext(self, entry)
  331. def _enter_builtin_return(self, w_arg):
  332. context = self.current_context
  333. if context is None:
  334. return
  335. w_func, w_type = prepare_spec(self.space, w_arg)
  336. try:
  337. entry = self._get_or_make_builtin_entry(w_func, w_type, False)
  338. except KeyError:
  339. pass
  340. else:
  341. context._stop(self, entry)
  342. self.current_context = context.previous
  343. def _flush_unmatched(self):
  344. context = self.current_context
  345. while context:
  346. entry = context.entry
  347. if entry:
  348. context._stop(self, entry)
  349. context = context.previous
  350. self.current_context = None
  351. def disable(self, space):
  352. if not self.is_enabled:
  353. return # ignored
  354. # We want total_real_time and total_timestamp to end up containing
  355. # (endtime - starttime), or the sum of such intervals if
  356. # enable() and disable() are called several times.
  357. self.is_enabled = False
  358. self.total_timestamp += read_timestamp()
  359. self.total_real_time += time.time()
  360. # unset profiler hook
  361. space.getexecutioncontext().setllprofile(None, None)
  362. c_teardown_profiling()
  363. self._flush_unmatched()
  364. def getstats(self, space):
  365. if self.w_callable is None:
  366. if self.is_enabled:
  367. raise oefmt(space.w_RuntimeError,
  368. "Profiler instance must be disabled before "
  369. "getting the stats")
  370. if self.total_timestamp:
  371. factor = self.total_real_time / float(self.total_timestamp)
  372. else:
  373. factor = 1.0 # probably not used
  374. elif self.time_unit > 0.0:
  375. factor = self.time_unit
  376. else:
  377. factor = 1.0 / sys.maxint
  378. return stats(space, self.data.values() + self.builtin_data.values(),
  379. factor)
  380. @unwrap_spec(time_unit=float, subcalls=bool, builtins=bool)
  381. def descr_new_profile(space, w_type, w_callable=None, time_unit=0.0,
  382. subcalls=True, builtins=True):
  383. p = space.allocate_instance(W_Profiler, w_type)
  384. p.__init__(space, w_callable, time_unit, subcalls, builtins)
  385. return space.wrap(p)
  386. W_Profiler.typedef = TypeDef(
  387. '_lsprof.Profiler',
  388. __new__ = interp2app(descr_new_profile),
  389. enable = interp2app(W_Profiler.enable),
  390. disable = interp2app(W_Profiler.disable),
  391. getstats = interp2app(W_Profiler.getstats),
  392. )