google-cloud-debugger 0.26.1 → 0.27.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,6 +13,8 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
+ require "google/cloud/debugger/breakpoint/status_message"
17
+
16
18
  module Google
17
19
  module Cloud
18
20
  module Debugger
@@ -83,12 +85,21 @@ module Google
83
85
 
84
86
  ##
85
87
  # Max number of member variables to evaluate in compound variables
86
- MAX_MEMBERS = 10
88
+ MAX_MEMBERS = 1000
87
89
 
88
90
  ##
89
91
  # Max length on variable inspect results. Truncate extra and replace
90
92
  # with ellipsis.
91
- MAX_STRING_LENGTH = 260
93
+ MAX_STRING_LENGTH = 500
94
+
95
+ ##
96
+ # @private Minimum amount of size limit needed for evaluation.
97
+ MIN_REQUIRED_SIZE = 100
98
+
99
+ ##
100
+ # @private Message to display on variables when snapshot buffer is
101
+ # full.
102
+ BUFFER_FULL_MSG = "Buffer full. Use an expression to see more data."
92
103
 
93
104
  ##
94
105
  # @private Name of the variable, if any.
@@ -111,6 +122,17 @@ module Google
111
122
  # @return [Array<Variable>]
112
123
  attr_accessor :members
113
124
 
125
+ ##
126
+ # @private Reference to a variable in the shared variable table. More
127
+ # than one variable can reference the same variable in the table. The
128
+ # var_table_index field is an index into variable_table in Breakpoint.
129
+ attr_accessor :var_table_index
130
+
131
+ ##
132
+ # @private The variable table this variable references to (if
133
+ # var_table_index is set).
134
+ attr_accessor :var_table
135
+
114
136
  ##
115
137
  # @private Status associated with the variable. This field will
116
138
  # usually stay unset. A status of a single variable only applies to
@@ -121,8 +143,12 @@ module Google
121
143
  # VARIABLE_NAME. Alternatively refers_to will be set to
122
144
  # VARIABLE_VALUE. In either case variable value and members will be
123
145
  # unset.
124
- # TODO: Implement variable status
125
- # attr_accessor :status
146
+ attr_accessor :status
147
+
148
+ ##
149
+ # @private The original Ruby object this Breakpoint::Variable is based
150
+ # upon.
151
+ attr_accessor :source_var
126
152
 
127
153
  ##
128
154
  # @private Create an empty Variable object.
@@ -132,22 +158,29 @@ module Google
132
158
 
133
159
  ##
134
160
  # Convert a Ruby variable into a
135
- # Google::Cloud::Debugger::Breakpoint::Variable object.
161
+ # Google::Cloud::Debugger::Breakpoint::Variable object. If
162
+ # a variable table is provided, it will store all the subsequently
163
+ # created compound variables into the variable table for sharing.
136
164
  #
137
165
  # @param [Any] source Source Ruby variable to convert from
138
166
  # @param [String] name Name of the varaible
139
167
  # @param [Integer] depth Number of levels to evaluate in compound
140
168
  # variables. Default to
141
169
  # {Google::Cloud::Debugger::Breakpoint::Variable::MAX_DEPTH}
170
+ # @param [Breakpoint::VariableTable] var_table A variable table
171
+ # to store shared compound variables. Optional.
172
+ # @param [Integer] limit Maximum number of bytes this conversion
173
+ # should take. This include nested compound member variables'
174
+ # conversions.
142
175
  #
143
- # @example
176
+ # @example Simple variable conversion
144
177
  # x = 3
145
178
  # var = Variable.from_rb_var x, name: "x"
146
179
  # var.name #=> "x"
147
180
  # var.value #=> "3"
148
181
  # var.type #=> "Integer"
149
182
  #
150
- # @example
183
+ # @example Hash conversion
151
184
  # hash = {a: 1, b: :two}
152
185
  # var = Variable.from_rb_var hash, name: "hash"
153
186
  # var.name #=> "hash"
@@ -159,8 +192,9 @@ module Google
159
192
  # var.members[1].value #=> "two"
160
193
  # var.members[1].type #=> "Symbol"
161
194
  #
162
- # @example
195
+ # @example Custom compound variable conversion
163
196
  # foo = Foo.new(a: 1, b: []) #=> #<Foo:0x0000 @a: 1, @b: []>
197
+ # var_table = VariableTable.new
164
198
  # var = Variable.from_rb_var foo, name: "foo"
165
199
  # var.name #=> "foo"
166
200
  # var.type #=> "Foo"
@@ -171,71 +205,186 @@ module Google
171
205
  # var.members[1].value #=> "[]"
172
206
  # var.members[1].type #=> "Array"
173
207
  #
