Beispiel #1
0
class LockCollector(collector.CaptureSamplerCollector):
    """Record lock usage."""

    nframes = attr.ib(
        factory=_attr.from_env("DD_PROFILING_MAX_FRAMES", 64, int))

    def start(self):
        """Start collecting `threading.Lock` usage."""
        super(LockCollector, self).start()
        self.patch()

    def stop(self):
        """Stop collecting `threading.Lock` usage."""
        self.unpatch()
        super(LockCollector, self).stop()

    def patch(self):
        """Patch the threading module for tracking lock allocation."""
        # We only patch the lock from the `threading` module.
        # Nobody should use locks from `_thread`; if they do so, then it's deliberate and we don't profile.
        self.original = threading.Lock

        @wrapt.function_wrapper
        def _allocate_lock(wrapped, instance, args, kwargs):
            lock = wrapped(*args, **kwargs)
            return _ProfiledLock(lock, self.recorder, self.nframes,
                                 self._capture_sampler)

        threading.Lock = _allocate_lock(self.original)

    def unpatch(self):
        """Unpatch the threading module for tracking lock allocation."""
        threading.Lock = self.original
Beispiel #2
0
class CaptureSamplerCollector(Collector):
    capture_pct = attr.ib(
        factory=_attr.from_env("DD_PROFILING_CAPTURE_PCT", 5, float))
    _capture_sampler = attr.ib(default=attr.Factory(_create_capture_sampler,
                                                    takes_self=True),
                               init=False,
                               repr=False)
Beispiel #3
0
class UncaughtExceptionCollector(collector.Collector):
    """Record uncaught thrown exceptions."""

    max_nframes = attr.ib(
        factory=_attr.from_env("DD_PROFILING_MAX_FRAMES", 64, int))

    def start(self):
        """Start collecting uncaught exceptions."""
        self.original_except_hook = sys.excepthook
        sys.excepthook = self.except_hook
        super(UncaughtExceptionCollector, self).start()

    def stop(self):
        """Stop collecting uncaught exceptions."""
        if hasattr(self, "original_except_hook"):
            sys.excepthook = self.original_except_hook
            del self.original_except_hook
        super(UncaughtExceptionCollector, self).stop()

    def except_hook(self, exctype, value, traceback):
        try:
            frames, nframes = _traceback.traceback_to_frames(
                traceback, self.max_nframes)
            thread_id, thread_name = threading._current_thread()
            self.recorder.push_event(
                UncaughtExceptionEvent(frames=frames,
                                       nframes=nframes,
                                       thread_id=thread_id,
                                       thread_name=thread_name,
                                       exc_type=exctype))
        finally:
            return self.original_except_hook(exctype, value, traceback)
Beispiel #4
0
class Scheduler(object):
    """Schedule export of recorded data."""

    recorder = attr.ib()
    exporters = attr.ib()
    interval = attr.ib(
        factory=_attr.from_env("DD_PROFILING_UPLOAD_INTERVAL", 60, float))
    _periodic = attr.ib(init=False, default=None)

    def __enter__(self):
        self.start()
        return self

    def start(self):
        """Start the scheduler."""
        self._periodic = _periodic.PeriodicThread(
            self.interval,
            self.flush,
            name="%s:%s" % (__name__, self.__class__.__name__))
        LOG.debug("Starting scheduler")
        self._periodic.start()
        LOG.debug("Scheduler started")

    def __exit__(self, exc_type, exc_value, traceback):
        return self.stop()

    def stop(self, flush=True):
        """Stop the scheduler.

        :param flush: Whetever to do a final flush.
        """
        LOG.debug("Stopping scheduler")
        if self._periodic:
            self._periodic.stop()
            self._periodic.join()
            self._periodic = None
        if flush:
            self.flush()
        LOG.debug("Scheduler stopped")

    def flush(self):
        """Flush events from recorder to exporters."""
        LOG.debug("Flushing events")
        events = self.recorder.reset()
        total_events = sum(len(v) for v in events.values())
        for exp in self.exporters:
            try:
                exp.export(events)
            except exporter.ExportError as e:
                LOG.error("Unable to export %d events: %s", total_events,
                          _traceback.format_exception(e))
            except Exception:
                LOG.exception("Error while exporting %d events", total_events)
