CubicLouve

Spring_MTの技術ブログです。https://github.com/SpringMT (http://spring-mt.tumblr.com/ からの移転)

Rubyのお気持ちを読む

Rubyのお気持ちが分からないことがたまにありますよね。

このスクリプトで最終的になにが表示されるでしょうか。

class A
  attr_accessor :foo
  def bar
    foo = baz(foo)
    foo
  end

  def baz(v)
    v
  end
end

a = A.new
a.foo = 1
p a.bar # ここで表示される値は?

答えは 1 ではなく nil です。

barメソッドの foo = baz(foo) において、foo =foo=メソッド呼び出しではなく、ローカル変数 foo への代入とみなされて、 ローカル変数fooが新たに定義され、そのローカル変数foobazメソッドに引数と渡されるため、最終的にnilが表示されることとなります。

ローカル変数の定義はリファレンスマニュアルにも明記されています。

変数と定数 (Ruby 2.4.0)

ただ、こういうRubyの挙動を忘れてしまったときはどのように判断すれば良いのでしょうか?

そういうときはRubyVMの命令シーケンスを確認することでRubyの挙動を追うことが可能です。

RubyVMの命令シーケンスを取得する

RubyVMの命令シーケンスを取得するにはRubyVM::InstructionSequenceクラスを使います。

https://docs.ruby-lang.org/ja/latest/class/RubyVM=3a=3aInstructionSequence.html

今回試したスクリプトをsample.rbとして保存し、そのファイルを読み込んで命令シーケンスを出力させてみます。

出力させるには下記のスクリプトを実行します。

puts RubyVM::InstructionSequence.compile_file("./sample.rb", true).disasm

実行した結果はこんな感じになります。

fooがローカル変数になる

これだけではわかりにくいので、実際にメソッド呼び出しがされる場合も見てみましょう。

class A
  attr_accessor :foo
  def bar
    test = baz(foo)
    test
  end

  def baz(v)
    v
  end
end

a = A.new
a.foo = 1
p a.bar

上記スクリプトの命令シーケンスはこんな感じです。

fooがメソッド呼び出しになる

わかりにくいので、diffを取ってみます。

gist.github.com

最初のスクリプトではローカル変数 fooが設定されていることが分かるかと思います。

Rubyの挙動で困ったときはVMの命令シーケンスを確認してみてはいかがでしょうか?

Macで自分のRubyのプロセスのrssを調べるには

bugs.ruby-lang.org

このチケットをみていて、Macで同じことをどうやるか?を考えてみました。

Macには/procがありません。

% ruby -rzlib -e'END{puts File.readlines("/proc/self/status").grep(/RSS/)}' -e'd=Zlib.gzip("data"); 100_000.times{Zlib.gunzip(d)}' 
-e:1:in `readlines': No such file or directory @ rb_sysopen - /proc/self/status (Errno::ENOENT)
    from -e:1:in `block in <main>'

なので、psコマンド経由でrssを取得します。

% ruby -rzlib -e'END{puts `ps -o rss= -p #{Process.pid}`.to_i}' -e'd=Zlib.gzip("data"); 100_000.times{Zlib.gunzip(d)}'
927852

(rssはデフォルトの単位はkB)

他にいい方法あるかな。。。

Array#samplesの実装を追う(途中)

candidates = [
  'hoge',
  'foo',
  'bar',
  'Sring_MT'
]

puts candidates.sample

のようなスクリプトでなぜか自分がよく当たるような気もしたので、Array#sampleの実装を追ってみた。

instance method Array#sample (Ruby 2.4.0)

source

メソッド定義場所はここ https://github.com/ruby/ruby/blob/trunk/array.c#L6258

sampleの実装はここ https://github.com/ruby/ruby/blob/trunk/array.c#L4802

特に乱数生成器のオブジェクトが指定されていなければ、random.cが使われます。 ruby/random.c at trunk · ruby/ruby · GitHub

sampleは下記マクロを呼び出して、ランダムなインデックスを返す。 https://github.com/ruby/ruby/blob/trunk/array.c#L4699

このマクロから呼び出されるのは、rb_random_ulong_limited https://github.com/ruby/ruby/blob/trunk/random.c#L1026

このrandom.cは MT19937 をベースにした擬似乱数生成を行う。

メルセンヌ・ツイスタ - Wikipedia

cpprefjp.github.io

random.cを追ってみる

Google SpreadsheetのJOINみたいなことがしたかったのでgoでコマンド作った

今まで改行された標準出力から、grepのOR検索するための条件を作るためにperlでゴニョゴニョしてました。

(他にもいい方法あるかな。。。)

% echo "test\ntest\ntest" | perl -e 'my @list; while (my $m = <>) {chomp($m); push @list, $m}; print join("\\\|", @list)'; echo
test\|test\|test

こんなときにGoogle SpreadsheetのJOIN関数みたいなことができば一発だなと思ってgoの勉強がてらコマンド作ってみました。

github.com

(すでにjoinコマンドは存在しているので、名前は結構やっつけでつけちゃいました。よい名前があればそれに変えます)

gs-joinを使うとこんな感じでできます。

% echo "test\ntest\ntest" | ./gs-join -d '\|'
test\|test\|test

まあ、goの勉強を始めて2日で作ったので中身は微妙なのです。。。

これからテスト書いたり、リファクタリングとかしていく予定です。