208
+ # @example Use variable table for shared compound variables
209
+ # hash = {a: 1}
210
+ # ary = [hash, hash]
211
+ # var_table = VariableTable.new
212
+ # var = Variable.from_rb_var ary, name: "ary", var_table: var_table
213
+ # var.name #=> "ary"
214
+ # var.var_table_index #=> 0
215
+ # var_table[0].type #=> "Array"
216
+ # var_table[0].members[0].name #=> "[0]"
217
+ # var_table[0].members[0].var_table_index #=> 1
218
+ # var_table[0].members[1].name #=> "[1]"
219
+ # var_table[0].members[1].var_table_index #=> 1
220
+ # var_table[1].type #=> "Hash"
221
+ # var_table[1].members[0].name #=> "a"
222
+ # var_table[1].members[0].type #=> "Integer"
223
+ # var_table[1].members[0].value #=> "1"
224
+ #
174
225
  # @return [Google::Cloud::Debugger::Breakpoint::Variable] Converted
175
226
  # variable.
176
227
  #
177
- def self.from_rb_var source, name: nil, depth: MAX_DEPTH
228
+ def self.from_rb_var source, name: nil, depth: MAX_DEPTH,
229
+ var_table: nil, limit: nil
178
230
  return source if source.is_a? Variable
179
231
 
232
+ if limit && limit < MIN_REQUIRED_SIZE
233
+ return buffer_full_variable var_table
234
+ end
235
+
180
236
  # If source is a non-empty Array or Hash, or source has instance
181
237
  # variables, evaluate source as a compound variable.
182
- if (((source.is_a?(Hash) || source.is_a?(Array)) &&
183
- !source.empty?) || !source.instance_variables.empty?) &&
184
- depth > 0
185
- from_compound_var source, name: name, depth: depth
238
+ if compound_var?(source) && depth > 0
239
+ from_compound_var source, name: name, depth: depth,
240
+ var_table: var_table, limit: limit
186
241
  else
187
- var = Variable.new
188
- var.name = name.to_s if name
189
- var.type = source.class.to_s
190
- var.value = truncate_value(source.inspect)
191
-
192
- var
242
+ new.tap do |var|
243
+ var.name = name.to_s if name
244
+ var.type = source.class.to_s
245
+ limit = deduct_limit limit,
246
+ var.name.to_s.bytesize + var.type.bytesize
247
+ var.value = truncate_value source.inspect, limit
248
+ var.source_var = source
249
+ end
193
250
  end
194
251
  end
195
252
 
196
253
  ##
197
254
  # @private Helper method that converts compound variables.
198
- def self.from_compound_var source, name: nil, depth: MAX_DEPTH
255
+ def self.from_compound_var source, name: nil, depth: MAX_DEPTH,
256
+ var_table: nil, limit: nil
199
257
  return source if source.is_a? Variable
200
- var = Variable.new
258
+
259
+ if limit && limit < MIN_REQUIRED_SIZE
260
+ return buffer_full_variable var_table
261
+ end
262
+
263
+ var = new
201
264
  var.name = name.to_s if name
265
+ var.source_var = source
266
+ limit = deduct_limit limit, var.name.to_s.bytesize
267
+
268
+ if var_table
269
+ var.var_table = var_table
270
+ var.var_table_index =
271
+ var_table.rb_var_index(source) ||
272
+ add_shared_compound_var(source, depth, var_table, limit: limit)
273
+ else
274
+ var.type = source.class.to_s
275
+ limit = deduct_limit limit, var.type.bytesize
276
+ add_compound_members var, source, depth, limit: limit
277
+ end
278
+ var
279
+ end
280
+
281
+ ##
282
+ # @private Determine if a given Ruby variable is a compound variable.
283
+ def self.compound_var? source
284
+ ((source.is_a?(Hash) || source.is_a?(Array)) && !source.empty?) ||
285
+ !source.instance_variables.empty?
286
+ end
287
+
288
+ ##
289
+ # @private Add a shared compound variable to the breakpoint
290
+ # variable table.
291
+ def self.add_shared_compound_var source, depth, var_table, limit: nil
292
+ var = new
202
293
  var.type = source.class.to_s
294
+ var.source_var = source
295
+ limit = deduct_limit limit, var.type.bytesize
296
+
297
+ table_index = var_table.size
298
+ var_table.add var
299
+
300
+ add_compound_members var, source, depth, var_table, limit: limit
203
301
 
302
+ table_index
303
+ end
304
+
305
+ ##
306
+ # @private Add member variables to a compound variable.
307
+ def self.add_compound_members var, source, depth, var_table = nil,
308
+ limit: nil
204
309
  case source
205
310
  when Hash
