makeのときにCPU個数を自動で調べて-jオプションのジョブ数に指定する簡単な方法

makeコマンドで並列に処理するには、-jオプションでジョブ数を指定します。

$ make -j4 

ジョブの個数にはビルドするホストのCPUの個数を指定したいので、スクリプトの中で定数で書いてしまうのはかっこ悪いです。(特に他の人に渡すようなビルドスクリプトでは。)並列に処理するジョブの個数はビルドの時間にダイレクトに効いてくるので適切な個数をセットしたいものです。

昨日の記事でperfをビルドするときには、makeのジョブ数が自動でCPUの個数にセットされていました。これはどうやっているのか調べました。

linux/tools/perf/Makefile

#
# Do a parallel build with multiple jobs, based on the number of CPUs online
# in this system: 'make -j8' on a 8-CPU system, etc.
#
# (To override it, run 'make JOBS=1' and similar.)
#
ifeq ($(JOBS),)
  JOBS := $(shell grep -c ^processor /proc/cpuinfo 2>/dev/null)
  ifeq ($(JOBS),)
    JOBS := 1
  endif
endif

/proc/cpuinfo の中の先頭がprocessorで始まる行の数を数えていました。
マッチした行数を数えるにはwcコマンドを使うまでもなく、grep -c でできるとは知りませんでした。
これでJOBSにCPUの個数が入っているので、それを使用して子プロセスでmakeコマンドを起動しています。

$(MAKE) -f Makefile.perf --no-print-directory -j$(JOBS) O=$(FULL_O) $(SET_DEBUG) $@

perfコマンドをソースからビルドする

linuxカーネルの計測ツールであるperfコマンドをソースからビルドしました。必要なライブラリを揃えるのにけっこう苦労したのでメモを残します。

linux 3.16.1
debian jessie armel

準備

$ sudo apt-get install \
 flex bison python pkg-config libaudit-dev binutils-dev \
 libelf-dev python-dev libpython-dev libperl-dev \
 libslang2-dev libdw-dev libiberty-dev 

armelにはlibunwind7-dev と libnuma-dev は無いので省きました。
また、GUIは使用しないのでgtkも入れませんでした。

ビルド

armのマシン上に、linux v3.16.1 のソースコードを展開して

$ cd tools/perf
$ make
  BUILD:   Doing 'make -j2' parallel build
config/Makefile:350: No libunwind found. Please install libunwind-dev[el] >= 1.1 and/or set LIBUNWIND_DIR
config/Makefile:426: GTK2 not found, disables GTK2 support. Please install gtk2-devel or libgtk2.0-dev
config/Makefile:585: No numa.h found, disables 'perf bench numa mem' benchmark, please install numactl-devel/libnuma-devel/libnuma-dev

Auto-detecting system features:
...                         dwarf: [ on  ]
...                         glibc: [ on  ]
...                          gtk2: [ OFF ]
...                      libaudit: [ on  ]
...                        libbfd: [ on  ]
...                        libelf: [ on  ]
...                       libnuma: [ OFF ]
...                       libperl: [ on  ]
...                     libpython: [ on  ]
...                      libslang: [ on  ]
...                     libunwind: [ OFF ]
...            libdw-dwarf-unwind: [ on  ]
...     DWARF post unwind library: libdw

  GEN      common-cmds.h
  ...
  (snip)
  ...

ライブラリが足りなくてauto detectがうまくいかないときは、config/feature-checks/ の下の検出用プログラムが何かビルドエラーになっていると思うので、そのディレクトリで単独でmakeすると何かわかります。

$ ./perf stat ls
CREDITS                  builtin-diff.o    builtin-record.c     config
Documentation            builtin-evlist.c  builtin-record.o     design.txt
MANIFEST                 builtin-evlist.o  builtin-report.c     lib
  ...(snip) ...
builtin-buildid-list.o   builtin-probe.c   command-list.txt     util
builtin-diff.c           builtin-probe.o   common-cmds.h

 Performance counter stats for 'ls':

          7.812500      task-clock (msec)         #    0.420 CPUs utilized
                28      context-switches          #    0.004 M/sec
                 0      cpu-migrations            #    0.000 K/sec
                78      page-faults               #    0.010 M/sec
           7946598      cycles                    #    1.017 GHz
           1677282      stalled-cycles-frontend   #   21.11% frontend cycles idle
           6520742      stalled-cycles-backend    #   82.06% backend  cycles idle
           2128870      instructions              #    0.27  insns per cycle
                                                  #    3.06  stalled cycles per insn
            233255      branches                  #   29.857 M/sec
             90247      branch-misses             #   38.69% of all branches

       0.018604500 seconds time elapsed

clang/llvmをソースからビルドする

