Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions deploy.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,52 @@ function Push-SentryApi {
}
}

function Push-SentryCheckIn {
param([string]$MonitorSlug, [string]$Status, [string]$CheckInId)
if ([string]::IsNullOrWhiteSpace($sentryAuthToken)) { return $null }
try {
$headers = @{ Authorization = "Bearer $sentryAuthToken"; 'Content-Type' = 'application/json' }
if ([string]::IsNullOrWhiteSpace($CheckInId)) {
# Initial check-in: POST with monitor_config for auto-creation
$url = "https://sentry.io/api/0/organizations/$sentryOrg/monitors/$MonitorSlug/checkins/"
$body = @{
status = $Status
monitor_config = @{
schedule = @{ type = 'interval'; value = 1; unit = 'day' }
checkin_margin = 5
max_runtime = 10
timezone = 'UTC'
}
}
$json = $body | ConvertTo-Json -Compress -Depth 5
$resp = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $json -ErrorAction Stop
$newId = $resp.id
Push-LokiEvent 'deploy_sentry_checkin' 'INFO' "Sentry cron check-in started: $MonitorSlug" @{
monitor_slug = $MonitorSlug
status = $Status
checkin_id = $newId
}
return $newId
} else {
# Completion check-in: PUT to update existing
$url = "https://sentry.io/api/0/organizations/$sentryOrg/monitors/$MonitorSlug/checkins/$CheckInId/"
$body = @{ status = $Status }
$json = $body | ConvertTo-Json -Compress -Depth 5
Invoke-RestMethod -Uri $url -Method Put -Headers $headers -Body $json -ErrorAction Stop | Out-Null
Push-LokiEvent 'deploy_sentry_checkin' 'INFO' "Sentry cron check-in completed: $MonitorSlug ($Status)" @{
monitor_slug = $MonitorSlug
status = $Status
checkin_id = $CheckInId
}
return $null
}
} catch {
# Non-fatal: deploy must not fail because Sentry API is down
Write-Warning "Sentry CheckIn ($MonitorSlug/$Status): $($_.Exception.Message)"
return $null
}
}

if (-not [string]::IsNullOrWhiteSpace($sentryAuthToken) -and -not [string]::IsNullOrWhiteSpace($sentryRelease)) {
Write-Host "Registering Sentry release: $sentryRelease (3 projects)"

Expand Down Expand Up @@ -509,6 +555,7 @@ if (-not $skipTests) {
script_count = $testScripts.Count
scripts = ($testScripts | ForEach-Object { $_.Name }) -join ','
}
$checkInId = Push-SentryCheckIn 'post-deploy-tests' 'in_progress'
foreach ($ts in $testScripts) {
Write-Host " Running: $($ts.Name)"
$tsStart = Get-Date
Expand Down Expand Up @@ -557,6 +604,8 @@ if (-not $skipTests) {
}
}
}
$checkInStatus = if ($postDeployFailed) { 'error' } else { 'ok' }
Push-SentryCheckIn 'post-deploy-tests' $checkInStatus $checkInId | Out-Null
if ($postDeployFailed) {
Write-Warning "Post-deploy tests failed. Deploy files are in place but integration is not fully verified."
} else {
Expand Down
5 changes: 4 additions & 1 deletion src/SimSteward.Dashboard/data-capture-suite.html
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,15 @@
.filter-clear { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 0.82rem; padding: 0 4px; margin-left: -28px; margin-right: 4px; transition: color 0.15s; z-index: 1; }
.filter-clear:hover { color: var(--text); }
</style>
<script src="https://browser.sentry-cdn.com/9.27.0/bundle.min.js" crossorigin="anonymous"></script>
<script src="https://browser.sentry-cdn.com/9.27.0/bundle.tracing.replay.min.js" crossorigin="anonymous"></script>
<script>
Sentry.init({
dsn: 'https://46ad94b35a51979ba4223eeffe38f5c8@o4511097126780928.ingest.us.sentry.io/4511103122210816',
environment: 'local',
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [Sentry.replayIntegration()],
});
Sentry.setTag('dashboard', 'data-capture-suite');
</script>
Expand Down
5 changes: 4 additions & 1 deletion src/SimSteward.Dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -475,13 +475,16 @@
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
</style>
<script src="https://browser.sentry-cdn.com/9.27.0/bundle.min.js" crossorigin="anonymous"></script>
<script src="https://browser.sentry-cdn.com/9.27.0/bundle.tracing.replay.min.js" crossorigin="anonymous"></script>
<script>
Sentry.init({
dsn: 'https://46ad94b35a51979ba4223eeffe38f5c8@o4511097126780928.ingest.us.sentry.io/4511103122210816',
environment: 'local',
release: '',
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [Sentry.replayIntegration()],
initialScope: { tags: { dashboard: 'index' } },
});
</script>
Expand Down
12 changes: 12 additions & 0 deletions src/SimSteward.Dashboard/replay-incident-index.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@
}
.toast.show { opacity: 1; }
</style>
<script src="https://browser.sentry-cdn.com/9.27.0/bundle.tracing.replay.min.js" crossorigin="anonymous"></script>
<script>
Sentry.init({
dsn: 'https://46ad94b35a51979ba4223eeffe38f5c8@o4511097126780928.ingest.us.sentry.io/4511103122210816',
environment: 'local',
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [Sentry.replayIntegration()],
initialScope: { tags: { dashboard: 'replay-incident-index' } },
});
</script>
</head>
<body>
<div class="wrap">
Expand Down
18 changes: 16 additions & 2 deletions src/SimSteward.Plugin/DashboardBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,22 @@ private void HandleMessage(IWebSocketConnection socket, string msg)
}