206
- add_compound_members var, source do |(k, v)|
207
- from_rb_var(v, name: k, depth: depth - 1)
311
+ add_member_vars var, source, limit: limit do |(k, v), _, lmt|
312
+ from_rb_var v, name: k, depth: depth - 1, var_table: var_table,
313
+ limit: lmt
208
314
  end
209
315
  when Array
210
- add_compound_members var, source do |el, i|
211
- from_rb_var(el, name: "[#{i}]", depth: depth - 1)
316
+ add_member_vars var, source, limit: limit do |el, i, lmt|
317
+ from_rb_var el, name: "[#{i}]", depth: depth - 1,
318
+ var_table: var_table, limit: lmt
212
319
  end
213
320
  else
214
- add_compound_members var, source.instance_variables do |var_name|
321
+ members = source.instance_variables
322
+ add_member_vars var, members, limit: limit do |var_name, _, lmt|
215
323
  instance_var = source.instance_variable_get var_name
216
- from_rb_var(instance_var, name: var_name, depth: depth - 1)
324
+ from_rb_var instance_var, name: var_name, depth: depth - 1,
325
+ var_table: var_table, limit: lmt
217
326
  end
218
327
  end
219
- var
220
328
  end
221
329
 
222
330
  ##
223
331
  # @private Help interate through collection of member variables for
224
332
  # compound variables.
225
- def self.add_compound_members var, members
226
- members.each_with_index do |el, i|
227
- if i < MAX_MEMBERS
228
- var.members << yield(el, i)
229
- else
333
+ def self.add_member_vars var, members, limit: nil
334
+ members.each_with_index do |member, i|
335
+ member_var = yield(member, i, limit)
336
+
337
+ limit = deduct_limit limit, member_var.total_size
338
+
339
+ buffer_full = (limit && limit < 0) ||
340
+ i >= MAX_MEMBERS ||
341
+ member_var.buffer_full_variable?
342
+
343
+ if buffer_full
230
344
  var.members << Variable.new.tap do |last_var|
231
- last_var.value =
232
- "(Only first #{MAX_MEMBERS} items were captured)"
345
+ last_var.set_error_state \
346
+ "Only first #{i} items were captured. Use in " \
347
+ "an expression to see all items.",
348
+ refers_to: StatusMessage::VARIABLE_VALUE
233
349
  end
234
350
  break
351
+ else
352
+ var.members << member_var
353
+ end
354
+ end
355
+ end
356
+
357
+ ##
358
+ # @private Create an empty variable that points to the shared
359
+ # "Buffer Full" variable in the given variable table (always index 0)
360
+ # if a variable table is passed in. Otherwise create an error variable
361
+ # with the buffer full message.
362
+ def self.buffer_full_variable var_table = nil, name: nil
363
+ new.tap do |var|
364
+ var.name = name if name
365
+
366
+ if var_table && var_table.first &&
367
+ var_table.first.buffer_full_variable?
368
+ var.var_table = var_table
369
+ var.var_table_index = 0
370
+ else
371
+ var.set_error_state BUFFER_FULL_MSG,
372
+ refers_to: StatusMessage::VARIABLE_VALUE
235
373
  end
236
374
  end
237
375
  end
238
376
 
377
+ ##
378
+ # @private Helper method to calculate bytesize limit deduction.
379
+ def self.deduct_limit limit, used
380
+ limit.nil? ? nil : limit - used
381
+ end
382
+
383
+ private_class_method :add_compound_members,
384
+ :add_shared_compound_var,
385
+ :add_member_vars, :compound_var?,
386
+ :deduct_limit
387
+
239
388
  ##
240
389
  # @private New Google::Cloud::Debugger::Breakpoint::Variable
241
390
  # from a Google::Devtools::Clouddebugger::V2::Variable object.
@@ -246,6 +395,8 @@ module Google
246
395
  o.value = grpc.value
247
396
  o.type = grpc.type
248
397
  o.members = from_grpc_list grpc.members
398
+ o.var_table_index = var_table_index_from_grpc grpc.var_table_index
399
+ o.status = Breakpoint::StatusMessage.from_grpc grpc.status
249
400
  end
250
401
  end
251
402
 
@@ -258,11 +409,18 @@ module Google
258
409
  grpc_list.map { |var_grpc| from_grpc var_grpc }
259
410
  end
260
411
 
412
+ ##
413
+ # @private Extract var_table_index from the equivalent GRPC struct
414
+ def self.var_table_index_from_grpc grpc
415
+ grpc.nil? ? nil : grpc.value
416
+ end
417
+
261
418
  ##
262
419
  # @private Limit string to MAX_STRING_LENTH. Replace extra characters
263
420
  # with ellipsis
