From 82ac506e20f63941b0025f2c74f2e9e022021384 Mon Sep 17 00:00:00 2001 From: Joe <4088382+JoeStech@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:10:26 -0700 Subject: [PATCH 1/9] partial backup --- .../2-environment-setup.md | 74 +++++++++++++++ .../3-checkout-build-baseline.md | 91 +++++++++++++++++++ .../4-dependency-mapping-manifests.md | 63 +++++++++++++ .../5-platform-code-deps.md | 81 +++++++++++++++++ .../6-containerization.md | 73 +++++++++++++++ .../7-deploy-verify-cobalt.md | 46 ++++++++++ .../8-performance-tuning.md | 75 +++++++++++++++ .../dotnet-migration-nopcommerce/_index.md | 83 +++++++++++++++++ .../_next-steps.md | 14 +++ 9 files changed, 600 insertions(+) create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/3-checkout-build-baseline.md create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/4-dependency-mapping-manifests.md create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-platform-code-deps.md create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-containerization.md create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-deploy-verify-cobalt.md create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/8-performance-tuning.md create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_next-steps.md diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md new file mode 100644 index 0000000000..8940ec71d0 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md @@ -0,0 +1,74 @@ +--- +title: Environment setup +weight: 2 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +Create two VMs in the same Azure region with matching OS and tooling so differences are attributable to architecture, not configuration drift. + +This path was validated with Ubuntu 24.04 LTS VMs in `westus2`: + +| | x86 | Arm | +|---|---|---| +| **VM size** | Standard_D2s_v6 | Standard_D2ps_v6 | +| **vCPUs** | 2 | 2 | + +## Log in and create VMs + +```bash +az login --use-device-code +az account set --subscription +``` + +Create both VMs in the same resource group and region. + +## Install tools on each VM + +```bash +sudo apt-get update -y +sudo apt-get install -y git curl jq ca-certificates gnupg docker.io +sudo usermod -aG docker "$USER" + +sudo add-apt-repository ppa:dotnet/backports -y +sudo apt-get update -y +sudo apt-get install -y dotnet-sdk-9.0 +``` + +Verify: + +```bash +uname -m +dotnet --version +docker --version +``` + +Expected output: `x86_64` on the x86 VM, `aarch64` on the Arm VM. Both should report the same .NET SDK version (9.0.x). + +## PostgreSQL prerequisite for nopCommerce install + +nopCommerce migrations on PostgreSQL require the `citext` extension. Create it before running the installer: + +```bash +docker run -d --name nop-postgres \ + -e POSTGRES_USER=nop \ + -e POSTGRES_PASSWORD= \ + -e POSTGRES_DB=nopcommerce \ + -p 5432:5432 postgres:16 + +docker exec nop-postgres psql -U nop -d nopcommerce \ + -c "CREATE EXTENSION IF NOT EXISTS citext;" +``` + +## Optional: script-driven remote execution + +```bash +az vm run-command invoke \ + -g \ + -n \ + --command-id RunShellScript \ + --scripts @./your-test-script.sh +``` + +This was used to run identical commands on both VMs. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/3-checkout-build-baseline.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/3-checkout-build-baseline.md new file mode 100644 index 0000000000..4c7394a18f --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/3-checkout-build-baseline.md @@ -0,0 +1,91 @@ +--- +title: Build and baseline +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +Pin nopCommerce to a reproducible release, build on both architectures, and capture a baseline that includes core storefront paths. + +## Clone and pin the source + +```bash +git clone https://github.com/nopSolutions/nopCommerce.git +cd nopCommerce +git fetch --tags --prune +git checkout release-4.90.3 +git rev-parse --short=10 HEAD # expect 9beda11c42 +``` + +## Restore and build + +```bash +dotnet restore src/Presentation/Nop.Web/Nop.Web.csproj +dotnet build src/Presentation/Nop.Web/Nop.Web.csproj -c Release --no-restore +``` + +## Start the application + +```bash +cd src/Presentation/Nop.Web +dotnet run -c Release --no-build --urls http://0.0.0.0:5000 +``` + +## Baseline methodology + +Do not rely on `/install` alone. Baseline at least: + +- Home page +- Search +- Product page +- Cart +- Checkout + +Prerequisite: complete nopCommerce setup first (database configured and installer finished). Otherwise, storefront paths redirect to the install flow and do not represent real behavior. + +Use a valid product URL from your seeded catalog (for example, `/apple-macbook-pro`), not a hard-coded slug that may not exist in your dataset. + +Quick smoke probe (pre-install, useful as a connectivity check only): + +```bash +for p in / \ + '/search?q=book' \ + '/apple-macbook-pro' \ + '/cart' \ + '/checkout'; do + echo "== $p ==" + for i in $(seq 1 20); do + curl -sS -o /dev/null -w '%{time_total}\n' "http://127.0.0.1:5000$p" + done +done +``` + +Before installation is complete, all storefront paths redirect to setup (`302`). Those numbers confirm reachability but are not valid performance baselines. + +## Installed-store endpoint baseline (validated) + +After fixing PostgreSQL setup (`citext`) and completing installation on both machines, the same endpoint benchmark was executed with `wrk` (`-t2 -c32 -d20s`) across this set: + +- `/` +- `/search?q=book` +- `/electronics` +- `/apple-macbook-pro` +- `/cart` +- `/checkout` + +Storefront pages returned `200`; `/checkout` returned `302` (expected without an authenticated session). + +Requests/second comparison: + +| Endpoint | x86 | Arm | Arm vs x86 | +|---|---:|---:|---:| +| `/apple-macbook-pro` | 35.40 | 32.28 | -8.8% | +| `/electronics` | 99.52 | 76.07 | -23.6% | +| `/search?q=book` | 28.18 | 27.40 | -2.8% | + +Different endpoints show different gaps. Avoid reducing migration performance to a single number — use per-endpoint baselines to guide architecture-specific optimizations. + +## Arm MCP accelerator (optional) + +Use MCP to script repeated baseline runs and summarize deltas, but keep the manual baseline commands as your ground truth. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/4-dependency-mapping-manifests.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/4-dependency-mapping-manifests.md new file mode 100644 index 0000000000..a8f778aea2 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/4-dependency-mapping-manifests.md @@ -0,0 +1,63 @@ +--- +title: Audit dependencies +weight: 4 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +Use a manual-first audit, then use the Arm MCP server to accelerate checks. + +## Manual workflow (required) + +### 1. Map direct references + +```bash +rg -n "//..nupkg /tmp/nupkg-audit/ +cd /tmp/nupkg-audit +unzip -l ..nupkg | rg "runtimes/|native/" +``` + +This is how you catch hidden architecture-specific binaries. + +## Dependency cascade rule + +Treat dependencies as a chain, not isolated items: + +- App depends on library A +- Library A depends on library B +- Library B is architecture-sensitive + +You must resolve B first, then validate A, then validate the app. In nopCommerce, `iTextSharp` and `System.Drawing` are an example of this chain. + +## Arm MCP accelerator (optional) + +After manual evidence is collected, use MCP tools to speed repetitive checks: + +- `knowledge_base_search` for package compatibility +- `skopeo` or `check_image` for container image architecture coverage +- `migrate_ease_scan` where scanner support exists + +Keep manual outputs as the source of truth. Use MCP to prioritize and scale, not to skip evidence. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-platform-code-deps.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-platform-code-deps.md new file mode 100644 index 0000000000..568cc7ab13 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-platform-code-deps.md @@ -0,0 +1,81 @@ +--- +title: Replace platform-specific code +weight: 5 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +Use one decision framework for every architecture-sensitive dependency. + +## Manual workflow (required) + +### 1. Find references + +```bash +rg -n "System\.Drawing|ColorTranslator" src +``` + +### 2. Decide using the 4-option framework + +1. Keep it and test it deeply. +2. Replace it with a managed cross-platform alternative. +3. Replace it with a native equivalent that has feature parity. +4. Remove the feature if business value is low. + +The workflow is the same for every dependency. + +### 3. Apply to nopCommerce + +- `System.Drawing` image paths: use option 2 with ImageSharp. +- `iTextSharp` PDF color usage: start with option 1 (keep and test), then evaluate option 2 or 3 if you need full decoupling. + +```bash +dotnet add src/Libraries/Nop.Services/Nop.Services.csproj package SixLabors.ImageSharp +``` + +## Baseline: exercise `System.Drawing.Common` first + +Before replacing code, run a direct image-operation baseline that uses `System.Drawing.Common` and `ImageSharp` on both x86 and Arm. + +Validated Azure results (150 resize+encode operations each) showed: + +- `System.Drawing.Common` lower average latency in this microbenchmark. +- `ImageSharp` higher average latency in this microbenchmark. + +| Architecture | System.Drawing.Common avg (ms) | ImageSharp avg (ms) | +|---|---:|---:| +| x86 | 15.528 | 32.329 | +| Arm | 15.941 | 70.512 | + +This does not invalidate the migration recommendation. Performance is only one dimension. + +## Why ImageSharp still improves the migration + +A second validated test removed `libgdiplus` on each VM: + +- With `libgdiplus` installed: both libraries succeeded. +- Without `libgdiplus`: `System.Drawing.Common` failed (`TypeInitializationException`), ImageSharp still succeeded. + +`System.Drawing.Common` depends on `libgdiplus`, a native library that can break portability and behave inconsistently across platforms. ImageSharp is fully managed, so it runs identically on x86 and Arm Linux without native dependencies. For this migration, portability and predictable runtime behavior are the primary goals; then tune and benchmark ImageSharp paths as needed. + +### 4. Handle cascade dependencies + +Do not patch only the top-level project. If a transitive dependency is architecture-sensitive, fix it first, then retest consumers up the chain. + +## Arm MCP accelerator (optional) + +Use MCP to accelerate candidate discovery and compatibility research, then validate with manual tests: + +- `knowledge_base_search` for replacement candidates +- `migrate_ease_scan` where supported + +## Upstream contribution path + +When you fix architecture issues in shared code, upstream the change. + +- Open a PR with Arm/x86 validation evidence. +- Include benchmark and compatibility notes. +- Track maintainer feedback and update migration docs with the final accepted approach. + +This has high leverage: each upstream fix reduces work for future migrations. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-containerization.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-containerization.md new file mode 100644 index 0000000000..74b5555d36 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-containerization.md @@ -0,0 +1,73 @@ +--- +title: Containerize the application +weight: 6 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +Use a manual-first container workflow, then add MCP checks. + +## Manual workflow (required) + +### 1. Dockerfile path + +```bash +docker build -f Dockerfile -t nop-web:dockerfile-test . +``` + +The nopCommerce Dockerfile uses `FROM --platform=$BUILDPLATFORM`, which is a BuildKit feature. The legacy Docker builder does not inject `$BUILDPLATFORM`, causing: + +``` +failed to parse platform : "" is an invalid OS component of "" +``` + +Fix by switching to buildx: + +```bash +docker buildx create --use --name nopx || true +docker buildx build --platform linux/amd64,linux/arm64 -t nop-web:multiarch --push . +``` + +If you cannot use buildx, create a simplified Dockerfile that removes the `--platform=$BUILDPLATFORM` argument and builds for the host architecture only. + +### 2. .NET SDK publish path (single architecture) + +```bash +dotnet publish src/Presentation/Nop.Web/Nop.Web.csproj \ + -c Release \ + /t:PublishContainer \ + -p:ContainerImageTag=publish-test +``` + +Validated outputs in this project: + +- x86 image: `591 MB` +- Arm image: `623 MB` + +### 3. .NET SDK multi-arch path + +Define multi-arch runtime identifiers in the project file, then publish once. + +```xml + + linux-x64;linux-arm64 + +``` + +```bash +dotnet publish src/Presentation/Nop.Web/Nop.Web.csproj -c Release /t:PublishContainer +``` + +This is the scalable path for one image definition across both architectures. + +## Arm MCP accelerator (optional) + +Use MCP image tools after manual build succeeds: + +- `skopeo` to inspect manifest architecture coverage +- `check_image` for a quick architecture report + +## Recommendation + +Use SDK publish as the default migration path. Keep Dockerfile for advanced layer control or custom build logic. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-deploy-verify-cobalt.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-deploy-verify-cobalt.md new file mode 100644 index 0000000000..a969eb74d8 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-deploy-verify-cobalt.md @@ -0,0 +1,46 @@ +--- +title: Deploy and verify on Cobalt +weight: 7 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +Deploy on Arm Cobalt, verify correctness, and record architecture-specific behavior. + +## Manual workflow (required) + +### 1. Run the container + +```bash +docker rm -f nop-web || true +docker run -d --name nop-web -p 5000:5000 nop-web:publish-test +``` + +### 2. Verify health and architecture + +```bash +curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5000/install +uname -m +``` + +### 3. Validate core user flows + +Verify at least home, search, product, cart, and checkout. + +Use a product URL that exists in your seeded catalog (for example, `/apple-macbook-pro`). In this path's validation runs, `/checkout` returned `302` when no authenticated checkout session existed. + +## Cobalt generation note + +This path was validated on Cobalt 100. Thread pool spin behavior and tuning results can vary between processor generations, so re-validate tuning settings if you deploy on a different generation (e.g., Cobalt 200). + +## Arm MCP accelerator (optional) + +Use MCP tools for faster deployment checks and architecture inspection, but keep manual functional checks as the release gate. + +## Production checklist + +- Persistence across restart +- Service dependency connectivity +- Functional flow completeness +- Architecture-specific configuration correctness diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/8-performance-tuning.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/8-performance-tuning.md new file mode 100644 index 0000000000..e9e2efa20a --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/8-performance-tuning.md @@ -0,0 +1,75 @@ +--- +title: Performance tuning +weight: 8 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +Tune with architecture-specific reasoning, not one shared profile. + +## Manual workflow (required) + +Start from endpoint throughput and latency, not from a single `/install` metric. + +Test candidate settings one at a time to isolate their effects: + +| Variable | What it does | +|----------|-------------| +| `DOTNET_ThreadPool_ForceMinWorkerThreads=2` | Pre-allocates worker threads to avoid cold-start ramp-up latency. | +| `COMPlus_ThreadPool_UnfairSemaphoreSpinLimit=0` | Disables thread pool semaphore spin-waits. On Arm, spinning can be less efficient than on x86 due to weaker memory ordering. | +| `DOTNET_gcServer=1` | Uses one GC heap per logical core instead of a shared heap. Reduces GC pause contention under parallel workloads. | + +Validated sweep results on Arm (same test harness, `wrk -t2 -c32 -d10s`): + +| Arm profile | `/apple-macbook-pro` req/s | `/electronics` req/s | `/search?q=book` req/s | +|---|---:|---:|---:| +| Default | 18.72 | 32.82 | 19.90 | +| `ForceMinWorkerThreads=2` | 22.10 | 33.26 | 20.21 | +| `UnfairSemaphoreSpinLimit=0` | 16.16 | 29.73 | 18.40 | + +`DOTNET_gcServer=1` was also tested but showed no measurable difference for this workload on a 2-vCPU VM (server GC has more impact at higher core counts). + +For this workload on Cobalt 100, `DOTNET_ThreadPool_ForceMinWorkerThreads=2` was the most consistent improvement. Disabling spin-waits regressed all three endpoints. + +## Why spin-wait can differ on Arm + +x86 uses stronger memory ordering, so visibility between cores is often cheaper for spin-heavy synchronization paths. Arm uses a weaker memory model, so those paths can require additional ordering and visibility work when threads move across cores. That makes spin behavior more workload-dependent, so validate with endpoint measurements instead of assuming one fixed spin setting. + +## Cobalt generation note + +Thread pool spin behavior can vary between Arm processor generations. Re-validate tuning results on the exact generation you deploy to production. + +## Architecture-conditional deployment examples + +Docker Compose: + +```yaml +services: + nop: + image: nop-web:publish-test + environment: + DOTNET_ThreadPool_ForceMinWorkerThreads: "2" + deploy: + placement: + constraints: + - node.labels.arch == arm +``` + +Kubernetes: + +```yaml +spec: + nodeSelector: + kubernetes.io/arch: arm64 + containers: + - name: nop + image: nop-web:publish-test + env: + - name: DOTNET_ThreadPool_ForceMinWorkerThreads + value: "2" +``` + +## Arm MCP accelerator (optional) + +Use MCP to automate repeated benchmark loops and summarize deltas, but keep manual before/after evidence for release decisions. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md new file mode 100644 index 0000000000..7ea92a5505 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md @@ -0,0 +1,83 @@ +--- +title: Migrate nopCommerce to Azure Cobalt 100 (Arm) + +minutes_to_complete: 75 + +who_is_this_for: This learning path is for .NET and platform engineers migrating a production .NET application from x86 to Arm on Azure. + +learning_objectives: + - Build a pinned nopCommerce release on both x86 and Arm with the same .NET toolchain + - Use a manual-first dependency workflow, then accelerate it with the Arm MCP server + - Produce an SBOM and resolve platform-specific dependency cascades + - Containerize with Dockerfile and .NET SDK multi-arch publish workflows + - Apply architecture-conditional runtime tuning with measured results + +prerequisites: + - Azure account with permissions to create VMs + - Azure CLI (`az`) installed locally + - Docker and .NET 9 SDK familiarity + - Basic Linux command-line knowledge + +author: Joe Stech + +### Tags +skilllevels: Advanced +subjects: Cloud Migration and Performance +armips: + - Neoverse +tools_software_languages: + - .NET + - C# + - Docker + - Azure CLI + - PostgreSQL +operatingsystems: + - Linux + +further_reading: + - resource: + title: nopCommerce repository + link: https://github.com/nopSolutions/nopCommerce + type: documentation + - resource: + title: .NET SDK container publish overview + link: https://learn.microsoft.com/dotnet/core/containers/overview + type: documentation + - resource: + title: .NET on Arm + link: https://learn.microsoft.com/dotnet/core/install/linux-arm64 + type: documentation + - resource: + title: SixLabors ImageSharp + link: https://docs.sixlabors.com/ + type: documentation + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- + +This path uses a consistent pattern in every module: + +1. Do it manually so the workflow is clear. +2. Use the Arm MCP server as an optional accelerator. + +All commands and results were validated on 2026-02-25 with nopCommerce `release-4.90.3` (`9beda11c42`) and .NET SDK `9.0.114`. + +## Why this matters + +- It helps teams move .NET workloads to Cobalt with fewer migration regressions. +- It closes the gap between generic modernization guidance and architecture-specific execution. +- It gives AI-assisted migration a measurable validation loop instead of guesswork. +- It encourages upstream fixes so future migrations are easier for everyone. + +## What you will do + +1. Set up comparable x86 and Arm environments on Azure. +2. Build and baseline with a workload that goes beyond `/install`. +3. Audit direct and transitive dependencies with an SBOM-first approach. +4. Apply a reusable 4-option decision framework to platform-specific dependencies. +5. Containerize with Dockerfile and .NET SDK multi-arch techniques. +6. Deploy on Cobalt, validate functionality, and tune with architecture-aware settings. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_next-steps.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_next-steps.md new file mode 100644 index 0000000000..f3b420825e --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_next-steps.md @@ -0,0 +1,14 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # The weight controls the order of the pages. _index.md always has weight 1. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- + +1. Expand baseline tests into sustained storefront scenarios with SLO-aligned pass/fail criteria. +2. Complete ImageSharp migration validation and document any image output deltas. +3. Finalize .NET SDK multi-arch publish properties and validate produced manifests. +4. Upstream portability fixes to nopCommerce or dependent libraries with Arm/x86 evidence. +5. Re-test tuning on your target Cobalt generation before production rollout. From 59501640bd1d3148ead9e9ee1dbaf5f72b819301 Mon Sep 17 00:00:00 2001 From: Joe <4088382+JoeStech@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:16:39 -0700 Subject: [PATCH 2/9] edit for clarity, remove unneeded sections --- .../2-environment-setup.md | 42 +++----- .../3-checkout-build-baseline.md | 97 ++++++++++--------- .../4-dependency-mapping-manifests.md | 26 +++-- .../5-platform-code-deps.md | 81 ---------------- .../6-containerization.md | 73 -------------- .../7-deploy-verify-cobalt.md | 46 --------- .../8-performance-tuning.md | 75 -------------- .../dotnet-migration-nopcommerce/_index.md | 23 +++-- 8 files changed, 87 insertions(+), 376 deletions(-) delete mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-platform-code-deps.md delete mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-containerization.md delete mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-deploy-verify-cobalt.md delete mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/8-performance-tuning.md diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md index 8940ec71d0..894b5ce0a6 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md @@ -6,25 +6,17 @@ weight: 2 layout: learningpathall --- -Create two VMs in the same Azure region with matching OS and tooling so differences are attributable to architecture, not configuration drift. +# Environment setup -This path was validated with Ubuntu 24.04 LTS VMs in `westus2`: +Set up an Arm Cobalt environment first, then keep your toolchain and runtime configuration stable across all test runs. This creates a reliable Arm baseline for migration and tuning work. -| | x86 | Arm | -|---|---|---| -| **VM size** | Standard_D2s_v6 | Standard_D2ps_v6 | -| **vCPUs** | 2 | 2 | +Start with the official [Azure Cobalt setup guide](https://learn.arm.com/learning-paths/servers-and-cloud-computing/cobalt/) and complete VM provisioning there first. -## Log in and create VMs +This path was validated with Ubuntu 24.04 LTS on Azure Cobalt in `westus2` (example VM size: `Standard_D2ps_v6`, 2 vCPUs). -```bash -az login --use-device-code -az account set --subscription -``` - -Create both VMs in the same resource group and region. +## Install tools on Cobalt VM -## Install tools on each VM +Install the toolchain on the Cobalt VM before building and testing. ```bash sudo apt-get update -y @@ -38,37 +30,31 @@ sudo apt-get install -y dotnet-sdk-9.0 Verify: +Confirm architecture and tool versions before proceeding. + ```bash uname -m dotnet --version docker --version ``` -Expected output: `x86_64` on the x86 VM, `aarch64` on the Arm VM. Both should report the same .NET SDK version (9.0.x). +Expected output: `Arm` (kernel strings may still show architecture-specific values). Ensure the .NET SDK version is 9.0.x. ## PostgreSQL prerequisite for nopCommerce install -nopCommerce migrations on PostgreSQL require the `citext` extension. Create it before running the installer: +nopCommerce defaults to SQL Server, but this learning path uses PostgreSQL for Arm validation. For PostgreSQL installs, `citext` is required before migration/installation. Without it, installer migrations fail with `type "citext" does not exist` (captured in local test artifacts). + +Create PostgreSQL and enable `citext` before running the installer: ```bash +# Start PostgreSQL for local validation. docker run -d --name nop-postgres \ -e POSTGRES_USER=nop \ -e POSTGRES_PASSWORD= \ -e POSTGRES_DB=nopcommerce \ -p 5432:5432 postgres:16 +# Enable the extension required by nopCommerce migrations. docker exec nop-postgres psql -U nop -d nopcommerce \ -c "CREATE EXTENSION IF NOT EXISTS citext;" ``` - -## Optional: script-driven remote execution - -```bash -az vm run-command invoke \ - -g \ - -n \ - --command-id RunShellScript \ - --scripts @./your-test-script.sh -``` - -This was used to run identical commands on both VMs. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/3-checkout-build-baseline.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/3-checkout-build-baseline.md index 4c7394a18f..fc0edcc2b0 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/3-checkout-build-baseline.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/3-checkout-build-baseline.md @@ -6,10 +6,14 @@ weight: 3 layout: learningpathall --- -Pin nopCommerce to a reproducible release, build on both architectures, and capture a baseline that includes core storefront paths. +# Build and baseline + +Create a reproducible Arm baseline before optimization work. This page pins source, verifies a clean build, and captures a representative endpoint baseline so later changes can be measured against a known control. ## Clone and pin the source +Pinning the exact tag and commit avoids silent drift in dependencies and behavior. + ```bash git clone https://github.com/nopSolutions/nopCommerce.git cd nopCommerce @@ -18,74 +22,73 @@ git checkout release-4.90.3 git rev-parse --short=10 HEAD # expect 9beda11c42 ``` -## Restore and build +## Restore and build on Arm + +Run on the Arm VM to establish a native Arm baseline. ```bash +# Restore dependencies first so build failures are easier to triage. dotnet restore src/Presentation/Nop.Web/Nop.Web.csproj + +# Build release binaries without re-restoring packages. dotnet build src/Presentation/Nop.Web/Nop.Web.csproj -c Release --no-restore ``` -## Start the application +## Start and install nopCommerce + +Start the app locally and complete installer setup with PostgreSQL. ```bash cd src/Presentation/Nop.Web dotnet run -c Release --no-build --urls http://0.0.0.0:5000 ``` -## Baseline methodology - -Do not rely on `/install` alone. Baseline at least: - -- Home page -- Search -- Product page -- Cart -- Checkout - -Prerequisite: complete nopCommerce setup first (database configured and installer finished). Otherwise, storefront paths redirect to the install flow and do not represent real behavior. - -Use a valid product URL from your seeded catalog (for example, `/apple-macbook-pro`), not a hard-coded slug that may not exist in your dataset. - -Quick smoke probe (pre-install, useful as a connectivity check only): +Complete installation with PostgreSQL (`citext` enabled), then verify: ```bash -for p in / \ - '/search?q=book' \ - '/apple-macbook-pro' \ - '/cart' \ - '/checkout'; do - echo "== $p ==" - for i in $(seq 1 20); do - curl -sS -o /dev/null -w '%{time_total}\n' "http://127.0.0.1:5000$p" - done -done +# Root should return storefront content. +curl -s -o /dev/null -w 'root=%{http_code}\n' http://127.0.0.1:5000/ + +# Install route should redirect once installation is complete. +curl -s -o /dev/null -w 'install=%{http_code}\n' http://127.0.0.1:5000/install ``` -Before installation is complete, all storefront paths redirect to setup (`302`). Those numbers confirm reachability but are not valid performance baselines. +Expected after successful install: `root=200`, `install=302`. -## Installed-store endpoint baseline (validated) +## Baseline methodology -After fixing PostgreSQL setup (`citext`) and completing installation on both machines, the same endpoint benchmark was executed with `wrk` (`-t2 -c32 -d20s`) across this set: +Do not benchmark `/install`. Baseline real storefront paths: - `/` -- `/search?q=book` -- `/electronics` -- `/apple-macbook-pro` -- `/cart` -- `/checkout` - -Storefront pages returned `200`; `/checkout` returned `302` (expected without an authenticated session). +- `/search/` +- `/catalog/searchtermautocomplete` +- `/product/search` +- `/category/products/` +- `/addproducttocart/catalog/...` +- `/addproducttocart/details/...` +- `/shoppingcart/productdetails_attributechange/...` +- `/product/combinations` +- `/cart/estimateshipping` +- `/cart/selectshippingoption` + +Use the endpoint tester: -Requests/second comparison: +```bash +# Use fixed concurrency and iterations for a repeatable starting point. +python3 test_nopcommerce_endpoints.py \ + --base-url http://127.0.0.1:5000 \ + --concurrency 8 \ + --iterations 20 \ + --json-out arm_before.json +``` -| Endpoint | x86 | Arm | Arm vs x86 | -|---|---:|---:|---:| -| `/apple-macbook-pro` | 35.40 | 32.28 | -8.8% | -| `/electronics` | 99.52 | 76.07 | -23.6% | -| `/search?q=book` | 28.18 | 27.40 | -2.8% | +## Baseline quality rules -Different endpoints show different gaps. Avoid reducing migration performance to a single number — use per-endpoint baselines to guide architecture-specific optimizations. +Use these rules before you compare any optimization result: -## Arm MCP accelerator (optional) +- Run at least 3 baseline trials with identical parameters. +- Keep endpoint order fixed per run (not randomized) when comparing before vs after. +- Keep database state and seeded data identical across runs. +- Capture raw JSON for every run and compare medians, not single outliers. -Use MCP to script repeated baseline runs and summarize deltas, but keep the manual baseline commands as your ground truth. +This baseline process becomes the control for every later tuning or code-change decision. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/4-dependency-mapping-manifests.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/4-dependency-mapping-manifests.md index a8f778aea2..5cc106b3ee 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/4-dependency-mapping-manifests.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/4-dependency-mapping-manifests.md @@ -6,23 +6,29 @@ weight: 4 layout: learningpathall --- -Use a manual-first audit, then use the Arm MCP server to accelerate checks. +# Audit dependencies -## Manual workflow (required) +Run dependency discovery before migration changes so you understand direct references, transitive risk, and hidden native payloads. This avoids late surprises when deploying to Arm. ### 1. Map direct references +Start with project-level references to see what your app explicitly depends on. + ```bash rg -n "//..nupkg /tmp/nupkg-audit/ @@ -50,14 +58,4 @@ Treat dependencies as a chain, not isolated items: - Library A depends on library B - Library B is architecture-sensitive -You must resolve B first, then validate A, then validate the app. In nopCommerce, `iTextSharp` and `System.Drawing` are an example of this chain. - -## Arm MCP accelerator (optional) - -After manual evidence is collected, use MCP tools to speed repetitive checks: - -- `knowledge_base_search` for package compatibility -- `skopeo` or `check_image` for container image architecture coverage -- `migrate_ease_scan` where scanner support exists - -Keep manual outputs as the source of truth. Use MCP to prioritize and scale, not to skip evidence. +You must resolve B first, then validate A, then validate the app. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-platform-code-deps.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-platform-code-deps.md deleted file mode 100644 index 568cc7ab13..0000000000 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-platform-code-deps.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Replace platform-specific code -weight: 5 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -Use one decision framework for every architecture-sensitive dependency. - -## Manual workflow (required) - -### 1. Find references - -```bash -rg -n "System\.Drawing|ColorTranslator" src -``` - -### 2. Decide using the 4-option framework - -1. Keep it and test it deeply. -2. Replace it with a managed cross-platform alternative. -3. Replace it with a native equivalent that has feature parity. -4. Remove the feature if business value is low. - -The workflow is the same for every dependency. - -### 3. Apply to nopCommerce - -- `System.Drawing` image paths: use option 2 with ImageSharp. -- `iTextSharp` PDF color usage: start with option 1 (keep and test), then evaluate option 2 or 3 if you need full decoupling. - -```bash -dotnet add src/Libraries/Nop.Services/Nop.Services.csproj package SixLabors.ImageSharp -``` - -## Baseline: exercise `System.Drawing.Common` first - -Before replacing code, run a direct image-operation baseline that uses `System.Drawing.Common` and `ImageSharp` on both x86 and Arm. - -Validated Azure results (150 resize+encode operations each) showed: - -- `System.Drawing.Common` lower average latency in this microbenchmark. -- `ImageSharp` higher average latency in this microbenchmark. - -| Architecture | System.Drawing.Common avg (ms) | ImageSharp avg (ms) | -|---|---:|---:| -| x86 | 15.528 | 32.329 | -| Arm | 15.941 | 70.512 | - -This does not invalidate the migration recommendation. Performance is only one dimension. - -## Why ImageSharp still improves the migration - -A second validated test removed `libgdiplus` on each VM: - -- With `libgdiplus` installed: both libraries succeeded. -- Without `libgdiplus`: `System.Drawing.Common` failed (`TypeInitializationException`), ImageSharp still succeeded. - -`System.Drawing.Common` depends on `libgdiplus`, a native library that can break portability and behave inconsistently across platforms. ImageSharp is fully managed, so it runs identically on x86 and Arm Linux without native dependencies. For this migration, portability and predictable runtime behavior are the primary goals; then tune and benchmark ImageSharp paths as needed. - -### 4. Handle cascade dependencies - -Do not patch only the top-level project. If a transitive dependency is architecture-sensitive, fix it first, then retest consumers up the chain. - -## Arm MCP accelerator (optional) - -Use MCP to accelerate candidate discovery and compatibility research, then validate with manual tests: - -- `knowledge_base_search` for replacement candidates -- `migrate_ease_scan` where supported - -## Upstream contribution path - -When you fix architecture issues in shared code, upstream the change. - -- Open a PR with Arm/x86 validation evidence. -- Include benchmark and compatibility notes. -- Track maintainer feedback and update migration docs with the final accepted approach. - -This has high leverage: each upstream fix reduces work for future migrations. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-containerization.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-containerization.md deleted file mode 100644 index 74b5555d36..0000000000 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-containerization.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: Containerize the application -weight: 6 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -Use a manual-first container workflow, then add MCP checks. - -## Manual workflow (required) - -### 1. Dockerfile path - -```bash -docker build -f Dockerfile -t nop-web:dockerfile-test . -``` - -The nopCommerce Dockerfile uses `FROM --platform=$BUILDPLATFORM`, which is a BuildKit feature. The legacy Docker builder does not inject `$BUILDPLATFORM`, causing: - -``` -failed to parse platform : "" is an invalid OS component of "" -``` - -Fix by switching to buildx: - -```bash -docker buildx create --use --name nopx || true -docker buildx build --platform linux/amd64,linux/arm64 -t nop-web:multiarch --push . -``` - -If you cannot use buildx, create a simplified Dockerfile that removes the `--platform=$BUILDPLATFORM` argument and builds for the host architecture only. - -### 2. .NET SDK publish path (single architecture) - -```bash -dotnet publish src/Presentation/Nop.Web/Nop.Web.csproj \ - -c Release \ - /t:PublishContainer \ - -p:ContainerImageTag=publish-test -``` - -Validated outputs in this project: - -- x86 image: `591 MB` -- Arm image: `623 MB` - -### 3. .NET SDK multi-arch path - -Define multi-arch runtime identifiers in the project file, then publish once. - -```xml - - linux-x64;linux-arm64 - -``` - -```bash -dotnet publish src/Presentation/Nop.Web/Nop.Web.csproj -c Release /t:PublishContainer -``` - -This is the scalable path for one image definition across both architectures. - -## Arm MCP accelerator (optional) - -Use MCP image tools after manual build succeeds: - -- `skopeo` to inspect manifest architecture coverage -- `check_image` for a quick architecture report - -## Recommendation - -Use SDK publish as the default migration path. Keep Dockerfile for advanced layer control or custom build logic. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-deploy-verify-cobalt.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-deploy-verify-cobalt.md deleted file mode 100644 index a969eb74d8..0000000000 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-deploy-verify-cobalt.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Deploy and verify on Cobalt -weight: 7 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -Deploy on Arm Cobalt, verify correctness, and record architecture-specific behavior. - -## Manual workflow (required) - -### 1. Run the container - -```bash -docker rm -f nop-web || true -docker run -d --name nop-web -p 5000:5000 nop-web:publish-test -``` - -### 2. Verify health and architecture - -```bash -curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5000/install -uname -m -``` - -### 3. Validate core user flows - -Verify at least home, search, product, cart, and checkout. - -Use a product URL that exists in your seeded catalog (for example, `/apple-macbook-pro`). In this path's validation runs, `/checkout` returned `302` when no authenticated checkout session existed. - -## Cobalt generation note - -This path was validated on Cobalt 100. Thread pool spin behavior and tuning results can vary between processor generations, so re-validate tuning settings if you deploy on a different generation (e.g., Cobalt 200). - -## Arm MCP accelerator (optional) - -Use MCP tools for faster deployment checks and architecture inspection, but keep manual functional checks as the release gate. - -## Production checklist - -- Persistence across restart -- Service dependency connectivity -- Functional flow completeness -- Architecture-specific configuration correctness diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/8-performance-tuning.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/8-performance-tuning.md deleted file mode 100644 index e9e2efa20a..0000000000 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/8-performance-tuning.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: Performance tuning -weight: 8 - -### FIXED, DO NOT MODIFY -layout: learningpathall ---- - -Tune with architecture-specific reasoning, not one shared profile. - -## Manual workflow (required) - -Start from endpoint throughput and latency, not from a single `/install` metric. - -Test candidate settings one at a time to isolate their effects: - -| Variable | What it does | -|----------|-------------| -| `DOTNET_ThreadPool_ForceMinWorkerThreads=2` | Pre-allocates worker threads to avoid cold-start ramp-up latency. | -| `COMPlus_ThreadPool_UnfairSemaphoreSpinLimit=0` | Disables thread pool semaphore spin-waits. On Arm, spinning can be less efficient than on x86 due to weaker memory ordering. | -| `DOTNET_gcServer=1` | Uses one GC heap per logical core instead of a shared heap. Reduces GC pause contention under parallel workloads. | - -Validated sweep results on Arm (same test harness, `wrk -t2 -c32 -d10s`): - -| Arm profile | `/apple-macbook-pro` req/s | `/electronics` req/s | `/search?q=book` req/s | -|---|---:|---:|---:| -| Default | 18.72 | 32.82 | 19.90 | -| `ForceMinWorkerThreads=2` | 22.10 | 33.26 | 20.21 | -| `UnfairSemaphoreSpinLimit=0` | 16.16 | 29.73 | 18.40 | - -`DOTNET_gcServer=1` was also tested but showed no measurable difference for this workload on a 2-vCPU VM (server GC has more impact at higher core counts). - -For this workload on Cobalt 100, `DOTNET_ThreadPool_ForceMinWorkerThreads=2` was the most consistent improvement. Disabling spin-waits regressed all three endpoints. - -## Why spin-wait can differ on Arm - -x86 uses stronger memory ordering, so visibility between cores is often cheaper for spin-heavy synchronization paths. Arm uses a weaker memory model, so those paths can require additional ordering and visibility work when threads move across cores. That makes spin behavior more workload-dependent, so validate with endpoint measurements instead of assuming one fixed spin setting. - -## Cobalt generation note - -Thread pool spin behavior can vary between Arm processor generations. Re-validate tuning results on the exact generation you deploy to production. - -## Architecture-conditional deployment examples - -Docker Compose: - -```yaml -services: - nop: - image: nop-web:publish-test - environment: - DOTNET_ThreadPool_ForceMinWorkerThreads: "2" - deploy: - placement: - constraints: - - node.labels.arch == arm -``` - -Kubernetes: - -```yaml -spec: - nodeSelector: - kubernetes.io/arch: arm64 - containers: - - name: nop - image: nop-web:publish-test - env: - - name: DOTNET_ThreadPool_ForceMinWorkerThreads - value: "2" -``` - -## Arm MCP accelerator (optional) - -Use MCP to automate repeated benchmark loops and summarize deltas, but keep manual before/after evidence for release decisions. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md index 7ea92a5505..b448cfb781 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md @@ -3,14 +3,14 @@ title: Migrate nopCommerce to Azure Cobalt 100 (Arm) minutes_to_complete: 75 -who_is_this_for: This learning path is for .NET and platform engineers migrating a production .NET application from x86 to Arm on Azure. +who_is_this_for: This learning path is for .NET and platform engineers migrating a production .NET application to Arm on Azure. learning_objectives: - - Build a pinned nopCommerce release on both x86 and Arm with the same .NET toolchain - - Use a manual-first dependency workflow, then accelerate it with the Arm MCP server + - Build a pinned nopCommerce release and baseline on Arm with the same .NET toolchain - Produce an SBOM and resolve platform-specific dependency cascades - Containerize with Dockerfile and .NET SDK multi-arch publish workflows - Apply architecture-conditional runtime tuning with measured results + - Use the Arm MCP Server to automate endpoint selection, test generation, and optimization planning prerequisites: - Azure account with permissions to create VMs @@ -59,12 +59,11 @@ layout: "learningpathall" # All files under learning paths have this same learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. --- -This path uses a consistent pattern in every module: +# Migrate nopCommerce to Azure Cobalt 100 (Arm) -1. Do it manually so the workflow is clear. -2. Use the Arm MCP server as an optional accelerator. +This learning path shows a practical, evidence-first workflow for migrating nopCommerce to Arm on Azure. You will establish a clean baseline, audit dependency risk, apply targeted changes, and validate outcomes with repeatable tests before making performance claims. -All commands and results were validated on 2026-02-25 with nopCommerce `release-4.90.3` (`9beda11c42`) and .NET SDK `9.0.114`. +All commands in this path were validated with nopCommerce `release-4.90.3` (`9beda11c42`) and .NET SDK `9.0.114`. ## Why this matters @@ -75,9 +74,9 @@ All commands and results were validated on 2026-02-25 with nopCommerce `release- ## What you will do -1. Set up comparable x86 and Arm environments on Azure. -2. Build and baseline with a workload that goes beyond `/install`. +1. Set up an Arm Cobalt environment on Azure. +2. Build and baseline on Arm with a workload that goes beyond `/install`. 3. Audit direct and transitive dependencies with an SBOM-first approach. -4. Apply a reusable 4-option decision framework to platform-specific dependencies. -5. Containerize with Dockerfile and .NET SDK multi-arch techniques. -6. Deploy on Cobalt, validate functionality, and tune with architecture-aware settings. +4. Containerize with Dockerfile and .NET SDK multi-arch techniques. +5. Tune and validate runtime settings with reproducible methodology on Azure Cobalt. +6. Use the Arm MCP Server to accelerate endpoint selection, test generation, and optimization planning. From bde19be2f044cbb9a0179769c729b558d9ce7404 Mon Sep 17 00:00:00 2001 From: Joe <4088382+JoeStech@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:23:23 -0700 Subject: [PATCH 3/9] pages for initial review --- .../dotnet-migration-nopcommerce/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md index b448cfb781..c654ef4544 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md @@ -1,5 +1,5 @@ --- -title: Migrate nopCommerce to Azure Cobalt 100 (Arm) +title: Migrate a .NET application (nopCommerce) to Azure Cobalt 100 minutes_to_complete: 75 From 3503e139e805fadbd7c99aa1d2bb7ce2f317ec05 Mon Sep 17 00:00:00 2001 From: Joe <4088382+JoeStech@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:28:03 -0700 Subject: [PATCH 4/9] add final pages --- .../5-containerization.md | 55 +++++++++++++ .../6-deploy-verify-cobalt.md | 54 +++++++++++++ .../7-arm-mcp-server.md | 78 +++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-containerization.md create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-deploy-verify-cobalt.md create mode 100644 content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-arm-mcp-server.md diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-containerization.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-containerization.md new file mode 100644 index 0000000000..d7a91a554f --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-containerization.md @@ -0,0 +1,55 @@ +--- +title: Containerize the application +weight: 5 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +# Containerize the application + +Containerization should preserve reproducibility across architectures. This page shows practical paths for Dockerfile-based builds and .NET SDK container publish, with guardrails for multi-architecture delivery. + +### 1. Dockerfile path + +Run a Dockerfile build with buildx: + +```bash +# Create or reuse a buildx builder that supports multi-architecture builds. +docker buildx create --use --name nopx + +# Build and publish a manifest list that includes multiple architectures. +docker buildx build --platform linux/amd64,linux/arm64 -t [your repo:tag name] --push . +``` + +### 2. .NET SDK publish path (single architecture) + +Use SDK publish when you want tighter integration with .NET build settings and fewer custom Docker steps. + +```bash +dotnet publish src/Presentation/Nop.Web/Nop.Web.csproj \ + -c Release \ + /t:PublishContainer \ + -p:ContainerImageTag=publish-test +``` + +### 3. .NET SDK multi-arch path + +Define multi-arch runtime identifiers in the project file, then publish once. + +```xml + + linux-x64;linux-arm64 + +``` + +```bash +# Publish all configured runtime identifiers from one project definition. +dotnet publish src/Presentation/Nop.Web/Nop.Web.csproj -c Release /t:PublishContainer +``` + +This is the scalable path for one image definition across both architectures. + +## Recommendation + +Use SDK publish as the default migration path. Use a Dockerfile workflow for advanced layer control or custom build logic. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-deploy-verify-cobalt.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-deploy-verify-cobalt.md new file mode 100644 index 0000000000..850266f7fb --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-deploy-verify-cobalt.md @@ -0,0 +1,54 @@ +--- +title: Performance tuning on Cobalt +weight: 6 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +# Performance tuning on Cobalt + +Treat tuning as an experiment pipeline, not a one-shot tweak. On Arm, apply changes in a controlled sequence, measure with fixed workloads, and keep only optimizations that repeatedly improve your target metrics. + +## Tuned run configuration + +Start with a conservative runtime profile that is commonly effective for server workloads. + +```bash +export DOTNET_TieredPGO=1 +export DOTNET_ReadyToRun=1 +export DOTNET_gcServer=1 +export DOTNET_gcConcurrent=1 +export DOTNET_EnableDiagnostics=0 +export DOTNET_ThreadPool_ForceMinWorkerThreads=2 +``` + +Run the same endpoint suite used in the baseline: + +```bash +# Keep benchmark parameters identical to baseline for valid comparison. +python3 test_nopcommerce_endpoints.py \ + --base-url http://127.0.0.1:5000 \ + --concurrency 8 \ + --iterations 20 \ + --json-out arm_after.json +``` + +## Validation rules for credible tuning gains + +Use these rules before adopting a tuning profile: + +- Run at least 5 baseline and 5 tuned trials. +- Keep endpoint sequence fixed across all runs. +- Reset warm-up policy consistently (always warm or always cold). +- Require improvement in both throughput and p95 latency, not just one. +- Set a minimum practical threshold (for example, >=5% median gain) before rollout. + +## Interpretation + +- If only one metric improves while p95 or error rate regresses, do not treat the change as a win. +- If run-to-run variation is near the observed delta, treat it as noise. +- Keep architecture-specific profiles separate; do not force one profile onto all architectures. + + +Use this page as a tuning workflow template, then validate with your production-like traffic profile before rollout. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-arm-mcp-server.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-arm-mcp-server.md new file mode 100644 index 0000000000..966b51b199 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-arm-mcp-server.md @@ -0,0 +1,78 @@ +--- +title: Optimizing with the Arm MCP Server +weight: 7 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +# Optimizing with the Arm MCP Server + +Use the Arm MCP Server after the manual baseline is complete. The agent should accelerate analysis and execution, but your benchmark artifacts remain the source of truth for what actually improved. + +If you are new to this toolchain, start with the [Arm MCP Server learning path](https://learn.arm.com/learning-paths/servers-and-cloud-computing/arm-mcp-server/) for setup and core usage patterns. + +## 1. Identify endpoints likely to benefit most + +Ask the agent to analyze your application routes and classify optimization candidates by CPU intensity, serialization cost, synchronization, and cache behavior. + +Prompt template: + +```text +Analyze this .NET application for Arm optimization opportunities. +Identify endpoints most likely to benefit from Arm tuning. +Rank them by expected impact and explain why. +``` + +Expected output: + +- Ranked endpoint list +- Bottleneck hypotheses per endpoint +- Instrumentation plan to validate hypotheses + +## 2. Generate and run a targeted endpoint test suite + +Ask the agent to build a repeatable suite that exercises the ranked endpoints and emits machine-readable output. + +Prompt template: + +```text +Create a reproducible endpoint benchmark suite for these routes. +Use concurrency, iterations, and JSON output. +Include pass/fail checks for HTTP behavior and error rate. +``` + +The suite should produce: + +- Per-endpoint latency percentiles +- Throughput summary +- Error counts +- Before/after comparison artifacts + +## 3. Plan and implement Arm optimizations on Azure Cobalt + +Ask the agent to create an execution plan, apply changes, run tests, and report deltas. + +Prompt template: + +```text +On this Ubuntu Neoverse (Azure Cobalt) instance: +1) establish baseline, +2) apply Arm-focused .NET runtime and deployment optimizations, +3) rerun tests, +4) report statistically meaningful deltas and risks. +``` + +Typical optimization actions include: + +- Runtime settings (`DOTNET_TieredPGO`, `DOTNET_ReadyToRun`, thread pool tuning) +- Container/runtime configuration cleanup +- Architecture-conditional deployment settings +- Repeated measurement loops with fixed workload parameters and fixed endpoint order + +## Practical guardrails + +- Keep manual baseline artifacts as source of truth. +- Require raw output files for every claim. +- Ask the agent to separate observed facts from inferred explanations. +- Re-run on production-like traffic before release. From 02c6bc936d76d52380536d6dfdd6149d58912757 Mon Sep 17 00:00:00 2001 From: Joe <4088382+JoeStech@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:28:58 -0700 Subject: [PATCH 5/9] remove todos --- .../dotnet-migration-nopcommerce/_next-steps.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_next-steps.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_next-steps.md index f3b420825e..e0f4700ee5 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_next-steps.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_next-steps.md @@ -5,10 +5,4 @@ weight: 21 # The weight controls the order of the pages. _index.md always has weight 1. title: "Next Steps" # Always the same, html page title. layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. ---- - -1. Expand baseline tests into sustained storefront scenarios with SLO-aligned pass/fail criteria. -2. Complete ImageSharp migration validation and document any image output deltas. -3. Finalize .NET SDK multi-arch publish properties and validate produced manifests. -4. Upstream portability fixes to nopCommerce or dependent libraries with Arm/x86 evidence. -5. Re-test tuning on your target Cobalt generation before production rollout. +--- \ No newline at end of file From 545ae4b7e5485902b2bf518f80914f9eff78661c Mon Sep 17 00:00:00 2001 From: Joe <4088382+JoeStech@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:30:04 -0700 Subject: [PATCH 6/9] clean index --- .../dotnet-migration-nopcommerce/_index.md | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md index c654ef4544..17865962bd 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md @@ -57,26 +57,4 @@ further_reading: weight: 1 # _index.md always has weight of 1 to order correctly layout: "learningpathall" # All files under learning paths have this same wrapper learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. ---- - -# Migrate nopCommerce to Azure Cobalt 100 (Arm) - -This learning path shows a practical, evidence-first workflow for migrating nopCommerce to Arm on Azure. You will establish a clean baseline, audit dependency risk, apply targeted changes, and validate outcomes with repeatable tests before making performance claims. - -All commands in this path were validated with nopCommerce `release-4.90.3` (`9beda11c42`) and .NET SDK `9.0.114`. - -## Why this matters - -- It helps teams move .NET workloads to Cobalt with fewer migration regressions. -- It closes the gap between generic modernization guidance and architecture-specific execution. -- It gives AI-assisted migration a measurable validation loop instead of guesswork. -- It encourages upstream fixes so future migrations are easier for everyone. - -## What you will do - -1. Set up an Arm Cobalt environment on Azure. -2. Build and baseline on Arm with a workload that goes beyond `/install`. -3. Audit direct and transitive dependencies with an SBOM-first approach. -4. Containerize with Dockerfile and .NET SDK multi-arch techniques. -5. Tune and validate runtime settings with reproducible methodology on Azure Cobalt. -6. Use the Arm MCP Server to accelerate endpoint selection, test generation, and optimization planning. +--- \ No newline at end of file From 545b7de781f115f92f49f206f04f6b071954004d Mon Sep 17 00:00:00 2001 From: Joe <4088382+JoeStech@users.noreply.github.com> Date: Sat, 4 Jul 2026 17:45:37 -0600 Subject: [PATCH 7/9] address comments from Richard Murillo --- .../2-environment-setup.md | 2 ++ .../5-containerization.md | 13 ++++++++- .../6-deploy-verify-cobalt.md | 27 ++++++++++++++++++- .../7-arm-mcp-server.md | 2 +- .../dotnet-migration-nopcommerce/_index.md | 6 ++++- 5 files changed, 46 insertions(+), 4 deletions(-) diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md index 894b5ce0a6..2177a7daf3 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md @@ -40,6 +40,8 @@ docker --version Expected output: `Arm` (kernel strings may still show architecture-specific values). Ensure the .NET SDK version is 9.0.x. +If you are upgrading from an older .NET version before migrating to Cobalt, you can use [GitHub Copilot modernization for .NET](https://learn.microsoft.com/dotnet/core/porting/github-copilot-app-modernization/overview) to assess the project and guide the upgrade. In Visual Studio Code, open Copilot Chat and use `@modernize-dotnet`; in Visual Studio, use the Modernize action from Solution Explorer. + ## PostgreSQL prerequisite for nopCommerce install nopCommerce defaults to SQL Server, but this learning path uses PostgreSQL for Arm validation. For PostgreSQL installs, `citext` is required before migration/installation. Without it, installer migrations fail with `type "citext" does not exist` (captured in local test artifacts). diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-containerization.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-containerization.md index d7a91a554f..64a742071b 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-containerization.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-containerization.md @@ -35,10 +35,11 @@ dotnet publish src/Presentation/Nop.Web/Nop.Web.csproj \ ### 3. .NET SDK multi-arch path -Define multi-arch runtime identifiers in the project file, then publish once. +Define runtime identifiers and multi-arch container runtime identifiers in the project file, then publish once. ```xml + linux-x64;linux-arm64 linux-x64;linux-arm64 ``` @@ -50,6 +51,16 @@ dotnet publish src/Presentation/Nop.Web/Nop.Web.csproj -c Release /t:PublishCont This is the scalable path for one image definition across both architectures. +## SDK version guardrail + +Before choosing the SDK publish path for multi-architecture images, check the SDK version used by CI and by developer workstations: + +```bash +dotnet --version +``` + +If you cannot use a .NET SDK version that supports multi-RID container publishing, or if SDK publish does not produce the multi-architecture image index you need, use the `docker buildx build --platform linux/amd64,linux/arm64` workflow instead. Multi-RID container publishing starts with .NET SDK versions 8.0.405, 9.0.102, and 9.0.2xx. + ## Recommendation Use SDK publish as the default migration path. Use a Dockerfile workflow for advanced layer control or custom build logic. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-deploy-verify-cobalt.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-deploy-verify-cobalt.md index 850266f7fb..8d5d7d124c 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-deploy-verify-cobalt.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-deploy-verify-cobalt.md @@ -12,9 +12,10 @@ Treat tuning as an experiment pipeline, not a one-shot tweak. On Arm, apply chan ## Tuned run configuration -Start with a conservative runtime profile that is commonly effective for server workloads. +Start with a conservative runtime profile that is commonly effective for server workloads. Keep these settings outside the application code at first so you can switch profiles without rebuilding. ```bash +export DOTNET_TieredCompilation=1 export DOTNET_TieredPGO=1 export DOTNET_ReadyToRun=1 export DOTNET_gcServer=1 @@ -23,6 +24,26 @@ export DOTNET_EnableDiagnostics=0 export DOTNET_ThreadPool_ForceMinWorkerThreads=2 ``` +Increase `DOTNET_ThreadPool_ForceMinWorkerThreads` only when traces or load-test data show thread-pool starvation. + +### Optional spin-wait experiment for .NET 8, 9, and 10 + +If the workload burns CPU while waiting for short-lived thread-pool work, test disabling the thread-pool unfair semaphore spin limit: + +```bash +export DOTNET_ThreadPool_UnfairSemaphoreSpinLimit=0 +``` + +Treat this as an experiment, not a default. Turning off spin waiting can reduce wasted CPU on small instances or oversubscribed containers, but it can also increase wake-up latency and reduce peak throughput. Validate it separately from the base tuned profile. + +### Tiered PGO and ReadyToRun optimize different phases + +`DOTNET_TieredPGO=1` and `DOTNET_ReadyToRun=1` are not the same optimization: + +- `ReadyToRun` favors startup and early request latency by using precompiled code when it is available. +- `TieredPGO` favors steady-state throughput by letting the JIT recompile hot methods with runtime profile data. +- Test them together and separately if startup latency and warmed throughput both matter. + Run the same endpoint suite used in the baseline: ```bash @@ -44,6 +65,10 @@ Use these rules before adopting a tuning profile: - Require improvement in both throughput and p95 latency, not just one. - Set a minimum practical threshold (for example, >=5% median gain) before rollout. +## Instance-size tradeoffs + +Do not assume the same profile wins on every Azure Cobalt VM size. Smaller instances often benefit from lower CPU burn and predictable p95 latency. Larger instances often benefit from higher concurrency, but can expose GC, database, and shared-resource contention. Compare throughput, p95/p99 latency, CPU utilization, and error rate for each instance size before keeping a tuning profile. + ## Interpretation - If only one metric improves while p95 or error rate regresses, do not treat the change as a win. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-arm-mcp-server.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-arm-mcp-server.md index 966b51b199..96e0fd4058 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-arm-mcp-server.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/7-arm-mcp-server.md @@ -65,7 +65,7 @@ On this Ubuntu Neoverse (Azure Cobalt) instance: Typical optimization actions include: -- Runtime settings (`DOTNET_TieredPGO`, `DOTNET_ReadyToRun`, thread pool tuning) +- Runtime settings (`DOTNET_TieredPGO`, `DOTNET_ReadyToRun`, thread pool tuning, and spin-wait experiments) - Container/runtime configuration cleanup - Architecture-conditional deployment settings - Repeated measurement loops with fixed workload parameters and fixed endpoint order diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md index 17865962bd..9debfa1a0e 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md @@ -43,6 +43,10 @@ further_reading: title: .NET SDK container publish overview link: https://learn.microsoft.com/dotnet/core/containers/overview type: documentation + - resource: + title: GitHub Copilot modernization for .NET + link: https://learn.microsoft.com/dotnet/core/porting/github-copilot-app-modernization/overview + type: documentation - resource: title: .NET on Arm link: https://learn.microsoft.com/dotnet/core/install/linux-arm64 @@ -57,4 +61,4 @@ further_reading: weight: 1 # _index.md always has weight of 1 to order correctly layout: "learningpathall" # All files under learning paths have this same wrapper learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. ---- \ No newline at end of file +--- From 7418d25b57168edff1417c513875dadd45ef88cb Mon Sep 17 00:00:00 2001 From: Joe <4088382+JoeStech@users.noreply.github.com> Date: Sat, 4 Jul 2026 17:58:01 -0600 Subject: [PATCH 8/9] metadata validation change --- .../dotnet-migration-nopcommerce/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md index 9debfa1a0e..ed93e67d69 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md @@ -22,7 +22,7 @@ author: Joe Stech ### Tags skilllevels: Advanced -subjects: Cloud Migration and Performance +subjects: Performance and Architecture armips: - Neoverse tools_software_languages: From fb7d78194e7878aaf840d3744f8825d2eb590280 Mon Sep 17 00:00:00 2001 From: pareenaverma Date: Mon, 6 Jul 2026 11:03:50 -0400 Subject: [PATCH 9/9] Update _index.md --- .../dotnet-migration-nopcommerce/_index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md index ed93e67d69..4df1b7ac5c 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md @@ -1,6 +1,10 @@ --- title: Migrate a .NET application (nopCommerce) to Azure Cobalt 100 +draft: true +cascade: + draft: true + minutes_to_complete: 75 who_is_this_for: This learning path is for .NET and platform engineers migrating a production .NET application to Arm on Azure.