diff --git a/CHANGELOG.md b/CHANGELOG.md index 80a611c..d8655ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Changes by Version ================== Release Notes. +0.2.0 +------------------ +#### Features +- Supports collecting metrics and logs. + +#### Plugins + +#### Documentation + 0.1.0 ------------------ #### Features diff --git a/Gemfile b/Gemfile index 3f5e0e4..993d132 100644 --- a/Gemfile +++ b/Gemfile @@ -17,4 +17,8 @@ source 'https://rubygems.org' gemspec name: 'skywalking' -ruby ">= 3.0.0" \ No newline at end of file +ruby ">= 3.0.0" + +# Optional dependencies for enhanced performance +# Uncomment the following line to enable FFI for better system call performance +# gem 'ffi', '~> 1.17' \ No newline at end of file diff --git a/docs/en/agent/meter-log-reporter.md b/docs/en/agent/meter-log-reporter.md new file mode 100644 index 0000000..89128c6 --- /dev/null +++ b/docs/en/agent/meter-log-reporter.md @@ -0,0 +1,59 @@ +# Ruby Agent Meter and Log Reporter + +The meter reporter feature enables collection and reporting of runtime metrics to the SkyWalking OAP backend. + +### Runtime Metrics (Enabled by Default) + +The agent automatically collects Ruby runtime metrics when `meter_reporter_active` is enabled (default: true). + +#### Collected Runtime Metrics + +**CPU Metrics:** + +- `instance_ruby_cpu_usage_percent` - Ruby process CPU usage percentage + +**Memory Metrics:** + +- `instance_ruby_memory_rss_mb` - Ruby process RSS memory usage in MB +- `instance_ruby_memory_usage_percent` - Ruby process memory usage percentage + +**Garbage Collection Metrics:** + +- `instance_ruby_gc_count_total` - Total GC execution count +- `instance_ruby_gc_minor_count_total` - Minor GC count (if available) +- `instance_ruby_gc_major_count_total` - Major GC count (if available) +- `instance_ruby_gc_time_total` - Total GC time in milliseconds (cumulative) +- `instance_ruby_heap_usage_percent` - Heap memory usage percentage +- `instance_ruby_heap_live_slots_count` - Number of live heap slots +- `instance_ruby_heap_available_slots_count` - Number of available heap slots + +**Thread Metrics:** + +- `instance_ruby_thread_count_active` - Number of active threads (all alive threads) +- `instance_ruby_thread_count_running` - Number of threads in running state + +### Log Integration + +When log reporter is enabled, the agent automatically: + +- **Intercepts Ruby Standard Logger**: Automatically patches the `Logger` class to collect logs +- **Integrates Trace Context**: Adds trace ID, segment ID, and span ID to log messages when available + +#### Supported Logger + +Currently, the agent supports: + +- **Ruby Standard Logger** (`Logger` class) - Automatically intercepted and collected + +#### Log Configuration Options + +```ruby +# Configure log reporter level (default: Logger::INFO) +config[:log_reporter_level] = Logger::INFO + +# Configure log report period in seconds (default: 5) +config[:log_report_period] = 5 + +# Configure maximum log queue size (default: 1000) +config[:max_log_queue_size] = 1000 +``` diff --git a/docs/en/setup/quick-start.md b/docs/en/setup/quick-start.md index 13670c7..4434057 100644 --- a/docs/en/setup/quick-start.md +++ b/docs/en/setup/quick-start.md @@ -33,7 +33,7 @@ gem build skywalking.gemspec If successful, the following will be displayed: -```ruby +```shell Successfully built RubyGem Name: skywalking Version: @@ -84,27 +84,39 @@ The following is an example of configuration at start: Skywalking.start( service_name: 'sw-srv', instance_name: 'sw-inst', - collector_backend_services: 'oap:11800' + collector_backend_services: 'oap:11800', + meter_reporter_active: true, + log_reporter_active: true, + meter_report_period: 30, + log_report_period: 10 ) ~~~ The following is an example of a configuration file: + ~~~yaml common: &defaults service_name: Ruby-Agent-Common log_level: debug + meter_reporter_active: true + log_reporter_active: true + meter_report_period: 20 + log_report_period: 5 development: <<: *defaults service_name: Ruby-Agent-Development + log_reporter_level: 0 # DEBUG test: <<: *defaults service_name: Ruby-Agent-Test + log_reporter_level: 1 # INFO production: <<: *defaults service_name: Ruby-Agent-Production + log_reporter_level: 2 # WARN ~~~ The following lists all the configuration options: @@ -127,3 +139,10 @@ The following lists all the configuration options: | collector_heartbeat_period | SW_AGENT_COLLECTOR_HEARTBEAT_PERIOD | 30 | he agent will send heartbeat to OAP every `collector_heartbeat_period` seconds. | | properties_report_period_factor | SW_AGENT_PROPERTIES_REPORT_PERIOD_FACTOR | 10 | The agent will report service instance properties every `collector_heartbeat_period * properties_report_period_factor` seconds. | | max_queue_size | SW_AGENT_MAX_QUEUE_SIZE | 10000 | The maximum queue size for reporting data. | +| meter_reporter_active | SW_AGENT_METER_REPORTER_ACTIVE | true | Enable/disable meter reporter for runtime metrics collection. | +| meter_report_period | SW_AGENT_METER_REPORT_PERIOD | 60 | Meter report period in seconds. | +| max_meter_queue_size | SW_AGENT_MAX_METER_QUEUE_SIZE | 1000 | Maximum meter queue size for buffering metrics data. | +| log_reporter_active | SW_AGENT_LOG_REPORTER_ACTIVE | true | Enable/disable log reporter for log collection. | +| log_reporter_level | SW_AGENT_LOG_REPORTER_LEVEL | 1 (INFO) | Minimum log level to report (Logger::DEBUG=0, INFO=1, WARN=2, ERROR=3, FATAL=4). | +| log_report_period | SW_AGENT_LOG_REPORT_PERIOD | 5 | Log report period in seconds. | +| max_log_queue_size | SW_AGENT_MAX_LOG_QUEUE_SIZE | 1000 | Maximum log queue size for buffering log data. | diff --git a/docs/menu.yml b/docs/menu.yml index 2fa797f..c283149 100644 --- a/docs/menu.yml +++ b/docs/menu.yml @@ -23,6 +23,8 @@ catalog: catalog: - name: Supported Plugins path: /en/agent/plugins + - name: Meter and Log Report + path: /en/agent/meter-and-log-report - name: Development and Contribution catalog: - name: How to Release diff --git a/lib/skywalking/agent.rb b/lib/skywalking/agent.rb index 2c61272..977de01 100644 --- a/lib/skywalking/agent.rb +++ b/lib/skywalking/agent.rb @@ -17,6 +17,7 @@ require_relative 'environment' require_relative 'plugins_manager' require_relative 'reporter/report' +require_relative 'reporter/log_buffer_trigger' require_relative 'tracing/span_context' require_relative 'tracing/carrier_item' require_relative 'tracing/segment' @@ -62,13 +63,21 @@ def started? end attr_reader :logger, :agent_config + + # Get the singleton instance + # @return [Agent, nil] the agent instance or nil if not started + def instance + @agent + end end - attr_reader :plugins, :reporter + attr_reader :plugins, :reporter, :log_buffer, :config def initialize(config) + @config = config @plugins = Plugins::PluginsManager.new(config) @reporter = Reporter::Report.new(config) + @log_buffer = Reporter::LogBufferTrigger.new(config) add_shutdown_hook end @@ -80,6 +89,9 @@ def environment def start! @plugins.init_plugins @reporter.init_reporter + # Start log reporting thread + start_log_reporting_thread if @config[:log_reporter_active] + self end @@ -94,5 +106,46 @@ def add_shutdown_hook shutdown end end + + # Check if log reporter is active + # @return [Boolean] true if log reporter is active + def log_reporter_active? + @config[:log_reporter_active] + end + + private + + # Start the log reporting thread + def start_log_reporting_thread + Thread.new do + loop do + break unless log_reporter_active? + + process_log_queue + sleep @config[:log_report_period] + end + end + end + + # Process the log queue and send data to the server + def process_log_queue + log_count = 0 + enumerator = Enumerator.new do |yielder| + while (log_data = log_buffer.stream_data) + log_data.each do |data| + log_count += 1 + yielder << data + end + end + end + + enumerator.each_slice(10) do |batch| + begin + reporter.report_log(batch) + rescue => e + Agent.logger.warn "Failed to report log data: #{e.message}" + end + end + end end end diff --git a/lib/skywalking/configuration.rb b/lib/skywalking/configuration.rb index 6efe3ae..0e4dc16 100644 --- a/lib/skywalking/configuration.rb +++ b/lib/skywalking/configuration.rb @@ -100,6 +100,46 @@ class Configuration default: 10000, desc: 'The maximum queue size for reporting data' }, + :meter_reporter_active => { + type: :bool, + default: true, + desc: 'Enable meter reporter' + }, + :runtime_meter_reporter_active => { + type: :bool, + default: true, + desc: 'Enable Ruby runtime meter reporter' + }, + :meter_report_period => { + type: :int, + default: 60, + desc: 'Meter report period in seconds' + }, + :max_meter_queue_size => { + type: :int, + default: 1000, + desc: 'Maximum meter queue size' + }, + :log_reporter_active => { + type: :bool, + default: true, + desc: 'Enable log reporter' + }, + :log_reporter_level => { + type: :int, + default: Logger::INFO, + desc: 'Minimum log level to report (Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR, Logger::FATAL)' + }, + :log_report_period => { + type: :int, + default: 5, + desc: 'Log report period in seconds' + }, + :max_log_queue_size => { + type: :int, + default: 1000, + desc: 'Maximum log queue size' + } }.freeze # @api private @@ -173,18 +213,18 @@ def override_config_by_env next if env_value.nil? type = env_schema[:type] - case type - when :string - new_config[env_key] = env_value.to_s - when :bool - # rubocop:disable Performance/CollectionLiteralInLoop - new_config[env_key] = !%w[0 false].include?(env_value.strip.downcase) - # rubocop:enable Performance/CollectionLiteralInLoop - when :int - new_config[env_key] = env_value.to_s - else - env_value - end + new_config[env_key] = case type + when :string + env_value.to_s + when :bool + # rubocop:disable Performance/CollectionLiteralInLoop + !%w[0 false].include?(env_value.strip.downcase) + # rubocop:enable Performance/CollectionLiteralInLoop + when :int + env_value.to_i + else + env_value + end end new_config diff --git a/lib/skywalking/environment.rb b/lib/skywalking/environment.rb index aa914e2..8fa1efd 100644 --- a/lib/skywalking/environment.rb +++ b/lib/skywalking/environment.rb @@ -37,7 +37,7 @@ def app_name rescue nil end - + def env ::Rails.env end @@ -63,7 +63,7 @@ def app_name rescue "Sinatra" end - + def env ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' end @@ -81,7 +81,7 @@ def present? def app_name "Ruby" end - + def env ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' end @@ -111,7 +111,7 @@ def framework_root "." end end - + def framework_env @framework_env ||= framework_info.env end diff --git a/lib/skywalking/meter.rb b/lib/skywalking/meter.rb new file mode 100644 index 0000000..7673fc5 --- /dev/null +++ b/lib/skywalking/meter.rb @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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_relative 'meter/base' +require_relative 'meter/meter_service' +require_relative 'meter/runtime/cpu_data_source' +require_relative 'meter/runtime/mem_data_source' +require_relative 'meter/runtime/gc_data_source' +require_relative 'meter/runtime/thread_data_source' + +module Skywalking + # Main module for meter functionality + module Meter + # Classes are already defined through require statements above + # No need to reassign them to themselves + end +end diff --git a/lib/skywalking/meter/base.rb b/lib/skywalking/meter/base.rb new file mode 100644 index 0000000..fac3f2b --- /dev/null +++ b/lib/skywalking/meter/base.rb @@ -0,0 +1,79 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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_relative '../reporter/client/proto' + +module Skywalking + module Meter + # Base class for all data sources + class DataSource + # Automatically register all generator methods as gauges + # @param meter_service [MeterService] the service to register gauges with + def register(meter_service) + methods.grep(/_generator$/).each do |method_name| + metric_name = "instance_ruby_#{method_name.to_s.sub('_generator', '')}" + # Create a lambda that calls the generator method + getter = lambda { send(method_name) } + gauge = Gauge.new(metric_name, getter) + meter_service.register(gauge) + end + end + end + + # Represents a gauge metric that reports instantaneous values + class Gauge + attr_reader :name + + # @param name [String] metric name + # @param getter [Proc] a callable that returns the current value + def initialize(name, getter) + @name = name + @getter = getter + @labels = [] + end + + # Add a label to this gauge + # @param key [String] label key + # @param value [String] label value + # @return [self] + def add_label(key, value) + @labels << Label.new(name: key, value: value) + self + end + + # Collect current metric value + # @return [MeterData] meter data + def collect + value = @getter.call + MeterData.new( + singleValue: MeterSingleValue.new( + name: @name, + value: value.to_f, + labels: @labels + ) + ) + rescue + # Return zero value if getter fails + MeterData.new( + singleValue: MeterSingleValue.new( + name: @name, + value: 0.0, + labels: @labels + ) + ) + end + end + end +end diff --git a/lib/skywalking/meter/meter_service.rb b/lib/skywalking/meter/meter_service.rb new file mode 100644 index 0000000..0d8eff7 --- /dev/null +++ b/lib/skywalking/meter/meter_service.rb @@ -0,0 +1,99 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. + +module Skywalking + module Meter + # Service to manage metric collection and reporting + class MeterService + include Log::Logging + + # @param config [Hash] configuration + # @param meter_trigger [MeterBufferTrigger] buffer trigger for queuing data + def initialize(config, meter_trigger) + @config = config + @gauges = [] + @meter_trigger = meter_trigger + @running = false + @mutex = Mutex.new + @collector_thread = nil + end + + # Register a gauge for collection + # @param gauge [Gauge] the gauge to register + def register(gauge) + @mutex.synchronize do + @gauges << gauge + end + end + + # Start the meter collection service + def start + return if @running + + @running = true + + @collector_thread = Thread.new do + Thread.current.name = 'MeterCollector' + run_collection_loop + end + end + + # Stop the meter collection service + def stop + return unless @running + + @running = false + @collector_thread&.join(5) + end + + private + + def run_collection_loop + period = @config[:meter_report_period] + + while @running + begin + collect_and_queue_metrics + sleep period + rescue => e + error "Error in meter collection loop: #{e.message}" + error e.backtrace.join("\n") + sleep period + end + end + end + + def collect_and_queue_metrics + @mutex.synchronize do + collected_count = 0 + @gauges.each do |gauge| + begin + meter_data = gauge.collect + if meter_data + meter_data.service = @config[:service_name] + meter_data.serviceInstance = @config[:instance_name] + meter_data.timestamp = (Time.now.to_f * 1000).to_i + @meter_trigger << meter_data + collected_count += 1 + end + rescue => e + warn "Error collecting gauge #{gauge.name}: #{e.message}" + end + end + end + end + end + end +end diff --git a/lib/skywalking/meter/runtime/cpu_data_source.rb b/lib/skywalking/meter/runtime/cpu_data_source.rb new file mode 100644 index 0000000..29269bc --- /dev/null +++ b/lib/skywalking/meter/runtime/cpu_data_source.rb @@ -0,0 +1,57 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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_relative '../base' + +module Skywalking + module Meter + module Runtime + # DataSource for collecting Ruby process CPU usage metrics + class CpuDataSource < DataSource + def initialize + @last_cpu_time = Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID) + @last_wall_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + + # Ruby process CPU usage percentage + def cpu_usage_percent_generator + begin + current_cpu_time = Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID) + current_wall_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + cpu_time_diff = current_cpu_time - @last_cpu_time + wall_time_diff = current_wall_time - @last_wall_time + + # Calculate CPU usage as percentage of wall time + if wall_time_diff > 0 + cpu_usage = (cpu_time_diff / wall_time_diff) * 100.0 + cpu_usage = [cpu_usage, 0.0].max + else + cpu_usage = 0.0 + end + + # Update last values for next calculation + @last_cpu_time = current_cpu_time + @last_wall_time = current_wall_time + + cpu_usage + rescue + 0.0 + end + end + end + end + end +end diff --git a/lib/skywalking/meter/runtime/gc_data_source.rb b/lib/skywalking/meter/runtime/gc_data_source.rb new file mode 100644 index 0000000..428a709 --- /dev/null +++ b/lib/skywalking/meter/runtime/gc_data_source.rb @@ -0,0 +1,99 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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_relative '../base' + +module Skywalking + module Meter + module Runtime + # DataSource for collecting GC metrics + class GcDataSource < DataSource + def initialize + @cached_stats = nil + @cached_count = nil + @cache_time = 0 + @cache_duration = 60 + end + + # Total GC count + def gc_count_total_generator + get_gc_data + @cached_count || 0 + rescue + 0 + end + + # Minor GC count (if available) + def gc_minor_count_total_generator + stats = get_gc_stats + stats[:minor_gc_count] || 0 + rescue + 0 + end + + # Major GC count (if available) + def gc_major_count_total_generator + stats = get_gc_stats + stats[:major_gc_count] || 0 + rescue + 0 + end + + # GC total time + def gc_time_total_generator + stats = get_gc_stats + stats[:time] || 0 + rescue + 0 + end + + # Heap usage percentage + def heap_usage_percent_generator + stats = get_gc_stats + if stats[:heap_available_slots] && stats[:heap_available_slots] > 0 + (stats[:heap_live_slots].to_f / stats[:heap_available_slots] * 100) + else + 0.0 + end + rescue + 0.0 + end + + private + + # Get cached GC statistics, refresh if cache is expired + def get_gc_stats + current_time = Time.now.to_i + if @cached_stats.nil? || (current_time - @cache_time) > @cache_duration + @cached_stats = GC.stat + @cache_time = current_time + end + @cached_stats + end + + # Get both GC.count and GC.stat data, refresh if cache is expired + def get_gc_data + current_time = Time.now.to_i + if @cached_stats.nil? || (current_time - @cache_time) > @cache_duration + @cached_stats = GC.stat + @cached_count = GC.count + @cache_time = current_time + end + @cached_stats + end + end + end + end +end diff --git a/lib/skywalking/meter/runtime/mem_data_source.rb b/lib/skywalking/meter/runtime/mem_data_source.rb new file mode 100644 index 0000000..3aba09f --- /dev/null +++ b/lib/skywalking/meter/runtime/mem_data_source.rb @@ -0,0 +1,137 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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 'get_process_mem' +require_relative '../base' + +module Skywalking + module Meter + module Runtime + # DataSource for collecting memory usage metrics + class MemDataSource < DataSource + def initialize + @process_mem = GetProcessMem.new + @cached_total_memory = nil + @cache_time = 0 + @cache_duration = 60 + @ffi_loaded = false + load_ffi_if_available + end + + # Process RSS (Resident Set Size) in MB + def memory_rss_mb_generator + @process_mem.bytes / (1024.0 * 1024.0) + rescue + 0.0 + end + + # Process memory usage percentage + def memory_usage_percent_generator + rss_bytes = @process_mem.bytes + total_memory = get_total_memory + + if total_memory && total_memory > 0 + (rss_bytes.to_f / total_memory * 100).round(2) + end + rescue + nil + end + + private + + # Load FFI if available for more efficient system calls + def load_ffi_if_available + require 'ffi' + @ffi_loaded = true + rescue LoadError + @ffi_loaded = false + end + + # Get total system memory with caching to avoid frequent system calls + def get_total_memory + current_time = Time.now.to_i + + if @cached_total_memory.nil? || (current_time - @cache_time) > @cache_duration + @cached_total_memory = fetch_total_memory + @cache_time = current_time + end + + @cached_total_memory + end + + # Fetch total memory using platform-specific methods + def fetch_total_memory + if RUBY_PLATFORM.include?('linux') + fetch_linux_total_memory + elsif RUBY_PLATFORM.include?('darwin') + fetch_macos_total_memory + end + end + + # Fetch total memory on Linux using /proc/meminfo + def fetch_linux_total_memory + meminfo = File.read('/proc/meminfo') + total = (meminfo.match(/MemTotal:\s+(\d+)/)&.captures&.first || '0').to_i * 1024 + total > 0 ? total : nil + rescue + nil + end + + # Fetch total memory on macOS using sysctl + def fetch_macos_total_memory + if @ffi_loaded + result = fetch_macos_total_memory_ffi + return result if result + end + + fetch_macos_total_memory_sysctl + end + + # Use FFI to call sysctl directly + def fetch_macos_total_memory_ffi + extend FFI::Library + ffi_lib 'c' + + attach_function :sysctl, [:pointer, :uint, :pointer, :pointer, :pointer, :size_t], :int + + # sysctlnametomib for hw.memsize + mib = FFI::MemoryPointer.new(:int, 2) + mib.put_int(0, 6) # CTL_HW + mib.put_int(4, 24) # HW_MEMSIZE + + size = FFI::MemoryPointer.new(:size_t) + size.put_size_t(0, 8) + + result = FFI::MemoryPointer.new(:uint64_t) + + if sysctl(mib, 2, result, size, nil, 0) == 0 + value = result.read_uint64 + value > 0 ? value : nil + end + rescue + nil + end + + # Fallback to sysctl command (still forks but cached) + def fetch_macos_total_memory_sysctl + result = `sysctl -n hw.memsize 2>/dev/null`.strip.to_i + result > 0 ? result : nil + rescue + nil + end + end + end + end +end diff --git a/lib/skywalking/meter/runtime/ruby_runtime_data_source.rb b/lib/skywalking/meter/runtime/ruby_runtime_data_source.rb new file mode 100644 index 0000000..23daf13 --- /dev/null +++ b/lib/skywalking/meter/runtime/ruby_runtime_data_source.rb @@ -0,0 +1,59 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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_relative '../base' + +module Skywalking + module Meter + module Runtime + # Enhanced Ruby-specific runtime metrics + class RubyRuntimeDataSource < DataSource + def initialize + @cached_stats = nil + @cache_time = 0 + @cache_duration = 60 + end + + # Heap live slots count - important for memory pressure + def heap_live_slots_count_generator + stats = get_gc_stats + stats[:heap_live_slots] || 0 + rescue + 0 + end + + # Heap available slots count - total capacity + def heap_available_slots_count_generator + stats = get_gc_stats + stats[:heap_available_slots] || 0 + rescue + 0 + end + + private + + # Get cached GC statistics, refresh if cache is expired + def get_gc_stats + current_time = Time.now.to_i + if @cached_stats.nil? || (current_time - @cache_time) > @cache_duration + @cached_stats = GC.stat + @cache_time = current_time + end + @cached_stats + end + end + end + end +end diff --git a/lib/skywalking/meter/runtime/thread_data_source.rb b/lib/skywalking/meter/runtime/thread_data_source.rb new file mode 100644 index 0000000..35549f7 --- /dev/null +++ b/lib/skywalking/meter/runtime/thread_data_source.rb @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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_relative '../base' + +module Skywalking + module Meter + module Runtime + # DataSource for collecting thread metrics + class ThreadDataSource < DataSource + # Active thread count (alive threads) + def thread_count_active_generator + Thread.list.count(&:alive?) + rescue + 0 + end + + # Running thread count (threads in run state) + def thread_count_running_generator + Thread.list.count { |t| t.alive? && t.status == "run" } + rescue + 0 + end + end + end + end +end diff --git a/lib/skywalking/plugins/logger.rb b/lib/skywalking/plugins/logger.rb new file mode 100644 index 0000000..b865f2a --- /dev/null +++ b/lib/skywalking/plugins/logger.rb @@ -0,0 +1,168 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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 'logger' + +module Skywalking + module Plugins + # Logger plugin intercepts Ruby's standard Logger to collect logs + class Logger < PluginsManager::SWPlugin + module LoggerIntercept + # Thread-local flag to prevent recursive log collection + COLLECTING_LOG_KEY = :skywalking_collecting_log + + # Map Logger severity levels to string names + SEVERITY_NAMES = { + ::Logger::DEBUG => 'DEBUG', + ::Logger::INFO => 'INFO', + ::Logger::WARN => 'WARN', + ::Logger::ERROR => 'ERROR', + ::Logger::FATAL => 'FATAL', + ::Logger::UNKNOWN => 'UNKNOWN' + }.freeze + + # Map severity names to levels + SEVERITY_LEVELS = { + 'DEBUG' => ::Logger::DEBUG, + 'INFO' => ::Logger::INFO, + 'WARN' => ::Logger::WARN, + 'ERROR' => ::Logger::ERROR, + 'FATAL' => ::Logger::FATAL, + 'UNKNOWN' => ::Logger::UNKNOWN + }.freeze + + # Override the add method to intercept log calls + # @param severity [Integer] log severity level + # @param message [String, nil] log message + # @param progname [String, nil] program name + # @param block [Proc, nil] block that returns message + def add(severity, message = nil, progname = nil, &block) + # Call original method first + result = super + + # Skip if we're already collecting logs to prevent recursion + return result if Thread.current[COLLECTING_LOG_KEY] + + # Skip if log reporter is not active + agent = Agent.instance + return result unless agent&.log_reporter_active? + + # Check severity threshold + configured_level = agent.config[:log_reporter_level] + min_level = if configured_level.is_a?(String) + SEVERITY_LEVELS[configured_level.upcase] || ::Logger::INFO + else + configured_level || ::Logger::INFO + end + + return result unless severity >= min_level + + # Set flag to prevent recursion + Thread.current[COLLECTING_LOG_KEY] = true + begin + collect_log_data(severity, message, progname, &block) + rescue => e + agent.logger.warn("SkyWalking log collection error: #{e.message}") if agent.config[:debug_mode] + ensure + # Always clear the flag + Thread.current[COLLECTING_LOG_KEY] = false + end + + result + end + + private + + # Collect log data and send to SkyWalking + # @param severity [Integer] log severity level + # @param message [String, nil] log message + # @param progname [String, nil] program name + # @param block [Proc, nil] block that returns message + def collect_log_data(severity, message, progname, &block) + # Format the message + msg = if message.nil? + if block_given? + yield + else + progname + end + else + message + end + + return if msg.nil? + + # Get current context + begin + context = Tracing::ContextManager.current_context + trace_context = if context && !context.is_a?(Tracing::IgnoredContext) && context.segment + segment = context.segment + span = context.active_span + + V3::TraceContext.new( + traceId: segment.related_traces.first&.to_s, + traceSegmentId: segment.segment_id.to_s, + spanId: span ? span.span_id : -1 + ) + end + rescue + # If tracing context is not available, continue without it + trace_context = nil + end + + # Create log data + agent = Agent.instance + return unless agent # Safety check + + log_data = V3::LogData.new( + timestamp: (Time.now.to_f * 1000).to_i, + service: agent.config[:service_name], + serviceInstance: agent.config[:instance_name], + endpoint: context&.active_span&.operation || '', + body: V3::LogDataBody.new( + type: 'text', + text: V3::TextLog.new(text: msg.to_s) + ), + traceContext: trace_context, + tags: V3::LogTags.new( + data: [ + V3::KeyStringValuePair.new(key: 'level', value: SEVERITY_NAMES[severity] || 'UNKNOWN'), + V3::KeyStringValuePair.new(key: 'logger', value: progname || self.class.name), + V3::KeyStringValuePair.new(key: 'thread', value: Thread.current.name || Thread.current.object_id.to_s) + ] + ), + layer: 'GENERAL' + ) + + # Send to log buffer + agent.log_buffer << log_data + end + end + + # Check if the plugin can be installed + # @return [Boolean] true if Logger is defined + def plugin_valid? + defined?(::Logger) + end + + # Install the plugin by prepending the intercept module + def install + ::Logger.prepend LoggerIntercept + end + + register :logger + end + end +end diff --git a/lib/skywalking/reporter/client/grpc_client.rb b/lib/skywalking/reporter/client/grpc_client.rb index 2d32df1..a87e8a8 100644 --- a/lib/skywalking/reporter/client/grpc_client.rb +++ b/lib/skywalking/reporter/client/grpc_client.rb @@ -33,10 +33,11 @@ def initialize(config) def report_instance_properties begin + properties = gen_service_instance req = InstanceProperties.new( service: @config[:service_name], serviceInstance: @config[:instance_name], - properties: gen_service_instance + properties: properties ) @management_service.report_instance_properties(req) @@ -108,6 +109,49 @@ def report_segment(enumerator) error "Error to report trace segment: #{e.message}}" end end + + class MeterReportServiceGrpc + include Log::Logging + + def initialize(config) + @config = config + @meter_service ||= MeterReportServiceStub.new( + @config[:collector_backend_services], + :this_channel_is_insecure + ) + end + + def report_meter(enumerator) + @meter_service.collect(enumerator) + rescue Exception => e + error "Error to report meter data: #{e.message}" + end + end + + class LogReportServiceGrpc + include Log::Logging + + def initialize(config) + @config = config + @log_service ||= V3::LogReportService::Stub.new( + @config[:collector_backend_services], + :this_channel_is_insecure + ) + end + + def report_log(log_data_array) + return if log_data_array.nil? || log_data_array.empty? + + # Create an enumerator that yields log data + enumerator = Enumerator.new do |yielder| + log_data_array.each { |log_data| yielder << log_data } + end + + @log_service.collect(enumerator) + rescue Exception => e + error "Error to report log data: #{e.message}" + end + end end end end diff --git a/lib/skywalking/reporter/client/proto.rb b/lib/skywalking/reporter/client/proto.rb index de1c639..2f3b6bf 100644 --- a/lib/skywalking/reporter/client/proto.rb +++ b/lib/skywalking/reporter/client/proto.rb @@ -16,6 +16,10 @@ require_relative '../../proto/management/Management_services_pb' require_relative '../../proto/language-agent/Tracing_services_pb' require_relative '../../proto/language-agent/Tracing_pb' +require_relative '../../proto/language-agent/Meter_services_pb' +require_relative '../../proto/language-agent/Meter_pb' +require_relative '../../proto/logging/Logging_services_pb' +require_relative '../../proto/logging/Logging_pb' module Skywalking ManagementServiceStub = Skywalking::V3::ManagementService::Stub @@ -26,4 +30,10 @@ module Skywalking SpanObject = Skywalking::V3::SpanObject SegmentReference = Skywalking::V3::SegmentReference KeyStringValuePair = Skywalking::V3::KeyStringValuePair -end \ No newline at end of file + MeterReportServiceStub = Skywalking::V3::MeterReportService::Stub + MeterData = Skywalking::V3::MeterData + MeterSingleValue = Skywalking::V3::MeterSingleValue + MeterHistogram = Skywalking::V3::MeterHistogram + MeterBucketValue = Skywalking::V3::MeterBucketValue + Label = Skywalking::V3::Label +end diff --git a/lib/skywalking/reporter/grpc.rb b/lib/skywalking/reporter/grpc.rb index db0ee4f..fcd6c3c 100644 --- a/lib/skywalking/reporter/grpc.rb +++ b/lib/skywalking/reporter/grpc.rb @@ -23,6 +23,8 @@ def initialize(config) @cfg = config @ms_client = Skywalking::Reporter::Client::GrpcClient::ManagementServiceGrpc.new(config) @trace_client = Skywalking::Reporter::Client::GrpcClient::TraceSegmentReportServiceGrpc.new(config) + @meter_client = Skywalking::Reporter::Client::GrpcClient::MeterReportServiceGrpc.new(config) + @log_client = Skywalking::Reporter::Client::GrpcClient::LogReportServiceGrpc.new(config) @send_properties_counter = 0 @counter_mutex = Mutex.new end @@ -41,6 +43,14 @@ def report_heartbeat def report_segment(enumerator) @trace_client.report_segment(enumerator) end + + def report_meter(enumerator) + @meter_client.report_meter(enumerator) + end + + def report_log(log_data_array) + @log_client.report_log(log_data_array) + end end end end diff --git a/lib/skywalking/reporter/log_buffer_trigger.rb b/lib/skywalking/reporter/log_buffer_trigger.rb new file mode 100644 index 0000000..b5de8ce --- /dev/null +++ b/lib/skywalking/reporter/log_buffer_trigger.rb @@ -0,0 +1,86 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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_relative 'client/proto' + +module Skywalking + module Reporter + # LogBufferTrigger manages the log data queue and generates protocol messages + class LogBufferTrigger + include Enumerable + + extend Forwardable + def_delegators :@buffer, :push + + # Initialize the log buffer trigger + # @param config [Hash] configuration options + def initialize(config) + @config = config + @max_size = @config[:max_log_queue_size] || 1000 + @buffer = Queue.new + @mutex = Mutex.new + @closed = false + end + + # Check if the buffer is empty + # @return [Boolean] true if empty + def empty? + @buffer.empty? + end + + # Add log data to the buffer + # @param log_data [LogData] the log data to add + def <<(log_data) + @mutex.synchronize do + clear_queue if @buffer.size >= @max_size + @buffer.push(log_data) + end + end + + # Clear the queue + def clear_queue + @buffer.clear + end + + # Close the queue + def close_queue + @mutex.synchronize do + @buffer.close + @closed = true + end + end + + # Check if the queue is closed + # @return [Boolean] true if closed + def closed? + @closed + end + + # Stream log data from the buffer + # @return [Enumerator, nil] enumerator of log data or nil if empty + def stream_data + begin + log_data = @buffer.pop(false) + rescue ThreadError + return nil + end + + Enumerator.new do |yielder| + yielder << log_data unless log_data.nil? + end + end + end + end +end diff --git a/lib/skywalking/reporter/meter_buffer_trigger.rb b/lib/skywalking/reporter/meter_buffer_trigger.rb new file mode 100644 index 0000000..8401b5f --- /dev/null +++ b/lib/skywalking/reporter/meter_buffer_trigger.rb @@ -0,0 +1,94 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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 'forwardable' + +module Skywalking + module Reporter + # Buffer trigger for meter data collection + class MeterBufferTrigger + include Enumerable + + extend Forwardable + def_delegators :@buffer, :push + + # @param config [Hash] configuration + def initialize(config) + @config = config + @max_size = @config[:max_meter_queue_size] || 1000 + @buffer = Queue.new + @mutex = Mutex.new + @closed = false + end + + # Check if buffer is empty + # @return [Boolean] + def empty? + @buffer.empty? + end + + # Add meter data to buffer + # @param meter_data [MeterData] the meter data to add + def <<(meter_data) + @mutex.synchronize do + clear_queue if @buffer.size >= @max_size + @buffer.push(meter_data) + end + end + + # Clear all data from the queue + def clear_queue + @buffer.clear + end + + # Close the queue + def close_queue + @mutex.synchronize do + @buffer.close + @closed = true + end + end + + # Check if the queue is closed + # @return [Boolean] + def closed? + @closed + end + + # Stream meter data from the buffer + # @return [Enumerator, nil] + def stream_data + data_batch = [] + + # Collect up to 100 items or until timeout + deadline = Time.now + 0.1 + while Time.now < deadline && data_batch.size < 100 + begin + meter_data = @buffer.pop(true) + data_batch << meter_data + rescue ThreadError + break + end + end + + return nil if data_batch.empty? + + Enumerator.new do |yielder| + data_batch.each { |data| yielder << data } + end + end + end + end +end diff --git a/lib/skywalking/reporter/protocol.rb b/lib/skywalking/reporter/protocol.rb index 2e5fb28..833bb54 100644 --- a/lib/skywalking/reporter/protocol.rb +++ b/lib/skywalking/reporter/protocol.rb @@ -19,7 +19,7 @@ class Protocol def report_heartbeat raise NotImplementedError 'The report_heartbeat method has not been implemented' end - + def report_segment raise NotImplementedError 'The report_segment method has not been implemented' end diff --git a/lib/skywalking/reporter/report.rb b/lib/skywalking/reporter/report.rb index 6c397d5..59db2cd 100644 --- a/lib/skywalking/reporter/report.rb +++ b/lib/skywalking/reporter/report.rb @@ -16,12 +16,24 @@ require_relative 'grpc' require_relative 'scheduler' require_relative 'buffer_trigger' +require_relative 'meter_buffer_trigger' +require_relative 'reporter_manager' +require_relative '../meter/base' +require_relative '../meter/meter_service' +require_relative '../meter/runtime/cpu_data_source' +require_relative '../meter/runtime/mem_data_source' +require_relative '../meter/runtime/gc_data_source' +require_relative '../meter/runtime/thread_data_source' +require_relative '../meter/runtime/ruby_runtime_data_source' module Skywalking module Reporter class Report def initialize(config) @config = config + @meter_service = nil + @scheduler_loop = nil + @reporter_manager = nil init_proto end @@ -37,22 +49,48 @@ def init_proto end def init_reporter - @daemon_loop = [] - + # Initialize scheduler for heartbeat @scheduler_loop = Scheduler.new - @daemon_loop << Thread.new do + @scheduler_thread = Thread.new do + Thread.current.name = 'Scheduler' init_worker_loop @scheduler_loop.run end - @@trigger = BufferTrigger.new(@config) - @daemon_loop << Thread.new do - report_segment + # Initialize reporter manager + @reporter_manager = ReporterManager.new(@config, @protocol) + + # Register segment reporter + segment_trigger = BufferTrigger.new(@config) + @reporter_manager.register_reporter(:segment, segment_trigger, :report_segment) + + # Register meter reporter if enabled + if @config[:meter_reporter_active] + meter_trigger = MeterBufferTrigger.new(@config) + @reporter_manager.register_reporter(:meter, meter_trigger, :report_meter) + + # Initialize meter service + @meter_service = Skywalking::Meter::MeterService.new(@config, meter_trigger) + + if @config[:runtime_meter_reporter_active] + register_runtime_data_sources + end + + @meter_service.start end + + # Start all reporters + @reporter_manager.start end + # Deprecated: Use instance method instead def self.trigger - @@trigger + warn "[DEPRECATED] Report.trigger is deprecated. Use instance method 'trigger' instead." + default_instance.trigger + end + + def self.default_instance + @default_instance ||= new(Skywalking::Configuration.new) end def init_worker_loop @@ -60,23 +98,42 @@ def init_worker_loop end def stop - @scheduler_loop.shutdown - @@trigger.close_queue - @daemon_loop.each do |daemon| - if daemon.alive? - daemon.wakeup - daemon.join - end - end + @scheduler_loop&.shutdown + @scheduler_thread&.join(5) + @reporter_manager&.stop + @meter_service&.stop end def report_heartbeat @protocol.report_heartbeat end - def report_segment - @protocol.report_segment(@@trigger.stream_data) until @@trigger.closed? + # Accessor methods for triggers + def trigger + @reporter_manager&.trigger(:segment) + end + + def meter_trigger + @reporter_manager&.trigger(:meter) + end + + # Report log data to the backend + # @param log_data_array [Array] array of log data to report + def report_log(log_data_array) + @protocol.report_log(log_data_array) if @protocol + rescue => e + warn "Failed to report log data: #{e.message}" + end + + private + + def register_runtime_data_sources + Skywalking::Meter::Runtime::CpuDataSource.new.register(@meter_service) + Skywalking::Meter::Runtime::MemDataSource.new.register(@meter_service) + Skywalking::Meter::Runtime::GcDataSource.new.register(@meter_service) + Skywalking::Meter::Runtime::ThreadDataSource.new.register(@meter_service) + Skywalking::Meter::Runtime::RubyRuntimeDataSource.new.register(@meter_service) end end end -end \ No newline at end of file +end diff --git a/lib/skywalking/reporter/reporter_manager.rb b/lib/skywalking/reporter/reporter_manager.rb new file mode 100644 index 0000000..7be9a9f --- /dev/null +++ b/lib/skywalking/reporter/reporter_manager.rb @@ -0,0 +1,92 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. + +module Skywalking + module Reporter + # Manages all reporters in a more elegant way using dependency injection + class ReporterManager + include Log::Logging + + attr_reader :reporters, :triggers + + def initialize(config, protocol) + @config = config + @protocol = protocol + @reporters = {} + @triggers = {} + @threads = [] + @running = false + end + + # Register a reporter with its trigger + # @param name [Symbol] reporter name + # @param trigger [Object] buffer trigger instance + # @param report_method [Symbol] method name on protocol to call + def register_reporter(name, trigger, report_method) + @triggers[name] = trigger + @reporters[name] = { + trigger: trigger, + report_method: report_method, + thread: nil + } + end + + # Start all registered reporters + def start + return if @running + @running = true + + @reporters.each do |name, reporter| + thread = Thread.new do + Thread.current.name = "Reporter-#{name}" + report_loop(reporter[:trigger], reporter[:report_method]) + end + reporter[:thread] = thread + @threads << thread + end + end + + # Stop all reporters + def stop + @running = false + + @triggers.each_value(&:close_queue) + @threads.each do |thread| + thread.join(5) if thread.alive? + end + @threads.clear + end + + # Get trigger by name + # @param name [Symbol] trigger name + # @return [Object, nil] trigger instance + def trigger(name) + @triggers[name] + end + + private + + def report_loop(trigger, method_name) + while @running && !trigger.closed? + data = trigger.stream_data + @protocol.send(method_name, data) if data + end + rescue => e + error "Error in reporter loop: #{e.message}" + error e.backtrace.join("\n") + end + end + end +end diff --git a/lib/skywalking/tracing/span_context.rb b/lib/skywalking/tracing/span_context.rb index fa882cc..39279a6 100644 --- a/lib/skywalking/tracing/span_context.rb +++ b/lib/skywalking/tracing/span_context.rb @@ -147,7 +147,9 @@ def stop?(span) spans.delete(span) @n_spans -= 1 if @n_spans.zero? - Reporter::Report.trigger << @segment + if (trigger = Agent.instance&.reporter&.trigger) + trigger << @segment + end return true end diff --git a/lib/skywalking/version.rb b/lib/skywalking/version.rb index cdaca0b..b29b4b2 100644 --- a/lib/skywalking/version.rb +++ b/lib/skywalking/version.rb @@ -14,5 +14,5 @@ # limitations under the License. module Skywalking - VERSION = "0.1.0".freeze + VERSION = "0.2.0".freeze end diff --git a/skywalking.gemspec b/skywalking.gemspec index fb0c5b7..824936e 100644 --- a/skywalking.gemspec +++ b/skywalking.gemspec @@ -43,6 +43,11 @@ Gem::Specification.new do |spec| # Communication with OAP spec.add_dependency 'grpc', '~> 1.68.0' + + # Meter dependencies + spec.add_dependency 'sys-cpu', '~> 1.0' + spec.add_dependency 'get_process_mem', '~> 0.2' + spec.add_dependency 'ffi', '~> 1.17', '>= 1.17.0' # Base dev dependency spec.add_development_dependency 'bundler', '~> 2.0' diff --git a/spec/scenarios/elasticsearch/docker-compose.yml b/spec/scenarios/elasticsearch/docker-compose.yml index 880ec79..4a38f1d 100644 --- a/spec/scenarios/elasticsearch/docker-compose.yml +++ b/spec/scenarios/elasticsearch/docker-compose.yml @@ -61,7 +61,7 @@ services: entrypoint: - "sh" - "-c" - - "gem install sinatra rackup puma elasticsearch && ruby /app/spec/scenarios/elasticsearch/elasticsearch.rb" + - "gem install sinatra rackup puma elasticsearch:8.16.1 && ruby /app/spec/scenarios/elasticsearch/elasticsearch.rb" depends_on: oap: condition: service_healthy