From d56911901bdc22960c0c50d849f50cf5a33086e1 Mon Sep 17 00:00:00 2001 From: MassiveAtoms Date: Fri, 17 Jul 2020 00:48:54 -0300 Subject: [PATCH] added benchmarking --- .gitignore | 1 + .../CalibreWebCompanion/settings.py | 15 +- .../CalibreWebCompanion/urls.py | 3 +- CalibreWebCompanion/library/urls.py | 19 +- CalibreWebCompanion/library/views.py | 55 +++++- loadtesting/bench.py | 22 +++ loadtesting/calibre_failures.csv | 2 + loadtesting/calibre_stats.csv | 20 ++ loadtesting/calibre_stats_history.csv | 61 ++++++ loadtesting/locust.conf | 9 + loadtesting/locustfile.py | 184 ++++++++++++++++++ requirements.txt | 3 +- 12 files changed, 376 insertions(+), 18 deletions(-) create mode 100644 loadtesting/bench.py create mode 100644 loadtesting/calibre_failures.csv create mode 100644 loadtesting/calibre_stats.csv create mode 100644 loadtesting/calibre_stats_history.csv create mode 100644 loadtesting/locust.conf create mode 100644 loadtesting/locustfile.py diff --git a/.gitignore b/.gitignore index b435135..3ce226a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # project specific settings.json db.sqlite3 +*.prof # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/CalibreWebCompanion/CalibreWebCompanion/settings.py b/CalibreWebCompanion/CalibreWebCompanion/settings.py index a5e6558..d64808e 100644 --- a/CalibreWebCompanion/CalibreWebCompanion/settings.py +++ b/CalibreWebCompanion/CalibreWebCompanion/settings.py @@ -80,6 +80,12 @@ DEBUG_TOOLBAR_PANELS = [ ######################################################################## ## DERUG ## + +# SILKY_PYTHON_PROFILER = True +# SILKY_PYTHON_PROFILER_BINARY = True +# SILKY_PYTHON_PROFILER_RESULT_PATH = BASE_DIR + "/profiler" +# SILKY_META = True + LOGIN_REDIRECT_URL = '/books' # Application definition @@ -92,14 +98,14 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', "library", - # "silk", + # "silk", # DEBUG/profilling purposes # 'debug_toolbar', # DEBUG purposes ] MIDDLEWARE = [ # 'silk.middleware.SilkyMiddleware', # DEBUG/profiling purposes # 'debug_toolbar.middleware.DebugToolbarMiddleware', # DEBUG purposes - 'django.middleware.cache.UpdateCacheMiddleware', # cache + # 'django.middleware.cache.UpdateCacheMiddleware', # cache 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -107,8 +113,11 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.cache.FetchFromCacheMiddleware', # cache + # 'django.middleware.cache.FetchFromCacheMiddleware', # cache ] +## ## +######################################################################## + ROOT_URLCONF = 'CalibreWebCompanion.urls' diff --git a/CalibreWebCompanion/CalibreWebCompanion/urls.py b/CalibreWebCompanion/CalibreWebCompanion/urls.py index 22e2ba5..bb6c363 100644 --- a/CalibreWebCompanion/CalibreWebCompanion/urls.py +++ b/CalibreWebCompanion/CalibreWebCompanion/urls.py @@ -22,6 +22,7 @@ from django.conf import settings from django.urls import include, path + urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('django.contrib.auth.urls')), @@ -31,7 +32,7 @@ urlpatterns = [ # if settings.DEBUG: # DEBUG purposes - # urlpatterns+= [path('silk/', include('silk.urls', namespace='silk'))] +# urlpatterns+= [path('silk/', include('silk.urls', namespace='silk'))] # import debug_toolbar # urlpatterns = [ # path('__debug__/', include(debug_toolbar.urls)), diff --git a/CalibreWebCompanion/library/urls.py b/CalibreWebCompanion/library/urls.py index e66bf6d..de06834 100644 --- a/CalibreWebCompanion/library/urls.py +++ b/CalibreWebCompanion/library/urls.py @@ -10,19 +10,20 @@ urlpatterns = [ path('ratings/', views.RatingListView.as_view(), name='ratings'), path('tags/', views.TagListView.as_view(), name='tags'), path('series/', views.SeriesListView.as_view(), name='series'), - - - path('author/', views.AuthorDetailView.as_view(), name='author-detail-view'), + path('author/', views.AuthorDetailView.as_view(), + name='author-detail-view'), path('book/', views.BookDetailView.as_view(), name='book-detail-view'), - path('publisher/', views.PublisherDetailView.as_view(), name='publisher-detail-view'), - path('rating/', views.RatingDetailView.as_view(), name='rating-detail-view'), - path('series/', views.SeriesDetailView.as_view(), name='series-detail-view'), + path('publisher/', views.PublisherDetailView.as_view(), + name='publisher-detail-view'), + path('rating/', views.RatingDetailView.as_view(), + name='rating-detail-view'), + path('series/', views.SeriesDetailView.as_view(), + name='series-detail-view'), path('tag/', views.TagDetailView.as_view(), name='tag-detail-view'), path('results/', views.ResultsView.as_view(), name='results'), path('search/', views.SearchView.as_view(), name='search'), - - path('accounts/sign_up/',views.sign_up,name="sign-up") + path('accounts/sign_up/', views.sign_up, name="sign-up") -] \ No newline at end of file +] diff --git a/CalibreWebCompanion/library/views.py b/CalibreWebCompanion/library/views.py index dbb821d..31629bc 100644 --- a/CalibreWebCompanion/library/views.py +++ b/CalibreWebCompanion/library/views.py @@ -10,6 +10,9 @@ from django.contrib.auth import login from django.contrib.auth.decorators import login_required +# might be helpful for vary headers later +from django.utils.decorators import method_decorator + @login_required def index(request): @@ -31,6 +34,9 @@ def sign_up(request): class SearchView(generic.TemplateView): template_name = 'search.html' + def dispatch(self, *args, **kwargs): + return super(SearchView, self).dispatch(*args, **kwargs) + class ResultsView(generic.ListView): # no clue if this is secure. # according to this https://stackoverflow.com/questions/13574043/how-do-django-forms-sanitize-text-input-to-prevent-sql-injection-xss-etc @@ -38,6 +44,9 @@ class ResultsView(generic.ListView): # no clue if this is secure. model = Book template_name = 'results.html' + def dispatch(self, *args, **kwargs): + return super(ResultsView, self).dispatch(*args, **kwargs) + def get_queryset(self): # new title = self.request.GET.get('title') author = self.request.GET.get('author') @@ -52,8 +61,8 @@ class ResultsView(generic.ListView): # no clue if this is secure. books = books.filter(identifier__val=identifier) if generic: books = books.filter( - Q(sort__icontains=generic) | - Q(author_sort__icontains=generic) | + Q(sort__icontains=generic) | + Q(author_sort__icontains=generic) | Q(identifier__val=generic) ) return books @@ -62,10 +71,16 @@ class ResultsView(generic.ListView): # no clue if this is secure. class AuthorListView(generic.ListView): model = Author + def dispatch(self, *args, **kwargs): + return super(AuthorListView, self).dispatch(*args, **kwargs) + class BookListView(generic.ListView): model = Book + def dispatch(self, *args, **kwargs): + return super(BookListView, self).dispatch(*args, **kwargs) + def get_queryset(self): # Annotate the books with ratings, tags, etc # books = Book.objects.annotate( @@ -76,21 +91,37 @@ class BookListView(generic.ListView): class PublisherListView(generic.ListView): model = Publisher + def dispatch(self, *args, **kwargs): + return super(PublisherListView, self).dispatch(*args, **kwargs) + class RatingListView(generic.ListView): model = Rating -class SeriesListView(generic.ListView): # make url entry and template, sometime + def dispatch(self, *args, **kwargs): + return super(RatingListView, self).dispatch(*args, **kwargs) + + +class SeriesListView(generic.ListView): # make url entry and template, sometime model = Series + def dispatch(self, *args, **kwargs): + return super(SeriesListView, self).dispatch(*args, **kwargs) + class TagListView(generic.ListView): model = Tag + def dispatch(self, *args, **kwargs): + return super(TagListView, self).dispatch(*args, **kwargs) + class AuthorDetailView(generic.DetailView): model = Author + def dispatch(self, *args, **kwargs): + return super(AuthorDetailView, self).dispatch(*args, **kwargs) + def get_context_data(self, **kwargs): # Call the base implementation first to get the context context = super(AuthorDetailView, self).get_context_data(**kwargs) @@ -104,6 +135,9 @@ class AuthorDetailView(generic.DetailView): class BookDetailView(generic.DetailView): model = Book + def dispatch(self, *args, **kwargs): + return super(BookDetailView, self).dispatch(*args, **kwargs) + def get_context_data(self, **kwargs): # Call the base implementation first to get the context context = super(BookDetailView, self).get_context_data(**kwargs) @@ -122,6 +156,9 @@ class BookDetailView(generic.DetailView): class PublisherDetailView(generic.DetailView): model = Publisher + def dispatch(self, *args, **kwargs): + return super(PublisherDetailView, self).dispatch(*args, **kwargs) + def get_context_data(self, **kwargs): # Call the base implementation first to get the context context = super(PublisherDetailView, self).get_context_data(**kwargs) @@ -135,6 +172,9 @@ class PublisherDetailView(generic.DetailView): class RatingDetailView(generic.DetailView): model = Rating + def dispatch(self, *args, **kwargs): + return super(RatingDetailView, self).dispatch(*args, **kwargs) + def get_context_data(self, **kwargs): # Call the base implementation first to get the context context = super(RatingDetailView, self).get_context_data(**kwargs) @@ -148,6 +188,9 @@ class RatingDetailView(generic.DetailView): class TagDetailView(generic.DetailView): model = Tag + def dispatch(self, *args, **kwargs): + return super(TagDetailView, self).dispatch(*args, **kwargs) + def get_context_data(self, **kwargs): # Call the base implementation first to get the context context = super(TagDetailView, self).get_context_data(**kwargs) @@ -157,9 +200,13 @@ class TagDetailView(generic.DetailView): context['books'] = sorted(books, key=lambda x: x.title) return context + class SeriesDetailView(generic.DetailView): model = Series + def dispatch(self, *args, **kwargs): + return super(SeriesDetailView, self).dispatch(*args, **kwargs) + def get_context_data(self, **kwargs): # Call the base implementation first to get the context context = super(SeriesDetailView, self).get_context_data(**kwargs) @@ -167,4 +214,4 @@ class SeriesDetailView(generic.DetailView): books = Book.objects.prefetch_related("tags", "ratings") books = books.filter(series=context["object"].id) context['books'] = sorted(books, key=lambda x: x.title) - return context \ No newline at end of file + return context diff --git a/loadtesting/bench.py b/loadtesting/bench.py new file mode 100644 index 0000000..53218b6 --- /dev/null +++ b/loadtesting/bench.py @@ -0,0 +1,22 @@ +import csv + + +def floatify(mystring): # floatify probable floats + try: + return f"{float(mystring):3.3f}" + + + +results = dict() +with open("calibre_stats.csv", "r") as cfile: + reader = csv.reader(cfile, delimiter=",") + for row in reader: + if not len(row): + continue + results[action] = { + "median" : floatify(row[4]), + "avg" : floatify(row[5]), + "min" : floatify(row[6]), + "max" : floatify(row[7]), + } + diff --git a/loadtesting/calibre_failures.csv b/loadtesting/calibre_failures.csv new file mode 100644 index 0000000..4a9ed13 --- /dev/null +++ b/loadtesting/calibre_failures.csv @@ -0,0 +1,2 @@ +Method,Name,Error,Occurrences +GET,/book/,500 Server Error: Internal Server Error for url: /book/,3 diff --git a/loadtesting/calibre_stats.csv b/loadtesting/calibre_stats.csv new file mode 100644 index 0000000..dda5845 --- /dev/null +++ b/loadtesting/calibre_stats.csv @@ -0,0 +1,20 @@ +Type,Name,Request Count,Failure Count,Median Response Time,Average Response Time,Min Response Time,Max Response Time,Average Content Size,Requests/s,Failures/s,50%,66%,75%,80%,90%,95%,98%,99%,99.9%,99.99%,99.999%,100% +GET,/accounts/login/,20,0,48,65.55451154708862,14.944314956665039,260.83922386169434,2024.0,0.16925718599032696,0.0,53,60,79,82,180,260,260,260,260,260,260,260 +POST,/accounts/login/,20,0,500.0,563.2974982261658,342.44489669799805,988.3866310119629,68954.9,0.16925718599032696,0.0,520,630,680,770,860,990,990,990,990,990,990,990 +GET,/author/,32,0,42,66.19161367416382,20.987987518310547,389.75977897644043,23424.375,0.2708114975845231,0.0,42,55,79,83,110,240,390,390,390,390,390,390 +GET,/authors/,36,0,52,63.24449512693617,13.991117477416992,158.9028835296631,38376.63888888889,0.30466293478258855,0.0,56,70,76,76,99,150,160,160,160,160,160,160 +GET,/book/,91,3,57,71.04945968795609,19.988059997558594,273.82922172546387,31187.23076923077,0.7701201962559876,0.025388577898549043,57,72,87,90,130,170,230,270,270,270,270,270 +GET,/books/,53,0,180.0,190.23628504771108,9.994029998779297,504.6887397766113,68252.92452830188,0.4485315428743664,0.0,180,200,220,240,270,410,480,500,500,500,500,500 +GET,/publisher/,19,0,46,62.593510276392884,22.986173629760742,190.87886810302734,29474.105263157893,0.16079432669081062,0.0,46,60,85,87,150,190,190,190,190,190,190,190 +GET,/publishers/,22,0,41,52.96537009152499,9.990453720092773,127.92515754699707,30564.363636363636,0.18618290458935965,0.0,45,55,71,78,110,110,130,130,130,130,130,130 +GET,/rating/,47,0,56,69.93506817107505,28.981924057006836,293.81442070007324,33666.68085106383,0.39775438707726835,0.0,56,71,89,95,110,140,290,290,290,290,290,290 +GET,/ratings/,9,0,84,76.82164510091145,22.31740951538086,146.39925956726074,30031.0,0.07616573369564714,0.0,84,93,120,130,150,150,150,150,150,150,150,150 +GET,/search/,48,0,25,34.25489366054535,10.997772216796875,106.93764686584473,27502.3125,0.4062172463767847,0.0,25,36,41,43,72,87,110,110,110,110,110,110 +GET,/series/,19,0,27,31.892036136827972,19.97995376586914,63.96055221557617,28473.894736842107,0.16079432669081062,0.0,27,31,37,39,62,64,64,64,64,64,64,64 +GET,/tag/,54,0,49,59.33219415170175,24.969100952148438,120.92876434326172,28703.61111111111,0.45699440217388276,0.0,52,70,85,92,110,110,120,120,120,120,120,120 +GET,/tags/,36,0,48,66.21244218614366,10.992288589477539,373.7668991088867,33423.77777777778,0.30466293478258855,0.0,51,63,71,73,130,180,370,370,370,370,370,370 +GET,search_by_author,48,0,35,49.331208070119224,8.999109268188477,187.8821849822998,27508.25,0.4062172463767847,0.0,35,41,56,62,120,160,190,190,190,190,190,190 +GET,search_by_identifier,58,0,37,46.17996051393706,12.995004653930664,165.89641571044922,27873.603448275862,0.49084583937194814,0.0,38,46,55,59,97,100,120,170,170,170,170,170 +GET,search_by_title,65,0,35,44.79830815241887,10.993003845214844,264.83631134033203,27183.56923076923,0.5500858544685626,0.0,35,41,50,54,61,110,140,260,260,260,260,260 +GET,search_generic,117,0,40,51.21477852519761,9.992837905883789,282.82952308654785,28374.79487179487,0.9901545380434127,0.0,40,52,58,67,83,130,180,200,280,280,280,280 +,Aggregated,794,3,45,77.63347637743433,8.999109268188477,988.3866310119629,32404.799748110832,6.71951028381598,0.025388577898549043,45,62,78,93,160,230,460,610,990,990,990,990 diff --git a/loadtesting/calibre_stats_history.csv b/loadtesting/calibre_stats_history.csv new file mode 100644 index 0000000..eecc5ff --- /dev/null +++ b/loadtesting/calibre_stats_history.csv @@ -0,0 +1,61 @@ +"Timestamp","User Count","Type","Name","Requests/s","Failures/s","50%","66%","75%","80%","90%","95%","98%","99%","99.9%","99.99%","99.999%","100%","Total Request Count","Total Failure Count","Total Median Response Time","Total Average Response Time","Total Min Response Time","Total Max Response Time","Total Average Content Size" +"1594955401","1","","Aggregated",0.00,0.00,"N/A","N/A","N/A","N/A","N/A","N/A","N/A","N/A","N/A","N/A","N/A","N/A",0,0,0,0,0,0,0 +"1594955403","5","","Aggregated",0.00,0.00,30,390,410,410,450,450,450,450,450,450,450,450,12,0,26,152,10,450,19557 +"1594955405","9","","Aggregated",5.00,0.00,31,48,360,380,410,450,470,470,470,470,470,470,25,0,31,135,10,470,25208 +"1594955407","13","","Aggregated",5.75,0.00,48,72,380,390,490,770,780,780,780,780,780,780,40,0,45,169,10,779,29961 +"1594955409","17","","Aggregated",6.17,0.00,64,260,380,410,500,780,860,990,990,990,990,990,57,0,64,210,10,988,31323 +"1594955411","20","","Aggregated",6.38,0.00,60,180,370,410,610,770,860,990,990,990,990,990,79,0,60,198,10,988,31968 +"1594955413","20","","Aggregated",7.00,0.00,56,140,260,370,610,770,860,990,990,990,990,990,92,0,53,182,10,988,32493 +"1594955415","20","","Aggregated",7.90,0.00,55,110,200,260,530,680,860,990,990,990,990,990,107,0,53,166,10,988,32284 +"1594955417","20","","Aggregated",8.00,0.00,51,95,150,180,520,630,860,990,990,990,990,990,119,0,51,153,10,988,32032 +"1594955419","20","","Aggregated",7.90,0.00,43,53,91,110,170,520,630,630,630,630,630,630,133,0,49,143,10,988,32464 +"1594955421","20","","Aggregated",7.70,0.00,43,55,73,85,120,160,190,240,240,240,240,240,147,0,50,136,10,988,32634 +"1594955423","20","","Aggregated",7.50,0.00,41,53,70,75,110,160,190,240,240,240,240,240,158,0,49,129,10,988,32266 +"1594955425","20","","Aggregated",6.40,0.00,39,50,56,70,110,160,170,190,190,190,190,190,173,0,48,123,9,988,32582 +"1594955427","20","","Aggregated",6.40,0.00,43,56,74,87,150,160,170,190,190,190,190,190,185,0,48,120,9,988,33041 +"1594955429","20","","Aggregated",6.60,0.00,41,55,70,78,150,160,160,170,170,170,170,170,199,0,48,115,9,988,32717 +"1594955431","20","","Aggregated",6.60,0.00,38,52,59,70,120,160,160,170,170,170,170,170,211,0,45,111,9,988,32736 +"1594955433","20","","Aggregated",6.30,0.00,38,45,54,59,110,150,160,170,170,170,170,170,224,0,43,106,9,988,32289 +"1594955435","20","","Aggregated",6.50,0.00,42,55,72,85,150,170,170,190,190,190,190,190,235,0,45,106,9,988,32805 +"1594955437","20","","Aggregated",6.40,0.00,38,51,59,70,95,160,170,190,190,190,190,190,246,0,45,104,9,988,32821 +"1594955439","20","","Aggregated",6.10,0.00,36,54,65,71,130,170,190,200,200,200,200,200,260,0,45,101,9,988,32811 +"1594955441","20","","Aggregated",5.90,0.00,47,60,71,72,150,180,190,200,200,200,200,200,272,0,45,99,9,988,32809 +"1594955443","20","","Aggregated",6.10,0.00,52,65,72,81,150,180,190,200,200,200,200,200,284,1,45,97,9,988,32796 +"1594955445","20","","Aggregated",6.30,0.10,49,60,66,72,100,160,200,210,210,210,210,210,297,1,46,96,9,988,32829 +"1594955447","20","","Aggregated",6.10,0.10,51,65,74,92,130,180,200,210,210,210,210,210,310,1,46,95,9,988,32921 +"1594955449","20","","Aggregated",6.00,0.10,46,55,66,80,130,140,190,210,210,210,210,210,326,1,45,92,8,988,32638 +"1594955451","20","","Aggregated",7.00,0.10,44,55,74,83,130,170,190,210,210,210,210,210,339,1,45,91,8,988,32716 +"1594955453","20","","Aggregated",6.80,0.10,42,57,82,88,140,180,190,210,210,210,210,210,356,1,45,90,8,988,32851 +"1594955455","20","","Aggregated",6.70,0.00,41,57,82,88,140,190,190,210,210,210,210,210,370,1,45,89,8,988,33034 +"1594955457","20","","Aggregated",7.00,0.00,38,56,63,82,120,180,190,210,210,210,210,210,386,1,45,88,8,988,32931 +"1594955459","20","","Aggregated",7.30,0.00,45,63,82,88,130,180,190,210,210,210,210,210,399,1,45,87,8,988,32863 +"1594955461","20","","Aggregated",7.00,0.00,45,62,74,93,140,160,190,210,210,210,210,210,414,1,45,86,8,988,32948 +"1594955463","20","","Aggregated",6.70,0.00,57,72,96,110,140,190,220,260,260,260,260,260,429,1,46,87,8,988,33186 +"1594955466","20","","Aggregated",7.30,0.00,58,72,93,110,140,160,220,260,260,260,260,260,441,1,45,85,8,988,33061 +"1594955468","20","","Aggregated",7.20,0.00,52,62,74,110,150,170,220,260,260,260,260,260,455,1,45,85,8,988,33127 +"1594955470","20","","Aggregated",7.10,0.00,47,62,73,93,150,200,220,260,260,260,260,260,472,1,45,83,8,988,32985 +"1594955472","20","","Aggregated",7.10,0.00,41,60,70,73,110,170,220,270,270,270,270,270,484,1,45,84,8,988,33133 +"1594955474","20","","Aggregated",7.60,0.00,39,56,73,78,110,170,220,270,270,270,270,270,498,1,45,82,8,988,32962 +"1594955476","20","","Aggregated",6.80,0.00,41,56,72,77,99,150,220,270,270,270,270,270,512,1,45,81,8,988,32885 +"1594955478","20","","Aggregated",7.10,0.00,43,73,85,92,110,200,240,270,270,270,270,270,525,1,45,82,8,988,32823 +"1594955480","20","","Aggregated",6.90,0.00,50,76,92,97,110,190,240,290,290,290,290,290,539,1,46,82,8,988,32767 +"1594955482","20","","Aggregated",6.70,0.00,58,85,110,110,150,200,280,290,290,290,290,290,553,1,47,83,8,988,32674 +"1594955484","20","","Aggregated",6.80,0.00,66,90,110,110,180,240,280,290,290,290,290,290,565,1,46,82,8,988,32603 +"1594955486","20","","Aggregated",6.60,0.00,70,87,110,110,150,260,290,500,500,500,500,500,578,1,47,82,8,988,32592 +"1594955488","20","","Aggregated",6.50,0.00,65,81,110,130,230,410,480,500,500,500,500,500,591,1,48,84,8,988,32680 +"1594955490","20","","Aggregated",6.50,0.00,70,89,120,150,260,410,480,500,500,500,500,500,600,1,48,84,8,988,32721 +"1594955492","20","","Aggregated",6.10,0.00,60,79,110,160,230,410,480,500,500,500,500,500,613,2,48,84,8,988,32745 +"1594955494","20","","Aggregated",5.80,0.00,60,79,110,160,230,410,480,500,500,500,500,500,625,2,48,83,8,988,32634 +"1594955496","20","","Aggregated",6.10,0.10,54,73,100,150,190,280,410,480,480,480,480,480,636,2,47,83,8,988,32677 +"1594955498","20","","Aggregated",6.00,0.10,49,65,73,86,170,240,280,390,390,390,390,390,649,2,47,83,8,988,32666 +"1594955500","20","","Aggregated",6.00,0.10,49,58,72,73,150,170,240,390,390,390,390,390,660,2,48,82,8,988,32557 +"1594955502","20","","Aggregated",5.90,0.10,47,54,58,65,73,150,240,390,390,390,390,390,679,2,47,81,8,988,32488 +"1594955504","20","","Aggregated",6.60,0.10,42,51,55,58,73,130,150,390,390,390,390,390,691,2,47,80,8,988,32380 +"1594955506","20","","Aggregated",6.50,0.00,45,53,58,71,130,160,190,220,220,220,220,220,708,2,47,81,8,988,32447 +"1594955508","20","","Aggregated",7.00,0.00,45,54,76,87,140,170,190,220,220,220,220,220,719,2,47,81,8,988,32424 +"1594955510","20","","Aggregated",6.70,0.00,45,53,70,84,140,170,190,220,220,220,220,220,733,2,47,80,8,988,32351 +"1594955512","20","","Aggregated",7.10,0.00,43,53,78,90,150,170,190,220,220,220,220,220,745,2,46,79,8,988,32376 +"1594955514","20","","Aggregated",6.70,0.00,40,58,70,87,150,170,190,220,220,220,220,220,761,2,46,78,8,988,32333 +"1594955516","20","","Aggregated",6.80,0.00,34,43,50,53,69,90,170,170,170,170,170,170,773,2,45,78,8,988,32295 +"1594955518","20","","Aggregated",6.40,0.00,34,43,55,62,89,100,170,220,220,220,220,220,787,3,46,78,8,988,32419 +"1594955519","0","","Aggregated",6.40,0.00,35,43,55,64,89,100,170,220,220,220,220,220,794,3,45,77,8,988,32404 diff --git a/loadtesting/locust.conf b/loadtesting/locust.conf new file mode 100644 index 0000000..ada3077 --- /dev/null +++ b/loadtesting/locust.conf @@ -0,0 +1,9 @@ +locustfile = locustfile.py +expect-workers = 50 +host = http://127.0.0.1:8000 +users = 20 +hatch-rate = 2 +run-time = 2m +headless = true +csv=calibre +only-summary=true diff --git a/loadtesting/locustfile.py b/loadtesting/locustfile.py new file mode 100644 index 0000000..f837969 --- /dev/null +++ b/loadtesting/locustfile.py @@ -0,0 +1,184 @@ +from locust import HttpUser, task, between +import random +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import json + +# -------------------------------- fetching data to test with + +with open("./../CalibreWebCompanion/settings.json", "r") as jfile: + calpath = json.load(jfile)["CALIBRE_DIR"] + "\\metadata.db" + +engine = create_engine(f'sqlite:///{calpath}') +Base = declarative_base(engine) + + +class Author(Base): # needed + """""" + __tablename__ = 'authors' + __table_args__ = {'autoload': True} + # has int id, text name, sort + + +class Identifier(Base): # needed + """""" + __tablename__ = 'identifiers' + __table_args__ = {'autoload': True} + # has int id, int book, text value + + +class Publisher(Base): # needed + """""" + __tablename__ = 'publishers' + __table_args__ = {'autoload': True} + # has int id, text name + + +class Rating(Base): # needed + """""" + __tablename__ = 'ratings' + __table_args__ = {'autoload': True} + # has int id, int rating + + +class Series(Base): # needed + """""" + __tablename__ = 'series' + __table_args__ = {'autoload': True} + # has int id, text name + + +class Tag(Base): # needed + """""" + __tablename__ = 'tags' + __table_args__ = {'autoload': True} + # has int id, text name + + +class Book(Base): # needed + """""" + __tablename__ = 'books' + __table_args__ = {'autoload': True} + # has int id, text title, text sort, time timestamp, time pubdate, + # float series_index, text path + + +def loadSession(): + """""" + metadata = Base.metadata + Session = sessionmaker(bind=engine) + session = Session() + return session + + +session = loadSession() + +titles = [i.title for i in session.query(Book).all()] +authors = [i.name for i in session.query(Author).all()] +identifiers = [i.val for i in session.query(Identifier).all()] +book_ids = [i.id for i in session.query(Book).all()] +author_ids = [i.id for i in session.query(Author).all()] +publisher_ids = [i.id for i in session.query(Publisher).all()] +rating_ids = [i.id for i in session.query(Rating).all()] +series_ids = [i.id for i in session.query(Series).all()] +tag_ids = [i.id for i in session.query(Tag).all()] + + +def randlist(mylist): + return mylist[random.randint(0, len(mylist) - 1)] + + +class UserBehavior(HttpUser): + wait_time = between(1, 5) + + def on_start(self): + """ on_start is called when a Locust start before any task is scheduled """ + r = self.client.get('/accounts/login/') + self.client.headers['Referer'] = self.client.base_url + n = random.randint(0, 5) + self.client.post('/accounts/login/', + { + "username": f"performance{n}", + "password": "profiling1234", + 'csrfmiddlewaretoken': r.cookies["csrftoken"] + }) + + @task(10) + def search_by_title(self): + title = randlist(titles) + self.client.get(f"/results/?title={title}", name="search_by_title") + + @task(10) + def booklist(self): + self.client.get("/books/") + + @task(15) + def bookdetail(self): + pk = randlist(book_ids) + self.client.get(f"/book/{pk}", name="/book/") + + @task(10) + def search_by_author(self): + author = randlist(authors) + self.client.get(f"/results/?author={author}", name="search_by_author") + + @task(6) + def authorlist(self): + self.client.get("/authors/") + + @task(5) + def authordetail(self): + pk = randlist(author_ids) + self.client.get(f"/author/{pk}", name="/author/") + + @task(10) + def search_by_id(self): + id_ = randlist(identifiers) + self.client.get(f"/results/?identifier={id_}", name="search_by_identifier") + + @task(20) + def search_generic(self): + t = random.randint(0, 3) + if not t: + term = randlist(titles) + elif t == 1: + term = randlist(authors) + else: + term = randlist(identifiers) + self.client.get(f"/results/?generic={term}", name="search_generic") + + @task(8) + def searchbad(self): + self.client.get("/search/") + + @task(1) + def ratingslist(self): + self.client.get("/ratings/") + + @task(10) + def ratingdetail(self): + pk = randlist(rating_ids) + self.client.get(f"/rating/{pk}", name="/rating/") + + @task(5) + def taglist(self): + self.client.get("/tags/") + + @task(10) + def tagdetail(self): + pk = randlist(tag_ids) + self.client.get(f"/tag/{pk}", name="/tag/") + + @task(4) + def serieslist(self): + self.client.get("/series/") + + @task(4) + def publisherlist(self): + self.client.get("/publishers/") + + @task(4) + def publisherdetail(self): + pk = randlist(publisher_ids) + self.client.get(f"/publisher/{pk}", name="/publisher/") diff --git a/requirements.txt b/requirements.txt index c8c0098..e98314c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ django>=3.0.8 # development django-debug-toolbar>=2.2 django-silk>=4.0 -locust>=1.1 \ No newline at end of file +locust>=1.1 +sqlalchemy>=1.3.15 \ No newline at end of file