mock-sessions 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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 confluent_kafka import Producer
  10. from django.conf import settings
  11. from django.utils import timezone
  12. from sentry.models import Organization, Project, ProjectKey, Release
  13. from sentry.snuba.metrics.naming_layer.mri import SessionMRI
  14. from sentry.utils import json
  15. os_choices = (
  16. "iOS 13",
  17. "iOS 14",
  18. "iOS 15",
  19. "Android 7",
  20. "Android 8",
  21. )
  22. family_choices = {
  23. "iOS 13": ("Apple iPhone X", "Apple iPhone 12", "Apple iPhone 13"),
  24. "iOS 14": ("Apple iPhone X", "Apple iPhone 12", "Apple iPhone 13"),
  25. "iOS 15": ("Apple iPhone X", "Apple iPhone 12", "Apple iPhone 13"),
  26. "Android 7": ("SM-G955F", "HTC 10", "Pixel 4"),
  27. "Android 8": ("SM-T510", "OnePlus Nord2 5G", "Pixel 6", "Moto x4"),
  28. }
  29. manufacturer_choices = {
  30. "iOS 13": ("Apple",),
  31. "iOS 14": ("Apple",),
  32. "iOS 15": ("Apple",),
  33. "Android 7": ("Samsung", "OnePlus", "Xiaomi", "HTC", "Google"),
  34. "Android 8": ("Samsung", "Xiaomi", "Moto", "Google"),
  35. }
  36. session_status = ["ok", "exited", "crashed", "abnormal"]
  37. session_status_weights = [0.9, 0.03, 0.03, 0.03]
  38. type_map = {
  39. "set": "s",
  40. "counter": "c",
  41. "distribution": "d",
  42. }
  43. topic = settings.KAFKA_INGEST_METRICS
  44. def create_producer():
  45. cluster_name = settings.KAFKA_TOPICS[topic]["cluster"]
  46. cluster = settings.KAFKA_CLUSTERS[cluster_name]
  47. producer = Producer({"bootstrap.servers": cluster["common"]["bootstrap.servers"]})
  48. return producer
  49. def generate_session(
  50. time: datetime.datetime,
  51. env: str,
  52. release: str,
  53. ):
  54. os = random.choice(os_choices)
  55. status = random.choices(session_status, session_status_weights)[0]
  56. return {
  57. "sid": str(uuid4()),
  58. "init": True,
  59. "started": time.isoformat(),
  60. "duration": random.randrange(10, 90),
  61. "error": random.randrange(1, 5) if random.random() > 0.95 else 0,
  62. "status": status,
  63. "attrs": {
  64. "environment": env,
  65. "release": release,
  66. "session.status": status,
  67. "os": os,
  68. "os.name": os,
  69. "device.family": random.choice(family_choices[os]),
  70. "device.manufacturer": random.choice(manufacturer_choices[os]),
  71. },
  72. }
  73. def convert_session_to_metrics(organization: Organization, project: Project, session):
  74. """
  75. Convert a session to metrics
  76. Shamelessly stolen from sentry.testutils.cases.BaseMetricsTestCase::store_session
  77. Mimic relays behavior of always emitting a metric for a started session,
  78. and emitting an additional one if the session is fatal
  79. See https://github.com/getsentry/relay/blob/e3c064e213281c36bde5d2b6f3032c6d36e22520/relay-server/src/actors/envelopes.rs#L357
  80. """
  81. user = session.get("distinct_id")
  82. base_tags = session.get("attrs")
  83. # This check is not yet reflected in relay, see https://getsentry.atlassian.net/browse/INGEST-464
  84. user_is_nil = user is None or user == "00000000-0000-0000-0000-000000000000"
  85. def generate_metric(type, mri: str, tags, value):
  86. short_type = type_map[type]
  87. return {
  88. "org_id": organization.id,
  89. "project_id": project.id,
  90. "type": short_type,
  91. "name": mri,
  92. "value": value,
  93. "timestamp": session.get("started"),
  94. "tags": {**tags, **base_tags},
  95. }
  96. if session["init"] is True: # init
  97. yield generate_metric("counter", SessionMRI.SESSION.value, {"session.status": "init"}, +1)
  98. status = session["status"]
  99. # Mark the session as errored, which includes fatal sessions.
  100. if session.get("errors", 0) > 0 or status not in ("ok", "exited"):
  101. yield generate_metric("set", SessionMRI.ERROR.value, {}, session["sid"])
  102. if not user_is_nil:
  103. yield generate_metric("set", SessionMRI.USER.value, {"session.status": "errored"}, user)
  104. elif not user_is_nil:
  105. yield generate_metric("set", SessionMRI.USER.value, {}, user)
  106. if status in ("abnormal", "crashed"): # fatal
  107. yield generate_metric("counter", SessionMRI.SESSION.value, {"session.status": status}, +1)
  108. if not user_is_nil:
  109. yield generate_metric("set", SessionMRI.USER.value, {"session.status": status}, user)
  110. if status == "exited":
  111. if session["duration"] is not None:
  112. yield generate_metric(
  113. "distribution",
  114. SessionMRI.RAW_DURATION.value,
  115. {"session.status": status},
  116. session["duration"],
  117. )
  118. @click.command()
  119. @click.option("--project", type=str, help="Project slug to insert data into", default="internal")
  120. @click.option("--org", type=str, help="Organization slug to insert data into", default="sentry")
  121. @click.option(
  122. "--release", type=str, help="Release name. Defaults to the most recent release in the project."
  123. )
  124. @click.option("--env", type=str, help="Environment to generate sessions for", default="dev")
  125. @click.option("--days", type=int, help="Number of days to generate data for", default=14)
  126. def main(project, org, env, days, release=None):
  127. click.echo("Mocking sessions for {project} in {org}")
  128. try:
  129. organization = Organization.objects.get(slug=org)
  130. except Organization.DoesNotExist:
  131. raise RuntimeError("! Organization does not exist")
  132. try:
  133. project = Project.objects.get(slug=project, organization=organization)
  134. except Project.DoesNotExist:
  135. raise RuntimeError("! Project does not exist")
  136. project_key = ProjectKey.objects.filter(project=project)[0]
  137. if not project_key:
  138. raise RuntimeError(f"! Could not find a DSN for {project.slug}")
  139. if release is None:
  140. release = (
  141. Release.objects.filter(projects=project, organization=organization)
  142. .order_by("-date_added")[0]
  143. .version
  144. )
  145. if not release:
  146. release = Release.objects.create(
  147. projects=project, organization=organization, version="session-data"
  148. ).version
  149. # producer = create_producer()
  150. click.echo(f"> Generating sessions for {days} day(s)")
  151. start = timezone.now()
  152. end = start - datetime.timedelta(days=days)
  153. current = start
  154. count = 0
  155. while current >= end:
  156. current = current - datetime.timedelta(seconds=15)
  157. session = generate_session(current, env, release)
  158. envelope = ["{}", '{"type":"session"}', json.dumps(session)]
  159. payload = "\n".join(envelope)
  160. # host = "http://dev.getsentry.net:8000"
  161. # TODO change this
  162. host = "http://dev.getsentry.net:8000"
  163. url = f"{host}/api/{project.id}/envelope/?sentry_key={project_key.public_key}&sentry_version=7&sentry_client=sentry.curl/1.1.1"
  164. headers = {
  165. "content-type": "application/json",
  166. }
  167. click.echo(session)
  168. # click.echo(f"> sending request to {url}")
  169. response = requests.post(url, payload, headers=headers)
  170. if response.status_code != 200:
  171. click.echo(f"Response failed {response}")
  172. # count += 1
  173. # for metric in convert_session_to_metrics(organization, project, session):
  174. # producer.produce(topic, json.dumps(metric))
  175. # if count % 100 == 0:
  176. # producer.flush()
  177. # producer.flush()
  178. click.echo(f"> Complete! generated {count} session records")
  179. if __name__ == "__main__":
  180. main()