Beispiel #5
0
class Recorder(object):
    """An object that records program activity."""

    events = attr.ib(init=False, repr=False)
    max_size = attr.ib(factory=_attr.from_env("DD_PROFILING_MAX_EVENTS", 49152, int))
    event_filters = attr.ib(factory=lambda: collections.defaultdict(list), repr=False)

    def __attrs_post_init__(self):
        self._reset_events()

    def add_event_filter(self, event_type, filter_fn):
        """Add an event filter function.

        A filter function must accept a lists of events as argument and returns a list of events that should be pushed
        into the recorder.

        :param event_type: A class of event.
        :param filter_fn: A filter function to append.

        """
        self.event_filters[event_type].append(filter_fn)

    def remove_event_filter(self, event_type, filter_fn):
        """Remove an event filter from the recorder.

        :param event_type: A class of event.
        :param filter_fn: The filter function to remove.
        """
        self.event_filters[event_type].remove(filter_fn)

    def push_event(self, event):
        """Push an event in the recorder.

        :param event: The `ddtrace.profile.event.Event` to push.
        """
        return self.push_events([event])

    def push_events(self, events):
        """Push multiple events in the recorder.

        All the events MUST be of the same type.
        There is no sanity check as whether all the events are from the same class for performance reasons.

        :param events: The event list to push.
        """
        if events:
            event_type = events[0].__class__
            for filter_fn in self.event_filters[event_type]:
                events = filter_fn(events)
            q = self.events[event_type]
            q.extend(events)

    def _reset_events(self):
        self.events = collections.defaultdict(lambda: collections.deque(maxlen=self.max_size))

    def reset(self):
        """Reset the recorder.

        This is useful when e.g. exporting data. Once the event queue is retrieved, a new one can be created by calling
        the reset method, avoiding iterating on a mutating event list.

        :return: The list of events that has been removed.
        """
        events = self.events
        self._reset_events()
        return events
Beispiel #6
0
class MemoryCollector(collector.PeriodicCollector,
                      collector.CaptureSamplerCollector):
    """Memory allocation collector."""

    # Arbitrary interval to use for enabling/disabling tracemalloc
    _interval = attr.ib(default=0.1, repr=False)

    nframes = attr.ib(
        factory=_attr.from_env("DD_PROFILING_MAX_FRAMES", 64, int))
    ignore_profiler = attr.ib(
        factory=_attr.from_env("DD_PROFILING_IGNORE_PROFILER", True, bool))

    def __attrs_post_init__(self):
        if sys.version_info[:2] <= (3, 5):
            self._filter_profiler = self._filter_profiler_35

    @staticmethod
    def _filter_profiler(traces):
        return [
            trace for trace in traces if all(
                map(lambda frame: not frame[0].startswith(_MODULE_TOP_DIR),
                    trace[2]))
        ]

    @staticmethod
    def _filter_profiler_35(traces):
        # Python <= 3.5 does not have support for domain
        return [
            trace for trace in traces if all(
                map(lambda frame: not frame[0].startswith(_MODULE_TOP_DIR),
                    trace[1]))
        ]

    def start(self):
        """Start collecting memory profiles."""
        if tracemalloc is None:
            raise RuntimeError("tracemalloc is unavailable")
        super(MemoryCollector, self).start()

    def stop(self):
        if tracemalloc is not None:
            tracemalloc.stop()
        super(MemoryCollector, self).stop()

    def _collect(self):
        try:
            snapshot = tracemalloc.take_snapshot()
        except RuntimeError:
            events = []
        else:
            tracemalloc.stop()

            if snapshot.traces and self.ignore_profiler:
                snapshot.traces._traces = self._filter_profiler(
                    snapshot.traces._traces)

            if snapshot.traces:
                events = [
                    MemorySampleEvent(snapshot=snapshot,
                                      sampling_pct=self.capture_pct)
                ]
            else:
                events = []

        if self._capture_sampler.capture():
            tracemalloc.start(self.nframes)

        return [events]