LoCの計測で一番簡単だと思われるのは次のような方法である。
find ./src -type f -name '*.c' -o -name '*.h' | xargs wc -l
この例では ./src
ディレクトリに含まれる C 言語のソースファイルとヘッダファイルについて全て wc(1)
に食わせて行数を出力する,といったことを行なっている。
しかしこれでは,プロジェクトで利用する言語が増える度に find(1)
のオプションを調整しなければならないし,コード内の空行やコメント行を全く考慮しない。
cloc
そこで,実は cloc
という便利な LoC 計測ツールがあるらしい。https://github.com/AlDanial/cloc
このコマンドでは,単純にプロジェクトのディレクトリを引数として渡すと,どういう言語がどのくらい使われており,それぞれに含まれる空行とコメントはどのぐらいか,といったことを一覧表示してくれる。
しかし,私はLinux kernelにおける各サブシステムがコード全体のうちどのぐらいを占めているかを知りたかったため,次のように cloc
を実行した。
cd /usr/src/linux/
for dir in $(find . -maxdepth 1 -type d ! \( -name '.*' -o -name 'Documentation' -o -name 'LICENSES' -o -name 'samples' -o -name 'scripts' -o -name 'tools' \)); do
echo $dir
cloc $dir
done > ~/linux_loc.txt
すると linux_loc.txt
には次のような出力が得られる。
./arch
16005 text files.
15902 unique files.
4299 files ignored.
github.com/AlDanial/cloc v 1.72 T=28.79 s (406.7 files/s, 90104.3 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
C 4725 226114 234741 1106210
C/C++ Header 5078 82383 114960 432840
Assembly 1245 44995 98313 220827
make 591 3298 3271 12514
Perl 7 1187 959 7692
Bourne Shell 55 398 621 2090
awk 5 51 58 486
Python 1 7 12 46
sed 1 2 5 5
-------------------------------------------------------------------------------
SUM: 11708 358435 452940 1782710
-------------------------------------------------------------------------------
./block
94 text files.
94 unique files.
3 files ignored.
...
さて,欲しいのは各サブシステムのコードが全体に占める割合であるため,次のようにして各サブシステムのコード行数を表にしてしまう。
dirs=$(find . -maxdepth 1 -type d ! \( -name '.*' -o -name 'Documentation' -o -name 'LICENSES' -o -name 'samples' -o -name 'scripts' -o -name 'tools' \))
for i in $(grep SUM ~/linux_loc.txt | awk '{print $5}'); do
echo -e "${dirs[$count]}\t$i"
set count (expr $count + 1)
done | sort -r -n -k 2,2 > loc.txt
これで loc.txt
には次のような出力が得られるはずである。
./drivers 13048806
./arch 1782710
./fs 967163
./sound 935637
./net 828886
./include 644501
./kernel 237713
./lib 131807
./mm 91805
./crypto 83988
./security 67041
./block 37711
./ipc 6689
./virt 5234
./init 3309
./usr 867
./certs 442
最後にこの TSV から集計と割合の計算を行う。そう,こういう時役立つのは awk
言語だ。
LOC_percentage=$(awk 'NR==FNR{sum=sum+$2;next}{percentage=$2/sum*100; print $0, "\t", percentage"%"}' loc.txt loc.txt)
LOC_total=$(awk '{sum+=$2} END {print "total\t",sum}' loc.txt)
echo "$LOC_percentage" > loc.txt
echo "$LOC_total" >> loc.txt
NR
は行数だが,FNR
はファイル単位の行数である。ここでは loc.txt
を二回食わせているため,一度目では NR
と FNR
が一致するが,二度目では FNR
はまた 1 からカウントアップされるが NR
は一度目の数を引き継ぐため,一致しなくなる。
そこで,一度目のループでのみ各ディレクトリのコード行数を足し合わせており,また,next
によってその次の {}
で囲まれているコードは実行しない。
二度目のループでは二つ目の {}
の中身のみを実行するため,一度目のループで取得した総和に対して各ディレクトリのコード行数の割合を計算する,といった具合になる。
最後に,Linux 5.8 に対してこれを行なって,得られた結果は次の通りとなる。
./drivers 13048806 69.1353%
./arch 1782710 9.44517%
./fs 967163 5.12423%
./sound 935637 4.9572%
./net 828886 4.39161%
./include 644501 3.4147%
./kernel 237713 1.25945%
./lib 131807 0.698341%
./mm 91805 0.486402%
./crypto 83988 0.444986%
./security 67041 0.355197%
./block 37711 0.199801%
./ipc 6689 0.0354397%
./virt 5234 0.0277308%
./init 3309 0.0175318%
./usr 867 0.00459355%
./certs 442 0.00234181%
total l18874309
コードの実に 8 割が drivers/
と arch/
で占められ,次点に fs/
,sound/
,net/
... と続く。
ちなみに同様のことを OSv
に対して実行するとこのようになる。
./modules 421847 51.0683%
./bsd 251317 30.4241%
./musl 68332 8.27219%
./include 22400 2.71172%
./drivers 15663 1.89614%
./core 12595 1.52474%
./libc 11885 1.43878%
./fs 9164 1.10938%
./arch 9136 1.10599%
./ 3166 0.383272%
./fastlz 526 0.0636769%
./compiler 14 0.00169482%
total 826045
最大の行数を誇るのは modules/
であるが,この部分は CLI や RESTful API server,Java VM のバルーニング実装に libz や ncurses,openssl といったものが並ぶ。通常の OS であればユーザーランドに置かれるものであり,OSv
のビルドや実行にも必ずしも含まれると限らない。
bsd/
には主に FreeBSD 由来であるネットワークスタックと ZFS のコードを含み,カーネルとしては実質この部分が最大になる。次に musl-libc の移植コードである musl/
が続き,それから以降は OSv の独自実装が主となっていく。
また,./
となっているのは OSv
の場合トップディレクトリにもいくつか重要なコードがあるためで,これは bootfs.S, dummy-shlib.cc, empty_bootfs.S, gen-ctype-data.cc, linux.cc, loader.cc, Makefile, runtime.cc
の 8 つのファイルを集計したものとなる。
OSv
は unikernel としては実装が大きいほうだが,それでも全体で 8 万行強であり,コア部分だけみるとたかだか 1 万行になっており,Linux に比べればカーネルの実装全体に目が通しやすい。