rbtrace 0.3.5 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbtrace (0.3.5)
4
+ rbtrace (0.3.6)
5
5
  ffi (>= 1.0.5)
6
6
  msgpack (>= 0.4.3)
7
7
  trollop (>= 1.16.2)
data/README.md CHANGED
@@ -15,7 +15,7 @@ in production.
15
15
 
16
16
  ## tracer types
17
17
 
18
- rbtrace has several different tracing modes:
18
+ rbtrace has several different tracing modes.
19
19
 
20
20
  ### firehose: show everything
21
21
 
@@ -33,6 +33,12 @@ rbtrace has several different tracing modes:
33
33
 
34
34
  % rbtrace -p <PID> --gc
35
35
 
36
+ ### notes
37
+
38
+ `--firehose` is not reliable on osx.
39
+
40
+ `--slow`, `--gc` and `--methods` can be combined.
41
+
36
42
  ## predefined tracers
37
43
 
38
44
  rbtrace also includes a set of [predefined tracers](https://github.com/tmm1/rbtrace/tree/master/tracers)
@@ -186,12 +192,6 @@ for popular ruby libraries and functions.
186
192
 
187
193
  ## todo
188
194
 
189
- * write some real tests (that assert things)
190
- * more expressive selectors (.*Controller#)
191
- * include date in show time (-t)
192
- * add --eval
193
- * add --forkgdb
194
- * add --strace mode/expression
195
195
  * add triggers to start tracing slow methods only inside another method
196
196
  * add watch expressions to fire tracers only when an expression is true
197
197
  * add special expressions for method args (_arg0_, _arguments_)
data/bin/rbtrace CHANGED
@@ -142,7 +142,7 @@ class RBTracer
142
142
  raise ArgumentError, 'could not signal process, are you running as root?'
143
143
  end
144
144
 
145
- Process.kill 'URG', @pid
145
+ signal
146
146
  sleep 0.25 # wait for process to create msgqs
147
147
 
148
148
  @qi = MsgQ.msgget( pid, 0666)
@@ -169,6 +169,7 @@ class RBTracer
169
169
 
170
170
  @out = STDOUT
171
171
  @prefix = ' '
172
+ @printed_newline = true
172
173
 
173
174
  @show_time = false
174
175
  @show_duration = true
@@ -201,6 +202,35 @@ class RBTracer
201
202
  send_cmd(:devmode)
202
203
  end
203
204
 
205
+ # Fork the process and return the copy's pid.
206
+ #
207
+ # Returns a Fixnum pid.
208
+ def fork
209
+ send_cmd(:fork)
210
+ if wait_for(30, 'for fork'){ !!@forked_pid }
211
+ @forked_pid
212
+ else
213
+ STDERR.puts '*** timed out waiting for fork'
214
+ end
215
+ end
216
+
217
+ # Evaluate some ruby code.
218
+ #
219
+ # Returns the String result.
220
+ def eval(code)
221
+ if (err = valid_syntax?(code)) != true
222
+ raise ArgumentError, "#{err.class} for expression #{code.inspect}"
223
+ end
224
+
225
+ send_cmd(:eval, code)
226
+
227
+ if wait_for(10, 'for eval response'){ !!@eval_result }
228
+ @eval_result
229
+ else
230
+ STDERR.puts '*** timed out waiting for eval response'
231
+ end
232
+ end
233
+
204
234
  # Turn on GC tracing.
205
235
  #
206
236
  # Returns nothing.
@@ -257,17 +287,17 @@ class RBTracer
257
287
  rescue Errno::ESRCH
258
288
  end
259
289
 
260
- puts
290
+ newline
261
291
 
262
292
  if wait_for{ @attached == false }
263
- puts
293
+ newline
264
294
  STDERR.puts "*** detached from process #{pid}"
265
295
  else
266
- puts
296
+ newline
267
297
  STDERR.puts "*** could not detach cleanly from process #{pid}"
268
298
  end
269
299
  rescue Errno::EINVAL, Errno::EIDRM
270
- puts
300
+ newline
271
301
  STDERR.puts "*** process #{pid} is gone"
272
302
  STDERR.puts "*** #{$!.inspect}"
273
303
  STDERR.puts $!.backtrace.join("\n ")
@@ -302,15 +332,24 @@ class RBTracer
302
332
  end
303
333
  end
304
334
 
335
+ def puts(arg=nil)
336
+ @printed_newline = true
337
+ arg ? @out.puts(arg) : @out.puts
338
+ end
339
+
305
340
  private
306
341
 
342
+ def signal
343
+ Process.kill 'URG', @pid
344
+ end
345
+
307
346
  # Process incoming events until either a timeout or a condition becomes true.
308
347
  #
309
348
  # time - The Fixnum timeout in seconds.
310
349
  # block - The Block that is checked every 50ms until it returns true.
311
350
  #
312
351
  # Returns true when the condition was met, or false on a timeout.
313
- def wait_for(time=5)
352
+ def wait_for(time=5, reason='to detach cleanly')
314
353
  wait = 0.05 # polling interval
315
354
 
316
355
  (time/wait).to_i.times do
@@ -321,7 +360,7 @@ class RBTracer
321
360
 
322
361
  return true if yield
323
362
  rescue Interrupt
324
- STDERR.puts "*** waiting to detach cleanly (#{time.to_i}s left)"
363
+ STDERR.puts "*** waiting #{reason} (#{time.to_i}s left)"
325
364
  retry
326
365
  end
327
366
  end
@@ -337,7 +376,7 @@ class RBTracer
337
376
  rescue Errno::EINTR
338
377
  retry
339
378
  end
340
- Process.kill 'URG', @pid
379
+ signal
341
380
  recv_lines
342
381
  end
343
382
 
@@ -351,18 +390,20 @@ class RBTracer
351
390
 
352
391
  def valid_syntax?(code)
353
392
  begin
354
- eval("#{code}\nBEGIN {return true}", nil, 'rbtrace_expression', 0)
393
+ Kernel.eval("#{code}\nBEGIN {return true}", nil, 'rbtrace_expression', 0)
355
394
  rescue Exception => e
356
395
  e
357
396
  end
358
397
  end
359
398
 
360
- def print(*args)
361
- @out.print(*args)
399
+ def print(arg)
400
+ @printed_newline = false
401
+ @out.print(arg)
362
402
  end
363
403
 
364
- def puts(*args)
365
- @out.puts(*args)
404
+ def newline
405
+ puts unless @printed_newline
406
+ @printed_newline = true
366
407
  end
367
408
 
368
409
  def parse_cmd(line)
@@ -379,6 +420,11 @@ class RBTracer
379
420
  event = cmd.shift
380
421
 
381
422
  case event
423
+ when 'during_gc'
424
+ sleep 0.01
425
+ signal
426
+ return
427
+
382
428
  when 'attached'
383
429
  tracer_pid, = *cmd
384
430
  if tracer_pid != Process.pid
@@ -406,6 +452,14 @@ class RBTracer
406
452
  end
407
453
 
408
454
  case event
455
+ when 'forked'
456
+ pid, = *cmd
457
+ @forked_pid = pid
458
+
459
+ when 'evaled'
460
+ res, = *cmd
461
+ @eval_result = res
462
+
409
463
  when 'mid'
410
464
  mid, name = *cmd
411
465
  @methods[mid] = name
@@ -562,7 +616,7 @@ class RBTracer
562
616
  parser = Trollop::Parser.new do
563
617
  version <<-EOS
564
618
  rbtrace: like strace, but for ruby code
565
- version 0.3.5
619
+ version 0.3.6
566
620
  (c) 2011 Aman Gupta (tmm1)
567
621
  http://github.com/tmm1/rbtrace
568
622
  EOS
@@ -665,6 +719,14 @@ EOS
665
719
 
666
720
  opt :devmode,
667
721
  "assume the ruby process is reloading classes and methods"
722
+
723
+ opt :fork,
724
+ "fork a copy of the process for debugging (so you can attach gdb.rb)"
725
+
726
+ opt :eval,
727
+ "evaluate a ruby expression in the process",
728
+ :type => String,
729
+ :short => '-e'
668
730
  end
669
731
 
670
732
  opts = Trollop.with_standard_exception_handling(parser) do
@@ -672,12 +734,16 @@ EOS
672
734
  parser.parse(ARGV)
673
735
  end
674
736
 
675
- unless %w[ slow firehose methods config gc ].find{ |n| opts[:"#{n}_given"] }
737
+ unless %w[ fork eval slow firehose methods config gc ].find{ |n| opts[:"#{n}_given"] }
676
738
  $stderr.puts "Error: --slow, --gc, --firehose, --methods or --config required."
677
739
  $stderr.puts "Try --help for help."
678
740
  exit(-1)
679
741
  end
680
742
 
743
+ if opts[:fork_given] and opts[:pid].size != 1
744
+ parser.die :fork, '(can only be invoked with one pid)'
745
+ end
746
+
681
747
  methods = []
682
748
 
683
749
  if opts[:methods_given]
@@ -764,37 +830,35 @@ EOS
764
830
  parser.die :pid, "(#{e.message})"
765
831
  end
766
832
 
767
- if opts[:devmode_given]
768
- tracer.devmode
769
- end
770
-
771
- if opts[:gc_given]
772
- tracer.gc
773
- end
774
-
775
- if opts[:firehose_given]
776
- tracer.firehose
777
- else
778
- if methods.any?
779
- tracer.add(methods)
780
- end
833
+ if opts[:fork_given]
834
+ pid = tracer.fork
835
+ STDERR.puts "*** forked off a busy looping copy at #{pid} (make sure to kill -9 it when you're done)"
781
836
 
782
- if opts[:slow_given]
783
- tracer.watch(opts[:slow])
837
+ elsif opts[:eval_given]
838
+ if res = tracer.eval(code = opts[:eval])
839
+ tracer.puts ">> #{code}"
840
+ tracer.puts "=> #{res}"
784
841
  end
785
- end
786
842
 
787
- if output
788
- tracer.out = output
789
- end
843
+ else
844
+ tracer.out = output if output
845
+ tracer.prefix = ' ' * opts[:prefix]
846
+ tracer.show_time = opts[:start_time]
847
+ tracer.show_duration = !opts[:no_duration]
790
848
 
791
- tracer.prefix = ' ' * opts[:prefix]
792
- tracer.show_duration = !opts[:no_duration]
793
- tracer.show_time = opts[:start_time]
849
+ tracer.devmode if opts[:devmode_given]
850
+ tracer.gc if opts[:gc_given]
794
851
 
795
- begin
796
- tracer.recv_loop
797
- rescue Interrupt, SignalException
852
+ if opts[:firehose_given]
853
+ tracer.firehose
854
+ else
855
+ tracer.add(methods) if methods.any?
856
+ tracer.watch(opts[:slow]) if opts[:slow_given]
857
+ end
858
+ begin
859
+ tracer.recv_loop
860
+ rescue Interrupt, SignalException
861
+ end
798
862
  end
799
863
  ensure
800
864
  if tracer
data/ext/extconf.rb CHANGED
@@ -35,6 +35,8 @@ unless have_library('msgpackc_ext') and have_header('msgpack.h')
35
35
  raise 'msgpack build failed'
36
36
  end
37
37
 
38
+ have_func('rb_during_gc', 'ruby.h')
39
+
38
40
  # increase message size on linux
39
41
  if RUBY_PLATFORM =~ /linux/
40
42
  $defs.push("-DBUF_SIZE=256")
data/ext/rbtrace.c CHANGED
@@ -21,7 +21,6 @@
21
21
 
22
22
  #ifndef RUBY_VM
23
23
  #include <env.h>
24
- #include <intern.h>
25
24
  #include <node.h>
26
25
  #include <st.h>
27
26
  #define rb_sourcefile() (ruby_current_node ? ruby_current_node->nd_file : 0)
@@ -264,8 +263,10 @@ rbtrace__send_names(ID mid, VALUE klass)
264
263
  if (!rbtracer.klass_tbl)
265
264
  rbtracer.klass_tbl = st_init_numtable();
266
265
 
267
- if (!st_is_member(rbtracer.klass_tbl, klass)) {
268
- st_insert(rbtracer.klass_tbl, (st_data_t)klass, (st_data_t)1);
266
+ if (rbtracer.devmode || !st_is_member(rbtracer.klass_tbl, klass)) {
267
+ if (!rbtracer.devmode)
268
+ st_insert(rbtracer.klass_tbl, (st_data_t)klass, (st_data_t)1);
269
+
269
270
  rbtrace__send_event(2,
270
271
  "klass",
271
272
  'l', klass,
@@ -715,26 +716,17 @@ rbtracer_watch(uint32_t threshold)
715
716
  }
716
717
  }
717
718
 
718
- static void
719
- rbtracer_unwatch()
720
- {
721
- if (rbtracer.slow) {
722
- event_hook_remove();
723
-
724
- rbtracer.firehose = false;
725
- rbtracer.slow = false;
726
- }
727
- }
728
-
729
719
  static void
730
720
  msgq_teardown()
731
721
  {
732
- if (rbtracer.mqo_id != -1) {
722
+ pid_t pid = getpid();
723
+
724
+ if (rbtracer.mqo_id != -1 && rbtracer.mqo_key == (key_t)pid) {
733
725
  msgctl(rbtracer.mqo_id, IPC_RMID, NULL);
734
726
  rbtracer.mqo_id = -1;
735
727
  rbtracer.mqo_key = 0;
736
728
  }
737
- if (rbtracer.mqi_id != -1) {
729
+ if (rbtracer.mqi_id != -1 && rbtracer.mqi_key == (key_t)-pid) {
738
730
  msgctl(rbtracer.mqi_id, IPC_RMID, NULL);
739
731
  rbtracer.mqi_id = -1;
740
732
  rbtracer.mqi_key = 0;
@@ -774,117 +766,188 @@ msgq_setup()
774
766
  }
775
767
 
776
768
  static void
777
- sigurg(int signal)
769
+ rbtrace__process_event(msgpack_object cmd)
778
770
  {
771
+ if (cmd.type != MSGPACK_OBJECT_ARRAY)
772
+ return;
773
+
779
774
  static int last_tracer_id = -1; // hax
780
- msgq_setup();
781
- if (rbtracer.mqi_id == -1) return;
775
+ char query[BUF_SIZE];
782
776
 
783
- event_msg_t msg;
784
- int n = 0;
777
+ char code[BUF_SIZE+150];
778
+ VALUE val = Qnil;
785
779
 
786
- while (true) {
787
- int ret = -1;
780
+ msgpack_object_array ary;
781
+ msgpack_object_raw str;
788
782
 
789
- for (n=0; n<10 && ret==-1; n++)
790
- ret = msgrcv(rbtracer.mqi_id, &msg, sizeof(msg)-sizeof(long), 0, IPC_NOWAIT);
783
+ /* fprintf(stderr, "GOT: ");*/
784
+ /* msgpack_object_print(stderr, cmd);*/
785
+ /* fprintf(stderr, "\n");*/
791
786
 
792
- if (ret == -1) {
793
- break;
794
- } else {
795
- char query[sizeof(msg.buf)];
787
+ ary = cmd.via.array;
788
+
789
+ if (ary.size < 1 ||
790
+ ary.ptr[0].type != MSGPACK_OBJECT_RAW)
791
+ return;
796
792
 
797
- msgpack_object cmd;
798
- msgpack_object_array ary;
799
- msgpack_object_raw str;
793
+ str = ary.ptr[0].via.raw;
800
794
 
801
- msgpack_unpacked unpacked;
802
- msgpack_unpacked_init(&unpacked);
795
+ if (0 == strncmp("attach", str.ptr, str.size)) {
796
+ if (ary.size != 2 ||
797
+ ary.ptr[1].type != MSGPACK_OBJECT_POSITIVE_INTEGER)
798
+ return;
803
799
 
804
- bool success = msgpack_unpack_next(&unpacked, msg.buf, sizeof(msg.buf), NULL);
805
- cmd = unpacked.data;
800
+ pid_t pid = (pid_t) ary.ptr[1].via.u64;
801
+
802
+ if (pid && rbtracer.attached_pid == 0)
803
+ rbtracer.attached_pid = pid;
806
804
 
807
- if (!success || cmd.type != MSGPACK_OBJECT_ARRAY)
808
- continue;
805
+ rbtrace__send_event(1,
806
+ "attached",
807
+ 'u', (uint32_t) rbtracer.attached_pid
808
+ );
809
+
810
+ } else if (0 == strncmp("detach", str.ptr, str.size)) {
811
+ if (rbtracer.attached_pid) {
812
+ rbtrace__send_event(1,
813
+ "detached",
814
+ 'u', (uint32_t) rbtracer.attached_pid
815
+ );
816
+ }
809
817
 
810
- /* fprintf(stderr, "GOT: ");*/
811
- /* msgpack_object_print(stderr, cmd);*/
812
- /* fprintf(stderr, "\n");*/
818
+ rbtracer_detach();
813
819
 
814
- ary = cmd.via.array;
820
+ } else if (0 == strncmp("watch", str.ptr, str.size)) {
821
+ if (ary.size != 2 ||
822
+ ary.ptr[1].type != MSGPACK_OBJECT_POSITIVE_INTEGER)
823
+ return;
815
824
 
816
- if (ary.size < 1 ||
817
- ary.ptr[0].type != MSGPACK_OBJECT_RAW)
818
- continue;
825
+ unsigned int msec = ary.ptr[1].via.u64;
826
+ rbtracer_watch(msec);
819
827
 
820
- str = ary.ptr[0].via.raw;
828
+ } else if (0 == strncmp("firehose", str.ptr, str.size)) {
829
+ rbtracer.firehose = true;
830
+ event_hook_install();
821
831
 
822
- if (0 == strncmp("attach", str.ptr, str.size)) {
823
- if (ary.size != 2 ||
824
- ary.ptr[1].type != MSGPACK_OBJECT_POSITIVE_INTEGER)
825
- continue;
832
+ } else if (0 == strncmp("add", str.ptr, str.size)) {
833
+ if (ary.size != 2 ||
834
+ ary.ptr[1].type != MSGPACK_OBJECT_RAW)
835
+ return;
826
836
 
827
- pid_t pid = (pid_t) ary.ptr[1].via.u64;
837
+ str = ary.ptr[1].via.raw;
828
838
 
829
- if (pid && rbtracer.attached_pid == 0)
830
- rbtracer.attached_pid = pid;
839
+ strncpy(query, str.ptr, str.size);
840
+ query[str.size] = 0;
841
+ last_tracer_id = rbtracer_add(query);
831
842
 
832
- rbtrace__send_event(1,
833
- "attached",
834
- 'u', (uint32_t) rbtracer.attached_pid
835
- );
843
+ } else if (0 == strncmp("addexpr", str.ptr, str.size)) {
844
+ if (ary.size != 2 ||
845
+ ary.ptr[1].type != MSGPACK_OBJECT_RAW)
846
+ return;
836
847
 
837
- } else if (0 == strncmp("detach", str.ptr, str.size)) {
838
- if (rbtracer.attached_pid) {
839
- rbtrace__send_event(1,
840
- "detached",
841
- 'u', (uint32_t) rbtracer.attached_pid
848
+ str = ary.ptr[1].via.raw;
849
+
850
+ strncpy(query, str.ptr, str.size);
851
+ query[str.size] = 0;
852
+ rbtracer_add_expr(last_tracer_id, query);
853
+
854
+ } else if (0 == strncmp("gc", str.ptr, str.size)) {
855
+ rbtracer.gc = true;
856
+
857
+ } else if (0 == strncmp("devmode", str.ptr, str.size)) {
858
+ rbtracer.devmode = true;
859
+
860
+ } else if (0 == strncmp("fork", str.ptr, str.size)) {
861
+ pid_t outer = fork();
862
+
863
+ if (outer == 0) {
864
+ rb_eval_string_protect("$0 = \"[DEBUG] #{Process.ppid}\"", 0);
865
+ setpgrp();
866
+ pid_t inner = fork();
867
+
868
+ if (inner == 0) {
869
+ // a ruby process will never have more than 20k
870
+ // open file descriptors, right?
871
+ int fd;
872
+ for (fd=3; fd<20000; fd++)
873
+ close(fd);
874
+
875
+ // busy loop
876
+ while (1) sleep(1);
877
+
878
+ // don't return to ruby
879
+ _exit(0);
880
+ }
881
+
882
+ rbtrace__send_event(1,
883
+ "forked",
884
+ inner == -1 ? 'b' : 'u',
885
+ inner == -1 ? false : (uint32_t) inner
842
886
  );
843
- }
844
887
 
845
- rbtracer_detach();
888
+ // kill off outer fork
889
+ _exit(0);
890
+ }
846
891
 
847
- } else if (0 == strncmp("watch", str.ptr, str.size)) {
848
- if (ary.size != 2 ||
849
- ary.ptr[1].type != MSGPACK_OBJECT_POSITIVE_INTEGER)
850
- continue;
892
+ if (outer != -1) {
893
+ waitpid(outer, NULL, 0);
894
+ }
851
895
 
852
- unsigned int msec = ary.ptr[1].via.u64;
853
- rbtracer_watch(msec);
896
+ } else if (0 == strncmp("eval", str.ptr, str.size)) {
897
+ if (ary.size != 2 ||
898
+ ary.ptr[1].type != MSGPACK_OBJECT_RAW)
899
+ return;
854
900
 
855
- } else if (0 == strncmp("firehose", str.ptr, str.size)) {
856
- rbtracer.firehose = true;
857
- event_hook_install();
901
+ str = ary.ptr[1].via.raw;
858
902
 
859
- } else if (0 == strncmp("add", str.ptr, str.size)) {
860
- if (ary.size != 2 ||
861
- ary.ptr[1].type != MSGPACK_OBJECT_RAW)
862
- continue;
903
+ strncpy(query, str.ptr, str.size);
904
+ query[str.size] = 0;
863
905
 
864
- str = ary.ptr[1].via.raw;
906
+ snprintf(code, BUF_SIZE+150, "(begin; %s; rescue Exception => e; e; end).inspect", query);
907
+ val = rb_eval_string_protect(code, 0);
865
908
 
866
- strncpy(query, str.ptr, str.size);
867
- query[str.size] = 0;
868
- last_tracer_id = rbtracer_add(query);
909
+ if (TYPE(val) == T_STRING) {
910
+ rbtrace__send_event(1,
911
+ "evaled",
912
+ 's', RSTRING_PTR(val)
913
+ );
914
+ }
869
915
 
870
- } else if (0 == strncmp("addexpr", str.ptr, str.size)) {
871
- if (ary.size != 2 ||
872
- ary.ptr[1].type != MSGPACK_OBJECT_RAW)
873
- continue;
916
+ }
917
+ }
874
918
 
875
- str = ary.ptr[1].via.raw;
919
+ static void
920
+ sigurg(int signal)
921
+ {
922
+ msgq_setup();
923
+ if (rbtracer.mqi_id == -1) return;
876
924
 
877
- strncpy(query, str.ptr, str.size);
878
- query[str.size] = 0;
879
- rbtracer_add_expr(last_tracer_id, query);
925
+ #ifdef HAVE_RB_DURING_GC
926
+ if (rb_during_gc()) {
927
+ rbtrace__send_event(0, "during_gc");
928
+ return;
929
+ }
930
+ #endif
880
931
 
881
- } else if (0 == strncmp("gc", str.ptr, str.size)) {
882
- rbtracer.gc = true;
932
+ event_msg_t msg;
933
+ int n = 0;
883
934
 
884
- } else if (0 == strncmp("devmode", str.ptr, str.size)) {
885
- rbtracer.devmode = true;
935
+ while (true) {
936
+ int ret = -1;
886
937
 
887
- }
938
+ for (n=0; n<10 && ret==-1; n++)
939
+ ret = msgrcv(rbtracer.mqi_id, &msg, sizeof(msg)-sizeof(long), 0, IPC_NOWAIT);
940
+
941
+ if (ret == -1) {
942
+ break;
943
+ } else {
944
+ msgpack_unpacked unpacked;
945
+ msgpack_unpacked_init(&unpacked);
946
+
947
+ bool success = msgpack_unpack_next(&unpacked, msg.buf, sizeof(msg.buf), NULL);
948
+ if (!success) continue;
949
+
950
+ rbtrace__process_event(unpacked.data);
888
951
  }
889
952
  }
890
953
  }
data/rbtrace.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'rbtrace'
3
- s.version = '0.3.5'
3
+ s.version = '0.3.6'
4
4
  s.homepage = 'http://github.com/tmm1/rbtrace'
5
5
 
6
6
  s.authors = 'Aman Gupta'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbtrace
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 5
10
- version: 0.3.5
9
+ - 6
10
+ version: 0.3.6
11
11
  platform: ruby
12
12
  authors:
13
13
  - Aman Gupta
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-02-21 00:00:00 -08:00
18
+ date: 2011-02-22 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency