a2a-ruby 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/.rspec +3 -0
- data/.rubocop.yml +137 -0
- data/.simplecov +46 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +33 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/CONTRIBUTING.md +165 -0
- data/Gemfile +43 -0
- data/Guardfile +34 -0
- data/LICENSE.txt +21 -0
- data/PUBLISHING_CHECKLIST.md +214 -0
- data/README.md +171 -0
- data/Rakefile +165 -0
- data/docs/agent_execution.md +309 -0
- data/docs/api_reference.md +792 -0
- data/docs/configuration.md +780 -0
- data/docs/events.md +475 -0
- data/docs/getting_started.md +668 -0
- data/docs/integration.md +262 -0
- data/docs/server_apps.md +621 -0
- data/docs/troubleshooting.md +765 -0
- data/lib/a2a/client/api_methods.rb +263 -0
- data/lib/a2a/client/auth/api_key.rb +161 -0
- data/lib/a2a/client/auth/interceptor.rb +288 -0
- data/lib/a2a/client/auth/jwt.rb +189 -0
- data/lib/a2a/client/auth/oauth2.rb +146 -0
- data/lib/a2a/client/auth.rb +137 -0
- data/lib/a2a/client/base.rb +316 -0
- data/lib/a2a/client/config.rb +210 -0
- data/lib/a2a/client/connection_pool.rb +233 -0
- data/lib/a2a/client/http_client.rb +524 -0
- data/lib/a2a/client/json_rpc_handler.rb +136 -0
- data/lib/a2a/client/middleware/circuit_breaker_interceptor.rb +245 -0
- data/lib/a2a/client/middleware/logging_interceptor.rb +371 -0
- data/lib/a2a/client/middleware/rate_limit_interceptor.rb +142 -0
- data/lib/a2a/client/middleware/retry_interceptor.rb +161 -0
- data/lib/a2a/client/middleware.rb +116 -0
- data/lib/a2a/client/performance_tracker.rb +60 -0
- data/lib/a2a/configuration/defaults.rb +34 -0
- data/lib/a2a/configuration/environment_loader.rb +76 -0
- data/lib/a2a/configuration/file_loader.rb +115 -0
- data/lib/a2a/configuration/inheritance.rb +101 -0
- data/lib/a2a/configuration/validator.rb +180 -0
- data/lib/a2a/configuration.rb +201 -0
- data/lib/a2a/errors.rb +291 -0
- data/lib/a2a/modules.rb +50 -0
- data/lib/a2a/monitoring/alerting.rb +490 -0
- data/lib/a2a/monitoring/distributed_tracing.rb +398 -0
- data/lib/a2a/monitoring/health_endpoints.rb +204 -0
- data/lib/a2a/monitoring/metrics_collector.rb +438 -0
- data/lib/a2a/monitoring.rb +463 -0
- data/lib/a2a/plugin.rb +358 -0
- data/lib/a2a/plugin_manager.rb +159 -0
- data/lib/a2a/plugins/example_auth.rb +81 -0
- data/lib/a2a/plugins/example_middleware.rb +118 -0
- data/lib/a2a/plugins/example_transport.rb +76 -0
- data/lib/a2a/protocol/agent_card.rb +8 -0
- data/lib/a2a/protocol/agent_card_server.rb +584 -0
- data/lib/a2a/protocol/capability.rb +496 -0
- data/lib/a2a/protocol/json_rpc.rb +254 -0
- data/lib/a2a/protocol/message.rb +8 -0
- data/lib/a2a/protocol/task.rb +8 -0
- data/lib/a2a/rails/a2a_controller.rb +258 -0
- data/lib/a2a/rails/controller_helpers.rb +499 -0
- data/lib/a2a/rails/engine.rb +167 -0
- data/lib/a2a/rails/generators/agent_generator.rb +311 -0
- data/lib/a2a/rails/generators/install_generator.rb +209 -0
- data/lib/a2a/rails/generators/migration_generator.rb +232 -0
- data/lib/a2a/rails/generators/templates/add_a2a_indexes.rb +57 -0
- data/lib/a2a/rails/generators/templates/agent_controller.rb +122 -0
- data/lib/a2a/rails/generators/templates/agent_controller_spec.rb +160 -0
- data/lib/a2a/rails/generators/templates/agent_readme.md +200 -0
- data/lib/a2a/rails/generators/templates/create_a2a_push_notification_configs.rb +68 -0
- data/lib/a2a/rails/generators/templates/create_a2a_tasks.rb +83 -0
- data/lib/a2a/rails/generators/templates/example_agent_controller.rb +228 -0
- data/lib/a2a/rails/generators/templates/initializer.rb +108 -0
- data/lib/a2a/rails/generators/templates/push_notification_config_model.rb +228 -0
- data/lib/a2a/rails/generators/templates/task_model.rb +200 -0
- data/lib/a2a/rails/tasks/a2a.rake +228 -0
- data/lib/a2a/server/a2a_methods.rb +520 -0
- data/lib/a2a/server/agent.rb +537 -0
- data/lib/a2a/server/agent_execution/agent_executor.rb +279 -0
- data/lib/a2a/server/agent_execution/request_context.rb +219 -0
- data/lib/a2a/server/apps/rack_app.rb +311 -0
- data/lib/a2a/server/apps/sinatra_app.rb +261 -0
- data/lib/a2a/server/default_request_handler.rb +350 -0
- data/lib/a2a/server/events/event_consumer.rb +116 -0
- data/lib/a2a/server/events/event_queue.rb +226 -0
- data/lib/a2a/server/example_agent.rb +248 -0
- data/lib/a2a/server/handler.rb +281 -0
- data/lib/a2a/server/middleware/authentication_middleware.rb +212 -0
- data/lib/a2a/server/middleware/cors_middleware.rb +171 -0
- data/lib/a2a/server/middleware/logging_middleware.rb +362 -0
- data/lib/a2a/server/middleware/rate_limit_middleware.rb +382 -0
- data/lib/a2a/server/middleware.rb +213 -0
- data/lib/a2a/server/push_notification_manager.rb +327 -0
- data/lib/a2a/server/request_handler.rb +136 -0
- data/lib/a2a/server/storage/base.rb +141 -0
- data/lib/a2a/server/storage/database.rb +266 -0
- data/lib/a2a/server/storage/memory.rb +274 -0
- data/lib/a2a/server/storage/redis.rb +320 -0
- data/lib/a2a/server/storage.rb +38 -0
- data/lib/a2a/server/task_manager.rb +534 -0
- data/lib/a2a/transport/grpc.rb +481 -0
- data/lib/a2a/transport/http.rb +415 -0
- data/lib/a2a/transport/sse.rb +499 -0
- data/lib/a2a/types/agent_card.rb +540 -0
- data/lib/a2a/types/artifact.rb +99 -0
- data/lib/a2a/types/base_model.rb +223 -0
- data/lib/a2a/types/events.rb +117 -0
- data/lib/a2a/types/message.rb +106 -0
- data/lib/a2a/types/part.rb +288 -0
- data/lib/a2a/types/push_notification.rb +139 -0
- data/lib/a2a/types/security.rb +167 -0
- data/lib/a2a/types/task.rb +154 -0
- data/lib/a2a/types.rb +88 -0
- data/lib/a2a/utils/helpers.rb +245 -0
- data/lib/a2a/utils/message_buffer.rb +278 -0
- data/lib/a2a/utils/performance.rb +247 -0
- data/lib/a2a/utils/rails_detection.rb +97 -0
- data/lib/a2a/utils/structured_logger.rb +306 -0
- data/lib/a2a/utils/time_helpers.rb +167 -0
- data/lib/a2a/utils/validation.rb +8 -0
- data/lib/a2a/version.rb +6 -0
- data/lib/a2a-rails.rb +58 -0
- data/lib/a2a.rb +198 -0
- metadata +437 -0
@@ -0,0 +1,438 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "monitor"
|
4
|
+
|
5
|
+
module A2A
|
6
|
+
module Monitoring
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Metrics collector for performance monitoring and alerting
|
12
|
+
#
|
13
|
+
# Collects and aggregates performance metrics, provides alerting capabilities,
|
14
|
+
# and integrates with monitoring systems like Prometheus, StatsD, etc.
|
15
|
+
#
|
16
|
+
module A2A
|
17
|
+
module Monitoring
|
18
|
+
class MetricsCollector
|
19
|
+
include MonitorMixin
|
20
|
+
|
21
|
+
# Metric types
|
22
|
+
COUNTER = :counter
|
23
|
+
GAUGE = :gauge
|
24
|
+
HISTOGRAM = :histogram
|
25
|
+
TIMER = :timer
|
26
|
+
|
27
|
+
attr_reader :metrics, :start_time
|
28
|
+
|
29
|
+
##
|
30
|
+
# Initialize metrics collector
|
31
|
+
#
|
32
|
+
# @param flush_interval [Integer] Interval to flush metrics (seconds)
|
33
|
+
# @param retention_period [Integer] How long to keep metrics (seconds)
|
34
|
+
def initialize(flush_interval: 60, retention_period: 3600)
|
35
|
+
super()
|
36
|
+
|
37
|
+
@metrics = {}
|
38
|
+
@flush_interval = flush_interval
|
39
|
+
@retention_period = retention_period
|
40
|
+
@start_time = Time.now
|
41
|
+
@last_flush = Time.now
|
42
|
+
@exporters = []
|
43
|
+
@alert_rules = []
|
44
|
+
|
45
|
+
start_background_flush if flush_interval.positive?
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Increment a counter metric
|
50
|
+
#
|
51
|
+
# @param name [String] Metric name
|
52
|
+
# @param value [Numeric] Value to add (default: 1)
|
53
|
+
# @param tags [Hash] Metric tags
|
54
|
+
def increment(name, value: 1, **tags)
|
55
|
+
synchronize do
|
56
|
+
metric = get_or_create_metric(name, COUNTER, tags)
|
57
|
+
metric[:value] += value
|
58
|
+
metric[:last_updated] = Time.now
|
59
|
+
|
60
|
+
check_alerts(name, metric[:value], tags)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Set a gauge metric value
|
66
|
+
#
|
67
|
+
# @param name [String] Metric name
|
68
|
+
# @param value [Numeric] Metric value
|
69
|
+
# @param tags [Hash] Metric tags
|
70
|
+
def gauge(name, value, **tags)
|
71
|
+
synchronize do
|
72
|
+
metric = get_or_create_metric(name, GAUGE, tags)
|
73
|
+
metric[:value] = value
|
74
|
+
metric[:last_updated] = Time.now
|
75
|
+
|
76
|
+
check_alerts(name, value, tags)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Record a histogram value
|
82
|
+
#
|
83
|
+
# @param name [String] Metric name
|
84
|
+
# @param value [Numeric] Value to record
|
85
|
+
# @param tags [Hash] Metric tags
|
86
|
+
def histogram(name, value, **tags)
|
87
|
+
synchronize do
|
88
|
+
metric = get_or_create_metric(name, HISTOGRAM, tags)
|
89
|
+
metric[:values] << value
|
90
|
+
metric[:count] += 1
|
91
|
+
metric[:sum] += value
|
92
|
+
metric[:last_updated] = Time.now
|
93
|
+
|
94
|
+
# Calculate percentiles
|
95
|
+
update_histogram_stats(metric)
|
96
|
+
|
97
|
+
check_alerts(name, value, tags)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Time a block of code
|
103
|
+
#
|
104
|
+
# @param name [String] Metric name
|
105
|
+
# @param tags [Hash] Metric tags
|
106
|
+
# @yield Block to time
|
107
|
+
# @return [Object] Result of the block
|
108
|
+
def time(name, **tags)
|
109
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
110
|
+
|
111
|
+
begin
|
112
|
+
result = yield
|
113
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
114
|
+
histogram("#{name}.duration", duration * 1000, **tags) # Convert to milliseconds
|
115
|
+
increment("#{name}.success", **tags)
|
116
|
+
result
|
117
|
+
rescue StandardError
|
118
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
119
|
+
histogram("#{name}.duration", duration * 1000, **tags)
|
120
|
+
increment("#{name}.error", **tags)
|
121
|
+
raise
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# Record HTTP request metrics
|
127
|
+
#
|
128
|
+
# @param method [String] HTTP method
|
129
|
+
# @param path [String] Request path
|
130
|
+
# @param status [Integer] Response status
|
131
|
+
# @param duration [Float] Request duration in seconds
|
132
|
+
# @param tags [Hash] Additional tags
|
133
|
+
def http_request(method:, path:, status:, duration:, **tags)
|
134
|
+
base_tags = {
|
135
|
+
method: method.to_s.upcase,
|
136
|
+
path: normalize_path(path),
|
137
|
+
status: status,
|
138
|
+
**tags
|
139
|
+
}
|
140
|
+
|
141
|
+
increment("http_requests_total", **base_tags)
|
142
|
+
histogram("http_request_duration_ms", duration * 1000, **base_tags)
|
143
|
+
|
144
|
+
# Track error rates
|
145
|
+
return unless status >= 400
|
146
|
+
|
147
|
+
increment("http_requests_errors_total", **base_tags)
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Record task metrics
|
152
|
+
#
|
153
|
+
# @param operation [String] Task operation
|
154
|
+
# @param task_type [String] Type of task
|
155
|
+
# @param status [String] Task status
|
156
|
+
# @param duration [Float, nil] Operation duration
|
157
|
+
# @param tags [Hash] Additional tags
|
158
|
+
def task_operation(operation:, task_type: nil, status: nil, duration: nil, **tags)
|
159
|
+
base_tags = {
|
160
|
+
operation: operation,
|
161
|
+
task_type: task_type,
|
162
|
+
status: status,
|
163
|
+
**tags
|
164
|
+
}.compact
|
165
|
+
|
166
|
+
increment("task_operations_total", **base_tags)
|
167
|
+
|
168
|
+
return unless duration
|
169
|
+
|
170
|
+
histogram("task_operation_duration_ms", duration * 1000, **base_tags)
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# Add a metrics exporter
|
175
|
+
#
|
176
|
+
# @param exporter [Object] Exporter object with export(metrics) method
|
177
|
+
def add_exporter(exporter)
|
178
|
+
synchronize { @exporters << exporter }
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# Remove a metrics exporter
|
183
|
+
#
|
184
|
+
# @param exporter [Object] Exporter to remove
|
185
|
+
def remove_exporter(exporter)
|
186
|
+
synchronize { @exporters.delete(exporter) }
|
187
|
+
end
|
188
|
+
|
189
|
+
##
|
190
|
+
# Add an alert rule
|
191
|
+
#
|
192
|
+
# @param name [String] Alert name
|
193
|
+
# @param metric [String] Metric name to monitor
|
194
|
+
# @param condition [Symbol] Condition (:gt, :lt, :eq, :gte, :lte)
|
195
|
+
# @param threshold [Numeric] Threshold value
|
196
|
+
# @param callback [Proc] Callback to execute when alert fires
|
197
|
+
def add_alert(name, metric:, condition:, threshold:, &callback)
|
198
|
+
synchronize do
|
199
|
+
@alert_rules << {
|
200
|
+
name: name,
|
201
|
+
metric: metric,
|
202
|
+
condition: condition,
|
203
|
+
threshold: threshold,
|
204
|
+
callback: callback,
|
205
|
+
last_fired: nil
|
206
|
+
}
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
##
|
211
|
+
# Get current metrics snapshot
|
212
|
+
#
|
213
|
+
# @return [Hash] Current metrics
|
214
|
+
def snapshot
|
215
|
+
synchronize { deep_copy(@metrics) }
|
216
|
+
end
|
217
|
+
|
218
|
+
##
|
219
|
+
# Get metrics summary
|
220
|
+
#
|
221
|
+
# @return [Hash] Metrics summary
|
222
|
+
def summary
|
223
|
+
synchronize do
|
224
|
+
{
|
225
|
+
total_metrics: @metrics.size,
|
226
|
+
uptime: Time.now - @start_time,
|
227
|
+
last_flush: @last_flush,
|
228
|
+
exporters: @exporters.size,
|
229
|
+
alert_rules: @alert_rules.size,
|
230
|
+
memory_usage: get_memory_usage
|
231
|
+
}
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
##
|
236
|
+
# Flush metrics to exporters
|
237
|
+
#
|
238
|
+
def flush!
|
239
|
+
metrics_snapshot = snapshot
|
240
|
+
|
241
|
+
@exporters.each do |exporter|
|
242
|
+
exporter.export(metrics_snapshot)
|
243
|
+
rescue StandardError => e
|
244
|
+
warn "Failed to export metrics: #{e.message}"
|
245
|
+
end
|
246
|
+
|
247
|
+
synchronize { @last_flush = Time.now }
|
248
|
+
cleanup_old_metrics
|
249
|
+
end
|
250
|
+
|
251
|
+
##
|
252
|
+
# Reset all metrics
|
253
|
+
#
|
254
|
+
def reset!
|
255
|
+
synchronize do
|
256
|
+
@metrics.clear
|
257
|
+
@start_time = Time.now
|
258
|
+
@last_flush = Time.now
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
##
|
263
|
+
# Stop the metrics collector
|
264
|
+
#
|
265
|
+
def stop
|
266
|
+
@flush_thread&.kill
|
267
|
+
@flush_thread = nil
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
##
|
273
|
+
# Get or create a metric
|
274
|
+
#
|
275
|
+
# @param name [String] Metric name
|
276
|
+
# @param type [Symbol] Metric type
|
277
|
+
# @param tags [Hash] Metric tags
|
278
|
+
# @return [Hash] Metric data
|
279
|
+
def get_or_create_metric(name, type, tags)
|
280
|
+
key = build_metric_key(name, tags)
|
281
|
+
|
282
|
+
@metrics[key] ||= {
|
283
|
+
name: name,
|
284
|
+
type: type,
|
285
|
+
tags: tags,
|
286
|
+
value: type == COUNTER ? 0 : nil,
|
287
|
+
values: type == HISTOGRAM ? [] : nil,
|
288
|
+
count: type == HISTOGRAM ? 0 : nil,
|
289
|
+
sum: type == HISTOGRAM ? 0 : nil,
|
290
|
+
created_at: Time.now,
|
291
|
+
last_updated: Time.now
|
292
|
+
}
|
293
|
+
end
|
294
|
+
|
295
|
+
##
|
296
|
+
# Build metric key from name and tags
|
297
|
+
#
|
298
|
+
# @param name [String] Metric name
|
299
|
+
# @param tags [Hash] Metric tags
|
300
|
+
# @return [String] Metric key
|
301
|
+
def build_metric_key(name, tags)
|
302
|
+
if tags.empty?
|
303
|
+
name
|
304
|
+
else
|
305
|
+
tag_string = tags.sort.map { |k, v| "#{k}=#{v}" }.join(",")
|
306
|
+
"#{name}{#{tag_string}}"
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
##
|
311
|
+
# Update histogram statistics
|
312
|
+
#
|
313
|
+
# @param metric [Hash] Histogram metric
|
314
|
+
def update_histogram_stats(metric)
|
315
|
+
values = metric[:values].sort
|
316
|
+
count = values.size
|
317
|
+
|
318
|
+
return if count.zero?
|
319
|
+
|
320
|
+
metric[:min] = values.first
|
321
|
+
metric[:max] = values.last
|
322
|
+
metric[:avg] = metric[:sum].to_f / count
|
323
|
+
|
324
|
+
# Calculate percentiles
|
325
|
+
metric[:p50] = percentile(values, 0.5)
|
326
|
+
metric[:p95] = percentile(values, 0.95)
|
327
|
+
metric[:p99] = percentile(values, 0.99)
|
328
|
+
|
329
|
+
# Keep only recent values to prevent memory growth
|
330
|
+
return unless values.size > 1000
|
331
|
+
|
332
|
+
metric[:values] = values.last(1000)
|
333
|
+
end
|
334
|
+
|
335
|
+
##
|
336
|
+
# Calculate percentile from sorted values
|
337
|
+
#
|
338
|
+
# @param values [Array] Sorted array of values
|
339
|
+
# @param percentile [Float] Percentile (0.0 to 1.0)
|
340
|
+
# @return [Numeric] Percentile value
|
341
|
+
def percentile(values, percentile)
|
342
|
+
return 0 if values.empty?
|
343
|
+
|
344
|
+
index = (percentile * (values.size - 1)).round
|
345
|
+
values[index]
|
346
|
+
end
|
347
|
+
|
348
|
+
##
|
349
|
+
# Check alert rules
|
350
|
+
#
|
351
|
+
# @param metric_name [String] Metric name
|
352
|
+
# @param value [Numeric] Metric value
|
353
|
+
# @param tags [Hash] Metric tags
|
354
|
+
def check_alerts(metric_name, value, tags)
|
355
|
+
@alert_rules.each do |rule|
|
356
|
+
next unless rule[:metric] == metric_name
|
357
|
+
|
358
|
+
should_fire = case rule[:condition]
|
359
|
+
when :gt then value > rule[:threshold]
|
360
|
+
when :gte then value >= rule[:threshold]
|
361
|
+
when :lt then value < rule[:threshold]
|
362
|
+
when :lte then value <= rule[:threshold]
|
363
|
+
when :eq then value == rule[:threshold]
|
364
|
+
else false
|
365
|
+
end
|
366
|
+
|
367
|
+
if should_fire && (rule[:last_fired].nil? || Time.now - rule[:last_fired] > 60)
|
368
|
+
rule[:callback]&.call(rule[:name], metric_name, value, tags)
|
369
|
+
rule[:last_fired] = Time.now
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
##
|
375
|
+
# Normalize URL path for metrics
|
376
|
+
#
|
377
|
+
# @param path [String] URL path
|
378
|
+
# @return [String] Normalized path
|
379
|
+
def normalize_path(path)
|
380
|
+
# Replace IDs and UUIDs with placeholders
|
381
|
+
path.gsub(%r{/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}, "/:uuid")
|
382
|
+
.gsub(%r{/\d+}, "/:id")
|
383
|
+
end
|
384
|
+
|
385
|
+
##
|
386
|
+
# Start background flush thread
|
387
|
+
#
|
388
|
+
def start_background_flush
|
389
|
+
@flush_thread = Thread.new do
|
390
|
+
loop do
|
391
|
+
sleep @flush_interval
|
392
|
+
flush!
|
393
|
+
rescue StandardError => e
|
394
|
+
warn "Error in metrics flush thread: #{e.message}"
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
##
|
400
|
+
# Clean up old metrics
|
401
|
+
#
|
402
|
+
def cleanup_old_metrics
|
403
|
+
cutoff_time = Time.now - @retention_period
|
404
|
+
|
405
|
+
synchronize do
|
406
|
+
@metrics.reject! do |_, metric|
|
407
|
+
metric[:last_updated] < cutoff_time
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
##
|
413
|
+
# Get current memory usage
|
414
|
+
#
|
415
|
+
# @return [Integer] Memory usage in bytes
|
416
|
+
def get_memory_usage
|
417
|
+
if defined?(GC.stat)
|
418
|
+
GC.stat(:heap_allocated_pages) * GC.stat(:heap_page_size)
|
419
|
+
else
|
420
|
+
0
|
421
|
+
end
|
422
|
+
rescue StandardError
|
423
|
+
0
|
424
|
+
end
|
425
|
+
|
426
|
+
##
|
427
|
+
# Deep copy a hash
|
428
|
+
#
|
429
|
+
# @param obj [Object] Object to copy
|
430
|
+
# @return [Object] Deep copy
|
431
|
+
def deep_copy(obj)
|
432
|
+
Marshal.load(Marshal.dump(obj))
|
433
|
+
rescue StandardError
|
434
|
+
obj.dup
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|