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,215 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "puma/plugin"
|
|
4
|
-
|
|
5
|
-
# `plugin :railwatch` in config/puma.rb. In embedded mode, forks one
|
|
6
|
-
# Railwatch::Writer from the Puma process once it has booted, restarts it if
|
|
7
|
-
# it dies, and stops it with Puma. Same shape as Solid Queue's
|
|
8
|
-
# `solid_queue_mode :fork`, in both cluster and single mode: a default Rails
|
|
9
|
-
# 8 app runs Puma in single mode (no WEB_CONCURRENCY), and that app's
|
|
10
|
-
# batches belong off its request threads just as much. The fork happens
|
|
11
|
-
# from the plugin's background thread with the app loaded and Rails'
|
|
12
|
-
# ForkTracker resetting every Railwatch thread in the child, exactly as it
|
|
13
|
-
# does for a Puma cluster worker. A no-op when Railwatch is off or the
|
|
14
|
-
# transport is not :local, so it is safe to leave in place.
|
|
15
|
-
#
|
|
16
|
-
# Nothing in `start` may touch Railwatch: `bundle exec puma` fires plugin
|
|
17
|
-
# starts BEFORE it loads the Rack app, so the constant may not exist yet.
|
|
18
|
-
# Every decision that needs the app is made from the background supervisor,
|
|
19
|
-
# which waits for after_booted.
|
|
20
|
-
Puma::Plugin.create do
|
|
21
|
-
attr_reader :log_writer, :writer_pid
|
|
22
|
-
|
|
23
|
-
POLL = 2
|
|
24
|
-
# How often a stopping writer is checked for, and how long a KILL is given
|
|
25
|
-
# to take before the pid is abandoned (a process stuck in disk I/O cannot
|
|
26
|
-
# die until the I/O returns, and Puma's exit should not wait for that).
|
|
27
|
-
REAP_POLL = 0.05
|
|
28
|
-
KILL_REAP = 1
|
|
29
|
-
|
|
30
|
-
def start(launcher)
|
|
31
|
-
@log_writer = launcher.log_writer
|
|
32
|
-
@puma_pid = $$
|
|
33
|
-
@launcher = launcher
|
|
34
|
-
@shutting_down = false
|
|
35
|
-
@booted = false
|
|
36
|
-
@warned = false
|
|
37
|
-
|
|
38
|
-
# When the app is already loaded (`bin/rails server` loads it before
|
|
39
|
-
# handing over to Puma), mark a writer expected now: cluster workers fork
|
|
40
|
-
# from this process after start and inherit the flag, so they retain
|
|
41
|
-
# batches while the writer is starting instead of writing them in-process.
|
|
42
|
-
# Under bare `puma` this is false and the workers find the writer on
|
|
43
|
-
# their transport's next re-check instead (Transport::Socket).
|
|
44
|
-
::Railwatch::Writer.expected! if active?
|
|
45
|
-
|
|
46
|
-
# in_background blocks are collected at plugin start and started by the
|
|
47
|
-
# runner once, so this has to be registered here, not from after_booted
|
|
48
|
-
# (by then they have already been fired and a late block never runs). It
|
|
49
|
-
# waits for boot itself.
|
|
50
|
-
in_background { supervise }
|
|
51
|
-
|
|
52
|
-
launcher.events.after_booted { booted! }
|
|
53
|
-
# A phased restart fires before_restart and then, once the new workers
|
|
54
|
-
# are up, after_booted again. The writer is stopped for the restart and
|
|
55
|
-
# the supervisor, which is still running, respawns it when @booted flips
|
|
56
|
-
# back. Only a real stop latches shutdown.
|
|
57
|
-
launcher.events.before_restart { restart_writer }
|
|
58
|
-
# after_stopped only latches. It is NOT where the writer is stopped:
|
|
59
|
-
# Puma's SIGTERM trap fires it BEFORE `stop_blocked` drains in-flight
|
|
60
|
-
# requests, so stopping here would take the writer away from requests
|
|
61
|
-
# that are still running. at_exit runs after the whole run loop.
|
|
62
|
-
launcher.events.after_stopped { @shutting_down = true }
|
|
63
|
-
at_exit { shutdown_writer if Process.pid == @puma_pid }
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
|
|
68
|
-
def booted!
|
|
69
|
-
@booted = true
|
|
70
|
-
::Railwatch::Writer.expected! if active?
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Whether this process should run a writer. Answered fresh each time, and
|
|
74
|
-
# safe before the app is loaded: `defined?(Railwatch)` is true as soon as
|
|
75
|
-
# the gem's entrypoint has been required (Bundler.require), but a bare
|
|
76
|
-
# `puma` on an app that has not required it yet sees neither, and must not
|
|
77
|
-
# raise out of Puma's boot.
|
|
78
|
-
def active?
|
|
79
|
-
return false unless defined?(::Railwatch) && ::Railwatch.respond_to?(:enabled?)
|
|
80
|
-
return false unless ::Railwatch.enabled? && ::Railwatch.config.local?
|
|
81
|
-
|
|
82
|
-
path = ::Railwatch.config.writer_socket_path
|
|
83
|
-
return false if path.nil?
|
|
84
|
-
return true if ::Railwatch::Writer.usable_path?(path)
|
|
85
|
-
|
|
86
|
-
unless @warned
|
|
87
|
-
@warned = true
|
|
88
|
-
log "Railwatch writer not started: socket path #{path} is #{path.bytesize} bytes, over the " \
|
|
89
|
-
"#{::Railwatch::Writer::MAX_SOCKET_PATH}-byte limit; set RAILWATCH_WRITER_SOCKET to a shorter path. " \
|
|
90
|
-
"Batches are written in-process meanwhile."
|
|
91
|
-
end
|
|
92
|
-
false
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def spawn_writer
|
|
96
|
-
@writer_pid = ::Railwatch::Writer.fork_writer! do
|
|
97
|
-
::Railwatch::Writer.run!(parent: @puma_pid)
|
|
98
|
-
end
|
|
99
|
-
log "Railwatch writer started (pid #{@writer_pid})"
|
|
100
|
-
rescue SystemCallError => e
|
|
101
|
-
@writer_pid = nil
|
|
102
|
-
log "Railwatch writer could not be forked (#{e.class}: #{e.message}); retrying"
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Puma's cluster reaps every child with wait2(-1), the writer included, so
|
|
106
|
-
# waitpid on the writer's pid raises ECHILD after it has died. Liveness is
|
|
107
|
-
# asked with signal 0 instead, which works whoever reaped it.
|
|
108
|
-
def supervise
|
|
109
|
-
loop do
|
|
110
|
-
sleep POLL
|
|
111
|
-
break if @shutting_down
|
|
112
|
-
next unless @booted && active?
|
|
113
|
-
next if writer_alive?
|
|
114
|
-
|
|
115
|
-
log "Railwatch writer (pid #{@writer_pid}) is gone; starting" if @writer_pid
|
|
116
|
-
spawn_writer
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def writer_alive?
|
|
121
|
-
return false unless @writer_pid
|
|
122
|
-
|
|
123
|
-
Process.kill(0, @writer_pid)
|
|
124
|
-
# A zombie the cluster has not reaped yet still answers signal 0.
|
|
125
|
-
!Process.waitpid(@writer_pid, Process::WNOHANG)
|
|
126
|
-
rescue Errno::ESRCH
|
|
127
|
-
false
|
|
128
|
-
rescue Errno::ECHILD
|
|
129
|
-
# Already reaped by the cluster: alive iff it still exists for kill 0.
|
|
130
|
-
begin
|
|
131
|
-
Process.kill(0, @writer_pid)
|
|
132
|
-
true
|
|
133
|
-
rescue Errno::ESRCH
|
|
134
|
-
false
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# For a restart: stop the writer and mark the cluster as not booted, so the
|
|
139
|
-
# supervisor spawns a fresh one once after_booted fires again.
|
|
140
|
-
def restart_writer
|
|
141
|
-
@booted = false
|
|
142
|
-
stop_writer
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Runs from at_exit, after Puma's run loop has returned and every request
|
|
146
|
-
# has been served, in both single and cluster mode. Flushes this process's
|
|
147
|
-
# own reporter (its process and health records, and in single mode the
|
|
148
|
-
# requests' records) while the writer is still listening, then stops it.
|
|
149
|
-
def shutdown_writer
|
|
150
|
-
@shutting_down = true
|
|
151
|
-
if defined?(::Railwatch) && ::Railwatch.respond_to?(:enabled?) && ::Railwatch.enabled? && ::Railwatch.config.local?
|
|
152
|
-
::Railwatch.reporter.shutdown
|
|
153
|
-
end
|
|
154
|
-
stop_writer
|
|
155
|
-
rescue StandardError => e
|
|
156
|
-
log "Railwatch writer shutdown failed (#{e.class}: #{e.message})"
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# TERM closes the writer's listener; it finishes what it is holding and
|
|
160
|
-
# exits on its own. Waited for with a deadline, not Process.wait, which
|
|
161
|
-
# has none: a writer wedged in a SQLite write or on a full disk would hold
|
|
162
|
-
# Puma's exit open for as long as it stayed wedged. KILL past the deadline.
|
|
163
|
-
# Reaped either way, so a cluster master never leaves a zombie behind.
|
|
164
|
-
def stop_writer
|
|
165
|
-
return unless @writer_pid
|
|
166
|
-
|
|
167
|
-
Process.kill(:TERM, @writer_pid)
|
|
168
|
-
return if reaped_within?(stop_timeout)
|
|
169
|
-
|
|
170
|
-
log "Railwatch writer (pid #{@writer_pid}) did not exit within #{stop_timeout}s of TERM; killing it"
|
|
171
|
-
Process.kill(:KILL, @writer_pid)
|
|
172
|
-
log "Railwatch writer (pid #{@writer_pid}) did not exit on KILL; leaving it" unless reaped_within?(KILL_REAP)
|
|
173
|
-
rescue Errno::ECHILD, Errno::ESRCH
|
|
174
|
-
nil
|
|
175
|
-
ensure
|
|
176
|
-
@writer_pid = nil
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
# shutdown_timeout: the same allowance this process gives its own
|
|
180
|
-
# reporter, and deliberately NOT the writer's full theoretical exit time
|
|
181
|
-
# (a sequential SHUTDOWN_DRAIN join per worker thread, its maintenance
|
|
182
|
-
# join, then its own reporter shutdown -- 13s at the defaults). Waiting
|
|
183
|
-
# that long would buy nothing: a writer killed mid-batch loses no data,
|
|
184
|
-
# because the transaction rolls back and the worker retries the batch by
|
|
185
|
-
# id against the next writer (Writer#serve says so). Exit time spent
|
|
186
|
-
# waiting for the drain is spent for nothing. An idle writer is gone in
|
|
187
|
-
# well under a second either way.
|
|
188
|
-
#
|
|
189
|
-
# This runs from at_exit, inside the container's TERM-to-KILL grace. Under
|
|
190
|
-
# Kamal that is Docker's 10s default for a proxied role that has not set
|
|
191
|
-
# `stop_timeout`, or whatever `stop_timeout` says when it has; an app that
|
|
192
|
-
# wants the writer given longer can raise RAILWATCH_SHUTDOWN_TIMEOUT to
|
|
193
|
-
# match its grace, and this bound rises with it.
|
|
194
|
-
def stop_timeout
|
|
195
|
-
[ ::Railwatch.config.shutdown_timeout.to_f, 0.0 ].max
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# Non-blocking waits on a short poll; Process.wait has no timeout and a
|
|
199
|
-
# child that never exits would hold it forever. ECHILD (the cluster's
|
|
200
|
-
# wait2(-1) reaped it first) propagates to stop_writer, which reads it as
|
|
201
|
-
# gone.
|
|
202
|
-
def reaped_within?(seconds)
|
|
203
|
-
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + seconds
|
|
204
|
-
loop do
|
|
205
|
-
return true if Process.waitpid(@writer_pid, Process::WNOHANG)
|
|
206
|
-
return false if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
207
|
-
|
|
208
|
-
sleep REAP_POLL
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def log(message)
|
|
213
|
-
log_writer.log(message)
|
|
214
|
-
end
|
|
215
|
-
end
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rack/files"
|
|
4
|
-
|
|
5
|
-
module Railwatch
|
|
6
|
-
# Serves the prebuilt dashboard bundle the gem ships under public/railwatch.
|
|
7
|
-
# Vite wrote every asset URL with base /railwatch/assets/, so a request for
|
|
8
|
-
# /railwatch/assets/assets/app-abc123.js maps to public/railwatch/assets/
|
|
9
|
-
# app-abc123.js. Files are content-hashed, so they are immutable. The
|
|
10
|
-
# favicon is the one unhashed file, served with a short cache instead.
|
|
11
|
-
class DashboardAssets
|
|
12
|
-
PREFIX = "/railwatch/assets/"
|
|
13
|
-
ICONS = { "/railwatch/icon.svg" => "/icon.svg", "/railwatch/icon.png" => "/icon.png" }.freeze
|
|
14
|
-
|
|
15
|
-
# Put this middleware in the host's stack. Before ActionDispatch::Static
|
|
16
|
-
# when the host has it, which is what keeps a same-named file in the
|
|
17
|
-
# app's own public/ from shadowing the dashboard's. An app that serves
|
|
18
|
-
# static files from nginx, a CDN or Thruster sets
|
|
19
|
-
# `config.public_file_server.enabled = false` and has no such middleware:
|
|
20
|
-
# inserting relative to it would raise and take the whole application
|
|
21
|
-
# down at boot, so go to the front instead, which is the same position
|
|
22
|
-
# relative to everything that remains.
|
|
23
|
-
def self.install!(app, root:)
|
|
24
|
-
if app.config.public_file_server.enabled
|
|
25
|
-
app.middleware.insert_before ActionDispatch::Static, self, root: root
|
|
26
|
-
else
|
|
27
|
-
app.middleware.insert 0, self, root: root
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def initialize(app, root:)
|
|
32
|
-
@app = app
|
|
33
|
-
@files = Rack::Files.new(root, { "cache-control" => "public, max-age=31536000, immutable" })
|
|
34
|
-
@icons = Rack::Files.new(root, { "cache-control" => "public, max-age=86400" })
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def call(env)
|
|
38
|
-
path = env["PATH_INFO"]
|
|
39
|
-
return @icons.call(env.merge("PATH_INFO" => ICONS[path])) if ICONS.key?(path)
|
|
40
|
-
return @app.call(env) unless path.start_with?(PREFIX) && !path.include?("..")
|
|
41
|
-
|
|
42
|
-
@files.call(env.merge("PATH_INFO" => path.delete_prefix("/railwatch/assets")))
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
data/lib/railwatch/embedded.rb
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
# Process-local state for the embedded (in-process) mode and the stand-ins
|
|
5
|
-
# the platform's code expects from its account layer.
|
|
6
|
-
module Embedded
|
|
7
|
-
# Relation-shaped empty set for platform associations an embedded install
|
|
8
|
-
# does not have (Linear links, integrations). Every scope returns itself.
|
|
9
|
-
NONE = Class.new do
|
|
10
|
-
def method_missing(*) = self
|
|
11
|
-
def respond_to_missing?(*) = true
|
|
12
|
-
def to_a = []
|
|
13
|
-
def each(&) = [].each(&)
|
|
14
|
-
def first = nil
|
|
15
|
-
def map(&) = []
|
|
16
|
-
def count = 0
|
|
17
|
-
def any? = false
|
|
18
|
-
def none? = true
|
|
19
|
-
def exists?(*) = false
|
|
20
|
-
def empty? = true
|
|
21
|
-
def limit(*) = self
|
|
22
|
-
def where(*) = self
|
|
23
|
-
def find_each(&) = nil
|
|
24
|
-
end.new.freeze
|
|
25
|
-
|
|
26
|
-
# The platform scopes everything by account (plan, quota, retention,
|
|
27
|
-
# members). One embedded install is one account with none of that.
|
|
28
|
-
module Account
|
|
29
|
-
module_function
|
|
30
|
-
|
|
31
|
-
def id = 1
|
|
32
|
-
def name = Rails.application.class.module_parent_name
|
|
33
|
-
def slug = name.parameterize
|
|
34
|
-
def plan = "embedded"
|
|
35
|
-
def retention_days = Railwatch.config.retention_days
|
|
36
|
-
def auto_resolve_after_days = 14
|
|
37
|
-
# Whoever the host's resolver named for this request, not the anonymous
|
|
38
|
-
# placeholder: an embedded install has no member list, so the account's
|
|
39
|
-
# one "member" is the person looking at it.
|
|
40
|
-
def users = [ User.current || User.default ]
|
|
41
|
-
def members = users
|
|
42
|
-
def memberships = NONE
|
|
43
|
-
def integrations = NONE
|
|
44
|
-
def applications = [ Application.current ]
|
|
45
|
-
def environments = [ Environment.current ]
|
|
46
|
-
def quota_exhausted? = false
|
|
47
|
-
def as_json(*) = { id: id, name: name, slug: slug, plan: plan }
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
module Export
|
|
5
|
-
# Speaks the receipt protocol: names the delivery so the receiver can
|
|
6
|
-
# recognise a repeat of it, and turns the answer into something the queue
|
|
7
|
-
# can act on.
|
|
8
|
-
#
|
|
9
|
-
# It makes one attempt and has no opinion about when to try again. That
|
|
10
|
-
# belongs to the queue, which is the thing with durable storage.
|
|
11
|
-
class Client
|
|
12
|
-
Outcome = Struct.new(:disposition, :status, :reason, :retry_after_at, :ack, keyword_init: true)
|
|
13
|
-
|
|
14
|
-
# The queue is bound to a fingerprint of the export credential, so the
|
|
15
|
-
# request has to be made with that credential: authenticating as someone
|
|
16
|
-
# else would deliver these bytes to a tenant that never admitted them.
|
|
17
|
-
def initialize(config)
|
|
18
|
-
@config = config
|
|
19
|
-
@transports = {}
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Not `send`: shadowing Object#send on a class makes it impossible to
|
|
23
|
-
# reach the real one, and reads as a coincidence rather than a verb.
|
|
24
|
-
def deliver(claim, producer_id:)
|
|
25
|
-
# Snapshot once, then verify and use those exact bytes. Configuration
|
|
26
|
-
# can change after Outbox#claim! checked the destination binding.
|
|
27
|
-
token = @config.resolved_export_token.to_s.dup.freeze
|
|
28
|
-
unless Digest::SHA256.hexdigest(token) == claim.token_digest
|
|
29
|
-
return Outcome.new(disposition: :deferred, status: 401, reason: "export credential changed after claim")
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
result = transport_for(claim, token: token).deliver_encoded(
|
|
33
|
-
body: claim.body, expected_count: claim.record_count, batch_id: claim.delivery_id,
|
|
34
|
-
dropped: claim.metadata.fetch("dropped", 0).to_i,
|
|
35
|
-
dropped_bytes: claim.metadata.fetch("dropped_bytes", 0).to_i,
|
|
36
|
-
# Stored with the delivery, not read from the reporter now: this is
|
|
37
|
-
# what the batch was carrying when it was queued.
|
|
38
|
-
backpressure_factor: claim.metadata.fetch("backpressure_factor", 1.0).to_f,
|
|
39
|
-
gem_version: claim.metadata.fetch("version", Railwatch::VERSION),
|
|
40
|
-
headers: headers(claim, producer_id)
|
|
41
|
-
)
|
|
42
|
-
interpret(result)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def reset_after_fork!
|
|
46
|
-
@transports = {}
|
|
47
|
-
self
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Drops any latch the transports have picked up. After a credential
|
|
51
|
-
# problem is fixed, the next send must be an actual send.
|
|
52
|
-
def reset!
|
|
53
|
-
@transports = {}
|
|
54
|
-
self
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
# Keyed on the destination this delivery is FOR, not on whatever the
|
|
60
|
-
# configuration says right now. A process reconfigured mid-flight would
|
|
61
|
-
# otherwise post bytes admitted for one receiver to another one -- and,
|
|
62
|
-
# if they belong to different tenants, hand a customer's telemetry to
|
|
63
|
-
# somebody else while marking it delivered.
|
|
64
|
-
def transport_for(claim, token:)
|
|
65
|
-
key = [ claim.url, claim.token_digest ]
|
|
66
|
-
@transports[key] ||= begin
|
|
67
|
-
credentialed = @config.dup
|
|
68
|
-
credentialed.token = token
|
|
69
|
-
Transport::Http.new(credentialed, endpoint: claim.url)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def headers(claim, producer_id)
|
|
74
|
-
{
|
|
75
|
-
"X-Railwatch-Producer-Id" => producer_id,
|
|
76
|
-
"X-Railwatch-Body-SHA256" => Digest::SHA256.hexdigest(claim.body),
|
|
77
|
-
"X-Railwatch-Policy" => claim.metadata.fetch("policy", Policy::Everything::VERSION)
|
|
78
|
-
}
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# The receiver's answer, reduced to what the queue needs to decide.
|
|
82
|
-
# Anything it cannot read is treated as "not stored": keeping bytes we
|
|
83
|
-
# might not need costs a retry, discarding bytes that never arrived
|
|
84
|
-
# costs the telemetry.
|
|
85
|
-
STORED = %w[committed already_committed].freeze
|
|
86
|
-
REJECTING_STATUSES = [ 400, 409, 410, 413, 422 ].freeze
|
|
87
|
-
|
|
88
|
-
def interpret(result)
|
|
89
|
-
return Outcome.new(disposition: :stored, status: result.status, ack: result.to_h) if stored?(result)
|
|
90
|
-
return Outcome.new(disposition: :rejected, status: result.status, reason: reason_for(result)) if rejected?(result)
|
|
91
|
-
|
|
92
|
-
Outcome.new(disposition: :deferred, status: result.status, reason: reason_for(result),
|
|
93
|
-
retry_after_at: result.retry_after_at)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# A 200 that stored the records, or a duplicate the receiver already
|
|
97
|
-
# holds. When the receiver names what it did, we believe the name and
|
|
98
|
-
# not the arithmetic: counts that happen to add up are not a receipt.
|
|
99
|
-
def stored?(result)
|
|
100
|
-
return false unless result.ok && !result.deferred?
|
|
101
|
-
return STORED.include?(result.ack_disposition) if result.ack_disposition
|
|
102
|
-
|
|
103
|
-
true
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Permanently unacceptable TO THE RECEIVER: sending it again cannot
|
|
107
|
-
# change the answer. 409 is a delivery id reused for different bytes,
|
|
108
|
-
# 410 one too old to be recognised.
|
|
109
|
-
#
|
|
110
|
-
# Deliberately not our own refusals. `:permanent` from the transport
|
|
111
|
-
# means we declined to send -- a latched credential failure, a
|
|
112
|
-
# configuration we will not use -- and throwing the bytes away because
|
|
113
|
-
# of something on this side would destroy telemetry that was never
|
|
114
|
-
# offered to anyone.
|
|
115
|
-
def rejected?(result)
|
|
116
|
-
REJECTING_STATUSES.include?(result.status)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def reason_for(result)
|
|
120
|
-
(result.reason || result.error || result.status).to_s[0, 64]
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Railwatch
|
|
4
|
-
module Export
|
|
5
|
-
# Which process is allowed to send, right now.
|
|
6
|
-
#
|
|
7
|
-
# Every eligible process runs a sender; the lease decides which one does
|
|
8
|
-
# anything. It is held for a short time and renewed, so a process that
|
|
9
|
-
# dies holding it blocks the queue for seconds rather than forever.
|
|
10
|
-
#
|
|
11
|
-
# The generation is a fence. A holder that stalls past its expiry may wake
|
|
12
|
-
# and finish a request it had already started -- the receipt at the other
|
|
13
|
-
# end makes that harmless -- but it must not then be able to overwrite
|
|
14
|
-
# what the new holder has since recorded. Every write it attempts carries
|
|
15
|
-
# the generation it was granted, and a stale one matches nothing.
|
|
16
|
-
module Lease
|
|
17
|
-
TTL = 30
|
|
18
|
-
# Renewed well inside the TTL: a renewal that has to wait on the write
|
|
19
|
-
# lock still has room to land before the lease it is extending lapses.
|
|
20
|
-
RENEW_EVERY = 10
|
|
21
|
-
|
|
22
|
-
module_function
|
|
23
|
-
|
|
24
|
-
# Takes the lease, or extends it if we already hold it. Returns the
|
|
25
|
-
# generation we hold it under, or nil if someone else has it.
|
|
26
|
-
def acquire(destination_id, owner:, now: Time.current)
|
|
27
|
-
taken = Telemetry::ExportDestination
|
|
28
|
-
.where(id: destination_id)
|
|
29
|
-
.where("lease_owner IS NULL OR lease_owner = ? OR lease_expires_at < ?", owner, now)
|
|
30
|
-
.update_all([
|
|
31
|
-
"lease_owner = ?, lease_expires_at = ?, lease_generation = lease_generation + 1, updated_at = ?",
|
|
32
|
-
owner, now + TTL, now
|
|
33
|
-
])
|
|
34
|
-
return nil if taken.zero?
|
|
35
|
-
|
|
36
|
-
Telemetry::ExportDestination.where(id: destination_id).pick(:lease_generation)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Extends a lease we still hold. Cannot revive an expired one: taking it
|
|
40
|
-
# again is acquire's job, and that increments the generation so anything
|
|
41
|
-
# in flight under the old one is fenced out.
|
|
42
|
-
def renew(destination_id, owner:, generation:, now: Time.current)
|
|
43
|
-
Telemetry::ExportDestination
|
|
44
|
-
.where(id: destination_id, lease_owner: owner, lease_generation: generation)
|
|
45
|
-
.where(lease_expires_at: now..)
|
|
46
|
-
.update_all([ "lease_expires_at = ?, updated_at = ?", now + TTL, now ]) == 1
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def release(destination_id, owner:, generation:, now: Time.current)
|
|
50
|
-
Telemetry::ExportDestination
|
|
51
|
-
.where(id: destination_id, lease_owner: owner, lease_generation: generation)
|
|
52
|
-
.update_all([ "lease_owner = NULL, lease_expires_at = NULL, updated_at = ?", now ]) == 1
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|