Интерпретация вывода RubyProf::GraphPrinter 0

Posted by pager
on Wednesday, October 08

Если кто-то почему-то не знает, для Руби есть профилировщик RubyProf. Он умеет выводить результаты своей работы в нескольких форматах, в частности, plain-text-овых FlatPrinter и GraphPrinter. GraphPrinter значительно веселее и полезнее.

Рассмотрим такой кусок вывода GraphPrinter:

  %total   %self     total      self      wait     child            calls   Name
--------------------------------------------------------------------------------
                      0.00      0.00      0.00      0.00              1/5     Rake::Application#raw_load_rakefile
                      0.00      0.00      0.00      0.00              1/5     Array#collect
                      4.60      0.00      0.00      4.60              1/5     Rake::Application#standard_exception_handling
                      0.00      0.00      0.00      0.00              1/5     Enumerable#inject
                      0.00      0.00      0.00      0.00              1/5     Module#local_constants
46029.83%   2.15%      4.60      0.00      0.00      4.60                5     Array#each
                      0.00      0.00      0.00      0.00           8/1827     Module#constants
                      0.00      0.00      0.00      0.00              1/4     #[]
                      0.00      0.00      0.00      0.00           9/2042     Module#==
                      0.00      0.00      0.00      0.00           1/6192     Module#const_get
                      0.00      0.00      0.00      0.00            1/253     Array#concat
                      0.00      0.00      0.00      0.00             9/12     Array#each-1
                      4.60      0.00      0.00      4.60              1/1     Rake::Application#invoke_task
                      0.00      0.00      0.00      0.00           1/9783     String#==
                      0.00      0.00      0.00      0.00            1/102     ActiveSupport::Dependencies#uninherited_const_defined?
-------------------------------------------------------------------------------- 

Что это всё означает?

Прежде всего, это блок про метод Array#each. Он был вызван 5 раз. Колонки с числами как бы говорят нам: В Array (включая то, что из него вызывалось) интерпретатор провёл 46029.83% времени, а в непосредственно его коде — 2.15% времени (по 4.60 и 0.00 сек. соответственно). В Array#each интерпретатор 0.00 секунд провёл в состоянии ожидания ввода-вывода; а все 4.60 времени прошли в том, что этот Array#each вызывал.

Теперь — самое интересное: строки над Array#each — это то, что его вызывало. Его вызывали по одному разу из пяти методов. А строки под Array#each — это то, что вызывалось из него. В частности, именно из него были сделаны 8 из 1827 вызовов Module#constants, и так далее.

А Array#each-1 — это тот же Array#each на следующем уровне вложенности. То есть, прямо из Array#each был дёрнут Array#each ещё 9 раз.

gem evilchelu-braid 0

Posted by pager
on Saturday, September 06

Если после gem install evilchelu-braid вам стали говорить что-то вроде

/opt/local/bin/braid:19:in `load': no such file to load -- braid (LoadError)
    from /opt/local/bin/braid:19

то это от неверных прав в геме, и лечится так:

$ chmod a+rx /path/to/gem/evilchelu-braid-0.4.0/bin/braid

Инкапсулированный атрибут класса в ruby

Posted by pager
on Monday, June 18

Оригинал. Немного рассуждений из области почти чистой эзотерики о том, какой это страшный язык — Ruby. Пусть есть такой код:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Foo
  @@partially_encapsulated = {}
  
  class << self
    def []=(key, value)
      @@partially_encapsulated[key] = value
    end
    
    def [](key)
      @@partially_encapsulated[key]
    end
  end
end

Здесь детали реализации методов [] и []= не спрятаны. Если $SAFE позволяет, можно вызвать protected-метод classvariableget и вытащить наружу хеш, к которому они обращаются:

1
2
3
4
5
6
7
Foo[:name] = 'Marcel'
Foo[:name]
# => "Marcel"
Foo.send(:class_variable_get, 
         :@@partially_encapsulated).clear
Foo[:name]
# => nil

А так будет выглядеть реализация, которая действительно прячет хеш:

1
2
3
4
5
6
7
8
9
10
11
class Bar
  fully_encapsulated = {}
  
  self.class.send(:define_method, :[]=) do |key, value|
    fully_encapsulated[key] = value
  end
  
  self.class.send(:define_method, :[]) do |key|
    fully_encapsulated[key]
  end
end

Здесь методы [] и []= обращаются уже к локальной переменной класса. Внимание, вопрос: как «влезть» в fully_encapsulated, не пользуясь расширениями на C? :-)

Подлая «фича» механизма подгрузки классов в Rails 1.2 0

Posted by pager
on Tuesday, May 22

Как известно, в Rails 1.2, кроме всего прочего, была улучшена система подгрузки классов и упразднен метод ActionController::Base.model. Теперь всё должно бы загружаться само собой без дополнительных усилий.

Так оно и происходит, пока дело не дойдёт до STI. Рассмотрим такой пример:

Разумеется, в табличке ports есть поля id, type и server_id. Более того, в базе точно есть несколько серверов и портов. Запускаем script/console, берём какой-нибудь сервер, делаем server.ports.find(:all) и ничего не находим. Причины этого просты: приложение ещё ничего не знает о том, что у класса Port есть подклассы. Никто в приложении пока их не трогал и ими не пользовался, и в таблицах STI они не учитываются. Поэтому приложение пытается выбирать порты примерно таким запросом:

Чтобы запрос стал правильным, нужно тронуть EthernetPort и ComPort. Хоть просто произведя «бессмысленное обращение к константе», как его называет плюющийся warningам’и ruby. То есть написать что-то вроде:

1
2
3
4
5
6
class ApplicationController < ActionController::Base
    ()
    EthernetPort
    ComPort
    ()
end

Забавно, что эта “фича” по-разному проявляется на девелопменте (при отключенном cache_classes) и на продакшне. На девелопменте, как правило, просто одна и та же страница для разных объектов работает по-разному. На продакшне же, стоит один раз дёрнуть какую-то страницу, которая эти классы использует — как всё резко становится в порядке. Но! только на одном mongrel’е из кластера, и всегда остаётся вероятность при релоаде попасть на другой, в котором эти классы ещё не подцепились. Всё это вместе создаёт неповторимое чарующее ощущение полтергейста. :-)

Upd: Да, всё, конечно же чуточку не так просто, спасибо зануде Chooh. Если приведённый выше пример написать и прогнать, то запрос будет на самом деле такой:

SELECT * FROM ports WHERE (ports.server_id=16) 

потому, что с самого начала работы сервер ничего не знает о том, что у Port есть какая-то иерархия подклассов. Чтобы совсем всё сошлось, код нужен, например, такой:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Server < ActiveRecord::Base
    has_many :ports
end

class ServerPart < ActiveRecord::Base; end

class Port < ServerPart
    belongs_to :server
end

class EthernetPort < Port; end

class ComPort < Port; end

ну и, разумеется, выборка при этом будет идти из таблицы server_parts.

Как в Руби превратить массив в хеш. 0

Posted by pager
on Thursday, April 19

Иногда очень хочется превратить массив в хеш. Например, когда find вернул массив объектов, у которых есть дата, и хочется обращаться к ним по дате.

Решение не сразу, но всё-таки нашлось:


hash = Hash[*array.map {|o| [o.date, o]}.flatten]