pax_global_header00006660000000000000000000000064136465511240014521gustar00rootroot0000000000000052 comment=bfd1ef748e9d29cf4abccac03098d1d369e6be12 spyder-memory-profiler-0.2.1/000077500000000000000000000000001364655112400161555ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/.github/000077500000000000000000000000001364655112400175155ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/.github/FUNDING.yml000066400000000000000000000000301364655112400213230ustar00rootroot00000000000000open_collective: spyder spyder-memory-profiler-0.2.1/.github/workflows/000077500000000000000000000000001364655112400215525ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/.github/workflows/linux-tests.yml000066400000000000000000000030141364655112400245720ustar00rootroot00000000000000name: Linux tests on: push: branches: - master pull_request: branches: - master jobs: linux: name: Linux Py${{ matrix.PYTHON_VERSION }} runs-on: ubuntu-latest env: CI: True PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }} RUNNER_OS: 'ubuntu' strategy: fail-fast: false matrix: PYTHON_VERSION: ['3.6', '3.7', '3.8'] steps: - name: Checkout branch uses: actions/checkout@v2 - name: Install System Packages run: | sudo apt-get update sudo apt-get install libegl1-mesa - name: Install Conda uses: goanpeca/setup-miniconda@v1 with: activate-environment: test auto-update-conda: true auto-activate-base: false python-version: ${{ matrix.PYTHON_VERSION }} - name: Install package dependencies shell: bash -l {0} run: conda install --file requirements/conda.txt -y -q - name: Install test dependencies shell: bash -l {0} run: | conda install nomkl -y -q conda install -c spyder-ide --file requirements/tests.txt -y -q - name: Install Package shell: bash -l {0} run: pip install --no-deps -e . - name: Show environment information shell: bash -l {0} run: | conda info conda list - name: Run tests shell: bash -l {0} run: xvfb-run --auto-servernum pytest spyder_memory_profiler -x -vv timeout-minutes: 10 spyder-memory-profiler-0.2.1/.github/workflows/macos-tests.yml000066400000000000000000000025561364655112400245470ustar00rootroot00000000000000name: Macos tests on: push: branches: - master pull_request: branches: - master jobs: macos: name: Mac Py${{ matrix.PYTHON_VERSION }} runs-on: macos-latest env: CI: True PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }} RUNNER_OS: 'macos' strategy: fail-fast: false matrix: PYTHON_VERSION: ['3.6', '3.7', '3.8'] steps: - name: Checkout branch uses: actions/checkout@v2 - name: Install Conda uses: goanpeca/setup-miniconda@v1 with: activate-environment: test auto-update-conda: true auto-activate-base: false python-version: ${{ matrix.PYTHON_VERSION }} - name: Install package dependencies shell: bash -l {0} run: conda install --file requirements/conda.txt -y -q - name: Install test dependencies shell: bash -l {0} run: | conda install nomkl -y -q conda install -c spyder-ide --file requirements/tests.txt -y -q - name: Install Package shell: bash -l {0} run: pip install --no-deps -e . - name: Show environment information shell: bash -l {0} run: | conda info conda list - name: Run tests shell: bash -l {0} run: pytest spyder_memory_profiler -x -vv timeout-minutes: 10 spyder-memory-profiler-0.2.1/.github/workflows/windows-tests.yml000066400000000000000000000025111364655112400251260ustar00rootroot00000000000000name: Windows tests on: push: branches: - master pull_request: branches: - master jobs: windows: name: Windows Py${{ matrix.PYTHON_VERSION }} runs-on: windows-latest env: CI: True PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }} RUNNER_OS: 'windows' strategy: fail-fast: false matrix: PYTHON_VERSION: ['3.6', '3.7', '3.8'] steps: - name: Checkout branch uses: actions/checkout@v2 - name: Install Conda uses: goanpeca/setup-miniconda@v1 with: activate-environment: test auto-update-conda: true auto-activate-base: false python-version: ${{ matrix.PYTHON_VERSION }} - name: Install package dependencies shell: bash -l {0} run: conda install --file requirements/conda.txt -y -q - name: Install test dependencies shell: bash -l {0} run: conda install -c spyder-ide --file requirements/tests.txt -y -q - name: Install Package shell: bash -l {0} run: pip install --no-deps -e . - name: Show environment information shell: bash -l {0} run: | conda info conda list - name: Run tests shell: bash -l {0} run: pytest spyder_memory_profiler -x -vv timeout-minutes: 10 spyder-memory-profiler-0.2.1/.gitignore000066400000000000000000000004731364655112400201510ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 __pycache__ # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject spyder-memory-profiler-0.2.1/AUTHORS.md000066400000000000000000000007511364655112400176270ustar00rootroot00000000000000Maintainer ========== The [Spyder Development Team](https://github.com/orgs/spyder-ide/teams/core-developers) Main Authors ============ Joseph Martinot-Lagarde ([@Nodd](http://github.com/Nodd)) Code Contributors ================= Joseph Martinot-Lagarde ([@Nodd](http://github.com/Nodd)) Christer van der Meeren ([@cmeeren](http://github.com/cmeeren)) Gonzalo Peña-Castellanos ([@goanpeca](http://github.com/goanpeca)) Steven Silvester ([@blink1073](http://github.com/blink1073)) spyder-memory-profiler-0.2.1/CHANGELOG.md000066400000000000000000000067371364655112400200030ustar00rootroot00000000000000# History of changes ## Version 0.2.1 (2020/04/18) This release fixes some compatibility issues with Spyder 4.1 and some other bugs. ### Issues Closed * [Issue 27](https://github.com/spyder-ide/spyder-memory-profiler/issues/27) - Check TextEditor call ([PR 29](https://github.com/spyder-ide/spyder-memory-profiler/pull/29)) * [Issue 26](https://github.com/spyder-ide/spyder-memory-profiler/issues/26) - Move README to markdown ([PR 28](https://github.com/spyder-ide/spyder-memory-profiler/pull/28)) * [Issue 25](https://github.com/spyder-ide/spyder-memory-profiler/issues/25) - Move CI to github actions ([PR 28](https://github.com/spyder-ide/spyder-memory-profiler/pull/28)) * [Issue 21](https://github.com/spyder-ide/spyder-memory-profiler/issues/21) - start Profile memory usage ([PR 31](https://github.com/spyder-ide/spyder-memory-profiler/pull/31)) ### Pull Requests Merged * [PR 31](https://github.com/spyder-ide/spyder-memory-profiler/pull/31) - PR: Fix Start button ([21](https://github.com/spyder-ide/spyder-memory-profiler/issues/21)) * [PR 30](https://github.com/spyder-ide/spyder-memory-profiler/pull/30) - PR: Fix opening editor from profiler widget * [PR 29](https://github.com/spyder-ide/spyder-memory-profiler/pull/29) - Widget: Fix initialization of TextEditor ([27](https://github.com/spyder-ide/spyder-memory-profiler/issues/27)) * [PR 28](https://github.com/spyder-ide/spyder-memory-profiler/pull/28) - PR: Transfer CI to GitHub Actions ([26](https://github.com/spyder-ide/spyder-memory-profiler/issues/26), [25](https://github.com/spyder-ide/spyder-memory-profiler/issues/25)) * [PR 24](https://github.com/spyder-ide/spyder-memory-profiler/pull/24) - PR: Add CONF_DEFAULTS In this release 4 issues and 5 pull requests were closed. ## Version 0.2.0 (2019/12/17) This release updates the plugin to be used with Spyder 4. ### Pull Requests Merged * [PR 23](https://github.com/spyder-ide/spyder-memory-profiler/pull/23) - PR: Update to Spyder 4 * [PR 13](https://github.com/spyder-ide/spyder-memory-profiler/pull/13) - Update readme: Plugin can now be installed using conda or pip In this release 2 pull requests were closed. ## Version 0.1.2 (2017/11/08) This release fixes a packaging mistake in the 0.1.1 release. ### Pull Requests Merged * [PR 19](https://github.com/spyder-ide/spyder-memory-profiler/pull/19) - Restrict recursive inclusion filter In this release 1 pull request was closed. ## Version 0.1.1 (2017/11/03) This release fixes some minor bugs. ### Issues Closed * [Issue 16](https://github.com/spyder-ide/spyder-memory-profiler/issues/16) - QColor::setAlphaF called with negative value ([PR 17](https://github.com/spyder-ide/spyder-memory-profiler/pull/17)) * [Issue 14](https://github.com/spyder-ide/spyder-memory-profiler/issues/14) - Tests fail on 32-bit platforms ([PR 15](https://github.com/spyder-ide/spyder-memory-profiler/pull/15)) In this release 2 issues were closed. ### Pull Requests Merged * [PR 18](https://github.com/spyder-ide/spyder-memory-profiler/pull/18) - Make test more robust * [PR 17](https://github.com/spyder-ide/spyder-memory-profiler/pull/17) - Make sure that alpha value is not negative ([16](https://github.com/spyder-ide/spyder-memory-profiler/issues/16)) * [PR 15](https://github.com/spyder-ide/spyder-memory-profiler/pull/15) - Use sys.getsizeof() to get expected memory consumption in tests ([14](https://github.com/spyder-ide/spyder-memory-profiler/issues/14)) In this release 3 pull requests were closed. ## Version 0.1.0 (2017/03/26) Initial release. spyder-memory-profiler-0.2.1/CONTRIBUTING.rst000066400000000000000000000000021364655112400206060ustar00rootroot00000000000000 spyder-memory-profiler-0.2.1/LICENSE.txt000066400000000000000000000021051364655112400177760ustar00rootroot00000000000000The MIT License (MIT) Copyright © 2013 Spyder Project Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. spyder-memory-profiler-0.2.1/MANIFEST.in000066400000000000000000000001371364655112400177140ustar00rootroot00000000000000include CHANGELOG.md LICENSE.txt README.md recursive-include spyder_memory_profiler *.py *.png spyder-memory-profiler-0.2.1/README.md000066400000000000000000000060501364655112400174350ustar00rootroot00000000000000# Spyder memory profiler plugin ## Project details ![license](https://img.shields.io/pypi/l/spyder-memory-profiler.svg) [![conda version](https://img.shields.io/conda/v/spyder-ide/spyder-memory-profiler.svg)](https://www.anaconda.com/download/) [![download count](https://img.shields.io/conda/d/spyder-ide/spyder-memory-profiler.svg)](https://www.anaconda.com/download/) [![pypi version](https://img.shields.io/pypi/v/spyder-memory-profiler.svg)](https://pypi.python.org/pypi/spyder-memory-profiler) [![Join the chat at https://gitter.im/spyder-ide/public](https://badges.gitter.im/spyder-ide/spyder.svg)](https://gitter.im/spyder-ide/public) [![OpenCollective Backers](https://opencollective.com/spyder/backers/badge.svg?color=blue)](#backers) [![OpenCollective Sponsors](https://opencollective.com/spyder/sponsors/badge.svg?color=blue)](#sponsors) ## Build status [![Windows status](https://github.com/spyder-ide/spyder-memory-profiler/workflows/Windows%20tests/badge.svg)](https://github.com/spyder-ide/spyder-memory-profiler/actions?query=workflow%3A%22Windows+tests%22) [![Linux status](https://github.com/spyder-ide/spyder-memory-profiler/workflows/Linux%20tests/badge.svg)](https://github.com/spyder-ide/spyder-memory-profiler/actions?query=workflow%3A%22Linux+tests%22) [![MacOS status](https://github.com/spyder-ide/spyder-memory-profiler/workflows/Macos%20tests/badge.svg)](https://github.com/spyder-ide/spyder-memory-profiler/actions?query=workflow%3A%22Macos+tests%22) ## Description This is a plugin to run the Python [memory_profiler](https://pypi.python.org/pypi/memory_profiler) from within the Python IDE [Spyder](https://github.com/spyder-ide/spyder). The code is an adaptation of the profiler plugin integrated in Spyder. ## Installation To install this plugin, you can use either ``pip`` or ``conda`` package managers, as follows: Using conda (the recommended way!): ``` conda install spyder-memory-profiler -c spyder-ide ``` Using pip: ``` pip install spyder-memory-profiler ``` ## Usage Add a `@profile` decorator to the functions that you wish to profile then Ctrl+Shift+F10 to run the profiler on the current script, or go to `Run > Profile memory line by line`. The results will be shown in a dockwidget, grouped by function. Lines with a stronger color have the largest increments in memory usage (memory profiler). ## Contributing Everyone is welcome to contribute! ## Sponsors Spyder and its subprojects are funded thanks to the generous support of [![Quansight](https://static.wixstatic.com/media/095d2c_2508c560e87d436ea00357abc404cf1d~mv2.png/v1/crop/x_0,y_9,w_915,h_329/fill/w_380,h_128,al_c,usm_0.66_1.00_0.01/095d2c_2508c560e87d436ea00357abc404cf1d~mv2.png)](https://www.quansight.com/)[![Numfocus](https://i2.wp.com/numfocus.org/wp-content/uploads/2017/07/NumFocus_LRG.png?fit=320%2C148&ssl=1)](https://numfocus.org/) and the donations we have received from our users around the world through [Open Collective](https://opencollective.com/spyder/): [![Sponsors](https://opencollective.com/spyder/sponsors.svg)](https://opencollective.com/spyder#support) spyder-memory-profiler-0.2.1/requirements/000077500000000000000000000000001364655112400207005ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/requirements/conda.txt000066400000000000000000000000411364655112400225200ustar00rootroot00000000000000memory_profiler spyder >=4.0.0b5 spyder-memory-profiler-0.2.1/requirements/tests.txt000066400000000000000000000000441364655112400226010ustar00rootroot00000000000000codecov pytest pytest-cov pytest-qt spyder-memory-profiler-0.2.1/setup.py000066400000000000000000000056201364655112400176720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright © 2013 Spyder Project Contributors # Licensed under the terms of the MIT License # (see LICENSE.txt for details) from setuptools import setup, find_packages import os import os.path as osp def get_version(): """Get version from source file""" import codecs with codecs.open("spyder_memory_profiler/__init__.py", encoding="utf-8") as f: lines = f.read().splitlines() for l in lines: if "__version__" in l: version = l.split("=")[1].strip() version = version.replace("'", '').replace('"', '') return version def get_package_data(name, extlist): """Return data files for package *name* with extensions in *extlist*""" flist = [] # Workaround to replace os.path.relpath (not available until Python 2.6): offset = len(name) + len(os.pathsep) for dirpath, _dirnames, filenames in os.walk(name): for fname in filenames: if not fname.startswith('.') and osp.splitext(fname)[1] in extlist: flist.append(osp.join(dirpath, fname)[offset:]) return flist # Requirements REQUIREMENTS = ['memory_profiler', 'spyder>=4'] EXTLIST = ['.jpg', '.png', '.json', '.mo', '.ini'] LIBNAME = 'spyder_memory_profiler' LONG_DESCRIPTION = """ This is a plugin for the Spyder IDE that integrates the Python memory profiler. It allows you to see the memory usage in every line. Usage ----- Add a ``@profile`` decorator to the functions that you wish to profile then press Ctrl+Shift+F10 to run the profiler on the current script, or go to ``Run > Profile memory line by line``. The results will be shown in a dockwidget, grouped by function. Lines with a stronger color have the largest increments in memory usage. """ setup( name=LIBNAME, version=get_version(), packages=find_packages(), package_data={LIBNAME: get_package_data(LIBNAME, EXTLIST)}, keywords=["Qt PyQt4 PyQt5 PySide spyder plugins spyplugins profiler"], install_requires=REQUIREMENTS, url='https://github.com/spyder-ide/spyder-memory-profiler', license='MIT', author='Spyder Project Contributors', description='Plugin for the Spyder IDE that integrates the Python' ' memory profiler', long_description=LONG_DESCRIPTION, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: X11 Applications :: Qt', 'Environment :: Win32 (MS Windows)', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Topic :: Software Development', 'Topic :: Text Editors :: Integrated Development Environments (IDE)']) spyder-memory-profiler-0.2.1/spyder_memory_profiler/000077500000000000000000000000001364655112400227555ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/spyder_memory_profiler/__init__.py000066400000000000000000000007451364655112400250740ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright © 2013 Spyder Project Contributors # Licensed under the terms of the MIT License # (see LICENSE.txt for details) __version__ = '0.2.1' # ============================================================================= # The following statements are required to register this 3rd party plugin: # ============================================================================= from .memoryprofiler import MemoryProfiler PLUGIN_CLASS = MemoryProfiler spyder-memory-profiler-0.2.1/spyder_memory_profiler/data/000077500000000000000000000000001364655112400236665ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/spyder_memory_profiler/data/__init__.py000066400000000000000000000000021364655112400257670ustar00rootroot00000000000000 spyder-memory-profiler-0.2.1/spyder_memory_profiler/data/images/000077500000000000000000000000001364655112400251335ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/spyder_memory_profiler/data/images/__init__.py000066400000000000000000000000021364655112400272340ustar00rootroot00000000000000 spyder-memory-profiler-0.2.1/spyder_memory_profiler/data/images/spyder.memory_profiler.png000066400000000000000000000027671364655112400323740ustar00rootroot00000000000000PNG  IHDRw=sRGBgAMA a cHRMz&u0`:pQ<tEXtSoftwarePaint.NET v3.5.4>vPIDATHKUOg͘?mYdɲ;%K6r\ܧX~r`AG8@r80 gO|O>yzg{B90E8OxyyY+ɰ4Sfv?[:<@[y,?}62"BD,3߳jP]PfLMJ7E1[KrYF a5SWo?~]TGa [V_$-z `cS\w$;^IENDB`spyder-memory-profiler-0.2.1/spyder_memory_profiler/example/000077500000000000000000000000001364655112400244105ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/spyder_memory_profiler/example/__init__.py000066400000000000000000000000001364655112400265070ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/spyder_memory_profiler/example/profiling_test_script.py000066400000000000000000000017551364655112400314060ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- u""" :author: Joseph Martinot-Lagarde Created on Sat Jan 19 14:57:57 2013 """ from __future__ import ( print_function, division, unicode_literals, absolute_import) import subdir.profiling_test_script2 as script2 @profile def fact(n): result = 1 for i in xrange(2, n // 4): result *= i result = 1 # This is a comment for i in xrange(2, n // 16): result *= i result = 1 if False: # This won't be run raise RuntimeError("What are you doing here ???") for i in xrange(2, n + 1): result *= i return result # This is after the end of the function. if False: # This won't be run raise RuntimeError("It's getting bad.") @profile def sum_(n): result = 0 for i in xrange(1, n + 1): result += i return result if __name__ == "__main__": print(fact(120)) print(sum_(120)) print(script2.fact2(120)) print(script2.sum2(120)) spyder-memory-profiler-0.2.1/spyder_memory_profiler/example/subdir/000077500000000000000000000000001364655112400257005ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/spyder_memory_profiler/example/subdir/__init__.py000066400000000000000000000000001364655112400277770ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/spyder_memory_profiler/example/subdir/profiling_test_script2.py000066400000000000000000000007741364655112400327600ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- u""" :author: Joseph Martinot-Lagarde Created on Sat Jan 19 14:57:57 2013 """ from __future__ import ( print_function, division, unicode_literals, absolute_import) @profile def fact2(n): result = 1 for i in xrange(2, n + 1): result *= i * 2 return result @profile def sum2(n): result = 0 for i in xrange(1, n + 1): result += i * 2 return result if __name__ == "__main__": print(fact2(120)) print(sum2(120)) spyder-memory-profiler-0.2.1/spyder_memory_profiler/memoryprofiler.py000066400000000000000000000133071364655112400264060ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright © 2013 Spyder Project Contributors # Licensed under the terms of the MIT License # (see LICENSE.txt for details) """Memory profiler Plugin.""" # Third party imports from qtpy.QtCore import Qt, Signal from qtpy.QtWidgets import QVBoxLayout, QGroupBox, QLabel # Need running QApplication before importing runconfig from spyder.utils.qthelpers import qapplication MAIN_APP = qapplication() from spyder.api.plugins import SpyderPluginWidget from spyder.api.preferences import PluginConfigPage from spyder.config.base import get_translation from spyder.utils.qthelpers import get_icon, create_action # Local imports from .widgets.memoryprofiler import (MemoryProfilerWidget, is_memoryprofiler_installed) _ = get_translation("memory_profiler", dirname="spyder_memory_profiler") class MemoryProfilerConfigPage(PluginConfigPage): """ Widget with configuration options for memory profiler. """ def setup_page(self): settings_group = QGroupBox(_("Settings")) use_color_box = self.create_checkbox( _("Use deterministic colors to differentiate functions"), 'use_colors', default=True) results_group = QGroupBox(_("Results")) results_label1 = QLabel(_("Memory profiler plugin results " "(the output of memory_profiler)\n" "is stored here:")) results_label1.setWordWrap(True) # Warning: do not try to regroup the following QLabel contents with # widgets above -- this string was isolated here in a single QLabel # on purpose: to fix Issue 863 of Profiler plugon results_label2 = QLabel(MemoryProfilerWidget.DATAPATH) results_label2.setTextInteractionFlags(Qt.TextSelectableByMouse) results_label2.setWordWrap(True) settings_layout = QVBoxLayout() settings_layout.addWidget(use_color_box) settings_group.setLayout(settings_layout) results_layout = QVBoxLayout() results_layout.addWidget(results_label1) results_layout.addWidget(results_label2) results_group.setLayout(results_layout) vlayout = QVBoxLayout() vlayout.addWidget(settings_group) vlayout.addWidget(results_group) vlayout.addStretch(1) self.setLayout(vlayout) class MemoryProfiler(SpyderPluginWidget): """Memory profiler.""" CONF_SECTION = 'memoryprofiler' CONF_DEFAULTS = [(CONF_SECTION, {'use_colors': True})] CONFIGWIDGET_CLASS = MemoryProfilerConfigPage def __init__(self, parent=None): SpyderPluginWidget.__init__(self, parent) # Create widget and add to dockwindow self.widget = MemoryProfilerWidget(self.main) layout = QVBoxLayout() layout.addWidget(self.widget) self.setLayout(layout) def update_pythonpath(self): """ Update the PYTHONPATH used when running the line_profiler. This function is called whenever the Python path set in Spyder changes. It synchronizes the PYTHONPATH in the line_profiler widget with the PYTHONPATH in Spyder. """ self.widget.spyder_pythonpath = self.main.get_spyder_pythonpath() # --- SpyderPluginWidget API ---------------------------------------------- def get_plugin_title(self): """Return widget title.""" return _("Memory profiler") def get_plugin_icon(self): """Return widget icon.""" return get_icon('profiler.png') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level. """ return self.widget.datatree def get_plugin_actions(self): """Return a list of actions related to plugin.""" return [] def on_first_registration(self): """Action to be performed on first plugin registration.""" self.main.tabify_plugins(self.main.help, self) self.dockwidget.hide() def register_plugin(self): """Register plugin in Spyder's main window.""" super(MemoryProfiler, self).register_plugin() # Spyder PYTHONPATH self.update_pythonpath() self.main.sig_pythonpath_changed.connect(self.update_pythonpath) self.widget.datatree.edit_goto.connect(self.main.editor.load) self.widget.redirect_stdio.connect(self.main.redirect_internalshell_stdio) memoryprofiler_act = create_action(self, _("Profile memory line by line"), icon=self.get_plugin_icon(), shortcut="Ctrl+Shift+F10", triggered=self.run_memoryprofiler) memoryprofiler_act.setEnabled(is_memoryprofiler_installed()) self.main.run_menu_actions += [memoryprofiler_act] self.main.editor.pythonfile_dependent_actions += [memoryprofiler_act] def refresh_plugin(self): """Refresh memory profiler widget.""" pass def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed.""" return True def apply_plugin_settings(self, options): """Apply configuration file's plugin settings.""" pass # --- Public API ---------------------------------------------------------- def run_memoryprofiler(self): """Run memory profiler.""" self.analyze(self.main.editor.get_current_filename()) def analyze(self, filename): """Reimplement analyze method.""" if self.dockwidget: self.switch_to_plugin() self.widget.analyze( filename, use_colors=self.get_option('use_colors', True)) spyder-memory-profiler-0.2.1/spyder_memory_profiler/widgets/000077500000000000000000000000001364655112400244235ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/spyder_memory_profiler/widgets/__init__.py000066400000000000000000000000021364655112400265240ustar00rootroot00000000000000 spyder-memory-profiler-0.2.1/spyder_memory_profiler/widgets/memoryprofiler.py000066400000000000000000000572411364655112400300610ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright © 2011 Spyder Project Contributors # Licensed under the terms of the MIT License # (see LICENSE.txt for details) """ Memory Profiler widget See the official documentation of memory_profiler: https://pypi.python.org/pypi/memory_profiler/ """ # Standar library imports from __future__ import with_statement import hashlib import inspect import linecache import os import os.path as osp import sys import time # Third party imports from qtpy.compat import getopenfilename from qtpy.QtCore import (QByteArray, QProcess, Qt, QTextCodec, QProcessEnvironment, Signal) from qtpy.QtGui import QBrush, QColor, QFont from qtpy.QtWidgets import (QHBoxLayout, QWidget, QMessageBox, QVBoxLayout, QLabel, QTreeWidget, QTreeWidgetItem, QApplication) from spyder.config.base import get_conf_path, get_translation from spyder.plugins.variableexplorer.widgets.texteditor import TextEditor from spyder.utils import programs from spyder.utils.misc import add_pathlist_to_PYTHONPATH, get_python_executable from spyder.utils.qthelpers import create_toolbutton, get_icon from spyder.widgets.comboboxes import PythonModulesComboBox try: from spyder.py3compat import to_text_string, getcwd except ImportError: # python2 to_text_string = unicode getcwd = os.getcwdu locale_codec = QTextCodec.codecForLocale() _ = get_translation("memory_profiler", dirname="spyder_memory_profiler") COL_NO = 0 COL_USAGE = 1 COL_INCREMENT = 2 COL_LINE = 3 COL_POS = 0 # Position is not displayed but set as Qt.UserRole CODE_NOT_RUN_COLOR = QBrush(QColor.fromRgb(128, 128, 128, 200)) WEBSITE_URL = 'https://pypi.python.org/pypi/memory_profiler/' def is_memoryprofiler_installed(): """ Checks if the library for memory_profiler is installed. """ return programs.is_module_installed('memory_profiler') class MemoryProfilerWidget(QWidget): """ Memory profiler widget. """ DATAPATH = get_conf_path('memoryprofiler.results') VERSION = '0.0.1' redirect_stdio = Signal(bool) sig_finished = Signal() def __init__(self, parent): QWidget.__init__(self, parent) # Need running QApplication before importing runconfig from spyder.preferences import runconfig self.runconfig = runconfig self.spyder_pythonpath = None self.setWindowTitle("Memory profiler") self.output = None self.error_output = None self.use_colors = True self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton( self, icon=get_icon('run.png'), text=_("Profile memory usage"), tip=_("Run memory profiler"), triggered=(lambda checked=False: self.analyze()), text_beside_icon=True) self.stop_button = create_toolbutton( self, icon=get_icon('terminate.png'), text=_("Stop"), tip=_("Stop current profiling"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton( self, icon=get_icon('fileopen.png'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton( self, icon=get_icon('log.png'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = MemoryProfilerDataTree(self) self.collapse_button = create_toolbutton( self, icon=get_icon('collapse.png'), triggered=lambda dD=-1: self.datatree.collapseAll(), tip=_('Collapse all')) self.expand_button = create_toolbutton( self, icon=get_icon('expand.png'), triggered=lambda dD=1: self.datatree.expandAll(), tip=_('Expand all')) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) if not is_memoryprofiler_installed(): for widget in (self.datatree, self.filecombo, self.log_button, self.start_button, self.stop_button, browse_button, self.collapse_button, self.expand_button): widget.setDisabled(True) text = _( 'Please install the memory_profiler module' ) % WEBSITE_URL self.datelabel.setText(text) self.datelabel.setOpenExternalLinks(True) else: pass # self.show_data() def analyze(self, filename=None, wdir=None, args=None, pythonpath=None, use_colors=True): self.use_colors = use_colors if not is_memoryprofiler_installed(): return self.kill_if_running() # FIXME: storing data is not implemented yet if filename is not None: filename = osp.abspath(to_text_string(filename)) index = self.filecombo.findText(filename) if index == -1: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count()-1) else: self.filecombo.setCurrentIndex(index) self.filecombo.selected() if self.filecombo.is_valid(): filename = to_text_string(self.filecombo.currentText()) runconf = self.runconfig.get_run_configuration(filename) if runconf is not None: if wdir is None: if runconf.wdir_enabled: wdir = runconf.wdir elif runconf.cw_dir: wdir = os.getcwd() elif runconf.file_dir: wdir = osp.dirname(filename) elif runconf.fixed_dir: wdir = runconf.dir if args is None: if runconf.args_enabled: args = runconf.args if wdir is None: wdir = osp.dirname(filename) if pythonpath is None: pythonpath = self.spyder_pythonpath self.start(wdir, args, pythonpath) def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd(), _("Python scripts")+" (*.py ; *.pyw)") self.redirect_stdio.emit(False) if filename: self.analyze(filename) def show_log(self): if self.output: editor = TextEditor(self.output, title=_("Memory profiler output"), readonly=True) # Call .show() to dynamically resize editor; # see spyder-ide/spyder#12202 editor.show() editor.exec_() def show_errorlog(self): if self.error_output: editor = TextEditor(self.error_output, title=_("Memory profiler output"), readonly=True) # Call .show() to dynamically resize editor; # see spyder-ide/spyder#12202 editor.show() editor.exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect(self.finished) self.stop_button.clicked.connect(self.process.kill) if pythonpath is not None: env = [to_text_string(_pth) for _pth in self.process.systemEnvironment()] add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' # remove previous results, since memory_profiler appends to output file # instead of replacing if osp.isfile(self.DATAPATH): os.remove(self.DATAPATH) if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): filename = osp.normpath(filename).replace(os.sep, '/') p_args = ['-m', 'memory_profiler', '-o', '"' + self.DATAPATH + '"', '"' + filename + '"'] if args: p_args.extend(programs.shell_split(args)) executable = get_python_executable() executable += ' ' + ' '.join(p_args) executable = executable.replace(os.sep, '/') self.process.start(executable) else: p_args = ['-m', 'memory_profiler', '-o', self.DATAPATH, filename] if args: p_args.extend(programs.shell_split(args)) executable = get_python_executable() self.process.start(executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self): self.set_running_state(False) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox self.show_data(justanalyzed=True) self.sig_finished.emit() def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled( self.output is not None and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return self.datatree.load_data(self.DATAPATH) self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.show_tree() text_style = "%s " date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) self.datelabel.setText(date_text) class MemoryProfilerDataTree(QTreeWidget): """ Convenience tree widget (with built-in model) to store and view memory profiler data. """ edit_goto = Signal(str, int, str) def __init__(self, parent=None): QTreeWidget.__init__(self, parent) self.header_list = [ _('Line #'), _('Memory usage'), _('Increment'), _('Line contents')] self.stats = None # To be filled by self.load_data() self.max_time = 0 # To be filled by self.load_data() self.header().setDefaultAlignment(Qt.AlignCenter) self.setColumnCount(len(self.header_list)) self.setHeaderLabels(self.header_list) self.clear() self.itemActivated.connect(self.item_activated) def show_tree(self): """Populate the tree with memory profiler data and display it.""" self.clear() # Clear before re-populating self.setItemsExpandable(True) self.setSortingEnabled(False) self.populate_tree() self.expandAll() for col in range(self.columnCount()-1): self.resizeColumnToContents(col) self.collapseAll() self.setSortingEnabled(True) self.sortItems(COL_POS, Qt.AscendingOrder) def load_data(self, profdatafile): """Load memory profiler data saved by memory_profiler module""" # NOTE: Description of lstats below is for line_profiler. Here we # create a mock Lstats class to emulate this behaviour, so we can reuse # the spyder_line_profiler code. The structure of lstats is the same, # but the entries (line_no, hits, total_time) are replaced # by (line_no, usage, increment) # lstats has the following layout : # lstats.timings = # {(filename1, line_no1, function_name1): # [(line_no1, hits1, total_time1), # (line_no2, hits2, total_time2)], # (filename2, line_no2, function_name2): # [(line_no1, hits1, total_time1), # (line_no2, hits2, total_time2), # (line_no3, hits3, total_time3)]} # lstats.unit = time_factor with open(profdatafile, 'r') as fid: reslines = fid.readlines() # get the results into an "lstats"-like format so that the code below # (originally for line_profiler) can be used without much modification class Lstats(object): def __init__(self): self.timings = {} lstats = Lstats() # find lines in results where new function starts newFuncAtLine = [] for i, line in enumerate(reslines): if line.startswith('Filename: '): newFuncAtLine.append(i) # parse results from each function for i in newFuncAtLine: # filename filename = reslines[i].rstrip()[10:] # line number l = reslines[i+4].lstrip() line_no = int(l[:l.find(' ')]) # function name l = reslines[i+5] function_name = l[l.find('def')+4:l.find('(')] # initiate structure lstats.timings[(filename, line_no, function_name)] = [] # parse lines, add code lines and memory usage of current function for l in reslines[i+4:]: l = l.lstrip() # break on empty line (we have ended the current function results) if l == '': break # split string (discard empty strings using filter) stuff = list(filter(None, l.split(' '))) # get line number, mem usage, and mem increment lineno = int(stuff[0]) if len(stuff) >= 5 and stuff[2] == 'MiB': usage = float(stuff[1]) else: usage = None if len(stuff) >= 5 and stuff[4] == 'MiB': increment = float(stuff[3]) else: increment = None # append lstats.timings[(filename, line_no, function_name)].append( (lineno, usage, increment)) # First pass to group by filename self.stats = dict() linecache.checkcache() for func_info, stats in lstats.timings.items(): # func_info is a tuple containing (filename, line, function anme) filename, start_line_no = func_info[:2] # Read code start_line_no -= 1 # include the @profile decorator all_lines = linecache.getlines(filename) block_lines = inspect.getblock(all_lines[start_line_no:]) # Loop on each line of code func_stats = [] func_initial_usage = stats[0][1] func_peak_usage = 0.0 next_stat_line = 0 for line_no, code_line in enumerate(block_lines): line_no += start_line_no + 1 # Lines start at 1 code_line = code_line.rstrip('\n') if (next_stat_line >= len(stats) or line_no != stats[next_stat_line][0]): # Line didn't run usage, increment = None, None else: # Compute line stats usage, increment = stats[next_stat_line][1:] if usage is not None: func_peak_usage = max(func_peak_usage, usage-func_initial_usage) next_stat_line += 1 func_stats.append( [line_no, code_line, usage, increment]) # Fill dict self.stats[func_info] = [func_stats, func_peak_usage] def fill_item(self, item, filename, line_no, code, usage, increment): item.setData(COL_POS, Qt.UserRole, (osp.normpath(filename), line_no)) item.setData(COL_NO, Qt.DisplayRole, line_no) item.setData(COL_LINE, Qt.DisplayRole, code) if usage is None: usage = '' else: usage = '%.3f MiB' % (usage) item.setData(COL_USAGE, Qt.DisplayRole, usage) item.setTextAlignment(COL_USAGE, Qt.AlignCenter) if increment is None: increment = '' else: increment = '%.3f MiB' % (increment) item.setData(COL_INCREMENT, Qt.DisplayRole, increment) item.setTextAlignment(COL_INCREMENT, Qt.AlignCenter) def populate_tree(self): """Create each item (and associated data) in the tree""" if not self.stats: warn_item = QTreeWidgetItem(self) warn_item.setData( 0, Qt.DisplayRole, _('No timings to display. ' 'Did you forget to add @profile decorators ?') .format(url=WEBSITE_URL)) warn_item.setFirstColumnSpanned(True) warn_item.setTextAlignment(0, Qt.AlignCenter) font = warn_item.font(0) font.setStyle(QFont.StyleItalic) warn_item.setFont(0, font) return try: monospace_font = self.window().editor.get_plugin_font() except AttributeError: # If run standalone for testing monospace_font = QFont("Courier New") monospace_font.setPointSize(10) for func_info, func_data in self.stats.items(): # Function name and position filename, start_line_no, func_name = func_info func_stats, func_peak_usage = func_data func_item = QTreeWidgetItem(self) func_item.setData( 0, Qt.DisplayRole, _('{func_name} (peak {peak_usage:.3f} MiB) in file "{filename}", ' 'line {line_no}').format( filename=filename, line_no=start_line_no, func_name=func_name, peak_usage=func_peak_usage)) func_item.setFirstColumnSpanned(True) func_item.setData(COL_POS, Qt.UserRole, (osp.normpath(filename), start_line_no)) # For sorting by time func_item.setData(COL_USAGE, Qt.DisplayRole, func_peak_usage) func_item.setData(COL_INCREMENT, Qt.DisplayRole, func_peak_usage) if self.parent().use_colors: # Choose deteministic unique color for the function md5 = hashlib.md5((filename + func_name).encode("utf8")).hexdigest() hue = (int(md5[:2], 16) - 68) % 360 # avoid blue (unreadable) func_color = QColor.fromHsv(hue, 200, 255) else: # Red color only func_color = QColor.fromRgb(255, 0, 0) # get max increment max_increment = 0 for line_info in func_stats: (line_no, code_line, usage, increment) = line_info if increment is not None: max_increment = max(max_increment, increment) # Lines of code for line_info in func_stats: line_item = QTreeWidgetItem(func_item) (line_no, code_line, usage, increment) = line_info self.fill_item( line_item, filename, line_no, code_line, usage, increment) # Color background if increment is not None: if increment > 0 and max_increment > 0: alpha = increment / max_increment else: alpha = 0 color = QColor(func_color) color.setAlphaF(alpha) # Returns None color = QBrush(color) for col in range(self.columnCount()): line_item.setBackground(col, color) else: for col in range(self.columnCount()): line_item.setForeground(col, CODE_NOT_RUN_COLOR) # Monospace font for code line_item.setFont(COL_LINE, monospace_font) def item_activated(self, item): filename, line_no = item.data(COL_POS, Qt.UserRole) self.edit_goto.emit(filename, line_no, '') def test(): """Run widget test""" from spyder.utils.qthelpers import qapplication app = qapplication() widget = MemoryProfilerWidget(None) widget.resize(800, 600) widget.show() widget.analyze(osp.normpath(osp.join(osp.dirname(__file__), os.pardir, 'tests/profiling_test_script.py')), use_colors=True) sys.exit(app.exec_()) if __name__ == '__main__': test() spyder-memory-profiler-0.2.1/spyder_memory_profiler/widgets/tests/000077500000000000000000000000001364655112400255655ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/spyder_memory_profiler/widgets/tests/__init__.py000066400000000000000000000000001364655112400276640ustar00rootroot00000000000000spyder-memory-profiler-0.2.1/spyder_memory_profiler/widgets/tests/test_memoryprofiler.py000066400000000000000000000046631364655112400322620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright © 2017 Spyder Project Contributors # Licensed under the terms of the MIT License # (see LICENSE.txt for details) """Tests for memoryprofiler.py.""" from __future__ import division # Standard library imports import os import sys # Third party imports import pytest from pytestqt import qtbot from qtpy.QtCore import Qt from spyder.utils.qthelpers import qapplication MAIN_APP = qapplication() # Local imports from spyder_memory_profiler.widgets.memoryprofiler import MemoryProfilerWidget try: from unittest.mock import Mock except ImportError: from mock import Mock # Python 2 TEST_SCRIPT = \ """@profile def foo(): a = [1] * (10 ** 6) b = [2] * (2 * 10 ** 7) del b return a foo()""" @pytest.mark.qt_log_level_fail('WARNING') def test_profile_and_display_results(qtbot, tmpdir, monkeypatch): """ Run profiler on simple script and check that results are okay. This is a fairly simple integration test which checks that the plugin works on a basic level. """ os.chdir(tmpdir.strpath) testfilename = tmpdir.join('test_foo.py').strpath with open(testfilename, 'w') as f: f.write(TEST_SCRIPT) MockQMessageBox = Mock() monkeypatch.setattr('spyder_memory_profiler.widgets.memoryprofiler.QMessageBox', MockQMessageBox) widget = MemoryProfilerWidget(None) qtbot.addWidget(widget) with qtbot.waitSignal(widget.sig_finished, timeout=10000, raising=True): widget.analyze(testfilename) MockQMessageBox.assert_not_called() dt = widget.datatree assert dt.topLevelItemCount() == 1 # number of functions profiled top = dt.topLevelItem(0) assert top.data(0, Qt.DisplayRole).startswith('foo ') assert top.childCount() == 6 for i in range(6): assert top.child(i).data(0, Qt.DisplayRole) == i + 1 # line no # Column 2 has increment (in MiB); displayed as 'xxx MiB' so need to strip # last 4 characters. To make the test robust, we only check the sign assert float(top.child(2).data(2, Qt.DisplayRole)[:-4]) > 0 assert float(top.child(3).data(2, Qt.DisplayRole)[:-4]) > 0 # Test in next line is broken because of bug in memory_profiler # https://github.com/pythonprofilers/memory_profiler/issues/226 # assert float(top.child(4).data(2, Qt.DisplayRole)[:-4]) < 0 assert float(top.child(5).data(2, Qt.DisplayRole)[:-4]) == 0