# Copyright 2017 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "google/cloud/debugger/breakpoint_manager" require "google/cloud/debugger/debuggee" require "google/cloud/debugger/debugger_c" require "google/cloud/debugger/tracer" require "google/cloud/debugger/transmitter" require "google/cloud/logging" require "stackdriver/core/async_actor" module Google module Cloud module Debugger ## # # Agent # # The Stackdriver Debugger Agent runs on the same system where a debuggee # application is running. The agent is responsible for sending state data, # such as the value of program variables and the call stack, to # Stackdriver Debugger when the code at a breakpoint location is executed. # # The Debugger Agent runs in its own child thread when started. It ensures # the instrumented application is registered properly and constantly # monitors for any active breakpoints. Once the agent gets updated with # active breakpoints from Stackdriver Debugger service, it facilitates # the breakpoints in application requests thread, then transport the # result snapshot back to Stackdriver Debugger service asynchronously. # # @example # require "google/cloud/debugger" # # debugger = Google::Cloud::Debugger.new # agent = debugger.agent # agent.start # class Agent ## # Name of the logpoints log file. DEFAULT_LOG_NAME = "debugger_logpoints".freeze ## # @private Debugger Agent is an asynchronous actor include Stackdriver::Core::AsyncActor ## # @private The gRPC Service object. attr_reader :service ## # The gRPC Debuggee representation of the debuggee application. It # contains identification information to match running application to # specific Cloud Source Repository code base, and correctly group # same versions of the debuggee application together through a generated # unique identifier. # @return [Google::Cloud::Debugger::Debuggee] attr_reader :debuggee ## # It manages syncing breakpoints between the Debugger Agent and # Stackdriver Debugger service # @return [Google::Cloud::Debugger::BreakpointManager] attr_reader :breakpoint_manager ## # It monitors the debuggee application and triggers breakpoint # evaluation when breakpoints are set. # @return [Google::Cloud::Debugger::Tracer] attr_reader :tracer ## # It sends evaluated breakpoints snapshot back to Stackdriver Debugger # Service. # @return [Google::Cloud::Debugger::Transmiter] attr_reader :transmitter ## # The logger used to write the results of Logpoints. attr_accessor :logger ## # A quota tracking object helps tracking resource consumption during # evaluations. attr_accessor :quota_manager ## # Absolute path to the debuggee Ruby application root directory. The # Stackdriver Debugger service creates canonical breakpoints with only # relative path. So the debugger agent combines the relative path to # the application directory to trace and evaluate breakpoints. # @return [String] attr_accessor :app_root ## # @private The last exception captured in the agent child thread attr_reader :last_exception ## # Create a new Debugger Agent instance. # # @param [Google::Cloud::Debugger::Service] service The gRPC Service # object # @param [Google::Cloud::Logging::Logger] logger The logger used # to write the results of Logpoints. # @param [String] service_name Name for the debuggee application. # @param [String] service_version Version identifier for the debuggee # application. # @param [String] app_root Absolute path to the root directory of # the debuggee application. Default to Rack root. # def initialize service, logger: nil, service_name:, service_version:, app_root: nil super() @service = service @debuggee = Debuggee.new service, service_name: service_name, service_version: service_version @tracer = Debugger::Tracer.new self @breakpoint_manager = BreakpointManager.new self, service @breakpoint_manager.on_breakpoints_change = method :breakpoints_change_callback @transmitter = Transmitter.new self, service @logger = logger || default_logger init_app_root app_root # Agent actor thread needs to force exit immediately. set_cleanup_options timeout: 0 end ## # Starts the Debugger Agent in a child thread, where debuggee # application registration and breakpoints querying will take place. # It also starts the transmitter in another child thread. # def start transmitter.start async_start end ## # Stops and terminates the Debugger Agent. It also properly shuts down # transmitter and tracer. # # Once Debugger Agent is stopped, it cannot be started again. # def stop transmitter.stop async_stop end ## # Stops the tracer regardless of whether any active breakpoints are # present. Once the tracer stops monitoring the debuggee application, # the application can return to normal performance. def stop_tracer tracer.stop end ## # @private Callback function for AsyncActor module that kicks off # the asynchronous job in a loop. def run_backgrounder sync_breakpoints = ensure_debuggee_registration if sync_breakpoints sync_result = breakpoint_manager.sync_active_breakpoints debuggee.id debuggee.revoke_registration unless sync_result end rescue StandardError => e warn ["#{e.class}: #{e.message}", e.backtrace].join("\n\t") @last_exception = e end ## # @private Callback function when the async actor thread state changes def on_async_state_change if async_running? tracer.start else tracer.stop end end private ## # @private Initialize `@app_root` instance variable. def init_app_root app_root = nil app_root ||= Google::Cloud::Debugger.configure.app_root app_root ||= Rack::Directory.new("").root if defined? Rack::Directory app_root ||= Dir.pwd @app_root = app_root end ## # @private Callback function for breakpoint manager when any updates # happens to the list of active breakpoints def breakpoints_change_callback active_breakpoints if active_breakpoints.empty? tracer.stop else tracer.start end end ## # @private Helper method to register the debuggee application if not # registered already. def ensure_debuggee_registration if debuggee.registered? registration_result = true else registration_result = debuggee.register if registration_result puts "Debuggee #{debuggee.id} successfully registered" end end registration_result end ## # @private Create a default logger def default_logger project_id = @service.project credentials = @service.credentials logging = Google::Cloud::Logging::Project.new( Google::Cloud::Logging::Service.new(project_id, credentials) ) resource = Google::Cloud::Logging::Middleware.build_monitored_resource logging.logger DEFAULT_LOG_NAME, resource end end end end end