tasks.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import tempfile
  2. import contextlib
  3. import logging
  4. from celery import shared_task
  5. from projects.models import Project
  6. from files.models import FileBlob, File
  7. from hashlib import sha1
  8. from symbolic import (
  9. Archive,
  10. )
  11. from difs.models import DebugInformationFile
  12. from events.models import Event
  13. from difs.stacktrace_processor import StacktraceProcessor
  14. from django.conf import settings
  15. def getLogger():
  16. return logging.getLogger("glitchtip.difs")
  17. class ChecksumMismatched(Exception):
  18. pass
  19. class UnsupportedFile(Exception):
  20. pass
  21. DIF_STATE_CREATED = "created"
  22. DIF_STATE_OK = "ok"
  23. DIF_STATE_NOT_FOUND = "not_found"
  24. @shared_task
  25. def difs_assemble(project_slug, name, checksum, chunks, debug_id):
  26. try:
  27. project = Project.objects.filter(
  28. slug=project_slug
  29. ).get()
  30. file = difs_get_file_from_chunks(checksum, chunks)
  31. if file is None:
  32. file = difs_create_file_from_chunks(name, checksum, chunks)
  33. difs_create_difs(project, name, file)
  34. except ChecksumMismatched:
  35. getLogger().error(f"difs_assemble: Checksum mismatched: {name}")
  36. except Exception as e:
  37. getLogger().error(f"difs_assemble: {e}")
  38. def difs_run_resolve_stacktrace(event_id):
  39. if settings.GLITCHTIP_ENABLE_DIFS:
  40. difs_resolve_stacktrace.delay(event_id)
  41. @shared_task
  42. def difs_resolve_stacktrace(event_id):
  43. try:
  44. event = Event.objects.get(event_id=event_id)
  45. event_json = event.data
  46. exception = event_json.get("exception")
  47. if exception is None:
  48. # It is not a crash report event
  49. return
  50. project_id = event.issue.project_id
  51. difs = DebugInformationFile.objects.filter(
  52. project_id=project_id).order_by("-created")
  53. resolved_stracktrackes = []
  54. for dif in difs:
  55. if StacktraceProcessor.is_supported(event_json, dif) is False:
  56. continue
  57. blobs = dif.file.blobs.all()
  58. with difs_concat_file_blobs_to_disk(blobs) as symbol_file:
  59. remapped_stacktrace = StacktraceProcessor.resolve_stacktrace(
  60. event_json,
  61. symbol_file.name
  62. )
  63. if (remapped_stacktrace is not None and
  64. remapped_stacktrace.score > 0):
  65. resolved_stracktrackes.append(remapped_stacktrace)
  66. if len(resolved_stracktrackes) > 0:
  67. best_remapped_stacktrace = max(
  68. resolved_stracktrackes, key=lambda item: item.score)
  69. StacktraceProcessor.update_frames(
  70. event, best_remapped_stacktrace.frames)
  71. event.save()
  72. except Exception as e:
  73. getLogger().error(f"Error: difs_resolve_stacktrace: {e}")
  74. def difs_get_file_from_chunks(checksum, chunks):
  75. files = File.objects.filter(checksum=checksum)
  76. for file in files:
  77. blobs = file.blobs.all()
  78. file_chunks = [blob.checksum for blob in blobs]
  79. if file_chunks == chunks:
  80. return file
  81. return None
  82. def difs_create_file_from_chunks(name, checksum, chunks):
  83. blobs = FileBlob.objects.filter(checksum__in=chunks)
  84. total_checksum = sha1(b'')
  85. size = 0
  86. for blob in blobs:
  87. size = size + blob.blob.size
  88. with open(blob.blob.path, "rb") as binary_file:
  89. content = binary_file.read()
  90. total_checksum.update(content)
  91. total_checksum = total_checksum.hexdigest()
  92. if checksum != total_checksum:
  93. raise ChecksumMismatched()
  94. file = File(
  95. name=name,
  96. headers={},
  97. size=size,
  98. checksum=checksum
  99. )
  100. file.save()
  101. file.blobs.set(blobs)
  102. return file
  103. @contextlib.contextmanager
  104. def difs_concat_file_blobs_to_disk(blobs):
  105. output = tempfile.NamedTemporaryFile(delete=False)
  106. for blob in blobs:
  107. with open(blob.blob.path, "rb") as binary_file:
  108. content = binary_file.read()
  109. output.write(content)
  110. output.flush()
  111. output.seek(0)
  112. try:
  113. yield output
  114. finally:
  115. output.close()
  116. def difs_extract_metadata_from_file(file):
  117. with difs_concat_file_blobs_to_disk(file.blobs.all()) as input:
  118. # Only one kind of file format is supported now
  119. try:
  120. archive = Archive.open(input.name)
  121. except Exception as e:
  122. getLogger().error(f"Extract metadata error: {e}")
  123. raise UnsupportedFile()
  124. else:
  125. return [
  126. {
  127. "arch": obj.arch,
  128. "file_format": obj.file_format,
  129. "code_id": obj.code_id,
  130. "debug_id": obj.debug_id,
  131. "kind": obj.kind,
  132. "features": list(obj.features),
  133. "symbol_type": "native"
  134. }
  135. for obj in archive.iter_objects()
  136. ]
  137. def difs_create_difs(project, name, file):
  138. metadatalist = difs_extract_metadata_from_file(file)
  139. for metadata in metadatalist:
  140. dif = DebugInformationFile.objects.filter(
  141. project_id=project.id,
  142. file=file
  143. ).first()
  144. if dif is not None:
  145. continue
  146. code_id = metadata["code_id"]
  147. debug_id = metadata["debug_id"]
  148. arch = metadata["arch"]
  149. kind = metadata["kind"]
  150. features = metadata["features"]
  151. symbol_type = metadata["symbol_type"]
  152. dif = DebugInformationFile(
  153. project=project,
  154. name=name,
  155. file=file,
  156. data={
  157. "arch": arch,
  158. "debug_id": debug_id,
  159. "code_id": code_id,
  160. "kind": kind,
  161. "features": features,
  162. "symbol_type": symbol_type
  163. }
  164. )
  165. dif.save()