From: John Snow <jsnow@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Kevin Wolf" <kwolf@redhat.com>,
"Michael Roth" <michael.roth@amd.com>,
"John Snow" <jsnow@redhat.com>, "Hanna Reitz" <hreitz@redhat.com>,
"Peter Maydell" <peter.maydell@linaro.org>,
"Cleber Rosa" <crosa@redhat.com>,
qemu-block@nongnu.org, "Markus Armbruster" <armbru@redhat.com>,
"Daniel P. Berrangé" <berrange@redhat.com>
Subject: [PULL 14/19] python: synchronize qemu.qmp documentation
Date: Tue, 16 Sep 2025 12:23:59 -0400 [thread overview]
Message-ID: <20250916162404.9195-15-jsnow@redhat.com> (raw)
In-Reply-To: <20250916162404.9195-1-jsnow@redhat.com>
This patch collects comments and documentation changes from many commits
in the python-qemu-qmp repository; bringing the qemu.git copy in
bit-identical alignment with the standalone library *except* for several
copyright messages that reference the "LICENSE" file which is, for QEMU,
named "COPYING" instead and are therefore left unchanged.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
---
python/qemu/qmp/__init__.py | 3 +-
python/qemu/qmp/events.py | 35 +++++++---
python/qemu/qmp/legacy.py | 4 +-
python/qemu/qmp/message.py | 10 ++-
python/qemu/qmp/models.py | 8 +--
python/qemu/qmp/protocol.py | 37 ++++++----
python/qemu/qmp/qmp_client.py | 117 +++++++++++++++++++++++--------
python/qemu/qmp/qmp_shell.py | 128 ++++++++++++++++++++++++++--------
python/qemu/qmp/util.py | 9 ++-
9 files changed, 264 insertions(+), 87 deletions(-)
diff --git a/python/qemu/qmp/__init__.py b/python/qemu/qmp/__init__.py
index 69190d057a5..058139dc3ca 100644
--- a/python/qemu/qmp/__init__.py
+++ b/python/qemu/qmp/__init__.py
@@ -39,7 +39,8 @@
logging.getLogger('qemu.qmp').addHandler(logging.NullHandler())
-# The order of these fields impact the Sphinx documentation order.
+# IMPORTANT: When modifying this list, update the Sphinx overview docs.
+# Anything visible in the qemu.qmp namespace should be on the overview page.
__all__ = (
# Classes, most to least important
'QMPClient',
diff --git a/python/qemu/qmp/events.py b/python/qemu/qmp/events.py
index 66583496192..cfb5f0ac621 100644
--- a/python/qemu/qmp/events.py
+++ b/python/qemu/qmp/events.py
@@ -12,7 +12,14 @@
----------------------
In all of the following examples, we assume that we have a `QMPClient`
-instantiated named ``qmp`` that is already connected.
+instantiated named ``qmp`` that is already connected. For example:
+
+.. code:: python
+
+ from qemu.qmp import QMPClient
+
+ qmp = QMPClient('example-vm')
+ await qmp.connect('127.0.0.1', 1234)
`listener()` context blocks with one name
@@ -87,7 +94,9 @@
event = listener.get()
print(f"Event arrived: {event['event']}")
-This event stream will never end, so these blocks will never terminate.
+This event stream will never end, so these blocks will never
+terminate. Even if the QMP connection errors out prematurely, this
+listener will go silent without raising an error.
Using asyncio.Task to concurrently retrieve events
@@ -227,16 +236,20 @@ async def print_events(listener):
.. code:: python
await qmp.execute('stop')
- qmp.events.clear()
+ discarded = qmp.events.clear()
await qmp.execute('cont')
event = await qmp.events.get()
assert event['event'] == 'RESUME'
+ assert discarded[0]['event'] == 'STOP'
`EventListener` objects are FIFO queues. If events are not consumed,
they will remain in the queue until they are witnessed or discarded via
`clear()`. FIFO queues will be drained automatically upon leaving a
context block, or when calling `remove_listener()`.
+Any events removed from the queue in this fashion will be returned by
+the clear call.
+
Accessing listener history
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -350,6 +363,12 @@ def filter(event: Message) -> bool:
break
+Note that in the above example, we explicitly wait on jobA to conclude
+first, and then wait for jobB to do the same. All we have guaranteed is
+that the code that waits for jobA will not accidentally consume the
+event intended for the jobB waiter.
+
+
Extending the `EventListener` class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -407,13 +426,13 @@ def accept(self, event) -> bool:
These interfaces are not ones I am sure I will keep or otherwise modify
heavily.
-qmp.listener()’s type signature
+qmp.listen()’s type signature
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-`listener()` does not return anything, because it was assumed the caller
+`listen()` does not return anything, because it was assumed the caller
already had a handle to the listener. However, for
-``qmp.listener(EventListener())`` forms, the caller will not have saved
-a handle to the listener.
+``qmp.listen(EventListener())`` forms, the caller will not have saved a
+handle to the listener.
Because this function can accept *many* listeners, I found it hard to
accurately type in a way where it could be used in both “one” or “many”
@@ -633,7 +652,7 @@ class Events:
def __init__(self) -> None:
self._listeners: List[EventListener] = []
- #: Default, all-events `EventListener`.
+ #: Default, all-events `EventListener`. See `qmp.events` for more info.
self.events: EventListener = EventListener()
self.register_listener(self.events)
diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py
index e46695ae2c8..060ed0eb9d4 100644
--- a/python/qemu/qmp/legacy.py
+++ b/python/qemu/qmp/legacy.py
@@ -293,8 +293,8 @@ def settimeout(self, timeout: Optional[float]) -> None:
"""
Set the timeout for QMP RPC execution.
- This timeout affects the `cmd`, `cmd_obj`, and `command` methods.
- The `accept`, `pull_event` and `get_event` methods have their
+ This timeout affects the `cmd`, `cmd_obj`, and `cmd_raw` methods.
+ The `accept`, `pull_event` and `get_events` methods have their
own configurable timeouts.
:param timeout:
diff --git a/python/qemu/qmp/message.py b/python/qemu/qmp/message.py
index c2e9dd0dd54..dabb8ec360e 100644
--- a/python/qemu/qmp/message.py
+++ b/python/qemu/qmp/message.py
@@ -28,7 +28,8 @@ class Message(MutableMapping[str, object]):
be instantiated from either another mapping (like a `dict`), or from
raw `bytes` that still need to be deserialized.
- Once instantiated, it may be treated like any other MutableMapping::
+ Once instantiated, it may be treated like any other
+ :py:obj:`~collections.abc.MutableMapping`::
>>> msg = Message(b'{"hello": "world"}')
>>> assert msg['hello'] == 'world'
@@ -50,12 +51,19 @@ class Message(MutableMapping[str, object]):
>>> dict(msg)
{'hello': 'world'}
+ Or pretty-printed::
+
+ >>> print(str(msg))
+ {
+ "hello": "world"
+ }
:param value: Initial value, if any.
:param eager:
When `True`, attempt to serialize or deserialize the initial value
immediately, so that conversion exceptions are raised during
the call to ``__init__()``.
+
"""
# pylint: disable=too-many-ancestors
diff --git a/python/qemu/qmp/models.py b/python/qemu/qmp/models.py
index da52848d5a7..7e0d0baf038 100644
--- a/python/qemu/qmp/models.py
+++ b/python/qemu/qmp/models.py
@@ -54,7 +54,7 @@ def __repr__(self) -> str:
class Greeting(Model):
"""
- Defined in qmp-spec.rst, section "Server Greeting".
+ Defined in `interop/qmp-spec`, "Server Greeting" section.
:param raw: The raw Greeting object.
:raise KeyError: If any required fields are absent.
@@ -82,7 +82,7 @@ def _asdict(self) -> Dict[str, object]:
class QMPGreeting(Model):
"""
- Defined in qmp-spec.rst, section "Server Greeting".
+ Defined in `interop/qmp-spec`, "Server Greeting" section.
:param raw: The raw QMPGreeting object.
:raise KeyError: If any required fields are absent.
@@ -104,7 +104,7 @@ def __init__(self, raw: Mapping[str, Any]):
class ErrorResponse(Model):
"""
- Defined in qmp-spec.rst, section "Error".
+ Defined in `interop/qmp-spec`, "Error" section.
:param raw: The raw ErrorResponse object.
:raise KeyError: If any required fields are absent.
@@ -126,7 +126,7 @@ def __init__(self, raw: Mapping[str, Any]):
class ErrorInfo(Model):
"""
- Defined in qmp-spec.rst, section "Error".
+ Defined in `interop/qmp-spec`, "Error" section.
:param raw: The raw ErrorInfo object.
:raise KeyError: If any required fields are absent.
diff --git a/python/qemu/qmp/protocol.py b/python/qemu/qmp/protocol.py
index 4d8a39f014b..219d092a792 100644
--- a/python/qemu/qmp/protocol.py
+++ b/python/qemu/qmp/protocol.py
@@ -79,6 +79,12 @@ class ConnectError(QMPError):
This Exception always wraps a "root cause" exception that can be
interrogated for additional information.
+ For example, when connecting to a non-existent socket::
+
+ await qmp.connect('not_found.sock')
+ # ConnectError: Failed to establish connection:
+ # [Errno 2] No such file or directory
+
:param error_message: Human-readable string describing the error.
:param exc: The root-cause exception.
"""
@@ -102,8 +108,8 @@ class StateError(QMPError):
An API command (connect, execute, etc) was issued at an inappropriate time.
This error is raised when a command like
- :py:meth:`~AsyncProtocol.connect()` is issued at an inappropriate
- time.
+ :py:meth:`~AsyncProtocol.connect()` is called when the client is
+ already connected.
:param error_message: Human-readable string describing the state violation.
:param state: The actual `Runstate` seen at the time of the violation.
@@ -298,7 +304,7 @@ def runstate(self) -> Runstate:
@upper_half
async def runstate_changed(self) -> Runstate:
"""
- Wait for the `runstate` to change, then return that runstate.
+ Wait for the `runstate` to change, then return that `Runstate`.
"""
await self._runstate_event.wait()
return self.runstate
@@ -312,9 +318,9 @@ async def start_server_and_accept(
"""
Accept a connection and begin processing message queues.
- If this call fails, `runstate` is guaranteed to be set back to `IDLE`.
- This method is precisely equivalent to calling `start_server()`
- followed by `accept()`.
+ If this call fails, `runstate` is guaranteed to be set back to
+ `IDLE`. This method is precisely equivalent to calling
+ `start_server()` followed by :py:meth:`~AsyncProtocol.accept()`.
:param address:
Address to listen on; UNIX socket path or TCP address/port.
@@ -327,7 +333,8 @@ async def start_server_and_accept(
This exception will wrap a more concrete one. In most cases,
the wrapped exception will be `OSError` or `EOFError`. If a
protocol-level failure occurs while establishing a new
- session, the wrapped error may also be an `QMPError`.
+ session, the wrapped error may also be a `QMPError`.
+
"""
await self.start_server(address, ssl)
await self.accept()
@@ -343,8 +350,8 @@ async def start_server(self, address: SocketAddrT,
This method starts listening for an incoming connection, but
does not block waiting for a peer. This call will return
immediately after binding and listening on a socket. A later
- call to `accept()` must be made in order to finalize the
- incoming connection.
+ call to :py:meth:`~AsyncProtocol.accept()` must be made in order
+ to finalize the incoming connection.
:param address:
Address to listen on; UNIX socket path or TCP address/port.
@@ -367,10 +374,12 @@ async def accept(self) -> None:
"""
Accept an incoming connection and begin processing message queues.
- If this call fails, `runstate` is guaranteed to be set back to `IDLE`.
+ Used after a previous call to `start_server()` to accept an
+ incoming connection. If this call fails, `runstate` is
+ guaranteed to be set back to `IDLE`.
:raise StateError: When the `Runstate` is not `CONNECTING`.
- :raise QMPError: When `start_server()` was not called yet.
+ :raise QMPError: When `start_server()` was not called first.
:raise ConnectError:
When a connection or session cannot be established.
@@ -423,7 +432,11 @@ async def disconnect(self) -> None:
If there was an exception that caused the reader/writers to
terminate prematurely, it will be raised here.
- :raise Exception: When the reader or writer terminate unexpectedly.
+ :raise Exception:
+ When the reader or writer terminate unexpectedly. You can
+ expect to see `EOFError` if the server hangs up, or
+ `OSError` for connection-related issues. If there was a QMP
+ protocol-level problem, `ProtocolError` will be seen.
"""
self.logger.debug("disconnect() called.")
self._schedule_disconnect()
diff --git a/python/qemu/qmp/qmp_client.py b/python/qemu/qmp/qmp_client.py
index d826331b6d5..8beccfe29d3 100644
--- a/python/qemu/qmp/qmp_client.py
+++ b/python/qemu/qmp/qmp_client.py
@@ -70,6 +70,17 @@ class ExecuteError(QMPError):
"""
Exception raised by `QMPClient.execute()` on RPC failure.
+ This exception is raised when the server received, interpreted, and
+ replied to a command successfully; but the command itself returned a
+ failure status.
+
+ For example::
+
+ await qmp.execute('block-dirty-bitmap-add',
+ {'node': 'foo', 'name': 'my_bitmap'})
+ # qemu.qmp.qmp_client.ExecuteError:
+ # Cannot find device='foo' nor node-name='foo'
+
:param error_response: The RPC error response object.
:param sent: The sent RPC message that caused the failure.
:param received: The raw RPC error reply received.
@@ -99,9 +110,22 @@ class ExecInterruptedError(QMPError):
This error is raised when an `execute()` statement could not be
completed. This can occur because the connection itself was
- terminated before a reply was received.
+ terminated before a reply was received. The true cause of the
+ interruption will be available via `disconnect()`.
- The true cause of the interruption will be available via `disconnect()`.
+ The QMP protocol does not make it possible to know if a command
+ succeeded or failed after such an event; the client will need to
+ query the server to determine the state of the server on a
+ case-by-case basis.
+
+ For example, ECONNRESET might look like this::
+
+ try:
+ await qmp.execute('query-block')
+ # ExecInterruptedError: Disconnected
+ except ExecInterruptedError:
+ await qmp.disconnect()
+ # ConnectionResetError: [Errno 104] Connection reset by peer
"""
@@ -162,13 +186,14 @@ def __init__(self, error_message: str, msg: Message, sent: Message):
class QMPClient(AsyncProtocol[Message], Events):
- """
- Implements a QMP client connection.
+ """Implements a QMP client connection.
- QMP can be used to establish a connection as either the transport
- client or server, though this class always acts as the QMP client.
+ `QMPClient` can be used to either connect or listen to a QMP server,
+ but always acts as the QMP client.
- :param name: Optional nickname for the connection, used for logging.
+ :param name:
+ Optional nickname for the connection, used to differentiate
+ instances when logging.
:param readbuflen:
The maximum buffer length for reads and writes to and from the QMP
@@ -178,14 +203,21 @@ class QMPClient(AsyncProtocol[Message], Events):
Basic script-style usage looks like this::
- qmp = QMPClient('my_virtual_machine_name')
- await qmp.connect(('127.0.0.1', 1234))
- ...
- res = await qmp.execute('block-query')
- ...
- await qmp.disconnect()
+ import asyncio
+ from qemu.qmp import QMPClient
- Basic async client-style usage looks like this::
+ async def main():
+ qmp = QMPClient('my_virtual_machine_name')
+ await qmp.connect(('127.0.0.1', 1234))
+ ...
+ res = await qmp.execute('query-block')
+ ...
+ await qmp.disconnect()
+
+ asyncio.run(main())
+
+ A more advanced example that starts to take advantage of asyncio
+ might look like this::
class Client:
def __init__(self, name: str):
@@ -205,6 +237,7 @@ async def run(self, address='/tmp/qemu.socket'):
await self.disconnect()
See `qmp.events` for more detail on event handling patterns.
+
"""
#: Logger object used for debugging messages.
logger = logging.getLogger(__name__)
@@ -224,10 +257,12 @@ def __init__(
Events.__init__(self)
#: Whether or not to await a greeting after establishing a connection.
+ #: Defaults to True; QGA servers expect this to be False.
self.await_greeting: bool = True
- #: Whether or not to perform capabilities negotiation upon connection.
- #: Implies `await_greeting`.
+ #: Whether or not to perform capabilities negotiation upon
+ #: connection. Implies `await_greeting`. Defaults to True; QGA
+ #: servers expect this to be False.
self.negotiate: bool = True
# Cached Greeting, if one was awaited.
@@ -244,7 +279,13 @@ def __init__(
@property
def greeting(self) -> Optional[Greeting]:
- """The `Greeting` from the QMP server, if any."""
+ """
+ The `Greeting` from the QMP server, if any.
+
+ Defaults to ``None``, and will be set after a greeting is
+ received during the connection process. It is reset at the start
+ of each connection attempt.
+ """
return self._greeting
@upper_half
@@ -385,7 +426,7 @@ async def _on_message(self, msg: Message) -> None:
# This is very likely a server parsing error.
# It doesn't inherently belong to any pending execution.
# Instead of performing clever recovery, just terminate.
- # See "NOTE" in qmp-spec.rst, section "Error".
+ # See "NOTE" in interop/qmp-spec, "Error" section.
raise ServerParseError(
("Server sent an error response without an ID, "
"but there are no ID-less executions pending. "
@@ -393,7 +434,7 @@ async def _on_message(self, msg: Message) -> None:
msg
)
- # qmp-spec.rst, section "Commands Responses":
+ # qmp-spec.rst, "Commands Responses" section:
# 'Clients should drop all the responses
# that have an unknown "id" field.'
self.logger.log(
@@ -566,7 +607,7 @@ async def _raw(
@require(Runstate.RUNNING)
async def execute_msg(self, msg: Message) -> object:
"""
- Execute a QMP command and return its value.
+ Execute a QMP command on the server and return its value.
:param msg: The QMP `Message` to execute.
@@ -578,7 +619,9 @@ async def execute_msg(self, msg: Message) -> object:
If the QMP `Message` does not have either the 'execute' or
'exec-oob' fields set.
:raise ExecuteError: When the server returns an error response.
- :raise ExecInterruptedError: if the connection was terminated early.
+ :raise ExecInterruptedError:
+ If the connection was disrupted before
+ receiving a reply from the server.
"""
if not ('execute' in msg or 'exec-oob' in msg):
raise ValueError("Requires 'execute' or 'exec-oob' message")
@@ -617,9 +660,11 @@ def make_execute_msg(cls, cmd: str,
:param cmd: QMP command name.
:param arguments: Arguments (if any). Must be JSON-serializable.
- :param oob: If `True`, execute "out of band".
+ :param oob:
+ If `True`, execute "out of band". See `interop/qmp-spec`
+ section "Out-of-band execution".
- :return: An executable QMP `Message`.
+ :return: A QMP `Message` that can be executed with `execute_msg()`.
"""
msg = Message({'exec-oob' if oob else 'execute': cmd})
if arguments is not None:
@@ -631,18 +676,22 @@ async def execute(self, cmd: str,
arguments: Optional[Mapping[str, object]] = None,
oob: bool = False) -> object:
"""
- Execute a QMP command and return its value.
+ Execute a QMP command on the server and return its value.
:param cmd: QMP command name.
:param arguments: Arguments (if any). Must be JSON-serializable.
- :param oob: If `True`, execute "out of band".
+ :param oob:
+ If `True`, execute "out of band". See `interop/qmp-spec`
+ section "Out-of-band execution".
:return:
The command execution return value from the server. The type of
object returned depends on the command that was issued,
though most in QEMU return a `dict`.
:raise ExecuteError: When the server returns an error response.
- :raise ExecInterruptedError: if the connection was terminated early.
+ :raise ExecInterruptedError:
+ If the connection was disrupted before
+ receiving a reply from the server.
"""
msg = self.make_execute_msg(cmd, arguments, oob=oob)
return await self.execute_msg(msg)
@@ -650,8 +699,20 @@ async def execute(self, cmd: str,
@upper_half
@require(Runstate.RUNNING)
def send_fd_scm(self, fd: int) -> None:
- """
- Send a file descriptor to the remote via SCM_RIGHTS.
+ """Send a file descriptor to the remote via SCM_RIGHTS.
+
+ This method does not close the file descriptor.
+
+ :param fd: The file descriptor to send to QEMU.
+
+ This is an advanced feature of QEMU where file descriptors can
+ be passed from client to server. This is usually used as a
+ security measure to isolate the QEMU process from being able to
+ open its own files. See the QMP commands ``getfd`` and
+ ``add-fd`` for more information.
+
+ See `socket.socket.sendmsg` for more information on the Python
+ implementation for sending file descriptors over a UNIX socket.
"""
assert self._writer is not None
sock = self._writer.transport.get_extra_info('socket')
diff --git a/python/qemu/qmp/qmp_shell.py b/python/qemu/qmp/qmp_shell.py
index c923ff09e1f..f8188005685 100644
--- a/python/qemu/qmp/qmp_shell.py
+++ b/python/qemu/qmp/qmp_shell.py
@@ -10,9 +10,15 @@
#
"""
-Low-level QEMU shell on top of QMP.
+qmp-shell - An interactive QEMU shell powered by QMP
-usage: qmp-shell [-h] [-H] [-N] [-v] [-p] qmp_server
+qmp-shell offers a simple shell with a convenient shorthand syntax as an
+alternative to typing JSON by hand. This syntax is not standardized and
+is not meant to be used as a scriptable interface. This shorthand *may*
+change incompatibly in the future, and it is strongly encouraged to use
+the QMP library to provide API-stable scripting when needed.
+
+usage: qmp-shell [-h] [-H] [-v] [-p] [-l LOGFILE] [-N] qmp_server
positional arguments:
qmp_server < UNIX socket path | TCP address:port >
@@ -20,41 +26,52 @@
optional arguments:
-h, --help show this help message and exit
-H, --hmp Use HMP interface
- -N, --skip-negotiation
- Skip negotiate (for qemu-ga)
-v, --verbose Verbose (echo commands sent and received)
-p, --pretty Pretty-print JSON
+ -l LOGFILE, --logfile LOGFILE
+ Save log of all QMP messages to PATH
+ -N, --skip-negotiation
+ Skip negotiate (for qemu-ga)
+Usage
+-----
-Start QEMU with:
+First, start QEMU with::
-# qemu [...] -qmp unix:./qmp-sock,server
+ > qemu [...] -qmp unix:./qmp-sock,server=on[,wait=off]
-Run the shell:
+Then run the shell, passing the address of the socket::
-$ qmp-shell ./qmp-sock
+ > qmp-shell ./qmp-sock
-Commands have the following format:
+Syntax
+------
- < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
+Commands have the following format::
-For example:
+ < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
-(QEMU) device_add driver=e1000 id=net1
-{'return': {}}
-(QEMU)
+For example, to add a network device::
-key=value pairs also support Python or JSON object literal subset notations,
-without spaces. Dictionaries/objects {} are supported as are arrays [].
+ (QEMU) device_add driver=e1000 id=net1
+ {'return': {}}
+ (QEMU)
- example-command arg-name1={'key':'value','obj'={'prop':"value"}}
+key=value pairs support either Python or JSON object literal notations,
+**without spaces**. Dictionaries/objects ``{}`` are supported, as are
+arrays ``[]``::
-Both JSON and Python formatting should work, including both styles of
-string literal quotes. Both paradigms of literal values should work,
-including null/true/false for JSON and None/True/False for Python.
+ example-command arg-name1={'key':'value','obj'={'prop':"value"}}
+Either JSON or Python formatting for compound values works, including
+both styles of string literal quotes (either single or double
+quotes). Both paradigms of literal values are accepted, including
+``null/true/false`` for JSON and ``None/True/False`` for Python.
-Transactions have the following multi-line format:
+Transactions
+------------
+
+Transactions have the following multi-line format::
transaction(
action-name1 [ arg-name1=arg1 ] ... [arg-nameN=argN ]
@@ -62,11 +79,11 @@
action-nameN [ arg-name1=arg1 ] ... [arg-nameN=argN ]
)
-One line transactions are also supported:
+One line transactions are also supported::
transaction( action-name1 ... )
-For example:
+For example::
(QEMU) transaction(
TRANS> block-dirty-bitmap-add node=drive0 name=bitmap1
@@ -75,9 +92,35 @@
{"return": {}}
(QEMU)
-Use the -v and -p options to activate the verbose and pretty-print options,
-which will echo back the properly formatted JSON-compliant QMP that is being
-sent to QEMU, which is useful for debugging and documentation generation.
+Commands
+--------
+
+Autocomplete of command names using <tab> is supported. Pressing <tab>
+at a blank CLI prompt will show you a list of all available commands
+that the connected QEMU instance supports.
+
+For documentation on QMP commands and their arguments, please see
+`qmp ref`.
+
+Events
+------
+
+qmp-shell will display events received from the server, but this version
+does not do so asynchronously. To check for new events from the server,
+press <enter> on a blank line::
+
+ (QEMU) ⏎
+ {'timestamp': {'seconds': 1660071944, 'microseconds': 184667},
+ 'event': 'STOP'}
+
+Display options
+---------------
+
+Use the -v and -p options to activate the verbose and pretty-print
+options, which will echo back the properly formatted JSON-compliant QMP
+that is being sent to QEMU. This is useful for debugging to see the
+wire-level QMP data being exchanged, and generating output for use in
+writing documentation for QEMU.
"""
import argparse
@@ -525,6 +568,8 @@ def common_parser() -> argparse.ArgumentParser:
help='Pretty-print JSON')
parser.add_argument('-l', '--logfile',
help='Save log of all QMP messages to PATH')
+ # NOTE: When changing arguments, update both this module docstring
+ # and the manpage synopsis in docs/man/qmp_shell.rst.
return parser
@@ -567,8 +612,35 @@ def main() -> None:
def main_wrap() -> None:
"""
- qmp-shell-wrap entry point: parse command line arguments and
- start the REPL.
+ qmp-shell-wrap - QEMU + qmp-shell launcher utility
+
+ Launch QEMU and connect to it with `qmp-shell` in a single command.
+ CLI arguments will be forwarded to qemu, with additional arguments
+ added to allow `qmp-shell` to then connect to the recently launched
+ QEMU instance.
+
+ usage: qmp-shell-wrap [-h] [-H] [-v] [-p] [-l LOGFILE] ...
+
+ positional arguments:
+ command QEMU command line to invoke
+
+ optional arguments:
+ -h, --help show this help message and exit
+ -H, --hmp Use HMP interface
+ -v, --verbose Verbose (echo commands sent and received)
+ -p, --pretty Pretty-print JSON
+ -l LOGFILE, --logfile LOGFILE
+ Save log of all QMP messages to PATH
+
+ Usage
+ -----
+
+ Prepend "qmp-shell-wrap" to your usual QEMU command line::
+
+ > qmp-shell-wrap qemu-system-x86_64 -M q35 -m 4096 -display none
+ Welcome to the QMP low-level shell!
+ Connected
+ (QEMU)
"""
parser = common_parser()
parser.add_argument('command', nargs=argparse.REMAINDER,
diff --git a/python/qemu/qmp/util.py b/python/qemu/qmp/util.py
index 47ec39a8b5e..a8229e55245 100644
--- a/python/qemu/qmp/util.py
+++ b/python/qemu/qmp/util.py
@@ -49,7 +49,7 @@ def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
async def flush(writer: asyncio.StreamWriter) -> None:
"""
- Utility function to ensure a StreamWriter is *fully* drained.
+ Utility function to ensure an `asyncio.StreamWriter` is *fully* drained.
`asyncio.StreamWriter.drain` only promises we will return to below
the "high-water mark". This function ensures we flush the entire
@@ -89,7 +89,7 @@ def bottom_half(func: T) -> T:
These methods do not, in general, have the ability to directly
report information to a caller’s context and will usually be
- collected as a Task result instead.
+ collected as an `asyncio.Task` result instead.
They must not call upper-half functions directly.
"""
@@ -105,8 +105,11 @@ def exception_summary(exc: BaseException) -> str:
"""
Return a summary string of an arbitrary exception.
- It will be of the form "ExceptionType: Error Message", if the error
+ It will be of the form "ExceptionType: Error Message" if the error
string is non-empty, and just "ExceptionType" otherwise.
+
+ This code is based on CPython's implementation of
+ `traceback.TracebackException.format_exception_only`.
"""
name = type(exc).__qualname__
smod = type(exc).__module__
--
2.51.0
next prev parent reply other threads:[~2025-09-16 16:27 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-16 16:23 [PULL 00/19] Python patches John Snow
2025-09-16 16:23 ` [PULL 01/19] python: backport 'Change error classes to have better repr methods' John Snow
2025-09-16 16:23 ` [PULL 02/19] python: backport 'EventListener: add __repr__ method' John Snow
2025-09-16 16:23 ` [PULL 03/19] python: backport 'kick event queue on legacy event_pull()' John Snow
2025-09-16 16:23 ` [PULL 04/19] python: backport 'protocol: adjust logging name when changing client name' John Snow
2025-10-15 7:11 ` Thomas Huth
2025-10-22 0:37 ` John Snow
2025-09-16 16:23 ` [PULL 05/19] python: backport 'drop Python3.6 workarounds' John Snow
2025-09-16 16:23 ` [PULL 06/19] python: backport 'Use @asynciocontextmanager' John Snow
2025-09-16 16:23 ` [PULL 07/19] python: backport 'qmp-shell: add common_parser()' John Snow
2025-09-16 16:23 ` [PULL 08/19] python: backport 'feat: allow setting read buffer limit' John Snow
2025-09-16 16:23 ` [PULL 09/19] python: backport 'make require() preserve async-ness' John Snow
2025-09-16 16:23 ` [PULL 10/19] python: backport 'qmp-shell-wrap: handle missing binary gracefully' John Snow
2025-09-16 16:23 ` [PULL 11/19] python: backport 'qmp-tui: Do not crash if optional dependencies are not met' John Snow
2025-09-16 16:23 ` [PULL 12/19] python: backport 'Remove deprecated get_event_loop calls' John Snow
2025-09-16 16:23 ` [PULL 13/19] python: backport 'avoid creating additional event loops per thread' John Snow
2025-09-16 16:23 ` John Snow [this message]
2025-09-16 16:24 ` [PULL 15/19] iotests: drop compat for old version context manager John Snow
2025-09-16 16:24 ` [PULL 16/19] python: ensure QEMUQtestProtocol closes its socket John Snow
2025-09-16 16:24 ` [PULL 17/19] iotests/147: ensure temporary sockets are closed before exiting John Snow
2025-09-16 16:24 ` [PULL 18/19] iotests/151: ensure subprocesses are cleaned up John Snow
2025-09-16 16:24 ` [PULL 19/19] iotests/check: always enable all python warnings John Snow
2025-09-16 18:20 ` [PULL 00/19] Python patches John Snow
2025-09-16 19:22 ` Richard Henderson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250916162404.9195-15-jsnow@redhat.com \
--to=jsnow@redhat.com \
--cc=armbru@redhat.com \
--cc=berrange@redhat.com \
--cc=crosa@redhat.com \
--cc=hreitz@redhat.com \
--cc=kwolf@redhat.com \
--cc=michael.roth@amd.com \
--cc=peter.maydell@linaro.org \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.