Compare commits

..
Author SHA1 Message Date
james.maesandCodex 5c0236b2d7 ci: centralize branch verification and source scanning
Co-Authored-By: Codex <noreply@openai.com>
2026-10-21 12:11:01 -05:00
james.maesandCodex 499383a73a ci: use the shared orb pipeline on main (#22)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 09:16:21 -05:00
70 changed files with 175 additions and 18328 deletions

No files matched your search

+11 -57
View File
@@ -1,60 +1,14 @@
version: 2.1
setup: true
orbs:
qqq-orb: kingsrook/qqq-orb@0.6.9
commands:
configure-package-install:
steps:
- run:
name: Keep package installation unattended
command: |
# These build machines are discarded after the job.
sudo install -d /etc/needrestart/conf.d
printf '%s\n' '$nrconf{restart} = "l";' | sudo tee /etc/needrestart/conf.d/99-qqq-ci.conf >/dev/null
qqq-orb: kingsrook/qqq-orb@0.7.15
workflows:
test_only:
jobs:
- qqq-orb/mvn_test_only:
pre-steps:
- configure-package-install
context: [qqq-maven-registry-credentials]
filters:
branches:
ignore: /(develop|release\/.*)/
publish_snapshot:
jobs:
- qqq-orb/mvn_publish:
pre-steps:
- configure-package-install
context: [qqq-maven-registry-credentials]
branch_type: snapshot
filters:
branches:
only: [develop]
publish_release_candidate:
jobs:
- qqq-orb/mvn_publish:
pre-steps:
- configure-package-install
context: [qqq-maven-registry-credentials]
branch_type: release_candidate
filters:
branches:
only: [/release\/.*/]
publish_release:
jobs:
- qqq-orb/mvn_publish:
pre-steps:
- configure-package-install
context: [qqq-maven-registry-credentials]
branch_type: release
filters:
branches:
ignore: /.*/
tags:
only: [/^v\d+\.\d+\.\d+$/]
setup:
jobs:
- qqq-orb/generate_pipeline:
orb_version: 0.7.15
filters:
branches:
ignore: automation/qbit-rc-pending
tags:
only: /^v\d+\.\d+\.\d+$/
+23
View File
@@ -0,0 +1,23 @@
name: CodeQL
'on':
push:
branches:
- main
- develop
- release/**
- hotfix/**
pull_request: {}
schedule:
- cron: 41 16 * * 0
workflow_dispatch: {}
permissions:
contents: read
actions: read
security-events: write
packages: read
concurrency:
group: codeql-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
analyze:
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
+62 -13
View File
@@ -1,25 +1,74 @@
# Cut the next release candidate (QRun-IO/qqq docs/QRUN-QBIT-REPO-STANDARD.md, C6):
# run this workflow by hand, or merge a develop PR labelled `release:rc`. It merges develop into
# the release line and the release/* push runs CircleCI publish_release_candidate.
name: Release candidate
on:
'on':
workflow_dispatch:
inputs:
dry_run:
description: Print the plan without merging or creating branches
description: Plan a cut without changing release state
type: boolean
default: false
finalize:
description: Retry finalization of an existing cut; do not cut another RC
type: boolean
default: false
recover:
description: Recover the same published RC after a deployment timeout; never
reupload
type: boolean
default: false
circle_build:
description: Original Circle publisher build number (recovery only)
type: string
default: ''
merge_sha:
description: Existing cut merge_sha (finalize retry only)
type: string
default: ''
bump_sha:
description: Existing cut bump_sha (finalize retry only)
type: string
default: ''
branch:
description: Existing cut branch (finalize retry only)
type: string
default: ''
version:
description: Existing cut version (finalize retry only)
type: string
default: ''
pull_request:
types: [closed]
branches: [develop]
types:
- closed
branches:
- develop
repository_dispatch:
types:
- qbit-rc-published
permissions:
contents: write
actions: read
checks: read
jobs:
release-rc:
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release:rc'))
uses: QRun-IO/.github/.github/workflows/qbit-release-rc.yml@main
if: github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch'
|| (github.event_name == 'pull_request' && github.event.pull_request.merged
== true && github.event.pull_request.base.ref == 'develop' && contains(github.event.pull_request.labels.*.name,
'release:rc'))
uses: QRun-IO/qqq-orb/.github/workflows/qbit-release-rc.yml@cc30f599e63c8e41629c99d23395bac1adb8bc44
with:
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false
}}
finalize: ${{ github.event_name == 'repository_dispatch' || (github.event_name
== 'workflow_dispatch' && inputs.finalize) }}
recover: ${{ github.event_name == 'workflow_dispatch' && inputs.recover || false
}}
circle_build: ${{ github.event_name == 'workflow_dispatch' && inputs.circle_build
|| '' }}
merge_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.merge_sha
|| inputs.merge_sha || '' }}
bump_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.bump_sha
|| inputs.bump_sha || '' }}
branch: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch
|| inputs.branch || '' }}
version: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.version
|| inputs.version || '' }}
secrets: inherit
+16
View File
@@ -0,0 +1,16 @@
profile: qbit
orb_version: 0.7.15
default_branch: develop
java_version: '21'
browsers: true
api_checks: false
docs_only: false
release_on_main: false
release_tag_policy: stable
test_branch_policy: non-release
publish:
snapshot: true
release_candidate: true
release: true
asciidoc: false
release_completion: true
+5 -42
View File
@@ -5,13 +5,12 @@
> Database performance for QQQ apps: slow and expensive queries from pg_stat_statements with delta snapshots, table scan and index analysis, live activity and blocking chains, connection capacity, maintenance, QQQ query attribution with N+1 detection, and ranked suggestions, all as standard QQQ tables.
**Status:** Phase A is implemented: schema and secured metadata, PostgreSQL statistics collection, daily rollups and retention. Attribution listeners, suggestions, explain actions, dashboard/API integration and help are later phases. Nothing is published yet. Approved design: [docs/SPEC.md](docs/SPEC.md).
**Status:** design: docs/SPEC.md in this repository; implementation in progress. Nothing is published yet.
## Requirements
- Java 21.
- QQQ 4.1 (built against `4.1.0-RC.11`).
- PostgreSQL 13+ for collection; PostgreSQL 17 and H2 2.2 are migration test targets.
- QQQ 4.1 (built against `4.1.0-RC.10`).
## Install
@@ -19,45 +18,13 @@ Not yet published. Once released, add `com.kingsrook.qbits:qbit-db-performance`
```java
new DbPerfQBitProducer()
.withQBitConfig(new DbPerfQBitConfig()
.withBackendName("rdbms")
.withSchedulerName("scheduler")) // omit to register no schedules
.withQBitConfig(new DbPerfQBitConfig().withBackendName("rdbms"))
.produce(qInstance);
```
The backend must already exist. For PostgreSQL, use QQQ's
`PostgreSQLBackendMetaData` from `qqq-backend-module-postgres` so table actions
use the PostgreSQL SQL dialect. The qbit reads statistics through the backend's
connection manager and writes its records through QQQ actions as the system user.
Include `db/liquibase/qbit-db-performance/db_performance.changelog.sql` in the
host's Liquibase changelog. It creates 17 tables and seeds 14 suggestion rules
without overwriting administrator edits. The runtime creates its Settings row
on first use. It does not run migrations itself.
Enable `pg_stat_statements` in PostgreSQL's `shared_preload_libraries`, create
the extension in the monitored database, and grant `pg_read_all_stats` to the
application database role. Missing statistics access produces a PARTIAL snapshot
and a Settings message; unsupported database vendors remain usable for metadata.
## Naming
Tables are `db_perf_<name>` (lowercase snake_case, backend name equal to the table name). Permissions are `db_performance.<action>`; the complete names are in `DbPerfPermissions.ALL`.
The app and tables require `db_performance.view`. Settings updates, rule changes
and Collect now require `db_performance.manage_settings`. Other tables are
read-only to users. Settings and rules have no AI exposure.
Collection defaults to every five minutes, with table/index samples hourly.
New statements establish a baseline instead of booking historical counts into
one interval. Snapshot totals include all statement deltas; only the top 200
details are stored by default. Therefore a statement's 24-hour totals and daily
rollup reflect its retained details, while its share uses the full database total.
The daily process rolls up yesterday and missing days in the preceding week,
then purges details after 30 days and daily rows after 365 days. These thresholds
are editable in Settings. Days use the configured time zone, UTC by default.
Processes are scheduled or exposed as table actions, never navigation children.
Tables are `db_perf_<name>` (lowercase snake_case, backend name equal to the table name). Permissions are `db_perf.<action>`.
## Adopting in another app
@@ -66,14 +33,10 @@ Follow the QQQ [adoption checklist](https://github.com/QRun-IO/qqq/blob/develop/
## Build
```bash
./mvnw -o verify
./mvnw verify
```
Always use the Maven wrapper (Maven 3.9.11); Maven 3.10 cannot read the parent POM.
On Windows use `./mvnw.cmd -o verify`. Validation includes main/test checkstyle,
JUnit tests and JaCoCo gates of 70% instructions and 90% classes. PostgreSQL
Testcontainers tests (including `PostgresStatsProviderIT`) run with the suite and
skip when Docker is unavailable.
## License
+1 -1
View File
@@ -1,3 +1,3 @@
# QBit DB Performance: design
The approved specification is [SPEC.md](SPEC.md) (owner decisions 2026-10-15).
The design for this qbit is tracked in docs/SPEC.md in this repository. This repository implements it; the implementation is in progress.
-281
View File
@@ -1,281 +0,0 @@
# QBit DB Performance: specification
Status: **approved for build** (owner decisions 2026-10-15, below). Author: James Maes (drafted with Claude).
Repository: `QRun-IO/qbit-db-performance` (Apache-2.0), package `com.kingsrook.qbits.dbperformance`,
Maven `com.kingsrook.qbits:qbit-db-performance`. First adopter: kof22 (`KofTwentyTwo/Website-Backend`).
## Owner decisions (2026-10-15)
| # | Decision |
|---|---|
| D1 | A **separate qbit**, not part of SQL Workbench. The two link both ways: a statement opens "Explain in SQL Workbench", and SQL Workbench can open a statement's history here. |
| D2 | **Retention:** 30 days of detailed snapshots plus 1 year of daily rollups, both configurable per monitored database. |
| D3 | **v1 scope is everything in this document.** PostgreSQL first, behind a provider interface so MySQL can follow. |
Standing owner standards apply (all recommendations below accepted as written): data in standard
visible QQQ tables, `db_perf_` snake_case table prefix, permissions under the full name
`db_performance.*`, processes as table or record actions with standard Review/Result screens and
plain labels, UI only through `/qqq/v1`, durable jobs for heavy work, no filesystem (database only),
central health, alerts through core `QAlerts`, data-driven help, readable at every width.
## 1. Summary
A system-administration tool that answers "why is the database slow, and what should we change?"
for a QQQ application. It records what the database itself knows (statement statistics, table and
index usage, sessions, locks, connections, maintenance), what QQQ knows about who issued each
statement (route, process, scheduled job, table), and turns both into ranked, deduplicated
findings with evidence and a ready-to-commit fix. It never changes the live database: fixes are
Liquibase snippets for a developer to commit.
It lives under **Admin → Operations → Database Performance** (an admin tool, not a business app).
## 2. Architecture
```
┌──────────── per pod ────────────┐ ┌──── one pod (scheduler) ────┐
QQQ RDBMS ──► StatementExecutionListener (core) │ │ Collect snapshot (5 min) │
actions └► Attribution aggregator (memory) │ │ Analyze → findings (hourly) │
└► flush every N s, 1 batch │ │ Roll up + purge (daily) │
insert per window ───────────┼──► DB ◄┤ (durable-eligible processes)│
└──────────────────────────────────┘ └─────────────┬───────────────┘
│ DbStatsProvider
PostgresStatsProvider (pg_stat_*)
```
- **`DbStatsProvider`** (SPI): `capabilities()`, `readDatabase`, `readStatements`, `readTables`,
`readIndexes`, `readActivity` (sessions plus lock graph), `readConnections`, `readSettings`,
`explain(...)`. `PostgresStatsProvider` is the v1 implementation (PostgreSQL 13+, features that
need 16+/17 such as `EXPLAIN (GENERIC_PLAN)` and per-entry `stats_since` are detected).
The provider is chosen per monitored backend from the JDBC vendor; an unsupported vendor is
reported in the Setup card, never a crash.
- **Connections** are the backend's own (QQQ `ConnectionManager`), so they run as the app's
database user. That user needs the built-in `pg_read_all_stats` role to see other sessions'
query text and all statements; without it the qbit runs in a reduced mode and the Setup card and
health check say exactly what to grant.
- **Core hook** (QQQ, next 4.1 RC): `StatementExecutionListener` SPI in `qqq-backend-core`, fired by
every `qqq-backend-module-rdbms` action (query, count, aggregate, insert, update, delete) with
backend, table, operation, parameterized SQL, elapsed nanoseconds, row count and success, plus
`UnitOfWork` scopes opened by core: HTTP request (route template), process run, scheduled job.
Listeners must be cheap and never throw; core catches and rate-limits their errors. With no
listener registered the hook costs one volatile read.
## 3. Tables
All tables are standard QQQ tables on the host's backend, created by the qbit's Liquibase
changelog (`db/liquibase/qbit-db-performance/db_performance.changelog.sql`, PostgreSQL and H2),
with foreign keys between the qbit's own tables (never into WORM tables), indexes on every foreign
key and on every time column that retention or a widget filters on.
| Table | Label | Written by | Notes |
|---|---|---|---|
| `db_perf_settings` | Settings | seeded per backend at first run (insert-only), admins | one row per monitored backend: enabled, intervals, retention (30 days detailed, 365 days daily), top-N, attribution on/sample rate/flush seconds/N+1 threshold, EXPLAIN timeout, alert thresholds. Field-audited. Hidden from the assistant. |
| `db_perf_rule` | Suggestion Rules | Liquibase seed (insert-only), admins | one row per rule: code, label, enabled, severity, thresholds. Every rule reads its row. Field-audited. Hidden from the assistant. |
| `db_perf_snapshot` | Snapshots | collector | one row per collection pass: status, duration, reset detection, database-level deltas (commits, rollbacks, blocks hit/read, cache hit ratio, temp files/bytes, deadlocks), connection counts vs `max_connections`, database size, total statement time. Doubles as the collection run log. |
| `db_perf_statement` | Queries | collector | one row per distinct statement (backend + `queryid` + user + top-level): normalized text, fingerprint, operation, main table, first/last seen, the last cumulative counters used for deltas, and 24-hour totals for sorting. **Main nav table.** |
| `db_perf_query_snapshot` | Query Snapshots | collector | per snapshot per statement **delta**: calls, total/mean time, max time (since reset), rows, shared hit/read, hit ratio, temp blocks, % of database time. Top N per pass (default 200) by delta time. Child list on Queries. |
| `db_perf_query_daily` | Query Daily | rollup | per statement per day: calls, total/mean/max time, rows, hit ratio, % of database time. |
| `db_perf_table_stat` | Tables | collector | one row per user table: latest scans, tuples, live/dead, sizes, maintenance times, growth. Nav. |
| `db_perf_table_snapshot` | Table Snapshots | collector (hourly) | per table **delta**: seq/idx scans, tuples read/fetched, inserts/updates/deletes/HOT, live/dead tuples, sizes, autovacuum/analyze counts, freeze age. |
| `db_perf_table_daily` | Table Daily | rollup | per table per day: size, live/dead, scans, writes. |
| `db_perf_index_stat` | Indexes | collector (hourly) | one row per index: definition, columns, unique/primary/valid, scans, size, write cost (table writes since stats reset). Nav. |
| `db_perf_activity_sample` | Activity Samples | collector | notable sessions only (long-running, idle in transaction, blocked or blocking): state, wait event, durations, blocking pids, application, user, `queryid` and **fingerprinted text, never raw text**. |
| `db_perf_connection_sample` | Connection Samples | collector | per snapshot per (database user, application, state): count. |
| `db_perf_attribution` | QQQ Attribution | per-pod flush | per window per (pod, unit kind, unit name, table, operation, statement fingerprint): units, statements, total/max time, rows, errors, sampled flag. |
| `db_perf_attribution_daily` | QQQ Attribution Daily | rollup | the same per day, pods merged. |
| `db_perf_repeat_pattern` | Repeated Statements (N+1) | per-pod flush | per window per (unit kind, unit name, fingerprint): units affected, max and total repeats in one unit, table. |
| `db_perf_finding` | Findings | analyzer, people (status) | rule, severity, score (rank), title, summary, evidence, recommendation, Liquibase snippet, subject table/statement/index, dedupe key (unique), status OPEN/ACKNOWLEDGED/RESOLVED/IGNORED, first/last seen, occurrences, note. Field-audited. **Nav.** |
| `db_perf_explain` | Query Plans | plan viewer | audit of every plan request: who, when, statement, mode (PLAN, GENERIC, ANALYZE), duration, outcome. Plain/generic plans of literal-free statements are kept as plan history; ANALYZE plans are shown, never stored. |
Retention (`db_perf_settings`): `detail_retention_days` (30) purges snapshots, query/table
snapshots, activity and connection samples, attribution, repeat patterns and query plans;
`rollup_retention_days` (365) purges the `*_daily` tables. Statements, tables and indexes not seen
within the rollup retention are deleted with their history. Findings are kept (resolved findings
older than the rollup retention are purged).
Seeded defaults (insert-only, so admin edits survive): every `db_perf_rule` row. `db_perf_settings`
is seeded by the runtime the first time it sees a backend (also insert-only). Tables that start
empty carry help saying what fills them and when; the app home's Setup card shows each step.
## 4. Collection (delta snapshots)
`pg_stat_statements` counters are cumulative. Each pass:
1. Read `pg_stat_statements_info` (`stats_reset`, `dealloc`) and every `pg_stat_statements` row of
this database (`toplevel` rows; `pg_stat_statements.track = top` on kof22).
2. For each entry, compare with the statement's stored last counters. **Delta = current − last.**
A reset is detected when `calls` went down, the entry's `stats_since` changed (PostgreSQL 17),
or `pg_stat_statements_info.stats_reset` moved; then the delta is the current value (activity
since the reset) and the snapshot records `stats_reset_detected`. New entries count from zero
only when their `stats_since` falls inside the interval; otherwise they are baselined
(no delta) so a restart of the qbit never books months of history into one interval.
3. Keep the top N by delta time (default 200); the remainder is summed into the snapshot's totals,
so % of database time stays exact.
4. Database counters (`pg_stat_database`) use the same delta and reset rules (`stats_reset`).
5. Tables and indexes (`pg_stat_user_tables`, `pg_statio_user_tables`, `pg_stat_user_indexes`,
`pg_index`, sizes) are collected every `table_snapshot_minutes` (60).
6. Sessions (`pg_stat_activity` with `pg_blocking_pids`) are sampled each pass; only notable
sessions are stored.
**Text and PHI.** `pg_stat_statements` already replaces constants with `$n`. The qbit
re-normalizes every stored statement text (string and numeric literals become placeholders,
`IN` lists collapse) and stores a SHA-256 fingerprint of the normalized text. Raw text from
`pg_stat_activity` is never persisted: samples keep the `queryid` and the normalized text of the
matching statement, or a fingerprint of the normalized live text. Live text is shown only to users
with `db_performance.view_live_text`, and only in the live activity response.
## 5. QQQ attribution and N+1
The qbit registers a `StatementExecutionListener` at startup (when `attribution_enabled`).
- On each statement: normalize the SQL (QQQ binds parameters, so text is literal-free; `?` lists
collapse), fingerprint it (cached), and add to an in-memory `LongAdder` aggregate keyed by
(unit kind, unit name, table, operation, fingerprint). Sampling (`attribution_sample_rate`,
default 1.0) applies per unit of work. The qbit's own tables are ignored, and the flush thread
suppresses itself, so attribution never measures itself.
- Per unit of work, a small per-thread map counts each fingerprint; when the unit ends, any
fingerprint repeated at least `n_plus_one_threshold` times (rule `N_PLUS_ONE`, default 10) is
added to the repeat-pattern aggregate.
- Every `attribution_flush_seconds` (60) each pod swaps its aggregate and writes it in one batch
insert per table. This flush is an in-JVM timer, not a durable job: the counts exist only in that
pod's memory, so no other pod could resume them. A crash loses at most one window of counts,
which is acceptable for statistics. Memory is bounded (`attribution_max_keys`, 10,000; overflow is
counted in an `(other)` key).
## 6. Suggestions engine
Rules read their `db_perf_rule` row (enabled, severity, thresholds) and produce findings with
evidence. Each finding has a stable dedupe key (rule + subject), so a rerun updates `last_seen`
and `occurrences` instead of adding rows. A finding that is no longer produced is marked RESOLVED
automatically unless a person IGNORED it; an IGNORED finding stays ignored.
| Rule | Finds | Evidence | Recommendation |
|---|---|---|---|
| `SLOW_QUERY` | statements with high total or mean time | calls, mean, total, % of DB time (24 h) | plan link, index candidates |
| `MISSING_INDEX` | tables with heavy sequential scans and statements filtering on unindexed columns | seq vs idx scans, tuples read, statement predicates | **ready-to-commit Liquibase changeset** (`CREATE INDEX CONCURRENTLY IF NOT EXISTS ...`, `runInTransaction:false`) |
| `UNUSED_INDEX` | non-unique, non-primary indexes with zero scans since stats reset (minimum age) | size, write cost | Liquibase `DROP INDEX CONCURRENTLY IF EXISTS` snippet |
| `DUPLICATE_INDEX` | identical or prefix-redundant indexes on the same table | both definitions, sizes | drop snippet for the redundant one |
| `N_PLUS_ONE` | repeated statement shapes in one unit of work | unit, statement, max/total repeats | batch the lookups (one query with `IN`) |
| `HOT_JOB` | scheduled jobs and processes with the most database time | attribution totals | review schedule or batching |
| `OVER_AUDITED` | QQQ audit tables with high write volume relative to their source tables | inserts per day, size | lower audit level on noisy tables |
| `BLOAT` | tables with high dead-tuple ratio | n_dead/n_live, size | autovacuum settings per table |
| `VACUUM_STALE` | tables not vacuumed or analyzed for long, or near freeze age | last (auto)vacuum/analyze, age | maintenance settings |
| `POOL_SIZING` | connection usage near `max_connections` or many idle connections | peaks by app/user | pool size changes |
| `IDLE_IN_TRANSACTION` | sessions idle in transaction too long | samples | fix the transaction scope; `idle_in_transaction_session_timeout` |
| `CACHE_HIT_LOW` | database cache hit ratio below threshold | hit ratio trend | memory or query fixes |
| `TEMP_SPILL` | statements writing temp blocks | temp blocks, statements | `work_mem` or query fixes |
| `DEADLOCKS` | deadlocks in the interval | count | lock ordering |
Score = severity weight × normalized impact (share of database time, size or connections), so the
Findings table and the home list sort by real impact. Findings never offer a button that changes
the live database.
## 7. Plan viewer
- **Plain EXPLAIN** by default: `EXPLAIN (FORMAT JSON)` for SQL with no parameters, or
`EXPLAIN (GENERIC_PLAN, FORMAT JSON)` (PostgreSQL 16+) for a normalized statement with `$n`.
- **EXPLAIN (ANALYZE, BUFFERS)** only for a single `SELECT` (or `WITH` without data-modifying
CTEs), with parameter values the user supplies, in a `READ ONLY` transaction with
`SET LOCAL statement_timeout` (`explain_timeout_ms`, 10 s) that is always **rolled back**.
It needs the separate `db_performance.explain_analyze` permission.
- Every request writes a `db_perf_explain` audit row (who, statement, mode, duration, outcome).
Parameter values are never stored.
- Entry points: the **Query plan** record action on a statement (standard process screens), and
`POST /qqq/v1/qbit/db-performance/explain` for any client.
- **Explain in SQL Workbench**: each statement links to the Workbench runner with `EXPLAIN` and its
text (`sqlWorkbenchRunnerPath`, configurable). The reverse link, Workbench → this qbit's query
history, uses `GET /qqq/v1/qbit/db-performance/statements/lookup?sql=...`, which normalizes the
SQL the same way and returns the statement's history URL.
- **Explain this plan (assistant, optional):** the plan response carries an `assistantPrompt`
(the statement's normalized text and the plan, nothing else). A client sends it to the assistant
through the assistant's own `/qqq/v1` operations with the user's session; the server never calls
a model. `db_perf_settings` and `db_perf_rule` are marked `AiExposure.NONE`, so the assistant's
tools cannot read the qbit's configuration.
## 8. UI
**App home (Database Performance)**, full-width widgets only, top to bottom:
1. **Setup** - monitored backend, provider, `pg_read_all_stats` and `pg_stat_statements` access,
last snapshot, attribution on/off; each with its state and next step.
2. **Database time** - line chart of statement time per snapshot (24 h) with calls.
3. **Top queries** - by total time over 24 h: statement, calls, mean, total, % of DB time, hit ratio.
4. **Findings** - open findings by score with severity and status.
5. **Live activity** - long-running, blocked/blocking chains, idle in transaction (live, not stored).
6. **Connections** - current by application/user vs `max_connections`, cache hit ratio, temp, deadlocks.
7. **QQQ attribution** - routes, processes and jobs by database time; repeated-statement (N+1) patterns.
8. **Tables** - sequential-scan heavy and fastest-growing tables.
Each table widget is full width, paginates instead of scrolling inside, and stacks into cards when
narrow. Record screens: Queries (snapshots and daily history child lists, findings, plan history),
Tables (snapshots, indexes, findings), Indexes, Findings (evidence, snippet).
**Record actions** (never in the nav, standard Review/Result screens):
- Queries: **Query plan**.
- Findings: **Acknowledge**, **Mark resolved**, **Ignore**, **Reopen** (note required for Ignore).
- Snapshots: **Collect now** (`manage_settings`).
- Settings: **Apply default thresholds** (insert-only reseed of missing rules).
**UI API extension** `db-performance` (`/qqq/v1/qbit/db-performance`, semver 1.0.0, committed
OpenAPI fragment with contract test): `GET overview`, `GET activity`, `POST explain`,
`GET statements/lookup`.
## 9. Durable jobs, alerts, health
**Scheduled processes** (host scheduler, `schedulerName` in the config):
`dbPerfCollectSnapshot` (every `snapshot_interval_minutes`, 5), `dbPerfAnalyze` (hourly),
`dbPerfRollupAndPurge` (daily 03:15). Each declares `DurableProcessMetaData` (eligible, ATOMIC,
idempotent), so a host with scheduled durable jobs runs them as crash-proof jobs with job records,
retries and the Jobs app's failure and stall alerts and email. Every step is idempotent: a
snapshot is keyed by backend and capture time, rollups replace the day's row, purges are keyset
batches, so a rerun after a crash never double counts. Each pass also writes its own
`db_perf_snapshot` row (status, duration, error), which is the report even where the host runs
scheduled processes inline.
**Alerts** (core `QAlerts`; delivered by qbit-alerts when installed, logged otherwise; keyed, and
resolved when clear): `db_perf.collection_failed`, `db_perf.query_spike` (a statement's total time
in an interval above `spike_factor` × its 7-day interval median and above a floor),
`db_perf.connection_saturation` (connections ≥ `connection_alert_percent` of `max_connections`),
`db_perf.blocked_chain` (a session blocked longer than `blocked_alert_seconds`).
**Health** (core health SPI, Admin → Health): `db_performance.collector` (last successful snapshot
age vs interval), `db_performance.stats_access` (pg_stat_statements present, `pg_read_all_stats`
held), `db_performance.connections` (saturation).
## 10. Permissions
| Permission | Grants |
|---|---|
| `db_performance.view` | the app, its tables (read), widgets, overview and activity (fingerprinted text) |
| `db_performance.view_live_text` | live query text in the activity view (never persisted) |
| `db_performance.explain` | plain and generic EXPLAIN |
| `db_performance.explain_analyze` | EXPLAIN (ANALYZE, BUFFERS) on SELECTs |
| `db_performance.manage_findings` | finding status actions |
| `db_performance.manage_settings` | edit settings and rules, Collect now |
## 11. Configuration (`DbPerfQBitConfig`)
`backendName` (required, the monitored backend and the tables' backend), `schedulerName`,
`appName` (`dbPerformance`), `parentAppName`, `permissionBaseName` (`db_performance`),
`attributionEnabled` (true), `sqlWorkbenchRunnerPath` (`/app/sqlWorkbenchRunner`), `podName`
(env `HOSTNAME`). Runtime thresholds live in `db_perf_settings` and `db_perf_rule`.
## 12. Host setup (kof22)
1. Grant the app role `pg_read_all_stats` declaratively: cluster-gitops
`tenants/kof22/website/pg-cluster.yaml`, CNPG managed role `kof22_website` `inRoles`.
`pg_stat_statements` is already preloaded and created (cluster-gitops#133, #136).
2. Website-Backend: add the dependency, include the qbit changelog, produce the qbit, register the
listener, grant the permissions to kof22-admins, place the app under Admin → Operations.
Migrations run as `kof22_website` (ChangelogDefaultRolePostgresIntegrationTest).
## 13. Acceptance
- Delta math: reset (calls down, `stats_since` change, `stats_reset`), new entries, evictions; unit tested.
- Normalizer: literals, IN lists, comments, whitespace; identical fingerprints for QQQ `?` SQL and
`pg_stat_statements` `$n` SQL of the same statement.
- Postgres Testcontainers: the changelog applies, collection against a live `pg_stat_statements`,
plan viewer READ ONLY rollback (a write in ANALYZE fails and nothing persists), statement timeout.
- H2: metadata, changelog, every table read by the runtime with a test proving a row changes behavior.
- Attribution: N+1 detection, flush batching (one insert per table per window), self-suppression.
- Findings dedupe, auto-resolve, IGNORED stays ignored, Liquibase snippet content.
- UI API fragment contract test; help bundle coverage test; FK guardrail test.
- JaCoCo ≥ 70% instructions, checkstyle clean.
+2 -80
View File
@@ -67,46 +67,6 @@
</scm>
<dependencies>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-postgres</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-rdbms</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.33.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.14</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.21.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.21.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-core</artifactId>
@@ -127,44 +87,6 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*IT.java</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.12.6</version>
</dependency>
</dependencies>
<configuration>
<configLocation>checkstyle/config.xml</configLocation>
<headerLocation>checkstyle/license.txt</headerLocation>
<consoleOutput>true</consoleOutput>
<failOnViolation>true</failOnViolation>
<violationSeverity>warning</violationSeverity>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
</configuration>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals><goal>check</goal></goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
@@ -216,14 +138,14 @@
<profiles>
<!-- Built against qqq 4.1 (QBitMetaDataProducer). Until qbit-build-parent -->
<!-- imports the released 4.1 BOM, this profile is active by default and -->
<!-- imports qqq-bom-pom 4.1.0-RC.11 ahead of the parent's 4.0.0 BOM. -->
<!-- imports qqq-bom-pom 4.1.0-RC.10 ahead of the parent's 4.0.0 BOM. -->
<profile>
<id>qqq-snapshot</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<qqq.snapshot.version>4.1.0-RC.11</qqq.snapshot.version>
<qqq.snapshot.version>4.1.0-RC.10</qqq.snapshot.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -1,60 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.permissions.UseOtherPermissionNameCustomPermissionChecker;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
/*******************************************************************************
** Stable public permission names for host role configuration.
*******************************************************************************/
public final class DbPerfPermissions
{
public static final String VIEW = "db_performance.view";
public static final String VIEW_LIVE_TEXT = "db_performance.view_live_text";
public static final String EXPLAIN = "db_performance.explain";
public static final String EXPLAIN_ANALYZE = "db_performance.explain_analyze";
public static final String MANAGE_FINDINGS = "db_performance.manage_findings";
public static final String MANAGE_SETTINGS = "db_performance.manage_settings";
public static final List<String> ALL = List.of(VIEW, VIEW_LIVE_TEXT, EXPLAIN, EXPLAIN_ANALYZE, MANAGE_FINDINGS, MANAGE_SETTINGS);
/*******************************************************************************
** Constants only.
*******************************************************************************/
private DbPerfPermissions()
{
}
/*******************************************************************************
** Require an exact permission name, without a generated suffix.
*******************************************************************************/
public static QPermissionRules rules(String permission)
{
return (new QPermissionRules().withLevel(PermissionLevel.HAS_ACCESS_PERMISSION).withPermissionBaseName(permission)
.withCustomPermissionChecker(UseOtherPermissionNameCustomPermissionChecker.build(permission)));
}
}
@@ -1,15 +1,11 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
* Copyright 2026 Kingsrook, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -24,54 +20,37 @@ package com.kingsrook.qbits.dbperformance;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitConfig;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** Host configuration; runtime thresholds live in the Settings table.
** Host-facing configuration for the DB Performance QBit.
**
** Tables are named db_perf_<name> (lowercase snake_case, backend name equal
** to the table name); permissions are db_perf.<action>.
*******************************************************************************/
public class DbPerfQBitConfig implements QBitConfig
{
public static final String TABLE_NAME_PREFIX = "db_perf_";
public static final String PERMISSION_BASE_NAME = "db_performance";
public static final String TABLE_NAME_PREFIX = "db_perf_";
public static final String PERMISSION_BASE_NAME = "db_perf";
private String backendName;
private String schedulerName;
private String appName = "dbPerformance";
private String parentAppName;
private String permissionBaseName = "db_performance";
private Boolean attributionEnabled = true;
private String sqlWorkbenchRunnerPath = "/app/sqlWorkbenchRunner";
private String podName = System.getenv("HOSTNAME") == null ? "local" : System.getenv("HOSTNAME");
private String snapshotCron = "0 */5 * * * ?";
private String analyzeCron = "0 7 * * * ?";
private String rollupCron = "0 15 3 * * ?";
private String timeZone = "UTC";
/*******************************************************************************
** Validate backend and application identity before registration.
** The backend must be named.
*******************************************************************************/
@Override
public void validate(QInstance qInstance, List<String> errors)
{
assertCondition(backendName != null && qInstance.getBackend(backendName) instanceof RDBMSBackendMetaData, "backendName must name an existing RDBMS backend", errors);
assertCondition(appName != null && !appName.isBlank() && qInstance.getApp(appName) == null, "appName must be nonblank and unused", errors);
assertCondition(permissionBaseName != null && !permissionBaseName.isBlank(), "permissionBaseName is required", errors);
assertCondition(parentAppName == null || qInstance.getApp(parentAppName) != null, "parentAppName must name an existing app", errors);
try
{
java.time.ZoneId.of(timeZone);
}
catch(Exception e)
{
errors.add("timeZone must be a valid zone ID");
}
assertCondition(StringUtils.hasContent(backendName), "backendName is required for the DB Performance QBit", errors);
}
/*******************************************************************************
** Default storage backend.
** Tables without their own backend use the configured backend.
*******************************************************************************/
@Override
public String getDefaultBackendNameForTables()
@@ -80,8 +59,9 @@ public class DbPerfQBitConfig implements QBitConfig
}
/*******************************************************************************
** Getter for backendName.
** Getter for backendName
*******************************************************************************/
public String getBackendName()
{
@@ -89,8 +69,9 @@ public class DbPerfQBitConfig implements QBitConfig
}
/*******************************************************************************
** Setter for backendName.
** Setter for backendName
*******************************************************************************/
public void setBackendName(String backendName)
{
@@ -98,8 +79,9 @@ public class DbPerfQBitConfig implements QBitConfig
}
/*******************************************************************************
** Fluent setter for backendName.
** Fluent setter for backendName
*******************************************************************************/
public DbPerfQBitConfig withBackendName(String backendName)
{
@@ -107,311 +89,4 @@ public class DbPerfQBitConfig implements QBitConfig
return (this);
}
/*******************************************************************************
** Getter for schedulerName.
*******************************************************************************/
public String getSchedulerName()
{
return (this.schedulerName);
}
/*******************************************************************************
** Setter for schedulerName.
*******************************************************************************/
public void setSchedulerName(String schedulerName)
{
this.schedulerName = schedulerName;
}
/*******************************************************************************
** Fluent setter for schedulerName.
*******************************************************************************/
public DbPerfQBitConfig withSchedulerName(String schedulerName)
{
this.schedulerName = schedulerName;
return (this);
}
/*******************************************************************************
** Getter for appName.
*******************************************************************************/
public String getAppName()
{
return (this.appName);
}
/*******************************************************************************
** Setter for appName.
*******************************************************************************/
public void setAppName(String appName)
{
this.appName = appName;
}
/*******************************************************************************
** Fluent setter for appName.
*******************************************************************************/
public DbPerfQBitConfig withAppName(String appName)
{
this.appName = appName;
return (this);
}
/*******************************************************************************
** Getter for parentAppName.
*******************************************************************************/
public String getParentAppName()
{
return (this.parentAppName);
}
/*******************************************************************************
** Setter for parentAppName.
*******************************************************************************/
public void setParentAppName(String parentAppName)
{
this.parentAppName = parentAppName;
}
/*******************************************************************************
** Fluent setter for parentAppName.
*******************************************************************************/
public DbPerfQBitConfig withParentAppName(String parentAppName)
{
this.parentAppName = parentAppName;
return (this);
}
/*******************************************************************************
** Getter for permissionBaseName.
*******************************************************************************/
public String getPermissionBaseName()
{
return (this.permissionBaseName);
}
/*******************************************************************************
** Setter for permissionBaseName.
*******************************************************************************/
public void setPermissionBaseName(String permissionBaseName)
{
this.permissionBaseName = permissionBaseName;
}
/*******************************************************************************
** Fluent setter for permissionBaseName.
*******************************************************************************/
public DbPerfQBitConfig withPermissionBaseName(String permissionBaseName)
{
this.permissionBaseName = permissionBaseName;
return (this);
}
/*******************************************************************************
** Getter for attributionEnabled.
*******************************************************************************/
public Boolean getAttributionEnabled()
{
return (this.attributionEnabled);
}
/*******************************************************************************
** Setter for attributionEnabled.
*******************************************************************************/
public void setAttributionEnabled(Boolean attributionEnabled)
{
this.attributionEnabled = attributionEnabled;
}
/*******************************************************************************
** Fluent setter for attributionEnabled.
*******************************************************************************/
public DbPerfQBitConfig withAttributionEnabled(Boolean attributionEnabled)
{
this.attributionEnabled = attributionEnabled;
return (this);
}
/*******************************************************************************
** Getter for sqlWorkbenchRunnerPath.
*******************************************************************************/
public String getSqlWorkbenchRunnerPath()
{
return (this.sqlWorkbenchRunnerPath);
}
/*******************************************************************************
** Setter for sqlWorkbenchRunnerPath.
*******************************************************************************/
public void setSqlWorkbenchRunnerPath(String sqlWorkbenchRunnerPath)
{
this.sqlWorkbenchRunnerPath = sqlWorkbenchRunnerPath;
}
/*******************************************************************************
** Fluent setter for sqlWorkbenchRunnerPath.
*******************************************************************************/
public DbPerfQBitConfig withSqlWorkbenchRunnerPath(String sqlWorkbenchRunnerPath)
{
this.sqlWorkbenchRunnerPath = sqlWorkbenchRunnerPath;
return (this);
}
/*******************************************************************************
** Getter for podName.
*******************************************************************************/
public String getPodName()
{
return (this.podName);
}
/*******************************************************************************
** Setter for podName.
*******************************************************************************/
public void setPodName(String podName)
{
this.podName = podName;
}
/*******************************************************************************
** Fluent setter for podName.
*******************************************************************************/
public DbPerfQBitConfig withPodName(String podName)
{
this.podName = podName;
return (this);
}
/*******************************************************************************
** Getter for snapshotCron.
*******************************************************************************/
public String getSnapshotCron()
{
return (this.snapshotCron);
}
/*******************************************************************************
** Setter for snapshotCron.
*******************************************************************************/
public void setSnapshotCron(String snapshotCron)
{
this.snapshotCron = snapshotCron;
}
/*******************************************************************************
** Fluent setter for snapshotCron.
*******************************************************************************/
public DbPerfQBitConfig withSnapshotCron(String snapshotCron)
{
this.snapshotCron = snapshotCron;
return (this);
}
/*******************************************************************************
** Getter for analyzeCron.
*******************************************************************************/
public String getAnalyzeCron()
{
return (this.analyzeCron);
}
/*******************************************************************************
** Setter for analyzeCron.
*******************************************************************************/
public void setAnalyzeCron(String analyzeCron)
{
this.analyzeCron = analyzeCron;
}
/*******************************************************************************
** Fluent setter for analyzeCron.
*******************************************************************************/
public DbPerfQBitConfig withAnalyzeCron(String analyzeCron)
{
this.analyzeCron = analyzeCron;
return (this);
}
/*******************************************************************************
** Getter for rollupCron.
*******************************************************************************/
public String getRollupCron()
{
return (this.rollupCron);
}
/*******************************************************************************
** Setter for rollupCron.
*******************************************************************************/
public void setRollupCron(String rollupCron)
{
this.rollupCron = rollupCron;
}
/*******************************************************************************
** Fluent setter for rollupCron.
*******************************************************************************/
public DbPerfQBitConfig withRollupCron(String rollupCron)
{
this.rollupCron = rollupCron;
return (this);
}
/*******************************************************************************
** Getter for timeZone.
*******************************************************************************/
public String getTimeZone()
{
return (this.timeZone);
}
/*******************************************************************************
** Setter for timeZone.
*******************************************************************************/
public void setTimeZone(String timeZone)
{
this.timeZone = timeZone;
}
/*******************************************************************************
** Fluent setter for timeZone.
*******************************************************************************/
public DbPerfQBitConfig withTimeZone(String timeZone)
{
this.timeZone = timeZone;
return (this);
}
}
@@ -1,15 +1,11 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
* Copyright 2026 Kingsrook, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -21,13 +17,6 @@
package com.kingsrook.qbits.dbperformance;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qbits.dbperformance.metadata.DbPerfMetaData;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitConfigValidationException;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaDataProducer;
@@ -111,33 +100,4 @@ public class DbPerfQBitProducer implements QBitMetaDataProducer<DbPerfQBitConfig
return (qBitConfig != null);
}
/*******************************************************************************
** Validate and register the Phase A metadata.
*******************************************************************************/
@Override
public MetaDataProducerMultiOutput produce(QInstance instance) throws QException
{
if(qBitConfig == null)
{
throw (new QException("DB Performance configuration is required"));
}
List<String> errors = new ArrayList<>();
qBitConfig.validate(instance, errors);
for(String table : DbPerfMetaData.TABLES)
{
if(instance.getTable(table) != null)
{
errors.add("Table already registered: " + table);
}
}
if(!errors.isEmpty())
{
throw (new QBitConfigValidationException(qBitConfig, errors));
}
instance.addQBit(getQBitMetaData());
DbPerfMetaData.addTo(instance, qBitConfig);
new DbPerfRuntime(instance, qBitConfig).addSelfToInstance(instance);
com.kingsrook.qbits.dbperformance.processes.DbPerfProcesses.addTo(instance, qBitConfig);
return (new MetaDataProducerMultiOutput());
}
}
@@ -1,153 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.util.concurrent.atomic.AtomicReference;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.context.CapturedContext;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
/*******************************************************************************
** Per-instance configuration and service boundary; no global singleton or filesystem.
*******************************************************************************/
public class DbPerfRuntime implements QSupplementalInstanceMetaData
{
public static final String NAME = "dbPerformance";
private final QInstance instance;
private final DbPerfQBitConfig config;
/*******************************************************************************
** Runtime bound to a single host instance.
*******************************************************************************/
public DbPerfRuntime(QInstance instance, DbPerfQBitConfig config)
{
this.instance = instance;
this.config = config;
}
/*******************************************************************************
** Find the registered runtime.
*******************************************************************************/
public static DbPerfRuntime of(QInstance instance)
{
DbPerfRuntime runtime = QSupplementalInstanceMetaData.of(instance, NAME);
if(runtime == null)
{
throw (new IllegalStateException("Database Performance is not registered"));
}
return (runtime);
}
/*******************************************************************************
** Supplemental metadata name.
*******************************************************************************/
@Override
public String getName()
{
return (NAME);
}
/*******************************************************************************
** Register on the owning instance.
*******************************************************************************/
@Override
public void addSelfToInstance(QInstance instance)
{
instance.withSupplementalMetaData(this);
}
/*******************************************************************************
** Host configuration.
*******************************************************************************/
public DbPerfQBitConfig getConfig()
{
return (config);
}
/*******************************************************************************
** Owning instance.
*******************************************************************************/
public QInstance getInstance()
{
return (instance);
}
/*******************************************************************************
** Execute as the system user, always restoring the caller context.
*******************************************************************************/
public <T> T asSystem(Work<T> work) throws Exception
{
AtomicReference<T> result = new AtomicReference<>();
QContext.withTemporaryContext(new CapturedContext(instance, new QSystemUserSession(instance)), () -> result.set(work.run()));
return (result.get());
}
/*******************************************************************************
** Atomic QQQ action transaction, rolled back on failure.
*******************************************************************************/
public <T> T transaction(Work<T> work) throws Exception
{
return (asSystem(() ->
{
try(QBackendTransaction transaction = QBackendTransaction.openFor(new QueryInput("db_perf_snapshot")))
{
AtomicReference<T> result = new AtomicReference<>();
try
{
QContext.withTemporaryContext(new CapturedContext(instance, new QSystemUserSession(instance), transaction, null), () -> result.set(work.run()));
transaction.commit();
return (result.get());
}
catch(Exception e)
{
transaction.rollback();
throw (e);
}
}
}));
}
/*******************************************************************************
** Checked service operation.
*******************************************************************************/
public interface Work<T>
{
/*******************************************************************************
** Execute the operation.
*******************************************************************************/
T run() throws Exception;
}
}
@@ -1,74 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.collect;
import java.util.List;
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Insert-only per-backend defaults; user edits are never overwritten.
*******************************************************************************/
public class DbPerfSettingsService
{
private final DbPerfRuntime runtime;
/*******************************************************************************
** Runtime dependency.
*******************************************************************************/
public DbPerfSettingsService(DbPerfRuntime runtime)
{
this.runtime = runtime;
}
/*******************************************************************************
** Load or insert the settings row. A concurrent winner is re-read.
*******************************************************************************/
public QRecord ensure(String backendName) throws Exception
{
return (runtime.asSystem(() ->
{
QRecord existing = DbPerfStore.first("db_perf_settings", DbPerfStore.eq("backendName", backendName));
if(existing != null)
{
return (existing);
}
try
{
return (DbPerfStore.insert("db_perf_settings", List.of(new QRecord().withValue("backendName", backendName)
.withValue("attributionEnabled", runtime.getConfig().getAttributionEnabled()))).getFirst());
}
catch(Exception e)
{
QRecord winner = DbPerfStore.first("db_perf_settings", DbPerfStore.eq("backendName", backendName));
if(winner != null)
{
return (winner);
}
throw (e);
}
}));
}
}
@@ -1,162 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.collect;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Small QQQ action adapter. All mutations preserve audit/customizer semantics.
*******************************************************************************/
public final class DbPerfStore
{
public static final int BATCH_SIZE = 1000;
/*******************************************************************************
** Utility class.
*******************************************************************************/
private DbPerfStore()
{
}
/*******************************************************************************
** Read rows using the ambient system transaction.
*******************************************************************************/
public static List<QRecord> query(String table, QQueryFilter filter) throws QException
{
return (new QueryAction().execute(new QueryInput(table).withFilter(filter).withTransaction(QContext.getQBackendTransaction())).getRecords());
}
/*******************************************************************************
** First matching record.
*******************************************************************************/
public static QRecord first(String table, QQueryFilter filter) throws QException
{
List<QRecord> rows = query(table, filter.withLimit(1));
return (rows.isEmpty() ? null : rows.getFirst());
}
/*******************************************************************************
** A field equality predicate.
*******************************************************************************/
public static QQueryFilter eq(String field, Serializable value)
{
return (new QQueryFilter(new QFilterCriteria(field, QCriteriaOperator.EQUALS, value)));
}
/*******************************************************************************
** Bounded batch insertion, returning generated IDs and rejecting row errors.
*******************************************************************************/
public static List<QRecord> insert(String table, List<QRecord> rows) throws QException
{
List<QRecord> result = new ArrayList<>();
for(int i = 0; i < rows.size(); i += BATCH_SIZE)
{
List<QRecord> inserted = InsertAction.executeForRecords(new InsertInput(table).withRecords(new ArrayList<>(rows.subList(i, Math.min(rows.size(), i + BATCH_SIZE)))).withTransaction(QContext.getQBackendTransaction()));
check(inserted);
result.addAll(inserted);
}
return (result);
}
/*******************************************************************************
** Batch updates, throwing on row errors rather than silently losing data.
*******************************************************************************/
public static void update(String table, List<QRecord> rows) throws QException
{
for(int i = 0; i < rows.size(); i += BATCH_SIZE)
{
check(UpdateAction.executeForRecords(new UpdateInput(table).withRecords(new ArrayList<>(rows.subList(i, Math.min(rows.size(), i + BATCH_SIZE)))).withTransaction(QContext.getQBackendTransaction())));
}
}
/*******************************************************************************
** Keyset deletion: select at most 1000 matching IDs, delete through QQQ,
** then advance the key. No OFFSET skips and no unbounded ID collection.
*******************************************************************************/
public static int delete(String table, QQueryFilter filter) throws QException
{
int count = 0;
int lastId = 0;
while(true)
{
QQueryFilter page = new QQueryFilter().withSubFilter(filter).withCriteria(new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN, lastId))
.withOrderBy(new QFilterOrderBy("id", true)).withLimit(BATCH_SIZE);
List<QRecord> rows = query(table, page);
if(rows.isEmpty())
{
return (count);
}
List<Serializable> ids = rows.stream().map(row -> (Serializable) row.getValueInteger("id")).toList();
var deleted = new DeleteAction().execute(new DeleteInput(table).withQueryFilter(new QQueryFilter().withSubFilter(filter).withCriteria(new QFilterCriteria("id", QCriteriaOperator.IN, ids))).withTransaction(QContext.getQBackendTransaction()));
if(deleted.getRecordsWithErrors() != null)
{
check(deleted.getRecordsWithErrors());
}
if(deleted.getDeletedRecordCount() != ids.size())
{
throw (new QException("Database Performance deletion did not remove all selected records"));
}
lastId = rows.getLast().getValueInteger("id");
count += rows.size();
}
}
/*******************************************************************************
** Reject action-level record errors without persisting potentially raw SQL.
*******************************************************************************/
private static void check(List<QRecord> rows) throws QException
{
for(QRecord row : rows)
{
if(row.getErrors() != null && !row.getErrors().isEmpty())
{
throw (new QException("Database Performance record write failed: " + row.getErrors().getFirst().getClass().getSimpleName()));
}
}
}
}
@@ -1,83 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.collect;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.Objects;
import com.kingsrook.qbits.dbperformance.provider.StatementStats;
/*******************************************************************************
** Pure cumulative-counter arithmetic with explicit baselines and reset detection.
*******************************************************************************/
public final class DeltaCalculator
{
/*******************************************************************************
** Utility class.
*******************************************************************************/
private DeltaCalculator()
{
}
/*******************************************************************************
** A first observation is not historical activity. Only an entry created
** within the previous capture interval can count from zero immediately.
*******************************************************************************/
public static Delta calculate(StatementStats previous, StatementStats current, Instant previousCapture, Instant globalReset)
{
boolean fresh = previous == null;
boolean baseline = fresh && (previousCapture == null || current.statsSince() == null || !current.statsSince().isAfter(previousCapture));
boolean reset = !fresh && (current.calls() < previous.calls()
|| current.statsSince() != null && !Objects.equals(current.statsSince(), previous.statsSince())
|| globalReset != null && previousCapture != null && globalReset.isAfter(previousCapture));
if(baseline)
{
return (new Delta(0, BigDecimal.ZERO, 0, 0, 0, 0, false, true));
}
boolean zero = fresh || reset;
return (new Delta(counter(zero ? 0L : previous.calls(), current.calls(), false),
current.totalExecMs().subtract(zero ? BigDecimal.ZERO : previous.totalExecMs()).max(BigDecimal.ZERO),
counter(zero ? 0L : previous.rows(), current.rows(), false),
counter(zero ? 0L : previous.sharedBlksHit(), current.sharedBlksHit(), false),
counter(zero ? 0L : previous.sharedBlksRead(), current.sharedBlksRead(), false),
counter(zero ? 0L : previous.tempBlksWritten(), current.tempBlksWritten(), false), reset, false));
}
/*******************************************************************************
** Null means no baseline; a decrease means reset for this counter.
*******************************************************************************/
public static long counter(Long previous, long current, boolean reset)
{
return (previous == null ? 0 : reset || current < previous ? current : current - previous);
}
/*******************************************************************************
** Interval statement activity; maximum duration remains a since-reset gauge.
*******************************************************************************/
public record Delta(long calls, BigDecimal totalMs, long rows, long sharedBlksHit, long sharedBlksRead, long tempBlksWritten, boolean reset, boolean baseline)
{
}
}
@@ -1,119 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.collect;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Configurable retention through bounded QQQ keyset deletes.
*******************************************************************************/
public class RetentionService
{
private final DbPerfRuntime runtime;
private final Clock clock;
/*******************************************************************************
** Production clock.
*******************************************************************************/
public RetentionService(DbPerfRuntime runtime)
{
this(runtime, Clock.systemUTC());
}
/*******************************************************************************
** Clock injection for exact boundary tests.
*******************************************************************************/
public RetentionService(DbPerfRuntime runtime, Clock clock)
{
this.runtime = runtime;
this.clock = clock;
}
/*******************************************************************************
** Only closed old findings are removed; FK actions preserve open findings.
*******************************************************************************/
public int purge() throws Exception
{
QRecord settings = new DbPerfSettingsService(runtime).ensure(runtime.getConfig().getBackendName());
Instant detailCutoff = clock.instant().minusSeconds(86400L * SnapshotCollector.positive(settings, "detailRetentionDays", 30));
int rollupDays = SnapshotCollector.positive(settings, "rollupRetentionDays", 365);
Instant staleCutoff = clock.instant().minusSeconds(86400L * rollupDays);
LocalDate dayCutoff = LocalDate.now(clock.withZone(ZoneId.of(runtime.getConfig().getTimeZone()))).minusDays(rollupDays);
return (runtime.asSystem(() ->
{
int count = 0;
for(String table : List.of("db_perf_query_snapshot", "db_perf_table_snapshot", "db_perf_activity_sample", "db_perf_connection_sample"))
{
count += DbPerfStore.delete(table, before("capturedAt", detailCutoff));
}
for(String table : List.of("db_perf_attribution", "db_perf_repeat_pattern"))
{
count += DbPerfStore.delete(table, before("windowStart", detailCutoff));
}
count += DbPerfStore.delete("db_perf_explain", before("requestedAt", detailCutoff));
count += DbPerfStore.delete("db_perf_snapshot", backend(before("capturedAt", detailCutoff)));
for(String table : List.of("db_perf_query_daily", "db_perf_table_daily", "db_perf_attribution_daily"))
{
count += DbPerfStore.delete(table, new QQueryFilter(new QFilterCriteria("day", QCriteriaOperator.LESS_THAN, dayCutoff)));
}
for(String table : List.of("db_perf_index_stat", "db_perf_statement", "db_perf_table_stat"))
{
count += DbPerfStore.delete(table, backend(before("lastSeenAt", staleCutoff)));
}
count += DbPerfStore.delete("db_perf_finding", backend(before("lastSeenAt", staleCutoff))
.withCriteria(new QFilterCriteria("status", QCriteriaOperator.IN, List.of("RESOLVED", "IGNORED"))));
return (count);
}));
}
/*******************************************************************************
** Scope backend-bearing rows to the configured monitored backend.
*******************************************************************************/
private QQueryFilter backend(QQueryFilter filter)
{
return (filter.withCriteria(new QFilterCriteria("backendName", QCriteriaOperator.EQUALS, runtime.getConfig().getBackendName())));
}
/*******************************************************************************
** Strict age boundary preserves records at the cutoff.
*******************************************************************************/
private static QQueryFilter before(String field, Instant cutoff)
{
return (new QQueryFilter(new QFilterCriteria(field, QCriteriaOperator.LESS_THAN, cutoff)));
}
}
@@ -1,232 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.collect;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
import com.kingsrook.qbits.dbperformance.normalize.SqlNormalizer;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Daily aggregates replaced atomically; reruns include late arriving detail rows.
*******************************************************************************/
public class RollupService
{
private final DbPerfRuntime runtime;
/*******************************************************************************
** Runtime dependency.
*******************************************************************************/
public RollupService(DbPerfRuntime runtime)
{
this.runtime = runtime;
}
/*******************************************************************************
** Replace all three rollups for a local calendar day in one transaction.
*******************************************************************************/
public void rollupDay(LocalDate day) throws Exception
{
synchronized(runtime)
{
runtime.transaction(() ->
{
ZoneId zone = ZoneId.of(runtime.getConfig().getTimeZone());
Instant start = day.atStartOfDay(zone).toInstant();
Instant end = day.plusDays(1).atStartOfDay(zone).toInstant();
rollupQueries(day, start, end);
rollupTables(day, start, end);
rollupAttribution(day, start, end);
return (null);
});
}
}
/*******************************************************************************
** A day needs backfill only if retained detail exists without its daily rows.
** Existing history must survive after a short detail-retention window expires.
*******************************************************************************/
public boolean needsBackfill(LocalDate day) throws Exception
{
return (runtime.asSystem(() ->
{
ZoneId zone = ZoneId.of(runtime.getConfig().getTimeZone());
Instant start = day.atStartOfDay(zone).toInstant();
Instant end = day.plusDays(1).atStartOfDay(zone).toInstant();
for(String kind : List.of("query", "table", "attribution"))
{
String detail = "db_perf_" + kind + (kind.equals("attribution") ? "" : "_snapshot");
String field = kind.equals("attribution") ? "windowStart" : "capturedAt";
if(DbPerfStore.first("db_perf_" + kind + "_daily", DbPerfStore.eq("day", day)) == null
&& DbPerfStore.first(detail, range(field, start, end)) != null)
{
return (true);
}
}
return (false);
}));
}
/*******************************************************************************
** Weighted statement means/hit ratios, since-reset maximum, exact day share.
*******************************************************************************/
private void rollupQueries(LocalDate day, Instant start, Instant end) throws Exception
{
Map<Integer, QRecord> groups = new LinkedHashMap<>();
Map<Integer, long[]> hits = new LinkedHashMap<>();
BigDecimal denominator = BigDecimal.ZERO;
for(QRecord snapshot : DbPerfStore.query("db_perf_snapshot", range("capturedAt", start, end)
.withCriteria(new QFilterCriteria("backendName", QCriteriaOperator.EQUALS, runtime.getConfig().getBackendName()))
.withCriteria(new QFilterCriteria("status", QCriteriaOperator.IN, List.of("OK", "PARTIAL")))))
{
denominator = denominator.add(SnapshotCollector.decimal(snapshot, "statementTotalMs"));
}
for(QRecord row : DbPerfStore.query("db_perf_query_snapshot", range("capturedAt", start, end)))
{
int id = row.getValueInteger("statementId");
QRecord aggregate = groups.computeIfAbsent(id, key -> new QRecord().withValue("statementId", key).withValue("day", day));
sum(aggregate, row, "calls", "rowsCount");
sumDecimal(aggregate, row, "totalMs");
aggregate.setValue("maxMs", SnapshotCollector.decimal(aggregate, "maxMs").max(SnapshotCollector.decimal(row, "maxMs")));
aggregate.setValue("snapshots", (int) SnapshotCollector.number(aggregate, "snapshots") + 1);
long[] counts = hits.computeIfAbsent(id, key -> new long[2]);
counts[0] += SnapshotCollector.number(row, "sharedBlksHit");
counts[1] += SnapshotCollector.number(row, "sharedBlksRead");
}
for(Map.Entry<Integer, QRecord> entry : groups.entrySet())
{
QRecord row = entry.getValue();
long[] counts = hits.get(entry.getKey());
row.withValue("meanMs", SnapshotCollector.divide(SnapshotCollector.decimal(row, "totalMs"), SnapshotCollector.number(row, "calls")))
.withValue("hitRatio", SnapshotCollector.ratio(counts[0], counts[0] + counts[1]))
.withValue("shareOfDbTime", denominator.signum() == 0 ? BigDecimal.ZERO : SnapshotCollector.decimal(row, "totalMs").multiply(BigDecimal.valueOf(100)).divide(denominator, 4, RoundingMode.HALF_UP));
}
replace("db_perf_query_daily", day, new ArrayList<>(groups.values()));
}
/*******************************************************************************
** Sum scan/write deltas, keeping the last size and tuple gauges of the day.
*******************************************************************************/
private void rollupTables(LocalDate day, Instant start, Instant end) throws Exception
{
Map<Integer, QRecord> groups = new LinkedHashMap<>();
for(QRecord row : DbPerfStore.query("db_perf_table_snapshot", range("capturedAt", start, end).withOrderBy(new QFilterOrderBy("capturedAt", true)).withOrderBy(new QFilterOrderBy("id", true))))
{
QRecord aggregate = groups.computeIfAbsent(row.getValueInteger("tableStatId"), key -> new QRecord().withValue("tableStatId", key).withValue("day", day));
sum(aggregate, row, "seqScan", "idxScan", "seqTupRead", "nTupIns", "nTupUpd", "nTupDel");
for(String gauge : List.of("nLiveTup", "nDeadTup", "totalBytes"))
{
aggregate.setValue(gauge, row.getValue(gauge));
}
}
replace("db_perf_table_daily", day, new ArrayList<>(groups.values()));
}
/*******************************************************************************
** Merge pods by all attribution dimensions, using an unambiguous length-
** prefixed digest for null-safe uniqueness and bounded index size.
*******************************************************************************/
private void rollupAttribution(LocalDate day, Instant start, Instant end) throws Exception
{
Map<String, QRecord> groups = new LinkedHashMap<>();
List<String> dimensions = List.of("unitKind", "unitName", "processName", "tableName", "operation", "fingerprint");
for(QRecord row : DbPerfStore.query("db_perf_attribution", range("windowStart", start, end)))
{
StringBuilder key = new StringBuilder();
for(String field : dimensions)
{
String value = row.getValueString(field);
key.append(value == null ? "-1:" : value.length() + ":" + value);
}
String hash = SqlNormalizer.fingerprint(key.toString());
QRecord aggregate = groups.computeIfAbsent(hash, ignored -> new QRecord().withValue("day", day).withValue("rollupKey", hash));
for(String field : dimensions)
{
aggregate.setValue(field, row.getValue(field));
}
aggregate.setValue("sqlText", row.getValueString("sqlText"));
sum(aggregate, row, "units", "statements", "rowsCount", "errors");
sumDecimal(aggregate, row, "totalMs");
aggregate.setValue("maxMs", SnapshotCollector.decimal(aggregate, "maxMs").max(SnapshotCollector.decimal(row, "maxMs")));
}
replace("db_perf_attribution_daily", day, new ArrayList<>(groups.values()));
}
/*******************************************************************************
** Half-open day window works on 23/25-hour local days too.
*******************************************************************************/
private static QQueryFilter range(String field, Instant start, Instant end)
{
return (new QQueryFilter(new QFilterCriteria(field, QCriteriaOperator.GREATER_THAN_OR_EQUALS, start), new QFilterCriteria(field, QCriteriaOperator.LESS_THAN, end)));
}
/*******************************************************************************
** Replace inside the caller's transaction.
*******************************************************************************/
private static void replace(String table, LocalDate day, List<QRecord> rows) throws Exception
{
DbPerfStore.delete(table, DbPerfStore.eq("day", day));
DbPerfStore.insert(table, rows);
}
/*******************************************************************************
** Sum integral counters.
*******************************************************************************/
private static void sum(QRecord target, QRecord source, String... fields)
{
for(String field : fields)
{
target.setValue(field, SnapshotCollector.number(target, field) + SnapshotCollector.number(source, field));
}
}
/*******************************************************************************
** Sum elapsed time without floating-point precision loss.
*******************************************************************************/
private static void sumDecimal(QRecord target, QRecord source, String field)
{
target.setValue(field, SnapshotCollector.decimal(target, field).add(SnapshotCollector.decimal(source, field)));
}
}
@@ -1,616 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.collect;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Connection;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
import com.kingsrook.qbits.dbperformance.normalize.SqlNormalizer;
import com.kingsrook.qbits.dbperformance.provider.ConnectionCount;
import com.kingsrook.qbits.dbperformance.provider.DatabaseStats;
import com.kingsrook.qbits.dbperformance.provider.DbStatsProvider;
import com.kingsrook.qbits.dbperformance.provider.DbStatsProviders;
import com.kingsrook.qbits.dbperformance.provider.IndexStats;
import com.kingsrook.qbits.dbperformance.provider.ProviderCapabilities;
import com.kingsrook.qbits.dbperformance.provider.SessionInfo;
import com.kingsrook.qbits.dbperformance.provider.StatementStats;
import com.kingsrook.qbits.dbperformance.provider.TableStats;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
/*******************************************************************************
** Transactional delta collection; raw JDBC is used only for statistics reads.
*******************************************************************************/
public class SnapshotCollector
{
private final DbPerfRuntime runtime;
private final Clock clock;
private final DbStatsProvider providerOverride;
/*******************************************************************************
** Production provider is chosen from the backend connection.
*******************************************************************************/
public SnapshotCollector(DbPerfRuntime runtime)
{
this(runtime, Clock.systemUTC(), null);
}
/*******************************************************************************
** Explicit clock and provider allow deterministic reset/failure tests.
*******************************************************************************/
public SnapshotCollector(DbPerfRuntime runtime, Clock clock, DbStatsProvider providerOverride)
{
this.runtime = runtime;
this.clock = clock;
this.providerOverride = providerOverride;
}
/*******************************************************************************
** Serialize within an instance and commit counters with their detail rows.
** A failure leaves a FAILED run record and never escapes to the scheduler.
*******************************************************************************/
public QRecord collect(String backendName, boolean forceTables)
{
synchronized(runtime)
{
long started = System.nanoTime();
QRecord snapshot = new QRecord().withValue("backendName", backendName).withValue("capturedAt", clock.instant()).withValue("status", "RUNNING");
try
{
if(!Objects.equals(backendName, runtime.getConfig().getBackendName()))
{
throw (new IllegalArgumentException("Backend is not configured for this runtime"));
}
QRecord settings = new DbPerfSettingsService(runtime).ensure(backendName);
QRecord inserted = runtime.asSystem(() -> DbPerfStore.insert("db_perf_snapshot", List.of(snapshot)).getFirst());
snapshot.setValue("id", inserted.getValueInteger("id"));
runtime.transaction(() ->
{
if(!Boolean.TRUE.equals(settings.getValueBoolean("enabled")))
{
snapshot.withValue("status", "PARTIAL").withValue("errorMessage", "Collection is disabled in Settings.");
}
else
{
collectInTransaction(backendName, forceTables, settings, snapshot);
}
snapshot.setValue("durationMs", (int) Math.min(Integer.MAX_VALUE, (System.nanoTime() - started) / 1000000));
DbPerfStore.update("db_perf_snapshot", List.of(snapshot));
return (null);
});
}
catch(Exception e)
{
// Never persist a JDBC exception message: it may contain SQL literals.
snapshot.withValue("status", "FAILED").withValue("errorMessage", "Collection failed (" + e.getClass().getSimpleName() + "). Check database connectivity, schema and statistics access.")
.withValue("durationMs", (int) Math.min(Integer.MAX_VALUE, (System.nanoTime() - started) / 1000000));
if(snapshot.getValueInteger("id") != null)
{
try
{
runtime.asSystem(() ->
{
DbPerfStore.update("db_perf_snapshot", List.of(new QRecord().withValue("id", snapshot.getValueInteger("id"))
.withValue("status", "FAILED").withValue("errorMessage", snapshot.getValueString("errorMessage")).withValue("durationMs", snapshot.getValueInteger("durationMs"))));
return (null);
});
}
catch(Exception persistenceFailure)
{
snapshot.setValue("errorMessage", snapshot.getValueString("errorMessage") + " Failure status could not be saved.");
}
}
}
return (snapshot);
}
}
/*******************************************************************************
** Statistics read and writes under the ambient system transaction.
*******************************************************************************/
private void collectInTransaction(String backendName, boolean forceTables, QRecord settings, QRecord snapshot) throws Exception
{
RDBMSBackendMetaData storageBackend = (RDBMSBackendMetaData) runtime.getInstance().getBackend(backendName);
if(storageBackend.getActionStrategy().supportsForUpdateSkipLocked())
{
var locked = new com.kingsrook.qqq.backend.core.actions.tables.QueryAction().execute(new com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput("db_perf_settings")
.withFilter(DbPerfStore.eq("backendName", backendName)).withTransaction(com.kingsrook.qqq.backend.core.context.QContext.getQBackendTransaction())
.withLockMode(com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryLockMode.FOR_UPDATE_SKIP_LOCKED)).getRecords();
if(locked.isEmpty())
{
snapshot.withValue("status", "PARTIAL").withValue("errorMessage", "Another collector is already running for this backend.");
return;
}
settings = locked.getFirst();
if(!Boolean.TRUE.equals(settings.getValueBoolean("enabled")))
{
snapshot.withValue("status", "PARTIAL").withValue("errorMessage", "Collection is disabled in Settings.");
return;
}
}
Instant now = snapshot.getValueInstant("capturedAt");
QQueryFilter filter = DbPerfStore.eq("backendName", backendName)
.withCriteria(new QFilterCriteria("status", QCriteriaOperator.IN, List.of("OK", "PARTIAL")))
.withCriteria(new QFilterCriteria("rawXactCommit", QCriteriaOperator.IS_NOT_BLANK))
.withOrderBy(new QFilterOrderBy("capturedAt", false));
QRecord previous = DbPerfStore.first("db_perf_snapshot", filter);
Instant previousCapture = previous == null ? null : previous.getValueInstant("capturedAt");
snapshot.withValue("intervalSeconds", previousCapture == null ? 0 : Math.max(0, (int) Duration.between(previousCapture, now).toSeconds()))
.withValue("statsResetDetected", false).withValue("includesTables", false);
RDBMSBackendMetaData backend = (RDBMSBackendMetaData) runtime.getInstance().getBackend(backendName);
try(Connection connection = ConnectionManager.getConnection(backend))
{
connection.setReadOnly(true);
DbStatsProvider provider = providerOverride == null ? DbStatsProviders.forConnection(connection) : providerOverride;
ProviderCapabilities capability = provider.capabilities(connection);
settings.withValue("provider", provider.getName()).withValue("statsAccess", capability.statsAccess()).withValue("statsAccessMessage", capability.message());
if(capability.supported())
{
DatabaseStats database = provider.readDatabase(connection);
boolean databaseReset = previous != null && database.statsReset() != null && !Objects.equals(database.statsReset(), previous.getValueInstant("rawStatsReset"));
if(previous != null)
{
for(Map.Entry<String, Long> entry : database.counters().entrySet())
{
String raw = "raw" + Character.toUpperCase(entry.getKey().charAt(0)) + entry.getKey().substring(1);
if(previous.getValueLong(raw) != null && entry.getValue() < previous.getValueLong(raw))
{
databaseReset = true;
}
}
}
for(Map.Entry<String, Long> entry : database.counters().entrySet())
{
String raw = "raw" + Character.toUpperCase(entry.getKey().charAt(0)) + entry.getKey().substring(1);
Long before = previous == null ? null : previous.getValueLong(raw);
snapshot.withValue(entry.getKey(), DeltaCalculator.counter(before, entry.getValue(), databaseReset)).withValue(raw, entry.getValue());
if(before != null && entry.getValue() < before)
{
databaseReset = true;
}
}
snapshot.withValue("rawStatsReset", database.statsReset()).withValue("statsResetDetected", databaseReset)
.withValue("databaseSizeBytes", database.sizeBytes()).withValue("maxConnections", database.maxConnections())
.withValue("cacheHitRatio", ratio(number(snapshot, "blksHit"), number(snapshot, "blksHit") + number(snapshot, "blksRead")));
if(capability.statements())
{
Instant reset = provider.readStatementResetInfo(connection).statsReset();
snapshot.setValue("rawStatementStatsReset", reset);
if(reset != null && previousCapture != null && reset.isAfter(previousCapture))
{
snapshot.setValue("statsResetDetected", true);
}
collectStatements(backendName, provider.readStatements(connection), previousCapture, reset, snapshot, settings);
}
List<ConnectionCount> connections = provider.readConnections(connection);
collectConnections(connections, snapshot);
collectSessions(provider.readSessions(connection), snapshot, settings, backendName);
Instant lastTables = settings.getValueInstant("lastTableSnapshotAt");
if(forceTables || lastTables == null || !lastTables.plusSeconds(60L * positive(settings, "tableSnapshotMinutes", 60)).isAfter(now))
{
collectTables(backendName, provider.readTables(connection), provider.readIndexes(connection), snapshot, databaseReset);
settings.setValue("lastTableSnapshotAt", now);
snapshot.setValue("includesTables", true);
}
}
snapshot.withValue("status", "FULL".equals(capability.statsAccess()) ? "OK" : "PARTIAL")
.withValue("errorMessage", "FULL".equals(capability.statsAccess()) ? null : capability.message());
QRecord runtimeSettings = new QRecord().withValue("id", settings.getValueInteger("id")).withValue("lastSnapshotAt", now)
.withValue("provider", settings.getValueString("provider")).withValue("statsAccess", settings.getValueString("statsAccess"))
.withValue("statsAccessMessage", settings.getValueString("statsAccessMessage"));
if(Boolean.TRUE.equals(snapshot.getValueBoolean("includesTables")))
{
runtimeSettings.setValue("lastTableSnapshotAt", now);
}
DbPerfStore.update("db_perf_settings", List.of(runtimeSettings));
}
}
/*******************************************************************************
** Upsert all statement baselines, but retain only the top N interval rows.
** The denominator and total calls include every observed statement delta.
*******************************************************************************/
private void collectStatements(String backend, List<StatementStats> current, Instant previousCapture, Instant reset, QRecord snapshot, QRecord settings) throws Exception
{
Map<String, QRecord> previous = new HashMap<>();
for(QRecord row : DbPerfStore.query("db_perf_statement", DbPerfStore.eq("backendName", backend)))
{
previous.put(statementKey(row.getValueLong("queryId"), row.getValueString("dbUser"), Boolean.TRUE.equals(row.getValueBoolean("toplevel"))), row);
}
List<QRecord> inserts = new ArrayList<>();
List<QRecord> updates = new ArrayList<>();
List<QRecord> deltas = new ArrayList<>();
Map<QRecord, String> keys = new LinkedHashMap<>();
BigDecimal total = BigDecimal.ZERO;
long calls = 0;
Instant now = snapshot.getValueInstant("capturedAt");
for(StatementStats entry : current)
{
String key = statementKey(entry.queryId(), entry.dbUser(), entry.toplevel());
QRecord old = previous.get(key);
DeltaCalculator.Delta delta = DeltaCalculator.calculate(old == null ? null : storedStatement(old), entry, previousCapture, reset);
String text = SqlNormalizer.normalize(entry.queryText());
QRecord row = new QRecord().withValue("backendName", backend).withValue("queryId", entry.queryId()).withValue("dbUser", entry.dbUser()).withValue("toplevel", entry.toplevel())
.withValue("queryText", text).withValue("fingerprint", SqlNormalizer.fingerprint(text)).withValue("operation", SqlNormalizer.operation(text)).withValue("mainTable", SqlNormalizer.mainTable(text))
.withValue("lastSeenAt", now).withValue("lastCalls", entry.calls()).withValue("lastTotalExecMs", entry.totalExecMs()).withValue("lastRows", entry.rows())
.withValue("lastSharedBlksHit", entry.sharedBlksHit()).withValue("lastSharedBlksRead", entry.sharedBlksRead()).withValue("lastTempBlksWritten", entry.tempBlksWritten())
.withValue("lastMaxExecMs", entry.maxExecMs()).withValue("lastStatsSince", entry.statsSince());
if(old == null)
{
row.setValue("firstSeenAt", now);
inserts.add(row);
}
else
{
row.setValue("id", old.getValueInteger("id"));
updates.add(row);
}
if(delta.reset())
{
snapshot.setValue("statsResetDetected", true);
}
total = total.add(delta.totalMs());
calls += delta.calls();
if(!delta.baseline() && (delta.calls() > 0 || delta.totalMs().signum() > 0))
{
QRecord detail = new QRecord().withValue("snapshotId", snapshot.getValueInteger("id")).withValue("capturedAt", now)
.withValue("calls", delta.calls()).withValue("totalMs", delta.totalMs()).withValue("meanMs", divide(delta.totalMs(), delta.calls()))
.withValue("maxMs", entry.maxExecMs()).withValue("rowsCount", delta.rows()).withValue("sharedBlksHit", delta.sharedBlksHit())
.withValue("sharedBlksRead", delta.sharedBlksRead()).withValue("hitRatio", ratio(delta.sharedBlksHit(), delta.sharedBlksHit() + delta.sharedBlksRead()))
.withValue("tempBlksWritten", delta.tempBlksWritten());
deltas.add(detail);
keys.put(detail, key);
}
}
for(QRecord inserted : DbPerfStore.insert("db_perf_statement", inserts))
{
previous.put(statementKey(inserted.getValueLong("queryId"), inserted.getValueString("dbUser"), Boolean.TRUE.equals(inserted.getValueBoolean("toplevel"))), inserted);
}
DbPerfStore.update("db_perf_statement", updates);
deltas.sort(Comparator.comparing((QRecord row) -> row.getValueBigDecimal("totalMs")).reversed());
List<QRecord> top = deltas.subList(0, Math.min(deltas.size(), positive(settings, "topStatementsPerSnapshot", 200)));
for(QRecord row : top)
{
row.withValue("statementId", previous.get(keys.get(row)).getValueInteger("id"))
.withValue("shareOfDbTime", total.signum() == 0 ? BigDecimal.ZERO : row.getValueBigDecimal("totalMs").multiply(BigDecimal.valueOf(100)).divide(total, 4, RoundingMode.HALF_UP));
}
DbPerfStore.insert("db_perf_query_snapshot", top);
snapshot.withValue("statementCount", current.size()).withValue("statementCalls", calls).withValue("statementTotalMs", total).withValue("storedStatementCount", top.size());
refresh24hours(backend, previous, current, snapshot);
}
/*******************************************************************************
** Stored counters have the same shape as provider counters.
*******************************************************************************/
private StatementStats storedStatement(QRecord row)
{
return (new StatementStats(number(row, "queryId"), row.getValueString("dbUser"), Boolean.TRUE.equals(row.getValueBoolean("toplevel")), row.getValueString("queryText"), number(row, "lastCalls"), decimal(row, "lastTotalExecMs"), BigDecimal.ZERO, decimal(row, "lastMaxExecMs"), number(row, "lastRows"), number(row, "lastSharedBlksHit"), number(row, "lastSharedBlksRead"), number(row, "lastTempBlksWritten"), row.getValueInstant("lastStatsSince"), null));
}
/*******************************************************************************
** Recompute touched statements against the exact all-statement denominator.
*******************************************************************************/
private void refresh24hours(String backend, Map<String, QRecord> known, List<StatementStats> touched, QRecord snapshot) throws Exception
{
Instant since = snapshot.getValueInstant("capturedAt").minusSeconds(86400);
BigDecimal denominator = decimal(snapshot, "statementTotalMs");
for(QRecord row : DbPerfStore.query("db_perf_snapshot", DbPerfStore.eq("backendName", backend).withCriteria(new QFilterCriteria("capturedAt", QCriteriaOperator.GREATER_THAN_OR_EQUALS, since))
.withCriteria(new QFilterCriteria("status", QCriteriaOperator.IN, List.of("OK", "PARTIAL")))))
{
denominator = denominator.add(decimal(row, "statementTotalMs"));
}
Map<Integer, List<QRecord>> details = DbPerfStore.query("db_perf_query_snapshot", new QQueryFilter(new QFilterCriteria("capturedAt", QCriteriaOperator.GREATER_THAN_OR_EQUALS, since)))
.stream().collect(Collectors.groupingBy(row -> row.getValueInteger("statementId")));
//////////////////////////////////////////////////////////////////////////
// refresh every statement with activity in the window, plus any whose //
// stored 24h numbers are non-zero (so idle statements decay to zero) //
//////////////////////////////////////////////////////////////////////////
Set<Integer> ids = new LinkedHashSet<>(details.keySet());
for(StatementStats entry : touched)
{
ids.add(known.get(statementKey(entry.queryId(), entry.dbUser(), entry.toplevel())).getValueInteger("id"));
}
for(QRecord row : known.values())
{
if(row.getValueInteger("id") != null && number(row, "calls24h") != 0)
{
ids.add(row.getValueInteger("id"));
}
}
List<QRecord> updates = new ArrayList<>();
for(Integer id : ids)
{
List<QRecord> rows = details.getOrDefault(id, List.of());
long calls = rows.stream().mapToLong(row -> number(row, "calls")).sum();
BigDecimal total = rows.stream().map(row -> decimal(row, "totalMs")).reduce(BigDecimal.ZERO, BigDecimal::add);
updates.add(new QRecord().withValue("id", id).withValue("calls24h", calls).withValue("totalMs24h", total).withValue("meanMs24h", divide(total, calls))
.withValue("shareOfDbTime24h", denominator.signum() == 0 ? BigDecimal.ZERO : total.multiply(BigDecimal.valueOf(100)).divide(denominator, 4, RoundingMode.HALF_UP)));
}
DbPerfStore.update("db_perf_statement", updates);
}
/*******************************************************************************
** Upsert table/index gauges; snapshot scan/write/maintenance counters as deltas.
*******************************************************************************/
private void collectTables(String backend, List<TableStats> tables, List<IndexStats> indexes, QRecord snapshot, boolean reset) throws Exception
{
Map<String, QRecord> known = new HashMap<>();
for(QRecord row : DbPerfStore.query("db_perf_table_stat", DbPerfStore.eq("backendName", backend)))
{
known.put(row.getValueString("schemaName") + "." + row.getValueString("tableName"), row);
}
List<QRecord> inserts = new ArrayList<>();
List<QRecord> updates = new ArrayList<>();
List<QRecord> details = new ArrayList<>();
Map<QRecord, String> detailKeys = new LinkedHashMap<>();
Instant now = snapshot.getValueInstant("capturedAt");
for(TableStats entry : tables)
{
String key = entry.schemaName() + "." + entry.tableName();
QRecord old = known.get(key);
QRecord row = new QRecord().withValue("backendName", backend).withValue("schemaName", entry.schemaName()).withValue("tableName", entry.tableName()).withValue("relid", entry.relid()).withValue("lastSeenAt", now);
entry.counters().forEach(row::setValue);
entry.maintenance().forEach(row::setValue);
row.withValue("deadRatio", ratio(number(row, "nDeadTup"), number(row, "nLiveTup") + number(row, "nDeadTup")))
.withValue("seqScanShare", ratio(number(row, "seqScan"), number(row, "seqScan") + number(row, "idxScan")));
if(old == null)
{
inserts.add(row);
}
else
{
row.setValue("id", old.getValueInteger("id"));
updates.add(row);
}
if(old != null)
{
QRecord weekAgo = DbPerfStore.first("db_perf_table_snapshot", DbPerfStore.eq("tableStatId", old.getValueInteger("id"))
.withCriteria(new QFilterCriteria("capturedAt", QCriteriaOperator.LESS_THAN_OR_EQUALS, now.minusSeconds(7 * 86400L)))
.withOrderBy(new QFilterOrderBy("capturedAt", false)));
row.setValue("growthBytes7d", weekAgo == null ? null : number(row, "totalBytes") - number(weekAgo, "totalBytes"));
}
QRecord detail = new QRecord().withValue("snapshotId", snapshot.getValueInteger("id")).withValue("capturedAt", now);
boolean tableReset = reset || old != null && number(old, "relid") != entry.relid();
for(String counter : List.of("seqScan", "seqTupRead", "idxScan", "idxTupFetch", "nTupIns", "nTupUpd", "nTupDel", "nTupHotUpd", "autovacuumCount", "autoanalyzeCount"))
{
detail.setValue(counter, DeltaCalculator.counter(old == null ? null : old.getValueLong(counter), number(row, counter), tableReset));
}
for(String gauge : List.of("nLiveTup", "nDeadTup", "totalBytes", "tableBytes", "indexBytes", "freezeAge"))
{
detail.setValue(gauge, number(row, gauge));
}
details.add(detail);
detailKeys.put(detail, key);
}
for(QRecord inserted : DbPerfStore.insert("db_perf_table_stat", inserts))
{
known.put(inserted.getValueString("schemaName") + "." + inserted.getValueString("tableName"), inserted);
}
DbPerfStore.update("db_perf_table_stat", updates);
for(QRecord detail : details)
{
detail.setValue("tableStatId", known.get(detailKeys.get(detail)).getValueInteger("id"));
}
DbPerfStore.insert("db_perf_table_snapshot", details);
collectIndexes(backend, indexes, known, now);
}
/*******************************************************************************
** Indexes reference collected tables and are written in batches.
*******************************************************************************/
private void collectIndexes(String backend, List<IndexStats> entries, Map<String, QRecord> tables, Instant now) throws Exception
{
Map<String, QRecord> old = new HashMap<>();
for(QRecord row : DbPerfStore.query("db_perf_index_stat", DbPerfStore.eq("backendName", backend)))
{
old.put(row.getValueString("schemaName") + "." + row.getValueString("indexName"), row);
}
List<QRecord> inserts = new ArrayList<>();
List<QRecord> updates = new ArrayList<>();
for(IndexStats entry : entries)
{
QRecord table = tables.get(entry.schemaName() + "." + entry.tableName());
if(table == null)
{
continue;
}
QRecord row = new QRecord().withValue("backendName", backend).withValue("tableStatId", table.getValueInteger("id"))
.withValue("schemaName", entry.schemaName()).withValue("tableName", entry.tableName()).withValue("indexName", entry.indexName()).withValue("relid", entry.relid())
.withValue("definition", truncate(entry.definition(), 2000)).withValue("columns", truncate(entry.columns(), 1000)).withValue("isUnique", entry.unique()).withValue("isPrimary", entry.primary()).withValue("isValid", entry.valid())
.withValue("idxScan", entry.idxScan()).withValue("idxTupRead", entry.idxTupRead()).withValue("idxTupFetch", entry.idxTupFetch()).withValue("sizeBytes", entry.sizeBytes()).withValue("writeCost", entry.writeCost())
.withValue("scansSinceReset", entry.idxScan()).withValue("lastSeenAt", now);
QRecord previous = old.get(entry.schemaName() + "." + entry.indexName());
if(previous == null)
{
inserts.add(row);
}
else
{
row.setValue("id", previous.getValueInteger("id"));
updates.add(row);
}
}
DbPerfStore.insert("db_perf_index_stat", inserts);
DbPerfStore.update("db_perf_index_stat", updates);
}
/*******************************************************************************
** All-database connection counts for capacity comparison.
*******************************************************************************/
private void collectConnections(List<ConnectionCount> connections, QRecord snapshot) throws Exception
{
List<QRecord> rows = new ArrayList<>();
int total = 0;
int active = 0;
int idle = 0;
int idleTx = 0;
for(ConnectionCount entry : connections)
{
total += entry.connectionCount();
active += "active".equals(entry.state()) ? entry.connectionCount() : 0;
idle += "idle".equals(entry.state()) ? entry.connectionCount() : 0;
idleTx += entry.state() != null && entry.state().startsWith("idle in transaction") ? entry.connectionCount() : 0;
rows.add(new QRecord().withValue("snapshotId", snapshot.getValueInteger("id")).withValue("capturedAt", snapshot.getValueInstant("capturedAt"))
.withValue("dbUser", entry.dbUser()).withValue("applicationName", truncate(entry.applicationName(), 200)).withValue("state", entry.state()).withValue("connectionCount", entry.connectionCount()));
}
DbPerfStore.insert("db_perf_connection_sample", rows);
snapshot.withValue("connectionsTotal", total).withValue("connectionsActive", active).withValue("connectionsIdle", idle).withValue("connectionsIdleInTx", idleTx);
}
/*******************************************************************************
** Persist only notable sessions; normalize before truncating or hashing.
*******************************************************************************/
private void collectSessions(List<SessionInfo> sessions, QRecord snapshot, QRecord settings, String backend) throws Exception
{
Map<Integer, Integer> blocking = new HashMap<>();
for(SessionInfo session : sessions)
{
session.blockedBy().forEach(pid -> blocking.merge(pid, 1, Integer::sum));
}
Map<String, QRecord> statements = new HashMap<>();
for(QRecord row : DbPerfStore.query("db_perf_statement", DbPerfStore.eq("backendName", backend)))
{
if(Boolean.TRUE.equals(row.getValueBoolean("toplevel")))
{
statements.put(row.getValueString("queryId") + "/" + row.getValueString("dbUser"), row);
}
}
List<QRecord> rows = new ArrayList<>();
for(SessionInfo entry : sessions)
{
String reason = !entry.blockedBy().isEmpty() ? "BLOCKED" : blocking.containsKey(entry.pid()) ? "BLOCKING"
: entry.state() != null && entry.state().startsWith("idle in transaction") && entry.stateChange() != null && Duration.between(entry.stateChange(), snapshot.getValueInstant("capturedAt")).getSeconds() >= positive(settings, "idleInTransactionSeconds", 60) ? "IDLE_IN_TRANSACTION"
: "active".equals(entry.state()) && entry.querySeconds() != null && entry.querySeconds().doubleValue() >= positive(settings, "activityLongRunningSeconds", 30) ? "LONG_RUNNING" : null;
if(reason == null)
{
continue;
}
QRecord statement = statements.get(entry.queryId() + "/" + entry.dbUser());
String normalized = SqlNormalizer.normalize(statement == null ? entry.queryText() : statement.getValueString("queryText"));
rows.add(new QRecord().withValue("snapshotId", snapshot.getValueInteger("id")).withValue("capturedAt", snapshot.getValueInstant("capturedAt"))
.withValue("pid", entry.pid()).withValue("dbUser", entry.dbUser()).withValue("applicationName", truncate(entry.applicationName(), 200)).withValue("clientAddr", entry.clientAddr())
.withValue("state", entry.state()).withValue("waitEventType", entry.waitEventType()).withValue("waitEvent", entry.waitEvent()).withValue("backendType", entry.backendType())
.withValue("xactStart", entry.xactStart()).withValue("queryStart", entry.queryStart()).withValue("stateChange", entry.stateChange()).withValue("querySeconds", entry.querySeconds()).withValue("xactSeconds", entry.xactSeconds())
.withValue("blockedBy", truncate(entry.blockedBy().stream().map(String::valueOf).collect(Collectors.joining(",")), 500)).withValue("blockingCount", blocking.getOrDefault(entry.pid(), 0))
.withValue("reason", reason).withValue("queryId", entry.queryId()).withValue("statementId", statement == null ? null : statement.getValueInteger("id"))
.withValue("queryFingerprint", SqlNormalizer.fingerprint(normalized)).withValue("queryTextNormalized", truncate(normalized, 4000)));
}
DbPerfStore.insert("db_perf_activity_sample", rows);
}
/*******************************************************************************
** Stable statement identity including role and nesting level.
*******************************************************************************/
private static String statementKey(long queryId, String user, boolean top)
{
return (queryId + "/" + top + "/" + user);
}
/*******************************************************************************
** Null counters are zero.
*******************************************************************************/
public static long number(QRecord row, String field)
{
Long value = row.getValueLong(field);
return (value == null ? 0 : value);
}
/*******************************************************************************
** Null decimal counters are zero.
*******************************************************************************/
public static BigDecimal decimal(QRecord row, String field)
{
BigDecimal value = row.getValueBigDecimal(field);
return (value == null ? BigDecimal.ZERO : value);
}
/*******************************************************************************
** Percentage, not a fractional ratio; matches seeded rule thresholds.
*******************************************************************************/
public static BigDecimal ratio(long numerator, long denominator)
{
return (denominator == 0 ? BigDecimal.ZERO : BigDecimal.valueOf(numerator).multiply(BigDecimal.valueOf(100)).divide(BigDecimal.valueOf(denominator), 4, RoundingMode.HALF_UP));
}
/*******************************************************************************
** Weighted mean with a zero-call guard.
*******************************************************************************/
public static BigDecimal divide(BigDecimal total, long count)
{
return (count == 0 ? BigDecimal.ZERO : total.divide(BigDecimal.valueOf(count), 3, RoundingMode.HALF_UP));
}
/*******************************************************************************
** Invalid persisted intervals fall back to safe positive defaults.
*******************************************************************************/
public static int positive(QRecord settings, String field, int fallback)
{
Integer value = settings.getValueInteger(field);
return (value == null || value < 1 ? fallback : value);
}
/*******************************************************************************
** Bound display strings after normalization where applicable.
*******************************************************************************/
private static String truncate(String value, int length)
{
return (value == null ? null : value.substring(0, Math.min(length, value.length())));
}
}
@@ -1,731 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.metadata;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qbits.dbperformance.DbPerfPermissions;
import com.kingsrook.qbits.dbperformance.DbPerfQBitConfig;
import com.kingsrook.qbits.dbperformance.model.DbPerfActivitySample;
import com.kingsrook.qbits.dbperformance.model.DbPerfAttribution;
import com.kingsrook.qbits.dbperformance.model.DbPerfAttributionDaily;
import com.kingsrook.qbits.dbperformance.model.DbPerfConnectionSample;
import com.kingsrook.qbits.dbperformance.model.DbPerfExplain;
import com.kingsrook.qbits.dbperformance.model.DbPerfFinding;
import com.kingsrook.qbits.dbperformance.model.DbPerfIndexStat;
import com.kingsrook.qbits.dbperformance.model.DbPerfQueryDaily;
import com.kingsrook.qbits.dbperformance.model.DbPerfQuerySnapshot;
import com.kingsrook.qbits.dbperformance.model.DbPerfRepeatPattern;
import com.kingsrook.qbits.dbperformance.model.DbPerfRule;
import com.kingsrook.qbits.dbperformance.model.DbPerfSettings;
import com.kingsrook.qbits.dbperformance.model.DbPerfSnapshot;
import com.kingsrook.qbits.dbperformance.model.DbPerfStatement;
import com.kingsrook.qbits.dbperformance.model.DbPerfTableDaily;
import com.kingsrook.qbits.dbperformance.model.DbPerfTableSnapshot;
import com.kingsrook.qbits.dbperformance.model.DbPerfTableStat;
import com.kingsrook.qqq.backend.core.actions.ai.AiExposure;
import com.kingsrook.qqq.backend.core.actions.ai.AiTableMetaData;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ChildRecordListRenderer;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
/*******************************************************************************
** QQQ tables, exact permissions, sections and parent-child record lists.
*******************************************************************************/
public final class DbPerfMetaData
{
public static final String CHANGELOG = "db/liquibase/qbit-db-performance/db_performance.changelog.sql";
public static final List<String> TABLES = List.of("db_perf_settings", "db_perf_rule", "db_perf_snapshot", "db_perf_statement", "db_perf_query_snapshot", "db_perf_query_daily", "db_perf_table_stat", "db_perf_table_snapshot", "db_perf_table_daily", "db_perf_index_stat", "db_perf_activity_sample", "db_perf_connection_sample", "db_perf_attribution", "db_perf_attribution_daily", "db_perf_repeat_pattern", "db_perf_finding", "db_perf_explain");
/*******************************************************************************
** Utility class.
*******************************************************************************/
private DbPerfMetaData()
{
}
/*******************************************************************************
** Register the complete Phase A metadata.
*******************************************************************************/
public static void addTo(QInstance instance, DbPerfQBitConfig config) throws QException
{
QTableMetaData settings = base(config, DbPerfSettings.TABLE_NAME, "Settings", DbPerfSettings.class);
settings.withRecordLabelFormat("%s").withRecordLabelFields("backendName");
settings.withUniqueKey(new UniqueKey("backendName"));
settings.getField("id").setBackendName("id");
settings.getField("backendName").setBackendName("backend_name");
settings.getField("enabled").setBackendName("enabled");
settings.getField("enabled").setDefaultValue(true);
settings.getField("provider").setBackendName("provider");
settings.getField("provider").setPossibleValueSourceName("db_perf_Provider");
settings.getField("snapshotIntervalMinutes").setBackendName("snapshot_interval_minutes");
settings.getField("snapshotIntervalMinutes").setDefaultValue(5);
settings.getField("tableSnapshotMinutes").setBackendName("table_snapshot_minutes");
settings.getField("tableSnapshotMinutes").setDefaultValue(60);
settings.getField("detailRetentionDays").setBackendName("detail_retention_days");
settings.getField("detailRetentionDays").setDefaultValue(30);
settings.getField("rollupRetentionDays").setBackendName("rollup_retention_days");
settings.getField("rollupRetentionDays").setDefaultValue(365);
settings.getField("topStatementsPerSnapshot").setBackendName("top_statements_per_snapshot");
settings.getField("topStatementsPerSnapshot").setDefaultValue(200);
settings.getField("activityLongRunningSeconds").setBackendName("activity_long_running_seconds");
settings.getField("activityLongRunningSeconds").setDefaultValue(30);
settings.getField("idleInTransactionSeconds").setBackendName("idle_in_transaction_seconds");
settings.getField("idleInTransactionSeconds").setDefaultValue(60);
settings.getField("attributionEnabled").setBackendName("attribution_enabled");
settings.getField("attributionEnabled").setDefaultValue(true);
settings.getField("attributionSampleRate").setBackendName("attribution_sample_rate");
settings.getField("attributionSampleRate").setDefaultValue(new java.math.BigDecimal("1.0"));
settings.getField("attributionFlushSeconds").setBackendName("attribution_flush_seconds");
settings.getField("attributionFlushSeconds").setDefaultValue(60);
settings.getField("attributionMaxKeys").setBackendName("attribution_max_keys");
settings.getField("attributionMaxKeys").setDefaultValue(10000);
settings.getField("nPlusOneThreshold").setBackendName("n_plus_one_threshold");
settings.getField("nPlusOneThreshold").setDefaultValue(10);
settings.getField("explainTimeoutMs").setBackendName("explain_timeout_ms");
settings.getField("explainTimeoutMs").setDefaultValue(10000);
settings.getField("alertsEnabled").setBackendName("alerts_enabled");
settings.getField("alertsEnabled").setDefaultValue(true);
settings.getField("spikeFactor").setBackendName("spike_factor");
settings.getField("spikeFactor").setDefaultValue(new java.math.BigDecimal("3"));
settings.getField("spikeMinTotalMs").setBackendName("spike_min_total_ms");
settings.getField("spikeMinTotalMs").setDefaultValue(5000);
settings.getField("connectionAlertPercent").setBackendName("connection_alert_percent");
settings.getField("connectionAlertPercent").setDefaultValue(80);
settings.getField("blockedAlertSeconds").setBackendName("blocked_alert_seconds");
settings.getField("blockedAlertSeconds").setDefaultValue(30);
settings.getField("lastSnapshotAt").setBackendName("last_snapshot_at");
settings.getField("lastTableSnapshotAt").setBackendName("last_table_snapshot_at");
settings.getField("statsAccess").setBackendName("stats_access");
settings.getField("statsAccess").setPossibleValueSourceName("db_perf_StatsAccess");
settings.getField("statsAccessMessage").setBackendName("stats_access_message");
settings.getField("createDate").setBackendName("create_date");
settings.getField("modifyDate").setBackendName("modify_date");
settings.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD));
settings.withSupplementalMetaData(new AiTableMetaData().withExposure(AiExposure.NONE));
settings.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_DELETE);
settings.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "backendName", "enabled", "provider", "snapshotIntervalMinutes", "tableSnapshotMinutes", "detailRetentionDays")));
settings.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("rollupRetentionDays", "topStatementsPerSnapshot", "activityLongRunningSeconds", "idleInTransactionSeconds", "attributionEnabled", "attributionSampleRate", "attributionFlushSeconds", "attributionMaxKeys", "nPlusOneThreshold", "explainTimeoutMs", "alertsEnabled", "spikeFactor", "spikeMinTotalMs", "connectionAlertPercent", "blockedAlertSeconds", "lastSnapshotAt", "lastTableSnapshotAt", "statsAccess", "statsAccessMessage")));
settings.withSection(new QFieldSection("system", new QIcon("table_rows"), Tier.T3, List.of("createDate", "modifyDate")));
instance.addTable(settings);
QTableMetaData rule = base(config, DbPerfRule.TABLE_NAME, "Suggestion Rules", DbPerfRule.class);
rule.withRecordLabelFormat("%s").withRecordLabelFields("label");
rule.withUniqueKey(new UniqueKey("code"));
rule.getField("id").setBackendName("id");
rule.getField("code").setBackendName("code");
rule.getField("label").setBackendName("label");
rule.getField("description").setBackendName("description");
rule.getField("enabled").setBackendName("enabled");
rule.getField("enabled").setDefaultValue(true);
rule.getField("severity").setBackendName("severity");
rule.getField("severity").setPossibleValueSourceName("db_perf_Severity");
rule.getField("threshold1").setBackendName("threshold_1");
rule.getField("threshold2").setBackendName("threshold_2");
rule.getField("threshold1Label").setBackendName("threshold_1_label");
rule.getField("threshold2Label").setBackendName("threshold_2_label");
rule.getField("createDate").setBackendName("create_date");
rule.getField("modifyDate").setBackendName("modify_date");
rule.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD));
rule.withSupplementalMetaData(new AiTableMetaData().withExposure(AiExposure.NONE));
rule.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "code", "label", "description", "enabled", "severity", "threshold1")));
rule.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("threshold2", "threshold1Label", "threshold2Label")));
rule.withSection(new QFieldSection("system", new QIcon("table_rows"), Tier.T3, List.of("createDate", "modifyDate")));
instance.addTable(rule);
QTableMetaData snapshot = base(config, DbPerfSnapshot.TABLE_NAME, "Snapshots", DbPerfSnapshot.class);
snapshot.withRecordLabelFormat("%s").withRecordLabelFields("capturedAt");
snapshot.withUniqueKey(new UniqueKey("backendName", "capturedAt"));
snapshot.getField("id").setBackendName("id");
snapshot.getField("backendName").setBackendName("backend_name");
snapshot.getField("capturedAt").setBackendName("captured_at");
snapshot.getField("intervalSeconds").setBackendName("interval_seconds");
snapshot.getField("status").setBackendName("status");
snapshot.getField("status").setPossibleValueSourceName("db_perf_SnapshotStatus");
snapshot.getField("durationMs").setBackendName("duration_ms");
snapshot.getField("errorMessage").setBackendName("error_message");
snapshot.getField("statsResetDetected").setBackendName("stats_reset_detected");
snapshot.getField("includesTables").setBackendName("includes_tables");
snapshot.getField("xactCommit").setBackendName("xact_commit");
snapshot.getField("xactRollback").setBackendName("xact_rollback");
snapshot.getField("blksHit").setBackendName("blks_hit");
snapshot.getField("blksRead").setBackendName("blks_read");
snapshot.getField("cacheHitRatio").setBackendName("cache_hit_ratio");
snapshot.getField("tempFiles").setBackendName("temp_files");
snapshot.getField("tempBytes").setBackendName("temp_bytes");
snapshot.getField("deadlocks").setBackendName("deadlocks");
snapshot.getField("conflicts").setBackendName("conflicts");
snapshot.getField("connectionsTotal").setBackendName("connections_total");
snapshot.getField("connectionsActive").setBackendName("connections_active");
snapshot.getField("connectionsIdle").setBackendName("connections_idle");
snapshot.getField("connectionsIdleInTx").setBackendName("connections_idle_in_tx");
snapshot.getField("maxConnections").setBackendName("max_connections");
snapshot.getField("databaseSizeBytes").setBackendName("database_size_bytes");
snapshot.getField("statementCount").setBackendName("statement_count");
snapshot.getField("statementCalls").setBackendName("statement_calls");
snapshot.getField("statementTotalMs").setBackendName("statement_total_ms");
snapshot.getField("storedStatementCount").setBackendName("stored_statement_count");
snapshot.getField("createDate").setBackendName("create_date");
snapshot.getField("rawXactCommit").setBackendName("raw_xact_commit");
snapshot.getField("rawXactRollback").setBackendName("raw_xact_rollback");
snapshot.getField("rawBlksHit").setBackendName("raw_blks_hit");
snapshot.getField("rawBlksRead").setBackendName("raw_blks_read");
snapshot.getField("rawTempFiles").setBackendName("raw_temp_files");
snapshot.getField("rawTempBytes").setBackendName("raw_temp_bytes");
snapshot.getField("rawDeadlocks").setBackendName("raw_deadlocks");
snapshot.getField("rawConflicts").setBackendName("raw_conflicts");
snapshot.getField("rawStatsReset").setBackendName("raw_stats_reset");
snapshot.getField("rawStatementStatsReset").setBackendName("raw_statement_stats_reset");
snapshot.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
snapshot.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "backendName", "capturedAt", "intervalSeconds", "status", "durationMs", "errorMessage")));
snapshot.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("statsResetDetected", "includesTables", "xactCommit", "xactRollback", "blksHit", "blksRead", "cacheHitRatio", "tempFiles", "tempBytes", "deadlocks", "conflicts", "connectionsTotal", "connectionsActive", "connectionsIdle", "connectionsIdleInTx", "maxConnections", "databaseSizeBytes", "statementCount", "statementCalls", "statementTotalMs", "storedStatementCount")));
snapshot.withSection(new QFieldSection("system", new QIcon("table_rows"), Tier.T3, List.of("createDate", "rawXactCommit", "rawXactRollback", "rawBlksHit", "rawBlksRead", "rawTempFiles", "rawTempBytes", "rawDeadlocks", "rawConflicts", "rawStatsReset", "rawStatementStatsReset")));
instance.addTable(snapshot);
QTableMetaData statement = base(config, DbPerfStatement.TABLE_NAME, "Queries", DbPerfStatement.class);
statement.withRecordLabelFormat("%s").withRecordLabelFields("queryText");
statement.withUniqueKey(new UniqueKey("backendName", "queryId", "dbUser", "toplevel"));
statement.getField("id").setBackendName("id");
statement.getField("backendName").setBackendName("backend_name");
statement.getField("queryId").setBackendName("query_id");
statement.getField("dbUser").setBackendName("db_user");
statement.getField("toplevel").setBackendName("toplevel");
statement.getField("fingerprint").setBackendName("fingerprint");
statement.getField("queryText").setBackendName("query_text");
statement.getField("queryText").setType(QFieldType.TEXT);
statement.getField("operation").setBackendName("operation");
statement.getField("operation").setPossibleValueSourceName("db_perf_Operation");
statement.getField("mainTable").setBackendName("main_table");
statement.getField("firstSeenAt").setBackendName("first_seen_at");
statement.getField("lastSeenAt").setBackendName("last_seen_at");
statement.getField("lastCalls").setBackendName("last_calls");
statement.getField("lastTotalExecMs").setBackendName("last_total_exec_ms");
statement.getField("lastRows").setBackendName("last_rows");
statement.getField("lastSharedBlksHit").setBackendName("last_shared_blks_hit");
statement.getField("lastSharedBlksRead").setBackendName("last_shared_blks_read");
statement.getField("lastTempBlksWritten").setBackendName("last_temp_blks_written");
statement.getField("lastMaxExecMs").setBackendName("last_max_exec_ms");
statement.getField("lastStatsSince").setBackendName("last_stats_since");
statement.getField("calls24h").setBackendName("calls_24h");
statement.getField("totalMs24h").setBackendName("total_ms_24h");
statement.getField("meanMs24h").setBackendName("mean_ms_24h");
statement.getField("shareOfDbTime24h").setBackendName("share_of_db_time_24h");
statement.getField("createDate").setBackendName("create_date");
statement.getField("modifyDate").setBackendName("modify_date");
statement.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
statement.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "backendName", "queryId", "dbUser", "toplevel", "fingerprint", "queryText")));
statement.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("operation", "mainTable", "firstSeenAt", "lastSeenAt", "lastCalls", "lastTotalExecMs", "lastRows", "lastSharedBlksHit", "lastSharedBlksRead", "lastTempBlksWritten", "lastMaxExecMs", "lastStatsSince", "calls24h", "totalMs24h", "meanMs24h", "shareOfDbTime24h")));
statement.withSection(new QFieldSection("system", new QIcon("table_rows"), Tier.T3, List.of("createDate", "modifyDate")));
instance.addTable(statement);
QTableMetaData querySnapshot = base(config, DbPerfQuerySnapshot.TABLE_NAME, "Query Snapshots", DbPerfQuerySnapshot.class);
querySnapshot.withRecordLabelFormat("%s").withRecordLabelFields("id");
querySnapshot.getField("id").setBackendName("id");
querySnapshot.getField("snapshotId").setBackendName("snapshot_id");
querySnapshot.getField("snapshotId").setPossibleValueSourceName("db_perf_snapshot");
querySnapshot.getField("statementId").setBackendName("statement_id");
querySnapshot.getField("statementId").setPossibleValueSourceName("db_perf_statement");
querySnapshot.getField("capturedAt").setBackendName("captured_at");
querySnapshot.getField("calls").setBackendName("calls");
querySnapshot.getField("totalMs").setBackendName("total_ms");
querySnapshot.getField("meanMs").setBackendName("mean_ms");
querySnapshot.getField("maxMs").setBackendName("max_ms");
querySnapshot.getField("rowsCount").setBackendName("rows_count");
querySnapshot.getField("sharedBlksHit").setBackendName("shared_blks_hit");
querySnapshot.getField("sharedBlksRead").setBackendName("shared_blks_read");
querySnapshot.getField("hitRatio").setBackendName("hit_ratio");
querySnapshot.getField("tempBlksWritten").setBackendName("temp_blks_written");
querySnapshot.getField("shareOfDbTime").setBackendName("share_of_db_time");
querySnapshot.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
querySnapshot.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "snapshotId", "statementId", "capturedAt", "calls", "totalMs", "meanMs")));
querySnapshot.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("maxMs", "rowsCount", "sharedBlksHit", "sharedBlksRead", "hitRatio", "tempBlksWritten", "shareOfDbTime")));
instance.addTable(querySnapshot);
QTableMetaData queryDaily = base(config, DbPerfQueryDaily.TABLE_NAME, "Query Daily", DbPerfQueryDaily.class);
queryDaily.withRecordLabelFormat("%s").withRecordLabelFields("day");
queryDaily.withUniqueKey(new UniqueKey("statementId", "day"));
queryDaily.getField("id").setBackendName("id");
queryDaily.getField("statementId").setBackendName("statement_id");
queryDaily.getField("statementId").setPossibleValueSourceName("db_perf_statement");
queryDaily.getField("day").setBackendName("day");
queryDaily.getField("calls").setBackendName("calls");
queryDaily.getField("totalMs").setBackendName("total_ms");
queryDaily.getField("meanMs").setBackendName("mean_ms");
queryDaily.getField("maxMs").setBackendName("max_ms");
queryDaily.getField("rowsCount").setBackendName("rows_count");
queryDaily.getField("hitRatio").setBackendName("hit_ratio");
queryDaily.getField("shareOfDbTime").setBackendName("share_of_db_time");
queryDaily.getField("snapshots").setBackendName("snapshots");
queryDaily.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
queryDaily.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "statementId", "day", "calls", "totalMs", "meanMs", "maxMs")));
queryDaily.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("rowsCount", "hitRatio", "shareOfDbTime", "snapshots")));
instance.addTable(queryDaily);
QTableMetaData tableStat = base(config, DbPerfTableStat.TABLE_NAME, "Tables", DbPerfTableStat.class);
tableStat.withRecordLabelFormat("%s").withRecordLabelFields("tableName");
tableStat.withUniqueKey(new UniqueKey("backendName", "schemaName", "tableName"));
tableStat.getField("id").setBackendName("id");
tableStat.getField("backendName").setBackendName("backend_name");
tableStat.getField("schemaName").setBackendName("schema_name");
tableStat.getField("tableName").setBackendName("table_name");
tableStat.getField("relid").setBackendName("relid");
tableStat.getField("seqScan").setBackendName("seq_scan");
tableStat.getField("seqTupRead").setBackendName("seq_tup_read");
tableStat.getField("idxScan").setBackendName("idx_scan");
tableStat.getField("idxTupFetch").setBackendName("idx_tup_fetch");
tableStat.getField("nTupIns").setBackendName("n_tup_ins");
tableStat.getField("nTupUpd").setBackendName("n_tup_upd");
tableStat.getField("nTupDel").setBackendName("n_tup_del");
tableStat.getField("nTupHotUpd").setBackendName("n_tup_hot_upd");
tableStat.getField("nLiveTup").setBackendName("n_live_tup");
tableStat.getField("nDeadTup").setBackendName("n_dead_tup");
tableStat.getField("deadRatio").setBackendName("dead_ratio");
tableStat.getField("totalBytes").setBackendName("total_bytes");
tableStat.getField("tableBytes").setBackendName("table_bytes");
tableStat.getField("indexBytes").setBackendName("index_bytes");
tableStat.getField("toastBytes").setBackendName("toast_bytes");
tableStat.getField("growthBytes7d").setBackendName("growth_bytes_7d");
tableStat.getField("lastVacuum").setBackendName("last_vacuum");
tableStat.getField("lastAutovacuum").setBackendName("last_autovacuum");
tableStat.getField("lastAnalyze").setBackendName("last_analyze");
tableStat.getField("lastAutoanalyze").setBackendName("last_autoanalyze");
tableStat.getField("vacuumCount").setBackendName("vacuum_count");
tableStat.getField("autovacuumCount").setBackendName("autovacuum_count");
tableStat.getField("analyzeCount").setBackendName("analyze_count");
tableStat.getField("autoanalyzeCount").setBackendName("autoanalyze_count");
tableStat.getField("freezeAge").setBackendName("freeze_age");
tableStat.getField("seqScanShare").setBackendName("seq_scan_share");
tableStat.getField("lastSeenAt").setBackendName("last_seen_at");
tableStat.getField("createDate").setBackendName("create_date");
tableStat.getField("modifyDate").setBackendName("modify_date");
tableStat.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
tableStat.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "backendName", "schemaName", "tableName", "relid", "seqScan", "seqTupRead")));
tableStat.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("idxScan", "idxTupFetch", "nTupIns", "nTupUpd", "nTupDel", "nTupHotUpd", "nLiveTup", "nDeadTup", "deadRatio", "totalBytes", "tableBytes", "indexBytes", "toastBytes", "growthBytes7d", "lastVacuum", "lastAutovacuum", "lastAnalyze", "lastAutoanalyze", "vacuumCount", "autovacuumCount", "analyzeCount", "autoanalyzeCount", "freezeAge", "seqScanShare", "lastSeenAt")));
tableStat.withSection(new QFieldSection("system", new QIcon("table_rows"), Tier.T3, List.of("createDate", "modifyDate")));
instance.addTable(tableStat);
QTableMetaData tableSnapshot = base(config, DbPerfTableSnapshot.TABLE_NAME, "Table Snapshots", DbPerfTableSnapshot.class);
tableSnapshot.withRecordLabelFormat("%s").withRecordLabelFields("id");
tableSnapshot.getField("id").setBackendName("id");
tableSnapshot.getField("snapshotId").setBackendName("snapshot_id");
tableSnapshot.getField("snapshotId").setPossibleValueSourceName("db_perf_snapshot");
tableSnapshot.getField("tableStatId").setBackendName("table_stat_id");
tableSnapshot.getField("tableStatId").setPossibleValueSourceName("db_perf_table_stat");
tableSnapshot.getField("capturedAt").setBackendName("captured_at");
tableSnapshot.getField("seqScan").setBackendName("seq_scan");
tableSnapshot.getField("seqTupRead").setBackendName("seq_tup_read");
tableSnapshot.getField("idxScan").setBackendName("idx_scan");
tableSnapshot.getField("idxTupFetch").setBackendName("idx_tup_fetch");
tableSnapshot.getField("nTupIns").setBackendName("n_tup_ins");
tableSnapshot.getField("nTupUpd").setBackendName("n_tup_upd");
tableSnapshot.getField("nTupDel").setBackendName("n_tup_del");
tableSnapshot.getField("nTupHotUpd").setBackendName("n_tup_hot_upd");
tableSnapshot.getField("nLiveTup").setBackendName("n_live_tup");
tableSnapshot.getField("nDeadTup").setBackendName("n_dead_tup");
tableSnapshot.getField("totalBytes").setBackendName("total_bytes");
tableSnapshot.getField("tableBytes").setBackendName("table_bytes");
tableSnapshot.getField("indexBytes").setBackendName("index_bytes");
tableSnapshot.getField("autovacuumCount").setBackendName("autovacuum_count");
tableSnapshot.getField("autoanalyzeCount").setBackendName("autoanalyze_count");
tableSnapshot.getField("freezeAge").setBackendName("freeze_age");
tableSnapshot.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
tableSnapshot.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "snapshotId", "tableStatId", "capturedAt", "seqScan", "seqTupRead", "idxScan")));
tableSnapshot.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("idxTupFetch", "nTupIns", "nTupUpd", "nTupDel", "nTupHotUpd", "nLiveTup", "nDeadTup", "totalBytes", "tableBytes", "indexBytes", "autovacuumCount", "autoanalyzeCount", "freezeAge")));
instance.addTable(tableSnapshot);
QTableMetaData tableDaily = base(config, DbPerfTableDaily.TABLE_NAME, "Table Daily", DbPerfTableDaily.class);
tableDaily.withRecordLabelFormat("%s").withRecordLabelFields("day");
tableDaily.withUniqueKey(new UniqueKey("tableStatId", "day"));
tableDaily.getField("id").setBackendName("id");
tableDaily.getField("tableStatId").setBackendName("table_stat_id");
tableDaily.getField("tableStatId").setPossibleValueSourceName("db_perf_table_stat");
tableDaily.getField("day").setBackendName("day");
tableDaily.getField("seqScan").setBackendName("seq_scan");
tableDaily.getField("idxScan").setBackendName("idx_scan");
tableDaily.getField("seqTupRead").setBackendName("seq_tup_read");
tableDaily.getField("nTupIns").setBackendName("n_tup_ins");
tableDaily.getField("nTupUpd").setBackendName("n_tup_upd");
tableDaily.getField("nTupDel").setBackendName("n_tup_del");
tableDaily.getField("nLiveTup").setBackendName("n_live_tup");
tableDaily.getField("nDeadTup").setBackendName("n_dead_tup");
tableDaily.getField("totalBytes").setBackendName("total_bytes");
tableDaily.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
tableDaily.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "tableStatId", "day", "seqScan", "idxScan", "seqTupRead", "nTupIns")));
tableDaily.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("nTupUpd", "nTupDel", "nLiveTup", "nDeadTup", "totalBytes")));
instance.addTable(tableDaily);
QTableMetaData indexStat = base(config, DbPerfIndexStat.TABLE_NAME, "Indexes", DbPerfIndexStat.class);
indexStat.withRecordLabelFormat("%s").withRecordLabelFields("indexName");
indexStat.withUniqueKey(new UniqueKey("backendName", "schemaName", "indexName"));
indexStat.getField("id").setBackendName("id");
indexStat.getField("backendName").setBackendName("backend_name");
indexStat.getField("tableStatId").setBackendName("table_stat_id");
indexStat.getField("tableStatId").setPossibleValueSourceName("db_perf_table_stat");
indexStat.getField("schemaName").setBackendName("schema_name");
indexStat.getField("tableName").setBackendName("table_name");
indexStat.getField("indexName").setBackendName("index_name");
indexStat.getField("relid").setBackendName("relid");
indexStat.getField("definition").setBackendName("definition");
indexStat.getField("columns").setBackendName("columns");
indexStat.getField("isUnique").setBackendName("is_unique");
indexStat.getField("isPrimary").setBackendName("is_primary");
indexStat.getField("isValid").setBackendName("is_valid");
indexStat.getField("idxScan").setBackendName("idx_scan");
indexStat.getField("idxTupRead").setBackendName("idx_tup_read");
indexStat.getField("idxTupFetch").setBackendName("idx_tup_fetch");
indexStat.getField("sizeBytes").setBackendName("size_bytes");
indexStat.getField("writeCost").setBackendName("write_cost");
indexStat.getField("scansSinceReset").setBackendName("scans_since_reset");
indexStat.getField("lastSeenAt").setBackendName("last_seen_at");
indexStat.getField("createDate").setBackendName("create_date");
indexStat.getField("modifyDate").setBackendName("modify_date");
indexStat.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
indexStat.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "backendName", "tableStatId", "schemaName", "tableName", "indexName", "relid")));
indexStat.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("definition", "columns", "isUnique", "isPrimary", "isValid", "idxScan", "idxTupRead", "idxTupFetch", "sizeBytes", "writeCost", "scansSinceReset", "lastSeenAt")));
indexStat.withSection(new QFieldSection("system", new QIcon("table_rows"), Tier.T3, List.of("createDate", "modifyDate")));
instance.addTable(indexStat);
QTableMetaData activitySample = base(config, DbPerfActivitySample.TABLE_NAME, "Activity Samples", DbPerfActivitySample.class);
activitySample.withRecordLabelFormat("%s").withRecordLabelFields("id");
activitySample.getField("id").setBackendName("id");
activitySample.getField("snapshotId").setBackendName("snapshot_id");
activitySample.getField("snapshotId").setPossibleValueSourceName("db_perf_snapshot");
activitySample.getField("capturedAt").setBackendName("captured_at");
activitySample.getField("pid").setBackendName("pid");
activitySample.getField("dbUser").setBackendName("db_user");
activitySample.getField("applicationName").setBackendName("application_name");
activitySample.getField("clientAddr").setBackendName("client_addr");
activitySample.getField("state").setBackendName("state");
activitySample.getField("state").setPossibleValueSourceName("db_perf_State");
activitySample.getField("waitEventType").setBackendName("wait_event_type");
activitySample.getField("waitEvent").setBackendName("wait_event");
activitySample.getField("backendType").setBackendName("backend_type");
activitySample.getField("xactStart").setBackendName("xact_start");
activitySample.getField("queryStart").setBackendName("query_start");
activitySample.getField("stateChange").setBackendName("state_change");
activitySample.getField("querySeconds").setBackendName("query_seconds");
activitySample.getField("xactSeconds").setBackendName("xact_seconds");
activitySample.getField("blockedBy").setBackendName("blocked_by");
activitySample.getField("blockingCount").setBackendName("blocking_count");
activitySample.getField("reason").setBackendName("reason");
activitySample.getField("reason").setPossibleValueSourceName("db_perf_Reason");
activitySample.getField("queryId").setBackendName("query_id");
activitySample.getField("statementId").setBackendName("statement_id");
activitySample.getField("statementId").setPossibleValueSourceName("db_perf_statement");
activitySample.getField("queryFingerprint").setBackendName("query_fingerprint");
activitySample.getField("queryTextNormalized").setBackendName("query_text_normalized");
activitySample.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
activitySample.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "snapshotId", "capturedAt", "pid", "dbUser", "applicationName", "clientAddr")));
activitySample.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("state", "waitEventType", "waitEvent", "backendType", "xactStart", "queryStart", "stateChange", "querySeconds", "xactSeconds", "blockedBy", "blockingCount", "reason", "queryId", "statementId", "queryFingerprint", "queryTextNormalized")));
instance.addTable(activitySample);
QTableMetaData connectionSample = base(config, DbPerfConnectionSample.TABLE_NAME, "Connection Samples", DbPerfConnectionSample.class);
connectionSample.withRecordLabelFormat("%s").withRecordLabelFields("id");
connectionSample.getField("id").setBackendName("id");
connectionSample.getField("snapshotId").setBackendName("snapshot_id");
connectionSample.getField("snapshotId").setPossibleValueSourceName("db_perf_snapshot");
connectionSample.getField("capturedAt").setBackendName("captured_at");
connectionSample.getField("dbUser").setBackendName("db_user");
connectionSample.getField("applicationName").setBackendName("application_name");
connectionSample.getField("state").setBackendName("state");
connectionSample.getField("state").setPossibleValueSourceName("db_perf_State");
connectionSample.getField("connectionCount").setBackendName("connection_count");
connectionSample.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
connectionSample.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "snapshotId", "capturedAt", "dbUser", "applicationName", "state", "connectionCount")));
instance.addTable(connectionSample);
QTableMetaData attribution = base(config, DbPerfAttribution.TABLE_NAME, "QQQ Attribution", DbPerfAttribution.class);
attribution.withRecordLabelFormat("%s").withRecordLabelFields("id");
attribution.getField("id").setBackendName("id");
attribution.getField("windowStart").setBackendName("window_start");
attribution.getField("windowEnd").setBackendName("window_end");
attribution.getField("podName").setBackendName("pod_name");
attribution.getField("unitKind").setBackendName("unit_kind");
attribution.getField("unitKind").setPossibleValueSourceName("db_perf_UnitKind");
attribution.getField("unitName").setBackendName("unit_name");
attribution.getField("processName").setBackendName("process_name");
attribution.getField("tableName").setBackendName("table_name");
attribution.getField("operation").setBackendName("operation");
attribution.getField("operation").setPossibleValueSourceName("db_perf_Operation");
attribution.getField("fingerprint").setBackendName("fingerprint");
attribution.getField("sqlText").setBackendName("sql_text");
attribution.getField("units").setBackendName("units");
attribution.getField("statements").setBackendName("statements");
attribution.getField("totalMs").setBackendName("total_ms");
attribution.getField("maxMs").setBackendName("max_ms");
attribution.getField("rowsCount").setBackendName("rows_count");
attribution.getField("errors").setBackendName("errors");
attribution.getField("sampleRate").setBackendName("sample_rate");
attribution.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
attribution.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "windowStart", "windowEnd", "podName", "unitKind", "unitName", "processName")));
attribution.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("tableName", "operation", "fingerprint", "sqlText", "units", "statements", "totalMs", "maxMs", "rowsCount", "errors", "sampleRate")));
instance.addTable(attribution);
QTableMetaData attributionDaily = base(config, DbPerfAttributionDaily.TABLE_NAME, "QQQ Attribution Daily", DbPerfAttributionDaily.class);
attributionDaily.withRecordLabelFormat("%s").withRecordLabelFields("day");
attributionDaily.withUniqueKey(new UniqueKey("day", "rollupKey"));
attributionDaily.getField("id").setBackendName("id");
attributionDaily.getField("day").setBackendName("day");
attributionDaily.getField("rollupKey").setBackendName("rollup_key");
attributionDaily.getField("unitKind").setBackendName("unit_kind");
attributionDaily.getField("unitKind").setPossibleValueSourceName("db_perf_UnitKind");
attributionDaily.getField("unitName").setBackendName("unit_name");
attributionDaily.getField("processName").setBackendName("process_name");
attributionDaily.getField("tableName").setBackendName("table_name");
attributionDaily.getField("operation").setBackendName("operation");
attributionDaily.getField("operation").setPossibleValueSourceName("db_perf_Operation");
attributionDaily.getField("fingerprint").setBackendName("fingerprint");
attributionDaily.getField("sqlText").setBackendName("sql_text");
attributionDaily.getField("units").setBackendName("units");
attributionDaily.getField("statements").setBackendName("statements");
attributionDaily.getField("totalMs").setBackendName("total_ms");
attributionDaily.getField("maxMs").setBackendName("max_ms");
attributionDaily.getField("rowsCount").setBackendName("rows_count");
attributionDaily.getField("errors").setBackendName("errors");
attributionDaily.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
attributionDaily.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "day", "rollupKey", "unitKind", "unitName", "processName", "tableName")));
attributionDaily.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("operation", "fingerprint", "sqlText", "units", "statements", "totalMs", "maxMs", "rowsCount", "errors")));
instance.addTable(attributionDaily);
QTableMetaData repeatPattern = base(config, DbPerfRepeatPattern.TABLE_NAME, "Repeated Statements", DbPerfRepeatPattern.class);
repeatPattern.withRecordLabelFormat("%s").withRecordLabelFields("id");
repeatPattern.getField("id").setBackendName("id");
repeatPattern.getField("windowStart").setBackendName("window_start");
repeatPattern.getField("windowEnd").setBackendName("window_end");
repeatPattern.getField("podName").setBackendName("pod_name");
repeatPattern.getField("unitKind").setBackendName("unit_kind");
repeatPattern.getField("unitKind").setPossibleValueSourceName("db_perf_UnitKind");
repeatPattern.getField("unitName").setBackendName("unit_name");
repeatPattern.getField("processName").setBackendName("process_name");
repeatPattern.getField("tableName").setBackendName("table_name");
repeatPattern.getField("fingerprint").setBackendName("fingerprint");
repeatPattern.getField("sqlText").setBackendName("sql_text");
repeatPattern.getField("unitsAffected").setBackendName("units_affected");
repeatPattern.getField("maxRepeats").setBackendName("max_repeats");
repeatPattern.getField("totalRepeats").setBackendName("total_repeats");
repeatPattern.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
repeatPattern.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "windowStart", "windowEnd", "podName", "unitKind", "unitName", "processName")));
repeatPattern.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("tableName", "fingerprint", "sqlText", "unitsAffected", "maxRepeats", "totalRepeats")));
instance.addTable(repeatPattern);
QTableMetaData finding = base(config, DbPerfFinding.TABLE_NAME, "Findings", DbPerfFinding.class);
finding.withRecordLabelFormat("%s").withRecordLabelFields("title");
finding.withUniqueKey(new UniqueKey("dedupeKey"));
finding.getField("id").setBackendName("id");
finding.getField("backendName").setBackendName("backend_name");
finding.getField("ruleId").setBackendName("rule_id");
finding.getField("ruleId").setPossibleValueSourceName("db_perf_rule");
finding.getField("ruleCode").setBackendName("rule_code");
finding.getField("dedupeKey").setBackendName("dedupe_key");
finding.getField("status").setBackendName("status");
finding.getField("status").setDefaultValue("OPEN");
finding.getField("status").setPossibleValueSourceName("db_perf_FindingStatus");
finding.getField("severity").setBackendName("severity");
finding.getField("severity").setPossibleValueSourceName("db_perf_Severity");
finding.getField("score").setBackendName("score");
finding.getField("title").setBackendName("title");
finding.getField("summary").setBackendName("summary");
finding.getField("evidence").setBackendName("evidence");
finding.getField("evidence").setType(QFieldType.TEXT);
finding.getField("recommendation").setBackendName("recommendation");
finding.getField("recommendation").setType(QFieldType.TEXT);
finding.getField("migrationSnippet").setBackendName("migration_snippet");
finding.getField("migrationSnippet").setType(QFieldType.TEXT);
finding.getField("subjectTable").setBackendName("subject_table");
finding.getField("statementId").setBackendName("statement_id");
finding.getField("statementId").setPossibleValueSourceName("db_perf_statement");
finding.getField("tableStatId").setBackendName("table_stat_id");
finding.getField("tableStatId").setPossibleValueSourceName("db_perf_table_stat");
finding.getField("indexStatId").setBackendName("index_stat_id");
finding.getField("indexStatId").setPossibleValueSourceName("db_perf_index_stat");
finding.getField("unitName").setBackendName("unit_name");
finding.getField("firstSeenAt").setBackendName("first_seen_at");
finding.getField("lastSeenAt").setBackendName("last_seen_at");
finding.getField("occurrences").setBackendName("occurrences");
finding.getField("resolvedAt").setBackendName("resolved_at");
finding.getField("statusNote").setBackendName("status_note");
finding.getField("statusChangedBy").setBackendName("status_changed_by");
finding.getField("createDate").setBackendName("create_date");
finding.getField("modifyDate").setBackendName("modify_date");
finding.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD));
finding.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
finding.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "backendName", "ruleId", "ruleCode", "dedupeKey", "status", "severity")));
finding.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("score", "title", "summary", "evidence", "recommendation", "migrationSnippet", "subjectTable", "statementId", "tableStatId", "indexStatId", "unitName", "firstSeenAt", "lastSeenAt", "occurrences", "resolvedAt", "statusNote", "statusChangedBy")));
finding.withSection(new QFieldSection("system", new QIcon("table_rows"), Tier.T3, List.of("createDate", "modifyDate")));
instance.addTable(finding);
QTableMetaData explain = base(config, DbPerfExplain.TABLE_NAME, "Query Plans", DbPerfExplain.class);
explain.withRecordLabelFormat("%s").withRecordLabelFields("requestedAt");
explain.getField("id").setBackendName("id");
explain.getField("statementId").setBackendName("statement_id");
explain.getField("statementId").setPossibleValueSourceName("db_perf_statement");
explain.getField("requestedBy").setBackendName("requested_by");
explain.getField("requestedAt").setBackendName("requested_at");
explain.getField("mode").setBackendName("mode");
explain.getField("mode").setPossibleValueSourceName("db_perf_ExplainMode");
explain.getField("sqlFingerprint").setBackendName("sql_fingerprint");
explain.getField("sqlText").setBackendName("sql_text");
explain.getField("sqlText").setType(QFieldType.TEXT);
explain.getField("parameterCount").setBackendName("parameter_count");
explain.getField("durationMs").setBackendName("duration_ms");
explain.getField("outcome").setBackendName("outcome");
explain.getField("outcome").setPossibleValueSourceName("db_perf_Outcome");
explain.getField("errorMessage").setBackendName("error_message");
explain.getField("planJson").setBackendName("plan_json");
explain.getField("planJson").setType(QFieldType.TEXT);
explain.getField("planText").setBackendName("plan_text");
explain.getField("planText").setType(QFieldType.TEXT);
explain.getField("totalCost").setBackendName("total_cost");
explain.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
explain.withSection(new QFieldSection("identity", new QIcon("table_rows"), Tier.T1, List.of("id", "statementId", "requestedBy", "requestedAt", "mode", "sqlFingerprint", "sqlText")));
explain.withSection(new QFieldSection("details", new QIcon("table_rows"), Tier.T2, List.of("parameterCount", "durationMs", "outcome", "errorMessage", "planJson", "planText", "totalCost")));
instance.addTable(explain);
instance.addPossibleValueSource(source("db_perf_Provider", DbPerfPossibleValues.Provider.values()));
instance.addPossibleValueSource(source("db_perf_StatsAccess", DbPerfPossibleValues.StatsAccess.values()));
instance.addPossibleValueSource(source("db_perf_SnapshotStatus", DbPerfPossibleValues.SnapshotStatus.values()));
instance.addPossibleValueSource(source("db_perf_Severity", DbPerfPossibleValues.Severity.values()));
instance.addPossibleValueSource(source("db_perf_Operation", DbPerfPossibleValues.Operation.values()));
instance.addPossibleValueSource(source("db_perf_UnitKind", DbPerfPossibleValues.UnitKind.values()));
instance.addPossibleValueSource(source("db_perf_FindingStatus", DbPerfPossibleValues.FindingStatus.values()));
instance.addPossibleValueSource(source("db_perf_ExplainMode", DbPerfPossibleValues.ExplainMode.values()));
instance.addPossibleValueSource(source("db_perf_Outcome", DbPerfPossibleValues.Outcome.values()));
instance.addPossibleValueSource(source("db_perf_Reason", DbPerfPossibleValues.Reason.values()));
instance.addPossibleValueSource(source("db_perf_State", DbPerfPossibleValues.State.values()));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_settings"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_rule"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_snapshot"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_statement"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_query_snapshot"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_query_daily"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_table_stat"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_table_snapshot"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_table_daily"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_index_stat"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_activity_sample"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_connection_sample"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_attribution"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_attribution_daily"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_repeat_pattern"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_finding"));
instance.addPossibleValueSource(QPossibleValueSource.newForTable("db_perf_explain"));
child(instance, "db_perf_snapshot", "db_perf_query_snapshot", "snapshotId");
child(instance, "db_perf_statement", "db_perf_query_snapshot", "statementId");
child(instance, "db_perf_statement", "db_perf_query_daily", "statementId");
child(instance, "db_perf_snapshot", "db_perf_table_snapshot", "snapshotId");
child(instance, "db_perf_table_stat", "db_perf_table_snapshot", "tableStatId");
child(instance, "db_perf_table_stat", "db_perf_table_daily", "tableStatId");
child(instance, "db_perf_table_stat", "db_perf_index_stat", "tableStatId");
child(instance, "db_perf_snapshot", "db_perf_activity_sample", "snapshotId");
child(instance, "db_perf_snapshot", "db_perf_connection_sample", "snapshotId");
child(instance, "db_perf_rule", "db_perf_finding", "ruleId");
child(instance, "db_perf_statement", "db_perf_finding", "statementId");
child(instance, "db_perf_table_stat", "db_perf_finding", "tableStatId");
child(instance, "db_perf_statement", "db_perf_explain", "statementId");
QAppMetaData app = new QAppMetaData().withName(config.getAppName()).withLabel("Database Performance").withIcon(new QIcon("speed"))
.withPermissionRules(DbPerfPermissions.rules(config.getPermissionBaseName() + ".view"))
.withSection(new QAppSection().withName("data").withLabel("Data").withIcon(new QIcon("table_rows"))
.withTables(List.of("db_perf_finding", "db_perf_statement", "db_perf_table_stat", "db_perf_index_stat", "db_perf_snapshot", "db_perf_settings", "db_perf_rule")));
instance.addApp(app);
if(config.getParentAppName() != null)
{
instance.getApp(config.getParentAppName()).withChild(app);
}
}
/*******************************************************************************
** Common storage, permissions and write guard.
*******************************************************************************/
private static QTableMetaData base(DbPerfQBitConfig config, String name, String label, Class<? extends QRecordEntity> entity) throws QException
{
QTableMetaData table = new QTableMetaData().withName(name).withLabel(label).withIcon(new QIcon("speed"))
.withBackendName(config.getBackendName()).withBackendDetails(new RDBMSTableBackendDetails().withTableName(name))
.withPrimaryKeyField("id").withFieldsFromEntity(entity)
.withPermissionRules(DbPerfPermissions.rules(config.getPermissionBaseName() + ".view"));
table.getPermissionRules().setCustomPermissionChecker(new com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceWithProperties(DbPerfTablePermissionChecker.class,
java.util.Map.of("permissionName", config.getPermissionBaseName() + ".view")));
QInstanceEnricher.setInferredFieldBackendNames(table);
for(TableCustomizers role : List.of(TableCustomizers.PRE_INSERT_RECORD, TableCustomizers.PRE_UPDATE_RECORD, TableCustomizers.PRE_DELETE_RECORD))
{
table.withCustomizer(role, new QCodeReference(DbPerfWriteGuard.class));
}
return (table);
}
/*******************************************************************************
** Enum-backed stored values.
*******************************************************************************/
private static QPossibleValueSource source(String name, PossibleValueEnum<String>[] values)
{
List<QPossibleValue<?>> choices = new ArrayList<>();
for(PossibleValueEnum<String> value : values)
{
choices.add(new QPossibleValue<>(value.getPossibleValueId(), value.getPossibleValueLabel()));
}
return (new QPossibleValueSource().withName(name).withType(QPossibleValueSourceType.ENUM).withEnumValues(choices));
}
/*******************************************************************************
** A join and its record-screen child list.
*******************************************************************************/
private static void child(QInstance instance, String parent, String child, String field)
{
String name = parent + "_" + child;
QJoinMetaData join = new QJoinMetaData().withName(name).withLeftTable(parent).withRightTable(child)
.withType(JoinType.ONE_TO_MANY).withJoinOn(new JoinOn("id", field));
instance.addJoin(join);
instance.addWidget(ChildRecordListRenderer.widgetMetaDataBuilder(join).withLabel(instance.getTable(child).getLabel())
.withMaxRows(25).withOrderBys(List.of(new QFilterOrderBy("id", false))).getWidgetMetaData()
.withPermissionRules(instance.getTable(parent).getPermissionRules()));
instance.getTable(parent).withSection(new QFieldSection(name, instance.getTable(child).getLabel(), new QIcon("list"), Tier.T2, null).withWidgetName(name));
}
}
@@ -1,369 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.metadata;
import java.util.Locale;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
/*******************************************************************************
** Enum-backed possible values with readable labels.
*******************************************************************************/
public final class DbPerfPossibleValues
{
/*******************************************************************************
** Utility class.
*******************************************************************************/
private DbPerfPossibleValues()
{
}
/*******************************************************************************
** Values for Provider.
*******************************************************************************/
public enum Provider implements PossibleValueEnum<String>
{
POSTGRES, MYSQL, UNSUPPORTED;
/*******************************************************************************
** Stored value.
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (name());
}
/*******************************************************************************
** Human-readable label.
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
String text = name().toLowerCase(Locale.ROOT).replace('_', ' ');
return (Character.toUpperCase(text.charAt(0)) + text.substring(1));
}
}
/*******************************************************************************
** Values for StatsAccess.
*******************************************************************************/
public enum StatsAccess implements PossibleValueEnum<String>
{
FULL, NO_READ_ALL_STATS, NO_PG_STAT_STATEMENTS, UNSUPPORTED;
/*******************************************************************************
** Stored value.
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (name());
}
/*******************************************************************************
** Human-readable label.
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
String text = name().toLowerCase(Locale.ROOT).replace('_', ' ');
return (Character.toUpperCase(text.charAt(0)) + text.substring(1));
}
}
/*******************************************************************************
** Values for SnapshotStatus.
*******************************************************************************/
public enum SnapshotStatus implements PossibleValueEnum<String>
{
RUNNING, OK, PARTIAL, FAILED;
/*******************************************************************************
** Stored value.
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (name());
}
/*******************************************************************************
** Human-readable label.
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
String text = name().toLowerCase(Locale.ROOT).replace('_', ' ');
return (Character.toUpperCase(text.charAt(0)) + text.substring(1));
}
}
/*******************************************************************************
** Values for Severity.
*******************************************************************************/
public enum Severity implements PossibleValueEnum<String>
{
INFO, WARNING, ERROR, CRITICAL;
/*******************************************************************************
** Stored value.
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (name());
}
/*******************************************************************************
** Human-readable label.
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
String text = name().toLowerCase(Locale.ROOT).replace('_', ' ');
return (Character.toUpperCase(text.charAt(0)) + text.substring(1));
}
}
/*******************************************************************************
** Values for Operation.
*******************************************************************************/
public enum Operation implements PossibleValueEnum<String>
{
SELECT, INSERT, UPDATE, DELETE, WITH, OTHER;
/*******************************************************************************
** Stored value.
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (name());
}
/*******************************************************************************
** Human-readable label.
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
String text = name().toLowerCase(Locale.ROOT).replace('_', ' ');
return (Character.toUpperCase(text.charAt(0)) + text.substring(1));
}
}
/*******************************************************************************
** Values for UnitKind.
*******************************************************************************/
public enum UnitKind implements PossibleValueEnum<String>
{
HTTP_REQUEST, PROCESS, SCHEDULED_JOB, OTHER;
/*******************************************************************************
** Stored value.
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (name());
}
/*******************************************************************************
** Human-readable label.
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
String text = name().toLowerCase(Locale.ROOT).replace('_', ' ');
return (Character.toUpperCase(text.charAt(0)) + text.substring(1));
}
}
/*******************************************************************************
** Values for FindingStatus.
*******************************************************************************/
public enum FindingStatus implements PossibleValueEnum<String>
{
OPEN, ACKNOWLEDGED, RESOLVED, IGNORED;
/*******************************************************************************
** Stored value.
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (name());
}
/*******************************************************************************
** Human-readable label.
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
String text = name().toLowerCase(Locale.ROOT).replace('_', ' ');
return (Character.toUpperCase(text.charAt(0)) + text.substring(1));
}
}
/*******************************************************************************
** Values for ExplainMode.
*******************************************************************************/
public enum ExplainMode implements PossibleValueEnum<String>
{
PLAN, GENERIC, ANALYZE;
/*******************************************************************************
** Stored value.
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (name());
}
/*******************************************************************************
** Human-readable label.
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
String text = name().toLowerCase(Locale.ROOT).replace('_', ' ');
return (Character.toUpperCase(text.charAt(0)) + text.substring(1));
}
}
/*******************************************************************************
** Values for Outcome.
*******************************************************************************/
public enum Outcome implements PossibleValueEnum<String>
{
OK, ERROR, DENIED, TIMEOUT;
/*******************************************************************************
** Stored value.
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (name());
}
/*******************************************************************************
** Human-readable label.
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
String text = name().toLowerCase(Locale.ROOT).replace('_', ' ');
return (Character.toUpperCase(text.charAt(0)) + text.substring(1));
}
}
/*******************************************************************************
** Values for Reason.
*******************************************************************************/
public enum Reason implements PossibleValueEnum<String>
{
LONG_RUNNING, IDLE_IN_TRANSACTION, BLOCKED, BLOCKING;
/*******************************************************************************
** Stored value.
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (name());
}
/*******************************************************************************
** Human-readable label.
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
String text = name().toLowerCase(Locale.ROOT).replace('_', ' ');
return (Character.toUpperCase(text.charAt(0)) + text.substring(1));
}
}
/*******************************************************************************
** Values for State.
*******************************************************************************/
public enum State implements PossibleValueEnum<String>
{
active, idle, idle_in_transaction, idle_in_transaction_aborted, fastpath_function_call, disabled;
/*******************************************************************************
** Stored value.
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (name().replace('_', ' '));
}
/*******************************************************************************
** Human-readable label.
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
String text = name().toLowerCase(Locale.ROOT).replace('_', ' ');
return (Character.toUpperCase(text.charAt(0)) + text.substring(1));
}
}
}
@@ -1,93 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.metadata;
import com.kingsrook.qqq.backend.core.actions.permissions.AvailablePermission;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionSubType;
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
import com.kingsrook.qqq.backend.core.actions.permissions.UseOtherPermissionNameCustomPermissionChecker;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithName;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
/*******************************************************************************
** Exact view and settings-write permissions without READ_WRITE permission names.
*******************************************************************************/
public class DbPerfTablePermissionChecker extends UseOtherPermissionNameCustomPermissionChecker
{
/*******************************************************************************
** Reads require view; only settings/rules have human write permissions.
*******************************************************************************/
@Override
public void checkPermissionsThrowing(AbstractActionInput input, MetaDataWithPermissionRules metadata) throws QPermissionDeniedException
{
if(QContext.getQSession() instanceof QSystemUserSession)
{
return;
}
if(input instanceof InsertInput || input instanceof UpdateInput || input instanceof DeleteInput)
{
boolean settings = "db_perf_settings".equals(metadata.getName());
boolean rule = "db_perf_rule".equals(metadata.getName());
if((!settings && !rule) || settings && !(input instanceof UpdateInput)
|| QContext.getQSession() == null || !QContext.getQSession().hasPermission(managePermission()))
{
throw (new QPermissionDeniedException("Database Performance write permission denied"));
}
return;
}
super.checkPermissionsThrowing(input, metadata);
}
/*******************************************************************************
** Publish the exact write permission in host role editors.
*******************************************************************************/
@Override
public AvailablePermission buildAvailablePermission(QPermissionRules rules, PermissionSubType subtype, String base, MetaDataWithName metadata, String objectType)
{
AvailablePermission permission = super.buildAvailablePermission(rules, subtype, base, metadata, objectType);
if(permission != null && (subtype == TablePermissionSubType.INSERT || subtype == TablePermissionSubType.EDIT || subtype == TablePermissionSubType.DELETE))
{
permission.setName(managePermission());
}
return (permission);
}
/*******************************************************************************
** The configurable base is shared with the view permission.
*******************************************************************************/
private String managePermission()
{
String view = getPermissionName();
return (view.substring(0, view.length() - "view".length()) + "manage_settings");
}
}
@@ -1,114 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.metadata;
import java.util.List;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
import com.kingsrook.qqq.backend.core.model.statusmessages.PermissionDeniedMessage;
/*******************************************************************************
** Enforces, in the core actions, what a table's capabilities hide in the UI
** and API: an insert, update or delete the table does not offer is rejected
** unless the qbit itself (the system user) makes it. So performance records only change through the engine and record actions,
** whoever calls the table actions (admins included).
*******************************************************************************/
public class DbPerfWriteGuard implements TableCustomizerInterface
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
return (deny(insertInput.getTableName(), Capability.TABLE_INSERT, records));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{
return (deny(updateInput.getTableName(), Capability.TABLE_UPDATE, records));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> preDelete(DeleteInput deleteInput, List<QRecord> records, boolean isPreview) throws QException
{
return (deny(deleteInput.getTableName(), Capability.TABLE_DELETE, records));
}
/*******************************************************************************
** Add an error to every record when the table lacks the capability and the
** caller is not the system user.
*******************************************************************************/
static List<QRecord> deny(String tableName, Capability capability, List<QRecord> records)
{
if(QContext.getQSession() instanceof QSystemUserSession)
{
return (records);
}
QTableMetaData table = QContext.getQInstance().getTable(tableName);
if(table != null && table.getDisabledCapabilities() != null && table.getDisabledCapabilities().contains(capability))
{
for(QRecord record : records)
{
record.addError(new PermissionDeniedMessage("Only the Database Performance runtime changes this table."));
}
}
if(table != null && (tableName.equals("db_perf_settings") || tableName.equals("db_perf_rule")))
{
String view = table.getPermissionRules().getPermissionBaseName();
String manage = view.substring(0, view.length() - "view".length()) + "manage_settings";
if(QContext.getQSession() == null || !QContext.getQSession().hasPermission(manage))
{
for(QRecord record : records)
{
record.addError(new PermissionDeniedMessage("Managing settings requires " + manage));
}
}
}
return (records);
}
}
@@ -1,767 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.math.BigDecimal;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Activity Samples persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfActivitySample extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_activity_sample";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField
private Integer snapshotId;
@QField
private Instant capturedAt;
@QField
private Integer pid;
@QField(maxLength = 100)
private String dbUser;
@QField(maxLength = 200)
private String applicationName;
@QField(maxLength = 100)
private String clientAddr;
@QField(maxLength = 40)
private String state;
@QField(maxLength = 60)
private String waitEventType;
@QField(maxLength = 100)
private String waitEvent;
@QField(maxLength = 60)
private String backendType;
@QField
private Instant xactStart;
@QField
private Instant queryStart;
@QField
private Instant stateChange;
@QField
private BigDecimal querySeconds;
@QField
private BigDecimal xactSeconds;
@QField(maxLength = 500)
private String blockedBy;
@QField
private Integer blockingCount;
@QField(maxLength = 30)
private String reason;
@QField
private Long queryId;
@QField
private Integer statementId;
@QField(maxLength = 64)
private String queryFingerprint;
@QField(maxLength = 4000)
private String queryTextNormalized;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfActivitySample()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfActivitySample(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfActivitySample withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for snapshotId.
*******************************************************************************/
public Integer getSnapshotId()
{
return (this.snapshotId);
}
/*******************************************************************************
** Setter for snapshotId.
*******************************************************************************/
public void setSnapshotId(Integer snapshotId)
{
this.snapshotId = snapshotId;
}
/*******************************************************************************
** Fluent setter for snapshotId.
*******************************************************************************/
public DbPerfActivitySample withSnapshotId(Integer snapshotId)
{
this.snapshotId = snapshotId;
return (this);
}
/*******************************************************************************
** Getter for capturedAt.
*******************************************************************************/
public Instant getCapturedAt()
{
return (this.capturedAt);
}
/*******************************************************************************
** Setter for capturedAt.
*******************************************************************************/
public void setCapturedAt(Instant capturedAt)
{
this.capturedAt = capturedAt;
}
/*******************************************************************************
** Fluent setter for capturedAt.
*******************************************************************************/
public DbPerfActivitySample withCapturedAt(Instant capturedAt)
{
this.capturedAt = capturedAt;
return (this);
}
/*******************************************************************************
** Getter for pid.
*******************************************************************************/
public Integer getPid()
{
return (this.pid);
}
/*******************************************************************************
** Setter for pid.
*******************************************************************************/
public void setPid(Integer pid)
{
this.pid = pid;
}
/*******************************************************************************
** Fluent setter for pid.
*******************************************************************************/
public DbPerfActivitySample withPid(Integer pid)
{
this.pid = pid;
return (this);
}
/*******************************************************************************
** Getter for dbUser.
*******************************************************************************/
public String getDbUser()
{
return (this.dbUser);
}
/*******************************************************************************
** Setter for dbUser.
*******************************************************************************/
public void setDbUser(String dbUser)
{
this.dbUser = dbUser;
}
/*******************************************************************************
** Fluent setter for dbUser.
*******************************************************************************/
public DbPerfActivitySample withDbUser(String dbUser)
{
this.dbUser = dbUser;
return (this);
}
/*******************************************************************************
** Getter for applicationName.
*******************************************************************************/
public String getApplicationName()
{
return (this.applicationName);
}
/*******************************************************************************
** Setter for applicationName.
*******************************************************************************/
public void setApplicationName(String applicationName)
{
this.applicationName = applicationName;
}
/*******************************************************************************
** Fluent setter for applicationName.
*******************************************************************************/
public DbPerfActivitySample withApplicationName(String applicationName)
{
this.applicationName = applicationName;
return (this);
}
/*******************************************************************************
** Getter for clientAddr.
*******************************************************************************/
public String getClientAddr()
{
return (this.clientAddr);
}
/*******************************************************************************
** Setter for clientAddr.
*******************************************************************************/
public void setClientAddr(String clientAddr)
{
this.clientAddr = clientAddr;
}
/*******************************************************************************
** Fluent setter for clientAddr.
*******************************************************************************/
public DbPerfActivitySample withClientAddr(String clientAddr)
{
this.clientAddr = clientAddr;
return (this);
}
/*******************************************************************************
** Getter for state.
*******************************************************************************/
public String getState()
{
return (this.state);
}
/*******************************************************************************
** Setter for state.
*******************************************************************************/
public void setState(String state)
{
this.state = state;
}
/*******************************************************************************
** Fluent setter for state.
*******************************************************************************/
public DbPerfActivitySample withState(String state)
{
this.state = state;
return (this);
}
/*******************************************************************************
** Getter for waitEventType.
*******************************************************************************/
public String getWaitEventType()
{
return (this.waitEventType);
}
/*******************************************************************************
** Setter for waitEventType.
*******************************************************************************/
public void setWaitEventType(String waitEventType)
{
this.waitEventType = waitEventType;
}
/*******************************************************************************
** Fluent setter for waitEventType.
*******************************************************************************/
public DbPerfActivitySample withWaitEventType(String waitEventType)
{
this.waitEventType = waitEventType;
return (this);
}
/*******************************************************************************
** Getter for waitEvent.
*******************************************************************************/
public String getWaitEvent()
{
return (this.waitEvent);
}
/*******************************************************************************
** Setter for waitEvent.
*******************************************************************************/
public void setWaitEvent(String waitEvent)
{
this.waitEvent = waitEvent;
}
/*******************************************************************************
** Fluent setter for waitEvent.
*******************************************************************************/
public DbPerfActivitySample withWaitEvent(String waitEvent)
{
this.waitEvent = waitEvent;
return (this);
}
/*******************************************************************************
** Getter for backendType.
*******************************************************************************/
public String getBackendType()
{
return (this.backendType);
}
/*******************************************************************************
** Setter for backendType.
*******************************************************************************/
public void setBackendType(String backendType)
{
this.backendType = backendType;
}
/*******************************************************************************
** Fluent setter for backendType.
*******************************************************************************/
public DbPerfActivitySample withBackendType(String backendType)
{
this.backendType = backendType;
return (this);
}
/*******************************************************************************
** Getter for xactStart.
*******************************************************************************/
public Instant getXactStart()
{
return (this.xactStart);
}
/*******************************************************************************
** Setter for xactStart.
*******************************************************************************/
public void setXactStart(Instant xactStart)
{
this.xactStart = xactStart;
}
/*******************************************************************************
** Fluent setter for xactStart.
*******************************************************************************/
public DbPerfActivitySample withXactStart(Instant xactStart)
{
this.xactStart = xactStart;
return (this);
}
/*******************************************************************************
** Getter for queryStart.
*******************************************************************************/
public Instant getQueryStart()
{
return (this.queryStart);
}
/*******************************************************************************
** Setter for queryStart.
*******************************************************************************/
public void setQueryStart(Instant queryStart)
{
this.queryStart = queryStart;
}
/*******************************************************************************
** Fluent setter for queryStart.
*******************************************************************************/
public DbPerfActivitySample withQueryStart(Instant queryStart)
{
this.queryStart = queryStart;
return (this);
}
/*******************************************************************************
** Getter for stateChange.
*******************************************************************************/
public Instant getStateChange()
{
return (this.stateChange);
}
/*******************************************************************************
** Setter for stateChange.
*******************************************************************************/
public void setStateChange(Instant stateChange)
{
this.stateChange = stateChange;
}
/*******************************************************************************
** Fluent setter for stateChange.
*******************************************************************************/
public DbPerfActivitySample withStateChange(Instant stateChange)
{
this.stateChange = stateChange;
return (this);
}
/*******************************************************************************
** Getter for querySeconds.
*******************************************************************************/
public BigDecimal getQuerySeconds()
{
return (this.querySeconds);
}
/*******************************************************************************
** Setter for querySeconds.
*******************************************************************************/
public void setQuerySeconds(BigDecimal querySeconds)
{
this.querySeconds = querySeconds;
}
/*******************************************************************************
** Fluent setter for querySeconds.
*******************************************************************************/
public DbPerfActivitySample withQuerySeconds(BigDecimal querySeconds)
{
this.querySeconds = querySeconds;
return (this);
}
/*******************************************************************************
** Getter for xactSeconds.
*******************************************************************************/
public BigDecimal getXactSeconds()
{
return (this.xactSeconds);
}
/*******************************************************************************
** Setter for xactSeconds.
*******************************************************************************/
public void setXactSeconds(BigDecimal xactSeconds)
{
this.xactSeconds = xactSeconds;
}
/*******************************************************************************
** Fluent setter for xactSeconds.
*******************************************************************************/
public DbPerfActivitySample withXactSeconds(BigDecimal xactSeconds)
{
this.xactSeconds = xactSeconds;
return (this);
}
/*******************************************************************************
** Getter for blockedBy.
*******************************************************************************/
public String getBlockedBy()
{
return (this.blockedBy);
}
/*******************************************************************************
** Setter for blockedBy.
*******************************************************************************/
public void setBlockedBy(String blockedBy)
{
this.blockedBy = blockedBy;
}
/*******************************************************************************
** Fluent setter for blockedBy.
*******************************************************************************/
public DbPerfActivitySample withBlockedBy(String blockedBy)
{
this.blockedBy = blockedBy;
return (this);
}
/*******************************************************************************
** Getter for blockingCount.
*******************************************************************************/
public Integer getBlockingCount()
{
return (this.blockingCount);
}
/*******************************************************************************
** Setter for blockingCount.
*******************************************************************************/
public void setBlockingCount(Integer blockingCount)
{
this.blockingCount = blockingCount;
}
/*******************************************************************************
** Fluent setter for blockingCount.
*******************************************************************************/
public DbPerfActivitySample withBlockingCount(Integer blockingCount)
{
this.blockingCount = blockingCount;
return (this);
}
/*******************************************************************************
** Getter for reason.
*******************************************************************************/
public String getReason()
{
return (this.reason);
}
/*******************************************************************************
** Setter for reason.
*******************************************************************************/
public void setReason(String reason)
{
this.reason = reason;
}
/*******************************************************************************
** Fluent setter for reason.
*******************************************************************************/
public DbPerfActivitySample withReason(String reason)
{
this.reason = reason;
return (this);
}
/*******************************************************************************
** Getter for queryId.
*******************************************************************************/
public Long getQueryId()
{
return (this.queryId);
}
/*******************************************************************************
** Setter for queryId.
*******************************************************************************/
public void setQueryId(Long queryId)
{
this.queryId = queryId;
}
/*******************************************************************************
** Fluent setter for queryId.
*******************************************************************************/
public DbPerfActivitySample withQueryId(Long queryId)
{
this.queryId = queryId;
return (this);
}
/*******************************************************************************
** Getter for statementId.
*******************************************************************************/
public Integer getStatementId()
{
return (this.statementId);
}
/*******************************************************************************
** Setter for statementId.
*******************************************************************************/
public void setStatementId(Integer statementId)
{
this.statementId = statementId;
}
/*******************************************************************************
** Fluent setter for statementId.
*******************************************************************************/
public DbPerfActivitySample withStatementId(Integer statementId)
{
this.statementId = statementId;
return (this);
}
/*******************************************************************************
** Getter for queryFingerprint.
*******************************************************************************/
public String getQueryFingerprint()
{
return (this.queryFingerprint);
}
/*******************************************************************************
** Setter for queryFingerprint.
*******************************************************************************/
public void setQueryFingerprint(String queryFingerprint)
{
this.queryFingerprint = queryFingerprint;
}
/*******************************************************************************
** Fluent setter for queryFingerprint.
*******************************************************************************/
public DbPerfActivitySample withQueryFingerprint(String queryFingerprint)
{
this.queryFingerprint = queryFingerprint;
return (this);
}
/*******************************************************************************
** Getter for queryTextNormalized.
*******************************************************************************/
public String getQueryTextNormalized()
{
return (this.queryTextNormalized);
}
/*******************************************************************************
** Setter for queryTextNormalized.
*******************************************************************************/
public void setQueryTextNormalized(String queryTextNormalized)
{
this.queryTextNormalized = queryTextNormalized;
}
/*******************************************************************************
** Fluent setter for queryTextNormalized.
*******************************************************************************/
public DbPerfActivitySample withQueryTextNormalized(String queryTextNormalized)
{
this.queryTextNormalized = queryTextNormalized;
return (this);
}
}
@@ -1,612 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.math.BigDecimal;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** QQQ Attribution persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfAttribution extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_attribution";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField
private Instant windowStart;
@QField
private Instant windowEnd;
@QField(maxLength = 200)
private String podName;
@QField(maxLength = 20)
private String unitKind;
@QField(maxLength = 300)
private String unitName;
@QField(maxLength = 200)
private String processName;
@QField(maxLength = 200)
private String tableName;
@QField(maxLength = 20)
private String operation;
@QField(maxLength = 64)
private String fingerprint;
@QField(maxLength = 4000)
private String sqlText;
@QField
private Long units;
@QField
private Long statements;
@QField
private BigDecimal totalMs;
@QField
private BigDecimal maxMs;
@QField
private Long rowsCount;
@QField
private Long errors;
@QField
private BigDecimal sampleRate;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfAttribution()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfAttribution(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfAttribution withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for windowStart.
*******************************************************************************/
public Instant getWindowStart()
{
return (this.windowStart);
}
/*******************************************************************************
** Setter for windowStart.
*******************************************************************************/
public void setWindowStart(Instant windowStart)
{
this.windowStart = windowStart;
}
/*******************************************************************************
** Fluent setter for windowStart.
*******************************************************************************/
public DbPerfAttribution withWindowStart(Instant windowStart)
{
this.windowStart = windowStart;
return (this);
}
/*******************************************************************************
** Getter for windowEnd.
*******************************************************************************/
public Instant getWindowEnd()
{
return (this.windowEnd);
}
/*******************************************************************************
** Setter for windowEnd.
*******************************************************************************/
public void setWindowEnd(Instant windowEnd)
{
this.windowEnd = windowEnd;
}
/*******************************************************************************
** Fluent setter for windowEnd.
*******************************************************************************/
public DbPerfAttribution withWindowEnd(Instant windowEnd)
{
this.windowEnd = windowEnd;
return (this);
}
/*******************************************************************************
** Getter for podName.
*******************************************************************************/
public String getPodName()
{
return (this.podName);
}
/*******************************************************************************
** Setter for podName.
*******************************************************************************/
public void setPodName(String podName)
{
this.podName = podName;
}
/*******************************************************************************
** Fluent setter for podName.
*******************************************************************************/
public DbPerfAttribution withPodName(String podName)
{
this.podName = podName;
return (this);
}
/*******************************************************************************
** Getter for unitKind.
*******************************************************************************/
public String getUnitKind()
{
return (this.unitKind);
}
/*******************************************************************************
** Setter for unitKind.
*******************************************************************************/
public void setUnitKind(String unitKind)
{
this.unitKind = unitKind;
}
/*******************************************************************************
** Fluent setter for unitKind.
*******************************************************************************/
public DbPerfAttribution withUnitKind(String unitKind)
{
this.unitKind = unitKind;
return (this);
}
/*******************************************************************************
** Getter for unitName.
*******************************************************************************/
public String getUnitName()
{
return (this.unitName);
}
/*******************************************************************************
** Setter for unitName.
*******************************************************************************/
public void setUnitName(String unitName)
{
this.unitName = unitName;
}
/*******************************************************************************
** Fluent setter for unitName.
*******************************************************************************/
public DbPerfAttribution withUnitName(String unitName)
{
this.unitName = unitName;
return (this);
}
/*******************************************************************************
** Getter for processName.
*******************************************************************************/
public String getProcessName()
{
return (this.processName);
}
/*******************************************************************************
** Setter for processName.
*******************************************************************************/
public void setProcessName(String processName)
{
this.processName = processName;
}
/*******************************************************************************
** Fluent setter for processName.
*******************************************************************************/
public DbPerfAttribution withProcessName(String processName)
{
this.processName = processName;
return (this);
}
/*******************************************************************************
** Getter for tableName.
*******************************************************************************/
public String getTableName()
{
return (this.tableName);
}
/*******************************************************************************
** Setter for tableName.
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName.
*******************************************************************************/
public DbPerfAttribution withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Getter for operation.
*******************************************************************************/
public String getOperation()
{
return (this.operation);
}
/*******************************************************************************
** Setter for operation.
*******************************************************************************/
public void setOperation(String operation)
{
this.operation = operation;
}
/*******************************************************************************
** Fluent setter for operation.
*******************************************************************************/
public DbPerfAttribution withOperation(String operation)
{
this.operation = operation;
return (this);
}
/*******************************************************************************
** Getter for fingerprint.
*******************************************************************************/
public String getFingerprint()
{
return (this.fingerprint);
}
/*******************************************************************************
** Setter for fingerprint.
*******************************************************************************/
public void setFingerprint(String fingerprint)
{
this.fingerprint = fingerprint;
}
/*******************************************************************************
** Fluent setter for fingerprint.
*******************************************************************************/
public DbPerfAttribution withFingerprint(String fingerprint)
{
this.fingerprint = fingerprint;
return (this);
}
/*******************************************************************************
** Getter for sqlText.
*******************************************************************************/
public String getSqlText()
{
return (this.sqlText);
}
/*******************************************************************************
** Setter for sqlText.
*******************************************************************************/
public void setSqlText(String sqlText)
{
this.sqlText = sqlText;
}
/*******************************************************************************
** Fluent setter for sqlText.
*******************************************************************************/
public DbPerfAttribution withSqlText(String sqlText)
{
this.sqlText = sqlText;
return (this);
}
/*******************************************************************************
** Getter for units.
*******************************************************************************/
public Long getUnits()
{
return (this.units);
}
/*******************************************************************************
** Setter for units.
*******************************************************************************/
public void setUnits(Long units)
{
this.units = units;
}
/*******************************************************************************
** Fluent setter for units.
*******************************************************************************/
public DbPerfAttribution withUnits(Long units)
{
this.units = units;
return (this);
}
/*******************************************************************************
** Getter for statements.
*******************************************************************************/
public Long getStatements()
{
return (this.statements);
}
/*******************************************************************************
** Setter for statements.
*******************************************************************************/
public void setStatements(Long statements)
{
this.statements = statements;
}
/*******************************************************************************
** Fluent setter for statements.
*******************************************************************************/
public DbPerfAttribution withStatements(Long statements)
{
this.statements = statements;
return (this);
}
/*******************************************************************************
** Getter for totalMs.
*******************************************************************************/
public BigDecimal getTotalMs()
{
return (this.totalMs);
}
/*******************************************************************************
** Setter for totalMs.
*******************************************************************************/
public void setTotalMs(BigDecimal totalMs)
{
this.totalMs = totalMs;
}
/*******************************************************************************
** Fluent setter for totalMs.
*******************************************************************************/
public DbPerfAttribution withTotalMs(BigDecimal totalMs)
{
this.totalMs = totalMs;
return (this);
}
/*******************************************************************************
** Getter for maxMs.
*******************************************************************************/
public BigDecimal getMaxMs()
{
return (this.maxMs);
}
/*******************************************************************************
** Setter for maxMs.
*******************************************************************************/
public void setMaxMs(BigDecimal maxMs)
{
this.maxMs = maxMs;
}
/*******************************************************************************
** Fluent setter for maxMs.
*******************************************************************************/
public DbPerfAttribution withMaxMs(BigDecimal maxMs)
{
this.maxMs = maxMs;
return (this);
}
/*******************************************************************************
** Getter for rowsCount.
*******************************************************************************/
public Long getRowsCount()
{
return (this.rowsCount);
}
/*******************************************************************************
** Setter for rowsCount.
*******************************************************************************/
public void setRowsCount(Long rowsCount)
{
this.rowsCount = rowsCount;
}
/*******************************************************************************
** Fluent setter for rowsCount.
*******************************************************************************/
public DbPerfAttribution withRowsCount(Long rowsCount)
{
this.rowsCount = rowsCount;
return (this);
}
/*******************************************************************************
** Getter for errors.
*******************************************************************************/
public Long getErrors()
{
return (this.errors);
}
/*******************************************************************************
** Setter for errors.
*******************************************************************************/
public void setErrors(Long errors)
{
this.errors = errors;
}
/*******************************************************************************
** Fluent setter for errors.
*******************************************************************************/
public DbPerfAttribution withErrors(Long errors)
{
this.errors = errors;
return (this);
}
/*******************************************************************************
** Getter for sampleRate.
*******************************************************************************/
public BigDecimal getSampleRate()
{
return (this.sampleRate);
}
/*******************************************************************************
** Setter for sampleRate.
*******************************************************************************/
public void setSampleRate(BigDecimal sampleRate)
{
this.sampleRate = sampleRate;
}
/*******************************************************************************
** Fluent setter for sampleRate.
*******************************************************************************/
public DbPerfAttribution withSampleRate(BigDecimal sampleRate)
{
this.sampleRate = sampleRate;
return (this);
}
}
@@ -1,550 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.math.BigDecimal;
import java.time.LocalDate;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** QQQ Attribution Daily persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfAttributionDaily extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_attribution_daily";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField
private LocalDate day;
@QField(maxLength = 64)
private String rollupKey;
@QField(maxLength = 20)
private String unitKind;
@QField(maxLength = 300)
private String unitName;
@QField(maxLength = 200)
private String processName;
@QField(maxLength = 200)
private String tableName;
@QField(maxLength = 20)
private String operation;
@QField(maxLength = 64)
private String fingerprint;
@QField(maxLength = 4000)
private String sqlText;
@QField
private Long units;
@QField
private Long statements;
@QField
private BigDecimal totalMs;
@QField
private BigDecimal maxMs;
@QField
private Long rowsCount;
@QField
private Long errors;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfAttributionDaily()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfAttributionDaily(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfAttributionDaily withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for day.
*******************************************************************************/
public LocalDate getDay()
{
return (this.day);
}
/*******************************************************************************
** Setter for day.
*******************************************************************************/
public void setDay(LocalDate day)
{
this.day = day;
}
/*******************************************************************************
** Fluent setter for day.
*******************************************************************************/
public DbPerfAttributionDaily withDay(LocalDate day)
{
this.day = day;
return (this);
}
/*******************************************************************************
** Getter for rollupKey.
*******************************************************************************/
public String getRollupKey()
{
return (this.rollupKey);
}
/*******************************************************************************
** Setter for rollupKey.
*******************************************************************************/
public void setRollupKey(String rollupKey)
{
this.rollupKey = rollupKey;
}
/*******************************************************************************
** Fluent setter for rollupKey.
*******************************************************************************/
public DbPerfAttributionDaily withRollupKey(String rollupKey)
{
this.rollupKey = rollupKey;
return (this);
}
/*******************************************************************************
** Getter for unitKind.
*******************************************************************************/
public String getUnitKind()
{
return (this.unitKind);
}
/*******************************************************************************
** Setter for unitKind.
*******************************************************************************/
public void setUnitKind(String unitKind)
{
this.unitKind = unitKind;
}
/*******************************************************************************
** Fluent setter for unitKind.
*******************************************************************************/
public DbPerfAttributionDaily withUnitKind(String unitKind)
{
this.unitKind = unitKind;
return (this);
}
/*******************************************************************************
** Getter for unitName.
*******************************************************************************/
public String getUnitName()
{
return (this.unitName);
}
/*******************************************************************************
** Setter for unitName.
*******************************************************************************/
public void setUnitName(String unitName)
{
this.unitName = unitName;
}
/*******************************************************************************
** Fluent setter for unitName.
*******************************************************************************/
public DbPerfAttributionDaily withUnitName(String unitName)
{
this.unitName = unitName;
return (this);
}
/*******************************************************************************
** Getter for processName.
*******************************************************************************/
public String getProcessName()
{
return (this.processName);
}
/*******************************************************************************
** Setter for processName.
*******************************************************************************/
public void setProcessName(String processName)
{
this.processName = processName;
}
/*******************************************************************************
** Fluent setter for processName.
*******************************************************************************/
public DbPerfAttributionDaily withProcessName(String processName)
{
this.processName = processName;
return (this);
}
/*******************************************************************************
** Getter for tableName.
*******************************************************************************/
public String getTableName()
{
return (this.tableName);
}
/*******************************************************************************
** Setter for tableName.
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName.
*******************************************************************************/
public DbPerfAttributionDaily withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Getter for operation.
*******************************************************************************/
public String getOperation()
{
return (this.operation);
}
/*******************************************************************************
** Setter for operation.
*******************************************************************************/
public void setOperation(String operation)
{
this.operation = operation;
}
/*******************************************************************************
** Fluent setter for operation.
*******************************************************************************/
public DbPerfAttributionDaily withOperation(String operation)
{
this.operation = operation;
return (this);
}
/*******************************************************************************
** Getter for fingerprint.
*******************************************************************************/
public String getFingerprint()
{
return (this.fingerprint);
}
/*******************************************************************************
** Setter for fingerprint.
*******************************************************************************/
public void setFingerprint(String fingerprint)
{
this.fingerprint = fingerprint;
}
/*******************************************************************************
** Fluent setter for fingerprint.
*******************************************************************************/
public DbPerfAttributionDaily withFingerprint(String fingerprint)
{
this.fingerprint = fingerprint;
return (this);
}
/*******************************************************************************
** Getter for sqlText.
*******************************************************************************/
public String getSqlText()
{
return (this.sqlText);
}
/*******************************************************************************
** Setter for sqlText.
*******************************************************************************/
public void setSqlText(String sqlText)
{
this.sqlText = sqlText;
}
/*******************************************************************************
** Fluent setter for sqlText.
*******************************************************************************/
public DbPerfAttributionDaily withSqlText(String sqlText)
{
this.sqlText = sqlText;
return (this);
}
/*******************************************************************************
** Getter for units.
*******************************************************************************/
public Long getUnits()
{
return (this.units);
}
/*******************************************************************************
** Setter for units.
*******************************************************************************/
public void setUnits(Long units)
{
this.units = units;
}
/*******************************************************************************
** Fluent setter for units.
*******************************************************************************/
public DbPerfAttributionDaily withUnits(Long units)
{
this.units = units;
return (this);
}
/*******************************************************************************
** Getter for statements.
*******************************************************************************/
public Long getStatements()
{
return (this.statements);
}
/*******************************************************************************
** Setter for statements.
*******************************************************************************/
public void setStatements(Long statements)
{
this.statements = statements;
}
/*******************************************************************************
** Fluent setter for statements.
*******************************************************************************/
public DbPerfAttributionDaily withStatements(Long statements)
{
this.statements = statements;
return (this);
}
/*******************************************************************************
** Getter for totalMs.
*******************************************************************************/
public BigDecimal getTotalMs()
{
return (this.totalMs);
}
/*******************************************************************************
** Setter for totalMs.
*******************************************************************************/
public void setTotalMs(BigDecimal totalMs)
{
this.totalMs = totalMs;
}
/*******************************************************************************
** Fluent setter for totalMs.
*******************************************************************************/
public DbPerfAttributionDaily withTotalMs(BigDecimal totalMs)
{
this.totalMs = totalMs;
return (this);
}
/*******************************************************************************
** Getter for maxMs.
*******************************************************************************/
public BigDecimal getMaxMs()
{
return (this.maxMs);
}
/*******************************************************************************
** Setter for maxMs.
*******************************************************************************/
public void setMaxMs(BigDecimal maxMs)
{
this.maxMs = maxMs;
}
/*******************************************************************************
** Fluent setter for maxMs.
*******************************************************************************/
public DbPerfAttributionDaily withMaxMs(BigDecimal maxMs)
{
this.maxMs = maxMs;
return (this);
}
/*******************************************************************************
** Getter for rowsCount.
*******************************************************************************/
public Long getRowsCount()
{
return (this.rowsCount);
}
/*******************************************************************************
** Setter for rowsCount.
*******************************************************************************/
public void setRowsCount(Long rowsCount)
{
this.rowsCount = rowsCount;
}
/*******************************************************************************
** Fluent setter for rowsCount.
*******************************************************************************/
public DbPerfAttributionDaily withRowsCount(Long rowsCount)
{
this.rowsCount = rowsCount;
return (this);
}
/*******************************************************************************
** Getter for errors.
*******************************************************************************/
public Long getErrors()
{
return (this.errors);
}
/*******************************************************************************
** Setter for errors.
*******************************************************************************/
public void setErrors(Long errors)
{
this.errors = errors;
}
/*******************************************************************************
** Fluent setter for errors.
*******************************************************************************/
public DbPerfAttributionDaily withErrors(Long errors)
{
this.errors = errors;
return (this);
}
}
@@ -1,270 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Connection Samples persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfConnectionSample extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_connection_sample";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField
private Integer snapshotId;
@QField
private Instant capturedAt;
@QField(maxLength = 100)
private String dbUser;
@QField(maxLength = 200)
private String applicationName;
@QField(maxLength = 40)
private String state;
@QField
private Integer connectionCount;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfConnectionSample()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfConnectionSample(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfConnectionSample withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for snapshotId.
*******************************************************************************/
public Integer getSnapshotId()
{
return (this.snapshotId);
}
/*******************************************************************************
** Setter for snapshotId.
*******************************************************************************/
public void setSnapshotId(Integer snapshotId)
{
this.snapshotId = snapshotId;
}
/*******************************************************************************
** Fluent setter for snapshotId.
*******************************************************************************/
public DbPerfConnectionSample withSnapshotId(Integer snapshotId)
{
this.snapshotId = snapshotId;
return (this);
}
/*******************************************************************************
** Getter for capturedAt.
*******************************************************************************/
public Instant getCapturedAt()
{
return (this.capturedAt);
}
/*******************************************************************************
** Setter for capturedAt.
*******************************************************************************/
public void setCapturedAt(Instant capturedAt)
{
this.capturedAt = capturedAt;
}
/*******************************************************************************
** Fluent setter for capturedAt.
*******************************************************************************/
public DbPerfConnectionSample withCapturedAt(Instant capturedAt)
{
this.capturedAt = capturedAt;
return (this);
}
/*******************************************************************************
** Getter for dbUser.
*******************************************************************************/
public String getDbUser()
{
return (this.dbUser);
}
/*******************************************************************************
** Setter for dbUser.
*******************************************************************************/
public void setDbUser(String dbUser)
{
this.dbUser = dbUser;
}
/*******************************************************************************
** Fluent setter for dbUser.
*******************************************************************************/
public DbPerfConnectionSample withDbUser(String dbUser)
{
this.dbUser = dbUser;
return (this);
}
/*******************************************************************************
** Getter for applicationName.
*******************************************************************************/
public String getApplicationName()
{
return (this.applicationName);
}
/*******************************************************************************
** Setter for applicationName.
*******************************************************************************/
public void setApplicationName(String applicationName)
{
this.applicationName = applicationName;
}
/*******************************************************************************
** Fluent setter for applicationName.
*******************************************************************************/
public DbPerfConnectionSample withApplicationName(String applicationName)
{
this.applicationName = applicationName;
return (this);
}
/*******************************************************************************
** Getter for state.
*******************************************************************************/
public String getState()
{
return (this.state);
}
/*******************************************************************************
** Setter for state.
*******************************************************************************/
public void setState(String state)
{
this.state = state;
}
/*******************************************************************************
** Fluent setter for state.
*******************************************************************************/
public DbPerfConnectionSample withState(String state)
{
this.state = state;
return (this);
}
/*******************************************************************************
** Getter for connectionCount.
*******************************************************************************/
public Integer getConnectionCount()
{
return (this.connectionCount);
}
/*******************************************************************************
** Setter for connectionCount.
*******************************************************************************/
public void setConnectionCount(Integer connectionCount)
{
this.connectionCount = connectionCount;
}
/*******************************************************************************
** Fluent setter for connectionCount.
*******************************************************************************/
public DbPerfConnectionSample withConnectionCount(Integer connectionCount)
{
this.connectionCount = connectionCount;
return (this);
}
}
@@ -1,488 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.math.BigDecimal;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Query Plans persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfExplain extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_explain";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField
private Integer statementId;
@QField(maxLength = 100)
private String requestedBy;
@QField
private Instant requestedAt;
@QField(maxLength = 20)
private String mode;
@QField(maxLength = 64)
private String sqlFingerprint;
@QField
private String sqlText;
@QField
private Integer parameterCount;
@QField
private Integer durationMs;
@QField(maxLength = 20)
private String outcome;
@QField(maxLength = 2000)
private String errorMessage;
@QField
private String planJson;
@QField
private String planText;
@QField
private BigDecimal totalCost;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfExplain()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfExplain(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfExplain withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for statementId.
*******************************************************************************/
public Integer getStatementId()
{
return (this.statementId);
}
/*******************************************************************************
** Setter for statementId.
*******************************************************************************/
public void setStatementId(Integer statementId)
{
this.statementId = statementId;
}
/*******************************************************************************
** Fluent setter for statementId.
*******************************************************************************/
public DbPerfExplain withStatementId(Integer statementId)
{
this.statementId = statementId;
return (this);
}
/*******************************************************************************
** Getter for requestedBy.
*******************************************************************************/
public String getRequestedBy()
{
return (this.requestedBy);
}
/*******************************************************************************
** Setter for requestedBy.
*******************************************************************************/
public void setRequestedBy(String requestedBy)
{
this.requestedBy = requestedBy;
}
/*******************************************************************************
** Fluent setter for requestedBy.
*******************************************************************************/
public DbPerfExplain withRequestedBy(String requestedBy)
{
this.requestedBy = requestedBy;
return (this);
}
/*******************************************************************************
** Getter for requestedAt.
*******************************************************************************/
public Instant getRequestedAt()
{
return (this.requestedAt);
}
/*******************************************************************************
** Setter for requestedAt.
*******************************************************************************/
public void setRequestedAt(Instant requestedAt)
{
this.requestedAt = requestedAt;
}
/*******************************************************************************
** Fluent setter for requestedAt.
*******************************************************************************/
public DbPerfExplain withRequestedAt(Instant requestedAt)
{
this.requestedAt = requestedAt;
return (this);
}
/*******************************************************************************
** Getter for mode.
*******************************************************************************/
public String getMode()
{
return (this.mode);
}
/*******************************************************************************
** Setter for mode.
*******************************************************************************/
public void setMode(String mode)
{
this.mode = mode;
}
/*******************************************************************************
** Fluent setter for mode.
*******************************************************************************/
public DbPerfExplain withMode(String mode)
{
this.mode = mode;
return (this);
}
/*******************************************************************************
** Getter for sqlFingerprint.
*******************************************************************************/
public String getSqlFingerprint()
{
return (this.sqlFingerprint);
}
/*******************************************************************************
** Setter for sqlFingerprint.
*******************************************************************************/
public void setSqlFingerprint(String sqlFingerprint)
{
this.sqlFingerprint = sqlFingerprint;
}
/*******************************************************************************
** Fluent setter for sqlFingerprint.
*******************************************************************************/
public DbPerfExplain withSqlFingerprint(String sqlFingerprint)
{
this.sqlFingerprint = sqlFingerprint;
return (this);
}
/*******************************************************************************
** Getter for sqlText.
*******************************************************************************/
public String getSqlText()
{
return (this.sqlText);
}
/*******************************************************************************
** Setter for sqlText.
*******************************************************************************/
public void setSqlText(String sqlText)
{
this.sqlText = sqlText;
}
/*******************************************************************************
** Fluent setter for sqlText.
*******************************************************************************/
public DbPerfExplain withSqlText(String sqlText)
{
this.sqlText = sqlText;
return (this);
}
/*******************************************************************************
** Getter for parameterCount.
*******************************************************************************/
public Integer getParameterCount()
{
return (this.parameterCount);
}
/*******************************************************************************
** Setter for parameterCount.
*******************************************************************************/
public void setParameterCount(Integer parameterCount)
{
this.parameterCount = parameterCount;
}
/*******************************************************************************
** Fluent setter for parameterCount.
*******************************************************************************/
public DbPerfExplain withParameterCount(Integer parameterCount)
{
this.parameterCount = parameterCount;
return (this);
}
/*******************************************************************************
** Getter for durationMs.
*******************************************************************************/
public Integer getDurationMs()
{
return (this.durationMs);
}
/*******************************************************************************
** Setter for durationMs.
*******************************************************************************/
public void setDurationMs(Integer durationMs)
{
this.durationMs = durationMs;
}
/*******************************************************************************
** Fluent setter for durationMs.
*******************************************************************************/
public DbPerfExplain withDurationMs(Integer durationMs)
{
this.durationMs = durationMs;
return (this);
}
/*******************************************************************************
** Getter for outcome.
*******************************************************************************/
public String getOutcome()
{
return (this.outcome);
}
/*******************************************************************************
** Setter for outcome.
*******************************************************************************/
public void setOutcome(String outcome)
{
this.outcome = outcome;
}
/*******************************************************************************
** Fluent setter for outcome.
*******************************************************************************/
public DbPerfExplain withOutcome(String outcome)
{
this.outcome = outcome;
return (this);
}
/*******************************************************************************
** Getter for errorMessage.
*******************************************************************************/
public String getErrorMessage()
{
return (this.errorMessage);
}
/*******************************************************************************
** Setter for errorMessage.
*******************************************************************************/
public void setErrorMessage(String errorMessage)
{
this.errorMessage = errorMessage;
}
/*******************************************************************************
** Fluent setter for errorMessage.
*******************************************************************************/
public DbPerfExplain withErrorMessage(String errorMessage)
{
this.errorMessage = errorMessage;
return (this);
}
/*******************************************************************************
** Getter for planJson.
*******************************************************************************/
public String getPlanJson()
{
return (this.planJson);
}
/*******************************************************************************
** Setter for planJson.
*******************************************************************************/
public void setPlanJson(String planJson)
{
this.planJson = planJson;
}
/*******************************************************************************
** Fluent setter for planJson.
*******************************************************************************/
public DbPerfExplain withPlanJson(String planJson)
{
this.planJson = planJson;
return (this);
}
/*******************************************************************************
** Getter for planText.
*******************************************************************************/
public String getPlanText()
{
return (this.planText);
}
/*******************************************************************************
** Setter for planText.
*******************************************************************************/
public void setPlanText(String planText)
{
this.planText = planText;
}
/*******************************************************************************
** Fluent setter for planText.
*******************************************************************************/
public DbPerfExplain withPlanText(String planText)
{
this.planText = planText;
return (this);
}
/*******************************************************************************
** Getter for totalCost.
*******************************************************************************/
public BigDecimal getTotalCost()
{
return (this.totalCost);
}
/*******************************************************************************
** Setter for totalCost.
*******************************************************************************/
public void setTotalCost(BigDecimal totalCost)
{
this.totalCost = totalCost;
}
/*******************************************************************************
** Fluent setter for totalCost.
*******************************************************************************/
public DbPerfExplain withTotalCost(BigDecimal totalCost)
{
this.totalCost = totalCost;
return (this);
}
}
@@ -1,860 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.math.BigDecimal;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Findings persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfFinding extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_finding";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(maxLength = 100)
private String backendName;
@QField
private Integer ruleId;
@QField(maxLength = 40)
private String ruleCode;
@QField(maxLength = 300)
private String dedupeKey;
@QField(maxLength = 20)
private String status;
@QField(maxLength = 20)
private String severity;
@QField
private BigDecimal score;
@QField(maxLength = 300)
private String title;
@QField(maxLength = 2000)
private String summary;
@QField
private String evidence;
@QField
private String recommendation;
@QField
private String migrationSnippet;
@QField(maxLength = 200)
private String subjectTable;
@QField
private Integer statementId;
@QField
private Integer tableStatId;
@QField
private Integer indexStatId;
@QField(maxLength = 300)
private String unitName;
@QField
private Instant firstSeenAt;
@QField
private Instant lastSeenAt;
@QField
private Integer occurrences;
@QField
private Instant resolvedAt;
@QField(maxLength = 2000)
private String statusNote;
@QField(maxLength = 100)
private String statusChangedBy;
@QField
private Instant createDate;
@QField
private Instant modifyDate;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfFinding()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfFinding(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfFinding withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for backendName.
*******************************************************************************/
public String getBackendName()
{
return (this.backendName);
}
/*******************************************************************************
** Setter for backendName.
*******************************************************************************/
public void setBackendName(String backendName)
{
this.backendName = backendName;
}
/*******************************************************************************
** Fluent setter for backendName.
*******************************************************************************/
public DbPerfFinding withBackendName(String backendName)
{
this.backendName = backendName;
return (this);
}
/*******************************************************************************
** Getter for ruleId.
*******************************************************************************/
public Integer getRuleId()
{
return (this.ruleId);
}
/*******************************************************************************
** Setter for ruleId.
*******************************************************************************/
public void setRuleId(Integer ruleId)
{
this.ruleId = ruleId;
}
/*******************************************************************************
** Fluent setter for ruleId.
*******************************************************************************/
public DbPerfFinding withRuleId(Integer ruleId)
{
this.ruleId = ruleId;
return (this);
}
/*******************************************************************************
** Getter for ruleCode.
*******************************************************************************/
public String getRuleCode()
{
return (this.ruleCode);
}
/*******************************************************************************
** Setter for ruleCode.
*******************************************************************************/
public void setRuleCode(String ruleCode)
{
this.ruleCode = ruleCode;
}
/*******************************************************************************
** Fluent setter for ruleCode.
*******************************************************************************/
public DbPerfFinding withRuleCode(String ruleCode)
{
this.ruleCode = ruleCode;
return (this);
}
/*******************************************************************************
** Getter for dedupeKey.
*******************************************************************************/
public String getDedupeKey()
{
return (this.dedupeKey);
}
/*******************************************************************************
** Setter for dedupeKey.
*******************************************************************************/
public void setDedupeKey(String dedupeKey)
{
this.dedupeKey = dedupeKey;
}
/*******************************************************************************
** Fluent setter for dedupeKey.
*******************************************************************************/
public DbPerfFinding withDedupeKey(String dedupeKey)
{
this.dedupeKey = dedupeKey;
return (this);
}
/*******************************************************************************
** Getter for status.
*******************************************************************************/
public String getStatus()
{
return (this.status);
}
/*******************************************************************************
** Setter for status.
*******************************************************************************/
public void setStatus(String status)
{
this.status = status;
}
/*******************************************************************************
** Fluent setter for status.
*******************************************************************************/
public DbPerfFinding withStatus(String status)
{
this.status = status;
return (this);
}
/*******************************************************************************
** Getter for severity.
*******************************************************************************/
public String getSeverity()
{
return (this.severity);
}
/*******************************************************************************
** Setter for severity.
*******************************************************************************/
public void setSeverity(String severity)
{
this.severity = severity;
}
/*******************************************************************************
** Fluent setter for severity.
*******************************************************************************/
public DbPerfFinding withSeverity(String severity)
{
this.severity = severity;
return (this);
}
/*******************************************************************************
** Getter for score.
*******************************************************************************/
public BigDecimal getScore()
{
return (this.score);
}
/*******************************************************************************
** Setter for score.
*******************************************************************************/
public void setScore(BigDecimal score)
{
this.score = score;
}
/*******************************************************************************
** Fluent setter for score.
*******************************************************************************/
public DbPerfFinding withScore(BigDecimal score)
{
this.score = score;
return (this);
}
/*******************************************************************************
** Getter for title.
*******************************************************************************/
public String getTitle()
{
return (this.title);
}
/*******************************************************************************
** Setter for title.
*******************************************************************************/
public void setTitle(String title)
{
this.title = title;
}
/*******************************************************************************
** Fluent setter for title.
*******************************************************************************/
public DbPerfFinding withTitle(String title)
{
this.title = title;
return (this);
}
/*******************************************************************************
** Getter for summary.
*******************************************************************************/
public String getSummary()
{
return (this.summary);
}
/*******************************************************************************
** Setter for summary.
*******************************************************************************/
public void setSummary(String summary)
{
this.summary = summary;
}
/*******************************************************************************
** Fluent setter for summary.
*******************************************************************************/
public DbPerfFinding withSummary(String summary)
{
this.summary = summary;
return (this);
}
/*******************************************************************************
** Getter for evidence.
*******************************************************************************/
public String getEvidence()
{
return (this.evidence);
}
/*******************************************************************************
** Setter for evidence.
*******************************************************************************/
public void setEvidence(String evidence)
{
this.evidence = evidence;
}
/*******************************************************************************
** Fluent setter for evidence.
*******************************************************************************/
public DbPerfFinding withEvidence(String evidence)
{
this.evidence = evidence;
return (this);
}
/*******************************************************************************
** Getter for recommendation.
*******************************************************************************/
public String getRecommendation()
{
return (this.recommendation);
}
/*******************************************************************************
** Setter for recommendation.
*******************************************************************************/
public void setRecommendation(String recommendation)
{
this.recommendation = recommendation;
}
/*******************************************************************************
** Fluent setter for recommendation.
*******************************************************************************/
public DbPerfFinding withRecommendation(String recommendation)
{
this.recommendation = recommendation;
return (this);
}
/*******************************************************************************
** Getter for migrationSnippet.
*******************************************************************************/
public String getMigrationSnippet()
{
return (this.migrationSnippet);
}
/*******************************************************************************
** Setter for migrationSnippet.
*******************************************************************************/
public void setMigrationSnippet(String migrationSnippet)
{
this.migrationSnippet = migrationSnippet;
}
/*******************************************************************************
** Fluent setter for migrationSnippet.
*******************************************************************************/
public DbPerfFinding withMigrationSnippet(String migrationSnippet)
{
this.migrationSnippet = migrationSnippet;
return (this);
}
/*******************************************************************************
** Getter for subjectTable.
*******************************************************************************/
public String getSubjectTable()
{
return (this.subjectTable);
}
/*******************************************************************************
** Setter for subjectTable.
*******************************************************************************/
public void setSubjectTable(String subjectTable)
{
this.subjectTable = subjectTable;
}
/*******************************************************************************
** Fluent setter for subjectTable.
*******************************************************************************/
public DbPerfFinding withSubjectTable(String subjectTable)
{
this.subjectTable = subjectTable;
return (this);
}
/*******************************************************************************
** Getter for statementId.
*******************************************************************************/
public Integer getStatementId()
{
return (this.statementId);
}
/*******************************************************************************
** Setter for statementId.
*******************************************************************************/
public void setStatementId(Integer statementId)
{
this.statementId = statementId;
}
/*******************************************************************************
** Fluent setter for statementId.
*******************************************************************************/
public DbPerfFinding withStatementId(Integer statementId)
{
this.statementId = statementId;
return (this);
}
/*******************************************************************************
** Getter for tableStatId.
*******************************************************************************/
public Integer getTableStatId()
{
return (this.tableStatId);
}
/*******************************************************************************
** Setter for tableStatId.
*******************************************************************************/
public void setTableStatId(Integer tableStatId)
{
this.tableStatId = tableStatId;
}
/*******************************************************************************
** Fluent setter for tableStatId.
*******************************************************************************/
public DbPerfFinding withTableStatId(Integer tableStatId)
{
this.tableStatId = tableStatId;
return (this);
}
/*******************************************************************************
** Getter for indexStatId.
*******************************************************************************/
public Integer getIndexStatId()
{
return (this.indexStatId);
}
/*******************************************************************************
** Setter for indexStatId.
*******************************************************************************/
public void setIndexStatId(Integer indexStatId)
{
this.indexStatId = indexStatId;
}
/*******************************************************************************
** Fluent setter for indexStatId.
*******************************************************************************/
public DbPerfFinding withIndexStatId(Integer indexStatId)
{
this.indexStatId = indexStatId;
return (this);
}
/*******************************************************************************
** Getter for unitName.
*******************************************************************************/
public String getUnitName()
{
return (this.unitName);
}
/*******************************************************************************
** Setter for unitName.
*******************************************************************************/
public void setUnitName(String unitName)
{
this.unitName = unitName;
}
/*******************************************************************************
** Fluent setter for unitName.
*******************************************************************************/
public DbPerfFinding withUnitName(String unitName)
{
this.unitName = unitName;
return (this);
}
/*******************************************************************************
** Getter for firstSeenAt.
*******************************************************************************/
public Instant getFirstSeenAt()
{
return (this.firstSeenAt);
}
/*******************************************************************************
** Setter for firstSeenAt.
*******************************************************************************/
public void setFirstSeenAt(Instant firstSeenAt)
{
this.firstSeenAt = firstSeenAt;
}
/*******************************************************************************
** Fluent setter for firstSeenAt.
*******************************************************************************/
public DbPerfFinding withFirstSeenAt(Instant firstSeenAt)
{
this.firstSeenAt = firstSeenAt;
return (this);
}
/*******************************************************************************
** Getter for lastSeenAt.
*******************************************************************************/
public Instant getLastSeenAt()
{
return (this.lastSeenAt);
}
/*******************************************************************************
** Setter for lastSeenAt.
*******************************************************************************/
public void setLastSeenAt(Instant lastSeenAt)
{
this.lastSeenAt = lastSeenAt;
}
/*******************************************************************************
** Fluent setter for lastSeenAt.
*******************************************************************************/
public DbPerfFinding withLastSeenAt(Instant lastSeenAt)
{
this.lastSeenAt = lastSeenAt;
return (this);
}
/*******************************************************************************
** Getter for occurrences.
*******************************************************************************/
public Integer getOccurrences()
{
return (this.occurrences);
}
/*******************************************************************************
** Setter for occurrences.
*******************************************************************************/
public void setOccurrences(Integer occurrences)
{
this.occurrences = occurrences;
}
/*******************************************************************************
** Fluent setter for occurrences.
*******************************************************************************/
public DbPerfFinding withOccurrences(Integer occurrences)
{
this.occurrences = occurrences;
return (this);
}
/*******************************************************************************
** Getter for resolvedAt.
*******************************************************************************/
public Instant getResolvedAt()
{
return (this.resolvedAt);
}
/*******************************************************************************
** Setter for resolvedAt.
*******************************************************************************/
public void setResolvedAt(Instant resolvedAt)
{
this.resolvedAt = resolvedAt;
}
/*******************************************************************************
** Fluent setter for resolvedAt.
*******************************************************************************/
public DbPerfFinding withResolvedAt(Instant resolvedAt)
{
this.resolvedAt = resolvedAt;
return (this);
}
/*******************************************************************************
** Getter for statusNote.
*******************************************************************************/
public String getStatusNote()
{
return (this.statusNote);
}
/*******************************************************************************
** Setter for statusNote.
*******************************************************************************/
public void setStatusNote(String statusNote)
{
this.statusNote = statusNote;
}
/*******************************************************************************
** Fluent setter for statusNote.
*******************************************************************************/
public DbPerfFinding withStatusNote(String statusNote)
{
this.statusNote = statusNote;
return (this);
}
/*******************************************************************************
** Getter for statusChangedBy.
*******************************************************************************/
public String getStatusChangedBy()
{
return (this.statusChangedBy);
}
/*******************************************************************************
** Setter for statusChangedBy.
*******************************************************************************/
public void setStatusChangedBy(String statusChangedBy)
{
this.statusChangedBy = statusChangedBy;
}
/*******************************************************************************
** Fluent setter for statusChangedBy.
*******************************************************************************/
public DbPerfFinding withStatusChangedBy(String statusChangedBy)
{
this.statusChangedBy = statusChangedBy;
return (this);
}
/*******************************************************************************
** Getter for createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (this.createDate);
}
/*******************************************************************************
** Setter for createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Fluent setter for createDate.
*******************************************************************************/
public DbPerfFinding withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Getter for modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (this.modifyDate);
}
/*******************************************************************************
** Setter for modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Fluent setter for modifyDate.
*******************************************************************************/
public DbPerfFinding withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
}
@@ -1,704 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Indexes persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfIndexStat extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_index_stat";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(maxLength = 100)
private String backendName;
@QField
private Integer tableStatId;
@QField(maxLength = 100)
private String schemaName;
@QField(maxLength = 200)
private String tableName;
@QField(maxLength = 200)
private String indexName;
@QField
private Long relid;
@QField(maxLength = 2000)
private String definition;
@QField(maxLength = 1000)
private String columns;
@QField
private Boolean isUnique;
@QField
private Boolean isPrimary;
@QField
private Boolean isValid;
@QField
private Long idxScan;
@QField
private Long idxTupRead;
@QField
private Long idxTupFetch;
@QField
private Long sizeBytes;
@QField
private Long writeCost;
@QField
private Long scansSinceReset;
@QField
private Instant lastSeenAt;
@QField
private Instant createDate;
@QField
private Instant modifyDate;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfIndexStat()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfIndexStat(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfIndexStat withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for backendName.
*******************************************************************************/
public String getBackendName()
{
return (this.backendName);
}
/*******************************************************************************
** Setter for backendName.
*******************************************************************************/
public void setBackendName(String backendName)
{
this.backendName = backendName;
}
/*******************************************************************************
** Fluent setter for backendName.
*******************************************************************************/
public DbPerfIndexStat withBackendName(String backendName)
{
this.backendName = backendName;
return (this);
}
/*******************************************************************************
** Getter for tableStatId.
*******************************************************************************/
public Integer getTableStatId()
{
return (this.tableStatId);
}
/*******************************************************************************
** Setter for tableStatId.
*******************************************************************************/
public void setTableStatId(Integer tableStatId)
{
this.tableStatId = tableStatId;
}
/*******************************************************************************
** Fluent setter for tableStatId.
*******************************************************************************/
public DbPerfIndexStat withTableStatId(Integer tableStatId)
{
this.tableStatId = tableStatId;
return (this);
}
/*******************************************************************************
** Getter for schemaName.
*******************************************************************************/
public String getSchemaName()
{
return (this.schemaName);
}
/*******************************************************************************
** Setter for schemaName.
*******************************************************************************/
public void setSchemaName(String schemaName)
{
this.schemaName = schemaName;
}
/*******************************************************************************
** Fluent setter for schemaName.
*******************************************************************************/
public DbPerfIndexStat withSchemaName(String schemaName)
{
this.schemaName = schemaName;
return (this);
}
/*******************************************************************************
** Getter for tableName.
*******************************************************************************/
public String getTableName()
{
return (this.tableName);
}
/*******************************************************************************
** Setter for tableName.
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName.
*******************************************************************************/
public DbPerfIndexStat withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Getter for indexName.
*******************************************************************************/
public String getIndexName()
{
return (this.indexName);
}
/*******************************************************************************
** Setter for indexName.
*******************************************************************************/
public void setIndexName(String indexName)
{
this.indexName = indexName;
}
/*******************************************************************************
** Fluent setter for indexName.
*******************************************************************************/
public DbPerfIndexStat withIndexName(String indexName)
{
this.indexName = indexName;
return (this);
}
/*******************************************************************************
** Getter for relid.
*******************************************************************************/
public Long getRelid()
{
return (this.relid);
}
/*******************************************************************************
** Setter for relid.
*******************************************************************************/
public void setRelid(Long relid)
{
this.relid = relid;
}
/*******************************************************************************
** Fluent setter for relid.
*******************************************************************************/
public DbPerfIndexStat withRelid(Long relid)
{
this.relid = relid;
return (this);
}
/*******************************************************************************
** Getter for definition.
*******************************************************************************/
public String getDefinition()
{
return (this.definition);
}
/*******************************************************************************
** Setter for definition.
*******************************************************************************/
public void setDefinition(String definition)
{
this.definition = definition;
}
/*******************************************************************************
** Fluent setter for definition.
*******************************************************************************/
public DbPerfIndexStat withDefinition(String definition)
{
this.definition = definition;
return (this);
}
/*******************************************************************************
** Getter for columns.
*******************************************************************************/
public String getColumns()
{
return (this.columns);
}
/*******************************************************************************
** Setter for columns.
*******************************************************************************/
public void setColumns(String columns)
{
this.columns = columns;
}
/*******************************************************************************
** Fluent setter for columns.
*******************************************************************************/
public DbPerfIndexStat withColumns(String columns)
{
this.columns = columns;
return (this);
}
/*******************************************************************************
** Getter for isUnique.
*******************************************************************************/
public Boolean getIsUnique()
{
return (this.isUnique);
}
/*******************************************************************************
** Setter for isUnique.
*******************************************************************************/
public void setIsUnique(Boolean isUnique)
{
this.isUnique = isUnique;
}
/*******************************************************************************
** Fluent setter for isUnique.
*******************************************************************************/
public DbPerfIndexStat withIsUnique(Boolean isUnique)
{
this.isUnique = isUnique;
return (this);
}
/*******************************************************************************
** Getter for isPrimary.
*******************************************************************************/
public Boolean getIsPrimary()
{
return (this.isPrimary);
}
/*******************************************************************************
** Setter for isPrimary.
*******************************************************************************/
public void setIsPrimary(Boolean isPrimary)
{
this.isPrimary = isPrimary;
}
/*******************************************************************************
** Fluent setter for isPrimary.
*******************************************************************************/
public DbPerfIndexStat withIsPrimary(Boolean isPrimary)
{
this.isPrimary = isPrimary;
return (this);
}
/*******************************************************************************
** Getter for isValid.
*******************************************************************************/
public Boolean getIsValid()
{
return (this.isValid);
}
/*******************************************************************************
** Setter for isValid.
*******************************************************************************/
public void setIsValid(Boolean isValid)
{
this.isValid = isValid;
}
/*******************************************************************************
** Fluent setter for isValid.
*******************************************************************************/
public DbPerfIndexStat withIsValid(Boolean isValid)
{
this.isValid = isValid;
return (this);
}
/*******************************************************************************
** Getter for idxScan.
*******************************************************************************/
public Long getIdxScan()
{
return (this.idxScan);
}
/*******************************************************************************
** Setter for idxScan.
*******************************************************************************/
public void setIdxScan(Long idxScan)
{
this.idxScan = idxScan;
}
/*******************************************************************************
** Fluent setter for idxScan.
*******************************************************************************/
public DbPerfIndexStat withIdxScan(Long idxScan)
{
this.idxScan = idxScan;
return (this);
}
/*******************************************************************************
** Getter for idxTupRead.
*******************************************************************************/
public Long getIdxTupRead()
{
return (this.idxTupRead);
}
/*******************************************************************************
** Setter for idxTupRead.
*******************************************************************************/
public void setIdxTupRead(Long idxTupRead)
{
this.idxTupRead = idxTupRead;
}
/*******************************************************************************
** Fluent setter for idxTupRead.
*******************************************************************************/
public DbPerfIndexStat withIdxTupRead(Long idxTupRead)
{
this.idxTupRead = idxTupRead;
return (this);
}
/*******************************************************************************
** Getter for idxTupFetch.
*******************************************************************************/
public Long getIdxTupFetch()
{
return (this.idxTupFetch);
}
/*******************************************************************************
** Setter for idxTupFetch.
*******************************************************************************/
public void setIdxTupFetch(Long idxTupFetch)
{
this.idxTupFetch = idxTupFetch;
}
/*******************************************************************************
** Fluent setter for idxTupFetch.
*******************************************************************************/
public DbPerfIndexStat withIdxTupFetch(Long idxTupFetch)
{
this.idxTupFetch = idxTupFetch;
return (this);
}
/*******************************************************************************
** Getter for sizeBytes.
*******************************************************************************/
public Long getSizeBytes()
{
return (this.sizeBytes);
}
/*******************************************************************************
** Setter for sizeBytes.
*******************************************************************************/
public void setSizeBytes(Long sizeBytes)
{
this.sizeBytes = sizeBytes;
}
/*******************************************************************************
** Fluent setter for sizeBytes.
*******************************************************************************/
public DbPerfIndexStat withSizeBytes(Long sizeBytes)
{
this.sizeBytes = sizeBytes;
return (this);
}
/*******************************************************************************
** Getter for writeCost.
*******************************************************************************/
public Long getWriteCost()
{
return (this.writeCost);
}
/*******************************************************************************
** Setter for writeCost.
*******************************************************************************/
public void setWriteCost(Long writeCost)
{
this.writeCost = writeCost;
}
/*******************************************************************************
** Fluent setter for writeCost.
*******************************************************************************/
public DbPerfIndexStat withWriteCost(Long writeCost)
{
this.writeCost = writeCost;
return (this);
}
/*******************************************************************************
** Getter for scansSinceReset.
*******************************************************************************/
public Long getScansSinceReset()
{
return (this.scansSinceReset);
}
/*******************************************************************************
** Setter for scansSinceReset.
*******************************************************************************/
public void setScansSinceReset(Long scansSinceReset)
{
this.scansSinceReset = scansSinceReset;
}
/*******************************************************************************
** Fluent setter for scansSinceReset.
*******************************************************************************/
public DbPerfIndexStat withScansSinceReset(Long scansSinceReset)
{
this.scansSinceReset = scansSinceReset;
return (this);
}
/*******************************************************************************
** Getter for lastSeenAt.
*******************************************************************************/
public Instant getLastSeenAt()
{
return (this.lastSeenAt);
}
/*******************************************************************************
** Setter for lastSeenAt.
*******************************************************************************/
public void setLastSeenAt(Instant lastSeenAt)
{
this.lastSeenAt = lastSeenAt;
}
/*******************************************************************************
** Fluent setter for lastSeenAt.
*******************************************************************************/
public DbPerfIndexStat withLastSeenAt(Instant lastSeenAt)
{
this.lastSeenAt = lastSeenAt;
return (this);
}
/*******************************************************************************
** Getter for createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (this.createDate);
}
/*******************************************************************************
** Setter for createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Fluent setter for createDate.
*******************************************************************************/
public DbPerfIndexStat withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Getter for modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (this.modifyDate);
}
/*******************************************************************************
** Setter for modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Fluent setter for modifyDate.
*******************************************************************************/
public DbPerfIndexStat withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
}
@@ -1,395 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.math.BigDecimal;
import java.time.LocalDate;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Query Daily persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfQueryDaily extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_query_daily";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField
private Integer statementId;
@QField
private LocalDate day;
@QField
private Long calls;
@QField
private BigDecimal totalMs;
@QField
private BigDecimal meanMs;
@QField
private BigDecimal maxMs;
@QField
private Long rowsCount;
@QField
private BigDecimal hitRatio;
@QField
private BigDecimal shareOfDbTime;
@QField
private Integer snapshots;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfQueryDaily()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfQueryDaily(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfQueryDaily withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for statementId.
*******************************************************************************/
public Integer getStatementId()
{
return (this.statementId);
}
/*******************************************************************************
** Setter for statementId.
*******************************************************************************/
public void setStatementId(Integer statementId)
{
this.statementId = statementId;
}
/*******************************************************************************
** Fluent setter for statementId.
*******************************************************************************/
public DbPerfQueryDaily withStatementId(Integer statementId)
{
this.statementId = statementId;
return (this);
}
/*******************************************************************************
** Getter for day.
*******************************************************************************/
public LocalDate getDay()
{
return (this.day);
}
/*******************************************************************************
** Setter for day.
*******************************************************************************/
public void setDay(LocalDate day)
{
this.day = day;
}
/*******************************************************************************
** Fluent setter for day.
*******************************************************************************/
public DbPerfQueryDaily withDay(LocalDate day)
{
this.day = day;
return (this);
}
/*******************************************************************************
** Getter for calls.
*******************************************************************************/
public Long getCalls()
{
return (this.calls);
}
/*******************************************************************************
** Setter for calls.
*******************************************************************************/
public void setCalls(Long calls)
{
this.calls = calls;
}
/*******************************************************************************
** Fluent setter for calls.
*******************************************************************************/
public DbPerfQueryDaily withCalls(Long calls)
{
this.calls = calls;
return (this);
}
/*******************************************************************************
** Getter for totalMs.
*******************************************************************************/
public BigDecimal getTotalMs()
{
return (this.totalMs);
}
/*******************************************************************************
** Setter for totalMs.
*******************************************************************************/
public void setTotalMs(BigDecimal totalMs)
{
this.totalMs = totalMs;
}
/*******************************************************************************
** Fluent setter for totalMs.
*******************************************************************************/
public DbPerfQueryDaily withTotalMs(BigDecimal totalMs)
{
this.totalMs = totalMs;
return (this);
}
/*******************************************************************************
** Getter for meanMs.
*******************************************************************************/
public BigDecimal getMeanMs()
{
return (this.meanMs);
}
/*******************************************************************************
** Setter for meanMs.
*******************************************************************************/
public void setMeanMs(BigDecimal meanMs)
{
this.meanMs = meanMs;
}
/*******************************************************************************
** Fluent setter for meanMs.
*******************************************************************************/
public DbPerfQueryDaily withMeanMs(BigDecimal meanMs)
{
this.meanMs = meanMs;
return (this);
}
/*******************************************************************************
** Getter for maxMs.
*******************************************************************************/
public BigDecimal getMaxMs()
{
return (this.maxMs);
}
/*******************************************************************************
** Setter for maxMs.
*******************************************************************************/
public void setMaxMs(BigDecimal maxMs)
{
this.maxMs = maxMs;
}
/*******************************************************************************
** Fluent setter for maxMs.
*******************************************************************************/
public DbPerfQueryDaily withMaxMs(BigDecimal maxMs)
{
this.maxMs = maxMs;
return (this);
}
/*******************************************************************************
** Getter for rowsCount.
*******************************************************************************/
public Long getRowsCount()
{
return (this.rowsCount);
}
/*******************************************************************************
** Setter for rowsCount.
*******************************************************************************/
public void setRowsCount(Long rowsCount)
{
this.rowsCount = rowsCount;
}
/*******************************************************************************
** Fluent setter for rowsCount.
*******************************************************************************/
public DbPerfQueryDaily withRowsCount(Long rowsCount)
{
this.rowsCount = rowsCount;
return (this);
}
/*******************************************************************************
** Getter for hitRatio.
*******************************************************************************/
public BigDecimal getHitRatio()
{
return (this.hitRatio);
}
/*******************************************************************************
** Setter for hitRatio.
*******************************************************************************/
public void setHitRatio(BigDecimal hitRatio)
{
this.hitRatio = hitRatio;
}
/*******************************************************************************
** Fluent setter for hitRatio.
*******************************************************************************/
public DbPerfQueryDaily withHitRatio(BigDecimal hitRatio)
{
this.hitRatio = hitRatio;
return (this);
}
/*******************************************************************************
** Getter for shareOfDbTime.
*******************************************************************************/
public BigDecimal getShareOfDbTime()
{
return (this.shareOfDbTime);
}
/*******************************************************************************
** Setter for shareOfDbTime.
*******************************************************************************/
public void setShareOfDbTime(BigDecimal shareOfDbTime)
{
this.shareOfDbTime = shareOfDbTime;
}
/*******************************************************************************
** Fluent setter for shareOfDbTime.
*******************************************************************************/
public DbPerfQueryDaily withShareOfDbTime(BigDecimal shareOfDbTime)
{
this.shareOfDbTime = shareOfDbTime;
return (this);
}
/*******************************************************************************
** Getter for snapshots.
*******************************************************************************/
public Integer getSnapshots()
{
return (this.snapshots);
}
/*******************************************************************************
** Setter for snapshots.
*******************************************************************************/
public void setSnapshots(Integer snapshots)
{
this.snapshots = snapshots;
}
/*******************************************************************************
** Fluent setter for snapshots.
*******************************************************************************/
public DbPerfQueryDaily withSnapshots(Integer snapshots)
{
this.snapshots = snapshots;
return (this);
}
}
@@ -1,488 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.math.BigDecimal;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Query Snapshots persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfQuerySnapshot extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_query_snapshot";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField
private Integer snapshotId;
@QField
private Integer statementId;
@QField
private Instant capturedAt;
@QField
private Long calls;
@QField
private BigDecimal totalMs;
@QField
private BigDecimal meanMs;
@QField
private BigDecimal maxMs;
@QField
private Long rowsCount;
@QField
private Long sharedBlksHit;
@QField
private Long sharedBlksRead;
@QField
private BigDecimal hitRatio;
@QField
private Long tempBlksWritten;
@QField
private BigDecimal shareOfDbTime;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfQuerySnapshot()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfQuerySnapshot(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfQuerySnapshot withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for snapshotId.
*******************************************************************************/
public Integer getSnapshotId()
{
return (this.snapshotId);
}
/*******************************************************************************
** Setter for snapshotId.
*******************************************************************************/
public void setSnapshotId(Integer snapshotId)
{
this.snapshotId = snapshotId;
}
/*******************************************************************************
** Fluent setter for snapshotId.
*******************************************************************************/
public DbPerfQuerySnapshot withSnapshotId(Integer snapshotId)
{
this.snapshotId = snapshotId;
return (this);
}
/*******************************************************************************
** Getter for statementId.
*******************************************************************************/
public Integer getStatementId()
{
return (this.statementId);
}
/*******************************************************************************
** Setter for statementId.
*******************************************************************************/
public void setStatementId(Integer statementId)
{
this.statementId = statementId;
}
/*******************************************************************************
** Fluent setter for statementId.
*******************************************************************************/
public DbPerfQuerySnapshot withStatementId(Integer statementId)
{
this.statementId = statementId;
return (this);
}
/*******************************************************************************
** Getter for capturedAt.
*******************************************************************************/
public Instant getCapturedAt()
{
return (this.capturedAt);
}
/*******************************************************************************
** Setter for capturedAt.
*******************************************************************************/
public void setCapturedAt(Instant capturedAt)
{
this.capturedAt = capturedAt;
}
/*******************************************************************************
** Fluent setter for capturedAt.
*******************************************************************************/
public DbPerfQuerySnapshot withCapturedAt(Instant capturedAt)
{
this.capturedAt = capturedAt;
return (this);
}
/*******************************************************************************
** Getter for calls.
*******************************************************************************/
public Long getCalls()
{
return (this.calls);
}
/*******************************************************************************
** Setter for calls.
*******************************************************************************/
public void setCalls(Long calls)
{
this.calls = calls;
}
/*******************************************************************************
** Fluent setter for calls.
*******************************************************************************/
public DbPerfQuerySnapshot withCalls(Long calls)
{
this.calls = calls;
return (this);
}
/*******************************************************************************
** Getter for totalMs.
*******************************************************************************/
public BigDecimal getTotalMs()
{
return (this.totalMs);
}
/*******************************************************************************
** Setter for totalMs.
*******************************************************************************/
public void setTotalMs(BigDecimal totalMs)
{
this.totalMs = totalMs;
}
/*******************************************************************************
** Fluent setter for totalMs.
*******************************************************************************/
public DbPerfQuerySnapshot withTotalMs(BigDecimal totalMs)
{
this.totalMs = totalMs;
return (this);
}
/*******************************************************************************
** Getter for meanMs.
*******************************************************************************/
public BigDecimal getMeanMs()
{
return (this.meanMs);
}
/*******************************************************************************
** Setter for meanMs.
*******************************************************************************/
public void setMeanMs(BigDecimal meanMs)
{
this.meanMs = meanMs;
}
/*******************************************************************************
** Fluent setter for meanMs.
*******************************************************************************/
public DbPerfQuerySnapshot withMeanMs(BigDecimal meanMs)
{
this.meanMs = meanMs;
return (this);
}
/*******************************************************************************
** Getter for maxMs.
*******************************************************************************/
public BigDecimal getMaxMs()
{
return (this.maxMs);
}
/*******************************************************************************
** Setter for maxMs.
*******************************************************************************/
public void setMaxMs(BigDecimal maxMs)
{
this.maxMs = maxMs;
}
/*******************************************************************************
** Fluent setter for maxMs.
*******************************************************************************/
public DbPerfQuerySnapshot withMaxMs(BigDecimal maxMs)
{
this.maxMs = maxMs;
return (this);
}
/*******************************************************************************
** Getter for rowsCount.
*******************************************************************************/
public Long getRowsCount()
{
return (this.rowsCount);
}
/*******************************************************************************
** Setter for rowsCount.
*******************************************************************************/
public void setRowsCount(Long rowsCount)
{
this.rowsCount = rowsCount;
}
/*******************************************************************************
** Fluent setter for rowsCount.
*******************************************************************************/
public DbPerfQuerySnapshot withRowsCount(Long rowsCount)
{
this.rowsCount = rowsCount;
return (this);
}
/*******************************************************************************
** Getter for sharedBlksHit.
*******************************************************************************/
public Long getSharedBlksHit()
{
return (this.sharedBlksHit);
}
/*******************************************************************************
** Setter for sharedBlksHit.
*******************************************************************************/
public void setSharedBlksHit(Long sharedBlksHit)
{
this.sharedBlksHit = sharedBlksHit;
}
/*******************************************************************************
** Fluent setter for sharedBlksHit.
*******************************************************************************/
public DbPerfQuerySnapshot withSharedBlksHit(Long sharedBlksHit)
{
this.sharedBlksHit = sharedBlksHit;
return (this);
}
/*******************************************************************************
** Getter for sharedBlksRead.
*******************************************************************************/
public Long getSharedBlksRead()
{
return (this.sharedBlksRead);
}
/*******************************************************************************
** Setter for sharedBlksRead.
*******************************************************************************/
public void setSharedBlksRead(Long sharedBlksRead)
{
this.sharedBlksRead = sharedBlksRead;
}
/*******************************************************************************
** Fluent setter for sharedBlksRead.
*******************************************************************************/
public DbPerfQuerySnapshot withSharedBlksRead(Long sharedBlksRead)
{
this.sharedBlksRead = sharedBlksRead;
return (this);
}
/*******************************************************************************
** Getter for hitRatio.
*******************************************************************************/
public BigDecimal getHitRatio()
{
return (this.hitRatio);
}
/*******************************************************************************
** Setter for hitRatio.
*******************************************************************************/
public void setHitRatio(BigDecimal hitRatio)
{
this.hitRatio = hitRatio;
}
/*******************************************************************************
** Fluent setter for hitRatio.
*******************************************************************************/
public DbPerfQuerySnapshot withHitRatio(BigDecimal hitRatio)
{
this.hitRatio = hitRatio;
return (this);
}
/*******************************************************************************
** Getter for tempBlksWritten.
*******************************************************************************/
public Long getTempBlksWritten()
{
return (this.tempBlksWritten);
}
/*******************************************************************************
** Setter for tempBlksWritten.
*******************************************************************************/
public void setTempBlksWritten(Long tempBlksWritten)
{
this.tempBlksWritten = tempBlksWritten;
}
/*******************************************************************************
** Fluent setter for tempBlksWritten.
*******************************************************************************/
public DbPerfQuerySnapshot withTempBlksWritten(Long tempBlksWritten)
{
this.tempBlksWritten = tempBlksWritten;
return (this);
}
/*******************************************************************************
** Getter for shareOfDbTime.
*******************************************************************************/
public BigDecimal getShareOfDbTime()
{
return (this.shareOfDbTime);
}
/*******************************************************************************
** Setter for shareOfDbTime.
*******************************************************************************/
public void setShareOfDbTime(BigDecimal shareOfDbTime)
{
this.shareOfDbTime = shareOfDbTime;
}
/*******************************************************************************
** Fluent setter for shareOfDbTime.
*******************************************************************************/
public DbPerfQuerySnapshot withShareOfDbTime(BigDecimal shareOfDbTime)
{
this.shareOfDbTime = shareOfDbTime;
return (this);
}
}
@@ -1,456 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Repeated Statements persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfRepeatPattern extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_repeat_pattern";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField
private Instant windowStart;
@QField
private Instant windowEnd;
@QField(maxLength = 200)
private String podName;
@QField(maxLength = 20)
private String unitKind;
@QField(maxLength = 300)
private String unitName;
@QField(maxLength = 200)
private String processName;
@QField(maxLength = 200)
private String tableName;
@QField(maxLength = 64)
private String fingerprint;
@QField(maxLength = 4000)
private String sqlText;
@QField
private Long unitsAffected;
@QField
private Integer maxRepeats;
@QField
private Long totalRepeats;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfRepeatPattern()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfRepeatPattern(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfRepeatPattern withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for windowStart.
*******************************************************************************/
public Instant getWindowStart()
{
return (this.windowStart);
}
/*******************************************************************************
** Setter for windowStart.
*******************************************************************************/
public void setWindowStart(Instant windowStart)
{
this.windowStart = windowStart;
}
/*******************************************************************************
** Fluent setter for windowStart.
*******************************************************************************/
public DbPerfRepeatPattern withWindowStart(Instant windowStart)
{
this.windowStart = windowStart;
return (this);
}
/*******************************************************************************
** Getter for windowEnd.
*******************************************************************************/
public Instant getWindowEnd()
{
return (this.windowEnd);
}
/*******************************************************************************
** Setter for windowEnd.
*******************************************************************************/
public void setWindowEnd(Instant windowEnd)
{
this.windowEnd = windowEnd;
}
/*******************************************************************************
** Fluent setter for windowEnd.
*******************************************************************************/
public DbPerfRepeatPattern withWindowEnd(Instant windowEnd)
{
this.windowEnd = windowEnd;
return (this);
}
/*******************************************************************************
** Getter for podName.
*******************************************************************************/
public String getPodName()
{
return (this.podName);
}
/*******************************************************************************
** Setter for podName.
*******************************************************************************/
public void setPodName(String podName)
{
this.podName = podName;
}
/*******************************************************************************
** Fluent setter for podName.
*******************************************************************************/
public DbPerfRepeatPattern withPodName(String podName)
{
this.podName = podName;
return (this);
}
/*******************************************************************************
** Getter for unitKind.
*******************************************************************************/
public String getUnitKind()
{
return (this.unitKind);
}
/*******************************************************************************
** Setter for unitKind.
*******************************************************************************/
public void setUnitKind(String unitKind)
{
this.unitKind = unitKind;
}
/*******************************************************************************
** Fluent setter for unitKind.
*******************************************************************************/
public DbPerfRepeatPattern withUnitKind(String unitKind)
{
this.unitKind = unitKind;
return (this);
}
/*******************************************************************************
** Getter for unitName.
*******************************************************************************/
public String getUnitName()
{
return (this.unitName);
}
/*******************************************************************************
** Setter for unitName.
*******************************************************************************/
public void setUnitName(String unitName)
{
this.unitName = unitName;
}
/*******************************************************************************
** Fluent setter for unitName.
*******************************************************************************/
public DbPerfRepeatPattern withUnitName(String unitName)
{
this.unitName = unitName;
return (this);
}
/*******************************************************************************
** Getter for processName.
*******************************************************************************/
public String getProcessName()
{
return (this.processName);
}
/*******************************************************************************
** Setter for processName.
*******************************************************************************/
public void setProcessName(String processName)
{
this.processName = processName;
}
/*******************************************************************************
** Fluent setter for processName.
*******************************************************************************/
public DbPerfRepeatPattern withProcessName(String processName)
{
this.processName = processName;
return (this);
}
/*******************************************************************************
** Getter for tableName.
*******************************************************************************/
public String getTableName()
{
return (this.tableName);
}
/*******************************************************************************
** Setter for tableName.
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName.
*******************************************************************************/
public DbPerfRepeatPattern withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Getter for fingerprint.
*******************************************************************************/
public String getFingerprint()
{
return (this.fingerprint);
}
/*******************************************************************************
** Setter for fingerprint.
*******************************************************************************/
public void setFingerprint(String fingerprint)
{
this.fingerprint = fingerprint;
}
/*******************************************************************************
** Fluent setter for fingerprint.
*******************************************************************************/
public DbPerfRepeatPattern withFingerprint(String fingerprint)
{
this.fingerprint = fingerprint;
return (this);
}
/*******************************************************************************
** Getter for sqlText.
*******************************************************************************/
public String getSqlText()
{
return (this.sqlText);
}
/*******************************************************************************
** Setter for sqlText.
*******************************************************************************/
public void setSqlText(String sqlText)
{
this.sqlText = sqlText;
}
/*******************************************************************************
** Fluent setter for sqlText.
*******************************************************************************/
public DbPerfRepeatPattern withSqlText(String sqlText)
{
this.sqlText = sqlText;
return (this);
}
/*******************************************************************************
** Getter for unitsAffected.
*******************************************************************************/
public Long getUnitsAffected()
{
return (this.unitsAffected);
}
/*******************************************************************************
** Setter for unitsAffected.
*******************************************************************************/
public void setUnitsAffected(Long unitsAffected)
{
this.unitsAffected = unitsAffected;
}
/*******************************************************************************
** Fluent setter for unitsAffected.
*******************************************************************************/
public DbPerfRepeatPattern withUnitsAffected(Long unitsAffected)
{
this.unitsAffected = unitsAffected;
return (this);
}
/*******************************************************************************
** Getter for maxRepeats.
*******************************************************************************/
public Integer getMaxRepeats()
{
return (this.maxRepeats);
}
/*******************************************************************************
** Setter for maxRepeats.
*******************************************************************************/
public void setMaxRepeats(Integer maxRepeats)
{
this.maxRepeats = maxRepeats;
}
/*******************************************************************************
** Fluent setter for maxRepeats.
*******************************************************************************/
public DbPerfRepeatPattern withMaxRepeats(Integer maxRepeats)
{
this.maxRepeats = maxRepeats;
return (this);
}
/*******************************************************************************
** Getter for totalRepeats.
*******************************************************************************/
public Long getTotalRepeats()
{
return (this.totalRepeats);
}
/*******************************************************************************
** Setter for totalRepeats.
*******************************************************************************/
public void setTotalRepeats(Long totalRepeats)
{
this.totalRepeats = totalRepeats;
}
/*******************************************************************************
** Fluent setter for totalRepeats.
*******************************************************************************/
public DbPerfRepeatPattern withTotalRepeats(Long totalRepeats)
{
this.totalRepeats = totalRepeats;
return (this);
}
}
@@ -1,426 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.math.BigDecimal;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Suggestion Rules persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfRule extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_rule";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(isRequired = true, maxLength = 40)
private String code;
@QField(maxLength = 100)
private String label;
@QField(maxLength = 1000)
private String description;
@QField
private Boolean enabled;
@QField(maxLength = 20)
private String severity;
@QField
private BigDecimal threshold1;
@QField
private BigDecimal threshold2;
@QField(maxLength = 100)
private String threshold1Label;
@QField(maxLength = 100)
private String threshold2Label;
@QField
private Instant createDate;
@QField
private Instant modifyDate;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfRule()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfRule(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfRule withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for code.
*******************************************************************************/
public String getCode()
{
return (this.code);
}
/*******************************************************************************
** Setter for code.
*******************************************************************************/
public void setCode(String code)
{
this.code = code;
}
/*******************************************************************************
** Fluent setter for code.
*******************************************************************************/
public DbPerfRule withCode(String code)
{
this.code = code;
return (this);
}
/*******************************************************************************
** Getter for label.
*******************************************************************************/
public String getLabel()
{
return (this.label);
}
/*******************************************************************************
** Setter for label.
*******************************************************************************/
public void setLabel(String label)
{
this.label = label;
}
/*******************************************************************************
** Fluent setter for label.
*******************************************************************************/
public DbPerfRule withLabel(String label)
{
this.label = label;
return (this);
}
/*******************************************************************************
** Getter for description.
*******************************************************************************/
public String getDescription()
{
return (this.description);
}
/*******************************************************************************
** Setter for description.
*******************************************************************************/
public void setDescription(String description)
{
this.description = description;
}
/*******************************************************************************
** Fluent setter for description.
*******************************************************************************/
public DbPerfRule withDescription(String description)
{
this.description = description;
return (this);
}
/*******************************************************************************
** Getter for enabled.
*******************************************************************************/
public Boolean getEnabled()
{
return (this.enabled);
}
/*******************************************************************************
** Setter for enabled.
*******************************************************************************/
public void setEnabled(Boolean enabled)
{
this.enabled = enabled;
}
/*******************************************************************************
** Fluent setter for enabled.
*******************************************************************************/
public DbPerfRule withEnabled(Boolean enabled)
{
this.enabled = enabled;
return (this);
}
/*******************************************************************************
** Getter for severity.
*******************************************************************************/
public String getSeverity()
{
return (this.severity);
}
/*******************************************************************************
** Setter for severity.
*******************************************************************************/
public void setSeverity(String severity)
{
this.severity = severity;
}
/*******************************************************************************
** Fluent setter for severity.
*******************************************************************************/
public DbPerfRule withSeverity(String severity)
{
this.severity = severity;
return (this);
}
/*******************************************************************************
** Getter for threshold1.
*******************************************************************************/
public BigDecimal getThreshold1()
{
return (this.threshold1);
}
/*******************************************************************************
** Setter for threshold1.
*******************************************************************************/
public void setThreshold1(BigDecimal threshold1)
{
this.threshold1 = threshold1;
}
/*******************************************************************************
** Fluent setter for threshold1.
*******************************************************************************/
public DbPerfRule withThreshold1(BigDecimal threshold1)
{
this.threshold1 = threshold1;
return (this);
}
/*******************************************************************************
** Getter for threshold2.
*******************************************************************************/
public BigDecimal getThreshold2()
{
return (this.threshold2);
}
/*******************************************************************************
** Setter for threshold2.
*******************************************************************************/
public void setThreshold2(BigDecimal threshold2)
{
this.threshold2 = threshold2;
}
/*******************************************************************************
** Fluent setter for threshold2.
*******************************************************************************/
public DbPerfRule withThreshold2(BigDecimal threshold2)
{
this.threshold2 = threshold2;
return (this);
}
/*******************************************************************************
** Getter for threshold1Label.
*******************************************************************************/
public String getThreshold1Label()
{
return (this.threshold1Label);
}
/*******************************************************************************
** Setter for threshold1Label.
*******************************************************************************/
public void setThreshold1Label(String threshold1Label)
{
this.threshold1Label = threshold1Label;
}
/*******************************************************************************
** Fluent setter for threshold1Label.
*******************************************************************************/
public DbPerfRule withThreshold1Label(String threshold1Label)
{
this.threshold1Label = threshold1Label;
return (this);
}
/*******************************************************************************
** Getter for threshold2Label.
*******************************************************************************/
public String getThreshold2Label()
{
return (this.threshold2Label);
}
/*******************************************************************************
** Setter for threshold2Label.
*******************************************************************************/
public void setThreshold2Label(String threshold2Label)
{
this.threshold2Label = threshold2Label;
}
/*******************************************************************************
** Fluent setter for threshold2Label.
*******************************************************************************/
public DbPerfRule withThreshold2Label(String threshold2Label)
{
this.threshold2Label = threshold2Label;
return (this);
}
/*******************************************************************************
** Getter for createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (this.createDate);
}
/*******************************************************************************
** Setter for createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Fluent setter for createDate.
*******************************************************************************/
public DbPerfRule withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Getter for modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (this.modifyDate);
}
/*******************************************************************************
** Setter for modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Fluent setter for modifyDate.
*******************************************************************************/
public DbPerfRule withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
}
@@ -1,922 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.math.BigDecimal;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Settings persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfSettings extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_settings";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(isRequired = true, maxLength = 100)
private String backendName;
@QField
private Boolean enabled;
@QField(maxLength = 20)
private String provider;
@QField
private Integer snapshotIntervalMinutes;
@QField
private Integer tableSnapshotMinutes;
@QField
private Integer detailRetentionDays;
@QField
private Integer rollupRetentionDays;
@QField
private Integer topStatementsPerSnapshot;
@QField
private Integer activityLongRunningSeconds;
@QField
private Integer idleInTransactionSeconds;
@QField
private Boolean attributionEnabled;
@QField
private BigDecimal attributionSampleRate;
@QField
private Integer attributionFlushSeconds;
@QField
private Integer attributionMaxKeys;
@QField
private Integer nPlusOneThreshold;
@QField
private Integer explainTimeoutMs;
@QField
private Boolean alertsEnabled;
@QField
private BigDecimal spikeFactor;
@QField
private Integer spikeMinTotalMs;
@QField
private Integer connectionAlertPercent;
@QField
private Integer blockedAlertSeconds;
@QField
private Instant lastSnapshotAt;
@QField
private Instant lastTableSnapshotAt;
@QField(maxLength = 30)
private String statsAccess;
@QField(maxLength = 500)
private String statsAccessMessage;
@QField
private Instant createDate;
@QField
private Instant modifyDate;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfSettings()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfSettings(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfSettings withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for backendName.
*******************************************************************************/
public String getBackendName()
{
return (this.backendName);
}
/*******************************************************************************
** Setter for backendName.
*******************************************************************************/
public void setBackendName(String backendName)
{
this.backendName = backendName;
}
/*******************************************************************************
** Fluent setter for backendName.
*******************************************************************************/
public DbPerfSettings withBackendName(String backendName)
{
this.backendName = backendName;
return (this);
}
/*******************************************************************************
** Getter for enabled.
*******************************************************************************/
public Boolean getEnabled()
{
return (this.enabled);
}
/*******************************************************************************
** Setter for enabled.
*******************************************************************************/
public void setEnabled(Boolean enabled)
{
this.enabled = enabled;
}
/*******************************************************************************
** Fluent setter for enabled.
*******************************************************************************/
public DbPerfSettings withEnabled(Boolean enabled)
{
this.enabled = enabled;
return (this);
}
/*******************************************************************************
** Getter for provider.
*******************************************************************************/
public String getProvider()
{
return (this.provider);
}
/*******************************************************************************
** Setter for provider.
*******************************************************************************/
public void setProvider(String provider)
{
this.provider = provider;
}
/*******************************************************************************
** Fluent setter for provider.
*******************************************************************************/
public DbPerfSettings withProvider(String provider)
{
this.provider = provider;
return (this);
}
/*******************************************************************************
** Getter for snapshotIntervalMinutes.
*******************************************************************************/
public Integer getSnapshotIntervalMinutes()
{
return (this.snapshotIntervalMinutes);
}
/*******************************************************************************
** Setter for snapshotIntervalMinutes.
*******************************************************************************/
public void setSnapshotIntervalMinutes(Integer snapshotIntervalMinutes)
{
this.snapshotIntervalMinutes = snapshotIntervalMinutes;
}
/*******************************************************************************
** Fluent setter for snapshotIntervalMinutes.
*******************************************************************************/
public DbPerfSettings withSnapshotIntervalMinutes(Integer snapshotIntervalMinutes)
{
this.snapshotIntervalMinutes = snapshotIntervalMinutes;
return (this);
}
/*******************************************************************************
** Getter for tableSnapshotMinutes.
*******************************************************************************/
public Integer getTableSnapshotMinutes()
{
return (this.tableSnapshotMinutes);
}
/*******************************************************************************
** Setter for tableSnapshotMinutes.
*******************************************************************************/
public void setTableSnapshotMinutes(Integer tableSnapshotMinutes)
{
this.tableSnapshotMinutes = tableSnapshotMinutes;
}
/*******************************************************************************
** Fluent setter for tableSnapshotMinutes.
*******************************************************************************/
public DbPerfSettings withTableSnapshotMinutes(Integer tableSnapshotMinutes)
{
this.tableSnapshotMinutes = tableSnapshotMinutes;
return (this);
}
/*******************************************************************************
** Getter for detailRetentionDays.
*******************************************************************************/
public Integer getDetailRetentionDays()
{
return (this.detailRetentionDays);
}
/*******************************************************************************
** Setter for detailRetentionDays.
*******************************************************************************/
public void setDetailRetentionDays(Integer detailRetentionDays)
{
this.detailRetentionDays = detailRetentionDays;
}
/*******************************************************************************
** Fluent setter for detailRetentionDays.
*******************************************************************************/
public DbPerfSettings withDetailRetentionDays(Integer detailRetentionDays)
{
this.detailRetentionDays = detailRetentionDays;
return (this);
}
/*******************************************************************************
** Getter for rollupRetentionDays.
*******************************************************************************/
public Integer getRollupRetentionDays()
{
return (this.rollupRetentionDays);
}
/*******************************************************************************
** Setter for rollupRetentionDays.
*******************************************************************************/
public void setRollupRetentionDays(Integer rollupRetentionDays)
{
this.rollupRetentionDays = rollupRetentionDays;
}
/*******************************************************************************
** Fluent setter for rollupRetentionDays.
*******************************************************************************/
public DbPerfSettings withRollupRetentionDays(Integer rollupRetentionDays)
{
this.rollupRetentionDays = rollupRetentionDays;
return (this);
}
/*******************************************************************************
** Getter for topStatementsPerSnapshot.
*******************************************************************************/
public Integer getTopStatementsPerSnapshot()
{
return (this.topStatementsPerSnapshot);
}
/*******************************************************************************
** Setter for topStatementsPerSnapshot.
*******************************************************************************/
public void setTopStatementsPerSnapshot(Integer topStatementsPerSnapshot)
{
this.topStatementsPerSnapshot = topStatementsPerSnapshot;
}
/*******************************************************************************
** Fluent setter for topStatementsPerSnapshot.
*******************************************************************************/
public DbPerfSettings withTopStatementsPerSnapshot(Integer topStatementsPerSnapshot)
{
this.topStatementsPerSnapshot = topStatementsPerSnapshot;
return (this);
}
/*******************************************************************************
** Getter for activityLongRunningSeconds.
*******************************************************************************/
public Integer getActivityLongRunningSeconds()
{
return (this.activityLongRunningSeconds);
}
/*******************************************************************************
** Setter for activityLongRunningSeconds.
*******************************************************************************/
public void setActivityLongRunningSeconds(Integer activityLongRunningSeconds)
{
this.activityLongRunningSeconds = activityLongRunningSeconds;
}
/*******************************************************************************
** Fluent setter for activityLongRunningSeconds.
*******************************************************************************/
public DbPerfSettings withActivityLongRunningSeconds(Integer activityLongRunningSeconds)
{
this.activityLongRunningSeconds = activityLongRunningSeconds;
return (this);
}
/*******************************************************************************
** Getter for idleInTransactionSeconds.
*******************************************************************************/
public Integer getIdleInTransactionSeconds()
{
return (this.idleInTransactionSeconds);
}
/*******************************************************************************
** Setter for idleInTransactionSeconds.
*******************************************************************************/
public void setIdleInTransactionSeconds(Integer idleInTransactionSeconds)
{
this.idleInTransactionSeconds = idleInTransactionSeconds;
}
/*******************************************************************************
** Fluent setter for idleInTransactionSeconds.
*******************************************************************************/
public DbPerfSettings withIdleInTransactionSeconds(Integer idleInTransactionSeconds)
{
this.idleInTransactionSeconds = idleInTransactionSeconds;
return (this);
}
/*******************************************************************************
** Getter for attributionEnabled.
*******************************************************************************/
public Boolean getAttributionEnabled()
{
return (this.attributionEnabled);
}
/*******************************************************************************
** Setter for attributionEnabled.
*******************************************************************************/
public void setAttributionEnabled(Boolean attributionEnabled)
{
this.attributionEnabled = attributionEnabled;
}
/*******************************************************************************
** Fluent setter for attributionEnabled.
*******************************************************************************/
public DbPerfSettings withAttributionEnabled(Boolean attributionEnabled)
{
this.attributionEnabled = attributionEnabled;
return (this);
}
/*******************************************************************************
** Getter for attributionSampleRate.
*******************************************************************************/
public BigDecimal getAttributionSampleRate()
{
return (this.attributionSampleRate);
}
/*******************************************************************************
** Setter for attributionSampleRate.
*******************************************************************************/
public void setAttributionSampleRate(BigDecimal attributionSampleRate)
{
this.attributionSampleRate = attributionSampleRate;
}
/*******************************************************************************
** Fluent setter for attributionSampleRate.
*******************************************************************************/
public DbPerfSettings withAttributionSampleRate(BigDecimal attributionSampleRate)
{
this.attributionSampleRate = attributionSampleRate;
return (this);
}
/*******************************************************************************
** Getter for attributionFlushSeconds.
*******************************************************************************/
public Integer getAttributionFlushSeconds()
{
return (this.attributionFlushSeconds);
}
/*******************************************************************************
** Setter for attributionFlushSeconds.
*******************************************************************************/
public void setAttributionFlushSeconds(Integer attributionFlushSeconds)
{
this.attributionFlushSeconds = attributionFlushSeconds;
}
/*******************************************************************************
** Fluent setter for attributionFlushSeconds.
*******************************************************************************/
public DbPerfSettings withAttributionFlushSeconds(Integer attributionFlushSeconds)
{
this.attributionFlushSeconds = attributionFlushSeconds;
return (this);
}
/*******************************************************************************
** Getter for attributionMaxKeys.
*******************************************************************************/
public Integer getAttributionMaxKeys()
{
return (this.attributionMaxKeys);
}
/*******************************************************************************
** Setter for attributionMaxKeys.
*******************************************************************************/
public void setAttributionMaxKeys(Integer attributionMaxKeys)
{
this.attributionMaxKeys = attributionMaxKeys;
}
/*******************************************************************************
** Fluent setter for attributionMaxKeys.
*******************************************************************************/
public DbPerfSettings withAttributionMaxKeys(Integer attributionMaxKeys)
{
this.attributionMaxKeys = attributionMaxKeys;
return (this);
}
/*******************************************************************************
** Getter for nPlusOneThreshold.
*******************************************************************************/
public Integer getNPlusOneThreshold()
{
return (this.nPlusOneThreshold);
}
/*******************************************************************************
** Setter for nPlusOneThreshold.
*******************************************************************************/
public void setNPlusOneThreshold(Integer nPlusOneThreshold)
{
this.nPlusOneThreshold = nPlusOneThreshold;
}
/*******************************************************************************
** Fluent setter for nPlusOneThreshold.
*******************************************************************************/
public DbPerfSettings withNPlusOneThreshold(Integer nPlusOneThreshold)
{
this.nPlusOneThreshold = nPlusOneThreshold;
return (this);
}
/*******************************************************************************
** Getter for explainTimeoutMs.
*******************************************************************************/
public Integer getExplainTimeoutMs()
{
return (this.explainTimeoutMs);
}
/*******************************************************************************
** Setter for explainTimeoutMs.
*******************************************************************************/
public void setExplainTimeoutMs(Integer explainTimeoutMs)
{
this.explainTimeoutMs = explainTimeoutMs;
}
/*******************************************************************************
** Fluent setter for explainTimeoutMs.
*******************************************************************************/
public DbPerfSettings withExplainTimeoutMs(Integer explainTimeoutMs)
{
this.explainTimeoutMs = explainTimeoutMs;
return (this);
}
/*******************************************************************************
** Getter for alertsEnabled.
*******************************************************************************/
public Boolean getAlertsEnabled()
{
return (this.alertsEnabled);
}
/*******************************************************************************
** Setter for alertsEnabled.
*******************************************************************************/
public void setAlertsEnabled(Boolean alertsEnabled)
{
this.alertsEnabled = alertsEnabled;
}
/*******************************************************************************
** Fluent setter for alertsEnabled.
*******************************************************************************/
public DbPerfSettings withAlertsEnabled(Boolean alertsEnabled)
{
this.alertsEnabled = alertsEnabled;
return (this);
}
/*******************************************************************************
** Getter for spikeFactor.
*******************************************************************************/
public BigDecimal getSpikeFactor()
{
return (this.spikeFactor);
}
/*******************************************************************************
** Setter for spikeFactor.
*******************************************************************************/
public void setSpikeFactor(BigDecimal spikeFactor)
{
this.spikeFactor = spikeFactor;
}
/*******************************************************************************
** Fluent setter for spikeFactor.
*******************************************************************************/
public DbPerfSettings withSpikeFactor(BigDecimal spikeFactor)
{
this.spikeFactor = spikeFactor;
return (this);
}
/*******************************************************************************
** Getter for spikeMinTotalMs.
*******************************************************************************/
public Integer getSpikeMinTotalMs()
{
return (this.spikeMinTotalMs);
}
/*******************************************************************************
** Setter for spikeMinTotalMs.
*******************************************************************************/
public void setSpikeMinTotalMs(Integer spikeMinTotalMs)
{
this.spikeMinTotalMs = spikeMinTotalMs;
}
/*******************************************************************************
** Fluent setter for spikeMinTotalMs.
*******************************************************************************/
public DbPerfSettings withSpikeMinTotalMs(Integer spikeMinTotalMs)
{
this.spikeMinTotalMs = spikeMinTotalMs;
return (this);
}
/*******************************************************************************
** Getter for connectionAlertPercent.
*******************************************************************************/
public Integer getConnectionAlertPercent()
{
return (this.connectionAlertPercent);
}
/*******************************************************************************
** Setter for connectionAlertPercent.
*******************************************************************************/
public void setConnectionAlertPercent(Integer connectionAlertPercent)
{
this.connectionAlertPercent = connectionAlertPercent;
}
/*******************************************************************************
** Fluent setter for connectionAlertPercent.
*******************************************************************************/
public DbPerfSettings withConnectionAlertPercent(Integer connectionAlertPercent)
{
this.connectionAlertPercent = connectionAlertPercent;
return (this);
}
/*******************************************************************************
** Getter for blockedAlertSeconds.
*******************************************************************************/
public Integer getBlockedAlertSeconds()
{
return (this.blockedAlertSeconds);
}
/*******************************************************************************
** Setter for blockedAlertSeconds.
*******************************************************************************/
public void setBlockedAlertSeconds(Integer blockedAlertSeconds)
{
this.blockedAlertSeconds = blockedAlertSeconds;
}
/*******************************************************************************
** Fluent setter for blockedAlertSeconds.
*******************************************************************************/
public DbPerfSettings withBlockedAlertSeconds(Integer blockedAlertSeconds)
{
this.blockedAlertSeconds = blockedAlertSeconds;
return (this);
}
/*******************************************************************************
** Getter for lastSnapshotAt.
*******************************************************************************/
public Instant getLastSnapshotAt()
{
return (this.lastSnapshotAt);
}
/*******************************************************************************
** Setter for lastSnapshotAt.
*******************************************************************************/
public void setLastSnapshotAt(Instant lastSnapshotAt)
{
this.lastSnapshotAt = lastSnapshotAt;
}
/*******************************************************************************
** Fluent setter for lastSnapshotAt.
*******************************************************************************/
public DbPerfSettings withLastSnapshotAt(Instant lastSnapshotAt)
{
this.lastSnapshotAt = lastSnapshotAt;
return (this);
}
/*******************************************************************************
** Getter for lastTableSnapshotAt.
*******************************************************************************/
public Instant getLastTableSnapshotAt()
{
return (this.lastTableSnapshotAt);
}
/*******************************************************************************
** Setter for lastTableSnapshotAt.
*******************************************************************************/
public void setLastTableSnapshotAt(Instant lastTableSnapshotAt)
{
this.lastTableSnapshotAt = lastTableSnapshotAt;
}
/*******************************************************************************
** Fluent setter for lastTableSnapshotAt.
*******************************************************************************/
public DbPerfSettings withLastTableSnapshotAt(Instant lastTableSnapshotAt)
{
this.lastTableSnapshotAt = lastTableSnapshotAt;
return (this);
}
/*******************************************************************************
** Getter for statsAccess.
*******************************************************************************/
public String getStatsAccess()
{
return (this.statsAccess);
}
/*******************************************************************************
** Setter for statsAccess.
*******************************************************************************/
public void setStatsAccess(String statsAccess)
{
this.statsAccess = statsAccess;
}
/*******************************************************************************
** Fluent setter for statsAccess.
*******************************************************************************/
public DbPerfSettings withStatsAccess(String statsAccess)
{
this.statsAccess = statsAccess;
return (this);
}
/*******************************************************************************
** Getter for statsAccessMessage.
*******************************************************************************/
public String getStatsAccessMessage()
{
return (this.statsAccessMessage);
}
/*******************************************************************************
** Setter for statsAccessMessage.
*******************************************************************************/
public void setStatsAccessMessage(String statsAccessMessage)
{
this.statsAccessMessage = statsAccessMessage;
}
/*******************************************************************************
** Fluent setter for statsAccessMessage.
*******************************************************************************/
public DbPerfSettings withStatsAccessMessage(String statsAccessMessage)
{
this.statsAccessMessage = statsAccessMessage;
return (this);
}
/*******************************************************************************
** Getter for createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (this.createDate);
}
/*******************************************************************************
** Setter for createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Fluent setter for createDate.
*******************************************************************************/
public DbPerfSettings withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Getter for modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (this.modifyDate);
}
/*******************************************************************************
** Setter for modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Fluent setter for modifyDate.
*******************************************************************************/
public DbPerfSettings withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
}
File diff suppressed because it is too large. Load diff
@@ -1,829 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.math.BigDecimal;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Queries persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfStatement extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_statement";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField(maxLength = 100)
private String backendName;
@QField
private Long queryId;
@QField(maxLength = 100)
private String dbUser;
@QField
private Boolean toplevel;
@QField(maxLength = 64)
private String fingerprint;
@QField
private String queryText;
@QField(maxLength = 20)
private String operation;
@QField(maxLength = 200)
private String mainTable;
@QField
private Instant firstSeenAt;
@QField
private Instant lastSeenAt;
@QField
private Long lastCalls;
@QField
private BigDecimal lastTotalExecMs;
@QField
private Long lastRows;
@QField
private Long lastSharedBlksHit;
@QField
private Long lastSharedBlksRead;
@QField
private Long lastTempBlksWritten;
@QField
private BigDecimal lastMaxExecMs;
@QField
private Instant lastStatsSince;
@QField
private Long calls24h;
@QField
private BigDecimal totalMs24h;
@QField
private BigDecimal meanMs24h;
@QField
private BigDecimal shareOfDbTime24h;
@QField
private Instant createDate;
@QField
private Instant modifyDate;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfStatement()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfStatement(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfStatement withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for backendName.
*******************************************************************************/
public String getBackendName()
{
return (this.backendName);
}
/*******************************************************************************
** Setter for backendName.
*******************************************************************************/
public void setBackendName(String backendName)
{
this.backendName = backendName;
}
/*******************************************************************************
** Fluent setter for backendName.
*******************************************************************************/
public DbPerfStatement withBackendName(String backendName)
{
this.backendName = backendName;
return (this);
}
/*******************************************************************************
** Getter for queryId.
*******************************************************************************/
public Long getQueryId()
{
return (this.queryId);
}
/*******************************************************************************
** Setter for queryId.
*******************************************************************************/
public void setQueryId(Long queryId)
{
this.queryId = queryId;
}
/*******************************************************************************
** Fluent setter for queryId.
*******************************************************************************/
public DbPerfStatement withQueryId(Long queryId)
{
this.queryId = queryId;
return (this);
}
/*******************************************************************************
** Getter for dbUser.
*******************************************************************************/
public String getDbUser()
{
return (this.dbUser);
}
/*******************************************************************************
** Setter for dbUser.
*******************************************************************************/
public void setDbUser(String dbUser)
{
this.dbUser = dbUser;
}
/*******************************************************************************
** Fluent setter for dbUser.
*******************************************************************************/
public DbPerfStatement withDbUser(String dbUser)
{
this.dbUser = dbUser;
return (this);
}
/*******************************************************************************
** Getter for toplevel.
*******************************************************************************/
public Boolean getToplevel()
{
return (this.toplevel);
}
/*******************************************************************************
** Setter for toplevel.
*******************************************************************************/
public void setToplevel(Boolean toplevel)
{
this.toplevel = toplevel;
}
/*******************************************************************************
** Fluent setter for toplevel.
*******************************************************************************/
public DbPerfStatement withToplevel(Boolean toplevel)
{
this.toplevel = toplevel;
return (this);
}
/*******************************************************************************
** Getter for fingerprint.
*******************************************************************************/
public String getFingerprint()
{
return (this.fingerprint);
}
/*******************************************************************************
** Setter for fingerprint.
*******************************************************************************/
public void setFingerprint(String fingerprint)
{
this.fingerprint = fingerprint;
}
/*******************************************************************************
** Fluent setter for fingerprint.
*******************************************************************************/
public DbPerfStatement withFingerprint(String fingerprint)
{
this.fingerprint = fingerprint;
return (this);
}
/*******************************************************************************
** Getter for queryText.
*******************************************************************************/
public String getQueryText()
{
return (this.queryText);
}
/*******************************************************************************
** Setter for queryText.
*******************************************************************************/
public void setQueryText(String queryText)
{
this.queryText = queryText;
}
/*******************************************************************************
** Fluent setter for queryText.
*******************************************************************************/
public DbPerfStatement withQueryText(String queryText)
{
this.queryText = queryText;
return (this);
}
/*******************************************************************************
** Getter for operation.
*******************************************************************************/
public String getOperation()
{
return (this.operation);
}
/*******************************************************************************
** Setter for operation.
*******************************************************************************/
public void setOperation(String operation)
{
this.operation = operation;
}
/*******************************************************************************
** Fluent setter for operation.
*******************************************************************************/
public DbPerfStatement withOperation(String operation)
{
this.operation = operation;
return (this);
}
/*******************************************************************************
** Getter for mainTable.
*******************************************************************************/
public String getMainTable()
{
return (this.mainTable);
}
/*******************************************************************************
** Setter for mainTable.
*******************************************************************************/
public void setMainTable(String mainTable)
{
this.mainTable = mainTable;
}
/*******************************************************************************
** Fluent setter for mainTable.
*******************************************************************************/
public DbPerfStatement withMainTable(String mainTable)
{
this.mainTable = mainTable;
return (this);
}
/*******************************************************************************
** Getter for firstSeenAt.
*******************************************************************************/
public Instant getFirstSeenAt()
{
return (this.firstSeenAt);
}
/*******************************************************************************
** Setter for firstSeenAt.
*******************************************************************************/
public void setFirstSeenAt(Instant firstSeenAt)
{
this.firstSeenAt = firstSeenAt;
}
/*******************************************************************************
** Fluent setter for firstSeenAt.
*******************************************************************************/
public DbPerfStatement withFirstSeenAt(Instant firstSeenAt)
{
this.firstSeenAt = firstSeenAt;
return (this);
}
/*******************************************************************************
** Getter for lastSeenAt.
*******************************************************************************/
public Instant getLastSeenAt()
{
return (this.lastSeenAt);
}
/*******************************************************************************
** Setter for lastSeenAt.
*******************************************************************************/
public void setLastSeenAt(Instant lastSeenAt)
{
this.lastSeenAt = lastSeenAt;
}
/*******************************************************************************
** Fluent setter for lastSeenAt.
*******************************************************************************/
public DbPerfStatement withLastSeenAt(Instant lastSeenAt)
{
this.lastSeenAt = lastSeenAt;
return (this);
}
/*******************************************************************************
** Getter for lastCalls.
*******************************************************************************/
public Long getLastCalls()
{
return (this.lastCalls);
}
/*******************************************************************************
** Setter for lastCalls.
*******************************************************************************/
public void setLastCalls(Long lastCalls)
{
this.lastCalls = lastCalls;
}
/*******************************************************************************
** Fluent setter for lastCalls.
*******************************************************************************/
public DbPerfStatement withLastCalls(Long lastCalls)
{
this.lastCalls = lastCalls;
return (this);
}
/*******************************************************************************
** Getter for lastTotalExecMs.
*******************************************************************************/
public BigDecimal getLastTotalExecMs()
{
return (this.lastTotalExecMs);
}
/*******************************************************************************
** Setter for lastTotalExecMs.
*******************************************************************************/
public void setLastTotalExecMs(BigDecimal lastTotalExecMs)
{
this.lastTotalExecMs = lastTotalExecMs;
}
/*******************************************************************************
** Fluent setter for lastTotalExecMs.
*******************************************************************************/
public DbPerfStatement withLastTotalExecMs(BigDecimal lastTotalExecMs)
{
this.lastTotalExecMs = lastTotalExecMs;
return (this);
}
/*******************************************************************************
** Getter for lastRows.
*******************************************************************************/
public Long getLastRows()
{
return (this.lastRows);
}
/*******************************************************************************
** Setter for lastRows.
*******************************************************************************/
public void setLastRows(Long lastRows)
{
this.lastRows = lastRows;
}
/*******************************************************************************
** Fluent setter for lastRows.
*******************************************************************************/
public DbPerfStatement withLastRows(Long lastRows)
{
this.lastRows = lastRows;
return (this);
}
/*******************************************************************************
** Getter for lastSharedBlksHit.
*******************************************************************************/
public Long getLastSharedBlksHit()
{
return (this.lastSharedBlksHit);
}
/*******************************************************************************
** Setter for lastSharedBlksHit.
*******************************************************************************/
public void setLastSharedBlksHit(Long lastSharedBlksHit)
{
this.lastSharedBlksHit = lastSharedBlksHit;
}
/*******************************************************************************
** Fluent setter for lastSharedBlksHit.
*******************************************************************************/
public DbPerfStatement withLastSharedBlksHit(Long lastSharedBlksHit)
{
this.lastSharedBlksHit = lastSharedBlksHit;
return (this);
}
/*******************************************************************************
** Getter for lastSharedBlksRead.
*******************************************************************************/
public Long getLastSharedBlksRead()
{
return (this.lastSharedBlksRead);
}
/*******************************************************************************
** Setter for lastSharedBlksRead.
*******************************************************************************/
public void setLastSharedBlksRead(Long lastSharedBlksRead)
{
this.lastSharedBlksRead = lastSharedBlksRead;
}
/*******************************************************************************
** Fluent setter for lastSharedBlksRead.
*******************************************************************************/
public DbPerfStatement withLastSharedBlksRead(Long lastSharedBlksRead)
{
this.lastSharedBlksRead = lastSharedBlksRead;
return (this);
}
/*******************************************************************************
** Getter for lastTempBlksWritten.
*******************************************************************************/
public Long getLastTempBlksWritten()
{
return (this.lastTempBlksWritten);
}
/*******************************************************************************
** Setter for lastTempBlksWritten.
*******************************************************************************/
public void setLastTempBlksWritten(Long lastTempBlksWritten)
{
this.lastTempBlksWritten = lastTempBlksWritten;
}
/*******************************************************************************
** Fluent setter for lastTempBlksWritten.
*******************************************************************************/
public DbPerfStatement withLastTempBlksWritten(Long lastTempBlksWritten)
{
this.lastTempBlksWritten = lastTempBlksWritten;
return (this);
}
/*******************************************************************************
** Getter for lastMaxExecMs.
*******************************************************************************/
public BigDecimal getLastMaxExecMs()
{
return (this.lastMaxExecMs);
}
/*******************************************************************************
** Setter for lastMaxExecMs.
*******************************************************************************/
public void setLastMaxExecMs(BigDecimal lastMaxExecMs)
{
this.lastMaxExecMs = lastMaxExecMs;
}
/*******************************************************************************
** Fluent setter for lastMaxExecMs.
*******************************************************************************/
public DbPerfStatement withLastMaxExecMs(BigDecimal lastMaxExecMs)
{
this.lastMaxExecMs = lastMaxExecMs;
return (this);
}
/*******************************************************************************
** Getter for lastStatsSince.
*******************************************************************************/
public Instant getLastStatsSince()
{
return (this.lastStatsSince);
}
/*******************************************************************************
** Setter for lastStatsSince.
*******************************************************************************/
public void setLastStatsSince(Instant lastStatsSince)
{
this.lastStatsSince = lastStatsSince;
}
/*******************************************************************************
** Fluent setter for lastStatsSince.
*******************************************************************************/
public DbPerfStatement withLastStatsSince(Instant lastStatsSince)
{
this.lastStatsSince = lastStatsSince;
return (this);
}
/*******************************************************************************
** Getter for calls24h.
*******************************************************************************/
public Long getCalls24h()
{
return (this.calls24h);
}
/*******************************************************************************
** Setter for calls24h.
*******************************************************************************/
public void setCalls24h(Long calls24h)
{
this.calls24h = calls24h;
}
/*******************************************************************************
** Fluent setter for calls24h.
*******************************************************************************/
public DbPerfStatement withCalls24h(Long calls24h)
{
this.calls24h = calls24h;
return (this);
}
/*******************************************************************************
** Getter for totalMs24h.
*******************************************************************************/
public BigDecimal getTotalMs24h()
{
return (this.totalMs24h);
}
/*******************************************************************************
** Setter for totalMs24h.
*******************************************************************************/
public void setTotalMs24h(BigDecimal totalMs24h)
{
this.totalMs24h = totalMs24h;
}
/*******************************************************************************
** Fluent setter for totalMs24h.
*******************************************************************************/
public DbPerfStatement withTotalMs24h(BigDecimal totalMs24h)
{
this.totalMs24h = totalMs24h;
return (this);
}
/*******************************************************************************
** Getter for meanMs24h.
*******************************************************************************/
public BigDecimal getMeanMs24h()
{
return (this.meanMs24h);
}
/*******************************************************************************
** Setter for meanMs24h.
*******************************************************************************/
public void setMeanMs24h(BigDecimal meanMs24h)
{
this.meanMs24h = meanMs24h;
}
/*******************************************************************************
** Fluent setter for meanMs24h.
*******************************************************************************/
public DbPerfStatement withMeanMs24h(BigDecimal meanMs24h)
{
this.meanMs24h = meanMs24h;
return (this);
}
/*******************************************************************************
** Getter for shareOfDbTime24h.
*******************************************************************************/
public BigDecimal getShareOfDbTime24h()
{
return (this.shareOfDbTime24h);
}
/*******************************************************************************
** Setter for shareOfDbTime24h.
*******************************************************************************/
public void setShareOfDbTime24h(BigDecimal shareOfDbTime24h)
{
this.shareOfDbTime24h = shareOfDbTime24h;
}
/*******************************************************************************
** Fluent setter for shareOfDbTime24h.
*******************************************************************************/
public DbPerfStatement withShareOfDbTime24h(BigDecimal shareOfDbTime24h)
{
this.shareOfDbTime24h = shareOfDbTime24h;
return (this);
}
/*******************************************************************************
** Getter for createDate.
*******************************************************************************/
public Instant getCreateDate()
{
return (this.createDate);
}
/*******************************************************************************
** Setter for createDate.
*******************************************************************************/
public void setCreateDate(Instant createDate)
{
this.createDate = createDate;
}
/*******************************************************************************
** Fluent setter for createDate.
*******************************************************************************/
public DbPerfStatement withCreateDate(Instant createDate)
{
this.createDate = createDate;
return (this);
}
/*******************************************************************************
** Getter for modifyDate.
*******************************************************************************/
public Instant getModifyDate()
{
return (this.modifyDate);
}
/*******************************************************************************
** Setter for modifyDate.
*******************************************************************************/
public void setModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
}
/*******************************************************************************
** Fluent setter for modifyDate.
*******************************************************************************/
public DbPerfStatement withModifyDate(Instant modifyDate)
{
this.modifyDate = modifyDate;
return (this);
}
}
@@ -1,425 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.time.LocalDate;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Table Daily persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfTableDaily extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_table_daily";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField
private Integer tableStatId;
@QField
private LocalDate day;
@QField
private Long seqScan;
@QField
private Long idxScan;
@QField
private Long seqTupRead;
@QField
private Long nTupIns;
@QField
private Long nTupUpd;
@QField
private Long nTupDel;
@QField
private Long nLiveTup;
@QField
private Long nDeadTup;
@QField
private Long totalBytes;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfTableDaily()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfTableDaily(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfTableDaily withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for tableStatId.
*******************************************************************************/
public Integer getTableStatId()
{
return (this.tableStatId);
}
/*******************************************************************************
** Setter for tableStatId.
*******************************************************************************/
public void setTableStatId(Integer tableStatId)
{
this.tableStatId = tableStatId;
}
/*******************************************************************************
** Fluent setter for tableStatId.
*******************************************************************************/
public DbPerfTableDaily withTableStatId(Integer tableStatId)
{
this.tableStatId = tableStatId;
return (this);
}
/*******************************************************************************
** Getter for day.
*******************************************************************************/
public LocalDate getDay()
{
return (this.day);
}
/*******************************************************************************
** Setter for day.
*******************************************************************************/
public void setDay(LocalDate day)
{
this.day = day;
}
/*******************************************************************************
** Fluent setter for day.
*******************************************************************************/
public DbPerfTableDaily withDay(LocalDate day)
{
this.day = day;
return (this);
}
/*******************************************************************************
** Getter for seqScan.
*******************************************************************************/
public Long getSeqScan()
{
return (this.seqScan);
}
/*******************************************************************************
** Setter for seqScan.
*******************************************************************************/
public void setSeqScan(Long seqScan)
{
this.seqScan = seqScan;
}
/*******************************************************************************
** Fluent setter for seqScan.
*******************************************************************************/
public DbPerfTableDaily withSeqScan(Long seqScan)
{
this.seqScan = seqScan;
return (this);
}
/*******************************************************************************
** Getter for idxScan.
*******************************************************************************/
public Long getIdxScan()
{
return (this.idxScan);
}
/*******************************************************************************
** Setter for idxScan.
*******************************************************************************/
public void setIdxScan(Long idxScan)
{
this.idxScan = idxScan;
}
/*******************************************************************************
** Fluent setter for idxScan.
*******************************************************************************/
public DbPerfTableDaily withIdxScan(Long idxScan)
{
this.idxScan = idxScan;
return (this);
}
/*******************************************************************************
** Getter for seqTupRead.
*******************************************************************************/
public Long getSeqTupRead()
{
return (this.seqTupRead);
}
/*******************************************************************************
** Setter for seqTupRead.
*******************************************************************************/
public void setSeqTupRead(Long seqTupRead)
{
this.seqTupRead = seqTupRead;
}
/*******************************************************************************
** Fluent setter for seqTupRead.
*******************************************************************************/
public DbPerfTableDaily withSeqTupRead(Long seqTupRead)
{
this.seqTupRead = seqTupRead;
return (this);
}
/*******************************************************************************
** Getter for nTupIns.
*******************************************************************************/
public Long getNTupIns()
{
return (this.nTupIns);
}
/*******************************************************************************
** Setter for nTupIns.
*******************************************************************************/
public void setNTupIns(Long nTupIns)
{
this.nTupIns = nTupIns;
}
/*******************************************************************************
** Fluent setter for nTupIns.
*******************************************************************************/
public DbPerfTableDaily withNTupIns(Long nTupIns)
{
this.nTupIns = nTupIns;
return (this);
}
/*******************************************************************************
** Getter for nTupUpd.
*******************************************************************************/
public Long getNTupUpd()
{
return (this.nTupUpd);
}
/*******************************************************************************
** Setter for nTupUpd.
*******************************************************************************/
public void setNTupUpd(Long nTupUpd)
{
this.nTupUpd = nTupUpd;
}
/*******************************************************************************
** Fluent setter for nTupUpd.
*******************************************************************************/
public DbPerfTableDaily withNTupUpd(Long nTupUpd)
{
this.nTupUpd = nTupUpd;
return (this);
}
/*******************************************************************************
** Getter for nTupDel.
*******************************************************************************/
public Long getNTupDel()
{
return (this.nTupDel);
}
/*******************************************************************************
** Setter for nTupDel.
*******************************************************************************/
public void setNTupDel(Long nTupDel)
{
this.nTupDel = nTupDel;
}
/*******************************************************************************
** Fluent setter for nTupDel.
*******************************************************************************/
public DbPerfTableDaily withNTupDel(Long nTupDel)
{
this.nTupDel = nTupDel;
return (this);
}
/*******************************************************************************
** Getter for nLiveTup.
*******************************************************************************/
public Long getNLiveTup()
{
return (this.nLiveTup);
}
/*******************************************************************************
** Setter for nLiveTup.
*******************************************************************************/
public void setNLiveTup(Long nLiveTup)
{
this.nLiveTup = nLiveTup;
}
/*******************************************************************************
** Fluent setter for nLiveTup.
*******************************************************************************/
public DbPerfTableDaily withNLiveTup(Long nLiveTup)
{
this.nLiveTup = nLiveTup;
return (this);
}
/*******************************************************************************
** Getter for nDeadTup.
*******************************************************************************/
public Long getNDeadTup()
{
return (this.nDeadTup);
}
/*******************************************************************************
** Setter for nDeadTup.
*******************************************************************************/
public void setNDeadTup(Long nDeadTup)
{
this.nDeadTup = nDeadTup;
}
/*******************************************************************************
** Fluent setter for nDeadTup.
*******************************************************************************/
public DbPerfTableDaily withNDeadTup(Long nDeadTup)
{
this.nDeadTup = nDeadTup;
return (this);
}
/*******************************************************************************
** Getter for totalBytes.
*******************************************************************************/
public Long getTotalBytes()
{
return (this.totalBytes);
}
/*******************************************************************************
** Setter for totalBytes.
*******************************************************************************/
public void setTotalBytes(Long totalBytes)
{
this.totalBytes = totalBytes;
}
/*******************************************************************************
** Fluent setter for totalBytes.
*******************************************************************************/
public DbPerfTableDaily withTotalBytes(Long totalBytes)
{
this.totalBytes = totalBytes;
return (this);
}
}
@@ -1,673 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.model;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Table Snapshots persisted through QQQ table actions.
*******************************************************************************/
public class DbPerfTableSnapshot extends QRecordEntity
{
public static final String TABLE_NAME = "db_perf_table_snapshot";
@QField(isPrimaryKey = true, isEditable = false)
private Integer id;
@QField
private Integer snapshotId;
@QField
private Integer tableStatId;
@QField
private Instant capturedAt;
@QField
private Long seqScan;
@QField
private Long seqTupRead;
@QField
private Long idxScan;
@QField
private Long idxTupFetch;
@QField
private Long nTupIns;
@QField
private Long nTupUpd;
@QField
private Long nTupDel;
@QField
private Long nTupHotUpd;
@QField
private Long nLiveTup;
@QField
private Long nDeadTup;
@QField
private Long totalBytes;
@QField
private Long tableBytes;
@QField
private Long indexBytes;
@QField
private Long autovacuumCount;
@QField
private Long autoanalyzeCount;
@QField
private Long freezeAge;
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public DbPerfTableSnapshot()
{
}
/*******************************************************************************
** Populate from a QQQ record.
*******************************************************************************/
public DbPerfTableSnapshot(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
** Getter for id.
*******************************************************************************/
public Integer getId()
{
return (this.id);
}
/*******************************************************************************
** Setter for id.
*******************************************************************************/
public void setId(Integer id)
{
this.id = id;
}
/*******************************************************************************
** Fluent setter for id.
*******************************************************************************/
public DbPerfTableSnapshot withId(Integer id)
{
this.id = id;
return (this);
}
/*******************************************************************************
** Getter for snapshotId.
*******************************************************************************/
public Integer getSnapshotId()
{
return (this.snapshotId);
}
/*******************************************************************************
** Setter for snapshotId.
*******************************************************************************/
public void setSnapshotId(Integer snapshotId)
{
this.snapshotId = snapshotId;
}
/*******************************************************************************
** Fluent setter for snapshotId.
*******************************************************************************/
public DbPerfTableSnapshot withSnapshotId(Integer snapshotId)
{
this.snapshotId = snapshotId;
return (this);
}
/*******************************************************************************
** Getter for tableStatId.
*******************************************************************************/
public Integer getTableStatId()
{
return (this.tableStatId);
}
/*******************************************************************************
** Setter for tableStatId.
*******************************************************************************/
public void setTableStatId(Integer tableStatId)
{
this.tableStatId = tableStatId;
}
/*******************************************************************************
** Fluent setter for tableStatId.
*******************************************************************************/
public DbPerfTableSnapshot withTableStatId(Integer tableStatId)
{
this.tableStatId = tableStatId;
return (this);
}
/*******************************************************************************
** Getter for capturedAt.
*******************************************************************************/
public Instant getCapturedAt()
{
return (this.capturedAt);
}
/*******************************************************************************
** Setter for capturedAt.
*******************************************************************************/
public void setCapturedAt(Instant capturedAt)
{
this.capturedAt = capturedAt;
}
/*******************************************************************************
** Fluent setter for capturedAt.
*******************************************************************************/
public DbPerfTableSnapshot withCapturedAt(Instant capturedAt)
{
this.capturedAt = capturedAt;
return (this);
}
/*******************************************************************************
** Getter for seqScan.
*******************************************************************************/
public Long getSeqScan()
{
return (this.seqScan);
}
/*******************************************************************************
** Setter for seqScan.
*******************************************************************************/
public void setSeqScan(Long seqScan)
{
this.seqScan = seqScan;
}
/*******************************************************************************
** Fluent setter for seqScan.
*******************************************************************************/
public DbPerfTableSnapshot withSeqScan(Long seqScan)
{
this.seqScan = seqScan;
return (this);
}
/*******************************************************************************
** Getter for seqTupRead.
*******************************************************************************/
public Long getSeqTupRead()
{
return (this.seqTupRead);
}
/*******************************************************************************
** Setter for seqTupRead.
*******************************************************************************/
public void setSeqTupRead(Long seqTupRead)
{
this.seqTupRead = seqTupRead;
}
/*******************************************************************************
** Fluent setter for seqTupRead.
*******************************************************************************/
public DbPerfTableSnapshot withSeqTupRead(Long seqTupRead)
{
this.seqTupRead = seqTupRead;
return (this);
}
/*******************************************************************************
** Getter for idxScan.
*******************************************************************************/
public Long getIdxScan()
{
return (this.idxScan);
}
/*******************************************************************************
** Setter for idxScan.
*******************************************************************************/
public void setIdxScan(Long idxScan)
{
this.idxScan = idxScan;
}
/*******************************************************************************
** Fluent setter for idxScan.
*******************************************************************************/
public DbPerfTableSnapshot withIdxScan(Long idxScan)
{
this.idxScan = idxScan;
return (this);
}
/*******************************************************************************
** Getter for idxTupFetch.
*******************************************************************************/
public Long getIdxTupFetch()
{
return (this.idxTupFetch);
}
/*******************************************************************************
** Setter for idxTupFetch.
*******************************************************************************/
public void setIdxTupFetch(Long idxTupFetch)
{
this.idxTupFetch = idxTupFetch;
}
/*******************************************************************************
** Fluent setter for idxTupFetch.
*******************************************************************************/
public DbPerfTableSnapshot withIdxTupFetch(Long idxTupFetch)
{
this.idxTupFetch = idxTupFetch;
return (this);
}
/*******************************************************************************
** Getter for nTupIns.
*******************************************************************************/
public Long getNTupIns()
{
return (this.nTupIns);
}
/*******************************************************************************
** Setter for nTupIns.
*******************************************************************************/
public void setNTupIns(Long nTupIns)
{
this.nTupIns = nTupIns;
}
/*******************************************************************************
** Fluent setter for nTupIns.
*******************************************************************************/
public DbPerfTableSnapshot withNTupIns(Long nTupIns)
{
this.nTupIns = nTupIns;
return (this);
}
/*******************************************************************************
** Getter for nTupUpd.
*******************************************************************************/
public Long getNTupUpd()
{
return (this.nTupUpd);
}
/*******************************************************************************
** Setter for nTupUpd.
*******************************************************************************/
public void setNTupUpd(Long nTupUpd)
{
this.nTupUpd = nTupUpd;
}
/*******************************************************************************
** Fluent setter for nTupUpd.
*******************************************************************************/
public DbPerfTableSnapshot withNTupUpd(Long nTupUpd)
{
this.nTupUpd = nTupUpd;
return (this);
}
/*******************************************************************************
** Getter for nTupDel.
*******************************************************************************/
public Long getNTupDel()
{
return (this.nTupDel);
}
/*******************************************************************************
** Setter for nTupDel.
*******************************************************************************/
public void setNTupDel(Long nTupDel)
{
this.nTupDel = nTupDel;
}
/*******************************************************************************
** Fluent setter for nTupDel.
*******************************************************************************/
public DbPerfTableSnapshot withNTupDel(Long nTupDel)
{
this.nTupDel = nTupDel;
return (this);
}
/*******************************************************************************
** Getter for nTupHotUpd.
*******************************************************************************/
public Long getNTupHotUpd()
{
return (this.nTupHotUpd);
}
/*******************************************************************************
** Setter for nTupHotUpd.
*******************************************************************************/
public void setNTupHotUpd(Long nTupHotUpd)
{
this.nTupHotUpd = nTupHotUpd;
}
/*******************************************************************************
** Fluent setter for nTupHotUpd.
*******************************************************************************/
public DbPerfTableSnapshot withNTupHotUpd(Long nTupHotUpd)
{
this.nTupHotUpd = nTupHotUpd;
return (this);
}
/*******************************************************************************
** Getter for nLiveTup.
*******************************************************************************/
public Long getNLiveTup()
{
return (this.nLiveTup);
}
/*******************************************************************************
** Setter for nLiveTup.
*******************************************************************************/
public void setNLiveTup(Long nLiveTup)
{
this.nLiveTup = nLiveTup;
}
/*******************************************************************************
** Fluent setter for nLiveTup.
*******************************************************************************/
public DbPerfTableSnapshot withNLiveTup(Long nLiveTup)
{
this.nLiveTup = nLiveTup;
return (this);
}
/*******************************************************************************
** Getter for nDeadTup.
*******************************************************************************/
public Long getNDeadTup()
{
return (this.nDeadTup);
}
/*******************************************************************************
** Setter for nDeadTup.
*******************************************************************************/
public void setNDeadTup(Long nDeadTup)
{
this.nDeadTup = nDeadTup;
}
/*******************************************************************************
** Fluent setter for nDeadTup.
*******************************************************************************/
public DbPerfTableSnapshot withNDeadTup(Long nDeadTup)
{
this.nDeadTup = nDeadTup;
return (this);
}
/*******************************************************************************
** Getter for totalBytes.
*******************************************************************************/
public Long getTotalBytes()
{
return (this.totalBytes);
}
/*******************************************************************************
** Setter for totalBytes.
*******************************************************************************/
public void setTotalBytes(Long totalBytes)
{
this.totalBytes = totalBytes;
}
/*******************************************************************************
** Fluent setter for totalBytes.
*******************************************************************************/
public DbPerfTableSnapshot withTotalBytes(Long totalBytes)
{
this.totalBytes = totalBytes;
return (this);
}
/*******************************************************************************
** Getter for tableBytes.
*******************************************************************************/
public Long getTableBytes()
{
return (this.tableBytes);
}
/*******************************************************************************
** Setter for tableBytes.
*******************************************************************************/
public void setTableBytes(Long tableBytes)
{
this.tableBytes = tableBytes;
}
/*******************************************************************************
** Fluent setter for tableBytes.
*******************************************************************************/
public DbPerfTableSnapshot withTableBytes(Long tableBytes)
{
this.tableBytes = tableBytes;
return (this);
}
/*******************************************************************************
** Getter for indexBytes.
*******************************************************************************/
public Long getIndexBytes()
{
return (this.indexBytes);
}
/*******************************************************************************
** Setter for indexBytes.
*******************************************************************************/
public void setIndexBytes(Long indexBytes)
{
this.indexBytes = indexBytes;
}
/*******************************************************************************
** Fluent setter for indexBytes.
*******************************************************************************/
public DbPerfTableSnapshot withIndexBytes(Long indexBytes)
{
this.indexBytes = indexBytes;
return (this);
}
/*******************************************************************************
** Getter for autovacuumCount.
*******************************************************************************/
public Long getAutovacuumCount()
{
return (this.autovacuumCount);
}
/*******************************************************************************
** Setter for autovacuumCount.
*******************************************************************************/
public void setAutovacuumCount(Long autovacuumCount)
{
this.autovacuumCount = autovacuumCount;
}
/*******************************************************************************
** Fluent setter for autovacuumCount.
*******************************************************************************/
public DbPerfTableSnapshot withAutovacuumCount(Long autovacuumCount)
{
this.autovacuumCount = autovacuumCount;
return (this);
}
/*******************************************************************************
** Getter for autoanalyzeCount.
*******************************************************************************/
public Long getAutoanalyzeCount()
{
return (this.autoanalyzeCount);
}
/*******************************************************************************
** Setter for autoanalyzeCount.
*******************************************************************************/
public void setAutoanalyzeCount(Long autoanalyzeCount)
{
this.autoanalyzeCount = autoanalyzeCount;
}
/*******************************************************************************
** Fluent setter for autoanalyzeCount.
*******************************************************************************/
public DbPerfTableSnapshot withAutoanalyzeCount(Long autoanalyzeCount)
{
this.autoanalyzeCount = autoanalyzeCount;
return (this);
}
/*******************************************************************************
** Getter for freezeAge.
*******************************************************************************/
public Long getFreezeAge()
{
return (this.freezeAge);
}
/*******************************************************************************
** Setter for freezeAge.
*******************************************************************************/
public void setFreezeAge(Long freezeAge)
{
this.freezeAge = freezeAge;
}
/*******************************************************************************
** Fluent setter for freezeAge.
*******************************************************************************/
public DbPerfTableSnapshot withFreezeAge(Long freezeAge)
{
this.freezeAge = freezeAge;
return (this);
}
}
File diff suppressed because it is too large. Load diff
@@ -1,280 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.normalize;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*******************************************************************************
** Literal-free SQL shapes shared by PostgreSQL snapshots and QQQ attribution.
*******************************************************************************/
public final class SqlNormalizer
{
private static final Pattern NUMBER = Pattern.compile("(?:0[xX][0-9a-fA-F]+|(?:[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+)(?:[eE][+-]?[0-9]+)?)");
private static final Pattern OPERATOR = Pattern.compile("(?:->>|#>>|!~\\*|\\|\\|/|::|>=|<=|<>|!=|\\|\\||->|#>|!~|~\\*|@>|<@|&&|\\|/|:=|=>|\\?\\||\\?&)");
private static final Pattern DOLLAR = Pattern.compile("\\$(?:[A-Za-z_][A-Za-z_0-9]*)?\\$");
private static final Pattern TABLE = Pattern.compile("(?:^| )(?:(?:from|into|update) )((?:\"(?:[^\"]|\"\")*\"|[a-z_][a-z_0-9$]*)(?: \\. (?:\"(?:[^\"]|\"\")*\"|[a-z_][a-z_0-9$]*))?)");
/*******************************************************************************
** Utility class.
*******************************************************************************/
private SqlNormalizer()
{
}
/*******************************************************************************
** Lex SQL before folding whitespace, so quoted identifiers stay intact.
** Unterminated literals and comments consume their remainder and never leak it.
*******************************************************************************/
public static String normalize(String sql)
{
if(sql == null || sql.isBlank())
{
return ("");
}
StringBuilder out = new StringBuilder();
int i = 0;
while(i < sql.length())
{
char c = sql.charAt(i);
if(Character.isWhitespace(c))
{
i++;
continue;
}
if(sql.startsWith("--", i))
{
int end = sql.indexOf('\n', i);
i = end < 0 ? sql.length() : end + 1;
continue;
}
if(sql.startsWith("/*", i))
{
int depth = 1;
i += 2;
while(i < sql.length() && depth > 0)
{
if(sql.startsWith("/*", i))
{
depth++;
i += 2;
}
else if(sql.startsWith("*/", i))
{
depth--;
i += 2;
}
else
{
i++;
}
}
continue;
}
boolean escape = (c == 'e' || c == 'E') && i + 1 < sql.length() && sql.charAt(i + 1) == '\'';
if(c == '\'' || escape)
{
i = quotedEnd(sql, i + (escape ? 1 : 0), '\'', escape);
out.append("? ");
continue;
}
if(c == '"')
{
int end = quotedEnd(sql, i, '"', false);
out.append(sql, i, end).append(' ');
i = end;
continue;
}
if(c == '$')
{
Matcher dollar = DOLLAR.matcher(sql).region(i, sql.length());
if(dollar.lookingAt())
{
int end = sql.indexOf(dollar.group(), dollar.end());
i = end < 0 ? sql.length() : end + dollar.group().length();
out.append("? ");
continue;
}
int end = i + 1;
while(end < sql.length() && Character.isDigit(sql.charAt(end)))
{
end++;
}
if(end > i + 1)
{
out.append("? ");
i = end;
continue;
}
}
if(Character.isDigit(c) || c == '.' && i + 1 < sql.length() && Character.isDigit(sql.charAt(i + 1)))
{
Matcher number = NUMBER.matcher(sql).region(i, sql.length());
if(number.lookingAt())
{
i = number.end();
out.append("? ");
continue;
}
}
if(Character.isLetter(c) || c == '_')
{
int end = i + 1;
while(end < sql.length() && (Character.isLetterOrDigit(sql.charAt(end)) || sql.charAt(end) == '_' || sql.charAt(end) == '$'))
{
end++;
}
out.append(sql.substring(i, end).toLowerCase(Locale.ROOT)).append(' ');
i = end;
continue;
}
Matcher operator = OPERATOR.matcher(sql).region(i, sql.length());
if(operator.lookingAt())
{
out.append(operator.group()).append(' ');
i = operator.end();
continue;
}
out.append(c).append(' ');
i++;
}
String normalized = collapseOutsideIdentifiers(out.toString().trim());
return (normalized.substring(0, Math.min(10000, normalized.length())));
}
/*******************************************************************************
** List folding applies only outside quoted identifiers, whose contents may
** themselves resemble SQL. Escaped identifier quotes remain untouched.
*******************************************************************************/
private static String collapseOutsideIdentifiers(String sql)
{
StringBuilder result = new StringBuilder();
int start = 0;
while(start < sql.length())
{
int quote = sql.indexOf('"', start);
if(quote < 0)
{
result.append(collapseLists(sql.substring(start)));
break;
}
result.append(collapseLists(sql.substring(start, quote)));
int end = quotedEnd(sql, quote, '"', false);
result.append(sql, quote, end);
start = end;
}
return (result.toString());
}
/*******************************************************************************
** Collapse placeholder repetitions, preserving VALUES column arity.
*******************************************************************************/
private static String collapseLists(String normalized)
{
normalized = normalized.replaceAll("\\bin \\( \\?(?: , \\?)* \\)", "in ( ? )");
normalized = normalized.replaceAll("\\bvalues (\\( \\?(?: , \\?)* \\))(?: , \\( \\?(?: , \\?)* \\))+", "values $1");
return (normalized);
}
/*******************************************************************************
** Consume a quote with SQL doubled-quote and optional escape handling.
*******************************************************************************/
private static int quotedEnd(String sql, int start, char quote, boolean escapes)
{
int i = start + 1;
while(i < sql.length())
{
if(escapes && sql.charAt(i) == '\\')
{
i = Math.min(i + 2, sql.length());
}
else if(sql.charAt(i++) == quote)
{
if(i < sql.length() && sql.charAt(i) == quote)
{
i++;
}
else
{
return (i);
}
}
}
return (i);
}
/*******************************************************************************
** Stable hexadecimal SHA-256; callers supply the normalized shape.
*******************************************************************************/
public static String fingerprint(String normalized)
{
try
{
return (HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(normalized.getBytes(StandardCharsets.UTF_8))));
}
catch(NoSuchAlgorithmException e)
{
throw (new IllegalStateException("SHA-256 is required by Java", e));
}
}
/*******************************************************************************
** Leading operation, including WITH as its own kind.
*******************************************************************************/
public static String operation(String sql)
{
String first = normalize(sql).split(" ", 2)[0].toUpperCase(Locale.ROOT);
return (switch(first)
{
case "SELECT", "INSERT", "UPDATE", "DELETE", "WITH" -> first;
default -> "OTHER";
});
}
/*******************************************************************************
** Best-effort first target table, without schema or identifier quotes.
*******************************************************************************/
public static String mainTable(String sql)
{
Matcher match = TABLE.matcher(normalize(sql));
if(!match.find())
{
return (null);
}
String[] parts = match.group(1).split(" \\. ");
return (parts[parts.length - 1].replace("\"", ""));
}
}
@@ -1,85 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.processes;
import java.time.Instant;
import java.util.ArrayList;
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
import com.kingsrook.qbits.dbperformance.collect.DbPerfSettingsService;
import com.kingsrook.qbits.dbperformance.collect.SnapshotCollector;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.processes.Status;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Collect on schedule or explicitly from the Snapshots table.
*******************************************************************************/
public class DbPerfCollectStep implements BackendStep
{
/*******************************************************************************
** Respect runtime interval settings on scheduled retries; Collect now forces
** table statistics and always returns the recorded outcome on its result screen.
*******************************************************************************/
@Override
public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
{
DbPerfRuntime runtime = DbPerfRuntime.of(QContext.getQInstance());
if(QContext.getQSession() == null || !QContext.getQSession().hasPermission(runtime.getConfig().getPermissionBaseName() + ".manage_settings"))
{
throw (new QException("Managing settings permission is required"));
}
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
boolean force = Boolean.TRUE.equals(input.getValueBoolean("forceTables"));
try
{
QRecord settings = new DbPerfSettingsService(runtime).ensure(runtime.getConfig().getBackendName());
Instant last = settings.getValueInstant("lastSnapshotAt");
if(!force && last != null && last.plusSeconds(60L * SnapshotCollector.positive(settings, "snapshotIntervalMinutes", 5)).isAfter(Instant.now()))
{
lines.add(new ProcessSummaryLine(Status.INFO, 0, "snapshots collected; the next interval is not due"));
}
else
{
QRecord snapshot = new SnapshotCollector(runtime).collect(runtime.getConfig().getBackendName(), force);
output.addRecord(snapshot);
lines.add(new ProcessSummaryLine("FAILED".equals(snapshot.getValueString("status")) ? Status.ERROR : Status.INFO, 1, "snapshot: " + snapshot.getValueString("status")));
lines.add(new ProcessSummaryLine(Status.INFO, snapshot.getValueInteger("storedStatementCount") == null ? 0 : snapshot.getValueInteger("storedStatementCount"), "query details stored"));
if(snapshot.getValueString("errorMessage") != null)
{
lines.add(new ProcessSummaryLine(Status.INFO, 0, snapshot.getValueString("errorMessage")));
}
}
}
catch(Exception e)
{
throw (new QException("Unable to collect database performance snapshot", e));
}
output.addValue("processResults", lines);
}
}
@@ -1,102 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.processes;
import com.kingsrook.qbits.dbperformance.DbPerfPermissions;
import com.kingsrook.qbits.dbperformance.DbPerfQBitConfig;
import com.kingsrook.qqq.backend.core.jobs.DurableProcessMetaData;
import com.kingsrook.qqq.backend.core.jobs.model.JobIdempotency;
import com.kingsrook.qqq.backend.core.jobs.model.JobSemantics;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
/*******************************************************************************
** Scheduled maintenance and the Snapshot table action; no process navigation.
*******************************************************************************/
public final class DbPerfProcesses
{
public static final String COLLECT = "dbPerfCollectSnapshot";
public static final String ROLLUP = "dbPerfRollupAndPurge";
public static final String COLLECT_NOW = "dbPerfCollectNow";
/*******************************************************************************
** Utility class.
*******************************************************************************/
private DbPerfProcesses()
{
}
/*******************************************************************************
** Add schedules only when the host supplies a scheduler name.
*******************************************************************************/
public static void addTo(QInstance instance, DbPerfQBitConfig config)
{
instance.addProcess(scheduled(config, COLLECT, "Collect database snapshot", DbPerfCollectStep.class, config.getSnapshotCron()));
instance.addProcess(scheduled(config, ROLLUP, "Roll up and purge database performance", DbPerfRollupStep.class, config.getRollupCron()));
instance.addProcess(base(config, COLLECT_NOW, "Collect now", DbPerfCollectStep.class, true)
.withTableName("db_perf_snapshot")
.withStep(new QFrontendStepMetaData().withName("result").withLabel("Result")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.PROCESS_SUMMARY_RESULTS))));
}
/*******************************************************************************
** Hidden durable-eligible maintenance process.
*******************************************************************************/
private static QProcessMetaData scheduled(DbPerfQBitConfig config, String name, String label, Class<?> step, String cron)
{
QProcessMetaData process = base(config, name, label, step, false).withIsHidden(true)
.withSupplementalMetaData(new DurableProcessMetaData().withEligible(true).withSemantics(JobSemantics.ATOMIC).withIdempotency(JobIdempotency.NATURAL).withMaxConcurrentJobs(1));
if(config.getSchedulerName() != null)
{
process.withSchedule(new QScheduleMetaData().withSchedulerName(config.getSchedulerName()).withCronExpression(cron).withCronTimeZoneId(config.getTimeZone()));
}
return (process);
}
/*******************************************************************************
** One backend step with standard process result lines.
*******************************************************************************/
private static QProcessMetaData base(DbPerfQBitConfig config, String name, String label, Class<?> step, boolean force)
{
return (new QProcessMetaData().withName(name).withLabel(label).withIcon(new QIcon("speed"))
.withPermissionRules(DbPerfPermissions.rules(config.getPermissionBaseName() + ".manage_settings"))
.withStep(new QBackendStepMetaData().withName("run").withCode(new QCodeReference(step))
.withInputData(new QFunctionInputMetaData().withField(new QFieldMetaData("forceTables", QFieldType.BOOLEAN).withDefaultValue(force)))
.withOutputMetaData(new QFunctionOutputMetaData().withField(new QFieldMetaData("processResults", QFieldType.STRING)))));
}
}
@@ -1,82 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.processes;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
import com.kingsrook.qbits.dbperformance.collect.RetentionService;
import com.kingsrook.qbits.dbperformance.collect.RollupService;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.processes.Status;
/*******************************************************************************
** Roll up yesterday and backfill missing recent days, then enforce retention.
*******************************************************************************/
public class DbPerfRollupStep implements BackendStep
{
/*******************************************************************************
** Yesterday includes late detail; missing older days are repaired only while
** their detail is retained. Daily history outlives the detail-retention window.
*******************************************************************************/
@Override
public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
{
DbPerfRuntime runtime = DbPerfRuntime.of(QContext.getQInstance());
if(QContext.getQSession() == null || !QContext.getQSession().hasPermission(runtime.getConfig().getPermissionBaseName() + ".manage_settings"))
{
throw (new QException("Managing settings permission is required"));
}
try
{
LocalDate today = LocalDate.now(ZoneId.of(runtime.getConfig().getTimeZone()));
RollupService rollup = new RollupService(runtime);
int rolledUp = 0;
for(int days = 1; days <= 7; days++)
{
LocalDate day = today.minusDays(days);
if(days == 1 || rollup.needsBackfill(day))
{
rollup.rollupDay(day);
rolledUp++;
}
}
int removed = new RetentionService(runtime).purge();
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
lines.add(new ProcessSummaryLine(Status.INFO, rolledUp, "days rolled up"));
lines.add(new ProcessSummaryLine(Status.INFO, removed, "expired records deleted"));
output.addValue("processResults", lines);
}
catch(Exception e)
{
throw (new QException("Database performance rollup and purge failed", e));
}
}
}
@@ -1,31 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
/*******************************************************************************
** ConnectionCount read from database statistics; no persistence or side effects.
*******************************************************************************/
public record ConnectionCount(String dbUser, String applicationName, String state, int connectionCount)
{
}
@@ -1,33 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
import java.time.Instant;
import java.util.Map;
/*******************************************************************************
** DatabaseStats read from database statistics; no persistence or side effects.
*******************************************************************************/
public record DatabaseStats(Map<String, Long> counters, Instant statsReset, int maxConnections, long sizeBytes)
{
}
@@ -1,93 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
/*******************************************************************************
** Read-only database statistics SPI. Implementations never change the database.
*******************************************************************************/
public interface DbStatsProvider
{
/*******************************************************************************
** Provider name.
*******************************************************************************/
String getName();
/*******************************************************************************
** capabilities using the supplied backend connection.
*******************************************************************************/
ProviderCapabilities capabilities(Connection connection) throws SQLException;
/*******************************************************************************
** readDatabase using the supplied backend connection.
*******************************************************************************/
DatabaseStats readDatabase(Connection connection) throws SQLException;
/*******************************************************************************
** readStatements using the supplied backend connection.
*******************************************************************************/
List<StatementStats> readStatements(Connection connection) throws SQLException;
/*******************************************************************************
** readStatementResetInfo using the supplied backend connection.
*******************************************************************************/
StatementResetInfo readStatementResetInfo(Connection connection) throws SQLException;
/*******************************************************************************
** readTables using the supplied backend connection.
*******************************************************************************/
List<TableStats> readTables(Connection connection) throws SQLException;
/*******************************************************************************
** readIndexes using the supplied backend connection.
*******************************************************************************/
List<IndexStats> readIndexes(Connection connection) throws SQLException;
/*******************************************************************************
** readSessions using the supplied backend connection.
*******************************************************************************/
List<SessionInfo> readSessions(Connection connection) throws SQLException;
/*******************************************************************************
** readConnections using the supplied backend connection.
*******************************************************************************/
List<ConnectionCount> readConnections(Connection connection) throws SQLException;
/*******************************************************************************
** readSettings using the supplied backend connection.
*******************************************************************************/
Map<String, String> readSettings(Connection connection) throws SQLException;
}
@@ -1,48 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
import java.sql.Connection;
import java.sql.SQLException;
/*******************************************************************************
** Select a provider from JDBC product identity.
*******************************************************************************/
public final class DbStatsProviders
{
/*******************************************************************************
** Utility class.
*******************************************************************************/
private DbStatsProviders()
{
}
/*******************************************************************************
** PostgreSQL first; other vendors are explicitly unsupported.
*******************************************************************************/
public static DbStatsProvider forConnection(Connection connection) throws SQLException
{
return ("PostgreSQL".equalsIgnoreCase(connection.getMetaData().getDatabaseProductName()) ? new PostgresStatsProvider() : new UnsupportedStatsProvider());
}
}
@@ -1,31 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
/*******************************************************************************
** IndexStats read from database statistics; no persistence or side effects.
*******************************************************************************/
public record IndexStats(String schemaName, String tableName, String indexName, long relid, String definition, String columns, boolean unique, boolean primary, boolean valid, long idxScan, long idxTupRead, long idxTupFetch, long sizeBytes, long writeCost)
{
}
@@ -1,318 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/*******************************************************************************
** PostgreSQL 13+ statistics reader; every statement is a read-only SELECT.
*******************************************************************************/
public class PostgresStatsProvider implements DbStatsProvider
{
/*******************************************************************************
** Provider name.
*******************************************************************************/
@Override
public String getName()
{
return ("POSTGRES");
}
/*******************************************************************************
** Server version and privileges, including explicit installation guidance.
*******************************************************************************/
@Override
public ProviderCapabilities capabilities(Connection connection) throws SQLException
{
int version = version(connection);
boolean installed = scalar(connection, "SELECT count(*) FROM pg_extension WHERE extname = 'pg_stat_statements'") > 0;
boolean privileged = scalar(connection, "SELECT CASE WHEN pg_has_role(current_user, 'pg_read_all_stats', 'MEMBER') OR (SELECT rolsuper FROM pg_roles WHERE rolname=current_user) THEN 1 ELSE 0 END") > 0;
String access = version < 130000 ? "UNSUPPORTED" : !installed ? "NO_PG_STAT_STATEMENTS" : !privileged ? "NO_READ_ALL_STATS" : "FULL";
String message = switch(access)
{
case "UNSUPPORTED" -> "PostgreSQL 13 or newer is required.";
case "NO_PG_STAT_STATEMENTS" -> "Install pg_stat_statements in this database and preload it in shared_preload_libraries.";
case "NO_READ_ALL_STATS" -> "Grant pg_read_all_stats to the application database role to see all statement statistics.";
default -> "Full statistics access.";
};
if(installed && version >= 130000)
{
try
{
scalar(connection, "SELECT count(*) FROM " + statementsView(connection));
}
catch(SQLException e)
{
if("55000".equals(e.getSQLState()) || "42501".equals(e.getSQLState()))
{
installed = false;
access = "NO_PG_STAT_STATEMENTS";
message = "pg_stat_statements must be preloaded and readable by the application database role.";
}
else
{
throw (e);
}
}
}
return (new ProviderCapabilities(version >= 130000, version, installed, privileged, access, message));
}
/*******************************************************************************
** Current database cumulative counters and capacity.
*******************************************************************************/
@Override
public DatabaseStats readDatabase(Connection connection) throws SQLException
{
return (read(connection, "SELECT d.*, pg_database_size(current_database()) AS size_bytes, current_setting('max_connections')::int AS max_connections FROM pg_stat_database d WHERE datname=current_database()", row ->
new DatabaseStats(counters(row, "xact_commit", "xact_rollback", "blks_hit", "blks_read", "temp_files", "temp_bytes", "deadlocks", "conflicts"), instant(row, "stats_reset"), row.getInt("max_connections"), row.getLong("size_bytes"))).getFirst());
}
/*******************************************************************************
** Current database statements only. Version-dependent columns are projected
** as null or true when older PostgreSQL versions do not provide them.
*******************************************************************************/
@Override
public List<StatementStats> readStatements(Connection connection) throws SQLException
{
int version = version(connection);
String sql = "SELECT s.queryid, r.rolname, s.query, s.calls, s.total_exec_time, s.mean_exec_time, s.max_exec_time, s.rows, s.shared_blks_hit, s.shared_blks_read, s.temp_blks_written, "
+ (version >= 140000 ? "s.toplevel" : "true") + " AS top_level, "
+ (version >= 170000 ? "s.stats_since, s.minmax_stats_since" : "NULL::timestamptz AS stats_since, NULL::timestamptz AS minmax_stats_since")
+ " FROM " + statementsView(connection) + " s LEFT JOIN pg_roles r ON r.oid=s.userid WHERE s.dbid=(SELECT oid FROM pg_database WHERE datname=current_database()) AND s.queryid IS NOT NULL AND s.query IS NOT NULL";
return (read(connection, sql, row -> new StatementStats(row.getLong("queryid"), row.getString("rolname"), row.getBoolean("top_level"), row.getString("query"), row.getLong("calls"), row.getBigDecimal("total_exec_time"), row.getBigDecimal("mean_exec_time"), row.getBigDecimal("max_exec_time"), row.getLong("rows"), row.getLong("shared_blks_hit"), row.getLong("shared_blks_read"), row.getLong("temp_blks_written"), instant(row, "stats_since"), instant(row, "minmax_stats_since"))));
}
/*******************************************************************************
** Global statement reset is available starting with PostgreSQL 14.
*******************************************************************************/
@Override
public StatementResetInfo readStatementResetInfo(Connection connection) throws SQLException
{
if(version(connection) < 140000)
{
return (new StatementResetInfo(null, 0));
}
return (read(connection, "SELECT stats_reset, dealloc FROM " + statementsView(connection) + "_info", row -> new StatementResetInfo(instant(row, "stats_reset"), row.getLong("dealloc"))).getFirst());
}
/*******************************************************************************
** Latest table counters, tuple gauges, maintenance times and sizes.
*******************************************************************************/
@Override
public List<TableStats> readTables(Connection connection) throws SQLException
{
String sql = "SELECT s.*, pg_total_relation_size(s.relid) AS total_bytes, pg_relation_size(s.relid) AS table_bytes, pg_indexes_size(s.relid) AS index_bytes, CASE WHEN c.reltoastrelid=0 THEN 0 ELSE pg_total_relation_size(c.reltoastrelid) END AS toast_bytes, age(c.relfrozenxid)::bigint AS freeze_age FROM pg_stat_user_tables s JOIN pg_class c ON c.oid=s.relid";
return (read(connection, sql, row ->
{
Map<String, Instant> maintenance = new LinkedHashMap<>();
for(String field : List.of("last_vacuum", "last_autovacuum", "last_analyze", "last_autoanalyze"))
{
maintenance.put(camel(field), instant(row, field));
}
return (new TableStats(row.getString("schemaname"), row.getString("relname"), row.getLong("relid"), counters(row, "seq_scan", "seq_tup_read", "idx_scan", "idx_tup_fetch", "n_tup_ins", "n_tup_upd", "n_tup_del", "n_tup_hot_upd", "n_live_tup", "n_dead_tup", "total_bytes", "table_bytes", "index_bytes", "toast_bytes", "vacuum_count", "autovacuum_count", "analyze_count", "autoanalyze_count", "freeze_age"), maintenance));
}));
}
/*******************************************************************************
** Index definitions, ordered keys (including expressions) and write cost.
*******************************************************************************/
@Override
public List<IndexStats> readIndexes(Connection connection) throws SQLException
{
String sql = "SELECT s.*, i.indisunique, i.indisprimary, i.indisvalid, pg_get_indexdef(s.indexrelid) AS definition, (SELECT string_agg(pg_get_indexdef(s.indexrelid, k, true), ', ' ORDER BY k) FROM generate_series(1, i.indnatts) AS k) AS column_names, pg_relation_size(s.indexrelid) AS size_bytes, t.n_tup_ins+t.n_tup_upd+t.n_tup_del AS write_cost FROM pg_stat_user_indexes s JOIN pg_index i ON i.indexrelid=s.indexrelid JOIN pg_stat_user_tables t ON t.relid=s.relid";
return (read(connection, sql, row -> new IndexStats(row.getString("schemaname"), row.getString("relname"), row.getString("indexrelname"), row.getLong("indexrelid"), row.getString("definition"), row.getString("column_names"), row.getBoolean("indisunique"), row.getBoolean("indisprimary"), row.getBoolean("indisvalid"), row.getLong("idx_scan"), row.getLong("idx_tup_read"), row.getLong("idx_tup_fetch"), row.getLong("size_bytes"), row.getLong("write_cost"))));
}
/*******************************************************************************
** Current database activity, excluding the collector's own session.
*******************************************************************************/
@Override
public List<SessionInfo> readSessions(Connection connection) throws SQLException
{
String sql = "SELECT pid, usename, application_name, client_addr::text, state, wait_event_type, wait_event, backend_type, xact_start, query_start, state_change, extract(epoch FROM clock_timestamp()-query_start) AS query_seconds, extract(epoch FROM clock_timestamp()-xact_start) AS xact_seconds, pg_blocking_pids(pid) AS blocked_by, "
+ (version(connection) >= 140000 ? "query_id" : "NULL::bigint AS query_id") + ", query FROM pg_stat_activity WHERE datname=current_database() AND pid<>pg_backend_pid()";
return (read(connection, sql, row ->
{
List<Integer> blockedBy = new ArrayList<>();
java.sql.Array array = row.getArray("blocked_by");
if(array != null)
{
try
{
for(Object pid : (Object[]) array.getArray())
{
blockedBy.add(((Number) pid).intValue());
}
}
finally
{
array.free();
}
}
Long queryId = row.getObject("query_id") == null ? null : row.getLong("query_id");
return (new SessionInfo(row.getInt("pid"), row.getString("usename"), row.getString("application_name"), row.getString("client_addr"), row.getString("state"), row.getString("wait_event_type"), row.getString("wait_event"), row.getString("backend_type"), instant(row, "xact_start"), instant(row, "query_start"), instant(row, "state_change"), row.getBigDecimal("query_seconds"), row.getBigDecimal("xact_seconds"), blockedBy, queryId, row.getString("query")));
}));
}
/*******************************************************************************
** Cluster-wide capacity counts, including connections in other databases.
*******************************************************************************/
@Override
public List<ConnectionCount> readConnections(Connection connection) throws SQLException
{
return (read(connection, "SELECT usename, application_name, state, count(*) AS connections FROM pg_stat_activity GROUP BY usename, application_name, state", row -> new ConnectionCount(row.getString("usename"), row.getString("application_name"), row.getString("state"), row.getInt("connections"))));
}
/*******************************************************************************
** Only operational, non-secret server settings relevant to this qbit.
*******************************************************************************/
@Override
public Map<String, String> readSettings(Connection connection) throws SQLException
{
Map<String, String> result = new LinkedHashMap<>();
read(connection, "SELECT name, setting FROM pg_settings WHERE name IN ('server_version_num','max_connections','shared_preload_libraries','pg_stat_statements.track','track_io_timing','block_size')", row -> result.put(row.getString("name"), row.getString("setting")));
return (result);
}
/*******************************************************************************
** Extension schema is obtained from the catalog, then identifier-quoted.
*******************************************************************************/
private String statementsView(Connection connection) throws SQLException
{
List<String> schemas = read(connection, "SELECT n.nspname FROM pg_extension e JOIN pg_namespace n ON n.oid=e.extnamespace WHERE e.extname='pg_stat_statements'", row -> row.getString(1));
if(schemas.isEmpty())
{
throw (new SQLException("pg_stat_statements is not installed", "42P01"));
}
return ("\"" + schemas.getFirst().replace("\"", "\"\"") + "\".pg_stat_statements");
}
/*******************************************************************************
** Numeric server version avoids JDBC minor-version ambiguity.
*******************************************************************************/
private int version(Connection connection) throws SQLException
{
return ((int) scalar(connection, "SELECT current_setting('server_version_num')::int"));
}
/*******************************************************************************
** A single numeric read.
*******************************************************************************/
private long scalar(Connection connection, String sql) throws SQLException
{
return (read(connection, sql, row -> row.getLong(1)).getFirst());
}
/*******************************************************************************
** Timestamp conversion preserves null reset/maintenance values.
*******************************************************************************/
private static Instant instant(ResultSet row, String column) throws SQLException
{
Timestamp timestamp = row.getTimestamp(column);
return (timestamp == null ? null : timestamp.toInstant());
}
/*******************************************************************************
** Copy cumulative counters to the entity's camelCase names.
*******************************************************************************/
private static Map<String, Long> counters(ResultSet row, String... columns) throws SQLException
{
Map<String, Long> counters = new LinkedHashMap<>();
for(String column : columns)
{
counters.put(camel(column), row.getLong(column));
}
return (counters);
}
/*******************************************************************************
** PostgreSQL column to entity field.
*******************************************************************************/
private static String camel(String column)
{
StringBuilder value = new StringBuilder();
for(String part : column.split("_"))
{
value.append(value.isEmpty() ? part : Character.toUpperCase(part.charAt(0)) + part.substring(1));
}
return (value.toString());
}
/*******************************************************************************
** Execute one bounded-time read and close JDBC resources.
*******************************************************************************/
private static <T> List<T> read(Connection connection, String sql, RowReader<T> mapper) throws SQLException
{
List<T> result = new ArrayList<>();
try(Statement statement = connection.createStatement())
{
statement.setQueryTimeout(30);
try(ResultSet rows = statement.executeQuery(sql))
{
while(rows.next())
{
result.add(mapper.read(rows));
}
}
}
return (result);
}
/*******************************************************************************
** Checked JDBC row mapping.
*******************************************************************************/
private interface RowReader<T>
{
/*******************************************************************************
** Map one row.
*******************************************************************************/
T read(ResultSet row) throws SQLException;
}
}
@@ -1,31 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
/*******************************************************************************
** ProviderCapabilities read from database statistics; no persistence or side effects.
*******************************************************************************/
public record ProviderCapabilities(boolean supported, int version, boolean statements, boolean readAllStats, String statsAccess, String message)
{
}
@@ -1,34 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
/*******************************************************************************
** SessionInfo read from database statistics; no persistence or side effects.
*******************************************************************************/
public record SessionInfo(int pid, String dbUser, String applicationName, String clientAddr, String state, String waitEventType, String waitEvent, String backendType, Instant xactStart, Instant queryStart, Instant stateChange, BigDecimal querySeconds, BigDecimal xactSeconds, List<Integer> blockedBy, Long queryId, String queryText)
{
}
@@ -1,32 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
import java.time.Instant;
/*******************************************************************************
** StatementResetInfo read from database statistics; no persistence or side effects.
*******************************************************************************/
public record StatementResetInfo(Instant statsReset, long dealloc)
{
}
@@ -1,33 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
import java.math.BigDecimal;
import java.time.Instant;
/*******************************************************************************
** StatementStats read from database statistics; no persistence or side effects.
*******************************************************************************/
public record StatementStats(long queryId, String dbUser, boolean toplevel, String queryText, long calls, BigDecimal totalExecMs, BigDecimal meanExecMs, BigDecimal maxExecMs, long rows, long sharedBlksHit, long sharedBlksRead, long tempBlksWritten, Instant statsSince, Instant minmaxStatsSince)
{
}
@@ -1,33 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
import java.time.Instant;
import java.util.Map;
/*******************************************************************************
** TableStats read from database statistics; no persistence or side effects.
*******************************************************************************/
public record TableStats(String schemaName, String tableName, long relid, Map<String, Long> counters, Map<String, Instant> maintenance)
{
}
@@ -1,132 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance.provider;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
/*******************************************************************************
** Unsupported vendors report reduced capability without reading vendor views.
*******************************************************************************/
public class UnsupportedStatsProvider implements DbStatsProvider
{
/*******************************************************************************
** Provider name.
*******************************************************************************/
@Override
public String getName()
{
return ("UNSUPPORTED");
}
/*******************************************************************************
** Empty unsupported result.
*******************************************************************************/
@Override
public ProviderCapabilities capabilities(Connection connection)
{
return (new ProviderCapabilities(false, 0, false, false, "UNSUPPORTED", "This database vendor is not supported; PostgreSQL 13 or newer is required."));
}
/*******************************************************************************
** Empty unsupported result.
*******************************************************************************/
@Override
public DatabaseStats readDatabase(Connection connection)
{
return (new DatabaseStats(Map.of(), null, 0, 0));
}
/*******************************************************************************
** Empty unsupported result.
*******************************************************************************/
@Override
public List<StatementStats> readStatements(Connection connection)
{
return (List.of());
}
/*******************************************************************************
** Empty unsupported result.
*******************************************************************************/
@Override
public StatementResetInfo readStatementResetInfo(Connection connection)
{
return (new StatementResetInfo(null, 0));
}
/*******************************************************************************
** Empty unsupported result.
*******************************************************************************/
@Override
public List<TableStats> readTables(Connection connection)
{
return (List.of());
}
/*******************************************************************************
** Empty unsupported result.
*******************************************************************************/
@Override
public List<IndexStats> readIndexes(Connection connection)
{
return (List.of());
}
/*******************************************************************************
** Empty unsupported result.
*******************************************************************************/
@Override
public List<SessionInfo> readSessions(Connection connection)
{
return (List.of());
}
/*******************************************************************************
** Empty unsupported result.
*******************************************************************************/
@Override
public List<ConnectionCount> readConnections(Connection connection)
{
return (List.of());
}
/*******************************************************************************
** Empty unsupported result.
*******************************************************************************/
@Override
public Map<String, String> readSettings(Connection connection)
{
return (Map.of());
}
}
@@ -1,607 +0,0 @@
-- liquibase formatted sql
-- changeset qbit-db-performance:1
-- comment: Settings table.
CREATE TABLE IF NOT EXISTS db_perf_settings (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
backend_name VARCHAR(100) NOT NULL,
enabled BOOLEAN DEFAULT TRUE,
provider VARCHAR(20),
snapshot_interval_minutes INT DEFAULT 5,
table_snapshot_minutes INT DEFAULT 60,
detail_retention_days INT DEFAULT 30,
rollup_retention_days INT DEFAULT 365,
top_statements_per_snapshot INT DEFAULT 200,
activity_long_running_seconds INT DEFAULT 30,
idle_in_transaction_seconds INT DEFAULT 60,
attribution_enabled BOOLEAN DEFAULT TRUE,
attribution_sample_rate DECIMAL(5,4) DEFAULT 1.0,
attribution_flush_seconds INT DEFAULT 60,
attribution_max_keys INT DEFAULT 10000,
n_plus_one_threshold INT DEFAULT 10,
explain_timeout_ms INT DEFAULT 10000,
alerts_enabled BOOLEAN DEFAULT TRUE,
spike_factor DECIMAL(6,2) DEFAULT 3,
spike_min_total_ms INT DEFAULT 5000,
connection_alert_percent INT DEFAULT 80,
blocked_alert_seconds INT DEFAULT 30,
last_snapshot_at TIMESTAMP,
last_table_snapshot_at TIMESTAMP,
stats_access VARCHAR(30),
stats_access_message VARCHAR(500),
create_date TIMESTAMP,
modify_date TIMESTAMP,
CONSTRAINT uq_settings_0 UNIQUE (backend_name)
);
-- changeset qbit-db-performance:2
-- comment: Suggestion Rules table.
CREATE TABLE IF NOT EXISTS db_perf_rule (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
code VARCHAR(40) NOT NULL,
label VARCHAR(100),
description VARCHAR(1000),
enabled BOOLEAN DEFAULT TRUE,
severity VARCHAR(20),
threshold_1 DECIMAL(18,4),
threshold_2 DECIMAL(18,4),
threshold_1_label VARCHAR(100),
threshold_2_label VARCHAR(100),
create_date TIMESTAMP,
modify_date TIMESTAMP,
CONSTRAINT uq_rule_0 UNIQUE (code)
);
-- changeset qbit-db-performance:3
-- comment: Snapshots table.
CREATE TABLE IF NOT EXISTS db_perf_snapshot (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
backend_name VARCHAR(100),
captured_at TIMESTAMP NOT NULL,
interval_seconds INT,
status VARCHAR(20),
duration_ms INT,
error_message VARCHAR(2000),
stats_reset_detected BOOLEAN,
includes_tables BOOLEAN,
xact_commit BIGINT,
xact_rollback BIGINT,
blks_hit BIGINT,
blks_read BIGINT,
cache_hit_ratio DECIMAL(7,4),
temp_files BIGINT,
temp_bytes BIGINT,
deadlocks BIGINT,
conflicts BIGINT,
connections_total INT,
connections_active INT,
connections_idle INT,
connections_idle_in_tx INT,
max_connections INT,
database_size_bytes BIGINT,
statement_count INT,
statement_calls BIGINT,
statement_total_ms DECIMAL(18,3),
stored_statement_count INT,
create_date TIMESTAMP,
raw_xact_commit BIGINT,
raw_xact_rollback BIGINT,
raw_blks_hit BIGINT,
raw_blks_read BIGINT,
raw_temp_files BIGINT,
raw_temp_bytes BIGINT,
raw_deadlocks BIGINT,
raw_conflicts BIGINT,
raw_stats_reset TIMESTAMP,
raw_statement_stats_reset TIMESTAMP,
CONSTRAINT uq_snapshot_0 UNIQUE (backend_name,captured_at)
);
CREATE INDEX IF NOT EXISTS ix_snapshot_0 ON db_perf_snapshot (captured_at);
-- changeset qbit-db-performance:4
-- comment: Queries table.
CREATE TABLE IF NOT EXISTS db_perf_statement (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
backend_name VARCHAR(100),
query_id BIGINT,
db_user VARCHAR(100),
toplevel BOOLEAN,
fingerprint CHAR(64),
query_text TEXT,
operation VARCHAR(20),
main_table VARCHAR(200),
first_seen_at TIMESTAMP,
last_seen_at TIMESTAMP,
last_calls BIGINT,
last_total_exec_ms DECIMAL(18,3),
last_rows BIGINT,
last_shared_blks_hit BIGINT,
last_shared_blks_read BIGINT,
last_temp_blks_written BIGINT,
last_max_exec_ms DECIMAL(18,3),
last_stats_since TIMESTAMP,
calls_24h BIGINT,
total_ms_24h DECIMAL(18,3),
mean_ms_24h DECIMAL(18,3),
share_of_db_time_24h DECIMAL(7,4),
create_date TIMESTAMP,
modify_date TIMESTAMP,
CONSTRAINT uq_statement_0 UNIQUE (backend_name,query_id,db_user,toplevel)
);
CREATE INDEX IF NOT EXISTS ix_statement_0 ON db_perf_statement (fingerprint);
CREATE INDEX IF NOT EXISTS ix_statement_1 ON db_perf_statement (last_seen_at);
CREATE INDEX IF NOT EXISTS ix_statement_2 ON db_perf_statement (total_ms_24h);
-- changeset qbit-db-performance:5
-- comment: Query Snapshots table.
CREATE TABLE IF NOT EXISTS db_perf_query_snapshot (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
snapshot_id BIGINT,
statement_id BIGINT,
captured_at TIMESTAMP,
calls BIGINT,
total_ms DECIMAL(18,3),
mean_ms DECIMAL(18,3),
max_ms DECIMAL(18,3),
rows_count BIGINT,
shared_blks_hit BIGINT,
shared_blks_read BIGINT,
hit_ratio DECIMAL(7,4),
temp_blks_written BIGINT,
share_of_db_time DECIMAL(7,4)
);
CREATE INDEX IF NOT EXISTS ix_query_snapshot_0 ON db_perf_query_snapshot (snapshot_id);
CREATE INDEX IF NOT EXISTS ix_query_snapshot_1 ON db_perf_query_snapshot (statement_id);
CREATE INDEX IF NOT EXISTS ix_query_snapshot_2 ON db_perf_query_snapshot (captured_at);
CREATE INDEX IF NOT EXISTS ix_query_snapshot_3 ON db_perf_query_snapshot (statement_id,captured_at);
-- changeset qbit-db-performance:6
-- comment: Query Daily table.
CREATE TABLE IF NOT EXISTS db_perf_query_daily (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
statement_id BIGINT,
"day" DATE,
calls BIGINT,
total_ms DECIMAL(18,3),
mean_ms DECIMAL(18,3),
max_ms DECIMAL(18,3),
rows_count BIGINT,
hit_ratio DECIMAL(7,4),
share_of_db_time DECIMAL(7,4),
snapshots INT,
CONSTRAINT uq_query_daily_0 UNIQUE (statement_id,"day")
);
CREATE INDEX IF NOT EXISTS ix_query_daily_0 ON db_perf_query_daily (statement_id);
CREATE INDEX IF NOT EXISTS ix_query_daily_1 ON db_perf_query_daily ("day");
-- changeset qbit-db-performance:7
-- comment: Tables table.
CREATE TABLE IF NOT EXISTS db_perf_table_stat (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
backend_name VARCHAR(100),
schema_name VARCHAR(100),
table_name VARCHAR(200),
relid BIGINT,
seq_scan BIGINT,
seq_tup_read BIGINT,
idx_scan BIGINT,
idx_tup_fetch BIGINT,
n_tup_ins BIGINT,
n_tup_upd BIGINT,
n_tup_del BIGINT,
n_tup_hot_upd BIGINT,
n_live_tup BIGINT,
n_dead_tup BIGINT,
dead_ratio DECIMAL(7,4),
total_bytes BIGINT,
table_bytes BIGINT,
index_bytes BIGINT,
toast_bytes BIGINT,
growth_bytes_7d BIGINT,
last_vacuum TIMESTAMP,
last_autovacuum TIMESTAMP,
last_analyze TIMESTAMP,
last_autoanalyze TIMESTAMP,
vacuum_count BIGINT,
autovacuum_count BIGINT,
analyze_count BIGINT,
autoanalyze_count BIGINT,
freeze_age BIGINT,
seq_scan_share DECIMAL(7,4),
last_seen_at TIMESTAMP,
create_date TIMESTAMP,
modify_date TIMESTAMP,
CONSTRAINT uq_table_stat_0 UNIQUE (backend_name,schema_name,table_name)
);
CREATE INDEX IF NOT EXISTS ix_table_stat_0 ON db_perf_table_stat (last_seen_at);
-- changeset qbit-db-performance:8
-- comment: Table Snapshots table.
CREATE TABLE IF NOT EXISTS db_perf_table_snapshot (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
snapshot_id BIGINT,
table_stat_id BIGINT,
captured_at TIMESTAMP,
seq_scan BIGINT,
seq_tup_read BIGINT,
idx_scan BIGINT,
idx_tup_fetch BIGINT,
n_tup_ins BIGINT,
n_tup_upd BIGINT,
n_tup_del BIGINT,
n_tup_hot_upd BIGINT,
n_live_tup BIGINT,
n_dead_tup BIGINT,
total_bytes BIGINT,
table_bytes BIGINT,
index_bytes BIGINT,
autovacuum_count BIGINT,
autoanalyze_count BIGINT,
freeze_age BIGINT
);
CREATE INDEX IF NOT EXISTS ix_table_snapshot_0 ON db_perf_table_snapshot (snapshot_id);
CREATE INDEX IF NOT EXISTS ix_table_snapshot_1 ON db_perf_table_snapshot (table_stat_id);
CREATE INDEX IF NOT EXISTS ix_table_snapshot_2 ON db_perf_table_snapshot (captured_at);
CREATE INDEX IF NOT EXISTS ix_table_snapshot_3 ON db_perf_table_snapshot (table_stat_id,captured_at);
-- changeset qbit-db-performance:9
-- comment: Table Daily table.
CREATE TABLE IF NOT EXISTS db_perf_table_daily (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
table_stat_id BIGINT,
"day" DATE,
seq_scan BIGINT,
idx_scan BIGINT,
seq_tup_read BIGINT,
n_tup_ins BIGINT,
n_tup_upd BIGINT,
n_tup_del BIGINT,
n_live_tup BIGINT,
n_dead_tup BIGINT,
total_bytes BIGINT,
CONSTRAINT uq_table_daily_0 UNIQUE (table_stat_id,"day")
);
CREATE INDEX IF NOT EXISTS ix_table_daily_0 ON db_perf_table_daily (table_stat_id);
CREATE INDEX IF NOT EXISTS ix_table_daily_1 ON db_perf_table_daily ("day");
-- changeset qbit-db-performance:10
-- comment: Indexes table.
CREATE TABLE IF NOT EXISTS db_perf_index_stat (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
backend_name VARCHAR(100),
table_stat_id BIGINT,
schema_name VARCHAR(100),
table_name VARCHAR(200),
index_name VARCHAR(200),
relid BIGINT,
definition VARCHAR(2000),
"columns" VARCHAR(1000),
is_unique BOOLEAN,
is_primary BOOLEAN,
is_valid BOOLEAN,
idx_scan BIGINT,
idx_tup_read BIGINT,
idx_tup_fetch BIGINT,
size_bytes BIGINT,
write_cost BIGINT,
scans_since_reset BIGINT,
last_seen_at TIMESTAMP,
create_date TIMESTAMP,
modify_date TIMESTAMP,
CONSTRAINT uq_index_stat_0 UNIQUE (backend_name,schema_name,index_name)
);
CREATE INDEX IF NOT EXISTS ix_index_stat_0 ON db_perf_index_stat (table_stat_id);
CREATE INDEX IF NOT EXISTS ix_index_stat_1 ON db_perf_index_stat (last_seen_at);
-- changeset qbit-db-performance:11
-- comment: Activity Samples table.
CREATE TABLE IF NOT EXISTS db_perf_activity_sample (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
snapshot_id BIGINT,
captured_at TIMESTAMP,
pid INT,
db_user VARCHAR(100),
application_name VARCHAR(200),
client_addr VARCHAR(100),
state VARCHAR(40),
wait_event_type VARCHAR(60),
wait_event VARCHAR(100),
backend_type VARCHAR(60),
xact_start TIMESTAMP,
query_start TIMESTAMP,
state_change TIMESTAMP,
query_seconds DECIMAL(12,3),
xact_seconds DECIMAL(12,3),
blocked_by VARCHAR(500),
blocking_count INT,
reason VARCHAR(30),
query_id BIGINT,
statement_id BIGINT,
query_fingerprint CHAR(64),
query_text_normalized VARCHAR(4000)
);
CREATE INDEX IF NOT EXISTS ix_activity_sample_0 ON db_perf_activity_sample (snapshot_id);
CREATE INDEX IF NOT EXISTS ix_activity_sample_1 ON db_perf_activity_sample (captured_at);
CREATE INDEX IF NOT EXISTS ix_activity_sample_2 ON db_perf_activity_sample (statement_id);
-- changeset qbit-db-performance:12
-- comment: Connection Samples table.
CREATE TABLE IF NOT EXISTS db_perf_connection_sample (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
snapshot_id BIGINT,
captured_at TIMESTAMP,
db_user VARCHAR(100),
application_name VARCHAR(200),
state VARCHAR(40),
connection_count INT
);
CREATE INDEX IF NOT EXISTS ix_connection_sample_0 ON db_perf_connection_sample (snapshot_id);
CREATE INDEX IF NOT EXISTS ix_connection_sample_1 ON db_perf_connection_sample (captured_at);
-- changeset qbit-db-performance:13
-- comment: QQQ Attribution table.
CREATE TABLE IF NOT EXISTS db_perf_attribution (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
window_start TIMESTAMP,
window_end TIMESTAMP,
pod_name VARCHAR(200),
unit_kind VARCHAR(20),
unit_name VARCHAR(300),
process_name VARCHAR(200),
table_name VARCHAR(200),
operation VARCHAR(20),
fingerprint CHAR(64),
sql_text VARCHAR(4000),
units BIGINT,
statements BIGINT,
total_ms DECIMAL(18,3),
max_ms DECIMAL(18,3),
rows_count BIGINT,
errors BIGINT,
sample_rate DECIMAL(5,4)
);
CREATE INDEX IF NOT EXISTS ix_attribution_0 ON db_perf_attribution (window_start);
CREATE INDEX IF NOT EXISTS ix_attribution_1 ON db_perf_attribution (fingerprint);
CREATE INDEX IF NOT EXISTS ix_attribution_2 ON db_perf_attribution (unit_kind,unit_name);
-- changeset qbit-db-performance:14
-- comment: QQQ Attribution Daily table.
CREATE TABLE IF NOT EXISTS db_perf_attribution_daily (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"day" DATE,
rollup_key CHAR(64),
unit_kind VARCHAR(20),
unit_name VARCHAR(300),
process_name VARCHAR(200),
table_name VARCHAR(200),
operation VARCHAR(20),
fingerprint CHAR(64),
sql_text VARCHAR(4000),
units BIGINT,
statements BIGINT,
total_ms DECIMAL(18,3),
max_ms DECIMAL(18,3),
rows_count BIGINT,
errors BIGINT,
CONSTRAINT uq_attribution_daily_0 UNIQUE ("day",rollup_key)
);
CREATE INDEX IF NOT EXISTS ix_attribution_daily_0 ON db_perf_attribution_daily ("day");
CREATE INDEX IF NOT EXISTS ix_attribution_daily_1 ON db_perf_attribution_daily (fingerprint);
-- changeset qbit-db-performance:15
-- comment: Repeated Statements table.
CREATE TABLE IF NOT EXISTS db_perf_repeat_pattern (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
window_start TIMESTAMP,
window_end TIMESTAMP,
pod_name VARCHAR(200),
unit_kind VARCHAR(20),
unit_name VARCHAR(300),
process_name VARCHAR(200),
table_name VARCHAR(200),
fingerprint CHAR(64),
sql_text VARCHAR(4000),
units_affected BIGINT,
max_repeats INT,
total_repeats BIGINT
);
CREATE INDEX IF NOT EXISTS ix_repeat_pattern_0 ON db_perf_repeat_pattern (window_start);
CREATE INDEX IF NOT EXISTS ix_repeat_pattern_1 ON db_perf_repeat_pattern (fingerprint);
-- changeset qbit-db-performance:16
-- comment: Findings table.
CREATE TABLE IF NOT EXISTS db_perf_finding (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
backend_name VARCHAR(100),
rule_id BIGINT,
rule_code VARCHAR(40),
dedupe_key VARCHAR(300),
status VARCHAR(20) DEFAULT 'OPEN',
severity VARCHAR(20),
score DECIMAL(12,4),
title VARCHAR(300),
summary VARCHAR(2000),
evidence TEXT,
recommendation TEXT,
migration_snippet TEXT,
subject_table VARCHAR(200),
statement_id BIGINT,
table_stat_id BIGINT,
index_stat_id BIGINT,
unit_name VARCHAR(300),
first_seen_at TIMESTAMP,
last_seen_at TIMESTAMP,
occurrences INT,
resolved_at TIMESTAMP,
status_note VARCHAR(2000),
status_changed_by VARCHAR(100),
create_date TIMESTAMP,
modify_date TIMESTAMP,
CONSTRAINT uq_finding_0 UNIQUE (dedupe_key)
);
CREATE INDEX IF NOT EXISTS ix_finding_0 ON db_perf_finding (rule_id);
CREATE INDEX IF NOT EXISTS ix_finding_1 ON db_perf_finding (statement_id);
CREATE INDEX IF NOT EXISTS ix_finding_2 ON db_perf_finding (table_stat_id);
CREATE INDEX IF NOT EXISTS ix_finding_3 ON db_perf_finding (index_stat_id);
CREATE INDEX IF NOT EXISTS ix_finding_4 ON db_perf_finding (last_seen_at);
CREATE INDEX IF NOT EXISTS ix_finding_5 ON db_perf_finding (resolved_at);
CREATE INDEX IF NOT EXISTS ix_finding_6 ON db_perf_finding (status,score);
-- changeset qbit-db-performance:17
-- comment: Query Plans table.
CREATE TABLE IF NOT EXISTS db_perf_explain (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
statement_id BIGINT,
requested_by VARCHAR(100),
requested_at TIMESTAMP,
mode VARCHAR(20),
sql_fingerprint CHAR(64),
sql_text TEXT,
parameter_count INT,
duration_ms INT,
outcome VARCHAR(20),
error_message VARCHAR(2000),
plan_json TEXT,
plan_text TEXT,
total_cost DECIMAL(18,3)
);
CREATE INDEX IF NOT EXISTS ix_explain_0 ON db_perf_explain (statement_id);
CREATE INDEX IF NOT EXISTS ix_explain_1 ON db_perf_explain (requested_at);
-- changeset qbit-db-performance:18
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_query_snapshot_snapshot_id' AND LOWER(TABLE_NAME) = 'db_perf_query_snapshot' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Query Snapshots reference to Snapshots.
ALTER TABLE db_perf_query_snapshot ADD CONSTRAINT fk_query_snapshot_snapshot_id FOREIGN KEY (snapshot_id) REFERENCES db_perf_snapshot(id) ON DELETE CASCADE;
-- changeset qbit-db-performance:19
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_query_snapshot_statement_id' AND LOWER(TABLE_NAME) = 'db_perf_query_snapshot' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Query Snapshots reference to Queries.
ALTER TABLE db_perf_query_snapshot ADD CONSTRAINT fk_query_snapshot_statement_id FOREIGN KEY (statement_id) REFERENCES db_perf_statement(id) ON DELETE CASCADE;
-- changeset qbit-db-performance:20
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_query_daily_statement_id' AND LOWER(TABLE_NAME) = 'db_perf_query_daily' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Query Daily reference to Queries.
ALTER TABLE db_perf_query_daily ADD CONSTRAINT fk_query_daily_statement_id FOREIGN KEY (statement_id) REFERENCES db_perf_statement(id) ON DELETE CASCADE;
-- changeset qbit-db-performance:21
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_table_snapshot_snapshot_id' AND LOWER(TABLE_NAME) = 'db_perf_table_snapshot' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Table Snapshots reference to Snapshots.
ALTER TABLE db_perf_table_snapshot ADD CONSTRAINT fk_table_snapshot_snapshot_id FOREIGN KEY (snapshot_id) REFERENCES db_perf_snapshot(id) ON DELETE CASCADE;
-- changeset qbit-db-performance:22
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_table_snapshot_table_stat_id' AND LOWER(TABLE_NAME) = 'db_perf_table_snapshot' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Table Snapshots reference to Tables.
ALTER TABLE db_perf_table_snapshot ADD CONSTRAINT fk_table_snapshot_table_stat_id FOREIGN KEY (table_stat_id) REFERENCES db_perf_table_stat(id) ON DELETE CASCADE;
-- changeset qbit-db-performance:23
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_table_daily_table_stat_id' AND LOWER(TABLE_NAME) = 'db_perf_table_daily' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Table Daily reference to Tables.
ALTER TABLE db_perf_table_daily ADD CONSTRAINT fk_table_daily_table_stat_id FOREIGN KEY (table_stat_id) REFERENCES db_perf_table_stat(id) ON DELETE CASCADE;
-- changeset qbit-db-performance:24
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_index_stat_table_stat_id' AND LOWER(TABLE_NAME) = 'db_perf_index_stat' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Indexes reference to Tables.
ALTER TABLE db_perf_index_stat ADD CONSTRAINT fk_index_stat_table_stat_id FOREIGN KEY (table_stat_id) REFERENCES db_perf_table_stat(id) ON DELETE CASCADE;
-- changeset qbit-db-performance:25
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_activity_sample_snapshot_id' AND LOWER(TABLE_NAME) = 'db_perf_activity_sample' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Activity Samples reference to Snapshots.
ALTER TABLE db_perf_activity_sample ADD CONSTRAINT fk_activity_sample_snapshot_id FOREIGN KEY (snapshot_id) REFERENCES db_perf_snapshot(id) ON DELETE CASCADE;
-- changeset qbit-db-performance:26
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_activity_sample_statement_id' AND LOWER(TABLE_NAME) = 'db_perf_activity_sample' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Activity Samples reference to Queries.
ALTER TABLE db_perf_activity_sample ADD CONSTRAINT fk_activity_sample_statement_id FOREIGN KEY (statement_id) REFERENCES db_perf_statement(id) ON DELETE SET NULL;
-- changeset qbit-db-performance:27
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_connection_sample_snapshot_id' AND LOWER(TABLE_NAME) = 'db_perf_connection_sample' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Connection Samples reference to Snapshots.
ALTER TABLE db_perf_connection_sample ADD CONSTRAINT fk_connection_sample_snapshot_id FOREIGN KEY (snapshot_id) REFERENCES db_perf_snapshot(id) ON DELETE CASCADE;
-- changeset qbit-db-performance:28
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_finding_rule_id' AND LOWER(TABLE_NAME) = 'db_perf_finding' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Findings reference to Suggestion Rules.
ALTER TABLE db_perf_finding ADD CONSTRAINT fk_finding_rule_id FOREIGN KEY (rule_id) REFERENCES db_perf_rule(id) ON DELETE RESTRICT;
-- changeset qbit-db-performance:29
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_finding_statement_id' AND LOWER(TABLE_NAME) = 'db_perf_finding' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Findings reference to Queries.
ALTER TABLE db_perf_finding ADD CONSTRAINT fk_finding_statement_id FOREIGN KEY (statement_id) REFERENCES db_perf_statement(id) ON DELETE SET NULL;
-- changeset qbit-db-performance:30
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_finding_table_stat_id' AND LOWER(TABLE_NAME) = 'db_perf_finding' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Findings reference to Tables.
ALTER TABLE db_perf_finding ADD CONSTRAINT fk_finding_table_stat_id FOREIGN KEY (table_stat_id) REFERENCES db_perf_table_stat(id) ON DELETE SET NULL;
-- changeset qbit-db-performance:31
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_finding_index_stat_id' AND LOWER(TABLE_NAME) = 'db_perf_finding' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Findings reference to Indexes.
ALTER TABLE db_perf_finding ADD CONSTRAINT fk_finding_index_stat_id FOREIGN KEY (index_stat_id) REFERENCES db_perf_index_stat(id) ON DELETE SET NULL;
-- changeset qbit-db-performance:32
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE LOWER(CONSTRAINT_NAME) = 'fk_explain_statement_id' AND LOWER(TABLE_NAME) = 'db_perf_explain' AND LOWER(TABLE_SCHEMA) = LOWER(CURRENT_SCHEMA())
-- comment: Query Plans reference to Queries.
ALTER TABLE db_perf_explain ADD CONSTRAINT fk_explain_statement_id FOREIGN KEY (statement_id) REFERENCES db_perf_statement(id) ON DELETE SET NULL;
-- changeset qbit-db-performance:33
-- comment: Suggestion Rules insert-only defaults; administrator edits survive.
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'SLOW_QUERY', 'Slow query', 'Slow query.', TRUE, 'WARNING', 500, 'Mean execution time (ms)', 5, 'Database time (%)' WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'SLOW_QUERY');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'MISSING_INDEX', 'Possible missing index', 'Possible missing index.', TRUE, 'WARNING', 50, 'Sequential scan share (%)', 1000000, 'Tuples read per "day"' WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'MISSING_INDEX');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'UNUSED_INDEX', 'Unused index', 'Unused index.', TRUE, 'WARNING', 14, 'Days since statistics reset', 1, 'Index size (MB)' WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'UNUSED_INDEX');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'DUPLICATE_INDEX', 'Duplicate index', 'Duplicate index.', TRUE, 'WARNING', NULL, NULL, NULL, NULL WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'DUPLICATE_INDEX');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'N_PLUS_ONE', 'Repeated statements', 'Repeated statements.', TRUE, 'WARNING', 10, 'Repeats per unit', 5, 'Affected units' WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'N_PLUS_ONE');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'HOT_JOB', 'Expensive job', 'Expensive job.', TRUE, 'WARNING', 10, 'Database time (%)', NULL, NULL WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'HOT_JOB');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'OVER_AUDITED', 'High audit volume', 'High audit volume.', TRUE, 'WARNING', 50000, 'Audit inserts per "day"', NULL, NULL WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'OVER_AUDITED');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'BLOAT', 'Dead tuples', 'Dead tuples.', TRUE, 'WARNING', 20, 'Dead tuple ratio (%)', 10000, 'Dead tuples' WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'BLOAT');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'VACUUM_STALE', 'Stale vacuum', 'Stale vacuum.', TRUE, 'WARNING', 7, 'Days since vacuum', 150000000, 'Transaction freeze age' WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'VACUUM_STALE');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'POOL_SIZING', 'Connection pool sizing', 'Connection pool sizing.', TRUE, 'WARNING', 80, 'Connections used (%)', 50, 'Idle connections (%)' WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'POOL_SIZING');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'IDLE_IN_TRANSACTION', 'Idle transaction', 'Idle transaction.', TRUE, 'WARNING', 60, 'Idle seconds', NULL, NULL WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'IDLE_IN_TRANSACTION');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'CACHE_HIT_LOW', 'Low cache hit ratio', 'Low cache hit ratio.', TRUE, 'WARNING', 95, 'Cache hit ratio (%)', NULL, NULL WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'CACHE_HIT_LOW');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'TEMP_SPILL', 'Temporary disk spill', 'Temporary disk spill.', TRUE, 'WARNING', 100, 'Temporary data per "day" (MB)', NULL, NULL WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'TEMP_SPILL');
INSERT INTO db_perf_rule (code,label,description,enabled,severity,threshold_1,threshold_1_label,threshold_2,threshold_2_label)
SELECT 'DEADLOCKS', 'Deadlocks', 'Deadlocks.', TRUE, 'WARNING', 1, 'Deadlocks per "day"', NULL, NULL WHERE NOT EXISTS (SELECT 1 FROM db_perf_rule WHERE code = 'DEADLOCKS');
@@ -1,79 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import com.kingsrook.qbits.dbperformance.metadata.DbPerfMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Real Liquibase migration and insert-only defaults on H2.
*******************************************************************************/
class DbPerfChangelogTest
{
/*******************************************************************************
** Tables exist, all defaults are seeded and reapplying preserves edits.
*******************************************************************************/
@Test
void migrateTwice() throws Exception
{
QInstance instance = DbPerfTestSupport.instance();
RDBMSBackendMetaData backend = (RDBMSBackendMetaData) instance.getBackends().values().iterator().next();
try(Connection connection = DriverManager.getConnection(backend.getJdbcUrl(), "sa", ""); Statement statement = connection.createStatement())
{
for(String table : DbPerfMetaData.TABLES)
{
try(ResultSet rows = statement.executeQuery("SELECT count(*) FROM " + table))
{
rows.next();
assertThat(rows.getInt(1)).isEqualTo(table.equals("db_perf_rule") ? 14 : 0);
}
}
statement.executeUpdate("UPDATE db_perf_rule SET threshold_1=123 WHERE code='SLOW_QUERY'");
DbPerfTestSupport.migrate(backend);
try(var resource = getClass().getClassLoader().getResourceAsStream(DbPerfMetaData.CHANGELOG))
{
String script = new String(resource.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8);
String seeds = script.substring(script.indexOf("INSERT INTO db_perf_rule"));
for(String sql : seeds.split(";"))
{
if(!sql.isBlank())
{
statement.executeUpdate(sql);
}
}
}
try(ResultSet rows = statement.executeQuery("SELECT threshold_1 FROM db_perf_rule WHERE code='SLOW_QUERY'"))
{
rows.next();
assertThat(rows.getInt(1)).isEqualTo(123);
}
}
}
}
@@ -1,147 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.math.BigDecimal;
import java.sql.Connection;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import com.kingsrook.qbits.dbperformance.provider.ConnectionCount;
import com.kingsrook.qbits.dbperformance.provider.DatabaseStats;
import com.kingsrook.qbits.dbperformance.provider.IndexStats;
import com.kingsrook.qbits.dbperformance.provider.ProviderCapabilities;
import com.kingsrook.qbits.dbperformance.provider.SessionInfo;
import com.kingsrook.qbits.dbperformance.provider.StatementResetInfo;
import com.kingsrook.qbits.dbperformance.provider.StatementStats;
import com.kingsrook.qbits.dbperformance.provider.TableStats;
import com.kingsrook.qbits.dbperformance.provider.UnsupportedStatsProvider;
/*******************************************************************************
** Deterministic statistics stream for real QQQ/H2 service integration tests.
*******************************************************************************/
public class DbPerfFixtureProvider extends UnsupportedStatsProvider
{
public static final Instant START = Instant.parse("2026-10-01T12:00:00Z");
public long calls = 10;
public Instant reset = START.minusSeconds(3600);
public boolean limited;
public boolean fail;
/*******************************************************************************
** Supported full or reduced access.
*******************************************************************************/
@Override
public ProviderCapabilities capabilities(Connection connection)
{
return (new ProviderCapabilities(true, 170000, true, !limited, limited ? "NO_READ_ALL_STATS" : "FULL", limited ? "Grant pg_read_all_stats" : "Full statistics access"));
}
/*******************************************************************************
** PostgreSQL identity.
*******************************************************************************/
@Override
public String getName()
{
return ("POSTGRES");
}
/*******************************************************************************
** Cumulative database counters.
*******************************************************************************/
@Override
public DatabaseStats readDatabase(Connection connection)
{
return (new DatabaseStats(Map.of("xactCommit", calls, "xactRollback", 0L, "blksHit", calls * 9, "blksRead", calls, "tempFiles", 0L, "tempBytes", 0L, "deadlocks", 0L, "conflicts", 0L), reset, 100, 4096));
}
/*******************************************************************************
** Two shapes with different total time exercise top-N denominators.
*******************************************************************************/
@Override
public List<StatementStats> readStatements(Connection connection)
{
return (List.of(new StatementStats(1, "app", true, "select * from public.accounts where id=$1", calls, BigDecimal.valueOf(calls * 10), BigDecimal.TEN, BigDecimal.valueOf(15), calls, calls * 9, calls, 0, reset, reset),
new StatementStats(2, "app", true, "select * from public.accounts where name='private'", calls, BigDecimal.valueOf(calls * 5), BigDecimal.valueOf(5), BigDecimal.TEN, calls, calls, calls, 2, reset, reset)));
}
/*******************************************************************************
** Statement reset epoch.
*******************************************************************************/
@Override
public StatementResetInfo readStatementResetInfo(Connection connection)
{
return (new StatementResetInfo(reset, 0));
}
/*******************************************************************************
** A failure here occurs after statement writes, proving atomic rollback.
*******************************************************************************/
@Override
public List<TableStats> readTables(Connection connection)
{
if(fail)
{
throw (new IllegalStateException("raw SQL contains private patient text"));
}
return (List.of(new TableStats("public", "accounts", 44, Map.of("seqScan", calls, "idxScan", calls * 3, "nTupIns", calls, "nLiveTup", calls, "nDeadTup", 1L, "totalBytes", calls * 100, "tableBytes", calls * 90, "indexBytes", calls * 10, "freezeAge", 100L), Map.of("lastAutovacuum", START))));
}
/*******************************************************************************
** Index gauge linked to the collected table.
*******************************************************************************/
@Override
public List<IndexStats> readIndexes(Connection connection)
{
return (List.of(new IndexStats("public", "accounts", "accounts_pkey", 45, "CREATE UNIQUE INDEX accounts_pkey ON public.accounts(id)", "id", true, true, true, calls * 3, calls, calls, 1024, calls)));
}
/*******************************************************************************
** A blocked query, its idle blocker, and a long idle transaction.
*******************************************************************************/
@Override
public List<SessionInfo> readSessions(Connection connection)
{
return (List.of(new SessionInfo(10, "app", "site", "127.0.0.1", "active", "Lock", "relation", "client backend", START, START, START, BigDecimal.valueOf(50), BigDecimal.valueOf(60), List.of(20), 1L, "select * from accounts where id=999"),
new SessionInfo(20, "app", "site", null, "idle", null, null, "client backend", START, START, START, BigDecimal.TEN, BigDecimal.TEN, List.of(), null, "select 'private secret'"),
new SessionInfo(30, "app", "site", null, "idle in transaction", null, null, "client backend", START, START, START.minusSeconds(120), BigDecimal.valueOf(100), BigDecimal.valueOf(100), List.of(), null, "select $$private secret$$")));
}
/*******************************************************************************
** Capacity gauges.
*******************************************************************************/
@Override
public List<ConnectionCount> readConnections(Connection connection)
{
return (List.of(new ConnectionCount("app", "site", "active", 2), new ConnectionCount("app", "site", "idle", 3), new ConnectionCount("app", "site", "idle in transaction", 1)));
}
}
@@ -1,93 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;
import com.kingsrook.qbits.dbperformance.metadata.DbPerfMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Every metadata reference is backed by an actual PostgreSQL foreign key.
*******************************************************************************/
@Testcontainers(disabledWithoutDocker = true)
class DbPerfForeignKeyTest
{
@Container
private static final PostgreSQLContainer<?> POSTGRES = DbPerfPostgresSupport.container();
/*******************************************************************************
** The host changelog creates all references and reruns without changes.
*******************************************************************************/
@Test
void everyReferenceHasForeignKey() throws Exception
{
RDBMSBackendMetaData backend = DbPerfPostgresSupport.backend(POSTGRES);
QInstance instance = DbPerfTestSupport.instance(backend);
DbPerfTestSupport.migrate(backend);
try(Connection connection = DbPerfPostgresSupport.connect(POSTGRES))
{
for(QTableMetaData table : instance.getTables().values())
{
Set<String> references = new HashSet<>();
try(ResultSet keys = connection.getMetaData().getImportedKeys(null, "public", table.getName()))
{
while(keys.next())
{
references.add(keys.getString("FKCOLUMN_NAME") + ":" + keys.getString("PKTABLE_NAME"));
}
}
for(QFieldMetaData field : table.getFields().values())
{
if(field.getPossibleValueSourceName() != null && DbPerfMetaData.TABLES.contains(field.getPossibleValueSourceName()))
{
assertThat(references).as(table.getName() + "." + field.getBackendName()).contains(field.getBackendName() + ":" + field.getPossibleValueSourceName());
}
}
}
try(Statement statement = connection.createStatement())
{
statement.execute("INSERT INTO db_perf_statement(id) VALUES (999)");
statement.execute("INSERT INTO db_perf_explain(statement_id) VALUES (999)");
statement.execute("DELETE FROM db_perf_statement WHERE id=999");
try(ResultSet row = statement.executeQuery("SELECT statement_id FROM db_perf_explain"))
{
row.next();
assertThat(row.getObject(1)).isNull();
}
}
}
}
}
@@ -1,87 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.util.List;
import com.kingsrook.qbits.dbperformance.collect.DbPerfSettingsService;
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/*******************************************************************************
** Exact read/write permissions enforced by core actions and write customizers.
*******************************************************************************/
class DbPerfPermissionTest
{
/*******************************************************************************
** Viewers can read configuration but cannot mutate it or runtime tables.
*******************************************************************************/
@Test
void viewAndManageAreDistinct() throws Exception
{
QInstance instance = DbPerfTestSupport.instance();
instance.setEnforcePermissionsInCoreActions(true);
DbPerfRuntime runtime = DbPerfRuntime.of(instance);
QRecord settings = new DbPerfSettingsService(runtime).ensure(runtime.getConfig().getBackendName());
QContext.setQSession(new QSession().withPermissions(DbPerfPermissions.VIEW));
assertThat(DbPerfStore.query("db_perf_settings", new QQueryFilter())).hasSize(1);
QRecord change = new QRecord().withValue("id", settings.getValueInteger("id")).withValue("topStatementsPerSnapshot", 7);
assertThatThrownBy(() -> DbPerfStore.update("db_perf_settings", List.of(change))).hasMessageContaining("permission");
QContext.setQSession(new QSession().withPermissions(DbPerfPermissions.VIEW, DbPerfPermissions.MANAGE_SETTINGS));
DbPerfStore.update("db_perf_settings", List.of(change));
assertThat(new DbPerfSettingsService(runtime).ensure(runtime.getConfig().getBackendName()).getValueInteger("topStatementsPerSnapshot")).isEqualTo(7);
assertThatThrownBy(() -> DbPerfStore.insert("db_perf_settings", List.of(new QRecord().withValue("backendName", "other")))).hasMessageContaining("permission");
assertThatThrownBy(() -> DbPerfStore.insert("db_perf_snapshot", List.of(new QRecord().withValue("capturedAt", java.time.Instant.now())))).hasMessageContaining("permission");
QRecord rule = DbPerfStore.insert("db_perf_rule", List.of(new QRecord().withValue("code", "CUSTOM").withValue("label", "Custom rule"))).getFirst();
DbPerfStore.update("db_perf_rule", List.of(new QRecord().withValue("id", rule.getValueInteger("id")).withValue("enabled", false)));
DbPerfStore.delete("db_perf_rule", DbPerfStore.eq("id", rule.getValueInteger("id")));
assertThat(PermissionsHelper.getAllAvailablePermissions(instance)).extracting(permission -> permission.getName()).contains(DbPerfPermissions.VIEW, DbPerfPermissions.MANAGE_SETTINGS)
.doesNotContain("db_performance.view.hasAccess", "db_performance.read", "db_performance.write");
QContext.setQSession(new QSession());
assertThatThrownBy(() -> DbPerfStore.query("db_perf_statement", new QQueryFilter())).hasMessageContaining("Permission");
}
/*******************************************************************************
** Runtime-only writes stay guarded when hosts disable core enforcement.
*******************************************************************************/
@Test
void guardWithoutCoreEnforcement() throws Exception
{
QInstance instance = DbPerfTestSupport.instance();
instance.setEnforcePermissionsInCoreActions(false);
DbPerfRuntime runtime = DbPerfRuntime.of(instance);
QRecord settings = new DbPerfSettingsService(runtime).ensure(runtime.getConfig().getBackendName());
QContext.setQSession(new QSession().withPermissions(DbPerfPermissions.VIEW));
assertThatThrownBy(() -> DbPerfStore.update("db_perf_settings", List.of(new QRecord().withValue("id", settings.getValueInteger("id")).withValue("enabled", false))))
.hasMessageContaining("record write failed");
assertThatThrownBy(() -> DbPerfStore.insert("db_perf_statement", List.of(new QRecord().withValue("queryId", 1L)))).hasMessageContaining("record write failed");
}
}
@@ -1,80 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.UUID;
import com.kingsrook.qqq.backend.module.postgres.model.metadata.PostgreSQLBackendMetaData;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import org.testcontainers.containers.PostgreSQLContainer;
/*******************************************************************************
** Shared PostgreSQL 17 test setup, using the same JDBC role as the host backend.
*******************************************************************************/
public final class DbPerfPostgresSupport
{
/*******************************************************************************
** Utility class.
*******************************************************************************/
private DbPerfPostgresSupport()
{
}
/*******************************************************************************
** pg_stat_statements must be loaded at server startup.
*******************************************************************************/
public static PostgreSQLContainer<?> container()
{
return (new PostgreSQLContainer<>("postgres:17-alpine")
.withCommand("postgres", "-c", "shared_preload_libraries=pg_stat_statements", "-c", "pg_stat_statements.track=top"));
}
/*******************************************************************************
** Install the extension in the current database.
*******************************************************************************/
public static RDBMSBackendMetaData backend(PostgreSQLContainer<?> postgres) throws Exception
{
try(Connection connection = connect(postgres); Statement statement = connection.createStatement())
{
statement.execute("CREATE EXTENSION IF NOT EXISTS pg_stat_statements");
}
RDBMSBackendMetaData backend = new PostgreSQLBackendMetaData().withName("postgres" + UUID.randomUUID().toString().replace("-", ""))
.withVendor("postgresql").withHostName(postgres.getHost()).withDatabaseName(postgres.getDatabaseName())
.withUsername(postgres.getUsername()).withPassword(postgres.getPassword());
backend.setJdbcUrl(postgres.getJdbcUrl());
return (backend);
}
/*******************************************************************************
** Direct test fixture connection.
*******************************************************************************/
public static Connection connect(PostgreSQLContainer<?> postgres) throws Exception
{
return (DriverManager.getConnection(postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword()));
}
}
@@ -1,80 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.util.List;
import com.kingsrook.qbits.dbperformance.collect.DbPerfSettingsService;
import com.kingsrook.qbits.dbperformance.processes.DbPerfCollectStep;
import com.kingsrook.qbits.dbperformance.processes.DbPerfProcesses;
import com.kingsrook.qbits.dbperformance.processes.DbPerfRollupStep;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.jobs.DurableProcessMetaData;
import com.kingsrook.qqq.backend.core.jobs.model.JobIdempotency;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/*******************************************************************************
** Process metadata, system execution, summaries and runtime permission guards.
*******************************************************************************/
class DbPerfProcessTest
{
/*******************************************************************************
** Scheduled processes stay out of navigation and register optional cron jobs.
*******************************************************************************/
@Test
void metadataAndExecution() throws Exception
{
QInstance instance = DbPerfTestSupport.instance();
assertThat(instance.getProcesses()).containsKeys(DbPerfProcesses.COLLECT, DbPerfProcesses.ROLLUP, DbPerfProcesses.COLLECT_NOW);
for(String name : List.of(DbPerfProcesses.COLLECT, DbPerfProcesses.ROLLUP))
{
QProcessMetaData process = instance.getProcess(name);
assertThat(process.getIsHidden()).isTrue();
assertThat(process.getSchedule()).isNull();
assertThat(DurableProcessMetaData.of(process).getEligible()).isTrue();
assertThat(DurableProcessMetaData.of(process).getIdempotency()).isEqualTo(JobIdempotency.NATURAL);
}
assertThat(instance.getProcess(DbPerfProcesses.COLLECT_NOW).getTableName()).isEqualTo("db_perf_snapshot");
RunBackendStepInput input = new RunBackendStepInput();
input.addValue("forceTables", true);
RunBackendStepOutput output = new RunBackendStepOutput();
new DbPerfCollectStep().run(input, output);
assertThat(output.getValue("processResults")).isNotNull();
input.addValue("forceTables", false);
new DbPerfCollectStep().run(input, new RunBackendStepOutput());
new DbPerfRollupStep().run(input, new RunBackendStepOutput());
DbPerfRuntime.of(instance).getConfig().setSchedulerName("scheduler");
QInstance scheduledInstance = new QInstance();
DbPerfProcesses.addTo(scheduledInstance, DbPerfRuntime.of(instance).getConfig());
assertThat(scheduledInstance.getProcess(DbPerfProcesses.COLLECT).getSchedule().getCronExpression()).isEqualTo("0 */5 * * * ?");
QContext.setQSession(new QSession().withPermissions(DbPerfPermissions.VIEW));
assertThatThrownBy(() -> new DbPerfCollectStep().run(input, output)).hasMessageContaining("permission");
assertThatThrownBy(() -> new DbPerfRollupStep().run(input, output)).hasMessageContaining("permission");
}
}
@@ -1,15 +1,11 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
* Copyright 2026 Kingsrook, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -21,115 +17,66 @@
package com.kingsrook.qbits.dbperformance;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.util.List;
import com.kingsrook.qbits.dbperformance.metadata.DbPerfMetaData;
import com.kingsrook.qqq.backend.core.actions.ai.AiExposure;
import com.kingsrook.qqq.backend.core.actions.ai.AiTableMetaData;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitConfigValidationException;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/*******************************************************************************
** Metadata and entity storage contract tests.
** Unit test for DbPerfQBitProducer
*******************************************************************************/
class DbPerfQBitProducerTest
{
/*******************************************************************************
** All tables use the exact storage names and protected navigation.
** A configured qbit registers itself in the instance.
*******************************************************************************/
@Test
void metadata() throws Exception
void testProduce() throws Exception
{
QInstance instance = DbPerfTestSupport.instance();
assertThat(instance.getTables()).hasSize(17);
assertThat(instance.getQBits()).hasSize(1);
for(String name : DbPerfMetaData.TABLES)
{
QTableMetaData table = instance.getTable(name);
assertThat(((RDBMSTableBackendDetails) table.getBackendDetails()).getTableName()).isEqualTo(name);
assertThat(table.getPermissionRules().getPermissionBaseName()).isEqualTo(DbPerfPermissions.VIEW);
table.getFields().values().forEach(field -> assertThat(field.getBackendName()).matches("[a-z][a-z0-9_]*"));
if(!List.of("db_perf_settings", "db_perf_rule").contains(name))
{
assertThat(table.getDisabledCapabilities()).contains(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
}
}
assertThat(AiTableMetaData.of(instance.getTable("db_perf_settings")).getExposure()).isEqualTo(AiExposure.NONE);
assertThat(AiTableMetaData.of(instance.getTable("db_perf_rule")).getExposure()).isEqualTo(AiExposure.NONE);
assertThat(instance.getApp("dbPerformance").getSections().getFirst().getTables()).containsExactly("db_perf_finding", "db_perf_statement", "db_perf_table_stat", "db_perf_index_stat", "db_perf_snapshot", "db_perf_settings", "db_perf_rule");
assertThat(instance.getJoins()).hasSize(13);
QInstance qInstance = new QInstance();
DbPerfQBitConfig config = new DbPerfQBitConfig().withBackendName("rdbms");
DbPerfQBitProducer producer = new DbPerfQBitProducer().withQBitConfig(config).withNamespace("db_perf");
assertThat(producer.isEnabled()).isTrue();
assertThat(producer.getQBitConfig()).isSameAs(config);
assertThat(producer.getNamespace()).isEqualTo("db_perf");
assertThat(config.getDefaultBackendNameForTables()).isEqualTo("rdbms");
producer.produce(qInstance);
assertThat(qInstance.getQBits()).hasSize(1);
assertThat(qInstance.getQBits().values().iterator().next().getArtifactId()).isEqualTo("qbit-db-performance");
}
/*******************************************************************************
** Fail before modifying the instance when configuration conflicts.
** Without a config the producer is disabled; without a backend it fails.
*******************************************************************************/
@Test
void configuration() throws Exception
void testDisabledAndInvalid()
{
assertThat(new DbPerfQBitProducer().isEnabled()).isFalse();
assertThatThrownBy(() -> new DbPerfQBitProducer().produce(new QInstance())).hasMessageContaining("configuration");
assertThatThrownBy(() -> new DbPerfQBitProducer().withQBitConfig(new DbPerfQBitConfig()).produce(new QInstance())).isInstanceOf(QBitConfigValidationException.class);
QInstance instance = DbPerfTestSupport.instance();
DbPerfQBitConfig config = new DbPerfQBitConfig().withBackendName(instance.getBackends().keySet().iterator().next());
DbPerfQBitProducer producer = new DbPerfQBitProducer().withQBitConfig(config).withNamespace("db_perf");
assertThat(producer.getNamespace()).isEqualTo("db_perf");
assertThat(producer.getQBitConfig()).isSameAs(config);
assertThat(producer.isEnabled()).isTrue();
assertThat(config.getDefaultBackendNameForTables()).isEqualTo(config.getBackendName());
assertThatThrownBy(() -> producer.produce(instance)).isInstanceOf(QBitConfigValidationException.class);
for(Method method : DbPerfQBitConfig.class.getDeclaredMethods())
{
if(method.getName().startsWith("with"))
{
Object value = method.getParameterTypes()[0] == Boolean.class ? false : "configured";
assertThat(method.invoke(config, value)).isSameAs(config);
assertThat(DbPerfQBitConfig.class.getMethod("get" + method.getName().substring(4)).invoke(config)).isEqualTo(value);
DbPerfQBitConfig.class.getMethod("set" + method.getName().substring(4), method.getParameterTypes()).invoke(config, value);
}
}
DbPerfQBitConfig config = new DbPerfQBitConfig();
config.setBackendName(null);
assertThat(config.getBackendName()).isNull();
assertThatThrownBy(() -> new DbPerfQBitProducer().withQBitConfig(config).produce(new QInstance()))
.isInstanceOf(QBitConfigValidationException.class);
}
/*******************************************************************************
** Every annotated entity round-trips through QRecord without type loss.
** Names follow the owner rule: lowercase snake_case with the full qbit name.
*******************************************************************************/
@Test
void entityStorageContract() throws Exception
void testNaming()
{
for(String name : DbPerfMetaData.TABLES)
{
StringBuilder typeName = new StringBuilder("com.kingsrook.qbits.dbperformance.model.DbPerf");
for(String part : name.substring("db_perf_".length()).split("_"))
{
typeName.append(Character.toUpperCase(part.charAt(0))).append(part.substring(1));
}
Class<?> type = Class.forName(typeName.toString());
QRecordEntity entity = (QRecordEntity) type.getConstructor().newInstance();
for(Method method : type.getDeclaredMethods())
{
if(method.getName().startsWith("with"))
{
Class<?> fieldType = method.getParameterTypes()[0];
Object value = fieldType == Integer.class ? 12 : fieldType == Long.class ? 9000000000L : fieldType == Boolean.class ? true : fieldType == Instant.class ? Instant.parse("2026-01-01T00:00:00Z") : fieldType == LocalDate.class ? LocalDate.of(2026, 1, 1) : fieldType == BigDecimal.class ? new BigDecimal("12.345") : "value";
assertThat(method.invoke(entity, value)).isSameAs(entity);
type.getMethod("set" + method.getName().substring(4), fieldType).invoke(entity, value);
Object copy = type.getConstructor(QRecord.class).newInstance(entity.toQRecord());
assertThat(type.getMethod("get" + method.getName().substring(4)).invoke(copy)).isEqualTo(value);
}
}
}
assertThat(DbPerfQBitConfig.TABLE_NAME_PREFIX).isEqualTo("db_perf_").matches("^[a-z][a-z0-9_]*_$");
assertThat(DbPerfQBitConfig.PERMISSION_BASE_NAME).isEqualTo("db_perf");
}
}
@@ -1,93 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.UUID;
import com.kingsrook.qbits.dbperformance.metadata.DbPerfMetaData;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import liquibase.Liquibase;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.resource.ClassLoaderResourceAccessor;
/*******************************************************************************
** Isolated H2 fixtures using the real host migration and QQQ actions.
*******************************************************************************/
public final class DbPerfTestSupport
{
/*******************************************************************************
** Utility class.
*******************************************************************************/
private DbPerfTestSupport()
{
}
/*******************************************************************************
** New isolated instance with schema and system context.
*******************************************************************************/
public static QInstance instance() throws Exception
{
String name = "dbperf" + UUID.randomUUID().toString().replace("-", "");
RDBMSBackendMetaData backend = new RDBMSBackendMetaData().withName(name).withVendor("h2").withHostName("mem").withDatabaseName(name).withUsername("sa");
backend.setJdbcUrl("jdbc:h2:mem:" + name + ";CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1;MODE=PostgreSQL");
return (instance(backend));
}
/*******************************************************************************
** Wire the producer onto an existing backend.
*******************************************************************************/
public static QInstance instance(RDBMSBackendMetaData backend) throws Exception
{
QInstance instance = new QInstance();
instance.addBackend(backend);
instance.withInstanceDefaultAuthentication(new QAuthenticationMetaData().withName("mock").withType(QAuthenticationType.MOCK));
new DbPerfQBitProducer().withQBitConfig(new DbPerfQBitConfig().withBackendName(backend.getName())).produce(instance);
migrate(backend);
QContext.init(instance, new QSystemUserSession(instance));
return (instance);
}
/*******************************************************************************
** Apply or reapply the shipped changelog through Liquibase.
*******************************************************************************/
public static void migrate(RDBMSBackendMetaData backend) throws Exception
{
try(Connection connection = DriverManager.getConnection(backend.getJdbcUrl(), backend.getUsername(), backend.getPassword());
Liquibase liquibase = new Liquibase(DbPerfMetaData.CHANGELOG, new ClassLoaderResourceAccessor(), DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection))))
{
liquibase.getDatabase().setDatabaseChangeLogTableName("databasechangelog");
liquibase.getDatabase().setDatabaseChangeLogLockTableName("databasechangeloglock");
liquibase.update("");
}
}
}
@@ -1,82 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.math.BigDecimal;
import java.time.Instant;
import com.kingsrook.qbits.dbperformance.collect.DeltaCalculator;
import com.kingsrook.qbits.dbperformance.provider.StatementStats;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Counter baseline, reset and monotonic interval arithmetic.
*******************************************************************************/
class DeltaCalculatorTest
{
private static final Instant CAPTURE = Instant.parse("2026-10-01T12:00:00Z");
/*******************************************************************************
** Old unknown entries baseline; new entries inside the interval count.
*******************************************************************************/
@Test
void baselines()
{
assertThat(DeltaCalculator.calculate(null, entry(100, null), CAPTURE, null).baseline()).isTrue();
assertThat(DeltaCalculator.calculate(null, entry(100, CAPTURE.minusSeconds(1)), CAPTURE, null).calls()).isZero();
assertThat(DeltaCalculator.calculate(null, entry(100, CAPTURE.plusSeconds(1)), CAPTURE, null).calls()).isEqualTo(100);
assertThat(DeltaCalculator.calculate(null, entry(100, CAPTURE.plusSeconds(1)), null, null).calls()).isZero();
}
/*******************************************************************************
** Decreased calls, changed entry epoch and global resets all count from zero.
*******************************************************************************/
@Test
void resetsAndDeltas()
{
StatementStats before = entry(10, CAPTURE.minusSeconds(60));
DeltaCalculator.Delta delta = DeltaCalculator.calculate(before, entry(15, before.statsSince()), CAPTURE, null);
assertThat(delta.calls()).isEqualTo(5);
assertThat(delta.totalMs()).isEqualByComparingTo("50");
assertThat(delta.rows()).isEqualTo(5);
assertThat(delta.reset()).isFalse();
assertThat(DeltaCalculator.calculate(before, entry(3, before.statsSince()), CAPTURE, null).reset()).isTrue();
assertThat(DeltaCalculator.calculate(before, entry(15, CAPTURE), CAPTURE, null).calls()).isEqualTo(15);
assertThat(DeltaCalculator.calculate(before, entry(15, before.statsSince()), CAPTURE, CAPTURE.plusSeconds(1)).reset()).isTrue();
assertThat(DeltaCalculator.counter(null, 100, false)).isZero();
assertThat(DeltaCalculator.counter(10L, 3, false)).isEqualTo(3);
assertThat(DeltaCalculator.counter(10L, 30, true)).isEqualTo(30);
assertThat(DeltaCalculator.counter(10L, 30, false)).isEqualTo(20);
}
/*******************************************************************************
** A coherent counter sample.
*******************************************************************************/
private static StatementStats entry(long calls, Instant since)
{
return (new StatementStats(1, "app", true, "select $1", calls, BigDecimal.valueOf(calls * 10), BigDecimal.TEN, BigDecimal.TEN, calls, calls, calls, calls, since, null));
}
}
@@ -1,82 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.sql.Connection;
import java.sql.Statement;
import com.kingsrook.qbits.dbperformance.provider.DbStatsProviders;
import com.kingsrook.qbits.dbperformance.provider.PostgresStatsProvider;
import com.kingsrook.qbits.dbperformance.provider.ProviderCapabilities;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Live PostgreSQL view contracts; skipped only when Docker is unavailable.
*******************************************************************************/
@Testcontainers(disabledWithoutDocker = true)
class PostgresStatsProviderIT
{
@Container
private static final PostgreSQLContainer<?> POSTGRES = DbPerfPostgresSupport.container();
/*******************************************************************************
** Read every provider surface against pg_stat_statements on PostgreSQL 17.
*******************************************************************************/
@Test
void readAllStatistics() throws Exception
{
DbPerfPostgresSupport.backend(POSTGRES);
try(Connection connection = DbPerfPostgresSupport.connect(POSTGRES); Statement statement = connection.createStatement())
{
statement.execute("CREATE TABLE provider_sample (id int PRIMARY KEY, value text)");
statement.execute("INSERT INTO provider_sample VALUES (1, 'private value')");
statement.execute("SELECT * FROM provider_sample WHERE id=1");
PostgresStatsProvider provider = (PostgresStatsProvider) DbStatsProviders.forConnection(connection);
ProviderCapabilities capabilities = provider.capabilities(connection);
assertThat(provider.getName()).isEqualTo("POSTGRES");
assertThat(capabilities.supported()).isTrue();
assertThat(capabilities.version()).isGreaterThanOrEqualTo(170000);
assertThat(capabilities.statsAccess()).isEqualTo("FULL");
assertThat(provider.readDatabase(connection).sizeBytes()).isPositive();
assertThat(provider.readStatements(connection)).anySatisfy(row ->
{
assertThat(row.queryText()).contains("SELECT * FROM provider_sample");
assertThat(row.toplevel()).isTrue();
assertThat(row.statsSince()).isNotNull();
});
assertThat(provider.readStatementResetInfo(connection).statsReset()).isNotNull();
assertThat(provider.readTables(connection)).anySatisfy(row -> assertThat(row.tableName()).isEqualTo("provider_sample"));
assertThat(provider.readIndexes(connection)).anySatisfy(row -> assertThat(row.indexName()).isEqualTo("provider_sample_pkey"));
try(Connection other = DbPerfPostgresSupport.connect(POSTGRES))
{
assertThat(provider.readSessions(connection)).isNotEmpty();
}
assertThat(provider.readConnections(connection)).isNotEmpty();
assertThat(provider.readSettings(connection)).containsKeys("server_version_num", "max_connections", "block_size");
}
}
}
@@ -1,139 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.math.BigDecimal;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qbits.dbperformance.collect.DbPerfSettingsService;
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
import com.kingsrook.qbits.dbperformance.collect.RetentionService;
import com.kingsrook.qbits.dbperformance.collect.RollupService;
import com.kingsrook.qbits.dbperformance.collect.SnapshotCollector;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Daily replacement and keyset retention integration using real QQQ/H2 writes.
*******************************************************************************/
class RollupRetentionTest
{
/*******************************************************************************
** Weighted query aggregates, last table gauges and null-safe attribution keys.
*******************************************************************************/
@Test
void rollupIsIdempotent() throws Exception
{
DbPerfRuntime runtime = DbPerfRuntime.of(DbPerfTestSupport.instance());
DbPerfFixtureProvider provider = new DbPerfFixtureProvider();
snapshot(runtime, provider, 0);
provider.calls = 15;
snapshot(runtime, provider, 300);
provider.calls = 25;
snapshot(runtime, provider, 600);
QRecord attribution = new QRecord().withValue("windowStart", DbPerfFixtureProvider.START).withValue("windowEnd", DbPerfFixtureProvider.START.plusSeconds(60))
.withValue("podName", "one").withValue("unitKind", "PROCESS").withValue("unitName", "example").withValue("operation", "SELECT").withValue("fingerprint", "a".repeat(64))
.withValue("sqlText", "select ?").withValue("units", 2L).withValue("statements", 5L).withValue("totalMs", BigDecimal.TEN).withValue("maxMs", BigDecimal.valueOf(4)).withValue("rowsCount", 3L).withValue("errors", 1L);
QRecord second = new QRecord(attribution).withValue("podName", "two");
DbPerfStore.insert("db_perf_attribution", List.of(attribution, second));
RollupService rollup = new RollupService(runtime);
LocalDate day = LocalDate.of(2026, 10, 1);
assertThat(rollup.needsBackfill(day)).isTrue();
rollup.rollupDay(day);
assertThat(rollup.needsBackfill(day)).isFalse();
rollup.rollupDay(day);
List<QRecord> daily = DbPerfStore.query("db_perf_query_daily", new QQueryFilter());
assertThat(daily).hasSize(2);
QRecord first = daily.stream().filter(row -> row.getValueBigDecimal("totalMs").compareTo(BigDecimal.valueOf(150)) == 0).findFirst().orElseThrow();
assertThat(first.getValueLong("calls")).isEqualTo(15);
assertThat(first.getValueBigDecimal("meanMs")).isEqualByComparingTo("10");
assertThat(first.getValueBigDecimal("hitRatio")).isEqualByComparingTo("90");
assertThat(first.getValueBigDecimal("shareOfDbTime")).isEqualByComparingTo("66.6667");
QRecord table = DbPerfStore.query("db_perf_table_daily", new QQueryFilter()).getFirst();
assertThat(table.getValueLong("seqScan")).isEqualTo(15);
assertThat(table.getValueLong("totalBytes")).isEqualTo(2500);
List<QRecord> attributed = DbPerfStore.query("db_perf_attribution_daily", new QQueryFilter());
assertThat(attributed).hasSize(1);
assertThat(attributed.getFirst().getValueLong("units")).isEqualTo(4);
assertThat(attributed.getFirst().getValueLong("statements")).isEqualTo(10);
assertThat(attributed.getFirst().getValueBigDecimal("totalMs")).isEqualByComparingTo("20");
assertThat(attributed.getFirst().getValueString("rollupKey")).hasSize(64);
rollup.rollupDay(day.plusDays(1));
assertThat(DbPerfStore.query("db_perf_query_daily", new QQueryFilter())).hasSize(2);
QRecord nextWeek = new SnapshotCollector(runtime, Clock.fixed(DbPerfFixtureProvider.START.plusSeconds(7 * 86400L), ZoneOffset.UTC), provider)
.collect(runtime.getConfig().getBackendName(), true);
assertThat(nextWeek.getValueString("status")).isEqualTo("OK");
assertThat(DbPerfStore.query("db_perf_table_stat", new QQueryFilter()).getFirst().getValueLong("growthBytes7d")).isEqualTo(1500);
}
/*******************************************************************************
** More than one deletion batch, strict cutoff boundaries and open findings.
*******************************************************************************/
@Test
void retentionBatchesAndClosedFindings() throws Exception
{
DbPerfRuntime runtime = DbPerfRuntime.of(DbPerfTestSupport.instance());
String backend = runtime.getConfig().getBackendName();
QRecord settings = new DbPerfSettingsService(runtime).ensure(backend);
settings.withValue("detailRetentionDays", 2).withValue("rollupRetentionDays", 5);
DbPerfStore.update("db_perf_settings", List.of(settings));
Instant now = DbPerfFixtureProvider.START;
List<QRecord> rows = new ArrayList<>();
for(int i = 0; i < 1005; i++)
{
rows.add(new QRecord().withValue("windowStart", now.minusSeconds(3 * 86400)).withValue("unitName", "old" + i));
}
rows.add(new QRecord().withValue("windowStart", now.minusSeconds(2 * 86400)).withValue("unitName", "boundary"));
DbPerfStore.insert("db_perf_attribution", rows);
DbPerfStore.insert("db_perf_attribution_daily", List.of(new QRecord().withValue("day", LocalDate.of(2026, 9, 20)).withValue("rollupKey", "old")));
int rule = DbPerfStore.query("db_perf_rule", new QQueryFilter()).getFirst().getValueInteger("id");
List<QRecord> findings = new ArrayList<>();
for(String status : List.of("OPEN", "ACKNOWLEDGED", "RESOLVED", "IGNORED"))
{
findings.add(new QRecord().withValue("backendName", backend).withValue("ruleId", rule).withValue("dedupeKey", status).withValue("status", status).withValue("lastSeenAt", now.minusSeconds(6 * 86400)));
}
DbPerfStore.insert("db_perf_finding", findings);
RetentionService retention = new RetentionService(runtime, Clock.fixed(now, ZoneOffset.UTC));
assertThat(retention.purge()).isEqualTo(1008);
assertThat(retention.purge()).isZero();
assertThat(DbPerfStore.query("db_perf_attribution", new QQueryFilter())).singleElement().satisfies(row -> assertThat(row.getValueString("unitName")).isEqualTo("boundary"));
assertThat(DbPerfStore.query("db_perf_finding", new QQueryFilter())).extracting(row -> row.getValueString("status")).containsExactlyInAnyOrder("OPEN", "ACKNOWLEDGED");
}
/*******************************************************************************
** Timestamped forced table capture.
*******************************************************************************/
private static void snapshot(DbPerfRuntime runtime, DbPerfFixtureProvider provider, int seconds)
{
QRecord result = new SnapshotCollector(runtime, Clock.fixed(DbPerfFixtureProvider.START.plusSeconds(seconds), ZoneOffset.UTC), provider).collect(runtime.getConfig().getBackendName(), true);
assertThat(result.getValueString("status")).as(result.getValueString("errorMessage")).isEqualTo("OK");
}
}
@@ -1,98 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.sql.Connection;
import java.sql.Statement;
import java.util.UUID;
import com.kingsrook.qbits.dbperformance.collect.DbPerfSettingsService;
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
import com.kingsrook.qbits.dbperformance.collect.SnapshotCollector;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.module.postgres.model.metadata.PostgreSQLBackendMetaData;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Live PostgreSQL collection, reset detection and reduced-role access.
*******************************************************************************/
@Testcontainers(disabledWithoutDocker = true)
class SnapshotCollectorPostgresTest
{
@Container
private static final PostgreSQLContainer<?> POSTGRES = DbPerfPostgresSupport.container();
/*******************************************************************************
** Two real passes retain deltas; reset is explicit and a limited role is PARTIAL.
*******************************************************************************/
@Test
void snapshotsAndReducedAccess() throws Exception
{
RDBMSBackendMetaData backend = DbPerfPostgresSupport.backend(POSTGRES);
QInstance instance = DbPerfTestSupport.instance(backend);
DbPerfRuntime runtime = DbPerfRuntime.of(instance);
SnapshotCollector collector = new SnapshotCollector(runtime);
assertThat(collector.collect(backend.getName(), true).getValueString("status")).isEqualTo("OK");
try(Connection connection = DbPerfPostgresSupport.connect(POSTGRES); Statement statement = connection.createStatement())
{
for(int i = 0; i < 5; i++)
{
statement.execute("SELECT sum(id) FROM db_perf_rule WHERE id > 0");
}
statement.execute("SELECT pg_stat_force_next_flush()");
}
QRecord second = collector.collect(backend.getName(), true);
assertThat(second.getValueString("status")).as(second.getValueString("errorMessage")).isEqualTo("OK");
assertThat(second.getValueLong("statementCalls")).isPositive();
assertThat(DbPerfStore.query("db_perf_query_snapshot", new QQueryFilter())).isNotEmpty();
assertThat(DbPerfStore.query("db_perf_table_stat", new QQueryFilter())).isNotEmpty();
assertThat(DbPerfStore.query("db_perf_index_stat", new QQueryFilter())).isNotEmpty();
try(Connection connection = DbPerfPostgresSupport.connect(POSTGRES); Statement statement = connection.createStatement())
{
statement.execute("SELECT pg_stat_statements_reset()");
}
assertThat(collector.collect(backend.getName(), true).getValueBoolean("statsResetDetected")).isTrue();
try(Connection connection = DbPerfPostgresSupport.connect(POSTGRES); Statement statement = connection.createStatement())
{
statement.execute("CREATE ROLE dbperf_limited LOGIN PASSWORD 'test-only'");
statement.execute("GRANT USAGE ON SCHEMA public TO dbperf_limited");
statement.execute("GRANT ALL ON ALL TABLES IN SCHEMA public TO dbperf_limited");
statement.execute("GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO dbperf_limited");
}
RDBMSBackendMetaData limited = new PostgreSQLBackendMetaData().withName("limited" + UUID.randomUUID().toString().replace("-", ""))
.withVendor("postgresql").withHostName(POSTGRES.getHost()).withDatabaseName(POSTGRES.getDatabaseName()).withUsername("dbperf_limited").withPassword("test-only");
limited.setJdbcUrl(POSTGRES.getJdbcUrl());
QInstance limitedInstance = DbPerfTestSupport.instance(limited);
DbPerfRuntime limitedRuntime = DbPerfRuntime.of(limitedInstance);
QRecord partial = new SnapshotCollector(limitedRuntime).collect(limited.getName(), true);
assertThat(partial.getValueString("status")).as(partial.getValueString("errorMessage")).isEqualTo("PARTIAL");
assertThat(new DbPerfSettingsService(limitedRuntime).ensure(limited.getName()).getValueString("statsAccessMessage")).contains("pg_read_all_stats");
}
}
@@ -1,182 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import java.math.BigDecimal;
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.List;
import com.kingsrook.qbits.dbperformance.collect.DbPerfSettingsService;
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
import com.kingsrook.qbits.dbperformance.collect.SnapshotCollector;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Real H2 QQQ actions with deterministic vendor samples: no mocked persistence.
*******************************************************************************/
class SnapshotCollectorTest
{
/*******************************************************************************
** Baseline, delta top-N, reset, tables, normalized activity and partial mode.
*******************************************************************************/
@Test
void collectionLifecycle() throws Exception
{
QInstance instance = DbPerfTestSupport.instance();
DbPerfRuntime runtime = DbPerfRuntime.of(instance);
String backend = runtime.getConfig().getBackendName();
QRecord settings = new DbPerfSettingsService(runtime).ensure(backend);
settings.setValue("topStatementsPerSnapshot", 1);
DbPerfStore.update("db_perf_settings", List.of(settings));
assertThat(new DbPerfSettingsService(runtime).ensure(backend).getValueInteger("topStatementsPerSnapshot")).isEqualTo(1);
DbPerfFixtureProvider provider = new DbPerfFixtureProvider();
QRecord baseline = collect(runtime, provider, 0, true);
assertThat(baseline.getValueString("status")).as(baseline.getValueString("errorMessage")).isEqualTo("OK");
assertThat(baseline.getValueLong("statementCalls")).isZero();
provider.calls = 15;
QRecord delta = collect(runtime, provider, 300, false);
assertThat(delta.getValueString("status")).as(delta.getValueString("errorMessage")).isEqualTo("OK");
assertThat(delta.getValueLong("statementCalls")).isEqualTo(10);
assertThat(delta.getValueBigDecimal("statementTotalMs")).isEqualByComparingTo("75");
assertThat(delta.getValueInteger("storedStatementCount")).isEqualTo(1);
assertThat(delta.getValueBoolean("includesTables")).isFalse();
assertThat(delta.getValueLong("xactCommit")).isEqualTo(5);
assertThat(delta.getValueBigDecimal("cacheHitRatio")).isEqualByComparingTo("90");
assertThat(delta.getValueInteger("connectionsTotal")).isEqualTo(6);
QRecord detail = DbPerfStore.query("db_perf_query_snapshot", new QQueryFilter()).getFirst();
assertThat(detail.getValueBigDecimal("shareOfDbTime")).isEqualByComparingTo("66.6667");
QRecord statement = DbPerfStore.first("db_perf_statement", DbPerfStore.eq("queryId", 1L));
assertThat(statement.getValueLong("calls24h")).isEqualTo(5);
assertThat(statement.getValueBigDecimal("totalMs24h")).isEqualByComparingTo("50");
assertThat(statement.getValueBigDecimal("meanMs24h")).isEqualByComparingTo("10");
assertThat(statement.getValueBigDecimal("shareOfDbTime24h")).isEqualByComparingTo("66.6667");
assertThat(DbPerfStore.query("db_perf_activity_sample", new QQueryFilter())).hasSize(6).allSatisfy(row -> assertThat(row.getValueString("queryTextNormalized")).doesNotContain("private", "999"));
assertThat(DbPerfStore.query("db_perf_table_stat", new QQueryFilter())).hasSize(1);
assertThat(DbPerfStore.query("db_perf_index_stat", new QQueryFilter())).hasSize(1);
provider.calls = 2;
provider.reset = DbPerfFixtureProvider.START.plusSeconds(400);
provider.limited = true;
QRecord reset = collect(runtime, provider, 600, true);
assertThat(reset.getValueString("status")).isEqualTo("PARTIAL");
assertThat(reset.getValueBoolean("statsResetDetected")).isTrue();
assertThat(reset.getValueLong("statementCalls")).isEqualTo(4);
assertThat(new DbPerfSettingsService(runtime).ensure(backend).getValueString("statsAccess")).isEqualTo("NO_READ_ALL_STATS");
assertThat(DbPerfStore.query("db_perf_table_snapshot", new QQueryFilter())).hasSize(2);
}
/*******************************************************************************
** Failed writes do not advance counters, and the caller session is restored.
*******************************************************************************/
@Test
void failureAndDisabled() throws Exception
{
QInstance instance = DbPerfTestSupport.instance();
DbPerfRuntime runtime = DbPerfRuntime.of(instance);
DbPerfFixtureProvider provider = new DbPerfFixtureProvider();
assertThat(collect(runtime, provider, 0, true).getValueString("status")).isEqualTo("OK");
QSession caller = new QSession().withPermissions(DbPerfPermissions.VIEW);
QContext.setQSession(caller);
provider.calls = 20;
provider.fail = true;
QRecord failed = collect(runtime, provider, 300, true);
assertThat(failed.getValueString("status")).isEqualTo("FAILED");
assertThat(failed.getValueString("errorMessage")).doesNotContain("patient", "private");
assertThat(QContext.getQSession()).isSameAs(caller);
runtime.asSystem(() ->
{
assertThat(DbPerfStore.query("db_perf_statement", new QQueryFilter())).allSatisfy(row -> assertThat(row.getValueLong("lastCalls")).isEqualTo(10));
assertThat(DbPerfStore.query("db_perf_query_snapshot", new QQueryFilter())).isEmpty();
QRecord settings = new DbPerfSettingsService(runtime).ensure(runtime.getConfig().getBackendName());
settings.setValue("enabled", false);
DbPerfStore.update("db_perf_settings", List.of(settings));
return (null);
});
assertThat(collect(runtime, provider, 600, true).getValueString("status")).isEqualTo("PARTIAL");
assertThat(new SnapshotCollector(runtime).collect("wrong-backend", false).getValueString("status")).isEqualTo("FAILED");
}
/*******************************************************************************
** H2 remains usable and explicitly unsupported by the production selector.
*******************************************************************************/
@Test
void unsupportedVendor() throws Exception
{
DbPerfRuntime runtime = DbPerfRuntime.of(DbPerfTestSupport.instance());
QRecord snapshot = new SnapshotCollector(runtime).collect(runtime.getConfig().getBackendName(), false);
assertThat(snapshot.getValueString("status")).isEqualTo("PARTIAL");
assertThat(snapshot.getValueString("errorMessage")).contains("not supported");
}
/*******************************************************************************
** A concurrent administrator edit is not overwritten by runtime status writes.
*******************************************************************************/
@Test
void collectionPreservesConcurrentSettingsEdit() throws Exception
{
DbPerfRuntime runtime = DbPerfRuntime.of(DbPerfTestSupport.instance());
QRecord settings = new DbPerfSettingsService(runtime).ensure(runtime.getConfig().getBackendName());
DbPerfFixtureProvider provider = new DbPerfFixtureProvider()
{
/*******************************************************************************
** Simulate an independently committed edit after settings were read.
*******************************************************************************/
@Override
public java.util.List<com.kingsrook.qbits.dbperformance.provider.ConnectionCount> readConnections(java.sql.Connection connection)
{
try
{
runtime.asSystem(() ->
{
DbPerfStore.update("db_perf_settings", List.of(new QRecord().withValue("id", settings.getValueInteger("id")).withValue("topStatementsPerSnapshot", 17)));
return (null);
});
}
catch(Exception e)
{
throw (new IllegalStateException(e));
}
return (super.readConnections(connection));
}
};
assertThat(collect(runtime, provider, 0, true).getValueString("status")).isEqualTo("OK");
assertThat(new DbPerfSettingsService(runtime).ensure(runtime.getConfig().getBackendName()).getValueInteger("topStatementsPerSnapshot")).isEqualTo(17);
}
/*******************************************************************************
** One timestamped pass.
*******************************************************************************/
private static QRecord collect(DbPerfRuntime runtime, DbPerfFixtureProvider provider, int seconds, boolean tables)
{
return (new SnapshotCollector(runtime, Clock.fixed(DbPerfFixtureProvider.START.plusSeconds(seconds), ZoneOffset.UTC), provider).collect(runtime.getConfig().getBackendName(), tables));
}
}
@@ -1,92 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.dbperformance;
import com.kingsrook.qbits.dbperformance.normalize.SqlNormalizer;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Privacy and stable-shape regressions for literal normalization.
*******************************************************************************/
class SqlNormalizerTest
{
/*******************************************************************************
** Different bind syntaxes and literals must have the same fingerprint.
*******************************************************************************/
@Test
void equivalentParameters()
{
String qqq = SqlNormalizer.normalize("SELECT * FROM account WHERE id=? AND name=?");
assertThat(SqlNormalizer.normalize("select * from account where id=$1 and name=$2")).isEqualTo(qqq);
assertThat(SqlNormalizer.fingerprint(SqlNormalizer.normalize("SELECT * FROM account WHERE id=123 AND name='secret'")))
.isEqualTo(SqlNormalizer.fingerprint(qqq)).hasSize(64);
}
/*******************************************************************************
** Comments, escaped and dollar-quoted strings never leave literal content.
*******************************************************************************/
@Test
void literalsAndComments()
{
for(String literal : new String[] {"'private''name'", "E'private\\'name'", "$$private name$$", "$body$private 'name'$body$", "'unterminated private", "$x$unterminated private"})
{
assertThat(SqlNormalizer.normalize("select " + literal)).isEqualTo("select ?");
}
assertThat(SqlNormalizer.normalize("SELECT /* outer /* nested */ done */ col12 -- private\nFROM t WHERE n=-1.2e+3"))
.isEqualTo("select col12 from t where n = - ?");
assertThat(SqlNormalizer.normalize("select '/* literal */', 0xff, .5, 2e-4")).isEqualTo("select ? , ? , ? , ?");
assertThat(SqlNormalizer.normalize("SELECT \"My Table\".\"Field2\" FROM \"My Table\""))
.isEqualTo("select \"My Table\" . \"Field2\" from \"My Table\"");
}
/*******************************************************************************
** Repeated list shapes collapse while column arity remains meaningful.
*******************************************************************************/
@Test
void listShapesAndClassification()
{
assertThat(SqlNormalizer.normalize("select * from t where id IN (1,2,3)"))
.isEqualTo(SqlNormalizer.normalize("select * from t where id in (?)"));
assertThat(SqlNormalizer.normalize("insert into t values (1,'a'),(2,'b')"))
.isEqualTo(SqlNormalizer.normalize("insert into t values (?,?)"));
for(String operation : new String[] {"select", "insert", "update", "delete", "with"})
{
assertThat(SqlNormalizer.operation(operation)).isEqualTo(operation.toUpperCase(java.util.Locale.ROOT));
}
assertThat(SqlNormalizer.normalize("SELECT id::bigint FROM t WHERE n>=1 AND name||'secret'=$1"))
.isEqualTo("select id :: bigint from t where n >= ? and name || ? = ?");
assertThat(SqlNormalizer.normalize("select payload->>'secret', n<>10 from t"))
.isEqualTo("select payload ->> ? , n <> ? from t");
assertThat(SqlNormalizer.operation("create table t(a int)")).isEqualTo("OTHER");
assertThat(SqlNormalizer.mainTable("select x from public.\"Account\" where x=1")).isEqualTo("Account");
assertThat(SqlNormalizer.mainTable("update public.account set x=1")).isEqualTo("account");
assertThat(SqlNormalizer.mainTable("select 1")).isNull();
assertThat(SqlNormalizer.normalize("select \"in ( ? , ? )\" from t")).isEqualTo("select \"in ( ? , ? )\" from t");
assertThat(SqlNormalizer.normalize(null)).isEmpty();
assertThat(SqlNormalizer.normalize(" ")).isEmpty();
assertThat(SqlNormalizer.normalize("select " + "field, ".repeat(5000))).hasSize(10000);
}
}