Skip to content

Feature/growth rates vlf count#608

Closed
ClaudioESSilva wants to merge 11 commits intoerikdarlingdata:devfrom
ClaudioESSilva:feature/GrowthRates-VLFCount
Closed

Feature/growth rates vlf count#608
ClaudioESSilva wants to merge 11 commits intoerikdarlingdata:devfrom
ClaudioESSilva:feature/GrowthRates-VLFCount

Conversation

@ClaudioESSilva
Copy link
Contributor

@ClaudioESSilva ClaudioESSilva commented Mar 17, 2026

What does this PR do?

Implements #567

Which component(s) does this affect?

  • Full Dashboard
  • Lite Dashboard
  • Lite Tests
  • SQL collection scripts
  • CLI Installer
  • GUI Installer
  • Documentation

How was this tested?

  1. Lite:
    i. Open the Lite version
    ii. Check if the columns get populated

  2. Dashboard
    i. Open InstallerGUI
    ii. Run Install (to upgrade)
    iii. Wait 1 minute for the collector
    iv. Open the Dashboard, go to FinOps -> Database Sizes grid and check the columns return data

Some screenshots

image image image

Checklist

  • I have read the contributing guide
  • My code builds with zero warnings (dotnet build -c Debug)
  • I have tested my changes against at least one SQL Server version
  • I have not introduced any hardcoded credentials or server names

Summary by CodeRabbit

  • New Features
    • Interactive filter buttons added to database-size table headers with popup-driven per-column filtering and visual indicators.
    • New "Auto Growth" and "VLF Count" columns in database size views with numeric formatting, right-alignment, and sorting support.
  • Chores
    • Collection, local store, schema and upgrade scripts extended to persist and surface growth-related fields (auto-growth type/value and VLF count).

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Adds auto-growth and VLF-count metrics end-to-end (collection SQL, DB schema, upgrade), surfaces them in data models and UI, and introduces per-column DataGrid filtering infrastructure and filter UI for the Database Sizes / FinOps views.

Changes

Cohort / File(s) Summary
Dashboard UI & filtering
Dashboard/Controls/FinOpsContent.xaml, Dashboard/Controls/FinOpsContent.xaml.cs
Reworked DataGrid headers to use a header template with filter buttons; added click handler to open a ColumnFilterPopup, introduced filter popup event handlers and wiring to a DataGridFilterManager for DatabaseSizes.
Filter manager implementation
Dashboard/Services/DataGridFilterManager.cs
New interface IDataGridFilterManager and generic DataGridFilterManager<T> implementing per-column filter state, UpdateData, SetFilter, ApplyFilters, and header-button style updates.
Server-side data model changes
Dashboard/Services/DatabaseService.FinOps.cs
Added FinOpsDatabaseSizeStats properties: IsPercentGrowth, GrowthPct, VlfCount and computed helpers GrowthDisplay, AutoGrowthSort, VlfCountDisplay, VlfCountSort; populated from reader.
Lite UI columns
Lite/Controls/FinOpsTab.xaml
Added GrowthDisplay and VlfCountDisplay DataGridTextColumns (sortable via new sort members); VLF column cells right-aligned.
Lite local data & mapping
Lite/Services/LocalDataService.FinOps.cs
Extended GetDatabaseSizeLatestAsync mapping: AutoGrowthMb, IsPercentGrowth, GrowthPct, VlfCount and added corresponding display/sort computed properties on the row type.
Remote collector & reader propagation
Lite/Services/RemoteCollectorService.DatabaseSize.cs
Extended collection SQL result shape and in-memory row to include IsPercentGrowth, GrowthPct, VlfCount; propagates them into DuckDB insert paths.
DuckDB schema & initializer
Lite/Database/Schema.cs, Lite/Database/DuckDbInitializer.cs
Added is_percent_growth, growth_pct, vlf_count columns to database_size_stats; bumped CurrentSchemaVersion 21→22 and added migration block for v22.
Install / collector SQL changes
install/02_create_tables.sql, install/06_ensure_collection_table.sql, install/52_collect_database_size_stats.sql
Added nullable columns is_percent_growth, growth_pct, vlf_count to create/ensure scripts and extended collection SQL to compute/populate growth_pct and vlf_count (uses sys.dm_db_log_info for logs).
Upgrade script & manifest
upgrades/2.2.0-to-2.3.0/03_add_growth_vlf_columns.sql, upgrades/2.2.0-to-2.3.0/upgrade.txt
New idempotent upgrade script to add the three columns when missing and added to upgrade manifest.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/growth rates vlf count' is related to the changeset but is vague and uses a branch-like format (with a slash) rather than a clear, descriptive summary of the main change. Rewrite the title to be more descriptive of the actual feature being added, for example: 'Add growth rate and VLF count metrics to database size statistics' or 'Display auto-growth and VLF count in FinOps views'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Dashboard/Controls/FinOpsContent.xaml (1)

