Skip to content

Commit d3cefa9

Browse files
supports collecting metrics and logs
1 parent da84efe commit d3cefa9

22 files changed

+1213
-25
lines changed

docs/en/setup/MeterReporter.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Ruby Agent Meter Reporter
2+
3+
The meter reporter feature enables collection and reporting of runtime metrics to the SkyWalking OAP backend.
4+
5+
## Features
6+
7+
### Runtime Metrics (Enabled by Default)
8+
9+
The agent automatically collects Ruby runtime metrics when `meter_reporter_active` and `runtime_meter_reporter_active` are enabled (default: true).
10+
11+
#### Collected Metrics
12+
13+
**CPU Metrics:**
14+
- `instance_ruby_total_cpu_utilization` - System total CPU usage percentage
15+
- `instance_ruby_process_cpu_utilization` - Ruby process CPU usage percentage
16+
17+
**Memory Metrics:**
18+
- `instance_ruby_total_mem_utilization` - System total memory usage percentage
19+
- `instance_ruby_process_mem_utilization` - Ruby process memory usage percentage
20+
21+
**Garbage Collection Metrics:**
22+
- `instance_ruby_gc_count` - Total GC execution count
23+
- `instance_ruby_gc_time` - GC execution time (milliseconds)
24+
- `instance_ruby_gc_minor_count` - Minor GC count (if available)
25+
- `instance_ruby_gc_major_count` - Major GC count (if available)
26+
- `instance_ruby_heap_live_slots` - Number of live heap slots
27+
- `instance_ruby_heap_free_slots` - Number of free heap slots
28+
29+
**Thread Metrics:**
30+
- `instance_ruby_thread_active_count` - Number of active threads
31+
- `instance_ruby_thread_count` - Total thread count
32+
- `instance_ruby_fiber_count` - Total fiber count
33+
34+
## Configuration
35+
36+
### Enable/Disable Meter Reporter
37+
38+
```ruby
39+
# Enable meter reporter (default: true)
40+
config[:meter_reporter_active] = true
41+
42+
# Enable runtime metrics (default: true)
43+
config[:runtime_meter_reporter_active] = true
44+
45+
# Or via environment variables
46+
ENV['SW_AGENT_METER_REPORTER_ACTIVE'] = 'true'
47+
ENV['SW_AGENT_RUNTIME_METER_REPORTER_ACTIVE'] = 'true'
48+
```
49+
50+
### Configure Report Period
51+
52+
```ruby
53+
# Set meter report period in seconds (default: 20)
54+
config[:meter_report_period] = 20
55+
56+
# Set maximum meter queue size (default: 1000)
57+
config[:max_meter_queue_size] = 1000
58+
```

lib/skywalking/agent.rb

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
require_relative 'environment'
1818
require_relative 'plugins_manager'
1919
require_relative 'reporter/report'
20+
require_relative 'reporter/log_buffer_trigger'
2021
require_relative 'tracing/span_context'
2122
require_relative 'tracing/carrier_item'
2223
require_relative 'tracing/segment'
@@ -62,13 +63,21 @@ def started?
6263
end
6364

6465
attr_reader :logger, :agent_config
66+
67+
# Get the singleton instance
68+
# @return [Agent, nil] the agent instance or nil if not started
69+
def instance
70+
@agent
71+
end
6572
end
6673

67-
attr_reader :plugins, :reporter
74+
attr_reader :plugins, :reporter, :log_buffer, :config
6875

6976
def initialize(config)
77+
@config = config
7078
@plugins = Plugins::PluginsManager.new(config)
7179
@reporter = Reporter::Report.new(config)
80+
@log_buffer = Reporter::LogBufferTrigger.new(config)
7281

7382
add_shutdown_hook
7483
end
@@ -80,6 +89,9 @@ def environment
8089
def start!
8190
@plugins.init_plugins
8291
@reporter.init_reporter
92+
# Start log reporting thread
93+
start_log_reporting_thread if @config[:log_reporter_active]
94+
8395
self
8496
end
8597

@@ -94,5 +106,44 @@ def add_shutdown_hook
94106
shutdown
95107
end
96108
end
109+
110+
# Check if log reporter is active
111+
# @return [Boolean] true if log reporter is active
112+
def log_reporter_active?
113+
@config[:log_reporter_active]
114+
end
115+
116+
private
117+
118+
# Start the log reporting thread
119+
def start_log_reporting_thread
120+
Thread.new do
121+
loop do
122+
break unless log_reporter_active?
123+
124+
process_log_queue
125+
sleep @config[:log_report_period]
126+
end
127+
end
128+
end
129+
130+
# Process the log queue and send data to the server
131+
def process_log_queue
132+
log_count = 0
133+
Enumerator.new do |yielder|
134+
while (log_data = log_buffer.stream_data)
135+
log_data.each do |data|
136+
log_count += 1
137+
yielder << data
138+
end
139+
end
140+
end.each_slice(10) do |batch|
141+
begin
142+
reporter.report_log(batch)
143+
rescue => e
144+
Agent.logger.warn "Failed to report log data: #{e.message}"
145+
end
146+
end
147+
end
97148
end
98149
end

