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.
Files changed (181) hide show
  1. pg_maker/__init__.py +3 -0
  2. pg_maker/__main__.py +3 -0
  3. pg_maker/api.py +594 -0
  4. pg_maker/artifacts/__init__.py +4 -0
  5. pg_maker/artifacts/catalog.py +67 -0
  6. pg_maker/artifacts/manifest.py +301 -0
  7. pg_maker/assets.py +93 -0
  8. pg_maker/builders/__init__.py +4 -0
  9. pg_maker/builders/autotools.py +430 -0
  10. pg_maker/builders/components.py +359 -0
  11. pg_maker/bundled/PROJECT.md +57 -0
  12. pg_maker/bundled/build.yaml +29 -0
  13. pg_maker/bundled/components/pg-stat-kcache.yaml +30 -0
  14. pg_maker/bundled/components/pg-stat-statements.yaml +24 -0
  15. pg_maker/bundled/components/pg-wait-sampling.yaml +30 -0
  16. pg_maker/bundled/config/defaults.yaml +80 -0
  17. pg_maker/bundled/config/documentation.yaml +24 -0
  18. pg_maker/bundled/config/features/my-feature.yaml +17 -0
  19. pg_maker/bundled/config/vendor.yaml +13 -0
  20. pg_maker/bundled/documentation/README.md +20 -0
  21. pg_maker/bundled/documentation/pg-stat-kcache.sgml +99 -0
  22. pg_maker/bundled/documentation/pg-stat-kcache.yaml +15 -0
  23. pg_maker/bundled/documentation/pg-wait-sampling.sgml +122 -0
  24. pg_maker/bundled/documentation/pg-wait-sampling.yaml +15 -0
  25. pg_maker/bundled/patches/unix-socket-directory.patch +5 -0
  26. pg_maker/changes/__init__.py +1 -0
  27. pg_maker/changes/apply.py +298 -0
  28. pg_maker/changes/loader.py +135 -0
  29. pg_maker/changes/workspace.py +221 -0
  30. pg_maker/cli.py +546 -0
  31. pg_maker/components/__init__.py +4 -0
  32. pg_maker/config.py +514 -0
  33. pg_maker/development/__init__.py +1 -0
  34. pg_maker/development/cli.py +104 -0
  35. pg_maker/development/git.py +362 -0
  36. pg_maker/development/session.py +735 -0
  37. pg_maker/documentation/__init__.py +4 -0
  38. pg_maker/documentation/builder.py +467 -0
  39. pg_maker/documentation/cli.py +24 -0
  40. pg_maker/documentation/config.py +127 -0
  41. pg_maker/documentation/install.py +79 -0
  42. pg_maker/documentation/site.py +166 -0
  43. pg_maker/documentation/sphinx_ext.py +150 -0
  44. pg_maker/documentation/theme/_static/pg-maker.css +26 -0
  45. pg_maker/documentation/theme/_static/pg-maker.js +35 -0
  46. pg_maker/documentation/theme/_templates/footer_en.html +5 -0
  47. pg_maker/documentation/theme/_templates/footer_metadata.html +13 -0
  48. pg_maker/documentation/theme/_templates/footer_ru.html +5 -0
  49. pg_maker/documentation/translation.py +341 -0
  50. pg_maker/documentation/validation.py +132 -0
  51. pg_maker/documentation/worker.py +62 -0
  52. pg_maker/documentation/writer.py +324 -0
  53. pg_maker/documentation/xml.py +210 -0
  54. pg_maker/documents.py +118 -0
  55. pg_maker/errors.py +50 -0
  56. pg_maker/execution/__init__.py +1 -0
  57. pg_maker/execution/build.py +502 -0
  58. pg_maker/execution/cache.py +287 -0
  59. pg_maker/execution/cancellation.py +60 -0
  60. pg_maker/execution/checkpoints.py +218 -0
  61. pg_maker/execution/cleanup.py +122 -0
  62. pg_maker/execution/component_cache.py +164 -0
  63. pg_maker/execution/development.py +109 -0
  64. pg_maker/execution/engine.py +171 -0
  65. pg_maker/execution/events.py +65 -0
  66. pg_maker/execution/incremental.py +178 -0
  67. pg_maker/execution/preparation.py +130 -0
  68. pg_maker/execution/process.py +155 -0
  69. pg_maker/execution/resources.py +41 -0
  70. pg_maker/execution/state.py +82 -0
  71. pg_maker/execution/worker.py +45 -0
  72. pg_maker/identity.py +116 -0
  73. pg_maker/integrations/__init__.py +1 -0
  74. pg_maker/integrations/machine.py +163 -0
  75. pg_maker/integrations/runtime.py +387 -0
  76. pg_maker/models.py +245 -0
  77. pg_maker/packaging/__init__.py +4 -0
  78. pg_maker/packaging/deb.py +337 -0
  79. pg_maker/packaging/service.py +165 -0
  80. pg_maker/packaging/spec.py +64 -0
  81. pg_maker/packaging/templates/deb-control.in +8 -0
  82. pg_maker/packaging/tree.py +239 -0
  83. pg_maker/planning/__init__.py +1 -0
  84. pg_maker/planning/documents.py +65 -0
  85. pg_maker/planning/environments.py +318 -0
  86. pg_maker/planning/graph.py +23 -0
  87. pg_maker/planning/plan.py +418 -0
  88. pg_maker/planning/resolve.py +117 -0
  89. pg_maker/planning/reuse.py +80 -0
  90. pg_maker/planning/runtime_repository.py +90 -0
  91. pg_maker/postgresql.py +59 -0
  92. pg_maker/progress.py +64 -0
  93. pg_maker/publishing/__init__.py +4 -0
  94. pg_maker/py.typed +0 -0
  95. pg_maker/recipes/README.md +13 -0
  96. pg_maker/recipes/platforms/debian-13.yaml +125 -0
  97. pg_maker/recipes/postgresql.yaml +114 -0
  98. pg_maker/runners/__init__.py +1 -0
  99. pg_maker/runners/base.py +29 -0
  100. pg_maker/runners/docker.py +312 -0
  101. pg_maker/schemas/build.schema.json +5 -0
  102. pg_maker/schemas/change-set.schema.json +5 -0
  103. pg_maker/schemas/component-envelope.schema.json +69 -0
  104. pg_maker/schemas/component.schema.json +5 -0
  105. pg_maker/schemas/defaults.schema.json +5 -0
  106. pg_maker/schemas/doc-rules.schema.json +5 -0
  107. pg_maker/schemas/documentation.schema.json +5 -0
  108. pg_maker/schemas/environment.schema.json +228 -0
  109. pg_maker/schemas/feature.schema.json +5 -0
  110. pg_maker/schemas/insertions.schema.json +5 -0
  111. pg_maker/schemas/lock.schema.json +3963 -0
  112. pg_maker/schemas/operation-input.schema.json +306 -0
  113. pg_maker/schemas/operation-state.schema.json +127 -0
  114. pg_maker/schemas/plan.schema.json +284 -0
  115. pg_maker/schemas/planning-common.schema.json +8192 -0
  116. pg_maker/schemas/platform.schema.json +5 -0
  117. pg_maker/schemas/postgresql-catalog.schema.json +5 -0
  118. pg_maker/schemas/run-event.schema.json +72 -0
  119. pg_maker/schemas/translations.schema.json +5 -0
  120. pg_maker/schemas/vendor.schema.json +5 -0
  121. pg_maker/security.py +73 -0
  122. pg_maker/sources/__init__.py +1 -0
  123. pg_maker/sources/resolver.py +238 -0
  124. pg_maker/sources/store.py +279 -0
  125. pg_maker/storage.py +127 -0
  126. pg_maker/validation/__init__.py +4 -0
  127. pg_maker/validation/installed.py +667 -0
  128. pg_maker/validation/packages.py +309 -0
  129. pg_maker/validation/systemctl.py +185 -0
  130. pg_maker/yamlio.py +54 -0
  131. pg_maker-0.1.0.data/data/share/doc/pg-maker/CHANGELOG.md +22 -0
  132. pg_maker-0.1.0.data/data/share/doc/pg-maker/CONTRIBUTING.md +63 -0
  133. pg_maker-0.1.0.data/data/share/doc/pg-maker/LICENSE +21 -0
  134. pg_maker-0.1.0.data/data/share/doc/pg-maker/README.md +163 -0
  135. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/architecture.md +55 -0
  136. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/development-storage.md +72 -0
  137. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/development.md +86 -0
  138. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/README.md +32 -0
  139. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/api.md +75 -0
  140. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/build.md +112 -0
  141. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/changes.md +58 -0
  142. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/cleanup.md +17 -0
  143. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/components.md +39 -0
  144. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/configuration.md +83 -0
  145. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/development.md +88 -0
  146. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/documentation.md +241 -0
  147. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/documents.md +42 -0
  148. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/environments.md +40 -0
  149. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/execution.md +49 -0
  150. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/machine.md +31 -0
  151. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/observability.md +28 -0
  152. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/packaging.md +107 -0
  153. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/planning.md +63 -0
  154. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/postgresql.md +79 -0
  155. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/publication.md +20 -0
  156. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/recovery.md +83 -0
  157. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/runtime.md +31 -0
  158. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/sources.md +48 -0
  159. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/state.md +49 -0
  160. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/storage.md +24 -0
  161. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/releasing.md +98 -0
  162. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/reuse.md +24 -0
  163. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/roadmap.md +54 -0
  164. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/default-components.md +56 -0
  165. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/development-release.md +49 -0
  166. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/documentation-theme.md +45 -0
  167. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/integration.md +49 -0
  168. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/postgresql-majors.md +107 -0
  169. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/review-fixes.md +68 -0
  170. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/stage4.md +65 -0
  171. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/stage56.md +66 -0
  172. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/stage7.md +72 -0
  173. pg_maker-0.1.0.data/data/share/doc/pg-maker/tests/fixtures/guc/README.md +11 -0
  174. pg_maker-0.1.0.data/data/share/doc/pg-maker/tests/fixtures/stage4/README.md +31 -0
  175. pg_maker-0.1.0.data/data/share/doc/pg-maker/tests/fixtures/stage7/README.md +16 -0
  176. pg_maker-0.1.0.dist-info/METADATA +200 -0
  177. pg_maker-0.1.0.dist-info/RECORD +181 -0
  178. pg_maker-0.1.0.dist-info/WHEEL +5 -0
  179. pg_maker-0.1.0.dist-info/entry_points.txt +4 -0
  180. pg_maker-0.1.0.dist-info/licenses/LICENSE +21 -0
  181. 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 &lt;&gt; 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,5 @@
1
+ --- a/src/include/pg_config_manual.h
2
+ +++ b/src/include/pg_config_manual.h
3
+ @@ -193 +193 @@
4
+ -#define DEFAULT_PGSOCKET_DIR "/tmp"
5
+ +#define DEFAULT_PGSOCKET_DIR "/var/run/postgresql" /* Shared by server and libpq. */
@@ -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