786-937: ⚠️ Potential issue | 🟠 Major

Add accessible names for per-column filter buttons.

The textless filter buttons at lines 786-937 are icon-only controls that lack accessible names. The ColumnFilterButtonStyle (defined in Dashboard/Themes/LightTheme.xaml and DarkTheme.xaml around lines 379-410) does not inject AutomationProperties.Name or ToolTip properties, and the individual buttons do not define these properties either. Screen readers will announce these as generic buttons without context, making them ambiguous to users with assistive technologies.

Each button should have a column-specific accessible name (e.g., via AutomationProperties.Name="{Binding ColumnName, StringFormat='Filter {0} column'}" or by updating the style to derive accessible names from the adjacent TextBlock content).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dashboard/Controls/FinOpsContent.xaml` around lines 786 - 937, The filter
buttons are icon-only and lack accessible names; update them so screen readers
get a meaningful label: add an AutomationProperties.Name (or ToolTip) to each
Button that derives the column-specific text (e.g., "Filter Database column") by
binding to the Button.Tag or the adjacent TextBlock content, or modify the
ColumnFilterButtonStyle to set AutomationProperties.Name using TemplateBinding
to Tag (or a passed-in property) so all buttons (e.g., those with
Tag="DatabaseName", "FileTypeDesc", "TotalSizeMb", etc.) expose a
column-specific accessible name while keeping the existing Click handler
DatabaseSizesFilter_Click unchanged.
🧹 Nitpick comments (2)
Lite/Services/RemoteCollectorService.DatabaseSize.cs (1)

119-125: Compute vlf_count once per database instead of per row.

(SELECT COUNT(*) FROM sys.dm_db_log_info(...)) is currently in the row projection, so it can be repeated for each matching file row. Precomputing by database_id (CTE/APPLY) will reduce redundant DMV calls.

Also applies to: 178-184

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Lite/Services/RemoteCollectorService.DatabaseSize.cs` around lines 119 - 125,
The vlf_count is being computed per file row by calling (SELECT COUNT(*) FROM
sys.dm_db_log_info(mf.database_id)) inside the projection; change this to
compute vlf_count once per database_id (e.g., a CTE or CROSS APPLY that selects
database_id, COUNT(*) AS vlf_count FROM sys.dm_db_log_info(...) GROUP BY
database_id) and then join/apply that result into the main query so the
projection can reference the precomputed vlf_count; update the CASE for
vlf_count (and the duplicate occurrence around the other projection) to use the
joined vlf_count when mf.type = 1 and NULL otherwise.
Dashboard/Services/DatabaseService.FinOps.cs (1)

2364-2382: Keep the growth/VLF presentation rules in one place.

