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..2177a7daf3 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/2-environment-setup.md @@ -0,0 +1,62 @@ +--- +title: Environment setup +weight: 2 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +# Environment setup + +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. + +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. + +This path was validated with Ubuntu 24.04 LTS on Azure Cobalt in `westus2` (example VM size: `Standard_D2ps_v6`, 2 vCPUs). + +## Install tools on Cobalt VM + +Install the toolchain on the Cobalt VM before building and testing. + +```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: + +Confirm architecture and tool versions before proceeding. + +```bash +uname -m +dotnet --version +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). + +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;" +``` 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..fc0edcc2b0 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/3-checkout-build-baseline.md @@ -0,0 +1,94 @@ +--- +title: Build and baseline +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +# 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 +git fetch --tags --prune +git checkout release-4.90.3 +git rev-parse --short=10 HEAD # expect 9beda11c42 +``` + +## 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 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 +``` + +Complete installation with PostgreSQL (`citext` enabled), then verify: + +```bash +# 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 +``` + +Expected after successful install: `root=200`, `install=302`. + +## Baseline methodology + +Do not benchmark `/install`. Baseline real storefront paths: + +- `/` +- `/search/` +- `/catalog/searchtermautocomplete` +- `/product/search` +- `/category/products/` +- `/addproducttocart/catalog/...` +- `/addproducttocart/details/...` +- `/shoppingcart/productdetails_attributechange/...` +- `/product/combinations` +- `/cart/estimateshipping` +- `/cart/selectshippingoption` + +Use the endpoint tester: + +```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 +``` + +## Baseline quality rules + +Use these rules before you compare any optimization result: + +- 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. + +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 new file mode 100644 index 0000000000..5cc106b3ee --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/4-dependency-mapping-manifests.md @@ -0,0 +1,61 @@ +--- +title: Audit dependencies +weight: 4 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +# Audit dependencies + +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/ +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. 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..64a742071b --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/5-containerization.md @@ -0,0 +1,66 @@ +--- +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 runtime identifiers and multi-arch container runtime identifiers in the project file, then publish once. + +```xml + + linux-x64;linux-arm64 + 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. + +## 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 new file mode 100644 index 0000000000..8d5d7d124c --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/6-deploy-verify-cobalt.md @@ -0,0 +1,79 @@ +--- +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. 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 +export DOTNET_gcConcurrent=1 +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 +# 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. + +## 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. +- 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..96e0fd4058 --- /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, and spin-wait experiments) +- 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. 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..4df1b7ac5c --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md @@ -0,0 +1,68 @@ +--- +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. + +learning_objectives: + - 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 + - Azure CLI (`az`) installed locally + - Docker and .NET 9 SDK familiarity + - Basic Linux command-line knowledge + +author: Joe Stech + +### Tags +skilllevels: Advanced +subjects: Performance and Architecture +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: 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 + 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. +--- 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..e0f4700ee5 --- /dev/null +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# 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. +--- \ No newline at end of file