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
data/lib/railwatch/writer.rb
DELETED
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "socket"
|
|
4
|
-
require "zlib"
|
|
5
|
-
require "json"
|
|
6
|
-
require "fileutils"
|
|
7
|
-
# The writer runs the maintenance clock. The engine requires it too, but a
|
|
8
|
-
# writer forked by the Puma plugin before the app has finished loading must
|
|
9
|
-
# not depend on that ordering.
|
|
10
|
-
require "railwatch/maintenance"
|
|
11
|
-
|
|
12
|
-
module Railwatch
|
|
13
|
-
# The embedded install's writer process. Puma workers hand their batches
|
|
14
|
-
# here over a Unix socket (Transport::Socket) and this process, and only
|
|
15
|
-
# this process, maps them, writes the two SQLite files, folds the rollups,
|
|
16
|
-
# groups exceptions, and runs the maintenance clock. Its GVL is its own, so
|
|
17
|
-
# none of that work is ever interleaved with a request.
|
|
18
|
-
#
|
|
19
|
-
# Forked from the Puma master by the gem's Puma plugin (lib/puma/plugin/
|
|
20
|
-
# railwatch.rb), the same shape as Solid Queue's `solid_queue_mode :fork`.
|
|
21
|
-
# It exits when its parent does. It can also be run on its own for a
|
|
22
|
-
# deployment that wants it supervised separately:
|
|
23
|
-
# bin/rails runner 'Railwatch::Writer.run!'
|
|
24
|
-
module Writer
|
|
25
|
-
THREADS = 2
|
|
26
|
-
MAX_REQUEST_BYTES = 64 << 20
|
|
27
|
-
# Inflated request ceiling: a batch is at most config.batch_bytes of
|
|
28
|
-
# NDJSON plus envelope, so anything past this is not a batch.
|
|
29
|
-
MAX_INFLATED_BYTES = 128 << 20
|
|
30
|
-
# Connections waiting for a writer thread. Past this the accept loop
|
|
31
|
-
# closes new ones outright; the worker sees a reset and retries on its
|
|
32
|
-
# backoff ladder, which is the right pressure to apply.
|
|
33
|
-
MAX_PENDING = 64
|
|
34
|
-
PARENT_POLL = 2
|
|
35
|
-
# How long a shutdown waits for a batch that is still being written.
|
|
36
|
-
SHUTDOWN_DRAIN = 5
|
|
37
|
-
# A batch write that has run this long is not slow, it is stuck: a
|
|
38
|
-
# SQLite lock that never clears, a t-digest on a pathological input, a
|
|
39
|
-
# connection pool that lost a connection. The supervisor cannot tell a
|
|
40
|
-
# wedged writer from a busy one (kill 0 answers for both), so the writer
|
|
41
|
-
# judges itself: past this the process exits and the plugin respawns it.
|
|
42
|
-
# The batch is retained on the worker, retried, and written by the new
|
|
43
|
-
# writer, with the ledger making that safe. Includes the time spent
|
|
44
|
-
# reading the request, so a client that connects and stops sending is a
|
|
45
|
-
# wedge too, not a permanently parked thread.
|
|
46
|
-
WEDGE_TIMEOUT = 60
|
|
47
|
-
# Linux caps a Unix socket path at 108 bytes including the terminator. A
|
|
48
|
-
# path past that cannot be bound or connected to at all, so the plugin
|
|
49
|
-
# refuses to start rather than fail on every batch, and the doctor says
|
|
50
|
-
# which path to set.
|
|
51
|
-
MAX_SOCKET_PATH = 107
|
|
52
|
-
|
|
53
|
-
@running = false
|
|
54
|
-
@expected = false
|
|
55
|
-
@server = nil
|
|
56
|
-
@stopping = false
|
|
57
|
-
@in_flight = {}
|
|
58
|
-
@in_flight_mutex = Mutex.new
|
|
59
|
-
@socket_ino = nil
|
|
60
|
-
|
|
61
|
-
module_function
|
|
62
|
-
|
|
63
|
-
# True in the writer process itself.
|
|
64
|
-
def running? = @running
|
|
65
|
-
|
|
66
|
-
# True in every process that should hand batches to a writer: set by the
|
|
67
|
-
# Puma plugin in the master before the workers fork, so they inherit it.
|
|
68
|
-
# A process without it (a runner, a Solid Queue worker) writes its own.
|
|
69
|
-
def expected? = @expected
|
|
70
|
-
|
|
71
|
-
def expected!
|
|
72
|
-
@expected = true
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Forks a child that is the writer from its first instruction. ForkTracker
|
|
76
|
-
# fires Railwatch.restart_after_fork! in the child before the block runs,
|
|
77
|
-
# and that reset chooses threads and transport by process role; setting
|
|
78
|
-
# the role here, before the fork, is what makes the child come up with
|
|
79
|
-
# the writer's set (reporter + Transport::Local + maintenance) rather
|
|
80
|
-
# than a web worker's followed by the writer's. The parent puts the flag
|
|
81
|
-
# back the moment fork returns, or fails.
|
|
82
|
-
def fork_writer!
|
|
83
|
-
@running = true
|
|
84
|
-
fork do
|
|
85
|
-
yield
|
|
86
|
-
exit!(0)
|
|
87
|
-
end
|
|
88
|
-
ensure
|
|
89
|
-
@running = false
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Whether something is listening at the configured socket right now.
|
|
93
|
-
def listening?(path = Railwatch.config.writer_socket_path)
|
|
94
|
-
return false if path.nil?
|
|
95
|
-
|
|
96
|
-
UNIXSocket.new(path).close
|
|
97
|
-
true
|
|
98
|
-
rescue SystemCallError, ArgumentError
|
|
99
|
-
# ArgumentError: the path is over the kernel's sun_path limit (108
|
|
100
|
-
# bytes on Linux), which a deep checkout can hit; see usable_path?.
|
|
101
|
-
false
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def usable_path?(path)
|
|
105
|
-
!path.nil? && path.bytesize <= MAX_SOCKET_PATH
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Serves until stopped or until `parent` (a pid) is gone. Never returns
|
|
109
|
-
# to a caller that expects the app to keep running; it is the process.
|
|
110
|
-
def run!(parent: nil)
|
|
111
|
-
path = Railwatch.config.writer_socket_path or raise ArgumentError, "Railwatch.config.writer_socket is unset"
|
|
112
|
-
unless usable_path?(path)
|
|
113
|
-
raise ArgumentError, "writer socket path is #{path.bytesize} bytes; Linux allows #{MAX_SOCKET_PATH}. " \
|
|
114
|
-
"Set RAILWATCH_WRITER_SOCKET to a shorter path (an absolute one under /tmp or /run works)."
|
|
115
|
-
end
|
|
116
|
-
@running = true
|
|
117
|
-
@stopping = false
|
|
118
|
-
$PROGRAM_NAME = "railwatch-writer: #{File.basename(path)}"
|
|
119
|
-
trap_signals
|
|
120
|
-
bind(path)
|
|
121
|
-
Railwatch.debug { "writer listening at #{path} (pid #{Process.pid})" }
|
|
122
|
-
# Under the Puma plugin ForkTracker has already reset and started
|
|
123
|
-
# everything for the writer role; run standalone (bin/rails runner)
|
|
124
|
-
# nothing has, so these are idempotent second calls at worst.
|
|
125
|
-
Railwatch.reporter.ensure_thread
|
|
126
|
-
Maintenance.start!
|
|
127
|
-
watch_parent(parent) if parent
|
|
128
|
-
watch_wedge
|
|
129
|
-
serve
|
|
130
|
-
ensure
|
|
131
|
-
cleanup(path)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# The socket's directory is created 0700 and the socket itself 0600: the
|
|
135
|
-
# writer accepts records as trusted, so only this user may reach it. A
|
|
136
|
-
# socket file already there is unlinked only when nothing answers on it;
|
|
137
|
-
# a live writer (an old container sharing a volume, a writer of a Puma
|
|
138
|
-
# that has not finished restarting) keeps its socket and this one fails
|
|
139
|
-
# to start, which the plugin retries.
|
|
140
|
-
def bind(path)
|
|
141
|
-
dir = File.dirname(path)
|
|
142
|
-
FileUtils.mkdir_p(dir, mode: 0o700)
|
|
143
|
-
if File.exist?(path)
|
|
144
|
-
raise Errno::EADDRINUSE, "#{path}: another writer is listening" if listening?(path)
|
|
145
|
-
|
|
146
|
-
File.unlink(path)
|
|
147
|
-
end
|
|
148
|
-
@server = UNIXServer.new(path)
|
|
149
|
-
File.chmod(0o600, path)
|
|
150
|
-
@socket_ino = File.stat(path).ino
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def stop!
|
|
154
|
-
@stopping = true
|
|
155
|
-
@server&.close
|
|
156
|
-
rescue IOError
|
|
157
|
-
nil
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# Handles one already-accepted connection: read the request, write the
|
|
161
|
-
# batch, reply. Public so a spec can drive it without a real socket. The
|
|
162
|
-
# whole thing, request read included, counts toward the wedge guard.
|
|
163
|
-
def handle(sock)
|
|
164
|
-
track_in_flight do
|
|
165
|
-
request = read_request(sock)
|
|
166
|
-
result = write_batch(request)
|
|
167
|
-
reply = JSON.generate(result.to_h)
|
|
168
|
-
write_with_deadline(sock, [ reply.bytesize ].pack("N") + reply)
|
|
169
|
-
end
|
|
170
|
-
rescue StandardError => e
|
|
171
|
-
Railwatch.debug { "writer request failed: #{e.class}: #{e.message}" }
|
|
172
|
-
reply = JSON.generate(ok: false, error: "#{e.class}: #{e.message}", retryable_error: true)
|
|
173
|
-
begin
|
|
174
|
-
write_with_deadline(sock, [ reply.bytesize ].pack("N") + reply)
|
|
175
|
-
rescue SystemCallError, IOError, Timeout::Error
|
|
176
|
-
nil
|
|
177
|
-
end
|
|
178
|
-
ensure
|
|
179
|
-
sock.close rescue nil
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# Exactly what Transport::Local#write does; the writer is that code
|
|
183
|
-
# running in a process of its own.
|
|
184
|
-
def write_batch(request)
|
|
185
|
-
Rails.application.executor.wrap do
|
|
186
|
-
environment = Environment.current
|
|
187
|
-
batch_id = request["batch_id"]
|
|
188
|
-
if (ledger = environment.with_telemetry { Ingest::Batch.committed(batch_id) })
|
|
189
|
-
next Transport::Local::Result.new(ok: true, status: 200, accepted: ledger.accepted, rejected: ledger.rejected, rejections: [])
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
result = Ingest::Batch.new(environment, request["records"], dropped_by_client: request["dropped"].to_i,
|
|
193
|
-
backpressure_factor: request["backpressure_factor"], gem_version: Railwatch::VERSION,
|
|
194
|
-
embedded: true, batch_id: batch_id).write!
|
|
195
|
-
Transport::Local::Result.new(ok: true, status: 200, accepted: result.accepted, rejected: result.rejected,
|
|
196
|
-
rejections: result.rejections.first(10))
|
|
197
|
-
rescue StandardError => e
|
|
198
|
-
Railwatch.debug { "writer batch failed: #{e.class}: #{e.message}" }
|
|
199
|
-
Transport::Local::Result.new(ok: false, error: "#{e.class}: #{e.message}", retryable_error: true)
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def serve
|
|
204
|
-
queue = Thread::SizedQueue.new(MAX_PENDING)
|
|
205
|
-
workers = Array.new(THREADS) do |i|
|
|
206
|
-
Thread.new do
|
|
207
|
-
Thread.current.name = "railwatch-writer-#{i}"
|
|
208
|
-
while (sock = queue.pop)
|
|
209
|
-
handle(sock)
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
loop do
|
|
214
|
-
sock = begin
|
|
215
|
-
@server.accept
|
|
216
|
-
rescue IOError, Errno::EBADF, Errno::EINVAL
|
|
217
|
-
break
|
|
218
|
-
end
|
|
219
|
-
# A full queue means both threads are busy and MAX_PENDING more are
|
|
220
|
-
# waiting; refusing here is what turns that into worker backoff
|
|
221
|
-
# instead of an unbounded pile of open descriptors.
|
|
222
|
-
sock.close unless queue.push(sock, true)
|
|
223
|
-
rescue ThreadError
|
|
224
|
-
sock&.close
|
|
225
|
-
end
|
|
226
|
-
ensure
|
|
227
|
-
# Bounded: a batch still running five seconds after the listener closed
|
|
228
|
-
# does not hold the shutdown open. Its transaction rolls back when the
|
|
229
|
-
# process exits and the client retries it by batch id against the next
|
|
230
|
-
# writer, so the batch is not lost -- it is just not finished here.
|
|
231
|
-
THREADS.times { queue&.push(nil) }
|
|
232
|
-
workers&.each { |t| t.join(SHUTDOWN_DRAIN) }
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# Length-prefixed gzip JSON, read under the wedge deadline, inflated in
|
|
236
|
-
# bounded steps so a request that decompresses past MAX_INFLATED_BYTES
|
|
237
|
-
# is refused before it is materialised.
|
|
238
|
-
def read_request(sock)
|
|
239
|
-
deadline = Clock.monotonic + WEDGE_TIMEOUT
|
|
240
|
-
header = read_exactly(sock, 4, deadline)
|
|
241
|
-
length = header.unpack1("N")
|
|
242
|
-
raise IOError, "request too large (#{length} bytes)" if length > MAX_REQUEST_BYTES
|
|
243
|
-
|
|
244
|
-
body = read_exactly(sock, length, deadline)
|
|
245
|
-
JSON.parse(inflate_bounded(body))
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
INFLATE_CHUNK = 64 << 10
|
|
249
|
-
|
|
250
|
-
def inflate_bounded(body)
|
|
251
|
-
out = +""
|
|
252
|
-
inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
|
|
253
|
-
# byteslice, not each_char: the body is gzip, so "characters" are a
|
|
254
|
-
# fiction its encoding may not even support, and walking a 64 MB
|
|
255
|
-
# request one character at a time to rebuild 64 KiB strings costs more
|
|
256
|
-
# than the inflate it is feeding.
|
|
257
|
-
offset = 0
|
|
258
|
-
while offset < body.bytesize
|
|
259
|
-
out << inflater.inflate(body.byteslice(offset, INFLATE_CHUNK))
|
|
260
|
-
offset += INFLATE_CHUNK
|
|
261
|
-
raise IOError, "request inflates past #{MAX_INFLATED_BYTES} bytes" if out.bytesize > MAX_INFLATED_BYTES
|
|
262
|
-
end
|
|
263
|
-
out << inflater.finish
|
|
264
|
-
raise IOError, "request inflates past #{MAX_INFLATED_BYTES} bytes" if out.bytesize > MAX_INFLATED_BYTES
|
|
265
|
-
|
|
266
|
-
out
|
|
267
|
-
ensure
|
|
268
|
-
inflater&.close
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
def read_exactly(sock, count, deadline)
|
|
272
|
-
buffer = +""
|
|
273
|
-
while buffer.bytesize < count
|
|
274
|
-
remaining = deadline - Clock.monotonic
|
|
275
|
-
raise Timeout::Error, "client did not finish sending within #{WEDGE_TIMEOUT}s" if remaining <= 0
|
|
276
|
-
raise IOError, "client closed the connection" unless sock.wait_readable(remaining)
|
|
277
|
-
|
|
278
|
-
chunk = sock.read_nonblock(count - buffer.bytesize, exception: false)
|
|
279
|
-
case chunk
|
|
280
|
-
when :wait_readable then next
|
|
281
|
-
when nil then raise IOError, "client closed the connection"
|
|
282
|
-
else buffer << chunk
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
buffer
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
def write_with_deadline(sock, data, timeout: Railwatch.config.timeout)
|
|
289
|
-
deadline = Clock.monotonic + timeout
|
|
290
|
-
offset = 0
|
|
291
|
-
while offset < data.bytesize
|
|
292
|
-
remaining = deadline - Clock.monotonic
|
|
293
|
-
raise Timeout::Error, "client did not read the reply within #{timeout}s" if remaining <= 0
|
|
294
|
-
raise IOError, "client closed the connection" unless sock.wait_writable(remaining)
|
|
295
|
-
|
|
296
|
-
written = sock.write_nonblock(data.byteslice(offset..), exception: false)
|
|
297
|
-
offset += written if written.is_a?(Integer)
|
|
298
|
-
end
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
def watch_parent(parent)
|
|
302
|
-
Thread.new do
|
|
303
|
-
Thread.current.name = "railwatch-writer-parent"
|
|
304
|
-
until @stopping
|
|
305
|
-
sleep PARENT_POLL
|
|
306
|
-
next if Process.ppid == parent
|
|
307
|
-
|
|
308
|
-
Railwatch.debug { "writer: parent #{parent} is gone, stopping" }
|
|
309
|
-
stop!
|
|
310
|
-
end
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
# Keyed by invocation, never by batch id: a worker that timed out and
|
|
315
|
-
# retried the same batch must not be able to overwrite, or delete, the
|
|
316
|
-
# entry of the original invocation still running.
|
|
317
|
-
def track_in_flight
|
|
318
|
-
token = Object.new
|
|
319
|
-
@in_flight_mutex.synchronize { @in_flight[token] = Clock.monotonic }
|
|
320
|
-
yield
|
|
321
|
-
ensure
|
|
322
|
-
@in_flight_mutex.synchronize { @in_flight.delete(token) }
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
# The oldest write still running, in seconds, or nil.
|
|
326
|
-
def oldest_in_flight
|
|
327
|
-
@in_flight_mutex.synchronize do
|
|
328
|
-
started = @in_flight.values.min
|
|
329
|
-
started && Clock.monotonic - started
|
|
330
|
-
end
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
# Exits the process, with a note, the moment any single write has run
|
|
334
|
-
# past WEDGE_TIMEOUT. Deliberately exit! and not stop!: a stuck write is
|
|
335
|
-
# holding whatever is stuck, and a graceful drain would wait on it.
|
|
336
|
-
def watch_wedge
|
|
337
|
-
Thread.new do
|
|
338
|
-
Thread.current.name = "railwatch-writer-wedge"
|
|
339
|
-
until @stopping
|
|
340
|
-
sleep PARENT_POLL
|
|
341
|
-
age = oldest_in_flight
|
|
342
|
-
next unless age && age > WEDGE_TIMEOUT
|
|
343
|
-
|
|
344
|
-
warn "[railwatch] writer: a batch write has run #{age.round}s (limit #{WEDGE_TIMEOUT}s); exiting so Puma restarts the writer"
|
|
345
|
-
Railwatch.notify_unrecoverable(WedgedError.new("writer batch write exceeded #{WEDGE_TIMEOUT}s"))
|
|
346
|
-
exit!(75)
|
|
347
|
-
end
|
|
348
|
-
end
|
|
349
|
-
end
|
|
350
|
-
|
|
351
|
-
class WedgedError < StandardError; end
|
|
352
|
-
|
|
353
|
-
def trap_signals
|
|
354
|
-
%w[TERM INT].each do |signal|
|
|
355
|
-
trap(signal) { Thread.new { stop! } }
|
|
356
|
-
end
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
# Unlinks the socket only if it is still the one this writer bound. A
|
|
360
|
-
# newer writer that took the path over keeps its socket.
|
|
361
|
-
def cleanup(path)
|
|
362
|
-
Maintenance.stop!
|
|
363
|
-
Railwatch.reporter.shutdown
|
|
364
|
-
if path && @socket_ino && File.exist?(path) && File.stat(path).ino == @socket_ino
|
|
365
|
-
File.unlink(path)
|
|
366
|
-
end
|
|
367
|
-
@running = false
|
|
368
|
-
end
|
|
369
|
-
end
|
|
370
|
-
end
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|