tasker-engine 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 +7 -0
- data/LICENSE +22 -0
- data/README.md +443 -0
- data/Rakefile +10 -0
- data/app/controllers/tasker/analytics_controller.rb +179 -0
- data/app/controllers/tasker/application_controller.rb +45 -0
- data/app/controllers/tasker/graphql_controller.rb +193 -0
- data/app/controllers/tasker/handlers_controller.rb +217 -0
- data/app/controllers/tasker/health_controller.rb +229 -0
- data/app/controllers/tasker/metrics_controller.rb +111 -0
- data/app/controllers/tasker/page_sort.rb +97 -0
- data/app/controllers/tasker/task_diagrams_controller.rb +30 -0
- data/app/controllers/tasker/tasks_controller.rb +123 -0
- data/app/controllers/tasker/workflow_steps_controller.rb +69 -0
- data/app/graphql/examples/all_tasks.graphql +22 -0
- data/app/graphql/examples/pending_tasks.graphql +23 -0
- data/app/graphql/tasker/graph_ql_types/annotation_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_argument.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_connection.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/base_edge.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_enum.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_field.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_input_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_interface.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_scalar.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_union.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_object_map_type.rb +18 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_type.rb +13 -0
- data/app/graphql/tasker/graph_ql_types/mutation_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_step_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_task_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/named_tasks_named_step_type.rb +19 -0
- data/app/graphql/tasker/graph_ql_types/node_type.rb +12 -0
- data/app/graphql/tasker/graph_ql_types/query_type.rb +20 -0
- data/app/graphql/tasker/graph_ql_types/task_annotation_type.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_interface.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_type.rb +26 -0
- data/app/graphql/tasker/graph_ql_types/workflow_step_type.rb +154 -0
- data/app/graphql/tasker/graph_ql_types.rb +42 -0
- data/app/graphql/tasker/mutations/base_mutation.rb +13 -0
- data/app/graphql/tasker/mutations/cancel_step.rb +29 -0
- data/app/graphql/tasker/mutations/cancel_task.rb +29 -0
- data/app/graphql/tasker/mutations/create_task.rb +52 -0
- data/app/graphql/tasker/mutations/update_step.rb +36 -0
- data/app/graphql/tasker/mutations/update_task.rb +41 -0
- data/app/graphql/tasker/queries/all_annotation_types.rb +17 -0
- data/app/graphql/tasker/queries/all_tasks.rb +23 -0
- data/app/graphql/tasker/queries/base_query.rb +9 -0
- data/app/graphql/tasker/queries/helpers.rb +16 -0
- data/app/graphql/tasker/queries/one_step.rb +24 -0
- data/app/graphql/tasker/queries/one_task.rb +18 -0
- data/app/graphql/tasker/queries/tasks_by_annotation.rb +31 -0
- data/app/graphql/tasker/queries/tasks_by_status.rb +30 -0
- data/app/graphql/tasker/tasker_rails_schema.rb +52 -0
- data/app/jobs/tasker/application_job.rb +8 -0
- data/app/jobs/tasker/metrics_export_job.rb +252 -0
- data/app/jobs/tasker/task_runner_job.rb +224 -0
- data/app/models/tasker/annotation_type.rb +26 -0
- data/app/models/tasker/application_record.rb +70 -0
- data/app/models/tasker/dependent_system.rb +26 -0
- data/app/models/tasker/dependent_system_object_map.rb +64 -0
- data/app/models/tasker/diagram/edge.rb +106 -0
- data/app/models/tasker/diagram/flowchart.rb +137 -0
- data/app/models/tasker/diagram/node.rb +99 -0
- data/app/models/tasker/named_step.rb +41 -0
- data/app/models/tasker/named_task.rb +121 -0
- data/app/models/tasker/named_tasks_named_step.rb +82 -0
- data/app/models/tasker/step_dag_relationship.rb +65 -0
- data/app/models/tasker/step_readiness_status.rb +59 -0
- data/app/models/tasker/task.rb +424 -0
- data/app/models/tasker/task_annotation.rb +36 -0
- data/app/models/tasker/task_diagram.rb +332 -0
- data/app/models/tasker/task_execution_context.rb +29 -0
- data/app/models/tasker/task_namespace.rb +41 -0
- data/app/models/tasker/task_transition.rb +235 -0
- data/app/models/tasker/workflow_step.rb +461 -0
- data/app/models/tasker/workflow_step_edge.rb +94 -0
- data/app/models/tasker/workflow_step_transition.rb +434 -0
- data/app/serializers/tasker/annotation_type_serializer.rb +8 -0
- data/app/serializers/tasker/handler_serializer.rb +109 -0
- data/app/serializers/tasker/task_annotation_serializer.rb +32 -0
- data/app/serializers/tasker/task_serializer.rb +168 -0
- data/app/serializers/tasker/workflow_step_serializer.rb +27 -0
- data/app/services/tasker/analytics_service.rb +409 -0
- data/app/views/tasker/task/_diagram.html.erb +32 -0
- data/config/initializers/dry_struct.rb +11 -0
- data/config/initializers/statesman.rb +6 -0
- data/config/initializers/tasker_orchestration.rb +17 -0
- data/config/initializers/time_formats.rb +4 -0
- data/config/routes.rb +34 -0
- data/config/tasker/subscriptions/example_integrations.yml +67 -0
- data/config/tasker/system_events.yml +305 -0
- data/db/functions/calculate_dependency_levels_v01.sql +45 -0
- data/db/functions/get_analytics_metrics_v01.sql +137 -0
- data/db/functions/get_slowest_steps_v01.sql +82 -0
- data/db/functions/get_slowest_tasks_v01.sql +96 -0
- data/db/functions/get_step_readiness_status_batch_v01.sql +140 -0
- data/db/functions/get_step_readiness_status_v01.sql +139 -0
- data/db/functions/get_system_health_counts_v01.sql +108 -0
- data/db/functions/get_task_execution_context_v01.sql +108 -0
- data/db/functions/get_task_execution_contexts_batch_v01.sql +104 -0
- data/db/init/schema.sql +2277 -0
- data/db/migrate/20250701165431_initial_tasker_schema.rb +116 -0
- data/db/views/tasker_step_dag_relationships_v01.sql +69 -0
- data/docs/APPLICATION_GENERATOR.md +384 -0
- data/docs/AUTH.md +1780 -0
- data/docs/CIRCUIT_BREAKER.md +224 -0
- data/docs/DEVELOPER_GUIDE.md +2665 -0
- data/docs/EVENT_SYSTEM.md +637 -0
- data/docs/EXECUTION_CONFIGURATION.md +341 -0
- data/docs/FLOW_CHART.md +149 -0
- data/docs/HEALTH.md +542 -0
- data/docs/METRICS.md +731 -0
- data/docs/OPTIMIZATION_PLAN.md +1479 -0
- data/docs/OVERVIEW.md +552 -0
- data/docs/QUICK_START.md +270 -0
- data/docs/REGISTRY_SYSTEMS.md +373 -0
- data/docs/REST_API.md +632 -0
- data/docs/ROADMAP.md +221 -0
- data/docs/SQL_FUNCTIONS.md +1408 -0
- data/docs/TASK_DIAGRAM.md +252 -0
- data/docs/TASK_EXECUTION_CONTROL_FLOW.md +237 -0
- data/docs/TELEMETRY.md +795 -0
- data/docs/TROUBLESHOOTING.md +756 -0
- data/docs/TaskHandlerGenerator.html +255 -0
- data/docs/Tasker/Analysis/RuntimeGraphAnalyzer.html +907 -0
- data/docs/Tasker/Analysis/TemplateGraphAnalyzer.html +1236 -0
- data/docs/Tasker/Analysis.html +117 -0
- data/docs/Tasker/AnalyticsController.html +450 -0
- data/docs/Tasker/AnalyticsService/BottleneckAnalytics.html +816 -0
- data/docs/Tasker/AnalyticsService/PerformanceAnalytics.html +586 -0
- data/docs/Tasker/AnalyticsService.html +2221 -0
- data/docs/Tasker/AnnotationType.html +137 -0
- data/docs/Tasker/AnnotationTypeSerializer.html +124 -0
- data/docs/Tasker/ApplicationController.html +147 -0
- data/docs/Tasker/ApplicationJob.html +128 -0
- data/docs/Tasker/ApplicationRecord.html +378 -0
- data/docs/Tasker/Authentication/AuthenticationError.html +124 -0
- data/docs/Tasker/Authentication/ConfigurationError.html +124 -0
- data/docs/Tasker/Authentication/Coordinator.html +242 -0
- data/docs/Tasker/Authentication/Interface.html +560 -0
- data/docs/Tasker/Authentication/InterfaceError.html +124 -0
- data/docs/Tasker/Authentication/NoneAuthenticator.html +338 -0
- data/docs/Tasker/Authentication.html +119 -0
- data/docs/Tasker/Authorization/AuthorizationError.html +139 -0
- data/docs/Tasker/Authorization/BaseCoordinator.html +927 -0
- data/docs/Tasker/Authorization/ConfigurationError.html +153 -0
- data/docs/Tasker/Authorization/ResourceConstants/ACTIONS.html +428 -0
- data/docs/Tasker/Authorization/ResourceConstants/RESOURCES.html +365 -0
- data/docs/Tasker/Authorization/ResourceConstants.html +146 -0
- data/docs/Tasker/Authorization/ResourceRegistry.html +882 -0
- data/docs/Tasker/Authorization/UnauthorizedError.html +153 -0
- data/docs/Tasker/Authorization.html +582 -0
- data/docs/Tasker/CacheCapabilities.html +167 -0
- data/docs/Tasker/CacheStrategy.html +1297 -0
- data/docs/Tasker/Concerns/Authenticatable.html +116 -0
- data/docs/Tasker/Concerns/Authorizable/AdminStatusChecker.html +256 -0
- data/docs/Tasker/Concerns/Authorizable.html +816 -0
- data/docs/Tasker/Concerns/ControllerAuthorizable.html +157 -0
- data/docs/Tasker/Concerns/EventPublisher.html +4023 -0
- data/docs/Tasker/Concerns/IdempotentStateTransitions.html +806 -0
- data/docs/Tasker/Concerns/LifecycleEventHelpers.html +129 -0
- data/docs/Tasker/Concerns/OrchestrationPublisher.html +129 -0
- data/docs/Tasker/Concerns/StateMachineBase/ClassMethods.html +1075 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase/ClassMethods.html +191 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase.html +126 -0
- data/docs/Tasker/Concerns/StateMachineBase.html +153 -0
- data/docs/Tasker/Concerns/StructuredLogging.html +1413 -0
- data/docs/Tasker/Concerns.html +117 -0
- data/docs/Tasker/Configuration/AuthConfiguration.html +1023 -0
- data/docs/Tasker/Configuration/ConfigurationProxy.html +581 -0
- data/docs/Tasker/Configuration/DatabaseConfiguration.html +475 -0
- data/docs/Tasker/Configuration/EngineConfiguration.html +1265 -0
- data/docs/Tasker/Configuration/HealthConfiguration.html +791 -0
- data/docs/Tasker/Configuration/TelemetryConfiguration.html +1308 -0
- data/docs/Tasker/Configuration/TelemetryConfigurationProxy.html +388 -0
- data/docs/Tasker/Configuration.html +1669 -0
- data/docs/Tasker/ConfigurationError.html +143 -0
- data/docs/Tasker/ConfiguredTask.html +514 -0
- data/docs/Tasker/Constants/EventDefinitions.html +590 -0
- data/docs/Tasker/Constants/LifecycleEvents.html +137 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Step.html +152 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Task.html +142 -0
- data/docs/Tasker/Constants/ObservabilityEvents.html +126 -0
- data/docs/Tasker/Constants/RegistryEvents.html +285 -0
- data/docs/Tasker/Constants/StepEvents.html +177 -0
- data/docs/Tasker/Constants/TaskEvents.html +167 -0
- data/docs/Tasker/Constants/TaskExecution/ExecutionStatus.html +207 -0
- data/docs/Tasker/Constants/TaskExecution/HealthStatus.html +191 -0
- data/docs/Tasker/Constants/TaskExecution/RecommendedAction.html +207 -0
- data/docs/Tasker/Constants/TaskExecution.html +126 -0
- data/docs/Tasker/Constants/TaskFinalization/ErrorMessages.html +132 -0
- data/docs/Tasker/Constants/TaskFinalization/PendingReasons.html +207 -0
- data/docs/Tasker/Constants/TaskFinalization/ReenqueueReasons.html +239 -0
- data/docs/Tasker/Constants/TaskFinalization.html +126 -0
- data/docs/Tasker/Constants/TaskStatuses.html +223 -0
- data/docs/Tasker/Constants/TestEvents.html +163 -0
- data/docs/Tasker/Constants/WorkflowEvents.html +222 -0
- data/docs/Tasker/Constants/WorkflowStepStatuses.html +223 -0
- data/docs/Tasker/Constants.html +561 -0
- data/docs/Tasker/DependentSystem.html +137 -0
- data/docs/Tasker/DependentSystemObjectMap.html +250 -0
- data/docs/Tasker/DetectorRegistry.html +598 -0
- data/docs/Tasker/Diagram/Edge.html +1191 -0
- data/docs/Tasker/Diagram/Flowchart.html +1539 -0
- data/docs/Tasker/Diagram/Node.html +1165 -0
- data/docs/Tasker/Diagram.html +117 -0
- data/docs/Tasker/Engine.html +215 -0
- data/docs/Tasker/Error.html +139 -0
- data/docs/Tasker/Events/Bus.html +1226 -0
- data/docs/Tasker/Events/Catalog/CatalogPrinter.html +258 -0
- data/docs/Tasker/Events/Catalog/CustomEventRegistrar.html +276 -0
- data/docs/Tasker/Events/Catalog/ExamplePayloadGenerator.html +294 -0
- data/docs/Tasker/Events/Catalog.html +1291 -0
- data/docs/Tasker/Events/CustomRegistry.html +943 -0
- data/docs/Tasker/Events/DefinitionLoader.html +575 -0
- data/docs/Tasker/Events/EventPayloadBuilder/ErrorInfoExtractor.html +286 -0
- data/docs/Tasker/Events/EventPayloadBuilder/StepPayloadBuilder.html +312 -0
- data/docs/Tasker/Events/EventPayloadBuilder.html +664 -0
- data/docs/Tasker/Events/Publisher.html +365 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer/ErrorTypeClassifier.html +1128 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer.html +270 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/MetricTagsExtractor.html +266 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber.html +2556 -0
- data/docs/Tasker/Events/Subscribers/MetricsSubscriber.html +723 -0
- data/docs/Tasker/Events/Subscribers/TelemetrySubscriber.html +2251 -0
- data/docs/Tasker/Events/Subscribers.html +117 -0
- data/docs/Tasker/Events/SubscriptionLoader.html +493 -0
- data/docs/Tasker/Events.html +294 -0
- data/docs/Tasker/EventsGenerator.html +459 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics/AnalyticsMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics.html +412 -0
- data/docs/Tasker/Functions/FunctionBasedDependencyLevels.html +598 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps/SlowestStep.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks/SlowestTask.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedStepReadinessStatus.html +1457 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts/HealthMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts.html +370 -0
- data/docs/Tasker/Functions/FunctionBasedTaskExecutionContext.html +1250 -0
- data/docs/Tasker/Functions/FunctionWrapper.html +479 -0
- data/docs/Tasker/Functions.html +117 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator/UsageInstructionsFormatter.html +244 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator.html +373 -0
- data/docs/Tasker/Generators/AuthorizationCoordinatorGenerator.html +430 -0
- data/docs/Tasker/Generators/SubscriberGenerator.html +377 -0
- data/docs/Tasker/Generators/TaskHandlerGenerator.html +263 -0
- data/docs/Tasker/Generators.html +117 -0
- data/docs/Tasker/GraphQLTypes/AnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/BaseArgument.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseConnection.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseEdge.html +130 -0
- data/docs/Tasker/GraphQLTypes/BaseEnum.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseField.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInputObject.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInterface.html +116 -0
- data/docs/Tasker/GraphQLTypes/BaseObject.html +128 -0
- data/docs/Tasker/GraphQLTypes/BaseScalar.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseUnion.html +124 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemObjectMapType.html +132 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemType.html +132 -0
- data/docs/Tasker/GraphQLTypes/MutationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTaskType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTasksNamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NodeType.html +118 -0
- data/docs/Tasker/GraphQLTypes/QueryType.html +139 -0
- data/docs/Tasker/GraphQLTypes/TaskAnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/TaskInterface.html +111 -0
- data/docs/Tasker/GraphQLTypes/TaskType.html +201 -0
- data/docs/Tasker/GraphQLTypes/WorkflowStepType.html +694 -0
- data/docs/Tasker/GraphQLTypes.html +130 -0
- data/docs/Tasker/GraphqlController.html +251 -0
- data/docs/Tasker/HandlerFactory.html +1518 -0
- data/docs/Tasker/HandlerSerializer.html +682 -0
- data/docs/Tasker/HandlersController.html +574 -0
- data/docs/Tasker/HashIdentityStrategy.html +278 -0
- data/docs/Tasker/Health/ReadinessChecker.html +712 -0
- data/docs/Tasker/Health/StatusChecker.html +653 -0
- data/docs/Tasker/Health.html +117 -0
- data/docs/Tasker/HealthController.html +523 -0
- data/docs/Tasker/IdentityStrategy.html +276 -0
- data/docs/Tasker/InvalidTaskHandlerConfig.html +135 -0
- data/docs/Tasker/LifecycleEvents/Events/Step.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events/Task.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events.html +204 -0
- data/docs/Tasker/LifecycleEvents/Publisher.html +132 -0
- data/docs/Tasker/LifecycleEvents.html +799 -0
- data/docs/Tasker/Logging/CorrelationIdGenerator.html +688 -0
- data/docs/Tasker/Logging.html +115 -0
- data/docs/Tasker/MetricsController.html +293 -0
- data/docs/Tasker/MetricsExportJob.html +414 -0
- data/docs/Tasker/Mutations/BaseMutation.html +128 -0
- data/docs/Tasker/Mutations/CancelStep.html +219 -0
- data/docs/Tasker/Mutations/CancelTask.html +221 -0
- data/docs/Tasker/Mutations/CreateTask.html +243 -0
- data/docs/Tasker/Mutations/UpdateStep.html +243 -0
- data/docs/Tasker/Mutations/UpdateTask.html +243 -0
- data/docs/Tasker/Mutations.html +117 -0
- data/docs/Tasker/NamedStep.html +216 -0
- data/docs/Tasker/NamedTask.html +910 -0
- data/docs/Tasker/NamedTasksNamedStep.html +435 -0
- data/docs/Tasker/Orchestration/BackoffCalculator.html +404 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder/ConfigValidator.html +258 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder.html +435 -0
- data/docs/Tasker/Orchestration/ConnectionPoolIntelligence.html +513 -0
- data/docs/Tasker/Orchestration/Coordinator.html +641 -0
- data/docs/Tasker/Orchestration/FutureStateAnalyzer.html +1045 -0
- data/docs/Tasker/Orchestration/Orchestrator.html +679 -0
- data/docs/Tasker/Orchestration/PluginIntegration.html +1127 -0
- data/docs/Tasker/Orchestration/ResponseProcessor.html +504 -0
- data/docs/Tasker/Orchestration/RetryHeaderParser.html +304 -0
- data/docs/Tasker/Orchestration/StepExecutor.html +995 -0
- data/docs/Tasker/Orchestration/StepSequenceFactory.html +644 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/BlockageChecker.html +264 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ContextManager.html +254 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/DelayCalculator.html +556 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationDecisionMaker.html +348 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationProcessor.html +286 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReasonDeterminer.html +432 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReenqueueManager.html +296 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/UnclearStateHandler.html +314 -0
- data/docs/Tasker/Orchestration/TaskFinalizer.html +1212 -0
- data/docs/Tasker/Orchestration/TaskInitializer.html +766 -0
- data/docs/Tasker/Orchestration/TaskReenqueuer.html +506 -0
- data/docs/Tasker/Orchestration/ViableStepDiscovery.html +442 -0
- data/docs/Tasker/Orchestration/WorkflowCoordinator.html +510 -0
- data/docs/Tasker/Orchestration.html +130 -0
- data/docs/Tasker/PageSort/PageSortParamsBuilder.html +296 -0
- data/docs/Tasker/PageSort.html +247 -0
- data/docs/Tasker/PermanentError.html +518 -0
- data/docs/Tasker/ProceduralError.html +147 -0
- data/docs/Tasker/Queries/AllAnnotationTypes.html +217 -0
- data/docs/Tasker/Queries/AllTasks.html +221 -0
- data/docs/Tasker/Queries/BaseQuery.html +128 -0
- data/docs/Tasker/Queries/Helpers.html +187 -0
- data/docs/Tasker/Queries/OneStep.html +225 -0
- data/docs/Tasker/Queries/OneTask.html +217 -0
- data/docs/Tasker/Queries/TasksByAnnotation.html +231 -0
- data/docs/Tasker/Queries/TasksByStatus.html +233 -0
- data/docs/Tasker/Queries.html +119 -0
- data/docs/Tasker/Railtie.html +124 -0
- data/docs/Tasker/Registry/BaseRegistry.html +1690 -0
- data/docs/Tasker/Registry/EventPublisher.html +667 -0
- data/docs/Tasker/Registry/InterfaceValidator.html +569 -0
- data/docs/Tasker/Registry/RegistrationError.html +132 -0
- data/docs/Tasker/Registry/RegistryError.html +139 -0
- data/docs/Tasker/Registry/StatisticsCollector.html +841 -0
- data/docs/Tasker/Registry/SubscriberRegistry.html +1504 -0
- data/docs/Tasker/Registry/ValidationError.html +132 -0
- data/docs/Tasker/Registry.html +119 -0
- data/docs/Tasker/RetryableError.html +515 -0
- data/docs/Tasker/StateMachine/Compatibility.html +282 -0
- data/docs/Tasker/StateMachine/InvalidStateTransition.html +135 -0
- data/docs/Tasker/StateMachine/StepStateMachine/StandardizedPayloadBuilder.html +260 -0
- data/docs/Tasker/StateMachine/StepStateMachine.html +2215 -0
- data/docs/Tasker/StateMachine/TaskStateMachine.html +734 -0
- data/docs/Tasker/StateMachine.html +602 -0
- data/docs/Tasker/StepDagRelationship.html +657 -0
- data/docs/Tasker/StepHandler/Api/Config.html +1091 -0
- data/docs/Tasker/StepHandler/Api.html +884 -0
- data/docs/Tasker/StepHandler/AutomaticEventPublishing.html +321 -0
- data/docs/Tasker/StepHandler/Base.html +970 -0
- data/docs/Tasker/StepHandler.html +119 -0
- data/docs/Tasker/StepReadinessStatus.html +836 -0
- data/docs/Tasker/Task.html +2575 -0
- data/docs/Tasker/TaskAnnotation.html +137 -0
- data/docs/Tasker/TaskAnnotationSerializer.html +124 -0
- data/docs/Tasker/TaskBuilder/StepNameValidator.html +264 -0
- data/docs/Tasker/TaskBuilder/StepTemplateDefiner.html +264 -0
- data/docs/Tasker/TaskBuilder.html +764 -0
- data/docs/Tasker/TaskDiagram/StepToStepEdgeBuilder.html +260 -0
- data/docs/Tasker/TaskDiagram/TaskToRootStepEdgeBuilder.html +290 -0
- data/docs/Tasker/TaskDiagram.html +548 -0
- data/docs/Tasker/TaskDiagramsController.html +240 -0
- data/docs/Tasker/TaskExecutionContext.html +469 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/ClassBasedEventRegistrar.html +238 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/YamlEventRegistrar.html +254 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner.html +988 -0
- data/docs/Tasker/TaskHandler/ClassMethods.html +357 -0
- data/docs/Tasker/TaskHandler/InstanceMethods.html +1396 -0
- data/docs/Tasker/TaskHandler/StepGroup.html +1748 -0
- data/docs/Tasker/TaskHandler.html +271 -0
- data/docs/Tasker/TaskNamespace.html +312 -0
- data/docs/Tasker/TaskRunnerJob.html +406 -0
- data/docs/Tasker/TaskSerializer.html +474 -0
- data/docs/Tasker/TaskTransition.html +1517 -0
- data/docs/Tasker/TaskWorkflowSummary.html +988 -0
- data/docs/Tasker/TaskerRailsSchema/InvalidObjectTypeError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema/TypeResolutionError.html +139 -0
- data/docs/Tasker/TaskerRailsSchema/UnknownInterfaceError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema.html +384 -0
- data/docs/Tasker/TasksController.html +595 -0
- data/docs/Tasker/Telemetry/EventMapping.html +1307 -0
- data/docs/Tasker/Telemetry/EventRouter.html +2178 -0
- data/docs/Tasker/Telemetry/Events/ExportEvents.html +246 -0
- data/docs/Tasker/Telemetry/Events.html +115 -0
- data/docs/Tasker/Telemetry/ExportCoordinator/DistributedLockTimeoutError.html +135 -0
- data/docs/Tasker/Telemetry/ExportCoordinator.html +2137 -0
- data/docs/Tasker/Telemetry/IntelligentCacheManager.html +1083 -0
- data/docs/Tasker/Telemetry/LogBackend.html +1088 -0
- data/docs/Tasker/Telemetry/MetricTypes/Counter.html +1054 -0
- data/docs/Tasker/Telemetry/MetricTypes/Gauge.html +1270 -0
- data/docs/Tasker/Telemetry/MetricTypes/Histogram.html +1492 -0
- data/docs/Tasker/Telemetry/MetricTypes.html +153 -0
- data/docs/Tasker/Telemetry/MetricsBackend.html +2510 -0
- data/docs/Tasker/Telemetry/MetricsExportService.html +578 -0
- data/docs/Tasker/Telemetry/PluginRegistry.html +1774 -0
- data/docs/Tasker/Telemetry/Plugins/BaseExporter.html +1835 -0
- data/docs/Tasker/Telemetry/Plugins/CsvExporter.html +768 -0
- data/docs/Tasker/Telemetry/Plugins/JsonExporter.html +747 -0
- data/docs/Tasker/Telemetry/Plugins.html +117 -0
- data/docs/Tasker/Telemetry/PrometheusExporter.html +481 -0
- data/docs/Tasker/Telemetry/TraceBackend.html +891 -0
- data/docs/Tasker/Telemetry.html +130 -0
- data/docs/Tasker/Types/AuthConfig.html +886 -0
- data/docs/Tasker/Types/BackoffConfig.html +1063 -0
- data/docs/Tasker/Types/BaseConfig.html +227 -0
- data/docs/Tasker/Types/CacheConfig.html +1731 -0
- data/docs/Tasker/Types/DatabaseConfig.html +388 -0
- data/docs/Tasker/Types/DependencyGraph.html +526 -0
- data/docs/Tasker/Types/DependencyGraphConfig.html +753 -0
- data/docs/Tasker/Types/EngineConfig.html +1181 -0
- data/docs/Tasker/Types/ExecutionConfig.html +1963 -0
- data/docs/Tasker/Types/GraphEdge.html +517 -0
- data/docs/Tasker/Types/GraphMetadata.html +781 -0
- data/docs/Tasker/Types/GraphNode.html +694 -0
- data/docs/Tasker/Types/HealthConfig.html +784 -0
- data/docs/Tasker/Types/StepSequence.html +353 -0
- data/docs/Tasker/Types/StepTemplate.html +1193 -0
- data/docs/Tasker/Types/TaskRequest.html +1179 -0
- data/docs/Tasker/Types/TelemetryConfig.html +2746 -0
- data/docs/Tasker/Types.html +154 -0
- data/docs/Tasker/WorkflowStep/StepFinder.html +282 -0
- data/docs/Tasker/WorkflowStep.html +2724 -0
- data/docs/Tasker/WorkflowStepEdge.html +304 -0
- data/docs/Tasker/WorkflowStepSerializer.html +305 -0
- data/docs/Tasker/WorkflowStepTransition/TransitionDescriptionFormatter.html +282 -0
- data/docs/Tasker/WorkflowStepTransition.html +2201 -0
- data/docs/Tasker/WorkflowStepsController.html +462 -0
- data/docs/Tasker.html +452 -0
- data/docs/VISION.md +584 -0
- data/docs/WHY.md +21 -0
- data/docs/_index.html +2375 -0
- data/docs/class_list.html +54 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +503 -0
- data/docs/events/migration_plan_outcomes.md +80 -0
- data/docs/file.README.html +541 -0
- data/docs/file_list.html +59 -0
- data/docs/frames.html +22 -0
- data/docs/index.html +541 -0
- data/docs/js/app.js +344 -0
- data/docs/js/full_list.js +242 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +9182 -0
- data/docs/top-level-namespace.html +110 -0
- data/lib/generators/tasker/authenticator_generator.rb +301 -0
- data/lib/generators/tasker/authorization_coordinator_generator.rb +139 -0
- data/lib/generators/tasker/events_generator.rb +91 -0
- data/lib/generators/tasker/subscriber_generator.rb +107 -0
- data/lib/generators/tasker/task_handler_generator.rb +138 -0
- data/lib/generators/tasker/templates/api_token_authenticator.rb.erb +113 -0
- data/lib/generators/tasker/templates/api_token_authenticator_spec.rb.erb +144 -0
- data/lib/generators/tasker/templates/authorization_coordinator.rb.erb +95 -0
- data/lib/generators/tasker/templates/authorization_coordinator_spec.rb.erb +142 -0
- data/lib/generators/tasker/templates/custom_authenticator.rb.erb +108 -0
- data/lib/generators/tasker/templates/custom_authenticator_spec.rb.erb +162 -0
- data/lib/generators/tasker/templates/custom_events.yml.erb +62 -0
- data/lib/generators/tasker/templates/custom_subscriber.rb.erb +72 -0
- data/lib/generators/tasker/templates/devise_authenticator.rb.erb +101 -0
- data/lib/generators/tasker/templates/devise_authenticator_spec.rb.erb +126 -0
- data/lib/generators/tasker/templates/initialize.rb.erb +202 -0
- data/lib/generators/tasker/templates/jwt_authenticator.rb.erb +144 -0
- data/lib/generators/tasker/templates/jwt_authenticator_spec.rb.erb +298 -0
- data/lib/generators/tasker/templates/metrics_subscriber.rb.erb +258 -0
- data/lib/generators/tasker/templates/metrics_subscriber_spec.rb.erb +308 -0
- data/lib/generators/tasker/templates/omniauth_authenticator.rb.erb +135 -0
- data/lib/generators/tasker/templates/omniauth_authenticator_spec.rb.erb +196 -0
- data/lib/generators/tasker/templates/opentelemetry_initializer.rb +52 -0
- data/lib/generators/tasker/templates/subscriber.rb.erb +64 -0
- data/lib/generators/tasker/templates/subscriber_spec.rb.erb +80 -0
- data/lib/generators/tasker/templates/task_config.yaml.erb +117 -0
- data/lib/generators/tasker/templates/task_handler.rb.erb +59 -0
- data/lib/generators/tasker/templates/task_handler_spec.rb.erb +159 -0
- data/lib/tasker/analysis/runtime_graph_analyzer.rb +1168 -0
- data/lib/tasker/analysis/template_graph_analyzer.rb +328 -0
- data/lib/tasker/authentication/coordinator.rb +78 -0
- data/lib/tasker/authentication/errors.rb +9 -0
- data/lib/tasker/authentication/interface.rb +36 -0
- data/lib/tasker/authentication/none_authenticator.rb +26 -0
- data/lib/tasker/authorization/base_coordinator.rb +112 -0
- data/lib/tasker/authorization/errors.rb +26 -0
- data/lib/tasker/authorization/resource_constants.rb +74 -0
- data/lib/tasker/authorization/resource_registry.rb +143 -0
- data/lib/tasker/authorization.rb +75 -0
- data/lib/tasker/cache_capabilities.rb +131 -0
- data/lib/tasker/cache_strategy.rb +469 -0
- data/lib/tasker/concerns/authenticatable.rb +41 -0
- data/lib/tasker/concerns/authorizable.rb +204 -0
- data/lib/tasker/concerns/controller_authorizable.rb +124 -0
- data/lib/tasker/concerns/event_publisher.rb +716 -0
- data/lib/tasker/concerns/idempotent_state_transitions.rb +128 -0
- data/lib/tasker/concerns/state_machine_base.rb +218 -0
- data/lib/tasker/concerns/structured_logging.rb +387 -0
- data/lib/tasker/configuration.rb +325 -0
- data/lib/tasker/constants/event_definitions.rb +147 -0
- data/lib/tasker/constants/registry_events.rb +54 -0
- data/lib/tasker/constants.rb +417 -0
- data/lib/tasker/engine.rb +90 -0
- data/lib/tasker/errors.rb +90 -0
- data/lib/tasker/events/catalog.rb +432 -0
- data/lib/tasker/events/custom_registry.rb +175 -0
- data/lib/tasker/events/definition_loader.rb +199 -0
- data/lib/tasker/events/event_payload_builder.rb +461 -0
- data/lib/tasker/events/publisher.rb +149 -0
- data/lib/tasker/events/subscribers/base_subscriber.rb +601 -0
- data/lib/tasker/events/subscribers/metrics_subscriber.rb +120 -0
- data/lib/tasker/events/subscribers/telemetry_subscriber.rb +462 -0
- data/lib/tasker/events/subscription_loader.rb +161 -0
- data/lib/tasker/events.rb +37 -0
- data/lib/tasker/functions/function_based_analytics_metrics.rb +103 -0
- data/lib/tasker/functions/function_based_dependency_levels.rb +54 -0
- data/lib/tasker/functions/function_based_slowest_steps.rb +84 -0
- data/lib/tasker/functions/function_based_slowest_tasks.rb +84 -0
- data/lib/tasker/functions/function_based_step_readiness_status.rb +183 -0
- data/lib/tasker/functions/function_based_system_health_counts.rb +94 -0
- data/lib/tasker/functions/function_based_task_execution_context.rb +148 -0
- data/lib/tasker/functions/function_wrapper.rb +42 -0
- data/lib/tasker/functions.rb +12 -0
- data/lib/tasker/handler_factory.rb +322 -0
- data/lib/tasker/health/readiness_checker.rb +186 -0
- data/lib/tasker/health/status_checker.rb +203 -0
- data/lib/tasker/identity_strategy.rb +38 -0
- data/lib/tasker/logging/correlation_id_generator.rb +120 -0
- data/lib/tasker/orchestration/backoff_calculator.rb +184 -0
- data/lib/tasker/orchestration/connection_builder.rb +122 -0
- data/lib/tasker/orchestration/connection_pool_intelligence.rb +177 -0
- data/lib/tasker/orchestration/coordinator.rb +119 -0
- data/lib/tasker/orchestration/future_state_analyzer.rb +137 -0
- data/lib/tasker/orchestration/plugin_integration.rb +124 -0
- data/lib/tasker/orchestration/response_processor.rb +168 -0
- data/lib/tasker/orchestration/retry_header_parser.rb +78 -0
- data/lib/tasker/orchestration/step_executor.rb +941 -0
- data/lib/tasker/orchestration/step_sequence_factory.rb +67 -0
- data/lib/tasker/orchestration/task_finalizer.rb +564 -0
- data/lib/tasker/orchestration/task_initializer.rb +140 -0
- data/lib/tasker/orchestration/task_reenqueuer.rb +71 -0
- data/lib/tasker/orchestration/viable_step_discovery.rb +65 -0
- data/lib/tasker/orchestration/workflow_coordinator.rb +294 -0
- data/lib/tasker/orchestration.rb +45 -0
- data/lib/tasker/railtie.rb +9 -0
- data/lib/tasker/registry/base_registry.rb +177 -0
- data/lib/tasker/registry/event_publisher.rb +91 -0
- data/lib/tasker/registry/interface_validator.rb +140 -0
- data/lib/tasker/registry/statistics_collector.rb +381 -0
- data/lib/tasker/registry/subscriber_registry.rb +285 -0
- data/lib/tasker/registry.rb +22 -0
- data/lib/tasker/state_machine/step_state_machine.rb +508 -0
- data/lib/tasker/state_machine/task_state_machine.rb +192 -0
- data/lib/tasker/state_machine.rb +83 -0
- data/lib/tasker/step_handler/api.rb +410 -0
- data/lib/tasker/step_handler/base.rb +206 -0
- data/lib/tasker/task_builder.rb +432 -0
- data/lib/tasker/task_handler/class_methods.rb +324 -0
- data/lib/tasker/task_handler/instance_methods.rb +293 -0
- data/lib/tasker/task_handler/step_group.rb +182 -0
- data/lib/tasker/task_handler.rb +43 -0
- data/lib/tasker/telemetry/event_mapping.rb +126 -0
- data/lib/tasker/telemetry/event_router.rb +318 -0
- data/lib/tasker/telemetry/events/export_events.rb +38 -0
- data/lib/tasker/telemetry/export_coordinator.rb +497 -0
- data/lib/tasker/telemetry/intelligent_cache_manager.rb +508 -0
- data/lib/tasker/telemetry/log_backend.rb +224 -0
- data/lib/tasker/telemetry/metric_types.rb +368 -0
- data/lib/tasker/telemetry/metrics_backend.rb +1227 -0
- data/lib/tasker/telemetry/metrics_export_service.rb +392 -0
- data/lib/tasker/telemetry/plugin_registry.rb +333 -0
- data/lib/tasker/telemetry/plugins/base_exporter.rb +246 -0
- data/lib/tasker/telemetry/plugins/csv_exporter.rb +198 -0
- data/lib/tasker/telemetry/plugins/json_exporter.rb +141 -0
- data/lib/tasker/telemetry/prometheus_exporter.rb +249 -0
- data/lib/tasker/telemetry/trace_backend.rb +186 -0
- data/lib/tasker/telemetry.rb +59 -0
- data/lib/tasker/types/auth_config.rb +81 -0
- data/lib/tasker/types/backoff_config.rb +142 -0
- data/lib/tasker/types/cache_config.rb +257 -0
- data/lib/tasker/types/database_config.rb +39 -0
- data/lib/tasker/types/dependency_graph.rb +225 -0
- data/lib/tasker/types/dependency_graph_config.rb +149 -0
- data/lib/tasker/types/engine_config.rb +131 -0
- data/lib/tasker/types/execution_config.rb +289 -0
- data/lib/tasker/types/health_config.rb +84 -0
- data/lib/tasker/types/step_sequence.rb +24 -0
- data/lib/tasker/types/step_template.rb +63 -0
- data/lib/tasker/types/task_request.rb +60 -0
- data/lib/tasker/types/telemetry_config.rb +273 -0
- data/lib/tasker/types.rb +64 -0
- data/lib/tasker/version.rb +7 -0
- data/lib/tasker.rb +82 -0
- data/lib/tasks/tasker_tasks.rake +302 -0
- metadata +958 -0
@@ -0,0 +1,1227 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent-ruby'
|
4
|
+
require 'socket'
|
5
|
+
require_relative 'metric_types'
|
6
|
+
|
7
|
+
module Tasker
|
8
|
+
module Telemetry
|
9
|
+
# MetricsBackend provides thread-safe, high-performance native metrics collection
|
10
|
+
#
|
11
|
+
# This class implements Tasker's core metrics storage system with thread-safe operations,
|
12
|
+
# automatic EventRouter integration, and zero-overhead metric collection. It follows
|
13
|
+
# the singleton pattern consistent with HandlerFactory and Events::Publisher.
|
14
|
+
#
|
15
|
+
# **Phase 4.2.2.3 Enhancement: Hybrid Rails Cache Architecture**
|
16
|
+
# The backend now supports cache-agnostic dual storage combining in-memory performance
|
17
|
+
# with Rails.cache persistence and cross-container coordination.
|
18
|
+
#
|
19
|
+
# The backend supports three core metric types:
|
20
|
+
# - Counter: Monotonically increasing values (requests, errors, completions)
|
21
|
+
# - Gauge: Values that can go up/down (active connections, queue depth)
|
22
|
+
# - Histogram: Statistical distributions (latencies, sizes, durations)
|
23
|
+
#
|
24
|
+
# **Cache Store Compatibility:**
|
25
|
+
# - Redis/Memcached: Full coordination with atomic operations and locking
|
26
|
+
# - File/Memory stores: Local-only mode with clear degradation messaging
|
27
|
+
# - Automatic feature detection with appropriate strategy selection
|
28
|
+
#
|
29
|
+
# @example Basic usage
|
30
|
+
# backend = MetricsBackend.instance
|
31
|
+
# backend.counter('api_requests_total').increment
|
32
|
+
# backend.gauge('active_tasks').set(42)
|
33
|
+
# backend.histogram('task_duration_seconds').observe(1.45)
|
34
|
+
#
|
35
|
+
# @example EventRouter integration
|
36
|
+
# # Automatic metric collection based on event routing
|
37
|
+
# backend.handle_event('task.completed', { duration: 2.1, status: 'success' })
|
38
|
+
#
|
39
|
+
# @example Cache synchronization
|
40
|
+
# backend.sync_to_cache! # Periodic background sync
|
41
|
+
# backend.export_distributed_metrics # Cross-container export
|
42
|
+
#
|
43
|
+
class MetricsBackend
|
44
|
+
include Singleton
|
45
|
+
|
46
|
+
# Core metric registry storing all active metrics
|
47
|
+
# Using ConcurrentHash for thread-safe operations without locks
|
48
|
+
# @return [Concurrent::Hash] Thread-safe metric storage
|
49
|
+
attr_reader :metrics
|
50
|
+
|
51
|
+
# EventRouter instance for intelligent event routing
|
52
|
+
# @return [EventRouter] The configured event router
|
53
|
+
attr_reader :event_router
|
54
|
+
|
55
|
+
# Backend creation timestamp for monitoring
|
56
|
+
# @return [Time] When this backend was initialized
|
57
|
+
attr_reader :created_at
|
58
|
+
|
59
|
+
# Cache capabilities detected at initialization
|
60
|
+
# @return [Hash] Detected Rails.cache capabilities
|
61
|
+
attr_reader :cache_capabilities
|
62
|
+
|
63
|
+
# Selected sync strategy based on cache capabilities
|
64
|
+
# @return [Symbol] One of :distributed_atomic, :distributed_basic, :local_only
|
65
|
+
attr_reader :sync_strategy
|
66
|
+
|
67
|
+
# Unique instance identifier for distributed coordination
|
68
|
+
# @return [String] Hostname-PID identifier for this instance
|
69
|
+
attr_reader :instance_id
|
70
|
+
|
71
|
+
# Configuration for cache synchronization
|
72
|
+
# @return [Hash] Sync configuration parameters
|
73
|
+
attr_reader :sync_config
|
74
|
+
|
75
|
+
# Cache strategy for this instance
|
76
|
+
# @return [Tasker::CacheStrategy] The cache strategy for this instance
|
77
|
+
attr_reader :cache_strategy
|
78
|
+
|
79
|
+
# Initialize the metrics backend
|
80
|
+
#
|
81
|
+
# Sets up thread-safe storage, integrates with EventRouter, and configures
|
82
|
+
# cache capabilities for hybrid architecture.
|
83
|
+
# Called automatically via singleton pattern.
|
84
|
+
def initialize
|
85
|
+
@metrics = Concurrent::Hash.new
|
86
|
+
@event_router = nil
|
87
|
+
@local_buffer = []
|
88
|
+
@last_sync = Time.current
|
89
|
+
@created_at = Time.current.freeze
|
90
|
+
|
91
|
+
# Thread-safe metric creation lock
|
92
|
+
@metric_creation_lock = Mutex.new
|
93
|
+
|
94
|
+
# Use unified cache strategy for capability detection
|
95
|
+
@cache_strategy = Tasker::CacheStrategy.detect
|
96
|
+
@instance_id = @cache_strategy.instance_id
|
97
|
+
@sync_strategy = @cache_strategy.coordination_mode
|
98
|
+
|
99
|
+
# Extract capabilities for backward compatibility
|
100
|
+
@cache_capabilities = @cache_strategy.export_capabilities
|
101
|
+
@sync_config = configure_sync_parameters
|
102
|
+
|
103
|
+
log_cache_strategy_selection
|
104
|
+
end
|
105
|
+
|
106
|
+
# Register the EventRouter for intelligent routing
|
107
|
+
#
|
108
|
+
# This method is called by EventRouter during configuration to enable
|
109
|
+
# automatic metric collection based on routing decisions.
|
110
|
+
#
|
111
|
+
# @param router [EventRouter] The configured event router
|
112
|
+
# @return [EventRouter] The registered router
|
113
|
+
def register_event_router(router)
|
114
|
+
@event_router = router
|
115
|
+
end
|
116
|
+
|
117
|
+
# Get or create a counter metric
|
118
|
+
#
|
119
|
+
# Counters are thread-safe and support only increment operations.
|
120
|
+
# They're ideal for counting events, requests, errors, etc.
|
121
|
+
#
|
122
|
+
# @param name [String] The metric name
|
123
|
+
# @param labels [Hash] Optional dimensional labels
|
124
|
+
# @return [MetricTypes::Counter] The counter instance
|
125
|
+
# @raise [ArgumentError] If name is invalid
|
126
|
+
#
|
127
|
+
# @example
|
128
|
+
# counter = backend.counter('http_requests_total', endpoint: '/api/tasks')
|
129
|
+
# counter.increment(5)
|
130
|
+
def counter(name, **labels)
|
131
|
+
get_or_create_metric(name, labels, :counter) do
|
132
|
+
MetricTypes::Counter.new(name, labels: labels)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Get or create a gauge metric
|
137
|
+
#
|
138
|
+
# Gauges are thread-safe and support set, increment, and decrement operations.
|
139
|
+
# They're ideal for values that fluctuate like connections, queue depth, etc.
|
140
|
+
#
|
141
|
+
# @param name [String] The metric name
|
142
|
+
# @param labels [Hash] Optional dimensional labels
|
143
|
+
# @return [MetricTypes::Gauge] The gauge instance
|
144
|
+
# @raise [ArgumentError] If name is invalid
|
145
|
+
#
|
146
|
+
# @example
|
147
|
+
# gauge = backend.gauge('active_connections', service: 'api')
|
148
|
+
# gauge.set(100)
|
149
|
+
# gauge.increment(5)
|
150
|
+
def gauge(name, **labels)
|
151
|
+
get_or_create_metric(name, labels, :gauge) do
|
152
|
+
MetricTypes::Gauge.new(name, labels: labels)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Get or create a histogram metric
|
157
|
+
#
|
158
|
+
# Histograms are thread-safe and provide statistical analysis of observed values.
|
159
|
+
# They're ideal for measuring durations, sizes, and distributions.
|
160
|
+
#
|
161
|
+
# @param name [String] The metric name
|
162
|
+
# @param labels [Hash] Optional dimensional labels
|
163
|
+
# @param buckets [Array<Numeric>] Optional custom bucket boundaries
|
164
|
+
# @return [MetricTypes::Histogram] The histogram instance
|
165
|
+
# @raise [ArgumentError] If name is invalid or buckets are malformed
|
166
|
+
#
|
167
|
+
# @example
|
168
|
+
# histogram = backend.histogram('request_duration_seconds', method: 'POST')
|
169
|
+
# histogram.observe(0.145)
|
170
|
+
def histogram(name, buckets: nil, **labels)
|
171
|
+
get_or_create_metric(name, labels, :histogram) do
|
172
|
+
if buckets
|
173
|
+
MetricTypes::Histogram.new(name, labels: labels, buckets: buckets)
|
174
|
+
else
|
175
|
+
MetricTypes::Histogram.new(name, labels: labels)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Synchronize in-memory metrics to Rails.cache using detected strategy
|
181
|
+
#
|
182
|
+
# This method implements the core cache synchronization logic that adapts
|
183
|
+
# to the available Rails.cache store capabilities.
|
184
|
+
#
|
185
|
+
# @return [Hash] Sync result with success status and metrics count
|
186
|
+
#
|
187
|
+
# @example Periodic sync (typically called from background job)
|
188
|
+
# result = backend.sync_to_cache!
|
189
|
+
# # => { success: true, synced_metrics: 42, strategy: :distributed_atomic }
|
190
|
+
def sync_to_cache!
|
191
|
+
return { success: false, error: 'Rails.cache not available' } unless rails_cache_available?
|
192
|
+
|
193
|
+
start_time = Time.current
|
194
|
+
|
195
|
+
case @sync_strategy
|
196
|
+
when :distributed_atomic
|
197
|
+
result = sync_with_atomic_operations
|
198
|
+
when :distributed_basic
|
199
|
+
result = sync_with_read_modify_write
|
200
|
+
when :local_only
|
201
|
+
result = sync_to_local_cache
|
202
|
+
else
|
203
|
+
return { success: false, error: "Unknown sync strategy: #{@sync_strategy}" }
|
204
|
+
end
|
205
|
+
|
206
|
+
final_result = result.merge(
|
207
|
+
duration: Time.current - start_time,
|
208
|
+
timestamp: Time.current.iso8601,
|
209
|
+
instance_id: @instance_id
|
210
|
+
)
|
211
|
+
|
212
|
+
# Coordinate with export system
|
213
|
+
coordinate_cache_sync(final_result)
|
214
|
+
|
215
|
+
final_result
|
216
|
+
rescue StandardError => e
|
217
|
+
log_sync_error(e)
|
218
|
+
{ success: false, error: e.message, timestamp: Time.current.iso8601 }
|
219
|
+
end
|
220
|
+
|
221
|
+
# Export metrics with distributed coordination when supported
|
222
|
+
#
|
223
|
+
# This method aggregates metrics across containers when possible,
|
224
|
+
# falls back to local export for limited cache stores.
|
225
|
+
#
|
226
|
+
# @param include_instances [Boolean] Whether to include per-instance metrics
|
227
|
+
# @return [Hash] Export data with aggregated metrics when available
|
228
|
+
def export_distributed_metrics(include_instances: false)
|
229
|
+
case @sync_strategy
|
230
|
+
when :distributed_atomic, :distributed_basic
|
231
|
+
aggregate_from_distributed_cache(include_instances: include_instances)
|
232
|
+
when :local_only
|
233
|
+
export_local_metrics_with_warning
|
234
|
+
else
|
235
|
+
export # Fallback to standard local export
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Handle an event from EventRouter and collect appropriate metrics
|
240
|
+
#
|
241
|
+
# This method is called by EventRouter when an event should be routed to
|
242
|
+
# the metrics backend. It automatically creates and updates metrics based
|
243
|
+
# on event type and payload.
|
244
|
+
#
|
245
|
+
# @param event_name [String] The lifecycle event name
|
246
|
+
# @param payload [Hash] Event payload with metric data
|
247
|
+
# @return [Boolean] True if metrics were collected successfully
|
248
|
+
#
|
249
|
+
# @example Automatic usage via EventRouter
|
250
|
+
# # EventRouter calls this automatically:
|
251
|
+
# backend.handle_event('task.completed', {
|
252
|
+
# task_id: '123',
|
253
|
+
# duration: 2.45,
|
254
|
+
# status: 'success'
|
255
|
+
# })
|
256
|
+
#
|
257
|
+
def handle_event(event_name, payload = {})
|
258
|
+
return false unless payload.is_a?(Hash)
|
259
|
+
|
260
|
+
case event_name
|
261
|
+
when /\.started$/
|
262
|
+
# Task/Step started events -> increment counter
|
263
|
+
counter("#{extract_entity_type(event_name)}_started_total", **extract_labels(payload)).increment
|
264
|
+
|
265
|
+
when /\.completed$/
|
266
|
+
# Task/Step completed events -> counter + duration histogram
|
267
|
+
entity_type = extract_entity_type(event_name)
|
268
|
+
labels = extract_labels(payload)
|
269
|
+
|
270
|
+
counter("#{entity_type}_completed_total", **labels).increment
|
271
|
+
|
272
|
+
if (duration = extract_duration(payload))
|
273
|
+
histogram("#{entity_type}_duration_seconds", **labels).observe(duration)
|
274
|
+
end
|
275
|
+
|
276
|
+
when /\.failed$/
|
277
|
+
# Task/Step failed events -> error counter + duration histogram
|
278
|
+
entity_type = extract_entity_type(event_name)
|
279
|
+
labels = extract_labels(payload)
|
280
|
+
|
281
|
+
counter("#{entity_type}_failed_total", **labels).increment
|
282
|
+
|
283
|
+
if (duration = extract_duration(payload))
|
284
|
+
histogram("#{entity_type}_duration_seconds", **labels).observe(duration)
|
285
|
+
end
|
286
|
+
|
287
|
+
when /\.cancelled$/
|
288
|
+
# Task/Step cancelled events -> cancellation counter
|
289
|
+
counter("#{extract_entity_type(event_name)}_cancelled_total", **extract_labels(payload)).increment
|
290
|
+
|
291
|
+
when /workflow\.iteration/
|
292
|
+
# Workflow iteration events -> gauge for active tasks
|
293
|
+
gauge('workflow_active_tasks').set(payload[:active_task_count]) if payload[:active_task_count]
|
294
|
+
|
295
|
+
if payload[:iteration_duration]
|
296
|
+
histogram('workflow_iteration_duration_seconds').observe(payload[:iteration_duration])
|
297
|
+
end
|
298
|
+
|
299
|
+
when /system\.health/
|
300
|
+
# System health events -> health gauges
|
301
|
+
gauge('system_healthy_tasks').set(payload[:healthy_task_count]) if payload[:healthy_task_count]
|
302
|
+
|
303
|
+
gauge('system_failed_tasks').set(payload[:failed_task_count]) if payload[:failed_task_count]
|
304
|
+
end
|
305
|
+
|
306
|
+
true
|
307
|
+
rescue StandardError => e
|
308
|
+
# Fail gracefully - metrics collection should never break the application
|
309
|
+
warn "MetricsBackend failed to handle event #{event_name}: #{e.message}"
|
310
|
+
false
|
311
|
+
end
|
312
|
+
|
313
|
+
# Get all registered metrics
|
314
|
+
#
|
315
|
+
# Returns a thread-safe snapshot of all current metrics for export
|
316
|
+
# to monitoring systems like Prometheus.
|
317
|
+
#
|
318
|
+
# @return [Hash] All metrics keyed by metric key
|
319
|
+
def all_metrics
|
320
|
+
@metrics.each_with_object({}) do |(key, metric), result|
|
321
|
+
result[key] = metric
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Export all metrics to a format suitable for monitoring systems
|
326
|
+
#
|
327
|
+
# Provides a comprehensive export of all metric data including
|
328
|
+
# metadata, current values, and statistical information.
|
329
|
+
#
|
330
|
+
# @return [Hash] Comprehensive metric export data
|
331
|
+
def export
|
332
|
+
{
|
333
|
+
timestamp: Time.current,
|
334
|
+
backend_created_at: @created_at,
|
335
|
+
total_metrics: @metrics.size,
|
336
|
+
metrics: all_metrics.transform_values(&:to_h)
|
337
|
+
}
|
338
|
+
end
|
339
|
+
|
340
|
+
# Get summary statistics about the metrics backend
|
341
|
+
#
|
342
|
+
# @return [Hash] Backend statistics and health information
|
343
|
+
def stats
|
344
|
+
metric_types = all_metrics.values.group_by { |m| m.to_h[:type] }
|
345
|
+
|
346
|
+
{
|
347
|
+
total_metrics: @metrics.size,
|
348
|
+
counter_metrics: metric_types[:counter]&.size || 0,
|
349
|
+
gauge_metrics: metric_types[:gauge]&.size || 0,
|
350
|
+
histogram_metrics: metric_types[:histogram]&.size || 0,
|
351
|
+
backend_uptime: Time.current - @created_at,
|
352
|
+
created_at: @created_at
|
353
|
+
}
|
354
|
+
end
|
355
|
+
|
356
|
+
# Clear all metrics (primarily for testing)
|
357
|
+
#
|
358
|
+
# @return [Integer] Number of metrics cleared
|
359
|
+
def clear!
|
360
|
+
cleared_count = @metrics.size
|
361
|
+
@metrics.clear
|
362
|
+
cleared_count
|
363
|
+
end
|
364
|
+
|
365
|
+
private
|
366
|
+
|
367
|
+
# Get or create a metric with thread-safe operations
|
368
|
+
#
|
369
|
+
# Uses double-checked locking pattern to minimize lock contention
|
370
|
+
# while ensuring thread safety during metric creation.
|
371
|
+
#
|
372
|
+
# @param name [String] Metric name
|
373
|
+
# @param labels [Hash] Metric labels
|
374
|
+
# @param type [Symbol] Metric type (:counter, :gauge, :histogram)
|
375
|
+
# @yield Block that creates the metric instance
|
376
|
+
# @return [Object] The metric instance
|
377
|
+
def get_or_create_metric(name, labels, _type)
|
378
|
+
raise ArgumentError, 'Metric name cannot be nil or empty' if name.nil? || name.to_s.strip.empty?
|
379
|
+
|
380
|
+
metric_key = build_metric_key(name, labels)
|
381
|
+
|
382
|
+
# Fast path: metric already exists
|
383
|
+
existing_metric = @metrics[metric_key]
|
384
|
+
return existing_metric if existing_metric
|
385
|
+
|
386
|
+
# Slow path: create new metric with lock
|
387
|
+
@metric_creation_lock.synchronize do
|
388
|
+
# Double-check pattern: another thread might have created it
|
389
|
+
existing_metric = @metrics[metric_key]
|
390
|
+
return existing_metric if existing_metric
|
391
|
+
|
392
|
+
# Create and store the new metric
|
393
|
+
new_metric = yield
|
394
|
+
@metrics[metric_key] = new_metric
|
395
|
+
new_metric
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
# Build a unique key for metric identification
|
400
|
+
#
|
401
|
+
# Creates a deterministic key that combines metric name and labels
|
402
|
+
# for efficient storage and retrieval.
|
403
|
+
#
|
404
|
+
# @param name [String] Metric name
|
405
|
+
# @param labels [Hash] Metric labels
|
406
|
+
# @return [String] Unique metric key
|
407
|
+
def build_metric_key(name, labels)
|
408
|
+
if labels.empty?
|
409
|
+
name.to_s
|
410
|
+
else
|
411
|
+
# Sort labels for deterministic key generation
|
412
|
+
sorted_labels = labels.sort.to_h
|
413
|
+
"#{name}#{sorted_labels.inspect}"
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# Extract entity type from event name (task, step, workflow, etc.)
|
418
|
+
#
|
419
|
+
# @param event_name [String] The lifecycle event name
|
420
|
+
# @return [String] The entity type
|
421
|
+
def extract_entity_type(event_name)
|
422
|
+
# Examples: 'task.completed' -> 'task', 'step.failed' -> 'step'
|
423
|
+
event_name.split('.').first || 'unknown'
|
424
|
+
end
|
425
|
+
|
426
|
+
# Extract duration value from event payload
|
427
|
+
#
|
428
|
+
# @param payload [Hash] Event payload
|
429
|
+
# @return [Numeric, nil] Duration in seconds, or nil if not present
|
430
|
+
def extract_duration(payload)
|
431
|
+
duration = payload[:duration] || payload['duration']
|
432
|
+
return nil unless duration.is_a?(Numeric)
|
433
|
+
|
434
|
+
duration
|
435
|
+
end
|
436
|
+
|
437
|
+
# Extract labels from event payload for dimensional metrics
|
438
|
+
#
|
439
|
+
# @param payload [Hash] Event payload
|
440
|
+
# @return [Hash] Extracted labels for metric dimensions
|
441
|
+
def extract_labels(payload)
|
442
|
+
labels = {}
|
443
|
+
|
444
|
+
# Common label extractions
|
445
|
+
labels[:status] = payload[:status] || payload['status'] if payload[:status] || payload['status']
|
446
|
+
if payload[:handler_class] || payload['handler_class']
|
447
|
+
labels[:handler] =
|
448
|
+
payload[:handler_class] || payload['handler_class']
|
449
|
+
end
|
450
|
+
labels[:namespace] = payload[:namespace] || payload['namespace'] if payload[:namespace] || payload['namespace']
|
451
|
+
|
452
|
+
# Filter out nil values and ensure string keys
|
453
|
+
labels.compact.transform_keys(&:to_s)
|
454
|
+
end
|
455
|
+
|
456
|
+
# Generate unique instance identifier for distributed coordination
|
457
|
+
#
|
458
|
+
# @return [String] Hostname-PID identifier
|
459
|
+
def generate_instance_id
|
460
|
+
hostname = begin
|
461
|
+
ENV['HOSTNAME'] || Socket.gethostname
|
462
|
+
rescue StandardError
|
463
|
+
'unknown'
|
464
|
+
end
|
465
|
+
"#{hostname}-#{Process.pid}"
|
466
|
+
end
|
467
|
+
|
468
|
+
# Configure sync parameters based on strategy and telemetry config
|
469
|
+
#
|
470
|
+
# @return [Hash] Sync configuration parameters
|
471
|
+
def configure_sync_parameters
|
472
|
+
base_config = {
|
473
|
+
retention_window: 5.minutes,
|
474
|
+
export_safety_margin: 1.minute,
|
475
|
+
sync_interval: 30.seconds
|
476
|
+
}
|
477
|
+
|
478
|
+
# Override with telemetry configuration if available
|
479
|
+
if defined?(Tasker.configuration) && Tasker.configuration.telemetry
|
480
|
+
telemetry_config = Tasker.configuration.telemetry
|
481
|
+
base_config[:retention_window] = (telemetry_config.metrics_retention_hours || 1).hours
|
482
|
+
end
|
483
|
+
|
484
|
+
base_config[:export_interval] = base_config[:retention_window] - base_config[:export_safety_margin]
|
485
|
+
base_config
|
486
|
+
end
|
487
|
+
|
488
|
+
# Synchronize metrics using atomic operations (Redis/Memcached)
|
489
|
+
# Synchronize metrics using atomic operations (Redis/advanced stores)
|
490
|
+
#
|
491
|
+
# **Phase 4.2.2.3.2**: Enhanced atomic synchronization with batch operations,
|
492
|
+
# conflict resolution, and performance optimizations for distributed coordination.
|
493
|
+
#
|
494
|
+
# @return [Hash] Detailed sync result with performance metrics
|
495
|
+
def sync_with_atomic_operations
|
496
|
+
execute_sync_operation(
|
497
|
+
strategy: :distributed_atomic,
|
498
|
+
stats_template: { counters: 0, gauges: 0, histograms: 0, conflicts: 0, batches: 0 },
|
499
|
+
logger: method(:log_atomic_sync_success)
|
500
|
+
) do |grouped_metrics, sync_stats|
|
501
|
+
process_atomic_sync_by_type(grouped_metrics, sync_stats)
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
# Synchronize metrics using read-modify-write (basic distributed caches)
|
506
|
+
#
|
507
|
+
# **Phase 4.2.2.3.2**: Enhanced read-modify-write with retry logic,
|
508
|
+
# conflict detection, and optimistic concurrency control for safe updates.
|
509
|
+
#
|
510
|
+
# @return [Hash] Detailed sync result with retry statistics
|
511
|
+
def sync_with_read_modify_write
|
512
|
+
execute_sync_operation(
|
513
|
+
strategy: :distributed_basic,
|
514
|
+
stats_template: { counters: 0, gauges: 0, histograms: 0, retries: 0, conflicts: 0, batches: 0, failed: 0 },
|
515
|
+
logger: method(:log_rmw_sync_success)
|
516
|
+
) do |grouped_metrics, sync_stats|
|
517
|
+
sync_stats.merge!(sync_with_optimistic_concurrency(grouped_metrics))
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
# Create local cache snapshot (memory/file stores)
|
522
|
+
#
|
523
|
+
# **Phase 4.2.2.3.2**: Enhanced local synchronization with versioned snapshots,
|
524
|
+
# efficient serialization, and proper state management for local-only deployment.
|
525
|
+
#
|
526
|
+
# @return [Hash] Detailed sync result with snapshot information
|
527
|
+
def sync_to_local_cache
|
528
|
+
execute_local_snapshot_sync
|
529
|
+
end
|
530
|
+
|
531
|
+
# Aggregate metrics from distributed cache
|
532
|
+
#
|
533
|
+
# @param include_instances [Boolean] Include per-instance breakdowns
|
534
|
+
# @return [Hash] Aggregated export data
|
535
|
+
def aggregate_from_distributed_cache(include_instances: false)
|
536
|
+
# Implementation for distributed aggregation
|
537
|
+
# This will be expanded in Phase 4.2.2.3.3
|
538
|
+
|
539
|
+
export_data = export
|
540
|
+
export_data[:distributed] = true
|
541
|
+
export_data[:sync_strategy] = @sync_strategy
|
542
|
+
export_data[:note] = 'Distributed aggregation - Phase 4.2.2.3.3'
|
543
|
+
export_data
|
544
|
+
end
|
545
|
+
|
546
|
+
# Export local metrics with cache limitation warning
|
547
|
+
#
|
548
|
+
# @return [Hash] Local export with warning
|
549
|
+
def export_local_metrics_with_warning
|
550
|
+
export_data = export
|
551
|
+
export_data[:distributed] = false
|
552
|
+
export_data[:sync_strategy] = @sync_strategy
|
553
|
+
export_data[:warning] = "Cache store doesn't support distribution - metrics are local-only"
|
554
|
+
export_data
|
555
|
+
end
|
556
|
+
|
557
|
+
# Build cache key for metric storage following Rails best practices
|
558
|
+
#
|
559
|
+
# Uses Rails-standard cache key patterns with proper namespacing
|
560
|
+
# and supports complex key structures as recommended in the Rails guide.
|
561
|
+
#
|
562
|
+
# @param metric_key [String] Internal metric key
|
563
|
+
# @return [Array] Rails.cache compatible structured key
|
564
|
+
def build_cache_key(metric_key)
|
565
|
+
# Use structured keys as recommended by Rails caching guide
|
566
|
+
# This allows Rails to handle namespacing, size limits, and transformations
|
567
|
+
[
|
568
|
+
'tasker',
|
569
|
+
'metrics',
|
570
|
+
@instance_id,
|
571
|
+
metric_key
|
572
|
+
]
|
573
|
+
end
|
574
|
+
|
575
|
+
# Prepare export data for local cache storage
|
576
|
+
#
|
577
|
+
# @return [Hash] Serializable export data
|
578
|
+
def prepare_local_export_data
|
579
|
+
{
|
580
|
+
timestamp: Time.current.iso8601,
|
581
|
+
instance_id: @instance_id,
|
582
|
+
total_metrics: @metrics.size,
|
583
|
+
metrics: @metrics.transform_values(&:to_h),
|
584
|
+
cache_capabilities: @cache_capabilities
|
585
|
+
}
|
586
|
+
end
|
587
|
+
|
588
|
+
# Create default metric data for merging
|
589
|
+
#
|
590
|
+
# @param type [Symbol] Metric type
|
591
|
+
# @return [Hash] Default data structure
|
592
|
+
def default_metric_data(type)
|
593
|
+
case type
|
594
|
+
when :counter
|
595
|
+
{ type: :counter, value: 0 }
|
596
|
+
when :gauge
|
597
|
+
{ type: :gauge, value: 0 }
|
598
|
+
when :histogram
|
599
|
+
{ type: :histogram, count: 0, sum: 0.0, buckets: {} }
|
600
|
+
else
|
601
|
+
{}
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
# Merge metric data for read-modify-write operations
|
606
|
+
#
|
607
|
+
# @param existing [Hash] Existing cached metric data
|
608
|
+
# @param current [Hash] Current in-memory metric data
|
609
|
+
# @return [Hash] Merged metric data
|
610
|
+
def merge_metric_data(existing, current)
|
611
|
+
return current unless existing.is_a?(Hash)
|
612
|
+
|
613
|
+
case current[:type]
|
614
|
+
when :counter
|
615
|
+
{
|
616
|
+
type: :counter,
|
617
|
+
value: (existing[:value] || 0) + current[:value],
|
618
|
+
labels: current[:labels]
|
619
|
+
}
|
620
|
+
when :gauge
|
621
|
+
# Gauges use most recent value (current instance wins)
|
622
|
+
current
|
623
|
+
when :histogram
|
624
|
+
# Histogram merging - sum the statistics
|
625
|
+
{
|
626
|
+
type: :histogram,
|
627
|
+
count: (existing[:count] || 0) + current[:count],
|
628
|
+
sum: (existing[:sum] || 0.0) + current[:sum],
|
629
|
+
buckets: merge_histogram_buckets(existing[:buckets] || {}, current[:buckets] || {}),
|
630
|
+
labels: current[:labels]
|
631
|
+
}
|
632
|
+
else
|
633
|
+
current
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
# Merge histogram bucket data
|
638
|
+
#
|
639
|
+
# @param existing_buckets [Hash] Existing bucket counts
|
640
|
+
# @param current_buckets [Hash] Current bucket counts
|
641
|
+
# @return [Hash] Merged bucket counts
|
642
|
+
def merge_histogram_buckets(existing_buckets, current_buckets)
|
643
|
+
all_buckets = (existing_buckets.keys + current_buckets.keys).uniq
|
644
|
+
all_buckets.index_with do |bucket|
|
645
|
+
(existing_buckets[bucket] || 0) + (current_buckets[bucket] || 0)
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
# **Phase 4.2.2.3.2 Supporting Methods**
|
650
|
+
# ====================================
|
651
|
+
|
652
|
+
# Execute a sync operation with common error handling and timing
|
653
|
+
#
|
654
|
+
# @param strategy [Symbol] Sync strategy identifier
|
655
|
+
# @param stats_template [Hash] Initial stats structure
|
656
|
+
# @param logger [Method] Logging method for success
|
657
|
+
# @yield [grouped_metrics, sync_stats] Block to execute sync logic
|
658
|
+
# @return [Hash] Standardized sync result
|
659
|
+
def execute_sync_operation(strategy:, stats_template:, logger:)
|
660
|
+
start_time = Time.current
|
661
|
+
sync_stats = stats_template.dup
|
662
|
+
|
663
|
+
begin
|
664
|
+
grouped_metrics = group_metrics_by_type
|
665
|
+
yield(grouped_metrics, sync_stats)
|
666
|
+
|
667
|
+
sync_duration = Time.current - start_time
|
668
|
+
total_synced = calculate_total_synced_metrics(sync_stats)
|
669
|
+
|
670
|
+
logger.call(sync_stats, sync_duration)
|
671
|
+
|
672
|
+
build_success_result(strategy, total_synced, sync_duration, sync_stats)
|
673
|
+
rescue StandardError => e
|
674
|
+
log_sync_error(e)
|
675
|
+
build_error_result(strategy, e, sync_stats)
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
# Process atomic sync operations by metric type
|
680
|
+
#
|
681
|
+
# @param grouped_metrics [Hash] Metrics grouped by type
|
682
|
+
# @param sync_stats [Hash] Statistics accumulator
|
683
|
+
def process_atomic_sync_by_type(grouped_metrics, sync_stats)
|
684
|
+
# Process counters with true atomic operations
|
685
|
+
sync_stats.merge!(sync_atomic_counters(grouped_metrics[:counter])) if grouped_metrics[:counter].any?
|
686
|
+
|
687
|
+
# Process gauges with last-writer-wins strategy
|
688
|
+
sync_stats.merge!(sync_distributed_gauges(grouped_metrics[:gauge])) if grouped_metrics[:gauge].any?
|
689
|
+
|
690
|
+
# Process histograms with atomic aggregation
|
691
|
+
return unless grouped_metrics[:histogram].any?
|
692
|
+
|
693
|
+
sync_stats.merge!(sync_distributed_histograms(grouped_metrics[:histogram]))
|
694
|
+
end
|
695
|
+
|
696
|
+
# Calculate total synced metrics from stats
|
697
|
+
#
|
698
|
+
# @param sync_stats [Hash] Statistics hash
|
699
|
+
# @return [Integer] Total synced metrics count
|
700
|
+
def calculate_total_synced_metrics(sync_stats)
|
701
|
+
sync_stats.values_at(:counters, :gauges, :histograms).compact.sum
|
702
|
+
end
|
703
|
+
|
704
|
+
# Build successful sync result
|
705
|
+
#
|
706
|
+
# @param strategy [Symbol] Sync strategy
|
707
|
+
# @param total_synced [Integer] Total metrics synced
|
708
|
+
# @param sync_duration [Float] Duration in seconds
|
709
|
+
# @param sync_stats [Hash] Performance statistics
|
710
|
+
# @return [Hash] Success result
|
711
|
+
def build_success_result(strategy, total_synced, sync_duration, sync_stats)
|
712
|
+
{
|
713
|
+
success: true,
|
714
|
+
strategy: strategy,
|
715
|
+
synced_metrics: total_synced,
|
716
|
+
duration_ms: (sync_duration * 1000).round(2),
|
717
|
+
performance: sync_stats,
|
718
|
+
timestamp: Time.current.iso8601
|
719
|
+
}
|
720
|
+
end
|
721
|
+
|
722
|
+
# Build error sync result
|
723
|
+
#
|
724
|
+
# @param strategy [Symbol] Sync strategy
|
725
|
+
# @param error [Exception] Error that occurred
|
726
|
+
# @param sync_stats [Hash] Partial statistics
|
727
|
+
# @return [Hash] Error result
|
728
|
+
def build_error_result(strategy, error, sync_stats)
|
729
|
+
{
|
730
|
+
success: false,
|
731
|
+
strategy: strategy,
|
732
|
+
error: error.message,
|
733
|
+
partial_results: sync_stats,
|
734
|
+
timestamp: Time.current.iso8601
|
735
|
+
}
|
736
|
+
end
|
737
|
+
|
738
|
+
# Execute local snapshot synchronization with proper error handling
|
739
|
+
#
|
740
|
+
# @return [Hash] Detailed sync result with snapshot information
|
741
|
+
def execute_local_snapshot_sync
|
742
|
+
start_time = Time.current
|
743
|
+
sync_stats = { snapshots: 0, metrics_serialized: 0, size_bytes: 0 }
|
744
|
+
|
745
|
+
begin
|
746
|
+
snapshot_data = create_versioned_snapshot
|
747
|
+
snapshot_keys = build_snapshot_keys
|
748
|
+
write_result = write_snapshot_data(snapshot_data, snapshot_keys, sync_stats)
|
749
|
+
|
750
|
+
sync_duration = Time.current - start_time
|
751
|
+
log_local_sync_success(sync_stats, sync_duration)
|
752
|
+
|
753
|
+
build_local_sync_result(write_result, sync_duration, sync_stats, snapshot_keys[:primary])
|
754
|
+
rescue StandardError => e
|
755
|
+
log_sync_error(e)
|
756
|
+
build_error_result(:local_only, e, {})
|
757
|
+
end
|
758
|
+
end
|
759
|
+
|
760
|
+
# Build snapshot cache keys for primary and history storage
|
761
|
+
#
|
762
|
+
# @return [Hash] Hash containing primary and timestamped keys
|
763
|
+
def build_snapshot_keys
|
764
|
+
{
|
765
|
+
primary: ['tasker', 'metrics', 'snapshot', @instance_id],
|
766
|
+
timestamped: ['tasker', 'metrics', 'history', @instance_id, Time.current.to_i]
|
767
|
+
}
|
768
|
+
end
|
769
|
+
|
770
|
+
# Write snapshot data to cache with redundancy
|
771
|
+
#
|
772
|
+
# @param snapshot_data [Hash] Snapshot data to write
|
773
|
+
# @param snapshot_keys [Hash] Cache keys for storage
|
774
|
+
# @param sync_stats [Hash] Statistics accumulator
|
775
|
+
# @return [Boolean] Success status of primary write
|
776
|
+
def write_snapshot_data(snapshot_data, snapshot_keys, sync_stats)
|
777
|
+
# Write primary snapshot with compression awareness
|
778
|
+
write_result = Rails.cache.write(snapshot_keys[:primary], snapshot_data,
|
779
|
+
expires_in: @sync_config[:retention_window])
|
780
|
+
|
781
|
+
if write_result
|
782
|
+
update_snapshot_stats(sync_stats, snapshot_data)
|
783
|
+
write_history_snapshot(snapshot_data, snapshot_keys[:timestamped], sync_stats)
|
784
|
+
end
|
785
|
+
|
786
|
+
write_result
|
787
|
+
end
|
788
|
+
|
789
|
+
# Update statistics after successful snapshot write
|
790
|
+
#
|
791
|
+
# @param sync_stats [Hash] Statistics accumulator
|
792
|
+
# @param snapshot_data [Hash] Snapshot data
|
793
|
+
def update_snapshot_stats(sync_stats, snapshot_data)
|
794
|
+
sync_stats[:snapshots] += 1
|
795
|
+
sync_stats[:metrics_serialized] = @metrics.size
|
796
|
+
sync_stats[:size_bytes] = estimate_snapshot_size(snapshot_data)
|
797
|
+
end
|
798
|
+
|
799
|
+
# Write optional history snapshot for redundancy
|
800
|
+
#
|
801
|
+
# @param snapshot_data [Hash] Snapshot data
|
802
|
+
# @param timestamped_key [Array] Timestamped cache key
|
803
|
+
# @param sync_stats [Hash] Statistics accumulator
|
804
|
+
def write_history_snapshot(snapshot_data, timestamped_key, sync_stats)
|
805
|
+
Rails.cache.write(timestamped_key, snapshot_data,
|
806
|
+
expires_in: @sync_config[:retention_window] / 2)
|
807
|
+
sync_stats[:snapshots] += 1
|
808
|
+
rescue StandardError
|
809
|
+
# History snapshot failure is non-critical
|
810
|
+
end
|
811
|
+
|
812
|
+
# Build result for local sync operation
|
813
|
+
#
|
814
|
+
# @param write_result [Boolean] Primary write success
|
815
|
+
# @param sync_duration [Float] Duration in seconds
|
816
|
+
# @param sync_stats [Hash] Performance statistics
|
817
|
+
# @param primary_key [Array] Primary snapshot key
|
818
|
+
# @return [Hash] Local sync result
|
819
|
+
def build_local_sync_result(write_result, sync_duration, sync_stats, primary_key)
|
820
|
+
{
|
821
|
+
success: write_result,
|
822
|
+
strategy: :local_only,
|
823
|
+
synced_metrics: @metrics.size,
|
824
|
+
duration_ms: (sync_duration * 1000).round(2),
|
825
|
+
performance: sync_stats,
|
826
|
+
snapshot_key: primary_key,
|
827
|
+
timestamp: Time.current.iso8601
|
828
|
+
}
|
829
|
+
end
|
830
|
+
|
831
|
+
# Group metrics by type for optimized batch processing
|
832
|
+
#
|
833
|
+
# @return [Hash] Metrics grouped by type (:counter, :gauge, :histogram)
|
834
|
+
def group_metrics_by_type
|
835
|
+
result = { counter: [], gauge: [], histogram: [] }
|
836
|
+
|
837
|
+
@metrics.each do |key, metric|
|
838
|
+
metric_data = metric.to_h
|
839
|
+
type = metric_data[:type]
|
840
|
+
result[type] << [key, metric_data] if result.key?(type)
|
841
|
+
end
|
842
|
+
|
843
|
+
result
|
844
|
+
end
|
845
|
+
|
846
|
+
# Synchronize counters using atomic operations
|
847
|
+
#
|
848
|
+
# @param counter_metrics [Array] Array of [key, metric_data] pairs
|
849
|
+
# @return [Hash] Sync statistics
|
850
|
+
def sync_atomic_counters(counter_metrics)
|
851
|
+
stats = { counters: 0, conflicts: 0 }
|
852
|
+
|
853
|
+
counter_metrics.each do |key, metric_data|
|
854
|
+
cache_key = build_cache_key(key)
|
855
|
+
|
856
|
+
begin
|
857
|
+
# Use atomic increment for true cross-container coordination
|
858
|
+
Rails.cache.increment(cache_key, metric_data[:value],
|
859
|
+
expires_in: @sync_config[:retention_window],
|
860
|
+
initial: 0)
|
861
|
+
stats[:counters] += 1
|
862
|
+
rescue StandardError
|
863
|
+
stats[:conflicts] += 1
|
864
|
+
# Fallback to regular write for non-atomic stores
|
865
|
+
Rails.cache.write(cache_key, metric_data,
|
866
|
+
expires_in: @sync_config[:retention_window])
|
867
|
+
end
|
868
|
+
end
|
869
|
+
|
870
|
+
stats
|
871
|
+
end
|
872
|
+
|
873
|
+
# Synchronize gauges with last-writer-wins strategy
|
874
|
+
#
|
875
|
+
# @param gauge_metrics [Array] Array of [key, metric_data] pairs
|
876
|
+
# @return [Hash] Sync statistics
|
877
|
+
def sync_distributed_gauges(gauge_metrics)
|
878
|
+
stats = { gauges: 0, conflicts: 0 }
|
879
|
+
|
880
|
+
gauge_metrics.each do |key, metric_data|
|
881
|
+
cache_key = build_cache_key(key)
|
882
|
+
|
883
|
+
# Add timestamp for conflict resolution
|
884
|
+
enhanced_data = metric_data.merge(
|
885
|
+
last_update: Time.current.to_f,
|
886
|
+
instance_id: @instance_id
|
887
|
+
)
|
888
|
+
|
889
|
+
begin
|
890
|
+
Rails.cache.write(cache_key, enhanced_data,
|
891
|
+
expires_in: @sync_config[:retention_window])
|
892
|
+
stats[:gauges] += 1
|
893
|
+
rescue StandardError
|
894
|
+
stats[:conflicts] += 1
|
895
|
+
end
|
896
|
+
end
|
897
|
+
|
898
|
+
stats
|
899
|
+
end
|
900
|
+
|
901
|
+
# Synchronize histograms with atomic aggregation
|
902
|
+
#
|
903
|
+
# @param histogram_metrics [Array] Array of [key, metric_data] pairs
|
904
|
+
# @return [Hash] Sync statistics
|
905
|
+
def sync_distributed_histograms(histogram_metrics)
|
906
|
+
stats = { histograms: 0, conflicts: 0 }
|
907
|
+
|
908
|
+
histogram_metrics.each do |key, metric_data|
|
909
|
+
cache_key = build_cache_key(key)
|
910
|
+
|
911
|
+
# Try atomic aggregation first, fallback to merge
|
912
|
+
if attempt_atomic_histogram_update(cache_key, metric_data)
|
913
|
+
stats[:histograms] += 1
|
914
|
+
else
|
915
|
+
# Fallback to read-modify-write for histograms
|
916
|
+
existing = Rails.cache.read(cache_key) || default_metric_data(:histogram)
|
917
|
+
merged = merge_metric_data(existing, metric_data)
|
918
|
+
Rails.cache.write(cache_key, merged, expires_in: @sync_config[:retention_window])
|
919
|
+
stats[:histograms] += 1
|
920
|
+
stats[:conflicts] += 1
|
921
|
+
end
|
922
|
+
end
|
923
|
+
|
924
|
+
stats
|
925
|
+
end
|
926
|
+
|
927
|
+
# Attempt atomic histogram update
|
928
|
+
#
|
929
|
+
# @param cache_key [Array] Structured cache key
|
930
|
+
# @param metric_data [Hash] Histogram metric data
|
931
|
+
# @return [Boolean] Success status
|
932
|
+
def attempt_atomic_histogram_update(cache_key, metric_data)
|
933
|
+
# For stores that support atomic operations, try to update histogram components
|
934
|
+
if @cache_capabilities[:atomic_increment]
|
935
|
+
count_key = cache_key + ['count']
|
936
|
+
sum_key = cache_key + ['sum']
|
937
|
+
|
938
|
+
begin
|
939
|
+
Rails.cache.increment(count_key, metric_data[:count], initial: 0,
|
940
|
+
expires_in: @sync_config[:retention_window])
|
941
|
+
Rails.cache.increment(sum_key, metric_data[:sum], initial: 0.0,
|
942
|
+
expires_in: @sync_config[:retention_window])
|
943
|
+
|
944
|
+
# Update buckets individually
|
945
|
+
metric_data[:buckets]&.each do |bucket, count|
|
946
|
+
bucket_key = cache_key + ['buckets', bucket.to_s]
|
947
|
+
Rails.cache.increment(bucket_key, count, initial: 0,
|
948
|
+
expires_in: @sync_config[:retention_window])
|
949
|
+
end
|
950
|
+
|
951
|
+
return true
|
952
|
+
rescue StandardError
|
953
|
+
return false
|
954
|
+
end
|
955
|
+
end
|
956
|
+
false
|
957
|
+
end
|
958
|
+
|
959
|
+
# Synchronize with optimistic concurrency control
|
960
|
+
#
|
961
|
+
# @param grouped_metrics [Hash] Metrics grouped by type
|
962
|
+
# @return [Hash] Combined sync statistics
|
963
|
+
def sync_with_optimistic_concurrency(grouped_metrics)
|
964
|
+
stats = initialize_concurrency_stats
|
965
|
+
|
966
|
+
grouped_metrics.each do |type, metrics|
|
967
|
+
process_metrics_with_concurrency_control(type, metrics, stats)
|
968
|
+
end
|
969
|
+
|
970
|
+
stats
|
971
|
+
end
|
972
|
+
|
973
|
+
# Initialize statistics for concurrency control
|
974
|
+
#
|
975
|
+
# @return [Hash] Initial statistics structure
|
976
|
+
def initialize_concurrency_stats
|
977
|
+
{ counters: 0, gauges: 0, histograms: 0, retries: 0, conflicts: 0, failed: 0 }
|
978
|
+
end
|
979
|
+
|
980
|
+
# Process metrics of a specific type with concurrency control
|
981
|
+
#
|
982
|
+
# @param type [Symbol] Metric type (:counter, :gauge, :histogram)
|
983
|
+
# @param metrics [Array] Array of [key, metric_data] pairs
|
984
|
+
# @param stats [Hash] Statistics accumulator
|
985
|
+
def process_metrics_with_concurrency_control(type, metrics, stats)
|
986
|
+
metrics.each do |key, metric_data|
|
987
|
+
success = sync_single_metric_with_retry(type, key, metric_data, stats)
|
988
|
+
stats[:failed] += 1 unless success
|
989
|
+
end
|
990
|
+
end
|
991
|
+
|
992
|
+
# Sync a single metric with retry logic
|
993
|
+
#
|
994
|
+
# @param type [Symbol] Metric type
|
995
|
+
# @param key [String] Metric key
|
996
|
+
# @param metric_data [Hash] Metric data
|
997
|
+
# @param stats [Hash] Statistics accumulator
|
998
|
+
# @return [Boolean] Success status
|
999
|
+
def sync_single_metric_with_retry(type, key, metric_data, stats)
|
1000
|
+
cache_key = build_cache_key(key)
|
1001
|
+
max_retries = 3
|
1002
|
+
retry_count = 0
|
1003
|
+
|
1004
|
+
while retry_count < max_retries
|
1005
|
+
success = attempt_optimistic_write(type, cache_key, metric_data, stats)
|
1006
|
+
return true if success
|
1007
|
+
|
1008
|
+
retry_count += 1
|
1009
|
+
stats[:retries] += 1
|
1010
|
+
sleep(calculate_backoff_delay(retry_count))
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
false
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
# Attempt a single optimistic write operation
|
1017
|
+
#
|
1018
|
+
# @param type [Symbol] Metric type
|
1019
|
+
# @param cache_key [Array] Cache key
|
1020
|
+
# @param metric_data [Hash] Metric data
|
1021
|
+
# @param stats [Hash] Statistics accumulator
|
1022
|
+
# @return [Boolean] Success status
|
1023
|
+
def attempt_optimistic_write(type, cache_key, metric_data, stats)
|
1024
|
+
# Read current value
|
1025
|
+
existing = Rails.cache.read(cache_key) || default_metric_data(type)
|
1026
|
+
|
1027
|
+
# Merge with current data
|
1028
|
+
merged = merge_metric_data(existing, metric_data)
|
1029
|
+
|
1030
|
+
# Attempt to write (this could fail if another process updates)
|
1031
|
+
if Rails.cache.write(cache_key, merged, expires_in: @sync_config[:retention_window])
|
1032
|
+
increment_metric_type_stats(type, stats)
|
1033
|
+
true
|
1034
|
+
else
|
1035
|
+
false
|
1036
|
+
end
|
1037
|
+
rescue StandardError
|
1038
|
+
stats[:conflicts] += 1
|
1039
|
+
false
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
# Increment statistics for successful metric sync
|
1043
|
+
#
|
1044
|
+
# @param type [Symbol] Metric type
|
1045
|
+
# @param stats [Hash] Statistics accumulator
|
1046
|
+
def increment_metric_type_stats(type, stats)
|
1047
|
+
plural_type = convert_type_to_plural(type)
|
1048
|
+
stats[plural_type] += 1
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
# Convert singular metric type to plural stats key
|
1052
|
+
#
|
1053
|
+
# @param type [Symbol] Singular metric type
|
1054
|
+
# @return [Symbol] Plural stats key
|
1055
|
+
def convert_type_to_plural(type)
|
1056
|
+
case type
|
1057
|
+
when :counter then :counters
|
1058
|
+
when :gauge then :gauges
|
1059
|
+
when :histogram then :histograms
|
1060
|
+
else type
|
1061
|
+
end
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
# Calculate exponential backoff delay
|
1065
|
+
#
|
1066
|
+
# @param retry_count [Integer] Current retry attempt
|
1067
|
+
# @return [Float] Delay in seconds
|
1068
|
+
def calculate_backoff_delay(retry_count)
|
1069
|
+
0.001 * retry_count
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
# Create versioned snapshot with enhanced metadata
|
1073
|
+
#
|
1074
|
+
# @return [Hash] Versioned snapshot data
|
1075
|
+
def create_versioned_snapshot
|
1076
|
+
{
|
1077
|
+
version: Tasker::VERSION,
|
1078
|
+
timestamp: Time.current.iso8601,
|
1079
|
+
instance_id: @instance_id,
|
1080
|
+
cache_strategy: @sync_strategy,
|
1081
|
+
cache_capabilities: @cache_capabilities,
|
1082
|
+
total_metrics: @metrics.size,
|
1083
|
+
metrics_by_type: @metrics.group_by { |_k, v| v.to_h[:type] }.transform_values(&:size),
|
1084
|
+
metrics: @metrics.transform_values(&:to_h),
|
1085
|
+
sync_config: @sync_config.slice(:retention_window, :batch_size),
|
1086
|
+
hostname: ENV['HOSTNAME'] || Socket.gethostname
|
1087
|
+
}
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
# Estimate snapshot size for monitoring
|
1091
|
+
#
|
1092
|
+
# @param snapshot_data [Hash] Snapshot data
|
1093
|
+
# @return [Integer] Estimated size in bytes
|
1094
|
+
def estimate_snapshot_size(snapshot_data)
|
1095
|
+
# Rough estimation based on JSON serialization
|
1096
|
+
snapshot_data.to_json.bytesize
|
1097
|
+
rescue StandardError
|
1098
|
+
0
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
# Check if Rails.cache is available and functional
|
1102
|
+
#
|
1103
|
+
# @return [Boolean] True if Rails.cache can be used
|
1104
|
+
def rails_cache_available?
|
1105
|
+
defined?(Rails) && Rails.cache.respond_to?(:read) && Rails.cache.respond_to?(:write)
|
1106
|
+
rescue StandardError
|
1107
|
+
false
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
# Default cache capabilities when detection fails
|
1111
|
+
#
|
1112
|
+
# Provides safe defaults aligned with Rails caching guide patterns
|
1113
|
+
#
|
1114
|
+
# @return [Hash] Safe default capabilities
|
1115
|
+
def default_cache_capabilities
|
1116
|
+
{
|
1117
|
+
distributed: false,
|
1118
|
+
atomic_increment: false,
|
1119
|
+
locking: false,
|
1120
|
+
ttl_inspection: false,
|
1121
|
+
store_class: 'Unknown',
|
1122
|
+
key_transformation: true, # Assume Rails key transformation
|
1123
|
+
namespace_support: false,
|
1124
|
+
compression_support: false
|
1125
|
+
}
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
# Log cache strategy selection for operational visibility
|
1129
|
+
def log_cache_strategy_selection
|
1130
|
+
return unless defined?(Rails) && Rails.logger
|
1131
|
+
|
1132
|
+
Rails.logger.info "[Tasker::MetricsBackend] Cache strategy selected: #{@sync_strategy}"
|
1133
|
+
Rails.logger.info "[Tasker::MetricsBackend] Cache capabilities: #{@cache_capabilities}"
|
1134
|
+
Rails.logger.info "[Tasker::MetricsBackend] Instance ID: #{@instance_id}"
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
# Log detected cache capabilities with detailed breakdown
|
1138
|
+
#
|
1139
|
+
# @param capabilities [Hash] Detected capabilities
|
1140
|
+
def log_cache_capabilities_detected(capabilities)
|
1141
|
+
return unless defined?(Rails) && Rails.logger
|
1142
|
+
|
1143
|
+
Rails.logger.debug { "[Tasker::MetricsBackend] Cache store detected: #{capabilities[:store_class]}" }
|
1144
|
+
Rails.logger.debug { "[Tasker::MetricsBackend] Distributed: #{capabilities[:distributed]}" }
|
1145
|
+
Rails.logger.debug { "[Tasker::MetricsBackend] Atomic operations: #{capabilities[:atomic_increment]}" }
|
1146
|
+
Rails.logger.debug { "[Tasker::MetricsBackend] Locking support: #{capabilities[:locking]}" }
|
1147
|
+
Rails.logger.debug { "[Tasker::MetricsBackend] Namespace support: #{capabilities[:namespace_support]}" }
|
1148
|
+
end
|
1149
|
+
|
1150
|
+
# Log cache detection errors
|
1151
|
+
#
|
1152
|
+
# @param error [Exception] Detection error
|
1153
|
+
def log_cache_detection_error(error)
|
1154
|
+
return unless defined?(Rails) && Rails.logger
|
1155
|
+
|
1156
|
+
Rails.logger.warn "[Tasker::MetricsBackend] Cache detection failed: #{error.message}"
|
1157
|
+
Rails.logger.warn '[Tasker::MetricsBackend] Falling back to local-only mode'
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
# Log sync errors
|
1161
|
+
#
|
1162
|
+
# @param error [Exception] Sync error
|
1163
|
+
def log_sync_error(error)
|
1164
|
+
return unless defined?(Rails) && Rails.logger
|
1165
|
+
|
1166
|
+
Rails.logger.error "[Tasker::MetricsBackend] Cache sync failed: #{error.message}"
|
1167
|
+
end
|
1168
|
+
|
1169
|
+
# Log local-only mode usage
|
1170
|
+
def log_local_only_mode
|
1171
|
+
return unless defined?(Rails) && Rails.logger
|
1172
|
+
|
1173
|
+
Rails.logger.info "[Tasker::MetricsBackend] Cache store doesn't support distribution - using local-only mode"
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
# **Phase 4.2.2.3.2 Enhanced Logging Methods**
|
1177
|
+
# =============================================
|
1178
|
+
|
1179
|
+
def log_atomic_sync_success(stats, duration)
|
1180
|
+
return unless defined?(Rails) && Rails.logger
|
1181
|
+
|
1182
|
+
total_synced = stats.values_at(:counters, :gauges, :histograms).sum
|
1183
|
+
Rails.logger.info(
|
1184
|
+
'[Tasker::Telemetry] Atomic sync completed: ' \
|
1185
|
+
"#{total_synced} metrics (#{stats[:counters]}c/#{stats[:gauges]}g/#{stats[:histograms]}h) " \
|
1186
|
+
"in #{(duration * 1000).round(2)}ms, #{stats[:conflicts]} conflicts, #{stats[:batches]} batches"
|
1187
|
+
)
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
def log_rmw_sync_success(stats, duration)
|
1191
|
+
return unless defined?(Rails) && Rails.logger
|
1192
|
+
|
1193
|
+
total_synced = stats.values_at(:counters, :gauges, :histograms).sum
|
1194
|
+
Rails.logger.info(
|
1195
|
+
'[Tasker::Telemetry] Read-modify-write sync completed: ' \
|
1196
|
+
"#{total_synced} metrics (#{stats[:counters]}c/#{stats[:gauges]}g/#{stats[:histograms]}h) " \
|
1197
|
+
"in #{(duration * 1000).round(2)}ms, #{stats[:retries]} retries, #{stats[:failed]} failed"
|
1198
|
+
)
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
def log_local_sync_success(stats, duration)
|
1202
|
+
return unless defined?(Rails) && Rails.logger
|
1203
|
+
|
1204
|
+
Rails.logger.info(
|
1205
|
+
'[Tasker::Telemetry] Local snapshot sync completed: ' \
|
1206
|
+
"#{stats[:metrics_serialized]} metrics in #{stats[:snapshots]} snapshots " \
|
1207
|
+
"(#{stats[:size_bytes]} bytes) in #{(duration * 1000).round(2)}ms"
|
1208
|
+
)
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
# Coordinate cache sync with export system
|
1212
|
+
#
|
1213
|
+
# @param sync_result [Hash] Result from cache sync operation
|
1214
|
+
def coordinate_cache_sync(sync_result)
|
1215
|
+
return unless defined?(Tasker::Telemetry::ExportCoordinator)
|
1216
|
+
|
1217
|
+
begin
|
1218
|
+
coordinator = Tasker::Telemetry::ExportCoordinator.instance
|
1219
|
+
coordinator.coordinate_cache_sync(sync_result)
|
1220
|
+
rescue StandardError => e
|
1221
|
+
# Don't fail sync operation due to coordination errors
|
1222
|
+
Rails.logger&.warn("Export coordination failed: #{e.message}")
|
1223
|
+
end
|
1224
|
+
end
|
1225
|
+
end
|
1226
|
+
end
|
1227
|
+
end
|