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,278 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
require "zlib"
|
5
|
+
require_relative "performance"
|
6
|
+
|
7
|
+
##
|
8
|
+
# Memory-efficient message buffer for handling large messages
|
9
|
+
#
|
10
|
+
# Provides streaming capabilities and compression for large message handling
|
11
|
+
# to optimize memory usage and performance.
|
12
|
+
#
|
13
|
+
module A2A
|
14
|
+
module Utils
|
15
|
+
class MessageBuffer
|
16
|
+
# Default buffer size (64KB)
|
17
|
+
DEFAULT_BUFFER_SIZE = 64 * 1024
|
18
|
+
|
19
|
+
# Compression threshold (1MB)
|
20
|
+
COMPRESSION_THRESHOLD = 1024 * 1024
|
21
|
+
|
22
|
+
attr_reader :size, :compressed, :encoding
|
23
|
+
|
24
|
+
##
|
25
|
+
# Initialize a new message buffer
|
26
|
+
#
|
27
|
+
# @param initial_capacity [Integer] Initial buffer capacity
|
28
|
+
# @param compress [Boolean] Whether to use compression for large messages
|
29
|
+
# @param encoding [Encoding] Text encoding to use
|
30
|
+
def initialize(initial_capacity: DEFAULT_BUFFER_SIZE, compress: true, encoding: Encoding::UTF_8)
|
31
|
+
@buffer = StringIO.new
|
32
|
+
@buffer.set_encoding(encoding)
|
33
|
+
@initial_capacity = initial_capacity
|
34
|
+
@compress = compress
|
35
|
+
@compressed = false
|
36
|
+
@encoding = encoding
|
37
|
+
@chunks = []
|
38
|
+
@total_size = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Write data to the buffer
|
43
|
+
#
|
44
|
+
# @param data [String] Data to write
|
45
|
+
# @return [Integer] Number of bytes written
|
46
|
+
def write(data)
|
47
|
+
data = data.to_s.encode(@encoding) unless data.encoding == @encoding
|
48
|
+
|
49
|
+
if @compressed
|
50
|
+
# If already compressed, decompress first
|
51
|
+
decompress!
|
52
|
+
end
|
53
|
+
|
54
|
+
bytes_written = @buffer.write(data)
|
55
|
+
@total_size += bytes_written
|
56
|
+
|
57
|
+
# Compress if buffer gets too large
|
58
|
+
compress! if @compress && @total_size > COMPRESSION_THRESHOLD && !@compressed
|
59
|
+
|
60
|
+
bytes_written
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Append data to the buffer (alias for write)
|
65
|
+
#
|
66
|
+
# @param data [String] Data to append
|
67
|
+
# @return [Integer] Number of bytes written
|
68
|
+
def <<(data)
|
69
|
+
write(data)
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Read data from the buffer
|
74
|
+
#
|
75
|
+
# @param length [Integer, nil] Number of bytes to read (nil for all)
|
76
|
+
# @return [String] Data read from buffer
|
77
|
+
def read(length = nil)
|
78
|
+
decompress! if @compressed
|
79
|
+
|
80
|
+
@buffer.rewind
|
81
|
+
data = @buffer.read(length)
|
82
|
+
@buffer.rewind
|
83
|
+
data
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Get the current size of the buffer
|
88
|
+
#
|
89
|
+
# @return [Integer] Size in bytes
|
90
|
+
def size
|
91
|
+
@compressed ? @compressed_size : @buffer.size
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Check if buffer is empty
|
96
|
+
#
|
97
|
+
# @return [Boolean] True if buffer is empty
|
98
|
+
def empty?
|
99
|
+
size.zero?
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Clear the buffer
|
104
|
+
#
|
105
|
+
def clear!
|
106
|
+
@buffer = StringIO.new
|
107
|
+
@buffer.set_encoding(@encoding)
|
108
|
+
@compressed = false
|
109
|
+
@compressed_size = 0
|
110
|
+
@total_size = 0
|
111
|
+
@chunks.clear
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Get buffer contents as string
|
116
|
+
#
|
117
|
+
# @return [String] Buffer contents
|
118
|
+
def to_s
|
119
|
+
read
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Get buffer contents as JSON
|
124
|
+
#
|
125
|
+
# @return [String] JSON representation
|
126
|
+
def to_json(*_args)
|
127
|
+
A2A::Utils::Performance.optimized_json_generate(to_s)
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Create buffer from JSON string
|
132
|
+
#
|
133
|
+
# @param json_string [String] JSON string
|
134
|
+
# @return [MessageBuffer] New buffer with JSON data
|
135
|
+
def self.from_json(json_string)
|
136
|
+
data = A2A::Utils::Performance.optimized_json_parse(json_string)
|
137
|
+
buffer = new
|
138
|
+
buffer.write(data.to_s)
|
139
|
+
buffer
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Stream data in chunks
|
144
|
+
#
|
145
|
+
# @param chunk_size [Integer] Size of each chunk
|
146
|
+
# @yield [String] Each chunk of data
|
147
|
+
def each_chunk(chunk_size = DEFAULT_BUFFER_SIZE)
|
148
|
+
return enum_for(:each_chunk, chunk_size) unless block_given?
|
149
|
+
|
150
|
+
decompress! if @compressed
|
151
|
+
|
152
|
+
@buffer.rewind
|
153
|
+
|
154
|
+
while (chunk = @buffer.read(chunk_size))
|
155
|
+
yield chunk
|
156
|
+
end
|
157
|
+
|
158
|
+
@buffer.rewind
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Compress buffer contents
|
163
|
+
#
|
164
|
+
def compress!
|
165
|
+
return if @compressed || @buffer.size < COMPRESSION_THRESHOLD
|
166
|
+
|
167
|
+
@buffer.rewind
|
168
|
+
original_data = @buffer.read
|
169
|
+
@buffer.rewind
|
170
|
+
|
171
|
+
compressed_data = Zlib::Deflate.deflate(original_data)
|
172
|
+
|
173
|
+
# Only use compression if it actually saves space
|
174
|
+
return unless compressed_data.size < original_data.size
|
175
|
+
|
176
|
+
@buffer = StringIO.new(compressed_data)
|
177
|
+
@compressed = true
|
178
|
+
@compressed_size = compressed_data.size
|
179
|
+
@original_size = original_data.size
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# Decompress buffer contents
|
184
|
+
#
|
185
|
+
def decompress!
|
186
|
+
return unless @compressed
|
187
|
+
|
188
|
+
@buffer.rewind
|
189
|
+
compressed_data = @buffer.read
|
190
|
+
@buffer.rewind
|
191
|
+
|
192
|
+
original_data = Zlib::Inflate.inflate(compressed_data)
|
193
|
+
|
194
|
+
@buffer = StringIO.new
|
195
|
+
@buffer.set_encoding(@encoding)
|
196
|
+
@buffer.write(original_data)
|
197
|
+
@buffer.rewind
|
198
|
+
|
199
|
+
@compressed = false
|
200
|
+
@total_size = original_data.size
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# Get compression ratio
|
205
|
+
#
|
206
|
+
# @return [Float] Compression ratio (0.0 to 1.0)
|
207
|
+
def compression_ratio
|
208
|
+
return 0.0 unless @compressed && @original_size&.positive?
|
209
|
+
|
210
|
+
1.0 - (@compressed_size.to_f / @original_size)
|
211
|
+
end
|
212
|
+
|
213
|
+
##
|
214
|
+
# Get buffer statistics
|
215
|
+
#
|
216
|
+
# @return [Hash] Buffer statistics
|
217
|
+
def stats
|
218
|
+
{
|
219
|
+
size: size,
|
220
|
+
compressed: @compressed,
|
221
|
+
compression_ratio: compression_ratio,
|
222
|
+
encoding: @encoding.name,
|
223
|
+
chunks: @chunks.size
|
224
|
+
}
|
225
|
+
end
|
226
|
+
|
227
|
+
##
|
228
|
+
# Optimize buffer for memory usage
|
229
|
+
#
|
230
|
+
def optimize!
|
231
|
+
# Force garbage collection on buffer
|
232
|
+
GC.start if defined?(GC)
|
233
|
+
|
234
|
+
# Compress if beneficial
|
235
|
+
compress! if @compress && !@compressed && size > COMPRESSION_THRESHOLD
|
236
|
+
|
237
|
+
# Compact string if possible (Ruby 2.7+)
|
238
|
+
return unless @buffer.string.respond_to?(:squeeze!)
|
239
|
+
|
240
|
+
@buffer.string.squeeze!
|
241
|
+
end
|
242
|
+
|
243
|
+
##
|
244
|
+
# Create a memory-mapped buffer for very large data
|
245
|
+
#
|
246
|
+
# @param file_path [String] Path to temporary file
|
247
|
+
# @return [MessageBuffer] Memory-mapped buffer
|
248
|
+
def self.create_memory_mapped(file_path)
|
249
|
+
# This would require additional gems like 'mmap' for true memory mapping
|
250
|
+
# For now, we'll use a file-backed buffer
|
251
|
+
buffer = new
|
252
|
+
buffer.instance_variable_set(:@file_backed, true)
|
253
|
+
buffer.instance_variable_set(:@file_path, file_path)
|
254
|
+
buffer
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
|
259
|
+
##
|
260
|
+
# Ensure buffer has minimum capacity
|
261
|
+
#
|
262
|
+
# @param capacity [Integer] Required capacity
|
263
|
+
def ensure_capacity(capacity)
|
264
|
+
return if @buffer.size >= capacity
|
265
|
+
|
266
|
+
# Expand buffer if needed
|
267
|
+
current_pos = @buffer.pos
|
268
|
+
@buffer.seek(0, IO::SEEK_END)
|
269
|
+
|
270
|
+
# Write null bytes to expand
|
271
|
+
padding_needed = capacity - @buffer.size
|
272
|
+
@buffer.write("\0" * padding_needed) if padding_needed.positive?
|
273
|
+
|
274
|
+
@buffer.pos = current_pos
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Performance optimization utilities
|
5
|
+
#
|
6
|
+
# Provides tools for profiling, memory optimization, and performance monitoring
|
7
|
+
# across the A2A Ruby gem.
|
8
|
+
#
|
9
|
+
module A2A
|
10
|
+
module Utils
|
11
|
+
class Performance
|
12
|
+
# Memory usage tracking
|
13
|
+
@memory_snapshots = []
|
14
|
+
@performance_data = {}
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_reader :memory_snapshots, :performance_data
|
18
|
+
|
19
|
+
##
|
20
|
+
# Profile a block of code and return execution time
|
21
|
+
#
|
22
|
+
# @param label [String] Label for the profiling session
|
23
|
+
# @yield Block to profile
|
24
|
+
# @return [Object] Result of the block
|
25
|
+
def profile(label = "operation")
|
26
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
27
|
+
start_memory = memory_usage
|
28
|
+
|
29
|
+
result = yield
|
30
|
+
|
31
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
32
|
+
end_memory = memory_usage
|
33
|
+
|
34
|
+
duration = end_time - start_time
|
35
|
+
memory_delta = end_memory - start_memory
|
36
|
+
|
37
|
+
record_performance_data(label, duration, memory_delta)
|
38
|
+
|
39
|
+
if A2A.configuration.respond_to?(:performance_logging) && A2A.configuration.performance_logging
|
40
|
+
A2A.logger.info("Performance [#{label}]: #{duration.round(4)}s, Memory: #{format_bytes(memory_delta)}")
|
41
|
+
end
|
42
|
+
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Get current memory usage in bytes
|
48
|
+
#
|
49
|
+
# @return [Integer] Memory usage in bytes
|
50
|
+
def memory_usage
|
51
|
+
if defined?(GC.stat)
|
52
|
+
# Use GC stats for more accurate memory tracking
|
53
|
+
GC.stat(:heap_allocated_pages) * GC.stat(:heap_page_size)
|
54
|
+
else
|
55
|
+
# Fallback to process memory (less accurate)
|
56
|
+
`ps -o rss= -p #{Process.pid}`.to_i * 1024
|
57
|
+
end
|
58
|
+
rescue StandardError
|
59
|
+
0
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Take a memory snapshot
|
64
|
+
#
|
65
|
+
# @param label [String] Label for the snapshot
|
66
|
+
def memory_snapshot(label = Time.now.to_s)
|
67
|
+
snapshot = {
|
68
|
+
label: label,
|
69
|
+
timestamp: Time.now,
|
70
|
+
memory: memory_usage,
|
71
|
+
gc_stats: defined?(GC.stat) ? GC.stat : {}
|
72
|
+
}
|
73
|
+
|
74
|
+
@memory_snapshots << snapshot
|
75
|
+
|
76
|
+
# Keep only last 100 snapshots
|
77
|
+
@memory_snapshots = @memory_snapshots.last(100) if @memory_snapshots.size > 100
|
78
|
+
|
79
|
+
snapshot
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Optimize garbage collection
|
84
|
+
#
|
85
|
+
def optimize_gc!
|
86
|
+
return unless defined?(GC)
|
87
|
+
|
88
|
+
# Force garbage collection
|
89
|
+
GC.start
|
90
|
+
|
91
|
+
# Compact heap if available (Ruby 2.7+)
|
92
|
+
GC.compact if GC.respond_to?(:compact)
|
93
|
+
|
94
|
+
# Tune GC settings for better performance
|
95
|
+
return unless defined?(GC.tune)
|
96
|
+
|
97
|
+
GC.tune(
|
98
|
+
heap_growth_factor: 1.8,
|
99
|
+
heap_growth_max_slots: 0,
|
100
|
+
heap_init_slots: 10_000,
|
101
|
+
heap_free_slots: 4096,
|
102
|
+
heap_oldobject_limit_factor: 2.0
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Optimize JSON parsing performance
|
108
|
+
#
|
109
|
+
# @param json_string [String] JSON string to parse
|
110
|
+
# @return [Hash, Array] Parsed JSON
|
111
|
+
def optimized_json_parse(json_string)
|
112
|
+
# Use Oj if available for better performance
|
113
|
+
if defined?(Oj)
|
114
|
+
Oj.load(json_string, mode: :strict)
|
115
|
+
else
|
116
|
+
JSON.parse(json_string)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Optimize JSON generation performance
|
122
|
+
#
|
123
|
+
# @param object [Object] Object to serialize
|
124
|
+
# @return [String] JSON string
|
125
|
+
def optimized_json_generate(object)
|
126
|
+
# Use Oj if available for better performance
|
127
|
+
if defined?(Oj)
|
128
|
+
Oj.dump(object, mode: :compat)
|
129
|
+
else
|
130
|
+
JSON.generate(object)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Create an optimized string buffer for large message handling
|
136
|
+
#
|
137
|
+
# @param initial_capacity [Integer] Initial buffer capacity
|
138
|
+
# @return [StringIO] Optimized string buffer
|
139
|
+
def create_string_buffer(initial_capacity = 8192)
|
140
|
+
buffer = StringIO.new
|
141
|
+
buffer.set_encoding(Encoding::UTF_8)
|
142
|
+
|
143
|
+
# Pre-allocate capacity if possible
|
144
|
+
buffer.capacity = initial_capacity if buffer.respond_to?(:capacity=)
|
145
|
+
|
146
|
+
buffer
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# Batch process items for better performance
|
151
|
+
#
|
152
|
+
# @param items [Array] Items to process
|
153
|
+
# @param batch_size [Integer] Size of each batch
|
154
|
+
# @yield [Array] Block to process each batch
|
155
|
+
# @return [Array] Results from all batches
|
156
|
+
def batch_process(items, batch_size = 100)
|
157
|
+
results = []
|
158
|
+
|
159
|
+
items.each_slice(batch_size) do |batch|
|
160
|
+
batch_result = yield(batch)
|
161
|
+
results.concat(Array(batch_result))
|
162
|
+
end
|
163
|
+
|
164
|
+
results
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Get performance statistics
|
169
|
+
#
|
170
|
+
# @return [Hash] Performance statistics
|
171
|
+
def statistics
|
172
|
+
{
|
173
|
+
memory_snapshots: @memory_snapshots.size,
|
174
|
+
current_memory: memory_usage,
|
175
|
+
performance_data: @performance_data,
|
176
|
+
gc_stats: defined?(GC.stat) ? GC.stat : {}
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Reset performance tracking data
|
182
|
+
#
|
183
|
+
def reset!
|
184
|
+
@memory_snapshots.clear
|
185
|
+
@performance_data.clear
|
186
|
+
end
|
187
|
+
|
188
|
+
##
|
189
|
+
# Check if performance optimizations are available
|
190
|
+
#
|
191
|
+
# @return [Hash] Available optimizations
|
192
|
+
def available_optimizations
|
193
|
+
{
|
194
|
+
oj_json: defined?(Oj),
|
195
|
+
gc_compact: defined?(GC) && GC.respond_to?(:compact),
|
196
|
+
gc_tune: defined?(GC.tune),
|
197
|
+
net_http_persistent: defined?(Net::HTTP::Persistent)
|
198
|
+
}
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
##
|
204
|
+
# Record performance data
|
205
|
+
#
|
206
|
+
# @param label [String] Operation label
|
207
|
+
# @param duration [Float] Duration in seconds
|
208
|
+
# @param memory_delta [Integer] Memory change in bytes
|
209
|
+
def record_performance_data(label, duration, memory_delta)
|
210
|
+
@performance_data[label] ||= {
|
211
|
+
count: 0,
|
212
|
+
total_time: 0.0,
|
213
|
+
avg_time: 0.0,
|
214
|
+
min_time: Float::INFINITY,
|
215
|
+
max_time: 0.0,
|
216
|
+
total_memory: 0,
|
217
|
+
avg_memory: 0.0
|
218
|
+
}
|
219
|
+
|
220
|
+
data = @performance_data[label]
|
221
|
+
data[:count] += 1
|
222
|
+
data[:total_time] += duration
|
223
|
+
data[:avg_time] = data[:total_time] / data[:count]
|
224
|
+
data[:min_time] = [data[:min_time], duration].min
|
225
|
+
data[:max_time] = [data[:max_time], duration].max
|
226
|
+
data[:total_memory] += memory_delta
|
227
|
+
data[:avg_memory] = data[:total_memory].to_f / data[:count]
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# Format bytes for human-readable output
|
232
|
+
#
|
233
|
+
# @param bytes [Integer] Number of bytes
|
234
|
+
# @return [String] Formatted string
|
235
|
+
def format_bytes(bytes)
|
236
|
+
return "0 B" if bytes.zero?
|
237
|
+
|
238
|
+
units = %w[B KB MB GB TB]
|
239
|
+
exp = (Math.log(bytes.abs) / Math.log(1024)).floor
|
240
|
+
exp = [exp, units.length - 1].min
|
241
|
+
|
242
|
+
"#{(bytes / (1024.0**exp)).round(2)} #{units[exp]}"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Utility module for Rails detection and compatibility
|
5
|
+
#
|
6
|
+
# Provides consistent Rails detection methods that can be used throughout
|
7
|
+
# the A2A Ruby gem to handle Rails availability gracefully.
|
8
|
+
#
|
9
|
+
module A2A
|
10
|
+
module Utils
|
11
|
+
module RailsDetection
|
12
|
+
# Check if Rails is available and properly defined
|
13
|
+
# @return [Boolean] true if Rails is available
|
14
|
+
def rails_available?
|
15
|
+
defined?(::Rails) && ::Rails.respond_to?(:version)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Check if Rails version requires validation
|
19
|
+
# @return [Boolean] true if Rails version is less than 6.0
|
20
|
+
def rails_version_requires_validation?
|
21
|
+
return false unless rails_available?
|
22
|
+
|
23
|
+
begin
|
24
|
+
::Rails.version < "6.0"
|
25
|
+
rescue StandardError
|
26
|
+
# If we can't get the version, assume validation is not required
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Check if Rails version is supported (6.0+)
|
32
|
+
# @return [Boolean] true if Rails version is 6.0 or higher
|
33
|
+
def rails_version_supported?
|
34
|
+
return false unless rails_available?
|
35
|
+
|
36
|
+
begin
|
37
|
+
::Rails.version >= "6.0"
|
38
|
+
rescue StandardError
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get Rails logger if available
|
44
|
+
# @return [Logger, nil] Rails logger or nil if not available
|
45
|
+
def rails_logger
|
46
|
+
return nil unless rails_available?
|
47
|
+
return nil unless ::Rails.respond_to?(:logger)
|
48
|
+
|
49
|
+
::Rails.logger
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get Rails environment if available
|
53
|
+
# @return [String, nil] Rails environment or nil if not available
|
54
|
+
def rails_environment
|
55
|
+
return nil unless rails_available?
|
56
|
+
return nil unless ::Rails.respond_to?(:env)
|
57
|
+
|
58
|
+
::Rails.env
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get Rails application if available
|
62
|
+
# @return [Rails::Application, nil] Rails application or nil if not available
|
63
|
+
def rails_application
|
64
|
+
return nil unless rails_available?
|
65
|
+
return nil unless ::Rails.respond_to?(:application)
|
66
|
+
|
67
|
+
::Rails.application
|
68
|
+
end
|
69
|
+
|
70
|
+
# Check if we're in a Rails production environment
|
71
|
+
# @return [Boolean] true if in Rails production environment
|
72
|
+
def rails_production?
|
73
|
+
env = rails_environment
|
74
|
+
env && env == "production"
|
75
|
+
end
|
76
|
+
|
77
|
+
# Check if we're in a Rails development environment
|
78
|
+
# @return [Boolean] true if in Rails development environment
|
79
|
+
def rails_development?
|
80
|
+
env = rails_environment
|
81
|
+
env && env == "development"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get Rails version string if available
|
85
|
+
# @return [String, nil] Rails version or nil if not available
|
86
|
+
def rails_version
|
87
|
+
return nil unless rails_available?
|
88
|
+
|
89
|
+
begin
|
90
|
+
::Rails.version
|
91
|
+
rescue StandardError
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|