ionelmc / python-hunter

Hunter is a flexible code tracing toolkit.
https://python-hunter.readthedocs.io/
BSD 2-Clause "Simplified" License
794 stars 46 forks source link

Backlog predicate #78

Open ionelmc opened 4 years ago

ionelmc commented 4 years ago

A predicate that can show a stacktrace or store a number of events up to a certain point:

# defaults
class Backlog:
  def __init__(self, filter, action=CallPrinter, size=None, stack=30):
    self.filter = filter
    self.action = action
    self.queue = collections.deque(maxlen=size) if size else None
    self.stack = stack
    ....

Handling could be:

ionelmc commented 4 years ago
trace(expression, action=SomePrinter) Meaning
Backlog(function="foobar", size=10, action=CodePrinter) Show 10 events prior to the first match for "foobar" (usually a call) using CodePrinter. Afterwards (events from "foobar") are shown using SomePrinter.
Backlog(function="foobar", size=10, action=CodePrinter).filter(stdlib=False) Show 10 events that aren't from stdlib prior to the first match for "foobar" (usually a call) using CodePrinter. Afterwards (events from "foobar") are shown using SomePrinter.
Backlog(function="foobar", stack=10, size=0, action=CodePrinter).filter(stdlib=False) Show 10 fake events created from the current call stack that aren't from stdlib prior to the first match for "foobar" (usually a call) using CodePrinter. For all intents and purposes those 10 events will look like a call stack (they would only be call events). Afterwards (events from "foobar") are shown using SomePrinter.
Backlog(function="foobar", kind="line", stack=10, size=0, action=CodePrinter) Show 10 fake events created from the current call stack prior to the first match for "foobar" (usually a call) using CodePrinter. Note that because the triggering event would not be a call event the call stack iterator would need to start from 0 instead of 1.
Backlog(function="foobar", stack=10, size=20, action=CallPrinter) Show 20 events prior to the first match for "foobar" (usually a call) using CallPrinter. If those 20 events don't go deeper than 10 calls then prepend fake events from the current call stack up to that level. Afterwards (events from "foobar") are shown using SomePrinter.
Backlog(function="foobar") Show 100 events prior to the first match for "foobar" (usually a call) using SomePrinter. Some helper code in trace would inject the main actions if they aren't already specified.
Or(Q(function="test123"), Backlog(function="foobar")) The helper code in trace would need to smart enough to be able to examine the whole predicate tree.
~Backlog(function="foobar") Essentially translate to Backlog(Not(Q(function="foobar"))) as it doesn't make sense to negate backlog on the outside (eg: hunter would display events from backlog 2 times - undesirable).

Perhaps stack=10, size=100, action=CallPrinter could be default.

ionelmc commented 4 years ago

There is a problem when creating fake events... the Event.__init__ wants a tracer (to fill depth/calls). Perhaps make it optional and add options for depth/calls. Value for "calls" in fake events is very problematic - nothing in hunter uses them now so maybe "-1" is a safe value. Value for "depth" should be computed by applying a stack delta to the dump-trigger event's depth.