railwatch 0.9.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AGENTS.md +37 -33
- data/CHANGELOG.md +50 -0
- data/README.md +51 -60
- data/config/routes.rb +0 -59
- data/docs/ai-and-mcp.md +9 -8
- data/docs/configuration.md +35 -66
- data/docs/faq.md +17 -25
- data/docs/getting-started.md +105 -117
- data/docs/replacing-nightwatch.md +8 -7
- data/docs/replacing-sentry.md +12 -16
- data/docs/security.md +14 -20
- data/docs/self-hosting.md +24 -22
- data/docs/testing.md +6 -14
- data/docs/troubleshooting.md +12 -46
- data/lib/generators/railwatch/install/install_generator.rb +12 -240
- data/lib/generators/railwatch/install/templates/initializer.rb.tt +10 -40
- data/lib/generators/railwatch/install/templates/post-deploy +0 -7
- data/lib/railwatch/authentication.rb +6 -5
- data/lib/railwatch/configuration.rb +21 -151
- data/lib/railwatch/engine.rb +9 -118
- data/lib/railwatch/patches.rb +0 -11
- data/lib/railwatch/reporter.rb +3 -19
- data/lib/railwatch/subscribers/process_info.rb +1 -2
- data/lib/railwatch/version.rb +1 -1
- data/lib/railwatch.rb +4 -62
- data/lib/tasks/railwatch_tasks.rake +3 -325
- data/llms.txt +23 -25
- metadata +3 -453
- data/app/channels/railwatch/environment_channel.rb +0 -62
- data/app/controllers/concerns/railwatch/telemetry_identity.rb +0 -25
- data/app/controllers/railwatch/alerts_controller.rb +0 -48
- data/app/controllers/railwatch/anomaly_rules_controller.rb +0 -31
- data/app/controllers/railwatch/attachments_controller.rb +0 -22
- data/app/controllers/railwatch/broadcasts_controller.rb +0 -15
- data/app/controllers/railwatch/cache_events_controller.rb +0 -30
- data/app/controllers/railwatch/commands_controller.rb +0 -16
- data/app/controllers/railwatch/comments_controller.rb +0 -11
- data/app/controllers/railwatch/dashboard_controller.rb +0 -70
- data/app/controllers/railwatch/deploys_controller.rb +0 -34
- data/app/controllers/railwatch/deprecations_controller.rb +0 -54
- data/app/controllers/railwatch/environment_scoped.rb +0 -81
- data/app/controllers/railwatch/exceptions_controller.rb +0 -52
- data/app/controllers/railwatch/executions_controller.rb +0 -13
- data/app/controllers/railwatch/issues_controller.rb +0 -258
- data/app/controllers/railwatch/jobs_controller.rb +0 -63
- data/app/controllers/railwatch/llm_calls_controller.rb +0 -124
- data/app/controllers/railwatch/logs_controller.rb +0 -40
- data/app/controllers/railwatch/mails_controller.rb +0 -19
- data/app/controllers/railwatch/notifications_controller.rb +0 -11
- data/app/controllers/railwatch/outgoing_requests_controller.rb +0 -19
- data/app/controllers/railwatch/overview_controller.rb +0 -38
- data/app/controllers/railwatch/people_controller.rb +0 -25
- data/app/controllers/railwatch/processes_controller.rb +0 -33
- data/app/controllers/railwatch/profiles_controller.rb +0 -65
- data/app/controllers/railwatch/queries_controller.rb +0 -59
- data/app/controllers/railwatch/releases_controller.rb +0 -75
- data/app/controllers/railwatch/requests_controller.rb +0 -37
- data/app/controllers/railwatch/saved_views_controller.rb +0 -53
- data/app/controllers/railwatch/scheduled_tasks_controller.rb +0 -45
- data/app/controllers/railwatch/spans_controller.rb +0 -47
- data/app/controllers/railwatch/storage_ops_controller.rb +0 -15
- data/app/controllers/railwatch/tenants_controller.rb +0 -44
- data/app/controllers/railwatch/thresholds_controller.rb +0 -47
- data/app/controllers/railwatch/traces_controller.rb +0 -72
- data/app/controllers/railwatch/transactions_controller.rb +0 -21
- data/app/controllers/railwatch/view_renders_controller.rb +0 -28
- data/app/controllers/railwatch/visits_controller.rb +0 -53
- data/app/frontend/components/app-content.tsx +0 -26
- data/app/frontend/components/app-logo-icon.tsx +0 -23
- data/app/frontend/components/app-logo.tsx +0 -14
- data/app/frontend/components/app-shell.tsx +0 -38
- data/app/frontend/components/app-sidebar-header.tsx +0 -54
- data/app/frontend/components/app-sidebar.tsx +0 -94
- data/app/frontend/components/app-wordmark.tsx +0 -29
- data/app/frontend/components/breadcrumbs.tsx +0 -46
- data/app/frontend/components/icon.tsx +0 -16
- data/app/frontend/components/input-error.tsx +0 -18
- data/app/frontend/components/nav-footer.tsx +0 -46
- data/app/frontend/components/nav-main.tsx +0 -41
- data/app/frontend/components/nav-user.tsx +0 -50
- data/app/frontend/components/railwatch/chart-hover.tsx +0 -34
- data/app/frontend/components/railwatch/chart-panel.tsx +0 -170
- data/app/frontend/components/railwatch/code.tsx +0 -28
- data/app/frontend/components/railwatch/command-palette.tsx +0 -132
- data/app/frontend/components/railwatch/copy-block.tsx +0 -52
- data/app/frontend/components/railwatch/copy-id.tsx +0 -49
- data/app/frontend/components/railwatch/cursor-load-more.tsx +0 -39
- data/app/frontend/components/railwatch/data-table.tsx +0 -195
- data/app/frontend/components/railwatch/digit-roll.tsx +0 -312
- data/app/frontend/components/railwatch/empty-state.tsx +0 -159
- data/app/frontend/components/railwatch/env-nav.tsx +0 -130
- data/app/frontend/components/railwatch/env-switcher.tsx +0 -145
- data/app/frontend/components/railwatch/filter-bar.tsx +0 -153
- data/app/frontend/components/railwatch/flamegraph.tsx +0 -324
- data/app/frontend/components/railwatch/frames.tsx +0 -295
- data/app/frontend/components/railwatch/json-viewer.tsx +0 -159
- data/app/frontend/components/railwatch/live-dot.tsx +0 -78
- data/app/frontend/components/railwatch/live-toggle.tsx +0 -76
- data/app/frontend/components/railwatch/metric-chart.tsx +0 -112
- data/app/frontend/components/railwatch/mobile-page-switcher.tsx +0 -89
- data/app/frontend/components/railwatch/onboarding.tsx +0 -91
- data/app/frontend/components/railwatch/origin-identity.tsx +0 -84
- data/app/frontend/components/railwatch/page-header.tsx +0 -42
- data/app/frontend/components/railwatch/percentile-picker.tsx +0 -18
- data/app/frontend/components/railwatch/relative-time.tsx +0 -18
- data/app/frontend/components/railwatch/saved-views.tsx +0 -232
- data/app/frontend/components/railwatch/segmented.tsx +0 -64
- data/app/frontend/components/railwatch/series-chart.tsx +0 -400
- data/app/frontend/components/railwatch/shortcuts-dialog.tsx +0 -89
- data/app/frontend/components/railwatch/signal-toast.tsx +0 -121
- data/app/frontend/components/railwatch/sort-header.tsx +0 -37
- data/app/frontend/components/railwatch/source-link.tsx +0 -57
- data/app/frontend/components/railwatch/sparkline-cell.tsx +0 -14
- data/app/frontend/components/railwatch/sql-block.tsx +0 -116
- data/app/frontend/components/railwatch/stat.tsx +0 -185
- data/app/frontend/components/railwatch/status-badge.tsx +0 -79
- data/app/frontend/components/railwatch/step-picker.tsx +0 -57
- data/app/frontend/components/railwatch/timeline.tsx +0 -405
- data/app/frontend/components/railwatch/utilisation-bar.tsx +0 -44
- data/app/frontend/components/railwatch/window-picker.tsx +0 -147
- data/app/frontend/components/ui/avatar.tsx +0 -51
- data/app/frontend/components/ui/badge.tsx +0 -46
- data/app/frontend/components/ui/breadcrumb.tsx +0 -109
- data/app/frontend/components/ui/button.tsx +0 -60
- data/app/frontend/components/ui/card.tsx +0 -92
- data/app/frontend/components/ui/chart.tsx +0 -374
- data/app/frontend/components/ui/checkbox.tsx +0 -32
- data/app/frontend/components/ui/collapsible.tsx +0 -31
- data/app/frontend/components/ui/command.tsx +0 -182
- data/app/frontend/components/ui/dialog.tsx +0 -158
- data/app/frontend/components/ui/dropdown-menu.tsx +0 -255
- data/app/frontend/components/ui/hover-card.tsx +0 -42
- data/app/frontend/components/ui/input.tsx +0 -21
- data/app/frontend/components/ui/label.tsx +0 -24
- data/app/frontend/components/ui/popover.tsx +0 -87
- data/app/frontend/components/ui/select.tsx +0 -185
- data/app/frontend/components/ui/separator.tsx +0 -28
- data/app/frontend/components/ui/sheet.tsx +0 -137
- data/app/frontend/components/ui/sidebar.tsx +0 -726
- data/app/frontend/components/ui/skeleton.tsx +0 -13
- data/app/frontend/components/ui/sonner.tsx +0 -41
- data/app/frontend/components/ui/table.tsx +0 -114
- data/app/frontend/components/ui/tabs.tsx +0 -91
- data/app/frontend/components/ui/textarea.tsx +0 -18
- data/app/frontend/components/ui/tooltip.tsx +0 -61
- data/app/frontend/components/user-info.tsx +0 -32
- data/app/frontend/components/user-menu-content.tsx +0 -94
- data/app/frontend/entrypoints/application.css +0 -244
- data/app/frontend/entrypoints/inertia.tsx +0 -92
- data/app/frontend/fonts/CommitMono-Bold.woff2 +0 -0
- data/app/frontend/fonts/CommitMono-Regular.woff2 +0 -0
- data/app/frontend/fonts/Roboto-Black.woff2 +0 -0
- data/app/frontend/fonts/Roboto-Bold.woff2 +0 -0
- data/app/frontend/fonts/Roboto-Medium.woff2 +0 -0
- data/app/frontend/fonts/Roboto-Regular.woff2 +0 -0
- data/app/frontend/hooks/use-appearance.tsx +0 -67
- data/app/frontend/hooks/use-clipboard.ts +0 -32
- data/app/frontend/hooks/use-flash.tsx +0 -23
- data/app/frontend/hooks/use-hotkeys.ts +0 -63
- data/app/frontend/hooks/use-initials.tsx +0 -15
- data/app/frontend/hooks/use-live.ts +0 -205
- data/app/frontend/hooks/use-mobile-navigation.ts +0 -8
- data/app/frontend/hooks/use-mobile.ts +0 -21
- data/app/frontend/hooks/use-percentile.ts +0 -24
- data/app/frontend/hooks/use-reduced-motion.ts +0 -25
- data/app/frontend/hooks/use-row-nav.ts +0 -106
- data/app/frontend/hooks/use-step.ts +0 -27
- data/app/frontend/hooks/use-ticker.ts +0 -12
- data/app/frontend/hooks/use-window.ts +0 -58
- data/app/frontend/layouts/app/app-sidebar-layout.tsx +0 -107
- data/app/frontend/layouts/app-layout.tsx +0 -21
- data/app/frontend/layouts/env-layout.tsx +0 -53
- data/app/frontend/layouts/persistent-layout.tsx +0 -18
- data/app/frontend/lib/cable.ts +0 -10
- data/app/frontend/lib/env-pages.ts +0 -236
- data/app/frontend/lib/execution-path.ts +0 -55
- data/app/frontend/lib/explain.ts +0 -50
- data/app/frontend/lib/filter.ts +0 -25
- data/app/frontend/lib/flamegraph.ts +0 -155
- data/app/frontend/lib/format.ts +0 -71
- data/app/frontend/lib/keyboard.ts +0 -18
- data/app/frontend/lib/mount.ts +0 -23
- data/app/frontend/lib/source-link.ts +0 -217
- data/app/frontend/lib/utils.ts +0 -6
- data/app/frontend/pages/alerts/index.tsx +0 -171
- data/app/frontend/pages/broadcasts/index.tsx +0 -171
- data/app/frontend/pages/cache_events/index.tsx +0 -127
- data/app/frontend/pages/commands/index.tsx +0 -143
- data/app/frontend/pages/deploys/index.tsx +0 -94
- data/app/frontend/pages/deploys/show.tsx +0 -309
- data/app/frontend/pages/deprecations/index.tsx +0 -176
- data/app/frontend/pages/exceptions/index.tsx +0 -271
- data/app/frontend/pages/executions/show.tsx +0 -821
- data/app/frontend/pages/issues/index.tsx +0 -399
- data/app/frontend/pages/issues/show.tsx +0 -1367
- data/app/frontend/pages/jobs/index.tsx +0 -381
- data/app/frontend/pages/jobs/klass.tsx +0 -215
- data/app/frontend/pages/llm_calls/index.tsx +0 -664
- data/app/frontend/pages/logs/index.tsx +0 -303
- data/app/frontend/pages/mails/index.tsx +0 -218
- data/app/frontend/pages/notifications/index.tsx +0 -161
- data/app/frontend/pages/outgoing_requests/index.tsx +0 -309
- data/app/frontend/pages/overview/show.tsx +0 -333
- data/app/frontend/pages/people/index.tsx +0 -102
- data/app/frontend/pages/people/show.tsx +0 -149
- data/app/frontend/pages/processes/index.tsx +0 -335
- data/app/frontend/pages/profiles/index.tsx +0 -110
- data/app/frontend/pages/profiles/show.tsx +0 -105
- data/app/frontend/pages/queries/index.tsx +0 -492
- data/app/frontend/pages/queries/show.tsx +0 -276
- data/app/frontend/pages/releases/index.tsx +0 -190
- data/app/frontend/pages/releases/release-health.tsx +0 -68
- data/app/frontend/pages/releases/release-path.ts +0 -25
- data/app/frontend/pages/releases/show.tsx +0 -262
- data/app/frontend/pages/requests/index.tsx +0 -235
- data/app/frontend/pages/requests/route.tsx +0 -159
- data/app/frontend/pages/scheduled_tasks/index.tsx +0 -220
- data/app/frontend/pages/spans/index.tsx +0 -229
- data/app/frontend/pages/spans/show.tsx +0 -220
- data/app/frontend/pages/storage_ops/index.tsx +0 -150
- data/app/frontend/pages/tenants/index.tsx +0 -236
- data/app/frontend/pages/tenants/show.tsx +0 -477
- data/app/frontend/pages/tenants/tenant-path.ts +0 -26
- data/app/frontend/pages/thresholds/index.tsx +0 -414
- data/app/frontend/pages/traces/show.tsx +0 -208
- data/app/frontend/pages/transactions/index.tsx +0 -220
- data/app/frontend/pages/view_renders/index.tsx +0 -241
- data/app/frontend/pages/visits/index.tsx +0 -321
- data/app/frontend/public/icon.png +0 -0
- data/app/frontend/public/icon.svg +0 -5
- data/app/frontend/routes/index.d.ts +0 -2136
- data/app/frontend/routes/index.js +0 -2112
- data/app/frontend/types/globals.d.ts +0 -9
- data/app/frontend/types/index.ts +0 -288
- data/app/frontend/types/vite-env.d.ts +0 -9
- data/app/helpers/railwatch/assets_helper.rb +0 -52
- data/app/jobs/railwatch/anomaly_scan_job.rb +0 -11
- data/app/jobs/railwatch/application_job.rb +0 -7
- data/app/jobs/railwatch/auto_resolve_issues_job.rb +0 -19
- data/app/jobs/railwatch/check_scheduled_tasks_job.rb +0 -113
- data/app/jobs/railwatch/detect_anomalies_job.rb +0 -171
- data/app/jobs/railwatch/detect_performance_issues_job.rb +0 -95
- data/app/jobs/railwatch/group_exceptions_job.rb +0 -91
- data/app/jobs/railwatch/optimize_telemetry_job.rb +0 -23
- data/app/jobs/railwatch/performance_scan_job.rb +0 -11
- data/app/jobs/railwatch/prune_telemetry_job.rb +0 -129
- data/app/jobs/railwatch/release_health_rollup_job.rb +0 -64
- data/app/jobs/railwatch/rollup_catchup_job.rb +0 -21
- data/app/jobs/railwatch/rollup_job.rb +0 -132
- data/app/jobs/railwatch/scheduled_task_scan_job.rb +0 -11
- data/app/models/concerns/railwatch/detection_snapshotting.rb +0 -20
- data/app/models/railwatch/alert.rb +0 -229
- data/app/models/railwatch/alert_rule.rb +0 -121
- data/app/models/railwatch/anomaly_rule.rb +0 -51
- data/app/models/railwatch/application.rb +0 -44
- data/app/models/railwatch/application_record.rb +0 -33
- data/app/models/railwatch/comment.rb +0 -36
- data/app/models/railwatch/deploy.rb +0 -67
- data/app/models/railwatch/environment.rb +0 -68
- data/app/models/railwatch/execution_presenter.rb +0 -185
- data/app/models/railwatch/filter_query.rb +0 -184
- data/app/models/railwatch/followup_receipt.rb +0 -27
- data/app/models/railwatch/ingest/batch.rb +0 -355
- data/app/models/railwatch/ingest/mapper.rb +0 -585
- data/app/models/railwatch/ingest/payload.rb +0 -96
- data/app/models/railwatch/ingest/rollup_absorber.rb +0 -170
- data/app/models/railwatch/ingest/writer.rb +0 -137
- data/app/models/railwatch/issue.rb +0 -277
- data/app/models/railwatch/issue_activity.rb +0 -23
- data/app/models/railwatch/issue_detection_presenter.rb +0 -309
- data/app/models/railwatch/issue_detection_snapshot.rb +0 -77
- data/app/models/railwatch/maintenance_task.rb +0 -53
- data/app/models/railwatch/saved_view.rb +0 -63
- data/app/models/railwatch/telemetry/aggregations.rb +0 -251
- data/app/models/railwatch/telemetry/attachment.rb +0 -31
- data/app/models/railwatch/telemetry/bounded_gzip.rb +0 -101
- data/app/models/railwatch/telemetry/broadcast.rb +0 -13
- data/app/models/railwatch/telemetry/cache_event.rb +0 -16
- data/app/models/railwatch/telemetry/child.rb +0 -53
- data/app/models/railwatch/telemetry/cursor_page.rb +0 -99
- data/app/models/railwatch/telemetry/deprecation.rb +0 -13
- data/app/models/railwatch/telemetry/enqueued_job.rb +0 -13
- data/app/models/railwatch/telemetry/exception.rb +0 -28
- data/app/models/railwatch/telemetry/execution.rb +0 -152
- data/app/models/railwatch/telemetry/export_delivery.rb +0 -32
- data/app/models/railwatch/telemetry/export_destination.rb +0 -66
- data/app/models/railwatch/telemetry/health_sample.rb +0 -72
- data/app/models/railwatch/telemetry/ingest_batch.rb +0 -31
- data/app/models/railwatch/telemetry/llm_call.rb +0 -37
- data/app/models/railwatch/telemetry/log.rb +0 -91
- data/app/models/railwatch/telemetry/mail.rb +0 -13
- data/app/models/railwatch/telemetry/n_plus_one.rb +0 -92
- data/app/models/railwatch/telemetry/notification.rb +0 -13
- data/app/models/railwatch/telemetry/outgoing_request.rb +0 -13
- data/app/models/railwatch/telemetry/person.rb +0 -50
- data/app/models/railwatch/telemetry/process.rb +0 -10
- data/app/models/railwatch/telemetry/profile.rb +0 -37
- data/app/models/railwatch/telemetry/query.rb +0 -57
- data/app/models/railwatch/telemetry/query_shape.rb +0 -45
- data/app/models/railwatch/telemetry/release_health.rb +0 -69
- data/app/models/railwatch/telemetry/rollup.rb +0 -157
- data/app/models/railwatch/telemetry/session.rb +0 -24
- data/app/models/railwatch/telemetry/span.rb +0 -19
- data/app/models/railwatch/telemetry/storage_op.rb +0 -13
- data/app/models/railwatch/telemetry/tenant.rb +0 -240
- data/app/models/railwatch/telemetry/transaction.rb +0 -13
- data/app/models/railwatch/telemetry/view_render.rb +0 -13
- data/app/models/railwatch/telemetry/visit.rb +0 -24
- data/app/models/railwatch/telemetry_record.rb +0 -107
- data/app/models/railwatch/threshold.rb +0 -66
- data/app/models/railwatch/user.rb +0 -47
- data/app/models/railwatch/viewer.rb +0 -13
- data/app/models/railwatch/window.rb +0 -66
- data/app/views/layouts/railwatch/dashboard.html.erb +0 -25
- data/db/railwatch_migrate/20260916000000_create_railwatch_tables.rb +0 -151
- data/db/railwatch_migrate/20260917000000_create_railwatch_maintenance_tasks.rb +0 -18
- data/db/railwatch_migrate/20260917120000_create_railwatch_followup_receipts.rb +0 -20
- data/db/railwatch_migrate/20260918120000_widen_host_user_ids.rb +0 -71
- data/db/railwatch_telemetry_migrate/20260903000000_enable_incremental_vacuum.rb +0 -62
- data/db/railwatch_telemetry_migrate/20260903000001_create_telemetry.rb +0 -481
- data/db/railwatch_telemetry_migrate/20260903000002_rename_tenant_to_app_tenant.rb +0 -14
- data/db/railwatch_telemetry_migrate/20260903000003_add_statement_count_to_transactions.rb +0 -9
- data/db/railwatch_telemetry_migrate/20260903000004_add_role_and_channel.rb +0 -10
- data/db/railwatch_telemetry_migrate/20260903000005_add_locals_to_exceptions.rb +0 -9
- data/db/railwatch_telemetry_migrate/20260903000006_add_spans_health_vitals_and_fts.rb +0 -82
- data/db/railwatch_telemetry_migrate/20260903000007_rename_span_attributes_to_payload.rb +0 -10
- data/db/railwatch_telemetry_migrate/20260903000008_add_profiles_and_attachments.rb +0 -60
- data/db/railwatch_telemetry_migrate/20260903000009_add_truncated_to_attachments.rb +0 -9
- data/db/railwatch_telemetry_migrate/20260903000010_create_sessions_and_release_health.rb +0 -51
- data/db/railwatch_telemetry_migrate/20260903000011_add_fingerprint_to_exceptions.rb +0 -11
- data/db/railwatch_telemetry_migrate/20260904000012_add_failed_to_broadcasts.rb +0 -10
- data/db/railwatch_telemetry_migrate/20260904010000_add_filter_cursor_indexes.rb +0 -37
- data/db/railwatch_telemetry_migrate/20260904020000_add_n_plus_ones_execution_id_index.rb +0 -11
- data/db/railwatch_telemetry_migrate/20260904120000_create_query_shapes.rb +0 -15
- data/db/railwatch_telemetry_migrate/20260906120000_add_backpressure_factor_to_ingest_batches.rb +0 -9
- data/db/railwatch_telemetry_migrate/20260907000000_rename_lantern_version_on_processes.rb +0 -10
- data/db/railwatch_telemetry_migrate/20260913000000_rename_nightrail_version_on_processes.rb +0 -9
- data/db/railwatch_telemetry_migrate/20260914000000_drop_orphan_durable_ingest_tables.rb +0 -18
- data/db/railwatch_telemetry_migrate/20260914010000_drop_orphan_durable_ingest_columns.rb +0 -25
- data/db/railwatch_telemetry_migrate/20260915000000_create_llm_calls.rb +0 -55
- data/db/railwatch_telemetry_migrate/20260915120000_add_detail_to_llm_calls.rb +0 -21
- data/db/railwatch_telemetry_migrate/20260915200000_add_explained_index_to_queries.rb +0 -16
- data/db/railwatch_telemetry_migrate/20260915210000_add_slowest_index_to_queries.rb +0 -18
- data/db/railwatch_telemetry_migrate/20260917010000_add_batch_ledger_to_ingest_batches.rb +0 -16
- data/db/railwatch_telemetry_migrate/20260919000100_create_export_queue.rb +0 -146
- data/db/railwatch_telemetry_migrate/20260922000000_add_covering_indexes_for_dashboard_aggregates.rb +0 -34
- data/db/railwatch_telemetry_migrate/20260923000000_add_people_index.rb +0 -15
- data/db/railwatch_telemetry_migrate/20260925000000_add_tenant_summary_index.rb +0 -17
- data/db/railwatch_telemetry_migrate/20261008000000_add_in_transaction_to_outgoing_requests.rb +0 -10
- data/docs/embedded.md +0 -503
- data/lib/puma/plugin/railwatch.rb +0 -215
- data/lib/railwatch/dashboard_assets.rb +0 -45
- data/lib/railwatch/embedded.rb +0 -50
- data/lib/railwatch/export/client.rb +0 -124
- data/lib/railwatch/export/lease.rb +0 -56
- data/lib/railwatch/export/outbox.rb +0 -361
- data/lib/railwatch/export/policy.rb +0 -79
- data/lib/railwatch/export/sender.rb +0 -130
- data/lib/railwatch/ingest_request_body_limit.rb +0 -10
- data/lib/railwatch/maintenance.rb +0 -194
- data/lib/railwatch/patches/migration_busy_timeout.rb +0 -73
- data/lib/railwatch/transport/local.rb +0 -78
- data/lib/railwatch/transport/socket.rb +0 -183
- data/lib/railwatch/writer.rb +0 -370
- data/public/railwatch/assets/CommitMono-Bold-D6h61ieg.woff2 +0 -0
- data/public/railwatch/assets/CommitMono-Regular-zr8w7Obm.woff2 +0 -0
- data/public/railwatch/assets/Roboto-Black-auA4GeOK.woff2 +0 -0
- data/public/railwatch/assets/Roboto-Bold-CJLnO8j1.woff2 +0 -0
- data/public/railwatch/assets/Roboto-Medium-Cm2bwKpj.woff2 +0 -0
- data/public/railwatch/assets/Roboto-Regular-Chaq1-PV.woff2 +0 -0
- data/public/railwatch/assets/app-layout-by7G3sa1.js +0 -41
- data/public/railwatch/assets/application-Cc5v_Vk9.css +0 -1
- data/public/railwatch/assets/arrow-up-DCZ7emux.js +0 -1
- data/public/railwatch/assets/braces-CykT5_5u.js +0 -1
- data/public/railwatch/assets/card-C-PPSA0g.js +0 -1
- data/public/railwatch/assets/chart-Cov79YC5.js +0 -39
- data/public/railwatch/assets/chart-hover-DdK4E-2h.js +0 -1
- data/public/railwatch/assets/chart-panel-BPaYRXJB.js +0 -1
- data/public/railwatch/assets/code-BYjC4YZ_.js +0 -1
- data/public/railwatch/assets/copy-id-LPfoj9cC.js +0 -1
- data/public/railwatch/assets/cursor-load-more-DSdhgpGl.js +0 -1
- data/public/railwatch/assets/data-table-CQ_YRWQs.js +0 -1
- data/public/railwatch/assets/env-layout-CdMWbUkm.js +0 -1
- data/public/railwatch/assets/execution-path-7MYt-Qbn.js +0 -1
- data/public/railwatch/assets/filter-bar-CrzpQ_vD.js +0 -1
- data/public/railwatch/assets/flamegraph-BIFEdTAg.js +0 -2
- data/public/railwatch/assets/format-B9SDkrWj.js +0 -1
- data/public/railwatch/assets/frames-C5t-x4f0.js +0 -1
- data/public/railwatch/assets/index--NQ7us-L.js +0 -1
- data/public/railwatch/assets/index-2kgYe_99.js +0 -1
- data/public/railwatch/assets/index-4YEPHxtO.js +0 -1
- data/public/railwatch/assets/index-AOCC1GDJ.js +0 -1
- data/public/railwatch/assets/index-BEo-u0eO.js +0 -1
- data/public/railwatch/assets/index-BJ1V3w2N.js +0 -1
- data/public/railwatch/assets/index-BPoHsBKb.js +0 -1
- data/public/railwatch/assets/index-BZzyGUM3.js +0 -1
- data/public/railwatch/assets/index-BgVq7yKL.js +0 -1
- data/public/railwatch/assets/index-BqFGEZvW.js +0 -1
- data/public/railwatch/assets/index-ByuHJ1CH.js +0 -1
- data/public/railwatch/assets/index-C2j50AL2.js +0 -1
- data/public/railwatch/assets/index-CLEdTkvW.js +0 -1
- data/public/railwatch/assets/index-CWgq8qBu.js +0 -1
- data/public/railwatch/assets/index-CXQ7_oVj.js +0 -2
- data/public/railwatch/assets/index-CmyYrjS2.js +0 -1
- data/public/railwatch/assets/index-Cn9_HjDo.js +0 -1
- data/public/railwatch/assets/index-CsMAm9UK.js +0 -1
- data/public/railwatch/assets/index-D0Kk_oGY.js +0 -1
- data/public/railwatch/assets/index-DC9ZptJh.js +0 -1
- data/public/railwatch/assets/index-DNzg7jmV.js +0 -1
- data/public/railwatch/assets/index-DQ8uJO5f.js +0 -1
- data/public/railwatch/assets/index-Deq14BxB.js +0 -1
- data/public/railwatch/assets/index-IaV3Blzp.js +0 -1
- data/public/railwatch/assets/index-TYNWcJQs.js +0 -1
- data/public/railwatch/assets/index-WonH2fcZ.js +0 -1
- data/public/railwatch/assets/index-WxWUaH_d.js +0 -1
- data/public/railwatch/assets/index-mUNrl4ky.js +0 -1
- data/public/railwatch/assets/inertia-DfNRARuA.js +0 -97
- data/public/railwatch/assets/json-viewer-DMsF5glp.js +0 -1
- data/public/railwatch/assets/klass-CmHYk5rz.js +0 -1
- data/public/railwatch/assets/origin-identity-1R3bqa3R.js +0 -1
- data/public/railwatch/assets/percentile-picker-73z5PAps.js +0 -1
- data/public/railwatch/assets/relative-time-Dsc_cCdX.js +0 -1
- data/public/railwatch/assets/release-health-BV5OaOWd.js +0 -1
- data/public/railwatch/assets/route-CWh58C7K.js +0 -1
- data/public/railwatch/assets/segmented-DB4zh-Am.js +0 -1
- data/public/railwatch/assets/select-DBKjFlOX.js +0 -1
- data/public/railwatch/assets/series-chart-BaiDG795.js +0 -1
- data/public/railwatch/assets/show-B4ICCFGh.js +0 -1
- data/public/railwatch/assets/show-B5lOU9Gv.js +0 -1
- data/public/railwatch/assets/show-BYPB74t4.js +0 -2
- data/public/railwatch/assets/show-C7A0m2M8.js +0 -1
- data/public/railwatch/assets/show-DT5X1zqE.js +0 -1
- data/public/railwatch/assets/show-D_NBdXsn.js +0 -2
- data/public/railwatch/assets/show-DgPCnRg8.js +0 -1
- data/public/railwatch/assets/show-Yl69HF0x.js +0 -1
- data/public/railwatch/assets/show-_QxuUQCm.js +0 -3
- data/public/railwatch/assets/show-g__jnN36.js +0 -1
- data/public/railwatch/assets/show-l4ztfjl9.js +0 -1
- data/public/railwatch/assets/sort-header-BcG8ulEH.js +0 -1
- data/public/railwatch/assets/source-link-B2183i2-.js +0 -1
- data/public/railwatch/assets/sparkline-cell-B47ipscB.js +0 -1
- data/public/railwatch/assets/stat-DT2hhsqS.js +0 -1
- data/public/railwatch/assets/status-badge-CmbRKlY7.js +0 -1
- data/public/railwatch/assets/tenant-path-C4rNGauE.js +0 -1
- data/public/railwatch/assets/use-clipboard-oTNeP4ax.js +0 -1
- data/public/railwatch/assets/use-live-CLZZj245.js +0 -1
- data/public/railwatch/icon.png +0 -0
- data/public/railwatch/icon.svg +0 -5
- data/public/railwatch/manifest.json +0 -1245
- data/public/railwatch/rails-vite.json +0 -1
- /data/{app/frontend/lib → lib/generators/railwatch/install/templates}/railwatch.ts +0 -0
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
class ProfilesController < DashboardController
|
|
5
|
-
# Profiles are never rolled up (they are opt-in and sampled, so a rollup
|
|
6
|
-
# row would be noise), which means every number on these pages comes from
|
|
7
|
-
# the raw table. This caps how many rows the grouping pass reads; the
|
|
8
|
-
# StatStrip totals still come from SQL aggregates over the whole window.
|
|
9
|
-
SCAN_LIMIT = 5_000
|
|
10
|
-
|
|
11
|
-
def index
|
|
12
|
-
from, to = window_range
|
|
13
|
-
rows, count, avg_samples, profiled, executions = telemetry do
|
|
14
|
-
scope = Telemetry::Profile.between(from, to)
|
|
15
|
-
[ scope.recent.limit(SCAN_LIMIT).select(:id, :group_hash, :execution_preview, :duration, :samples, :occurred_at).to_a,
|
|
16
|
-
scope.count, scope.average(:samples), scope.distinct.count(:execution_id), executions_in(from, to) ]
|
|
17
|
-
end
|
|
18
|
-
render inertia: { profiles: groups(rows),
|
|
19
|
-
summary: { profiles: count, executions: executions, profiled: profiled, avg_samples: avg_samples.to_f.round(0) } }
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def show
|
|
23
|
-
profile = telemetry { Telemetry::Profile.find(params[:id]) }
|
|
24
|
-
collapsed, truncated = profile.collapsed_capped
|
|
25
|
-
render inertia: { profile: profile_row(profile), collapsed: collapsed, truncated: truncated }
|
|
26
|
-
rescue Telemetry::BoundedGzip::Error
|
|
27
|
-
head :unprocessable_content
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
# How many executions the window holds, as the profiled share's
|
|
33
|
-
# denominator. Every kind is rolled up, so whole hours come from a few
|
|
34
|
-
# hundred rollup rows rather than counting every execution (1.2 s over a
|
|
35
|
-
# week of the platform's own). A rollup holds its whole hour, so the
|
|
36
|
-
# partial hours at either end of the window are counted from the rows.
|
|
37
|
-
def executions_in(from, to)
|
|
38
|
-
first = from.beginning_of_hour == from ? from : from.beginning_of_hour + 1.hour
|
|
39
|
-
last = to.beginning_of_hour
|
|
40
|
-
return Telemetry::Execution.between(from, to).count if first >= last
|
|
41
|
-
|
|
42
|
-
Telemetry::Execution.where(occurred_at: from...first).count +
|
|
43
|
-
Telemetry::Rollup.for_type(Telemetry::Execution::KINDS).where(bucket: first...last).sum(:count) +
|
|
44
|
-
Telemetry::Execution.where(occurred_at: last..to).count
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Rows arrive newest-first, so the head of each group is the profile the
|
|
48
|
-
# page links to.
|
|
49
|
-
def groups(rows)
|
|
50
|
-
rows.group_by(&:group_hash).map { |group_hash, group|
|
|
51
|
-
newest = group.first
|
|
52
|
-
{ group_hash: group_hash, name: newest.execution_preview, profile_id: newest.id, count: group.size,
|
|
53
|
-
avg_duration: (group.sum { |row| row.duration.to_i } / group.size.to_f / 1000.0).round(2),
|
|
54
|
-
max_samples: group.filter_map(&:samples).max, last_seen_at: newest.occurred_at }
|
|
55
|
-
}.sort_by { |g| -g[:count] }.first(100)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def profile_row(row)
|
|
59
|
-
{ id: row.id, profiler: row.profiler, mode: row.mode, interval: row.interval, duration: row.duration_ms.round(2),
|
|
60
|
-
samples: row.samples, stacks_bytes: row.stacks_bytes, group_hash: row.group_hash, execution_id: row.execution_id,
|
|
61
|
-
execution_preview: row.execution_preview, execution_source: row.execution_source, occurred_at: row.occurred_at,
|
|
62
|
-
deploy: row.deploy, server: row.server }
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
class QueriesController < DashboardController
|
|
5
|
-
def index
|
|
6
|
-
from, to = window_range
|
|
7
|
-
page_data = nil
|
|
8
|
-
page = lambda do
|
|
9
|
-
page_data ||= telemetry do
|
|
10
|
-
scope = FilterQuery.apply(Telemetry::Query.with_sql, resource: :queries, query: params[:q], from: from, to: to)
|
|
11
|
-
rows, meta = Telemetry::CursorPage.call(scope, cursor: params[:cursor], limit: params[:limit], order: :slowest,
|
|
12
|
-
context: telemetry_cursor_context(:queries))
|
|
13
|
-
[ rows.map { |q| query_row(q) }, meta ]
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
render inertia: {
|
|
17
|
-
queries: -> { grouped("query", limit: 200, order: params[:sort], dir: params[:dir]) },
|
|
18
|
-
n_plus_ones: -> { n_plus_ones(from, to) }, slowest: InertiaRails.merge { page.call.first },
|
|
19
|
-
pagination: -> { page.call.last }, q: params[:q].to_s, summary: -> { summary_with_delta("query") },
|
|
20
|
-
sort: params[:sort] || "count", dir: params[:dir] || "desc"
|
|
21
|
-
}
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def show
|
|
25
|
-
group_hash = params[:id]
|
|
26
|
-
rows = telemetry { Telemetry::Query.where(group_hash: group_hash).between(*window_range).recent.limit(100).with_sql.to_a }
|
|
27
|
-
summary = telemetry { Telemetry::Rollup.summarize(Telemetry::Rollup.for_type("query").where(group_hash: group_hash).between(*window_range)) }
|
|
28
|
-
sources = rows.filter_map(&:source).tally.sort_by { |_s, c| -c }.first(10)
|
|
29
|
-
callers = rows.map(&:execution_preview).compact.tally.sort_by { |_s, c| -c }.first(10)
|
|
30
|
-
render inertia: { group_hash: group_hash, sql: rows.first&.sql, summary: summary, series: series("query", group_hash: group_hash),
|
|
31
|
-
sources: sources, callers: callers, roles: rows.filter_map(&:role).tally.sort_by { |_r, c| -c },
|
|
32
|
-
explain: explain_sample(group_hash), samples: rows.first(50).map { |q| query_row(q) } }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def n_plus_ones(from, to)
|
|
38
|
-
telemetry do
|
|
39
|
-
Telemetry::NPlusOne.between(from, to).group(:group_hash).pluck(:group_hash, Arel.sql("COUNT(*)"), Arel.sql("MAX(sql)"), Arel.sql("MAX(source)"), Arel.sql("MAX(count)"))
|
|
40
|
-
.map { |g, c, sql, src, mx| { group_hash: g, occurrences: c, sql: sql, source: src, max_count: mx, suggestion: Telemetry::NPlusOne.new(sql: sql, source: src).suggestion } }
|
|
41
|
-
.sort_by { |r| -r[:occurrences] }.first(50)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# The newest sample in the group that the gem attached a query plan to.
|
|
46
|
-
# Only slow queries get one (RAILWATCH_CAPTURE_QUERY_EXPLAIN), so this is
|
|
47
|
-
# usually a different row than the newest sample overall.
|
|
48
|
-
def explain_sample(group_hash)
|
|
49
|
-
q = telemetry { Telemetry::Query.where(group_hash: group_hash).between(*window_range).where.not(explain: nil).recent.with_sql.first }
|
|
50
|
-
return nil if q.nil?
|
|
51
|
-
{ plan: q.explain, occurred_at: q.occurred_at, duration: q.duration_ms.round(3), execution_id: q.execution_id }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def query_row(q)
|
|
55
|
-
{ id: q.id, sql: q.sql.first(300), duration: q.duration_ms.round(3), occurred_at: q.occurred_at, execution_id: q.execution_id,
|
|
56
|
-
execution_preview: q.execution_preview, source: q.source, connection: q.connection, row_count: q.row_count, group_hash: q.group_hash }
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Sentry-style release health: sessions per deploy, and the crash-free rates
|
|
4
|
-
# read off them. The release is the deploy string the gem stamps on every
|
|
5
|
-
# record it ships, so `show`'s :id is that string rather than a Deploy id.
|
|
6
|
-
module Railwatch
|
|
7
|
-
class ReleasesController < DashboardController
|
|
8
|
-
SESSIONS_SHOWN = 50
|
|
9
|
-
RELEASES_SHOWN = 50
|
|
10
|
-
# SQLite reads the deploy back out of an issue's sample payload, which is
|
|
11
|
-
# the release its latest occurrence came from.
|
|
12
|
-
SAMPLE_DEPLOY = "json_extract(sample, '$.deploy') = ?"
|
|
13
|
-
|
|
14
|
-
def index
|
|
15
|
-
from, to = window_range
|
|
16
|
-
deploys = environment.deploys.between(from, to).recent.limit(RELEASES_SHOWN).to_a
|
|
17
|
-
new_issues = new_issue_counts(deploys, to)
|
|
18
|
-
summary, series, adoption, health = telemetry do
|
|
19
|
-
[ Telemetry::ReleaseHealth.for_deploy(nil, from, to),
|
|
20
|
-
Telemetry::ReleaseHealth.series(nil, from, to, step: step_key),
|
|
21
|
-
Telemetry::ReleaseHealth.adoption(from, to),
|
|
22
|
-
deploys.to_h { |d| [ d.deploy, Telemetry::ReleaseHealth.for_deploy(d.deploy, from, to) ] } ]
|
|
23
|
-
end
|
|
24
|
-
releases = deploys.map do |d|
|
|
25
|
-
health.fetch(d.deploy).merge(deploy: d.deploy, ref: d.short_ref, name: d.name, deployed_at: d.deployed_at,
|
|
26
|
-
adoption: adoption[d.deploy] || 0.0, new_issues: new_issues[d.id].to_i)
|
|
27
|
-
end
|
|
28
|
-
render inertia: { releases: releases, series: series,
|
|
29
|
-
summary: summary.merge(releases: adoption.count { |_deploy, share| share.positive? }) }
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def show
|
|
33
|
-
release = params[:id]
|
|
34
|
-
deploy = environment.deploys.find_by(deploy: release)
|
|
35
|
-
previous = deploy&.previous_deploy
|
|
36
|
-
from, to = deploy ? deploy.window : window_range
|
|
37
|
-
data = telemetry do
|
|
38
|
-
{ summary: Telemetry::ReleaseHealth.for_deploy(release, from, to),
|
|
39
|
-
previous_summary: previous && Telemetry::ReleaseHealth.for_deploy(previous.deploy, *previous.window),
|
|
40
|
-
series: Telemetry::ReleaseHealth.series(release, from, to, step: step_key),
|
|
41
|
-
sessions: Telemetry::Session.where(deploy: release).recent.limit(SESSIONS_SHOWN).map { |s| session_row(s) } }
|
|
42
|
-
end
|
|
43
|
-
render inertia: data.merge(
|
|
44
|
-
release: { deploy: release, ref: deploy&.short_ref || release.first(12), name: deploy&.name, deploy_id: deploy&.id,
|
|
45
|
-
deployed_at: deploy&.deployed_at, previous_ref: previous&.short_ref },
|
|
46
|
-
range: { from: from.iso8601, to: to.iso8601 },
|
|
47
|
-
new_issues: issue_rows(environment.issues.where(first_seen_at: from..to).or(environment.issues.where(SAMPLE_DEPLOY, release))),
|
|
48
|
-
resolved_issues: issue_rows(environment.issues.where(resolved_in_deploy: release)))
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
private
|
|
52
|
-
|
|
53
|
-
def session_row(s)
|
|
54
|
-
{ id: s.id, session_id: s.session_id, source: s.source, status: s.status, user_ref: s.user_ref,
|
|
55
|
-
duration: s.duration_ms, requests: s.requests, visits: s.visits, errors: s.error_count, occurred_at: s.occurred_at,
|
|
56
|
-
started_at: s.started_at }
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def issue_rows(scope)
|
|
60
|
-
scope.recent.limit(50).map { |i| { id: i.id, key: i.key, title: i.title, status: i.status } }
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Issues first seen while each release was live -- one query for the table,
|
|
64
|
-
# not one per row. A release is live until the next one deploys.
|
|
65
|
-
def new_issue_counts(deploys, to)
|
|
66
|
-
return {} if deploys.empty?
|
|
67
|
-
|
|
68
|
-
ordered = deploys.sort_by(&:deployed_at)
|
|
69
|
-
windows = ordered.each_cons(2).to_h { |release, following| [ release.id, release.deployed_at...following.deployed_at ] }
|
|
70
|
-
windows[ordered.last.id] = ordered.last.deployed_at...to
|
|
71
|
-
first_seen = environment.issues.where(first_seen_at: ordered.first.deployed_at..to).pluck(:first_seen_at)
|
|
72
|
-
windows.transform_values { |window| first_seen.count { |at| window.cover?(at) } }
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
class RequestsController < DashboardController
|
|
5
|
-
def index
|
|
6
|
-
routes = grouped("request", limit: 200, order: params[:sort], dir: params[:dir])
|
|
7
|
-
routes = FilterQuery.filter_routes(routes, params[:q])
|
|
8
|
-
render inertia: { routes: routes, series: series("request"), deploys: deploys_in_window,
|
|
9
|
-
sort: params[:sort] || "count", dir: params[:dir] || "desc", q: params[:q].to_s }
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# All requests to one route in the window.
|
|
13
|
-
def route
|
|
14
|
-
group_hash = params[:group_hash]
|
|
15
|
-
rows = telemetry { Telemetry::Execution.requests.where(group_hash: group_hash).between(*window_range).recent.limit(200).to_a }
|
|
16
|
-
render inertia: "requests/route", props: {
|
|
17
|
-
route: rows.first&.name || telemetry { Telemetry::Rollup.for_type("request").where(group_hash: group_hash).pick(:name) },
|
|
18
|
-
group_hash: group_hash, summary: summary_with_delta("request", group_hash: group_hash),
|
|
19
|
-
series: series("request", group_hash: group_hash), deploys: deploys_in_window,
|
|
20
|
-
requests: rows.map { |r| execution_row(r) }
|
|
21
|
-
}
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def show
|
|
25
|
-
exe = telemetry { Telemetry::Execution.find_by!(execution_id: params[:id]) }
|
|
26
|
-
render inertia: "executions/show", props: ExecutionPresenter.new(exe, environment).props
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
def execution_row(r)
|
|
32
|
-
{ execution_id: r.execution_id, name: r.name, status: r.status, duration: r.duration_ms.round(2), occurred_at: r.occurred_at,
|
|
33
|
-
user_ref: r.user_ref, tenant: r.app_tenant, exception_preview: r.exception_preview, inertia_component: r.inertia_component,
|
|
34
|
-
queries: r.counters["queries"], deploy: r.deploy }
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
class SavedViewsController < DashboardController
|
|
5
|
-
def create
|
|
6
|
-
view = environment.saved_views.new(create_params.merge(user: Viewer.user))
|
|
7
|
-
if view.save
|
|
8
|
-
redirect_back fallback_location: fallback, notice: "View saved"
|
|
9
|
-
else
|
|
10
|
-
redirect_back fallback_location: fallback, inertia: { errors: view.errors }
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def update
|
|
15
|
-
view = environment.saved_views.find(params[:id])
|
|
16
|
-
return head :forbidden unless editable?(view)
|
|
17
|
-
if view.update(update_params)
|
|
18
|
-
redirect_back fallback_location: fallback, notice: "View updated"
|
|
19
|
-
else
|
|
20
|
-
redirect_back fallback_location: fallback, inertia: { errors: view.errors }
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def destroy
|
|
25
|
-
view = environment.saved_views.find(params[:id])
|
|
26
|
-
return head :forbidden unless editable?(view)
|
|
27
|
-
view.destroy
|
|
28
|
-
redirect_back fallback_location: fallback, notice: "View deleted"
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
# Its owner can change a view, and only its owner. The platform also lets
|
|
34
|
-
# an account admin, so a shared view does not outlive the person who made
|
|
35
|
-
# it; embedded mode has no account and no admin, and `|| true` here let
|
|
36
|
-
# every dashboard user edit and delete everyone else's views. An app whose
|
|
37
|
-
# dashboard_user resolver returns one identity for everybody is unaffected
|
|
38
|
-
# either way: one owner, one editor.
|
|
39
|
-
def editable?(view)
|
|
40
|
-
view.viewer_id.to_s == Viewer.user.id.to_s
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def fallback = application_environment_overview_path(application, environment)
|
|
44
|
-
|
|
45
|
-
def create_params
|
|
46
|
-
params.permit(:name, :page, :query, :window, :pinned, :shared, params: {})
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def update_params
|
|
50
|
-
params.permit(:name, :pinned, :shared)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
class ScheduledTasksController < DashboardController
|
|
5
|
-
include TelemetryIdentity
|
|
6
|
-
|
|
7
|
-
def index
|
|
8
|
-
from, to = window_range
|
|
9
|
-
tasks = telemetry do
|
|
10
|
-
rows = Telemetry::Execution.scheduled.between(from, to).group(:task_key)
|
|
11
|
-
.pluck(:task_key, Arel.sql("COUNT(*)"), Arel.sql("SUM(CASE WHEN outcome = 'failed' THEN 1 ELSE 0 END)"), Arel.sql("AVG(duration)"), Arel.sql("MAX(occurred_at)"))
|
|
12
|
-
schedules = Telemetry::Execution.latest_schedules(rows.map(&:first))
|
|
13
|
-
rows.map { |k, c, f, d, last| { task_key: k, runs: c, failed: f, avg: (d.to_f / 1000.0).round(1), last_run_at: last, schedule: schedules[k] } }
|
|
14
|
-
end
|
|
15
|
-
tasks.each { |t| t[:next_run_at] = next_run(t[:schedule]) }
|
|
16
|
-
recent = telemetry do
|
|
17
|
-
rows = Telemetry::Execution.scheduled.between(from, to).recent.limit(100).to_a
|
|
18
|
-
people = origin_people(rows)
|
|
19
|
-
rows.map do |row|
|
|
20
|
-
{ execution_id: row.execution_id, task_key: row.task_key, name: row.name, outcome: row.outcome,
|
|
21
|
-
duration: row.duration_ms.round(1), occurred_at: row.occurred_at,
|
|
22
|
-
exception_preview: row.exception_preview }.merge(origin_identity(row, people))
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
render inertia: { tasks: tasks, runs: recent, missed: environment.issues.open.where("group_hash LIKE 'missed:%'").map { |i| { key: i.key, title: i.title, id: i.id } } }
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def show
|
|
29
|
-
exe = telemetry { Telemetry::Execution.find_by!(execution_id: params[:id]) }
|
|
30
|
-
render inertia: "executions/show", props: ExecutionPresenter.new(exe, environment).props
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
# Solid Queue schedules are cron or natural language ("every 5 minutes"),
|
|
36
|
-
# both of which fugit parses; anything it can't parse has no next run.
|
|
37
|
-
def next_run(schedule)
|
|
38
|
-
return nil if schedule.blank?
|
|
39
|
-
require "fugit"
|
|
40
|
-
Fugit.parse(schedule)&.next_time&.to_t
|
|
41
|
-
rescue StandardError
|
|
42
|
-
nil
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
class SpansController < DashboardController
|
|
5
|
-
def index
|
|
6
|
-
spans = grouped("span", limit: 200, order: params[:sort], dir: params[:dir])
|
|
7
|
-
spans = apply_filters(spans, FilterQuery.parse(params[:q]).fetch(:fields))
|
|
8
|
-
render inertia: { spans: spans, series: series("span"), summary: summary_with_delta("span"),
|
|
9
|
-
sort: params[:sort] || "count", dir: params[:dir] || "desc", q: params[:q].to_s }
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def show
|
|
13
|
-
group_hash = params[:id]
|
|
14
|
-
rows = telemetry { Telemetry::Span.where(group_hash: group_hash).between(*window_range).recent.limit(200).to_a }
|
|
15
|
-
render inertia: {
|
|
16
|
-
group_hash: group_hash,
|
|
17
|
-
name: rows.first&.name || telemetry { Telemetry::Rollup.for_type("span").where(group_hash: group_hash).pick(:name) },
|
|
18
|
-
summary: summary_with_delta("span", group_hash: group_hash), series: series("span", group_hash: group_hash),
|
|
19
|
-
facets: attribute_facets(rows), samples: rows.first(50).map { |s| span_row(s) }
|
|
20
|
-
}
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
# Rollup rows for "span" carry the span name and a failure count, so
|
|
26
|
-
# status:failed is "this group failed at least once in the window" rather
|
|
27
|
-
# than a per-span lookup.
|
|
28
|
-
def apply_filters(spans, fields)
|
|
29
|
-
spans = spans.select { |s| s[:name].to_s.include?(fields["name"]) } if fields["name"].present?
|
|
30
|
-
spans = spans.select { |s| s[:errors].positive? } if fields["status"] == "failed"
|
|
31
|
-
spans
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Which attribute keys these spans carry, and the most common values for
|
|
35
|
-
# each -- the span equivalent of the query page's "called from" breakdown.
|
|
36
|
-
def attribute_facets(rows)
|
|
37
|
-
rows.flat_map { |r| r.payload.to_a }.group_by(&:first).map { |key, pairs|
|
|
38
|
-
{ key: key, count: pairs.size, values: pairs.map { |_k, value| value.to_s }.tally.sort_by { |_v, c| -c }.first(5) }
|
|
39
|
-
}.sort_by { |facet| -facet[:count] }.first(10)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def span_row(s)
|
|
43
|
-
{ id: s.id, name: s.name, duration: s.duration_ms.round(3), status: s.status, attributes: s.payload,
|
|
44
|
-
occurred_at: s.occurred_at, execution_id: s.execution_id, execution_preview: s.execution_preview }
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
class StorageOpsController < DashboardController
|
|
5
|
-
def index
|
|
6
|
-
from, to = window_range
|
|
7
|
-
ops = telemetry do
|
|
8
|
-
Telemetry::StorageOp.between(from, to).group(:service, :op).pluck(:service, :op, Arel.sql("COUNT(*)"), Arel.sql("AVG(duration)"), Arel.sql("MAX(duration)"))
|
|
9
|
-
.map { |s, o, c, avg, mx| { service: s, op: o, count: c, avg: (avg.to_f / 1000.0).round(1), max: (mx.to_f / 1000.0).round(1) } }
|
|
10
|
-
end
|
|
11
|
-
recent = telemetry { Telemetry::StorageOp.between(from, to).recent.limit(100).map { |r| { id: r.id, service: r.service, op: r.op, key: r.key, duration: r.duration_ms.round(1), occurred_at: r.occurred_at, execution_id: r.execution_id, execution_preview: r.execution_preview } } }
|
|
12
|
-
render inertia: { ops: ops, recent: recent }
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
class TenantsController < DashboardController
|
|
5
|
-
def index
|
|
6
|
-
from, to = window_range
|
|
7
|
-
tenants, summary = telemetry do
|
|
8
|
-
rows = Telemetry::Tenant.index(from, to, q: params[:q], sort: params[:sort], dir: params[:dir])
|
|
9
|
-
[ rows, Telemetry::Tenant.overview(rows, from, to) ]
|
|
10
|
-
end
|
|
11
|
-
render inertia: { tenants: tenants, summary: summary,
|
|
12
|
-
sort: params[:sort] || "requests", dir: params[:dir] || "desc", q: params[:q].to_s }
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# :id is the tenant string. Rails reads a dot in a path segment as a format
|
|
16
|
-
# separator, so tenants like "acme.co" arrive percent-encoded (see the
|
|
17
|
-
# tenants index page's tenantPath).
|
|
18
|
-
def show
|
|
19
|
-
tenant = params[:id]
|
|
20
|
-
from, to = window_range
|
|
21
|
-
data = telemetry do
|
|
22
|
-
{ tenant: tenant, summary: { current: Telemetry::Tenant.summary(tenant, from, to),
|
|
23
|
-
previous: Telemetry::Tenant.summary(tenant, *window.previous.range) },
|
|
24
|
-
series: Telemetry::Tenant.series(tenant, from, to, step: step_key), routes: Telemetry::Tenant.routes(tenant, from, to),
|
|
25
|
-
jobs: Telemetry::Tenant.job_classes(tenant, from, to), exceptions: Telemetry::Tenant.exceptions(tenant, from, to),
|
|
26
|
-
people: Telemetry::Tenant.people(tenant), recent_requests: Telemetry::Tenant.recent_requests(tenant, from, to) }
|
|
27
|
-
end
|
|
28
|
-
issues = environment.issues.where(group_hash: data[:exceptions].map { |e| e[:group_hash] }.uniq).index_by(&:group_hash)
|
|
29
|
-
render inertia: data.merge(exceptions: data[:exceptions].map { |e| with_issue(e, issues) }, links: links(tenant))
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
def with_issue(exception, issues)
|
|
35
|
-
issue = issues[exception[:group_hash]]
|
|
36
|
-
exception.merge(issue_id: issue&.id, issue_key: issue&.key)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def links(tenant)
|
|
40
|
-
{ logs: application_environment_logs_path(application, environment, window: window_key, q: "tenant:#{tenant}"),
|
|
41
|
-
people: application_environment_people_path(application, environment, window: window_key) }
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
class ThresholdsController < DashboardController
|
|
5
|
-
def index
|
|
6
|
-
render inertia: { thresholds: environment.thresholds.order(:target_kind, :target).map { |t| t.slice(:id, :target_kind, :target, :metric, :limit, :window_minutes).merge(description: t.description) },
|
|
7
|
-
routes: telemetry { Telemetry::Rollup.for_type("request").where("bucket > ?", 7.days.ago).distinct.pluck(:name).sort },
|
|
8
|
-
jobs: telemetry { Telemetry::Rollup.for_type("job_attempt").where("bucket > ?", 7.days.ago).distinct.pluck(:name).sort },
|
|
9
|
-
kinds: Threshold::TARGET_KINDS, metrics: Threshold::METRICS,
|
|
10
|
-
anomaly_rules: environment.anomaly_rules.order(:target_kind, :target).map { |r| r.slice(:id, :target_kind, :target, :metric, :deviation, :window_minutes, :baseline_days, :enabled, :last_fired_at).merge(description: r.description) },
|
|
11
|
-
# Spend and tokens only exist on llm_calls, so the form
|
|
12
|
-
# narrows with the kind rather than offering a rule that
|
|
13
|
-
# could never fire.
|
|
14
|
-
metrics_for_kind: Threshold::TARGET_KINDS.index_with { |k| Threshold.metrics_for(k) },
|
|
15
|
-
units: Threshold::UNITS,
|
|
16
|
-
llm_models: telemetry { Telemetry::Rollup.for_type("llm_call").where("bucket > ?", 7.days.ago).distinct.pluck(:name).compact.sort },
|
|
17
|
-
anomaly_target_kinds: AnomalyRule::TARGET_KINDS, anomaly_metrics: AnomalyRule::METRICS,
|
|
18
|
-
anomaly_metrics_for_kind: AnomalyRule::TARGET_KINDS.index_with { |k| AnomalyRule.metrics_for(k) } }
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def create
|
|
22
|
-
threshold = environment.thresholds.new(threshold_params)
|
|
23
|
-
if threshold.save
|
|
24
|
-
redirect_to application_environment_thresholds_path(application, environment), notice: "Threshold added"
|
|
25
|
-
else
|
|
26
|
-
redirect_to application_environment_thresholds_path(application, environment), inertia: { errors: threshold.errors }
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def update
|
|
31
|
-
threshold = environment.thresholds.find(params[:id])
|
|
32
|
-
threshold.update(threshold_params)
|
|
33
|
-
redirect_to application_environment_thresholds_path(application, environment), notice: "Threshold updated"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def destroy
|
|
37
|
-
environment.thresholds.find(params[:id]).destroy
|
|
38
|
-
redirect_to application_environment_thresholds_path(application, environment), notice: "Threshold removed"
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
def threshold_params
|
|
44
|
-
params.permit(:target_kind, :target, :metric, :limit, :window_minutes)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# One distributed trace: every execution that shares a trace_id, nested by
|
|
4
|
-
# the parent_id the gem propagates (the enqueuing execution's id for jobs,
|
|
5
|
-
# the W3C traceparent parent-id for HTTP hops).
|
|
6
|
-
module Railwatch
|
|
7
|
-
class TracesController < DashboardController
|
|
8
|
-
LIMIT = 200
|
|
9
|
-
|
|
10
|
-
def show
|
|
11
|
-
trace_id = params[:trace_id]
|
|
12
|
-
executions, outgoing = telemetry do
|
|
13
|
-
rows = Telemetry::Execution.where(trace_id: trace_id).order(:occurred_at).limit(LIMIT).to_a
|
|
14
|
-
[ rows, Telemetry::OutgoingRequest.where(execution_id: rows.map(&:execution_id)).order(:occurred_at).group_by(&:execution_id) ]
|
|
15
|
-
end
|
|
16
|
-
raise ActiveRecord::RecordNotFound if executions.empty?
|
|
17
|
-
|
|
18
|
-
@start = executions.first.occurred_at
|
|
19
|
-
@outgoing = outgoing
|
|
20
|
-
render inertia: { trace_id: trace_id, roots: tree(executions),
|
|
21
|
-
span_count: executions.size + outgoing.values.sum(&:size),
|
|
22
|
-
services: executions.filter_map(&:server).uniq, duration: total_duration(executions) }
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
# An execution's parent is the one whose execution_id matches its
|
|
28
|
-
# parent_id outright (a job enqueued by that execution) or whose id
|
|
29
|
-
# matches once dashes are dropped and it is cut to the 16 hex chars a
|
|
30
|
-
# W3C traceparent carries (an incoming HTTP hop). Anything whose parent
|
|
31
|
-
# is missing -- the trace's entry point, or a hop whose caller was not
|
|
32
|
-
# reported -- is a root.
|
|
33
|
-
def tree(executions)
|
|
34
|
-
by_id = executions.index_by(&:execution_id)
|
|
35
|
-
by_short_id = executions.index_by { |e| e.execution_id.to_s.delete("-").first(16) }
|
|
36
|
-
children = Hash.new { |hash, key| hash[key] = [] }
|
|
37
|
-
roots = []
|
|
38
|
-
executions.each do |exe|
|
|
39
|
-
parent = exe.parent_id.presence && (by_id[exe.parent_id] || by_short_id[exe.parent_id])
|
|
40
|
-
parent = nil if parent && parent.execution_id == exe.execution_id
|
|
41
|
-
parent ? children[parent.execution_id] << exe : roots << exe
|
|
42
|
-
end
|
|
43
|
-
roots.map { |exe| node(exe, children) }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def node(exe, children)
|
|
47
|
-
{ execution: execution_row(exe), children: children[exe.execution_id].map { |child| node(child, children) } }
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def execution_row(exe)
|
|
51
|
-
{ execution_id: exe.execution_id, kind: exe.kind, name: exe.name, status: exe.status, outcome: exe.outcome,
|
|
52
|
-
parent_id: exe.parent_id, server: exe.server, occurred_at: exe.occurred_at,
|
|
53
|
-
duration: exe.duration_ms.round(2), offset: offset(exe.occurred_at),
|
|
54
|
-
outgoing: @outgoing.fetch(exe.execution_id, []).map { |o| outgoing_row(o) } }
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def outgoing_row(o)
|
|
58
|
-
{ id: o.id, method: o.method, url: o.url.to_s.first(200), status_code: o.status_code,
|
|
59
|
-
duration: o.duration_ms&.round(2), offset: offset(o.occurred_at) }
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def offset(time)
|
|
63
|
-
((time - @start) * 1000.0).round(2)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Wall-clock span of the whole trace: the last thing to finish, minus the
|
|
67
|
-
# first thing to start.
|
|
68
|
-
def total_duration(executions)
|
|
69
|
-
executions.map { |e| offset(e.occurred_at) + e.duration_ms }.max.round(2)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
class TransactionsController < DashboardController
|
|
5
|
-
def index
|
|
6
|
-
from, to = window_range
|
|
7
|
-
rows = grouped("transaction", limit: 200, order: params[:sort], dir: params[:dir])
|
|
8
|
-
recent = telemetry { Telemetry::Transaction.between(from, to).recent.limit(50).map { |t| transaction_row(t) } }
|
|
9
|
-
render inertia: { transactions: rows, series: series("transaction"), recent: recent,
|
|
10
|
-
sort: params[:sort] || "count", dir: params[:dir] || "desc" }
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
private
|
|
14
|
-
|
|
15
|
-
def transaction_row(t)
|
|
16
|
-
{ id: t.id, outcome: t.outcome, connection: t.connection, duration: t.duration_ms.round(3), statement_count: t.statement_count, occurred_at: t.occurred_at,
|
|
17
|
-
execution_id: t.execution_id, execution_source: t.execution_source, execution_preview: t.execution_preview,
|
|
18
|
-
group_hash: t.group_hash }
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
class ViewRendersController < DashboardController
|
|
5
|
-
def index
|
|
6
|
-
from, to = window_range
|
|
7
|
-
fields = FilterQuery.parse(params[:q]).fetch(:fields)
|
|
8
|
-
rows = grouped("view_render", limit: 200, order: params[:sort], dir: params[:dir])
|
|
9
|
-
rows = rows.select { |r| r[:name].include?(fields["template"]) } if fields["template"].present?
|
|
10
|
-
kinds = telemetry do
|
|
11
|
-
Telemetry::Rollup.for_type("view_render").between(from, to).to_a.group_by(&:group_hash)
|
|
12
|
-
.transform_values { |group_rows| group_rows.max_by(&:bucket).extra["kind"] }
|
|
13
|
-
end
|
|
14
|
-
rows.each { |r| r[:kind] = kinds[r[:group_hash]] }
|
|
15
|
-
slowest = telemetry { Telemetry::ViewRender.between(from, to).order(duration: :desc).limit(30).map { |v| render_row(v) } }
|
|
16
|
-
render inertia: { renders: rows, series: series("view_render"), slowest: slowest,
|
|
17
|
-
sort: params[:sort] || "count", dir: params[:dir] || "desc", q: params[:q].to_s }
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
def render_row(v)
|
|
23
|
-
{ id: v.id, identifier: v.identifier, kind: v.kind, layout: v.layout, duration: v.duration_ms.round(3),
|
|
24
|
-
occurred_at: v.occurred_at, execution_id: v.execution_id, execution_source: v.execution_source,
|
|
25
|
-
execution_preview: v.execution_preview, group_hash: v.group_hash }
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|