daytona 0.140.0 → 0.141.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97efbea0f339d4ec24eec1fe2a40ece74aa8f3eb0e74939d68e2fcd175f0c5e3
4
- data.tar.gz: 9b97d704d4853538570dcb6e82c4ce82e3492526fe327e9b0ea9183f90279a0c
3
+ metadata.gz: 0c236b5ef310e5792aa5d1fcb33692ef2fda2a9c089e0ebe5dd0ee7e1a9b7a90
4
+ data.tar.gz: 2e0c388b5daa26e90b8eac72b578e0bcc8d5ce189b3cdfe128cb1a9d44da53e2
5
5
  SHA512:
6
- metadata.gz: 2eb373cf9c9d84ea459df3c384265d674bba34b87671fef7be02cbf6964d907398685d9258a85742260c39cdd6755169c04155277657714dd44946d78bc89737
7
- data.tar.gz: b728c4cd356eb6965b664cbd822283098ccd5e5dc5fc692826f6e743359973b2c64bde1c2ce447364774147f35bd30cef78134a24d509ff47e423c17e7515394
6
+ metadata.gz: 5e9dd352903ba61d1de768ab36c41ccfa94d0756b2cca41b11a30727d7b89a00a0add7a676d2c9fd663a364680f7f3ebeddd681c130d7a396db862d7242545e3
7
+ data.tar.gz: b0acf6153a896da1027e34c85e20d0433e5d741b5af700c4bd63e1a55f073e8d38fe34aafe6cf3df49a99795f8191ea1612f88d3538223f3d7aa5bd07c420325
@@ -15,6 +15,8 @@ module Daytona
15
15
  # For other languages, use the `code_run` method from the `Process` interface,
16
16
  # or execute the appropriate command directly in the sandbox terminal.
17
17
  class CodeInterpreter
18
+ include Instrumentation
19
+
18
20
  WEBSOCKET_TIMEOUT_CODE = 4008
19
21
  WS_PORT = 2280
20
22
  private_constant :WS_PORT
@@ -22,10 +24,12 @@ module Daytona
22
24
  # @param sandbox_id [String]
23
25
  # @param toolbox_api [DaytonaToolboxApiClient::InterpreterApi]
24
26
  # @param get_preview_link [Proc]
25
- def initialize(sandbox_id:, toolbox_api:, get_preview_link:)
27
+ # @param otel_state [Daytona::OtelState, nil]
28
+ def initialize(sandbox_id:, toolbox_api:, get_preview_link:, otel_state: nil)
26
29
  @sandbox_id = sandbox_id
27
30
  @toolbox_api = toolbox_api
28
31
  @get_preview_link = get_preview_link
32
+ @otel_state = otel_state
29
33
  end
30
34
 
31
35
  # Execute Python code in the sandbox.
@@ -288,8 +292,14 @@ module Daytona
288
292
  raise Sdk::Error, "Failed to delete interpreter context: #{e.message}"
289
293
  end
290
294
 
295
+ instrument :run_code, :create_context, :list_contexts, :delete_context,
296
+ component: 'CodeInterpreter'
297
+
291
298
  private
292
299
 
300
+ # @return [Daytona::OtelState, nil]
301
+ attr_reader :otel_state
302
+
293
303
  # @return [Hash<String, String>]
294
304
  def build_headers
295
305
  headers = {}
@@ -3,6 +3,8 @@
3
3
  module Daytona
4
4
  class ComputerUse
5
5
  class Mouse
6
+ include Instrumentation
7
+
6
8
  # @return [String] The ID of the sandbox
7
9
  attr_reader :sandbox_id
8
10
 
@@ -11,9 +13,11 @@ module Daytona
11
13
 
12
14
  # @param sandbox_id [String] The ID of the sandbox
13
15
  # @param toolbox_api [DaytonaToolboxApiClient::ComputerUseApi] API client for sandbox operations
14
- def initialize(sandbox_id:, toolbox_api:)
16
+ # @param otel_state [Daytona::OtelState, nil]
17
+ def initialize(sandbox_id:, toolbox_api:, otel_state: nil)
15
18
  @sandbox_id = sandbox_id
16
19
  @toolbox_api = toolbox_api
20
+ @otel_state = otel_state
17
21
  end
18
22
 
19
23
  # Gets the current mouse cursor position.
@@ -40,7 +44,7 @@ module Daytona
40
44
  # @example
41
45
  # result = sandbox.computer_use.mouse.move(x: 100, y: 200)
42
46
  # puts "Mouse moved to: #{result.x}, #{result.y}"
43
- def move(x:, y:) # rubocop:disable Naming/MethodParameterName
47
+ def move(x:, y:)
44
48
  request = DaytonaToolboxApiClient::MouseMoveRequest.new(x:, y:)
45
49
  toolbox_api.move_mouse(request)
46
50
  rescue StandardError => e
@@ -65,7 +69,7 @@ module Daytona
65
69
  #
66
70
  # # Right click
67
71
  # right_click = sandbox.computer_use.mouse.click(x: 100, y: 200, button: 'right')
68
- def click(x:, y:, button: 'left', double: false) # rubocop:disable Naming/MethodParameterName
72
+ def click(x:, y:, button: 'left', double: false)
69
73
  request = DaytonaToolboxApiClient::MouseClickRequest.new(x:, y:, button:, double:)
70
74
  toolbox_api.click(request)
71
75
  rescue StandardError => e
@@ -107,17 +111,26 @@ module Daytona
107
111
  #
108
112
  # # Scroll down
109
113
  # scroll_down = sandbox.computer_use.mouse.scroll(x: 100, y: 200, direction: 'down', amount: 5)