slack APIで自分がjoinしているチャンネル一覧取得

token取得

api.slack.com

レガシーなのでいつdisableになるかわからないですが。。。。

id取得

curl -s -XPOST 'https://slack.com/api/users.list?token=#{自分のToken}&pretty=1' | jq -r '.members[]|"\(.id) \(.profile.email)"' | grep '登録しているメアド' | cut -c 1-9`

publicなチャンネル取得

curl -s -XPOST 'https://slack.com/api/channels.list?token=#{自分のToken}&pretty=1' | jq -r '.channels[]|select(.is_archived =  "false")' | jq -r '"\(.name) \(.members[])"' | grep #{自分のID} | cut -d " " -f1

privateなチャンネル取得

 curl -s -XPOST 'https://slack.com/api/groups.list?token=#{自分のToken}&pretty=1' | jq -r '.groups[]|select(.is_archived =  "false")' | jq -r '"\(.name) \(.members[])"' | grep #{自分のID} | cut -d " " -f1

tmpwatchについて

よく忘れるので、整理しておく。

tmpwatchコマンド

コマンドは/usr/sbin/配下にあります。
使い方は下記の通り。

$ tmpwatch [option] [hours]  [dirs]

dirsの指定にはワイルドカード(*)が使えます。
基本的に空dir、ファイル、シンボリックリンクを削除するそうです。
root のファイルは消さない、シンボリックリンクをたどって削除することもないそうです。

When changing directories, tmpwatch is very sensitive to possible race conditions and will exit with an error if one is detected. It does not follow symbolic links in the directories it’s cleaning (even if a symbolic link is given as its argument), will not switch filesystems, skips lost+found directories owned by the root user, and only removes empty directories regular files, and symbolic links. 

オプション

man tmpwatch の内容

-u, --atime : 最終アクセス時刻  
-m, --mtime : 最終ファイル変更時刻  
-c, --ctime : 最終inode変更時刻(パーミッション、所有者、グループ、または他のメタデータが変更された時刻)  
-M, --dirmtime : 最終dir修正時刻  
-a, --all :全てのファイルタイプを削除する。  
-d, --nodirs : 空dirであってもdirを削除しない。  
-l, --nosymlinks :symbolic link は削除しない  
-f, --force : rootであっても削除する。  
-q, --quiet : エラー時のみ、ログを出す。  
-s, --fuser : ファイルを開いているか確認。  
-t, --test : test。実際には削除しない。  
-U, --exclude-user=user : ここで指定したuserが所有しているファイルは削除対象外。  
-v, --verbose : 詳細なログ表示。  
-x, --exclude=path : ここで指定したパスは削除対象外。  
-X, --exclude-pattern=pattern :ここで指定したパターンに一致すれば削除対象外。 

-umcとすると、上記3つの中の最大値(最新時刻)が削除判定のベースとなる。下記参照

If the --atime, --ctime or --mtime options are used in combination, the decision about deleting a file will be based on the maximum of these times. The --dirmtime option implies ignoring atime of directories, even if the --atime option is used. 

試してみる

/usr/sbin/tmpwatch -tumcv 72 /home/*/logs/
tオプションをつけてdry-runをしてみる

$ /usr/sbin/tmpwatch -tumcv 72 /home/*/logs/
grace period is 259200
cleaning up directory /home/hoge/logs
cleaning up directory /home/hoge/logs/httpd
removing file /home/hoge/logs/httpd/www29204u.sakura.ne.jp.error_log.20110506
removing file /home/hoge/logs/httpd/www29204u.sakura.ne.jp.access_log.20110506

tmpwatchをcronに登録

/etc/cron.daily/tmpwatchには↓のように既に登録されていいるものがあります。

$ less /etc/cron.daily/tmpwatch
flags=-umc
/usr/sbin/tmpwatch "$flags" -x /tmp/.X11-unix -x /tmp/.XIM-unix 
        -x /tmp/.font-unix -x /tmp/.ICE-unix -x /tmp/.Test-unix 
        -X '/tmp/hsperfdata_*' 240 /tmp
/usr/sbin/tmpwatch "$flags" 720 /var/tmp
for d in /var/{cache/man,catman}/{cat?,X11R6/cat?,local/cat?}; do
    if [ -d "$d" ]; then
        /usr/sbin/tmpwatch "$flags" -f 720 "$d"
    fi
done

ここに

/usr/sbin/tmpwatch "$flags" 240 /home/*/hoge

for d in `ls /home/`; do
    if [ -d "/home/$d/foo/bar" ]; then
    /usr/sbin/tmpwatch "$flags" 240 "/home/$d/foo/bar";
    fi
done

みたいに追記すればよいと思います。

参考にしたサイト

ameblo.jp

Stray Penguin - Linux Memo (logrotate)

nageyari.dig-it-all.jp

MySQLに数百万件のテストデータを作る方法

お手軽にMySQLにダミーデータを作る方法です。

INSERT INTO hoge SELECT * FROM hoge; を繰り返す

スキーマ

gist.github.com

(プライマリキーなしというありあえないテーブルで試しています。。。)

結果

gist.github.com

1280000 rows作るのに1分かからないですね。

プライマリキーの重複が起こる場合は、

INSERT INTO :table_name (プライマリキー以外のカラム) SELECT プライマリキー以外のカラム FROM :table_name;

LOAD DATA INFILE

MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.6 LOAD DATA INFILE 構文