CubicLouve

Spring_MTの技術ブログ

unix domain socketのstatus

cat /proc/net/unixの中身

項目 説明
Num カーネルのテーブルスロット ffff8800798ec0c0
RefCount ソケットを使用しているユーザー数 00000002
Protocol いまのところいつも 0
Flags ソケット の状態を保持しているカーネル内部のフラグ
Type always '1' とかいてあるが2もある。。。 0001
St ソケットの内部状態 01 02 03 https://github.com/torvalds/linux/blob/master/include/uapi/linux/net.h#L47
Inode
Path (もしあれば) ソケットのパス名

sinatraで設定したパス一覧を取得する

メモ

こんな感じでスクリプトを書いた。

require 'rack'
require 'sinatra'

class Sinatra::Base
  private
  class << self
    attr_reader :original_routes
    # なぜかsuperが呼べない...
    def route(verb, path, options = {}, &block)
      @original_routes ||= {}
      (@original_routes[verb] ||= []) << path
      host_name(options.delete(:host)) if options.key?(:host)
      enable :empty_path_info if path == "" and empty_path_info.nil?
      signature = compile!(verb, path, block, options)
      (@routes[verb] ||= []) << signature
      invoke_hook(:route_added, verb, path, block)
      signature
    end
  end
end

def run(*args)
  args.first.instance_variable_get(:@mapping).each do |mapping|
    # 二番目の要素がmappingのbase path
    base_path = mapping[1]
    # 三番目の要素がcontroller class
    mapping[3].original_routes.each do |method, paths|
      paths.each { |path| puts "#{method}\t#{base_path}#{path}" }
    end
  end
end

Sinatra::Base.set :running_server, true
load './config.ru'

railsで特定のモデルに対してbelongs_toで参照先を設定しているモデルを取り出す

まあ、いろいろ調べる必要がありまして。。。

ざっくりとこんな感じでとれた。

# production環境では要らないが、development環境では必要かと
Dir.glob(File.expand_path('./app/models/*.rb', Rails.root)).each do |f|
  require f
end

# 定数を全部取得
constants = Object.constants.map do |name|
  Object.const_get(name)
end

# 定数一覧からモデルを絞り込む
all_models = constants.select do |c|
  c.class == Class && c < ActiveRecord::Base && !c.abstract_class?
end
                                                                                                                          
target = {}
all_models.each do |model|
  model.reflect_on_all_associations(:belongs_to).each do |association|
    if association.name == :hoge || %w(Hoge).include?(association.options[:class_name])
      target[association.active_record.name] = { model: association.active_record, foreign_key: association.foreign_key }
    end
  end
end
target

これで対象モデルが取得できたのであとはよしなに使う。 例えば、一つも参照がされていないデータを取ってくるとか。

target_ids = Hoge.pluck(:id)

target.each do |name, modle|
  used_ids = model[:model].where(model[:foreign_key] => target_ids).pluck(:"#{model[:foreign_key]}")
  target_ids -= used_ids
end
target_ids

とか

rubyで局所的にパッチを当てる方法を模索中

下記のようなコードで、Cクラスの中だけで何とかAクラスに対するパッチを当てたい。

class A
  def self.foo
    p 'foo'
  end
  def bar
    p 'bar'
  end
end

module ExtendFoo
  refine A.singleton_class do
    def foo
      p 'extend foo'
    end
  end
  refine A do
    def bar
      p 'extend bar'
    end
  end
end

class B
  def initialize
    A.foo
    A.new.bar
  end
end

class C
  using ExtendFoo
  def initialize
    B.new
  end
end

C.new

これを実行すると、

"foo"
"bar"

になるのだが、

"extend foo"
"extend bar"

にする方法がないものか。。。

railsアプリでstackprofを使ってボトルネックを探す + JSON::Schema(2.2.1)の高速化

railsアプリが遅いって言われたので、久しぶりにrubyでisuconしてみました。

