Skip to main content

Posts

Reading a Profiler trace without guessing

Note: this is part 3 of a series on the Azure Monitor Profiler. Part 1 covered what the Profiler is, part 2 covered enabling it. This post is about the part that actually matters: making sense of a trace once you have one. Having profiler traces is only half the job. I've seen people enable the profiler, open a trace, stare at a wall of unfamiliar method names, and close the tab. The trace explorer isn't self-explanatory the first time - it took me a few real incidents before the views clicked. This post is the walkthrough I wish I'd had. Getting to a trace From your Application Insights resource: Go to the Performance tab Pick an operation from the list or leave Overall selected Click on Profiler traces   Pick one of the captured requests, ideally one with a longer duration than usual - that's where you'll actually find something. Once you're in a trace, you get two views of the same call stack data: Flame graph - the full...
Recent posts

Enabling the Application Insights Profiler

Note: this is part 2 of a series on the Azure Monitor Profiler. Part 1 covered what the Profiler is and where to find the results - this post covers actually turning it on. There are two ways to enable the profiler: through app settings on App Service, or by wiring it into your code directly. Which one you need depends on where your app runs and how much control you want over the setup. Option 1: codeless enablement on App Service If your app runs on App Service (Windows) and your Application Insights resource is in the same subscription, this is the easiest path - no code changes, no redeploy. From the portal: In your App Service instance, select Monitoring > Application Insights Select Turn on Application Insights , then Enable Scroll down to the .NET or .NET Core tab Set Collection level to Recommended Under Profiler and Code Optimizations , select On Apply , then confirm with Yes Or skip the portal entirely and set the app settings directly...

Our P95 spiked, now what?

Yesterday one of our Application Insights dashboard showed a P95 latency spike and we had no idea why. The telemetry told us that a request was slow. But it didn't tell us why . Was it a database call? A CPU-bound loop? Lock contention? A GC pause? The naive approach would have to been to add logging statements around the code you suspect, redeploy, wait for the issue to reproduce, and repeat. In production, that's slow and it doesn't scale - you're guessing, and every guess costs a deployment cycle. We stayed away from all that guess work and reached out to the Application Insights Profiler . Instead of reasoning from logs, you get actual flame graphs of real production requests, showing exactly where time was spent. What the profiler actually captures The profiler runs as an agent alongside your application and periodically captures traces of live requests - not synthetic load, actual production traffic. For each captured request, it builds a trace you can in...

.NET Aspire: The price of forgetting WithReference

Recently I lost way more time than I'd like to admit on an error that turned out to be one missing line of code. The symptom looked like a networking problem. The cause was a missing WithReference call in my Aspire AppHost. Here's the exception my proxy threw the moment it tried to forward a request to my API: System.Net.Http.HttpRequestException: No such host is known. (api:443) ---> System.Net.Sockets.SocketException (11001): No such host is known. at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken) at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken) --- End of inner exception stack trace --- at System.Net.Http.HttpConnectionPool....

The mysterious .dev.localhost checkbox

When you create a new ASP.NET Core project in Visual Studio, there's a checkbox that's easy to click past: "Use the .dev.localhost TLD in the application URL." I always want to understand what a checkbox actually does before I tick it, so let's dig into this one. The problem it solves When you're working on more than one local web project, they all end up living at the same address: localhost . Only the port number tells them apart. Open your browser's address bar with three projects running and you'll see localhost:5001 , localhost:5215 , localhost:7099 — and you have no idea which is which until you actually look at the page. There's a second, less visible issue: because everything shares the localhost name, cookies and other domain-scoped browser storage are also shared across all your local apps. That's not something you usually want when you're testing. What .dev.localhost actually is .localhost is a reserved top-level dom...

Talking to Copilot like a caveman

  I think that everyone who uses AI recognizes the following pattern; you ask an LLM a simple question and it answers like it's writing a blog post: introduction, context, three examples, a closing summary. Fine for a first read, expensive when you're chaining calls or running an agent loop all day. The trick to avoid this is called "caveman prompting". You tell the model to drop articles, pleasantries and filler, and answer in short, blunt fragments. It sounds silly. But it works up to a point. A first attempt: just say "be concise" Most people's first instinct is a one-line system prompt: Be concise. No fluff. This already gets you a good chunk of the savings. In benchmarks I've seen floating around, a plain "be concise, return structured output" instruction accounts can already give you a nice reduction. It's the cheapest fix and most people stop here, which is reasonable. The caveman approach The caveman skill takes...

Cache stampede: when our cache turned against us

While investigating some performance issues, we ran into an ASP.NET Core API that cached a fairly expensive aggregation query for 60 seconds. Under normal load, that was fine: one request rebuilds the cache, everyone else reads from it. Under peak load, dozens of requests would arrive in that same expiry window, all see a cache miss, and all fire the same expensive query in parallel. The database didn't like that. That was the moment when our caching layer stopped helping and started hurting. A burst of requests comes in at the same time, all miss the cache, and all go hammer the database or the downstream API at once. That's a cache stampede . The cache was supposed to protect our backend, and for a few hundred milliseconds it did the opposite. Why this happens IMemoryCache.GetOrCreate (and its async sibling) looks like it protects you, but it doesn't add any locking on its own. Look at the naive version: public async Task<Report> GetReportAsync(string key) ...