Repository navigation
feat(profiling): Hide frames, Find frame and Pop out on eBPF, Async and pprof profiling #1118
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # | |
| # Licensed to the Apache Software Foundation (ASF) under one or more | |
| # contributor license agreements. See the NOTICE file distributed with | |
| # this work for additional information regarding copyright ownership. | |
| # The ASF licenses this file to You under the Apache License, Version 2.0 | |
| # (the "License"); you may not use this file except in compliance with | |
| # the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| name: CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: | |
| - main | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| license-header: | |
| # Kept separate because it needs neither Node nor `pnpm install` — it is a | |
| # container action that would otherwise sit behind the toolchain setup the | |
| # build job spends its first half-minute on. | |
| name: License header | |
| runs-on: ubuntu-26.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| - name: Check license header | |
| uses: apache/skywalking-eyes@61275cc80d0798a405cb070f7d3a8aaf7cf2c2c1 # v0.8.0 | |
| build-and-check: | |
| # Every Node-side gate in one job. They all need the same checkout + pnpm | |
| # install, and each gate itself finishes in seconds, so fanning them across | |
| # runners bought a few tens of seconds of wall clock at the price of paying | |
| # the setup cost eight times over. Steps run cheapest-first so a lint or | |
| # type error reports before the package build starts. | |
| name: Build and check | |
| runs-on: ubuntu-26.04 | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 — the commit on the ASF allow-list; a version tag is refused | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '24' | |
| cache: 'pnpm' | |
| - run: pnpm install --frozen-lockfile | |
| # ESLint (rules + 2000-line cap, comments excluded) across ui + bff, the | |
| # source-budget gate (≤2000 code / ≤500 comment lines per file), both | |
| # i18n gates (UI catalog coverage/parity and the BFF template-overlay | |
| # validator), and the bundled layer / overview templates. Those templates | |
| # are configuration that ships inside the release, so a broken one is a | |
| # shipping defect rather than operator error — and the loader only | |
| # JSON.parses them, which is how an orderBy naming a nonexistent column | |
| # and an unsupported aggregation both shipped. | |
| - name: Lint, source budget, i18n, security, docs links, bundled templates | |
| run: pnpm lint | |
| # The vendored BanyanDB wire contract, in one regenerate-then-diff gate — | |
| # the same shape as `licenses:bin:check`. Re-fetches the .proto from the | |
| # PINNED upstream commits, regenerates proto.pb.d.ts from what it fetched, | |
| # then diffs both: so it proves in one pass that nobody hand-edited a | |
| # vendored .proto, that nobody hand-edited the generated declarations, and | |
| # that the two are in step. `git diff --exit-code` names the paths that | |
| # moved, which is what says whether the contract or the generator drifted. | |
| # | |
| # Pinning commits rather than branches is what stops an upstream release | |
| # turning an unrelated PR red. `pnpm proto:check:types` is the same check | |
| # minus the fetch, kept for working offline — redundant here, since this | |
| # step regenerates from the freshly fetched tree anyway. | |
| - name: Vendored proto tree matches its pinned upstream | |
| run: pnpm proto:check | |
| # Advisories against what actually SHIPS. `--prod` matches the scope the | |
| # binary LICENSE is generated from, so the two gates describe the same | |
| # artifact. A finding here is always something an operator would run, so | |
| # it blocks. Only high+ does: a gate that fails on things nobody can act | |
| # on gets ignored. | |
| # remove, will aduit periodically, not every PR | |
| # - name: Audit shipped dependencies | |
| # run: pnpm audit --prod --audit-level=high | |
| # Dev tooling is a different threat model, not a lesser one — it runs in | |
| # CI with repository credentials and on contributors' machines, so a | |
| # compromised build tool can reach the artifact without ever being IN it. | |
| # Reported rather than enforced: an advisory in a linter's transitive | |
| # tree with no patch available would otherwise block every unrelated PR. | |
| - name: Audit build tooling (report only) | |
| continue-on-error: true | |
| run: pnpm audit --audit-level=high | |
| - name: Type-check | |
| run: pnpm -r run type-check | |
| # `pnpm -r` walks every workspace package and runs `test:unit` — | |
| # currently @skywalking-horizon-ui/ui (vitest + jsdom), | |
| # @skywalking-horizon-ui/bff and @skywalking-horizon-ui/api-client | |
| # (vitest, node env). Packages without the script (design-tokens, | |
| # templates) are skipped automatically. | |
| # The scripts already include `vitest run`, so no `--run` forwarding | |
| # needed — keeps the CI line robust against pnpm-flag parsing quirks. | |
| # The runner pool is pinned in those same scripts, not on this line, so a | |
| # contributor's `pnpm test:unit` and this job share one isolation model. | |
| # Why `threads`, and what enforces it: `src/runner-pool.test.ts`. | |
| - name: Unit tests | |
| run: pnpm -r run test:unit | |
| # ASSEMBLES the distributable: the three packages/* builds, the BFF | |
| # esbuild bundle, the Vite UI build, dist/server.js, the built UI at | |
| # dist/static/, bundled templates, i18n catalogs, AI resources. Because it | |
| # compiles the UI and the BFF on the way, it subsumes standalone build | |
| # steps. Until this ran in CI the assembly happened for the first time | |
| # inside the release image build — i.e. after merge — so a broken tarball | |
| # merged green. | |
| - name: Package the distributable | |
| run: pnpm package | |
| # The binary tarball bundles the whole production dependency tree — the | |
| # BFF runtime deps and the UI deps Vite compiles into the static bundle — | |
| # so the binary LICENSE has to name every one of them. license-eye | |
| # regenerates it from LICENSE.tpl and `git diff` is the drift gate: a | |
| # dependency added or removed without running `pnpm licenses:bin:update` | |
| # fails here. | |
| # | |
| # Built from a commit rather than v0.8.0 because pnpm support landed after | |
| # that release; revert to a tag once one ships with it. | |
| # | |
| # A license the tool cannot identify makes `dependency resolve` itself | |
| # exit non-zero, so an unknown license fails this step too. The licenses/ | |
| # directory beside the LICENSE is maintained by hand — see CLAUDE.md. | |
| - name: Set up Go for license-eye | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: '1.25' | |
| cache: false | |
| - name: Verify binary LICENSE is current | |
| run: | | |
| go install "github.com/apache/skywalking-eyes/cmd/license-eye@$(tr -d '[:space:]' < .license-eye-version)" | |
| pnpm licenses:bin:check | |
| - name: Compare source vs. binary LICENSE/NOTICE shape | |
| run: | | |
| # Source-flavored LICENSE = repo root; must NOT contain the | |
| # bundled-dep summary section. | |
| if grep -q 'Horizon UI Subcomponents' LICENSE; then | |
| echo "ERROR: repo-root LICENSE contains the bundled-dep summary section." | |
| echo " That section belongs only in the binary LICENSE." | |
| exit 1 | |
| fi | |
| # Committed binary-flavored LICENSE must contain the summary. | |
| if ! grep -q 'Horizon UI Subcomponents' dist-material/release-docs/LICENSE; then | |
| echo "ERROR: committed binary LICENSE missing 'Horizon UI Subcomponents'." | |
| exit 1 | |
| fi | |
| # NOTICE: both must exist and start with the same ASF copyright line. | |
| test -f NOTICE && test -f dist-material/release-docs/NOTICE | |
| head -1 NOTICE | grep -q 'Apache SkyWalking Horizon UI' | |
| head -1 dist-material/release-docs/NOTICE | grep -q 'Apache SkyWalking Horizon UI' | |
| echo "src/bin LICENSE+NOTICE shape OK." | |
| - name: Upload dependency report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-license-report | |
| path: | | |
| dist-material/release-docs/LICENSE | |
| dist-material/release-docs/NOTICE | |
| # A dist/ that assembles but 404s at `/` is not a working release. | |
| - name: Boot the packaged server and smoke-test it | |
| run: | | |
| set -eu | |
| # No HORIZON_STATIC_DIR on purpose: the packaged layout must serve | |
| # the UI from the static/ dir beside server.js without being told. | |
| HORIZON_SERVER_PORT=8099 \ | |
| node dist/server.js > /tmp/ci-boot.log 2>&1 & | |
| for _ in $(seq 1 60); do | |
| curl -sf -o /dev/null "http://127.0.0.1:8099/api/health" && break | |
| sleep 1 | |
| done | |
| curl -sf -o /dev/null "http://127.0.0.1:8099/api/health" || { echo "server never became healthy"; cat /tmp/ci-boot.log; exit 1; } | |
| fail() { echo "::error::$1"; cat /tmp/ci-boot.log; exit 1; } | |
| # `/` must return the SPA shell, not a 404. | |
| code=$(curl -s -o /tmp/index.html -w '%{http_code}' "http://127.0.0.1:8099/") | |
| [ "$code" = "200" ] || fail "GET / returned ${code}, expected 200 (is the UI in the package?)" | |
| grep -qi '<div id="app"' /tmp/index.html || fail "GET / did not return the SPA shell" | |
| # A hashed asset referenced by that shell must resolve. | |
| asset=$(grep -o '/assets/[^"]*\.js' /tmp/index.html | head -1) | |
| [ -n "$asset" ] || fail "no hashed asset referenced by index.html" | |
| code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:8099${asset}") | |
| [ "$code" = "200" ] || fail "asset ${asset} returned ${code}" | |
| # Client-side route falls through to the shell; /api/* still 404s as JSON. | |
| code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:8099/layer/general/traces") | |
| [ "$code" = "200" ] || fail "SPA deep link returned ${code}" | |
| curl -s "http://127.0.0.1:8099/api/nope" | grep -q '"code":"not_found"' || fail "/api/* 404 is not the JSON envelope" | |
| # Security headers, asserted against the SHIPPED artifact — a unit | |
| # test on the header map cannot catch a hook that never runs, a | |
| # plugin registered ahead of it, or a route that writes its own head. | |
| hdr() { curl -s -D - -o /dev/null "$1" | tr -d '\r' | grep -i "^$2:" | cut -d' ' -f2-; } | |
| csp=$(hdr "http://127.0.0.1:8099/" "content-security-policy") | |
| [ -n "$csp" ] || fail "no Content-Security-Policy on the SPA shell" | |
| # Pull script-src out and judge it alone. Matching the whole policy | |
| # would be both wrong ways round: style-src legitimately carries | |
| # 'unsafe-inline', and a substring test on the joined string depends | |
| # on token order. This is the directive that makes an injected | |
| # handler or a stored javascript: URL inert. | |
| scriptsrc=$(printf '%s' "$csp" | tr ';' '\n' | grep -i 'script-src' | head -1 || true) | |
| [ -n "$scriptsrc" ] || fail "CSP has no script-src: ${csp}" | |
| case "$scriptsrc" in *"'self'"*) ;; *) fail "script-src lacks 'self': ${scriptsrc}" ;; esac | |
| case "$scriptsrc" in *unsafe-inline*|*unsafe-eval*|*unsafe-hashes*) | |
| fail "script-src grants an unsafe keyword: ${scriptsrc}" ;; esac | |
| for h in x-content-type-options x-frame-options referrer-policy; do | |
| [ -n "$(hdr "http://127.0.0.1:8099/" "$h")" ] || fail "missing ${h} on the SPA shell" | |
| done | |
| # Every /api/ response, including a 404 and an error, is unstorable. | |
| for p in /api/health /api/nope; do | |
| cc=$(hdr "http://127.0.0.1:8099${p}" "cache-control") | |
| [ "$cc" = "no-store" ] || fail "${p} Cache-Control is '${cc}', expected no-store" | |
| [ -n "$(hdr "http://127.0.0.1:8099${p}" "content-security-policy")" ] || fail "no CSP on ${p}" | |
| done | |
| # ...while content-hashed assets stay cacheable. | |
| cc=$(hdr "http://127.0.0.1:8099${asset}" "cache-control") | |
| case "$cc" in *no-store*) fail "hashed asset ${asset} is no-store — static files must stay cacheable" ;; esac | |
| echo "packaged artifact boots, serves the UI, and carries its security headers" | |
| banyandb-client-it: | |
| # The BanyanDB client's only real coverage. Its unit tests exercise | |
| # encoding in isolation; everything that matters — schema reconciliation, | |
| # the write path's series construction, query criteria — is only true or | |
| # false against a running server, and each of those has already shipped a | |
| # bug that type-check, lint and unit tests all passed. | |
| # | |
| # Self-contained: testcontainers starts the BanyanDB that | |
| # `test/e2e/script/env` pins, on an ephemeral port, and stops it after. No | |
| # service container to declare and no port to collide with. | |
| name: BanyanDB client IT | |
| runs-on: ubuntu-26.04 | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 — the commit on the ASF allow-list; a version tag is refused | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '24' | |
| cache: 'pnpm' | |
| - run: pnpm install --frozen-lockfile | |
| - name: Integration tests against a real BanyanDB | |
| run: pnpm test:it | |
| required: | |
| # Status-check name pinned to "Required" so Apache branch protection | |
| # (`.asf.yaml: protected_branches.main.required_status_checks.contexts: | |
| # [Required]`) can gate on a single rolled-up signal. | |
| # | |
| # E2E is a separate workflow with its own rolled-up "E2E Required" | |
| # status, listed alongside this one in .asf.yaml — it cannot be a `needs` | |
| # here because `needs` does not cross workflow boundaries. | |
| if: always() && !cancelled() | |
| name: Required | |
| needs: [license-header, build-and-check, banyandb-client-it] | |
| runs-on: ubuntu-26.04 | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Check upstream jobs | |
| run: | | |
| [[ ${{ needs.license-header.result }} == 'success' ]] || exit 1 | |
| [[ ${{ needs.build-and-check.result }} == 'success' ]] || exit 1 | |
| [[ ${{ needs.banyandb-client-it.result }} == 'success' ]] || exit 1 |