Skip to content

[BUG] report.trace_flag_changes fails with bit-to-nvarchar conversion error #1635

Closed

Description

@argpna

Component

SQL collection scripts

Performance Monitor Version

3.2.0

SQL Server Version

Microsoft SQL Server 2025 (RTM-CU7) (KB5096981) - 17.0.4065.4 (X64) Jul 8 2026 23:26:08 Copyright (C) 2025 Microsoft Corporation Enterprise Developer Edition (64-bit) on Linux (Ubuntu 24.04.4 LTS)

Windows Version

N/A - reproduced via Docker - the bug is independent of the client/host OS

Describe the Bug

The report.trace_flag_changes view contains a CASE expression that compares previous_status and status (both bit columns from config.trace_flags_history) against nvarchar literals:

WHEN rc.previous_status = N'OFF' AND rc.status = N'ON'  THEN ...
WHEN rc.previous_status = N'ON'  AND rc.status = N'OFF' THEN ...

SQL Server resolves the type mismatch by converting the string literal to bit. The error always names 'OFF' here likely because it's the first literal SQL Server attempts to fold

Conversion failed when converting the nvarchar value 'OFF' to data type bit.

The view filters to change_number > 1 (rows with a non-NULL previous_status), so the first INSERT into the history table never triggers the error. The view breaks the moment a second row exists for any trace flag - i.e. after any toggle is recorded.

Steps to Reproduce

docker pull mcr.microsoft.com/mssql/server:2022-latest

docker run -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD='HopeThisIsGoodEnough123!' \
  --name sqltest -p 1433:1433 -d mcr.microsoft.com/mssql/server:2022-latest

# brief pause for sql service to start up
sleep 15

Create a minimal history table in tempdb (not a #temp table - each docker exec sqlcmd -Q call is its own connection/session, so a session-scoped temp table wouldn't survive between steps) and insert two rows (initial + toggle):

docker exec -it sqltest /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U sa -P 'HopeThisIsGoodEnough123!' -C -Q \
  "CREATE TABLE tempdb.dbo.history (
     collection_time datetime2,
     trace_flag      int,
     status          bit,
     is_global       bit,
     is_session      bit
   );
   /* Initial capture: TF 1222 off */
   INSERT tempdb.dbo.history VALUES ('2026-07-24 00:00:00', 1222, 0, 1, 0);
   /* Second capture: TF 1222 toggled on */
   INSERT tempdb.dbo.history VALUES ('2026-07-24 01:00:00', 1222, 1, 1, 0);
   SELECT trace_flag, status, is_global, collection_time FROM tempdb.dbo.history;"

Expected: two rows, status 0 then 1.

Run the CASE expression and observe the error:

docker exec -it sqltest /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U sa -P 'HopeThisIsGoodEnough123!' -C -Q \
  "WITH ranked AS (
     SELECT
       status,
       is_global,
       is_session,
       trace_flag,
       previous_status =
         LAG(status, 1) OVER (PARTITION BY trace_flag, is_global ORDER BY collection_time),
       change_number   =
         ROW_NUMBER()  OVER (PARTITION BY trace_flag, is_global ORDER BY collection_time)
     FROM tempdb.dbo.history
   )
   SELECT
     trace_flag,
     change_description =
       CASE
         WHEN previous_status = N'OFF' AND status = N'ON'
         THEN N'Trace flag ' + CONVERT(nvarchar(10), trace_flag) + N' ENABLED'
         WHEN previous_status = N'ON' AND status = N'OFF'
         THEN N'Trace flag ' + CONVERT(nvarchar(10), trace_flag) + N' DISABLED'
         ELSE N'Status unchanged'
       END
   FROM ranked
   WHERE change_number > 1;"

Compare against the fixed expression (replace nvarchar literals with 0 and 1):

