When SQL time is low but total request time is high in the Request Profiler, the app server's CPU is where you look next. On a private-cloud or on-premises deployment you have full access to profile the w3wp.exe process; on SaaS you are limited to what the Profiler screen and your own customization code tell you, which is still more than most people check.
Finding the hot worker process
Multiple app pools usually run on one box, so the first job is matching a spiking w3wp.exe in Task Manager or Performance Monitor to the right Acumatica site. appcmd list wp from an elevated command prompt lists worker processes with their PIDs and app pool names:
%windir%\system32\inetsrv\appcmd list wp
W3WP "3812" (applicationPool:AcumaticaProd)
W3WP "4460" (applicationPool:AcumaticaTest)
Once you have the PID, attach a profiler — dotnet-trace is the lightweight option that works without installing Visual Studio on a production box:
dotnet-trace collect --process-id 3812 --duration 00:00:60 -o acumatica_trace.nettrace
Capture during the actual slow operation, not during idle time — a 60-second trace of an idle app pool tells you nothing. Coordinate with whoever is reproducing the issue, or script the reproduction yourself against a test instance.
What actually shows up in the hot path
Across the customizations I have profiled, three patterns account for most of the CPU time that is not legitimately "the business logic doing its job":
- PXCache re-evaluation storms. A
FieldUpdatedorRowUpdatedhandler that callsCache.SetValueExton fields that trigger further handlers can cascade across hundreds of rows during a batch operation. The stack trace shows the same handler recursing through cache events rather than doing new work. - Attribute overhead on large grids. Every
PXSelector,PXFormula, andPXDefaultattribute runs its logic per row per relevant event. A grid with 500 rows and a formula attribute referencing three other fields is 500 evaluations minimum — fine in isolation, expensive when several such attributes stack on one DAC. - Reflection-heavy custom code. Hand-rolled reflection to read attribute metadata at runtime, when the framework already exposes the same data through
PXCache.GetAttributesReadonlyor similar, shows up as disproportionate CPU for what the code is nominally doing.
A customization DLL compiled in Debug configuration and deployed to production carries JIT and exception-handling overhead that Release builds do not. I have seen this alone account for a double-digit percentage CPU difference on write-heavy screens. Check the build configuration before profiling anything else — it is a five-minute check that sometimes ends the investigation immediately.
Is it your code or the platform?
Disable the customization project (or a subset of it) on a test instance and re-run the same operation. If CPU time drops proportionally, the cost is in your code; if it barely moves, you are looking at a platform-level cost — often unavoidable, but worth confirming with Acumatica support rather than chasing a fix that does not exist in your own codebase.
Wrapping up
CPU profiling on Acumatica is standard .NET profiling with one extra layer of context: know which PXCache events are firing and why, because a flat CPU graph over a business-logic-shaped stack trace is usually cache event cascades, not the operation itself. Confirm with a customization-disabled control run before assuming the platform is at fault.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.