production-request 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. docs/source/conf.py +290 -0
  2. production_request/__init__.py +13 -0
  3. production_request/api.py +297 -0
  4. production_request/base.py +61 -0
  5. production_request/celery_handlers.py +58 -0
  6. production_request/enums.py +51 -0
  7. production_request/kibana.py +398 -0
  8. production_request/kibana_templates/00e3358b-2ad5-4105-a8f8-f78876c18b1b.json +164 -0
  9. production_request/kibana_templates/042005bc-1ab4-4139-9582-6ca1e08fb67d.json +117 -0
  10. production_request/kibana_templates/0ac8157f-70c5-404f-a219-5d7047a41aa6.json +108 -0
  11. production_request/kibana_templates/14bb8fb2-849a-4d88-81fc-9b1e221d374c.json +104 -0
  12. production_request/kibana_templates/339465aa-d599-47c9-a012-0a54f7371592.json +103 -0
  13. production_request/kibana_templates/3a512643-ea85-463d-b408-e2a527030178.json +192 -0
  14. production_request/kibana_templates/4dc730e6-5b8a-4946-b71d-b813b9db66df.json +102 -0
  15. production_request/kibana_templates/51e1b7b6-54d0-40b1-a3af-d3a453978643.json +103 -0
  16. production_request/kibana_templates/584cdda6-291b-4402-b7f1-2de3ba15f896.json +103 -0
  17. production_request/kibana_templates/64be2e2b-7009-4e79-b1bc-de7eda3bb7e7.json +116 -0
  18. production_request/kibana_templates/6b79ba24-7a52-4db1-93c1-3317d45b397f.json +57 -0
  19. production_request/kibana_templates/6f09dbaf-1fb8-457e-94ff-75e0efa71e6f.json +192 -0
  20. production_request/kibana_templates/77d1d261-5025-4f00-822e-ecce3a61886b.json +192 -0
  21. production_request/kibana_templates/841f8f54-2011-42d0-ae6b-c7cfd2586135.json +43 -0
  22. production_request/kibana_templates/84eb1ad1-72ce-481d-844f-94bf2f6029d3.json +103 -0
  23. production_request/kibana_templates/8c2d2451-7732-4bde-9fab-b45772311c02.json +106 -0
  24. production_request/kibana_templates/929f45e1-f584-40c5-9150-7ec864105243.json +116 -0
  25. production_request/kibana_templates/934140d7-538d-4cd3-9ca6-900eb3b3b459.json +116 -0
  26. production_request/kibana_templates/96d1a6b7-d47d-4002-9956-dc8a599d99c4.json +103 -0
  27. production_request/kibana_templates/a143eecb-30cf-4469-9510-c9842d0d15cd.json +108 -0
  28. production_request/kibana_templates/a7901d1d-8478-41ec-83ae-fdadb35095b5.json +108 -0
  29. production_request/kibana_templates/b3e3060d-e4da-4649-99f9-9c427ef58b10.json +57 -0
  30. production_request/kibana_templates/bcbf9911-e0bb-4609-8057-94f3d0e38e7b.json +103 -0
  31. production_request/kibana_templates/c7d15a8b-9854-4331-82b8-9c06e6725f56.json +108 -0
  32. production_request/kibana_templates/c9169a10-06a0-4ec9-bd17-53ce822275d7.json +153 -0
  33. production_request/kibana_templates/c9579300-eb07-4255-9c2b-f6a3680e6761.json +117 -0
  34. production_request/kibana_templates/d5cc5991-e2d1-4136-98a6-fc3363be947b.json +107 -0
  35. production_request/kibana_templates/dashboard.json +16 -0
  36. production_request/kibana_templates/dashboard_item.json +16 -0
  37. production_request/kibana_templates/f8e6099c-5a10-4102-875e-170f90ec5882.json +153 -0
  38. production_request/kibana_templates/fc79a5af-187e-413f-8a92-9957935ea126.json +108 -0
  39. production_request/kibana_templates/visualization.json +17 -0
  40. production_request/loggers.py +12 -0
  41. production_request/middleware.py +98 -0
  42. production_request/processes.py +62 -0
  43. production_request/static/js/production_request_client.js +147 -0
  44. production_request/static/js/xhr_interceptor.js +125 -0
  45. production_request/urls.py +12 -0
  46. production_request/utils.py +284 -0
  47. production_request/views.py +36 -0
  48. production_request-0.9.0.dist-info/METADATA +72 -0
  49. production_request-0.9.0.dist-info/RECORD +52 -0
  50. production_request-0.9.0.dist-info/WHEEL +5 -0
  51. production_request-0.9.0.dist-info/licenses/LICENSE +30 -0
  52. production_request-0.9.0.dist-info/top_level.txt +2 -0