110
- def scroll(x:, y:, direction:, amount: 1) # rubocop:disable Naming/MethodParameterName
114
+ def scroll(x:, y:, direction:, amount: 1)
111
115
  request = DaytonaToolboxApiClient::MouseScrollRequest.new(x:, y:, direction:, amount:)
112
116
  toolbox_api.scroll(request)
113
117
  true
114
118
  rescue StandardError => e
115
119
  raise Sdk::Error, "Failed to scroll mouse: #{e.message}"
116
120
  end
121
+
122
+ instrument :position, :move, :click, :drag, :scroll, component: 'Mouse'
123
+
124
+ private
125
+
126
+ # @return [Daytona::OtelState, nil]
127
+ attr_reader :otel_state
117
128
  end
118
129
 
119
130
  # Keyboard operations for computer use functionality.
120
131
  class Keyboard
132
+ include Instrumentation
133
+
121
134
  # @return [String] The ID of the sandbox
122
135
  attr_reader :sandbox_id
123
136
 
@@ -126,9 +139,11 @@ module Daytona
126
139
 
127
140
  # @param sandbox_id [String] The ID of the sandbox
128
141
  # @param toolbox_api [DaytonaToolboxApiClient::ComputerUseApi] API client for sandbox operations
129
- def initialize(sandbox_id:, toolbox_api:)
142
+ # @param otel_state [Daytona::OtelState, nil]
143
+ def initialize(sandbox_id:, toolbox_api:, otel_state: nil)
130
144
  @sandbox_id = sandbox_id
131
145
  @toolbox_api = toolbox_api
146
+ @otel_state = otel_state
132
147
  end
133
148
 
134
149
  # Types the specified text.
@@ -194,10 +209,19 @@ module Daytona
194
209
  rescue StandardError => e
195
210
  raise Sdk::Error, "Failed to press hotkey: #{e.message}"
196
211
  end
212
+
213
+ instrument :type, :press, :hotkey, component: 'Keyboard'
214
+
215
+ private
216
+
217
+ # @return [Daytona::OtelState, nil]
218
+ attr_reader :otel_state
197
219
  end
198
220
 
199
221
  # Screenshot operations for computer use functionality.
200
222
  class Screenshot
223
+ include Instrumentation
224
+
201
225
  # @return [String] The ID of the sandbox
202
226
  attr_reader :sandbox_id
203
227
 
@@ -206,9 +230,11 @@ module Daytona
206
230
 
207
231
  # @param sandbox_id [String] The ID of the sandbox
208
232
  # @param toolbox_api [DaytonaToolboxApiClient::ComputerUseApi] API client for sandbox operations
209
- def initialize(sandbox_id:, toolbox_api:)
233
+ # @param otel_state [Daytona::OtelState, nil]
234
+ def initialize(sandbox_id:, toolbox_api:, otel_state: nil)
210
235
  @sandbox_id = sandbox_id
211
236
  @toolbox_api = toolbox_api
237
+ @otel_state = otel_state
212
238
  end
213
239
 
214
240
  # Takes a screenshot of the entire screen.
@@ -292,7 +318,7 @@ module Daytona
292
318
  # options: ScreenshotOptions.new(format: "webp", quality: 80, show_cursor: true)
293
319
  # )
294
320
  # puts "Compressed size: #{screenshot.size_bytes} bytes"
295
- def take_compressed_region(region:, options: nil) # rubocop:disable Metrics/MethodLength
321
+ def take_compressed_region(region:, options: nil)
296
322
  options ||= ScreenshotOptions.new
