pg-maker 0.1.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.
- pg_maker/__init__.py +3 -0
- pg_maker/__main__.py +3 -0
- pg_maker/api.py +594 -0
- pg_maker/artifacts/__init__.py +4 -0
- pg_maker/artifacts/catalog.py +67 -0
- pg_maker/artifacts/manifest.py +301 -0
- pg_maker/assets.py +93 -0
- pg_maker/builders/__init__.py +4 -0
- pg_maker/builders/autotools.py +430 -0
- pg_maker/builders/components.py +359 -0
- pg_maker/bundled/PROJECT.md +57 -0
- pg_maker/bundled/build.yaml +29 -0
- pg_maker/bundled/components/pg-stat-kcache.yaml +30 -0
- pg_maker/bundled/components/pg-stat-statements.yaml +24 -0
- pg_maker/bundled/components/pg-wait-sampling.yaml +30 -0
- pg_maker/bundled/config/defaults.yaml +80 -0
- pg_maker/bundled/config/documentation.yaml +24 -0
- pg_maker/bundled/config/features/my-feature.yaml +17 -0
- pg_maker/bundled/config/vendor.yaml +13 -0
- pg_maker/bundled/documentation/README.md +20 -0
- pg_maker/bundled/documentation/pg-stat-kcache.sgml +99 -0
- pg_maker/bundled/documentation/pg-stat-kcache.yaml +15 -0
- pg_maker/bundled/documentation/pg-wait-sampling.sgml +122 -0
- pg_maker/bundled/documentation/pg-wait-sampling.yaml +15 -0
- pg_maker/bundled/patches/unix-socket-directory.patch +5 -0
- pg_maker/changes/__init__.py +1 -0
- pg_maker/changes/apply.py +298 -0
- pg_maker/changes/loader.py +135 -0
- pg_maker/changes/workspace.py +221 -0
- pg_maker/cli.py +546 -0
- pg_maker/components/__init__.py +4 -0
- pg_maker/config.py +514 -0
- pg_maker/development/__init__.py +1 -0
- pg_maker/development/cli.py +104 -0
- pg_maker/development/git.py +362 -0
- pg_maker/development/session.py +735 -0
- pg_maker/documentation/__init__.py +4 -0
- pg_maker/documentation/builder.py +467 -0
- pg_maker/documentation/cli.py +24 -0
- pg_maker/documentation/config.py +127 -0
- pg_maker/documentation/install.py +79 -0
- pg_maker/documentation/site.py +166 -0
- pg_maker/documentation/sphinx_ext.py +150 -0
- pg_maker/documentation/theme/_static/pg-maker.css +26 -0
- pg_maker/documentation/theme/_static/pg-maker.js +35 -0
- pg_maker/documentation/theme/_templates/footer_en.html +5 -0
- pg_maker/documentation/theme/_templates/footer_metadata.html +13 -0
- pg_maker/documentation/theme/_templates/footer_ru.html +5 -0
- pg_maker/documentation/translation.py +341 -0
- pg_maker/documentation/validation.py +132 -0
- pg_maker/documentation/worker.py +62 -0
- pg_maker/documentation/writer.py +324 -0
- pg_maker/documentation/xml.py +210 -0
- pg_maker/documents.py +118 -0
- pg_maker/errors.py +50 -0
- pg_maker/execution/__init__.py +1 -0
- pg_maker/execution/build.py +502 -0
- pg_maker/execution/cache.py +287 -0
- pg_maker/execution/cancellation.py +60 -0
- pg_maker/execution/checkpoints.py +218 -0
- pg_maker/execution/cleanup.py +122 -0
- pg_maker/execution/component_cache.py +164 -0
- pg_maker/execution/development.py +109 -0
- pg_maker/execution/engine.py +171 -0
- pg_maker/execution/events.py +65 -0
- pg_maker/execution/incremental.py +178 -0
- pg_maker/execution/preparation.py +130 -0
- pg_maker/execution/process.py +155 -0
- pg_maker/execution/resources.py +41 -0
- pg_maker/execution/state.py +82 -0
- pg_maker/execution/worker.py +45 -0
- pg_maker/identity.py +116 -0
- pg_maker/integrations/__init__.py +1 -0
- pg_maker/integrations/machine.py +163 -0
- pg_maker/integrations/runtime.py +387 -0
- pg_maker/models.py +245 -0
- pg_maker/packaging/__init__.py +4 -0
- pg_maker/packaging/deb.py +337 -0
- pg_maker/packaging/service.py +165 -0
- pg_maker/packaging/spec.py +64 -0
- pg_maker/packaging/templates/deb-control.in +8 -0
- pg_maker/packaging/tree.py +239 -0
- pg_maker/planning/__init__.py +1 -0
- pg_maker/planning/documents.py +65 -0
- pg_maker/planning/environments.py +318 -0
- pg_maker/planning/graph.py +23 -0
- pg_maker/planning/plan.py +418 -0
- pg_maker/planning/resolve.py +117 -0
- pg_maker/planning/reuse.py +80 -0
- pg_maker/planning/runtime_repository.py +90 -0
- pg_maker/postgresql.py +59 -0
- pg_maker/progress.py +64 -0
- pg_maker/publishing/__init__.py +4 -0
- pg_maker/py.typed +0 -0
- pg_maker/recipes/README.md +13 -0
- pg_maker/recipes/platforms/debian-13.yaml +125 -0
- pg_maker/recipes/postgresql.yaml +114 -0
- pg_maker/runners/__init__.py +1 -0
- pg_maker/runners/base.py +29 -0
- pg_maker/runners/docker.py +312 -0
- pg_maker/schemas/build.schema.json +5 -0
- pg_maker/schemas/change-set.schema.json +5 -0
- pg_maker/schemas/component-envelope.schema.json +69 -0
- pg_maker/schemas/component.schema.json +5 -0
- pg_maker/schemas/defaults.schema.json +5 -0
- pg_maker/schemas/doc-rules.schema.json +5 -0
- pg_maker/schemas/documentation.schema.json +5 -0
- pg_maker/schemas/environment.schema.json +228 -0
- pg_maker/schemas/feature.schema.json +5 -0
- pg_maker/schemas/insertions.schema.json +5 -0
- pg_maker/schemas/lock.schema.json +3963 -0
- pg_maker/schemas/operation-input.schema.json +306 -0
- pg_maker/schemas/operation-state.schema.json +127 -0
- pg_maker/schemas/plan.schema.json +284 -0
- pg_maker/schemas/planning-common.schema.json +8192 -0
- pg_maker/schemas/platform.schema.json +5 -0
- pg_maker/schemas/postgresql-catalog.schema.json +5 -0
- pg_maker/schemas/run-event.schema.json +72 -0
- pg_maker/schemas/translations.schema.json +5 -0
- pg_maker/schemas/vendor.schema.json +5 -0
- pg_maker/security.py +73 -0
- pg_maker/sources/__init__.py +1 -0
- pg_maker/sources/resolver.py +238 -0
- pg_maker/sources/store.py +279 -0
- pg_maker/storage.py +127 -0
- pg_maker/validation/__init__.py +4 -0
- pg_maker/validation/installed.py +667 -0
- pg_maker/validation/packages.py +309 -0
- pg_maker/validation/systemctl.py +185 -0
- pg_maker/yamlio.py +54 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/CHANGELOG.md +22 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/CONTRIBUTING.md +63 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/LICENSE +21 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/README.md +163 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/architecture.md +55 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/development-storage.md +72 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/development.md +86 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/README.md +32 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/api.md +75 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/build.md +112 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/changes.md +58 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/cleanup.md +17 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/components.md +39 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/configuration.md +83 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/development.md +88 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/documentation.md +241 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/documents.md +42 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/environments.md +40 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/execution.md +49 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/machine.md +31 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/observability.md +28 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/packaging.md +107 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/planning.md +63 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/postgresql.md +79 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/publication.md +20 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/recovery.md +83 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/runtime.md +31 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/sources.md +48 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/state.md +49 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/storage.md +24 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/releasing.md +98 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/reuse.md +24 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/roadmap.md +54 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/default-components.md +56 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/development-release.md +49 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/documentation-theme.md +45 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/integration.md +49 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/postgresql-majors.md +107 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/review-fixes.md +68 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/stage4.md +65 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/stage56.md +66 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/stage7.md +72 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/tests/fixtures/guc/README.md +11 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/tests/fixtures/stage4/README.md +31 -0
- pg_maker-0.1.0.data/data/share/doc/pg-maker/tests/fixtures/stage7/README.md +16 -0
- pg_maker-0.1.0.dist-info/METADATA +200 -0
- pg_maker-0.1.0.dist-info/RECORD +181 -0
- pg_maker-0.1.0.dist-info/WHEEL +5 -0
- pg_maker-0.1.0.dist-info/entry_points.txt +4 -0
- pg_maker-0.1.0.dist-info/licenses/LICENSE +21 -0
- pg_maker-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Component manuals
|
|
2
|
+
|
|
3
|
+
These editable DocBook guides accompany the component recipes in `../components`.
|
|
4
|
+
They are included only when their component is selected and documentation is enabled.
|
|
5
|
+
The writer adds them to the upstream `contrib` appendix, using the same HTML theme,
|
|
6
|
+
navigation, full-text search, PDF and translation pipeline as the rest of the book.
|
|
7
|
+
|
|
8
|
+
| Guide | Source release | Pinned commit |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| pg-stat-kcache.sgml | pg_stat_kcache 2.3.2 | 1e39814453ab747a3817cfdf4f6f6bd5c0fb9507 |
|
|
11
|
+
| pg-wait-sampling.sgml | pg_wait_sampling 1.1.11 (SQL version 1.1) | 7e3a5fc937393596c0a51c5881dd63d7208408d1 |
|
|
12
|
+
|
|
13
|
+
Guides contain distribution-specific setup instructions and references to those
|
|
14
|
+
exact upstream revisions. Review the matching guide when changing a recipe's pin;
|
|
15
|
+
resolve fixes both source commits and documentation asset hashes in the lock.
|
|
16
|
+
These are maintained guides, not automatic imports of arbitrary upstream README files.
|
|
17
|
+
The pg_stat_statements manual already belongs to the selected PostgreSQL source tree.
|
|
18
|
+
|
|
19
|
+
English is the source language. Russian builds require reviewed translation bundles
|
|
20
|
+
for the new sections under the existing translation policy.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<sect1 id="pg-stat-kcache">
|
|
2
|
+
<title>pg_stat_kcache — operating system resource statistics</title>
|
|
3
|
+
<para><filename>pg_stat_kcache</filename> supplements
|
|
4
|
+
<xref linkend="pgstatstatements"/> with CPU consumption and filesystem I/O
|
|
5
|
+
counters. Use the two extensions together to identify queries that consume
|
|
6
|
+
CPU or cause reads and writes beyond PostgreSQL's shared buffers.</para>
|
|
7
|
+
<para>This distribution includes release 2.3.2. Installing its server package
|
|
8
|
+
supplies the library and SQL files; statistics collection is enabled separately
|
|
9
|
+
for an initialized cluster.</para>
|
|
10
|
+
|
|
11
|
+
<sect2 id="pg-stat-kcache-enable">
|
|
12
|
+
<title>Enabling collection</title>
|
|
13
|
+
<para>Add the required libraries to <filename>postgresql.conf</filename> inside
|
|
14
|
+
<envar>PGDATA</envar>, keeping any libraries already needed by the cluster.
|
|
15
|
+
The following example enables all three bundled diagnostic extensions:</para>
|
|
16
|
+
<programlisting>shared_preload_libraries = 'pg_stat_statements,pg_stat_kcache,pg_wait_sampling'
|
|
17
|
+
compute_query_id = auto</programlisting>
|
|
18
|
+
<para>Restart the server after changing <varname>shared_preload_libraries</varname>.
|
|
19
|
+
Then connect as an administrator to each database where the SQL interface is
|
|
20
|
+
needed and execute:</para>
|
|
21
|
+
<programlisting>CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
|
|
22
|
+
CREATE EXTENSION IF NOT EXISTS pg_stat_kcache;</programlisting>
|
|
23
|
+
<para>The matching client is <filename>${paths.runtime_prefix}/bin/psql</filename>.
|
|
24
|
+
Use libraries from the same PostgreSQL build and major version as the server.
|
|
25
|
+
Existing databases are not modified by installing the package.</para>
|
|
26
|
+
</sect2>
|
|
27
|
+
|
|
28
|
+
<sect2 id="pg-stat-kcache-settings">
|
|
29
|
+
<title>Configuration</title>
|
|
30
|
+
<variablelist>
|
|
31
|
+
<varlistentry><term><varname>pg_stat_kcache.track</varname></term><listitem>
|
|
32
|
+
<para><literal>top</literal>, the default, records top-level statements;
|
|
33
|
+
<literal>all</literal> also records nested statements; <literal>none</literal>
|
|
34
|
+
disables collection. Nested and parent totals can overlap.</para>
|
|
35
|
+
</listitem></varlistentry>
|
|
36
|
+
<varlistentry><term><varname>pg_stat_kcache.track_planning</varname></term><listitem>
|
|
37
|
+
<para>Controls planning measurements. The default is <literal>off</literal>;
|
|
38
|
+
planning counters remain zero while disabled.</para>
|
|
39
|
+
</listitem></varlistentry>
|
|
40
|
+
<varlistentry><term><varname>pg_stat_kcache.linux_hz</varname></term><listitem>
|
|
41
|
+
<para>Kernel clock frequency used to compensate for CPU measurement sampling.
|
|
42
|
+
The default, <literal>-1</literal>, requests automatic detection at startup.</para>
|
|
43
|
+
</listitem></varlistentry>
|
|
44
|
+
</variablelist>
|
|
45
|
+
</sect2>
|
|
46
|
+
|
|
47
|
+
<sect2 id="pg-stat-kcache-views">
|
|
48
|
+
<title>Views, counters and reset</title>
|
|
49
|
+
<para><structname>pg_stat_kcache_detail</structname> joins kernel counters to
|
|
50
|
+
query statistics. Its identity fields are <structfield>query</structfield>,
|
|
51
|
+
<structfield>top</structfield>, <structfield>datname</structfield> and
|
|
52
|
+
<structfield>rolname</structfield>.
|
|
53
|
+
<structname>pg_stat_kcache</structname> aggregates top-level statistics by database.
|
|
54
|
+
The function <function>pg_stat_kcache()</function> exposes the underlying rows,
|
|
55
|
+
including query, user and database identifiers.</para>
|
|
56
|
+
<para>Most measurements have a <literal>plan_</literal> and an
|
|
57
|
+
<literal>exec_</literal> variant. For example,
|
|
58
|
+
<structfield>exec_user_time</structfield> is execution CPU time in user mode.</para>
|
|
59
|
+
<informaltable><tgroup cols="2"><thead><row><entry>Counter suffix</entry><entry>Meaning</entry></row></thead><tbody>
|
|
60
|
+
<row><entry><literal>user_time</literal>, <literal>system_time</literal></entry><entry>CPU time in seconds, in user and kernel mode.</entry></row>
|
|
61
|
+
<row><entry><literal>reads</literal>, <literal>writes</literal></entry><entry>Filesystem I/O bytes attributed by the operating system.</entry></row>
|
|
62
|
+
<row><entry><literal>reads_blks</literal>, <literal>writes_blks</literal></entry><entry>The corresponding byte values divided by PostgreSQL's configured <varname>block_size</varname> in the SQL views (normally 8 KiB).</entry></row>
|
|
63
|
+
<row><entry><literal>minflts</literal>, <literal>majflts</literal></entry><entry>Minor and major page faults.</entry></row>
|
|
64
|
+
<row><entry><literal>nswaps</literal></entry><entry>Swaps reported by the operating system.</entry></row>
|
|
65
|
+
<row><entry><literal>msgsnds</literal>, <literal>msgrcvs</literal>, <literal>nsignals</literal></entry><entry>IPC messages sent/received and delivered signals.</entry></row>
|
|
66
|
+
<row><entry><literal>nvcsws</literal>, <literal>nivcsws</literal></entry><entry>Voluntary and involuntary context switches.</entry></row>
|
|
67
|
+
</tbody></tgroup></informaltable>
|
|
68
|
+
<para><structfield>stats_since</structfield> records when an entry began collecting
|
|
69
|
+
statistics. Availability and precision of operating system counters vary by
|
|
70
|
+
platform. Filesystem counters are different from PostgreSQL buffer hit/read
|
|
71
|
+
counters; CPU time is different from elapsed query time.</para>
|
|
72
|
+
<para>A superuser can clear the extension's counters with
|
|
73
|
+
<literal>SELECT pg_stat_kcache_reset();</literal>. This changes the measurement
|
|
74
|
+
baseline for other monitoring clients; take a snapshot before resetting.</para>
|
|
75
|
+
</sect2>
|
|
76
|
+
|
|
77
|
+
<sect2 id="pg-stat-kcache-examples">
|
|
78
|
+
<title>Finding resource-intensive queries</title>
|
|
79
|
+
<programlisting>SELECT datname, rolname, query,
|
|
80
|
+
exec_user_time + exec_system_time AS cpu_seconds,
|
|
81
|
+
exec_reads, exec_writes
|
|
82
|
+
FROM pg_stat_kcache_detail
|
|
83
|
+
WHERE top
|
|
84
|
+
ORDER BY cpu_seconds DESC
|
|
85
|
+
LIMIT 20;</programlisting>
|
|
86
|
+
<para>Collect two snapshots over a known interval and compare counter deltas to
|
|
87
|
+
measure current activity. A row can disappear when associated query statistics
|
|
88
|
+
are evicted. Access to other users' query text follows PostgreSQL's statistics
|
|
89
|
+
privileges.</para>
|
|
90
|
+
</sect2>
|
|
91
|
+
|
|
92
|
+
<sect2 id="pg-stat-kcache-source">
|
|
93
|
+
<title>Source and license</title>
|
|
94
|
+
<para>The extension is maintained by the PoWA community and distributed under
|
|
95
|
+
the PostgreSQL license. This guide accompanies the pinned
|
|
96
|
+
<ulink url="https://github.com/powa-team/pg_stat_kcache/blob/1e39814453ab747a3817cfdf4f6f6bd5c0fb9507/README.rst">2.3.2 upstream documentation</ulink>.
|
|
97
|
+
Source licenses are included in the installed package's copyright directory.</para>
|
|
98
|
+
</sect2>
|
|
99
|
+
</sect1>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
api_version: pg_maker/doc-rules/v1
|
|
2
|
+
kind: DocumentationRules
|
|
3
|
+
metadata:
|
|
4
|
+
id: pg-stat-kcache-manual
|
|
5
|
+
revision: 1
|
|
6
|
+
spec:
|
|
7
|
+
rules:
|
|
8
|
+
- id: pg-stat-kcache-page
|
|
9
|
+
action: add-page
|
|
10
|
+
file: pg-maker-pg-stat-kcache.sgml
|
|
11
|
+
fragment: pg-stat-kcache.sgml
|
|
12
|
+
entity: pg-maker-pg-stat-kcache
|
|
13
|
+
include_file: contrib.sgml
|
|
14
|
+
selector: {id: contrib}
|
|
15
|
+
expect_matches: 0
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<sect1 id="pg-wait-sampling">
|
|
2
|
+
<title>pg_wait_sampling — sampled wait event statistics</title>
|
|
3
|
+
<para><filename>pg_wait_sampling</filename> periodically samples backend wait
|
|
4
|
+
events. It keeps a recent history in a ring buffer and accumulates a profile
|
|
5
|
+
by process, event and optionally query. This helps identify I/O, lock and other
|
|
6
|
+
waits that a single inspection of <structname>pg_stat_activity</structname>
|
|
7
|
+
could miss.</para>
|
|
8
|
+
<para>This distribution includes release 1.1.11. Its SQL extension version is
|
|
9
|
+
<literal>1.1</literal>; the SQL version and the source release number are different.</para>
|
|
10
|
+
|
|
11
|
+
<sect2 id="pg-wait-sampling-enable">
|
|
12
|
+
<title>Enabling collection</title>
|
|
13
|
+
<para>The collector uses shared memory and a background worker. Add it to
|
|
14
|
+
<varname>shared_preload_libraries</varname> in the cluster's
|
|
15
|
+
<filename>postgresql.conf</filename> under <envar>PGDATA</envar> and restart the
|
|
16
|
+
server. Keep existing required libraries. When using
|
|
17
|
+
<xref linkend="pgstatstatements"/>, put it before
|
|
18
|
+
<literal>pg_wait_sampling</literal> so utility query identifiers are preserved:</para>
|
|
19
|
+
<programlisting>shared_preload_libraries = 'pg_stat_statements,pg_stat_kcache,pg_wait_sampling'
|
|
20
|
+
compute_query_id = auto</programlisting>
|
|
21
|
+
<para>After the restart, connect with
|
|
22
|
+
<filename>${paths.runtime_prefix}/bin/psql</filename> and create the SQL
|
|
23
|
+
interface in each database where it is needed:</para>
|
|
24
|
+
<programlisting>CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
|
|
25
|
+
CREATE EXTENSION IF NOT EXISTS pg_wait_sampling;</programlisting>
|
|
26
|
+
<para>The server package supplies the files but does not start the collector or
|
|
27
|
+
create extensions in existing databases. The collector can operate without
|
|
28
|
+
<literal>pg_stat_statements</literal>; combining them adds query statistics
|
|
29
|
+
useful for interpreting the sampled profile.</para>
|
|
30
|
+
</sect2>
|
|
31
|
+
|
|
32
|
+
<sect2 id="pg-wait-sampling-settings">
|
|
33
|
+
<title>Configuration</title>
|
|
34
|
+
<variablelist>
|
|
35
|
+
<varlistentry><term><varname>pg_wait_sampling.history_size</varname></term><listitem>
|
|
36
|
+
<para>Number of records retained in the history ring buffer. The default is
|
|
37
|
+
<literal>5000</literal>.</para>
|
|
38
|
+
</listitem></varlistentry>
|
|
39
|
+
<varlistentry><term><varname>pg_wait_sampling.history_period</varname></term><listitem>
|
|
40
|
+
<para>History sampling interval in milliseconds. The default is
|
|
41
|
+
<literal>10</literal>.</para>
|
|
42
|
+
</listitem></varlistentry>
|
|
43
|
+
<varlistentry><term><varname>pg_wait_sampling.profile_period</varname></term><listitem>
|
|
44
|
+
<para>Profile sampling interval in milliseconds. The default is
|
|
45
|
+
<literal>10</literal>.</para>
|
|
46
|
+
</listitem></varlistentry>
|
|
47
|
+
<varlistentry><term><varname>pg_wait_sampling.profile_pid</varname></term><listitem>
|
|
48
|
+
<para>The default, <literal>true</literal>, keeps separate profile rows for
|
|
49
|
+
processes; <literal>false</literal> aggregates processes with pid zero.</para>
|
|
50
|
+
</listitem></varlistentry>
|
|
51
|
+
<varlistentry><term><varname>pg_wait_sampling.profile_queries</varname></term><listitem>
|
|
52
|
+
<para>The default, <literal>top</literal>, records top-level query identifiers;
|
|
53
|
+
<literal>all</literal> includes nested queries; <literal>none</literal> records
|
|
54
|
+
queryid zero.</para>
|
|
55
|
+
</listitem></varlistentry>
|
|
56
|
+
<varlistentry><term><varname>pg_wait_sampling.sample_cpu</varname></term><listitem>
|
|
57
|
+
<para>The default, <literal>true</literal>, also samples processes with no
|
|
58
|
+
reported wait. Their event fields are NULL.</para>
|
|
59
|
+
</listitem></varlistentry>
|
|
60
|
+
</variablelist>
|
|
61
|
+
<para>Configure these settings in <filename>postgresql.conf</filename> or with
|
|
62
|
+
<command>ALTER SYSTEM</command>, then reload the configuration. Shorter intervals
|
|
63
|
+
produce more samples and increase collection work. The history covers a variable
|
|
64
|
+
amount of time because its capacity is shared by sampled processes.</para>
|
|
65
|
+
</sect2>
|
|
66
|
+
|
|
67
|
+
<sect2 id="pg-wait-sampling-views">
|
|
68
|
+
<title>Views and functions</title>
|
|
69
|
+
<variablelist>
|
|
70
|
+
<varlistentry><term><structname>pg_wait_sampling_current</structname></term><listitem>
|
|
71
|
+
<para>Current process events: <structfield>pid</structfield>,
|
|
72
|
+
<structfield>event_type</structfield>, <structfield>event</structfield> and
|
|
73
|
+
<structfield>queryid</structfield>.
|
|
74
|
+
<function>pg_wait_sampling_get_current(pid)</function> restricts the result
|
|
75
|
+
to one process.</para>
|
|
76
|
+
</listitem></varlistentry>
|
|
77
|
+
<varlistentry><term><structname>pg_wait_sampling_history</structname></term><listitem>
|
|
78
|
+
<para>Recent samples with the same identifiers and a sample timestamp
|
|
79
|
+
<structfield>ts</structfield>. Old rows are overwritten as the buffer fills.</para>
|
|
80
|
+
</listitem></varlistentry>
|
|
81
|
+
<varlistentry><term><structname>pg_wait_sampling_profile</structname></term><listitem>
|
|
82
|
+
<para>Accumulated sample counts in <structfield>count</structfield> (bigint),
|
|
83
|
+
grouped by process, event and query according to the collection settings.</para>
|
|
84
|
+
</listitem></varlistentry>
|
|
85
|
+
<varlistentry><term><function>pg_wait_sampling_reset_profile()</function></term><listitem>
|
|
86
|
+
<para>Clear the accumulated profile. Coordinate resets with monitoring clients
|
|
87
|
+
and retain snapshots when historical comparison is required.</para>
|
|
88
|
+
</listitem></varlistentry>
|
|
89
|
+
</variablelist>
|
|
90
|
+
</sect2>
|
|
91
|
+
|
|
92
|
+
<sect2 id="pg-wait-sampling-examples">
|
|
93
|
+
<title>Inspecting the profile</title>
|
|
94
|
+
<programlisting>SELECT event_type, event, sum(count) AS samples
|
|
95
|
+
FROM pg_wait_sampling_profile
|
|
96
|
+
GROUP BY event_type, event
|
|
97
|
+
ORDER BY samples DESC;
|
|
98
|
+
|
|
99
|
+
SELECT queryid, event_type, event, sum(count) AS samples
|
|
100
|
+
FROM pg_wait_sampling_profile
|
|
101
|
+
WHERE queryid <> 0
|
|
102
|
+
GROUP BY queryid, event_type, event
|
|
103
|
+
ORDER BY samples DESC
|
|
104
|
+
LIMIT 20;</programlisting>
|
|
105
|
+
<para>Counts represent samples, not measured event durations. Short waits can
|
|
106
|
+
fall between samples, and simultaneous processes contribute independently.
|
|
107
|
+
NULL event fields indicate that no wait was reported at sampling time; they
|
|
108
|
+
do not prove that a process was executing on a CPU. Query identifiers alone
|
|
109
|
+
do not uniquely identify database/user rows in
|
|
110
|
+
<structname>pg_stat_statements</structname>; retain that context when correlating
|
|
111
|
+
results.</para>
|
|
112
|
+
</sect2>
|
|
113
|
+
|
|
114
|
+
<sect2 id="pg-wait-sampling-source">
|
|
115
|
+
<title>Source and license</title>
|
|
116
|
+
<para>The upstream project was authored by Alexander Korotkov and Ildus
|
|
117
|
+
Kurbangaliev and is distributed under the PostgreSQL license. This guide
|
|
118
|
+
accompanies the pinned
|
|
119
|
+
<ulink url="https://github.com/postgrespro/pg_wait_sampling/blob/7e3a5fc937393596c0a51c5881dd63d7208408d1/README.md">1.1.11 upstream documentation</ulink>.
|
|
120
|
+
Source licenses are included in the installed package's copyright directory.</para>
|
|
121
|
+
</sect2>
|
|
122
|
+
</sect1>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
api_version: pg_maker/doc-rules/v1
|
|
2
|
+
kind: DocumentationRules
|
|
3
|
+
metadata:
|
|
4
|
+
id: pg-wait-sampling-manual
|
|
5
|
+
revision: 1
|
|
6
|
+
spec:
|
|
7
|
+
rules:
|
|
8
|
+
- id: pg-wait-sampling-page
|
|
9
|
+
action: add-page
|
|
10
|
+
file: pg-maker-pg-wait-sampling.sgml
|
|
11
|
+
fragment: pg-wait-sampling.sgml
|
|
12
|
+
entity: pg-maker-pg-wait-sampling
|
|
13
|
+
include_file: contrib.sgml
|
|
14
|
+
selector: {id: contrib}
|
|
15
|
+
expect_matches: 0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""ChangeSet loading, dependencies and frozen inputs; application follows in stage 4."""
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""Strict, transactional text changes on private source copies; no shell or fuzz."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import difflib
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from pg_maker.documents import relative_output
|
|
12
|
+
from pg_maker.errors import PreconditionError, UnsupportedError, ValidationError
|
|
13
|
+
from pg_maker.sources.store import inventory
|
|
14
|
+
from pg_maker.storage import content_hash, file_hash, write_json
|
|
15
|
+
from pg_maker.yamlio import load_yaml
|
|
16
|
+
|
|
17
|
+
SOCKET_DEFAULT_LINE = (
|
|
18
|
+
'#define DEFAULT_PGSOCKET_DIR "/var/run/postgresql" /* Shared by server and libpq. */\n'
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def target_path(root: Path, relative: str) -> Path:
|
|
23
|
+
if not relative_output(relative):
|
|
24
|
+
raise ValidationError("change path must be relative", details={"path": relative})
|
|
25
|
+
path = root / relative
|
|
26
|
+
if any(p.is_symlink() for p in (path, *path.parents)):
|
|
27
|
+
raise ValidationError(
|
|
28
|
+
"change target must not traverse a symlink", details={"path": relative}
|
|
29
|
+
)
|
|
30
|
+
if path.exists() and not path.is_file():
|
|
31
|
+
raise ValidationError("change target must be a regular file", details={"path": relative})
|
|
32
|
+
return path
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def insert_text(root: Path, document: dict) -> None:
|
|
36
|
+
path = target_path(root, document["target_file"])
|
|
37
|
+
if not path.is_file():
|
|
38
|
+
raise PreconditionError("insertion target is missing")
|
|
39
|
+
if document.get("input_sha256") and file_hash(path) != document["input_sha256"]:
|
|
40
|
+
raise PreconditionError("insertion input hash mismatch")
|
|
41
|
+
text = path.read_bytes().decode("utf-8")
|
|
42
|
+
for mod in document["modifications"]:
|
|
43
|
+
top, bot, addition = mod["top"], mod["bot"], mod["txt"]
|
|
44
|
+
if not top or not bot or any(c in top + bot for c in "\r\n") or not addition:
|
|
45
|
+
raise ValidationError("insertion requires nonempty, single-line anchors and text")
|
|
46
|
+
lines = text.splitlines(keepends=True)
|
|
47
|
+
tops = [i for i, line in enumerate(lines) if top in line]
|
|
48
|
+
pairs = []
|
|
49
|
+
for i, start in enumerate(tops):
|
|
50
|
+
end = tops[i + 1] if i + 1 < len(tops) else len(lines)
|
|
51
|
+
bottom = next((j for j in range(start + 1, end) if bot in lines[j]), None)
|
|
52
|
+
if bottom is None:
|
|
53
|
+
raise PreconditionError("insertion bottom anchor is missing")
|
|
54
|
+
pairs.append((start, bottom))
|
|
55
|
+
if len(pairs) != mod.get("expect_matches", 1):
|
|
56
|
+
raise PreconditionError("insertion anchor count mismatch")
|
|
57
|
+
for start, end in reversed(pairs):
|
|
58
|
+
if addition in "".join(lines[start + 1 : end]):
|
|
59
|
+
raise PreconditionError("insertion already present without a receipt")
|
|
60
|
+
index = start + 1 if mod["alignment"] == "top" else end
|
|
61
|
+
if index and not lines[index - 1].endswith("\n"):
|
|
62
|
+
raise PreconditionError("insertion anchor has no line terminator")
|
|
63
|
+
lines.insert(index, addition if addition.endswith("\n") else addition + "\n")
|
|
64
|
+
text = "".join(lines)
|
|
65
|
+
path.write_bytes(text.encode("utf-8"))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def socket_patch(source: Path) -> str:
|
|
69
|
+
"""Pin an exact-offset patch for this source, using one unique full-line anchor."""
|
|
70
|
+
relative = "src/include/pg_config_manual.h"
|
|
71
|
+
path = target_path(source, relative)
|
|
72
|
+
if not path.is_file():
|
|
73
|
+
raise PreconditionError("socket default header is missing")
|
|
74
|
+
upstream = '#define DEFAULT_PGSOCKET_DIR "/tmp"\n'
|
|
75
|
+
configured = '#define DEFAULT_PGSOCKET_DIR "/var/run/postgresql"\n'
|
|
76
|
+
lines = path.read_text().splitlines(keepends=True)
|
|
77
|
+
if sum(lines.count(line) for line in (upstream, configured, SOCKET_DEFAULT_LINE)) != 1:
|
|
78
|
+
raise PreconditionError("builtin socket patch requires one exact upstream/default line")
|
|
79
|
+
before = [upstream if line == SOCKET_DEFAULT_LINE else line for line in lines]
|
|
80
|
+
after = [SOCKET_DEFAULT_LINE if line in (upstream, configured) else line for line in lines]
|
|
81
|
+
return "".join(difflib.unified_diff(before, after, "a/" + relative, "b/" + relative, n=0))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def apply_patch(root: Path, patch: str) -> None:
|
|
85
|
+
"""Unified UTF-8 diffs, exact offsets/context, additions/deletions; reject other formats."""
|
|
86
|
+
lines = patch.splitlines(keepends=True)
|
|
87
|
+
if lines and re.match(r"From [0-9a-f]{40,64} Mon Sep 17 00:00:00 2001", lines[0]):
|
|
88
|
+
first = next((i for i, line in enumerate(lines) if line.startswith("diff --git ")), None)
|
|
89
|
+
if first is None:
|
|
90
|
+
raise ValidationError("format-patch contains no diff")
|
|
91
|
+
lines = lines[first:]
|
|
92
|
+
# git's mail signature is outside the diff; arbitrary trailing input is rejected.
|
|
93
|
+
if (
|
|
94
|
+
len(lines) >= 2
|
|
95
|
+
and lines[-2] == "-- \n"
|
|
96
|
+
and re.fullmatch(r"[0-9][A-Za-z0-9.+-]*\n?", lines[-1])
|
|
97
|
+
):
|
|
98
|
+
lines = lines[:-2]
|
|
99
|
+
elif len(lines) >= 3 and not lines[-1].strip() and lines[-3] == "-- \n":
|
|
100
|
+
if not re.fullmatch(r"[0-9][A-Za-z0-9.+-]*\n?", lines[-2]):
|
|
101
|
+
raise ValidationError("invalid format-patch signature")
|
|
102
|
+
lines = lines[:-3]
|
|
103
|
+
index, applied = 0, 0
|
|
104
|
+
mode, mode_kind = None, None
|
|
105
|
+
while index < len(lines):
|
|
106
|
+
if lines[index].startswith("diff --git "):
|
|
107
|
+
match = re.fullmatch(r"diff --git a/(\S+) b/(\S+)\n?", lines[index])
|
|
108
|
+
if not match or match[1] != match[2] or not relative_output(match[1]):
|
|
109
|
+
raise UnsupportedError("patch requires safe unquoted paths without renames")
|
|
110
|
+
name = match[1]
|
|
111
|
+
mode, mode_kind = None, None
|
|
112
|
+
index += 1
|
|
113
|
+
while index < len(lines) and not lines[index].startswith(("--- ", "diff --git ")):
|
|
114
|
+
line = lines[index].rstrip("\n")
|
|
115
|
+
metadata = re.fullmatch(r"(new file|deleted file) mode (100644|100755)", line)
|
|
116
|
+
if metadata and mode is None:
|
|
117
|
+
mode_kind, mode = metadata[1], int(metadata[2], 8) & 0o777
|
|
118
|
+
elif re.fullmatch(r"index [0-9a-f]+\.\.[0-9a-f]+(?: (?:100644|100755))?", line):
|
|
119
|
+
pass
|
|
120
|
+
else:
|
|
121
|
+
raise UnsupportedError("binary, rename, copy and mode changes are unsupported")
|
|
122
|
+
index += 1
|
|
123
|
+
if index == len(lines) or lines[index].startswith("diff --git "):
|
|
124
|
+
if mode_kind is None:
|
|
125
|
+
raise ValidationError("patch has no file content")
|
|
126
|
+
old, new = (
|
|
127
|
+
("/dev/null", "b/" + name)
|
|
128
|
+
if mode_kind == "new file"
|
|
129
|
+
else ("a/" + name, "/dev/null")
|
|
130
|
+
)
|
|
131
|
+
lines[index:index] = [f"--- {old}\n", f"+++ {new}\n", "@@ -0,0 +0,0 @@\n"]
|
|
132
|
+
continue
|
|
133
|
+
if not lines[index].strip():
|
|
134
|
+
index += 1
|
|
135
|
+
continue
|
|
136
|
+
if not lines[index].startswith("--- ") or index + 1 >= len(lines):
|
|
137
|
+
raise UnsupportedError(
|
|
138
|
+
"only unified text patches without mode/rename changes are supported"
|
|
139
|
+
)
|
|
140
|
+
old = lines[index][4:].split("\t")[0].rstrip("\r\n")
|
|
141
|
+
if not lines[index + 1].startswith("+++ "):
|
|
142
|
+
raise ValidationError("patch lacks new file header")
|
|
143
|
+
new = lines[index + 1][4:].split("\t")[0].rstrip("\r\n")
|
|
144
|
+
for name in (old, new):
|
|
145
|
+
if name != "/dev/null" and (
|
|
146
|
+
not name.startswith(("a/", "b/")) or not relative_output(name[2:])
|
|
147
|
+
):
|
|
148
|
+
raise ValidationError("patch requires safe a/ and b/ paths")
|
|
149
|
+
if old != "/dev/null" and new != "/dev/null" and old[2:] != new[2:]:
|
|
150
|
+
raise UnsupportedError("patch renames are not supported")
|
|
151
|
+
if (
|
|
152
|
+
(mode_kind == "new file" and old != "/dev/null")
|
|
153
|
+
or (mode_kind == "deleted file" and new != "/dev/null")
|
|
154
|
+
or old == new == "/dev/null"
|
|
155
|
+
):
|
|
156
|
+
raise ValidationError("patch mode metadata differs from file headers")
|
|
157
|
+
path = target_path(root, (new if old == "/dev/null" else old)[2:])
|
|
158
|
+
if old == "/dev/null":
|
|
159
|
+
if path.exists():
|
|
160
|
+
raise PreconditionError("patch addition would overwrite an existing file")
|
|
161
|
+
original = []
|
|
162
|
+
else:
|
|
163
|
+
if not path.is_file():
|
|
164
|
+
raise PreconditionError("patch input is missing")
|
|
165
|
+
if mode is not None and bool(path.stat().st_mode & 0o111) != bool(mode & 0o111):
|
|
166
|
+
raise PreconditionError("patch input executable mode mismatch")
|
|
167
|
+
original = path.read_bytes().decode("utf-8").splitlines(keepends=True)
|
|
168
|
+
index += 2
|
|
169
|
+
output, cursor, hunks = [], 0, 0
|
|
170
|
+
while index < len(lines) and lines[index].startswith("@@ "):
|
|
171
|
+
match = re.fullmatch(
|
|
172
|
+
r"@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[^\n]*\n?", lines[index]
|
|
173
|
+
)
|
|
174
|
+
if not match:
|
|
175
|
+
raise ValidationError("invalid patch hunk")
|
|
176
|
+
old_start, old_n, new_start, new_n = (
|
|
177
|
+
int(v) if v is not None else 1 for v in match.groups()
|
|
178
|
+
)
|
|
179
|
+
start = old_start - 1 if old_n else old_start
|
|
180
|
+
if start < cursor or start > len(original):
|
|
181
|
+
raise PreconditionError("patch hunk position mismatch")
|
|
182
|
+
output.extend(original[cursor:start])
|
|
183
|
+
if len(output) != (new_start - 1 if new_n else new_start):
|
|
184
|
+
raise PreconditionError("patch output position mismatch")
|
|
185
|
+
before, after = [], []
|
|
186
|
+
index += 1
|
|
187
|
+
while index < len(lines) and (len(before) < old_n or len(after) < new_n):
|
|
188
|
+
line = lines[index]
|
|
189
|
+
if not line or line[0] not in " +-":
|
|
190
|
+
raise ValidationError("invalid patch hunk body")
|
|
191
|
+
value = line[1:]
|
|
192
|
+
index += 1
|
|
193
|
+
if index < len(lines) and lines[index].startswith("\"):
|
|
194
|
+
value = value.removesuffix("\n")
|
|
195
|
+
index += 1
|
|
196
|
+
if line[0] in " -":
|
|
197
|
+
before.append(value)
|
|
198
|
+
if line[0] in " +":
|
|
199
|
+
after.append(value)
|
|
200
|
+
if (
|
|
201
|
+
len(before) != old_n
|
|
202
|
+
or len(after) != new_n
|
|
203
|
+
or original[start : start + old_n] != before
|
|
204
|
+
):
|
|
205
|
+
raise PreconditionError("patch context mismatch; offsets and fuzz are forbidden")
|
|
206
|
+
output.extend(after)
|
|
207
|
+
cursor = start + old_n
|
|
208
|
+
hunks += 1
|
|
209
|
+
if not hunks:
|
|
210
|
+
raise ValidationError("patch has no hunks")
|
|
211
|
+
output.extend(original[cursor:])
|
|
212
|
+
if new == "/dev/null":
|
|
213
|
+
if output:
|
|
214
|
+
raise ValidationError("file deletion leaves content")
|
|
215
|
+
path.unlink()
|
|
216
|
+
else:
|
|
217
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
218
|
+
path.write_bytes("".join(output).encode("utf-8"))
|
|
219
|
+
if old == "/dev/null" and mode is not None:
|
|
220
|
+
path.chmod(mode)
|
|
221
|
+
applied += 1
|
|
222
|
+
mode, mode_kind = None, None
|
|
223
|
+
if not applied:
|
|
224
|
+
raise ValidationError("empty patch")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def change_receipt(before: Path, after: Path, identifier: str) -> dict:
|
|
228
|
+
left, right = inventory(before), inventory(after)
|
|
229
|
+
a = {e["path"]: e for e in left["entries"]}
|
|
230
|
+
b = {e["path"]: e for e in right["entries"]}
|
|
231
|
+
changed = [p for p in sorted(a.keys() | b.keys()) if a.get(p) != b.get(p)]
|
|
232
|
+
diffs = []
|
|
233
|
+
for p in changed:
|
|
234
|
+
if any(e.get("type") not in {None, "file"} for e in (a.get(p, {}), b.get(p, {}))):
|
|
235
|
+
continue
|
|
236
|
+
old = (before / p).read_text().splitlines(keepends=True) if p in a else []
|
|
237
|
+
new = (after / p).read_text().splitlines(keepends=True) if p in b else []
|
|
238
|
+
diffs.extend(difflib.unified_diff(old, new, fromfile="a/" + p, tofile="b/" + p))
|
|
239
|
+
return {
|
|
240
|
+
"id": identifier,
|
|
241
|
+
"input_hash": content_hash(left),
|
|
242
|
+
"output_hash": content_hash(right),
|
|
243
|
+
"changed_paths": changed,
|
|
244
|
+
"diff": "".join(diffs),
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def apply_changes(
|
|
249
|
+
source: Path, feature: dict, lock: dict, store, branch: str, receipts: Path, check=lambda: None
|
|
250
|
+
) -> list[dict]:
|
|
251
|
+
changes = []
|
|
252
|
+
if lock["spec"]["config"]["defaults"]["common_changes"]:
|
|
253
|
+
changes.append(("builtin/unix-socket-directory", None))
|
|
254
|
+
if branch == "candidate":
|
|
255
|
+
changes.extend((c["metadata"]["id"], c) for c in feature["spec"]["changes"])
|
|
256
|
+
results = []
|
|
257
|
+
receipts.mkdir(parents=True, exist_ok=True)
|
|
258
|
+
for number, (identifier, change) in enumerate(changes):
|
|
259
|
+
check()
|
|
260
|
+
temporary = source.with_name("source.change-" + str(number))
|
|
261
|
+
shutil.copytree(source, temporary, symlinks=True)
|
|
262
|
+
if change is None:
|
|
263
|
+
path = temporary / "src/include/pg_config_manual.h"
|
|
264
|
+
value = SOCKET_DEFAULT_LINE.rstrip("\n")
|
|
265
|
+
if value not in path.read_text().splitlines():
|
|
266
|
+
digest = lock["spec"]["blobs"][identifier]
|
|
267
|
+
apply_patch(temporary, store.blob(digest).read_text())
|
|
268
|
+
if value not in path.read_text().splitlines():
|
|
269
|
+
raise PreconditionError("builtin socket postcondition failed")
|
|
270
|
+
else:
|
|
271
|
+
for operation in change["spec"]["operations"]:
|
|
272
|
+
check()
|
|
273
|
+
blob = store.blob(operation["file"]["sha256"])
|
|
274
|
+
if operation["kind"] == "patch":
|
|
275
|
+
apply_patch(temporary, blob.read_text())
|
|
276
|
+
else:
|
|
277
|
+
insert_text(temporary, load_yaml(blob, "insertions"))
|
|
278
|
+
expected = change["spec"].get("postconditions", {}).get("socket_directory")
|
|
279
|
+
if expected is not None:
|
|
280
|
+
actual = re.findall(
|
|
281
|
+
r'^#define DEFAULT_PGSOCKET_DIR\s+"([^"]+)"',
|
|
282
|
+
(temporary / "src/include/pg_config_manual.h").read_text(),
|
|
283
|
+
re.M,
|
|
284
|
+
)
|
|
285
|
+
if [value for value in actual if value] != [expected]:
|
|
286
|
+
raise PreconditionError("socket directory postcondition failed")
|
|
287
|
+
receipt = change_receipt(source, temporary, identifier)
|
|
288
|
+
receipt["status"] = "applied" if receipt["changed_paths"] else "already_satisfied"
|
|
289
|
+
if change is not None and not receipt["changed_paths"]:
|
|
290
|
+
raise PreconditionError("custom change has no effect")
|
|
291
|
+
check()
|
|
292
|
+
backup = source.with_name("source.before-" + str(number))
|
|
293
|
+
os.replace(source, backup)
|
|
294
|
+
os.replace(temporary, source)
|
|
295
|
+
write_json(receipts / (str(number) + ".json"), receipt)
|
|
296
|
+
shutil.rmtree(backup)
|
|
297
|
+
results.append(receipt)
|
|
298
|
+
return results
|