Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c0236b2d7
|
||
|
|
499383a73a |
No files matched your search
+11
-57
@@ -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+$/
|
||||
@@ -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
|
||||
@@ -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
@@ -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,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
@@ -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
@@ -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.
|
||||
@@ -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,35 +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);
|
||||
com.kingsrook.qbits.dbperformance.alerts.SnapshotEvaluator.register(instance, qBitConfig);
|
||||
com.kingsrook.qbits.dbperformance.health.DbPerfHealthChecks.register(instance);
|
||||
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,214 +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.alerts;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qbits.dbperformance.DbPerfQBitConfig;
|
||||
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.RuleContext;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
|
||||
import com.kingsrook.qbits.dbperformance.normalize.SqlNormalizer;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertRecordRef;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertSeverity;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertSignal;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertsInstanceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.alerts.QAlertTypeMetaData;
|
||||
import com.kingsrook.qqq.backend.core.alerts.QAlerts;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Optional QAlerts signals from committed observations. Alert delivery cannot
|
||||
** turn a successfully collected snapshot into a failed collection.
|
||||
*******************************************************************************/
|
||||
public class SnapshotEvaluator
|
||||
{
|
||||
public static final String COLLECTION_FAILED = "dbperf.collection-failed";
|
||||
public static final String QUERY_SPIKE = "dbperf.query-spike";
|
||||
public static final String CONNECTION_SATURATION = "dbperf.connection-saturation";
|
||||
public static final String BLOCKED_CHAIN = "dbperf.blocked-chain";
|
||||
private static final QLogger LOG = QLogger.getLogger(SnapshotEvaluator.class);
|
||||
private final DbPerfRuntime runtime;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Instance-bound evaluator; the core resolves the optional alert sink.
|
||||
*******************************************************************************/
|
||||
public SnapshotEvaluator(DbPerfRuntime runtime)
|
||||
{
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Alert types are discoverable even without an installed Alerts QBit.
|
||||
*******************************************************************************/
|
||||
public static void register(QInstance instance, DbPerfQBitConfig config)
|
||||
{
|
||||
Map<String, String> labels = Map.of(COLLECTION_FAILED, "Database performance collection failed", QUERY_SPIKE, "Database query time spike",
|
||||
CONNECTION_SATURATION, "Database connections saturated", BLOCKED_CHAIN, "Database blocking chain");
|
||||
Map<String, String> descriptions = Map.of(COLLECTION_FAILED, "Collection failed; resolves after the next OK snapshot.",
|
||||
QUERY_SPIKE, "Interval query time exceeds its seven-day median and configured floor.",
|
||||
CONNECTION_SATURATION, "Observed connections reached the configured percentage of capacity.",
|
||||
BLOCKED_CHAIN, "A blocked session exceeded the configured duration.");
|
||||
for(String type : List.of(COLLECTION_FAILED, QUERY_SPIKE, CONNECTION_SATURATION, BLOCKED_CHAIN))
|
||||
{
|
||||
AlertsInstanceMetaData.ofOrWithNew(instance).withAlertType(new QAlertTypeMetaData().withName(type).withLabel(labels.get(type))
|
||||
.withDescription(descriptions.get(type)).withSourceQBitName("db-performance")
|
||||
.withDefaultSeverity(type.equals(COLLECTION_FAILED) ? AlertSeverity.ERROR : AlertSeverity.WARNING)
|
||||
.withSubjectTableName(type.equals(QUERY_SPIKE) ? "db_perf_statement" : "db_perf_snapshot")
|
||||
.withVisibilityPermission(config.getPermissionBaseName() + ".view"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A failed evaluation is logged without SQL, credentials or exception text.
|
||||
*******************************************************************************/
|
||||
public void evaluate(QRecord snapshot)
|
||||
{
|
||||
try
|
||||
{
|
||||
runtime.asSystem(() ->
|
||||
{
|
||||
evaluateStored(snapshot);
|
||||
return (null);
|
||||
});
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Database performance alert evaluation could not read stored observations");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Read the current settings every pass. Failed/partial collection cannot
|
||||
** clear connection, blocking or query signals without fresh measurements.
|
||||
*******************************************************************************/
|
||||
private void evaluateStored(QRecord snapshot) throws Exception
|
||||
{
|
||||
String backend = snapshot.getValueString("backendName");
|
||||
QRecord settings = DbPerfStore.first("db_perf_settings", DbPerfStore.eq("backendName", backend));
|
||||
if(settings == null || !Boolean.TRUE.equals(settings.getValueBoolean("alertsEnabled")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
String status = snapshot.getValueString("status");
|
||||
if("FAILED".equals(status))
|
||||
{
|
||||
signal(COLLECTION_FAILED, backend, null, true, "Database performance collection failed", snapshot.getValueString("errorMessage"), snapshot, "db_perf_snapshot");
|
||||
return;
|
||||
}
|
||||
if("OK".equals(status))
|
||||
{
|
||||
signal(COLLECTION_FAILED, backend, null, false, "Collection recovered", "An OK snapshot was collected", snapshot, "db_perf_snapshot");
|
||||
}
|
||||
else if(!"PARTIAL".equals(status) || snapshot.getValue("connectionsTotal") == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
double maximum = RuleContext.number(snapshot, "maxConnections");
|
||||
if(maximum > 0)
|
||||
{
|
||||
double used = 100 * RuleContext.number(snapshot, "connectionsTotal") / maximum;
|
||||
signal(CONNECTION_SATURATION, backend, null, used >= RuleContext.number(settings, "connectionAlertPercent"),
|
||||
"Database connection capacity", used + "% of " + maximum + " connections", snapshot, "db_perf_snapshot");
|
||||
}
|
||||
Instant now = snapshot.getValueInstant("capturedAt");
|
||||
RuleContext ctx = new RuleContext(runtime, backend, new QRecord(), settings, now);
|
||||
List<QRecord> activity = DbPerfStore.query("db_perf_activity_sample", DbPerfStore.eq("snapshotId", snapshot.getValueInteger("id")));
|
||||
long blocked = activity.stream().filter(r -> "BLOCKED".equals(r.getValueString("reason")))
|
||||
.filter(r -> Math.max(RuleContext.number(r, "querySeconds"), RuleContext.number(r, "xactSeconds")) >= RuleContext.number(settings, "blockedAlertSeconds")).count();
|
||||
signal(BLOCKED_CHAIN, backend, null, blocked > 0, "Database blocking chain", blocked + " blocked sessions exceed the duration threshold", snapshot, "db_perf_snapshot");
|
||||
if("OK".equals(status) || "NO_READ_ALL_STATS".equals(settings.getValueString("statsAccess")))
|
||||
{
|
||||
spikes(ctx, snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A baseline excludes the current observation and needs twelve prior samples.
|
||||
** Missing top-N entries clear after one hour, including after a restart.
|
||||
*******************************************************************************/
|
||||
private void spikes(RuleContext ctx, QRecord snapshot) throws Exception
|
||||
{
|
||||
Map<Integer, List<QRecord>> history = ctx.window("query_snapshot", 7).stream().collect(Collectors.groupingBy(r -> r.getValueInteger("statementId")));
|
||||
for(QRecord statement : ctx.identities("statement"))
|
||||
{
|
||||
List<QRecord> samples = history.getOrDefault(statement.getValueInteger("id"), List.of());
|
||||
QRecord current = samples.stream().filter(r -> snapshot.getValueInteger("id").equals(r.getValueInteger("snapshotId"))).findFirst().orElse(null);
|
||||
if(current == null)
|
||||
{
|
||||
Instant last = samples.stream().map(r -> r.getValueInstant("capturedAt")).max(Comparator.naturalOrder()).orElse(Instant.EPOCH);
|
||||
if(!last.plusSeconds(3600).isAfter(ctx.now))
|
||||
{
|
||||
signal(QUERY_SPIKE, ctx.backend, statement.getValueString("id"), false, "Query absent", "Statement absent from snapshots for at least one hour", statement, "db_perf_statement");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
List<Double> previous = samples.stream().filter(r -> r.getValueInstant("capturedAt").isBefore(ctx.now))
|
||||
.map(r -> RuleContext.number(r, "totalMs")).sorted().toList();
|
||||
if(previous.size() < 12)
|
||||
{
|
||||
if(RuleContext.number(current, "totalMs") < RuleContext.number(ctx.settings, "spikeMinTotalMs"))
|
||||
{
|
||||
signal(QUERY_SPIKE, ctx.backend, statement.getValueString("id"), false, "Query recovered", "Interval time is below the configured floor", statement, "db_perf_statement");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
int middle = previous.size() / 2;
|
||||
double median = previous.size() % 2 == 0 ? (previous.get(middle - 1) + previous.get(middle)) / 2 : previous.get(middle);
|
||||
double time = RuleContext.number(current, "totalMs");
|
||||
boolean spike = time >= RuleContext.number(ctx.settings, "spikeMinTotalMs") && time >= median * RuleContext.number(ctx.settings, "spikeFactor");
|
||||
String normalized = SqlNormalizer.normalize(statement.getValueString("queryText"));
|
||||
String text = normalized.substring(0, Math.min(200, normalized.length()));
|
||||
signal(QUERY_SPIKE, ctx.backend, statement.getValueString("id"), spike, "Database query time spike",
|
||||
"Statement " + statement.getValueInteger("id") + ": " + time + " ms; prior median " + median + " ms. " + text, statement, "db_perf_statement");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Stable backend/subject keys make raises and resolutions restart-safe.
|
||||
*******************************************************************************/
|
||||
private static void signal(String type, String backend, String subject, boolean raised, String title, String summary, QRecord row, String table)
|
||||
{
|
||||
String key = type.replace("dbperf.", "db_perf.").replace('-', '_') + ":" + backend + (subject == null ? "" : ":" + subject);
|
||||
if(raised)
|
||||
{
|
||||
QAlerts.raise(new AlertSignal(type).withDedupKey(key).withTitle(title).withSummary(summary)
|
||||
.withRecordRef(AlertRecordRef.of(table, row.getValueInteger("id"))));
|
||||
}
|
||||
else
|
||||
{
|
||||
QAlerts.resolve(type, key, "Condition no longer detected");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.analyze;
|
||||
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfSettingsService;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Each rule has its own failure boundary and atomic finding reconciliation.
|
||||
*******************************************************************************/
|
||||
public class AnalyzeService
|
||||
{
|
||||
private final DbPerfRuntime runtime;
|
||||
private final Clock clock;
|
||||
private final List<DbPerfRule> rules;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** All seeded rules, using the production clock.
|
||||
*******************************************************************************/
|
||||
public AnalyzeService(DbPerfRuntime runtime)
|
||||
{
|
||||
this(runtime, Clock.systemUTC(), Arrays.asList(StoredRule.values()));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Explicit rules permit isolation tests and future provider-independent rules.
|
||||
*******************************************************************************/
|
||||
public AnalyzeService(DbPerfRuntime runtime, Clock clock, List<DbPerfRule> rules)
|
||||
{
|
||||
this.runtime = runtime;
|
||||
this.clock = clock;
|
||||
this.rules = List.copyOf(rules);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Error text is sanitized so no database exception leaks SQL or literals.
|
||||
*******************************************************************************/
|
||||
public record Result(String ruleCode, FindingService.Counts counts, String error)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Disabled rules are neither evaluated nor reconciled.
|
||||
*******************************************************************************/
|
||||
public List<Result> run(String backend) throws Exception
|
||||
{
|
||||
if(!runtime.getConfig().getBackendName().equals(backend))
|
||||
{
|
||||
throw (new IllegalArgumentException("Backend is not configured for this runtime"));
|
||||
}
|
||||
QRecord settings = new DbPerfSettingsService(runtime).ensure(backend);
|
||||
List<Result> results = new ArrayList<>();
|
||||
for(DbPerfRule rule : rules)
|
||||
{
|
||||
try
|
||||
{
|
||||
QRecord row = runtime.asSystem(() -> DbPerfStore.first("db_perf_rule", DbPerfStore.eq("code", rule.code())));
|
||||
if(row == null || !Boolean.TRUE.equals(row.getValueBoolean("enabled")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
List<FindingDraft> drafts = runtime.asSystem(() -> rule.evaluate(new RuleContext(runtime, backend, row, settings, clock.instant())));
|
||||
results.add(new Result(rule.code(), new FindingService(runtime, clock).apply(backend, rule.code(), drafts), null));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
results.add(new Result(rule.code(), new FindingService.Counts(0, 0, 0), "Evaluation failed (" + e.getClass().getSimpleName() + ")"));
|
||||
}
|
||||
}
|
||||
return (results);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +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.analyze;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A suggestion reads collected evidence only; it never probes the monitored DB.
|
||||
*******************************************************************************/
|
||||
public interface DbPerfRule
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Seeded rule identity.
|
||||
*******************************************************************************/
|
||||
String code();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Evaluate the configured thresholds against stored observations.
|
||||
*******************************************************************************/
|
||||
List<FindingDraft> evaluate(RuleContext ctx) throws Exception;
|
||||
}
|
||||
@@ -1,68 +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.analyze;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Immutable proposal. The lifecycle service owns identity, timestamps and state.
|
||||
*******************************************************************************/
|
||||
public record FindingDraft(String ruleCode, String dedupeKey, String severity, double impact,
|
||||
String title, String summary, String evidence, String recommendation,
|
||||
String migrationSnippet, String subjectTable, Integer statementId,
|
||||
Integer tableStatId, Integer indexStatId, String unitName)
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Rank even small findings, with impact clamped to a finite fraction.
|
||||
*******************************************************************************/
|
||||
public BigDecimal score()
|
||||
{
|
||||
int weight = switch(severity)
|
||||
{
|
||||
case "INFO" -> 1;
|
||||
case "WARNING" -> 2;
|
||||
case "ERROR" -> 3;
|
||||
case "CRITICAL" -> 4;
|
||||
default -> throw new IllegalArgumentException("Unknown finding severity");
|
||||
};
|
||||
if(!Double.isFinite(impact))
|
||||
{
|
||||
throw (new IllegalArgumentException("Impact must be finite"));
|
||||
}
|
||||
return (BigDecimal.valueOf(weight * (0.1 + Math.max(0, Math.min(1, impact)))));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Persist only the generated evidence; do not overwrite human status fields.
|
||||
*******************************************************************************/
|
||||
public QRecord record()
|
||||
{
|
||||
return (new QRecord().withValue("ruleCode", ruleCode).withValue("dedupeKey", dedupeKey)
|
||||
.withValue("severity", severity).withValue("score", score()).withValue("title", title)
|
||||
.withValue("summary", summary).withValue("evidence", evidence).withValue("recommendation", recommendation)
|
||||
.withValue("migrationSnippet", migrationSnippet).withValue("subjectTable", subjectTable)
|
||||
.withValue("statementId", statementId).withValue("tableStatId", tableStatId).withValue("indexStatId", indexStatId).withValue("unitName", unitName));
|
||||
}
|
||||
}
|
||||
@@ -1,176 +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.analyze;
|
||||
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
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.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryLockMode;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Atomic reconciliation of one rule. Human acknowledgement and ignore survive
|
||||
** recurrence; only resolved findings reopen. Disabled rules are entirely inert.
|
||||
*******************************************************************************/
|
||||
public class FindingService
|
||||
{
|
||||
private final DbPerfRuntime runtime;
|
||||
private final Clock clock;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Production clock.
|
||||
*******************************************************************************/
|
||||
public FindingService(DbPerfRuntime runtime)
|
||||
{
|
||||
this(runtime, Clock.systemUTC());
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Testable observation time.
|
||||
*******************************************************************************/
|
||||
public FindingService(DbPerfRuntime runtime, Clock clock)
|
||||
{
|
||||
this.runtime = runtime;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Counts describe actual writes, not the size of the input list.
|
||||
*******************************************************************************/
|
||||
public record Counts(int created, int updated, int resolved)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Validate scope before any mutations; a failed rule never clears findings.
|
||||
*******************************************************************************/
|
||||
public Counts apply(String backend, String ruleCode, List<FindingDraft> drafts) throws Exception
|
||||
{
|
||||
if(!runtime.getConfig().getBackendName().equals(backend))
|
||||
{
|
||||
throw (new IllegalArgumentException("Backend is not configured for this runtime"));
|
||||
}
|
||||
synchronized(runtime)
|
||||
{
|
||||
return (runtime.transaction(() -> reconcile(backend, ruleCode, drafts)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Serialize analyzer and human status writes across host instances. A busy
|
||||
** rule reports a retryable error instead of overwriting a concurrent Ignore.
|
||||
** Call inside the runtime transaction and synchronized(runtime) boundary.
|
||||
*******************************************************************************/
|
||||
public static QRecord lockRule(DbPerfRuntime runtime, String code) throws Exception
|
||||
{
|
||||
RDBMSBackendMetaData backend = (RDBMSBackendMetaData) runtime.getInstance().getBackend(runtime.getConfig().getBackendName());
|
||||
if(backend.getActionStrategy().supportsForUpdateSkipLocked())
|
||||
{
|
||||
List<QRecord> rows = new QueryAction().execute(new QueryInput("db_perf_rule").withFilter(DbPerfStore.eq("code", code))
|
||||
.withTransaction(QContext.getQBackendTransaction()).withLockMode(QueryLockMode.FOR_UPDATE_SKIP_LOCKED)).getRecords();
|
||||
if(rows.isEmpty() && DbPerfStore.first("db_perf_rule", DbPerfStore.eq("code", code)) != null)
|
||||
{
|
||||
throw (new IllegalStateException("Rule is being updated; retry analysis or the finding action"));
|
||||
}
|
||||
return (rows.isEmpty() ? null : rows.getFirst());
|
||||
}
|
||||
return (DbPerfStore.first("db_perf_rule", DbPerfStore.eq("code", code)));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Re-read the enabled flag in the transaction, preserving administrator edits.
|
||||
*******************************************************************************/
|
||||
private Counts reconcile(String backend, String ruleCode, List<FindingDraft> drafts) throws Exception
|
||||
{
|
||||
QRecord rule = lockRule(runtime, ruleCode);
|
||||
if(rule == null || !Boolean.TRUE.equals(rule.getValueBoolean("enabled")))
|
||||
{
|
||||
return (new Counts(0, 0, 0));
|
||||
}
|
||||
Map<String, QRecord> existing = new HashMap<>();
|
||||
for(QRecord row : RuleContext.read("db_perf_finding", DbPerfStore.eq("backendName", backend)
|
||||
.withCriteria(new QFilterCriteria("ruleCode", QCriteriaOperator.EQUALS, ruleCode))))
|
||||
{
|
||||
existing.put(row.getValueString("dedupeKey"), row);
|
||||
}
|
||||
Instant now = clock.instant();
|
||||
Set<String> seen = new HashSet<>();
|
||||
int created = 0;
|
||||
int updated = 0;
|
||||
int resolved = 0;
|
||||
for(FindingDraft draft : drafts)
|
||||
{
|
||||
if(!ruleCode.equals(draft.ruleCode()) || !draft.dedupeKey().startsWith(ruleCode + ":" + backend + ":") || !seen.add(draft.dedupeKey()))
|
||||
{
|
||||
throw (new IllegalArgumentException("Invalid or duplicate finding identity"));
|
||||
}
|
||||
QRecord old = existing.get(draft.dedupeKey());
|
||||
QRecord row = draft.record().withValue("backendName", backend).withValue("ruleId", rule.getValueInteger("id"))
|
||||
.withValue("lastSeenAt", now);
|
||||
if(old == null)
|
||||
{
|
||||
row.withValue("status", "OPEN").withValue("firstSeenAt", now).withValue("occurrences", 1);
|
||||
DbPerfStore.insert("db_perf_finding", List.of(row));
|
||||
created++;
|
||||
}
|
||||
else
|
||||
{
|
||||
row.withValue("id", old.getValueInteger("id")).withValue("occurrences", (int) RuleContext.number(old, "occurrences") + 1);
|
||||
if("RESOLVED".equals(old.getValueString("status")))
|
||||
{
|
||||
row.withValue("status", "OPEN").withValue("resolvedAt", null).withValue("statusNote", "Detected again").withValue("statusChangedBy", null);
|
||||
}
|
||||
DbPerfStore.update("db_perf_finding", List.of(row));
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
for(QRecord old : existing.values())
|
||||
{
|
||||
if(!seen.contains(old.getValueString("dedupeKey")) && List.of("OPEN", "ACKNOWLEDGED").contains(old.getValueString("status")))
|
||||
{
|
||||
DbPerfStore.update("db_perf_finding", List.of(new QRecord().withValue("id", old.getValueInteger("id"))
|
||||
.withValue("status", "RESOLVED").withValue("statusNote", "No longer detected").withValue("resolvedAt", now).withValue("statusChangedBy", null)));
|
||||
resolved++;
|
||||
}
|
||||
}
|
||||
return (new Counts(created, updated, resolved));
|
||||
}
|
||||
}
|
||||
@@ -1,125 +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.analyze;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Reviewable Liquibase text only. These suggestions are never executed.
|
||||
*******************************************************************************/
|
||||
public final class MigrationSnippets
|
||||
{
|
||||
private static final Set<String> RESERVED = Set.of("user", "order", "group", "select", "table", "index", "where", "from", "to", "primary", "constraint", "limit", "offset", "references", "column", "default", "check", "unique", "all", "and", "or", "not", "null", "true", "false", "case", "when", "end", "join", "on", "in", "as", "by", "values", "grant", "current_user", "authorization", "returning",
|
||||
"analyse", "analyze", "array", "asc", "asymmetric", "both", "cast", "collate", "collation", "concurrently", "current_catalog", "current_date", "current_role", "current_schema", "current_time", "current_timestamp",
|
||||
"desc", "distinct", "do", "else", "except", "fetch", "for", "foreign", "freeze", "ilike", "initially", "intersect", "into", "is", "isnull", "lateral", "leading", "like", "localtime", "localtimestamp", "natural", "notnull",
|
||||
"only", "placing", "session_user", "similar", "some", "symmetric", "system_user", "tablesample", "then", "trailing", "union", "using", "variadic", "verbose", "window", "with");
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class.
|
||||
*******************************************************************************/
|
||||
private MigrationSnippets()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Quote identifiers with punctuation, mixed case, or reserved spelling.
|
||||
*******************************************************************************/
|
||||
public static String quote(String name)
|
||||
{
|
||||
return (name.matches("[a-z_][a-z0-9_]*") && !RESERVED.contains(name) ? name : "\"" + name.replace("\"", "\"\"") + "\"");
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** ASCII names are byte-bounded as well as character-bounded for PostgreSQL.
|
||||
*******************************************************************************/
|
||||
public static String indexName(String table, List<String> columns)
|
||||
{
|
||||
String raw = "idx_" + table + "_" + String.join("_", columns);
|
||||
String safe = raw.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9_]", "_");
|
||||
return (safe.length() <= 63 && safe.equals(raw) ? safe : safe.substring(0, Math.min(54, safe.length())) + "_" + String.format("%08x", raw.hashCode()));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Create concurrently, with a matching schema-qualified rollback.
|
||||
*******************************************************************************/
|
||||
public static String create(Instant now, QRecord table, List<String> columns, String reason)
|
||||
{
|
||||
String name = indexName(table.getValueString("tableName"), columns);
|
||||
String schema = quote(table.getValueString("schemaName"));
|
||||
return (header(now, name, "MISSING_INDEX", reason)
|
||||
+ "CREATE INDEX CONCURRENTLY IF NOT EXISTS " + quote(name) + " ON " + schema + "." + quote(table.getValueString("tableName"))
|
||||
+ " (" + columns.stream().map(MigrationSnippets::quote).collect(Collectors.joining(", ")) + ");\n"
|
||||
+ "-- rollback DROP INDEX CONCURRENTLY IF EXISTS " + schema + "." + quote(name) + ";\n");
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Preserve the collected definition in rollback; no guessed index structure.
|
||||
*******************************************************************************/
|
||||
public static String drop(Instant now, QRecord index, String rule, String reason)
|
||||
{
|
||||
String definition = index.getValueString("definition");
|
||||
if(definition == null || !definition.matches("(?is)^CREATE\\s+(UNIQUE\\s+)?INDEX\\s+.*"))
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
String rollback = definition.replaceFirst("(?i)^(CREATE\\s+(?:UNIQUE\\s+)?INDEX)\\s+(?:CONCURRENTLY\\s+)?", "$1 CONCURRENTLY ").replaceAll("[\\r\\n]+", " ").replaceAll(";+$", "");
|
||||
return (header(now, index.getValueString("indexName"), rule, reason)
|
||||
+ "DROP INDEX CONCURRENTLY IF EXISTS " + quote(index.getValueString("schemaName")) + "." + quote(index.getValueString("indexName")) + ";\n"
|
||||
+ "-- rollback " + rollback + ";\n");
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Optional tuning suggestion; rollback resets the override to server defaults.
|
||||
*******************************************************************************/
|
||||
public static String vacuum(Instant now, QRecord table, String reason)
|
||||
{
|
||||
String target = quote(table.getValueString("schemaName")) + "." + quote(table.getValueString("tableName"));
|
||||
return (header(now, table.getValueString("tableName"), "BLOAT", reason)
|
||||
+ "ALTER TABLE " + target + " SET (autovacuum_vacuum_scale_factor = 0.05);\n"
|
||||
+ "-- rollback ALTER TABLE " + target + " RESET (autovacuum_vacuum_scale_factor);\n");
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A stable daily changeset identity and a single-line evidence comment.
|
||||
*******************************************************************************/
|
||||
private static String header(Instant now, String subject, String rule, String reason)
|
||||
{
|
||||
String date = DateTimeFormatter.ofPattern("yyyyMMdd").withZone(ZoneOffset.UTC).format(now);
|
||||
return ("-- liquibase formatted sql\n-- changeset db-performance:" + date + "-" + rule.toLowerCase(Locale.ROOT) + "-" + subject.replaceAll("[^a-zA-Z0-9_-]", "_")
|
||||
+ " runInTransaction:false\n-- comment: Suggested by Database Performance (" + rule + "): " + reason.replaceAll("[\\r\\n]+", " ") + "\n");
|
||||
}
|
||||
}
|
||||
@@ -1,189 +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.analyze;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import com.kingsrook.qbits.dbperformance.normalize.SqlNormalizer;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Conservative tokenizer for simple WHERE equality/IN and equijoins. Complex
|
||||
** subqueries and expressions are deliberately left for the Query plan action.
|
||||
*******************************************************************************/
|
||||
public final class PredicateParser
|
||||
{
|
||||
private static final Pattern TOKEN = Pattern.compile("\"(?:\"\"|[^\"])*\"|[a-zA-Z_][a-zA-Z_0-9$]*|\\?|[^\\s]");
|
||||
private static final Set<String> KEYWORDS = Set.of("where", "join", "inner", "outer", "left", "right", "full", "cross", "on", "group", "order", "limit", "having", "set", "returning", "union");
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class.
|
||||
*******************************************************************************/
|
||||
private PredicateParser()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Decode SQL identifiers; unquoted names fold to lower case.
|
||||
*******************************************************************************/
|
||||
public static String identifier(String token)
|
||||
{
|
||||
return (token.startsWith("\"") ? token.substring(1, token.length() - 1).replace("\"\"", "\"") : token.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Only simple column indexes can be compared as prefixes.
|
||||
*******************************************************************************/
|
||||
public static List<String> indexColumns(String columns)
|
||||
{
|
||||
if(columns == null || columns.isBlank())
|
||||
{
|
||||
return (List.of());
|
||||
}
|
||||
List<String> result = new ArrayList<>();
|
||||
Matcher matcher = TOKEN.matcher(columns);
|
||||
boolean wantColumn = true;
|
||||
while(matcher.find())
|
||||
{
|
||||
String token = matcher.group();
|
||||
if(wantColumn && isIdentifier(token))
|
||||
{
|
||||
result.add(identifier(token));
|
||||
}
|
||||
else if(wantColumn || !token.equals(","))
|
||||
{
|
||||
return (List.of());
|
||||
}
|
||||
wantColumn = !wantColumn;
|
||||
}
|
||||
return (wantColumn ? List.of() : result);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Resolve aliases before collecting predicates so joined tables do not lend
|
||||
** their columns to the main table's suggested index.
|
||||
*******************************************************************************/
|
||||
public static List<String> candidates(String sql, String schema, String table)
|
||||
{
|
||||
List<String> tokens = new ArrayList<>();
|
||||
Matcher matcher = TOKEN.matcher(SqlNormalizer.normalize(sql));
|
||||
while(matcher.find())
|
||||
{
|
||||
tokens.add(matcher.group());
|
||||
}
|
||||
if(tokens.stream().filter(t -> t.equals("select")).count() > 1 || tokens.contains("union"))
|
||||
{
|
||||
return (List.of());
|
||||
}
|
||||
Map<String, Boolean> aliases = new LinkedHashMap<>();
|
||||
for(int i = 0; i + 1 < tokens.size(); i++)
|
||||
{
|
||||
if(Set.of("from", "join", "update").contains(tokens.get(i)) && isIdentifier(tokens.get(i + 1)))
|
||||
{
|
||||
int end = i + 2;
|
||||
String relation = identifier(tokens.get(i + 1));
|
||||
boolean schemaMatches = true;
|
||||
if(end + 1 < tokens.size() && tokens.get(end).equals("."))
|
||||
{
|
||||
schemaMatches = relation.equals(schema);
|
||||
relation = identifier(tokens.get(end + 1));
|
||||
end += 2;
|
||||
}
|
||||
boolean target = schemaMatches && relation.equals(table);
|
||||
aliases.put(relation, target);
|
||||
if(end < tokens.size() && tokens.get(end).equals("as"))
|
||||
{
|
||||
end++;
|
||||
}
|
||||
if(end < tokens.size() && isIdentifier(tokens.get(end)) && !KEYWORDS.contains(tokens.get(end)))
|
||||
{
|
||||
aliases.put(identifier(tokens.get(end)), target);
|
||||
}
|
||||
}
|
||||
}
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
boolean predicate = false;
|
||||
for(int i = 0; i < tokens.size(); i++)
|
||||
{
|
||||
String token = tokens.get(i);
|
||||
if(token.equals("where") || token.equals("on"))
|
||||
{
|
||||
predicate = true;
|
||||
continue;
|
||||
}
|
||||
if(KEYWORDS.contains(token) && !token.equals("on"))
|
||||
{
|
||||
predicate = false;
|
||||
}
|
||||
if(!predicate || !isIdentifier(token) || i > 0 && tokens.get(i - 1).equals("."))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int end = i + 1;
|
||||
String column = identifier(token);
|
||||
boolean belongs = !aliases.isEmpty() && aliases.values().stream().allMatch(Boolean::booleanValue);
|
||||
if(end + 1 < tokens.size() && tokens.get(end).equals("."))
|
||||
{
|
||||
belongs = Boolean.TRUE.equals(aliases.get(column));
|
||||
column = identifier(tokens.get(end + 1));
|
||||
end += 2;
|
||||
}
|
||||
if(end + 1 >= tokens.size())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String operator = tokens.get(end);
|
||||
String right = tokens.get(end + 1);
|
||||
boolean joinedColumn = end + 3 < tokens.size() && tokens.get(end + 2).equals(".") && aliases.containsKey(identifier(right));
|
||||
if(belongs && (operator.equals("in") && right.equals("(") || operator.equals("=") && (right.equals("?") || joinedColumn)))
|
||||
{
|
||||
result.add(column);
|
||||
}
|
||||
if(operator.equals("=") && end + 3 < tokens.size() && tokens.get(end + 2).equals(".")
|
||||
&& Boolean.TRUE.equals(aliases.get(identifier(right))))
|
||||
{
|
||||
result.add(identifier(tokens.get(end + 3)));
|
||||
}
|
||||
}
|
||||
return (List.copyOf(result));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Tokens are already balanced quoted identifiers or simple words.
|
||||
*******************************************************************************/
|
||||
private static boolean isIdentifier(String token)
|
||||
{
|
||||
return (token.startsWith("\"") || token.matches("[a-zA-Z_][a-zA-Z_0-9$]*"));
|
||||
}
|
||||
}
|
||||
@@ -1,202 +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.analyze;
|
||||
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
|
||||
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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Backend-scoped, time-bounded reads of the qbit's own tables. Ratios in storage
|
||||
** are percentages. Attribution belongs to the single configured backend.
|
||||
*******************************************************************************/
|
||||
public class RuleContext
|
||||
{
|
||||
public final DbPerfRuntime runtime;
|
||||
public final String backend;
|
||||
public final QRecord rule;
|
||||
public final QRecord settings;
|
||||
public final Instant now;
|
||||
private final Map<String, List<QRecord>> cache = new HashMap<>();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** An explicit observation time makes windows repeatable in tests and runs.
|
||||
*******************************************************************************/
|
||||
public RuleContext(DbPerfRuntime runtime, String backend, QRecord rule, QRecord settings, Instant now)
|
||||
{
|
||||
if(!runtime.getConfig().getBackendName().equals(backend))
|
||||
{
|
||||
throw (new IllegalArgumentException("Backend is not configured for this runtime"));
|
||||
}
|
||||
this.runtime = runtime;
|
||||
this.backend = backend;
|
||||
this.rule = rule;
|
||||
this.settings = settings;
|
||||
this.now = now;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Seeded thresholds remain editable by the administrator.
|
||||
*******************************************************************************/
|
||||
public double threshold(int ordinal)
|
||||
{
|
||||
return (number(rule, "threshold" + ordinal));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Page by primary key rather than silently accepting backend row limits.
|
||||
*******************************************************************************/
|
||||
public static List<QRecord> read(String table, QQueryFilter filter) throws Exception
|
||||
{
|
||||
List<QRecord> rows = new ArrayList<>();
|
||||
int last = 0;
|
||||
while(true)
|
||||
{
|
||||
List<QRecord> page = DbPerfStore.query(table, new QQueryFilter().withSubFilter(filter)
|
||||
.withCriteria(new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN, last))
|
||||
.withOrderBy(new QFilterOrderBy("id", true)).withLimit(DbPerfStore.BATCH_SIZE));
|
||||
rows.addAll(page);
|
||||
if(page.size() < DbPerfStore.BATCH_SIZE)
|
||||
{
|
||||
return (rows);
|
||||
}
|
||||
last = page.getLast().getValueInteger("id");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Current identities, filtered by backend. Cached only for this evaluation.
|
||||
*******************************************************************************/
|
||||
public List<QRecord> identities(String suffix) throws Exception
|
||||
{
|
||||
String key = "identity:" + suffix;
|
||||
if(!cache.containsKey(key))
|
||||
{
|
||||
cache.put(key, read("db_perf_" + suffix, DbPerfStore.eq("backendName", backend)));
|
||||
}
|
||||
return (cache.get(key));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Read observations inside (now-days, now], excluding other backends through
|
||||
** their snapshot foreign key. Partial snapshots still contain useful data.
|
||||
*******************************************************************************/
|
||||
public List<QRecord> window(String suffix, int days) throws Exception
|
||||
{
|
||||
String key = suffix + ":" + days;
|
||||
if(!cache.containsKey(key))
|
||||
{
|
||||
String time = Set.of("attribution", "repeat_pattern").contains(suffix) ? "windowEnd" : "capturedAt";
|
||||
QQueryFilter filter = new QQueryFilter(new QFilterCriteria(time, QCriteriaOperator.GREATER_THAN, now.minusSeconds(days * 86400L)),
|
||||
new QFilterCriteria(time, QCriteriaOperator.LESS_THAN_OR_EQUALS, now));
|
||||
if(suffix.equals("snapshot"))
|
||||
{
|
||||
filter.withCriteria(new QFilterCriteria("backendName", QCriteriaOperator.EQUALS, backend));
|
||||
}
|
||||
List<QRecord> rows = read("db_perf_" + suffix, filter);
|
||||
if(!Set.of("snapshot", "attribution", "repeat_pattern").contains(suffix))
|
||||
{
|
||||
Set<Integer> ids = window("snapshot", days).stream()
|
||||
.filter(r -> List.of("OK", "PARTIAL").contains(r.getValueString("status")))
|
||||
.map(r -> r.getValueInteger("id")).collect(Collectors.toSet());
|
||||
rows = rows.stream().filter(r -> ids.contains(r.getValueInteger("snapshotId"))).toList();
|
||||
}
|
||||
cache.put(key, rows);
|
||||
}
|
||||
return (cache.get(key));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Latest inventory only: retired tables and dropped indexes cannot recur.
|
||||
*******************************************************************************/
|
||||
public List<QRecord> inventory(String suffix) throws Exception
|
||||
{
|
||||
Instant latest = settings.getValueInstant("lastTableSnapshotAt");
|
||||
return (identities(suffix).stream().filter(r -> latest == null || latest.equals(r.getValueInstant("lastSeenAt"))).toList());
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Scale observed table deltas to a day when the observation window is short.
|
||||
** Table sampling is independent of the faster database snapshot interval.
|
||||
*******************************************************************************/
|
||||
public double perDay(QRecord table, String field) throws Exception
|
||||
{
|
||||
List<QRecord> rows = window("table_snapshot", 1).stream()
|
||||
.filter(r -> table.getValueInteger("id").equals(r.getValueInteger("tableStatId"))).toList();
|
||||
if(rows.isEmpty())
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
Instant first = rows.stream().map(r -> r.getValueInstant("capturedAt")).min(Instant::compareTo).orElseThrow();
|
||||
Instant last = rows.stream().map(r -> r.getValueInstant("capturedAt")).max(Instant::compareTo).orElseThrow();
|
||||
double seconds = Math.min(86400, Duration.between(first, last).getSeconds() + Math.max(60, number(settings, "tableSnapshotMinutes") * 60));
|
||||
return (rows.stream().mapToDouble(r -> number(r, field)).sum() * 86400 / seconds);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Minimum age of the current database statistics, including retained history.
|
||||
*******************************************************************************/
|
||||
public double statsAgeDays() throws Exception
|
||||
{
|
||||
QRecord latest = DbPerfStore.first("db_perf_snapshot", DbPerfStore.eq("backendName", backend)
|
||||
.withCriteria(new QFilterCriteria("rawStatsReset", QCriteriaOperator.IS_NOT_BLANK))
|
||||
.withOrderBy(new QFilterOrderBy("capturedAt", false)));
|
||||
Instant start = latest == null ? null : latest.getValueInstant("rawStatsReset");
|
||||
if(start == null)
|
||||
{
|
||||
QRecord first = DbPerfStore.first("db_perf_snapshot", DbPerfStore.eq("backendName", backend)
|
||||
.withOrderBy(new QFilterOrderBy("capturedAt", true)));
|
||||
start = first == null ? now : first.getValueInstant("capturedAt");
|
||||
}
|
||||
return (Math.max(0, Duration.between(start, now).toSeconds() / 86400.0));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Null counters mean no observed activity, never an invented measurement.
|
||||
*******************************************************************************/
|
||||
public static double number(QRecord row, String field)
|
||||
{
|
||||
return (row == null || row.getValue(field) == null ? 0 : row.getValueBigDecimal(field).doubleValue());
|
||||
}
|
||||
}
|
||||
@@ -1,516 +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.analyze;
|
||||
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qbits.dbperformance.normalize.SqlNormalizer;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The fourteen seeded rules. Every decision is based on stored collector rows;
|
||||
** administrators control severities and thresholds through db_perf_rule.
|
||||
*******************************************************************************/
|
||||
public enum StoredRule implements DbPerfRule
|
||||
{
|
||||
SLOW_QUERY, MISSING_INDEX, UNUSED_INDEX, DUPLICATE_INDEX, N_PLUS_ONE, HOT_JOB,
|
||||
OVER_AUDITED, BLOAT, VACUUM_STALE, POOL_SIZING, IDLE_IN_TRANSACTION,
|
||||
CACHE_HIT_LOW, TEMP_SPILL, DEADLOCKS;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Seed identity.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String code()
|
||||
{
|
||||
return (name());
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Disabled evaluation is inert even when called outside AnalyzeService.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<FindingDraft> evaluate(RuleContext ctx) throws Exception
|
||||
{
|
||||
if(!Boolean.TRUE.equals(ctx.rule.getValueBoolean("enabled")))
|
||||
{
|
||||
return (List.of());
|
||||
}
|
||||
return (switch(this)
|
||||
{
|
||||
case SLOW_QUERY -> slow(ctx);
|
||||
case MISSING_INDEX, OVER_AUDITED, BLOAT, VACUUM_STALE -> tables(ctx);
|
||||
case UNUSED_INDEX, DUPLICATE_INDEX -> indexes(ctx);
|
||||
case N_PLUS_ONE -> repeats(ctx);
|
||||
case HOT_JOB -> jobs(ctx);
|
||||
case POOL_SIZING -> pools(ctx);
|
||||
case IDLE_IN_TRANSACTION -> idle(ctx);
|
||||
case CACHE_HIT_LOW, DEADLOCKS, TEMP_SPILL -> database(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Mean and share use the collector's 24-hour aggregates, excluding stale rows.
|
||||
*******************************************************************************/
|
||||
private List<FindingDraft> slow(RuleContext ctx) throws Exception
|
||||
{
|
||||
List<FindingDraft> findings = new ArrayList<>();
|
||||
for(QRecord row : ctx.identities("statement"))
|
||||
{
|
||||
if(row.getValueInstant("lastSeenAt") == null || row.getValueInstant("lastSeenAt").isBefore(ctx.now.minusSeconds(86400)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
double share = n(row, "shareOfDbTime24h");
|
||||
if(n(row, "meanMs24h") >= ctx.threshold(1) || share >= ctx.threshold(2))
|
||||
{
|
||||
String sql = URLEncoder.encode("EXPLAIN " + row.getValueString("queryText"), StandardCharsets.UTF_8).replace("+", "%20");
|
||||
String recommendation = "Open the [Query plan action](/record/db_perf_statement/" + row.getValueInteger("id")
|
||||
+ ") to inspect this query, or [Explain in SQL Workbench](" + ctx.runtime.getConfig().getSqlWorkbenchRunnerPath() + "?sql=" + sql + "). Review scan and join costs before changing indexes.";
|
||||
findings.add(draft(ctx, row.getValueString("id"), share / 100, "High query execution cost over 24 hours",
|
||||
evidence("Calls", n(row, "calls24h"), "Mean ms", n(row, "meanMs24h"), "Total ms", n(row, "totalMs24h"), "DB time %", share), recommendation, null, row, "statement", null));
|
||||
}
|
||||
}
|
||||
return (findings);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Inventory-based rules share the same current table list and daily deltas.
|
||||
*******************************************************************************/
|
||||
private List<FindingDraft> tables(RuleContext ctx) throws Exception
|
||||
{
|
||||
List<FindingDraft> findings = new ArrayList<>();
|
||||
for(QRecord table : ctx.inventory("table_stat"))
|
||||
{
|
||||
String name = table.getValueString("tableName");
|
||||
String subject = table.getValueString("schemaName") + "." + name;
|
||||
if(this == MISSING_INDEX)
|
||||
{
|
||||
double reads = ctx.perDay(table, "seqTupRead");
|
||||
if(n(table, "seqScanShare") < ctx.threshold(1) || reads < ctx.threshold(2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Set<String> columns = new LinkedHashSet<>();
|
||||
for(QRecord statement : ctx.identities("statement").stream()
|
||||
.filter(s -> name.equals(s.getValueString("mainTable")) || subject.equals(s.getValueString("mainTable")))
|
||||
.filter(s -> n(s, "totalMs24h") > 0 && s.getValueInstant("lastSeenAt") != null && !s.getValueInstant("lastSeenAt").isBefore(ctx.now.minusSeconds(86400)))
|
||||
.sorted(Comparator.comparingDouble((QRecord s) -> n(s, "totalMs24h")).reversed()).limit(20).toList())
|
||||
{
|
||||
columns.addAll(PredicateParser.candidates(statement.getValueString("queryText"), table.getValueString("schemaName"), name));
|
||||
}
|
||||
for(QRecord index : ctx.inventory("index_stat"))
|
||||
{
|
||||
List<String> indexed = PredicateParser.indexColumns(index.getValueString("columns"));
|
||||
if(sameTable(table, index) && Boolean.TRUE.equals(index.getValueBoolean("isValid")) && !indexed.isEmpty() && plainIndex(index))
|
||||
{
|
||||
columns.remove(indexed.getFirst());
|
||||
}
|
||||
}
|
||||
if(!columns.isEmpty())
|
||||
{
|
||||
String reason = reads + " sequential tuples/day; " + n(table, "seqScanShare") + "% sequential scans";
|
||||
findings.add(draft(ctx, subject, n(table, "seqScanShare") / 100, reason,
|
||||
evidence("Sequential scan %", n(table, "seqScanShare"), "Tuples/day", reads, "Candidate columns", String.join(", ", columns)),
|
||||
"Check Query plan and selectivity for " + String.join(", ", columns) + ". Review the suggested column order and migration before committing it.",
|
||||
MigrationSnippets.create(ctx.now, table, List.copyOf(columns), reason), table, "table", null));
|
||||
}
|
||||
}
|
||||
else if(this == BLOAT && n(table, "deadRatio") >= ctx.threshold(1) && n(table, "nDeadTup") >= ctx.threshold(2))
|
||||
{
|
||||
String reason = n(table, "nDeadTup") + " dead tuples (" + n(table, "deadRatio") + "%)";
|
||||
findings.add(draft(ctx, subject, n(table, "deadRatio") / 100, reason,
|
||||
evidence("Dead tuples", n(table, "nDeadTup"), "Live tuples", n(table, "nLiveTup"), "Dead %", n(table, "deadRatio")),
|
||||
"Review long transactions and autovacuum capacity. Consider a lower per-table vacuum scale factor; the optional rollback resets it to the server default.",
|
||||
MigrationSnippets.vacuum(ctx.now, table, reason), table, "table", null));
|
||||
}
|
||||
else if(this == VACUUM_STALE)
|
||||
{
|
||||
Instant vacuum = table.getValueInstant("lastVacuum");
|
||||
Instant auto = table.getValueInstant("lastAutovacuum");
|
||||
if(vacuum == null || auto != null && auto.isAfter(vacuum))
|
||||
{
|
||||
vacuum = auto;
|
||||
}
|
||||
double age = vacuum == null ? ctx.statsAgeDays() : Duration.between(vacuum, ctx.now).toSeconds() / 86400.0;
|
||||
double writes = n(table, "nTupIns") + n(table, "nTupUpd") + n(table, "nTupDel");
|
||||
if(writes > 0 && age > ctx.threshold(1) || n(table, "freezeAge") >= ctx.threshold(2))
|
||||
{
|
||||
findings.add(draft(ctx, subject, Math.min(1, n(table, "freezeAge") / Math.max(1, ctx.threshold(2))), "Vacuum maintenance needs attention",
|
||||
evidence("Days since vacuum (or observation age)", age, "Writes", writes, "Freeze age", n(table, "freezeAge")),
|
||||
"Review autovacuum settings and long transactions preventing vacuum. Schedule maintenance and investigate transaction freeze age.", null, table, "table", null));
|
||||
}
|
||||
}
|
||||
else if(this == OVER_AUDITED && name.toLowerCase(Locale.ROOT).matches("(?:audit.*|.*_audit.*)"))
|
||||
{
|
||||
double inserts = ctx.perDay(table, "nTupIns");
|
||||
if(inserts >= ctx.threshold(1))
|
||||
{
|
||||
Map<String, Double> units = new LinkedHashMap<>();
|
||||
for(QRecord row : ctx.window("attribution", 1))
|
||||
{
|
||||
if((name.equals(row.getValueString("tableName")) || subject.equals(row.getValueString("tableName"))) && "INSERT".equalsIgnoreCase(row.getValueString("operation")))
|
||||
{
|
||||
units.merge(String.valueOf(row.getValueString("unitName")), n(row, "statements"), Double::sum);
|
||||
}
|
||||
}
|
||||
String contributors = units.entrySet().stream().sorted(Map.Entry.<String, Double>comparingByValue().reversed()).limit(5)
|
||||
.map(e -> e.getKey() + " (" + e.getValue() + " inserts)").collect(Collectors.joining(", "));
|
||||
Map<String, Double> sources = new LinkedHashMap<>();
|
||||
for(QRecord row : ctx.window("attribution", 1))
|
||||
{
|
||||
String source = row.getValueString("tableName");
|
||||
if(source != null && !source.toLowerCase(Locale.ROOT).matches("(?:audit.*|.*_audit.*)")
|
||||
&& units.containsKey(row.getValueString("unitName")) && Set.of("INSERT", "UPDATE", "DELETE").contains(String.valueOf(row.getValueString("operation")).toUpperCase(Locale.ROOT)))
|
||||
{
|
||||
sources.merge(source, n(row, "statements"), Double::sum);
|
||||
}
|
||||
}
|
||||
String sourceTables = sources.entrySet().stream().sorted(Map.Entry.<String, Double>comparingByValue().reversed()).limit(5)
|
||||
.map(e -> e.getKey() + " (" + e.getValue() + " writes)").collect(Collectors.joining(", "));
|
||||
findings.add(draft(ctx, subject, Math.min(1, inserts / Math.max(1, ctx.threshold(1) * 10)), "High audit insert volume",
|
||||
evidence("Audit inserts/day", inserts, "Attributed units", contributors.isEmpty() ? "Unavailable" : contributors),
|
||||
"Lower AuditLevel on noisy source tables after reviewing audit requirements. " + (contributors.isEmpty() ? "Source-table attribution is unavailable." : "Largest audit-producing units: " + contributors + ".")
|
||||
+ (sourceTables.isEmpty() ? "" : " Source tables with the most writes in those units: " + sourceTables + ". These are correlated writes, not measured per-source audit row counts."),
|
||||
null, table, "table", null));
|
||||
}
|
||||
}
|
||||
}
|
||||
return (findings);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Never propose dropping unique, primary, invalid or structurally incomparable
|
||||
** indexes. Equivalent peers choose one survivor deterministically.
|
||||
*******************************************************************************/
|
||||
private List<FindingDraft> indexes(RuleContext ctx) throws Exception
|
||||
{
|
||||
List<QRecord> indexes = ctx.inventory("index_stat");
|
||||
double bytes = indexes.stream().mapToDouble(r -> n(r, "sizeBytes")).sum();
|
||||
List<FindingDraft> findings = new ArrayList<>();
|
||||
for(QRecord index : indexes)
|
||||
{
|
||||
if(Boolean.TRUE.equals(index.getValueBoolean("isUnique")) || Boolean.TRUE.equals(index.getValueBoolean("isPrimary")) || !Boolean.TRUE.equals(index.getValueBoolean("isValid")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String reason = null;
|
||||
if(this == UNUSED_INDEX && n(index, "idxScan") == 0 && ctx.statsAgeDays() >= ctx.threshold(1) && n(index, "sizeBytes") / 1048576 >= ctx.threshold(2))
|
||||
{
|
||||
reason = "Zero scans over " + ctx.statsAgeDays() + " days; " + n(index, "sizeBytes") / 1048576 + " MB";
|
||||
}
|
||||
if(this == DUPLICATE_INDEX && plainIndex(index))
|
||||
{
|
||||
List<String> columns = PredicateParser.indexColumns(index.getValueString("columns"));
|
||||
for(QRecord other : indexes)
|
||||
{
|
||||
List<String> covering = PredicateParser.indexColumns(other.getValueString("columns"));
|
||||
if(!index.getValueInteger("id").equals(other.getValueInteger("id")) && Objects.equals(index.getValueInteger("tableStatId"), other.getValueInteger("tableStatId"))
|
||||
&& Boolean.TRUE.equals(other.getValueBoolean("isValid")) && plainIndex(other) && !columns.isEmpty() && covering.size() >= columns.size()
|
||||
&& covering.subList(0, columns.size()).equals(columns))
|
||||
{
|
||||
boolean exact = columns.equals(covering);
|
||||
boolean unique = Boolean.TRUE.equals(other.getValueBoolean("isUnique")) || Boolean.TRUE.equals(other.getValueBoolean("isPrimary"));
|
||||
if(exact && (unique || other.getValueInteger("id") < index.getValueInteger("id")) || !exact && !unique)
|
||||
{
|
||||
reason = "Redundant with " + other.getValueString("indexName") + ": " + other.getValueString("definition");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(reason != null)
|
||||
{
|
||||
findings.add(draft(ctx, index.getValueString("schemaName") + "." + index.getValueString("indexName"), n(index, "sizeBytes") / Math.max(1, bytes), reason,
|
||||
evidence("Index bytes", n(index, "sizeBytes"), "Scans", n(index, "idxScan"), "Write cost", n(index, "writeCost"), "Definition", index.getValueString("definition")),
|
||||
"Review rare workloads and dependencies before dropping this index. Commit the migration only after verifying the replacement and rollback.",
|
||||
MigrationSnippets.drop(ctx.now, index, code(), reason), index, "index", null));
|
||||
}
|
||||
}
|
||||
return (findings);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Column prefixes alone cannot prove equivalence for partial/expression or
|
||||
** non-btree indexes. Such structures need human review, not a drop snippet.
|
||||
*******************************************************************************/
|
||||
private static boolean plainIndex(QRecord index)
|
||||
{
|
||||
String definition = index.getValueString("definition");
|
||||
if(definition == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
String lower = definition.toLowerCase(Locale.ROOT);
|
||||
return (!PredicateParser.indexColumns(index.getValueString("columns")).isEmpty() && !lower.contains(" where ") && !lower.contains(" include ")
|
||||
&& (!lower.contains(" using ") || lower.contains(" using btree ")));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Aggregate pods/windows before applying the minimum affected-unit threshold.
|
||||
*******************************************************************************/
|
||||
private List<FindingDraft> repeats(RuleContext ctx) throws Exception
|
||||
{
|
||||
Map<String, List<QRecord>> groups = group(ctx.window("repeat_pattern", 1), "unitKind", "unitName", "fingerprint");
|
||||
List<FindingDraft> findings = new ArrayList<>();
|
||||
for(var entry : groups.entrySet())
|
||||
{
|
||||
List<QRecord> rows = entry.getValue();
|
||||
double max = rows.stream().mapToDouble(r -> n(r, "maxRepeats")).max().orElse(0);
|
||||
double units = rows.stream().mapToDouble(r -> n(r, "unitsAffected")).sum();
|
||||
if(max >= ctx.threshold(1) && units >= ctx.threshold(2))
|
||||
{
|
||||
QRecord row = rows.getFirst();
|
||||
findings.add(draft(ctx, entry.getKey(), Math.min(1, max / Math.max(1, ctx.threshold(1) * 10)), "Repeated queries in " + row.getValueString("unitKind") + " " + row.getValueString("unitName"),
|
||||
evidence("Max repeats", max, "Units affected", units, "Total repeats", rows.stream().mapToDouble(r -> n(r, "totalRepeats")).sum()),
|
||||
"Batch lookups on " + row.getValueString("tableName") + " in " + row.getValueString("unitName") + " into one query with IN (...), a QQQ QueryJoin, or a record pre-fetch.", null, row, "unit", row.getValueString("unitName")));
|
||||
}
|
||||
}
|
||||
return (findings);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Share is relative to all attributed work, including HTTP routes.
|
||||
*******************************************************************************/
|
||||
private List<FindingDraft> jobs(RuleContext ctx) throws Exception
|
||||
{
|
||||
List<QRecord> rows = ctx.window("attribution", 1);
|
||||
double total = rows.stream().mapToDouble(r -> n(r, "totalMs")).sum();
|
||||
List<FindingDraft> findings = new ArrayList<>();
|
||||
for(var entry : group(rows, "unitKind", "unitName").entrySet())
|
||||
{
|
||||
QRecord row = entry.getValue().getFirst();
|
||||
double time = entry.getValue().stream().mapToDouble(r -> n(r, "totalMs")).sum();
|
||||
double share = total == 0 ? 0 : time / total;
|
||||
if(List.of("SCHEDULED_JOB", "PROCESS").contains(row.getValueString("unitKind")) && total > 0 && share * 100 >= ctx.threshold(1))
|
||||
{
|
||||
findings.add(draft(ctx, entry.getKey(), share, "Expensive " + row.getValueString("unitKind") + " " + row.getValueString("unitName"),
|
||||
evidence("Total ms", time, "Attributed time %", share * 100), "Review the schedule and batch database operations in " + row.getValueString("unitName") + ".", null, row, "unit", row.getValueString("unitName")));
|
||||
}
|
||||
}
|
||||
return (findings);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Per-application peaks sum users/states within each snapshot first.
|
||||
*******************************************************************************/
|
||||
private List<FindingDraft> pools(RuleContext ctx) throws Exception
|
||||
{
|
||||
double peak = 0;
|
||||
double idle = 0;
|
||||
double maxConnections = 0;
|
||||
for(QRecord row : ctx.window("snapshot", 1))
|
||||
{
|
||||
if(n(row, "maxConnections") > 0)
|
||||
{
|
||||
peak = Math.max(peak, n(row, "connectionsTotal") / n(row, "maxConnections") * 100);
|
||||
maxConnections = Math.max(maxConnections, n(row, "maxConnections"));
|
||||
}
|
||||
if(n(row, "connectionsTotal") > 0)
|
||||
{
|
||||
idle = Math.max(idle, n(row, "connectionsIdle") / n(row, "connectionsTotal") * 100);
|
||||
}
|
||||
}
|
||||
if(maxConnections == 0 || peak < ctx.threshold(1) && idle < ctx.threshold(2))
|
||||
{
|
||||
return (List.of());
|
||||
}
|
||||
Map<String, Double> peaks = new LinkedHashMap<>();
|
||||
Map<String, Double> activePeaks = new LinkedHashMap<>();
|
||||
for(List<QRecord> rows : group(ctx.window("connection_sample", 1), "snapshotId", "applicationName").values())
|
||||
{
|
||||
String app = String.valueOf(rows.getFirst().getValueString("applicationName"));
|
||||
peaks.merge(app, rows.stream().mapToDouble(r -> n(r, "connectionCount")).sum(), Math::max);
|
||||
activePeaks.merge(app, rows.stream().filter(r -> "active".equals(r.getValueString("state"))).mapToDouble(r -> n(r, "connectionCount")).sum(), Math::max);
|
||||
}
|
||||
double desired = activePeaks.values().stream().mapToDouble(v -> Math.max(1, Math.ceil(v * 1.2))).sum();
|
||||
double scale = Math.min(1, maxConnections * 0.8 / Math.max(1, desired));
|
||||
String sizes = activePeaks.entrySet().stream().map(e -> e.getKey() + ": combined pool cap " + Math.max(1, (int) Math.floor(Math.max(1, Math.ceil(e.getValue() * 1.2)) * scale))
|
||||
+ " across replicas (peak active " + e.getValue().intValue() + ")").collect(Collectors.joining("; "));
|
||||
String applications = peaks.entrySet().stream().map(e -> e.getKey() + ": peak " + e.getValue().intValue()).collect(Collectors.joining("; "));
|
||||
return (List.of(draft(ctx, "database", Math.max(peak, idle) / 100, "Review connection pool sizes",
|
||||
evidence("Peak used %", peak, "Peak idle %", idle, "Max connections", maxConnections, "Application peaks", applications),
|
||||
"Starting pool sizes from observed active demand: " + sizes + ". Validate under representative load and divide combined caps across replicas; reserve capacity for administration and other applications. Observed application peaks: " + applications,
|
||||
null, null, "database", null)));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** One finding per application, retaining the worst observed transaction age.
|
||||
*******************************************************************************/
|
||||
private List<FindingDraft> idle(RuleContext ctx) throws Exception
|
||||
{
|
||||
List<QRecord> rows = ctx.window("activity_sample", 1).stream().filter(r -> "IDLE_IN_TRANSACTION".equals(r.getValueString("reason"))).toList();
|
||||
List<FindingDraft> findings = new ArrayList<>();
|
||||
for(var entry : group(rows, "applicationName").entrySet())
|
||||
{
|
||||
double seconds = entry.getValue().stream().mapToDouble(r -> n(r, "xactSeconds")).max().orElse(0);
|
||||
if(seconds >= ctx.threshold(1))
|
||||
{
|
||||
String application = String.valueOf(entry.getValue().getFirst().getValueString("applicationName"));
|
||||
findings.add(draft(ctx, entry.getKey(), Math.min(1, seconds / Math.max(1, ctx.threshold(1) * 10)), "Idle transaction in " + application,
|
||||
evidence("Maximum transaction seconds", seconds, "Samples", entry.getValue().size()),
|
||||
"Fix transaction scope in " + application + "; consider idle_in_transaction_session_timeout.", null, null, "database", application));
|
||||
}
|
||||
}
|
||||
return (findings);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Database counters are already deltas; temporary blocks are 8 KiB each.
|
||||
*******************************************************************************/
|
||||
private List<FindingDraft> database(RuleContext ctx) throws Exception
|
||||
{
|
||||
List<QRecord> snapshots = ctx.window("snapshot", 1).stream().filter(r -> "OK".equals(r.getValueString("status")) || this != CACHE_HIT_LOW && "PARTIAL".equals(r.getValueString("status"))).toList();
|
||||
List<FindingDraft> findings = new ArrayList<>();
|
||||
if(this == CACHE_HIT_LOW)
|
||||
{
|
||||
var average = snapshots.stream().filter(r -> r.getValue("cacheHitRatio") != null).mapToDouble(r -> n(r, "cacheHitRatio")).average();
|
||||
if(average.isPresent() && average.getAsDouble() < ctx.threshold(1))
|
||||
{
|
||||
findings.add(draft(ctx, "database", 1 - average.getAsDouble() / 100, "Low average database cache hit ratio", evidence("Average hit %", average.getAsDouble(), "OK snapshots", snapshots.size()),
|
||||
"Review query scans and the working set before increasing database memory.", null, null, "database", null));
|
||||
}
|
||||
}
|
||||
else if(this == DEADLOCKS)
|
||||
{
|
||||
double count = snapshots.stream().mapToDouble(r -> n(r, "deadlocks")).sum();
|
||||
if(!snapshots.isEmpty() && count >= ctx.threshold(1))
|
||||
{
|
||||
findings.add(draft(ctx, "database", Math.min(1, count / Math.max(1, ctx.threshold(1) * 10)), "Database deadlocks detected", evidence("Deadlocks in 24h", count),
|
||||
"Use consistent lock ordering and shorter transactions. Review affected application operations.", null, null, "database", null));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double databaseMb = snapshots.stream().mapToDouble(r -> n(r, "tempBytes")).sum() / 1048576;
|
||||
Map<Integer, QRecord> statements = ctx.identities("statement").stream().collect(Collectors.toMap(r -> r.getValueInteger("id"), r -> r));
|
||||
for(var entry : group(ctx.window("query_snapshot", 1), "statementId").entrySet())
|
||||
{
|
||||
double mb = entry.getValue().stream().mapToDouble(r -> n(r, "tempBlksWritten")).sum() / 128;
|
||||
if(mb >= ctx.threshold(1))
|
||||
{
|
||||
QRecord statement = statements.get(entry.getValue().getFirst().getValueInteger("statementId"));
|
||||
findings.add(draft(ctx, entry.getKey(), mb / Math.max(1, databaseMb), "Query spilled temporary data to disk",
|
||||
evidence("Query temp MB", mb, "Database temp MB", databaseMb), "Inspect sorts and hash joins in Query plan. Tune the query and review work_mem per operation, accounting for concurrent queries.", null, statement, "statement", null));
|
||||
}
|
||||
}
|
||||
if(databaseMb >= ctx.threshold(1))
|
||||
{
|
||||
findings.add(draft(ctx, "database", Math.min(1, databaseMb / Math.max(1, ctx.threshold(1) * 10)), "Database temporary disk spill",
|
||||
evidence("Database temp MB in 24h", databaseMb), "Review spilling queries, including those outside the stored top-N. Size work_mem with concurrency in mind.", null, null, "database", null));
|
||||
}
|
||||
}
|
||||
return (findings);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Delimit parts with lengths to avoid collisions between unit names.
|
||||
*******************************************************************************/
|
||||
private static Map<String, List<QRecord>> group(List<QRecord> rows, String... fields)
|
||||
{
|
||||
Map<String, List<QRecord>> groups = new LinkedHashMap<>();
|
||||
for(QRecord row : rows)
|
||||
{
|
||||
String key = java.util.Arrays.stream(fields).map(f -> String.valueOf(row.getValue(f))).map(s -> s.length() + ":" + s).collect(Collectors.joining("|"));
|
||||
groups.computeIfAbsent(key, ignored -> new ArrayList<>()).add(row);
|
||||
}
|
||||
return (groups);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Shared foreign-key comparison.
|
||||
*******************************************************************************/
|
||||
private static boolean sameTable(QRecord table, QRecord index)
|
||||
{
|
||||
return (table.getValueInteger("id").equals(index.getValueInteger("tableStatId")));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Numeric convenience keeps units visible at the comparison sites.
|
||||
*******************************************************************************/
|
||||
private static double n(QRecord row, String field)
|
||||
{
|
||||
return (RuleContext.number(row, field));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Small Markdown table with escaped cell delimiters.
|
||||
*******************************************************************************/
|
||||
private static String evidence(Object... cells)
|
||||
{
|
||||
StringBuilder text = new StringBuilder("| Measurement | Value |\n| --- | --- |\n");
|
||||
for(int i = 0; i < cells.length; i += 2)
|
||||
{
|
||||
text.append("| ").append(cells[i]).append(" | ").append(String.valueOf(cells[i + 1]).replace("|", "\\|").replace("\n", " ")).append(" |\n");
|
||||
}
|
||||
return (text.toString());
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Centralize persisted links and severity; no rule invents a fixed severity.
|
||||
*******************************************************************************/
|
||||
private FindingDraft draft(RuleContext ctx, String subject, double impact, String summary, String evidence, String recommendation,
|
||||
String snippet, QRecord row, String kind, String unit)
|
||||
{
|
||||
String table = row == null ? null : row.getValueString(kind.equals("statement") ? "mainTable" : "tableName");
|
||||
Integer id = row == null ? null : row.getValueInteger("id");
|
||||
String key = code() + ":" + ctx.backend + ":" + subject;
|
||||
if(key.length() > 300)
|
||||
{
|
||||
key = code() + ":" + ctx.backend + ":" + SqlNormalizer.fingerprint(subject);
|
||||
}
|
||||
String title = ctx.rule.getValueString("label") + ": " + (unit == null ? subject : unit);
|
||||
return (new FindingDraft(code(), key, ctx.rule.getValueString("severity"), impact,
|
||||
title.substring(0, Math.min(300, title.length())), summary.substring(0, Math.min(2000, summary.length())), evidence, recommendation, snippet, table,
|
||||
kind.equals("statement") ? id : null, kind.equals("table") ? id : kind.equals("index") ? row.getValueInteger("tableStatId") : null,
|
||||
kind.equals("index") ? id : null, unit));
|
||||
}
|
||||
}
|
||||
@@ -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,617 +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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
new com.kingsrook.qbits.dbperformance.alerts.SnapshotEvaluator(runtime).evaluate(snapshot);
|
||||
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,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.health;
|
||||
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.RuleContext;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
|
||||
import com.kingsrook.qqq.backend.core.health.HealthCategory;
|
||||
import com.kingsrook.qqq.backend.core.health.HealthCheckKind;
|
||||
import com.kingsrook.qqq.backend.core.health.HealthCheckMetaData;
|
||||
import com.kingsrook.qqq.backend.core.health.HealthCheckOutcome;
|
||||
import com.kingsrook.qqq.backend.core.health.HealthChecksMetaData;
|
||||
import com.kingsrook.qqq.backend.core.health.HealthDetails;
|
||||
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.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Passive core health checks. No live database probes and no SQL in details.
|
||||
*******************************************************************************/
|
||||
public class DbPerfHealthChecks
|
||||
{
|
||||
private final DbPerfRuntime runtime;
|
||||
private final Clock clock;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Bind observations to this runtime and clock.
|
||||
*******************************************************************************/
|
||||
public DbPerfHealthChecks(DbPerfRuntime runtime, Clock clock)
|
||||
{
|
||||
this.runtime = runtime;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Register the three central database dependency checks.
|
||||
*******************************************************************************/
|
||||
public static void register(QInstance instance)
|
||||
{
|
||||
DbPerfHealthChecks checks = new DbPerfHealthChecks(DbPerfRuntime.of(instance), Clock.systemUTC());
|
||||
for(String name : List.of("collector", "stats_access", "connections"))
|
||||
{
|
||||
HealthChecksMetaData.ofOrWithNew(instance).withCheck(new HealthCheckMetaData().withName("db_performance." + name)
|
||||
.withLabel("Database performance " + name.replace('_', ' ')).withComponent("db_performance")
|
||||
.withCategory(HealthCategory.DATABASE).withKind(HealthCheckKind.DEPENDENCY).withIntervalSeconds(60).withTimeoutMs(5000)
|
||||
.withCheckInstance(input -> checks.check(name)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Errors expose only a remedy, never a JDBC exception or query text.
|
||||
*******************************************************************************/
|
||||
public HealthCheckOutcome check(String name)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (runtime.asSystem(() -> evaluate(name)));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
return (HealthCheckOutcome.down("Unable to read Database Performance health; check connectivity and migrations"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Missing settings never cause a write during a health request.
|
||||
*******************************************************************************/
|
||||
private HealthCheckOutcome evaluate(String name) throws Exception
|
||||
{
|
||||
String backend = runtime.getConfig().getBackendName();
|
||||
QRecord settings = DbPerfStore.first("db_perf_settings", DbPerfStore.eq("backendName", backend));
|
||||
if(settings == null || !Boolean.TRUE.equals(settings.getValueBoolean("enabled")))
|
||||
{
|
||||
return (HealthCheckOutcome.unknown("Database performance collection is disabled or not configured"));
|
||||
}
|
||||
if(name.equals("stats_access"))
|
||||
{
|
||||
String access = settings.getValueString("statsAccess");
|
||||
if(access == null)
|
||||
{
|
||||
return (HealthCheckOutcome.unknown("No statistics access observation yet"));
|
||||
}
|
||||
String user = ((RDBMSBackendMetaData) runtime.getInstance().getBackend(backend)).getUsername();
|
||||
HealthCheckOutcome outcome = switch(access)
|
||||
{
|
||||
case "FULL" -> HealthCheckOutcome.up("Full statistics access");
|
||||
case "NO_READ_ALL_STATS" -> HealthCheckOutcome.degraded("grant pg_read_all_stats to " + user);
|
||||
case "NO_PG_STAT_STATEMENTS" -> HealthCheckOutcome.down("Enable pg_stat_statements in shared_preload_libraries and create the extension");
|
||||
case "UNSUPPORTED" -> HealthCheckOutcome.down("Use a supported PostgreSQL version (13 or newer) and statistics provider");
|
||||
default -> HealthCheckOutcome.unknown("Statistics access has not been classified");
|
||||
};
|
||||
String safe = List.of("FULL", "NO_READ_ALL_STATS", "NO_PG_STAT_STATEMENTS", "UNSUPPORTED").contains(access) ? access : "UNKNOWN";
|
||||
return (outcome.withDetails(new HealthDetails().withValue("statsAccess", safe)));
|
||||
}
|
||||
var filter = DbPerfStore.eq("backendName", backend).withOrderBy(new QFilterOrderBy("capturedAt", false));
|
||||
if(name.equals("collector"))
|
||||
{
|
||||
filter.withCriteria(new QFilterCriteria("status", QCriteriaOperator.EQUALS, "OK"));
|
||||
}
|
||||
QRecord snapshot = DbPerfStore.first("db_perf_snapshot", filter);
|
||||
if(snapshot == null)
|
||||
{
|
||||
return (name.equals("collector") ? HealthCheckOutcome.down("No successful snapshot; run Collect now and check statistics access") : HealthCheckOutcome.unknown("No connection sample yet"));
|
||||
}
|
||||
if(name.equals("collector"))
|
||||
{
|
||||
long age = Math.max(0, Duration.between(snapshot.getValueInstant("capturedAt"), clock.instant()).getSeconds());
|
||||
double interval = Math.max(1, RuleContext.number(settings, "snapshotIntervalMinutes")) * 60;
|
||||
HealthCheckOutcome outcome = age <= 3 * interval ? HealthCheckOutcome.up("Collector is current")
|
||||
: age <= 6 * interval ? HealthCheckOutcome.degraded("Collector is delayed") : HealthCheckOutcome.down("Collector is stale; check the collection schedule");
|
||||
return (outcome.withDetails(new HealthDetails().withValue("ageSeconds", age).withValue("intervalSeconds", interval)));
|
||||
}
|
||||
double maximum = RuleContext.number(snapshot, "maxConnections");
|
||||
if(maximum <= 0 || snapshot.getValue("connectionsTotal") == null)
|
||||
{
|
||||
return (HealthCheckOutcome.unknown("Latest snapshot has no connection capacity measurement"));
|
||||
}
|
||||
double used = 100 * RuleContext.number(snapshot, "connectionsTotal") / maximum;
|
||||
HealthCheckOutcome outcome = used >= 95 ? HealthCheckOutcome.down("Connection usage is at least 95%")
|
||||
: used >= RuleContext.number(settings, "connectionAlertPercent") ? HealthCheckOutcome.degraded("Connection usage reached the alert threshold") : HealthCheckOutcome.up("Connection capacity is available");
|
||||
return (outcome.withDetails(new HealthDetails().withValue("usedPercent", used).withValue("connectionsTotal", snapshot.getValueInteger("connectionsTotal")).withValue("maxConnections", snapshot.getValueInteger("maxConnections"))));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
-93
@@ -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,76 +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.util.ArrayList;
|
||||
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.AnalyzeService;
|
||||
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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Scheduled analysis and Analyze now use the same stored-data evaluation path.
|
||||
*******************************************************************************/
|
||||
public class DbPerfAnalyzeStep implements BackendStep
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Include per-rule counts and failures in the standard 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<>();
|
||||
try
|
||||
{
|
||||
for(AnalyzeService.Result result : new AnalyzeService(runtime).run(runtime.getConfig().getBackendName()))
|
||||
{
|
||||
if(result.error() != null)
|
||||
{
|
||||
lines.add(new ProcessSummaryLine(Status.ERROR, null, result.ruleCode() + ": " + result.error()));
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, result.counts().created(), result.ruleCode() + " findings created"));
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, result.counts().updated(), result.ruleCode() + " findings updated"));
|
||||
lines.add(new ProcessSummaryLine(Status.INFO, result.counts().resolved(), result.ruleCode() + " findings resolved"));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Unable to analyze database performance", e));
|
||||
}
|
||||
output.addValue("processResults", lines);
|
||||
}
|
||||
}
|
||||
@@ -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,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.processes;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
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;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Scheduled maintenance and the Snapshot table action; no process navigation.
|
||||
*******************************************************************************/
|
||||
public final class DbPerfProcesses
|
||||
{
|
||||
public static final String ANALYZE = "dbPerfAnalyze";
|
||||
public static final String ANALYZE_NOW = "dbPerfAnalyzeNow";
|
||||
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, ANALYZE, "Analyze database performance", DbPerfAnalyzeStep.class, config.getAnalyzeCron()));
|
||||
instance.addProcess(base(config, ANALYZE_NOW, "Analyze now", DbPerfAnalyzeStep.class, false).withTableName("db_perf_finding")
|
||||
.withStep(new QFrontendStepMetaData().withName("result").withLabel("Result")
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.PROCESS_SUMMARY_RESULTS))));
|
||||
for(String status : List.of("ACKNOWLEDGED", "RESOLVED", "IGNORED", "OPEN"))
|
||||
{
|
||||
String label = switch(status)
|
||||
{
|
||||
case "ACKNOWLEDGED" -> "Acknowledge";
|
||||
case "RESOLVED" -> "Mark resolved";
|
||||
case "IGNORED" -> "Ignore";
|
||||
default -> "Reopen";
|
||||
};
|
||||
QProcessMetaData action = StreamedETLWithFrontendProcess.processMetaDataBuilder()
|
||||
.withName("dbPerfFinding" + status).withLabel(label).withIcon(new QIcon("done"))
|
||||
.withTableName("db_perf_finding").withSourceTable("db_perf_finding").withDestinationTable("db_perf_finding")
|
||||
.withExtractStepClass(ExtractViaQueryStep.class).withTransformStepClass(FindingSteps.Transform.class).withLoadStepClass(FindingSteps.Load.class)
|
||||
.withSupportsFullValidation(true).withDoFullValidation(true).withMinInputRecords(1)
|
||||
.withPreviewMessage("Review the selected findings before changing their status.")
|
||||
.withFields(List.of(new QFieldMetaData(FindingSteps.TARGET, QFieldType.STRING).withDefaultValue(status))).getProcessMetaData();
|
||||
action.withPermissionRules(DbPerfPermissions.rules(config.getPermissionBaseName() + ".manage_findings"))
|
||||
.withStep(0, new QFrontendStepMetaData().withName("note").withLabel("Note")
|
||||
.withFormField(new QFieldMetaData(FindingSteps.NOTE, QFieldType.STRING).withLabel("Note").withMaxLength(2000).withIsRequired("IGNORED".equals(status))));
|
||||
instance.addProcess(action);
|
||||
}
|
||||
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,212 +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 java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qbits.dbperformance.DbPerfRuntime;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.FindingService;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
|
||||
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;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractLoadStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractTransformStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ProcessSummaryProviderInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Selected-record streamed actions. Review does not write; Load rechecks access
|
||||
** under the caller session before narrowly scoped system writes.
|
||||
*******************************************************************************/
|
||||
public final class FindingSteps
|
||||
{
|
||||
public static final String NOTE = "statusNote";
|
||||
public static final String TARGET = "findingStatus";
|
||||
private static final Map<String, String> VERBS = Map.of("ACKNOWLEDGED", "acknowledged", "RESOLVED", "marked resolved", "IGNORED", "ignored", "OPEN", "reopened");
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class.
|
||||
*******************************************************************************/
|
||||
private FindingSteps()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Both phases validate permission, requested state and Ignore's required note.
|
||||
*******************************************************************************/
|
||||
private static String validate(RunBackendStepInput input) throws QException
|
||||
{
|
||||
DbPerfRuntime runtime = DbPerfRuntime.of(QContext.getQInstance());
|
||||
if(QContext.getQSession() == null || !QContext.getQSession().hasPermission(runtime.getConfig().getPermissionBaseName() + ".manage_findings"))
|
||||
{
|
||||
throw (new QException("Managing findings permission is required"));
|
||||
}
|
||||
String status = input.getValueString(TARGET);
|
||||
if(!VERBS.containsKey(status == null ? "" : status))
|
||||
{
|
||||
throw (new QException("Invalid finding status"));
|
||||
}
|
||||
String note = input.getValueString(NOTE);
|
||||
if("IGNORED".equals(status) && (note == null || note.isBlank()) || note != null && note.length() > 2000)
|
||||
{
|
||||
throw (new QException("Ignore requires a note; notes must be at most 2000 characters"));
|
||||
}
|
||||
return (status);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Standard singular/plural and future/past wording.
|
||||
*******************************************************************************/
|
||||
private static ProcessSummaryLine line(Status status, String future, String past)
|
||||
{
|
||||
ProcessSummaryLine line = new ProcessSummaryLine(status, 0, null);
|
||||
line.setSingularFutureMessage("finding will be " + future);
|
||||
line.setPluralFutureMessage("findings will be " + future);
|
||||
line.setSingularPastMessage("finding was " + past);
|
||||
line.setPluralPastMessage("findings were " + past);
|
||||
return (line);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The Review screen describes precisely the selected records.
|
||||
*******************************************************************************/
|
||||
public static class Transform extends AbstractTransformStep implements ProcessSummaryProviderInterface
|
||||
{
|
||||
private ProcessSummaryLine changes;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Validate and forward a bounded page with no mutations.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void runOnePage(RunBackendStepInput input, RunBackendStepOutput output) throws QException
|
||||
{
|
||||
String verb = VERBS.get(validate(input));
|
||||
if(changes == null)
|
||||
{
|
||||
changes = line(Status.OK, verb, verb);
|
||||
}
|
||||
for(QRecord record : input.getRecords())
|
||||
{
|
||||
changes.incrementCountAndAddPrimaryKey(record.getValue("id"));
|
||||
output.addRecord(record);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The frontend chooses the correct tense and plurality.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput output, boolean result)
|
||||
{
|
||||
return (changes == null ? new ArrayList<>() : new ArrayList<>(List.of(changes)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Persist the status fields only; supplied record data is never trusted.
|
||||
*******************************************************************************/
|
||||
public static class Load extends AbstractLoadStep implements ProcessSummaryProviderInterface
|
||||
{
|
||||
private ProcessSummaryLine changes;
|
||||
private final ProcessSummaryLine errors = line(Status.ERROR, "checked for access", "not updated; unavailable or write failed");
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Preserve the human user id before entering the system write context.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void runOnePage(RunBackendStepInput input, RunBackendStepOutput output) throws QException
|
||||
{
|
||||
String status = validate(input);
|
||||
String actor = QContext.getQSession().getUser() == null ? null : QContext.getQSession().getUser().getIdReference();
|
||||
DbPerfRuntime runtime = DbPerfRuntime.of(QContext.getQInstance());
|
||||
if(changes == null)
|
||||
{
|
||||
changes = line(Status.OK, VERBS.get(status), VERBS.get(status));
|
||||
}
|
||||
for(QRecord selected : input.getRecords())
|
||||
{
|
||||
try
|
||||
{
|
||||
// Read under the caller context so record security still applies.
|
||||
QRecord visible = DbPerfStore.first("db_perf_finding", DbPerfStore.eq("id", selected.getValueInteger("id")));
|
||||
if(visible == null || !runtime.getConfig().getBackendName().equals(visible.getValueString("backendName")))
|
||||
{
|
||||
errors.incrementCountAndAddPrimaryKey(selected.getValue("id"));
|
||||
continue;
|
||||
}
|
||||
QRecord patch = new QRecord().withValue("id", visible.getValueInteger("id")).withValue("status", status)
|
||||
.withValue("statusChangedBy", actor).withValue("resolvedAt", "RESOLVED".equals(status) ? Instant.now() : null);
|
||||
if(input.getValueString(NOTE) != null && !input.getValueString(NOTE).isBlank())
|
||||
{
|
||||
patch.withValue(NOTE, input.getValueString(NOTE));
|
||||
}
|
||||
synchronized(runtime)
|
||||
{
|
||||
runtime.transaction(() ->
|
||||
{
|
||||
FindingService.lockRule(runtime, visible.getValueString("ruleCode"));
|
||||
DbPerfStore.update("db_perf_finding", List.of(patch));
|
||||
return (null);
|
||||
});
|
||||
}
|
||||
changes.incrementCountAndAddPrimaryKey(selected.getValue("id"));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
errors.incrementCountAndAddPrimaryKey(selected.getValue("id"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Include failures while allowing other selected records to complete.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput output, boolean result)
|
||||
{
|
||||
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
|
||||
if(changes != null)
|
||||
{
|
||||
lines.add(changes);
|
||||
}
|
||||
errors.addSelfToListIfAnyCount(lines);
|
||||
return (lines);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
-132
@@ -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,121 +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.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.RuleContext;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.StoredRule;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfSettingsService;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Real H2/QRecord fixtures shared by rule, alert and action integration tests.
|
||||
*******************************************************************************/
|
||||
class B1Fixture
|
||||
{
|
||||
static final Instant NOW = Instant.parse("2026-10-15T15:00:00Z");
|
||||
static final Clock CLOCK = Clock.fixed(NOW, ZoneOffset.UTC);
|
||||
final DbPerfRuntime runtime;
|
||||
final String backend;
|
||||
final QRecord settings;
|
||||
final QRecord snapshot;
|
||||
final QRecord table;
|
||||
final QRecord statement;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Evidence crossing every seeded threshold without a live statistics probe.
|
||||
*******************************************************************************/
|
||||
B1Fixture() throws Exception
|
||||
{
|
||||
runtime = DbPerfRuntime.of(DbPerfTestSupport.instance());
|
||||
backend = runtime.getConfig().getBackendName();
|
||||
settings = new DbPerfSettingsService(runtime).ensure(backend);
|
||||
snapshot = insert("snapshot", new QRecord().withValue("backendName", backend).withValue("capturedAt", NOW).withValue("status", "OK")
|
||||
.withValue("rawStatsReset", NOW.minusSeconds(30 * 86400L)).withValue("cacheHitRatio", 90).withValue("deadlocks", 2L)
|
||||
.withValue("connectionsTotal", 90).withValue("connectionsIdle", 60).withValue("maxConnections", 100).withValue("tempBytes", 200 * 1048576L));
|
||||
table = insert("table_stat", new QRecord().withValue("backendName", backend).withValue("schemaName", "public").withValue("tableName", "audit_orders")
|
||||
.withValue("lastSeenAt", NOW).withValue("seqScanShare", 80).withValue("seqScan", 800L).withValue("idxScan", 200L)
|
||||
.withValue("nTupIns", 200000L).withValue("nDeadTup", 30000L).withValue("nLiveTup", 70000L).withValue("deadRatio", 30)
|
||||
.withValue("lastVacuum", NOW.minusSeconds(10 * 86400L)).withValue("freezeAge", 160000000L));
|
||||
insert("table_snapshot", new QRecord().withValue("snapshotId", snapshot.getValueInteger("id")).withValue("tableStatId", table.getValueInteger("id"))
|
||||
.withValue("capturedAt", NOW).withValue("seqTupRead", 100000L).withValue("nTupIns", 10000L));
|
||||
statement = insert("statement", new QRecord().withValue("backendName", backend).withValue("queryId", 1L).withValue("dbUser", "app").withValue("toplevel", true)
|
||||
.withValue("fingerprint", "a".repeat(64)).withValue("queryText", "select * from audit_orders where customer_id = ? and status in (?)")
|
||||
.withValue("mainTable", "audit_orders").withValue("lastSeenAt", NOW).withValue("calls24h", 100L).withValue("meanMs24h", 600)
|
||||
.withValue("totalMs24h", 60000).withValue("shareOfDbTime24h", 25));
|
||||
insert("query_snapshot", new QRecord().withValue("snapshotId", snapshot.getValueInteger("id")).withValue("statementId", statement.getValueInteger("id"))
|
||||
.withValue("capturedAt", NOW).withValue("totalMs", 10000).withValue("tempBlksWritten", 150 * 128L));
|
||||
index("idx_customer", "customer_id", false, 0);
|
||||
index("idx_customer_copy", "customer_id", false, 0);
|
||||
index("idx_customer_wide", "customer_id, created_at", false, 20);
|
||||
insert("repeat_pattern", new QRecord().withValue("windowStart", NOW.minusSeconds(60)).withValue("windowEnd", NOW).withValue("unitKind", "PROCESS")
|
||||
.withValue("unitName", "shipOrders").withValue("tableName", "orders").withValue("fingerprint", "a".repeat(64))
|
||||
.withValue("maxRepeats", 20).withValue("unitsAffected", 3L).withValue("totalRepeats", 60L));
|
||||
insert("repeat_pattern", new QRecord().withValue("windowStart", NOW.minusSeconds(60)).withValue("windowEnd", NOW).withValue("unitKind", "PROCESS")
|
||||
.withValue("unitName", "shipOrders").withValue("tableName", "orders").withValue("fingerprint", "a".repeat(64))
|
||||
.withValue("maxRepeats", 10).withValue("unitsAffected", 3L).withValue("totalRepeats", 30L));
|
||||
insert("attribution", new QRecord().withValue("windowStart", NOW.minusSeconds(60)).withValue("windowEnd", NOW).withValue("unitKind", "PROCESS")
|
||||
.withValue("unitName", "shipOrders").withValue("tableName", "audit_orders").withValue("operation", "INSERT").withValue("totalMs", 1000).withValue("statements", 300L));
|
||||
insert("connection_sample", new QRecord().withValue("snapshotId", snapshot.getValueInteger("id")).withValue("capturedAt", NOW)
|
||||
.withValue("applicationName", "orders-app").withValue("state", "active").withValue("connectionCount", 30));
|
||||
insert("connection_sample", new QRecord().withValue("snapshotId", snapshot.getValueInteger("id")).withValue("capturedAt", NOW)
|
||||
.withValue("applicationName", "orders-app").withValue("state", "idle").withValue("connectionCount", 60));
|
||||
insert("activity_sample", new QRecord().withValue("snapshotId", snapshot.getValueInteger("id")).withValue("capturedAt", NOW)
|
||||
.withValue("applicationName", "orders-app").withValue("reason", "IDLE_IN_TRANSACTION").withValue("xactSeconds", 120));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Consistent index metadata with collected definition.
|
||||
*******************************************************************************/
|
||||
QRecord index(String name, String columns, boolean unique, long scans) throws Exception
|
||||
{
|
||||
return (insert("index_stat", new QRecord().withValue("backendName", backend).withValue("schemaName", "public").withValue("tableName", "audit_orders")
|
||||
.withValue("tableStatId", table.getValueInteger("id")).withValue("indexName", name).withValue("columns", columns)
|
||||
.withValue("definition", "CREATE " + (unique ? "UNIQUE " : "") + "INDEX " + name + " ON public.audit_orders USING btree (" + columns + ")")
|
||||
.withValue("isValid", true).withValue("isUnique", unique).withValue("isPrimary", false).withValue("idxScan", scans).withValue("sizeBytes", 2 * 1048576L).withValue("lastSeenAt", NOW)));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A fresh context observes edits to threshold rows on every call.
|
||||
*******************************************************************************/
|
||||
RuleContext context(StoredRule rule) throws Exception
|
||||
{
|
||||
return (new RuleContext(runtime, backend, DbPerfStore.first("db_perf_rule", DbPerfStore.eq("code", rule.code())), settings, NOW));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Use normal QQQ inserts and return the generated identity.
|
||||
*******************************************************************************/
|
||||
static QRecord insert(String suffix, QRecord row) throws Exception
|
||||
{
|
||||
return (DbPerfStore.insert("db_perf_" + suffix, List.of(row)).getFirst());
|
||||
}
|
||||
}
|
||||
@@ -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,112 +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.time.Clock;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
|
||||
import com.kingsrook.qbits.dbperformance.health.DbPerfHealthChecks;
|
||||
import com.kingsrook.qqq.backend.core.health.HealthCategory;
|
||||
import com.kingsrook.qqq.backend.core.health.HealthCheckKind;
|
||||
import com.kingsrook.qqq.backend.core.health.HealthChecksMetaData;
|
||||
import com.kingsrook.qqq.backend.core.health.HealthStatus;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Passive health boundaries, metadata registration and typed detail guarantees.
|
||||
*******************************************************************************/
|
||||
class DbPerfHealthTest
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Collector boundaries are inclusive at three and six intervals.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void collectorAgeAndDisabled() throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
assertThat(checks(f, 900).check("collector").getStatus()).isEqualTo(HealthStatus.UP);
|
||||
assertThat(checks(f, 901).check("collector").getStatus()).isEqualTo(HealthStatus.DEGRADED);
|
||||
assertThat(checks(f, 1800).check("collector").getStatus()).isEqualTo(HealthStatus.DEGRADED);
|
||||
assertThat(checks(f, 1801).check("collector").getStatus()).isEqualTo(HealthStatus.DOWN);
|
||||
DbPerfStore.update("db_perf_snapshot", List.of(new QRecord().withValue("id", f.snapshot.getValueInteger("id")).withValue("status", "FAILED")));
|
||||
assertThat(checks(f, 0).check("collector").getStatus()).isEqualTo(HealthStatus.DOWN);
|
||||
DbPerfStore.update("db_perf_settings", List.of(new QRecord().withValue("id", f.settings.getValueInteger("id")).withValue("enabled", false)));
|
||||
assertThat(checks(f, 0).check("collector").getStatus()).isEqualTo(HealthStatus.UNKNOWN);
|
||||
assertThat(HealthChecksMetaData.ofOrWithNew(f.runtime.getInstance()).getChecks()).hasSize(3).allSatisfy(metadata ->
|
||||
{
|
||||
assertThat(metadata.getComponent()).isEqualTo("db_performance");
|
||||
assertThat(metadata.getCategory()).isEqualTo(HealthCategory.DATABASE);
|
||||
assertThat(metadata.getKind()).isEqualTo(HealthCheckKind.DEPENDENCY);
|
||||
assertThat(metadata.getIntervalSeconds()).isBetween(60, 300);
|
||||
assertThat(metadata.getCheckInstance().run(null).getStatus()).isEqualTo(HealthStatus.UNKNOWN);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Access remedies and connection capacity reflect persisted data only.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void accessAndCapacity() throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
DbPerfHealthChecks checks = checks(f, 0);
|
||||
for(String access : List.of("FULL", "NO_READ_ALL_STATS", "NO_PG_STAT_STATEMENTS", "UNSUPPORTED", "UNKNOWN"))
|
||||
{
|
||||
DbPerfStore.update("db_perf_settings", List.of(new QRecord().withValue("id", f.settings.getValueInteger("id")).withValue("statsAccess", access)));
|
||||
var outcome = checks.check("stats_access");
|
||||
assertThat(outcome.getDetails().getValues()).containsEntry("statsAccess", access).hasSize(1);
|
||||
assertThat(outcome.getStatus()).isEqualTo(switch(access)
|
||||
{
|
||||
case "FULL" -> HealthStatus.UP;
|
||||
case "NO_READ_ALL_STATS" -> HealthStatus.DEGRADED;
|
||||
case "UNKNOWN" -> HealthStatus.UNKNOWN;
|
||||
default -> HealthStatus.DOWN;
|
||||
});
|
||||
if(access.equals("NO_READ_ALL_STATS"))
|
||||
{
|
||||
assertThat(outcome.getMessage()).isEqualTo("grant pg_read_all_stats to sa");
|
||||
}
|
||||
}
|
||||
for(int total : List.of(79, 80, 94, 95))
|
||||
{
|
||||
DbPerfStore.update("db_perf_snapshot", List.of(new QRecord().withValue("id", f.snapshot.getValueInteger("id")).withValue("connectionsTotal", total)));
|
||||
assertThat(checks.check("connections").getStatus()).isEqualTo(total < 80 ? HealthStatus.UP : total < 95 ? HealthStatus.DEGRADED : HealthStatus.DOWN);
|
||||
assertThat(checks.check("connections").getDetails().getValues().values()).allSatisfy(v -> assertThat(v).isInstanceOf(Number.class));
|
||||
}
|
||||
DbPerfStore.update("db_perf_snapshot", List.of(new QRecord().withValue("id", f.snapshot.getValueInteger("id")).withValue("maxConnections", null)));
|
||||
assertThat(checks.check("connections").getStatus()).isEqualTo(HealthStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A controllable clock avoids waiting for collection intervals in tests.
|
||||
*******************************************************************************/
|
||||
private static DbPerfHealthChecks checks(B1Fixture f, int seconds)
|
||||
{
|
||||
return (new DbPerfHealthChecks(f.runtime, Clock.fixed(B1Fixture.NOW.plusSeconds(seconds), ZoneOffset.UTC)));
|
||||
}
|
||||
}
|
||||
@@ -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,148 +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.analyze.FindingService;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.StoredRule;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
|
||||
import com.kingsrook.qbits.dbperformance.processes.DbPerfAnalyzeStep;
|
||||
import com.kingsrook.qbits.dbperformance.processes.DbPerfProcesses;
|
||||
import com.kingsrook.qbits.dbperformance.processes.FindingSteps;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.jobs.DurableProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.jobs.model.JobSemantics;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
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.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Record selection, Review/Result summaries, permission and note enforcement.
|
||||
*******************************************************************************/
|
||||
class FindingActionsTest
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Every action updates only selected records and records the human actor.
|
||||
*******************************************************************************/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "ACKNOWLEDGED", "RESOLVED", "IGNORED", "OPEN" })
|
||||
void selectedRecordAction(String status) throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
new FindingService(f.runtime).apply(f.backend, "UNUSED_INDEX", StoredRule.UNUSED_INDEX.evaluate(f.context(StoredRule.UNUSED_INDEX)));
|
||||
List<QRecord> findings = DbPerfStore.query("db_perf_finding", new QQueryFilter());
|
||||
f.runtime.getInstance().setEnforcePermissionsInCoreActions(true);
|
||||
QContext.setQSession(new QSession().withPermissions(DbPerfPermissions.VIEW, DbPerfPermissions.MANAGE_FINDINGS).withUser(new QUser().withIdReference("operator-42")));
|
||||
RunBackendStepInput input = new RunBackendStepInput();
|
||||
input.setRecords(List.of(findings.getFirst()));
|
||||
input.addValue(FindingSteps.TARGET, status);
|
||||
input.addValue(FindingSteps.NOTE, "Reviewed by operator");
|
||||
FindingSteps.Transform transform = new FindingSteps.Transform();
|
||||
RunBackendStepOutput preview = new RunBackendStepOutput();
|
||||
transform.runOnePage(input, preview);
|
||||
ProcessSummaryLine line = (ProcessSummaryLine) transform.getProcessSummary(preview, false).getFirst();
|
||||
assertThat(line.getCount()).isEqualTo(1);
|
||||
assertThat(line.getPluralFutureMessage()).startsWith("findings will be ");
|
||||
assertThat(DbPerfStore.first("db_perf_finding", DbPerfStore.eq("id", findings.getFirst().getValueInteger("id"))).getValueString("status")).isEqualTo("OPEN");
|
||||
FindingSteps.Load load = new FindingSteps.Load();
|
||||
RunBackendStepOutput result = new RunBackendStepOutput();
|
||||
load.runOnePage(input, result);
|
||||
ProcessSummaryLine actual = (ProcessSummaryLine) load.getProcessSummary(result, true).getFirst();
|
||||
assertThat(actual.getCount()).isEqualTo(1);
|
||||
assertThat(actual.getPluralPastMessage()).startsWith("findings were ");
|
||||
QRecord changed = DbPerfStore.first("db_perf_finding", DbPerfStore.eq("id", findings.getFirst().getValueInteger("id")));
|
||||
assertThat(changed.getValueString("status")).isEqualTo(status);
|
||||
assertThat(changed.getValueString("statusChangedBy")).isEqualTo("operator-42");
|
||||
assertThat(changed.getValueString("statusNote")).isEqualTo("Reviewed by operator");
|
||||
assertThat(changed.getValueInstant("resolvedAt") != null).isEqualTo(status.equals("RESOLVED"));
|
||||
assertThat(DbPerfStore.first("db_perf_finding", DbPerfStore.eq("id", findings.getLast().getValueInteger("id"))).getValueString("status")).isEqualTo("OPEN");
|
||||
var metadata = f.runtime.getInstance().getProcess("dbPerfFinding" + status);
|
||||
assertThat(metadata.getTableName()).isEqualTo("db_perf_finding");
|
||||
assertThat(metadata.getPermissionRules().getPermissionBaseName()).isEqualTo(DbPerfPermissions.MANAGE_FINDINGS);
|
||||
assertThat(metadata.getStepList()).extracting(step -> step.getLabel()).contains("Review", "Result");
|
||||
QFrontendStepMetaData note = (QFrontendStepMetaData) metadata.getStepList().getFirst();
|
||||
assertThat(note.getFormField(FindingSteps.NOTE).getIsRequired()).isEqualTo(status.equals("IGNORED"));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Direct invocation cannot bypass permission or Ignore validation.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void validationAndErrors() throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
RunBackendStepInput input = new RunBackendStepInput();
|
||||
input.addValue(FindingSteps.TARGET, "IGNORED");
|
||||
RunBackendStepOutput output = new RunBackendStepOutput();
|
||||
assertThatThrownBy(() -> new FindingSteps.Transform().runOnePage(input, output)).hasMessageContaining("note");
|
||||
assertThatThrownBy(() -> new FindingSteps.Load().runOnePage(input, output)).hasMessageContaining("note");
|
||||
input.addValue(FindingSteps.NOTE, "x".repeat(2001));
|
||||
assertThatThrownBy(() -> new FindingSteps.Load().runOnePage(input, output)).hasMessageContaining("2000");
|
||||
input.addValue(FindingSteps.NOTE, "Reviewed");
|
||||
QContext.setQSession(new QSession().withPermissions(DbPerfPermissions.VIEW));
|
||||
assertThatThrownBy(() -> new FindingSteps.Transform().runOnePage(input, output)).hasMessageContaining("permission");
|
||||
assertThatThrownBy(() -> new FindingSteps.Load().runOnePage(input, output)).hasMessageContaining("permission");
|
||||
assertThatThrownBy(() -> new DbPerfAnalyzeStep().run(input, output)).hasMessageContaining("permission");
|
||||
QContext.setQSession(new QSession().withPermissions(DbPerfPermissions.VIEW, DbPerfPermissions.MANAGE_FINDINGS));
|
||||
input.addValue(FindingSteps.TARGET, "BAD_STATUS");
|
||||
assertThatThrownBy(() -> new FindingSteps.Load().runOnePage(input, output)).hasMessageContaining("Invalid");
|
||||
input.addValue(FindingSteps.TARGET, "RESOLVED");
|
||||
input.setRecords(List.of(new QRecord().withValue("id", 999999)));
|
||||
FindingSteps.Load load = new FindingSteps.Load();
|
||||
load.runOnePage(input, output);
|
||||
assertThat(load.getProcessSummary(output, true)).anySatisfy(line ->
|
||||
{
|
||||
assertThat(((ProcessSummaryLine) line).getStatus()).isEqualTo(Status.ERROR);
|
||||
assertThat(((ProcessSummaryLine) line).getCount()).isEqualTo(1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Analyzer metadata follows phase A durable scheduling and summary conventions.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void analyzeAction() throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
var process = f.runtime.getInstance().getProcess(DbPerfProcesses.ANALYZE);
|
||||
assertThat(process.getIsHidden()).isTrue();
|
||||
assertThat(DurableProcessMetaData.of(process).getSemantics()).isEqualTo(JobSemantics.ATOMIC);
|
||||
assertThat(f.runtime.getInstance().getProcess(DbPerfProcesses.ANALYZE_NOW).getTableName()).isEqualTo("db_perf_finding");
|
||||
RunBackendStepOutput output = new RunBackendStepOutput();
|
||||
new DbPerfAnalyzeStep().run(new RunBackendStepInput(), output);
|
||||
assertThat((List<?>) output.getValue("processResults")).hasSize(42);
|
||||
}
|
||||
}
|
||||
@@ -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,81 +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.analyze.MigrationSnippets;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.PredicateParser;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Best-effort SQL parsing and ready-to-review PostgreSQL migration text.
|
||||
*******************************************************************************/
|
||||
class PredicateAndSnippetTest
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Bind values, comments, aliases and quoted columns retain table ownership.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void predicates()
|
||||
{
|
||||
assertThat(PredicateParser.candidates("SELECT * FROM public.orders o JOIN customers c ON c.id = o.customer_id WHERE o.status IN ($1, $2) AND c.name = $3", "public", "orders"))
|
||||
.containsExactly("customer_id", "status");
|
||||
assertThat(PredicateParser.candidates("select * from orders where id = ? and status in (?)", "public", "orders")).containsExactly("id", "status");
|
||||
assertThat(PredicateParser.candidates("select * from orders where lower(name) = ? order by created_at", "public", "orders")).isEmpty();
|
||||
assertThat(PredicateParser.candidates("select * from orders where id in (select id from customers)", "public", "orders")).isEmpty();
|
||||
assertThat(PredicateParser.candidates("select * from other.orders where id = ?", "public", "orders")).isEmpty();
|
||||
assertThat(PredicateParser.candidates("select * from \"Order\" where \"Customer ID\" = 'private' /* where fake = ? */", "public", "Order")).containsExactly("Customer ID");
|
||||
assertThat(PredicateParser.indexColumns("\"last,name\", id")).containsExactly("last,name", "id");
|
||||
assertThat(PredicateParser.indexColumns("lower(name)")).isEmpty();
|
||||
assertThat(PredicateParser.indexColumns("id,")).isEmpty();
|
||||
assertThat(PredicateParser.indexColumns(null)).isEmpty();
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Concurrent DDL has a transaction opt-out and schema-qualified rollback.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void snippets()
|
||||
{
|
||||
QRecord table = new QRecord().withValue("schemaName", "public").withValue("tableName", "order");
|
||||
String create = MigrationSnippets.create(B1Fixture.NOW, table, List.of("Customer ID", "status"), "1000000 reads/day\nreview first");
|
||||
assertThat(create).startsWith("-- liquibase formatted sql\n-- changeset db-performance:20261015-")
|
||||
.contains("runInTransaction:false", "CREATE INDEX CONCURRENTLY IF NOT EXISTS", "ON public.\"order\" (\"Customer ID\", status)", "-- rollback DROP INDEX CONCURRENTLY IF EXISTS public.")
|
||||
.contains("1000000 reads/day review first");
|
||||
String name = MigrationSnippets.indexName("really_long_table_".repeat(8), List.of("one", "two"));
|
||||
assertThat(name.length()).isLessThanOrEqualTo(63);
|
||||
assertThat(name).matches("[a-z_][a-z0-9_]*");
|
||||
assertThat(MigrationSnippets.indexName("orders", List.of("id"))).isEqualTo("idx_orders_id");
|
||||
QRecord index = new QRecord().withValue("schemaName", "sales").withValue("indexName", "idx_orders_id")
|
||||
.withValue("definition", "CREATE INDEX idx_orders_id ON sales.orders USING btree (id)");
|
||||
assertThat(MigrationSnippets.drop(B1Fixture.NOW, index, "UNUSED_INDEX", "0 scans"))
|
||||
.contains("DROP INDEX CONCURRENTLY IF EXISTS sales.idx_orders_id;", "-- rollback CREATE INDEX CONCURRENTLY idx_orders_id ON sales.orders USING btree (id);");
|
||||
assertThat(MigrationSnippets.drop(B1Fixture.NOW, index.withValue("definition", null), "UNUSED_INDEX", "0 scans")).isNull();
|
||||
assertThat(MigrationSnippets.vacuum(B1Fixture.NOW, table, "30% dead"))
|
||||
.contains("SET (autovacuum_vacuum_scale_factor = 0.05)", "-- rollback ALTER TABLE public.\"order\" RESET");
|
||||
assertThat(MigrationSnippets.quote("a\"b")).isEqualTo("\"a\"\"b\"");
|
||||
}
|
||||
}
|
||||
@@ -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,210 +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.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.dbperformance.alerts.SnapshotEvaluator;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
|
||||
import com.kingsrook.qbits.dbperformance.collect.SnapshotCollector;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertResolution;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertSignal;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertSinkInterface;
|
||||
import com.kingsrook.qqq.backend.core.alerts.AlertsInstanceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.alerts.QAlertTypeMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Captured core signals verify stable keys, baseline math, recovery and gating.
|
||||
*******************************************************************************/
|
||||
class SnapshotEvaluatorTest
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Core constructs this sink, with captured signals reset between fixtures.
|
||||
*******************************************************************************/
|
||||
public static class CapturingSink implements AlertSinkInterface
|
||||
{
|
||||
static final List<AlertSignal> RAISED = new ArrayList<>();
|
||||
static final List<AlertResolution> RESOLVED = new ArrayList<>();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Capture raises.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void raise(AlertSignal signal)
|
||||
{
|
||||
RAISED.add(signal);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Capture resolutions.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void resolve(AlertResolution resolution)
|
||||
{
|
||||
RESOLVED.add(resolution);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** These producers do not send heartbeat signals.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void heartbeat(String key, Instant at)
|
||||
{
|
||||
throw (new AssertionError("Unexpected heartbeat"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Connection, blocking and collection failure recover on observed good data.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void raisesResolvesAndGates() throws Exception
|
||||
{
|
||||
B1Fixture f = fixture();
|
||||
B1Fixture.insert("activity_sample", new QRecord().withValue("snapshotId", f.snapshot.getValueInteger("id")).withValue("capturedAt", B1Fixture.NOW)
|
||||
.withValue("reason", "BLOCKED").withValue("querySeconds", 600));
|
||||
SnapshotEvaluator evaluator = new SnapshotEvaluator(f.runtime);
|
||||
evaluator.evaluate(f.snapshot);
|
||||
assertThat(CapturingSink.RAISED).extracting(AlertSignal::getAlertTypeName).contains(SnapshotEvaluator.CONNECTION_SATURATION, SnapshotEvaluator.BLOCKED_CHAIN);
|
||||
assertThat(CapturingSink.RAISED).allSatisfy(signal -> assertThat(signal.getDedupKey()).startsWith(signal.getAlertTypeName().replace("dbperf.", "db_perf.").replace('-', '_') + ":" + f.backend));
|
||||
QRecord failed = B1Fixture.insert("snapshot", new QRecord().withValue("backendName", f.backend).withValue("capturedAt", B1Fixture.NOW.plusSeconds(300))
|
||||
.withValue("status", "FAILED").withValue("errorMessage", "Collection failed (SQLException). Check connectivity."));
|
||||
evaluator.evaluate(failed);
|
||||
assertThat(CapturingSink.RAISED).anySatisfy(signal ->
|
||||
{
|
||||
assertThat(signal.getAlertTypeName()).isEqualTo(SnapshotEvaluator.COLLECTION_FAILED);
|
||||
assertThat(signal.getSummary()).contains("SQLException");
|
||||
});
|
||||
QRecord healthy = B1Fixture.insert("snapshot", new QRecord().withValue("backendName", f.backend).withValue("capturedAt", B1Fixture.NOW.plusSeconds(600))
|
||||
.withValue("status", "OK").withValue("connectionsTotal", 10).withValue("maxConnections", 100));
|
||||
evaluator.evaluate(healthy);
|
||||
assertThat(CapturingSink.RESOLVED).extracting(AlertResolution::getAlertTypeName).contains(SnapshotEvaluator.COLLECTION_FAILED, SnapshotEvaluator.CONNECTION_SATURATION, SnapshotEvaluator.BLOCKED_CHAIN);
|
||||
CapturingSink.RAISED.clear();
|
||||
CapturingSink.RESOLVED.clear();
|
||||
DbPerfStore.update("db_perf_settings", List.of(new QRecord().withValue("id", f.settings.getValueInteger("id")).withValue("alertsEnabled", false)));
|
||||
evaluator.evaluate(failed);
|
||||
evaluator.evaluate(f.snapshot);
|
||||
assertThat(CapturingSink.RAISED).isEmpty();
|
||||
assertThat(CapturingSink.RESOLVED).isEmpty();
|
||||
for(String type : List.of(SnapshotEvaluator.COLLECTION_FAILED, SnapshotEvaluator.QUERY_SPIKE, SnapshotEvaluator.CONNECTION_SATURATION, SnapshotEvaluator.BLOCKED_CHAIN))
|
||||
{
|
||||
assertThat(QAlertTypeMetaData.of(f.runtime.getInstance(), type).getDescription()).isNotBlank();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Twelve prior samples, median rather than mean, recovery and absence expiry.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void spikeBaselineAndRecovery() throws Exception
|
||||
{
|
||||
B1Fixture f = fixture();
|
||||
SnapshotEvaluator evaluator = new SnapshotEvaluator(f.runtime);
|
||||
for(int i = 1; i <= 12; i++)
|
||||
{
|
||||
Instant at = B1Fixture.NOW.minusSeconds(i * 300L);
|
||||
QRecord snapshot = B1Fixture.insert("snapshot", new QRecord().withValue("backendName", f.backend).withValue("capturedAt", at).withValue("status", "OK"));
|
||||
B1Fixture.insert("query_snapshot", new QRecord().withValue("snapshotId", snapshot.getValueInteger("id")).withValue("statementId", f.statement.getValueInteger("id"))
|
||||
.withValue("capturedAt", at).withValue("totalMs", i == 12 ? 1000000 : 1000));
|
||||
if(i == 11)
|
||||
{
|
||||
evaluator.evaluate(f.snapshot);
|
||||
assertThat(CapturingSink.RAISED).noneSatisfy(signal -> assertThat(signal.getAlertTypeName()).isEqualTo(SnapshotEvaluator.QUERY_SPIKE));
|
||||
}
|
||||
}
|
||||
DbPerfStore.update("db_perf_statement", List.of(new QRecord().withValue("id", f.statement.getValueInteger("id"))
|
||||
.withValue("queryText", "select * from audit_orders where name = 'private patient' and id = 123 " + " and status = ?".repeat(50))));
|
||||
evaluator.evaluate(f.snapshot);
|
||||
String key = "db_perf.query_spike:" + f.backend + ":" + f.statement.getValueInteger("id");
|
||||
AlertSignal spike = CapturingSink.RAISED.stream().filter(signal -> signal.getDedupKey().equals(key)).findFirst().orElseThrow();
|
||||
assertThat(spike.getSummary()).contains("prior median 1000.0 ms", "Statement " + f.statement.getValueInteger("id")).doesNotContain("private patient", "123");
|
||||
assertThat(spike.getSummary().length()).isLessThan(300);
|
||||
CapturingSink.RESOLVED.clear();
|
||||
QRecord recovered = B1Fixture.insert("snapshot", new QRecord().withValue("backendName", f.backend).withValue("capturedAt", B1Fixture.NOW.plusSeconds(300)).withValue("status", "OK"));
|
||||
B1Fixture.insert("query_snapshot", new QRecord().withValue("snapshotId", recovered.getValueInteger("id")).withValue("statementId", f.statement.getValueInteger("id"))
|
||||
.withValue("capturedAt", B1Fixture.NOW.plusSeconds(300)).withValue("totalMs", 100));
|
||||
evaluator.evaluate(recovered);
|
||||
assertThat(CapturingSink.RESOLVED).extracting(AlertResolution::getDedupKey).contains(key);
|
||||
CapturingSink.RESOLVED.clear();
|
||||
QRecord absent = B1Fixture.insert("snapshot", new QRecord().withValue("backendName", f.backend).withValue("capturedAt", B1Fixture.NOW.plusSeconds(3899)).withValue("status", "OK"));
|
||||
evaluator.evaluate(absent);
|
||||
assertThat(CapturingSink.RESOLVED).extracting(AlertResolution::getDedupKey).doesNotContain(key);
|
||||
absent.withValue("capturedAt", B1Fixture.NOW.plusSeconds(3900));
|
||||
new SnapshotEvaluator(f.runtime).evaluate(absent);
|
||||
assertThat(CapturingSink.RESOLVED).extracting(AlertResolution::getDedupKey).contains(key);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Partial statistics still report connection pressure, but cannot clear a
|
||||
** collection failure. A low query time can recover without a full baseline.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void partialSnapshotsAndRecoveryWithoutBaseline() throws Exception
|
||||
{
|
||||
B1Fixture f = fixture();
|
||||
SnapshotEvaluator evaluator = new SnapshotEvaluator(f.runtime);
|
||||
evaluator.evaluate(f.snapshot.withValue("status", "PARTIAL"));
|
||||
assertThat(CapturingSink.RAISED).extracting(AlertSignal::getAlertTypeName).contains(SnapshotEvaluator.CONNECTION_SATURATION);
|
||||
assertThat(CapturingSink.RESOLVED).extracting(AlertResolution::getAlertTypeName).doesNotContain(SnapshotEvaluator.COLLECTION_FAILED);
|
||||
QRecord query = DbPerfStore.first("db_perf_query_snapshot", DbPerfStore.eq("snapshotId", f.snapshot.getValueInteger("id")));
|
||||
DbPerfStore.update("db_perf_query_snapshot", List.of(new QRecord().withValue("id", query.getValueInteger("id")).withValue("totalMs", 1)));
|
||||
evaluator.evaluate(f.snapshot.withValue("status", "OK"));
|
||||
assertThat(CapturingSink.RESOLVED).extracting(AlertResolution::getDedupKey).contains("db_perf.query_spike:" + f.backend + ":" + f.statement.getValueInteger("id"));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Collector invokes evaluation after committing; no sink installation needed.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void collectorIntegration() throws Exception
|
||||
{
|
||||
B1Fixture f = fixture();
|
||||
new SnapshotCollector(f.runtime, B1Fixture.CLOCK, null).collect(f.backend, true);
|
||||
assertThat(CapturingSink.RAISED).extracting(AlertSignal::getAlertTypeName).contains(SnapshotEvaluator.COLLECTION_FAILED);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Register a clean capturing sink on the migrated instance.
|
||||
*******************************************************************************/
|
||||
private static B1Fixture fixture() throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
CapturingSink.RAISED.clear();
|
||||
CapturingSink.RESOLVED.clear();
|
||||
AlertsInstanceMetaData.ofOrWithNew(f.runtime.getInstance()).withSink(new QCodeReference(CapturingSink.class));
|
||||
return (f);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,221 +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.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.AnalyzeService;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.DbPerfRule;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.FindingDraft;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.FindingService;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.RuleContext;
|
||||
import com.kingsrook.qbits.dbperformance.analyze.StoredRule;
|
||||
import com.kingsrook.qbits.dbperformance.collect.DbPerfStore;
|
||||
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 org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Every seeded rule is exercised against migrated H2 rows, including lifecycle
|
||||
** reconciliation and live edits of rule configuration.
|
||||
*******************************************************************************/
|
||||
class StoredRuleTest
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Created, deduped, acknowledged, ignored, resolved, reopened and disabled.
|
||||
*******************************************************************************/
|
||||
@ParameterizedTest
|
||||
@EnumSource(StoredRule.class)
|
||||
void ruleLifecycle(StoredRule rule) throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
List<FindingDraft> drafts = rule.evaluate(f.context(rule));
|
||||
assertThat(drafts).as(rule.code()).isNotEmpty().allSatisfy(d ->
|
||||
{
|
||||
assertThat(d.severity()).isEqualTo("WARNING");
|
||||
assertThat(d.evidence()).contains("| Measurement | Value |");
|
||||
assertThat(d.score()).isBetween(BigDecimal.valueOf(0.2), BigDecimal.valueOf(2.2));
|
||||
});
|
||||
FindingService service = new FindingService(f.runtime, B1Fixture.CLOCK);
|
||||
assertThat(service.apply(f.backend, rule.code(), drafts).created()).isEqualTo(drafts.size());
|
||||
assertThat(service.apply(f.backend, rule.code(), drafts).updated()).isEqualTo(drafts.size());
|
||||
List<QRecord> findings = RuleContext.read("db_perf_finding", new QQueryFilter());
|
||||
assertThat(findings).hasSize(drafts.size()).allSatisfy(r -> assertThat(r.getValueInteger("occurrences")).isEqualTo(2));
|
||||
QRecord first = findings.getFirst();
|
||||
DbPerfStore.update("db_perf_finding", List.of(new QRecord().withValue("id", first.getValueInteger("id")).withValue("status", "ACKNOWLEDGED")));
|
||||
service.apply(f.backend, rule.code(), drafts);
|
||||
assertThat(finding(first).getValueString("status")).isEqualTo("ACKNOWLEDGED");
|
||||
assertThat(service.apply(f.backend, rule.code(), List.of()).resolved()).isEqualTo(drafts.size());
|
||||
assertThat(finding(first).getValueString("statusNote")).isEqualTo("No longer detected");
|
||||
service.apply(f.backend, rule.code(), drafts);
|
||||
assertThat(finding(first).getValueString("status")).isEqualTo("OPEN");
|
||||
assertThat(finding(first).getValue("resolvedAt")).isNull();
|
||||
DbPerfStore.update("db_perf_finding", List.of(new QRecord().withValue("id", first.getValueInteger("id")).withValue("status", "IGNORED").withValue("statusNote", "Accepted")));
|
||||
service.apply(f.backend, rule.code(), drafts);
|
||||
service.apply(f.backend, rule.code(), List.of());
|
||||
assertThat(finding(first).getValueString("status")).isEqualTo("IGNORED");
|
||||
assertThat(finding(first).getValueString("statusNote")).isEqualTo("Accepted");
|
||||
QRecord row = f.context(rule).rule;
|
||||
DbPerfStore.update("db_perf_rule", List.of(new QRecord().withValue("id", row.getValueInteger("id")).withValue("enabled", false)));
|
||||
int occurrences = finding(first).getValueInteger("occurrences");
|
||||
assertThat(rule.evaluate(f.context(rule))).isEmpty();
|
||||
assertThat(service.apply(f.backend, rule.code(), drafts)).isEqualTo(new FindingService.Counts(0, 0, 0));
|
||||
assertThat(finding(first).getValueInteger("occurrences")).isEqualTo(occurrences);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Changing thresholds in the persisted rule row changes the result.
|
||||
*******************************************************************************/
|
||||
@ParameterizedTest
|
||||
@EnumSource(value = StoredRule.class, names = "DUPLICATE_INDEX", mode = EnumSource.Mode.EXCLUDE)
|
||||
void thresholdsAreData(StoredRule rule) throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
QRecord row = f.context(rule).rule;
|
||||
assertThat(rule.evaluate(f.context(rule))).isNotEmpty();
|
||||
DbPerfStore.update("db_perf_rule", List.of(new QRecord().withValue("id", row.getValueInteger("id"))
|
||||
.withValue("threshold1", rule == StoredRule.CACHE_HIT_LOW ? 0 : 999999999).withValue("threshold2", 999999999)));
|
||||
assertThat(rule.evaluate(f.context(rule))).isEmpty();
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** One failing rule cannot resolve its findings or abort subsequent rules.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void failureIsolationAndSeverity() throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
DbPerfRule broken = new DbPerfRule()
|
||||
{
|
||||
@Override
|
||||
public String code()
|
||||
{
|
||||
return ("SLOW_QUERY");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FindingDraft> evaluate(RuleContext context)
|
||||
{
|
||||
throw (new IllegalStateException("private SQL must not escape"));
|
||||
}
|
||||
};
|
||||
new FindingService(f.runtime).apply(f.backend, "SLOW_QUERY", StoredRule.SLOW_QUERY.evaluate(f.context(StoredRule.SLOW_QUERY)));
|
||||
var result = new AnalyzeService(f.runtime, B1Fixture.CLOCK, List.of(broken, StoredRule.BLOAT)).run(f.backend);
|
||||
assertThat(result.getFirst().error()).contains("IllegalStateException").doesNotContain("private SQL");
|
||||
assertThat(result.getLast().counts().created()).isEqualTo(1);
|
||||
assertThat(DbPerfStore.first("db_perf_finding", DbPerfStore.eq("ruleCode", "SLOW_QUERY")).getValueString("status")).isEqualTo("OPEN");
|
||||
QRecord row = f.context(StoredRule.SLOW_QUERY).rule;
|
||||
DbPerfStore.update("db_perf_rule", List.of(new QRecord().withValue("id", row.getValueInteger("id")).withValue("severity", "CRITICAL")));
|
||||
FindingDraft draft = StoredRule.SLOW_QUERY.evaluate(f.context(StoredRule.SLOW_QUERY)).getFirst();
|
||||
assertThat(draft.score()).isEqualByComparingTo("1.4");
|
||||
assertThat(draft.recommendation()).contains("Query plan", "?sql=EXPLAIN%20");
|
||||
assertThat(new AnalyzeService(f.runtime, B1Fixture.CLOCK, Arrays.asList(StoredRule.values())).run(f.backend)).allSatisfy(r -> assertThat(r.error()).isNull());
|
||||
assertThatThrownBy(() -> new AnalyzeService(f.runtime).run("other")).isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Invalid scopes and duplicate identities roll back the entire reconciliation.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void atomicValidation() throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
FindingService service = new FindingService(f.runtime);
|
||||
FindingDraft draft = StoredRule.SLOW_QUERY.evaluate(f.context(StoredRule.SLOW_QUERY)).getFirst();
|
||||
assertThatThrownBy(() -> service.apply(f.backend, "SLOW_QUERY", List.of(draft, draft))).isInstanceOf(IllegalArgumentException.class);
|
||||
assertThat(RuleContext.read("db_perf_finding", new QQueryFilter())).isEmpty();
|
||||
assertThatThrownBy(() -> service.apply(f.backend, "BLOAT", List.of(draft))).isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatThrownBy(() -> service.apply("other", "SLOW_QUERY", List.of(draft))).isInstanceOf(IllegalArgumentException.class);
|
||||
assertThat(service.apply(f.backend, "NO_SUCH_RULE", List.of())).isEqualTo(new FindingService.Counts(0, 0, 0));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unrelated backends, old samples and future samples cannot influence rules.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void evidenceWindowsAndIndexSafety() throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
FindingDraft missing = StoredRule.MISSING_INDEX.evaluate(f.context(StoredRule.MISSING_INDEX)).getFirst();
|
||||
assertThat(missing.migrationSnippet()).contains("(status)").doesNotContain("(customer_id");
|
||||
assertThat(StoredRule.POOL_SIZING.evaluate(f.context(StoredRule.POOL_SIZING)).getFirst().recommendation()).contains("orders-app: peak 90");
|
||||
assertThat(StoredRule.OVER_AUDITED.evaluate(f.context(StoredRule.OVER_AUDITED)).getFirst().recommendation()).contains("shipOrders");
|
||||
QRecord unique = f.index("idx_unique", "status", true, 0);
|
||||
QRecord partial = f.index("idx_partial", "customer_id", false, 1);
|
||||
DbPerfStore.update("db_perf_index_stat", List.of(new QRecord().withValue("id", partial.getValueInteger("id"))
|
||||
.withValue("definition", "CREATE INDEX idx_partial ON public.audit_orders USING btree (customer_id) WHERE status = 1")));
|
||||
assertThat(StoredRule.DUPLICATE_INDEX.evaluate(f.context(StoredRule.DUPLICATE_INDEX)))
|
||||
.noneSatisfy(d -> assertThat(d.indexStatId()).isEqualTo(unique.getValueInteger("id")))
|
||||
.noneSatisfy(d -> assertThat(d.indexStatId()).isEqualTo(partial.getValueInteger("id")));
|
||||
assertThat(StoredRule.MISSING_INDEX.evaluate(f.context(StoredRule.MISSING_INDEX))).isEmpty();
|
||||
QRecord other = B1Fixture.insert("snapshot", new QRecord().withValue("backendName", "another").withValue("capturedAt", B1Fixture.NOW).withValue("status", "OK"));
|
||||
B1Fixture.insert("activity_sample", new QRecord().withValue("snapshotId", other.getValueInteger("id")).withValue("capturedAt", B1Fixture.NOW).withValue("reason", "IDLE_IN_TRANSACTION").withValue("xactSeconds", 99999).withValue("applicationName", "foreign"));
|
||||
assertThat(StoredRule.IDLE_IN_TRANSACTION.evaluate(f.context(StoredRule.IDLE_IN_TRANSACTION))).hasSize(1);
|
||||
f.settings.withValue("lastTableSnapshotAt", B1Fixture.NOW.plusSeconds(60));
|
||||
assertThat(StoredRule.BLOAT.evaluate(f.context(StoredRule.BLOAT))).isEmpty();
|
||||
assertThat(StoredRule.UNUSED_INDEX.evaluate(f.context(StoredRule.UNUSED_INDEX))).isEmpty();
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A later failed collection must not hide a recent statistics reset.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void resetAgeAndLongUnitIdentity() throws Exception
|
||||
{
|
||||
B1Fixture f = new B1Fixture();
|
||||
B1Fixture.insert("snapshot", new QRecord().withValue("backendName", f.backend).withValue("capturedAt", B1Fixture.NOW.minusSeconds(40 * 86400L)).withValue("status", "OK"));
|
||||
DbPerfStore.update("db_perf_snapshot", List.of(new QRecord().withValue("id", f.snapshot.getValueInteger("id")).withValue("rawStatsReset", B1Fixture.NOW.minusSeconds(86400))));
|
||||
B1Fixture.insert("snapshot", new QRecord().withValue("backendName", f.backend).withValue("capturedAt", B1Fixture.NOW.plusSeconds(1)).withValue("status", "FAILED"));
|
||||
assertThat(StoredRule.UNUSED_INDEX.evaluate(f.context(StoredRule.UNUSED_INDEX))).isEmpty();
|
||||
String unit = "job".repeat(100);
|
||||
B1Fixture.insert("attribution", new QRecord().withValue("windowStart", B1Fixture.NOW.minusSeconds(60)).withValue("windowEnd", B1Fixture.NOW)
|
||||
.withValue("unitKind", "SCHEDULED_JOB").withValue("unitName", unit).withValue("totalMs", 1000000));
|
||||
List<FindingDraft> drafts = StoredRule.HOT_JOB.evaluate(f.context(StoredRule.HOT_JOB));
|
||||
assertThat(drafts).allSatisfy(d ->
|
||||
{
|
||||
assertThat(d.dedupeKey().length()).isLessThanOrEqualTo(300);
|
||||
assertThat(d.title().length()).isLessThanOrEqualTo(300);
|
||||
});
|
||||
assertThat(new FindingService(f.runtime).apply(f.backend, "HOT_JOB", drafts).created()).isPositive();
|
||||
assertThat(StoredRule.POOL_SIZING.evaluate(f.context(StoredRule.POOL_SIZING)).getFirst().recommendation()).contains("orders-app: combined pool cap 36");
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Reload lifecycle state after each transaction.
|
||||
*******************************************************************************/
|
||||
private static QRecord finding(QRecord row) throws Exception
|
||||
{
|
||||
return (DbPerfStore.first("db_perf_finding", DbPerfStore.eq("id", row.getValueInteger("id"))));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user