railsアプリでstackprofを使ったプロファイリング

まず、自分がいつもやってる方法なのですが、config.ruにstackprofの設定を仕込みます。

stackprofはrackミドルウェアとして差し込めるようになっています。

下記設定はrailsだけでなく、sinatraでももちろん動きます。(これをいつも仕込んでおいてあります。)

Gemfileにgem 'stackprof'を書いてconfig.ruに下記のように仕込んでいます。

is_stackprof         =  ENV['ENABLE_STACKPROF'].to_i.nonzero?
stackprof_mode       = (ENV['STACKPROF_MODE']       || :cpu).to_sym
stackprof_interval   = (ENV['STACKPROF_INTERVAL']   || 1000).to_i
stackprof_save_every = (ENV['STACKPROF_SAVE_EVERY'] || 100 ).to_i
stackprof_path       =  ENV['STACKPROF_PATH']       || 'tmp'
use StackProf::Middleware, enabled:    is_stackprof,
                           mode:       stackprof_mode,
                           raw:        true,
                           interval:   stackprof_interval,
                           save_every: stackprof_save_every,
                           path:       stackprof_path

run Rails.application

環境変数でstackprofの設定を渡しています。 stackprofを仕込んでrailsを起動するときはこんな感じです。

ENABLE_STACKPROF=1 bundle exec rails s 

で、今回とったプロファイリング結果がこちら。 (まあ、色々削った結果を載せています)

