diff --git a/.github/workflows/build-viztracer.yml b/.github/workflows/build-viztracer.yml new file mode 100644 index 000000000..afc325095 --- /dev/null +++ b/.github/workflows/build-viztracer.yml @@ -0,0 +1,96 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# Based on upstream's CI: +# https://github.com/gaogaotiantian/viztracer/blob/1.1.1/.github/workflows/wheels.yml +name: Build viztracer wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'viztracer version to build (git tag, e.g. 1.1.1)' + required: true + default: '1.1.1' + pull_request: + paths: + - '.github/workflows/build-viztracer.yml' + - 'patches/viztracer/**' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '1.1.1' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + VIZTRACER_VERSION: ${{ inputs.version || '1.1.1' }} + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + + build_wheels: + needs: [setup] + name: Build viztracer ${{ inputs.version || '1.1.1' }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + python: ["cp312", "cp313", "cp314", "cp314t"] + + steps: + - name: Checkout viztracer ${{ env.VIZTRACER_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: gaogaotiantian/viztracer + ref: ${{ env.VIZTRACER_VERSION }} + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + # Two tests hang forever on riscv64 and four fail (see the patches): self-tracing + # overhead, Perfetto's trace_processor_shell having no riscv64 prebuilt, and no + # riscv64 build of the cross-process attach .so. + - name: Patch viztracer source + run: | + git apply python-wheels/patches/viztracer/${{ env.VIZTRACER_VERSION }}/00*.patch + + - name: Build and test wheel + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + only: ${{ matrix.python }}-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + # TestLoky (tests/test_multiprocess.py) imports loky unconditionally; every + # other optional dep upstream's suite touches (torch, jaxlib, ipywidgets) + # is self-skipping on ImportError or platform. + CIBW_TEST_REQUIRES: loky>=3.0.0,<3.5.0 + # cibuildwheel runs the tests in an empty directory, so stage upstream's suite + # there; test_combine also reaches outside tests/ for its example data files. + CIBW_TEST_SOURCES: tests example/json + CIBW_TEST_COMMAND: python -m unittest -v + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: viztracer-${{ env.VIZTRACER_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish viztracer ${{ inputs.version || '1.1.1' }} + needs: [setup, build_wheels] + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + with: + artifact-pattern: viztracer-${{ inputs.version || '1.1.1' }}-*-manylinux_riscv64 diff --git a/patches/viztracer/1.1.1/0001-tests-skip-test_trace_self-which-hangs-indefinitely.patch b/patches/viztracer/1.1.1/0001-tests-skip-test_trace_self-which-hangs-indefinitely.patch new file mode 100644 index 000000000..97c04f785 --- /dev/null +++ b/patches/viztracer/1.1.1/0001-tests-skip-test_trace_self-which-hangs-indefinitely.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 6 Sep 2026 00:00:00 +0000 +Subject: [PATCH] tests: skip test_trace_self, which hangs indefinitely on + riscv64 + +Upstream-Status: Inappropriate [riscv64-only hang, never exercised by upstream's own x86/aarch64 CI hardware] + +test_trace_self runs `viztracer --trace_self vizviewer --server_only +` and waits for "Ctrl+C" to appear on the child's stdout before +sending SIGTERM (tests/cmdline_tmpl.py's `template()`, string-`wait` +branch): a plain `while True: line = p.stdout.readline()` loop with no +timeout at all. + +On riscv64 this readline loop never returns: this exact test hung +identically across all four interpreters (cp312/cp313/cp314/cp314t) until +GitHub's own 60-minute job timeout killed it, producing zero output after +"test_trace_self start". + +A likely contributing mechanism: get_system_ts()/get_system_ns() in +src/viztracer/modules/quicktime.h use __rdtsc() on x86 (a single +non-syscall instruction) but fall back to clock_gettime(CLOCK_MONOTONIC) +everywhere else, including riscv64, and --trace_self hooks every +Python-level call. A bounded self-trace smoke test (`viztracer --trace_self +-c "print(1)"`) still completes in ~1s on this runner, so self-tracing +alone is not universally catastrophic here -- something specific to +self-tracing `vizviewer --server_only`'s heavier import graph (argparse, +socketserver, http.server, ...) and/or its long-running server loop is +what never reaches the "Press Ctrl+C to quit" print. Regardless of the +exact mechanism, the test's own wait has no timeout, so any slowdown here +becomes a permanent hang instead of a failure. + +Signed-off-by: Ludovic Henry +--- + tests/test_cmdline.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py +index c37d4a2..98efd4d 100644 +--- a/tests/test_cmdline.py ++++ b/tests/test_cmdline.py +@@ -435,6 +435,7 @@ class TestCommandLineBasic(CmdlineTmpl): + self.template([sys.executable, "-m", "viztracer", "--tracer_entries", "1000", "cmdline_test.py"]) + self.template([sys.executable, "-m", "viztracer", "--tracer_entries", "50", "cmdline_test.py"]) + ++ @skipIf(True, "riscv64: hangs forever, see build-viztracer.yml") + def test_trace_self(self): + def check_func(data): + self.assertGreater(len(data["traceEvents"]), 1000) diff --git a/patches/viztracer/1.1.1/0002-tests-skip-test_use_external_processor-perfetto-has.patch b/patches/viztracer/1.1.1/0002-tests-skip-test_use_external_processor-perfetto-has.patch new file mode 100644 index 000000000..22e2e13ce --- /dev/null +++ b/patches/viztracer/1.1.1/0002-tests-skip-test_use_external_processor-perfetto-has.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 6 Sep 2026 00:00:00 +0000 +Subject: [PATCH] tests: skip test_use_external_processor, which hangs + forever on riscv64 + +Upstream-Status: Inappropriate [riscv64 has no Perfetto trace_processor_shell prebuilt; upstream never exercises this combination] + +--use_external_processor shells out to src/viztracer/web_dist/trace_processor, +Perfetto's own prebuilt-binary launcher. Its TRACE_PROCESSOR_SHELL_MANIFEST +lists prebuilts only for x86_64 and aarch64 machines; on riscv64 no entry +matches and it raises "No prebuilts available for linux-riscv64" and exits +immediately. + +viztracer's own ExternalProcessorProcess._wait_start() does not handle that: + + while True: + line = self._process.stderr.readline().decode("utf-8") + if "This server can be used" in line: + break + +Once the child has exited, readline() on its now-closed stderr pipe returns +"" forever instead of blocking, so this becomes a tight busy-loop that spins +until the CI job's own timeout kills it rather than raising or returning. +This is a real bug in viztracer (any crashed/fast-exiting trace_processor +child triggers it, not just a missing riscv64 prebuilt), but the practical +blocker for us is Perfetto shipping no riscv64 binary at all, which is +outside what a build/test patch here can fix. + +Signed-off-by: Ludovic Henry +--- + tests/test_viewer.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/tests/test_viewer.py b/tests/test_viewer.py +index 0000000..0000000 100644 +--- a/tests/test_viewer.py ++++ b/tests/test_viewer.py +@@ -225,6 +225,7 @@ class TestViewer(CmdlineTmpl): + os.remove(f.name) + + @unittest.skipIf(sys.platform == "win32", "Can't send Ctrl+C reliably on Windows") ++ @unittest.skip("riscv64: hangs forever, see build-viztracer.yml") + def test_use_external_processor(self): + json_script = '{"file_info": {}, "traceEvents": []}' + try: diff --git a/patches/viztracer/1.1.1/0003-tests-skip-attach-tests-riscv64-has-no-attach-so.patch b/patches/viztracer/1.1.1/0003-tests-skip-attach-tests-riscv64-has-no-attach-so.patch new file mode 100644 index 000000000..acf5cca0a --- /dev/null +++ b/patches/viztracer/1.1.1/0003-tests-skip-attach-tests-riscv64-has-no-attach-so.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 6 Sep 2026 00:00:00 +0000 +Subject: [PATCH] tests: skip attach tests riscv64 has no attach .so for + +Upstream-Status: Inappropriate [riscv64 has no prebuilt attach_riscv64.so; there is no source to build one from] + +viztracer ships prebuilt cross-process "attach to a running interpreter" +shared libraries under src/viztracer/attach_process/ (attach_linux_amd64.so, +attach_linux_x86.so, attach_x86_64.dylib, ...) vendored from a third-party +project (see attach_process/LICENSE); there is no C/C++ source for them in +this repository and no build step compiles them - setup.py's package_data +only lists whichever prebuilt file matches platform.machine(). There is no +riscv64 entry at all, so add_code_to_python_process.py's +run_python_code_linux() always raises "Could not find .so for attach to +process." on this architecture. This is a real, upstream, no-source-available +gap, not something a build/test patch here can fix. + +tests/test_remote.py's own `attach_unavailable` skip guard only excludes +win32 and some macOS/Python combinations, so it never anticipated "Linux +but no attach binary for this CPU architecture" and these tests run and +fail on riscv64: + + FAIL: test_basic (tests.test_remote.TestAttachSanity.test_basic) + FAIL: test_attach (tests.test_remote.TestRemote.test_attach) + FAIL: test_attach_installed (tests.test_remote.TestRemote.test_attach_installed) + FAIL: test_uninstall (tests.test_remote.TestRemote.test_uninstall) + +test_remote.py's own test_install and test_attach_script are unaffected - +both drive viztracer's in-process attach API (SIGUSR-based install/ +viztracer.attach.start_attach), not the cross-process .so injection path. + +Signed-off-by: Ludovic Henry +--- + tests/test_remote.py | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/tests/test_remote.py b/tests/test_remote.py +index bbeb27c..f78db47 100644 +--- a/tests/test_remote.py ++++ b/tests/test_remote.py +@@ -39,6 +39,7 @@ class TestRemote(CmdlineTmpl): + self.assertFileExists("remote.json") + os.remove("remote.json") + ++ @unittest.skip("riscv64: no attach_riscv64.so shipped, see build-viztracer.yml") + def test_attach_installed(self): + file_to_attach = textwrap.dedent(""" + from viztracer import VizTracer +@@ -57,6 +58,7 @@ class TestRemote(CmdlineTmpl): + self.attach_check(file_to_attach, attach_cmd, output_file) + self.attach_check(file_to_attach, attach_installed_cmd, output_file, use_installed=True) + ++ @unittest.skip("riscv64: no attach_riscv64.so shipped, see build-viztracer.yml") + def test_attach(self): + file_to_attach = textwrap.dedent(""" + import time +@@ -142,6 +144,7 @@ class TestRemote(CmdlineTmpl): + p_attach_invalid.wait() + self.assertTrue(p_attach_invalid.returncode != 0) + ++ @unittest.skip("riscv64: no attach_riscv64.so shipped, see build-viztracer.yml") + def test_uninstall(self): + file_to_attach = textwrap.dedent(""" + import time +@@ -228,6 +231,7 @@ class TestRemoteFail(CmdlineTmpl): + + @unittest.skipIf(attach_unavailable, "Does not support this platform") + class TestAttachSanity(CmdlineTmpl): ++ @unittest.skip("riscv64: no attach_riscv64.so shipped, see build-viztracer.yml") + def test_basic(self): + file_to_attach = textwrap.dedent(""" + import time