297
323
  toolbox_api.take_compressed_region_screenshot(
298
324
  sandbox_id,
@@ -308,10 +334,20 @@ module Daytona
308
334
  rescue StandardError => e
309
335
  raise Sdk::Error, "Failed to take compressed region screenshot: #{e.message}"
310
336
  end
337
+
338
+ instrument :take_full_screen, :take_region, :take_compressed, :take_compressed_region,
339
+ component: 'Screenshot'
340
+
341
+ private
342
+
343
+ # @return [Daytona::OtelState, nil]
344
+ attr_reader :otel_state
311
345
  end
312
346
 
313
347
  # Display operations for computer use functionality.
314
348
  class Display
349
+ include Instrumentation
350
+
315
351
  # @return [String] The ID of the sandbox
316
352
  attr_reader :sandbox_id
317
353
 
@@ -320,9 +356,11 @@ module Daytona
320
356
 
321
357
  # @param sandbox_id [String] The ID of the sandbox
322
358
  # @param toolbox_api [DaytonaToolboxApiClient::ComputerUseApi] API client for sandbox operations
323
- def initialize(sandbox_id:, toolbox_api:)
359
+ # @param otel_state [Daytona::OtelState, nil]
360
+ def initialize(sandbox_id:, toolbox_api:, otel_state: nil)
324
361
  @sandbox_id = sandbox_id
325
362
  @toolbox_api = toolbox_api
363
+ @otel_state = otel_state
326
364
  end
327
365
 
328
366
  # Gets information about the displays.
@@ -359,6 +397,13 @@ module Daytona
359
397
  rescue StandardError => e
360
398
  raise Sdk::Error, "Failed to get windows: #{e.message}"
361
399
  end
400
+
401
+ instrument :info, :windows, component: 'Display'
402
+
403
+ private
404
+
405
+ # @return [Daytona::OtelState, nil]
406
+ attr_reader :otel_state
362
407
  end
363
408
 
364
409
  # Region coordinates for screenshot operations.
@@ -379,7 +424,7 @@ module Daytona
379
424
  # @param y [Integer] Y coordinate of the region
380
425
  # @param width [Integer] Width of the region
381
426
  # @param height [Integer] Height of the region
382
- def initialize(x:, y:, width:, height:) # rubocop:disable Naming/MethodParameterName
427
+ def initialize(x:, y:, width:, height:)
383
428
  @x = x
384
429
  @y = y
385
430
  @width = width
@@ -415,6 +460,8 @@ module Daytona
415
460
 
416
461
  # Recording operations for computer use functionality.
417
462
  class Recording
463
+ include Instrumentation
464
+
418
465
  # @return [String] The ID of the sandbox
419
466
  attr_reader :sandbox_id
420
467
 
@@ -423,9 +470,11 @@ module Daytona
423
470
 
424
471
  # @param sandbox_id [String] The ID of the sandbox
425
472
  # @param toolbox_api [DaytonaToolboxApiClient::ComputerUseApi] API client for sandbox operations
426
- def initialize(sandbox_id:, toolbox_api:)
473
+ # @param otel_state [Daytona::OtelState, nil]
474
+ def initialize(sandbox_id:, toolbox_api:, otel_state: nil)
427
475
  @sandbox_id = sandbox_id
428
476
  @toolbox_api = toolbox_api
477
+ @otel_state = otel_state
429
478
  end
430
479
 
431
480
  # Starts a new screen recording session.
@@ -568,8 +617,17 @@ module Daytona
568
617
  File.delete(local_path) if File.exist?(local_path)
569
618
  raise Sdk::Error, "Failed to download recording: #{e.message}"
570
619
  end
620
+
621
+ instrument :start, :stop, :list, :get, :delete, :download, component: 'Recording'
622
+
623
+ private
624
+
625
+ # @return [Daytona::OtelState, nil]
626
+ attr_reader :otel_state
571
627
  end
572
628
 
629
+ include Instrumentation
630
+
573
631
  # @return [String] The ID of the sandbox
574
632
  attr_reader :sandbox_id
575
633
 
@@ -595,14 +653,16 @@ module Daytona
595
653
  #
596
654
  # @param sandbox_id [String] The ID of the sandbox
597
655
  # @param toolbox_api [DaytonaApiClient::ToolboxApi] API client for sandbox operations
598
- def initialize(sandbox_id:, toolbox_api:)
656
+ # @param otel_state [Daytona::OtelState, nil]
657
+ def initialize(sandbox_id:, toolbox_api:, otel_state: nil)
599
658
  @sandbox_id = sandbox_id
600
659
  @toolbox_api = toolbox_api
601
- @mouse = Mouse.new(sandbox_id:, toolbox_api:)
602
- @keyboard = Keyboard.new(sandbox_id:, toolbox_api:)
603
- @screenshot = Screenshot.new(sandbox_id:, toolbox_api:)
604
- @display = Display.new(sandbox_id:, toolbox_api:)
605
- @recording = Recording.new(sandbox_id:, toolbox_api:)
660
+ @otel_state = otel_state
661
+ @mouse = Mouse.new(sandbox_id:, toolbox_api:, otel_state:)
662
+ @keyboard = Keyboard.new(sandbox_id:, toolbox_api:, otel_state:)
663
+ @screenshot = Screenshot.new(sandbox_id:, toolbox_api:, otel_state:)
664
+ @display = Display.new(sandbox_id:, toolbox_api:, otel_state:)
665
+ @recording = Recording.new(sandbox_id:, toolbox_api:, otel_state:)
606
666
  end
607
667
 
608
668
  # Starts all computer use processes (Xvfb, xfce4, x11vnc, novnc).
@@ -706,5 +766,14 @@ module Daytona
706
766
  rescue StandardError => e
707
767
  raise Sdk::Error, "Failed to get process errors: #{e.message}"
708
768
  end
769
+
770
+ instrument :start, :stop, :status, :get_process_status, :restart_process,
771
+ :get_process_logs, :get_process_errors,
772
+ component: 'ComputerUse'
773
+
774
+ private
775
+
776
+ # @return [Daytona::OtelState, nil]
777
+ attr_reader :otel_state
709
778
  end
710
779
  end
@@ -31,6 +31,11 @@ module Daytona
31
31
  # @return [String, nil] Daytona target
32
32
  attr_accessor :target
33
33
 
34
+ # Experimental configuration options
35
+ #
36
+ # @return [Hash, nil] Experimental configuration hash
37
+ attr_accessor :_experimental
38
+
34
39
  # Initializes a new Daytona::Config object.
35
40
  #
36
41
  # @param api_key [String, nil] Daytona API key. Defaults to ENV['DAYTONA_API_KEY'].
@@ -38,12 +43,14 @@ module Daytona
38
43
  # @param api_url [String, nil] Daytona API URL. Defaults to ENV['DAYTONA_API_URL'] or Daytona::Config::API_URL.
39
44
  # @param organization_id [String, nil] Daytona organization ID. Defaults to ENV['DAYTONA_ORGANIZATION_ID'].
40
45
  # @param target [String, nil] Daytona target. Defaults to ENV['DAYTONA_TARGET'].
41
- def initialize(
46
+ # @param _experimental [Hash, nil] Experimental configuration options.
47
+ def initialize( # rubocop:disable Metrics/ParameterLists
42
48
  api_key: nil,
43
49
  jwt_token: nil,
44
50
  api_url: nil,
45
51
  organization_id: nil,
46
- target: nil
52
+ target: nil,
53
+ _experimental: nil
47
54
  )
48
55
  # Load environment variables from .env and .env.local files
49
56
  # Files are loaded from the current working directory (where the code is executed)
@@ -54,6 +61,7 @@ module Daytona
54
61
  @api_url = api_url || ENV.fetch('DAYTONA_API_URL', API_URL)
55
62
  @target = target || ENV.fetch('DAYTONA_TARGET', nil)
56
63
  @organization_id = organization_id || ENV.fetch('DAYTONA_ORGANIZATION_ID', nil)
64
+ @_experimental = _experimental
57
65
  end
58
66
 
59
67
  private
@@ -4,7 +4,9 @@ require 'json'
4
4
  require 'uri'
5
5
 
6
6
  module Daytona
7
- class Daytona # rubocop:disable Metrics/ClassLength
7
+ class Daytona
8
+ include Instrumentation
9
+
8
10
  # @return [Daytona::Config]
9
11
  attr_reader :config
10
12
 
@@ -27,20 +29,33 @@ module Daytona
27
29
  attr_reader :snapshot
28
30
 
29
31
  # @param config [Daytona::Config] Configuration options. Defaults to Daytona::Config.new
30
- def initialize(config = Config.new) # rubocop:disable Metrics/AbcSize
32
+ def initialize(config = Config.new)
31
33
  @config = config
32
34
  ensure_access_token_defined
35
+
36
+ otel_enabled = config._experimental&.dig('otel_enabled') || ENV['DAYTONA_EXPERIMENTAL_OTEL_ENABLED'] == 'true'
37
+ @otel_state = (::Daytona.init_otel(Sdk::VERSION) if otel_enabled)
38
+
33
39
  @api_client = build_api_client
34
40
  @sandbox_api = DaytonaApiClient::SandboxApi.new(api_client)
35
41
  @config_api = DaytonaApiClient::ConfigApi.new(api_client)
36
- @volume = VolumeService.new(DaytonaApiClient::VolumesApi.new(api_client))
42
+ @volume = VolumeService.new(DaytonaApiClient::VolumesApi.new(api_client), otel_state:)
37
43
  @object_storage_api = DaytonaApiClient::ObjectStorageApi.new(api_client)
38
44
  @snapshots_api = DaytonaApiClient::SnapshotsApi.new(api_client)
39
- @snapshot = SnapshotService.new(snapshots_api:, object_storage_api:, default_region_id: config.target)
45
+ @snapshot = SnapshotService.new(snapshots_api:, object_storage_api:, default_region_id: config.target,
46
+ otel_state:)
40
47
  @proxy_toolbox_url_cache = {}
41
48
  @proxy_toolbox_url_mutex = Mutex.new
42
49
  end
43
50
 
51
+ # Shuts down OTel providers, flushing any pending telemetry data.
52
+ #
53
+ # @return [void]
54
+ def close
55
+ ::Daytona.shutdown_otel(@otel_state)
56
+ @otel_state = nil
57
+ end
58
+
44
59
  # Creates a sandbox with the specified parameters
45
60
  #
46
61
  # @param params [Daytona::CreateSandboxFromSnapshotParams, Daytona::CreateSandboxFromImageParams, Nil] Sandbox creation parameters
@@ -93,7 +108,7 @@ module Daytona
93
108
  # @param limit [Integer, Nil]
94
109
  # @return [Daytona::PaginatedResource]
95
110
  # @raise [Daytona::Sdk::Error]
96
- def list(labels = {}, page: nil, limit: nil) # rubocop:disable Metrics/MethodLength
111
+ def list(labels = {}, page: nil, limit: nil)
97
112
  raise Sdk::Error, 'page must be positive integer' if page && page < 1
98
113
 
99
114
  raise Sdk::Error, 'limit must be positive integer' if limit && limit < 1
@@ -124,8 +139,13 @@ module Daytona
124
139
  # @return [void]
125
140
  def stop(sandbox, timeout = Sandbox::DEFAULT_TIMEOUT) = sandbox.stop(timeout)
126
141
 
142
+ instrument :create, :delete, :get, :find_one, :list, :start, :stop, component: 'Daytona'
143
+
127
144
  private
128
145
 
146
+ # @return [Daytona::OtelState, nil]
147
+ attr_reader :otel_state
148
+
129
149
  # Creates a sandbox with the specified parameters
130
150
  #
131
151
  # @param params [Daytona::CreateSandboxFromSnapshotParams, Daytona::CreateSandboxFromImageParams] Sandbox creation parameters
@@ -133,7 +153,7 @@ module Daytona
133
153
  # @param on_snapshot_create_logs [Proc]
134
154
  # @return [Daytona::Sandbox] The created sandbox
135
155
  # @raise [Daytona::Sdk::Error] If auto_stop_interval or auto_archive_interval is negative
136
- def _create(params, timeout: 60, on_snapshot_create_logs: nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
156
+ def _create(params, timeout: 60, on_snapshot_create_logs: nil)
137
157
  raise Sdk::Error, 'Timeout must be a non-negative number' if timeout.negative?
138
158
 
139
159
  start_time = Time.now
@@ -245,7 +265,8 @@ module Daytona
245
265
  config:,
246
266
  sandbox_api:,
247
267
  code_toolbox:,
248
- get_proxy_toolbox_url: proc { |sandbox_id, region_id| proxy_toolbox_url(sandbox_id, region_id) }
268
+ get_proxy_toolbox_url: proc { |sandbox_id, region_id| proxy_toolbox_url(sandbox_id, region_id) },
269
+ otel_state: @otel_state
249
270
  )
250
271
  end
251
272
 
@@ -5,6 +5,8 @@ require 'fileutils'
5
5
 
6
6
  module Daytona
7
7
  class FileSystem
8
+ include Instrumentation
9
+
8
10
  # @return [String] The Sandbox ID
9
11
  attr_reader :sandbox_id
10
12
 
@@ -15,9 +17,11 @@ module Daytona
15
17
  #
16
18
  # @param sandbox_id [String] The Sandbox ID
17
19
  # @param toolbox_api [DaytonaToolboxApiClient::FileSystemApi] API client for Sandbox operations
18
- def initialize(sandbox_id:, toolbox_api:)
20
+ # @param otel_state [Daytona::OtelState, nil]
21
+ def initialize(sandbox_id:, toolbox_api:, otel_state: nil)
19
22
  @sandbox_id = sandbox_id
20
23
  @toolbox_api = toolbox_api
24
+ @otel_state = otel_state
21
25
  end
22
26
 
23
27
  # Creates a new directory in the Sandbox at the specified path with the given
@@ -355,5 +359,15 @@ module Daytona
355
359
  rescue StandardError => e
356
360
  raise Sdk::Error, "Failed to set file permissions: #{e.message}"
357
361
  end
362
+
363
+ instrument :create_folder, :delete_file, :get_file_info, :list_files, :download_file,
364
+ :upload_file, :upload_files, :find_files, :search_files, :move_files,
365
+ :replace_in_files, :set_file_permissions,
366
+ component: 'FileSystem'
367
+
368
+ private
369
+
370
+ # @return [Daytona::OtelState, nil]
371
+ attr_reader :otel_state
358
372
  end
359
373
  end
data/lib/daytona/git.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Daytona
4
4
  class Git
5
+ include Instrumentation
6
+
5
7
  # @return [String] The Sandbox ID
6
8
  attr_reader :sandbox_id
7
9
 
@@ -12,9 +14,11 @@ module Daytona
12
14
  #
13
15
  # @param sandbox_id [String] The Sandbox ID.
14
16
  # @param toolbox_api [DaytonaToolboxApiClient::GitApi] API client for Sandbox operations.
15
- def initialize(sandbox_id:, toolbox_api:)
17
+ # @param otel_state [Daytona::OtelState, nil]
18
+ def initialize(sandbox_id:, toolbox_api:, otel_state: nil)
16
19
  @sandbox_id = sandbox_id
17
20
  @toolbox_api = toolbox_api
21
+ @otel_state = otel_state
18
22
  end
19
23
 
20
24
  # Stages the specified files for the next commit, similar to
@@ -276,5 +280,14 @@ module Daytona
276
280
  rescue StandardError => e
277
281
  raise Sdk::Error, "Failed to delete branch: #{e.message}"
278
282
  end
283
+
284
+ instrument :add, :branches, :clone, :commit, :push, :pull, :status,
285
+ :checkout_branch, :create_branch, :delete_branch,
286
+ component: 'Git'
287
+
288
+ private
289
+
290
+ # @return [Daytona::OtelState, nil]
291
+ attr_reader :otel_state
279
292
  end
280
293
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Daytona
4
4
  class LspServer
5
+ include Instrumentation
6
+
5
7
  module Language
6
8
  ALL = [
7
9
  JAVASCRIPT = :javascript,
@@ -31,11 +33,13 @@ module Daytona
31
33
  # @param path_to_project [String]
32
34
  # @param toolbox_api [DaytonaToolboxApiClient::LspApi]
33
35
  # @param sandbox_id [String]
34
- def initialize(language_id:, path_to_project:, toolbox_api:, sandbox_id:)
36
+ # @param otel_state [Daytona::OtelState, nil]
37
+ def initialize(language_id:, path_to_project:, toolbox_api:, sandbox_id:, otel_state: nil)
35
38
  @language_id = language_id
36
39
  @path_to_project = path_to_project
37
40
  @toolbox_api = toolbox_api
38
41
  @sandbox_id = sandbox_id
42
+ @otel_state = otel_state
39
43
  end
40
44
 
41
45
  # Gets completion suggestions at a position in a file
@@ -114,8 +118,15 @@ module Daytona
114
118
  )
115
119
  end
116
120
 
121
+ instrument :completions, :did_close, :did_open, :document_symbols, :sandbox_symbols,
122
+ :start, :stop,
123
+ component: 'LspServer'
124
+
117
125
  private
118
126
 
127
+ # @return [Daytona::OtelState, nil]
128
+ attr_reader :otel_state
129
+
119
130
  # Convert path to file uri.
120
131
  #
121
132
  # @param path [String]
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Daytona
4
+ # Holds OTel provider state for the SDK.
5
+ class OtelState
6
+ attr_reader :tracer_provider
7
+ attr_reader :meter_provider
8
+ attr_reader :tracer
9
+ attr_reader :meter
10
+
11
+ def initialize(tracer_provider:, meter_provider:, tracer:, meter:)
12
+ @tracer_provider = tracer_provider
13
+ @meter_provider = meter_provider
14
+ @tracer = tracer
15
+ @meter = meter
16
+ @histograms = {}
17
+ @histograms_mutex = Mutex.new
18
+ end
19
+
20
+ # Returns a cached histogram for the given metric name.
21
+ def histogram(name)
22
+ @histograms_mutex.synchronize do
23
+ @histograms[name] ||= meter.create_histogram(name, unit: 'ms')
24
+ end
25
+ end
26
+
27
+ def shutdown
28
+ tracer_provider.shutdown
29
+ meter_provider.shutdown
30
+ end
31
+ end
32
+
33
+ # Initializes OTel providers, sets globals, installs Typhoeus propagation.
34
+ # OTel gems are required lazily so they are never loaded when disabled.
35
+ #
36
+ # @param sdk_version [String]
37
+ # @return [OtelState]
38
+ def self.init_otel(sdk_version) # rubocop:disable Metrics/MethodLength
39
+ require 'opentelemetry-sdk'
40
+ require 'opentelemetry-metrics-sdk'
41
+ require 'opentelemetry-exporter-otlp'
42
+ require 'opentelemetry-exporter-otlp-metrics'
43
+
44
+ resource = OpenTelemetry::SDK::Resources::Resource.create(
45
+ 'service.name' => 'daytona-ruby-sdk',
46
+ 'service.version' => sdk_version
47
+ )
48
+
49
+ tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new(resource:)
50
+ tracer_provider.add_span_processor(
51
+ OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
52
+ OpenTelemetry::Exporter::OTLP::Exporter.new
53
+ )
54
+ )
55
+ OpenTelemetry.tracer_provider = tracer_provider
56
+
57
+ meter_provider = OpenTelemetry::SDK::Metrics::MeterProvider.new(resource:)
58
+ meter_provider.add_metric_reader(
59
+ OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(
60
+ exporter: OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.new
61
+ )
62
+ )
63
+ OpenTelemetry.meter_provider = meter_provider
64
+
65
+ tracer = tracer_provider.tracer('daytona-ruby-sdk', sdk_version)
66
+ meter = meter_provider.meter('daytona-ruby-sdk')
67
+
68
+ # Install Typhoeus trace-context propagation
69
+ install_typhoeus_propagation
70
+
71
+ OtelState.new(tracer_provider:, meter_provider:, tracer:, meter:)
72
+ end
73
+
74
+ # Flushes and shuts down OTel providers.
75
+ def self.shutdown_otel(state)
76
+ state&.shutdown
77
+ end
78
+
79
+ # Wraps a block with OTel span creation and duration histogram recording.
80
+ # When otel_state is nil (OTel disabled), calls the block directly.
81
+ #
82
+ # @param otel_state [OtelState, nil]
83
+ # @param component [String]
84
+ # @param method_name [String]
85
+ # @return [Object] The block's return value
86
+ def self.with_instrumentation(otel_state, component, method_name, &block) # rubocop:disable Metrics/MethodLength
87
+ return block.call unless otel_state
88
+
89
+ span_name = "#{component}.#{method_name}"
90
+ metric_name = "#{to_snake_case(span_name)}_duration"
91
+ status = 'success'
92
+
93
+ otel_state.tracer.in_span(
94
+ span_name,
95
+ attributes: { 'component' => component, 'method' => method_name }
96
+ ) do |_span|
97
+ start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
98
+ begin
99
+ block.call
100
+ rescue StandardError => e
101
+ status = 'error'
102
+ raise e
103
+ ensure
104
+ duration_ms = (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_time) * 1000.0
105
+ otel_state.histogram(metric_name).record(
106
+ duration_ms,
107
+ attributes: { 'component' => component, 'method' => method_name, 'status' => status }
108
+ )
109
+ end
110
+ end
111
+ end
112
+
113
+ # Converts "ClassName.method_name" to "class_name_method_name".
114
+ def self.to_snake_case(str)
115
+ result = +''
116
+ str.each_char.with_index do |char, i|
117
+ if char == '.'
118
+ result << '_'
119
+ elsif char =~ /[A-Z]/ && i > 0 && str[i - 1] != '.'
120
+ result << '_' << char.downcase
121
+ else
122
+ result << char.downcase
123
+ end
124
+ end
125
+ result
126
+ end
127
+
128
+ # Installs Typhoeus.before callback for W3C trace-context propagation.
129
+ def self.install_typhoeus_propagation
130
+ return unless defined?(Typhoeus)
131
+
132
+ Typhoeus.before do |request|
133
+ headers = request.options[:headers] ||= {}
134
+ OpenTelemetry.propagation.inject(headers)
135
+ true
136
+ end
137
+ end
138
+
139
+ # Mixin that provides the `instrument` class macro for wrapping methods
140
+ # with OTel spans and metrics.
141
+ module Instrumentation
142
+ def self.included(base)
143
+ base.extend(ClassMethods)
144
+ end
145
+
146
+ module ClassMethods
147
+ # Instruments the listed methods with OTel tracing/metrics.
148
+ # Must be called after all target methods are defined.
149
+ #
150
+ # @param method_names [Array<Symbol>] methods to instrument
151
+ # @param component [String] component name for span/metric attributes
152
+ def instrument(*method_names, component:) # rubocop:disable Metrics/MethodLength
153
+ method_names.each do |method_name|
154
+ original = instance_method(method_name)
155
+
156
+ # Detect original visibility
157
+ visibility = if private_method_defined?(method_name, false)
158
+ :private
159
+ elsif protected_method_defined?(method_name, false)
160
+ :protected
161
+ else
162
+ :public
163
+ end
164
+
165
+ define_method(method_name) do |*args, **kwargs, &blk|
166
+ ::Daytona.with_instrumentation(otel_state, component, method_name.to_s) do
167
+ original.bind_call(self, *args, **kwargs, &blk)
168
+ end
169
+ end
170
+
171
+ # Restore visibility
172
+ case visibility
173
+ when :private then private method_name
174
+ when :protected then protected method_name
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
@@ -6,6 +6,8 @@ require 'uri'
6
6
 
7
7
  module Daytona
8
8
  class Process # rubocop:disable Metrics/ClassLength
9
+ include Instrumentation
10
+
9
11
  # @return [Daytona::SandboxPythonCodeToolbox,
10
12
  attr_reader :code_toolbox
11
13
 
@@ -24,11 +26,13 @@ module Daytona
24
26
  # @param sandbox_id [String] The ID of the Sandbox
25
27
  # @param toolbox_api [DaytonaToolboxApiClient::ProcessApi] API client for Sandbox operations
26
28
  # @param get_preview_link [Proc] Function to get preview link for a port
27
- def initialize(code_toolbox:, sandbox_id:, toolbox_api:, get_preview_link:)
29
+ # @param otel_state [Daytona::OtelState, nil]
30
+ def initialize(code_toolbox:, sandbox_id:, toolbox_api:, get_preview_link:, otel_state: nil)
28
31
  @code_toolbox = code_toolbox
29
32
  @sandbox_id = sandbox_id
30
33
  @toolbox_api = toolbox_api
31
34
  @get_preview_link = get_preview_link
35
+ @otel_state = otel_state
32
36
  end
33
37
 
34
38
  # Execute a shell command in the Sandbox
@@ -448,8 +452,18 @@ module Daytona
448
452
  toolbox_api.get_pty_session(session_id)
449
453
  end
450
454
 
455
+ instrument :exec, :code_run, :create_session, :get_session, :get_session_command,
456
+ :execute_session_command, :get_session_command_logs, :get_session_command_logs_async,
457
+ :send_session_command_input, :list_sessions, :delete_session,
458
+ :create_pty_session, :connect_pty_session, :resize_pty_session,
459
+ :delete_pty_session, :list_pty_sessions, :get_pty_session_info,
460
+ component: 'Process'
461
+
451
462
  private
452
463
 
464
+ # @return [Daytona::OtelState, nil]
465
+ attr_reader :otel_state
466
+
453
467
  # Parse the output of a command to extract ExecutionArtifacts
454
468
  #
455
469
  # @param lines [Array<String>] A list of lines of output from a command
@@ -4,6 +4,8 @@ require 'timeout'
4
4
 
5
5
  module Daytona
6
6
  class Sandbox # rubocop:disable Metrics/ClassLength
7
+ include Instrumentation
8
+
7
9
  DEFAULT_TIMEOUT = 60
8
10
 
9
11
  # @return [String] The ID of the sandbox
@@ -116,12 +118,14 @@ module Daytona
116
118
  # @params config [Daytona::Config]
117
119
  # @params sandbox_api [DaytonaApiClient::SandboxApi]
118
120
  # @params sandbox_dto [DaytonaApiClient::Sandbox]
119
- def initialize(code_toolbox:, sandbox_dto:, config:, sandbox_api:, get_proxy_toolbox_url:) # rubocop:disable Metrics/MethodLength
121
+ # @params otel_state [Daytona::OtelState, nil]
122
+ def initialize(code_toolbox:, sandbox_dto:, config:, sandbox_api:, get_proxy_toolbox_url:, otel_state: nil) # rubocop:disable Metrics/MethodLength
120
123
  process_response(sandbox_dto)
121
124
  @code_toolbox = code_toolbox
122
125
  @config = config
123
126
  @sandbox_api = sandbox_api
124
127
  @get_proxy_toolbox_url = get_proxy_toolbox_url
128
+ @otel_state = otel_state
125
129
 
126
130
  # Create toolbox API clients with dynamic configuration
127
131
  toolbox_api_config = build_toolbox_api_config
@@ -148,15 +152,17 @@ module Daytona
148
152
  sandbox_id: id,
149
153
  code_toolbox:,
150
154
  toolbox_api: process_api,
151
- get_preview_link: proc { |port| preview_url(port) }
155
+ get_preview_link: proc { |port| preview_url(port) },
156
+ otel_state:
152
157
  )
