diff --git a/.github/actions/install-deps/action.yaml b/.github/actions/install-deps/action.yaml index 31e20de7c1..7eb4704cd9 100644 --- a/.github/actions/install-deps/action.yaml +++ b/.github/actions/install-deps/action.yaml @@ -197,21 +197,6 @@ runs: fi if: ${{ runner.os == 'Linux' && inputs.cpp == 'true' && inputs.javascript == 'false' }} - - name: Install Python Pyodide dependencies - shell: bash - run: python -m pip install -r rust/perspective-python/requirements-pyodide.txt - if: ${{ inputs.pyodide == 'true' }} - - - name: Install Pyodide distribution - shell: bash - run: pnpm run install_pyodide - if: ${{ inputs.pyodide == 'true' }} - - - name: Install Python Playwright browsers - shell: bash - run: python -m playwright install - if: ${{ inputs.pyodide == 'true' }} - # - name: Install CCache # shell: bash # run: sudo apt install -y ccache diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2cf6cf0b09..19f0447884 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -398,7 +398,6 @@ jobs: id: init-step uses: ./.github/actions/install-deps with: - pyodide: "true" javascript: "false" arch: ${{ matrix.arch }} manylinux: "false" @@ -411,18 +410,64 @@ jobs: PACKAGE: "perspective-python" CI: 1 + - uses: actions/upload-artifact@v4 + with: + name: perspective-python-dist-wasm32-emscripten-${{ matrix.python-version }} + path: rust/target/wheels/*.whl + + # ~*~ Test pyodide ~*~ + test_emscripten_wheel: + runs-on: ${{ matrix.os }} + needs: [build_emscripten_wheel] + strategy: + fail-fast: false + matrix: + os: + - ubuntu-22.04 + arch: + - x86_64 + python-version: + - 3.9 + node-version: [20.x] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Config + id: config-step + uses: ./.github/actions/config + + - name: Initialize Build + uses: ./.github/actions/install-deps + with: + javascript: "false" + arch: ${{ matrix.arch }} + manylinux: "false" + skip_cache: ${{ steps.config-step.outputs.SKIP_CACHE }} + + - uses: actions/download-artifact@v4 + with: + name: perspective-python-dist-wasm32-emscripten-${{ matrix.python-version }} + path: rust/target/wheels/ + + - name: Install Python Pyodide dependencies + shell: bash + run: python -m pip install -r rust/perspective-python/requirements-pyodide.txt + + - name: Install Pyodide distribution + shell: bash + run: pnpm run install_pyodide + + - name: Install Python Playwright browsers + shell: bash + run: python -m playwright install + - name: "Test Pyodide" run: pnpm run test env: PSP_PYODIDE: 1 PACKAGE: "perspective-python" CI: 1 - - - uses: actions/upload-artifact@v4 - with: - name: perspective-python-dist-wasm32-emscripten-${{ matrix.python-version }} - path: rust/target/wheels/*.whl - # ,-,---. . . . ,--,--' . # '|___/ . . . | ,-| ,-. ,-. ,-| `- | ,-. ,-. |- # ,| \ | | | | | | ,-| | | | | , | |-' `-. | @@ -859,6 +904,7 @@ jobs: benchmark_js, benchmark_python, build_emscripten_wheel, + test_emscripten_wheel, build_and_test_rust, lint_and_docs, ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f2aefdf80..bf51bdcf31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# [v3.3.3](https://github.com/finos/perspective/releases/tag/v3.3.3) + +_4 February 2025_ ([Full changelog](https://github.com/finos/perspective/compare/v3.3.2...v3.3.3)) + +Misc + +- Heap Profiling Tooling [#2913](https://github.com/finos/perspective/pull/2913) +- Fix API docs [#2910](https://github.com/finos/perspective/pull/2910) +- Support for pyodide 0.27.1, including pyarrow + pandas [#2901](https://github.com/finos/perspective/pull/2901) + # [v3.3.2](https://github.com/finos/perspective/releases/tag/v3.3.2) _1 February 2025_ ([Full changelog](https://github.com/finos/perspective/compare/v3.3.1...v3.3.2)) @@ -96,7 +106,7 @@ Misc # [v3.1.5](https://github.com/finos/perspective/releases/tag/v3.1.5) -_15 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.5-test1...v3.1.5)) +_15 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4...v3.1.5)) Features @@ -113,14 +123,9 @@ Misc - Update ExprTk [#2825](https://github.com/finos/perspective/pull/2825) - sdist licensing follow ups [#2843](https://github.com/finos/perspective/pull/2843) -# [v3.1.5-test1](https://github.com/finos/perspective/releases/tag/v3.1.5-test1) - -_11 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4...v3.1.5-test1)) - - # [v3.1.4](https://github.com/finos/perspective/releases/tag/v3.1.4) -_7 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4-test8...v3.1.4)) +_7 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.3...v3.1.4)) Features @@ -137,56 +142,6 @@ Misc - Fix failed color range update [#2823](https://github.com/finos/perspective/pull/2823) - CI sdist fixes, port conda patch, fix ODR violation [#2826](https://github.com/finos/perspective/pull/2826) -# [v3.1.4-test8](https://github.com/finos/perspective/releases/tag/v3.1.4-test8) - -_4 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4-test7...v3.1.4-test8)) - - -# [v3.1.4-test7](https://github.com/finos/perspective/releases/tag/v3.1.4-test7) - -_1 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4-test6...v3.1.4-test7)) - - -# [v3.1.4-test6](https://github.com/finos/perspective/releases/tag/v3.1.4-test6) - -_1 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4-test5...v3.1.4-test6)) - - -# [v3.1.4-test5](https://github.com/finos/perspective/releases/tag/v3.1.4-test5) - -_1 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4-test4...v3.1.4-test5)) - - -# [v3.1.4-test4](https://github.com/finos/perspective/releases/tag/v3.1.4-test4) - -_1 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4-test3...v3.1.4-test4)) - - -# [v3.1.4-test3](https://github.com/finos/perspective/releases/tag/v3.1.4-test3) - -_1 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4-test2...v3.1.4-test3)) - - -# [v3.1.4-test2](https://github.com/finos/perspective/releases/tag/v3.1.4-test2) - -_31 October 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4-test12...v3.1.4-test2)) - - -# [v3.1.4-test12](https://github.com/finos/perspective/releases/tag/v3.1.4-test12) - -_4 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4-test11...v3.1.4-test12)) - - -# [v3.1.4-test11](https://github.com/finos/perspective/releases/tag/v3.1.4-test11) - -_4 November 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.4-test1...v3.1.4-test11)) - - -# [v3.1.4-test1](https://github.com/finos/perspective/releases/tag/v3.1.4-test1) - -_31 October 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.3...v3.1.4-test1)) - - # [v3.1.3](https://github.com/finos/perspective/releases/tag/v3.1.3) _31 October 2024_ ([Full changelog](https://github.com/finos/perspective/compare/v3.1.2...v3.1.3)) diff --git a/Cargo.lock b/Cargo.lock index 7a8b9f77eb..84accda689 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1977,7 +1977,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "perspective" -version = "3.3.2" +version = "3.3.3" dependencies = [ "async-lock", "axum", @@ -2016,7 +2016,7 @@ dependencies = [ [[package]] name = "perspective-client" -version = "3.3.2" +version = "3.3.3" dependencies = [ "async-lock", "futures", @@ -2038,7 +2038,7 @@ dependencies = [ [[package]] name = "perspective-js" -version = "3.3.2" +version = "3.3.3" dependencies = [ "anyhow", "base64 0.13.1", @@ -2068,7 +2068,7 @@ dependencies = [ [[package]] name = "perspective-lint" -version = "3.3.2" +version = "3.3.3" dependencies = [ "glob", "yew-fmt", @@ -2088,7 +2088,7 @@ dependencies = [ [[package]] name = "perspective-python" -version = "3.3.2" +version = "3.3.3" dependencies = [ "async-lock", "cmake", @@ -2109,7 +2109,7 @@ dependencies = [ [[package]] name = "perspective-server" -version = "3.3.2" +version = "3.3.3" dependencies = [ "async-lock", "cmake", @@ -2123,7 +2123,7 @@ dependencies = [ [[package]] name = "perspective-viewer" -version = "3.3.2" +version = "3.3.3" dependencies = [ "anyhow", "async-lock", diff --git a/cmake/modules/FindInstallDependency.cmake b/cmake/modules/FindInstallDependency.cmake index 840d6d9b4e..0e89113578 100644 --- a/cmake/modules/FindInstallDependency.cmake +++ b/cmake/modules/FindInstallDependency.cmake @@ -37,6 +37,7 @@ function(psp_build_dep name cmake_file) message(FATAL_ERROR "Build step for ${name} failed: ${result}") endif() endif() + if(${name} STREQUAL arrow) set(ARROW_DEFINE_OPTIONS ON) set(ARROW_BUILD_SHARED OFF) diff --git a/cpp/perspective/CMakeLists.txt b/cpp/perspective/CMakeLists.txt index d636739879..fca91b04fb 100644 --- a/cpp/perspective/CMakeLists.txt +++ b/cpp/perspective/CMakeLists.txt @@ -86,6 +86,7 @@ option(PSP_CPP_BUILD "Build the C++ Project" OFF) option(PSP_PYTHON_BUILD "Build the Python Bindings" OFF) option(PSP_CPP_BUILD_STRICT "Build the C++ with strict warnings" OFF) option(PSP_SANITIZE "Build with sanitizers" OFF) +option(PSP_HEAP_INSTRUMENTS "Build with heap inspection tooling" OFF) if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") set(PSP_WASM_BUILD ON) @@ -107,6 +108,12 @@ else() endif() endif() +if(DEFINED ENV{PSP_HEAP_INSTRUMENTS}) + set(PSP_HEAP_INSTRUMENTS ON) +else() + set(PSP_HEAP_INSTRUMENTS OFF) +endif() + if(DEFINED ENV{PSP_MANYLINUX}) set(MANYLINUX ON) else() @@ -201,6 +208,11 @@ if(NOT DEFINED PSP_WASM_EXCEPTIONS AND NOT PSP_PYTHON_BUILD) set(PSP_WASM_EXCEPTIONS ON) endif() +set(DEBUG_LEVEL "0") +if(PSP_HEAP_INSTRUMENTS) + set(DEBUG_LEVEL "3") +endif() + if(PSP_WASM_BUILD) #################### # EMSCRIPTEN BUILD # @@ -231,7 +243,7 @@ if(PSP_WASM_BUILD) ") endif() else() - set(OPT_FLAGS " -O3 -g0 ") + set(OPT_FLAGS " -O3 -g${DEBUG_LEVEL} ") if (PSP_WASM_EXCEPTIONS) set(OPT_FLAGS "${OPT_FLAGS} -fwasm-exceptions -flto --emit-tsd=perspective-server.d.ts ") endif() @@ -301,12 +313,12 @@ endif() if (PSP_WASM_EXCEPTIONS) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} \ -O3 \ - -g0 \ + -g${DEBUG_LEVEL} \ ") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ -fwasm-exceptions \ -O3 \ - -g0 \ + -g${DEBUG_LEVEL} \ ") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ @@ -338,6 +350,15 @@ if(PSP_PYODIDE) string(APPEND CMAKE_CXX_FLAGS "${RELOCATABLE_FLAGS}") endif() +if(PSP_PYODIDE) + string(APPEND CMAKE_CXX_FLAGS " -fvisibility=hidden") +endif() + +if (PSP_PYODIDE AND NOT PSP_WASM_EXCEPTIONS) + # Emscripten exceptions + string(APPEND CMAKE_CXX_FLAGS " -fexceptions") +endif() + # Build (read: download and extract) header-only dependencies from external sources set(all_deps_INCLUDE_DIRS "") psp_build_dep("date" "${PSP_CMAKE_MODULE_PATH}/date.txt.in") @@ -489,6 +510,11 @@ set(SOURCE_FILES ${PSP_CPP_SRC}/src/cpp/binding_api.cpp ) +if(PSP_HEAP_INSTRUMENTS) + list(APPEND SOURCE_FILES ${PSP_CPP_SRC}/src/cpp/heap_instruments.cpp) + add_compile_definitions(HEAP_INSTRUMENTS=1) +endif() + set(PYTHON_SOURCE_FILES ${SOURCE_FILES}) set(WASM_SOURCE_FILES ${SOURCE_FILES}) @@ -500,11 +526,33 @@ else() # set(CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS}") endif() +set(PSP_EXPORTED_FUNCTIONS + _psp_poll + _psp_new_server + _psp_free + _psp_alloc + _psp_handle_request + _psp_new_session + _psp_close_session + _psp_delete_server + _psp_is_memory64 +) + +if(PSP_HEAP_INSTRUMENTS) + list(APPEND PSP_EXPORTED_FUNCTIONS + _psp_print_used_memory + _psp_dump_stack_traces + _psp_clear_stack_traces + ) +endif() + +string(JOIN "," PSP_EXPORTED_FUNCTIONS_JOINED ${PSP_EXPORTED_FUNCTIONS}) + # Common flags for WASM/JS build and Pyodide if(PSP_PYODIDE) set(PSP_WASM_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} \ --no-entry \ - -s EXPORTED_FUNCTIONS=_psp_poll,_psp_new_server,_psp_free,_psp_alloc,_psp_handle_request,_psp_new_session,_psp_close_session,_psp_delete_server,_psp_is_memory64 \ + -s EXPORTED_FUNCTIONS=${PSP_EXPORTED_FUNCTIONS_JOINED} \ -s SIDE_MODULE=2 \ ") else() @@ -528,7 +576,7 @@ else() -s NODEJS_CATCH_REJECTION=0 \ -s USE_ES6_IMPORT_META=1 \ -s EXPORT_ES6=1 \ - -s EXPORTED_FUNCTIONS=_psp_poll,_psp_new_server,_psp_free,_psp_alloc,_psp_handle_request,_psp_new_session,_psp_close_session,_psp_delete_server,_psp_is_memory64 \ + -s EXPORTED_FUNCTIONS=${PSP_EXPORTED_FUNCTIONS_JOINED} \ ") if(PSP_WASM64) @@ -608,11 +656,6 @@ elseif(PSP_CPP_BUILD OR PSP_PYTHON_BUILD) target_include_directories(psp PRIVATE ${psp_INCLUDE_DIRS}) target_include_directories(psp SYSTEM PRIVATE ${all_deps_INCLUDE_DIRS}) target_compile_definitions(psp PRIVATE PSP_ENABLE_PYTHON=1 PSP_ENABLE_WASM=1) - # support for emscripten exceptions https://emscripten.org/docs/porting/exceptions.html#emscripten-javascript-based-exception-support - target_compile_options(psp PUBLIC -fexceptions -fvisibility=hidden) - target_compile_options(arrow_static PUBLIC -fexceptions -fvisibility=hidden) - target_compile_options(re2 PUBLIC -fexceptions -fvisibility=hidden) - target_compile_options(protos PUBLIC -fexceptions -fvisibility=hidden) else() # Cpython add_library(psp STATIC ${PYTHON_SOURCE_FILES}) @@ -631,7 +674,6 @@ elseif(PSP_CPP_BUILD OR PSP_PYTHON_BUILD) target_compile_options(psp PRIVATE -fvisibility=hidden) endif() - # Link against arrow static library # Linking against arrow_static also links against its bundled dependencies target_link_libraries(psp PRIVATE arrow_static re2 protos) else() diff --git a/cpp/perspective/build.js b/cpp/perspective/build.js index 2092acba68..c42bd601ea 100644 --- a/cpp/perspective/build.js +++ b/cpp/perspective/build.js @@ -61,7 +61,9 @@ try { execSync(`cpy web/**/* ../web`, { cwd, stdio }); execSync(`cpy node/**/* ../node`, { cwd, stdio }); - bootstrap(`../../cpp/perspective/dist/web/perspective-server.wasm`); + if (!process.env.PSP_HEAP_INSTRUMENTS) { + bootstrap(`../../cpp/perspective/dist/web/perspective-server.wasm`); + } } catch (e) { console.error(e); process.exit(1); diff --git a/cpp/perspective/package.json b/cpp/perspective/package.json index a98ed12d55..3f056da174 100644 --- a/cpp/perspective/package.json +++ b/cpp/perspective/package.json @@ -3,7 +3,7 @@ "private": true, "author": "The Perspective Authors", "license": "Apache-2.0", - "version": "3.3.2", + "version": "3.3.3", "main": "./dist/esm/perspective.cpp.js", "files": [ "dist/esm/**/*", diff --git a/cpp/perspective/src/cpp/binding_api.cpp b/cpp/perspective/src/cpp/binding_api.cpp index 46f791363c..fd189440ca 100644 --- a/cpp/perspective/src/cpp/binding_api.cpp +++ b/cpp/perspective/src/cpp/binding_api.cpp @@ -18,6 +18,16 @@ #include #include +#if HEAP_INSTRUMENTS +#include + +#define UNINSTRUMENTED_MALLOC(x) emscripten_builtin_malloc(x) +#define UNINSTRUMENTED_FREE(x) emscripten_builtin_free(x) +#else +#define UNINSTRUMENTED_MALLOC(x) malloc(x) +#define UNINSTRUMENTED_FREE(x) free(x) +#endif + using namespace perspective::server; #pragma pack(push, 1) @@ -102,14 +112,16 @@ psp_close_session(ProtoServer* server, std::uint32_t client_id) { PERSPECTIVE_EXPORT std::size_t psp_alloc(std::size_t size) { - auto* mem = (char*)malloc(size); + // We use this to allocate stack traces for instrumentation with heap + // profiling builds. + auto* mem = (char*)UNINSTRUMENTED_MALLOC(size); return (size_t)mem; } PERSPECTIVE_EXPORT void psp_free(void* ptr) { - free(ptr); + UNINSTRUMENTED_FREE(ptr); } PERSPECTIVE_EXPORT diff --git a/cpp/perspective/src/cpp/heap_instruments.cpp b/cpp/perspective/src/cpp/heap_instruments.cpp new file mode 100644 index 0000000000..9c54ae9fe2 --- /dev/null +++ b/cpp/perspective/src/cpp/heap_instruments.cpp @@ -0,0 +1,234 @@ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ +// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ +// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ +// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ +// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ +// ┃ Copyright (c) 2017, the Perspective Authors. ┃ +// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃ +// ┃ This file is part of the Perspective library, distributed under the terms ┃ +// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ +// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +#include "perspective/base.h" +#include "perspective/heap_instruments.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static std::uint64_t USED_MEMORY = 0; + +static constexpr std::uint64_t MIN_RELEVANT_SIZE = 5 * 1024 * 1024; + +extern "C" void +psp_print_used_memory() { + printf("Used memory: %llu\n", USED_MEMORY); +} + +struct Header { + std::uint64_t size; +}; + +using UnderlyingString = + std::basic_string, UnderlyingAllocator>; + +using UnderlyingIStringStream = std::basic_istringstream< + char, + std::char_traits, + UnderlyingAllocator>; + +struct AllocMeta { + Header header; + const UnderlyingString* trace; + std::uint64_t size; +}; + +static std::unordered_map< + UnderlyingString, + AllocMeta, + std::hash, + std::equal_to<>, + UnderlyingAllocator>> + stack_traces; + +static UnderlyingString IRRELEVANT = "irrelevant"; + +static inline void +record_stack_trace(Header* header, std::uint64_t size) { + if (size >= MIN_RELEVANT_SIZE) { + const char* stack_c_str = perspective::psp_stack_trace(); + UnderlyingIStringStream stack(stack_c_str); + UnderlyingString line; + UnderlyingString out; + + while (std::getline(stack, line)) { + line = line.substr(0, line.find_last_of(" (")); + out += line + "\n"; + } + + emscripten_builtin_free(const_cast(stack_c_str)); + + // stack_traces[ptr] = {.header = *header, .trace = out}; + if (stack_traces.find(out) == stack_traces.end()) { + stack_traces[out] = + AllocMeta{.header = *header, .trace = nullptr, .size = size}; + + stack_traces[out].trace = &stack_traces.find(out)->first; + } else { + stack_traces[out].size += size; + } + } else { + if (stack_traces.find(IRRELEVANT) == stack_traces.end()) { + stack_traces[IRRELEVANT] = AllocMeta{ + .header = *header, .trace = &IRRELEVANT, .size = size + }; + } else { + stack_traces[IRRELEVANT].size += size; + } + } +} + +extern "C" void +psp_dump_stack_traces() { + std::vector> metas; + metas.reserve(stack_traces.size()); + for (const auto& [_, meta] : stack_traces) { + metas.push_back(meta); + } + std::sort( + metas.begin(), + metas.end(), + [](const AllocMeta& a, const AllocMeta& b) { + return a.header.size > b.header.size; + } + ); + for (const auto& meta : metas) { + printf("Allocated %llu bytes\n", meta.header.size); + printf("Stacktrace:\n%s\n", meta.trace->c_str()); + } +} + +extern "C" void +psp_clear_stack_traces() { + stack_traces.clear(); +} + +void* +malloc(size_t size) { + if (size > MIN_RELEVANT_SIZE) { + printf("Allocating %zu bytes\n", size); + } + USED_MEMORY += size; + const size_t total_size = size + sizeof(Header); + auto* header = static_cast(emscripten_builtin_malloc(total_size)); + if (header == nullptr) { + fprintf(stderr, "Failed to allocate %zu bytes\n", size); + } + header->size = size; + record_stack_trace(header, size); + return header + 1; +} + +void* +calloc(size_t nmemb, size_t size) { + // printf("Allocating array: %zu elements of size %zu\n", nmemb, size); + USED_MEMORY += nmemb * size; + // return emscripten_builtin_calloc(nmemb, size); + const size_t total_size = (nmemb * size) + sizeof(Header); + auto* header = static_cast(emscripten_builtin_malloc(total_size)); + if (header == nullptr) { + fprintf(stderr, "Failed to allocate %zu bytes\n", size); + } + header->size = nmemb * size; + memset(header + 1, 0, nmemb * size); + record_stack_trace(header, size); + return header + 1; +} + +void +free(void* ptr) { + // printf("Freeing memory at %p\n", ptr); + + if (ptr == nullptr) { + emscripten_builtin_free(ptr); + } else { + auto* header = static_cast(ptr) - 1; + auto old_memory = USED_MEMORY; + USED_MEMORY -= header->size; + if (USED_MEMORY > old_memory) { + std::abort(); + } + emscripten_builtin_free(header); + } +} + +void* +memalign(size_t alignment, size_t size) { + const size_t total_size = size + sizeof(Header); + auto* header = + static_cast(emscripten_builtin_memalign(alignment, total_size) + ); + if (header == nullptr) { + fprintf(stderr, "Failed to allocate %zu bytes\n", size); + } + header->size = size; + record_stack_trace(header, size); + return header + 1; +} + +int +posix_memalign(void** memptr, size_t alignment, size_t size) { + auto* header = static_cast( + emscripten_builtin_memalign(alignment, size + sizeof(Header)) + ); + if (header == nullptr) { + fprintf(stderr, "Failed to allocate %zu bytes\n", size); + } + header->size = size; + USED_MEMORY += size; + record_stack_trace(header, size); + *memptr = header + 1; + return 0; +} + +void* +realloc(void* ptr, size_t new_size) { + if (ptr == nullptr) { + // If ptr is nullptr, realloc behaves like malloc + return malloc(new_size); + } + + if (new_size == 0) { + // If new_size is 0, realloc behaves like free + free(ptr); + return nullptr; + } + + auto* header = static_cast(ptr) - 1; + const size_t old_size = header->size; + + if (new_size <= old_size) { + USED_MEMORY -= old_size - new_size; + // If the new size is smaller or equal, we can potentially shrink the + // block in place. For simplicity, we don't actually shrink the block + // here. + header->size = new_size; // Update the size in the header + return ptr; // Return the same pointer + } + + // If the new size is larger, allocate a new block + void* new_ptr = malloc(new_size); + if (new_ptr == nullptr) { + return nullptr; + } + + memcpy(new_ptr, ptr, old_size); + free(ptr); + + return new_ptr; +} \ No newline at end of file diff --git a/cpp/perspective/src/include/perspective/heap_instruments.h b/cpp/perspective/src/include/perspective/heap_instruments.h new file mode 100644 index 0000000000..d90df7bb51 --- /dev/null +++ b/cpp/perspective/src/include/perspective/heap_instruments.h @@ -0,0 +1,59 @@ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ +// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ +// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ +// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ +// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ +// ┃ Copyright (c) 2017, the Perspective Authors. ┃ +// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃ +// ┃ This file is part of the Perspective library, distributed under the terms ┃ +// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ +// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +#include +#include +#include +#include + +// Don't track our own memory usage +template +class UnderlyingAllocator : std::allocator { +public: + using value_type = T; + + UnderlyingAllocator() = default; + + template + UnderlyingAllocator(const UnderlyingAllocator& /*unused*/) noexcept {} + + T* + allocate(std::size_t n) { + if (n > std::numeric_limits::max() / sizeof(T)) { + throw std::bad_alloc(); + } + void* ptr = emscripten_builtin_malloc(n * sizeof(T)); + if (ptr == nullptr) { + throw std::bad_alloc(); + } + return static_cast(ptr); + } + + // Deallocate memory + void + deallocate(T* ptr, std::size_t /*unused*/) noexcept { + emscripten_builtin_free(ptr); + } + + // Equality comparison (required for allocators) + template + bool + operator==(const UnderlyingAllocator& /*unused*/) const noexcept { + return true; + } + + template + bool + operator!=(const UnderlyingAllocator& other) const noexcept { + return !(*this == other); + } +}; diff --git a/docs/md/SUMMARY.md b/docs/md/SUMMARY.md index 3a161513cc..9e39ef12e6 100644 --- a/docs/md/SUMMARY.md +++ b/docs/md/SUMMARY.md @@ -42,6 +42,7 @@ - [Hosting a `WebSocketServer` in Node.js](./how_to/javascript/nodejs_server.md) - [`perspective-viewer` Custom Element library](./how_to/javascript/viewer.md) - [Theming](./how_to/javascript/theming.md) + - [Custom Themes](./how_to/javascript/custom_themes.md) - [Loading data from a `Table`](./how_to/javascript/loading_data.md) - [Loading data from a virtual `Table`](./how_to/javascript/loading_virtual_data.md) - [Saving and restoring UI state](./how_to/javascript/save_restore.md) diff --git a/docs/md/how_to/javascript/custom_themes.md b/docs/md/how_to/javascript/custom_themes.md new file mode 100644 index 0000000000..baaccb2927 --- /dev/null +++ b/docs/md/how_to/javascript/custom_themes.md @@ -0,0 +1,28 @@ +# Custom themes + +The best way to write a new theme is to +[fork and modify an existing theme](https://github.com/finos/perspective/tree/master/rust/perspective-viewer/src/themes), +which are _just_ collections of regular CSS variables (no preprocessor is +required, though Perspective's own themes use one). `` is +not "themed" by default and will lack icons and label text in addition to colors +and fonts, so starting from an empty theme forces you to define _every_ +theme-able variable to get a functional UI. + +### Icons and Translation + +UI icons are defined by CSS variables provided by +[`@finos/perspective-viewer/dist/css/icons.css`](https://github.com/finos/perspective/blob/master/rust/perspective-viewer/src/themes/icons.less). +These variables must be defined for the UI icons to work - there are no default +icons without a theme. + +UI text is also defined in CSS variables provided by +[`@finos/perspective-viewer/dist/css/intl.css`](https://github.com/finos/perspective/blob/master/rust/perspective-viewer/src/themes/intl.less), +and has identical import requirements. Some _example definitions_ +(automatically-translated sans-editing) can be found +[`@finos/perspective-viewer/dist/css/intl/` folder](https://github.com/finos/perspective/tree/master/rust/perspective-viewer/src/themes/intl). + +Importing the pre-built `themes.css` stylesheet as well as a custom theme will +define Icons and Translation globally as a side-effect. You can still customize +icons in this mode with rules (of the appropriate specificity), _but_ if you do +not still remember to define these variables yourself, your theme will not work +without the base `themes.css` pacage available. diff --git a/docs/package.json b/docs/package.json index 62959d1884..c483b3b526 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-docs", - "version": "3.3.2", + "version": "3.3.3", "private": true, "scripts": { "build": "node build.js && docusaurus build", diff --git a/examples/blocks/package.json b/examples/blocks/package.json index 99d07658ef..583e09a2c0 100644 --- a/examples/blocks/package.json +++ b/examples/blocks/package.json @@ -1,7 +1,7 @@ { "name": "blocks", "private": true, - "version": "3.3.2", + "version": "3.3.3", "description": "A collection of simple client-side Perspective examples for `http://bl.ocks.org`.", "scripts": { "start": "mkdirp dist && node --experimental-wasm-memory64 --experimental-modules server.mjs", diff --git a/examples/blocks/src/superstore/index.json b/examples/blocks/src/superstore/index.json index a3704c27be..092be49a46 100644 --- a/examples/blocks/src/superstore/index.json +++ b/examples/blocks/src/superstore/index.json @@ -39,7 +39,7 @@ index.html: 48 { }, "viewers": { "PERSPECTIVE_GENERATED_ID_0": { - "version": "3.3.2", + "version": "3.3.3", "plugin": "Datagrid", "plugin_config": { "columns": {}, @@ -68,7 +68,7 @@ index.html: 48 { "settings": false }, "PERSPECTIVE_GENERATED_ID_1": { - "version": "3.3.2", + "version": "3.3.3", "plugin": "Datagrid", "plugin_config": { "columns": {}, @@ -110,7 +110,7 @@ index.html: 48 { "settings": false }, "PERSPECTIVE_GENERATED_ID_3": { - "version": "3.3.2", + "version": "3.3.3", "plugin": "Datagrid", "plugin_config": { "columns": {}, @@ -139,7 +139,7 @@ index.html: 48 { "settings": false }, "PERSPECTIVE_GENERATED_ID_2": { - "version": "3.3.2", + "version": "3.3.3", "plugin": "Treemap", "plugin_config": {}, "columns_config": {}, diff --git a/examples/esbuild-example/package.json b/examples/esbuild-example/package.json index 6f350cd3dc..d66139013c 100644 --- a/examples/esbuild-example/package.json +++ b/examples/esbuild-example/package.json @@ -1,7 +1,7 @@ { "name": "esbuild-example", "private": true, - "version": "3.3.2", + "version": "3.3.3", "description": "An esbuild example app built using `@finos/perspective-viewer`.", "scripts": { "build": "node build.js", diff --git a/examples/esbuild-remote/package.json b/examples/esbuild-remote/package.json index fedfee1326..b836d30b7f 100644 --- a/examples/esbuild-remote/package.json +++ b/examples/esbuild-remote/package.json @@ -1,7 +1,7 @@ { "name": "esbuild-remote", "private": true, - "version": "3.3.2", + "version": "3.3.3", "description": "An example of 2 Perspectives, one client and one server, streaming via Apache Arrow.", "scripts": { "start": "node build.js && node server/index.mjs" diff --git a/examples/python-aiohttp/package.json b/examples/python-aiohttp/package.json index f9453c55be..a6381622a8 100644 --- a/examples/python-aiohttp/package.json +++ b/examples/python-aiohttp/package.json @@ -1,7 +1,7 @@ { "name": "python-aiohttp", "private": true, - "version": "3.3.2", + "version": "3.3.3", "description": "An example of editing a `perspective-python` server from the browser.", "scripts": { "start": "PYTHONPATH=../../python/perspective python3 server.py" diff --git a/examples/python-starlette/package.json b/examples/python-starlette/package.json index 71213a4279..6ea27c3ebf 100644 --- a/examples/python-starlette/package.json +++ b/examples/python-starlette/package.json @@ -1,7 +1,7 @@ { "name": "python-starlette", "private": true, - "version": "3.3.2", + "version": "3.3.3", "description": "An example of editing a `perspective-python` server from the browser.", "scripts": { "start": "PYTHONPATH=../../python/perspective python3 server.py" diff --git a/examples/python-tornado-streaming/package.json b/examples/python-tornado-streaming/package.json index 9a58575797..f093d8fe6c 100644 --- a/examples/python-tornado-streaming/package.json +++ b/examples/python-tornado-streaming/package.json @@ -1,7 +1,7 @@ { "name": "python-tornado-streaming", "private": true, - "version": "3.3.2", + "version": "3.3.3", "description": "An example of streaming a `perspective-python` server to the browser.", "scripts": { "start": "PYTHONPATH=../../python/perspective python3 server.py" diff --git a/examples/python-tornado/package.json b/examples/python-tornado/package.json index 5c7cd2aad9..aa5c1def35 100644 --- a/examples/python-tornado/package.json +++ b/examples/python-tornado/package.json @@ -1,7 +1,7 @@ { "name": "python-tornado", "private": true, - "version": "3.3.2", + "version": "3.3.3", "description": "An example of editing a `perspective-python` server from the browser.", "scripts": { "start": "PYTHONPATH=../../python/perspective python3 server.py" diff --git a/examples/react-example/package.json b/examples/react-example/package.json index 94a9b77ec8..d679a2ed52 100644 --- a/examples/react-example/package.json +++ b/examples/react-example/package.json @@ -1,7 +1,7 @@ { "name": "react-example", "private": true, - "version": "3.3.2", + "version": "3.3.3", "description": "An example app built using `@finos/perspective-viewer`.", "scripts": { "build": "node build.js", @@ -20,6 +20,8 @@ }, "devDependencies": { "esbuild": "^0.14.54", - "http-server": "^14.1.1" + "http-server": "^14.1.1", + "@types/react": "^18", + "@types/react-dom": "^18" } } diff --git a/examples/react-example/src/index.tsx b/examples/react-example/src/index.tsx index bd7bf177d0..ba591c935b 100644 --- a/examples/react-example/src/index.tsx +++ b/examples/react-example/src/index.tsx @@ -17,7 +17,10 @@ import perspective from "@finos/perspective"; import perspective_viewer from "@finos/perspective-viewer"; import "@finos/perspective-viewer-datagrid"; import "@finos/perspective-viewer-d3fc"; -import type { ViewerConfigUpdate } from "@finos/perspective-viewer"; +import type { + HTMLPerspectiveViewerElement, + ViewerConfigUpdate, +} from "@finos/perspective-viewer"; import "@finos/perspective-viewer/dist/css/themes.css"; import "./index.css"; @@ -50,7 +53,7 @@ const config: ViewerConfigUpdate = { }; const App = (): React.ReactElement => { - const viewer = React.useRef(null); + const viewer = React.useRef(null); React.useEffect(() => { getTable().then((table) => { if (viewer.current) { @@ -63,4 +66,6 @@ const App = (): React.ReactElement => { return ; }; -ReactDOM.createRoot(document.getElementById("root")).render(); +ReactDOM.createRoot( + document.getElementById("root") as ReactDOM.Container +).render(); diff --git a/examples/react-example/tsconfig.json b/examples/react-example/tsconfig.json index 71f38ef538..11b724fb10 100644 --- a/examples/react-example/tsconfig.json +++ b/examples/react-example/tsconfig.json @@ -12,7 +12,7 @@ "noImplicitUseStrict": false, "noUnusedLocals": true, "strictNullChecks": true, - "skipLibCheck": true, + "skipLibCheck": false, "removeComments": false, "jsx": "react", "allowSyntheticDefaultImports": true, diff --git a/examples/rust-axum/Cargo.toml b/examples/rust-axum/Cargo.toml index 9225b2486b..50d9f4b765 100644 --- a/examples/rust-axum/Cargo.toml +++ b/examples/rust-axum/Cargo.toml @@ -17,7 +17,7 @@ edition = "2021" publish = false [dependencies] -perspective = { version = "3.3.2" } +perspective = { version = "3.3.3" } axum = { version = ">=0.7,<2", features = ["ws"] } futures = "0.3" tokio = { version = "1.0", features = ["full"] } diff --git a/examples/rust-axum/package.json b/examples/rust-axum/package.json index 0c2a855805..0f93329823 100644 --- a/examples/rust-axum/package.json +++ b/examples/rust-axum/package.json @@ -1,7 +1,7 @@ { "name": "rust-axum", "private": true, - "version": "3.3.2", + "version": "3.3.3", "description": "An example of a Rust/Axum virtual Perspective server", "scripts": { "start": "cargo run" diff --git a/examples/vite-example/package.json b/examples/vite-example/package.json index 690347bb12..e1a090f30b 100644 --- a/examples/vite-example/package.json +++ b/examples/vite-example/package.json @@ -1,7 +1,7 @@ { "name": "vite-example", "private": true, - "version": "3.3.2", + "version": "3.3.3", "type": "module", "scripts": { "start": "vite", diff --git a/examples/webpack-example/package.json b/examples/webpack-example/package.json index fc62a99f5a..914bca26fc 100644 --- a/examples/webpack-example/package.json +++ b/examples/webpack-example/package.json @@ -1,7 +1,7 @@ { "name": "webpack-example", "private": true, - "version": "3.3.2", + "version": "3.3.3", "description": "An example app built using `@finos/perspective-viewer`.", "scripts": { "webpack_build": "webpack", diff --git a/package.json b/package.json index a44c959e2e..93e8de7d6c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "git", "url": "https://github.com/finos/perspective" }, - "version": "3.3.2", + "version": "3.3.3", "changelog": { "labels": { "enhancement": "Added", @@ -15,7 +15,7 @@ "type": "module", "emscripten": "3.1.58", "llvm": "17.0.6", - "pyodide": "0.26.2", + "pyodide": "0.27.1", "engines": { "node": ">=16" }, diff --git a/packages/perspective-cli/package.json b/packages/perspective-cli/package.json index 38ffd4d748..7292ef4208 100644 --- a/packages/perspective-cli/package.json +++ b/packages/perspective-cli/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-cli", - "version": "3.3.2", + "version": "3.3.3", "description": "Perspective.js CLI", "main": "src/js/index.js", "publishConfig": { diff --git a/packages/perspective-jupyterlab/package.json b/packages/perspective-jupyterlab/package.json index 0e347b7e30..a13c542667 100644 --- a/packages/perspective-jupyterlab/package.json +++ b/packages/perspective-jupyterlab/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-jupyterlab", - "version": "3.3.2", + "version": "3.3.3", "description": "A Jupyterlab extension for the Perspective library, designed to be used with perspective-python.", "files": [ "dist/**/*", diff --git a/packages/perspective-viewer-d3fc/package.json b/packages/perspective-viewer-d3fc/package.json index 0654495b80..f9b4a04739 100644 --- a/packages/perspective-viewer-d3fc/package.json +++ b/packages/perspective-viewer-d3fc/package.json @@ -1,13 +1,13 @@ { "name": "@finos/perspective-viewer-d3fc", - "version": "3.3.2", + "version": "3.3.3", "description": "Perspective.js D3FC Plugin", "unpkg": "./dist/cdn/perspective-viewer-d3fc.js", "jsdelivr": "./dist/cdn/perspective-viewer-d3fc.js", "type": "module", "exports": { ".": { - "types": "./dist/esm/types/index.d.ts", + "types": "./dist/esm/index.d.ts", "default": "./dist/esm/perspective-viewer-d3fc.js" }, "./src/*": "./src/*", @@ -31,7 +31,7 @@ "src/**/*", "index.d.ts" ], - "types": "dist/esm/types/index.d.ts", + "types": "dist/esm/index.d.ts", "scripts": { "prebuild": "mkdirp dist/esm", "build": "node ./build.js", diff --git a/packages/perspective-viewer-datagrid/package.json b/packages/perspective-viewer-datagrid/package.json index e72c66d150..abdebe2cd2 100644 --- a/packages/perspective-viewer-datagrid/package.json +++ b/packages/perspective-viewer-datagrid/package.json @@ -1,11 +1,14 @@ { "name": "@finos/perspective-viewer-datagrid", - "version": "3.3.2", + "version": "3.3.3", "description": "Perspective datagrid plugin based on `regular-table`", "unpkg": "dist/cdn/perspective-viewer-datagrid.js", "jsdelivr": "dist/cdn/perspective-viewer-datagrid.js", "exports": { - ".": "./dist/esm/perspective-viewer-datagrid.js", + ".": { + "types": "./index.d.ts", + "default": "./dist/esm/perspective-viewer-datagrid.js" + }, "./dist/*": "./dist/*", "./package.json": "./package.json" }, @@ -14,7 +17,6 @@ "dist/**/*", "index.d.ts" ], - "types": "index.d.ts", "scripts": { "build": "node build.js", "clean": "rimraf dist", @@ -33,7 +35,7 @@ "@finos/perspective": "workspace:^", "@finos/perspective-viewer": "workspace:^", "chroma-js": "^1.3.4", - "regular-table": "=0.6.4" + "regular-table": "=0.6.5" }, "devDependencies": { "@prospective.co/procss": "^0.1.16", diff --git a/packages/perspective-viewer-openlayers/package.json b/packages/perspective-viewer-openlayers/package.json index 6eb59f1f4c..a7398057df 100644 --- a/packages/perspective-viewer-openlayers/package.json +++ b/packages/perspective-viewer-openlayers/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-viewer-openlayers", - "version": "3.3.2", + "version": "3.3.3", "unpkg": "dist/cdn/perspective-viewer-openlayers.js", "jsdelivr": "dist/cdn/perspective-viewer-openlayers.js", "exports": { diff --git a/packages/perspective-workspace/package.json b/packages/perspective-workspace/package.json index 51647e52fc..baf0302bf2 100644 --- a/packages/perspective-workspace/package.json +++ b/packages/perspective-workspace/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-workspace", - "version": "3.3.2", + "version": "3.3.3", "description": "Perspective Workspace", "files": [ "dist/**/*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1f2de528c..7f5d174dba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -345,6 +345,12 @@ importers: specifier: ^3.0.0 version: 3.0.0 devDependencies: + '@types/react': + specifier: ^18 + version: 18.3.8 + '@types/react-dom': + specifier: ^18 + version: 18.3.5(@types/react@18.3.8) esbuild: specifier: ^0.14.54 version: 0.14.54 @@ -544,8 +550,8 @@ importers: specifier: ^1.3.4 version: 1.4.1 regular-table: - specifier: '=0.6.4' - version: 0.6.4 + specifier: '=0.6.5' + version: 0.6.5 devDependencies: '@finos/perspective-esbuild-plugin': specifier: workspace:^ @@ -4023,6 +4029,11 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-dom@18.3.5': + resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} + peerDependencies: + '@types/react': ^18.0.0 + '@types/react-router-config@5.0.11': resolution: {integrity: sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==} @@ -8745,8 +8756,8 @@ packages: resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} hasBin: true - regular-table@0.6.4: - resolution: {integrity: sha512-N53k0gMxK9X4pwc8/+UOqiigoLwANsUBTlfoPruLoU3248kDswgt2H1LpuK6RqKkTU9V9Gr1XOOOxK0rXAWsSQ==} + regular-table@0.6.5: + resolution: {integrity: sha512-KbQ07NMZGHpY8KuLirfn60HWfOsrZCVwrXBfVYTi/EE97zlfTOsc1A5UhR0QodPzO+ZR5BPKDLACuFT7mIG8hA==} engines: {node: '>=16'} rehype-raw@7.0.0: @@ -12704,7 +12715,7 @@ snapshots: '@docusaurus/react-loadable@6.0.0(react@18.3.1)': dependencies: - '@types/react': 17.0.82 + '@types/react': 18.3.8 react: 18.3.1 '@docusaurus/theme-classic@3.7.0(@types/react@18.3.8)(esbuild@0.14.54)(eslint@9.17.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.2.2)': @@ -14822,22 +14833,26 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/react-dom@18.3.5(@types/react@18.3.8)': + dependencies: + '@types/react': 18.3.8 + '@types/react-router-config@5.0.11': dependencies: '@types/history': 4.7.11 - '@types/react': 17.0.82 + '@types/react': 18.3.8 '@types/react-router': 5.1.20 '@types/react-router-dom@5.3.3': dependencies: '@types/history': 4.7.11 - '@types/react': 17.0.82 + '@types/react': 18.3.8 '@types/react-router': 5.1.20 '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 17.0.82 + '@types/react': 18.3.8 '@types/react@17.0.82': dependencies: @@ -20536,7 +20551,7 @@ snapshots: dependencies: jsesc: 0.5.0 - regular-table@0.6.4: {} + regular-table@0.6.5: {} rehype-raw@7.0.0: dependencies: diff --git a/rust/lint/Cargo.toml b/rust/lint/Cargo.toml index 3088ad5536..37cd3dbc0f 100644 --- a/rust/lint/Cargo.toml +++ b/rust/lint/Cargo.toml @@ -13,7 +13,7 @@ [package] name = "perspective-lint" description = "A CLI utility to lint rust code" -version = "3.3.2" +version = "3.3.3" edition = "2021" publish = false diff --git a/rust/perspective-client/Cargo.toml b/rust/perspective-client/Cargo.toml index 2901b116be..6838515d4b 100644 --- a/rust/perspective-client/Cargo.toml +++ b/rust/perspective-client/Cargo.toml @@ -12,7 +12,7 @@ [package] name = "perspective-client" -version = "3.3.2" +version = "3.3.3" authors = ["Andrew Stein "] edition = "2021" description = "A data visualization and analytics component, especially well-suited for large and/or streaming datasets." diff --git a/rust/perspective-client/package.json b/rust/perspective-client/package.json index 8f834adf8b..67860b2af1 100644 --- a/rust/perspective-client/package.json +++ b/rust/perspective-client/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-client", - "version": "3.3.2", + "version": "3.3.3", "description": "", "private": true, "repository": { diff --git a/rust/perspective-js/Cargo.toml b/rust/perspective-js/Cargo.toml index 87a2b29a08..fa781fe054 100644 --- a/rust/perspective-js/Cargo.toml +++ b/rust/perspective-js/Cargo.toml @@ -12,7 +12,7 @@ [package] name = "perspective-js" -version = "3.3.2" +version = "3.3.3" authors = ["Andrew Stein "] edition = "2021" description = "A data visualization and analytics component, especially well-suited for large and/or streaming datasets." @@ -23,6 +23,7 @@ keywords = ["experimental"] include = ["src/**/*", "Cargo.toml", "./package.json", "docs/**/*", "build.rs"] [package.metadata.docs.rs] +features = ["external-docs"] rustc-args = ["--cfg", "web_sys_unstable_apis"] rustdoc-args = ["--html-in-header", "docs/index.html"] @@ -48,7 +49,7 @@ wasm-bindgen-test = "0.3.13" [dependencies] macro_rules_attribute = "0.2.0" -perspective-client = { version = "3.3.2" } +perspective-client = { version = "3.3.3" } base64 = "0.13.0" chrono = "0.4" extend = "1.1.2" diff --git a/rust/perspective-js/package.json b/rust/perspective-js/package.json index f55699c47b..b4fde80bc6 100644 --- a/rust/perspective-js/package.json +++ b/rust/perspective-js/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective", - "version": "3.3.2", + "version": "3.3.3", "description": "", "repository": { "type": "git", diff --git a/rust/perspective-js/src/ts/wasm/emscripten_api.ts b/rust/perspective-js/src/ts/wasm/emscripten_api.ts index aa222f514b..df1d4b33ae 100644 --- a/rust/perspective-js/src/ts/wasm/emscripten_api.ts +++ b/rust/perspective-js/src/ts/wasm/emscripten_api.ts @@ -31,7 +31,10 @@ export async function compile_perspective( imports["env"] = { ...imports["env"], psp_stack_trace() { + const old = Error.stackTraceLimit; + Error.stackTraceLimit = 1000; const str = Error().stack || ""; + Error.stackTraceLimit = old; const textEncoder = new TextEncoder(); const bytes = textEncoder.encode(str); const ptr = module._psp_alloc( diff --git a/rust/perspective-python/Cargo.toml b/rust/perspective-python/Cargo.toml index d8b671069c..470c85082d 100644 --- a/rust/perspective-python/Cargo.toml +++ b/rust/perspective-python/Cargo.toml @@ -12,7 +12,7 @@ [package] name = "perspective-python" -version = "3.3.2" +version = "3.3.3" edition = "2021" description = "A data visualization and analytics component, especially well-suited for large and/or streaming datasets." repository = "https://github.com/finos/perspective" @@ -39,6 +39,7 @@ include = [ ] [package.metadata.docs.rs] +features = ["external-docs"] rustdoc-args = ["--html-in-header", "docs/index.html"] [features] @@ -59,8 +60,8 @@ pyo3-build-config = "0.20.2" python-config-rs = "0.1.2" [dependencies] -perspective-client = { version = "3.3.2" } -perspective-server = { version = "3.3.2" } +perspective-client = { version = "3.3.3" } +perspective-server = { version = "3.3.3" } macro_rules_attribute = "0.2.0" async-lock = "2.5.0" pollster = "0.3.0" diff --git a/rust/perspective-python/package.json b/rust/perspective-python/package.json index 0615df606b..982cc84fa7 100644 --- a/rust/perspective-python/package.json +++ b/rust/perspective-python/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-python", - "version": "3.3.2", + "version": "3.3.3", "description": "", "private": true, "repository": { diff --git a/rust/perspective-python/perspective/__init__.py b/rust/perspective-python/perspective/__init__.py index 0a984684fd..d20e1eb9b2 100644 --- a/rust/perspective-python/perspective/__init__.py +++ b/rust/perspective-python/perspective/__init__.py @@ -10,7 +10,7 @@ # ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -__version__ = "3.3.2" +__version__ = "3.3.3" __all__ = [ "_jupyter_labextension_paths", "Server", diff --git a/rust/perspective-python/pyodide-tests/README.md b/rust/perspective-python/pyodide-tests/README.md index 4a748f230e..f6c2fd763c 100644 --- a/rust/perspective-python/pyodide-tests/README.md +++ b/rust/perspective-python/pyodide-tests/README.md @@ -1,5 +1,37 @@ # pyodide-tests -Smoke and integration tests for the perspective-python Pyodide wheel. +Smoke and integration tests for the perspective-python Pyodide wheel. The tests +are specced in `pytest` and executed with `playwright`, using the +`pytest-pyodide` package. These tests require that a Pyodide wheel has been built to rust/target/wheels + +## test setup + +Create a virtual environment. Install perspective-python requirements and +special pyodide-only requirements: + +``` +pip install -r rust/perspective-python/requirements.txt +pip install -r rust/perspective-python/requirements-pyodide.txt +``` + +## running tests + +Run setup, select `perspective-pyodide` target: + +``` +pnpm -w run setup +``` + +Then run tests: + +``` +pnpm -w test +``` + +If you are prompted to install playwright browsers, run this in your venv: + +``` +python -m playwright install +``` diff --git a/rust/perspective-python/pyodide-tests/tests/test_smoke.py b/rust/perspective-python/pyodide-tests/tests/test_smoke.py index bdec58da15..d6c305293a 100644 --- a/rust/perspective-python/pyodide-tests/tests/test_smoke.py +++ b/rust/perspective-python/pyodide-tests/tests/test_smoke.py @@ -30,6 +30,8 @@ def psp_wheel_path(pytestconfig): # Based on micropip's test server fixture: # https://github.com/pyodide/micropip/blob/eb8c4497d742e515d24d532db2b9cc014328265b/tests/conftest.py#L64-L87 +# Run web server serving the directory containing the perspective wheel, +# yielding the wheel's URL for the fixture value @pytest.fixture() def psp_wheel_url(psp_wheel_path): from pathlib import Path @@ -72,3 +74,44 @@ async def test_parsing_good_csv(selenium): client = server.new_local_client() abc123 = client.table("a,b,c\n1,2,3\n") assert abc123.columns() == ["a", "b", "c"] + + +# Test that perspective can load pyarrow Table values +@run_in_pyodide +async def test_perspective_and_pyarrow_together_at_last(selenium): + import micropip + + await micropip.install("pyarrow") + + import perspective + import pyarrow as pa + + n_legs = pa.array([2, 2, 4, 4, 5, 100]) + animals = pa.array( + ["Flamingo", "Parrot", "Dog", "Horse", "Brittle stars", "Centipede"] + ) + names = ["n_legs", "animals"] + table = pa.Table.from_arrays([n_legs, animals], names=names) + + server = perspective.Server() + client = server.new_local_client() + + animals = client.table(table, name="animals") + assert animals.columns() == ["n_legs", "animals"] + + +# Test that perspective can load pandas DataFrame values +@run_in_pyodide +async def test_pandas_dataframes(selenium): + import micropip + + await micropip.install(["pyarrow", "pandas"]) + + import perspective + import pandas as pd + + server = perspective.Server() + client = server.new_local_client() + df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) + ab = client.table(df, name="ab") + assert sorted(ab.columns()) == ["a", "b", "index"] diff --git a/rust/perspective-python/pyproject.toml b/rust/perspective-python/pyproject.toml index dd940e5dd0..1ac3365f7c 100644 --- a/rust/perspective-python/pyproject.toml +++ b/rust/perspective-python/pyproject.toml @@ -44,7 +44,7 @@ starlette = ["starlette<1"] [tool.maturin] module-name = "perspective" -data = "perspective_python-3.3.2.data" +data = "perspective_python-3.3.3.data" features = ["pyo3/extension-module"] include = [ { path = "perspective/*libpsp.so", format = "wheel" }, diff --git a/rust/perspective-python/requirements-pyodide.txt b/rust/perspective-python/requirements-pyodide.txt index 5c2dd70ceb..b20daffe76 100644 --- a/rust/perspective-python/requirements-pyodide.txt +++ b/rust/perspective-python/requirements-pyodide.txt @@ -2,4 +2,4 @@ pytest==8.2.2 pytest-playwright==0.5.2 pytest-pyodide==0.58.3 - +pytest-timeout==2.3.1 diff --git a/rust/perspective-python/test.mjs b/rust/perspective-python/test.mjs index 4c17062681..2b3573087e 100644 --- a/rust/perspective-python/test.mjs +++ b/rust/perspective-python/test.mjs @@ -41,6 +41,12 @@ if (process.env.PSP_PYODIDE) { "--runtime=chrome", `--dist-dir=${pyodideDistDir}`, `--perspective-emscripten-wheel=${emscriptenWheel}`, + "--verbose", + "--timeout=300", + // with --timeout_method=signal, tests hang. seems like the + // pytest-pyodide webserver fixture does not shut down + "--timeout_method=thread", + ...process.argv.slice(2), ], execOpts ); diff --git a/rust/perspective-server/Cargo.toml b/rust/perspective-server/Cargo.toml index c8eda1a413..97adeadb16 100644 --- a/rust/perspective-server/Cargo.toml +++ b/rust/perspective-server/Cargo.toml @@ -12,7 +12,7 @@ [package] name = "perspective-server" -version = "3.3.2" +version = "3.3.3" authors = ["Andrew Stein "] edition = "2021" description = "A data visualization and analytics component, especially well-suited for large and/or streaming datasets." @@ -45,7 +45,7 @@ shlex = "1.3.0" [dependencies] link-cplusplus = "1.0.9" -perspective-client = { version = "3.3.2" } +perspective-client = { version = "3.3.3" } async-lock = "2.5.0" tracing = { version = ">=0.1.36" } futures = "0.3" diff --git a/rust/perspective-viewer/Cargo.toml b/rust/perspective-viewer/Cargo.toml index ffb4a0b60c..25c7757330 100644 --- a/rust/perspective-viewer/Cargo.toml +++ b/rust/perspective-viewer/Cargo.toml @@ -12,7 +12,7 @@ [package] name = "perspective-viewer" -version = "3.3.2" +version = "3.3.3" authors = ["Andrew Stein "] edition = "2021" description = "A data visualization and analytics component, especially well-suited for large and/or streaming datasets." @@ -46,8 +46,8 @@ anyhow = "1.0.66" wasm-bindgen-test = "0.3.13" [dependencies] -perspective-client = { version = "3.3.2" } -perspective-js = { version = "3.3.2" } +perspective-client = { version = "3.3.3" } +perspective-js = { version = "3.3.3" } # Provides async `Mutex` for locked sections such as `render` async-lock = "2.5.0" diff --git a/rust/perspective-viewer/package.json b/rust/perspective-viewer/package.json index 3fea9283d0..aadf439740 100644 --- a/rust/perspective-viewer/package.json +++ b/rust/perspective-viewer/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-viewer", - "version": "3.3.2", + "version": "3.3.3", "description": "The `` Custom Element, frontend for Perspective.js", "repository": { "type": "git", diff --git a/rust/perspective-viewer/src/less/config-selector.less b/rust/perspective-viewer/src/less/config-selector.less index 3d9185caf9..df4e96a6b2 100644 --- a/rust/perspective-viewer/src/less/config-selector.less +++ b/rust/perspective-viewer/src/less/config-selector.less @@ -267,7 +267,7 @@ top: 0px; margin: var(--column-drop-label--margin, -16px 0px 0px 0px); font-size: var(--label--font-size, 0.75em); - display: var(--column-drop-label--display, none); + display: inline-block; } #transpose_button { diff --git a/rust/perspective-viewer/src/themes/pro.less b/rust/perspective-viewer/src/themes/pro.less index cef4612747..283c2e0b8d 100644 --- a/rust/perspective-viewer/src/themes/pro.less +++ b/rust/perspective-viewer/src/themes/pro.less @@ -63,7 +63,6 @@ perspective-string-column-style[theme="Pro Light"] { --config-button--padding: 15px 8px 6px 8px; --column-drop-label--font-size: 8px; --column-drop-container--padding: 0px; - --column-drop-label--display: inline-block; --column-selector--width: 20px; --column-selector--font-size: 16px; diff --git a/rust/perspective/Cargo.toml b/rust/perspective/Cargo.toml index 0f127a35fe..68ed26566f 100644 --- a/rust/perspective/Cargo.toml +++ b/rust/perspective/Cargo.toml @@ -12,7 +12,7 @@ [package] name = "perspective" -version = "3.3.2" +version = "3.3.3" authors = ["Andrew Stein "] edition = "2021" description = "A data visualization and analytics component, especially well-suited for large and/or streaming datasets." @@ -38,8 +38,8 @@ external-cpp = [ [dependencies] async-lock = "2.5.0" -perspective-client = { version = "3.3.2" } -perspective-server = { version = "3.3.2" } +perspective-client = { version = "3.3.3" } +perspective-server = { version = "3.3.3" } tracing = { version = ">=0.1.36" } axum = { version = ">=0.7,<0.8", features = ["ws"], optional = true } tokio = { version = "~1", features = ["full"], optional = true } diff --git a/rust/perspective/package.json b/rust/perspective/package.json index ea174f8f21..ede1f9ba79 100644 --- a/rust/perspective/package.json +++ b/rust/perspective/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-rs", - "version": "3.3.2", + "version": "3.3.3", "description": "", "private": true, "repository": { diff --git a/tools/perspective-bench/basic_suite.mjs b/tools/perspective-bench/basic_suite.mjs index 240070429f..f9f9a840fb 100644 --- a/tools/perspective-bench/basic_suite.mjs +++ b/tools/perspective-bench/basic_suite.mjs @@ -43,7 +43,7 @@ perspective_bench.suite( const { default: perspective } = await import("@finos/perspective"); client = await perspective.websocket(path); metadata = { - version: "3.3.2", + version: "3.3.3", version_idx, }; } else { diff --git a/tools/perspective-esbuild-plugin/package.json b/tools/perspective-esbuild-plugin/package.json index 648c4be0e7..1cda291e36 100644 --- a/tools/perspective-esbuild-plugin/package.json +++ b/tools/perspective-esbuild-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-esbuild-plugin", - "version": "3.3.2", + "version": "3.3.3", "description": "esbuild plugin for Perspective", "author": "", "license": "Apache-2.0", diff --git a/tools/perspective-scripts/package.json b/tools/perspective-scripts/package.json index 86e013929a..e3a0aeb592 100644 --- a/tools/perspective-scripts/package.json +++ b/tools/perspective-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-scripts", - "version": "3.3.2", + "version": "3.3.3", "description": "Build scripts based on perspective", "private": true, "files": [ diff --git a/tools/perspective-scripts/setup.mjs b/tools/perspective-scripts/setup.mjs index 2308cb28ff..26edd7d39a 100644 --- a/tools/perspective-scripts/setup.mjs +++ b/tools/perspective-scripts/setup.mjs @@ -168,7 +168,14 @@ async function focus_package() { message: "Focus NPM package(s)?", default: () => { if (CONFIG["PACKAGE"]) { - return CONFIG["PACKAGE"].split(","); + const packages = CONFIG["PACKAGE"].split(","); + if (CONFIG["PSP_PYODIDE"] === "1") { + const py = packages.indexOf("perspective-python"); + if (py >= 0) { + packages[py] = "perspective-pyodide"; + } + } + return packages; } else { return [""]; } diff --git a/tools/perspective-test/package.json b/tools/perspective-test/package.json index 64c2dc63d9..d537b15cc8 100644 --- a/tools/perspective-test/package.json +++ b/tools/perspective-test/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-test", - "version": "3.3.2", + "version": "3.3.3", "description": "Test utility based on perspective", "private": true, "main": "src/js/index.ts",