264
- def self.truncate_value str
265
- str.gsub(/(.{#{MAX_STRING_LENGTH - 3}}).+/, '\1...')
421
+ def self.truncate_value str, limit = nil
422
+ limit ||= MAX_STRING_LENGTH
423
+ str.gsub(/(.{#{limit - 3}}).+/, '\1...')
266
424
  end
267
425
  private_class_method :add_compound_members, :truncate_value
268
426
 
@@ -272,12 +430,13 @@ module Google
272
430
  name.nil? &&
273
431
  value.nil? &&
274
432
  type.nil? &&
275
- members.nil?
276
- # TODO: Add status when implementing variable status
433
+ members.nil? &&
434
+ var_table_index.nil? &&
435
+ status.nil?
277
436
  end
278
437
 
279
438
  ##
280
- # @private Exports the Variable to a
439
+ # Exports the Variable to a
281
440
  # Google::Devtools::Clouddebugger::V2::Variable object.
282
441
  def to_grpc
283
442
  return nil if empty?
@@ -285,12 +444,125 @@ module Google
285
444
  name: name.to_s,
286
445
  value: value.to_s,
287
446
  type: type.to_s,
288
- members: members_to_grpc || []
447
+ var_table_index: var_table_index_to_grpc,
448
+ members: members_to_grpc || [],
449
+ status: status_to_grpc
289
450
  )
290
451
  end
291
452
 
453
+ ##
454
+ # Set this variable to an error state by setting the status field
455
+ def set_error_state message, refers_to: StatusMessage::UNSPECIFIED
456
+ @status = StatusMessage.new.tap do |s|
457
+ s.is_error = true
458
+ s.refers_to = refers_to
459
+ s.description = message
460
+ end
461
+ end
462
+
463
+ ##
464
+ # Calculate the total bytesize of all the attributes and that of the
465
+ # member variables, plus references into other variables in the
466
+ # variable table.
467
+ #
468
+ # @return [Integer] The total payload size of this variable in bytes.
469
+ def total_size
470
+ unless @total_size
471
+ vars = [self, *(unique_members || [])]
472
+
473
+ @total_size = vars.inject(payload_size) do |sum, var|
474
+ if var.var_table && var.var_table_index
475
+ sum + var.var_table[var.var_table_index].total_size
476
+ else
477
+ sum
478
+ end
479
+ end
480
+ end
481
+
482
+ @total_size
483
+ end
484
+
485
+ ##
486
+ # Calculate the bytesize of all the attributes and that of the
487
+ # member variables.
488
+ #
489
+ # @return [Integer] The total payload size of this variable in bytes.
490
+ def payload_size
491
+ unless @payload_size
492
+ @payload_size = name.to_s.bytesize +
493
+ type.to_s.bytesize +
494
+ value.to_s.bytesize
495
+
496
+ unless members.nil?
497
+ @payload_size = members.inject(@payload_size) do |sum, member|
498
+ sum + member.payload_size
499
+ end
500
+ end
501
+ end
502
+
503
+ @payload_size
504
+ end
505
+
506
+ ##
507
+ # @private Get a unique array of members that don't reference
508
+ # same object in variable table
509
+ def unique_members
510
+ seen_indices = {}
511
+
512
+ members.select do |member|
513
+ if seen_indices[member.var_table_index]
514
+ false
515
+ else
516
+ seen_indices[member.var_table_index] = true
517
+ true
518
+ end
519
+ end
520
+ end
521
+
522
+ ##
523
+ # @private Whether this variable is a reference variable into
524
+ # the shared variable table or not.
525
+ def reference_variable?
526
+ value.nil? && members.empty? && !var_table_index.nil?
527
+ end
528
+
529
+ ##
530
+ # @private Check if a given variable is a buffer full variable, or an
531
+ # reference variable to the shared buffer full variable
532
+ def buffer_full_variable?
533
+ if (status &&
534
+ status.description == BUFFER_FULL_MSG) ||
535
+ (var_table &&
536
+ reference_variable? &&
537
+ var_table_index.zero? &&
538
+ var_table[0] &&
539
+ var_table[0].status &&
540
+ var_table[0].status.description == BUFFER_FULL_MSG)
541
+ true
542
+ else
543
+ false
544
+ end
545
+ end
546
+
292
547
  private
293
548
 
549
+ ##
550
+ # @private Exports the Variable status to grpc
551
+ def status_to_grpc
552
+ status.nil? ? nil : status.to_grpc
553
+ end
554
+
555
+ ##
556
+ # @private Exports the Variable var_table_index attribute to
557
+ # an Int32Value gRPC struct
558
+ def var_table_index_to_grpc
559
+ if var_table_index
560
+ Google::Protobuf::Int32Value.new value: var_table_index
561
+ else
562
+ nil
563
+ end
564
+ end
565
+
294
566
  ##
295
567
  # @private Exports the Variable members to an array of
296
568
  # Google::Devtools::Clouddebugger::V2::Variable objects.