tasks.py 5.4 KB

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