Skip to content

Audit collector/query permission + platform requirements; surface WHY tabs/columns don't populate #1591

Closed

Description

@erikdarlingdata

Problem

Many collectors and live queries depend on permissions (and platform features) that a monitoring login may not have — most notably VIEW DATABASE STATE on Azure SQL DB, which every sys.dm_os_* DMV requires. When a login lacks the permission, the affected query throws and the data silently doesn't populate. The user sees empty tabs/columns with no indication of why, and often can't tell "there's no data yet" from "your login can't read this."

This surfaced concretely in #1535: the connectivity probe (fixed) and the FinOps live inventory (fixed separately) both coupled SERVERPROPERTY('EngineEdition') to sys.dm_os_sys_info, so a permission-starved Azure login lost the whole result.

Scope of the audit

  1. Catalog every collector + live query's permission/platform requirements. For each: which DMV/catalog/feature it needs, what permission that requires (VIEW DATABASE STATE, VIEW SERVER STATE, VIEW DATABASE PERFORMANCE STATE, msdb access, ALTER ANY EVENT SESSION, etc.), and which platforms it applies to (on-prem / Azure SQL DB / MI / RDS).

    • Known instance to fold in: PerformanceMonitor.Collectors/ServerPropertiesCollector.cs selects SERVERPROPERTY('EngineEdition') in the same query as FROM sys.dm_os_sys_info (lines ~119/151/170). A login without VIEW DATABASE STATE fails to collect server properties at all (not just the hardware columns). Same class as the [QUESTION] About deadlocks, generic webhook #1535 display-query fix, but on the collection path (Lite + Darling).
  2. Surface WHY a tab/column is empty, not just log it. When a collector/query fails on a permission (or a platform doesn't support a feature), record a structured reason and show it in the affected UI — a per-tab/per-section note ("Requires VIEW DATABASE STATE on this database" / "Not available on Azure SQL DB"), so the operator can distinguish a permission gap from no-data-yet and knows exactly what to grant. Today the FinOps tabs only AppLogger.Error(...).

  3. Decouple permission-free facts from permission-gated ones where a single query mixes them (the [QUESTION] About deadlocks, generic webhook #1535 pattern) so a partial-permission login still gets what it's entitled to.

  4. Document the required grants per platform in the README (the immediate VIEW DATABASE STATE doc lands with the [QUESTION] About deadlocks, generic webhook #1535 FinOps fix; this audit generalizes it into a full matrix).

Value

Turns a class of silent "empty tab, no idea why" support issues into a self-service "grant X to see Y" experience, across Lite / Dashboard / Darling. Larger than any one fix — filing so it's tracked as its own initiative.

Activity

  1. erikdarlingdata commented on Jul 25, 2026

    @erikdarlingdata
    OwnerAuthor

    Audit note: I started the ServerPropertiesCollector decoupling this issue names, then backed out. It is not the ~15-line mechanical change it looks like, and the reason is worth recording before someone else picks it up.

    The defect is real and confirmed

    PerformanceMonitor.Collectors/ServerPropertiesCollector.cs mixes permission-free SERVERPROPERTY scalars with FROM sys.dm_os_sys_info AS osi (:151) in one SELECT with no TRY/CATCH. A login without VIEW DATABASE STATE loses the entire server_properties row, in both Lite and Darling — not just the hardware columns.

    The fix pattern does sit ten lines below, at :162-174, where the supplemental health query wraps its own dm_os_sys_info read so LPIM degrades to NULL instead of killing the batch.

    Why it is not mechanical

    Wrapping the DMV read means those six values can now be absent, and the consuming code is not built for that:

    • ServerPropertiesCollector.cs:40-42 declares int CpuCount, int HyperthreadRatio, long PhysicalMemoryMb — non-nullable.
    • The reader takes them unguarded: reader.GetInt32(6), GetInt32(7), GetInt64(8) at :307-309. GetInt32 on DBNull throws InvalidCastException — the same failure class as Full Dashboard (deprecated): Trace Flag Changes grid throws InvalidCastException now that the view returns rows #1660, just relocated.
    • Making them nullable ripples outward: the MCP JSON contract (DarlingMcpDataTools.cs:727-731) and viewer arithmetic (Darling/.../FinOpsTab.Loaders.cs:260, :275-276, :281, :311, which divide by and format PhysicalMemoryMb), plus the Lite equivalents and the stored column definitions.

    The decision it needs

    When hardware is unreadable, what gets stored?

    1. NULL — honest, but a nullability change through both apps' Row type, MCP output shape, UI arithmetic, and possibly the DuckDB/Postgres column definitions.
    2. Zero sentinel — no downstream change at all, and the viewer already guards PhysicalMemoryMb > 0, so the plumbing is arguably there. But it stores a number that is not true.
    3. Split rows — collect the permission-free properties always, hardware separately.

    Option 2 is by far the cheapest and option 1 is the most correct; that is a product call, not a cleanup. Pairing whichever wins with the existing HardwareUnavailableReason surfacing (already in Lite at LocalDataService.FinOps.ServerProperties.cs:181-183, and missing from the Darling viewer — a parity gap in its own right) is what would make this issue's "surface WHY" half real rather than reactive.

  2. added a commit that references this issue on Jul 26, 2026
  3. added 3 commits that reference this issue on Jul 26, 2026
  4. erikdarlingdata commented on Jul 28, 2026

    @erikdarlingdata
    OwnerAuthor

    Closing as delivered: the audit surfacing this issue asked for shipped across four PRs - #1663 (a missing DMV grant no longer loses the whole server_properties row), #1678 (the Darling viewer's Server Inventory explains missing hardware instead of showing blanks), #1679 (permission-denied collectors surfaced + the platform matrix documented), and #1732 (database_size_stats stops touching master on Azure SQL DB; error 300 explains itself). The one remaining slice is the ServerPropertiesCollector decoupling, and the comment above stands as the record of why it is not the mechanical change it looks like - whoever picks it up should start from that note, in a fresh focused issue.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions