Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/sibling-pins.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"codetracer": "latest-trace-format",
"trace_record": "master"
}
170 changes: 170 additions & 0 deletions .github/workflows/codetracer-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
name: CodeTracer Integration Tests

on:
pull_request:
branches: [wasm-tracing, main]
push:
branches: [wasm-tracing, main]
workflow_dispatch:
inputs:
codetracer_ref:
description: 'codetracer git ref to test against (default: from sibling-pins.json)'
required: false
default: ''
trace_record_ref:
description: 'trace_record git ref (default: from sibling-pins.json)'
required: false
default: ''

permissions:
contents: read

jobs:
codetracer-integration:
runs-on: [self-hosted, nixos]

steps:
- name: Checkout wasm-recorder
uses: actions/checkout@v5
with:
path: codetracer-wasm-recorder

- name: Check required secrets
run: |
TOKEN="${{ secrets.GH_READ_METACRAFT_PRIVATE_REPOS }}"
if [[ -z "$TOKEN" ]]; then
echo "::error::GH_READ_METACRAFT_PRIVATE_REPOS secret is not configured. This secret is required to clone the codetracer repo (which has private submodules). Please add it in the repo Settings > Secrets and variables > Actions."
exit 1
fi

- name: Install Nix
uses: cachix/install-nix-action@v27

- name: Configure Nix and Git authentication
run: |
mkdir -p $HOME/.config/nix
cat << EOF > "$HOME/.config/nix/nix.conf"
accept-flake-config = true
allow-import-from-derivation = true
substituters = https://cache.nixos.org ${{ vars.SUBSTITUTERS }}
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ${{ vars.TRUSTED_PUBLIC_KEYS }}
access-tokens = github.com=${{ secrets.GH_READ_METACRAFT_PRIVATE_REPOS }}
EOF

# Rewrite GitHub URLs for authenticated access to private repos
git config --global url."https://x-access-token:${{ secrets.GH_READ_METACRAFT_PRIVATE_REPOS }}@github.com/".insteadOf "git@github.com:"
git config --global url."https://x-access-token:${{ secrets.GH_READ_METACRAFT_PRIVATE_REPOS }}@github.com/".insteadOf "ssh://git@github.com/"
git config --global url."https://x-access-token:${{ secrets.GH_READ_METACRAFT_PRIVATE_REPOS }}@github.com/".insteadOf "https://github.com/"

- name: Setup Cachix
uses: cachix/cachix-action@v15
if: vars.CACHIX_CACHE != ''
with:
name: ${{ vars.CACHIX_CACHE }}
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}

- name: Resolve sibling refs
id: resolve-refs
run: |
PINS="codetracer-wasm-recorder/.github/sibling-pins.json"