153
- @fs = FileSystem.new(sandbox_id: id, toolbox_api: fs_api)
154
- @git = Git.new(sandbox_id: id, toolbox_api: git_api)
155
- @computer_use = ComputerUse.new(sandbox_id: id, toolbox_api: computer_use_api)
158
+ @fs = FileSystem.new(sandbox_id: id, toolbox_api: fs_api, otel_state:)
159
+ @git = Git.new(sandbox_id: id, toolbox_api: git_api, otel_state:)
160
+ @computer_use = ComputerUse.new(sandbox_id: id, toolbox_api: computer_use_api, otel_state:)
156
161
  @code_interpreter = CodeInterpreter.new(
157
162
  sandbox_id: id,
158
163
  toolbox_api: interpreter_api,
159
- get_preview_link: proc { |port| preview_url(port) }
164
+ get_preview_link: proc { |port| preview_url(port) },
165
+ otel_state:
160
166
  )
161
167
  @lsp_api = lsp_api
162
168
  @info_api = info_api
@@ -382,7 +388,7 @@ module Daytona
382
388
  # based on the sandbox working directory.
383
389
  # @return [Daytona::LspServer]
384
390
  def create_lsp_server(language_id:, path_to_project:)
385
- LspServer.new(language_id:, path_to_project:, toolbox_api: @lsp_api, sandbox_id: id)
391
+ LspServer.new(language_id:, path_to_project:, toolbox_api: @lsp_api, sandbox_id: id, otel_state:)
386
392
  end