This block is now mirrored in Lite/Services/LocalDataService.FinOps.cs:2054-2072. Pulling the formatting/sort rules into a shared helper or value object would make it much harder for the Dashboard and Lite views to drift the next time these display semantics change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dashboard/Services/DatabaseService.FinOps.cs` around lines 2364 - 2382, The
presentation and sorting logic for growth and VLF (GrowthDisplay,
AutoGrowthSort, VlfCountDisplay, VlfCountSort) is duplicated in
Dashboard/Services/DatabaseService.FinOps.cs and
Lite/Services/LocalDataService.FinOps.cs; extract these rules into a single
shared helper/value object (e.g., FileGrowthDisplayHelper or
FileGrowthViewModel) that exposes the same computed properties (GrowthDisplay,
AutoGrowthSort, VlfCountDisplay, VlfCountSort) and reuse it from both
DatabaseService and LocalDataService to centralize formatting/sort semantics and
eliminate duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@install/52_collect_database_size_stats.sql`:
- Around line 213-218: The vlf_count scalar subquery calling
sys.dm_db_log_info(DB_ID()) can fail if the DMV is unavailable or the collector
lacks VIEW SERVER STATE / VIEW DATABASE PERFORMANCE STATE; replace the direct
scalar subquery with a guarded lookup that returns NULL on failure—for example,
move the DMV call into a TRY/CATCH or an outer APPLY that tests capability first
(e.g. check server version/permissions or wrap the COUNT(*) FROM
sys.dm_db_log_info(DB_ID()) in BEGIN TRY/END TRY ... BEGIN CATCH RETURN NULL),
and use that result for vlf_count when df.type = 1; apply the same guarded
pattern to the second occurrence mentioned for lines 324–329 so VLF collection
failures do not abort the whole database snapshot.

In `@Lite/Database/DuckDbInitializer.cs`:
- Around line 587-600: The migration block for v22 currently swallows ALTER
TABLE failures (inside the fromVersion < 22 branch) by catching Exception and
only logging via _logger.LogWarning, which can leave the schema and collectors
out of sync; update the DuckDbInitializer migration code so that failures from
ExecuteNonQueryAsync calls (the ALTER TABLE statements on database_size_stats)
are not suppressed — either remove the try/catch or rethrow the caught exception
after logging (replace _logger.LogWarning with a fatal action), ensuring the
exception bubbles out of the method that handles migrations so the process fails
fast and the schema version is not advanced incorrectly.

---

Outside diff comments:
In `@Dashboard/Controls/FinOpsContent.xaml`:
- Around line 786-937: The filter buttons are icon-only and lack accessible
names; update them so screen readers get a meaningful label: add an
AutomationProperties.Name (or ToolTip) to each Button that derives the
column-specific text (e.g., "Filter Database column") by binding to the
Button.Tag or the adjacent TextBlock content, or modify the
ColumnFilterButtonStyle to set AutomationProperties.Name using TemplateBinding
to Tag (or a passed-in property) so all buttons (e.g., those with
Tag="DatabaseName", "FileTypeDesc", "TotalSizeMb", etc.) expose a
column-specific accessible name while keeping the existing Click handler
DatabaseSizesFilter_Click unchanged.

---

Nitpick comments:
In `@Dashboard/Services/DatabaseService.FinOps.cs`:
- Around line 2364-2382: The presentation and sorting logic for growth and VLF
(GrowthDisplay, AutoGrowthSort, VlfCountDisplay, VlfCountSort) is duplicated in
Dashboard/Services/DatabaseService.FinOps.cs and
Lite/Services/LocalDataService.FinOps.cs; extract these rules into a single
shared helper/value object (e.g., FileGrowthDisplayHelper or
FileGrowthViewModel) that exposes the same computed properties (GrowthDisplay,
AutoGrowthSort, VlfCountDisplay, VlfCountSort) and reuse it from both
DatabaseService and LocalDataService to centralize formatting/sort semantics and
eliminate duplication.

In `@Lite/Services/RemoteCollectorService.DatabaseSize.cs`:
- Around line 119-125: The vlf_count is being computed per file row by calling
(SELECT COUNT(*) FROM sys.dm_db_log_info(mf.database_id)) inside the projection;
change this to compute vlf_count once per database_id (e.g., a CTE or CROSS
APPLY that selects database_id, COUNT(*) AS vlf_count FROM
sys.dm_db_log_info(...) GROUP BY database_id) and then join/apply that result
into the main query so the projection can reference the precomputed vlf_count;
update the CASE for vlf_count (and the duplicate occurrence around the other
projection) to use the joined vlf_count when mf.type = 1 and NULL otherwise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e99e9d8f-f72f-412e-949a-b2642e88c7f2

