tasks.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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. event = Event.objects.get(event_id=event_id)
  44. event_json = event.data
  45. exception = event_json.get("exception")
  46. if exception is None:
  47. # It is not a crash report event
  48. return
  49. project_id = event.issue.project_id
  50. difs = DebugInformationFile.objects.filter(
  51. project_id=project_id).order_by("-created")
  52. resolved_stracktrackes = []
  53. for dif in difs:
  54. if StacktraceProcessor.is_supported(event_json, dif) is False:
  55. continue
  56. blobs = dif.file.blobs.all()
  57. with difs_concat_file_blobs_to_disk(blobs) as symbol_file:
  58. remapped_stacktrace = StacktraceProcessor.resolve_stacktrace(
  59. event_json,
  60. symbol_file.name
  61. )
  62. if (remapped_stacktrace is not None and
  63. remapped_stacktrace.score > 0):
  64. resolved_stracktrackes.append(remapped_stacktrace)
  65. if len(resolved_stracktrackes) > 0:
  66. best_remapped_stacktrace = max(
  67. resolved_stracktrackes, key=lambda item: item.score)
  68. StacktraceProcessor.update_frames(
  69. event, best_remapped_stacktrace.frames)
  70. event.save()
  71. def difs_get_file_from_chunks(checksum, chunks):
  72. files = File.objects.filter(checksum=checksum)
  73. for file in files:
  74. blobs = file.blobs.all()
  75. file_chunks = [blob.checksum for blob in blobs]
  76. if file_chunks == chunks:
  77. return file
  78. return None
  79. def difs_create_file_from_chunks(name, checksum, chunks):
  80. blobs = FileBlob.objects.filter(checksum__in=chunks)
  81. total_checksum = sha1(b'')
  82. size = 0
  83. for blob in blobs:
  84. size = size + blob.blob.size
  85. with open(blob.blob.path, "rb") as binary_file:
  86. content = binary_file.read()
  87. total_checksum.update(content)
  88. total_checksum = total_checksum.hexdigest()
  89. if checksum != total_checksum:
  90. raise ChecksumMismatched()
  91. file = File(
  92. name=name,
  93. headers={},
  94. size=size,
  95. checksum=checksum
  96. )
  97. file.save()
  98. file.blobs.set(blobs)
  99. return file
  100. @contextlib.contextmanager
  101. def difs_concat_file_blobs_to_disk(blobs):
  102. output = tempfile.NamedTemporaryFile(delete=False)
  103. for blob in blobs:
  104. with open(blob.blob.path, "rb") as binary_file:
  105. content = binary_file.read()
  106. output.write(content)
  107. output.flush()
  108. output.seek(0)
  109. try:
  110. yield output
  111. finally:
  112. output.close()
  113. def difs_extract_metadata_from_file(file):
  114. with difs_concat_file_blobs_to_disk(file.blobs.all()) as input:
  115. # Only one kind of file format is supported now
  116. try:
  117. archive = Archive.open(input.name)
  118. except Exception as e:
  119. getLogger().error(f"Extract metadata error: {e}")
  120. raise UnsupportedFile()
  121. else:
  122. return [
  123. {
  124. "arch": obj.arch,
  125. "file_format": obj.file_format,
  126. "code_id": obj.code_id,
  127. "debug_id": obj.debug_id,
  128. "kind": obj.kind,
  129. "features": list(obj.features),
  130. "symbol_type": "native"
  131. }
  132. for obj in archive.iter_objects()
  133. ]
  134. def difs_create_difs(project, name, file):
  135. metadatalist = difs_extract_metadata_from_file(file)
  136. for metadata in metadatalist:
  137. dif = DebugInformationFile.objects.filter(
  138. project_id=project.id,
  139. file=file
  140. ).first()
  141. if dif is not None:
  142. continue
  143. code_id = metadata["code_id"]
  144. debug_id = metadata["debug_id"]
  145. arch = metadata["arch"]
  146. kind = metadata["kind"]
  147. features = metadata["features"]
  148. symbol_type = metadata["symbol_type"]
  149. dif = DebugInformationFile(
  150. project=project,
  151. name=name,
  152. file=file,
  153. data={
  154. "arch": arch,
  155. "debug_id": debug_id,
  156. "code_id": code_id,
  157. "kind": kind,
  158. "features": features,
  159. "symbol_type": symbol_type
  160. }
  161. )
  162. dif.save()