LLVM 3.5 がそろそろリリースされそうですが、それを待たずにソースコードからclang/llvmをビルドしてみました。
ビルドの方法は以下に書かれています。
Getting Started with the LLVM System — LLVM 9 documentation
本家のリポジトリsvnなのですが、gitのミラーも用意されているのでそちらからソースを入手しました。

$ WORKDIR=$HOME/work
$ mkdir $WORKDIR
$ cd $WORKDIR
$ git clone http://llvm.org/git/llvm.git
$ cd llvm
$ (cd tools/; git clone http://llvm.org/git/clang.git)
$ (cd projects/; git clone http://llvm.org/git/compiler-rt.git)

release_35のブランチをcheckoutします。

$ git checkout -b work origin/release_35
$ (cd tools/clang; git checkout -b work origin/release_35)
$ (cd projects/compiler-rt/; git checkout -b work origin/release_35)

ビルドは別のディレクトリで行います。

$ cd $WORKDIR
$ mkdir obj
$ cd obj
$ ../llvm/configure 

ビルドは時間がかかるので、nohupをつけて途中でログアウトしても大丈夫なようにしました。

$ nohup make ENABLE_OPTIMIZED=1 DISABLE_ASSERTIONS=1 -j4 &

インストール

$ sudo make ENABLE_OPTIMIZED=1 DISABLE_ASSERTIONS=1 install
$ which clang
/usr/local/bin/clang
$ clang --version
clang version 3.5.0 (http://llvm.org/git/clang.git 3d96c02ac11c62ff959aed67163bef2c79012a83) (http://llvm.org/git/llvm.git a3313efbbeab739eacbba6faf4e941129576dbd2)
Target: x86_64-unknown-linux-gnu
Thread model: posix

クロスDockerのためのDockerfile

dockerhubにはx86, x86_64以外のアーキテクチャ用のコンテナもいくつかpushされています。これをqemuを利用したクロス環境のDockerとして動作させるためには、以下の準備が必要です。

(1) binfmt_misc でクロスで動かしたいアーキテクチャのELFファイルの設定がされていること
(2) コンテナ内の/usr/bin にstatic linkされたユーザーモードqemuのバイナリがコピーされていること

(1)はsudo apt-get install qemu-user-static を行ったときに自動的に行われます。
昨日の記事で使用したericvh/arm64-ubuntu-dev はすで(2)がなされた状態でdockerhubに置かれていたので、すぐにクロスで動かすことができました。

(1)だけで(2)がされていない状態では、docker runしたときに no such file or directoryのエラーになります。(1)で設定されたstatic linkされたユーザーモードqemuのバイナリがコンテナの名前空間で見つからないためです。

githubのarmel/debian:jessieを実行しようとすると正にこのエラーが発生します。

$ docker run -it armel/debian:jessie /bin/bash
2014/08/19 13:31:22 no such file or directory
2014/08/19 22:31:22 Error response from daemon: Cannot start container d9a83fa5578a44fd168bfc4d49a24a5a02e8cd88e6d5db46658e299582d61d56: no such file or directory

これを解決するには、qemu-arm-staticをコンテナ内の/usr/binにコピーします。
それを行うためのDockerfileは以下のようになります。

FROM armel/debian:jessie
ADD ./qemu-arm-static /usr/bin/

RUNコマンドを追加するときには、必ず "ADD ./qemu-arm-static /usr/bin/"より後ろに追加してください。そうでないとそのコマンドの実行がno such file or directoryのエラーになります。

このDockerfileをカレントディレクトリに置いて以下を実行して新たなコンテナを作成します。

$ cp /usr/bin/qemu-arm-static .
$ docker build --tag="koba/debian_armel" .
Sending build context to Docker daemon 6.195 MB
Sending build context to Docker daemon 
Step 0 : FROM armel/debian:jessie
 ---> 693f87a07cac
Step 1 : ADD ./qemu-arm-static /usr/bin/
 ---> Using cache
 ---> aaa2d71f8e2c
Successfully built aaa2d71f8e2c

このコンテナには koba/debian_armel のタグをつけたので、以下のようにして実行できます。

$ docker run -it koba/debian_armel /bin/bash
root@56400d6857a3:/# uname -m
armv7l
root@56400d6857a3:/# 

Aarch64で遊ぶ最も手軽な方法

Aarch64(ARM64)の実機はまだまだ入手は困難ですが、qemuを使ってAarch64のユーザーランドを動かすことは可能です。そしてDockerを利用すると手軽にAarch64の環境を試すことができます。コンパイラも動きます。

準備

Ubuntu 14.04 (x86_64)を使用しています。
まずはDockerをインストールします。詳細はここを見る
次にqemuをインストールします。

 $ sudo apt-get install qemu-user-static

起動

$ docker run -it ericvh/arm64-ubuntu-dev /bin/bash

これだけで、dockerhubからaarch64のルートファイルシステムをロードしてきて実行し、bashのプロンプトが出ます。

アーキテクチャの確認。

root@5031da283529:/# uname -m
aarch64

gccも利用可能です。

root@5031da283529:/# gcc -v
Using built-in specs.
COLLECT_GCC=/usr/bin/gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/aarch64-linux-gnu/4.8/lto-wrapper
Target: aarch64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.8.2-19ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --disable-libitm --disable-libsanitizer --disable-libquadmath --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-arm64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-arm64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-arm64 --with-arch-directory=arm64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-multiarch --disable-werror --enable-checking=release --build=aarch64-linux-gnu --host=aarch64-linux-gnu --target=aarch64-linux-gnu
Thread model: posix
gcc version 4.8.2 (Ubuntu/Linaro 4.8.2-19ubuntu1) 

root@5031da283529:/root# vi hello.c
root@5031da283529:/root# gcc hello.c
root@5031da283529:/root# ./a.out
Hello, world
root@5031da283529:/root# file ./a.out 
./a.out: ELF 64-bit LSB  executable, ARM aarch64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 3.7.0, BuildID[sha1]=a6ee525387725894f818f789acd9b0203adff657, not stripped

gdbもあるのですが、残念ながら以下のエラーでブレークポイントがうまくかかりませんでした。

qemu: Unsupported syscall: 117

追記(2014.8.19)

syscall 117は ipc ですが、そもそもptraceのシステムコールアーキテクチャ依存でユーザーモードqemuではサポートされないので、このクロスのdocker環境ではgdbは使用できないですね。

Dockerをソースからビルドしてみた

昨日に続いてDockerの話。Dockerに関しては驚くことばかりです。検索するとほぼ一年前に盛り上がっていたようですね。周回遅れです。

DockerはGo言語で書かれていてソースコードGithubで公開されています。
GitHub - moby/moby: Moby Project - a collaborative project for the container ecosystem to assemble container-based systems
このソースをとってきてビルドしてみました。

$ git clone https://github.com/dotcloud/docker
$ git checkout -b work v1.0.1

ビルドの方法がすぐに見つけられなかったので、とりあえずMakefileがあることだしmakeコマンドを実行してみました。

$ make

え?!え?!何が起こった?
いきなりdockerが起動してコンテナをビルドし始めました。ubuntu 14.04を取ってきて、apt-getでツールチェインをインストールして、goのソースを取ってきてそれをビルドして ...
そのビルドログ
そして最後にはたったひとつの実行ファイルがごろんとできていました。それ以外の中間ファイルが全くないのが不思議な感じです。全てのビルドはコンテナの中で行われたからです。

$ ls -l bundles/1.0.1/binary/
 total 17132
 lrwxrwxrwx 1 root root       12 Jun 26 22:24 docker -> docker-1.0.1
 -rwxr-xr-x 1 root root 17533452 Jun 26 22:24 docker-1.0.1
 -rw-r--r-- 1 root root       47 Jun 26 22:24 docker-1.0.1.md5
 -rw-r--r-- 1 root root       79 Jun 26 22:24 docker-1.0.1.sha256
$ file bundles/1.0.1/binary/*
bundles/1.0.1/binary/docker:              symbolic link to `docker-1.0.1'
bundles/1.0.1/binary/docker-1.0.1:        ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=0xe58e81b17140670d7a3228ea83ee3861fb87cd0b, not stripped
bundles/1.0.1/binary/docker-1.0.1.md5:    ASCII text
bundles/1.0.1/binary/docker-1.0.1.sha256: ASCII text

これはどうしたらいいのかな?無謀にも現在の/usr/bin/docker.ioに差し替えて動かしてみました。

$ sudo service docker.io stop
$ sudo mv /usr/bin/docker.io /usr/bin/docker.io.org
$ sudo cp bundles/1.0.1/binary/docker-1.0.1 /usr/bin/docker.io
$ sudo service docker.io start

バージョンを確認。

$ docker version
Client version: 1.0.1
Client API version: 1.12
Go version (client): go1.2.1
Git commit (client): 990021a
Server version: 1.0.1
Server API version: 1.12
Go version (server): go1.2.1
Git commit (server): 990021a

動いているようです。docker run なども問題なく動きました。

試行錯誤してやってみましたが、その後でビルド方法のドキュメントを見つけました。
http://docs.docker.com/contributing/devenvironment/
DockerをビルドするのにまずDockerをインストールしろと書いてあります。
不思議な話ですが、これによって誰がビルドしてもOSとかツールチェインとかライブラリのバージョンの違いによるトラブルが発生することがありません。これからのOSSのソースリポジトリにはそのビルド環境構築のためのDockerfileがついてくるのが当たり前になるかもしれません。

ちなみに初回のビルドにはビルド用のコンテナの作成のためにそれなりの時間がかかりましたが、2回目以降はコンテナの作成がうまい具合にcacheされるのであっという間にビルドが完了します。
2回目のビルドのログ
apt-get install とかgit cloneとかの実行がなんでcacheできるのかとても不思議で魔法のようです。

なお、make cross とすると、linux/amd64の他に以下の7種類のdockerがビルドされます。

そのうちlinux/armのdockerを試してみよう。

Dockerでダウンロードしたファイルはどこに置かれるのか

昨日に続いてDockerをさわってみた話です。

docker run ... で自動的にルートファイルシステムがダウンロードされますが、それらのファイルはいったいどこに置かれたのでしょうか?
カレントディレクトリには何も新しいファイルはできていません。ダウンロードしたのはubuntubusyboxのルートファイルシステムなので、それらしい名前で新しくファイルができていないか探したのですが見つかりません。
それでもうまく動いているのですから気にしなくてもいいとも言えるのですが、私としてはわからないのは気持ちが悪い。少なくともギガバイトのサイズをダウンロードしたはずなのに、いったいどこに消えてしまったのか?

ようやく見つけたヒントがこれ。
コンテナの中のプロセスの/proc/self/mountinfo を見るとそれらしいディレクトリが見つかりました。

$ docker run -it busybox cat /proc/self/mountinfo
81 48 0:37 / / rw,relatime - aufs none rw,si=dd4c1e0e1d1a2e1c
82 81 0:39 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
83 81 0:40 / /sys ro,relatime - sysfs sysfs ro
84 81 0:41 / /dev rw,nosuid - tmpfs tmpfs rw,mode=755
85 84 0:42 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k
86 84 0:43 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
87 81 8:1 /var/lib/docker/init/dockerinit-1.0.1-dev /.dockerinit ro,relatime - ext4 /dev/disk/by-uuid/29ad72a0-27da-42e3-9f5f-3a13990ab837 rw,errors=remount-ro,data=ordered
88 81 0:16 /resolvconf/resolv.conf /etc/resolv.conf ro,relatime - tmpfs tmpfs rw,size=404108k,mode=755
89 81 8:1 /var/lib/docker/containers/d37d6c8e7de990094775c91dc1832f9ed30d10a0a554557ccb487263282a59cd/hostname /etc/hostname ro,relatime - ext4 /dev/disk/by-uuid/29ad72a0-27da-42e3-9f5f-3a13990ab837 rw,errors=remount-ro,data=ordered
90 81 8:1 /var/lib/docker/containers/d37d6c8e7de990094775c91dc1832f9ed30d10a0a554557ccb487263282a59cd/hosts /etc/hosts ro,relatime - ext4 /dev/disk/by-uuid/29ad72a0-27da-42e3-9f5f-3a13990ab837 rw,errors=remount-ro,data=ordered
91 84 0:12 /5 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
50 82 0:39 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw
52 82 0:39 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw
53 82 0:39 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw
54 82 0:39 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw
55 82 0:41 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,mode=755

/var/lib/dockerを調べてみます。

$ ls -l /var/lib/docker
ls: cannot open directory /var/lib/docker: Permission denied
$ ls -ld /var/lib/docker
drwx------ 9 root root 4096 Jun 25 22:59 /var/lib/docker

このディレクトリはrootにしかアクセス権限がありません。

$ sudo bash
[sudo] password for koba: 
# cd /var/lib/docker
# ls
aufs  containers  execdriver  graph  init  linkgraph.db  repositories-aufs  vfs  volumes
# du -s -h *
3.6G	aufs
4.0M	containers
8.0K	execdriver
808K	graph
25M	init
16K	linkgraph.db
4.0K	repositories-aufs
8.0K	vfs
8.0K	volumes
#

見つけました。ギガバイトサイズのルートファイルシステムはaufsのフォーマットで格納されていました。

補足

先ほどの/proc/self/mountinfoを見ると、/.dockerinit, /etc/resolv.conf, /etc/hostname, /etc/hostsなどは単一のファイルがmountされています。
てっきりmountはディレクトリに対して行うものだとばかり思っていましたが、ファイル単位でmountもできるんですね。

追記(2014.0626): /var/lib/docker を移動する

/var/lib のディスクの容量にそんなに余裕がないと思ったので、別のディスクにある/proj の下に引っ越しました。
以下のようにしたらできました。
root権限のshellで

# mkdir -p /proj/var/lib
# service docker stop
# mv /var/lib/docker /proj/var/lib/
# cd /var/lib/
# mv docker docker.org
# ln -s /proj/var/lib/dorcker .
# service docker start

(Ubuntu 14.04の場合はサービス名をdocker.ioに変更。)