コードのデバッグ#
プリントデバッグ法とログ#
- ログをファイル、ソケット、またはリモートサーバーに送信することができ、標準出力だけに限りません;
- ログは重大度レベル(例:INFO、DEBUG、WARN、ERROR など)をサポートしており、必要に応じてログをフィルタリングできます;
- 新たに発見された問題については、ログに問題の特定に役立つ十分な情報が含まれている可能性が高いです。
サードパーティのログシステム#
- Unix
/var/log
dmesg
- Linux
systemd
/var/log/journal
journalctl
- MacOS
/var/log/system.log
log show
logger
システムログにログを書き込む
logger "Hello Logs"
# macOSで
log show --last 1m | grep Hello
# Linuxで
journalctl --since "1m ago" | grep Hello
lnav
ログビューア、より良い表示とブラウジング方法
デバッガ#
ipdb
拡張型 pdb
Python デバッガ
- l(ist) - 現在の行の近くの 11 行を表示するか、以前の表示を続行します;
- s(tep) - 現在の行を実行し、最初の可能な場所で停止します;
- n(ext) - 現在の関数の次の文または return 文まで実行を続けます;
- b(reak) - ブレークポイントを設定します(渡された引数に基づいて);
- p(rint) - 現在のコンテキストで式を評価し、結果を印刷します。もう一つのコマンドはppで、
pprint
を使用して印刷します; - r(eturn) - 現在の関数が返るまで実行を続けます;
- q(uit) - デバッガを終了します。
より低レベルのプログラミング言語については、gdb
(およびその改良版pwndbg
)やlldb
について知っておく必要があります。
これらはすべて C 言語系のデバッグに最適化されており、任意のプロセスとそのマシン状態(レジスタ、スタック、プログラムカウンタなど)を探索することを可能にします。
専用ツール#
プログラムの実行を追跡するシステムコール
- Linux
strace
- MacOS BSD
dtrace
、dtruss
でラップしてstrace
に似たインターフェースを持たせます
# Linuxで
sudo strace -e lstat ls -l > /dev/null
4
# macOSで
sudo dtruss -t lstat64_extended ls -l > /dev/null
ネットワークパケット分析ツール: tcpdump Wireshark
Chrome/Firefox の開発者ツール
- ソースコード - 任意のサイトの HTML/CSS/JS ソースコードを表示;
- HTML、CSS、JS コードをリアルタイムで変更 - ウェブサイトの内容、スタイル、動作をテストのために変更(この点からも、ウェブページのスクリーンショットは信頼できないことがわかります);
- Javascript シェル - JS REPL でコマンドを実行;
- ネットワーク - リクエストのタイムラインを分析;
- ストレージ - Cookies とローカルアプリケーションストレージを表示。
静的分析#
プログラムのソースコードを入力として、コーディングルールに基づいて分析し、コードの正確性を推論します。
Python: pyflakes
mypy
シェルスクリプト:shellcheck
コードリント スタイルチェックまたはセキュリティチェック
Vim: ale
syntastic
Python: pylint
pep8
スタイルチェック bandit
セキュリティチェック
他の言語の開発者向けには、静的分析ツールのリストを参考にできます:Awesome Static Analysis(_Writing_セクションに興味があるかもしれません)。リンターについてはこのリストを参考にしてください:Awesome Linters。
パフォーマンス分析#
タイミング#
- 実時間 - プログラムの開始から終了までの実際の時間、他のプロセスの実行時間やブロッキングにかかる時間(例:I/O やネットワーク待機を含む);
- User - CPU がユーザーコードを実行するのにかかる時間;
- Sys - CPU がシステムカーネルコードを実行するのにかかる時間。
$ time curl https://missing.csail.mit.edu &> /dev/null
real 0m2.561s
user 0m0.015s
sys 0m0.012s
パフォーマンス分析ツール(プロファイラ)#
CPU#
CPU パフォーマンス分析ツールには、トレースアナライザ(tracing)とサンプリングアナライザ(sampling)の 2 種類があります。トレースアナライザはプログラムの各関数呼び出しを記録し、サンプリングアナライザは通常は毎ミリ秒ごとにプログラムを監視し、プログラムスタックを記録します。
メモリ#
C や C++ のような言語では、メモリリークが発生すると、プログラムがメモリを使い終わった後にそれを解放しません。メモリ関連のバグに対処するために、Valgrindのようなツールを使用してメモリリークの問題をチェックできます。
Python のようなガーベジコレクション機構を持つ言語では、メモリアナライザも非常に役立ちます。なぜなら、あるオブジェクトに対してポインタがまだ指している限り、それは回収されないからです。
イベント分析#
strace
を使用してコードをデバッグしているとき、特定のコードを無視したい場合や、分析時にそれをブラックボックスとして扱いたい場合があります。perf
コマンドは CPU の違いを抽象化し、時間やメモリの消費を報告するのではなく、プログラムに関連するシステムイベントを報告します。
例えば、perf
は悪いキャッシュ局所性(poor cache locality)、大量のページフォールト(page faults)やライブロック(livelocks)を報告できます。以下は一般的なコマンドの概要です:
perf list
- pref が追跡できるイベントをリストします;perf stat COMMAND ARG1 ARG2
- 特定のプロセスや命令に関連するイベントを収集します;perf record COMMAND ARG1 ARG2
- コマンド実行のサンプリング情報を記録し、統計データをperf.data
に保存します;perf report
-perf.data
のデータをフォーマットして印刷します。
可視化#
実際のプログラムを分析するためにアナライザを使用すると、ソフトウェアの複雑性により、その出力結果には大量の情報が含まれます。人間は視覚的な生き物であり、大量のテキストを読むのが得意ではありません。そのため、多くのツールは可視化されたアナライザ出力結果の機能を提供しています。
サンプリングアナライザの場合、CPU 分析データを表示する一般的な形式はフレームグラフです。フレームグラフは Y 軸に関数呼び出しの関係を表示し、X 軸にその時間の割合を表示します。フレームグラフはインタラクティブでもあり、プログラムの特定の部分に深く入り込み、そのスタックトレースを確認できます。
呼び出しグラフと制御フローグラフは、サブプログラム間の関係を表示します。関数をノードとして、関数呼び出しをエッジとして表示します。これらをアナライザの情報(例えば呼び出し回数、時間など)と組み合わせて使用すると、呼び出しグラフは非常に役立ちます。Python では、pycallgraph
を使用してこれらの画像を生成できます。
リソース監視#
- 一般的な監視 - 最も人気のあるツールは
htop
で、これはtop
の改良版です。htop
は現在実行中のプロセスのさまざまな統計情報を表示できます。htop
には多くのオプションとショートカットキーがあり、一般的なものには:<F6>
プロセスのソート、t
ツリー構造の表示、h
スレッドの開閉があります。glances
も注目に値します。これは実装が似ていますが、ユーザーインターフェースがより良好です。すべてのプロセスの測定を統合する必要がある場合、dstat
も非常に便利なツールで、I/O、ネットワーク、CPU 使用率、コンテキストスイッチなど、さまざまなサブシステムリソースの測定データをリアルタイムで計算できます; - I/O 操作 -
iotop
はリアルタイムの I/O 使用情報を表示し、特定のプロセスが大量のディスク読み書きを実行しているかどうかを簡単に確認できます; - ディスク使用 -
df
は各パーティションの情報を表示し、du
は現在のディレクトリ内の各ファイルのディスク使用状況(disk usage)を表示します。-h
オプションを使用すると、コマンドが人間(human)により優しい形式でデータを表示します;ncdu
はよりインタラクティブなdu
で、異なるディレクトリをナビゲートしたり、ファイルやフォルダを削除したりできます; - メモリ使用 -
free
はシステムの現在の空きメモリを表示します。メモリもhtop
のようなツールを使用して表示できます; - オープンファイル -
lsof
はプロセスによってオープンされているファイルの情報をリストします。特定のファイルがどのプロセスによってオープンされているかを確認する必要があるとき、このコマンドは非常に便利です; - ネットワーク接続と設定 -
ss
はネットワークパケットの送受信状況やネットワークインターフェースの表示情報を監視するのに役立ちます。ss
の一般的な使用シーンは、ポートがプロセスによって占有されている情報を見つけることです。ルーティング、ネットワークデバイス、インターフェース情報を表示するには、ip
コマンドを使用できます。注意:netstat
とifconfig
の 2 つのコマンドは、前述のツールに置き換えられました。 - ネットワーク使用 -
nethogs
とiftop
は、ネットワーク使用を監視するための非常に良いインタラクティブコマンドラインツールです。
これらのツールをテストしたい場合、stress
コマンドを使用してシステムに人工的に負荷をかけることができます。
専用ツール#
時には、ブラックボックスプログラムのベンチマークテストを行い、それに基づいてソフトウェアの選択を評価する必要があります。hyperfine
のようなコマンドラインツールは、迅速にベンチマークテストを行うのに役立ちます。
デバッグと同様に、ブラウザにもページの読み込みを分析するための優れたパフォーマンス分析ツールが含まれており、時間がどこに消費されているのか(読み込み、レンダリング、スクリプトなど)を明らかにすることができます。より多くの情報は、FirefoxやChromeのリンクをクリックしてください。
課題#
デバッグ#
-
Linux の
journalctl
または macOS のlog show
コマンドを使用して、最近 1 日間のスーパーユーザーのログイン情報と実行したコマンドを取得します。関連情報が見つからない場合は、無害なコマンド(例:sudo ls
)を実行してから再度確認してください。 -
shellcheck
をインストールし、以下のスクリプトをチェックしてみてください。このコードには何か問題がありますか?関連する問題を修正してください。エディタにリンタープラグインをインストールして、自動的に関連する警告情報を表示できるようにします。#!/bin/sh ## 例:いくつかの問題を持つ典型的なスクリプト for f in $(ls *.m3u) do grep -qi hq.*mp3 $f \ && echo -e 'Playlist $f contains a HQ file in mp3 format' done
パフォーマンス分析#
-
ここにいくつかのソートアルゴリズムの実装があります。
cProfile
とline_profiler
を使用して、挿入ソートとクイックソートのパフォーマンスを比較してください。2 つのアルゴリズムのボトルネックはそれぞれどこですか?次に、memory_profiler
を使用してメモリ消費をチェックし、なぜ挿入ソートがより良いのかを考えてみてください。その後、インプレースソートバージョンのクイックソートを確認してください。追加の質問:perf
を使用して異なるアルゴリズムのループ回数とキャッシュヒット・ミスの状況を確認してください。 -
ここにフィボナッチ数列の計算のための Python コードがあります。各数字を計算するために関数を定義しています:
#!/usr/bin/env python def fib0(): return 0 def fib1(): return 1 s = """def fib{}(): return fib{}() + fib{}()""" if __name__ == '__main__': for n in range(2, 10): exec(s.format(n, n-1, n-2)) # from functools import lru_cache # for n in range(10): # exec("fib{} = lru_cache(1)(fib{})".format(n, n)) print(eval("fib9()"))
コードをファイルにコピーして実行可能なプログラムにします。まず、
pycallgraph
とgraphviz
をインストールします(dot
を実行できれば、GraphViz がインストールされています)。そして、pycallgraph graphviz -- ./fib.py
を使用してコードを実行し、pycallgraph.png
ファイルを確認します。fib0
は何回呼び出されましたか?メモ化を使用して最適化できます。コメントアウトされている部分を有効にして、再度画像を生成してください。今度は各fibN
関数は何回呼び出されましたか? -
よくある状況として、リスニングしたいポートが他のプロセスによって占有されていることがあります。プロセスの PID を使用して関連するプロセスを見つけましょう。まず、
python -m http.server 4444
を実行して、最もシンプルなウェブサーバーを起動し、4444
ポートをリスニングします。別のターミナルで、lsof | grep LISTEN
を実行して、すべてのリスニングポートのプロセスと関連するポートを表示します。対応する PID を見つけて、kill <PID>
を使用してそのプロセスを停止します。 -
プロセスリソースの制限も非常に有用な技術です。
stress -c 3
を実行し、htop
を使用して CPU 消費を可視化します。次に、taskset --cpu-list 0,2 stress -c 3
を実行し、可視化します。stress
は 3 つの CPU を占有していますか?なぜそうならないのでしょうか?man taskset
を読んで答えを探してください。追加の質問:cgroups
を使用して同じ操作を実現し、stress -m
のメモリ使用を制限します。 -
(上級問題)
curl ipinfo.io
コマンドを実行して、IP に関する情報を取得します。curl
が発起したリクエストと受信した応答メッセージをキャプチャするために、Wiresharkを開きます。(ヒント:http
を使用してフィルタリングし、HTTP メッセージのみを表示できます)