# Resolve codetracer ref
if [[ -n "${{ github.event.inputs.codetracer_ref }}" ]]; then
CT_REF="${{ github.event.inputs.codetracer_ref }}"
elif [[ -f "$PINS" ]]; then
CT_REF=$(grep '"codetracer"' "$PINS" | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/')
fi
CT_REF="${CT_REF:-main}"
echo "codetracer_ref=$CT_REF" >> "$GITHUB_OUTPUT"

# Resolve trace_record ref
if [[ -n "${{ github.event.inputs.trace_record_ref }}" ]]; then
TR_REF="${{ github.event.inputs.trace_record_ref }}"
elif [[ -f "$PINS" ]]; then
TR_REF=$(grep '"trace_record"' "$PINS" | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/')
fi
TR_REF="${TR_REF:-master}"
echo "trace_record_ref=$TR_REF" >> "$GITHUB_OUTPUT"

echo "Resolved codetracer ref: $CT_REF"
echo "Resolved trace_record ref: $TR_REF"

- name: Clone codetracer
run: |
REF="${{ steps.resolve-refs.outputs.codetracer_ref }}"
echo "Cloning codetracer at ref: $REF"

rm -rf codetracer
git clone --recursive \
"https://x-access-token:${{ secrets.GH_READ_METACRAFT_PRIVATE_REPOS }}@github.com/metacraft-labs/codetracer.git" \
codetracer
cd codetracer
git checkout "$REF"
git submodule update --init --recursive

# Rewrite .gitmodules SSH URLs to HTTPS for nix's internal git fetcher
sed -i 's|git@github.com:|https://github.com/|g' .gitmodules
sed -i 's|ssh://git@github.com/|https://github.com/|g' .gitmodules
git add .gitmodules
git -c user.name="CI" -c user.email="ci@local" commit --no-gpg-sign -m "CI: rewrite submodule URLs to HTTPS" || true

- name: Clone trace_record
run: |
REF="${{ steps.resolve-refs.outputs.trace_record_ref }}"
echo "Cloning trace_record at ref: $REF"

rm -rf trace_record
git clone \
"https://x-access-token:${{ secrets.GH_READ_METACRAFT_PRIVATE_REPOS }}@github.com/metacraft-labs/trace_record.git" \
trace_record
cd trace_record
git checkout "$REF"

- name: Configure wasm-recorder dependencies
run: |
cd codetracer-wasm-recorder

# Ensure go.mod replace directive points to the sibling trace_record.
if ! grep -q 'replace.*trace_record' go.mod; then
echo 'replace github.com/metacraft-labs/trace_record => ../trace_record' >> go.mod
fi

# Update go.sum
go mod tidy || true

- name: Build wazero
run: |
cd codetracer-wasm-recorder
nix develop ../codetracer?submodules=1 --command bash -c '
go build -o wazero ./cmd/wazero
echo "Built wazero: $(./wazero version 2>/dev/null || echo unknown)"
'

- name: Run WASM flow integration test
run: |
WAZERO_PATH="$(pwd)/codetracer-wasm-recorder/wazero"
cd codetracer
nix develop .?submodules=1 --command bash -c "
set -e
rustup target add wasm32-wasip1 2>/dev/null || true
export CODETRACER_WASM_VM_PATH='$WAZERO_PATH'
echo \"Using wazero: \$CODETRACER_WASM_VM_PATH\"
echo 'Running WASM flow integration test...'
cd src/db-backend
OUTPUT=\$(cargo test --test wasm_flow_integration -- --nocapture 2>&1) || { echo \"\$OUTPUT\"; exit 1; }
echo \"\$OUTPUT\"
if echo \"\$OUTPUT\" | grep -q 'running 0 tests'; then
echo 'ERROR: wasm_flow_integration test binary compiled but no tests ran'
exit 1
fi
echo 'WASM flow integration test passed!'
"

- name: Upload test logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: codetracer-integration-logs
path: codetracer/target/
retention-days: 14
if-no-files-found: ignore
40 changes: 40 additions & 0 deletions .github/workflows/tracewriter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Tracewriter Tests

on:
pull_request:
branches: [wasm-tracing, main]
push:
branches: [wasm-tracing, main]

env:
GO_VERSION: "1.24"

jobs:
tracewriter:
name: Tracewriter tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}

- name: Clone trace_record
run: |
PINS=".github/sibling-pins.json"
if [[ -f "$PINS" ]]; then
TR_REF=$(grep '"trace_record"' "$PINS" | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/')
fi
TR_REF="${TR_REF:-master}"
echo "Cloning trace_record at ref: $TR_REF"
git clone https://github.com/metacraft-labs/trace_record.git ../trace_record
cd ../trace_record
git checkout "$TR_REF"

- name: Install just
uses: extractions/setup-just@v2

- name: Run tracewriter tests
run: just test-tracewriter
35 changes: 25 additions & 10 deletions cmd/wazero/wazero.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/version"
"github.com/tetratelabs/wazero/sys"
"github.com/tetratelabs/wazero/tracewriter"
)

func main() {
Expand Down Expand Up @@ -218,6 +219,11 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int {
flags.StringVar(&traceDir, "trace-dir", "",
"Directory where to save the trace record. If empty - no trace is produced. Default \"\".")

var useRustWriter bool
flags.BoolVar(&useRustWriter, "use-rust-writer", false,
"Use the Rust FFI trace writer instead of the Go trace writer. "+
"Requires the binary to be built with cgo and the codetracer_trace_writer_ffi library.")

cacheDir := cacheDirFlag(flags)

_ = flags.Parse(args)
Expand Down Expand Up @@ -336,15 +342,24 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int {
return 1
}

traceRecord := trace_record.MakeTraceRecord()
var traceRecordPtr *trace_record.TraceRecord
var recorder tracewriter.TraceRecorder
if traceDir != "" {
traceRecordPtr = &traceRecord
if useRustWriter {
rw, rwErr := tracewriter.NewRustTraceWriter()
if rwErr != nil {
fmt.Fprintf(stdErr, "error creating rust trace writer: %v\n", rwErr)
return 1
}
recorder = rw
} else {
goRecord := trace_record.MakeTraceRecord()
recorder = tracewriter.NewGoWriter(&goRecord)
}
}

var stylusState *stylus.StylusTrace
if stylusTracePath != "" {
stylusState, err = stylus.Instantiate(ctx, rt, stylusTracePath, traceRecordPtr)
stylusState, err = stylus.Instantiate(ctx, rt, stylusTracePath, recorder)
if err != nil {
fmt.Fprintf(stdErr, "error reading stylus trace: %v\n", err)
return 1
Expand All @@ -366,7 +381,7 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int {

var module api.Module
if err == nil {
module, err = rt.InstantiateModuleWithRecord(ctx, guest, conf, traceRecordPtr)
module, err = rt.InstantiateModuleWithRecord(ctx, guest, conf, recorder)
}

if err != nil {
Expand All @@ -375,7 +390,7 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int {
if exitCode == sys.ExitCodeDeadlineExceeded {
fmt.Fprintf(stdErr, "error: %v (timeout %v)\n", exitErr, timeout)
}
produceTrace(traceDir, wasmFile, traceRecord)
produceTrace(traceDir, wasmFile, recorder)
return int(exitCode)
}
fmt.Fprintf(stdErr, "error instantiating wasm binary: %v\n", err)
Expand Down Expand Up @@ -405,17 +420,17 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int {
// We're done, _start was called as part of instantiating the module.
}

produceTrace(traceDir, wasmFile, traceRecord)
produceTrace(traceDir, wasmFile, recorder)

return 0
}

func produceTrace(traceDir string, fileName string, traceRecord trace_record.TraceRecord) {
func produceTrace(traceDir string, fileName string, recorder tracewriter.TraceRecorder) {

// TODO: Handle error
workDir, _ := os.Getwd()
if traceDir != "" {
err := traceRecord.ProduceTrace(traceDir, fileName, workDir)
if traceDir != "" && recorder != nil {
err := recorder.ProduceTrace(traceDir, fileName, workDir)
if err != nil {
fmt.Fprintf(os.Stderr, "error creating trace: %v\n", err)
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ require (
github.com/metacraft-labs/trace_record v0.0.0-20250703085553-b8a5a24619c8
github.com/rdleal/intervalst v1.5.0
)

replace github.com/metacraft-labs/trace_record => ../trace_record
8 changes: 4 additions & 4 deletions internal/engine/interpreter/rust_variable_readers.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func bytesToStringRust(rawBytes []byte, typ *dwarf.StructType, m *wasm.ModuleIns
str := ""

for i := 0; i < int(length); i++ {
data, _ := mem.Read(addr+uint32(i), 1)
data, _ := mem.Read(uint32(addr)+uint32(i), 1)
str += string(data[0])
}

Expand Down Expand Up @@ -110,7 +110,7 @@ func bytesToSliceRust(rawBytes []byte, typ *dwarf.StructType, m *wasm.ModuleInst

var addr uint32
if addrRecord, ok := fields[0].(trace_record.ReferenceValueRecord); ok {
addr = addrRecord.Address
addr = uint32(addrRecord.Address)
} else {
return nil, INVALID_TYPE_ID, fmt.Errorf("not a slice")
}
Expand Down Expand Up @@ -145,7 +145,7 @@ func bytesToSliceRust(rawBytes []byte, typ *dwarf.StructType, m *wasm.ModuleInst

elems := make([]trace_record.ValueRecord, 0)
for i := uint32(0); i < length; i++ {
elemBytes, ok := mem.Read(addr+i*elemSize, elemSize)
elemBytes, ok := mem.Read(uint32(addr)+i*elemSize, elemSize)
if !ok {
return trace_record.SequenceValue(elems, true, typeId), INVALID_TYPE_ID, fmt.Errorf("invalid memory access")
}
Expand Down Expand Up @@ -251,7 +251,7 @@ func bytesToVecRust(rawBytes []byte, typ *dwarf.StructType, m *wasm.ModuleInstan

elems := make([]trace_record.ValueRecord, 0)
for i := uint32(0); i < length; i++ {
elemBytes, ok := mem.Read(addr+i*elemSize, elemSize)
elemBytes, ok := mem.Read(uint32(addr)+i*elemSize, elemSize)
if !ok {
return trace_record.SequenceValue(elems, true, typeId), INVALID_TYPE_ID, fmt.Errorf("invalid memory access")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/engine/interpreter/variable_readers.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ func bytesToPointer(rawBytes []byte, typ *dwarf.PtrType, m *wasm.ModuleInstance)

// TODO: Record pointer Type info

return trace_record.ReferenceValue(dereferencedValueRecord, addr, false, typeId), typeId, nil
return trace_record.ReferenceValue(dereferencedValueRecord, uint64(addr), false, typeId), typeId, nil

}

Expand Down
4 changes: 2 additions & 2 deletions internal/stylus/stylus.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import (
"encoding/json"
"os"

"github.com/metacraft-labs/trace_record"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/tracewriter"
)

func Instantiate(ctx context.Context, r wazero.Runtime, stylusTracePath string, record *trace_record.TraceRecord) (*StylusTrace, error) {
func Instantiate(ctx context.Context, r wazero.Runtime, stylusTracePath string, record tracewriter.TraceRecorder) (*StylusTrace, error) {
stylusTraceJson, err := os.ReadFile(stylusTracePath)
if err != nil {
return nil, err
Expand Down
Loading