387
393
 
388
394
  # Validates an SSH access token for the sandbox.
@@ -411,8 +417,19 @@ module Daytona
411
417
  DaytonaApiClient::SandboxState::DESTROYED])
412
418
  end
413
419
 
420
+ instrument :archive, :auto_archive_interval=, :auto_delete_interval=, :auto_stop_interval=,
421
+ :create_ssh_access, :delete, :get_user_home_dir, :get_work_dir, :labels=,
422
+ :preview_url, :create_signed_preview_url, :expire_signed_preview_url,
423
+ :refresh, :refresh_activity, :revoke_ssh_access, :start, :recover, :stop,
424
+ :create_lsp_server, :validate_ssh_access, :wait_for_sandbox_start,
425
+ :wait_for_sandbox_stop,
426
+ component: 'Sandbox'
427
+
414
428
  private
415
429
 
430
+ # @return [Daytona::OtelState, nil]
431
+ attr_reader :otel_state
432
+
416
433
  # Build toolbox API configuration with dynamic base URL from preview link
417
434
  # @return [DaytonaToolboxApiClient::Configuration]
418
435
  def build_toolbox_api_config
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Daytona
4
4
  module Sdk
5
- VERSION = '0.140.0'
5
+ VERSION = '0.141.0'
6
6
  end