==================================
  Mode: cpu(1000)
  Samples: 7758 (7.75% miss rate)
  GC: 1720 (22.17%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
      5799  (74.7%)        2987  (38.5%)     JSON::Schema.add_indifferent_access
       858  (11.1%)         638   (8.2%)     JSON#generate
       439   (5.7%)         439   (5.7%)     block in JSON::Schema::TypeV4Attribute.data_valid_for_type?
       866  (11.2%)         359   (4.6%)     ActiveSupport::JSON::Encoding::JSONGemEncoder#jsonify
       279   (3.6%)         279   (3.6%)     block in JSON::Schema.add_indifferent_access
       174   (2.2%)         174   (2.2%)     String#to_json_with_active_support_encoder
       296   (3.8%)         118   (1.5%)     block in Array#as_json
       111   (1.4%)          98   (1.3%)     block in Hash#as_json
      2205  (28.4%)          96   (1.2%)     block in JSON::Schema::PropertiesV4Attribute.validate
・
・
      7150  (92.2%)          32   (0.4%)     JSON::Schema::Validator#validate
・
・
      3361  (43.3%)          11   (0.1%)     JSON::Schema#initialize

JSON::Schema.add_indifferent_accessが悪さしているぽい。

ボトルネックの洗い出しと解消

で、ここからは、最近のコード変更点とJSON::Schemaに関わる部分を洗い出して、悪さしているコードを洗い出して、最小の再現コード書いてみたのがこちら。

require 'json'
require 'json-schema'
require 'benchmark'

class BenchmarkJSONSchmea
  def initialize
    @json_data = []
    70_000.times do |i|
      @json_data << {id: i, name: "てすとてすと", point: 12345, description: "テストてすと"}
    end

    @json_schema = <<-JSON
    {
      "type": "array",
      "items": {
        "type": "object", "required": ["id"],
        "properties": {
          "id": {"type": "integer"},
          "name": {"type": "string", "minLength": 2, "maxLength": 15},
          "point": {"type": "integer"},
          "description": {"type": "string", "minLength": 2, "maxLength": 15}
        }
      }
    }
    JSON
  end

  def run
    # 試行回数は1回
    Benchmark.bm(7, ">total:", ">ave:") do |x|
      x.report("raw: ") { JSON::Validator.validate!(JSON.parse(@json_schema), JSON.parse(@json_data.to_json)) }
    end
  end
end

BenchmarkJSONSchmea.new.run

で、結果がこちら

% bundle exec ruby bench.rb
              user     system      total        real
raw:      6.310000   0.110000   6.420000 (  7.567306)

遅い。。。

stackprofの結果を鑑みて

stackprofではJSON::Schema.add_indifferent_accessが遅いって言われているので、ここを見てみてパッチを当ててみた。

require 'json'
require 'json-schema'
require 'benchmark'

class JSON::Schema
  def self.add_indifferent_access(schema)
    if schema.is_a?(Hash)
      schema.default_proc = proc do |hash,key|
        if hash.has_key?(key)
          hash[key]
        else
          hash.has_key?(key) ? hash[key] : nil
        end
      end
      schema.keys.each do |key|
        add_indifferent_access(schema[key])
      end
    end
  end
end

class BenchmarkJSONSchmea
  def initialize
    @json_data = []
    10_000.times do |i|
      @json_data << {"id" => i, "name" => "てすとてすと", "point" => 12345, "description" => "テストてすと"}
    end

    @json_schema = <<-JSON
    {
      "type": "array",
      "items": {
        "type": "object", "required": ["id"],
        "properties": {
          "id": {"type": "integer"},
          "name": {"type": "string", "minLength": 2, "maxLength": 15},
          "point": {"type": "integer"},
          "description": {"type": "string", "minLength": 2, "maxLength": 15}
        }
      }
    }
    JSON
  end

  def run
    # 試行回数は1回
    Benchmark.bm(7, ">total:", ">ave:") do |x|
      x.report("patch: ") do
        JSON::Validator.validate!(JSON.parse(@json_schema), JSON.parse(@json_data.to_json))
      end
    end
  end
end

BenchmarkJSONSchmea.new.run

結果がこちら

% bundle exec ruby patch_bench.rb
              user     system      total        real
patch:    0.890000   0.010000   0.900000 (  0.993027)

これで5倍くらいは速くなってます。

あとはrefimentsでパッチ当てて、こんな感じで局所化したかったけどなぜかうまく動作しない。。。

module JSONSchemaExtension
  refine JSON::Schema.singleton_class do
    def add_indifferent_access(schema)
      if schema.is_a?(Hash)
        schema.default_proc = proc do |hash,key|
          if hash.has_key?(key)
            hash[key]
          else
            hash.has_key?(key) ? hash[key] : nil
          end
        end
        schema.keys.each do |key|
          add_indifferent_access(schema[key])
        end
      end
    end
  end
end

class BenchmarkJSONSchmea
  using JSONSchemaExtension
end

Webサイトに必要なfaviconが21個になっていたらしいのでrack-favicon_allっての書いた

Webサイトに必要なfaviconが21個になっていた - IT探検記

どうもWebサイトに必要なfaviconが21個もあるらしい。

@bayashi

増殖中の favicon を Plack::Middleware ひとつで配信する - @bayashi Diary

のような記事を書いていたから、rackミドルウェア版作ってみた。

SpringMT/rack-favicon_all · GitHub

rack-favicon_all | RubyGems.org | your community gem host

誰か作ってるかもしれないけれど、後悔はしていない。

SSL/TLSのシーケンスまとめ

TCP 3-way handshakeの復習 - CubicLouve

上記を踏まえた上でSSL/TLSのシーケンスをtcpdumpwiresharkで追ってみました。

まだ理解しきれていない部分があるので直すかも。

clientのコードはこちらから。

SpringMT/socket_test · GitHub

clientのコードは下記ページのものを使っています。

SSL/TLS でアクセスしてみよう (1)

流れ

1. クライアントからSYN(TCPと一緒)

22:41:54.444298 IP 192.168.100.101.51013 > nrt04s05-in-f18.1e100.net.https: Flags [S], seq 1497432669, win 65535, options [mss 1460,nop,wscale 4,nop,nop,TS val 946049109 ecr 0,sackOK,eol], length 0

2. サーバーから SYN + ACK(TCPと一緒)

22:41:54.609915 IP nrt04s05-in-f18.1e100.net.https > 192.168.100.101.51013: Flags [S.], seq 1784227309, ack 1497432670, win 42540, options [mss 1360,sackOK,TS val 3615605143 ecr 946049109,nop,wscale 7], length 0

3. クライアントから ACK(TCPと一緒)

22:41:54.609979 IP 192.168.100.101.51013 > nrt04s05-in-f18.1e100.net.https: Flags [.], ack 1, win 8256, options [nop,nop,TS val 946049273 ecr 3615605143], length 0

4. クライアントがClient Helloメッセージを送る

クライアントがサポート可能な暗号化/圧縮アルゴリズムを通知する

wiresharkでみてみると、CipherSpecsが送られている。

ここではフラグがPUSHになっている。(すぐに返せってことなのかな)

f:id:Spring_MT:20150103001633p:plain

22:41:54.611008 IP 192.168.100.101.51013 > nrt04s05-in-f18.1e100.net.https: Flags [P.], seq 1:131, ack 1, win 8256, options [nop,nop,TS val 946049274 ecr 3615605143], length 130

5. サーバーがACKを返し、さらにServer Helloを返す

使用する暗号アルゴリズムをServerHelloメッセージに格納しておくる。

今回はCipher Suite: TLS_RSA_WITH_RC4_128_SHA (0x0005)を使っていた。

この時点で証明書送ってそうだけどな。。。。

f:id:Spring_MT:20150103001656p:plain

22:41:54.769011 IP nrt04s05-in-f18.1e100.net.https > 192.168.100.101.51013: Flags [.], seq 1:1349, ack 131, win 341, options [nop,nop,TS val 3615605273 ecr 946049274], length 1348

22:41:54.769090 IP nrt04s05-in-f18.1e100.net.https > 192.168.100.101.51013: Flags [.], seq 1349:2697, ack 131, win 341, options [nop,nop,TS val 3615605273 ecr 946049274], length 1348

6. サーバーがCertificateを送る

サーバの証明書をCertificateメッセージに格納して送る。

f:id:Spring_MT:20150103001716p:plain

22:41:54.769249 IP nrt04s05-in-f18.1e100.net.https > 192.168.100.101.51013: Flags [P.], seq 2697:3172, ack 131, win 341, options [nop,nop,TS val 3615605273 ecr 946049274], length 475

7. クライアントがClient Key Exchange, Change Cipher Spec, Encrypted Handshake Messageを送る

クライアントが生成した、プリマスターシークレットをサーバーの公開鍵で暗号化して送信する。

f:id:Spring_MT:20150103001753p:plain

22:41:54.804024 IP 192.168.100.101.51013 > nrt04s05-in-f18.1e100.net.https: Flags [P.], seq 131:445, ack 3172, win 8192, options [nop,nop,TS val 946049465 ecr 3615605273], length 314

8. サーバーがChange Cipher Spec, Encrypted Handshake Messageを送る

新しい暗号化仕様の利用開始を通知。

f:id:Spring_MT:20150103001810p:plain

22:41:54.904878 IP nrt04s05-in-f18.1e100.net.https > 192.168.100.101.51013: Flags [P.], seq 3172:3219, ack 445, win 350, options [nop,nop,TS val 3615605439 ecr 946049465], length 47

9. 通信開始

ここからやっと通信開始 ここまでをSSL_connectがやっている。

SSL_connect - initiate the TLS/SSL handshake with an TLS/SSL server

参考文献

WiresharkでSSL通信の中身を覗いてみる - ろば電子が詰まっている

SSL/TLS の暗号アルゴリズムの決定メカニズムをパケットレベルで追いかけてみる - /dev/null blog

「不正アクセスを防止するSSL/TLS」最新記事一覧 - ITmedia Keywords

OpenSSLライブラリを使ってプログラミング(1) - 再帰の反復

raw パケット

https://gist.github.com/SpringMT/feeb955226df21a633ad