From 17c7dd7bc9e200a95bfbad4c160cc9adbedf459e Mon Sep 17 00:00:00 2001 From: Salman Alfarisyi Date: Wed, 29 Jul 2026 11:43:03 +0200 Subject: [PATCH 1/6] fix CSharp live dashboard --- projects/csharp/LiveDashboard/Program.cs | 167 +++++++++++++++-------- 1 file changed, 107 insertions(+), 60 deletions(-) diff --git a/projects/csharp/LiveDashboard/Program.cs b/projects/csharp/LiveDashboard/Program.cs index 6acd91b..30dd47b 100644 --- a/projects/csharp/LiveDashboard/Program.cs +++ b/projects/csharp/LiveDashboard/Program.cs @@ -1,9 +1,12 @@ +using LiveDashboard; + using MESL.SqlRace.Domain; -using MESL.SqlRace.Domain.Infrastructure.Enumerators; +using MESL.SqlRace.Domain.Infrastructure.DataPipeline; using MESL.SqlRace.Domain.Query; + using Microsoft.Extensions.Configuration; + using SqlRace.Examples.Core; -using LiveDashboard; // ─── Configuration ─────────────────────────────────────────── var configuration = new ConfigurationBuilder() @@ -48,7 +51,6 @@ var sessionManager = SessionManager.CreateSessionManager(); IClientSession? clientSession = null; Session? session = null; -dynamic? loadKey = null; // Store the key for reloading if (!string.IsNullOrEmpty(configuredSessionId)) { @@ -57,13 +59,16 @@ { Console.WriteLine($"Session '{configuredSessionId}' not found. Available:"); foreach (var s in sessions.OrderByDescending(s => s.TimeOfRecording).Take(5)) + { Console.WriteLine($" {s.Identifier} ({s.Key})"); + } + return; } - clientSession = sessionManager.Load(match.Key, connectionString); + var sessionConnectionString = match.GetConnectionString(); + clientSession = sessionManager.Load(match.Key, sessionConnectionString); session = clientSession.Session; - loadKey = match.Key; Console.WriteLine($"Loading configured session: {match.Identifier} ({match.Key})"); } else @@ -71,40 +76,45 @@ // Try sessions newest-first, skip any with no sample data foreach (var candidate in sessions.OrderByDescending(s => s.TimeOfRecording)) { - var tempClient = sessionManager.Load(candidate.Key, connectionString); + var sessionConnectionString = candidate.GetConnectionString(); + var tempClient = sessionManager.Load(candidate.Key, sessionConnectionString); var tempSession = tempClient.Session; // Check if the first configured parameter has any data var testParam = paramIds.FirstOrDefault(id => tempSession.Parameters.Any(p => p.Identifier == id)); - if (testParam != null) + if (testParam == null) { - using var testPda = tempSession.CreateParameterDataAccess(testParam); - testPda.GoTo(long.MaxValue); - var testSamples = testPda.GetNextSamples(1, StepDirection.Reverse); - - if (testSamples.SampleCount > 0) - { - clientSession = tempClient; - session = tempSession; - loadKey = candidate.Key; - Console.WriteLine($"Loading session: {candidate.Identifier} ({candidate.Key})"); - break; - } + tempClient.Close(); + Console.WriteLine($"Skipping session {candidate.Identifier} (test parameter not found)"); + continue; } - tempClient.Dispose(); - Console.WriteLine($"Skipping session {candidate.Identifier} (no sample data)"); - } + using var testPda = tempSession.CreateParameterDataAccess(testParam); + var sampleCount = testPda.GetSamplesCount(tempSession.StartTime, tempSession.EndTime); - if (session == null) - { - Console.WriteLine("No sessions with sample data found."); - return; + if (sampleCount == 0) + { + Console.WriteLine($"Skipping session {candidate.Identifier} (no sample data)"); + tempClient.Close(); + continue; + } + + clientSession = tempClient; + session = tempSession; + Console.WriteLine($"Loading session: {candidate.Identifier} ({candidate.Key})"); + break; } } +if (clientSession == null || + session == null) +{ + Console.WriteLine("No sessions with sample data found."); + return; +} + // ─── Resolve available parameters ──────────────────────────── var availableParams = paramIds .Where(id => session.Parameters.Any(p => p.Identifier == id)) @@ -121,61 +131,98 @@ var renderer = new DashboardRenderer(pollingMs); var sessionId = session.Identifier; +var lastEndTime = long.MinValue; +// Create a list of PDAs for the available parameters. +var parameterPdas = new List(availableParams.Count); +foreach (var param in availableParams) +{ + var pda = session.CreateParameterDataAccess(param); + parameterPdas.Add(pda); +} + while (!cts.Token.IsCancellationRequested) { - // Reload session each cycle to pick up newly flushed data - clientSession?.Dispose(); - clientSession = sessionManager.Load(loadKey!, connectionString); - session = clientSession.Session; + try + { + await Task.Delay(pollingMs, cts.Token); + } + catch (OperationCanceledException) + { + break; + } var stats = new List(); - - foreach (var paramId in availableParams) + if (lastEndTime >= session.EndTime) { - // Create a fresh PDA each cycle so we always see the latest live data - using var pda = session.CreateParameterDataAccess(paramId); - pda.GoTo(long.MaxValue); - var samples = pda.GetNextSamples(windowSize, StepDirection.Reverse); + // -- No updates to the session, skip this polling cycle -- + continue; + } + + lastEndTime = session.EndTime; - if (samples.SampleCount == 0) + foreach (var pda in parameterPdas) + { + // Query the PDA for new samples + var pdaEndTime = pda.GetEndTime(); + if (pdaEndTime == null) { - stats.Add(new DashboardRenderer.ParameterStats( - paramId, double.NaN, double.NaN, double.NaN, double.NaN, 0)); + // No samples are available for the PDA. + stats.Add( + new DashboardRenderer.ParameterStats( + pda.ParameterIdentifier, + double.NaN, + double.NaN, + double.NaN, + double.NaN, + 0)); continue; } - var latest = samples.Data[0]; - var min = double.MaxValue; - var max = double.MinValue; - var sum = 0.0; + var maxSampleIntervalNs = (long)(1_000_000_000 / pda.GetMaximumFrequency()); + var duration = maxSampleIntervalNs * windowSize; + var startTime = pdaEndTime.Value - duration; + // Grab specifically the end, Max, Min, and Average values for the parameter over the window. + var dataStatistics = pda.GetDataStatistics( + startTime, + duration, + statisticOption: StatisticOption.End | StatisticOption.Max | StatisticOption.Min | StatisticOption.Mean); - for (var j = 0; j < samples.SampleCount; j++) + if (dataStatistics.NumberOfSamples == 0) { - var v = samples.Data[j]; - if (v < min) min = v; - if (v > max) max = v; - sum += v; + // No samples are available for the PDA in the specified window. + stats.Add( + new DashboardRenderer.ParameterStats( + pda.ParameterIdentifier, + double.NaN, + double.NaN, + double.NaN, + double.NaN, + 0)); + continue; } - stats.Add(new DashboardRenderer.ParameterStats( - paramId, latest, min, max, sum / samples.SampleCount, samples.SampleCount)); + var latest = dataStatistics.EndValue; + var min = dataStatistics.MinimumValue; + var max = dataStatistics.MaximumValue; + + stats.Add( + new DashboardRenderer.ParameterStats( + pda.ParameterIdentifier, + latest, + min, + max, + dataStatistics.MeanValue, + dataStatistics.NumberOfSamples)); } renderer.Render( sessionId, session.EndTime > session.StartTime ? "Data" : "Empty", stats); - - try - { - await Task.Delay(pollingMs, cts.Token); - } - catch (OperationCanceledException) - { - break; - } } Console.SetCursorPosition(0, Console.CursorTop + 1); Console.WriteLine("Dashboard stopped."); -clientSession?.Dispose(); +// Dispose all PDAs once we are done. +parameterPdas.ForEach(x => x.Dispose()); +clientSession.Close(); \ No newline at end of file From 13dc97ee8ab9ea62152189ebd711ca26423c60dc Mon Sep 17 00:00:00 2001 From: Salman Alfarisyi Date: Fri, 31 Jul 2026 09:55:50 +0200 Subject: [PATCH 2/6] update python example --- notebooks/06-live-data-monitoring.ipynb | 10 +++++++--- notebooks/sqlrace_helpers.py | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/notebooks/06-live-data-monitoring.ipynb b/notebooks/06-live-data-monitoring.ipynb index 02f98b3..7a8984e 100644 --- a/notebooks/06-live-data-monitoring.ipynb +++ b/notebooks/06-live-data-monitoring.ipynb @@ -78,6 +78,8 @@ "history = {p: [] for p in monitor_params}\n", "timestamps = []\n", "\n", + "pdas = [session.CreateParameterDataAccess(p) for p in monitor_params]\n", + "\n", "print(\"Starting live polling...\")\n", "print(f\"Polling every {POLL_INTERVAL}s for up to {MAX_POLLS} iterations.\")\n", "print(\"Interrupt the kernel to stop early.\\n\")\n", @@ -117,7 +119,9 @@ "except KeyboardInterrupt:\n", " print(\"\\nPolling stopped by user.\")\n", "\n", - "print(f\"\\nCollected {len(timestamps)} samples.\")" + "print(f\"\\nCollected {len(timestamps)} samples.\")\n", + "for p in pdas:\n", + " p.Dispose()" ] }, { @@ -167,13 +171,13 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": ".venv (3.13.9.final.0)", "language": "python", "name": "python3" }, "language_info": { "name": "python", - "version": "3.11.0" + "version": "3.13.9" } }, "nbformat": 4, diff --git a/notebooks/sqlrace_helpers.py b/notebooks/sqlrace_helpers.py index 09b8024..feaa85d 100644 --- a/notebooks/sqlrace_helpers.py +++ b/notebooks/sqlrace_helpers.py @@ -111,7 +111,8 @@ def load_session(session_manager, session_guid, connection_string=None): key = SessionKey.Parse(session_guid) cs = connection_string or get_connection_string() - client_session = session_manager.Load(key, cs) + session_summary = session_manager.FindSummaryBy(key, cs) + client_session = session_manager.Load(session_summary.key, session_summary.GetConnectionString()) return client_session, client_session.Session From c0a7612cccc64e10a1e30546b7381d82eb7e35ad Mon Sep 17 00:00:00 2001 From: Federica Donald Date: Fri, 16 Oct 2026 15:03:03 +0000 Subject: [PATCH 3/6] Fix poll-live-samples reading data minutes behind live (ADO 91065) The snippet loaded with SessionSummary.GetConnectionString(), which only returns the Server Listener connection string when the recorder registered a listener for the session. For a LiveNotInServer session it silently falls back to the database, whose copy trails the leading edge by the recorder's flush delay (MaximumTimeInMemoryBeforeFlushingInSeconds, 300 s by default), and whose EndTime is fixed at load. Reproduced locally: the first poll returned data 341 s behind EndTime, then EndTime never moved again. Require State == Live and a SqlRaceServer connection string, and fail with a pointer to docs/server-listener.md otherwise; this matches the API's own guidance that LiveNotInServer sessions should not be loaded. Drop the extra database load used only to validate the parameter, and print how far the latest sample is behind the session end. Correct the server-listener.md troubleshooting row, which told users to reload the session every poll and cited this snippet for it. --- docs/server-listener.md | 2 +- .../csharp/live-data/poll-live-samples.cs | 64 ++++++++++--------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/docs/server-listener.md b/docs/server-listener.md index 0707114..d53caf9 100644 --- a/docs/server-listener.md +++ b/docs/server-listener.md @@ -120,7 +120,7 @@ This queries the database, loads recent sessions, and monitors for EndTime growt | Client can't connect | Firewall blocking the port | Open the Server Listener port (TCP+UDP) on both machines | | `FindLiveSession` says "no live sessions" | No active recording, or data hasn't flushed yet | Start a recorder on the ADS; use `--wait 90` for slow flush intervals | | `CreateParameterDataAccess` throws | Session loaded before parameters were flushed | Wait for the first data flush (~60–70s), then reload the session | -| Data values don't change between polls | Cached session data from initial load | Reload the session (`SessionManager.Load()`) each poll cycle — see `poll-live-samples.cs` | +| Data values don't change between polls, or arrive ~5 minutes behind the session end | Session loaded from the database instead of the Server Listener (state **LiveNotInServer**) | Enable the Server Listener on the recorder and load the session with the connection string from `SessionSummary.GetConnectionString()`; check `State == Live` first. See `poll-live-samples.cs` | ## Related diff --git a/snippets/csharp/live-data/poll-live-samples.cs b/snippets/csharp/live-data/poll-live-samples.cs index 2826cc1..889a893 100644 --- a/snippets/csharp/live-data/poll-live-samples.cs +++ b/snippets/csharp/live-data/poll-live-samples.cs @@ -3,8 +3,10 @@ // ───────────────────────────────────────────────────────────── // // Demonstrates: Polling a live session's latest samples with async delay and cancellation -// Prerequisites: MESL.SQLRace.API NuGet package, .NET 8, a live session -// Input: A live session key and parameter identifier +// Prerequisites: MESL.SQLRace.API NuGet package, .NET 8, a live session recorded with the +// Server Listener enabled (see docs/server-listener.md) +// Input: A live session key, the connection string of the database it is recording to, +// and a parameter identifier // Output: Latest sample values printed at each poll interval // // Related: snippets/csharp/live-data/subscribe-to-lap-events.cs @@ -32,58 +34,62 @@ var paramId = args.Length > 2 ? args[2] : "Temperature:Sensors"; var sessionManager = SessionManager.CreateSessionManager(); +var sessionSummary = sessionManager.FindSummaryBy(sessionKey, connectionString); -using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); - -Console.WriteLine($"Polling {paramId} every 2s (30s timeout)...\n"); - -// --- Verify the parameter exists before polling --- +// --- Live data only comes from the recorder's Server Listener --- +// For a Live session GetConnectionString() returns the listener's "SqlRaceServer:..." string. +// A LiveNotInServer session has no listener: its database copy trails the leading edge by +// the recorder's flush delay (5 minutes by default), so it never gives live data. +var liveConnectionString = sessionSummary?.State == SessionState.Live ? sessionSummary.GetConnectionString() : null; +if (liveConnectionString is null || !liveConnectionString.StartsWith("SqlRaceServer", StringComparison.OrdinalIgnoreCase)) { - using var initClient = sessionManager.Load(sessionKey, connectionString); - if (!initClient.Session.Parameters.Any(p => p.Identifier == paramId)) - { - Console.WriteLine($"ERROR: Parameter '{paramId}' not found in session."); - Console.WriteLine("Available (first 20):"); - foreach (var p in initClient.Session.Parameters.Take(20)) - Console.WriteLine($" {p.Identifier}"); - return; - } + Console.WriteLine($"ERROR: Session is {sessionSummary?.State.ToString() ?? "not found"}, not Live with a Server Listener."); + Console.WriteLine("Enable the Server Listener on the recorder (docs/server-listener.md) and record a new session."); + return; } -var sessionSummary = sessionManager.FindSummaryBy(sessionKey, connectionString); +using var clientSession = sessionManager.Load(sessionSummary.Key, liveConnectionString); +var session = clientSession.Session; -var pollCount = 0; +if (!session.Parameters.Any(p => p.Identifier == paramId)) +{ + Console.WriteLine($"ERROR: Parameter '{paramId}' not found in session."); + Console.WriteLine("Available (first 20):"); + foreach (var p in session.Parameters.Take(20)) + Console.WriteLine($" {p.Identifier}"); + return; +} -// --- Load the session with server listener so we can pick up live data --- -using var clientSession = sessionManager.Load(sessionSummary.Key, sessionSummary.GetConnectionString()); -var session = clientSession.Session; +using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); +Console.WriteLine($"Polling {paramId} every 2s (30s timeout)...\n"); using var pda = session.CreateParameterDataAccess(paramId); +var pollCount = 0; var lastEndTime = long.MinValue; -while (!cts.Token.IsCancellationRequested) +while (!cts.Token.IsCancellationRequested && session.State == SessionState.Live) { try { await Task.Delay(2000, cts.Token); } catch (OperationCanceledException) { break; } - - // --- Check if the end time is updated. --- + + // --- EndTime is kept up to date by the Server Listener while the session is live --- var endTime = session.EndTime; if (lastEndTime >= endTime) { - // --- End time has not been changed. No new data. --- - Console.WriteLine($" Poll {++pollCount}: no data yet"); + Console.WriteLine($" Poll {++pollCount}: no new data"); continue; } - // --- End time has changed. Update lastEndTime and fetch the latest samples. --- lastEndTime = endTime; - pda.GoTo(lastEndTime); + pda.GoTo(endTime); var samples = pda.GetNextSamples(5, StepDirection.Reverse); if (samples.SampleCount > 0) { + // Reverse reads return newest first. var latest = samples.Data[0]; var latestTime = samples.Timestamp[0]; - Console.WriteLine($" Poll {++pollCount}: latest={latest:F2} at {latestTime} ns ({samples.SampleCount} recent)"); + var behindMs = (endTime - latestTime) / 1_000_000.0; + Console.WriteLine($" Poll {++pollCount}: latest={latest:F2} at {latestTime} ns, {behindMs:F0} ms behind session end"); } else { From bfa503a1cecca100b37da56b14f123f59d614aaa Mon Sep 17 00:00:00 2001 From: Federica Donald Date: Fri, 16 Oct 2026 15:03:03 +0000 Subject: [PATCH 4/6] Fix live notebook helper and polling loop - load_session used SessionSummary.key; pythonnet is case-sensitive, so it raised AttributeError. Use .Key, raise when the session is not found, warn on LiveNotInServer, and stop shadowing the session_summary() helper. - The polling loop created its own PDA per parameter per poll, leaving the new pdas list unused. Reuse the pre-created PDAs. - samples.Data(i) is not callable from pythonnet; index the array instead. - Revert the kernelspec/language_info metadata to the repo's defaults. --- notebooks/06-live-data-monitoring.ipynb | 24 ++++++++++-------------- notebooks/sqlrace_helpers.py | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/notebooks/06-live-data-monitoring.ipynb b/notebooks/06-live-data-monitoring.ipynb index 7a8984e..d57f158 100644 --- a/notebooks/06-live-data-monitoring.ipynb +++ b/notebooks/06-live-data-monitoring.ipynb @@ -92,18 +92,14 @@ " start_time = max(session.StartTime, end_time - window_ns)\n", "\n", " latest = {}\n", - " for param_id in monitor_params:\n", - " pda = session.CreateParameterDataAccess(param_id)\n", - " try:\n", - " samples = pda.GetSamplesBetween(start_time, end_time)\n", - " if samples.SampleCount > 0:\n", - " val = float(samples.Data(samples.SampleCount - 1))\n", - " latest[param_id] = val\n", - " history[param_id].append(val)\n", - " else:\n", - " latest[param_id] = float('nan')\n", - " finally:\n", - " pda.Dispose()\n", + " for param_id, pda in zip(monitor_params, pdas):\n", + " samples = pda.GetSamplesBetween(start_time, end_time)\n", + " if samples.SampleCount > 0:\n", + " val = float(samples.Data[samples.SampleCount - 1])\n", + " latest[param_id] = val\n", + " history[param_id].append(val)\n", + " else:\n", + " latest[param_id] = float('nan')\n", "\n", " timestamps.append(poll * POLL_INTERVAL)\n", "\n", @@ -171,13 +167,13 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv (3.13.9.final.0)", + "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", - "version": "3.13.9" + "version": "3.11.0" } }, "nbformat": 4, diff --git a/notebooks/sqlrace_helpers.py b/notebooks/sqlrace_helpers.py index feaa85d..37697f7 100644 --- a/notebooks/sqlrace_helpers.py +++ b/notebooks/sqlrace_helpers.py @@ -111,8 +111,18 @@ def load_session(session_manager, session_guid, connection_string=None): key = SessionKey.Parse(session_guid) cs = connection_string or get_connection_string() - session_summary = session_manager.FindSummaryBy(key, cs) - client_session = session_manager.Load(session_summary.key, session_summary.GetConnectionString()) + summary = session_manager.FindSummaryBy(key, cs) + if summary is None: + raise LookupError(f"Session {session_guid} not found in {cs}") + + # For a Live session this is the recorder's Server Listener connection string. + # A LiveNotInServer session has no listener, so its database copy trails the + # leading edge by the recorder's flush delay (5 minutes by default). + if str(summary.State) == "LiveNotInServer": + print("WARNING: Session is LiveNotInServer - enable the recorder's Server Listener " + "for live data (docs/server-listener.md).") + + client_session = session_manager.Load(summary.Key, summary.GetConnectionString()) return client_session, client_session.Session From cf1d1fe1d718cfeffa37211b752ab6cf84f5b70f Mon Sep 17 00:00:00 2001 From: Federica Donald Date: Fri, 16 Oct 2026 15:03:03 +0000 Subject: [PATCH 5/6] Skip LiveNotInServer sessions in LiveDashboard GetConnectionString() falls back to the database for a session whose recorder has no Server Listener, where EndTime is fixed at load and data trails the leading edge by the flush delay, so the dashboard froze with no explanation. Refuse a configured LiveNotInServer session and skip it when scanning, pointing at docs/server-listener.md. --- projects/csharp/LiveDashboard/Program.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/projects/csharp/LiveDashboard/Program.cs b/projects/csharp/LiveDashboard/Program.cs index 30dd47b..001ac52 100644 --- a/projects/csharp/LiveDashboard/Program.cs +++ b/projects/csharp/LiveDashboard/Program.cs @@ -52,6 +52,12 @@ IClientSession? clientSession = null; Session? session = null; +// A LiveNotInServer session is still recording but has no Server Listener, so the only +// copy is the database's, which trails the leading edge by the recorder's flush delay +// (5 minutes by default). Its EndTime is also fixed at load, so the dashboard would freeze. +const string NoListenerHelp = + "Enable the Server Listener on the recorder for live data - see docs/server-listener.md."; + if (!string.IsNullOrEmpty(configuredSessionId)) { var match = sessions.FirstOrDefault(s => s.Identifier == configuredSessionId); @@ -66,6 +72,12 @@ return; } + if (match.State == SessionState.LiveNotInServer) + { + Console.WriteLine($"Session '{configuredSessionId}' is LiveNotInServer. {NoListenerHelp}"); + return; + } + var sessionConnectionString = match.GetConnectionString(); clientSession = sessionManager.Load(match.Key, sessionConnectionString); session = clientSession.Session; @@ -76,6 +88,12 @@ // Try sessions newest-first, skip any with no sample data foreach (var candidate in sessions.OrderByDescending(s => s.TimeOfRecording)) { + if (candidate.State == SessionState.LiveNotInServer) + { + Console.WriteLine($"Skipping session {candidate.Identifier} (LiveNotInServer). {NoListenerHelp}"); + continue; + } + var sessionConnectionString = candidate.GetConnectionString(); var tempClient = sessionManager.Load(candidate.Key, sessionConnectionString); var tempSession = tempClient.Session; From 0d40ac4451a57220e30a3bdd37fe4b6850d4f7e1 Mon Sep 17 00:00:00 2001 From: Federica Donald Date: Tue, 20 Oct 2026 10:18:54 +0000 Subject: [PATCH 6/6] Skip parameters whose PDA can't be created in the live notebook CreateParameterDataAccess can return None (composite sessions), so skip those parameters and drop them from monitor_params, keeping the plotted history columns aligned. --- notebooks/06-live-data-monitoring.ipynb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/notebooks/06-live-data-monitoring.ipynb b/notebooks/06-live-data-monitoring.ipynb index d57f158..2a154cf 100644 --- a/notebooks/06-live-data-monitoring.ipynb +++ b/notebooks/06-live-data-monitoring.ipynb @@ -75,11 +75,19 @@ "metadata": {}, "outputs": [], "source": [ + "# CreateParameterDataAccess can return None (e.g. for a composite session); skip those parameters.\n", + "pdas = {}\n", + "for param_id in monitor_params:\n", + " pda = session.CreateParameterDataAccess(param_id)\n", + " if pda is None:\n", + " print(f\"Skipping {param_id}: could not create a ParameterDataAccess.\")\n", + " else:\n", + " pdas[param_id] = pda\n", + "monitor_params = list(pdas)\n", + "\n", "history = {p: [] for p in monitor_params}\n", "timestamps = []\n", "\n", - "pdas = [session.CreateParameterDataAccess(p) for p in monitor_params]\n", - "\n", "print(\"Starting live polling...\")\n", "print(f\"Polling every {POLL_INTERVAL}s for up to {MAX_POLLS} iterations.\")\n", "print(\"Interrupt the kernel to stop early.\\n\")\n", @@ -92,7 +100,7 @@ " start_time = max(session.StartTime, end_time - window_ns)\n", "\n", " latest = {}\n", - " for param_id, pda in zip(monitor_params, pdas):\n", + " for param_id, pda in pdas.items():\n", " samples = pda.GetSamplesBetween(start_time, end_time)\n", " if samples.SampleCount > 0:\n", " val = float(samples.Data[samples.SampleCount - 1])\n", @@ -116,8 +124,8 @@ " print(\"\\nPolling stopped by user.\")\n", "\n", "print(f\"\\nCollected {len(timestamps)} samples.\")\n", - "for p in pdas:\n", - " p.Dispose()" + "for pda in pdas.values():\n", + " pda.Dispose()" ] }, {