7
7
  end
data/lib/daytona/sdk.rb CHANGED
@@ -9,6 +9,7 @@ require 'websocket-client-simple'
9
9
 
10
10
  require_relative 'sdk/version'
11
11
  require_relative 'config'
12
+ require_relative 'otel'
12
13
  require_relative 'common/charts'
13
14
  require_relative 'common/code_interpreter'
14
15
  require_relative 'common/code_language'
@@ -4,15 +4,19 @@ require 'uri'
4
4
 
5
5
  module Daytona
6
6
  class SnapshotService
7
+ include Instrumentation
8
+
7
9
  SNAPSHOTS_FETCH_LIMIT = 200
8
10
 
9
11
  # @param snapshots_api [DaytonaApiClient::SnapshotsApi] The snapshots API client
10
12
  # @param object_storage_api [DaytonaApiClient::ObjectStorageApi] The object storage API client
11
13
  # @param default_region_id [String, nil] Default region ID for snapshot creation
12
- def initialize(snapshots_api:, object_storage_api:, default_region_id: nil)
14
+ # @param otel_state [Daytona::OtelState, nil]
15
+ def initialize(snapshots_api:, object_storage_api:, default_region_id: nil, otel_state: nil)
13
16
  @snapshots_api = snapshots_api
14
17
  @object_storage_api = object_storage_api