docs/source/conf.py ADDED
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # production_request documentation build configuration file, created by
5
+ # cookiecutter pipproject
6
+ #
7
+ # This file is execfile()d with the current directory set to its
8
+ # containing dir.
9
+ #
10
+ # Note that not all possible configuration values are present in this
11
+ # autogenerated file.
12
+ #
13
+ # All configuration values have a default; values that are commented out
14
+ # serve to show the default.
15
+
16
+ import os
17
+ import sys
18
+
19
+
20
+ # If extensions (or modules to document with autodoc) are in another directory,
21
+ # add these directories to sys.path here. If the directory is relative to the
22
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
23
+ sys.path.insert(0, os.path.abspath('../..'))
24
+
25
+ # -- General configuration ------------------------------------------------
26
+
27
+ # If your documentation needs a minimal Sphinx version, state it here.
28
+ #needs_sphinx = '1.0'
29
+
30
+ # Add any Sphinx extension module names here, as strings. They can be
31
+ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
32
+ # ones.
33
+ extensions = [
34
+ 'sphinx.ext.autodoc',
35
+ ]
36
+
37
+ # Add any paths that contain templates here, relative to this directory.
38
+ templates_path = ['_templates']
39
+
40
+ # The suffix(es) of source filenames.
41
+ # You can specify multiple suffix as a list of string:
42
+ # source_suffix = ['.rst', '.md']
43
+ source_suffix = '.rst'
44
+
45
+ # The encoding of source files.
46
+ #source_encoding = 'utf-8-sig'
47
+
48
+ # The master toctree document.
49
+ master_doc = 'index'
50
+
51
+ # General information about the project.
52
+ project = 'production_request'
53
+ copyright = '2016, Dmitriy Chernyshov'
54
+ author = 'Dmitriy Chernyshov'
55
+
56
+ # The version info for the project you're documenting, acts as replacement for
57
+ # |version| and |release|, also used in various other places throughout the
58
+ # built documents.
59
+ #
60
+ # The short X.Y version.
61
+ version = '0.0.1'
62
+ # The full version, including alpha/beta/rc tags.
63
+ release = '0.0.1'
64
+
65
+ # The language for content autogenerated by Sphinx. Refer to documentation
66
+ # for a list of supported languages.
67
+ #
68
+ # This is also used if you do content translation via gettext catalogs.
69
+ # Usually you set "language" from the command line for these cases.
70
+ language = None
71
+
72
+ # There are two options for replacing |today|: either, you set today to some
73
+ # non-false value, then it is used:
74
+ #today = ''
75
+ # Else, today_fmt is used as the format for a strftime call.
76
+ #today_fmt = '%B %d, %Y'
77
+
78
+ # List of patterns, relative to source directory, that match files and
79
+ # directories to ignore when looking for source files.
80
+ # This patterns also effect to html_static_path and html_extra_path
81
+ exclude_patterns = []
82
+
83
+ # The reST default role (used for this markup: `text`) to use for all
84
+ # documents.
85
+ #default_role = None
86
+
87
+ # If true, '()' will be appended to :func: etc. cross-reference text.
88
+ #add_function_parentheses = True
89
+
90
+ # If true, the current module name will be prepended to all description
91
+ # unit titles (such as .. function::).
92
+ #add_module_names = True
93
+
94
+ # If true, sectionauthor and moduleauthor directives will be shown in the
95
+ # output. They are ignored by default.
96
+ #show_authors = False
97
+
98
+ # The name of the Pygments (syntax highlighting) style to use.
99
+ pygments_style = 'sphinx'
100
+
101
+ # A list of ignored prefixes for module index sorting.
102
+ #modindex_common_prefix = []
103
+
104
+ # If true, keep warnings as "system message" paragraphs in the built documents.
105
+ #keep_warnings = False
106
+
107
+ # If true, `todo` and `todoList` produce output, else they produce nothing.
108
+ todo_include_todos = False
109
+
110
+
111
+ # -- Options for HTML output ----------------------------------------------
112
+
113
+ # The theme to use for HTML and HTML Help pages. See the documentation for
114
+ # a list of builtin themes.
115
+ html_theme = 'sphinx_rtd_theme'
116
+
117
+ # Theme options are theme-specific and customize the look and feel of a theme
118
+ # further. For a list of options available for each theme, see the
119
+ # documentation.
120
+ #html_theme_options = {}
121
+
122
+ # Add any paths that contain custom themes here, relative to this directory.
123
+ #html_theme_path = []
124
+
125
+ # The name for this set of Sphinx documents.
126
+ # "<project> v<release> documentation" by default.
127
+ #html_title = 'production_request v0.0.1'
128
+
129
+ # A shorter title for the navigation bar. Default is the same as html_title.
130
+ #html_short_title = None
131
+
132
+ # The name of an image file (relative to this directory) to place at the top
133
+ # of the sidebar.
134
+ #html_logo = None
135
+
136
+ # The name of an image file (relative to this directory) to use as a favicon of
137
+ # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
138
+ # pixels large.
139
+ #html_favicon = None
140
+
141
+ # Add any paths that contain custom static files (such as style sheets) here,
142
+ # relative to this directory. They are copied after the builtin static files,
143
+ # so a file named "default.css" will overwrite the builtin "default.css".
144
+ html_static_path = ['_static']
145
+
146
+ # Add any extra paths that contain custom files (such as robots.txt or
147
+ # .htaccess) here, relative to this directory. These files are copied
148
+ # directly to the root of the documentation.
149
+ #html_extra_path = []
150
+
151
+ # If not None, a 'Last updated on:' timestamp is inserted at every page
152
+ # bottom, using the given strftime format.
153
+ # The empty string is equivalent to '%b %d, %Y'.
154
+ #html_last_updated_fmt = None
155
+
156
+ # If true, SmartyPants will be used to convert quotes and dashes to
157
+ # typographically correct entities.
158
+ #html_use_smartypants = True
159
+
160
+ # Custom sidebar templates, maps document names to template names.
161
+ #html_sidebars = {}
162
+
163
+ # Additional templates that should be rendered to pages, maps page names to
164
+ # template names.
165
+ #html_additional_pages = {}
166
+
167
+ # If false, no module index is generated.
168
+ #html_domain_indices = True
169
+
170
+ # If false, no index is generated.
171
+ #html_use_index = True
172
+
173
+ # If true, the index is split into individual pages for each letter.
174
+ #html_split_index = False
175
+
176
+ # If true, links to the reST sources are added to the pages.
177
+ #html_show_sourcelink = True
178
+
179
+ # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
180
+ #html_show_sphinx = True
181
+
182
+ # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
183
+ #html_show_copyright = True
184
+
185
+ # If true, an OpenSearch description file will be output, and all pages will
186
+ # contain a <link> tag referring to it. The value of this option must be the
187
+ # base URL from which the finished HTML is served.
188
+ #html_use_opensearch = ''
189
+
190
+ # This is the file name suffix for HTML files (e.g. ".xhtml").
191
+ #html_file_suffix = None
192
+
193
+ # Language to be used for generating the HTML full-text search index.
194
+ # Sphinx supports the following languages:
195
+ # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
196
+ # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
197
+ #html_search_language = 'en'
198
+
199
+ # A dictionary with options for the search language support, empty by default.
200
+ # 'ja' uses this config value.
201
+ # 'zh' user can custom change `jieba` dictionary path.
202
+ #html_search_options = {'type': 'default'}
203
+
204
+ # The name of a javascript file (relative to the configuration directory) that
205
+ # implements a search results scorer. If empty, the default will be used.
206
+ #html_search_scorer = 'scorer.js'
207
+
208
+ # Output file base name for HTML help builder.
209
+ htmlhelp_basename = 'production_requestdoc'
210
+
211
+ # -- Options for LaTeX output ---------------------------------------------
212
+
213
+ latex_elements = {
214
+ # The paper size ('letterpaper' or 'a4paper').
215
+ #'papersize': 'letterpaper',
216
+
217
+ # The font size ('10pt', '11pt' or '12pt').
218
+ #'pointsize': '10pt',
219
+
220
+ # Additional stuff for the LaTeX preamble.
221
+ #'preamble': '',
222
+
223
+ # Latex figure (float) alignment
224
+ #'figure_align': 'htbp',
225
+ }
226
+
227
+ # Grouping the document tree into LaTeX files. List of tuples
228
+ # (source start file, target name, title,
229
+ # author, documentclass [howto, manual, or own class]).
230
+ latex_documents = [
231
+ (master_doc, 'production_request.tex', 'production_request Documentation',
232
+ 'Dmitriy Chernyshov', 'manual'),
233
+ ]
234
+
235
+ # The name of an image file (relative to this directory) to place at the top of
236
+ # the title page.
237
+ #latex_logo = None
238
+
239
+ # For "manual" documents, if this is true, then toplevel headings are parts,
240
+ # not chapters.
241
+ #latex_use_parts = False
242
+
243
+ # If true, show page references after internal links.
244
+ #latex_show_pagerefs = False
245
+
246
+ # If true, show URL addresses after external links.
247
+ #latex_show_urls = False
248
+
249
+ # Documents to append as an appendix to all manuals.
250
+ #latex_appendices = []
251
+
252
+ # If false, no module index is generated.
253
+ #latex_domain_indices = True
254
+
255
+
256
+ # -- Options for manual page output ---------------------------------------
257
+
258
+ # One entry per manual page. List of tuples
259
+ # (source start file, name, description, authors, manual section).
260
+ man_pages = [
261
+ (master_doc, 'production_request', 'production_request Documentation',
262
+ [author], 1)
263
+ ]
264
+
265
+ # If true, show URL addresses after external links.
266
+ #man_show_urls = False
267
+
268
+
269
+ # -- Options for Texinfo output -------------------------------------------
270
+
271
+ # Grouping the document tree into Texinfo files. List of tuples
272
+ # (source start file, target name, title, author,
273
+ # dir menu entry, description, category)
274
+ texinfo_documents = [
275
+ (master_doc, 'production_request', 'production_request Documentation',
276
+ author, 'production_request', 'One line description of project.',
277
+ 'Miscellaneous'),
278
+ ]
279
+
280
+ # Documents to append as an appendix to all manuals.
281
+ #texinfo_appendices = []
282
+
283
+ # If false, no module index is generated.
284
+ #texinfo_domain_indices = True
285
+
286
+ # How to display URL addresses: 'footnote', 'no', or 'inline'.
287
+ #texinfo_show_urls = 'footnote'
288
+
289
+ # If true, do not generate a @detailmenu in the "Top" node's menu.
290
+ #texinfo_no_detailmenu = False
@@ -0,0 +1,13 @@
1
+ # -*- coding: utf-8 -*-
2
+ from importlib.metadata import (
3
+ PackageNotFoundError,
4
+ version,
5
+ )
6
+
7
+
8
+ try:
9
+ # Change here if project is renamed and does not equal the package name
10
+ dist_name = __name__
11
+ __version__ = version(dist_name)
12
+ except PackageNotFoundError:
13
+ __version__ = 'unknown'
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ import json
4
+ import logging
5
+ import os
6
+ import socket
7
+ from datetime import (
8
+ datetime,
9
+ )
10
+
11
+ from django.conf import (
12
+ settings,
13
+ )
14
+ from psutil import (
15
+ Process,
16
+ )
17
+
18
+ from production_request.enums import (
19
+ SqlOperationEnum,
20
+ )
21
+ from production_request.utils import (
22
+ CUSTOM_ATTR,
23
+ SimpleRequest,
24
+ clear_thread_data,
25
+ generate_uuid,
26
+ get_client_ip,
27
+ get_custom_attrs_from_thread_data,
28
+ get_thread_data,
29
+ ms_from_timedelta,
30
+ register_logger,
31
+ to_MB,
32
+ )
33
+
34
+
35
+ PRODUCTION_REQUEST_LOGGER = register_logger(
36
+ 'production_request',
37
+ formatter=logging.Formatter("%(message)s"))
38
+
39
+ PRODUCTION_REQUEST_ENTER_LOGGER = register_logger(
40
+ 'production_request_enter',
41
+ formatter=logging.Formatter("%(message)s"))
42
+
43
+
44
+ class ProductionLogger(object):
45
+ def __init__(self, uuid_):
46
+ super(ProductionLogger, self).__init__()
47
+ self.uuid = uuid_
48
+
49
+ def log_start(self, request):
50
+ """Начинает процесс логирования объекта"""
51
+ request.production_request_uuid = self.uuid
52
+
53
+ if settings.PRODUCTION_REQUEST:
54
+ thread_data = get_thread_data()
55
+ thread_data.production_request_uuid = self.uuid
56
+ thread_data.production_request_original_path = self.get_path(request)
57
+
58
+ request.started_at = datetime.now()
59
+
60
+ if settings.PRODUCTION_REQUEST_LOG_MEMORY:
61
+ memory = self.get_memory_info()
62
+
63
+ request.production_request_uss = memory.uss
64
+ request.production_request_pss = memory.pss
65
+ request.production_request_swap = memory.swap
66
+ request.production_request_rss = memory.rss
67
+
68
+ if settings.PRODUCTION_REQUEST_LOG_ENTER:
69
+ self.log_enter(self.uuid, self.get_path(request))
70
+
71
+ def log_end(self, request, is_success):
72
+ """Логирует параметры запроса и результаты обработки"""
73
+ row = None
74
+
75
+ if settings.PRODUCTION_REQUEST:
76
+ try:
77
+ thread_data = get_thread_data()
78
+
79
+ process_request_time = ms_from_timedelta(
80
+ datetime.now() - request.started_at
81
+ )
82
+ process_sql_time = thread_data.sql_time.pop(
83
+ request.production_request_uuid, float())
84
+ sync_sql_total = '{:.4f}'.format(process_sql_time)
85
+
86
+ process_async_sql_time = thread_data.async_sql_time_total.pop(
87
+ request.production_request_uuid, float()
88
+ )
89
+ async_sql_total = '{:.4f}'.format(process_async_sql_time)
90
+
91
+ sql_total = '{:.4f}'.format(process_sql_time + process_async_sql_time)
92
+
93
+ sync_sql_count = thread_data.sql_count.pop(
94
+ request.production_request_uuid, 0)
95
+
96
+ async_sql_count = thread_data.async_sql_count.pop(
97
+ request.production_request_uuid, 0)
98
+
99
+ sql_duplicate_count = sync_sql_count - len(
100
+ thread_data.sql_uniq_hashes.pop(
101
+ request.production_request_uuid,
102
+ set()
103
+ )
104
+ )
105
+
106
+ user = getattr(request, 'user', None)
107
+ user_id = user.id if user else ''
108
+
109
+ path = self.get_path(request, thread_data)
110
+
111
+ if hasattr(thread_data, 'transaction_count'):
112
+ transaction_count = thread_data.transaction_count[
113
+ request.production_request_uuid]
114
+ else:
115
+ transaction_count = 0
116
+
117
+ if hasattr(thread_data, 'transaction_total'):
118
+ transaction_total = thread_data.transaction_total[
119
+ request.production_request_uuid]
120
+ else:
121
+ transaction_total = 0.0
122
+
123
+ db_aliases = ', '.join(sorted(thread_data.sql_db_aliases))
124
+
125
+ row = {
126
+ 'uuid': self.uuid,
127
+ 'is_success': json.dumps(is_success),
128
+ 'started': str(request.started_at),
129
+ 'path': path,
130
+ 'user': str(user_id or ''),
131
+ 'hostname': socket.gethostname(),
132
+ 'total': '{:.4f}'.format(process_request_time),
133
+ 'sql_total': sql_total,
134
+ 'sync_sql_total': sync_sql_total,
135
+ 'async_sql_total': async_sql_total,
136
+ 'sql_count': str(sync_sql_count + async_sql_count),
137
+ 'sync_sql_count': str(sync_sql_count),
138
+ 'async_sql_count': str(async_sql_count),
139
+ 'sql_duplicate_count': str(sql_duplicate_count),
140
+ 'sql_transaction_count': str(transaction_count),
141
+ 'sql_transaction_total': '{:.4f}'.format(transaction_total),
142
+ 'sql_db_aliases': db_aliases,
143
+ 'client_ip': get_client_ip(request),
144
+ 'pid': str(os.getpid()),
145
+ }
146
+
147
+ # количество и время sql-запросов в разрезе типов
148
+ for operator in SqlOperationEnum.values:
149
+ attrs = ['sync_sql_{}_count'.format(operator)]
150
+ attrs.extend(['async_sql_{}_count'.format(operator)])
151
+
152
+ if operator in SqlOperationEnum.operations:
153
+ attrs.append('sync_sql_{}_total'.format(operator))
154
+
155
+ for operator_attr in attrs:
156
+ counter = getattr(
157
+ thread_data,
158
+ operator_attr,
159
+ {}
160
+ )
161
+ _value = counter.pop(
162
+ request.production_request_uuid,
163
+ 0
164
+ )
165
+ if isinstance(_value, float):
166
+ _value = '{:.4f}'.format(_value)
167
+ else:
168
+ _value = str(_value)
169
+
170
+ row[operator_attr] = _value
171
+
172
+ # добавляем кастомные значения к общему результату
173
+ row[CUSTOM_ATTR] = []
174
+ for attr, value in get_custom_attrs_from_thread_data():
175
+ key = CUSTOM_ATTR + '_' + attr
176
+ row[CUSTOM_ATTR].append(key)
177
+ row[key] = str(value)
178
+
179
+ if settings.PRODUCTION_REQUEST_LOG_MEMORY:
180
+ memory = self.get_memory_info()
181
+
182
+ row['uss'] = '{:.2f}'.format(
183
+ self.get_memory_diff(
184
+ request.production_request_uss,
185
+ memory.uss
186
+ )
187
+ )
188
+ row['pss'] = '{:.2f}'.format(
189
+ self.get_memory_diff(
190
+ request.production_request_pss,
191
+ memory.pss
192
+ )
193
+ )
194
+ row['swap'] = '{:.2f}'.format(
195
+ self.get_memory_diff(
196
+ request.production_request_swap,
197
+ memory.swap
198
+ )
199
+ )
200
+ row['rss'] = '{:.2f}'.format(
201
+ self.get_memory_diff(
202
+ request.production_request_rss,
203
+ memory.rss
204
+ )
205
+ )
206
+
207
+ if settings.PRODUCTION_REQUEST_LOG_SERVER:
208
+ PRODUCTION_REQUEST_LOGGER.info(
209
+ json.dumps(row)
210
+ )
211
+ except Exception as err:
212
+ if settings.PRODUCTION_REQUEST_LOG_SERVER:
213
+ PRODUCTION_REQUEST_LOGGER.error(
214
+ json.dumps(
215
+ {
216
+ 'error': str(err),
217
+ }
218
+ )
219
+ )
220
+
221
+ if settings.PRODUCTION_REQUEST_LOG_ENTER:
222
+ self.log_exit(self.uuid, self.get_path(request))
223
+
224
+ clear_thread_data()
225
+
226
+ return row
227
+
228
+ def get_path(self, request, thread_data=None):
229
+ """Возвращает параметр path."""
230
+
231
+ if hasattr(thread_data, 'production_request_original_path'):
232
+ path = thread_data.production_request_original_path
233
+ elif hasattr(request, 'path'):
234
+ path = request.path
235
+ else:
236
+ path = getattr(request, 'name', '')
237
+
238
+ return path
239
+
240
+ def get_memory_info(self):
241
+ """Возвращает данные о памяти процесса"""
242
+ return Process(os.getpid()).memory_full_info()
243
+
244
+ def get_memory_diff(self, m_begin, m_end):
245
+ """Возвращает разницу размера памяти в МБ"""
246
+ return to_MB((m_end - m_begin))
247
+
248
+ def log_enter(self, request_uuid, request_path):
249
+ """Логирует момент входа объекта в приложение"""
250
+ self.log_state('enter', request_uuid, request_path)
251
+
252
+ def log_exit(self, request_uuid, request_path):
253
+ """Логирует момент выхода запроса в приложение"""
254
+ self.log_state('exit', request_uuid, request_path)
255
+
256
+ def log_state(self, state, request_uuid, request_path):
257
+ """Логирует определенный момент запроса"""
258
+ PRODUCTION_REQUEST_ENTER_LOGGER.info(
259
+ json.dumps(
260
+ {
261
+ 'uuid': request_uuid,
262
+ 'path': request_path,
263
+ 'state': state,
264
+ 'pid': os.getpid(),
265
+ 'timestamp': str(datetime.now()),
266
+ }
267
+ )
268
+ )
269
+
270
+
271
+ def production_request(path=''):
272
+
273
+ def decorator(func):
274
+
275
+ def wrapper(*args, **kwargs):
276
+ request = SimpleRequest(path=path)
277
+ logger = ProductionLogger(generate_uuid())
278
+
279
+ logger.log_start(request)
280
+ is_success = True
281
+ try:
282
+ result = func(*args, **kwargs)
283
+ except:
284
+ is_success = False
285
+ raise
286
+ finally:
287
+ logger.log_end(request, is_success)
288
+
289
+ return result
290
+
291
+ return wrapper
292
+
293
+ return decorator
294
+
295
+
296
+
297
+
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ import django
4
+
5
+
6
+ try:
7
+ from django.db.backends import (
8
+ util as utils,
9
+ )
10
+ except ImportError:
11
+ from django.db.backends import (
12
+ utils,
13
+ )
14
+
15
+
16
+ if django.VERSION >= (3, 0):
17
+ from django.db.backends.postgresql import (
18
+ base,
19
+ )
20
+ else:
21
+ from django.db.backends.postgresql_psycopg2 import (
22
+ base,
23
+ )
24
+
25
+ from production_request.utils import (
26
+ calc_sql_metrics,
27
+ calc_transaction_metrics,
28
+ )
29
+
30
+
31
+ class CursorWrapper(utils.CursorWrapper):
32
+ """
33
+ Враппер курсора БД для подсчета времени выполнения запроса
34
+ """
35
+ def execute(self, sql, params=None):
36
+ with calc_sql_metrics(self, sql):
37
+ return self.cursor.execute(sql, params)
38
+
39
+ def executemany(self, sql, param_list):
40
+ with calc_sql_metrics(self, sql):
41
+ return self.cursor.executemany(sql, param_list)
42
+
43
+
44
+ class DatabaseWrapper(base.DatabaseWrapper):
45
+ """
46
+ Враппер БД с кастомным курсором
47
+ """
48
+ def cursor(self):
49
+ if hasattr(self, 'validate_thread_sharing'):
50
+ self.validate_thread_sharing()
51
+ cursor = CursorWrapper(self._cursor(), self)
52
+ return cursor
53
+
54
+ def _commit(self):
55
+ with calc_sql_metrics(self.cursor(), 'COMMIT'):
56
+ return super(DatabaseWrapper, self)._commit()
57
+
58
+ def _set_autocommit(self, autocommit):
59
+ with calc_transaction_metrics(autocommit):
60
+ super(DatabaseWrapper, self)._set_autocommit(autocommit)
61
+