Skip to content

Commit bc6f5cb

Browse files
committed
Merge origin/main into claude/express-checkup-cli-DBMWi
Resolve conflict in reporter/postgres_reports.py docstring: - Keep detailed scope documentation from our branch - Add new check types (K004-K008) from main branch
2 parents 004085a + 9f47244 commit bc6f5cb

14 files changed

Lines changed: 661 additions & 77 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ postgres_ai monitoring generates automated health check reports based on [postgr
275275
| Check ID | Title |
276276
|----------|-------|
277277
| K001 | Globally aggregated query metrics |
278-
| K003 | Top queries by total_time |
278+
| K003 | Top queries by total time (total_exec_time + total_plan_time) |
279279
| K004 | Top queries by temp bytes written |
280280
| K005 | Top queries by WAL generation |
281281
| K006 | Top queries by shared blocks read |

reporter/postgres_reports.py

Lines changed: 279 additions & 71 deletions
Large diffs are not rendered by default.

reporter/requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pytest-postgresql==7.0.2
44
coverage==7.6.10
55
pytest-cov==6.0.0
66
jsonschema==4.23.0
7+
PyYAML==6.0.2

reporter/schemas/K003.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"build_ts": { "type": ["string", "null"] },
1010
"generation_mode": { "type": ["string", "null"] },
1111
"checkId": { "const": "K003" },
12-
"checkTitle": { "const": "Top queries by total_time" },
12+
"checkTitle": { "const": "Top queries by total time (total_exec_time + total_plan_time)" },
1313
"timestamptz": { "type": "string" },
1414
"nodes": { "$ref": "#/$defs/nodes" },
1515
"results": {

reporter/schemas/K004.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"build_ts": { "type": ["string", "null"] },
1010
"generation_mode": { "type": ["string", "null"] },
1111
"checkId": { "const": "K004" },
12-
"checkTitle": { "const": "Check K004" },
12+
"checkTitle": { "const": "Top queries by temp bytes written" },
1313
"timestamptz": { "type": "string" },
1414
"nodes": { "$ref": "#/$defs/nodes" },
1515
"results": {

reporter/schemas/K005.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"build_ts": { "type": ["string", "null"] },
1010
"generation_mode": { "type": ["string", "null"] },
1111
"checkId": { "const": "K005" },
12-
"checkTitle": { "const": "Check K005" },
12+
"checkTitle": { "const": "Top queries by WAL generation" },
1313
"timestamptz": { "type": "string" },
1414
"nodes": { "$ref": "#/$defs/nodes" },
1515
"results": {

reporter/schemas/K006.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"build_ts": { "type": ["string", "null"] },
1010
"generation_mode": { "type": ["string", "null"] },
1111
"checkId": { "const": "K006" },
12-
"checkTitle": { "const": "Check K006" },
12+
"checkTitle": { "const": "Top queries by shared blocks read" },
1313
"timestamptz": { "type": "string" },
1414
"nodes": { "$ref": "#/$defs/nodes" },
1515
"results": {

reporter/schemas/K007.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"build_ts": { "type": ["string", "null"] },
1010
"generation_mode": { "type": ["string", "null"] },
1111
"checkId": { "const": "K007" },
12-
"checkTitle": { "const": "Check K007" },
12+
"checkTitle": { "const": "Top queries by shared blocks hit" },
1313
"timestamptz": { "type": "string" },
1414
"nodes": { "$ref": "#/$defs/nodes" },
1515
"results": {

reporter/schemas/K008.schema.json

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"title": "K008 report schema",
4+
"type": "object",
5+
"additionalProperties": false,
6+
"required": ["checkId", "checkTitle", "timestamptz", "nodes", "results"],
7+
"properties": {
8+
"version": { "type": ["string", "null"] },
9+
"build_ts": { "type": ["string", "null"] },
10+
"checkId": { "const": "K008" },
11+
"checkTitle": { "const": "Top queries by shared blocks hit+read" },
12+
"timestamptz": { "type": "string" },
13+
"nodes": { "$ref": "#/$defs/nodes" },
14+
"results": {
15+
"type": "object",
16+
"minProperties": 1,
17+
"additionalProperties": { "$ref": "#/$defs/nodeResult" }
18+
}
19+
},
20+
"$defs": {
21+
"nodes": {
22+
"type": "object",
23+
"additionalProperties": false,
24+
"required": ["primary", "standbys"],
25+
"properties": {
26+
"primary": { "type": "string" },
27+
"standbys": { "type": "array", "items": { "type": "string" } }
28+
}
29+
},
30+
"postgresVersion": {
31+
"type": "object",
32+
"additionalProperties": false,
33+
"required": ["version", "server_version_num", "server_major_ver", "server_minor_ver"],
34+
"properties": {
35+
"version": { "type": "string" },
36+
"server_version_num": { "type": "string" },
37+
"server_major_ver": { "type": "string" },
38+
"server_minor_ver": { "type": "string" }
39+
}
40+
},
41+
"timeline": {
42+
"type": "array",
43+
"items": { "type": "integer" }
44+
},
45+
"hourlyNumbers": {
46+
"type": "array",
47+
"items": { "type": "number" }
48+
},
49+
"queryEntry": {
50+
"type": "object",
51+
"additionalProperties": false,
52+
"required": ["queryid", "total_shared_hit_read_bytes", "hourly_shared_hit_read_bytes"],
53+
"properties": {
54+
"queryid": { "type": "string" },
55+
"total_shared_hit_read_bytes": { "type": "number" },
56+
"hourly_shared_hit_read_bytes": { "$ref": "#/$defs/hourlyNumbers" }
57+
}
58+
},
59+
"summary": {
60+
"type": "object",
61+
"additionalProperties": false,
62+
"required": [
63+
"queries_returned",
64+
"total_shared_hit_read_bytes",
65+
"total_shared_hit_read_bytes_tracked_queries",
66+
"total_shared_hit_read_bytes_other",
67+
"time_range_hours",
68+
"hourly_timestamps",
69+
"limit"
70+
],
71+
"properties": {
72+
"queries_returned": { "type": "integer" },
73+
"total_shared_hit_read_bytes": { "type": "number" },
74+
"total_shared_hit_read_bytes_tracked_queries": { "type": "number" },
75+
"total_shared_hit_read_bytes_other": { "type": "number" },
76+
"time_range_hours": { "type": "integer" },
77+
"hourly_timestamps": { "$ref": "#/$defs/timeline" },
78+
"limit": { "type": "integer" }
79+
}
80+
},
81+
"dbEntry": {
82+
"type": "object",
83+
"additionalProperties": false,
84+
"required": ["top_queries", "other_shared_hit_read_bytes_hourly", "summary"],
85+
"properties": {
86+
"top_queries": { "type": "array", "items": { "$ref": "#/$defs/queryEntry" } },
87+
"other_shared_hit_read_bytes_hourly": { "$ref": "#/$defs/hourlyNumbers" },
88+
"summary": { "$ref": "#/$defs/summary" }
89+
}
90+
},
91+
"data": {
92+
"type": "object",
93+
"additionalProperties": { "$ref": "#/$defs/dbEntry" }
94+
},
95+
"nodeResult": {
96+
"type": "object",
97+
"additionalProperties": false,
98+
"required": ["data"],
99+
"properties": {
100+
"data": { "$ref": "#/$defs/data" },
101+
"postgres_version": { "$ref": "#/$defs/postgresVersion" }
102+
}
103+
}
104+
}
105+
}
106+
107+
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from reporter.postgres_reports import PostgresReportGenerator
6+
7+
8+
@pytest.fixture(name="generator")
9+
def fixture_generator() -> PostgresReportGenerator:
10+
return PostgresReportGenerator(
11+
prometheus_url="http://prom.test",
12+
postgres_sink_url="",
13+
)
14+
15+
16+
@pytest.mark.unit
17+
def test_extract_queryids_from_reports_includes_query_metrics_and_top_queries(
18+
generator: PostgresReportGenerator,
19+
) -> None:
20+
reports = {
21+
# K001-style report: query_metrics
22+
"K001": {
23+
"results": {
24+
"node-1": {
25+
"data": {
26+
"db1": {
27+
"query_metrics": [
28+
{"queryid": "1"},
29+
{"queryid": "0"}, # excluded
30+
{"queryid": 2}, # int form
31+
]
32+
}
33+
}
34+
}
35+
}
36+
},
37+
# K003-style report: top_queries
38+
"K003": {
39+
"results": {
40+
"node-1": {
41+
"data": {
42+
"db1": {
43+
"top_queries": [
44+
{"queryid": "3"},
45+
{"queryid": "-4"},
46+
]
47+
},
48+
"db2": {
49+
"top_queries": [
50+
{"queryid": "5"},
51+
]
52+
},
53+
}
54+
}
55+
}
56+
},
57+
# D004 has sample_queries but should NOT be used for per-query file generation.
58+
"D004": {
59+
"results": {
60+
"node-1": {
61+
"data": {
62+
"pg_stat_statements_status": {
63+
"sample_queries": [
64+
{"queryid": "999"},
65+
]
66+
}
67+
}
68+
}
69+
}
70+
},
71+
}
72+
73+
out = generator.extract_queryids_from_reports(reports)
74+
75+
assert out["db1"] == {"1", "2", "3", "-4"}
76+
assert out["db2"] == {"5"}
77+
assert "999" not in (out["db1"] | out["db2"])
78+
79+
80+
@pytest.mark.unit
81+
def test_extract_queryids_from_reports_n001_includes_nonzero_query_id_only(
82+
generator: PostgresReportGenerator,
83+
) -> None:
84+
reports = {
85+
"N001": {
86+
"results": {
87+
"node-1": {
88+
"data": {
89+
"db1": {
90+
"wait_event_types": {
91+
"CPU*": {
92+
"queries_list": [
93+
{"query_id": "0"}, # excluded
94+
{"query_id": "10"},
95+
]
96+
}
97+
}
98+
}
99+
}
100+
}
101+
}
102+
}
103+
}
104+
105+
out = generator.extract_queryids_from_reports(reports)
106+
107+
assert out == {"db1": {"10"}}
108+
109+
110+
@pytest.mark.unit
111+
def test_extract_queryids_from_reports_d004_only_is_empty(
112+
generator: PostgresReportGenerator,
113+
) -> None:
114+
reports = {
115+
"D004": {
116+
"results": {
117+
"node-1": {
118+
"data": {
119+
"pg_stat_statements_status": {
120+
"sample_queries": [
121+
{"queryid": "-1100697950502680692"},
122+
{"queryid": "-115926913472768758"},
123+
]
124+
}
125+
}
126+
}
127+
}
128+
}
129+
}
130+
131+
out = generator.extract_queryids_from_reports(reports)
132+
assert out == {}
133+
134+

0 commit comments

Comments
 (0)