Compare commits

...
Author SHA1 Message Date
james.maesandClaude Opus 5.5 63d4c9669f fix(attribution): one active attribution runtime per JVM
Statement listeners are JVM-wide. Starting attribution on another
instance now stops the previous runtime's listener and flusher, so
repeated server starts (tests, hot swaps) never pin old instances.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:11:08 -05:00
james.maes 1663f48073 Merge pull request #9 from QRun-IO/fix/table-icons
fix(nav): distinct icon per table; cuts 0.1.0-RC.3
2026-10-15 14:15:35 -05:00
james.maesandClaude Opus 5.5 0b517d7e16 fix(nav): a distinct icon per table and a storage app icon
Every db_perf_ table used the speed icon, which repeats in the nav and
clashes with web performance (kof22's nav icon uniqueness check).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:12:56 -05:00
james.maes 1bc250c1b8 Merge pull request #8 from QRun-IO/fix/widget-columns
fix(home): at most four columns per widget table; cuts 0.1.0-RC.2
2026-10-15 13:25:03 -05:00
4 changed files with 60 additions and 2 deletions

No files matched your search

+2
View File
@@ -5,6 +5,8 @@ All notable changes are documented here. This project follows Keep a Changelog a
## [Unreleased]
### Fixed
- Only one runtime attributes per JVM: starting attribution on a newer instance (a hot swap, or repeated server starts in tests) stops the previous one, so old instances are never pinned by a forgotten listener and flusher.
- Each table has its own icon (the app icon is now `storage`), so nav entries are distinguishable and do not repeat icons used elsewhere.
- Home tables keep at most four columns with plain headers (readable at every width): secondary facts such as calls and mean time, the session's query text, and a unit's kind and top table move into a second line of the first cell.
### Added
@@ -42,6 +42,12 @@ public class DbPerfRuntime implements QSupplementalInstanceMetaData
private final DbPerfQBitConfig config;
private AttributionFlusher attributionFlusher;
/////////////////////////////////////////////////////////////////////////////
// statement listeners are JVM-wide: only one runtime attributes at a time //
// (a newer instance, e.g. after a hot swap or in tests, replaces the old) //
/////////////////////////////////////////////////////////////////////////////
private static final AtomicReference<DbPerfRuntime> ACTIVE_ATTRIBUTION = new AtomicReference<>();
/*******************************************************************************
** Runtime bound to a single host instance.
@@ -149,6 +155,11 @@ public class DbPerfRuntime implements QSupplementalInstanceMetaData
{
if(attributionFlusher == null && Boolean.TRUE.equals(config.getAttributionEnabled()))
{
DbPerfRuntime previous = ACTIVE_ATTRIBUTION.getAndSet(this);
if(previous != null && previous != this)
{
previous.stopAttribution();
}
attributionFlusher = new AttributionFlusher(this);
attributionFlusher.start();
}
@@ -160,6 +171,7 @@ public class DbPerfRuntime implements QSupplementalInstanceMetaData
*******************************************************************************/
public synchronized void stopAttribution()
{
ACTIVE_ATTRIBUTION.compareAndSet(this, null);
if(attributionFlusher != null)
{
attributionFlusher.close();
@@ -23,6 +23,7 @@ package com.kingsrook.qbits.dbperformance.metadata;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.kingsrook.qbits.dbperformance.DbPerfPermissions;
import com.kingsrook.qbits.dbperformance.DbPerfQBitConfig;
import com.kingsrook.qbits.dbperformance.model.DbPerfActivitySample;
@@ -669,7 +670,7 @@ public final class DbPerfMetaData
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"))
QAppMetaData app = new QAppMetaData().withName(config.getAppName()).withLabel("Database Performance").withIcon(new QIcon("storage"))
.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")));
@@ -681,12 +682,36 @@ public final class DbPerfMetaData
}
/*******************************************************************************
** A distinct icon per table, so nav entries are told apart at a glance.
*******************************************************************************/
static final Map<String, String> TABLE_ICONS = Map.ofEntries(
Map.entry("db_perf_finding", "tips_and_updates"),
Map.entry("db_perf_statement", "query_stats"),
Map.entry("db_perf_table_stat", "grid_on"),
Map.entry("db_perf_index_stat", "bookmarks"),
Map.entry("db_perf_snapshot", "photo_camera"),
Map.entry("db_perf_settings", "settings_suggest"),
Map.entry("db_perf_rule", "playlist_add_check"),
Map.entry("db_perf_explain", "device_hub"),
Map.entry("db_perf_query_snapshot", "timeline"),
Map.entry("db_perf_query_daily", "calendar_month"),
Map.entry("db_perf_table_snapshot", "stacked_line_chart"),
Map.entry("db_perf_table_daily", "date_range"),
Map.entry("db_perf_activity_sample", "monitor_heart"),
Map.entry("db_perf_connection_sample", "lan"),
Map.entry("db_perf_attribution", "hub"),
Map.entry("db_perf_attribution_daily", "event_note"),
Map.entry("db_perf_repeat_pattern", "repeat"));
/*******************************************************************************
** 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"))
QTableMetaData table = new QTableMetaData().withName(name).withLabel(label).withIcon(new QIcon(TABLE_ICONS.getOrDefault(name, "table_rows")))
.withBackendName(config.getBackendName()).withBackendDetails(new RDBMSTableBackendDetails().withTableName(name))
.withPrimaryKeyField("id").withFieldsFromEntity(entity)
.withPermissionRules(DbPerfPermissions.rules(config.getPermissionBaseName() + ".view"));
@@ -569,6 +569,25 @@ class AttributionTest
}
/*******************************************************************************
** Only one runtime attributes per JVM: starting another stops the first, so
** repeated server starts (tests, hot swaps) never pile up listeners.
*******************************************************************************/
@Test
void oneActiveRuntime() throws Exception
{
StatementExecutionListeners.unregister(listener);
runtime.startAttribution();
DbPerfRuntime other = new DbPerfRuntime(runtime.getInstance(), runtime.getConfig());
other.startAttribution();
runtime.stopAttribution();
assertThat(StatementExecutionListeners.isActive()).isTrue();
other.stopAttribution();
assertThat(StatementExecutionListeners.isActive()).isFalse();
}
/*******************************************************************************
** Deliver a statement with exact counter values.
*******************************************************************************/