gooderp18绿色标准版
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

215 行
8.0KB

  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. import reprlib
  4. shortener = reprlib.Repr()
  5. shortener.maxstring = 150
  6. shorten = shortener.repr
  7. class Speedscope:
  8. def __init__(self, name='Speedscope', init_stack_trace=None):
  9. self.init_stack_trace = init_stack_trace or []
  10. self.init_stack_trace_level = len(self.init_stack_trace)
  11. self.caller_frame = None
  12. self.convert_stack(self.init_stack_trace)
  13. self.init_caller_frame = None
  14. if self.init_stack_trace:
  15. self.init_caller_frame = self.init_stack_trace[-1]
  16. self.profiles_raw = {}
  17. self.name = name
  18. self.frames_indexes = {}
  19. self.frame_count = 0
  20. self.profiles = []
  21. def add(self, key, profile):
  22. for entry in profile:
  23. self.caller_frame = self.init_caller_frame
  24. self.convert_stack(entry['stack'] or [])
  25. if 'query' in entry:
  26. query = entry['query']
  27. full_query = entry['full_query']
  28. entry['stack'].append((f'sql({shorten(query)})', full_query, None))
  29. self.profiles_raw[key] = profile
  30. def convert_stack(self, stack):
  31. for index, frame in enumerate(stack):
  32. method = frame[2]
  33. line = ''
  34. number = ''
  35. if self.caller_frame and len(self.caller_frame) == 4:
  36. line = f"called at {self.caller_frame[0]} ({self.caller_frame[3].strip()})"
  37. number = self.caller_frame[1]
  38. stack[index] = (method, line, number,)
  39. self.caller_frame = frame
  40. def add_output(self, names, complete=True, display_name=None, use_context=True, **params):
  41. entries = []
  42. display_name = display_name or ','.join(names)
  43. for name in names:
  44. entries += self.profiles_raw[name]
  45. entries.sort(key=lambda e: e['start'])
  46. result = self.process(entries, use_context=use_context, **params)
  47. if not result:
  48. return self
  49. start = result[0]['at']
  50. end = result[-1]['at']
  51. if complete:
  52. start_stack = []
  53. end_stack = []
  54. init_stack_trace_ids = self.stack_to_ids(self.init_stack_trace, use_context and entries[0].get('exec_context'))
  55. for frame_id in init_stack_trace_ids:
  56. start_stack.append({
  57. "type": "O",
  58. "frame": frame_id,
  59. "at": start
  60. })
  61. for frame_id in reversed(init_stack_trace_ids):
  62. end_stack.append({
  63. "type": "C",
  64. "frame": frame_id,
  65. "at": end
  66. })
  67. result = start_stack + result + end_stack
  68. self.profiles.append({
  69. "name": display_name,
  70. "type": "evented",
  71. "unit": "seconds",
  72. "startValue": 0,
  73. "endValue": end - start,
  74. "events": result
  75. })
  76. return self
  77. def add_default(self):
  78. if len(self.profiles_raw) > 1:
  79. self.add_output(self.profiles_raw, display_name='Combined')
  80. self.add_output(self.profiles_raw, display_name='Combined no context', use_context=False)
  81. for key, profile in self.profiles_raw.items():
  82. sql = profile and profile[0].get('query')
  83. if sql:
  84. self.add_output([key], hide_gaps=True, display_name=f'{key} (no gap)')
  85. self.add_output([key], continuous=False, complete=False, display_name=f'{key} (density)')
  86. else:
  87. self.add_output([key], display_name=key)
  88. return self
  89. def make(self):
  90. if not self.profiles:
  91. self.add_default()
  92. return {
  93. "name": self.name,
  94. "activeProfileIndex": 0,
  95. "$schema": "https://www.speedscope.app/file-format-schema.json",
  96. "shared": {
  97. "frames": [{
  98. "name": frame[0],
  99. "file": frame[1],
  100. "line": frame[2]
  101. } for frame in self.frames_indexes]
  102. },
  103. "profiles": self.profiles,
  104. }
  105. def get_frame_id(self, frame):
  106. if frame not in self.frames_indexes:
  107. self.frames_indexes[frame] = self.frame_count
  108. self.frame_count += 1
  109. return self.frames_indexes[frame]
  110. def stack_to_ids(self, stack, context, stack_offset=0):
  111. """
  112. :param stack: A list of hashable frame
  113. :param context: an iterable of (level, value) ordered by level
  114. :param stack_offset: offset level for stack
  115. Assemble stack and context and return a list of ids representing
  116. this stack, adding each corresponding context at the corresponding
  117. level.
  118. """
  119. stack_ids = []
  120. context_iterator = iter(context or ())
  121. context_level, context_value = next(context_iterator, (None, None))
  122. # consume iterator until we are over stack_offset
  123. while context_level is not None and context_level < stack_offset:
  124. context_level, context_value = next(context_iterator, (None, None))
  125. for level, frame in enumerate(stack, start=stack_offset + 1):
  126. while context_level == level:
  127. context_frame = (", ".join(f"{k}={v}" for k, v in context_value.items()), '', '')
  128. stack_ids.append(self.get_frame_id(context_frame))
  129. context_level, context_value = next(context_iterator, (None, None))
  130. stack_ids.append(self.get_frame_id(frame))
  131. return stack_ids
  132. def process(self, entries, continuous=True, hide_gaps=False, use_context=True, constant_time=False):
  133. # constant_time parameters is mainly useful to hide temporality when focussing on sql determinism
  134. entry_end = previous_end = None
  135. if not entries:
  136. return []
  137. events = []
  138. current_stack_ids = []
  139. frames_start = entries[0]['start']
  140. # add last closing entry if missing
  141. last_entry = entries[-1]
  142. if last_entry['stack']:
  143. entries.append({'stack': [], 'start': last_entry['start'] + last_entry.get('time', 0)})
  144. for index, entry in enumerate(entries):
  145. if constant_time:
  146. entry_start = close_time = index
  147. else:
  148. previous_end = entry_end
  149. if hide_gaps and previous_end:
  150. entry_start = previous_end
  151. else:
  152. entry_start = entry['start'] - frames_start
  153. if previous_end and previous_end > entry_start:
  154. # skip entry if entry starts after another entry end
  155. continue
  156. if previous_end:
  157. close_time = min(entry_start, previous_end)
  158. else:
  159. close_time = entry_start
  160. entry_time = entry.get('time')
  161. entry_end = None if entry_time is None else entry_start + entry_time
  162. entry_stack_ids = self.stack_to_ids(
  163. entry['stack'] or [],
  164. use_context and entry.get('exec_context'),
  165. self.init_stack_trace_level
  166. )
  167. level = 0
  168. if continuous:
  169. level = -1
  170. for level, at_level in enumerate(zip(current_stack_ids, entry_stack_ids)):
  171. current, new = at_level
  172. if current != new:
  173. break
  174. else:
  175. level += 1
  176. for frame in reversed(current_stack_ids[level:]):
  177. events.append({
  178. "type": "C",
  179. "frame": frame,
  180. "at": close_time
  181. })
  182. for frame in entry_stack_ids[level:]:
  183. events.append({
  184. "type": "O",
  185. "frame": frame,
  186. "at": entry_start
  187. })
  188. current_stack_ids = entry_stack_ids
  189. return events
上海开阖软件有限公司 沪ICP备12045867号-1