var correlationId = Guid.NewGuid().ToString("N").Substring(0, 8);
var (success, result, error) = _dispatchAction(action, arg ?? "", correlationId);
SendActionResult(socket, action, success, result, error);
var tx = SentrySdk.StartTransaction("ws.message", "handle");
tx.SetExtra("action", action);
tx.SetExtra("correlation_id", correlationId);
SentrySdk.ConfigureScope(scope => scope.Transaction = tx);
try
{
var (success, result, error) = _dispatchAction(action, arg ?? "", correlationId);
SendActionResult(socket, action, success, result, error);
tx.Finish(success ? SpanStatus.Ok : SpanStatus.InternalError);
}
catch (Exception ex)
{
SentrySdk.CaptureException(ex);
tx.Finish(SpanStatus.InternalError);
throw;
}
}

private void SendActionResult(IWebSocketConnection socket, string action, bool success, string result, string error)
Expand Down
35 changes: 35 additions & 0 deletions src/SimSteward.Plugin/SimStewardPlugin.DataCaptureSuite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ private enum PreflightStep
private int _suiteT8PollTicks;
private bool _suiteT8BuildWasRunning;

// Sentry performance tracing
private ITransactionTracer _sentryTx;
private ISpan _sentryCurrentSpan;

// ── Public entry points (called from DataUpdate / DispatchAction) ──────

private void TryStartDataCaptureSuite(string[] skipIds = null)
Expand Down Expand Up @@ -184,6 +188,13 @@ private void ProcessDataCaptureSuiteTick()
_suite60HzRecorder = null;
StopReplayIncidentIndexRecordModeLocked("suite_cancel");
EmitSuiteLifecycleEvent("sdk_capture_suite_cancelled", "Suite cancelled.", "T_cancel");

// Sentry: finish spans/transaction as cancelled
_sentryCurrentSpan?.Finish(SpanStatus.Cancelled);
_sentryCurrentSpan = null;
_sentryTx?.Finish(SpanStatus.Cancelled);
_sentryTx = null;

_suitePhase = DataCaptureSuitePhase.Cancelled;
}
return;
Expand Down Expand Up @@ -683,6 +694,12 @@ private void BeginDataCaptureSuite()
_suiteStep = SuiteInternalStep.T0_Rewind;
_suitePhase = DataCaptureSuitePhase.Running;

