Skip to content

Commit 2e2e9fc

Browse files
committed
Best effort thread naming based on inspect output
1 parent 4f1d583 commit 2e2e9fc

File tree

3 files changed

+84
-16
lines changed

3 files changed

+84
-16
lines changed

ext/vernier/vernier.cc

+4-15
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,6 @@ class Thread {
859859
markers->record(Marker::Type::MARKER_GVL_THREAD_EXITED);
860860

861861
stopped_at = now;
862-
capture_name();
863862

864863
break;
865864
}
@@ -872,13 +871,6 @@ class Thread {
872871
return state != State::STOPPED;
873872
}
874873

875-
void capture_name() {
876-
//char buf[128];
877-
//int rc = pthread_getname_np(pthread_id, buf, sizeof(buf));
878-
//if (rc == 0)
879-
// name = std::string(buf);
880-
}
881-
882874
void mark() {
883875
}
884876
};
@@ -945,8 +937,12 @@ class ThreadTable {
945937
thread.set_state(new_state);
946938

947939
if (thread.state == Thread::State::RUNNING) {
940+
// rb_inspect should be safe here, as RUNNING should correspond to RESUMED hook from internal_thread_event_cb
941+
// which is called with GVL per https://github.com/ruby/ruby/blob/v3_3_0/include/ruby/thread.h#L247-L248
942+
VALUE thread_str = rb_inspect(th);
948943
thread.pthread_id = pthread_self();
949944
thread.native_tid = get_native_thread_id();
945+
thread.name = StringValueCStr(thread_str);
950946
} else {
951947
thread.pthread_id = 0;
952948
thread.native_tid = 0;
@@ -1496,13 +1492,6 @@ class TimeCollector : public BaseCollector {
14961492
rb_remove_event_hook(internal_gc_event_cb);
14971493
rb_remove_event_hook(internal_thread_event_cb);
14981494

1499-
// capture thread names
1500-
for (auto& thread: this->threads.list) {
1501-
if (thread.running()) {
1502-
thread.capture_name();
1503-
}
1504-
}
1505-
15061495
frame_list.finalize();
15071496

15081497
VALUE result = build_collector_result();

lib/vernier/output/firefox.rb

+15-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def initialize(ruby_thread_id, profile, categorizer, name:, tid:, samples:, weig
163163
@profile = profile
164164
@categorizer = categorizer
165165
@tid = tid
166-
@name = name
166+
@name = pretty_name(name)
167167

168168
timestamps ||= [0] * samples.size
169169
@samples, @weights, @timestamps = samples, weights, timestamps
@@ -385,6 +385,20 @@ def string_table
385385

386386
private
387387

388+
def pretty_name(name)
389+
if name.empty?
390+
tr = ObjectSpace._id2ref(@ruby_thread_id)
391+
name = tr.inspect if tr
392+
end
393+
return name unless name.start_with?("#<Thread")
394+
pretty = []
395+
obj_address = name[/Thread:(0x\d+)/,1]
396+
best_id = name[/\#<Thread:0x\w+@?\s?(.*)\s+\S+>/,1]
397+
pretty << best_id unless best_id.empty?
398+
pretty << "(#{obj_address})"
399+
pretty.join(' ')
400+
end
401+
388402
def gc_category
389403
@categorizer.get_category("GC")
390404
end

test/output/test_firefox.rb

+65
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,69 @@ def test_custom_intervals
151151
markers = JSON.parse(output)["threads"].flat_map { _1["markers"]["data"] }
152152
assert_includes markers, {"type"=>"UserTiming", "entryType"=>"measure", "name"=>"custom"}
153153
end
154+
155+
156+
def test_thread_names
157+
orig_name = Thread.current.name
158+
th1_loc, th2_loc = nil
159+
160+
# Case with just the named, main thread and no location
161+
result = Vernier.trace do
162+
Thread.current.name="main"
163+
end
164+
165+
output = Vernier::Output::Firefox.new(result).output
166+
assert_valid_firefox_profile(output)
167+
168+
data = JSON.parse(output)
169+
threads = data["threads"]
170+
assert_equal 1, threads.size
171+
assert_match /^main \(0x\w+\)/, threads.first["name"]
172+
173+
# Case with unnamed thread and location
174+
result = Vernier.trace do
175+
th1 = Thread.new { th1_loc = file_lineno; sleep 0.01 }
176+
th1.join
177+
end
178+
179+
output = Vernier::Output::Firefox.new(result).output
180+
assert_valid_firefox_profile(output)
181+
182+
data = JSON.parse(output)
183+
threads = data["threads"]
184+
assert_equal 2, threads.size
185+
186+
threads.each do |tr|
187+
next if tr["isMainThread"]
188+
assert_match /^#{th1_loc} \(0x\w+\)/, tr["name"]
189+
end
190+
191+
# Case with named thread and location
192+
result = Vernier.trace do
193+
th2 = Thread.new { th2_loc = file_lineno; sleep 0.01 }
194+
th2.name = "named thread"
195+
th2.join
196+
end
197+
198+
output = Vernier::Output::Firefox.new(result).output
199+
assert_valid_firefox_profile(output)
200+
201+
data = JSON.parse(output)
202+
threads = data["threads"]
203+
assert_equal 2, threads.size
204+
205+
threads.each do |tr|
206+
next if tr["isMainThread"]
207+
assert_match /^named thread #{th2_loc} \(0x\w+\)/, tr["name"]
208+
end
209+
210+
ensure
211+
Thread.current.name = orig_name
212+
end
213+
214+
private
215+
216+
def file_lineno
217+
caller_locations(1, 1).first.yield_self{|loc| "#{loc.path}:#{loc.lineno}"}
218+
end
154219
end

0 commit comments

Comments
 (0)