Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a distribution tab to visualize request time distributions over time #281

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,14 @@ Silk currently generates two bits of code per request:

Both are intended for use in replaying the request. The curl command can be used to replay via command-line and the python code can be used within a Django unit test or simply as a standalone script.

## Distribution View

By setting the configuration ``SILKY_DISTRIBUTION_TAB`` to ``True`` a distribution tab can be enabled with a visualisation for plotting the distribution of timings of requests. At present it is possible to group the requests by date or revision.

<img src="https://raw.githubusercontent.com/jazzband/django-silk/master/screenshots/11.png" width="720px"/>

You can click on the various groups to drill down to eventually see the actual requests that are running slowly. This visualisation can be useful for benchmarking an application over versions or time to see if performance improves or degrades.

## Configuration

### Authentication/Authorisation
Expand Down
2 changes: 1 addition & 1 deletion project/example_app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
def index(request):
@silk_profile()
def do_something_long():
sleep(1.345)
sleep(0.345)

with silk_profile(name='Why do this take so long?'):
do_something_long()
Expand Down
2 changes: 2 additions & 0 deletions project/project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,5 @@
SILKY_MAX_RECORDED_REQUESTS_CHECK_PERCENT = 0
# SILKY_AUTHENTICATION = True
# SILKY_AUTHORISATION = True

SILKY_DISTRIBUTION_TAB = True
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ Django>=1.11
freezegun>=0.3
factory-boy>=2.8.1
gprof2dot>=2016.10.13,<2017.09.19
dealer>=2.0.5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking out dealer, it seems dealer may be out of date. Have you tried testing with django >= 1.10 (klen/dealer#24)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will take a look for an alternative to dealer. I think the revision fuctionality is not that useful in production, at least we don't use it, we set SILKY_REVISION to a version number in settings.py. But it is quite useful for benchmarking locally, for example for comparing a number of commits. If I can't find a better alternative I guess another option is to just remove this dependency and let users set to the revision number using whichever method they prefer (probably nice to have an example in the readme)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will check out the failing tests.

Binary file modified screenshots/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/11.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ def read_md(f):
'autopep8',
'pytz',
'gprof2dot<2017.09.19',
'dealer',
]
)
27 changes: 26 additions & 1 deletion silk/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,37 @@

from django.utils import six

from dealer.auto import auto

from silk.singleton import Singleton



def default_permissions(user):
if user:
return user.is_staff
return False


def default_revision():
revision = auto.revision
try:
# This is a pretty flaky way of getting revisions in a semi chronological
# order (of course two commits from the same second cannot be correctly
# ordered) for a couple of reasons:
#
# - it only works for git
# - it's using the protected _repo attribute from dealer
# - the timestamp becomes visible in the revision
#
# However it is better than order commit hashes alphabetically
timestamp = auto._repo.git('show -s --format=%at ' + revision)
revision = ' # '.join([timestamp.decode(), revision])
except:
pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this make more sense to be revision = ''?

And does the rest of the app work correctly with an empty string or None value? There seem to be several areas of the app that filter or group by revision value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@albertyw I removed the dealer dependency now - I think it's simpler just to let users to set the revision themselves using whichever method they prefer. This is what we have ended up doing when running this branch in production.

return revision


class SilkyConfig(six.with_metaclass(Singleton, object)):
defaults = {
'SILKY_DYNAMIC_PROFILING': [],
Expand All @@ -28,7 +50,10 @@ class SilkyConfig(six.with_metaclass(Singleton, object)):
'SILKY_INTERCEPT_PERCENT': 100,
'SILKY_INTERCEPT_FUNC': None,
'SILKY_PYTHON_PROFILER': False,
'SILKY_STORAGE_CLASS': 'silk.storage.ProfilerResultStorage'
'SILKY_REVISION': default_revision(),
'SILKY_POST_PROCESS_REQUEST': lambda x: None,
'SILKY_STORAGE_CLASS': 'silk.storage.ProfilerResultStorage',
'SILKY_DISTRIBUTION_TAB': False,
}

def _setup(self):
Expand Down
21 changes: 21 additions & 0 deletions silk/migrations/0007_request_revision.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-12-08 12:52
from __future__ import unicode_literals

from django.db import migrations, models
import silk.storage


class Migration(migrations.Migration):

dependencies = [
('silk', '0006_fix_request_prof_file_blank'),
]

operations = [
migrations.AddField(
model_name='request',
name='revision',
field=models.TextField(blank=True, default=''),
),
]
31 changes: 31 additions & 0 deletions silk/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class Request(models.Model):
meta_time_spent_queries = FloatField(null=True, blank=True)
pyprofile = TextField(blank=True, default='')
prof_file = FileField(max_length=300, blank=True, storage=silk_storage)
revision = TextField(blank=True, default='')

@property
def total_meta_time(self):
Expand Down Expand Up @@ -112,6 +113,30 @@ def time_spent_on_sql_queries(self):
"""
return sum(x.time_taken for x in SQLQuery.objects.filter(request=self))

@classmethod
def get_date(cls, start_time):
return start_time.date()

@property
def date(self):
return self.get_date(self.start_time)

@classmethod
def get_hour(cls, start_time):
return start_time.strftime('%Y-%m-%d %H')

@property
def hour(self):
return self.get_hour(self.start_time)

@classmethod
def get_minute(cls, start_time):
return start_time.strftime('%Y-%m-%d %H:%M')

@property
def minute(self):
return self.get_minute(self.start_time)

@property
def headers(self):
if self.encoded_headers:
Expand Down Expand Up @@ -163,6 +188,12 @@ def save(self, *args, **kwargs):
interval = self.end_time - self.start_time
self.time_taken = interval.total_seconds() * 1000

config = SilkyConfig()

self.revision = config.SILKY_REVISION

config.SILKY_POST_PROCESS_REQUEST(self)

super(Request, self).save(*args, **kwargs)
Request.garbage_collect(force=False)

Expand Down
11 changes: 8 additions & 3 deletions silk/request_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,16 +206,21 @@ def __init__(self, value):
super(MethodFilter, self).__init__(value, method=value)


def filters_from_request(request):
class RevisionFilter(BaseFilter):
def __init__(self, value):
super(RevisionFilter, self).__init__(value, revision=value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need of expliclit RevisionFilter, self) in super()



def filters_from_query_dict(query_dict):
raw_filters = {}
for key in request.POST:
for key in query_dict:
splt = key.split('-')
if splt[0].startswith('filter'):
ident = splt[1]
typ = splt[2]
if ident not in raw_filters:
raw_filters[ident] = {}
raw_filters[ident][typ] = request.POST[key]
raw_filters[ident][typ] = query_dict[key]
filters = {}
for ident, raw_filter in raw_filters.items():
value = raw_filter.get('value', '')
Expand Down
Loading