Ruby มีระบบติดตาม (tracing) ในตัวที่คุณสามารถเข้าถึงได้ผ่านคลาส TracePoint สิ่งที่คุณติดตามได้รวมถึงการเรียกเมธอด การสร้างเธรดใหม่ และข้อยกเว้น (exceptions)
ทำไมต้องใช้ TracePoint?
มันมีประโยชน์ถ้าคุณอยากติดตามการทำงานของเมธอดใดเมธอดหนึ่ง คุณจะเห็นว่าเมธอดอื่นๆ ถูกเรียกอย่างไร และค่าที่คืนกลับมาคืออะไร
มาเริ่มดูตัวอย่างกัน!
การติดตามการเรียกเมธอด
ส่วนใหญ่คุณจะอยากให้ TracePoint ติดตามโค้ดแอปพลิเคชัน ไม่ใช่เมธอดในตัว (built-in methods) เช่น puts, size ฯลฯ
คุณทำได้โดยใช้ event call
ตัวอย่าง:
def the_method; other_method; end
def other_method; end
def start_trace
trace = TracePoint.new(:call) { |tp| p [tp.path, tp.lineno, tp.event, tp.method_id] }
trace.enable
yield
trace.disable
end
start_trace { the_method }
โค้ดนี้จะพิมพ์พาธไฟล์ หมายเลขบรรทัด ชื่อ event และชื่อเมธอด
["test.rb", 1, :call, :the_method]
["test.rb", 2, :call, :other_method]
ถ้าคุณไม่ระบุ event Ruby จะเรียกบล็อกของคุณสำหรับทุก event ทำให้เอาต์พุตเยอะขึ้น แนะนำให้โฟกัสที่ event เฉพาะเจาะจงเพื่อหาข้อมูลที่ต้องการเร็วขึ้น 🙂
นี่คือตาราง event ของ TracePoint:
| Event name | คำอธิบาย |
|---|---|
| call | เมธอดระดับแอปพลิเคชัน |
| c_call | เมธอดระดับ C (อย่าง puts) |
| return | การคืนค่าของเมธอด (สำหรับติดตามค่าที่คืนและความลึกของการเรียก) |
| b_call | การเรียกบล็อก |
| b_return | การคืนค่าของบล็อก |
| raise | มีข้อยกเว้นเกิดขึ้น |
| thread_begin | มีเธรดใหม่สร้างขึ้น |
| thread_end | เธรดสิ้นสุด |
TracePoint + Graphviz
เมธอดหลายตัวจะเรียกเมธอดอื่นมากกว่าแค่ 3 ครั้ง โดยเฉพาะในโค้ด framework ทำให้เอาต์พุตจาก TracePoint ยากต่อการจินตนาการ
เลยทำ gem ขึ้นมาหนึ่งตัวที่ช่วยสร้างกราฟการเรียกแบบภาพได้:
require 'visual_call_graph'
VisualCallGraph.trace { "Your method call here..." }
จะสร้างไฟล์ call_graph.png ไปพร้อมกับผลลัพธ์

จำไว้ว่า นี่ไม่ใช่ static analysis มันจะเรียกเมธอดจริงๆ!
การแสดงพาธไฟล์
อยากรู้ว่าเมธอดเหล่านี้ถูกกำหนดที่ไหน?
ไม่ต้องกังวล ผมเตรียมไว้ให้แล้ว! เพิ่มออปชันให้เปิดใช้งานเพื่อแสดงพาธไฟล์สำหรับแต่ละการเรียกเมธอด
VisualCallGraph.trace(show_path: true) { Foo.aaa }
ผลลัพธ์:

ถ้าอยากดู call graph ใหญ่ๆ ลองติดตามเมธอดของ Rails ดู 😉
ค่าที่คืนกลับ (Return Values)
ใน مقدمة กล่าวไปว่าสามารถดึงค่าที่คืนกลับได้...
สำหรับนี้ ต้องติดตาม event return และใช้เมธอด return_value
ตัวอย่าง:
def the_method; "A" * 10; end
trace = TracePoint.new(:return) { |tp| puts "Return value for #{tp.method_id} is #{tp.return_value}." }
trace.enable
the_method
trace.disable
จะพิมพ์:
Return value for the_method is AAAAAAAAAA.
Event เกิดขึ้นก่อนการทำงานจริง
มีคนถามบน Reddit ว่าจะกันไม่ให้คำว่า "bar" ถูกพิมพ์เมื่อเรียกเมธอด foo ในโค้ดนี้:
class Thing
def foo
puts "foo"
bar
end
def bar
puts "bar"
end
end
# your code here
t = Thing.new
t.foo
มีหลายวิธีทำได้ เช่น prepend module, redirect $stdout หรือ redefine เมธอด bar
ถ้าคุณรู้สึกสร้างสรรค์ ลองคอมเมนต์ไอเดียของคุณด้านล่างดูสิ!
แต่วิธีตอบหนึ่งน่าสนใจมาก เพราะใช้คลาส TracePoint
นี่คือคำตอบ:
TracePoint.trace(:call) { |tp| exit if tp.method_id == :bar }
โค้ดนี้จะเรียก exit เมื่อเมธอด bar ถูกเรียก ทำให้โปรแกรมหยุดทำงานก่อนพิมพ์สตริง
อาจไม่ใช่สิ่งที่ควรใช้ในโค้ดจริง แต่พิสูจน์ได้ว่าสำหรับ TracePoint: Event ถูกกระตุ้นก่อนที่การทำงานจริงจะเกิดขึ้น
ควรจำไว้ถ้าจะสร้างเครื่องมือรอบๆ TracePoint 🙂
สรุป
ในบทความนี้ คุณเรียนรู้เกี่ยวกับคลาส TracePoint ซึ่งช่วยให้ติดตาม event ต่างๆ เช่น การเรียกเมธอด หรือการสร้างเธรดใหม่ สามารถนำไปใช้เป็นเครื่องมือ debug หรือสำรวจโค้ดได้
จำไว้ แชร์บทความนี้ ให้เพื่อนๆ ได้อ่านด้วยนะครับ 🙂