Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@
/sentry-sdk-benchmark

/FrameworkBenchmarks/

__pycache__
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,27 @@ docker images -f "dangling=true" -q | xargs -tn10 docker rmi -f
## Adding More Platforms

See [platform/README.md](platform/README.md).

## Licensing
This repostitory may contain code from https://github.com/nylas/nylas-perftools, which is licensed under the following license:
> The MIT License (MIT)
>
> Copyright (c) 2014 Nylas
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3.9.1-buster

WORKDIR /django

COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY requirements-sentry.txt ./
RUN pip install -r requirements-sentry.txt
COPY . ./

EXPOSE 8080

CMD gunicorn --pid=gunicorn.pid hello.wsgi:application -c gunicorn_conf.py --env DJANGO_DB=postgresql
26 changes: 26 additions & 0 deletions platform/python/django/instrumented_nylas/gunicorn_conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import multiprocessing
import os
import sys

_is_pypy = hasattr(sys, 'pypy_version_info')
_is_travis = os.environ.get('TRAVIS') == 'true'

workers = multiprocessing.cpu_count() * 3
if _is_travis:
workers = 2

bind = "0.0.0.0:8080"
keepalive = 120
errorlog = '-'
pidfile = 'gunicorn.pid'
pythonpath = 'hello'

if _is_pypy:
worker_class = "tornado"
else:
worker_class = "meinheld.gmeinheld.MeinheldWorker"

def post_fork(server, worker):
# Disalbe access log
import meinheld.server
meinheld.server.set_access_logger(None)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import hello.nylas_profiler as nylas_profiler

class NylasMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
profiler = nylas_profiler.Sampler('test')
profiler.start()
response = self.get_response(request)
profiler.stop()

