12/08/2018, 14:15

Chuyện gì đang xảy ra trong ứng dụng Ruby của bạn?

Bạn sẽ làm gì nếu như bạn muốn biết những điều đang xảy ra trong ứng dụng Ruby của bạn? Trong Ruby chúng ta không có các tools như Java, nhưng chúng ta có module ObjectSpace, module này sẽ cung cấp cho bạn một số thông tin về trạng thái hiện tại của ứng dụng của bạn. Đếm số lượng object Sử ...

Bạn sẽ làm gì nếu như bạn muốn biết những điều đang xảy ra trong ứng dụng Ruby của bạn?

Trong Ruby chúng ta không có các tools như Java, nhưng chúng ta có module ObjectSpace, module này sẽ cung cấp cho bạn một số thông tin về trạng thái hiện tại của ứng dụng của bạn.

Đếm số lượng object

Sử dụng ObjectSpace bạn có thể biết được những objects nào đang sống trong program của bạn.

Vậy một object được coi là vẫn còn sống có nghĩa gì? Một object là còn sống có nghĩa là nó vẫn còn ràng buộc nào đó trỏ đến nó. Một ràng buộc ở đây đơn giản là các để access đến object đó, nhưn là một variable hoặc một constant nào đó. Nếu object không thể tiếp cận được thì nó có nghĩa là an toàn khi loại bỏ chúng khỏi bộ nhớ.

Ví dụ:

# Biến 'name' giữ một liên kết ràng buộc tới string 'Dave'.
name = 'Dave'

# Biến 'name' bây giờ có trỏ đến 'John'.
# => 'Dave' không còn liên kết nào tới biến 'name' nữa
name = 'John'

Bây giờ chúng ta hãy cùng xem ví dụ về ObjectSpace module:

require 'objspace'

# This is valid Ruby syntax, but doesn't work on irb/pry
ObjectSpace
  .each_object
  .inject(Hash.new 0) { |h,o| h[o.class] += 1; h }
  .sort_by { |k,v| -v }
  .take(10)
  .each { |klass, count| puts "#{count.to_s.ljust(10)} #{klass}" }

# Copy & paste version (use this for irb/pry)
ObjectSpace.each_object.inject(Hash.new 0) { |h,o| h[o.class] += 1; h }.sort_by { |k,v| -v }.take(10).each { |klass, count| puts "#{count.to_s.ljust(10)} #{klass}" }

Nó sẽ trả về top 10 classes mà có số lượng lớn nhất.

Count      Class
-------------------------
215853     String
64145      Array
53819      RubyVM::InstructionSequence
24337      Hash
14831      Proc
13539      RubyVM::Env
5575       Set
5430       Class
5102       ActionDispatch::Journey::Nodes::Cat
4685       Regexp

Nếu bạn nghi ngờ memory bị leak bạn có thể log data mỗi giờ thì sẽ tìm thấy được một số objects nó liên tục tăng và không có dấu hiệu giảm xuống (vì bản thân câu lệnh trên cũng đã tạo ra object mới.)

Fun with Objects

Khi bạn sử dụng ObjectSpace thì bạn thực sự có quyền truy cập vào một object thực tế, không chỉ là các thông tin về chúng, vì vậy bạn có thể làm một số điều thú vị với chúng như là in giá trị của strings hoặc là in path của tất cả File objects.

ObjectSpace
  .each_object(String)
  .sort_by { |s| s.size }
  .each { |s| p s }

=> Lệnh trên sẽ in tất cả strings trong bộ nhớ, sắp xếp theo kích thước. Bạn sẽ nhận thấy rằng sẽ có rất nhiều string mà không phải do bạn tạo ra, chúng được tạo ra bởi Ruby interpreter.

Hầu như sẽ không được dùng trong thực tế nhiều. CHúng thường được sử dụng để thống kê và debugging về ứng dụng của bạn.

Object Memory Size

Một điều bạn có thể làm là sử dụng ObjectSpace.memsize_of để tìm kích thước bộ nhớ của một đối tượng cụ thể:

ObjectSpace.memsize_of("a" * 100)
=> 141
ObjectSpace.memsize_of("a" * 22)
=> 40

Bạn cần phải chú ý một điều là: Kích thước trả về là không đầy đủ (hiểu một cách là không chính xác toàn bộ). Cái này chỉ được xem như là một gợi ý. Đặc biệt kích thước của T_DATA có thể không chính xác.

Nếu bạn thử sử dụng method này với các loại object khác nhau bạn sẽ thấy được một điều thú vị rằng Fixnum luôn luôn return 0

ObjectSpace.memsize_of(1)
=> 0
ObjectSpace.memsize_of(1000)
=> 0

Và nguyên nhân là do Ruby không tạo một object Fixnum bên trong nó, bạn có thể tìm hiểu thêm về nó ở đây. Một vài điều thú vị khác như:

ObjectSpace.memsize_of("A" * 22)
=> 40
ObjectSpace.memsize_of("A" * 23)
=> 40
ObjectSpace.memsize_of("A" * 24)
=> 65

Tại sao lại có một bước nhảy lớn như vậy từ 23-24 kí tự? Nhìn kết quả trên ta có thể thấy rằng Ruby có một sự tối ưu hóa cho các chuỗi string nhỏ hơn 24 kí tự, đó là lý do tại sao ta lại có một sự khác biệt khi lấy size của 24 kí tự và nhỏ hơn nó. Bạn có thể thấy điều này chi tiết hơn trong bài viết này của Pat Shaughnessy.

Finding Aliased Methods

Hãy cùng xem mã code dưới đây

class Module
  def aliased_methods
    instance_methods(false)
      .group_by{|m| instance_method(m)}
      .map(&:last)
      .keep_if{|symbols| symbols.length > 1}
  end
end

Tôi đã tìm thấy nó trên Stackoverflow. Nó định nghĩa một phương thức aliased_methodstrong Module class, sử dụng phương thức instance_methods để get tất cả danh sách các instance methods được định nghĩa ở trong class.

Dưới đây là phần còn lại của code, nó sẽ tạo một array của tất cả các class names mà có ít nhất một object còn sống, sau đó nó sẽ gọi aliased_methods cho mọi class và in ra kết quả

objects = ObjectSpace.each_object.map(&:class).uniq

objects.each do |klass|
   methods = "#{'-'*20}#{klass}#{'-'*20}
"

   klass.send(:aliased_methods).each do |m1, m2|
     methods << "#{m1.to_s.ljust(15)} #{m2}
"
   end

   puts methods
end

Nó sẽ trả về kết quả giống như sau:

--------------------String--------------------
==              ===
[]              slice
length          size
succ            next
succ!           next!
to_s            to_str
concat          <<
intern          to_sym
start_with?     starts_with?
end_with?       ends_with?
camelize        camelcase
titleize        titlecase
to_json_with_active_support_encoder to_json
--------------------Class--------------------
superclass_delegating_accessor superclass_delegating_accessor_with_deprecation
--------------------Array--------------------
inspect         to_default_s
to_s            to_formatted_s
[]              slice
<<              append
unshift         prepend
length          size
empty?          blank?
find_index      index
collect         map
collect!        map!
to_json_with_active_support_encoder to_json
--------------------Proc--------------------
call            []
......

Kết luận

Hy vọng bạn sẽ học được thêm những điều mới lạ từ ObjectSpace module.

0