profiling-explorer 1.2.0__tar.gz → 1.3.0__tar.gz
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.
- {profiling_explorer-1.2.0/src/profiling_explorer.egg-info → profiling_explorer-1.3.0}/PKG-INFO +32 -13
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/README.rst +21 -3
- profiling_explorer-1.3.0/pyproject.toml +124 -0
- profiling_explorer-1.2.0/pyproject.toml → profiling_explorer-1.3.0/pyproject.toml.orig +15 -14
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/static/script.js +38 -1
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/static/styles.css +17 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/templates/_row.html +2 -1
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/templates/callers_callees.html +1 -1
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/urls.py +1 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/views.py +63 -16
- profiling_explorer-1.2.0/CHANGELOG.rst +0 -50
- profiling_explorer-1.2.0/MANIFEST.in +0 -8
- profiling_explorer-1.2.0/PKG-INFO +0 -155
- profiling_explorer-1.2.0/setup.cfg +0 -4
- profiling_explorer-1.2.0/src/profiling_explorer.egg-info/SOURCES.txt +0 -29
- profiling_explorer-1.2.0/src/profiling_explorer.egg-info/dependency_links.txt +0 -1
- profiling_explorer-1.2.0/src/profiling_explorer.egg-info/entry_points.txt +0 -2
- profiling_explorer-1.2.0/src/profiling_explorer.egg-info/requires.txt +0 -1
- profiling_explorer-1.2.0/src/profiling_explorer.egg-info/top_level.txt +0 -1
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/LICENSE +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/__init__.py +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/__main__.py +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/main.py +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/management/__init__.py +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/management/commands/__init__.py +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/management/commands/runserver.py +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/py.typed +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/settings.py +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/templates/base.html +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/templates/index.html +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/templatetags/__init__.py +0 -0
- {profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/templatetags/profiling_explorer_tags.py +0 -0
{profiling_explorer-1.2.0/src/profiling_explorer.egg-info → profiling_explorer-1.3.0}/PKG-INFO
RENAMED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: profiling-explorer
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Table-based exploration tool for Python profiling data (pstats files).
|
|
5
|
+
Keywords: profiler,profiling,pstats
|
|
6
|
+
Author: Adam Johnson
|
|
5
7
|
Author-email: Adam Johnson <me@adamj.eu>
|
|
6
8
|
License-Expression: MIT
|
|
7
|
-
|
|
8
|
-
Project-URL: Funding, https://adamj.eu/books/
|
|
9
|
-
Project-URL: Repository, https://github.com/adamchainz/profiling-explorer
|
|
10
|
-
Keywords: profiler,profiling,pstats
|
|
9
|
+
License-File: LICENSE
|
|
11
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
11
|
Classifier: Intended Audience :: Developers
|
|
13
12
|
Classifier: Natural Language :: English
|
|
14
13
|
Classifier: Operating System :: OS Independent
|
|
15
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
21
20
|
Classifier: Typing :: Typed
|
|
22
|
-
Requires-Python: >=3.10
|
|
23
|
-
Description-Content-Type: text/x-rst
|
|
24
|
-
License-File: LICENSE
|
|
25
21
|
Requires-Dist: django>=5.2
|
|
26
|
-
|
|
22
|
+
Requires-Dist: launch-editor-py>=1
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Project-URL: Changelog, https://github.com/adamchainz/profiling-explorer/blob/main/CHANGELOG.rst
|
|
25
|
+
Project-URL: Funding, https://adamj.eu/books/
|
|
26
|
+
Project-URL: Repository, https://github.com/adamchainz/profiling-explorer
|
|
27
|
+
Description-Content-Type: text/x-rst
|
|
27
28
|
|
|
28
29
|
==================
|
|
29
30
|
profiling-explorer
|
|
@@ -69,7 +70,7 @@ Read more in `the introductory blog post <https://adamj.eu/tech/2026/04/03/pytho
|
|
|
69
70
|
Requirements
|
|
70
71
|
------------
|
|
71
72
|
|
|
72
|
-
Python 3.
|
|
73
|
+
Python 3.11 to 3.15 supported.
|
|
73
74
|
|
|
74
75
|
Installation
|
|
75
76
|
------------
|
|
@@ -110,15 +111,33 @@ Then run ``profiling-explorer`` with the generated file:
|
|
|
110
111
|
The report will open in your web browser, and you can explore the profile data with the interactive interface.
|
|
111
112
|
Features:
|
|
112
113
|
|
|
113
|
-
* Click the **calls**, **internal ms**,
|
|
114
|
+
* Click the **calls**, **internal ms**, **cumulative ms**, or **ms/call** column headers to sort by that column.
|
|
115
|
+
**ms/call** is the cumulative time divided by the number of calls, useful for finding functions that are slow each time they run, rather than those called many times.
|
|
114
116
|
* Use the search box to filter by filename or function name.
|
|
115
117
|
Separate multiple search terms by spaces to match records that contain all terms.
|
|
116
118
|
* Press a keyboard shortcut to focus the search box: ``/``, ``Control+K``, or ``Command+K``.
|
|
117
119
|
Select some text first, like a filename, to pre-fill the search box with that text.
|
|
118
120
|
* Hover by a filename + line number pair to reveal the copy button, which copies the location to your clipboard for faster opening.
|
|
121
|
+
* Click a filename + line number pair to open that file at that line in your editor.
|
|
122
|
+
See `Opening files in your editor`_ below.
|
|
119
123
|
* Click the **callers** or **callees** links on the right of a row to see the callers or callees of that function.
|
|
124
|
+
The **← back** link returns to the main table with your previous search and sort order.
|
|
125
|
+
|
|
126
|
+
Opening files in your editor
|
|
127
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
128
|
+
|
|
129
|
+
When you click a filename, profiling-explorer runs your editor to open the file at that line, using `launch-editor-py <https://pypi.org/project/launch-editor-py/>`__.
|
|
130
|
+
It picks the editor from one of these options, in this order:
|
|
131
|
+
|
|
132
|
+
1. The ``LAUNCH_EDITOR`` environment variable, for override.s
|
|
133
|
+
2. A supported editor that’s currently running, such as VS Code, Sublime Text, or Zed.
|
|
134
|
+
3. The ``VISUAL`` environment variable.
|
|
135
|
+
4. The ``EDITOR`` environment variable.
|
|
136
|
+
|
|
137
|
+
Terminal editors like Vim open in the terminal running profiling-explorer.
|
|
120
138
|
|
|
121
|
-
Full help
|
|
139
|
+
Full help
|
|
140
|
+
~~~~~~~~~
|
|
122
141
|
|
|
123
142
|
.. [[[cog
|
|
124
143
|
.. import cog
|
|
@@ -42,7 +42,7 @@ Read more in `the introductory blog post <https://adamj.eu/tech/2026/04/03/pytho
|
|
|
42
42
|
Requirements
|
|
43
43
|
------------
|
|
44
44
|
|
|
45
|
-
Python 3.
|
|
45
|
+
Python 3.11 to 3.15 supported.
|
|
46
46
|
|
|
47
47
|
Installation
|
|
48
48
|
------------
|
|
@@ -83,15 +83,33 @@ Then run ``profiling-explorer`` with the generated file:
|
|
|
83
83
|
The report will open in your web browser, and you can explore the profile data with the interactive interface.
|
|
84
84
|
Features:
|
|
85
85
|
|
|
86
|
-
* Click the **calls**, **internal ms**,
|
|
86
|
+
* Click the **calls**, **internal ms**, **cumulative ms**, or **ms/call** column headers to sort by that column.
|
|
87
|
+
**ms/call** is the cumulative time divided by the number of calls, useful for finding functions that are slow each time they run, rather than those called many times.
|
|
87
88
|
* Use the search box to filter by filename or function name.
|
|
88
89
|
Separate multiple search terms by spaces to match records that contain all terms.
|
|
89
90
|
* Press a keyboard shortcut to focus the search box: ``/``, ``Control+K``, or ``Command+K``.
|
|
90
91
|
Select some text first, like a filename, to pre-fill the search box with that text.
|
|
91
92
|
* Hover by a filename + line number pair to reveal the copy button, which copies the location to your clipboard for faster opening.
|
|
93
|
+
* Click a filename + line number pair to open that file at that line in your editor.
|
|
94
|
+
See `Opening files in your editor`_ below.
|
|
92
95
|
* Click the **callers** or **callees** links on the right of a row to see the callers or callees of that function.
|
|
96
|
+
The **← back** link returns to the main table with your previous search and sort order.
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
Opening files in your editor
|
|
99
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
100
|
+
|
|
101
|
+
When you click a filename, profiling-explorer runs your editor to open the file at that line, using `launch-editor-py <https://pypi.org/project/launch-editor-py/>`__.
|
|
102
|
+
It picks the editor from one of these options, in this order:
|
|
103
|
+
|
|
104
|
+
1. The ``LAUNCH_EDITOR`` environment variable, for override.s
|
|
105
|
+
2. A supported editor that’s currently running, such as VS Code, Sublime Text, or Zed.
|
|
106
|
+
3. The ``VISUAL`` environment variable.
|
|
107
|
+
4. The ``EDITOR`` environment variable.
|
|
108
|
+
|
|
109
|
+
Terminal editors like Vim open in the terminal running profiling-explorer.
|
|
110
|
+
|
|
111
|
+
Full help
|
|
112
|
+
~~~~~~~~~
|
|
95
113
|
|
|
96
114
|
.. [[[cog
|
|
97
115
|
.. import cog
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "uv_build"
|
|
3
|
+
requires = ["uv-build>=0.12.1,<0.13"]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "profiling-explorer"
|
|
7
|
+
version = "1.3.0"
|
|
8
|
+
description = "Table-based exploration tool for Python profiling data (pstats files)."
|
|
9
|
+
readme = "README.rst"
|
|
10
|
+
keywords = [
|
|
11
|
+
"profiler",
|
|
12
|
+
"profiling",
|
|
13
|
+
"pstats",
|
|
14
|
+
]
|
|
15
|
+
license = "MIT"
|
|
16
|
+
license-files = ["LICENSE"]
|
|
17
|
+
requires-python = ">=3.11"
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 5 - Production/Stable",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Natural Language :: English",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Programming Language :: Python :: 3.15",
|
|
29
|
+
"Typing :: Typed",
|
|
30
|
+
]
|
|
31
|
+
dependencies = [
|
|
32
|
+
"django>=5.2",
|
|
33
|
+
"launch-editor-py>=1",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[[project.authors]]
|
|
37
|
+
name = "Adam Johnson"
|
|
38
|
+
email = "me@adamj.eu"
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Changelog = "https://github.com/adamchainz/profiling-explorer/blob/main/CHANGELOG.rst"
|
|
42
|
+
Funding = "https://adamj.eu/books/"
|
|
43
|
+
Repository = "https://github.com/adamchainz/profiling-explorer"
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
profiling-explorer = "profiling_explorer.main:main"
|
|
47
|
+
|
|
48
|
+
[dependency-groups]
|
|
49
|
+
test = [
|
|
50
|
+
"cogapp>=3.6",
|
|
51
|
+
"coverage[toml]>=7.10",
|
|
52
|
+
"pytest>=9",
|
|
53
|
+
"pytest-django",
|
|
54
|
+
"pytest-randomly",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint]
|
|
58
|
+
select = [
|
|
59
|
+
"B",
|
|
60
|
+
"C4",
|
|
61
|
+
"E",
|
|
62
|
+
"F",
|
|
63
|
+
"I",
|
|
64
|
+
"SIM",
|
|
65
|
+
"TID",
|
|
66
|
+
"UP",
|
|
67
|
+
"W",
|
|
68
|
+
]
|
|
69
|
+
ignore = [
|
|
70
|
+
"B9",
|
|
71
|
+
"E501",
|
|
72
|
+
"SIM105",
|
|
73
|
+
"SIM108",
|
|
74
|
+
]
|
|
75
|
+
extend-safe-fixes = ["UP006"]
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint.isort]
|
|
78
|
+
required-imports = ["from __future__ import annotations"]
|
|
79
|
+
|
|
80
|
+
[tool.pyproject-fmt]
|
|
81
|
+
max_supported_python = "3.15"
|
|
82
|
+
|
|
83
|
+
[tool.mypy]
|
|
84
|
+
mypy_path = "src/"
|
|
85
|
+
namespace_packages = false
|
|
86
|
+
warn_unreachable = true
|
|
87
|
+
enable_error_code = [
|
|
88
|
+
"ignore-without-code",
|
|
89
|
+
"redundant-expr",
|
|
90
|
+
"truthy-bool",
|
|
91
|
+
]
|
|
92
|
+
strict = true
|
|
93
|
+
|
|
94
|
+
[[tool.mypy.overrides]]
|
|
95
|
+
module = "tests.*"
|
|
96
|
+
allow_untyped_defs = true
|
|
97
|
+
|
|
98
|
+
[tool.pytest]
|
|
99
|
+
django_find_project = false
|
|
100
|
+
DJANGO_SETTINGS_MODULE = "profiling_explorer.settings"
|
|
101
|
+
strict = true
|
|
102
|
+
|
|
103
|
+
[tool.coverage.run]
|
|
104
|
+
source = [
|
|
105
|
+
"profiling_explorer",
|
|
106
|
+
"tests",
|
|
107
|
+
]
|
|
108
|
+
branch = true
|
|
109
|
+
data_file = ".coverage/cov"
|
|
110
|
+
parallel = true
|
|
111
|
+
|
|
112
|
+
[tool.coverage.paths]
|
|
113
|
+
source = [
|
|
114
|
+
"src",
|
|
115
|
+
".tox/**/site-packages",
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
[tool.coverage.report]
|
|
119
|
+
skip_covered = true
|
|
120
|
+
skip_empty = true
|
|
121
|
+
show_missing = true
|
|
122
|
+
|
|
123
|
+
[tool.rstcheck]
|
|
124
|
+
report_level = "ERROR"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
build-backend = "
|
|
2
|
+
build-backend = "uv_build"
|
|
3
3
|
requires = [
|
|
4
|
-
"
|
|
4
|
+
"uv-build>=0.12.1,<0.13",
|
|
5
5
|
]
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "profiling-explorer"
|
|
9
|
-
version = "1.
|
|
9
|
+
version = "1.3.0"
|
|
10
10
|
description = "Table-based exploration tool for Python profiling data (pstats files)."
|
|
11
11
|
readme = "README.rst"
|
|
12
12
|
keywords = [
|
|
@@ -19,22 +19,23 @@ license-files = [ "LICENSE" ]
|
|
|
19
19
|
authors = [
|
|
20
20
|
{ name = "Adam Johnson", email = "me@adamj.eu" },
|
|
21
21
|
]
|
|
22
|
-
requires-python = ">=3.
|
|
22
|
+
requires-python = ">=3.11"
|
|
23
23
|
classifiers = [
|
|
24
24
|
"Development Status :: 5 - Production/Stable",
|
|
25
25
|
"Intended Audience :: Developers",
|
|
26
26
|
"Natural Language :: English",
|
|
27
27
|
"Operating System :: OS Independent",
|
|
28
28
|
"Programming Language :: Python :: 3 :: Only",
|
|
29
|
-
"Programming Language :: Python :: 3.10",
|
|
30
29
|
"Programming Language :: Python :: 3.11",
|
|
31
30
|
"Programming Language :: Python :: 3.12",
|
|
32
31
|
"Programming Language :: Python :: 3.13",
|
|
33
32
|
"Programming Language :: Python :: 3.14",
|
|
33
|
+
"Programming Language :: Python :: 3.15",
|
|
34
34
|
"Typing :: Typed",
|
|
35
35
|
]
|
|
36
36
|
dependencies = [
|
|
37
37
|
"django>=5.2",
|
|
38
|
+
"launch-editor-py>=1",
|
|
38
39
|
]
|
|
39
40
|
urls.Changelog = "https://github.com/adamchainz/profiling-explorer/blob/main/CHANGELOG.rst"
|
|
40
41
|
urls.Funding = "https://adamj.eu/books/"
|
|
@@ -88,40 +89,40 @@ lint.extend-safe-fixes = [
|
|
|
88
89
|
lint.isort.required-imports = [ "from __future__ import annotations" ]
|
|
89
90
|
|
|
90
91
|
[tool.pyproject-fmt]
|
|
91
|
-
max_supported_python = "3.
|
|
92
|
+
max_supported_python = "3.15"
|
|
92
93
|
|
|
93
94
|
[tool.mypy]
|
|
95
|
+
mypy_path = "src/"
|
|
96
|
+
namespace_packages = false
|
|
97
|
+
warn_unreachable = true
|
|
94
98
|
enable_error_code = [
|
|
95
99
|
"ignore-without-code",
|
|
96
100
|
"redundant-expr",
|
|
97
101
|
"truthy-bool",
|
|
98
102
|
]
|
|
99
|
-
mypy_path = "src/"
|
|
100
|
-
namespace_packages = false
|
|
101
103
|
strict = true
|
|
102
|
-
warn_unreachable = true
|
|
103
104
|
overrides = [ { module = "tests.*", allow_untyped_defs = true } ]
|
|
104
105
|
|
|
105
106
|
[tool.pytest]
|
|
106
|
-
strict = true
|
|
107
107
|
django_find_project = false
|
|
108
108
|
DJANGO_SETTINGS_MODULE = "profiling_explorer.settings"
|
|
109
|
+
strict = true
|
|
109
110
|
|
|
110
111
|
[tool.coverage]
|
|
111
|
-
run.branch = true
|
|
112
|
-
run.data_file = ".coverage/cov"
|
|
113
|
-
run.parallel = true
|
|
114
112
|
run.source = [
|
|
115
113
|
"profiling_explorer",
|
|
116
114
|
"tests",
|
|
117
115
|
]
|
|
116
|
+
run.branch = true
|
|
117
|
+
run.data_file = ".coverage/cov"
|
|
118
|
+
run.parallel = true
|
|
118
119
|
paths.source = [
|
|
119
120
|
"src",
|
|
120
121
|
".tox/**/site-packages",
|
|
121
122
|
]
|
|
122
|
-
report.show_missing = true
|
|
123
123
|
report.skip_covered = true
|
|
124
124
|
report.skip_empty = true
|
|
125
|
+
report.show_missing = true
|
|
125
126
|
|
|
126
127
|
[tool.rstcheck]
|
|
127
128
|
report_level = "ERROR"
|
{profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/static/script.js
RENAMED
|
@@ -7,7 +7,7 @@ async function fetchDoc(url, options) {
|
|
|
7
7
|
// Filter box
|
|
8
8
|
const searchInput = document.getElementById('pe-search');
|
|
9
9
|
|
|
10
|
-
document.addEventListener('keydown', (
|
|
10
|
+
document.addEventListener('keydown', (event) => {
|
|
11
11
|
const target = event.composedPath()?.[0] || event.target;
|
|
12
12
|
const isTextField =
|
|
13
13
|
target instanceof HTMLElement &&
|
|
@@ -43,6 +43,26 @@ searchInput.addEventListener('input', () => {
|
|
|
43
43
|
|
|
44
44
|
let searchAbort = null;
|
|
45
45
|
|
|
46
|
+
// Remember the index page's URL, with its search and sort, for back links
|
|
47
|
+
const INDEX_URL_KEY = 'pe-index-url';
|
|
48
|
+
|
|
49
|
+
function rememberIndexUrl() {
|
|
50
|
+
if (window.location.pathname !== '/') return;
|
|
51
|
+
try {
|
|
52
|
+
sessionStorage.setItem(INDEX_URL_KEY, window.location.search);
|
|
53
|
+
} catch {}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
rememberIndexUrl();
|
|
57
|
+
|
|
58
|
+
const backLink = document.querySelector('a.back-link');
|
|
59
|
+
if (backLink) {
|
|
60
|
+
try {
|
|
61
|
+
const search = sessionStorage.getItem(INDEX_URL_KEY);
|
|
62
|
+
if (search) backLink.href = '/' + search;
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
async function doSearch() {
|
|
47
67
|
const q = searchInput.value.trim();
|
|
48
68
|
const url = new URL(window.location.href);
|
|
@@ -52,6 +72,7 @@ async function doSearch() {
|
|
|
52
72
|
url.searchParams.delete('q');
|
|
53
73
|
}
|
|
54
74
|
history.replaceState(null, '', url);
|
|
75
|
+
rememberIndexUrl();
|
|
55
76
|
|
|
56
77
|
searchAbort?.abort();
|
|
57
78
|
searchAbort = new AbortController();
|
|
@@ -75,6 +96,19 @@ document.addEventListener('click', (e) => {
|
|
|
75
96
|
});
|
|
76
97
|
});
|
|
77
98
|
|
|
99
|
+
// Open-in-editor buttons
|
|
100
|
+
document.addEventListener('click', async (e) => {
|
|
101
|
+
const btn = e.target.closest('button.open-btn');
|
|
102
|
+
if (!btn) return;
|
|
103
|
+
|
|
104
|
+
const response = await fetch(btn.dataset.url, { method: 'POST' });
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
btn.classList.add('failed');
|
|
107
|
+
setTimeout(() => btn.classList.remove('failed'), 1500);
|
|
108
|
+
alert(await response.text());
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
78
112
|
|
|
79
113
|
// Pagination
|
|
80
114
|
class PePaginationMarker extends HTMLElement {
|
|
@@ -98,6 +132,9 @@ class PePaginationMarker extends HTMLElement {
|
|
|
98
132
|
async _load() {
|
|
99
133
|
const doc = await fetchDoc(this.dataset.url);
|
|
100
134
|
|
|
135
|
+
// The table may have been replaced by a search while fetching.
|
|
136
|
+
if (!this.isConnected) return;
|
|
137
|
+
|
|
101
138
|
const tbody = document.querySelector('tbody');
|
|
102
139
|
for (const row of doc.querySelectorAll('tbody tr')) {
|
|
103
140
|
tbody.appendChild(document.adoptNode(row));
|
{profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/static/styles.css
RENAMED
|
@@ -198,6 +198,23 @@ pe-funcname {
|
|
|
198
198
|
color: var(--funcname);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
button.open-btn {
|
|
202
|
+
background: none;
|
|
203
|
+
border: none;
|
|
204
|
+
padding: 0;
|
|
205
|
+
font: inherit;
|
|
206
|
+
color: inherit;
|
|
207
|
+
cursor: pointer;
|
|
208
|
+
|
|
209
|
+
&:hover {
|
|
210
|
+
text-decoration: underline;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
&.failed {
|
|
214
|
+
color: red;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
201
218
|
button.copy-btn {
|
|
202
219
|
display: inline-flex;
|
|
203
220
|
align-items: center;
|
{profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/templates/_row.html
RENAMED
|
@@ -5,5 +5,6 @@
|
|
|
5
5
|
<td>{% if stats.internal_ms != None %}{{ stats.internal_ms|intcomma }}{% endif %}
|
|
6
6
|
<td>{{ stats.cumulative_ms|intcomma }}
|
|
7
7
|
<td class="{{ stats.cumulative_ms_pct|pct_class }}">{{ stats.cumulative_ms_pct|pct }}
|
|
8
|
-
<td
|
|
8
|
+
<td>{{ stats.per_call_ms|intcomma }}
|
|
9
|
+
<td class=filename title="{{ row.full_filename }}"><button {% if not row.full_filename %}disabled{% endif %} class=copy-btn data-copy="{{ row.full_filename }}:{{ row.lineno }}" aria-label="Copy"><svg aria-hidden="true" width="18" height="18"><use href="#icon-copy"></use></svg></button>{% if row.filename %}{% if row.is_file %}<button class=open-btn data-url="/open/{{ row.id }}/" title="Open in editor">{{ row.filename }}:{{ row.lineno }}</button>{% else %}{{ row.filename }}:{{ row.lineno }}{% endif %} {% endif %}<pe-funcname>{{ row.funcname }}</pe-funcname>
|
|
9
10
|
<td>{% if row.id %}<a class=callexs href="/callers/{{ row.id }}/"><svg aria-hidden="true" width="18" height="18"><use href="#icon-tree" transform="rotate(180 9 9)"></use></svg> callers</a> <a class=callexs href="/callees/{{ row.id }}/"><svg aria-hidden="true" width="18" height="18"><use href="#icon-tree"></use></svg> callees</a>{% endif %}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<th>
|
|
19
19
|
</thead>
|
|
20
20
|
<tbody>
|
|
21
|
-
{% include "_row.html" with row=focal_row stats=focal_row focal=True only %}
|
|
21
|
+
{% if first_page %}{% include "_row.html" with row=focal_row stats=focal_row focal=True only %}{% endif %}
|
|
22
22
|
{% for row, edge in rows_with_edges %}
|
|
23
23
|
{% include "_row.html" with row=row stats=edge focal=False only %}
|
|
24
24
|
{% endfor %}
|
|
@@ -8,6 +8,7 @@ urlpatterns = [
|
|
|
8
8
|
path("", views.index, name="index"),
|
|
9
9
|
path("callers/<str:row_id>/", views.callers_view, name="callers"),
|
|
10
10
|
path("callees/<str:row_id>/", views.callees_view, name="callees"),
|
|
11
|
+
path("open/<str:row_id>/", views.open_in_editor, name="open"),
|
|
11
12
|
path("favicon.ico", views.favicon),
|
|
12
13
|
path("styles.css", views.file, {"filename": "styles.css"}),
|
|
13
14
|
path("script.js", views.file, {"filename": "script.js"}),
|
|
@@ -10,9 +10,17 @@ from importlib.resources import files as resource_files
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
from urllib.parse import urlencode
|
|
12
12
|
|
|
13
|
-
from django.http import
|
|
13
|
+
from django.http import (
|
|
14
|
+
FileResponse,
|
|
15
|
+
Http404,
|
|
16
|
+
HttpRequest,
|
|
17
|
+
HttpResponse,
|
|
18
|
+
HttpResponseBadRequest,
|
|
19
|
+
HttpResponseForbidden,
|
|
20
|
+
)
|
|
14
21
|
from django.shortcuts import render
|
|
15
|
-
from django.views.decorators.http import require_GET
|
|
22
|
+
from django.views.decorators.http import require_GET, require_POST
|
|
23
|
+
from launch_editor import LaunchEditorError, launch_editor
|
|
16
24
|
|
|
17
25
|
|
|
18
26
|
@dataclass(slots=True)
|
|
@@ -22,6 +30,7 @@ class RowStats:
|
|
|
22
30
|
internal_ms: int | None
|
|
23
31
|
cumulative_ms: int
|
|
24
32
|
cumulative_ms_pct: float
|
|
33
|
+
per_call_ms: int
|
|
25
34
|
|
|
26
35
|
|
|
27
36
|
@dataclass(slots=True)
|
|
@@ -31,6 +40,8 @@ class Row(RowStats):
|
|
|
31
40
|
full_filename: str
|
|
32
41
|
lineno: int
|
|
33
42
|
funcname: str
|
|
43
|
+
# Whether the file exists, so can be opened in an editor
|
|
44
|
+
is_file: bool
|
|
34
45
|
|
|
35
46
|
|
|
36
47
|
@dataclass(slots=True)
|
|
@@ -91,7 +102,7 @@ def _row_id(full_filename: str, lineno: int, funcname: str) -> str:
|
|
|
91
102
|
|
|
92
103
|
def _row_id_from_pstats_key(key: tuple[str, int, str]) -> str:
|
|
93
104
|
filename, lineno, funcname = key
|
|
94
|
-
full_filename
|
|
105
|
+
full_filename, _, funcname = _shorten_filename_function(filename, funcname)
|
|
95
106
|
return _row_id(full_filename, lineno, funcname)
|
|
96
107
|
|
|
97
108
|
|
|
@@ -103,13 +114,18 @@ def build_profile(s: pstats.Stats, path: str) -> Profile:
|
|
|
103
114
|
rows_by_id = {}
|
|
104
115
|
callers_map: defaultdict[str, dict[str, RowStats]] = defaultdict(dict)
|
|
105
116
|
callees_map: defaultdict[str, dict[str, RowStats]] = defaultdict(dict)
|
|
117
|
+
is_file_cache: dict[str, bool] = {}
|
|
106
118
|
for key in s.fcn_list: # type: ignore[attr-defined]
|
|
107
119
|
filename, lineno, funcname = key
|
|
108
120
|
_, calls, tottime, cumtime, callers = s.stats[key] # type: ignore[attr-defined]
|
|
109
121
|
full_filename, short_filename, funcname = _shorten_filename_function(
|
|
110
122
|
filename, funcname
|
|
111
123
|
)
|
|
112
|
-
row_id =
|
|
124
|
+
row_id = _row_id_from_pstats_key(key)
|
|
125
|
+
if full_filename not in is_file_cache:
|
|
126
|
+
is_file_cache[full_filename] = bool(full_filename) and os.path.isfile(
|
|
127
|
+
full_filename
|
|
128
|
+
)
|
|
113
129
|
cumulative_ms = round(cumtime * 1_000)
|
|
114
130
|
row = Row(
|
|
115
131
|
id=row_id,
|
|
@@ -120,23 +136,17 @@ def build_profile(s: pstats.Stats, path: str) -> Profile:
|
|
|
120
136
|
cumulative_ms_pct=min(100.0, cumulative_ms / total_time_ms * 100)
|
|
121
137
|
if total_time_ms
|
|
122
138
|
else 0.0,
|
|
139
|
+
per_call_ms=round(cumtime * 1_000 / calls) if calls else 0,
|
|
123
140
|
filename=short_filename,
|
|
124
141
|
full_filename=full_filename,
|
|
125
142
|
lineno=lineno,
|
|
126
143
|
funcname=funcname,
|
|
144
|
+
is_file=is_file_cache[full_filename],
|
|
127
145
|
)
|
|
128
146
|
rows.append(row)
|
|
129
147
|
rows_by_id[row_id] = row
|
|
130
|
-
for (
|
|
131
|
-
|
|
132
|
-
_,
|
|
133
|
-
__,
|
|
134
|
-
callee_cumtime,
|
|
135
|
-
) in callers.items():
|
|
136
|
-
caller_filename, _, caller_funcname = _shorten_filename_function(
|
|
137
|
-
caller_filename, caller_funcname
|
|
138
|
-
)
|
|
139
|
-
caller_id = _row_id(caller_filename, caller_lineno, caller_funcname)
|
|
148
|
+
for caller_key, (callee_calls, _, __, callee_cumtime) in callers.items():
|
|
149
|
+
caller_id = _row_id_from_pstats_key(caller_key)
|
|
140
150
|
callee_cumulative_ms = round(callee_cumtime * 1_000)
|
|
141
151
|
edge = RowStats(
|
|
142
152
|
calls=callee_calls,
|
|
@@ -150,6 +160,9 @@ def build_profile(s: pstats.Stats, path: str) -> Profile:
|
|
|
150
160
|
if total_time_ms
|
|
151
161
|
else 0.0
|
|
152
162
|
),
|
|
163
|
+
per_call_ms=(
|
|
164
|
+
round(callee_cumtime * 1_000 / callee_calls) if callee_calls else 0
|
|
165
|
+
),
|
|
153
166
|
)
|
|
154
167
|
callers_map[row_id][caller_id] = edge
|
|
155
168
|
callees_map[caller_id][row_id] = edge
|
|
@@ -179,7 +192,7 @@ def _build_edge_rows(
|
|
|
179
192
|
|
|
180
193
|
PAGE_SIZE = 200
|
|
181
194
|
|
|
182
|
-
_VALID_SORT_COLS = {"calls", "internal_ms", "cumulative_ms"}
|
|
195
|
+
_VALID_SORT_COLS = {"calls", "internal_ms", "cumulative_ms", "per_call_ms"}
|
|
183
196
|
|
|
184
197
|
|
|
185
198
|
def _render_table(
|
|
@@ -225,7 +238,10 @@ def _render_table(
|
|
|
225
238
|
reverse=sort_desc,
|
|
226
239
|
)
|
|
227
240
|
|
|
228
|
-
|
|
241
|
+
try:
|
|
242
|
+
offset = max(0, int(request.GET.get("offset", 0)))
|
|
243
|
+
except ValueError:
|
|
244
|
+
offset = 0
|
|
229
245
|
page_rows = sorted_rows[offset : offset + PAGE_SIZE]
|
|
230
246
|
|
|
231
247
|
next_url = None
|
|
@@ -249,12 +265,14 @@ def _render_table(
|
|
|
249
265
|
context = {
|
|
250
266
|
"profile": profile,
|
|
251
267
|
"rows": page_rows,
|
|
268
|
+
"first_page": offset == 0,
|
|
252
269
|
"next_url": next_url,
|
|
253
270
|
"q": q,
|
|
254
271
|
"columns": [
|
|
255
272
|
col_config("calls", "calls"),
|
|
256
273
|
col_config("internal_ms", "internal ms"),
|
|
257
274
|
col_config("cumulative_ms", "cumulative ms"),
|
|
275
|
+
col_config("per_call_ms", "ms/call"),
|
|
258
276
|
],
|
|
259
277
|
**extra_context,
|
|
260
278
|
}
|
|
@@ -324,6 +342,35 @@ def callees_view(request: HttpRequest, row_id: str) -> HttpResponse:
|
|
|
324
342
|
)
|
|
325
343
|
|
|
326
344
|
|
|
345
|
+
_LOCAL_HOSTS = {"127.0.0.1", "localhost", "[::1]"}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@require_POST
|
|
349
|
+
def open_in_editor(request: HttpRequest, row_id: str) -> HttpResponse:
|
|
350
|
+
# This view runs a program, so only accept requests from our own pages.
|
|
351
|
+
# Websites can send cross-origin POSTs to localhost, and DNS rebinding can
|
|
352
|
+
# make another hostname resolve here.
|
|
353
|
+
host = request.get_host().rsplit(":", 1)[0]
|
|
354
|
+
if host not in _LOCAL_HOSTS:
|
|
355
|
+
return HttpResponseForbidden("Invalid host.")
|
|
356
|
+
if request.headers.get("Sec-Fetch-Site", "same-origin") != "same-origin":
|
|
357
|
+
return HttpResponseForbidden("Cross-site request.")
|
|
358
|
+
origin = request.headers.get("Origin")
|
|
359
|
+
if origin is not None and origin != f"{request.scheme}://{request.get_host()}":
|
|
360
|
+
return HttpResponseForbidden("Cross-origin request.")
|
|
361
|
+
|
|
362
|
+
row = profile.rows_by_id.get(row_id)
|
|
363
|
+
if row is None or not row.is_file:
|
|
364
|
+
raise Http404()
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
launch_editor(row.full_filename, row.lineno)
|
|
368
|
+
except LaunchEditorError as exc:
|
|
369
|
+
return HttpResponseBadRequest(str(exc))
|
|
370
|
+
|
|
371
|
+
return HttpResponse(status=204)
|
|
372
|
+
|
|
373
|
+
|
|
327
374
|
@require_GET
|
|
328
375
|
def file(request: HttpRequest, *, filename: str) -> FileResponse:
|
|
329
376
|
return FileResponse(
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
=========
|
|
2
|
-
Changelog
|
|
3
|
-
=========
|
|
4
|
-
|
|
5
|
-
1.2.0 (2026-05-18)
|
|
6
|
-
------------------
|
|
7
|
-
|
|
8
|
-
* Enable changing the sort order in callees and callers views.
|
|
9
|
-
|
|
10
|
-
`PR #43 <https://github.com/adamchainz/profiling-explorer/pull/43>`__.
|
|
11
|
-
|
|
12
|
-
1.1.2 (2026-04-14)
|
|
13
|
-
------------------
|
|
14
|
-
|
|
15
|
-
* Fix caller percentage calculation.
|
|
16
|
-
|
|
17
|
-
`PR #37 <https://github.com/adamchainz/profiling-explorer/pull/37>`__.
|
|
18
|
-
|
|
19
|
-
1.1.1 (2026-04-13)
|
|
20
|
-
------------------
|
|
21
|
-
|
|
22
|
-
* Fix filename simplification.
|
|
23
|
-
|
|
24
|
-
`PR #36 <https://github.com/adamchainz/profiling-explorer/pull/36>`__.
|
|
25
|
-
|
|
26
|
-
1.1.0 (2026-04-13)
|
|
27
|
-
------------------
|
|
28
|
-
|
|
29
|
-
* Extend search to support multiple terms.
|
|
30
|
-
|
|
31
|
-
`PR #33 <https://github.com/adamchainz/profiling-explorer/pull/33>`__.
|
|
32
|
-
|
|
33
|
-
* Add keyboard shortcuts to focus the search box.
|
|
34
|
-
|
|
35
|
-
`PR #35 <https://github.com/adamchainz/profiling-explorer/pull/35>`__.
|
|
36
|
-
|
|
37
|
-
* Fix callers/callees views for built-in functions.
|
|
38
|
-
|
|
39
|
-
`PR #34 <https://github.com/adamchainz/profiling-explorer/pull/34>`__.
|
|
40
|
-
Thanks to David Szotten for the report in `Issue #32 <https://github.com/adamchainz/profiling-explorer/issues/32>`__.
|
|
41
|
-
|
|
42
|
-
1.0.1 (2026-04-02)
|
|
43
|
-
------------------
|
|
44
|
-
|
|
45
|
-
* Fix crash when a record has an empty filename.
|
|
46
|
-
|
|
47
|
-
1.0.0 (2026-04-02)
|
|
48
|
-
------------------
|
|
49
|
-
|
|
50
|
-
* Initial release.
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: profiling-explorer
|
|
3
|
-
Version: 1.2.0
|
|
4
|
-
Summary: Table-based exploration tool for Python profiling data (pstats files).
|
|
5
|
-
Author-email: Adam Johnson <me@adamj.eu>
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Project-URL: Changelog, https://github.com/adamchainz/profiling-explorer/blob/main/CHANGELOG.rst
|
|
8
|
-
Project-URL: Funding, https://adamj.eu/books/
|
|
9
|
-
Project-URL: Repository, https://github.com/adamchainz/profiling-explorer
|
|
10
|
-
Keywords: profiler,profiling,pstats
|
|
11
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: Natural Language :: English
|
|
14
|
-
Classifier: Operating System :: OS Independent
|
|
15
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
-
Classifier: Typing :: Typed
|
|
22
|
-
Requires-Python: >=3.10
|
|
23
|
-
Description-Content-Type: text/x-rst
|
|
24
|
-
License-File: LICENSE
|
|
25
|
-
Requires-Dist: django>=5.2
|
|
26
|
-
Dynamic: license-file
|
|
27
|
-
|
|
28
|
-
==================
|
|
29
|
-
profiling-explorer
|
|
30
|
-
==================
|
|
31
|
-
|
|
32
|
-
.. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/profiling-explorer/main.yml.svg?branch=main&style=for-the-badge
|
|
33
|
-
:target: https://github.com/adamchainz/profiling-explorer/actions?workflow=CI
|
|
34
|
-
|
|
35
|
-
.. image:: https://img.shields.io/badge/Coverage-70%25-success?style=for-the-badge
|
|
36
|
-
:target: https://github.com/adamchainz/profiling-explorer/actions?workflow=CI
|
|
37
|
-
|
|
38
|
-
.. image:: https://img.shields.io/pypi/v/profiling-explorer.svg?style=for-the-badge
|
|
39
|
-
:target: https://pypi.org/project/profiling-explorer/
|
|
40
|
-
|
|
41
|
-
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge
|
|
42
|
-
:target: https://github.com/psf/black
|
|
43
|
-
|
|
44
|
-
.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge
|
|
45
|
-
:target: https://github.com/pre-commit/pre-commit
|
|
46
|
-
:alt: pre-commit
|
|
47
|
-
|
|
48
|
-
----
|
|
49
|
-
|
|
50
|
-
Table-based exploration tool for Python profiling data (pstats files).
|
|
51
|
-
|
|
52
|
-
.. Generated with:
|
|
53
|
-
.. uvx --with django python -m cProfile -o django_utils_html.pstats -m django.utils.html
|
|
54
|
-
.. uvr profiling-explorer django_utils_html.pstats
|
|
55
|
-
.. …then in another tab:
|
|
56
|
-
.. uvx shot-scraper --width 1280 --height 720 --retina --wait 1000 http://127.0.0.1:8099/ -o screenshot.png
|
|
57
|
-
|
|
58
|
-
.. figure:: https://raw.githubusercontent.com/adamchainz/profiling-explorer/main/screenshot.png
|
|
59
|
-
:alt: profiling-explorer screenshot
|
|
60
|
-
|
|
61
|
-
Read more in `the introductory blog post <https://adamj.eu/tech/2026/04/03/python-introducing-profiling-explorer/>`__.
|
|
62
|
-
|
|
63
|
-
----
|
|
64
|
-
|
|
65
|
-
**Get better at command line Git** with my book `Boost Your Git DX <https://adamchainz.gumroad.com/l/bygdx>`__.
|
|
66
|
-
|
|
67
|
-
----
|
|
68
|
-
|
|
69
|
-
Requirements
|
|
70
|
-
------------
|
|
71
|
-
|
|
72
|
-
Python 3.10 to 3.14 supported.
|
|
73
|
-
|
|
74
|
-
Installation
|
|
75
|
-
------------
|
|
76
|
-
|
|
77
|
-
1. Install with **pip**:
|
|
78
|
-
|
|
79
|
-
.. code-block:: sh
|
|
80
|
-
|
|
81
|
-
python -m pip install profiling-explorer
|
|
82
|
-
|
|
83
|
-
Usage
|
|
84
|
-
-----
|
|
85
|
-
|
|
86
|
-
``profiling-explorer`` reads |pstats|__ files as generated by Python’s profilers: |profiling.tracing|__ (called ``cProfile`` on Python < 3.15) and |profiling.sampling|__ (new in Python 3.15).
|
|
87
|
-
To use it, first generate a profile file, for example by running your program under cProfile:
|
|
88
|
-
|
|
89
|
-
.. |pstats| replace:: ``pstats``
|
|
90
|
-
__ https://docs.python.org/3.15/library/pstats.html
|
|
91
|
-
|
|
92
|
-
.. |profiling.tracing| replace:: ``profiling.tracing``
|
|
93
|
-
__ https://docs.python.org/3.15/library/profiling.tracing.html
|
|
94
|
-
|
|
95
|
-
.. |profiling.sampling| replace:: ``profiling.sampling``
|
|
96
|
-
__ https://docs.python.org/3.15/library/profiling.sampling.html
|
|
97
|
-
|
|
98
|
-
.. code-block:: console
|
|
99
|
-
|
|
100
|
-
$ python -m cProfile -o example.pstats example.py
|
|
101
|
-
|
|
102
|
-
(Also runnable as ``python -m profiling.tracing`` instead on Python 3.15+.)
|
|
103
|
-
|
|
104
|
-
Then run ``profiling-explorer`` with the generated file:
|
|
105
|
-
|
|
106
|
-
.. code-block:: console
|
|
107
|
-
|
|
108
|
-
$ profiling-explorer example.pstats
|
|
109
|
-
|
|
110
|
-
The report will open in your web browser, and you can explore the profile data with the interactive interface.
|
|
111
|
-
Features:
|
|
112
|
-
|
|
113
|
-
* Click the **calls**, **internal ms**, or **cumulative ms** column headers to sort by that column.
|
|
114
|
-
* Use the search box to filter by filename or function name.
|
|
115
|
-
Separate multiple search terms by spaces to match records that contain all terms.
|
|
116
|
-
* Press a keyboard shortcut to focus the search box: ``/``, ``Control+K``, or ``Command+K``.
|
|
117
|
-
Select some text first, like a filename, to pre-fill the search box with that text.
|
|
118
|
-
* Hover by a filename + line number pair to reveal the copy button, which copies the location to your clipboard for faster opening.
|
|
119
|
-
* Click the **callers** or **callees** links on the right of a row to see the callers or callees of that function.
|
|
120
|
-
|
|
121
|
-
Full help:
|
|
122
|
-
|
|
123
|
-
.. [[[cog
|
|
124
|
-
.. import cog
|
|
125
|
-
.. import subprocess
|
|
126
|
-
.. import sys
|
|
127
|
-
.. result = subprocess.run(
|
|
128
|
-
.. [sys.executable, "-m", "profiling_explorer", "--help"],
|
|
129
|
-
.. capture_output=True,
|
|
130
|
-
.. text=True,
|
|
131
|
-
.. )
|
|
132
|
-
.. cog.outl("")
|
|
133
|
-
.. cog.outl(".. code-block:: console")
|
|
134
|
-
.. cog.outl("")
|
|
135
|
-
.. for line in result.stdout.splitlines():
|
|
136
|
-
.. if line.strip() == "":
|
|
137
|
-
.. cog.outl("")
|
|
138
|
-
.. else:
|
|
139
|
-
.. cog.outl(" " + line.rstrip())
|
|
140
|
-
.. cog.outl("")
|
|
141
|
-
.. ]]]
|
|
142
|
-
|
|
143
|
-
.. code-block:: console
|
|
144
|
-
|
|
145
|
-
usage: profiling-explorer [-h] [--port PORT] [--dev] FILE
|
|
146
|
-
|
|
147
|
-
positional arguments:
|
|
148
|
-
FILE The pstats data file to explore.
|
|
149
|
-
|
|
150
|
-
options:
|
|
151
|
-
-h, --help show this help message and exit
|
|
152
|
-
--port PORT Port for the local web server (default: 8099).
|
|
153
|
-
--dev Run in development mode (enables server reload and debug mode).
|
|
154
|
-
|
|
155
|
-
.. [[[end]]]
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
CHANGELOG.rst
|
|
2
|
-
LICENSE
|
|
3
|
-
MANIFEST.in
|
|
4
|
-
README.rst
|
|
5
|
-
pyproject.toml
|
|
6
|
-
src/profiling_explorer/__init__.py
|
|
7
|
-
src/profiling_explorer/__main__.py
|
|
8
|
-
src/profiling_explorer/main.py
|
|
9
|
-
src/profiling_explorer/py.typed
|
|
10
|
-
src/profiling_explorer/settings.py
|
|
11
|
-
src/profiling_explorer/urls.py
|
|
12
|
-
src/profiling_explorer/views.py
|
|
13
|
-
src/profiling_explorer.egg-info/PKG-INFO
|
|
14
|
-
src/profiling_explorer.egg-info/SOURCES.txt
|
|
15
|
-
src/profiling_explorer.egg-info/dependency_links.txt
|
|
16
|
-
src/profiling_explorer.egg-info/entry_points.txt
|
|
17
|
-
src/profiling_explorer.egg-info/requires.txt
|
|
18
|
-
src/profiling_explorer.egg-info/top_level.txt
|
|
19
|
-
src/profiling_explorer/management/__init__.py
|
|
20
|
-
src/profiling_explorer/management/commands/__init__.py
|
|
21
|
-
src/profiling_explorer/management/commands/runserver.py
|
|
22
|
-
src/profiling_explorer/static/script.js
|
|
23
|
-
src/profiling_explorer/static/styles.css
|
|
24
|
-
src/profiling_explorer/templates/_row.html
|
|
25
|
-
src/profiling_explorer/templates/base.html
|
|
26
|
-
src/profiling_explorer/templates/callers_callees.html
|
|
27
|
-
src/profiling_explorer/templates/index.html
|
|
28
|
-
src/profiling_explorer/templatetags/__init__.py
|
|
29
|
-
src/profiling_explorer/templatetags/profiling_explorer_tags.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
django>=5.2
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
profiling_explorer
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/management/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/templates/base.html
RENAMED
|
File without changes
|
{profiling_explorer-1.2.0 → profiling_explorer-1.3.0}/src/profiling_explorer/templates/index.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|