return response
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"""
This file contains code from https://github.com/nylas/nylas-perftools, which is published under the following license:

The MIT License (MIT)

Copyright (c) 2014 Nylas

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

import atexit
import os
import signal
import time
import threading
import json

def nanosecond_time():
return int(time.perf_counter() * 1e9)

class FrameData:
def __init__(self, frame):
self.function_name = frame.f_code.co_name
self.module = frame.f_globals['__name__']

# Depending on Python version, frame.f_code.co_filename either stores just the file name or the entire absolute path.
self.file_name = frame.f_code.co_filename
self.line_number = frame.f_code.co_firstlineno

@property
def _attribute_tuple(self):
"""Returns a tuple of the attributes used in comparison"""
return (self.function_name, self.module, self.file_name, self.line_number)

def __eq__(self, other):
if isinstance(other, FrameData):
return self._attribute_tuple == other._attribute_tuple
return False

def __hash__(self):
return hash(self._attribute_tuple)

def __str__(self):
return f'{self.function_name}({self.module}) in {self.file_name}:{self.line_number}'

class StackSample:
def __init__(self, top_frame, profiler_start_time, frame_indices):
self.sample_time = nanosecond_time() - profiler_start_time
self.stack = []
self._add_all_frames(top_frame, frame_indices)

def _add_all_frames(self, top_frame, frame_indices):
frame = top_frame
while frame is not None:
frame_data = FrameData(frame)
if frame_data not in frame_indices:
frame_indices[frame_data] = len(frame_indices)
self.stack.append(frame_indices[frame_data])
frame = frame.f_back
self.stack = list(reversed(self.stack))

def __str__(self):
return f'Time: {self.sample_time}; Stack: {[str(frame) for frame in reversed(self.stack)]}'

class Sampler(object):
"""
A simple stack sampler for low-overhead CPU profiling: samples the call
stack every `interval` seconds and keeps track of counts by frame. Because
this uses signals, it only works on the main thread.
"""
def __init__(self, transaction, interval=0.01):
self.interval = interval
self.stack_samples = []
self._transaction = transaction

def __enter__(self):
self.start()

def __exit__(self, *_):
self.stop()
# if len(self.stack_samples) > 0:
# with open('test_profile.json', 'w') as f:
# f.write(self.to_json())

def start(self):
self._start_time = nanosecond_time()
self.stack_samples = []
self._frame_indices = dict()
try:
signal.signal(signal.SIGVTALRM, self._sample)
except ValueError:
raise ValueError('cannot run on non main thread')
return

signal.setitimer(signal.ITIMER_VIRTUAL, self.interval)
atexit.register(self.stop)

def sample_weights(self):
"""
Return the weights of each sample (difference between the sample's and previous sample's timestamp).
"""
if self.stack_samples == []:
return []

return [self.stack_samples[0].sample_time, *(sample.sample_time - prev_sample.sample_time for sample, prev_sample in zip(self.stack_samples[1:], self.stack_samples))]

def _sample(self, _, frame):
self.stack_samples.append(StackSample(frame, self._start_time, self._frame_indices))
signal.setitimer(signal.ITIMER_VIRTUAL, self.interval)

def to_json(self):
"""
Exports this object to a JSON format compatible with Sentry's profiling visualizer
"""
return json.dumps(self, cls=self.JSONEncoder)

def frame_list(self):
# Build frame array from the frame indices
frames = [None] * len(self._frame_indices)
for frame, index in self._frame_indices.items():
frames[index] = frame
return frames

def samples(self):
return len(self.stack_samples)

def __str__(self):
return '\n'.join([str(sample) for sample in self.stack_samples])

def stop(self):
signal.setitimer(signal.ITIMER_VIRTUAL, 0)

def __del__(self):
self.stop()

@property
def transaction_name(self):
return self._transaction.name

class JSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Sampler):
return {
'transactionName': o.transaction_name,
'profiles': [{
'weights': o.sample_weights(),
'samples': [sample.stack for sample in o.stack_samples],
'type': 'sampled',
'endValue': o.stack_samples[-1].sample_time, # end ts
'startValue': 0, # start ts
'name': 'main',
'unit': 'nanoseconds',
'threadID': threading.get_ident()
}],
'shared': {
'frames': [{
'name': frame.function_name,
'file': frame.file_name,
'line': frame.line_number
} for frame in o.frame_list()] # TODO: Add all elements
# 'frames': [{
# 'key': string | number,
# 'name': string,
# 'file': string,
# 'line': number,
# 'column': number,
# 'is_application': boolean,
# 'image': string,
# 'resource': string,
# 'threadId': number
# }] # TODO: Add all elements
}
}

else:
return json.JSONEncoder.default(self, o)
71 changes: 71 additions & 0 deletions platform/python/django/instrumented_nylas/hello/hello/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import os
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration

sentry_sdk.init(
integrations=[DjangoIntegration()],
traces_sample_rate=1.0,
send_default_pii=True,
# debug=True,
)

DEBUG = False

SECRET_KEY = '_7mb6#v4yf@qhc(r(zbyh&z_iby-na*7wz&-v6pohsul-d#y5f'
ADMINS = ()

MANAGERS = ADMINS

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.' + os.environ['DJANGO_DB'], # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'hello_world', # Or path to database file if using sqlite3.
'USER': 'benchmarkdbuser', # Not used with sqlite3.
'PASSWORD': 'benchmarkdbpass', # Not used with sqlite3.
'HOST': 'tfb-database', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
'CONN_MAX_AGE': 30,
}
}

TIME_ZONE = 'America/Chicago'
LANGUAGE_CODE = 'en-us'
USE_I18N = False
USE_L10N = False
USE_TZ = False

MEDIA_ROOT = ''
MEDIA_URL = ''
STATIC_ROOT = ''
STATIC_URL = '/static/'
STATICFILES_DIRS = ()
STATICFILES_FINDERS = ()
MIDDLEWARE = ['hello.nylas_middleware.NylasMiddleware']

ROOT_URLCONF = 'hello.urls'
WSGI_APPLICATION = 'hello.wsgi.application'

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {},
},
]

INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.sessions',
'world',
)

LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'handlers': {},
'loggers': {},

}

ALLOWED_HOSTS = ['*']
11 changes: 11 additions & 0 deletions platform/python/django/instrumented_nylas/hello/hello/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.conf.urls import url
from world.views import plaintext, json, db, dbs, fortunes, update

urlpatterns = [
url(r'^plaintext$', plaintext),
url(r'^json$', json),
url(r'^db$', db),
url(r'^dbs$', dbs),
url(r'^fortunes$', fortunes),
url(r'^update$', update),
]
21 changes: 21 additions & 0 deletions platform/python/django/instrumented_nylas/hello/hello/wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
WSGI config for hello project.

This module contains the WSGI application used by Django's development server
and any production WSGI deployments. It should expose a module-level variable
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
this application via the ``WSGI_APPLICATION`` setting.

Usually you will have the standard Django WSGI application here, but it also
might make sense to replace the whole Django WSGI application with a custom one
that later delegates to the Django one. For example, you could introduce WSGI
middleware here, or combine a Django application with an application of another
framework.

"""
import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hello.settings")

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
10 changes: 10 additions & 0 deletions platform/python/django/instrumented_nylas/hello/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hello.settings")

from django.core.management import execute_from_command_line

execute_from_command_line(sys.argv)
Loading