docker exec -it sqltest /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U sa -P 'HopeThisIsGoodEnough123!' -C -Q \
  "WITH ranked AS (
     SELECT
       status,
       is_global,
       is_session,
       trace_flag,
       previous_status =
         LAG(status, 1) OVER (PARTITION BY trace_flag, is_global ORDER BY collection_time),
       change_number   =
         ROW_NUMBER()  OVER (PARTITION BY trace_flag, is_global ORDER BY collection_time)
     FROM tempdb.dbo.history
   )
   SELECT
     trace_flag,
     change_description =
       CASE
         WHEN previous_status = 0 AND status = 1
         THEN N'Trace flag ' + CONVERT(nvarchar(10), trace_flag) + N' ENABLED'
         WHEN previous_status = 1 AND status = 0
         THEN N'Trace flag ' + CONVERT(nvarchar(10), trace_flag) + N' DISABLED'
         ELSE N'Status unchanged'
       END
   FROM ranked
   WHERE change_number > 1;"

Cleanup:

docker rm -f sqltest

Expected Behavior

The view should return one row describing the toggle:

trace_flag  change_description
----------- ---------------------------------
1222        Trace flag 1222 ENABLED

Actual Behavior

Msg 245, Level 16, State 1, Server ...
Conversion failed when converting the nvarchar value 'OFF' to data type bit.

Any query against report.trace_flag_changes fails outright once a second history row exists for any trace flag - i.e. after the first toggle is recorded.

Error Messages / Log Output

Msg 245, Level 16, State 1, Server ...
Conversion failed when converting the nvarchar value 'OFF' to data type bit.

Screenshots

NA

Additional Context

Fix: compare previous_status/status against 0/1 instead of N'OFF'/N'ON', matching the underlying bit type. Applied as a two-line change to the CASE expression in install/47_create_reporting_views.sql (report.trace_flag_changes view definition), idempotent via CREATE OR ALTER VIEW.

Activity

  1. argpna commented on Jul 24, 2026

    @argpna
    ContributorAuthor

    P.S. Hey Erik - sorry for the barrage of bug fix issues today, figured I owed you some context. I built a downstream companion repo that plugs the monitoring queries into Grafana, and chasing that down tripped a bunch of bugs over the past few weeks, so I'm finally getting around to posting them.

    Repo's here if you want to poke around: https://github.com/argpna/eriksperfmon (called it "erik's perfmon" - hope that's fine by you 😄 ).

    Screenshots of the Grafana dashboards are here: https://argpna.github.io/eriksperfmon-demo/ - covers both the main perfmon dashboards and the finops ones.

    It auto-discovers wherever the PerformanceMonitor database lives in an environment and wires up the Grafana MSSQL datasource for it, plus an installer/upgrader for PerformanceMonitor releases itself - all Ansible, scales to however many servers you throw at it. There's also a local Docker setup so anyone can click through a real Grafana experience without a real environment (synthetic workloads feed a few SQL containers so data actually flows).

    The Grafana dashboards aren't meant as a full replacement for the WPF dashboard - there's still a handful of things missing compared to it, all called out in the downstream repo's docs.

    ... BTW, I did find a few more issues building it out, too - I'll post those over the next few days.

  2. erikdarlingdata commented on Jul 24, 2026

    @erikdarlingdata
    Owner

    @argpna all good with me. I played around with Grafana a bit when I first started building this stuff, but didn't end up going that direction because not everyone is set up to use it immediately. Anyway, glad you're finding some of this stuff useful. Happy to get any bug fixes you find in.

  3. argpna commented on Jul 24, 2026

    @argpna
    ContributorAuthor

    Awesome, thanks!

  4. erikdarlingdata commented on Jul 25, 2026

    @erikdarlingdata
    Owner

    Fixed and merged to dev — thanks for the fix, @argpna.

    The view compared the bit-typed previous_status/status columns against N'OFF'/N'ON' literals. Bit outranks nvarchar in type precedence, so SQL Server converted the literal to bit and the view failed the moment a toggle row flowed through it. It now compares 0/1.

    Fix: PR #1637, merge 4f2b7ced. Ships in the next release.

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

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions