diff mbox series

[v2,01/38,DO-NOT-MERGE] qapi: add debugging tools

Message ID 20200922210101.4081073-2-jsnow@redhat.com (mailing list archive)
State New, archived
Headers show
Series qapi: static typing conversion, pt1 | expand

Commit Message

John Snow Sept. 22, 2020, 9 p.m. UTC
This adds some really childishly simple debugging tools. Maybe they're
interesting for someone else, too?

Signed-off-by: John Snow <jsnow@redhat.com>
---
 scripts/qapi/debug.py | 78 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 78 insertions(+)
 create mode 100644 scripts/qapi/debug.py

Comments

Cleber Rosa Sept. 22, 2020, 11:43 p.m. UTC | #1
On Tue, Sep 22, 2020 at 05:00:24PM -0400, John Snow wrote:
> This adds some really childishly simple debugging tools. Maybe they're
> interesting for someone else, too?
> 
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>  scripts/qapi/debug.py | 78 +++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 78 insertions(+)
>  create mode 100644 scripts/qapi/debug.py
> 
> diff --git a/scripts/qapi/debug.py b/scripts/qapi/debug.py
> new file mode 100644
> index 0000000000..bacf5ee180
> --- /dev/null
> +++ b/scripts/qapi/debug.py
> @@ -0,0 +1,78 @@
> +"""
> +Small debugging facilities for mypy static analysis work.
> +(C) 2020 John Snow, for Red Hat, Inc.
> +"""
> +
> +import inspect
> +import json
> +from typing import Dict, List, Any
> +from types import FrameType
> +
> +
> +OBSERVED_TYPES: Dict[str, List[str]] = {}
> +
> +
> +# You have no idea how long it took to find this return type...
> +def caller_frame() -> FrameType:
> +    """
> +    Returns the stack frame of the caller's caller.
> +    e.g. foo() -> caller() -> caller_frame() return's foo's stack frame.
> +    """
> +    stack = inspect.stack()
> +    caller = stack[2].frame
> +    if caller is None:
> +        msg = "Python interpreter does not support stack frame inspection"
> +        raise RuntimeError(msg)
> +    return caller
> +
> +
> +def _add_type_record(name: str, typestr: str) -> None:
> +    seen = OBSERVED_TYPES.setdefault(name, [])
> +    if typestr not in seen:
> +        seen.append(typestr)
> +
> +
> +def record_type(name: str, value: Any, dict_names: bool = False) -> None:
> +    """
> +    Record the type of a variable.
> +
> +    :param name: The name of the variable
> +    :param value: The value of the variable
> +    """
> +    _add_type_record(name, str(type(value)))
> +
> +    try:
> +        for key, subvalue in value.items():
> +            subname = f"{name}.{key}" if dict_names else f"{name}.[dict_value]"
> +            _add_type_record(subname, str(type(subvalue)))
> +        return
> +    except AttributeError:
> +        # (Wasn't a dict or anything resembling one.)
> +        pass
> +
> +    # str is iterable, but not in the way we want!
> +    if isinstance(value, str):
> +        return
> +
> +    try:
> +        for elem in value:
> +            _add_type_record(f"{name}.[list_elem]", str(type(elem)))
> +    except TypeError:
> +        # (Wasn't a list or anything else iterable.)
> +        pass
> +
> +
> +def show_types() -> None:
> +    """
> +    Print all of the currently known variable types to stdout.
> +    """
> +    print(json.dumps(OBSERVED_TYPES, indent=2))
> +

Maybe the following will be cheaper (no json conversion):

   pprint.pprint(OBSERVED_TYPES, indent=2)

Other than that, I'd vote for including this if there's a bit more
documentation on how to use it, or an example script.  Maybe there
already is, and I did not get to it yet.

- Cleber.

> +
> +def record_locals(show: bool = False, dict_names: bool = False) -> None:
> +    caller = caller_frame()
> +    name = caller.f_code.co_name
> +    for key, value in caller.f_locals.items():
> +        record_type(f"{name}.{key}", value, dict_names=dict_names)
> +    if show:
> +        show_types()
> -- 
> 2.26.2
>
John Snow Sept. 23, 2020, 4:48 p.m. UTC | #2
On 9/22/20 7:43 PM, Cleber Rosa wrote:
> On Tue, Sep 22, 2020 at 05:00:24PM -0400, John Snow wrote:
>> This adds some really childishly simple debugging tools. Maybe they're
>> interesting for someone else, too?
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   scripts/qapi/debug.py | 78 +++++++++++++++++++++++++++++++++++++++++++
>>   1 file changed, 78 insertions(+)
>>   create mode 100644 scripts/qapi/debug.py
>>
>> diff --git a/scripts/qapi/debug.py b/scripts/qapi/debug.py
>> new file mode 100644
>> index 0000000000..bacf5ee180
>> --- /dev/null
>> +++ b/scripts/qapi/debug.py
>> @@ -0,0 +1,78 @@
>> +"""
>> +Small debugging facilities for mypy static analysis work.
>> +(C) 2020 John Snow, for Red Hat, Inc.
>> +"""
>> +
>> +import inspect
>> +import json
>> +from typing import Dict, List, Any
>> +from types import FrameType
>> +
>> +
>> +OBSERVED_TYPES: Dict[str, List[str]] = {}
>> +
>> +
>> +# You have no idea how long it took to find this return type...
>> +def caller_frame() -> FrameType:
>> +    """
>> +    Returns the stack frame of the caller's caller.
>> +    e.g. foo() -> caller() -> caller_frame() return's foo's stack frame.
>> +    """
>> +    stack = inspect.stack()
>> +    caller = stack[2].frame
>> +    if caller is None:
>> +        msg = "Python interpreter does not support stack frame inspection"
>> +        raise RuntimeError(msg)
>> +    return caller
>> +
>> +
>> +def _add_type_record(name: str, typestr: str) -> None:
>> +    seen = OBSERVED_TYPES.setdefault(name, [])
>> +    if typestr not in seen:
>> +        seen.append(typestr)
>> +
>> +
>> +def record_type(name: str, value: Any, dict_names: bool = False) -> None:
>> +    """
>> +    Record the type of a variable.
>> +
>> +    :param name: The name of the variable
>> +    :param value: The value of the variable
>> +    """
>> +    _add_type_record(name, str(type(value)))
>> +
>> +    try:
>> +        for key, subvalue in value.items():
>> +            subname = f"{name}.{key}" if dict_names else f"{name}.[dict_value]"
>> +            _add_type_record(subname, str(type(subvalue)))
>> +        return
>> +    except AttributeError:
>> +        # (Wasn't a dict or anything resembling one.)
>> +        pass
>> +
>> +    # str is iterable, but not in the way we want!
>> +    if isinstance(value, str):
>> +        return
>> +
>> +    try:
>> +        for elem in value:
>> +            _add_type_record(f"{name}.[list_elem]", str(type(elem)))
>> +    except TypeError:
>> +        # (Wasn't a list or anything else iterable.)
>> +        pass
>> +
>> +
>> +def show_types() -> None:
>> +    """
>> +    Print all of the currently known variable types to stdout.
>> +    """
>> +    print(json.dumps(OBSERVED_TYPES, indent=2))
>> +
> 
> Maybe the following will be cheaper (no json conversion):
> 
>     pprint.pprint(OBSERVED_TYPES, indent=2)
> 
> Other than that, I'd vote for including this if there's a bit more
> documentation on how to use it, or an example script.  Maybe there
> already is, and I did not get to it yet.
> 
> - Cleber.
> 

Nope, this is just a dumb script I did to observe types in flight.

There are apparently bigger, beefier tools that I don't know how to use 
yet: https://github.com/dropbox/pyannotate

I just included my own little tool as a reference thing to be archived 
on list, I have no desire to spruce it up. I'd rather spend my time 
learning pyannotate.

--js
diff mbox series

Patch

diff --git a/scripts/qapi/debug.py b/scripts/qapi/debug.py
new file mode 100644
index 0000000000..bacf5ee180
--- /dev/null
+++ b/scripts/qapi/debug.py
@@ -0,0 +1,78 @@ 
+"""
+Small debugging facilities for mypy static analysis work.
+(C) 2020 John Snow, for Red Hat, Inc.
+"""
+
+import inspect
+import json
+from typing import Dict, List, Any
+from types import FrameType
+
+
+OBSERVED_TYPES: Dict[str, List[str]] = {}
+
+
+# You have no idea how long it took to find this return type...
+def caller_frame() -> FrameType:
+    """
+    Returns the stack frame of the caller's caller.
+    e.g. foo() -> caller() -> caller_frame() return's foo's stack frame.
+    """
+    stack = inspect.stack()
+    caller = stack[2].frame
+    if caller is None:
+        msg = "Python interpreter does not support stack frame inspection"
+        raise RuntimeError(msg)
+    return caller
+
+
+def _add_type_record(name: str, typestr: str) -> None:
+    seen = OBSERVED_TYPES.setdefault(name, [])
+    if typestr not in seen:
+        seen.append(typestr)
+
+
+def record_type(name: str, value: Any, dict_names: bool = False) -> None:
+    """
+    Record the type of a variable.
+
+    :param name: The name of the variable
+    :param value: The value of the variable
+    """
+    _add_type_record(name, str(type(value)))
+
+    try:
+        for key, subvalue in value.items():
+            subname = f"{name}.{key}" if dict_names else f"{name}.[dict_value]"
+            _add_type_record(subname, str(type(subvalue)))
+        return
+    except AttributeError:
+        # (Wasn't a dict or anything resembling one.)
+        pass
+
+    # str is iterable, but not in the way we want!
+    if isinstance(value, str):
+        return
+
+    try:
+        for elem in value:
+            _add_type_record(f"{name}.[list_elem]", str(type(elem)))
+    except TypeError:
+        # (Wasn't a list or anything else iterable.)
+        pass
+
+
+def show_types() -> None:
+    """
+    Print all of the currently known variable types to stdout.
+    """
+    print(json.dumps(OBSERVED_TYPES, indent=2))
+
+
+def record_locals(show: bool = False, dict_names: bool = False) -> None:
+    caller = caller_frame()
+    name = caller.f_code.co_name
+    for key, value in caller.f_locals.items():
+        record_type(f"{name}.{key}", value, dict_names=dict_names)
+    if show:
+        show_types()