sawmill-parser 0.2.4__tar.gz → 0.4.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.
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/.github/workflows/docs.yml +1 -3
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/.gitignore +1 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/PKG-INFO +15 -9
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/README.md +5 -4
- sawmill_parser-0.4.0/SAWMILL_STREAMING_SPEC.md +66 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/index.rst +8 -0
- sawmill_parser-0.4.0/docs/performance/overview.rst +103 -0
- sawmill_parser-0.4.0/docs/performance/plugin-api-2.rst +97 -0
- sawmill_parser-0.4.0/docs/performance/store-investigation.rst +127 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/plugin-guide/what-is-a-plugin.rst +4 -4
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/plugin-guide/writing-a-plugin.rst +27 -10
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/pyproject.toml +9 -6
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/cli/commands.py +50 -9
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/cli/processing.py +40 -65
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/core/filter.py +51 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/core/plugin.py +49 -2
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/core/waiver.py +33 -7
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/models/message.py +20 -9
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/plugin/__init__.py +15 -9
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/plugin/hookspec.py +18 -11
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/tui/app.py +385 -186
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/tui/base.tcss +1 -5
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/tui/filter_parser.py +9 -0
- sawmill_parser-0.4.0/sawmill/tui/session.py +135 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/tui/widgets/footer.py +1 -6
- sawmill_parser-0.4.0/sawmill/tui/widgets/help_modal.py +68 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/tui/widgets/quit_modal.py +6 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/tui/widgets/waive_modal.py +6 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/core/test_filter.py +114 -2
- sawmill_parser-0.4.0/tests/core/test_plugin_load.py +25 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/core/test_plugin_manager.py +37 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/core/test_waiver.py +106 -2
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/models/test_message.py +19 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/plugin/test_hookspec.py +8 -10
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/plugins/test_vivado.py +11 -11
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/tui/test_app.py +41 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/tui/test_base_styles.py +15 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/tui/test_filter_parser.py +19 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/tui/test_persistence.py +3 -3
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/tui/test_tabs.py +24 -12
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/tui/test_waive.py +19 -8
- sawmill_parser-0.4.0/tests/tui/test_windowed_table.py +192 -0
- sawmill_parser-0.4.0/tools/bench/baseline.json +56 -0
- sawmill_parser-0.4.0/tools/bench/bench_message.py +157 -0
- sawmill_parser-0.4.0/tools/bench/gen_xcelium_log.py +146 -0
- sawmill_parser-0.4.0/tools/bench/pr1.json +59 -0
- sawmill_parser-0.4.0/tools/bench/pr2.json +59 -0
- sawmill_parser-0.4.0/tools/bench/run_bench.py +246 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/generator.py +1 -1
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/claude.md.j2 +11 -6
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/plugin.py.j2 +17 -13
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/pyproject.toml.j2 +1 -2
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/test_parsing.py.j2 +13 -11
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/test_plugin_contract.py.j2 +6 -4
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/tests/test_generator.py +1 -8
- sawmill_parser-0.2.4/docs/requirements.txt +0 -4
- sawmill_parser-0.2.4/uv.lock +0 -924
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/.gitattributes +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/.github/workflows/publish.yml +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/.pre-commit-config.yaml +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/CHANGELOG.md +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/CLAUDE.md +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/Dockerfile +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/PRD.md +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/PROMPT.md +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/STATUS.md +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/assets/sawmill.png +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/Makefile +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/conf.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/development/contributing.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/getting-started/quickstart.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/getting-started/what-is-sawmill.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/plugin-guide/pre-defined-filters.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/reference/api.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/reference/cli-reference.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/reference/file-formats.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/user-guide/ci-integration.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/user-guide/cli-usage.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/user-guide/sawmill-directory.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/user-guide/tui-usage.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/docs/user-guide/waivers.rst +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/ralph-loop.sh +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/__main__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/cli/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/cli/formatting.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/cli/reporting.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/core/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/core/aggregation.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/core/config.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/core/severity.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/core/suppress.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/models/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/models/filter_def.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/models/plugin_api.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/models/waiver.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/tui/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/tui/theme.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/tui/widgets/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/utils/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/utils/author.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/utils/dirs.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/utils/git.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sawmill/utils/toml.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/sourceme +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/conftest.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/core/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/core/test_aggregation.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/core/test_config.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/core/test_plugin_autodetect.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/core/test_suppress.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/core/test_waiver_matching.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/fixtures/vivado.log +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/models/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/models/test_filter_def.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/plugin/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/plugins/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_ci_mode.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_ci_report.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_ci_waivers.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_cli.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_cli_formats.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_cli_id_filter.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_cli_integration.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_cli_plugin_discovery.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_cli_summary.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_fixtures.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_plugin_api.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_project_setup.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/test_waiver_generation.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/tui/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/tui/test_footer.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/tui/test_suppress.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/utils/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/utils/test_dirs.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/utils/test_git.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tests/utils/test_toml.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/pyproject.toml +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/__init__.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/cli.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/names.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/conftest.py.j2 +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/gitignore.j2 +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/init.py.j2 +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/makefile.j2 +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/patterns.py.j2 +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/readme.md.j2 +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/sample_log.j2 +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/test_detection.py.j2 +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/src/sawmill_plugin_generator/templates/test_severity.py.j2 +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/tests/test_cli.py +0 -0
- {sawmill_parser-0.2.4 → sawmill_parser-0.4.0}/tools/sawmill-plugin-generator/tests/test_names.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: sawmill-parser
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A configurable TUI for post-processing and filtering structured tool logs
|
|
5
|
-
Project-URL: Homepage, https://github.com/
|
|
6
|
-
Project-URL: Documentation, https://github.
|
|
7
|
-
Project-URL: Repository, https://github.com/
|
|
5
|
+
Project-URL: Homepage, https://github.com/sjalloq/sawmill
|
|
6
|
+
Project-URL: Documentation, https://sjalloq.github.io/sawmill
|
|
7
|
+
Project-URL: Repository, https://github.com/sjalloq/sawmill
|
|
8
8
|
Author: Sawmill Contributors
|
|
9
9
|
License: MIT
|
|
10
10
|
Keywords: eda,filter,log,terminal,tui
|
|
@@ -33,6 +33,11 @@ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
|
33
33
|
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
34
34
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
35
35
|
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
36
|
+
Provides-Extra: docs
|
|
37
|
+
Requires-Dist: furo; extra == 'docs'
|
|
38
|
+
Requires-Dist: sphinx; extra == 'docs'
|
|
39
|
+
Requires-Dist: sphinx-autodoc-typehints; extra == 'docs'
|
|
40
|
+
Requires-Dist: sphinx-copybutton; extra == 'docs'
|
|
36
41
|
Description-Content-Type: text/markdown
|
|
37
42
|
|
|
38
43
|
<p align="center">
|
|
@@ -125,11 +130,12 @@ sawmill vivado.log --suppress-id "Synth 8-7129"
|
|
|
125
130
|
|
|
126
131
|
Sawmill uses a plugin architecture to support different log formats. Plugins are auto-detected based on log content.
|
|
127
132
|
|
|
128
|
-
###
|
|
133
|
+
### Available Plugins
|
|
129
134
|
|
|
130
|
-
| Plugin | Description |
|
|
131
|
-
|
|
132
|
-
| `vivado` | Xilinx Vivado synthesis and implementation logs |
|
|
135
|
+
| Plugin | Repo | Description |
|
|
136
|
+
|--------|--------|-------------|
|
|
137
|
+
| `vivado` | https://github.com/sjalloq/sawmill-plugin-vivado | Xilinx Vivado synthesis and implementation logs |
|
|
138
|
+
| `slang` | https://github.com/sjalloq/sawmill-plugin-slang | Slang System Verilog Compiler |
|
|
133
139
|
|
|
134
140
|
### External Plugins
|
|
135
141
|
|
|
@@ -88,11 +88,12 @@ sawmill vivado.log --suppress-id "Synth 8-7129"
|
|
|
88
88
|
|
|
89
89
|
Sawmill uses a plugin architecture to support different log formats. Plugins are auto-detected based on log content.
|
|
90
90
|
|
|
91
|
-
###
|
|
91
|
+
### Available Plugins
|
|
92
92
|
|
|
93
|
-
| Plugin | Description |
|
|
94
|
-
|
|
95
|
-
| `vivado` | Xilinx Vivado synthesis and implementation logs |
|
|
93
|
+
| Plugin | Repo | Description |
|
|
94
|
+
|--------|--------|-------------|
|
|
95
|
+
| `vivado` | https://github.com/sjalloq/sawmill-plugin-vivado | Xilinx Vivado synthesis and implementation logs |
|
|
96
|
+
| `slang` | https://github.com/sjalloq/sawmill-plugin-slang | Slang System Verilog Compiler |
|
|
96
97
|
|
|
97
98
|
### External Plugins
|
|
98
99
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Sawmill performance: findings and plan
|
|
2
|
+
|
|
3
|
+
Working notes for making chip-level logs (100 MB+, 1M+ messages) usable.
|
|
4
|
+
Supersedes the earlier streaming spec, whose diagnosis was wrong.
|
|
5
|
+
|
|
6
|
+
## 1. What was measured (2026-11-04)
|
|
7
|
+
|
|
8
|
+
Synthetic Xcelium log, 200k messages, 24 MB, Python 3.14, Textual DataTable.
|
|
9
|
+
|
|
10
|
+
| Step | Time |
|
|
11
|
+
|---|---|
|
|
12
|
+
| Plugin parse alone | 0.7 s |
|
|
13
|
+
| TUI load until table drawn | 9.4 s |
|
|
14
|
+
| Filter logic excluding table rebuild | 0.14 s |
|
|
15
|
+
| Clear filter / sort / severity toggle | 6 to 9 s |
|
|
16
|
+
| First scroll after rebuild | 5 s |
|
|
17
|
+
| RSS after parse / after table populated | 219 MB / 404 MB |
|
|
18
|
+
|
|
19
|
+
- About 90 % of load time and 95 % of every interactive action is spent
|
|
20
|
+
inside `DataTable.add_row` and the deferred `_update_dimensions` pass that
|
|
21
|
+
measures every cell. DataTable virtualises rendering, not population.
|
|
22
|
+
- Parse is bound by Python object creation (dict per match, Message, FileRef,
|
|
23
|
+
metadata dict), not regex (under 20 %) or I/O (about 6 %).
|
|
24
|
+
- Memory per Message was about 820 B: 184 B metadata dict, 167 B raw_text,
|
|
25
|
+
104 B slots object, 86 B content (always a substring of raw_text), plus
|
|
26
|
+
unshared copies of message_id, category and file path.
|
|
27
|
+
|
|
28
|
+
## 2. Done
|
|
29
|
+
|
|
30
|
+
- Plugin hook `load_and_parse` returns `Iterator[Message]` (plugin API 2).
|
|
31
|
+
`sawmill.core.plugin.load_messages()` wraps list-returning plugins.
|
|
32
|
+
Plugins open the file eagerly so errors surface at call time.
|
|
33
|
+
- `Message` and `FileRef` are `@dataclass(slots=True)` with int coercion.
|
|
34
|
+
- Xcelium plugin: single regex, no per-match dict, interned strings, shared
|
|
35
|
+
per-tool metadata dict. 820 B to 520 B per message, output identical.
|
|
36
|
+
- Benchmarks in `tools/bench/` report current and peak RSS and go through
|
|
37
|
+
the real filter helpers.
|
|
38
|
+
|
|
39
|
+
## 3. In progress
|
|
40
|
+
|
|
41
|
+
- Windowed DataTable: the table holds a window of rows around the cursor;
|
|
42
|
+
`_filtered_messages` stays the full list. Branch `perf/windowed-table`.
|
|
43
|
+
- One waiver match per message; redundant list copies removed.
|
|
44
|
+
|
|
45
|
+
## 4. Next: store investigation
|
|
46
|
+
|
|
47
|
+
After the view is fixed, measure a real 130 MB log. If memory is still the
|
|
48
|
+
complaint, compare on a branch:
|
|
49
|
+
|
|
50
|
+
- Intern-only (current design, about 500 B/message, no new dependency).
|
|
51
|
+
- DuckDB: columnar, dictionary-encoded, vectorised filter/sort/group-by,
|
|
52
|
+
native regex, spill to disk, `LIMIT/OFFSET` paging. Roughly 150 to 250 MB
|
|
53
|
+
per 1M messages. Cost: 50 MB wheel, 100 ms import, SQL in the app, waiver
|
|
54
|
+
and suppression logic expressed as predicates.
|
|
55
|
+
- Polars: same idea, dataframe API, lighter, no spill.
|
|
56
|
+
- Hand-rolled columnar (int arrays plus byte offsets, text re-read from
|
|
57
|
+
mmap): about 40 B/message but a third plugin contract and binary-mode
|
|
58
|
+
offsets in every plugin. Only if the above are insufficient.
|
|
59
|
+
|
|
60
|
+
Decide with numbers from the real log, not estimates.
|
|
61
|
+
|
|
62
|
+
## 5. Dropped
|
|
63
|
+
|
|
64
|
+
Background parse thread, stats-only mode, Rust hot loop and FSM preamble
|
|
65
|
+
matcher are parked. Parse is under 10 % of load; none of these move the
|
|
66
|
+
user-visible numbers until the view and store are settled.
|
|
@@ -33,6 +33,14 @@ parse each tool's output.
|
|
|
33
33
|
plugin-guide/pre-defined-filters
|
|
34
34
|
plugin-guide/writing-a-plugin
|
|
35
35
|
|
|
36
|
+
.. toctree::
|
|
37
|
+
:maxdepth: 2
|
|
38
|
+
:caption: Performance
|
|
39
|
+
|
|
40
|
+
performance/overview
|
|
41
|
+
performance/plugin-api-2
|
|
42
|
+
performance/store-investigation
|
|
43
|
+
|
|
36
44
|
.. toctree::
|
|
37
45
|
:maxdepth: 2
|
|
38
46
|
:caption: Reference
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Performance overview
|
|
2
|
+
====================
|
|
3
|
+
|
|
4
|
+
This page records what was measured, what was changed, and the current
|
|
5
|
+
numbers.
|
|
6
|
+
|
|
7
|
+
What was measured
|
|
8
|
+
-----------------
|
|
9
|
+
|
|
10
|
+
Measurements were taken on 2026-11-04 with a synthetic Xcelium log from
|
|
11
|
+
``tools/bench/gen_xcelium_log.py`` (200k messages, 24 MB).
|
|
12
|
+
|
|
13
|
+
.. list-table:: Before
|
|
14
|
+
:header-rows: 1
|
|
15
|
+
|
|
16
|
+
* - Step
|
|
17
|
+
- Before
|
|
18
|
+
* - Plugin parse alone
|
|
19
|
+
- 0.7 s
|
|
20
|
+
* - TUI load until table drawn
|
|
21
|
+
- 9.4 s
|
|
22
|
+
* - Filter logic excluding table rebuild
|
|
23
|
+
- 0.14 s
|
|
24
|
+
* - Clear filter / sort / severity toggle
|
|
25
|
+
- 6 to 9 s
|
|
26
|
+
* - First scroll after rebuild
|
|
27
|
+
- 5 s
|
|
28
|
+
* - RSS after parse / after table drawn
|
|
29
|
+
- 219 MB / 404 MB
|
|
30
|
+
|
|
31
|
+
Diagnosis
|
|
32
|
+
---------
|
|
33
|
+
|
|
34
|
+
- About 90% of load and 95% of each interactive action was Textual
|
|
35
|
+
``DataTable.add_row`` plus its deferred ``_update_dimensions`` cell
|
|
36
|
+
measurement. ``DataTable`` virtualises rendering, not population.
|
|
37
|
+
- Parse was bound by Python object creation (about 40%: per-match dict,
|
|
38
|
+
``Message``, ``FileRef``, metadata dict). Regex was under 20%, I/O
|
|
39
|
+
about 6%.
|
|
40
|
+
- Memory per ``Message`` was about 820 B:
|
|
41
|
+
|
|
42
|
+
- metadata dict: 184 B
|
|
43
|
+
- ``raw_text``: 167 B
|
|
44
|
+
- slots object: 104 B
|
|
45
|
+
- ``content``: 86 B (always a substring of ``raw_text``)
|
|
46
|
+
- unshared ``message_id`` / ``category`` / path strings: about 144 B
|
|
47
|
+
|
|
48
|
+
What was changed
|
|
49
|
+
----------------
|
|
50
|
+
|
|
51
|
+
- **Plugin API 2**: ``load_and_parse`` returns ``Iterator[Message]``.
|
|
52
|
+
API 1 plugins returning lists keep working. See :doc:`plugin-api-2`.
|
|
53
|
+
- **Message model**: ``Message`` and ``FileRef`` are
|
|
54
|
+
``@dataclass(slots=True)`` (``FileRef`` is frozen).
|
|
55
|
+
``__post_init__`` coerces ``start_line``, ``end_line`` and ``line`` to
|
|
56
|
+
``int``. No Pydantic on the hot path.
|
|
57
|
+
- **Windowed table**: the TUI populates only a window of rows around the
|
|
58
|
+
cursor, ``max(3 × visible rows, 200)``, and re-windows centred on the
|
|
59
|
+
cursor as it moves. The subtitle shows ``row i of N``.
|
|
60
|
+
- **Waivers**: matched once per message; redundant list copies removed.
|
|
61
|
+
|
|
62
|
+
Benchmarks
|
|
63
|
+
----------
|
|
64
|
+
|
|
65
|
+
- ``tools/bench/gen_xcelium_log.py``: synthetic log generator.
|
|
66
|
+
- ``tools/bench/run_bench.py``: size sweep, fresh subprocess per size,
|
|
67
|
+
current RSS from ``/proc/self/statm`` plus peak.
|
|
68
|
+
- ``tools/bench/bench_message.py``: ``Message`` construction cost.
|
|
69
|
+
|
|
70
|
+
Current numbers
|
|
71
|
+
---------------
|
|
72
|
+
|
|
73
|
+
.. list-table:: 200k messages
|
|
74
|
+
:header-rows: 1
|
|
75
|
+
|
|
76
|
+
* - Action
|
|
77
|
+
- Before
|
|
78
|
+
- After
|
|
79
|
+
* - Load
|
|
80
|
+
- 9.7 s
|
|
81
|
+
- 0.24 s
|
|
82
|
+
* - Clear filter
|
|
83
|
+
- 8.87 s
|
|
84
|
+
- 0.12 s
|
|
85
|
+
* - Sort
|
|
86
|
+
- 8.93 s
|
|
87
|
+
- 0.26 s
|
|
88
|
+
* - Severity toggle
|
|
89
|
+
- 6.66 s
|
|
90
|
+
- 0.18 s
|
|
91
|
+
* - RSS after table drawn
|
|
92
|
+
- 332 MB
|
|
93
|
+
- 158 MB
|
|
94
|
+
|
|
95
|
+
At 1M messages:
|
|
96
|
+
|
|
97
|
+
- parse: 3.5 s
|
|
98
|
+
- load: 0.49 s
|
|
99
|
+
- filter / sort / toggles: 0.36 to 0.70 s
|
|
100
|
+
- RSS: about 550 MB after parse, 660 MB at end
|
|
101
|
+
|
|
102
|
+
A column store was also evaluated and not adopted; see
|
|
103
|
+
:doc:`store-investigation`.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Plugin API 2
|
|
2
|
+
============
|
|
3
|
+
|
|
4
|
+
``SAWMILL_PLUGIN_API`` is ``"2"``. The change is to ``load_and_parse``,
|
|
5
|
+
which now streams messages.
|
|
6
|
+
|
|
7
|
+
The contract
|
|
8
|
+
------------
|
|
9
|
+
|
|
10
|
+
.. code-block:: python
|
|
11
|
+
|
|
12
|
+
@hookspec
|
|
13
|
+
def load_and_parse(self, path: Path) -> Iterator[Message]: ...
|
|
14
|
+
|
|
15
|
+
- Plugins should be generators reading the file line by line.
|
|
16
|
+
- Yield each ``Message`` as soon as it is complete.
|
|
17
|
+
- The ``SawmillPlugin`` base class default is a generator that yields
|
|
18
|
+
nothing.
|
|
19
|
+
- ``[tool.sawmill] plugin_api`` in a plugin's ``pyproject.toml`` is
|
|
20
|
+
informational; nothing enforces it.
|
|
21
|
+
|
|
22
|
+
Migrating from API 1
|
|
23
|
+
--------------------
|
|
24
|
+
|
|
25
|
+
API 1 returned ``list[Message]``. To migrate:
|
|
26
|
+
|
|
27
|
+
- Replace ``messages.append(msg)`` with ``yield msg``.
|
|
28
|
+
- Drop the final ``return messages``.
|
|
29
|
+
- Flush any in-flight multi-line message after the loop with a final
|
|
30
|
+
``yield``.
|
|
31
|
+
|
|
32
|
+
The ``load_messages`` shim
|
|
33
|
+
--------------------------
|
|
34
|
+
|
|
35
|
+
The base app calls plugins through ``sawmill.core.plugin.load_messages``:
|
|
36
|
+
|
|
37
|
+
.. code-block:: python
|
|
38
|
+
|
|
39
|
+
def load_messages(plugin, path) -> Iterator[Message]:
|
|
40
|
+
return iter(plugin.load_and_parse(path))
|
|
41
|
+
|
|
42
|
+
A legacy list is wrapped with ``iter()``, so API 1 plugins keep working
|
|
43
|
+
unchanged. They do not get the memory benefit.
|
|
44
|
+
|
|
45
|
+
Eager-open pattern
|
|
46
|
+
------------------
|
|
47
|
+
|
|
48
|
+
A plain generator does not run until the first ``next()``, so a missing
|
|
49
|
+
file would raise deep inside the consumer. Open the file in an outer
|
|
50
|
+
function and return an inner generator over the handle:
|
|
51
|
+
|
|
52
|
+
.. code-block:: python
|
|
53
|
+
|
|
54
|
+
@hookimpl
|
|
55
|
+
def load_and_parse(self, path):
|
|
56
|
+
f = open(path, errors="replace") # raises here if missing
|
|
57
|
+
return self._iter_messages(f)
|
|
58
|
+
|
|
59
|
+
def _iter_messages(self, f):
|
|
60
|
+
try:
|
|
61
|
+
current = None
|
|
62
|
+
for line_num, line in enumerate(f, start=1):
|
|
63
|
+
... # build or extend current; yield when complete
|
|
64
|
+
if current is not None:
|
|
65
|
+
yield current # flush the in-flight message
|
|
66
|
+
finally:
|
|
67
|
+
f.close()
|
|
68
|
+
|
|
69
|
+
Caveat: a generator that is never iterated keeps its file handle open
|
|
70
|
+
until garbage collection.
|
|
71
|
+
|
|
72
|
+
Allocation guidance
|
|
73
|
+
-------------------
|
|
74
|
+
|
|
75
|
+
Parse time is dominated by Python object creation, not regex or I/O.
|
|
76
|
+
|
|
77
|
+
- Read with ``for line in f``, never ``readlines()``.
|
|
78
|
+
- Use one combined regex per line rather than two tried in sequence.
|
|
79
|
+
- Use ``m.groups()`` or a ``NamedTuple``, not a dict per line.
|
|
80
|
+
- Intern repeated strings (tool, rule / message ID, file path) through a
|
|
81
|
+
small cache dict.
|
|
82
|
+
- Share one ``metadata`` dict per constant value, or omit ``metadata``.
|
|
83
|
+
|
|
84
|
+
Measured effect on an Xcelium plugin at 200k messages, output identical:
|
|
85
|
+
|
|
86
|
+
- memory: 820 → 520 B per message
|
|
87
|
+
- parse: 0.67 → 0.63 s
|
|
88
|
+
|
|
89
|
+
The remaining per-message cost is mostly the ``Message`` object itself.
|
|
90
|
+
The realistic Python ceiling is about 2 µs and 400 B per message.
|
|
91
|
+
|
|
92
|
+
Generated plugins
|
|
93
|
+
-----------------
|
|
94
|
+
|
|
95
|
+
The plugin generator template ``plugin.py.j2`` (under
|
|
96
|
+
``tools/sawmill-plugin-generator/``) emits a generator-shaped
|
|
97
|
+
``load_and_parse``.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Column-store investigation
|
|
2
|
+
==========================
|
|
3
|
+
|
|
4
|
+
A spike tested whether a column store would beat a list of ``Message``
|
|
5
|
+
objects. The code lives on branch ``spike/duckdb-store``
|
|
6
|
+
(``tools/bench/store_spike.py``).
|
|
7
|
+
|
|
8
|
+
Stores compared
|
|
9
|
+
---------------
|
|
10
|
+
|
|
11
|
+
Three stores behind one query interface:
|
|
12
|
+
|
|
13
|
+
- **List**: list of ``Message`` (today).
|
|
14
|
+
- **DuckDB**: in-memory table, ingested in 50k-row batches via pyarrow.
|
|
15
|
+
``executemany`` was unusable (25 s per 50k rows).
|
|
16
|
+
- **Polars**: DataFrame built from dicts of columns per batch.
|
|
17
|
+
|
|
18
|
+
Pandas was ruled out up front: per-row string handling is Python
|
|
19
|
+
objects anyway, and the wheel is heavy.
|
|
20
|
+
|
|
21
|
+
Results
|
|
22
|
+
-------
|
|
23
|
+
|
|
24
|
+
1M messages, 128 MB log. "DuckDB file" is file-backed with a 256 MB
|
|
25
|
+
memory limit and late materialisation.
|
|
26
|
+
|
|
27
|
+
.. list-table::
|
|
28
|
+
:header-rows: 1
|
|
29
|
+
|
|
30
|
+
* -
|
|
31
|
+
- List
|
|
32
|
+
- DuckDB in-memory
|
|
33
|
+
- DuckDB file
|
|
34
|
+
- Polars
|
|
35
|
+
* - Ingest
|
|
36
|
+
- 3.56 s
|
|
37
|
+
- 4.03 s
|
|
38
|
+
- 3.87 s
|
|
39
|
+
- 2.33 s
|
|
40
|
+
* - Time to first batch
|
|
41
|
+
- 0.15 s
|
|
42
|
+
- 0.16 s
|
|
43
|
+
- 0.18 s
|
|
44
|
+
- 0.12 s
|
|
45
|
+
* - RSS after ingest
|
|
46
|
+
- 531 MB
|
|
47
|
+
- 495 MB
|
|
48
|
+
- 294 MB
|
|
49
|
+
- 423 MB
|
|
50
|
+
* - Severity filter
|
|
51
|
+
- 22 ms
|
|
52
|
+
- 4 ms
|
|
53
|
+
- 3 ms
|
|
54
|
+
- 6 ms
|
|
55
|
+
* - ID glob
|
|
56
|
+
- 200 ms
|
|
57
|
+
- 1 ms
|
|
58
|
+
- 1 ms
|
|
59
|
+
- 6 ms
|
|
60
|
+
* - Regex (common pattern)
|
|
61
|
+
- 439 ms
|
|
62
|
+
- 19 ms
|
|
63
|
+
- 22 ms
|
|
64
|
+
- 7 ms
|
|
65
|
+
* - ID counts (stats)
|
|
66
|
+
- 46 ms
|
|
67
|
+
- 3 ms
|
|
68
|
+
- 1 ms
|
|
69
|
+
- 1 ms
|
|
70
|
+
* - Sort by ID count + page of 200
|
|
71
|
+
- 398 ms
|
|
72
|
+
- 113 ms
|
|
73
|
+
- 124 ms
|
|
74
|
+
- 32 ms
|
|
75
|
+
* - Combined keystroke (filter + sort + page)
|
|
76
|
+
- 389 ms
|
|
77
|
+
- 35 ms
|
|
78
|
+
- 36 ms
|
|
79
|
+
- 12 ms
|
|
80
|
+
|
|
81
|
+
Other findings
|
|
82
|
+
--------------
|
|
83
|
+
|
|
84
|
+
- With a 256 MB ``memory_limit``, a full-row ``ORDER BY`` raised
|
|
85
|
+
``OutOfMemoryException`` even at 200k. Sorting only key columns, then
|
|
86
|
+
joining back the 200 page rows (late materialisation), fixed it and
|
|
87
|
+
was 5 to 10× faster generally.
|
|
88
|
+
- Without a limit and without late materialisation, RSS reached 2.4 GB.
|
|
89
|
+
- Parsing to ``Message`` costs about 0.8 s per 1M over yielding tuples
|
|
90
|
+
(2.58 vs 1.78 s). Ingest only improves if plugins yield tuples.
|
|
91
|
+
- Import time (warm): duckdb 45 ms, polars 48 ms, pyarrow 12 ms.
|
|
92
|
+
- Installed size: duckdb about 65 MB, pyarrow about 165 MB (needed for
|
|
93
|
+
DuckDB ingest), polars about 180 MB.
|
|
94
|
+
|
|
95
|
+
Awkward points
|
|
96
|
+
--------------
|
|
97
|
+
|
|
98
|
+
- Regex dialect changes from Python ``re`` to RE2 (DuckDB) or Rust regex
|
|
99
|
+
(Polars): no backreferences or lookaround. Saved filters and waiver
|
|
100
|
+
content patterns could break.
|
|
101
|
+
- Waivers would become a table joined on ``message_id`` with
|
|
102
|
+
``regexp_matches(raw_text, p, 's')``.
|
|
103
|
+
- Suppressions become ``message_id IN (...)`` and
|
|
104
|
+
``NOT regexp_matches``.
|
|
105
|
+
- The TUI, waiver generation and export hold ``Message`` objects and
|
|
106
|
+
would move to row indexes.
|
|
107
|
+
- ``metadata`` has no column.
|
|
108
|
+
- A tuple-yielding plugin hook is needed to realise the ingest gain.
|
|
109
|
+
|
|
110
|
+
Recommendation
|
|
111
|
+
--------------
|
|
112
|
+
|
|
113
|
+
Not adopted.
|
|
114
|
+
|
|
115
|
+
- The windowed view removed the user-visible problem.
|
|
116
|
+
- A store buys 10× faster keystrokes on any backend, but only
|
|
117
|
+
file-backed DuckDB with a limit saves memory (about 45%).
|
|
118
|
+
- Revisit only if a real 130 MB log shows memory is still the complaint,
|
|
119
|
+
or sub-50 ms filtering becomes a feature. File-backed DuckDB is then
|
|
120
|
+
the path.
|
|
121
|
+
|
|
122
|
+
Parked alternatives:
|
|
123
|
+
|
|
124
|
+
- background parse thread
|
|
125
|
+
- Rust inner loop
|
|
126
|
+
- hand-rolled columnar arrays with byte offsets (about 40 B per message,
|
|
127
|
+
but a third plugin contract)
|
|
@@ -22,10 +22,10 @@ interface:
|
|
|
22
22
|
Detection --- examine a file and return a confidence score (0.0 to
|
|
23
23
|
1.0) indicating how well this plugin can parse it.
|
|
24
24
|
|
|
25
|
-
``load_and_parse(path) ->
|
|
26
|
-
Parsing --- open the file, parse
|
|
27
|
-
messages, extract severity and message IDs, and
|
|
28
|
-
|
|
25
|
+
``load_and_parse(path) -> Iterator[Message]``
|
|
26
|
+
Parsing --- open the file, parse it line by line, group multi-line
|
|
27
|
+
messages, extract severity and message IDs, and yield each
|
|
28
|
+
``Message`` as it is completed (plugin API 2).
|
|
29
29
|
|
|
30
30
|
``get_severity_levels() -> list[dict]`` (**required**)
|
|
31
31
|
Define the tool's severity levels with ``id``, ``name``, ``level``
|
|
@@ -88,11 +88,13 @@ extension, or first few lines of the file:
|
|
|
88
88
|
pass
|
|
89
89
|
return 0.0
|
|
90
90
|
|
|
91
|
-
``load_and_parse(path) ->
|
|
92
|
-
|
|
91
|
+
``load_and_parse(path) -> Iterator[Message]`` (optional)
|
|
92
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
93
93
|
|
|
94
|
-
Open the file, parse
|
|
95
|
-
|
|
94
|
+
Open the file, parse it line by line, group multi-line messages, and
|
|
95
|
+
``yield`` each ``Message`` as soon as it is complete. Since plugin API 2
|
|
96
|
+
this hook is a generator; returning a list still works but forces the
|
|
97
|
+
whole log into memory. See :doc:`/performance/plugin-api-2`.
|
|
96
98
|
|
|
97
99
|
.. code-block:: python
|
|
98
100
|
|
|
@@ -100,20 +102,24 @@ a flat list of ``Message`` objects:
|
|
|
100
102
|
|
|
101
103
|
@hookimpl
|
|
102
104
|
def load_and_parse(self, path):
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
f = open(path, errors="replace") # open eagerly: errors surface here
|
|
106
|
+
return self._iter_messages(f)
|
|
107
|
+
|
|
108
|
+
def _iter_messages(self, f):
|
|
109
|
+
try:
|
|
105
110
|
for line_num, line in enumerate(f, start=1):
|
|
106
111
|
severity, msg_id, content = self._parse_line(line)
|
|
107
112
|
if severity:
|
|
108
|
-
|
|
113
|
+
yield Message(
|
|
109
114
|
start_line=line_num,
|
|
110
115
|
end_line=line_num,
|
|
111
116
|
raw_text=line.rstrip(),
|
|
112
117
|
content=content,
|
|
113
118
|
severity=severity,
|
|
114
119
|
message_id=msg_id,
|
|
115
|
-
)
|
|
116
|
-
|
|
120
|
+
)
|
|
121
|
+
finally:
|
|
122
|
+
f.close()
|
|
117
123
|
|
|
118
124
|
``get_severity_levels() -> list[dict]`` (**required**)
|
|
119
125
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
@@ -235,10 +241,21 @@ The typical workflow is:
|
|
|
235
241
|
message ID format, and multi-line grouping rules.
|
|
236
242
|
2. Write regex patterns in ``patterns.py``.
|
|
237
243
|
3. Implement ``can_handle()`` to detect the tool's log files.
|
|
238
|
-
4. Implement ``load_and_parse()``
|
|
244
|
+
4. Implement ``load_and_parse()`` as a generator that yields messages.
|
|
239
245
|
5. Implement ``get_severity_levels()`` with the correct level mapping.
|
|
240
246
|
6. Optionally add ``get_filters()`` for common categories.
|
|
241
247
|
|
|
248
|
+
Performance checklist
|
|
249
|
+
---------------------
|
|
250
|
+
|
|
251
|
+
- Make ``load_and_parse()`` a generator; read with ``for line in f``,
|
|
252
|
+
never ``readlines()``.
|
|
253
|
+
- Open the file eagerly so missing-file errors are raised at the call.
|
|
254
|
+
- Use one combined regex per line; avoid a dict per match.
|
|
255
|
+
- Intern repeated strings and share or omit ``metadata`` dicts.
|
|
256
|
+
|
|
257
|
+
Details and measurements: :doc:`/performance/plugin-api-2`.
|
|
258
|
+
|
|
242
259
|
Testing
|
|
243
260
|
-------
|
|
244
261
|
|
|
@@ -45,21 +45,24 @@ dev = [
|
|
|
45
45
|
"ruff>=0.4.0",
|
|
46
46
|
"pre-commit>=3.0.0",
|
|
47
47
|
]
|
|
48
|
+
docs = [
|
|
49
|
+
"sphinx",
|
|
50
|
+
"furo",
|
|
51
|
+
"sphinx-autodoc-typehints",
|
|
52
|
+
"sphinx-copybutton",
|
|
53
|
+
]
|
|
48
54
|
|
|
49
55
|
[project.scripts]
|
|
50
56
|
sawmill = "sawmill.__main__:cli"
|
|
51
57
|
|
|
52
58
|
[project.urls]
|
|
53
|
-
Homepage = "https://github.com/
|
|
54
|
-
Documentation = "https://github.
|
|
55
|
-
Repository = "https://github.com/
|
|
59
|
+
Homepage = "https://github.com/sjalloq/sawmill"
|
|
60
|
+
Documentation = "https://sjalloq.github.io/sawmill"
|
|
61
|
+
Repository = "https://github.com/sjalloq/sawmill"
|
|
56
62
|
|
|
57
63
|
[tool.hatch.version]
|
|
58
64
|
source = "vcs"
|
|
59
65
|
|
|
60
|
-
[tool.hatch.metadata]
|
|
61
|
-
allow-direct-references = true
|
|
62
|
-
|
|
63
66
|
[tool.hatch.build.targets.wheel]
|
|
64
67
|
packages = ["sawmill"]
|
|
65
68
|
|