📥 Commits

Reviewing files that changed from the base of the PR and between f1f3fe9 and 28afa2f.

📒 Files selected for processing (12)
  • Dashboard/Controls/FinOpsContent.xaml
  • Dashboard/Controls/FinOpsContent.xaml.cs
  • Dashboard/Services/DatabaseService.FinOps.cs
  • Lite/Controls/FinOpsTab.xaml
  • Lite/Database/DuckDbInitializer.cs
  • Lite/Database/Schema.cs
  • Lite/Services/LocalDataService.FinOps.cs
  • Lite/Services/RemoteCollectorService.DatabaseSize.cs
  • install/02_create_tables.sql
  • install/06_ensure_collection_table.sql
  • install/52_collect_database_size_stats.sql
  • upgrades/2.2.0-to-2.3.0/03_add_growth_vlf_columns.sql

Update Lite/Database/DuckDbInitializer.cs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Owner

@erikdarlingdata erikdarlingdata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work overall — the feature itself is solid. Two blockers before merge:

1. upgrade.txt missing the new script

upgrades/2.2.0-to-2.3.0/upgrade.txt doesn't list 03_add_growth_vlf_columns.sql:

01_widen_query_stats_columns.sql
02_widen_audit_columns.sql

Without it, the upgrade script won't run for existing installations. They'll never get the new columns. Add 03_add_growth_vlf_columns.sql to the file.

2. ALTER TABLE blocks in install script 52_collect_database_size_stats.sql