lib/skywalking/configuration.rb

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,46 @@ class Configuration
100100
default: 10000,
101101
desc: 'The maximum queue size for reporting data'
102102
},
103+
:meter_reporter_active => {
104+
type: :bool,
105+
default: true,
106+
desc: 'Enable meter reporter'
107+
},
108+
:runtime_meter_reporter_active => {
109+
type: :bool,
110+
default: true,
111+
desc: 'Enable Ruby runtime meter reporter'
112+
},
113+
:meter_report_period => {
114+
type: :int,
115+
default: 20,
116+
desc: 'Meter report period in seconds'
117+
},
118+
:max_meter_queue_size => {
119+
type: :int,
120+
default: 1000,
121+
desc: 'Maximum meter queue size'
122+
},
123+
:log_reporter_active => {
124+
type: :bool,
125+
default: true,
126+
desc: 'Enable log reporter'
127+
},
128+
:log_reporter_level => {
129+
type: :int,
130+
default: Logger::INFO,
131+
desc: 'Minimum log level to report (Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR, Logger::FATAL)'
132+
},
133+
:log_report_period => {
134+
type: :int,
135+
default: 5,
136+
desc: 'Log report period in seconds'
137+
},
138+
:max_log_queue_size => {
139+
type: :int,
140+
default: 1000,
141+
desc: 'Maximum log queue size'
142+
}
103143
}.freeze
104144

105145
# @api private
@@ -181,7 +221,7 @@ def override_config_by_env
181221
new_config[env_key] = !%w[0 false].include?(env_value.strip.downcase)
182222
# rubocop:enable Performance/CollectionLiteralInLoop
183223
when :int
184-
new_config[env_key] = env_value.to_s
224+
new_config[env_key] = env_value.to_i
185225
else
186226
env_value
187227
end

lib/skywalking/meter.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
require_relative 'meter/base'
17+
require_relative 'meter/meter_service'
18+
require_relative 'meter/runtime/cpu_data_source'
19+
require_relative 'meter/runtime/mem_data_source'
20+
require_relative 'meter/runtime/gc_data_source'
21+
require_relative 'meter/runtime/thread_data_source'
22+
23+
module Skywalking
24+
# Main module for meter functionality
25+
module Meter
26+
# Export key classes for external use
27+
DataSource = DataSource
28+
Gauge = Gauge
29+
MeterService = MeterService
30+
31+
module Runtime
32+
CpuDataSource = CpuDataSource
33+
MemDataSource = MemDataSource
34+
GcDataSource = GcDataSource
35+
ThreadDataSource = ThreadDataSource
36+
end
37+
end
38+
end

lib/skywalking/meter/base.rb

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
require_relative '../reporter/client/proto'
17+
18+
module Skywalking
19+
module Meter
20+
# Base class for all data sources
21+
class DataSource
22+
# Automatically register all generator methods as gauges
23+
# @param meter_service [MeterService] the service to register gauges with
24+
def register(meter_service)
25+
methods.grep(/_generator$/).each do |method_name|
26+
metric_name = "instance_ruby_#{method_name.to_s.sub('_generator', '')}"
27+
# Create a lambda that calls the generator method
28+
getter = lambda { send(method_name) }
29+
gauge = Gauge.new(metric_name, getter)
30+
meter_service.register(gauge)
31+
end
32+
end
33+
end
34+
35+
# Represents a gauge metric that reports instantaneous values
36+
class Gauge
37+
attr_reader :name
38+
39+
# @param name [String] metric name
40+
# @param getter [Proc] a callable that returns the current value
41+
def initialize(name, getter)
42+
@name = name
43+
@getter = getter
44+
@labels = []
45+
end
46+
47+
# Add a label to this gauge
48+
# @param key [String] label key
49+
# @param value [String] label value
50+
# @return [self]
51+
def add_label(key, value)
52+
@labels << Label.new(name: key, value: value)
53+
self
54+
end
55+
56+
# Collect current metric value
57+
# @return [MeterData] meter data
58+
def collect
59+
value = @getter.call
60+
MeterData.new(
61+
singleValue: MeterSingleValue.new(
62+
name: @name,
63+
value: value.to_f,
64+
labels: @labels
65+
)
66+
)
67+
rescue
68+
# Return zero value if getter fails
69+
MeterData.new(
70+
singleValue: MeterSingleValue.new(
71+
name: @name,
72+
value: 0.0,
73+
labels: @labels
74+
)
75+
)
76+
end
77+
end
78+
end
79+
end

0 commit comments

Comments
 (0)