15
18
  @default_region_id = default_region_id
19
+ @otel_state = otel_state
16
20
  end
17
21
 
18
22
  # List all Snapshots.
@@ -119,6 +123,8 @@ module Daytona
119
123
  # @return [Daytona::Snapshot]
120
124
  def activate(snapshot) = Snapshot.from_dto(snapshots_api.activate_snapshot(snapshot.id))
121
125
 
126
+ instrument :list, :delete, :get, :create, :activate, component: 'SnapshotService'
127
+
122
128
  # Processes the image context by uploading it to object storage
123
129
  #
124
130
  # @param image [Daytona::Image] The Image instance
@@ -156,6 +162,9 @@ module Daytona
156
162
  # @return [String, nil] Default region ID for snapshot creation
157
163
  attr_reader :default_region_id
158
164
 
165
+ # @return [Daytona::OtelState, nil]
166
+ attr_reader :otel_state
167
+
159
168
  # Wait for snapshot to reach a terminal state (ACTIVE, ERROR, or BUILD_FAILED)
160
169
  # Optionally streams logs if on_logs callback is provided
161
170
  #
@@ -2,11 +2,15 @@
2
2
 
3
3
  module Daytona
4
4
  class VolumeService
5
+ include Instrumentation
6
+
5
7
  # Service for managing Daytona Volumes. Can be used to list, get, create and delete Volumes.
