diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6a17436b4..5b96bdc084 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -347,9 +347,9 @@ jobs: shell: powershell run: | $wc = New-Object System.Net.WebClient - $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-win64.zip", "thirdparty-tools.zip") + $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win64.zip", "thirdparty-tools.zip") Expand-Archive -Path thirdparty-tools.zip - Move-Item -Path thirdparty-tools/panda3d-1.10.11/thirdparty -Destination . + Move-Item -Path thirdparty-tools/panda3d-1.10.13/thirdparty -Destination . - name: Get thirdparty packages (macOS) if: runner.os == 'macOS' run: | @@ -358,10 +358,39 @@ jobs: mv panda3d-1.10.13/thirdparty thirdparty rmdir panda3d-1.10.13 (cd thirdparty/darwin-libs-a && rm -rf rocket) - - name: Set up Python 3.9 - uses: actions/setup-python@v2 + + - name: Set up Python 3.11 + uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.11' + - name: Build Python 3.11 + shell: bash + run: | + python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 + - name: Test Python 3.11 + shell: bash + run: | + python -m pip install pytest + PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Build Python 3.10 + shell: bash + run: | + python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 + - name: Test Python 3.10 + shell: bash + run: | + python -m pip install pytest + PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + + - name: Set up Python 3.9 + uses: actions/setup-python@v4 + with: + python-version: '3.9' - name: Build Python 3.9 shell: bash run: | @@ -371,10 +400,11 @@ jobs: run: | python -m pip install pytest PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + - name: Set up Python 3.8 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: '3.8' - name: Build Python 3.8 shell: bash run: | @@ -384,10 +414,11 @@ jobs: run: | python -m pip install pytest PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + - name: Set up Python 3.7 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: 3.7 + python-version: '3.7' - name: Build Python 3.7 shell: bash run: | @@ -397,6 +428,7 @@ jobs: run: | python -m pip install pytest PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + - name: Make installer run: | python makepanda/makepackage.py --verbose --lzma diff --git a/README.md b/README.md index e9f82265e4..b6af9a2c91 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on building them from source. -- https://www.panda3d.org/download/panda3d-1.10.12/panda3d-1.10.12-tools-win64.zip -- https://www.panda3d.org/download/panda3d-1.10.12/panda3d-1.10.12-tools-win32.zip +- https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win64.zip +- https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win32.zip After acquiring these dependencies, you can build Panda3D from the command prompt using the following command. Change the `--msvc-version` option based diff --git a/contrib/src/rplight/pssmCameraRig.cxx b/contrib/src/rplight/pssmCameraRig.cxx index f9744e2129..410ded4c83 100644 --- a/contrib/src/rplight/pssmCameraRig.cxx +++ b/contrib/src/rplight/pssmCameraRig.cxx @@ -296,9 +296,10 @@ void PSSMCameraRig::compute_pssm_splits(const LMatrix4& transform, float max_dis // Reset the film size, offset and far-plane Camera* cam = DCAST(Camera, _cam_nodes[i].node()); - cam->get_lens()->set_film_size(1, 1); - cam->get_lens()->set_film_offset(0, 0); - cam->get_lens()->set_near_far(1, 100); + Lens *lens = cam->get_lens(); + lens->set_film_size(1, 1); + lens->set_film_offset(0, 0); + lens->set_near_far(1, 100); // Find a good initial position _cam_nodes[i].set_pos(cam_start); @@ -320,16 +321,16 @@ void PSSMCameraRig::compute_pssm_splits(const LMatrix4& transform, float max_dis if (_max_film_sizes[i].get_x() < film_size.get_x()) _max_film_sizes[i].set_x(film_size.get_x()); if (_max_film_sizes[i].get_y() < film_size.get_y()) _max_film_sizes[i].set_y(film_size.get_y()); - cam->get_lens()->set_film_size(_max_film_sizes[i] * filmsize_bias); + lens->set_film_size(_max_film_sizes[i] * filmsize_bias); } else { // If we don't use a fixed film size, we can just set the film size // on the lens. - cam->get_lens()->set_film_size(film_size * filmsize_bias); + lens->set_film_size(film_size * filmsize_bias); } // Compute new film offset - cam->get_lens()->set_film_offset(film_offset); - cam->get_lens()->set_near_far(10, best_max_extent.get_z()); + lens->set_film_offset(film_offset); + lens->set_near_far(10, best_max_extent.get_z()); _camera_nearfar[i] = LVecBase2(10, best_max_extent.get_z()); // Compute the camera MVP @@ -399,7 +400,8 @@ void PSSMCameraRig::update(NodePath cam_node, const LVecBase3 &light_vector) { } // Do the actual PSSM - compute_pssm_splits( transform, _pssm_distance / lens->get_far(), light_vector ); + double far_recip = std::max(1.0 / (double)lens->get_far(), (double)lens_far_limit); + compute_pssm_splits( transform, _pssm_distance * far_recip, light_vector ); _update_collector.stop(); } diff --git a/dtool/src/interrogate/interrogate.cxx b/dtool/src/interrogate/interrogate.cxx index 0c515ac662..942eb905f5 100644 --- a/dtool/src/interrogate/interrogate.cxx +++ b/dtool/src/interrogate/interrogate.cxx @@ -513,7 +513,7 @@ main(int argc, char **argv) { for (i = 1; i < argc; ++i) { Filename filename = Filename::from_os_specific(argv[i]); if (!parser.parse_file(filename)) { - cerr << "Error parsing file: '" << argv[i] << "'\n"; + cerr << "interrogate failed to parse file: '" << argv[i] << "'\n"; exit(1); } builder.add_source_file(filename.to_os_generic()); diff --git a/dtool/src/interrogatedb/py_compat.h b/dtool/src/interrogatedb/py_compat.h index 8cdcb107cf..ed58977190 100644 --- a/dtool/src/interrogatedb/py_compat.h +++ b/dtool/src/interrogatedb/py_compat.h @@ -211,6 +211,10 @@ INLINE PyObject *_PyLong_Lshift(PyObject *a, size_t shiftby) { /* Python 3.9 */ +#ifndef PyCFunction_CheckExact +# define PyCFunction_CheckExact(op) (Py_TYPE(op) == &PyCFunction_Type) +#endif + #if PY_VERSION_HEX < 0x03090000 INLINE PyObject *PyObject_CallNoArgs(PyObject *func) { #if PY_VERSION_HEX >= 0x03080000 diff --git a/dtool/src/parser-inc/Python.h b/dtool/src/parser-inc/Python.h index 5fa71246a5..ec9ad04008 100644 --- a/dtool/src/parser-inc/Python.h +++ b/dtool/src/parser-inc/Python.h @@ -25,6 +25,9 @@ typedef _object PyObject; struct _typeobject; typedef _typeobject PyTypeObject; +struct _frame; +typedef _frame PyFrameObject; + typedef struct {} PyStringObject; typedef struct {} PyUnicodeObject; diff --git a/makepanda/installer.nsi b/makepanda/installer.nsi index bf4b2013be..977e99bdac 100644 --- a/makepanda/installer.nsi +++ b/makepanda/installer.nsi @@ -375,6 +375,7 @@ SectionGroup "Python modules" SecGroupPython !insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd !insertmacro PyBindingSection 3.10-32 .cp310-win32.pyd !insertmacro PyBindingSection 3.11-32 .cp311-win32.pyd + !insertmacro PyBindingSection 3.12-32 .cp312-win32.pyd !else !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd @@ -383,6 +384,7 @@ SectionGroup "Python modules" SecGroupPython !insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd !insertmacro PyBindingSection 3.10 .cp310-win_amd64.pyd !insertmacro PyBindingSection 3.11 .cp311-win_amd64.pyd + !insertmacro PyBindingSection 3.12 .cp312-win_amd64.pyd !endif SectionGroupEnd @@ -492,6 +494,7 @@ Function .onInit !insertmacro MaybeEnablePyBindingSection 3.9-32 !insertmacro MaybeEnablePyBindingSection 3.10-32 !insertmacro MaybeEnablePyBindingSection 3.11-32 + !insertmacro MaybeEnablePyBindingSection 3.12-32 ${EndIf} !else !insertmacro MaybeEnablePyBindingSection 3.5 @@ -502,6 +505,7 @@ Function .onInit !insertmacro MaybeEnablePyBindingSection 3.9 !insertmacro MaybeEnablePyBindingSection 3.10 !insertmacro MaybeEnablePyBindingSection 3.11 + !insertmacro MaybeEnablePyBindingSection 3.12 ${EndIf} !endif @@ -519,6 +523,10 @@ Function .onInit SectionSetFlags ${SecPyBindings3.11} ${SF_RO} SectionSetInstTypes ${SecPyBindings3.11} 0 !endif + !ifdef SecPyBindings3.12 + SectionSetFlags ${SecPyBindings3.12} ${SF_RO} + SectionSetInstTypes ${SecPyBindings3.12} 0 + !endif ${EndUnless} FunctionEnd @@ -831,6 +839,7 @@ Section Uninstall !insertmacro RemovePythonPath 3.9-32 !insertmacro RemovePythonPath 3.10-32 !insertmacro RemovePythonPath 3.11-32 + !insertmacro RemovePythonPath 3.12-32 !else !insertmacro RemovePythonPath 3.5 !insertmacro RemovePythonPath 3.6 @@ -839,6 +848,7 @@ Section Uninstall !insertmacro RemovePythonPath 3.9 !insertmacro RemovePythonPath 3.10 !insertmacro RemovePythonPath 3.11 + !insertmacro RemovePythonPath 3.12 !endif SetDetailsPrint both @@ -908,6 +918,7 @@ SectionEnd !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32) !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10-32} $(DESC_SecPyBindings3.10-32) !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11-32} $(DESC_SecPyBindings3.11-32) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.12-32} $(DESC_SecPyBindings3.12-32) !else !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5) !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6) @@ -916,6 +927,7 @@ SectionEnd !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9) !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10} $(DESC_SecPyBindings3.10) !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11} $(DESC_SecPyBindings3.11) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.12} $(DESC_SecPyBindings3.12) !endif !ifdef INCLUDE_PYVER !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 0fe083fea7..9a64e4ed51 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -3815,6 +3815,7 @@ IGATEFILES=GetDirectoryContents('panda/src/pstatclient', ["*.h", "*_composite*.c IGATEFILES.remove("config_pstats.h") TargetAdd('libp3pstatclient.in', opts=OPTS, input=IGATEFILES) TargetAdd('libp3pstatclient.in', opts=['IMOD:panda3d.core', 'ILIB:libp3pstatclient', 'SRCDIR:panda/src/pstatclient']) +PyTargetAdd('p3pstatclient_pStatClient_ext.obj', opts=OPTS, input='pStatClient_ext.cxx') # # DIRECTORY: panda/src/gobj/ @@ -4244,6 +4245,7 @@ PyTargetAdd('core.pyd', input='p3putil_ext_composite.obj') PyTargetAdd('core.pyd', input='p3pnmimage_pfmFile_ext.obj') PyTargetAdd('core.pyd', input='p3event_asyncFuture_ext.obj') PyTargetAdd('core.pyd', input='p3event_pythonTask.obj') +PyTargetAdd('core.pyd', input='p3pstatclient_pStatClient_ext.obj') PyTargetAdd('core.pyd', input='p3gobj_ext_composite.obj') PyTargetAdd('core.pyd', input='p3pgraph_ext_composite.obj') PyTargetAdd('core.pyd', input='p3display_ext_composite.obj') diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py index 5c0ba76623..281ba23564 100644 --- a/makepanda/makepandacore.py +++ b/makepanda/makepandacore.py @@ -433,7 +433,7 @@ def SetTarget(target, arch=None): ANDROID_ABI = 'x86_64' ANDROID_TRIPLE = 'x86_64-linux-android' else: - exit('Android architecture must be arm, armv7a, arm64, mips, mips64, x86 or x86_64') + exit('Android architecture must be arm, armv7a, arm64, mips, mips64, x86 or x86_64, use --arch to specify') ANDROID_TRIPLE += str(ANDROID_API) TOOLCHAIN_PREFIX = ANDROID_TRIPLE + '-' diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx index edeffcea39..75c2502bde 100644 --- a/panda/src/gobj/texture.cxx +++ b/panda/src/gobj/texture.cxx @@ -381,8 +381,7 @@ Texture(const string &name) : _reloading = false; CDWriter cdata(_cycler, true); - do_set_format(cdata, F_rgb); - do_set_component_type(cdata, T_unsigned_byte); + cdata->inc_properties_modified(); } /** @@ -10782,11 +10781,10 @@ CData() { _y_size = 1; _z_size = 1; _num_views = 1; - - // We will override the format in a moment (in the Texture constructor), but - // set it to something else first to avoid the check in do_set_format - // depending on an uninitialized value. - _format = F_rgba; + _num_components = 3; + _component_width = 1; + _format = F_rgb; + _component_type = T_unsigned_byte; // Only used for buffer textures. _usage_hint = GeomEnums::UH_unspecified; diff --git a/panda/src/pstatclient/CMakeLists.txt b/panda/src/pstatclient/CMakeLists.txt index 2aa8e37f77..faffec2588 100644 --- a/panda/src/pstatclient/CMakeLists.txt +++ b/panda/src/pstatclient/CMakeLists.txt @@ -22,6 +22,12 @@ set(P3PSTATCLIENT_SOURCES pStatThread.cxx ) +set(P3PSTATCLIENT_IGATEEXT + pStatClient_ext.I + pStatClient_ext.cxx + pStatClient_ext.h +) + composite_sources(p3pstatclient P3PSTATCLIENT_SOURCES) add_component_library(p3pstatclient SYMBOL BUILDING_PANDA_PSTATCLIENT ${P3PSTATCLIENT_HEADERS} ${P3PSTATCLIENT_SOURCES}) @@ -31,7 +37,7 @@ if(HAVE_NET AND WANT_NATIVE_NET) target_link_libraries(p3pstatclient p3net) endif() -target_interrogate(p3pstatclient ALL) +target_interrogate(p3pstatclient ALL EXTENSIONS ${P3PSTATCLIENT_IGATEEXT}) if(NOT BUILD_METALIBS) install(TARGETS p3pstatclient diff --git a/panda/src/pstatclient/config_pstatclient.cxx b/panda/src/pstatclient/config_pstatclient.cxx index 7f9443cdb4..cfe2040e49 100644 --- a/panda/src/pstatclient/config_pstatclient.cxx +++ b/panda/src/pstatclient/config_pstatclient.cxx @@ -87,6 +87,16 @@ ConfigVariableBool pstats_thread_profiling PRC_DESC("Set this true to query the system for thread statistics, such as " "the number of context switches and time spent waiting.")); +ConfigVariableBool pstats_python_profiler +("pstats-python-profiler", false, + PRC_DESC("Set this true to integrate with the Python profiler to show " + "detailed information about individual Python functions in " + "PStats, similar to the information offered by Python's built-in " + "profiler. This can be really useful to find bottlenecks in a " + "Python program, but enabling this will slow down the application " + "somewhat, and requires a recent version of the PStats server, so " + "it is not enabled by default.")); + // The rest are different in that they directly control the server, not the // client. ConfigVariableBool pstats_scroll_mode diff --git a/panda/src/pstatclient/config_pstatclient.h b/panda/src/pstatclient/config_pstatclient.h index 856031e1f2..f4a083afdd 100644 --- a/panda/src/pstatclient/config_pstatclient.h +++ b/panda/src/pstatclient/config_pstatclient.h @@ -39,6 +39,7 @@ extern EXPCL_PANDA_PSTATCLIENT ConfigVariableInt pstats_port; extern EXPCL_PANDA_PSTATCLIENT ConfigVariableDouble pstats_target_frame_rate; extern EXPCL_PANDA_PSTATCLIENT ConfigVariableBool pstats_gpu_timing; extern EXPCL_PANDA_PSTATCLIENT ConfigVariableBool pstats_thread_profiling; +extern EXPCL_PANDA_PSTATCLIENT ConfigVariableBool pstats_python_profiler; extern EXPCL_PANDA_PSTATCLIENT ConfigVariableBool pstats_scroll_mode; extern EXPCL_PANDA_PSTATCLIENT ConfigVariableDouble pstats_history; diff --git a/panda/src/pstatclient/pStatClient.h b/panda/src/pstatclient/pStatClient.h index d164cb29b1..f6dfd18a42 100644 --- a/panda/src/pstatclient/pStatClient.h +++ b/panda/src/pstatclient/pStatClient.h @@ -29,6 +29,7 @@ #include "patomic.h" #include "numeric_types.h" #include "bitArray.h" +#include "extension.h" class PStatClientImpl; class PStatCollector; @@ -88,8 +89,8 @@ PUBLISHED: MAKE_PROPERTY(current_thread, get_current_thread); MAKE_PROPERTY(real_time, get_real_time); - INLINE static bool connect(const std::string &hostname = std::string(), int port = -1); - INLINE static void disconnect(); + EXTEND INLINE static bool connect(const std::string &hostname = std::string(), int port = -1); + EXTEND INLINE static void disconnect(); INLINE static bool is_connected(); INLINE static void resume_after_pause(); @@ -101,8 +102,8 @@ PUBLISHED: void client_main_tick(); void client_thread_tick(); void client_thread_tick(const std::string &sync_name); - bool client_connect(std::string hostname, int port); - void client_disconnect(); + EXTEND bool client_connect(std::string hostname, int port); + EXTEND void client_disconnect(); bool client_is_connected() const; void client_resume_after_pause(); @@ -258,6 +259,8 @@ private: friend class PStatThread; friend class PStatClientImpl; friend class GraphicsStateGuardian; + + friend class Extension; }; #include "pStatClient.I" diff --git a/panda/src/pstatclient/pStatClientImpl.cxx b/panda/src/pstatclient/pStatClientImpl.cxx index 69415569fb..eaa55fe56b 100644 --- a/panda/src/pstatclient/pStatClientImpl.cxx +++ b/panda/src/pstatclient/pStatClientImpl.cxx @@ -566,6 +566,13 @@ send_hello() { message._major_version = get_current_pstat_major_version(); message._minor_version = get_current_pstat_minor_version(); + // The Python profiling feature may send nested start/stop pairs, so requires + // a server version capable of dealing with this. + if (pstats_python_profiler && message._major_version <= 3) { + message._major_version = 3; + message._minor_version = std::max(message._minor_version, 1); + } + Datagram datagram; message.encode(datagram); _writer.send(datagram, _tcp_connection, true); diff --git a/panda/src/pstatclient/pStatClient_ext.I b/panda/src/pstatclient/pStatClient_ext.I new file mode 100644 index 0000000000..c1c255d418 --- /dev/null +++ b/panda/src/pstatclient/pStatClient_ext.I @@ -0,0 +1,31 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatClient_ext.I + * @author rdb + * @date 2022-11-29 + */ + +/** + * Attempts to establish a connection to the indicated PStatServer. Returns + * true if successful, false on failure. + */ +INLINE bool Extension:: +connect(const std::string &hostname, int port) { + PStatClient *client = PStatClient::get_global_pstats(); + return invoke_extension(client).client_connect(hostname, port); +} + +/** + * Closes the connection previously established. + */ +INLINE void Extension:: +disconnect() { + PStatClient *client = PStatClient::get_global_pstats(); + invoke_extension(client).client_disconnect(); +} diff --git a/panda/src/pstatclient/pStatClient_ext.cxx b/panda/src/pstatclient/pStatClient_ext.cxx new file mode 100644 index 0000000000..391bfcb7ba --- /dev/null +++ b/panda/src/pstatclient/pStatClient_ext.cxx @@ -0,0 +1,336 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatClient_ext.cxx + * @author rdb + * @date 2022-11-23 + */ + +#include "pStatClient_ext.h" + +#if defined(HAVE_PYTHON) && defined(DO_PSTATS) + +#include "pStatCollector.h" +#include "config_pstatclient.h" + +#ifndef CPPPARSER +#include "frameobject.h" +#endif + +static bool _python_profiler_enabled = false; + +// Used to cache stuff onto PyCodeObjects. +static Py_ssize_t _extra_index = -1; + +// Stores a mapping between C method definitions and collector indices. +static pmap _c_method_collectors; + +// Parent collector for all Python profiling collectors. +static PStatCollector code_collector("App:Python"); + +/** + * Walks up the type hierarchy to find the class where the method originates. + */ +static bool +find_method(PyTypeObject *&cls, PyObject *name, PyCodeObject *code) { + PyObject *meth = _PyType_Lookup(cls, name); + if (meth == nullptr || !PyFunction_Check(meth) || + PyFunction_GET_CODE(meth) != (PyObject *)code) { + return false; + } + + if (cls->tp_bases != nullptr) { + Py_ssize_t size = PyTuple_GET_SIZE(cls->tp_bases); + for (Py_ssize_t i = 0; i < size; ++i) { + PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(cls->tp_bases, i); + + if (find_method(base, name, code)) { + cls = base; + return true; + } + } + } + + // Didn't find it in any of the bases, it must be defined here. + return true; +} + +/** + * Returns the collector for a Python frame. + */ +static int +#ifdef __GNUC__ +__attribute__ ((noinline)) +#elif defined(_MSC_VER) +__declspec(noinline) +#endif +make_python_frame_collector(PyFrameObject *frame, PyCodeObject *code) { +#if PY_VERSION_HEX >= 0x030B0000 // 3.11 + // Fetch the module name out of the frame's global scope. + PyObject *globals = PyFrame_GetGlobals(frame); + PyObject *py_mod_name = PyDict_GetItemString(globals, "__name__"); + Py_DECREF(globals); + + const char *mod_name = py_mod_name ? PyUnicode_AsUTF8(py_mod_name) : ""; + const char *meth_name = PyUnicode_AsUTF8(code->co_qualname); + char buffer[1024]; + size_t len = snprintf(buffer, sizeof(buffer), "%s:%s", mod_name, meth_name); + for (size_t i = 0; i < len - 1; ++i) { + if (buffer[i] == '.') { + buffer[i] = ':'; + } + } +#else + // Try to figure out the type name. There's no obvious way to do this. + // It's possible that the first argument passed to this function is the + // self instance or the current type (for a classmethod), but we have to + // double-check that to make sure. + PyTypeObject *cls = nullptr; + if (code->co_argcount >= 1) { + PyFrame_FastToLocals(frame); + PyObject *first_arg = PyDict_GetItem(frame->f_locals, PyTuple_GET_ITEM(code->co_varnames, 0)); + cls = PyType_Check(first_arg) ? (PyTypeObject *)first_arg : Py_TYPE(first_arg); + if ((cls->tp_flags & Py_TPFLAGS_HEAPTYPE) != 0) { + // Mangling scheme for methods starting (but not ending) with "__" + PyObject *meth_name = code->co_name; + Py_ssize_t len = PyUnicode_GET_LENGTH(meth_name); + if (len >= 2 && PyUnicode_READ_CHAR(meth_name, 0) == '_' && PyUnicode_READ_CHAR(meth_name, 1) == '_' && + (len < 4 || PyUnicode_READ_CHAR(meth_name, len - 1) != '_' || PyUnicode_READ_CHAR(meth_name, len - 2) != '_')) { + const char *cls_name = cls->tp_name; + while (cls_name[0] == '_') { + ++cls_name; + } + meth_name = PyUnicode_FromFormat("_%s%S", cls_name, meth_name); + } else { + Py_INCREF(meth_name); + } + if (!find_method(cls, meth_name, code)) { + // Not a matching method object, it's something else. Forget it. + cls = nullptr; + } + Py_DECREF(meth_name); + } else { + cls = nullptr; + } + } + + // Fetch the module name out of the frame's global scope. + PyObject *py_mod_name = PyDict_GetItemString(frame->f_globals, "__name__"); + if (py_mod_name == nullptr && cls != nullptr) { + py_mod_name = PyDict_GetItemString(cls->tp_dict, "__module__"); + } + + const char *mod_name = py_mod_name ? PyUnicode_AsUTF8(py_mod_name) : ""; + char buffer[1024]; + size_t len = snprintf(buffer, sizeof(buffer), "%s:", mod_name); + for (size_t i = 0; i < len - 1; ++i) { + if (buffer[i] == '.') { + buffer[i] = ':'; + } + } + + const char *meth_name = PyUnicode_AsUTF8(code->co_name); + if (cls != nullptr) { + len += snprintf(buffer + len, sizeof(buffer) - len, "%s:%s", cls->tp_name, meth_name); + } else { + len += snprintf(buffer + len, sizeof(buffer) - len, "%s", meth_name); + } +#endif + + // Add parentheses, unless it's something special like + if (len < sizeof(buffer) - 2 && buffer[len - 1] != '>') { + buffer[len++] = '('; + buffer[len++] = ')'; + buffer[len] = '\0'; + } + + PStatCollector collector(code_collector, buffer); + intptr_t collector_index = collector.get_index(); + if (_extra_index != -1) { + _PyCode_SetExtra((PyObject *)code, _extra_index, (void *)collector_index); + } + return collector_index; +} + +/** + * Creates a collector for a C function. + */ +static int +#ifdef __GNUC__ +__attribute__ ((noinline)) +#elif defined(_MSC_VER) +__declspec(noinline) +#endif +make_c_function_collector(PyCFunctionObject *meth) { + char buffer[1024]; + size_t len; + if (meth->m_self != nullptr && !PyModule_Check(meth->m_self)) { + PyTypeObject *cls = PyType_Check(meth->m_self) ? (PyTypeObject *)meth->m_self : Py_TYPE(meth->m_self); + + const char *dot = strrchr(cls->tp_name, '.'); + if (dot != nullptr) { + // The module name is included in the type name. + snprintf(buffer, sizeof(buffer), "%s:%s()", cls->tp_name, meth->m_ml->ml_name); + len = (dot - cls->tp_name) + 1; + } else { + // If there's no module name, we need to get it from __module__. + PyObject *py_mod_name = cls->tp_dict ? PyDict_GetItemString(cls->tp_dict, "__module__") : nullptr; + const char *mod_name; + if (py_mod_name != nullptr) { + mod_name = PyUnicode_AsUTF8(py_mod_name); + } else { + // Is it a built-in, like int or dict? + PyObject *builtins = PyEval_GetBuiltins(); + if (PyDict_GetItemString(builtins, cls->tp_name) == (PyObject *)cls) { + mod_name = "builtins"; + } else { + mod_name = ""; + } + } + len = snprintf(buffer, sizeof(buffer), "%s:%s:%s()", mod_name, cls->tp_name, meth->m_ml->ml_name) - 2; + } + } + else if (meth->m_self != nullptr) { + const char *mod_name = PyModule_GetName(meth->m_self); + len = snprintf(buffer, sizeof(buffer), "%s:%s()", mod_name, meth->m_ml->ml_name) - 2; + } + else { + snprintf(buffer, sizeof(buffer), "%s()", meth->m_ml->ml_name); + len = 0; + } + for (size_t i = 0; i < len; ++i) { + if (buffer[i] == '.') { + buffer[i] = ':'; + } + } + PStatCollector collector(code_collector, buffer); + int collector_index = collector.get_index(); + _c_method_collectors[meth->m_ml] = collector.get_index(); + return collector_index; +} + +/** + * Attempts to establish a connection to the indicated PStatServer. Returns + * true if successful, false on failure. + */ +bool Extension:: +client_connect(std::string hostname, int port) { + extern struct Dtool_PyTypedObject Dtool_PStatThread; + + if (_this->client_connect(std::move(hostname), port)) { + // Pass a PStatThread as argument. + if (!_python_profiler_enabled && pstats_python_profiler) { + PStatThread *thread = new PStatThread(_this->get_current_thread()); + PyObject *arg = DTool_CreatePyInstance((void *)thread, Dtool_PStatThread, true, false); + if (_extra_index == -1) { + _extra_index = _PyEval_RequestCodeExtraIndex(nullptr); + } + PyEval_SetProfile(&trace_callback, arg); + _python_profiler_enabled = false; + } + return true; + } + else if (_python_profiler_enabled) { + PyEval_SetProfile(nullptr, nullptr); + _python_profiler_enabled = false; + } + return false; +} + +/** + * Closes the connection previously established. + */ +void Extension:: +client_disconnect() { + _this->client_disconnect(); + if (_python_profiler_enabled) { + PyEval_SetProfile(nullptr, nullptr); + _python_profiler_enabled = false; + } +} + +/** + * Callback passed to PyEval_SetProfile. + */ +int Extension:: +trace_callback(PyObject *py_thread, PyFrameObject *frame, int what, PyObject *arg) { + intptr_t collector_index; + + if (what == PyTrace_CALL || what == PyTrace_RETURN || what == PyTrace_EXCEPTION) { + // Normal Python frame entry/exit. +#if PY_VERSION_HEX >= 0x030B0000 // 3.11 + PyCodeObject *code = PyFrame_GetCode(frame); +#else + PyCodeObject *code = frame->f_code; +#endif + + // The index for this collector is cached on the code object. + if (_PyCode_GetExtra((PyObject *)code, _extra_index, (void **)&collector_index) != 0 || collector_index == 0) { + collector_index = make_python_frame_collector(frame, code); + } + +#if PY_VERSION_HEX >= 0x030B0000 // 3.11 + Py_DECREF(code); +#endif + } else if (what == PyTrace_C_CALL || what == PyTrace_C_RETURN || what == PyTrace_C_EXCEPTION) { + // Call to a C function or method, which has no frame of its own. + if (PyCFunction_CheckExact(arg)) { + PyCFunctionObject *meth = (PyCFunctionObject *)arg; + auto it = _c_method_collectors.find(meth->m_ml); + if (it != _c_method_collectors.end()) { + collector_index = it->second; + } else { + collector_index = make_c_function_collector(meth); + } + } else { + return 0; + } + } else { + return 0; + } + + if (collector_index <= 0) { + return 0; + } + + PStatThread &pthread = *(PStatThread *)DtoolInstance_VOID_PTR(py_thread); + PStatClient *client = pthread.get_client(); + if (!client->client_is_connected()) { + // Client was disconnected, disable Python profiling. + PyEval_SetProfile(nullptr, nullptr); + _python_profiler_enabled = false; + return 0; + } + + int thread_index = pthread.get_index(); + +#ifdef _DEBUG + nassertr(collector_index >= 0 && collector_index < client->get_num_collectors(), -1); + nassertr(thread_index >= 0 && thread_index < client->get_num_threads(), -1); +#endif + + PStatClient::Collector *collector = client->get_collector_ptr(collector_index); + PStatClient::InternalThread *thread = client->get_thread_ptr(thread_index); + + if (collector->is_active() && thread->_is_active) { + double as_of = client->get_real_time(); + + LightMutexHolder holder(thread->_thread_lock); + if (thread->_thread_active) { + if (what == PyTrace_CALL || what == PyTrace_C_CALL) { + thread->_frame_data.add_start(collector_index, as_of); + } else { + thread->_frame_data.add_stop(collector_index, as_of); + } + } + } + + return 0; +} + +#endif // HAVE_PYTHON && DO_PSTATS diff --git a/panda/src/pstatclient/pStatClient_ext.h b/panda/src/pstatclient/pStatClient_ext.h new file mode 100644 index 0000000000..216d7777b1 --- /dev/null +++ b/panda/src/pstatclient/pStatClient_ext.h @@ -0,0 +1,49 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatClient_ext.h + * @author rdb + * @date 2022-11-23 + */ + +#ifndef PSTATCLIENT_EXT_H +#define PSTATCLIENT_EXT_H + +#include "dtoolbase.h" + +#if defined(HAVE_PYTHON) && defined(DO_PSTATS) + +#include "extension.h" +#include "pStatClient.h" +#include "py_panda.h" + +typedef struct _frame PyFrameObject; + +/** + * This class defines the extension methods for PStatClient, which are called + * instead of any C++ methods with the same prototype. + */ +template<> +class Extension : public ExtensionBase { +public: + INLINE static bool connect(const std::string &hostname = std::string(), int port = -1); + INLINE static void disconnect(); + + bool client_connect(std::string hostname, int port); + void client_disconnect(); + +private: + static int trace_callback(PyObject *py_thread, PyFrameObject *frame, + int what, PyObject *arg); +}; + +#include "pStatClient_ext.I" + +#endif // HAVE_PYTHON && DO_PSTATS + +#endif // PSTATCLIENT_EXT_H diff --git a/panda/src/pstatclient/pStatCollector.h b/panda/src/pstatclient/pStatCollector.h index aeb74f7cdb..2fa98c68cf 100644 --- a/panda/src/pstatclient/pStatCollector.h +++ b/panda/src/pstatclient/pStatCollector.h @@ -43,11 +43,9 @@ class Thread; class EXPCL_PANDA_PSTATCLIENT PStatCollector { #ifdef DO_PSTATS -private: - INLINE PStatCollector(PStatClient *client, int index); - public: PStatCollector() = default; + INLINE PStatCollector(PStatClient *client, int index); PUBLISHED: INLINE explicit PStatCollector(const std::string &name, diff --git a/panda/src/pstatclient/pStatProperties.cxx b/panda/src/pstatclient/pStatProperties.cxx index c44d06d575..8edd0c586c 100644 --- a/panda/src/pstatclient/pStatProperties.cxx +++ b/panda/src/pstatclient/pStatProperties.cxx @@ -27,9 +27,10 @@ using std::string; static const int current_pstat_major_version = 3; static const int current_pstat_minor_version = 0; -// Initialized at 2.0 on 51801, when version numbers were first added. -// Incremented to 2.1 on 52101 to add support for TCP frame data. Incremented -// to 3.0 on 42805 to bump TCP headers to 32 bits. +// Initialized at 2.0 on 5/18/01, when version numbers were first added. +// Incremented to 2.1 on 5/21/01 to add support for TCP frame data. +// Incremented to 3.0 on 4/28/05 to bump TCP headers to 32 bits. +// Incremented to 3.1 on 11/29/22 to support nested start/stop pairs. /** * Returns the current major version number of the PStats protocol. This is diff --git a/panda/src/pstatclient/pStatThread.I b/panda/src/pstatclient/pStatThread.I index fb68d4d3f9..af1b8c8bff 100644 --- a/panda/src/pstatclient/pStatThread.I +++ b/panda/src/pstatclient/pStatThread.I @@ -82,3 +82,11 @@ INLINE int PStatThread:: get_index() const { return _index; } + +/** + * + */ +INLINE PStatClient *PStatThread:: +get_client() const { + return _client; +} diff --git a/panda/src/pstatclient/pStatThread.h b/panda/src/pstatclient/pStatThread.h index e3f4acb7e5..45a4a38e4b 100644 --- a/panda/src/pstatclient/pStatThread.h +++ b/panda/src/pstatclient/pStatThread.h @@ -45,6 +45,9 @@ PUBLISHED: MAKE_PROPERTY(thread, get_thread); MAKE_PROPERTY(index, get_index); +public: + PStatClient *get_client() const; + private: PStatClient *_client; int _index; diff --git a/pandatool/src/gtk-stats/gtkStatsStripChart.cxx b/pandatool/src/gtk-stats/gtkStatsStripChart.cxx index ba1df8a7b9..ee3fc547a1 100644 --- a/pandatool/src/gtk-stats/gtkStatsStripChart.cxx +++ b/pandatool/src/gtk-stats/gtkStatsStripChart.cxx @@ -15,6 +15,7 @@ #include "gtkStatsMonitor.h" #include "pStatCollectorDef.h" #include "numeric_types.h" +#include "string_utils.h" static const int default_strip_chart_width = 400; static const int default_strip_chart_height = 100; @@ -119,7 +120,7 @@ new_data(int thread_index, int frame_number) { if (!_pause) { update(); - std::string text = format_number(get_average_net_value(), get_guide_bar_units(), get_guide_bar_unit_name()); + std::string text = get_total_text(); if (_net_value_text != text) { _net_value_text = text; gtk_label_set_text(GTK_LABEL(_total_label), _net_value_text.c_str()); diff --git a/pandatool/src/pstatserver/pStatReader.cxx b/pandatool/src/pstatserver/pStatReader.cxx index 3d94ffcd50..10d05d8f14 100644 --- a/pandatool/src/pstatserver/pStatReader.cxx +++ b/pandatool/src/pstatserver/pStatReader.cxx @@ -194,7 +194,8 @@ handle_client_control_message(const PStatClientControlMessage &message) { if (message._major_version != server_major_version || (message._major_version == server_major_version && - message._minor_version > server_minor_version)) { + message._minor_version > server_minor_version && + (message._major_version != 3 || message._minor_version != 1))) { _monitor->bad_version(message._client_hostname, message._client_progname, message._client_pid, message._major_version, message._minor_version, diff --git a/pandatool/src/pstatserver/pStatStripChart.cxx b/pandatool/src/pstatserver/pStatStripChart.cxx index e0859dd965..530916cd97 100644 --- a/pandatool/src/pstatserver/pStatStripChart.cxx +++ b/pandatool/src/pstatserver/pStatStripChart.cxx @@ -306,6 +306,21 @@ get_title_text() { return text; } +/** + * Returns the text suitable for the total label above the graph. + */ +std::string PStatStripChart:: +get_total_text() { + std::string text = format_number(get_average_net_value(), get_guide_bar_units(), get_guide_bar_unit_name()); + if (get_collector_index() != 0 && !_view.get_show_level()) { + const PStatViewLevel *level = _view.get_level(get_collector_index()); + if (level != nullptr && level->get_count() > 0) { + text += " / " + format_string(level->get_count()) + "x"; + } + } + return text; +} + /** * Called when the mouse hovers over a label, and should return the text that * should appear on the tooltip. diff --git a/pandatool/src/pstatserver/pStatStripChart.h b/pandatool/src/pstatserver/pStatStripChart.h index abfcd1ec99..7a6a4924ca 100644 --- a/pandatool/src/pstatserver/pStatStripChart.h +++ b/pandatool/src/pstatserver/pStatStripChart.h @@ -70,8 +70,9 @@ public: INLINE int height_to_pixel(double value) const; INLINE double pixel_to_height(int y) const; - bool is_title_unknown() const; + INLINE bool is_title_unknown() const; std::string get_title_text(); + std::string get_total_text(); std::string get_label_tooltip(int collector_index) const; virtual void write_datagram(Datagram &dg) const final; diff --git a/pandatool/src/pstatserver/pStatView.cxx b/pandatool/src/pstatserver/pStatView.cxx index 7d26455e7c..c72213bf17 100644 --- a/pandatool/src/pstatserver/pStatView.cxx +++ b/pandatool/src/pstatserver/pStatView.cxx @@ -15,13 +15,9 @@ #include "pStatFrameData.h" #include "pStatCollectorDef.h" -#include "vector_int.h" -#include "plist.h" -#include "pset.h" #include - - +#include /** * This class is used within this module only--in fact, within @@ -30,94 +26,89 @@ */ class FrameSample { public: - typedef plist Started; + void start(double time, FrameSample *started) { + // Keep track of nested start/stop pairs. We only consider the outer one. + if (_started++ > 0) { + return; + } - FrameSample() { - _touched = false; - _is_started = false; - _pushed = false; - _net_time = 0.0; + nassertv(!_pushed); + _net_time -= time; + push_all(time, started); + nassertv(_next == nullptr && _prev == nullptr); + _prev = started->_prev; + _next = started; + _prev->_next = this; + started->_prev = this; } - void data_point(double time, bool is_start, Started &started) { - _touched = true; - // We only consider events that change the startstop state. With two - // consecutive 'start' events, for instance, we ignore the second one. + void stop(double time, FrameSample *started) { + nassertv(_started > 0); + if (--_started > 0) { + return; + } -/* - * *** That's not quite the right thing to do. We should keep track of the - * nesting level and bracket things correctly, so that we ignore the second - * start and the *first* stop, but respect the outer startstop. For the short - * term, this works, because the client is already doing this logic and won't - * send us nested startstop pairs, but we'd like to generalize this in the - * future so we can deal with these nested pairs properly. - */ - nassertv(is_start != _is_started); - - _is_started = is_start; + nassertv(_next != nullptr && _prev != nullptr); if (_pushed) { - nassertv(!_is_started); - Started::iterator si = find(started.begin(), started.end(), this); - nassertv(si != started.end()); - started.erase(si); - + _prev->_next = _next; + _next->_prev = _prev; + _next = _prev = nullptr; } else { - if (_is_started) { - _net_time -= time; - push_all(time, started); - started.push_back(this); - } else { - _net_time += time; - Started::iterator si = find(started.begin(), started.end(), this); - nassertv(si != started.end()); - started.erase(si); - pop_one(time, started); - } + _net_time += time; + _prev->_next = _next; + _next->_prev = _prev; + _next = _prev = nullptr; + pop_one(time, started); } } + +private: void push(double time) { if (!_pushed) { _pushed = true; - if (_is_started) { + if (_started > 0) { _net_time += time; } } } + void pop(double time) { if (_pushed) { _pushed = false; - if (_is_started) { + if (_started > 0) { _net_time -= time; } } } - void push_all(double time, Started &started) { - Started::iterator si; - for (si = started.begin(); si != started.end(); ++si) { - (*si)->push(time); + void push_all(double time, FrameSample *started) { + for (FrameSample *sample = started->_next; + sample != started; sample = sample->_next) { + sample->push(time); } } - void pop_one(double time, Started &started) { - Started::reverse_iterator si; - for (si = started.rbegin(); si != started.rend(); ++si) { - if ((*si)->_pushed) { - (*si)->pop(time); + void pop_one(double time, FrameSample *started) { + for (FrameSample *sample = started->_prev; + sample != started; sample = sample->_prev) { + if (sample->_pushed) { + sample->pop(time); return; } } } - bool _touched; - bool _is_started; - bool _pushed; - double _net_time; +public: + FrameSample *_next = nullptr; + FrameSample *_prev = nullptr; + double _net_time = 0.0; + int _started = 0; + int _count = 0; + bool _pushed = false; + bool _is_new = false; }; - - /** * */ @@ -279,19 +270,19 @@ get_level(int collector) { void PStatView:: update_time_data(const PStatFrameData &frame_data) { int num_events = frame_data.get_num_events(); + int num_collectors = _client_data->get_num_collectors(); - typedef pvector Samples; - Samples samples(_client_data->get_num_collectors()); + typedef std::vector Samples; + Samples samples(num_collectors); - FrameSample::Started started; + // Keep a linked list of started samples. + FrameSample started; + started._next = &started; + started._prev = &started; _all_collectors_known = true; - - // This tracks the set of samples we actually care about. - typedef pset GotSamples; - GotSamples got_samples; - + int new_collectors = 0; int i; for (i = 0; i < num_events; i++) { int collector_index = frame_data.get_time_collector(i); @@ -301,42 +292,40 @@ update_time_data(const PStatFrameData &frame_data) { _all_collectors_known = false; } else { - nassertv(collector_index >= 0 && collector_index < (int)samples.size()); + nassertv(collector_index >= 0 && collector_index < num_collectors); if (_client_data->get_child_distance(_constraint, collector_index) >= 0) { // Here's a data point we care about: anything at constraint level or // below. - if (is_start == samples[collector_index]._is_started) { - if (!is_start) { - // A "stop" in the middle of a frame implies a "start" since time - // 0 (that is, since the first data point in the frame). - samples[collector_index].data_point(frame_data.get_time(0), true, started); - samples[collector_index].data_point(frame_data.get_time(i), is_start, started); - } else { - // An extra "start" for a collector that's already started is an - // error. - nout << "Unexpected data point for " - << _client_data->get_collector_fullname(collector_index) - << "\n"; - } + if (is_start) { + samples[collector_index].start(frame_data.get_time(i), &started); + samples[collector_index]._count++; } else { - samples[collector_index].data_point(frame_data.get_time(i), is_start, started); - got_samples.insert(collector_index); + // A "stop" in the middle of a frame implies a "start" since time + // 0 (that is, since the first data point in the frame). + if (samples[collector_index]._started == 0) { + samples[collector_index].start(frame_data.get_time(0), &started); + } + samples[collector_index].stop(frame_data.get_time(i), &started); + } + + if (!samples[collector_index]._is_new) { + samples[collector_index]._is_new = true; + ++new_collectors; } } } } // Make sure everything is stopped. - Samples::iterator si; for (i = 0, si = samples.begin(); si != samples.end(); ++i, ++si) { - if ((*si)._is_started) { - (*si).data_point(frame_data.get_end(), false, started); + if ((*si)._started > 0) { + (*si).stop(frame_data.get_end(), &started); } } - nassertv(started.empty()); + nassertv(started._next == &started && started._prev == &started); bool any_new_levels = false; @@ -356,11 +345,11 @@ update_time_data(const PStatFrameData &frame_data) { } int collector_index = level->_collector; - GotSamples::iterator gi; - gi = got_samples.find(collector_index); - if (gi != got_samples.end()) { + if (samples[collector_index]._is_new) { level->_value_alone = samples[collector_index]._net_time; - got_samples.erase(gi); + level->_count = samples[collector_index]._count; + samples[collector_index]._is_new = false; + --new_collectors; } li = lnext; @@ -368,14 +357,15 @@ update_time_data(const PStatFrameData &frame_data) { // Finally, any samples left over in the got_samples set are new collectors // that we need to add to the Levels list. - if (!got_samples.empty()) { + if (new_collectors > 0) { any_new_levels = true; - GotSamples::const_iterator gi; - for (gi = got_samples.begin(); gi != got_samples.end(); ++gi) { - int collector_index = (*gi); - PStatViewLevel *level = get_level(collector_index); - level->_value_alone = samples[*gi]._net_time; + for (int collector_index = 0; collector_index < num_collectors; ++collector_index) { + if (samples[collector_index]._is_new) { + PStatViewLevel *level = get_level(collector_index); + level->_value_alone = samples[collector_index]._net_time; + level->_count = samples[collector_index]._count; + } } } @@ -509,6 +499,7 @@ bool PStatView:: reset_level(PStatViewLevel *level) { bool any_changed = false; level->_value_alone = 0.0; + level->_count = 0; if (level->_collector == _constraint) { return false; diff --git a/pandatool/src/pstatserver/pStatViewLevel.I b/pandatool/src/pstatserver/pStatViewLevel.I index f682c36b73..38fdec95a6 100644 --- a/pandatool/src/pstatserver/pStatViewLevel.I +++ b/pandatool/src/pstatserver/pStatViewLevel.I @@ -27,3 +27,11 @@ INLINE double PStatViewLevel:: get_value_alone() const { return _value_alone; } + +/** + * Returns the number of start/stop pairs for this collector. + */ +INLINE int PStatViewLevel:: +get_count() const { + return _count; +} diff --git a/pandatool/src/pstatserver/pStatViewLevel.h b/pandatool/src/pstatserver/pStatViewLevel.h index 006a512ddc..802850c04b 100644 --- a/pandatool/src/pstatserver/pStatViewLevel.h +++ b/pandatool/src/pstatserver/pStatViewLevel.h @@ -31,6 +31,7 @@ public: INLINE int get_collector() const; INLINE double get_value_alone() const; double get_net_value() const; + INLINE int get_count() const; void sort_children(const PStatClientData *client_data); @@ -39,6 +40,7 @@ public: private: int _collector; + int _count = 0; double _value_alone; PStatViewLevel *_parent; diff --git a/pandatool/src/win-stats/winStatsStripChart.cxx b/pandatool/src/win-stats/winStatsStripChart.cxx index de977da484..f6f894eab6 100644 --- a/pandatool/src/win-stats/winStatsStripChart.cxx +++ b/pandatool/src/win-stats/winStatsStripChart.cxx @@ -97,7 +97,7 @@ new_data(int thread_index, int frame_number) { if (!_pause) { update(); - std::string text = format_number(get_average_net_value(), get_guide_bar_units(), get_guide_bar_unit_name()); + std::string text = get_total_text(); if (_net_value_text != text) { _net_value_text = text; RECT rect;