The IF NOT EXISTS + ALTER TABLE block (lines 641-678) should be removed from the install script. Schema changes for existing installations go exclusively in the upgrades/ folder — the install scripts only need the columns in the 02_create_tables.sql CREATE TABLE block (which you've already done correctly).

The install scripts run on every install/upgrade after the upgrade scripts, so by the time 52_ executes, the upgrade script will have already added the columns. Having ALTER TABLE in both places creates a maintenance burden and goes against the project convention.

Not a blocker but worth noting

The Dashboard filter implementation hand-rolls the popup/state/button-style logic (~100 lines) instead of using DataGridFilterManager<T> which already handles all of that. The existing pattern is in ServerTab.xaml.cs lines 3841-3942. Works fine as-is but adds duplicate code to maintain.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
install/52_collect_database_size_stats.sql (1)

167-172: ⚠️ Potential issue | 🟠 Major

Guard sys.dm_db_log_info() so VLF lookup failures don’t drop per-database rows.

At Line 170 and Line 281, the direct DMV call can fail (permissions/version), which causes the insert to fail for that database iteration. Please wrap this lookup in a local TRY/CATCH and fall back to NULL for vlf_count.

🛠️ Suggested pattern
+DECLARE `@vlf_count` int = NULL;
+BEGIN TRY
+    SELECT `@vlf_count` = COUNT(*) FROM sys.dm_db_log_info(DB_ID());
+END TRY
+BEGIN CATCH
+    SET `@vlf_count` = NULL;
+END CATCH;
...
- vlf_count = CASE WHEN df.type = 1 THEN (SELECT COUNT(*) FROM sys.dm_db_log_info(DB_ID())) ELSE NULL END
+ vlf_count = CASE WHEN df.type = 1 THEN `@vlf_count` ELSE NULL END
What SQL Server versions and permissions are required for sys.dm_db_log_info(), and what fallback pattern is recommended when permission is missing?

As per coding guidelines, **/*.sql: “Watch for: ... missing error handling (TRY/CATCH) ...”.

Also applies to: 278-283

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@install/52_collect_database_size_stats.sql` around lines 167 - 172, The
vlf_count calculation using sys.dm_db_log_info(DB_ID()) can error and abort the
INSERT; change the inline CASE to call a small guarded block that sets vlf_count
via a local TRY/CATCH: when df.type = 1, BEGIN TRY SELECT `@vlf_count` = (SELECT
COUNT(*) FROM sys.dm_db_log_info(DB_ID())); END TRY BEGIN CATCH SET `@vlf_count` =
NULL; END CATCH, otherwise set `@vlf_count` = NULL; then use that `@vlf_count` in
place of the direct DMV call in the CASE expression (reference vlf_count,
df.type, and sys.dm_db_log_info).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Dashboard/Controls/FinOpsContent.xaml.cs`:
- Around line 683-687: The Escape key handler in ColumnFilterPopup.xaml.cs
currently only raises FilterCleared leaving the active filter unchanged; update
that handler so it mirrors ClearButton_Click by raising both FilterCleared and
FilterApplied with an empty filter state. Specifically, in the Escape/KeyDown
handler for ColumnFilterPopup, construct the same empty filter object used by
ClearButton_Click and invoke both the FilterCleared and FilterApplied events
(the same event signatures used by ColumnFilterPopup.ClearButton_Click) so that
FilterPopup_DbSizeFilterApplied receives the empty filter and
_dbSizesFilterMgr.SetFilter(...) is called.
- Around line 78-79: The _dbSizesFilterMgr field can be null when events fire
before OnLoaded, so either initialize it in the constructor or add defensive
null-checks where it is used: create the
DataGridFilterManager<FinOpsDatabaseSizeStats> instance (same call as in
OnLoaded: new
DataGridFilterManager<FinOpsDatabaseSizeStats>(DatabaseSizesDataGrid)) in the
class constructor to eliminate the event-ordering race, or in
LoadDatabaseSizesAsync and the filter popup handlers (the methods
ServerSelector_SelectionChanged → RefreshDataAsync → LoadDatabaseSizesAsync and
the popup handler methods) check _dbSizesFilterMgr for null and return/skip
filter logic if null instead of using the null-forgiving operator.

---

Duplicate comments:
In `@install/52_collect_database_size_stats.sql`:
- Around line 167-172: The vlf_count calculation using
sys.dm_db_log_info(DB_ID()) can error and abort the INSERT; change the inline
CASE to call a small guarded block that sets vlf_count via a local TRY/CATCH:
when df.type = 1, BEGIN TRY SELECT `@vlf_count` = (SELECT COUNT(*) FROM
sys.dm_db_log_info(DB_ID())); END TRY BEGIN CATCH SET `@vlf_count` = NULL; END
CATCH, otherwise set `@vlf_count` = NULL; then use that `@vlf_count` in place of the
direct DMV call in the CASE expression (reference vlf_count, df.type, and
sys.dm_db_log_info).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 36f3ff71-d698-4074-b7d6-5aaf369e990d

📥 Commits

Reviewing files that changed from the base of the PR and between 68aba96 and 2aced6b.

📒 Files selected for processing (4)
  • Dashboard/Controls/FinOpsContent.xaml.cs
  • Dashboard/Services/DataGridFilterManager.cs
  • install/52_collect_database_size_stats.sql
  • upgrades/2.2.0-to-2.3.0/upgrade.txt
✅ Files skipped from review due to trivial changes (1)
  • upgrades/2.2.0-to-2.3.0/upgrade.txt

@ClaudioESSilva
Copy link
Contributor Author

All addressed. Thanks

erikdarlingdata added a commit that referenced this pull request Mar 18, 2026
Resolves XAML conflict by taking dev's FinOpsContent.xaml and adding the
two new Database Sizes columns (Auto Growth, VLF Count) from ClaudioESSilva's
feature/GrowthRates-VLFCount branch. All other files auto-merged cleanly.

Changes from PR #608:
- Dashboard + Lite: new GrowthDisplay and VlfCountDisplay columns on Database Sizes grid
- SQL collector: collect file growth settings and VLF counts
- Upgrade script: 2.2.0-to-2.3.0/03_add_growth_vlf_columns.sql
- Dashboard: DataGridFilterManager for Database Sizes column filtering
- DuckDB schema: new growth/VLF columns in database_size_stats

Co-Authored-By: Cláudio Silva <ClaudioESSilva@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@erikdarlingdata
Copy link
Owner

@ClaudioESSilva The merge conflict on this one is pretty rugged, and in testing I'm hitting some other issues. I've carried your changes over to #625 to deal with the conflict and address. Thanks for this, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants