ruby-prof 1.4.5 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -40,7 +40,7 @@ VALUE resolve_klass(VALUE klass, unsigned int* klass_flags)
40
40
  *klass_flags |= kObjectSingleton;
41
41
  result = rb_class_superclass(klass);
42
42
  }
43
- /* Ok, this could be other things like an array made put onto
43
+ /* Ok, this could be other things like an array put onto
44
44
  a singleton object (yeah, it happens, see the singleton
45
45
  objects test case). */
46
46
  else
@@ -97,7 +97,12 @@ st_data_t method_key(VALUE klass, VALUE msym)
97
97
  resolved_klass = RBASIC(klass)->klass;
98
98
  }
99
99
 
100
- return (resolved_klass << 4) + (msym);
100
+ st_data_t hash = rb_hash_start(0);
101
+ hash = rb_hash_uint(hash, resolved_klass);
102
+ hash = rb_hash_uint(hash, msym);
103
+ hash = rb_hash_end(hash);
104
+
105
+ return hash;
101
106
  }
102
107
 
103
108
  /* ====== Allocation Table ====== */
@@ -309,6 +314,45 @@ created. RubyProf::MethodInfo objects can be accessed via
309
314
  the RubyProf::Profile object.
310
315
  */
311
316
 
317
+ /* call-seq:
318
+ new(klass, method_name) -> method_info
319
+
320
+ Creates a new MethodInfo instance. +Klass+ should be a reference to
321
+ a Ruby class and +method_name+ a symbol identifying one of its instance methods.*/
322
+ static VALUE prof_method_initialize(VALUE self, VALUE klass, VALUE method_name)
323
+ {
324
+ prof_method_t* method_ptr = prof_get_method(self);
325
+ method_ptr->klass = klass;
326
+ method_ptr->method_name = method_name;
327
+
328
+ // Setup method key
329
+ method_ptr->key = method_key(klass, method_name);
330
+
331
+ // Get method object
332
+ VALUE ruby_method = rb_funcall(klass, rb_intern("instance_method"), 1, method_name);
333
+
334
+ // Get source file and line number
335
+ VALUE location_array = rb_funcall(ruby_method, rb_intern("source_location"), 0);
336
+ if (location_array != Qnil && RARRAY_LEN(location_array) == 2)
337
+ {
338
+ method_ptr->source_file = rb_ary_entry(location_array, 0);
339
+ method_ptr->source_line = NUM2INT(rb_ary_entry(location_array, 1));
340
+ }
341
+
342
+ return self;
343
+ }
344
+
345
+ /* call-seq:
346
+ hash -> hash
347
+
348
+ Returns the hash key for this method info. The hash key is calculated based on the
349
+ klass name and method name */
350
+ static VALUE prof_method_hash(VALUE self)
351
+ {
352
+ prof_method_t* method_ptr = prof_get_method(self);
353
+ return ULL2NUM(method_ptr->key);
354
+ }
355
+
312
356
  /* call-seq:
313
357
  allocations -> array
314
358
 
@@ -334,7 +378,7 @@ static VALUE prof_method_measurement(VALUE self)
334
378
  /* call-seq:
335
379
  source_file => string
336
380
 
337
- return the source file of the method
381
+ Returns the source file of the method
338
382
  */
339
383
  static VALUE prof_method_source_file(VALUE self)
340
384
  {
@@ -420,7 +464,7 @@ static VALUE prof_method_dump(VALUE self)
420
464
  rb_hash_aset(result, ID2SYM(rb_intern("klass_flags")), INT2FIX(method_data->klass_flags));
421
465
  rb_hash_aset(result, ID2SYM(rb_intern("method_name")), method_data->method_name);
422
466
 
423
- rb_hash_aset(result, ID2SYM(rb_intern("key")), INT2FIX(method_data->key));
467
+ rb_hash_aset(result, ID2SYM(rb_intern("key")), ULL2NUM(method_data->key));
424
468
  rb_hash_aset(result, ID2SYM(rb_intern("recursive")), prof_method_recursive(self));
425
469
  rb_hash_aset(result, ID2SYM(rb_intern("source_file")), method_data->source_file);
426
470
  rb_hash_aset(result, ID2SYM(rb_intern("source_line")), INT2FIX(method_data->source_line));
@@ -441,7 +485,7 @@ static VALUE prof_method_load(VALUE self, VALUE data)
441
485
  method_data->klass_name = rb_hash_aref(data, ID2SYM(rb_intern("klass_name")));
442
486
  method_data->klass_flags = FIX2INT(rb_hash_aref(data, ID2SYM(rb_intern("klass_flags"))));
443
487
  method_data->method_name = rb_hash_aref(data, ID2SYM(rb_intern("method_name")));
444
- method_data->key = FIX2LONG(rb_hash_aref(data, ID2SYM(rb_intern("key"))));
488
+ method_data->key = RB_NUM2ULL(rb_hash_aref(data, ID2SYM(rb_intern("key"))));
445
489
 
446
490
  method_data->recursive = rb_hash_aref(data, ID2SYM(rb_intern("recursive"))) == Qtrue ? true : false;
447
491
 
@@ -469,8 +513,16 @@ void rp_init_method_info()
469
513
  {
470
514
  /* MethodInfo */
471
515
  cRpMethodInfo = rb_define_class_under(mProf, "MethodInfo", rb_cObject);
472
- rb_undef_method(CLASS_OF(cRpMethodInfo), "new");
516
+
517
+ rb_define_const(cRpMethodInfo, "MODULE_INCLUDEE", INT2NUM(kModuleIncludee));
518
+ rb_define_const(cRpMethodInfo, "CLASS_SINGLETON", INT2NUM(kClassSingleton));
519
+ rb_define_const(cRpMethodInfo, "MODULE_SINGLETON", INT2NUM(kModuleSingleton));
520
+ rb_define_const(cRpMethodInfo, "OBJECT_SINGLETON", INT2NUM(kObjectSingleton));
521
+ rb_define_const(cRpMethodInfo, "OTHER_SINGLETON", INT2NUM(kOtherSingleton));
522
+
473
523
  rb_define_alloc_func(cRpMethodInfo, prof_method_allocate);
524
+ rb_define_method(cRpMethodInfo, "initialize", prof_method_initialize, 2);
525
+ rb_define_method(cRpMethodInfo, "hash", prof_method_hash, 0);
474
526
 
475
527
  rb_define_method(cRpMethodInfo, "klass_name", prof_method_klass_name, 0);
476
528
  rb_define_method(cRpMethodInfo, "klass_flags", prof_method_klass_flags, 0);
@@ -15,7 +15,7 @@ enum {
15
15
  kClassSingleton = 0x2, // Singleton of a class
16
16
  kModuleSingleton = 0x4, // Singleton of a module
17
17
  kObjectSingleton = 0x8, // Singleton of an object
18
- kOtherSingleton = 0x10 // Singleton of unkown object
18
+ kOtherSingleton = 0x10 // Singleton of unknown object
19
19
  };
20
20
 
21
21
  // Profiling information for each method.
@@ -779,6 +779,36 @@ static VALUE prof_threads(VALUE self)
779
779
  return result;
780
780
  }
781
781
 
782
+ /* call-seq:
783
+ add_thread(thread) -> thread
784
+
785
+ Adds the specified RubyProf thread to the profile. */
786
+ static VALUE prof_add_thread(VALUE self, VALUE thread)
787
+ {
788
+ prof_profile_t* profile_ptr = prof_get_profile(self);
789
+
790
+ // This thread is now going to be owned by C
791
+ thread_data_t* thread_ptr = prof_get_thread(thread);
792
+ thread_ptr->owner = OWNER_C;
793
+
794
+ rb_st_insert(profile_ptr->threads_tbl, thread_ptr->fiber_id, (st_data_t)thread_ptr);
795
+ return thread;
796
+ }
797
+
798
+ /* call-seq:
799
+ remove_thread(thread) -> thread
800
+
801
+ Removes the specified thread from the profile. This is used to remove threads
802
+ after they have been merged togher. Retuns the removed thread. */
803
+ static VALUE prof_remove_thread(VALUE self, VALUE thread)
804
+ {
805
+ prof_profile_t* profile_ptr = prof_get_profile(self);
806
+ thread_data_t* thread_ptr = prof_get_thread(thread);
807
+ VALUE fiber_id = thread_ptr->fiber_id;
808
+ rb_st_delete(profile_ptr->threads_tbl, (st_data_t*)&fiber_id, NULL);
809
+ return thread;
810
+ }
811
+
782
812
  /* Document-method: RubyProf::Profile#Profile
783
813
  call-seq:
784
814
  profile(&block) -> self
@@ -897,19 +927,22 @@ void rp_init_profile(void)
897
927
 
898
928
  rb_define_singleton_method(cProfile, "profile", prof_profile_class, -1);
899
929
  rb_define_method(cProfile, "initialize", prof_initialize, -1);
930
+ rb_define_method(cProfile, "profile", prof_profile_object, 0);
900
931
  rb_define_method(cProfile, "start", prof_start, 0);
901
932
  rb_define_method(cProfile, "stop", prof_stop, 0);
902
933
  rb_define_method(cProfile, "resume", prof_resume, 0);
903
934
  rb_define_method(cProfile, "pause", prof_pause, 0);
904
935
  rb_define_method(cProfile, "running?", prof_running, 0);
905
936
  rb_define_method(cProfile, "paused?", prof_paused, 0);
906
- rb_define_method(cProfile, "threads", prof_threads, 0);
907
- rb_define_method(cProfile, "exclude_method!", prof_exclude_method, 2);
908
- rb_define_method(cProfile, "profile", prof_profile_object, 0);
909
937
 
938
+ rb_define_method(cProfile, "exclude_method!", prof_exclude_method, 2);
910
939
  rb_define_method(cProfile, "measure_mode", prof_profile_measure_mode, 0);
911
940
  rb_define_method(cProfile, "track_allocations?", prof_profile_track_allocations, 0);
912
941
 
942
+ rb_define_method(cProfile, "threads", prof_threads, 0);
943
+ rb_define_method(cProfile, "add_thread", prof_add_thread, 1);
944
+ rb_define_method(cProfile, "remove_thread", prof_remove_thread, 1);
945
+
913
946
  rb_define_method(cProfile, "_dump_data", prof_profile_dump, 0);
914
947
  rb_define_method(cProfile, "_load_data", prof_profile_load, 1);
915
948
  }
@@ -25,6 +25,7 @@ VALUE cRpThread;
25
25
  thread_data_t* thread_data_create(void)
26
26
  {
27
27
  thread_data_t* result = ALLOC(thread_data_t);
28
+ result->owner = OWNER_C;
28
29
  result->stack = prof_stack_create();
29
30
  result->method_table = method_table_create();
30
31
  result->call_tree = NULL;
@@ -76,15 +77,6 @@ void prof_thread_mark(void* data)
76
77
  rb_st_foreach(thread->method_table, mark_methods, 0);
77
78
  }
78
79
 
79
- void prof_thread_ruby_gc_free(void* data)
80
- {
81
- if (data)
82
- {
83
- thread_data_t* thread_data = (thread_data_t*)data;
84
- thread_data->object = Qnil;
85
- }
86
- }
87
-
88
80
  static void prof_thread_free(thread_data_t* thread_data)
89
81
  {
90
82
  /* Has this method object been accessed by Ruby? If
@@ -105,6 +97,27 @@ static void prof_thread_free(thread_data_t* thread_data)
105
97
  xfree(thread_data);
106
98
  }
107
99
 
100
+ void prof_thread_ruby_gc_free(void* data)
101
+ {
102
+ thread_data_t* thread_data = (thread_data_t*)data;
103
+
104
+ if (!thread_data)
105
+ {
106
+ // Object has already been freed by C code
107
+ return;
108
+ }
109
+ else if (thread_data->owner == OWNER_RUBY)
110
+ {
111
+ // Ruby owns this object, we need to free the underlying C struct
112
+ prof_thread_free(thread_data);
113
+ }
114
+ else
115
+ {
116
+ // The Ruby object is being freed, but not the underlying C structure. So unlink the two.
117
+ thread_data->object = Qnil;
118
+ }
119
+ }
120
+
108
121
  static const rb_data_type_t thread_type =
109
122
  {
110
123
  .wrap_struct_name = "ThreadInfo",
@@ -130,6 +143,7 @@ VALUE prof_thread_wrap(thread_data_t* thread)
130
143
  static VALUE prof_thread_allocate(VALUE klass)
131
144
  {
132
145
  thread_data_t* thread_data = thread_data_create();
146
+ thread_data->owner = OWNER_RUBY;
133
147
  thread_data->object = prof_thread_wrap(thread_data);
134
148
  return thread_data->object;
135
149
  }
@@ -171,7 +185,8 @@ thread_data_t* threads_table_lookup(void* prof, VALUE fiber)
171
185
  thread_data_t* result = NULL;
172
186
  st_data_t val;
173
187
 
174
- if (rb_st_lookup(profile->threads_tbl, fiber, &val))
188
+ VALUE fiber_id = rb_obj_id(fiber);
189
+ if (rb_st_lookup(profile->threads_tbl, fiber_id, &val))
175
190
  {
176
191
  result = (thread_data_t*)val;
177
192
  }
@@ -188,7 +203,7 @@ thread_data_t* threads_table_insert(void* prof, VALUE fiber)
188
203
  result->fiber = fiber;
189
204
  result->fiber_id = rb_obj_id(fiber);
190
205
  result->thread_id = rb_obj_id(thread);
191
- rb_st_insert(profile->threads_tbl, (st_data_t)fiber, (st_data_t)result);
206
+ rb_st_insert(profile->threads_tbl, (st_data_t)result->fiber_id, (st_data_t)result);
192
207
 
193
208
  // Are we tracing this thread?
194
209
  if (profile->include_threads_tbl && !rb_st_lookup(profile->include_threads_tbl, thread, 0))
@@ -267,6 +282,26 @@ static int collect_methods(st_data_t key, st_data_t value, st_data_t result)
267
282
  }
268
283
 
269
284
  // ====== RubyProf::Thread ======
285
+ /* call-seq:
286
+ new(call_tree, thread, fiber) -> thread
287
+
288
+ Creates a new RubyProf thread instance. +call_tree+ is the root call_tree instance,
289
+ +thread+ is a reference to a Ruby thread and +fiber+ is a reference to a Ruby fiber.*/
290
+ static VALUE prof_thread_initialize(VALUE self, VALUE call_tree, VALUE thread, VALUE fiber)
291
+ {
292
+ thread_data_t* thread_ptr = prof_get_thread(self);
293
+
294
+ // This call tree must now be managed by C
295
+ thread_ptr->call_tree = prof_get_call_tree(call_tree);
296
+ thread_ptr->call_tree->owner = OWNER_C;
297
+
298
+ thread_ptr->fiber = fiber;
299
+ thread_ptr->fiber_id = rb_obj_id(fiber);
300
+ thread_ptr->thread_id = rb_obj_id(thread);
301
+
302
+ return self;
303
+ }
304
+
270
305
  /* call-seq:
271
306
  id -> number
272
307
 
@@ -290,7 +325,7 @@ static VALUE prof_fiber_id(VALUE self)
290
325
  /* call-seq:
291
326
  call_tree -> CallTree
292
327
 
293
- Returns the root of the call tree. */
328
+ Returns the root call tree. */
294
329
  static VALUE prof_call_tree(VALUE self)
295
330
  {
296
331
  thread_data_t* thread = prof_get_thread(self);
@@ -313,12 +348,22 @@ static VALUE prof_thread_methods(VALUE self)
313
348
  return thread->methods;
314
349
  }
315
350
 
351
+ static VALUE prof_thread_merge(VALUE self, VALUE other)
352
+ {
353
+ thread_data_t* self_ptr = prof_get_thread(self);
354
+ thread_data_t* other_ptr = prof_get_thread(other);
355
+ prof_call_tree_merge_internal(self_ptr->call_tree, other_ptr->call_tree);
356
+
357
+ return other;
358
+ }
359
+
316
360
  /* :nodoc: */
317
361
  static VALUE prof_thread_dump(VALUE self)
318
362
  {
319
363
  thread_data_t* thread_data = RTYPEDDATA_DATA(self);
320
364
 
321
365
  VALUE result = rb_hash_new();
366
+ rb_hash_aset(result, ID2SYM(rb_intern("owner")), INT2FIX(thread_data->owner));
322
367
  rb_hash_aset(result, ID2SYM(rb_intern("fiber_id")), thread_data->fiber_id);
323
368
  rb_hash_aset(result, ID2SYM(rb_intern("methods")), prof_thread_methods(self));
324
369
  rb_hash_aset(result, ID2SYM(rb_intern("call_tree")), prof_call_tree(self));
@@ -331,6 +376,8 @@ static VALUE prof_thread_load(VALUE self, VALUE data)
331
376
  {
332
377
  thread_data_t* thread_data = RTYPEDDATA_DATA(self);
333
378
 
379
+ thread_data->owner = FIX2INT(rb_hash_aref(data, ID2SYM(rb_intern("owner"))));
380
+
334
381
  VALUE call_tree = rb_hash_aref(data, ID2SYM(rb_intern("call_tree")));
335
382
  thread_data->call_tree = prof_get_call_tree(call_tree);
336
383
 
@@ -350,13 +397,14 @@ static VALUE prof_thread_load(VALUE self, VALUE data)
350
397
  void rp_init_thread(void)
351
398
  {
352
399
  cRpThread = rb_define_class_under(mProf, "Thread", rb_cObject);
353
- rb_undef_method(CLASS_OF(cRpThread), "new");
354
400
  rb_define_alloc_func(cRpThread, prof_thread_allocate);
401
+ rb_define_method(cRpThread, "initialize", prof_thread_initialize, 3);
355
402
 
356
403
  rb_define_method(cRpThread, "id", prof_thread_id, 0);
357
404
  rb_define_method(cRpThread, "call_tree", prof_call_tree, 0);
358
405
  rb_define_method(cRpThread, "fiber_id", prof_fiber_id, 0);
359
406
  rb_define_method(cRpThread, "methods", prof_thread_methods, 0);
407
+ rb_define_method(cRpThread, "merge!", prof_thread_merge, 1);
360
408
  rb_define_method(cRpThread, "_dump_data", prof_thread_dump, 0);
361
409
  rb_define_method(cRpThread, "_load_data", prof_thread_load, 1);
362
410
  }
@@ -10,7 +10,7 @@
10
10
  /* Profiling information for a thread. */
11
11
  typedef struct thread_data_t
12
12
  {
13
- // Runtime
13
+ prof_owner_t owner; /* Who owns this object */
14
14
  VALUE object; /* Cache to wrapped object */
15
15
  VALUE fiber; /* Fiber */
16
16
  prof_stack_t* stack; /* Stack of frames */
@@ -29,7 +29,6 @@
29
29
  #include "rp_measurement.h"
30
30
  #include "rp_method.h"
31
31
  #include "rp_call_tree.h"
32
- #include "rp_aggregate_call_tree.h"
33
32
  #include "rp_call_trees.h"
34
33
  #include "rp_profile.h"
35
34
  #include "rp_stack.h"
@@ -43,7 +42,6 @@ void Init_ruby_prof()
43
42
 
44
43
  rp_init_allocation();
45
44
  rp_init_call_tree();
46
- rp_init_aggregate_call_tree();
47
45
  rp_init_call_trees();
48
46
  rp_init_measure();
49
47
  rp_init_method_info();
@@ -23,4 +23,12 @@ extern VALUE mProf;
23
23
  // This method is not exposed in Ruby header files - at least not as of Ruby 2.6.3 :(
24
24
  extern size_t rb_obj_memsize_of(VALUE);
25
25
 
26
+ typedef enum
27
+ {
28
+ OWNER_UNKNOWN = 0,
29
+ OWNER_RUBY = 1,
30
+ OWNER_C = 2
31
+ } prof_owner_t;
32
+
33
+
26
34
  #endif //__RUBY_PROF_H__
@@ -66,7 +66,7 @@
66
66
  </PropertyGroup>
67
67
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
68
68
  <TargetExt>.so</TargetExt>
69
- <OutDir>$(SolutionDir)\..</OutDir>
69
+ <OutDir>$(SolutionDir)\..\..\..\lib\</OutDir>
70
70
  </PropertyGroup>
71
71
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
72
72
  <ClCompile>
@@ -104,14 +104,14 @@
104
104
  </ItemDefinitionGroup>
105
105
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
106
106
  <ClCompile>
107
- <AdditionalIncludeDirectories>C:\msys64\usr\local\ruby-3.1.2-vc\include\ruby-3.1.0\x64-mswin64_140;C:\msys64\usr\local\ruby-3.1.2-vc\include\ruby-3.1.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
107
+ <AdditionalIncludeDirectories>C:\msys64\usr\local\ruby-3.2.0-vc\include\ruby-3.2.0\x64-mswin64_140;C:\msys64\usr\local\ruby-3.2.0-vc\include\ruby-3.2.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
108
108
  <Optimization>Disabled</Optimization>
109
109
  <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
110
110
  <WarningLevel>Level3</WarningLevel>
111
111
  </ClCompile>
112
112
  <Link>
113
- <AdditionalLibraryDirectories>C:\msys64\usr\local\ruby-3.1.2-vc\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
114
- <AdditionalDependencies>x64-vcruntime140-ruby310.lib;%(AdditionalDependencies)</AdditionalDependencies>
113
+ <AdditionalLibraryDirectories>C:\msys64\usr\local\ruby-3.2.0-vc\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
114
+ <AdditionalDependencies>x64-vcruntime140-ruby320.lib;%(AdditionalDependencies)</AdditionalDependencies>
115
115
  <ModuleDefinitionFile>ruby_prof.def</ModuleDefinitionFile>
116
116
  <SubSystem>Console</SubSystem>
117
117
  </Link>
@@ -127,7 +127,6 @@
127
127
  </Link>
128
128
  </ItemDefinitionGroup>
129
129
  <ItemGroup>
130
- <ClInclude Include="..\rp_aggregate_call_tree.h" />
131
130
  <ClInclude Include="..\rp_allocation.h" />
132
131
  <ClInclude Include="..\rp_call_tree.h" />
133
132
  <ClInclude Include="..\rp_call_trees.h" />
@@ -139,7 +138,6 @@
139
138
  <ClInclude Include="..\ruby_prof.h" />
140
139
  </ItemGroup>
141
140
  <ItemGroup>
142
- <ClCompile Include="..\rp_aggregate_call_tree.c" />
143
141
  <ClCompile Include="..\rp_allocation.c" />
144
142
  <ClCompile Include="..\rp_call_tree.c" />
145
143
  <ClCompile Include="..\rp_call_trees.c" />
@@ -52,7 +52,14 @@ module RubyProf
52
52
  self.total_time - self.self_time - self.wait_time
53
53
  end
54
54
 
55
- # :enddoc:
55
+ def eql?(other)
56
+ self.hash == other.hash
57
+ end
58
+
59
+ def ==(other)
60
+ self.eql?(other)
61
+ end
62
+
56
63
  def <=>(other)
57
64
  if other.nil?
58
65
  -1
@@ -68,8 +68,6 @@ module RubyProf
68
68
 
69
69
  def print_threads
70
70
  remove_subsidiary_files_from_previous_profile_runs
71
- # TODO: merge fibers of a given thread here, instead of relying
72
- # on the profiler to merge fibers.
73
71
  @result.threads.each do |thread|
74
72
  print_thread(thread)
75
73
  end
@@ -4,7 +4,6 @@ require 'ruby-prof/exclude_common_methods'
4
4
 
5
5
  module RubyProf
6
6
  class Profile
7
- # :nodoc:
8
7
  def measure_mode_string
9
8
  case self.measure_mode
10
9
  when WALL_TIME
@@ -33,5 +32,39 @@ module RubyProf
33
32
  def exclude_singleton_methods!(mod, *method_or_methods)
34
33
  exclude_methods!(mod.singleton_class, *method_or_methods)
35
34
  end
35
+
36
+ # call-seq:
37
+ # merge! -> self
38
+ #
39
+ # Merges RubyProf threads whose root call_trees reference the same target method. This is useful
40
+ # when profiling code that uses a main thread/fiber to distribute work to multiple workers.
41
+ # If there are tens or hundreds of workers, viewing results per worker thread/fiber can be
42
+ # overwhelming. Using +merge!+ will combine the worker times together into one result.
43
+ #
44
+ # Note the reported time will be much greater than the actual wall time. For example, if there
45
+ # are 10 workers that each run for 5 seconds, merged results will show one thread that
46
+ # ran for 50 seconds.
47
+ #
48
+ def merge!
49
+ # First group threads by their root call tree target (method). If the methods are
50
+ # different than there is nothing to merge
51
+ grouped = threads.group_by do |thread|
52
+ thread.call_tree.target
53
+ end
54
+
55
+ # For each target, get the first thread. Then loop over the remaining threads,
56
+ # and merge them into the first one and ten delete them. So we will be left with
57
+ # one thread per target.
58
+ grouped.each do |target, threads|
59
+ thread = threads.shift
60
+ threads.each do |other_thread|
61
+ thread.merge!(other_thread)
62
+ remove_thread(other_thread)
63
+ end
64
+ thread
65
+ end
66
+
67
+ self
68
+ end
36
69
  end
37
70
  end
@@ -1,3 +1,3 @@
1
1
  module RubyProf
2
- VERSION = "1.4.5"
2
+ VERSION = "1.5.0"
3
3
  end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('../test_helper', __FILE__)
4
+ require 'base64'
5
+
6
+ # Create a DummyClass with methods so we can create call trees in the test_merge method below
7
+ class DummyClass
8
+ %i[root a b aa ab ba bb].each do |method_name|
9
+ define_method(method_name) do
10
+ end
11
+ end
12
+ end
13
+
14
+ def create_call_tree(method_name)
15
+ method_info = RubyProf::MethodInfo.new(DummyClass, method_name)
16
+ RubyProf::CallTree.new(method_info)
17
+ end
18
+
19
+ def build_call_tree(tree_hash)
20
+ # tree_hash is a hash keyed on the parent method_name whose values are
21
+ # child methods. Example:
22
+ #
23
+ # tree_hash = {:root => [:a, :b],
24
+ # :a => [:aa, :ab],
25
+ # :b => [:bb]}
26
+ #
27
+ # Note this is a simplified structure for testing. It assumes methods
28
+ # are only called from one call_tree.
29
+
30
+ call_trees = Hash.new
31
+ tree_hash.each do |method_name, children|
32
+ parent = call_trees[method_name] ||= create_call_tree(method_name)
33
+ children.each do |child_method_name|
34
+ child = call_trees[child_method_name] ||= create_call_tree(child_method_name)
35
+ parent.add_child(child)
36
+ end
37
+ end
38
+
39
+ call_trees
40
+ end
41
+
42
+ def create_call_tree_1
43
+ #
44
+ # root
45
+ # / \
46
+ # a b
47
+ # / \ \
48
+ # aa ab bb
49
+ #
50
+
51
+ # ------ Call Trees 1 -------------
52
+ tree_hash = {:root => [:a, :b],
53
+ :a => [:aa, :ab],
54
+ :b => [:bb]}
55
+
56
+ call_trees = build_call_tree(tree_hash)
57
+
58
+ # Setup times
59
+ call_trees[:aa].measurement.total_time = 1.5
60
+ call_trees[:aa].measurement.self_time = 1.5
61
+ call_trees[:ab].measurement.total_time = 2.2
62
+ call_trees[:ab].measurement.self_time = 2.2
63
+ call_trees[:a].measurement.total_time = 3.7
64
+
65
+ call_trees[:aa].target.measurement.total_time = 1.5
66
+ call_trees[:aa].target.measurement.self_time = 1.5
67
+ call_trees[:ab].target.measurement.total_time = 2.2
68
+ call_trees[:ab].target.measurement.self_time = 2.2
69
+ call_trees[:a].target.measurement.total_time = 3.7
70
+
71
+ call_trees[:bb].measurement.total_time = 4.3
72
+ call_trees[:bb].measurement.self_time = 4.3
73
+ call_trees[:b].measurement.total_time = 4.3
74
+
75
+ call_trees[:bb].target.measurement.total_time = 4.3
76
+ call_trees[:bb].target.measurement.self_time = 4.3
77
+ call_trees[:b].target.measurement.total_time = 4.3
78
+
79
+ call_trees[:root].measurement.total_time = 8.0
80
+ call_trees[:root].target.measurement.total_time = 8.0
81
+
82
+ call_trees[:root]
83
+ end
84
+
85
+ def create_call_tree_2
86
+ #
87
+ # root
88
+ # / \
89
+ # a b
90
+ # \ / \
91
+ # ab ba bb
92
+
93
+ tree_hash = {:root => [:a, :b],
94
+ :a => [:ab],
95
+ :b => [:ba, :bb]}
96
+
97
+ call_trees = build_call_tree(tree_hash)
98
+
99
+ # Setup times
100
+ call_trees[:ab].measurement.total_time = 0.4
101
+ call_trees[:ab].measurement.self_time = 0.4
102
+ call_trees[:a].measurement.total_time = 0.4
103
+
104
+ call_trees[:ab].target.measurement.total_time = 0.4
105
+ call_trees[:ab].target.measurement.self_time = 0.4
106
+ call_trees[:a].target.measurement.total_time = 0.4
107
+
108
+ call_trees[:ba].measurement.total_time = 0.9
109
+ call_trees[:ba].measurement.self_time = 0.7
110
+ call_trees[:ba].measurement.wait_time = 0.2
111
+ call_trees[:bb].measurement.total_time = 2.3
112
+ call_trees[:bb].measurement.self_time = 2.3
113
+ call_trees[:b].measurement.total_time = 3.2
114
+
115
+ call_trees[:ba].target.measurement.total_time = 0.9
116
+ call_trees[:ba].target.measurement.self_time = 0.7
117
+ call_trees[:ba].target.measurement.wait_time = 0.2
118
+ call_trees[:bb].target.measurement.total_time = 2.3
119
+ call_trees[:bb].target.measurement.self_time = 2.3
120
+ call_trees[:b].target.measurement.total_time = 3.2
121
+
122
+ call_trees[:root].measurement.total_time = 3.6
123
+ call_trees[:root].target.measurement.total_time = 3.6
124
+
125
+ call_trees[:root]
126
+ end