From 25b29f55f81fb5917e9d483218933bba315c0db1 Mon Sep 17 00:00:00 2001 From: Michael Demoret <42954918+mdemoret-nv@users.noreply.github.com> Date: Thu, 5 Nov 2020 20:30:37 -0700 Subject: [PATCH] [REVIEW] Adding Cython to Code Coverage (#3111) * Adding ability to build with --linetrace=1 to support cython codecov * Adding PR to CHANGELOG * Style cleanup * Converting BUILD_PYTHON_ARGS to be a argument in build.sh --- CHANGELOG.md | 1 + build.sh | 15 +++-- ci/gpu/build.sh | 4 +- python/.coveragerc | 1 + python/cython_build_ext.py | 110 ++++++++++++++++++++++++++++--------- python/setup.cfg | 1 + 6 files changed, 100 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 181f4a8f6c..f9ad18d379 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - PR #3052: Speeding up MNMG KNN Cl&Re testing - PR #3115: Speeding up MNMG UMAP testing - PR #3112: Speed test_array +- PR #3111: Adding Cython to Code Coverage ## Bug Fixes - PR #3065: Refactoring prims metrics function names from camelcase to underscore format diff --git a/build.sh b/build.sh index 1366b0cf9c..c2b44d0f36 100755 --- a/build.sh +++ b/build.sh @@ -19,7 +19,7 @@ ARGS=$* REPODIR=$(cd $(dirname $0); pwd) VALIDTARGETS="clean libcuml cuml cpp-mgtests prims bench prims-bench cppdocs pydocs" -VALIDFLAGS="-v -g -n --allgpuarch --buildfaiss --buildgtest --singlegpu --nvtx --show_depr_warn -h --help " +VALIDFLAGS="-v -g -n --allgpuarch --buildfaiss --buildgtest --singlegpu --nvtx --show_depr_warn --codecov -h --help " VALIDARGS="${VALIDTARGETS} ${VALIDFLAGS}" HELP="$0 [ ...] [ ...] where is: @@ -43,6 +43,8 @@ HELP="$0 [ ...] [ ...] --singlegpu - Build libcuml and cuml without multigpu components --nvtx - Enable nvtx for profiling support --show_depr_warn - show cmake deprecation warnings + --codecov - Enable code coverage support by compiling with Cython linetracing + and profiling enabled (WARNING: Impacts performance) -h - print this text default action (no args) is to build and install 'libcuml', 'cuml', and 'prims' targets only for the detected GPU arch @@ -58,7 +60,7 @@ BUILD_TYPE=Release INSTALL_TARGET=install BUILD_ALL_GPU_ARCH=0 SINGLEGPU_CPP_FLAG="" -SINGLEGPU_PYTHON_FLAG="" +BUILD_PYTHON_ARGS=${BUILD_PYTHON_ARGS:=""} NVTX=OFF CLEAN=0 BUILD_DISABLE_DEPRECATION_WARNING=ON @@ -115,7 +117,7 @@ if hasArg --allgpuarch; then BUILD_ALL_GPU_ARCH=1 fi if hasArg --singlegpu; then - SINGLEGPU_PYTHON_FLAG="--singlegpu" + BUILD_PYTHON_ARGS="${BUILD_PYTHON_ARGS} --singlegpu" SINGLEGPU_CPP_FLAG=ON fi if hasArg cpp-mgtests; then @@ -133,6 +135,9 @@ fi if hasArg --show_depr_warn; then BUILD_DISABLE_DEPRECATION_WARNING=OFF fi +if hasArg --codecov; then + BUILD_PYTHON_ARGS="${BUILD_PYTHON_ARGS} --linetrace=1 --profile" +fi if hasArg clean; then CLEAN=1 fi @@ -224,9 +229,9 @@ fi if completeBuild || hasArg cuml || hasArg pydocs; then cd ${REPODIR}/python if [[ ${INSTALL_TARGET} != "" ]]; then - python setup.py build_ext -j${PARALLEL_LEVEL:-1} ${SINGLEGPU_PYTHON_FLAG} --library-dir=${LIBCUML_BUILD_DIR} install --single-version-externally-managed --record=record.txt + python setup.py build_ext -j${PARALLEL_LEVEL:-1} ${BUILD_PYTHON_ARGS} --library-dir=${LIBCUML_BUILD_DIR} install --single-version-externally-managed --record=record.txt else - python setup.py build_ext -j${PARALLEL_LEVEL:-1} --library-dir=${LIBCUML_BUILD_DIR} ${SINGLEGPU_PYTHON_FLAG} + python setup.py build_ext -j${PARALLEL_LEVEL:-1} ${BUILD_PYTHON_ARGS} --library-dir=${LIBCUML_BUILD_DIR} fi if hasArg pydocs; then diff --git a/ci/gpu/build.sh b/ci/gpu/build.sh index 9223621ebd..fcaa3f032e 100755 --- a/ci/gpu/build.sh +++ b/ci/gpu/build.sh @@ -98,7 +98,7 @@ if [[ -z "$PROJECT_FLASH" || "$PROJECT_FLASH" == "0" ]]; then ################################################################################ gpuci_logger "Build from source" - $WORKSPACE/build.sh clean libcuml cuml prims bench -v + $WORKSPACE/build.sh clean libcuml cuml prims bench -v --codecov gpuci_logger "Resetting LD_LIBRARY_PATH" @@ -190,7 +190,7 @@ else conda install -c $WORKSPACE/ci/artifacts/cuml/cpu/conda-bld/ libcuml gpuci_logger "Building cuml" - "$WORKSPACE/build.sh" -v cuml + "$WORKSPACE/build.sh" -v cuml --codecov gpuci_logger "Python pytest for cuml" cd $WORKSPACE/python diff --git a/python/.coveragerc b/python/.coveragerc index 4b8eeccf0f..1c3d0bc67f 100644 --- a/python/.coveragerc +++ b/python/.coveragerc @@ -2,3 +2,4 @@ [run] include = cuml/* omit = cuml/test/* +plugins = Cython.Coverage \ No newline at end of file diff --git a/python/cython_build_ext.py b/python/cython_build_ext.py index dc5f502d4b..0c0cb5aeb1 100644 --- a/python/cython_build_ext.py +++ b/python/cython_build_ext.py @@ -46,10 +46,15 @@ class cython_build_ext(_build_ext, object): Parameters ---------- - language_level : {"2", "3", "3str"}, default="2" - Globally set the Python language level to be used for module - compilation. Default is compatibility with Python 2. To enable Python 3 - source code semantics, set this to 3 (or 3str) + annotate : bool, default=False + If True, will produce a HTML file for each of the .pyx or .py files + compiled. The HTML file gives an indication of how much Python + interaction there is in each of the source code lines, compared to + plain C code. It also allows you to see the C/C++ code generated for + each line of Cython code. This report is invaluable when optimizing a + function for speed, and for determining when to release the GIL: in + general, a nogil block may contain only “white” code. See examples in + Determining where to add types or Primes. binding : bool, default=True Controls whether free functions behave more like Python’s CFunctions (e.g. len()) or, when set to True, more like Python’s functions. When @@ -59,46 +64,74 @@ class cython_build_ext(_build_ext, object): annotations. Changed in version 3.0.0: Default changed from False to True - profile : bool, default=False - Write hooks for Python profilers into the compiled C code. + cython_exclude : list of str + When passing glob patterns as module_list, you can exclude certain + module names explicitly by passing them into the exclude option. embedsignature : bool, default=False If set to True, Cython will embed a textual copy of the call signature in the docstring of all Python visible functions and classes. Tools like IPython and epydoc can thus display the signature, which cannot otherwise be retrieved after compilation. - cython_exclude : list of str - When passing glob patterns as module_list, you can exclude certain - module names explicitly by passing them into the exclude option. gdb_debug : bool, default=False Passes the `gdb_debug` argument to `cythonize()`. Setting up debugging for Cython can be difficult. See the debugging docs here https://cython.readthedocs.io/en/latest/src/userguide/debugging.html + language_level : {"2", "3", "3str"}, default="2" + Globally set the Python language level to be used for module + compilation. Default is compatibility with Python 2. To enable Python 3 + source code semantics, set this to 3 (or 3str) + linetrace : bool, default=False + Write line tracing hooks for Python profilers or coverage reporting + into the compiled C code. This also enables profiling. Default is + False. Note that the generated module will not actually use line + tracing, unless you additionally pass the C macro definition + ``CYTHON_TRACE=1`` to the C compiler (e.g. using the setuptools option + define_macros). Define ``CYTHON_TRACE_NOGIL=1`` to also include nogil + functions and sections. + profile : bool, default=False + Write hooks for Python profilers into the compiled C code. """ user_options = [ - ('language-level=', None, - 'Sets the python language syntax to use "2", "3", "3str".'), - ("binding", None, + ("annotate=", + None, + "Passes the `annotate` argument to `cythonize()`. See the Cython " + "docs for more info."), + ("binding", + None, "Sets the binding Cython compiler directive. See the Cython docs for " "more info."), - ("profile", None, - "Sets the profile Cython compiler directive. See the Cython docs for " - "more info."), - ("embedsignature", None, - "Sets the `embedsignature` Cython compiler directive. See the Cython " - "docs for more info."), - ("cython-exclude=", None, + ("cython-exclude=", + None, "Sets the exclude argument for `cythonize()`. See the Cython docs for" " more info."), - ("gdb-debug=", None, + ("embedsignature", + None, + "Sets the `embedsignature` Cython compiler directive. See the Cython " + "docs for more info."), + ("gdb-debug=", + None, "Passes the `gdb_debug` argument to `cythonize()`. See the Cython " - "docs for more info.") + "docs for more info."), + ('language-level=', + None, + 'Sets the python language syntax to use "2", "3", "3str".'), + ("linetrace=", + None, + "Passes the `linetrace` argument to `cythonize()`. See the Cython " + "docs for more info."), + ("profile", + None, + "Sets the profile Cython compiler directive. See the Cython docs for " + "more info."), ] + _build_ext.user_options boolean_options = [ + "annotate", "binding", - "profile", "embedsignature", "gdb-debug", + "linetrace", + "profile", ] + _build_ext.boolean_options def initialize_options(self): @@ -107,12 +140,15 @@ def initialize_options(self): detect if they were set by the user """ - self.language_level = None + self.annotate = None self.binding = None - self.profile = None - self.embedsignature = None self.cython_exclude = None + self.embedsignature = None self.gdb_debug = None + self.language_level = None + self.linetrace = None + self.profile = None + super().initialize_options() def finalize_options(self): @@ -153,6 +189,26 @@ def finalize_options(self): self.profile = bool(self.profile) compiler_directives.update({"profile": self.profile}) + if (self.linetrace is not None): + self.linetrace = bool(self.linetrace) + compiler_directives.update({"linetrace": self.linetrace}) + + # Also need the compiler directive. Only add if it hasnt been + # specified yet + for ext in self.distribution.ext_modules: + if (not hasattr(ext, "define_macros")): + ext.define_macros = [] + + found_macro = False + + for mac in ext.define_macros: + if (mac[0] == "CYTHON_TRACE_NOGIL"): + found_macro = True + break + + if not found_macro: + ext.define_macros.append(("CYTHON_TRACE_NOGIL", 1)) + if (self.embedsignature is not None): self.embedsignature = bool(self.embedsignature) compiler_directives.update( @@ -160,6 +216,10 @@ def finalize_options(self): cythonize_kwargs = {} + if (self.annotate is not None): + + cythonize_kwargs.update({"annotate": self.annotate}) + if (self.cython_exclude is not None): if (isinstance(self.cython_exclude, str)): diff --git a/python/setup.cfg b/python/setup.cfg index a4c9a02e46..ac653a6a08 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -23,4 +23,5 @@ inplace = True binding = True language_level = 3 profile = False +linetrace = False embedsignature = True \ No newline at end of file