Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
$ git diff test.perf.1..test.perf.2
diff --git a/doc/developer-notes.md b/doc/developer-notes.md
index a6df17ddd..a952eeed7 100644
--- a/doc/developer-notes.md
+++ b/doc/developer-notes.md
@@ -257,6 +257,48 @@ make cov
# A coverage report will now be accessible at `./test_bitcoin.coverage/index.html`.
```
+### Performance profiling with perf
+
+Profiling is a good way to get a precise idea of where time is being spent in
+code. One tool for doing profiling on Linux platforms is called
+[`perf`](http://www.brendangregg.com/perf.html), and has been integrated into
+the functional test framework. Perf can observe a running process and sample
+(at some frequency) where its execution is.
+
+Perf installation is contingent on which kernel version you're running; see
+[this StackExchange
+thread](https://askubuntu.com/questions/50145/how-to-install-perf-monitoring-tool)
+for specific instructions.
+
+Certain kernel parameters may need to be set for perf to be able to inspect the
+running process' stack.
+
+```sh
+$ sudo sysctl -w kernel.perf_event_paranoid=-1
+$ echo 1 | sudo tee /proc/sys/kernel/kptr_restrict
+```
+
+To profile a running bitcoind process for 60 seconds, you could use an
+invocation of `perf record` like this:
+
+```sh
+$ perf record \
+ -g --call-graph dwarf --per-thread -F 140 \
+ -p `pgrep bitcoind` -- sleep 60
+```
+
+You could then analyze the results by running
+
+```sh
+perf report --stdio | c++filt | less`
+```
+
+or using a graphical tool like [Hotspot](https://github.com/KDAB/hotspot).
+
+See the [test documentation](./test/functional#benchmarking-with-perf) for how
+to invoke perf within a functional test.
+
+
**Sanitizers**
Bitcoin Core can be compiled with various "sanitizers" enabled, which add
diff --git a/test/functional/README.md b/test/functional/README.md
index d40052ac9..7f39439d3 100644
--- a/test/functional/README.md
+++ b/test/functional/README.md
@@ -118,3 +118,36 @@ Helpers for script.py
#### [test_framework/blocktools.py](test_framework/blocktools.py)
Helper functions for creating blocks and transactions.
+
+### Benchmarking with perf
+
+An easy way to profile node performance during functional tests is provided
+for Linux platforms using `perf`.
+
+Perf will sample the running node and will generate profile data in the node's
+datadir. The profile data can then be presented using `perf report` or a graphical
+tool like `hotspot`<https://github.com/KDAB/hotspot>.
+
+There are two ways of invoking perf: one is to use the `--perf` flag when
+running tests, which will profile each node during the entire test run: perf
+begins to profile when the node starts and ends when it shuts down. The other
+way is the use the `profile_with_perf` context manager, e.g.
+
+```python
+with node.profile_with_perf("send-big-msgs"):
+ # Perform activity on the node you're interested in profiling, e.g.:
+ for _ in range(10000):
+ node.p2p.send_message(some_large_message)
+```
+
+To see useful textual output, run
+
+```sh
+perf report -i /path/to/datadir/send-big-msgs.perf.data.xxxx --stdio | c++filt | less`
+```
+
+#### See also:
+
+- [Installing perf](https://askubuntu.com/q/50145)
+- [Perf examples](http://www.brendangregg.com/perf.html)
+- [Hotspot](https://github.com/KDAB/hotspot): a GUI for perf output analysis
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 3fa3bb348..bd5be4ea2 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -19,6 +19,7 @@ import time
import urllib.parse
import collections
import shlex
+import sys
from .authproxy import JSONRPCException
from .util import (
@@ -339,30 +340,11 @@ class TestNode():
"""
Context manager that allows easy profiling of node activity using `perf`.
- Perf will sample the running node and will generate profile data in the node's
- datadir. The profile data can then be presented using `perf report` or a graphical
- tool like `hotspot`<https://github.com/KDAB/hotspot>.
-
- To see useful textual output, run
-
- perf report -i /path/to/datadir/node-0.perf.data --stdio | c++filt | less
-
- See also:
-
- Installing perf: https://askubuntu.com/q/50145
- Perf examples: http://www.brendangregg.com/perf.html
+ See `test/functional/README.md` for details on perf usage.
Args:
-
profile_name (str): This string will be appended to the
profile data filename generated by perf.
-
- Usage:
-
- with node.profile_with_perf("send-big-msgs"):
- # Perform activity on the node you're interested in profiling, e.g.:
- for _ in range(10000):
- node.p2p.send_message(some_large_message)
"""
subp = self._start_perf(profile_name)
@@ -374,8 +356,7 @@ class TestNode():
def _start_perf(self, profile_name):
"""Start a perf process to profile this node.
- Returns the subprocess running perf.
- """
+ Returns the subprocess running perf."""
subp = None
def test_success(cmd):
@@ -384,25 +365,34 @@ class TestNode():
cmd, shell=True,
stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) == 0
+ if not sys.platform.startswith('linux'):
+ self.log.warning("Can't profile with perf; only availabe on Linux platforms")
+ return None
+
+ if not test_success('which perf'):
+ self.log.warning("Can't profile with perf; must install perf-tools")
+ return None
+
if not test_success('readelf -S {} | grep .debug_str'.format(shlex.quote(self.binary))):
self.log.warning(
"perf output won't be very useful without debug symbols compiled into bitcoind")
- if not test_success('which perf'):
- self.log.warning("Can't profile with perf; must install perf-tools")
- else:
- output_path = self._get_unique_perf_data_path(profile_name)
-
- cmd = [
- 'perf', 'record',
- '-g', # Record the callgraph.
- '--call-graph', 'dwarf', # Compatibility for gcc's --fomit-frame-pointer.
- '-F', '101', # Sampling frequency in Hz.
- '-p', str(self.process.pid),
- '-o', output_path,
- ]
- subp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- self.perf_subprocesses[profile_name] = subp
+ output_path = tempfile.NamedTemporaryFile(
+ dir=self.datadir,
+ prefix="{}.perf.data.".format(profile_name or 'test'),
+ delete=False,
+ ).name
+
+ cmd = [
+ 'perf', 'record',
+ '-g', # Record the callgraph.
+ '--call-graph', 'dwarf', # Compatibility for gcc's --fomit-frame-pointer.
+ '-F', '101', # Sampling frequency in Hz.
+ '-p', str(self.process.pid),
+ '-o', output_path,
+ ]
+ subp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ self.perf_subprocesses[profile_name] = subp
return subp
@@ -412,7 +402,7 @@ class TestNode():
output_path = subp.args[subp.args.index('-o') + 1]
subp.terminate()
- subp.wait()
+ subp.wait(timeout=10)
stderr = subp.stderr.read().decode()
if 'Consider tweaking /proc/sys/kernel/perf_event_paranoid' in stderr:
@@ -420,30 +410,8 @@ class TestNode():
"perf couldn't collect data! Try "
"'sudo sysctl -w kernel.perf_event_paranoid=-1'")
else:
- self.log.info("Wrote perf output to '{}'".format(output_path))
-
- def _get_unique_perf_data_path(self, profile_name):
- """Return a suitable path for perf data given some descriptive name.
-
- If a file exists with the proposed path, append a number to the filename until
- we find an unused path."""
- def find_unused_path(unique_suffix=""):
- suffix = "{}.".format(profile_name) if profile_name else ""
- path = os.path.join(self.datadir, "{}{}perf.data".format(
- suffix, unique_suffix))
- return None if os.path.exists(path) else path
-
- unused_path = find_unused_path()
- i = 1
-
- # If we've already saved profiling data with the same profile_name
- # (e.g. if the node has restarted and is profiling again), then find a
- # unique suffix for the new data path.
- while not unused_path:
- unused_path = find_unused_path("{}.".format(i))
- i += 1
-
- return unused_path
+ report_cmd = "perf report -i {}".format(output_path)
+ self.log.info("See perf output by running '{}'".format(report_cmd))
def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, match=ErrorMatch.FULL_TEXT, *args, **kwargs):
"""Attempt to start the node and expect it to raise an error.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.