6
8
  #
7
9
  # @param volumes_api [DaytonaApiClient::VolumesApi]
8
- def initialize(volumes_api)
10
+ # @param otel_state [Daytona::OtelState, nil]
11
+ def initialize(volumes_api, otel_state: nil)
9
12
  @volumes_api = volumes_api
13
+ @otel_state = otel_state
10
14
  end
11
15
 
12
16
  # Create new Volume.
@@ -41,9 +45,14 @@ module Daytona
41
45
  volumes_api.list_volumes.map { |volume| Volume.new(volume) }
42
46
  end
43
47
 
48
+ instrument :create, :delete, :get, :list, component: 'VolumeService'
49
+
44
50
  private
45
51
 
46
52
  # @return [DaytonaApiClient::VolumesApi]
47
53
  attr_reader :volumes_api
54
+
55
+ # @return [Daytona::OtelState, nil]
56
+ attr_reader :otel_state
48
57
  end
49
58
  end
metadata CHANGED
@@ -1,15 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daytona
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.140.0
4
+ version: 0.141.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daytona Platforms Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-10 00:00:00.000000000 Z
11
+ date: 2026-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: opentelemetry-exporter-otlp
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.29'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.29'
27
+ - !ruby/object:Gem::Dependency
28
+ name: opentelemetry-exporter-otlp-metrics
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: opentelemetry-metrics-sdk
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: opentelemetry-sdk
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.4'
13
69
  - !ruby/object:Gem::Dependency
14
70
  name: aws-sdk-s3
15
71
  requirement: !ruby/object:Gem::Requirement
@@ -30,28 +86,28 @@ dependencies:
30
86
  requirements:
31
87
  - - '='
32
88
  - !ruby/object:Gem::Version
33
- version: 0.140.0
89
+ version: 0.141.0
34
90
  type: :runtime
35
91
  prerelease: false
36
92
  version_requirements: !ruby/object:Gem::Requirement
37
93
  requirements:
38
94
  - - '='
39
95
  - !ruby/object:Gem::Version
40
- version: 0.140.0
96
+ version: 0.141.0
41
97
  - !ruby/object:Gem::Dependency
42
98
  name: daytona_toolbox_api_client
43
99
  requirement: !ruby/object:Gem::Requirement
44
100
  requirements:
45
101
  - - '='
46
102
  - !ruby/object:Gem::Version
47
- version: 0.140.0
103
+ version: 0.141.0
48
104
  type: :runtime
49
105
  prerelease: false
50
106
  version_requirements: !ruby/object:Gem::Requirement
51
107
  requirements:
52
108
  - - '='
53
109
  - !ruby/object:Gem::Version
54
- version: 0.140.0
110
+ version: 0.141.0
55
111
  - !ruby/object:Gem::Dependency
56
112
  name: dotenv
57
113
  requirement: !ruby/object:Gem::Requirement
@@ -131,6 +187,7 @@ files:
131
187
  - lib/daytona/git.rb
132
188
  - lib/daytona/lsp_server.rb
133
189
  - lib/daytona/object_storage.rb
190
+ - lib/daytona/otel.rb
134
191
  - lib/daytona/process.rb
135
192
  - lib/daytona/sandbox.rb
136
193
  - lib/daytona/sdk.rb