mock-sessions 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. #!/usr/bin/env python
  2. from sentry.runner import configure
  3. configure()
  4. import datetime
  5. import random
  6. from uuid import uuid4
  7. import click
  8. import requests
  9. from django.utils import timezone
  10. from sentry import options
  11. from sentry.models import Organization, Project, ProjectKey, Release
  12. from sentry.snuba.metrics.naming_layer.mri import SessionMRI
  13. from sentry.utils import json
  14. abnormal_mechanism_choices = (
  15. "anr_background",
  16. "anr_foreground",
  17. )
  18. session_status = ["ok", "exited", "crashed", "abnormal"]
  19. session_status_weights = [0.5, 0.05, 0.05, 0.40]
  20. type_map = {
  21. "set": "s",
  22. "counter": "c",
  23. "distribution": "d",
  24. }
  25. def generate_session(
  26. id: str,
  27. user_id: str,
  28. time: datetime.datetime,
  29. env: str,
  30. release: str,
  31. init: bool,
  32. ):
  33. abnormal_mechanism = random.choice(abnormal_mechanism_choices)
  34. status = random.choices(session_status, session_status_weights)[0]
  35. return {
  36. "sid": id,
  37. "did": user_id,
  38. "init": init,
  39. "started": time.isoformat(),
  40. "duration": random.randrange(10, 90),
  41. "error": 1 if status == "abnormal" else 0,
  42. "status": "ok" if init else status,
  43. "abnormal_mechanism": abnormal_mechanism if status == "abnormal" else None,
  44. "attrs": {
  45. "environment": env,
  46. "release": release,
  47. },
  48. }
  49. def send_session(session, project, project_key):
  50. envelope = ["{}", '{"type":"session"}', json.dumps(session)]
  51. payload = "\n".join(envelope)
  52. host = "http://127.0.0.1:8000"
  53. url = f"{host}/api/{project.id}/envelope/?sentry_key={project_key.public_key}&sentry_version=7&sentry_client=sentry.curl/1.1.1"
  54. headers = {
  55. "content-type": "application/json",
  56. }
  57. response = requests.post(url, payload, headers=headers)
  58. return response
  59. def convert_session_to_metrics(organization: Organization, project: Project, session):
  60. """
  61. Convert a session to metrics
  62. Shamelessly stolen from sentry.testutils.cases.BaseMetricsTestCase::store_session
  63. Mimic relays behavior of always emitting a metric for a started session,
  64. and emitting an additional one if the session is fatal
  65. See https://github.com/getsentry/relay/blob/e3c064e213281c36bde5d2b6f3032c6d36e22520/relay-server/src/actors/envelopes.rs#L357
  66. """
  67. user = session.get("distinct_id")
  68. base_tags = session.get("attrs")
  69. # This check is not yet reflected in relay, see https://getsentry.atlassian.net/browse/INGEST-464
  70. user_is_nil = user is None or user == "00000000-0000-0000-0000-000000000000"
  71. def generate_metric(type, mri: str, tags, value):
  72. short_type = type_map[type]
  73. return {
  74. "org_id": organization.id,
  75. "project_id": project.id,
  76. "type": short_type,
  77. "name": mri,
  78. "value": value,
  79. "timestamp": session.get("started"),
  80. "tags": {**tags, **base_tags},
  81. }
  82. if session["init"] is True: # init
  83. yield generate_metric("counter", SessionMRI.SESSION.value, {"session.status": "init"}, +1)
  84. status = session["status"]
  85. # Mark the session as errored, which includes fatal sessions.
  86. if session.get("errors", 0) > 0 or status not in ("ok", "exited"):
  87. yield generate_metric("set", SessionMRI.ERROR.value, {}, session["sid"])
  88. if not user_is_nil:
  89. yield generate_metric("set", SessionMRI.USER.value, {"session.status": "errored"}, user)
  90. elif not user_is_nil:
  91. yield generate_metric("set", SessionMRI.USER.value, {}, user)
  92. if status in ("abnormal", "crashed"): # fatal
  93. yield generate_metric("counter", SessionMRI.SESSION.value, {"session.status": status}, +1)
  94. if not user_is_nil:
  95. yield generate_metric("set", SessionMRI.USER.value, {"session.status": status}, user)
  96. if status == "exited":
  97. if session["duration"] is not None:
  98. yield generate_metric(
  99. "distribution",
  100. SessionMRI.RAW_DURATION.value,
  101. {"session.status": status},
  102. session["duration"],
  103. )
  104. @click.command()
  105. @click.option("--project", type=str, help="Project slug to insert data into", default="internal")
  106. @click.option("--org", type=str, help="Organization slug to insert data into", default="sentry")
  107. @click.option(
  108. "--release", type=str, help="Release name. Defaults to the most recent release in the project."
  109. )
  110. @click.option("--env", type=str, help="Environment to generate sessions for", default="dev")
  111. @click.option("--days", type=int, help="Number of days to generate data for", default=5)
  112. def main(project, org, env, days, release=None):
  113. click.echo("Mocking sessions for {project} in {org}")
  114. options.set("sentry-metrics.releasehealth.abnormal-mechanism-extraction-rate", 1.0)
  115. try:
  116. organization = Organization.objects.get(slug=org)
  117. except Organization.DoesNotExist:
  118. raise RuntimeError("! Organization does not exist")
  119. try:
  120. project = Project.objects.get(slug=project, organization=organization)
  121. except Project.DoesNotExist:
  122. raise RuntimeError("! Project does not exist")
  123. project_key = ProjectKey.objects.filter(project=project)[0]
  124. if not project_key:
  125. raise RuntimeError(f"! Could not find a DSN for {project.slug}")
  126. if release is None:
  127. release = (
  128. Release.objects.filter(projects=project, organization=organization)
  129. .order_by("-date_added")[0]
  130. .version
  131. )
  132. if not release:
  133. release = Release.objects.create(
  134. projects=project, organization=organization, version="session-data"
  135. ).version
  136. click.echo(f"> Generating sessions for {days} day(s)")
  137. start = timezone.now()
  138. end = start - datetime.timedelta(days=days)
  139. current = start
  140. while current >= end:
  141. current = current - datetime.timedelta(hours=1)
  142. id = str(uuid4().hex)
  143. did = str(uuid4().hex)
  144. session = generate_session(id, did, current, env, release, True)
  145. response = send_session(session, project, project_key)
  146. session = generate_session(id, did, current, env, release, False)
  147. response = send_session(session, project, project_key)
  148. if response.status_code != 200:
  149. click.echo(f"Response failed {response}")
  150. click.echo("> Complete! generated session records")
  151. if __name__ == "__main__":
  152. main()