// Sentry performance transaction for the entire suite run
_sentryTx = SentrySdk.StartTransaction("data-capture-suite", "test.run");
_sentryTx.SetExtra("test_run_id", _suiteTestRunId);
SentrySdk.ConfigureScope(scope => scope.Transaction = _sentryTx);
_sentryCurrentSpan = _sentryTx.StartChild("step", SuiteInternalStep.T0_Rewind.ToString());

EmitSuiteLifecycleEvent(DataCaptureSuiteConstants.EventSuiteStarted,
$"Data capture suite started. test_run_id={_suiteTestRunId}", "T_start");
SentrySdk.AddBreadcrumb("Data capture suite started", "lifecycle",
Expand All @@ -694,6 +711,8 @@ private void BeginDataCaptureSuite()

private void TickSuiteRunning()
{
var stepBefore = _suiteStep;

switch (_suiteStep)
{
case SuiteInternalStep.T0_Rewind: TickT0_Rewind(); break;
Expand Down Expand Up @@ -724,6 +743,16 @@ private void TickSuiteRunning()
case SuiteInternalStep.Done: TransitionToLoki(); break;
}

// Sentry: finish previous span and start new one when step changes
if (_suiteStep != stepBefore && _sentryTx != null)
{
_sentryCurrentSpan?.Finish(SpanStatus.Ok);
if (_suiteStep != SuiteInternalStep.Done)
_sentryCurrentSpan = _sentryTx.StartChild("step", _suiteStep.ToString());
else
_sentryCurrentSpan = null;
}

// 60Hz recording: every tick while running
_suite60HzRecorder?.RecordTick(_irsdk);
}
Expand Down Expand Up @@ -1638,6 +1667,12 @@ private void TransitionToLoki()
_suiteEmitCompleteUtc = DateTime.UtcNow;
_suitePhase = DataCaptureSuitePhase.AwaitingLoki;

// Sentry: finish any remaining span and the transaction
_sentryCurrentSpan?.Finish(SpanStatus.Ok);
_sentryCurrentSpan = null;
_sentryTx?.Finish(SpanStatus.Ok);
_sentryTx = null;

var fields = BuildTestFields("T_done");
fields["loki_wait_ms"] = DataCaptureSuiteConstants.LokiVerifyDelayMs;
MergeSessionAndRoutingFields(fields);
Expand Down
25 changes: 25 additions & 0 deletions src/SimSteward.Plugin/SimStewardPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,16 @@ private System.Collections.Generic.Dictionary<string, object> BuildCaptureIncide
{
if (string.IsNullOrEmpty(action))
return (false, null, "missing_action");

// Sentry: create a child span under the current transaction (if any)
ISpan _actionSpan = null;
SentrySdk.ConfigureScope(scope =>
{
var parentTx = scope.Transaction;
if (parentTx != null)
_actionSpan = parentTx.StartChild("action", action ?? "unknown");
});

var dispatchFields = new System.Collections.Generic.Dictionary<string, object>
{
["action"] = action,
Expand All @@ -618,6 +628,9 @@ private System.Collections.Generic.Dictionary<string, object> BuildCaptureIncide
MergeSessionAndRoutingFields(dispatchFields);
_logger?.Structured("INFO", "simhub-plugin", "action_dispatched", action, dispatchFields, "action", null);

try
{

if (string.Equals(action, "replay_session", StringComparison.OrdinalIgnoreCase))
{
var dir = (arg ?? "").Trim().ToLowerInvariant();
Expand Down Expand Up @@ -943,6 +956,18 @@ private System.Collections.Generic.Dictionary<string, object> BuildCaptureIncide

LogActionResult(action, arg, correlationId, false, "not_supported");
return (false, null, "not_supported");

}
catch
{
_actionSpan?.Finish(SpanStatus.InternalError);
_actionSpan = null;
throw;
}
finally
{
_actionSpan?.Finish(SpanStatus.Ok);
}
}

private void OnLog(string level, string message, string source)
Expand Down
Loading