The distrobox documentation describes the project with:
Use any Linux distribution inside your terminal. Enable both backward and forward compatibility with software and freedom to use whatever distribution you’re more comfortable with. Distrobox uses podman, docker or lilipod to create containers using the Linux distribution of your choice. The created container will be tightly integrated with the host, allowing sharing of the HOME directory of the user, external storage, external USB devices and graphical apps (X11/Wayland), and audio. https://distrobox.it/
Here is a short guide on how to create an isolated development environment for .NET 8.0 within a few minutes, including the latest JetBrains Rider 2023.3.
The development container runs with the current Debian 12 image. As host environment I use Fedora 38 or Fedora 39 Silverblue. Of course, any distribution supported by Distrobox can be used.
In a first step, we create the container for the .NET development environment with the help of distrobox create.
distrobox create -i debian -n debian --home ~/.distrobox/debian
With distrobox enter we now “jump” into the container so that all further instructions are executed in the container.
distrobox enter debian
The installation of .NET 8.0 under Debian is simple. You only need the official package repositories.
wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
The entire .NET 8.0 SDK can now be easily installed with apt.
sudo apt update && sudo apt install dotnet-sdk-8.0
The necessary steps are explained in detail in the official Microsoft documentation.
For Rider to run smoothly, we need a few more libraries and of course git.
sudo apt install git libxtst6 libglib2.0-bin
Finally, all you need to do is download and unzip Rider.
wget https://download.jetbrains.com/rider/JetBrains.Rider-2023.3.tar.gz
tar xfvz JetBrains.Rider-2023.3.tar.gz
And you’re ready to start .NET development in a completely isolated environment 🍺
cd 'JetBrains\ Rider-2023.3/bin'
./rider.sh
Under Linux, the choice is unfortunately very limited. Fortunately, Microsoft provides a whole set of tools for analyzing memory/performance problems of .NET programs with the dotnet diagnostic tools. These work under Linux as well as in Windows and with restrictions also under macOS.
The first step is to find out if there really is a problem with the managed memory (GC Heap).
This is easily done with dotnet counters. With the help of dotnet counters monitor -p <PROCESS ID> monitoring can be activated for any .NET process.
dotnet counters monitor -p 1234
Press p to pause, r to resume, q to quit.
Status: Running
[System.Runtime]
% Time in GC since last GC (%) 0
Allocation Rate (B / 1 sec) 8,168
CPU Usage (%) 0.007
Exception Count (Count / 1 sec) 0
GC Committed Bytes (MB) 321.516
GC Fragmentation (%) 0.046
GC Heap Size (MB) 1,074.347
Watch for GC Heap Size (MB). If the value is always increasing but never decreasing, then the application has a memory leak.
A simple overview of all objects managed by the garbage collector (GC) can be displayed with dotnet gcdump. To do this, first create a memory dump of the desired .NET process with dotnet gcdump collect -p <PROCESS ID>.
dotnet gcdump collect -p 1234
The managed objects can now be viewed with dotnet gcdump report <DUMP FILE>.
dotnet gcdump report 20230906_205437_23141.gcdump
1,283,668,344 GC Heap bytes
4,315 GC Heap objects
Object Bytes Count Type
1,048,600 1,224 System.Byte[] (Bytes > 1M) [System.Private.CoreLib.dll]
28,588 1 System.String (Bytes > 10K) [System.Private.CoreLib.dll]
16,408 1 MemoryLeak.Lib.LeakData[] (Bytes > 10K) [MemoryLeak.Lib.dll]
16,344 1 System.Object[] (Bytes > 10K) [System.Private.CoreLib.dll]
8,184 4 System.Object[] (Bytes > 1K) [System.Private.CoreLib.dll]
The report now shows a list of all objects including their size. The output is about the same as dumpheap -stat from the SOS debugging extension.
At the moment, more information cannot be displayed with dotnet gcdump report. For memory dumps created with dotnet gcdump, the only convenient way at the moment is under Windows with the help of Perfview or Visual Studio.
dotnet-dump is the most comprehensive tool for memory analysis of .NET applications. The tool can create memory dumps of .NET processes and provides a virtual environment for analyzing these dumps.
A memory dump of any .NET process is done with dotnet dump collect -p <PROCESS ID>
dotnet dump collect -p 1234
For the analysis of the dump a virtual environment is available afterwards which is started with dotnet dump analyse <DUMP FILE> .
dotnet dump analyze core_20230830_213024
The available SOS commands are documented here and can be displayed with the help command.
An overview of all objects can be displayed with dumpheap -stat. The output is analogous to the dotnet gcdump report <DUMP FILE> used above and shows the number of all managed objects as well as their size in memory.
dumpheap -stat
Statistics:
MT Count TotalSize Class Name
7f230ed2e098 18 984 System.String[]
7f230ed56608 7 3,292 System.Char[]
7f230ec4b9d8 14 4,760 System.Int32[]
7f230ebda508 7 36,016 System.Object[]
7f230f1aba78 3 49,224 MemoryLeak.Lib.LeakData[]
7f230ed01038 408 49,754 System.String
7f230f1aac60 2,200 52,800 MemoryLeak.Lib.LeakData
7f230f1ab2a0 2,200 52,800 MemoryLeak.Lib.Data
56490bf28700 2,280 93,408 Free
To get more information about the object of type MemoryLeak.Lib.Data, you can use dumpheap -mt 7f230f1ab2a0 to output each instance. Here in the example there are 2’200 instances of the type MemoryLeak.Lib.Data
dumpheap -mt 7f230f1ab2a0
7ee302816f70 7f230f1ab2a0 24
7ee302816ff0 7f230f1ab2a0 24
7ee302817020 7f230f1ab2a0 24
Now that we have an overview of all objects of the MemoryLeak.Lib.Data type, we can perform further analysis. For example, we can display the “content” of the object. For this we take any object from the list and execute do (dumpobject).
do 7ee302817020
Name: MemoryLeak.Lib.Data
MethodTable: 00007f230f1ab2a0
EEClass: 00007f230f17be00
Tracked Type: false
Size: 24(0x18) bytes
File: /home/stef/projects/performance/MemoryLeak/bin/Debug/net7.0/MemoryLeak.Lib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007f230f01fcd0 400000a 8 System.Byte[] 0 instance 00007ee302e000b0 <Buffer>k__BackingField
The object contains only one byte array and is defined as follows:
internal class Data
{
public byte[]? Buffer { get; set; }
}
Further we can display the used memory of this object with objsize:
objsize 7ee302817020
Objects which 7ee302817020(MemoryLeak.Lib.Data) transitively keep alive:
Address MT Size
7ee302817020 7f230f1ab2a0 24
7ee302e000b0 7f230f01fcd0 1,048,600
Statistics:
MT Count TotalSize Class Name
7f230f1ab2a0 1 24 MemoryLeak.Lib.Data
7f230f01fcd0 1 1,048,600 System.Byte[]
Total 2 objects, 1,048,624 bytes
It happens from time to time that the garbage collector does not free the memory anymore, because an object is still used somehow. Here gcroot helps. With gcroot you can see which objects reference a certain object.
gcroot 7ee302817020
HandleTable:
00007f238af413e8 (strong handle)
-> 7ee300000020 System.Object[]
-> 7ee302816f20 System.Collections.Generic.List<MemoryLeak.Lib.LeakData> (static variable: System.Random._random)
-> 7ee30201e750 MemoryLeak.Lib.LeakData[]
-> 7ee302817008 MemoryLeak.Lib.LeakData
-> 7ee302817020 MemoryLeak.Lib.Data
For the analysis of a memory dump, LLDB can also be used instead of dotnet-dump. The advantage of LLDB is that the analysis of managed and native code is possible.
LLDB is probably available in all major linux distributions in the package sources and can be installed with the help of the package manager. Here’s an example of dnf (Fedora):
sudo dnf install lldb
The memory dump is also generated with dotnet dump collect -p <PROCESS ID>. LLDB uses the SOS debugging extension to debug managed code. It may be necessary to install dotnet-sos first so that the SOS debugging extension is found by LLDB.
dotnet tool install --global dotnet-sos
dotnet sos install
The memory dump can then be loaded into the interactive shell of LLDB with lldb –core DUMPFILE
lldb --core core_20231003_100537
Current symbol store settings:
-> Cache: /home/stef/.dotnet/symbolcache
-> Server: https://msdl.microsoft.com/download/symbols/ Timeout: 4 RetryCount: 0
(lldb) target create --core "core_20231003_100537"
Core file '/home/stef/projects/performance/dumps/core_20231003_100537' (x86_64) was loaded.
(lldb)
The memory analysis now works exactly as with dotnet dump. All SOS commands of the SOS debugging extension are available, like dumpheap -stat and so on.
More detailed information for using LLDB can be found in the official documentation from Microsoft.
Of course, there are also commercial products like JetBrains Rider that are available for both Windows and Linux thanks to their platform independence. Since version 2023.2 of Rider you can collect memory dumps and analyze them directly in Rider, just like in the standalone version of dotMemory.
]]>The problem is exacerbated in productive environments, where it is usually not possible to simply install additional software.
Fortunately, Windows also offers the possibility of recording network traffic with home remedies. The tool is called Network Shell, or netsh for short, and is able to record network traffic.
The recording can be started in an elevated command prompt as follows:
netsh trace start capture=yes tracefile=C:\capture.etl maxsize=1000 filemode=circular overwrite=yes report=no
As soon as enough data has been collected, the trace can be stopped again with the following command:
netsh trace stop
In principle, such a trace can be easily analysed with the help of the Network Monitor. Unfortunately, the Network Monitor is no longer being developed. However, the generated ETL file can also be analysed with PerfView or the Windows Performance Analyzer (WPA).
In my view, it is easier to analyse network traffic with Wireshark. Unfortunately, Wireshark cannot do much with the ETL file.
The remedy is a program published by Microsoft that is able to convert an ETL file into a PCAP file. The program, called etl2pcapng.exe, is open source and can be downloaded from GitHub under the following link:
https://github.com/microsoft/etl2pcapng
The conversion is very simple:
etl2pcapng.exe in.etl out.pcapng
And already the recorded network traffic can be analysed with the help of Wireshark 😎. This article provides further information.
]]>This post is not primarily about what new features .NET 7.0 brings, but more about how to install the latest runtime from microsoft on fedora. Unfortunately, only .NET 6.0 can be installed via dnf. .NET 7.0 is still completely missing from the package sources.
The official documentation for .NET 7.0 and fedora only shows the not much meaningful information (Supported Version):
.NET 7 isn’t yet ready for Fedora. This article will be updated when it’s available.
whatever that means
This article is a kind of continuation of my last article about Use .NET 7 with Fedora 36 and Distrobox and describes the installation of .NET 7.0 on Fedora 37. The same procedure should most likely work for Fedora 36/35 as well.
First, head over to the official .NET 7.0 download page and select dotnet-install scripts or use wget to download the official dotnet-install.sh script.
wget https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh
chmod +x ./dotnet-install.sh
The installation is pretty much straightforward. Just define the specific major version with the –channel parameter to indicate a specific version. The following command installs the .NET 7.0 SDK on your computer.
./dotnet-install.sh --channel 7.0
All necessary bits and bytes are installed by default under ~/.dotnet.
ls -l ~/.dotnet
drwxr-xr-x. 1 stef stef 24 15. Nov 20:07 corefx
drwxr-xr-x. 1 stef stef 6 7. Dez 21:04 host
drwxr-xr-x. 1 stef stef 0 8. Dez 20:52 metadata
drwxr-xr-x. 1 stef stef 224 7. Dez 21:04 packs
drwxr-xr-x. 1 stef stef 28 7. Dez 21:16 sdk
drwxr-xr-x. 1 stef stef 28 7. Dez 21:07 sdk-advertising
drwxr-xr-x. 1 stef stef 52 7. Dez 21:16 sdk-manifests
drwxr-xr-x. 1 stef stef 90 7. Dez 21:04 shared
drwxr-xr-x. 1 stef stef 6300 27. Nov 19:00 symbolcache
drwxr-xr-x. 1 stef stef 3774 8. Dez 20:52 TelemetryStorageService
drwxr-xr-x. 1 stef stef 22 7. Dez 21:16 templates
drwxr-xr-x. 1 stef stef 112 27. Nov 18:55 tools
-rwxr-xr-x. 1 stef stef 175832 7. Dez 21:04 dotnet
Once the installation has been completed successfully, two environment variables must be set so that the SDK can be accessed correctly from the console. In your .bashrc set the following two environment variables:
export DOTNET_ROOT=$HOME/.dotnet
export PATH=$DOTNET_ROOT:$PATH:$DOTNET_ROOT/tools
DOTNET_ROOT variable is set to the folder .NET was installed to. And PATH should include both the DOTNET_ROOT folder and the user’s .dotnet/tools folder.
The correct installation can be checked relatively easily with the help of the dotnet command line tool:
dotnet --info
Runtime Environment:
OS Name: fedora
OS Version: 37
OS Platform: Linux
RID: fedora.37-x64
Base Path: /home/stef/.dotnet/sdk/7.0.100/
.NET SDKs installed:
6.0.403 [/home/stef/.dotnet/sdk]
7.0.100 [/home/stef/.dotnet/sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 6.0.11 [/home/stef/.dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.0 [/home/stef/.dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 6.0.11 [/home/stef/.dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.0 [/home/stef/.dotnet/shared/Microsoft.NETCore.App]
Environment variables:
DOTNET_ROOT [/home/stef/.dotnet]
In Rider I had the problem that the SDK was not recognized. However, this can be fixed without much effort. Under File -> Settings go to Build, Execution, Deployment and select Toolset and Build.
Now change the .NET CLI executable path so that it matches your own installation.
]]>Since this is still a preview (beta) version of .NET, I don’t want to mess up my current installation. As we all know, there are various options available for trying out new software versions or products like Docker or Virtual Machines (Gnome Boxes / VirtualBox) and so on.
In this post I want to show how to install the latest .NET Preview version on Fedora 36 Workstation without affecting the existing .NET installation. As an example I use Fedora 36 Workstation, but the steps mentioned below should also work fine under all Debian based distributions.
First, head over to the official .NET download page https://dotnet.microsoft.com/en-us/download/dotnet and select the latest preview version, which is .NET 7.0 in my case. Under SDK, select Linux and download the x64 binaries, or click this link
If you are already running .NET on your local linux system, then all the necessary dependencies should already be installed on your system. Otherwise visit https://docs.microsoft.com/en-us/dotnet/core/install/linux for further information.
Create a dedicated direcory for the preview dotnet installation and extract the downloaded package into it
mkdir -p $HOME/dotnet-preview
tar zxf dotnet-sdk-7.0.100-preview.7.22377.5-linux-x64.tar.gz -C $HOME/dotnet-preview
Define the necessary environment variables and that’s it
export DOTNET_ROOT=$HOME/dotnet-preview
export PATH=$HOME/dotnet-preview:$PATH
Check if everything is working properly with dotnet –info
dotnet --info
.NET SDK:
Version: 7.0.100-preview.7.22377.5
Commit: ba310d9309
Runtime Environment:
OS Name: fedora
OS Version: 36
OS Platform: Linux
RID: fedora.36-x64
Base Path: /home/stef/dotnet-preview/sdk/7.0.100-preview.7.22377.5/
Note: We set the environment variables for the terminal session in which it was run, so when you restart the computer or even the terminal, the .NET SDK commands would no longer be found, or the “old” version would be invoked.
It is of course possible to store the corresponding variables in the shell profile (~/.bashrc), but this may affect the existing .NET installation. And exactly for this problem Distrobox is the perfect solution. From the official documentation:
Use any Linux distribution inside your terminal. Enable both backward and forward compatibility with software and freedom to use whatever distribution you’re more comfortable with. Distrobox uses podman or docker to create containers using the Linux distribution of your choice. The created container will be tightly integrated with the host, allowing sharing of the HOME directory of the user, external storage, external USB devices and graphical apps (X11/Wayland), and audio.
Checkout the official distrobox documentation to explore all the possibilities or watch the introduction video on Youtube by Jorge Castro.
Distrobox is included in Fedora and can be easily installed with dnf
sudo dnf install distrobox
Let’s create a new container based on Fedora 36 with a custom home directory under ~/.distrobox/dotnet-preview and named dotnet-preview
mkdir -p ~/.distrobox/dotnet-preview
distrobox create --image fedora:36 --name dotnet-preview --home ~/.distrobox/dotnet-preview
Enter into your new container with the distrobox enter command
distrobox enter dotnet-preview
After the distrobox has been started, the following commands are executed in the container you’ve just created (dotnet-preview). First, we install all updates and additional packages that are mandatory for dotnet
sudo dnf upgrade -y && dnf install nano libicu -y
The next steps are almost identical as before, with the difference that this time the dotnet package is extracted under /opt
sudo mkdir -p /opt/dotnet-preview
sudo tar zxf dotnet-sdk-7.0.100-preview.7.22377.5-linux-x64.tar.gz -C /opt/dotnet-preview
And this time, we can persist the environment variables without touching the configuration of our main system
echo 'export DOTNET_ROOT=/opt/dotnet-preview' >> ~/.bashrc
echo 'export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools' >> ~/.bashrc
Now check your installation again with dotnet –info
dotnet --info
.NET SDK:
Version: 7.0.100-preview.7.22377.5
Commit: ba310d9309
Runtime Environment:
OS Name: fedora
OS Version: 36
OS Platform: Linux
RID: fedora.36-x64
Base Path: /opt/dotnet-preview/sdk/7.0.100-preview.7.22377.5/
Now every time i want to check one of my projects for .NET 7 compatibility, i simply start my distrobox, navigate to the project and run a dotnet build or dotnet run.
]]>The application runs as a Kubernetes Service behind a NGINX Controller. To get to the root of the problem, it is probably helpful to take a closer look at the ingress logs with the help of kubectl logs:
kubectl logs -n ingress-nginx ingress-nginx-controller-6d5f55986b-q9t7k -f
2022/02/07 19:52:42 [error] 172#172: *3825 upstream timed out (110: Operation timed out) while reading response header from upstream, client: 192.168.49.1, server: my-app.ch, request: "GET /api/slow HTTP/1.1", upstream: "http://172.17.0.2:80/api/slow", host: "my-app.ch"
2022/02/07 19:52:43 [error] 172#172: *3825 upstream timed out (110: Operation timed out) while reading response header from upstream, client: 192.168.49.1, server: my-app.ch, request: "GET /api/slow HTTP/1.1", upstream: "http://172.17.0.2:80/api/slow", host: "my-app.ch"
2022/02/07 19:52:44 [error] 172#172: *3825 upstream timed out (110: Operation timed out) while reading response header from upstream, client: 192.168.49.1, server: my-app.ch, request: "GET /api/slow HTTP/1.1", upstream: "http://172.17.0.2:80/api/slow", host: "my-app.ch"
192.168.49.1 - - [07/Feb/2022:19:52:44 +0000] "GET /api/slow HTTP/1.1" 504 562 "-" "Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36" 459 3.003 [myapp-myapp-80] [] 172.17.0.2:80, 172.17.0.2:80, 172.17.0.2:80 0, 0, 0 1.000, 1.002, 1.001 504, 504, 504 77e32c15c5339f90fb10c7f195f887e4
The logs quickly show where the problem lies. There is obviously a problem with the communication between the NGINX Ingress Controller and our application. It looks like the API call to http://172.17.0.2:80/api/slow runs into a timeout.
The configured timeout in nginx can be easily found out. For this we only have to output the file /etc/nginx/nginx.conf
kubectl exec -it -n ingress-nginx ingress-nginx-controller-6d5f55986b-dwnw8 -- cat /etc/nginx/nginx.conf
There are various options to configure the timeouts on the nginx. Relevant for us is the property proxy-read-timeout and this was set to 60s, which is the default.
proxy_read_timeout 60s;
Unfortunately a quick fix for the affected api was not possible, so the timeout of the ingress had to be increased. In our case, we’ve simply modified the YAML file by setting the annotation nginx.ingress.kubernetes.io/proxy-read-timeout to 180 to temporarily solve the problem.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
labels:
app: myapp
name: ingress-myapp
namespace: myapp
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
nginx.ingress.kubernetes.io/proxy-read-timeout: "180"
spec:
ingressClassName: nginx
rules:
- host: my-app.ch
http:
paths:
- path: /(.*)
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80
There are many reasons why the application initialization feature may fail. If the actual process is not logged, you may not even notice that the initialization was not executed as desired. The problem is that the actual application has not been started yet. Therefore the error analysis is not trivial.
But you can track down the errors relatively quickly with PerfView. If you are unfamiliar with PerfView, there are PerfView video tutorials on Channel9.
To trace the application initialization of your web site, select the “Collect” item in the main menu and then choose “Collect”, or press ALT+C (click here for more details). The only thing to consider now is the use of the Microsoft-Windows-IIS ETW Provider by selecting the IIS checkbox.
Start a trace with Start Collection. Now, go to the IIS Manager and restart (start/stop) your IIS App Pool or simply press “Recycle” in the right pane under Application Pool Tasks. In Perfview you can stop the collection and double click on the Events node
The events viewer should now be displayed. It makes sense to filter the events by iis
The application initialization module calls the endpoint specified under initializationPage via HTTP GET request.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<applicationInitialization doAppInitAfterRestart="true" skipManagedModules="true">
<add initializationPage="/api/init/start" />
</applicationInitialization>
</system.webServer>
</location>
</configuration>
If the controller action does not allow a HTTP GET request, then a 405 Method Not Allowed is returned from the controller. Look in PerfView for HttpStatus 405 events like this:
Therefore, the initialization routine must be able to be called via HTTP GET request. This is usually configured with the HttpGetAttribute applied directly on the action
[ApiController]
public class InitController : ControllerBase
{
private readonly ILogger<InitController> _logger;
public InitController(ILogger<InitController> logger)
{
_logger = logger;
}
[HttpGet]
[Route("api/init/start")]
public IActionResult AppInit()
{
_logger.LogInformation("app initializer");
return Ok();
}
}
Another common problem is the authentication of http requests. Within companies, windows authentication is often used in the intranet. however, IIS application Initialization only works with endpoints that also allow anonymous calls. For this purpose, both the web server and the application must be configured accordingly.
In the case of IIS, anonymous authentication must be enabled. In addition, the API must also allow an anonymous call. This is done with the AllowAnonymous-Attribute applied on the corresponding controller action.
[ApiController]
public class InitController : ControllerBase
{
private readonly ILogger<InitController> _logger;
public InitController(ILogger<InitController> logger)
{
_logger = logger;
}
[HttpGet]
[Route("api/init/start")]
[AllowAnonymous]
public IActionResult AppInit()
{
_logger.LogInformation("app initializer");
return Ok();
}
}
If the controller action does not allow a Anonymous requests, then a 401 Unauthorized is returned from the controller. Look in PerfView for HttpStatus 401 events like this:
Usually websites in IIS are configured with an HTTP to HTTPS rule. This means that all HTTP requests are automatically redirected to HTTPS. This is usually configured using the URL Rewrite module. An exemplary configuration of such a rule could look like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="HTTP to HTTPS redirect for all requests" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Application Initialization module isn’t working for web site configured to require HTTPS only. Look in PerfView for HttpStatus 301 events like this:
The initialization endpoint must be callable with http. For this to work, the existing URL rewrite rule has to be extended to allow http calls from the app initialization module:
<rewrite>
<rules>
<rule name="No redirect on warmup request (request from localhost with warmup user agent)"
stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{HTTP_HOST}" pattern="localhost" />
<add input="{HTTP_USER_AGENT}" pattern="Initialization" />
</conditions>
<action type="Rewrite" url="{URL}" />
</rule>
<rule name="HTTP to HTTPS redirect for all requests" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" />
</rule>
</rules>
</rewrite>
Tip: Also read Application Initialization module fails when web site requires SSL
When using the IIS Application Initialization feature always ensure your web site allows anonymous calls. This means that the corresponding controller action specified in the web.config under initializationPage is marked with the AllowAnonymous attribute. Furthermore, the iis website must allow anonymous authentication.
And finally, it is always a good idea to have the IIS-HttpTracing module installed on your IIS.
]]>The IIS 8.0 Application Initialization feature enables website administrators to configure IIS 8.0 to proactively perform initialization tasks for one or more web applications. While an application is being initialized, IIS 8.0 can also be configured to return static content as a placeholder or “splash page” until an application has completed its initialization tasks.
Before you can use Application Initialization Feature you must ensure, the appropriate IIS Module is installed on your web server. This can be done either by using the Server Manager or Powershell. In an elevated PowerShell prompt run the following command:
Enable-WindowsOptionalFeature -Online -FeatureName IIS-ApplicationInit
Tip: Use my IIS feature installation script for a complete setup of an IIS Server on Windows Server 2016/2019 and Windows 10.
Let’s start with a simple ASP.NET Core Api controller which acts as an entry point for the initialization. The idea behind this endpoint is to provide an API which will be called by the IIS Application Initialization Module to do some initialization tasks like data loading, caching etc. before the web sites handles user requests.
[ApiController]
public class InitController : ControllerBase
{
private readonly ILogger<InitController> _logger;
public InitController(ILogger<InitController> logger)
{
_logger = logger;
}
[HttpGet]
[Route("api/init/start")]
public IActionResult AppInit()
{
_logger.LogInformation("app initializer");
return Ok();
}
}
Since Application Initialization is an IIS feature, of course a web.config file is required to perform all necessary configurations. Note: /api/init/start points to my api controller route
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<applicationInitialization doAppInitAfterRestart="true" skipManagedModules="true">
<add initializationPage="/api/init/start" />
</applicationInitialization>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\dotnet-iis-preload.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
</system.webServer>
</location>
</configuration>
Copy and Paste this web.config file into your Web-Project root directory and set Copy to Output Directory to always either in Visual Studio or directly inside csproj.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Content Update="web.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
In the configuration file shown above, i have set the property stdoutLogEnabled to true, so that the correct functionality can be checked by using the built-in logging capabilities. In productive environments, this property should always be set to false. See Log creation and redirection for further details.
Now, you’re ready to publish your web app using dotnet publish command
dotnet publish -o d:\temp\preload
Create a new IIS-Site and set the physical path to d:\temp\preload. For ASP.NET Core applications, the application pool must be set to No Managed Code.
On your site under “Advanced Settings” you must set Preload Enabled to true. With this setting, IIS sends a “fake request” to the application every time it restarts or the associated application pool starts up.
As soon your application pool has been started, you should see your app initializers log entries under .\logs\stdout
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: D:\temp\preload
info: dotnet_iis_preload.Controllers.InitController[0]
app initializer
this happens everytime your appliction pool has been started or the pool has been recycled.
The Application Initialization feature usually works quite well, but problems can occur depending on the configuration of your Web Site. In order for the feature to work properly, a few conditions must be met. See my blog post IIS application initialization - troubleshooting where I point out the possible problems and explain how to analyze and fix them.
]]>Now you may ask yourself why an additional monitoring tool for .NET Core is necessary?
Well, because performance counters for .NET only work under Windows, while .NET Core can also run Linux or macOS. As a way to make performance counters platform-independent, it needs a new API to read them on other systems as well. And this is where EventCounter API comes in.
Various .NET Core components publish perf counters using the EventCounter API while running:
An incomplete list of counters can be retrieved with the following command
$ dotnet-counters list
All .NET Core runtime diagnostic tools can be installed as .NET Core global tool and run from the commandline. The installation of dotnet-counters is really simple. Just open a terminal and use the dotnet tool install command:
$ dotnet tool install --global dotnet-counters
You can update the tool with dotnet tool update if its already installed
$ dotnet tool update --global dotnet-counters
Or simple trigger the removal with dotnet tool uninstall, if you don’t need/use it anymore
$ dotnet tool uninstall --global dotnet-counters
Let’s create a simple web application using the built-in ASP.NET Core Empty web template.
$ dotnet new web --name perf-counter
The application generated with dotnet new web is quite simple, it just returns hello world to the browser. To show the functionality of dotnet-counters, this small application is sufficient. Now start the application with dotnet run.
To monitor the running application, we need the appropriate process id. With dotnet counters ps a list of all running .NET core processes can be retrieved, including process name and process id:
$ dotnet counters ps
50415 perf-counter /home/stef/dev/perf-counter/bin/Debug/netcoreapp3.1/perf-counter
Starting with a monitoring session is easy. Once again use the dotnet counters command, but this time with the ”–monitor” parameter. The –providers parameter can be used to define the runtime components to be monitored.
$ dotnet counters monitor --process-id 50415 --providers Microsoft.AspNetCore.Hosting System.Runtime
In my case, i’ve enabled the Microsoft.AspNetCore.Hosting and the System.Runtime providers. With Microsoft.AspNetCore.Hosting you can watch things like Current Requests or Request Rate / 1 sec, System.Runtime provides counters about the runtime like CPU Usage (%) or LOH Size (B).
Open the url http://localhost:5000 in a browser and press F5 a few times to simulate some traffic. Several counters should now change as shown below
The EventCounter API makes it really easy to write your own event counters and integrate them into your application. Write your own class and inherit from System.Diagnostics.Tracing.EventSource. It makes sense to mark your event counter with the EventSourceAttribute which lets you define a name and also a GUID
[EventSource(Name = "MyEventCounter")]
public sealed class MyEventCounterSource : EventSource
{
public readonly static MyEventCounterSource EventSource = new MyEventCounterSource();
private readonly PollingCounter _incrementingCounter1;
private int _routeCounter1;
private MyEventCounterSource() : base(EventSourceSettings.EtwSelfDescribingEventFormat)
{
_incrementingCounter1 = new PollingCounter("route-counter-1", this, () => _routeCounter1);
}
public void CountRoute1()
{
Interlocked.Increment(ref _routeCounter1);
}
}
Keep in mind that the method CountRoute1 can be called by different threads. For this reason, make sure that the method updates the _routeCounter1 variable in a thread-safe way. That’s why i’m using the Interlocked.Increment statement. With the help of singleton property MyEventCounterSource.EventSource, you can integrate MyEventCounterSource easily into your own application.
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
endpoints.MapGet("/route1", async context =>
{
MyEventCounterSource.EventSource.CountRoute1();
await context.Response.WriteAsync("Hello Route1!");
});
});
}
}
Now start the monitoring session again, but this time we enable our MyEventCounter provider
$ dotnet counters monitor --process-id 50415 --providers MyEventCounter
Open a browser and navigate to http://localhost:5000/route1. Make sure the endpoint is called a few times so that the changes are visible in the monitor
]]>Now you may wonder what the difference is between dotnet-dump and dotnet-gcdump? In this blog post i would like to show you the differences between dotnet-dump and dotnet-gcdump, as well as explain the use of the tools with an example.
All .NET Core runtime diagnostic tools can be installed as .NET Core global tool and run from the commandline. The installation of these tools is really simple. Just open a terminal and use the dotnet tool install command:
$ dotnet tool install --global dotnet-<tool>
You can update the tool with dotnet tool update if its already installed
$ dotnet tool update --global dotnet-<tool>
Or simple trigger the removal with dotnet tool uninstall, if its not needed anymore
$ dotnet tool uninstall --global dotnet-<tool>
The following small c# program reserves some memory and thus serves as a memory leak
class MyData {
private int[] _data = new int[1024];
}
class Program {
static readonly List<MyData> _data = new List<MyData>();
public static void Main() {
for (int i = 0; i < 1000000; i++) {
_data.Add(new MyData());
}
Console.ReadLine();
}
}
dotnet-dump provides a simple way to collect and analyze Windows and Linux process dumps (macOS is still unsupported). Now, run the sample program and open a new terminal. To get a list of all running .NET Core processes, you can use the dotnet dump ps command:
$ dotnet dump ps
13308 dotnet 'C:\Program Files\dotnet\dotnet.exe'
14180 dotnet 'C:\Program Files\dotnet\dotnet.exe'
1480 MemoryLeaker 'D:\dev\MemoryLeaker\MemoryLeaker\bin\Debug\netcoreapp3.1\MemoryLeaker.exe'
Next, a memory dump can be collected with the appropriate process id
$ dotnet dump collect --process-id 1480
Writing minidump with heap to 'C:\Users\stef\dump_20200417_220807.dmp'
You have several options for analyzing the memory dump. First, you can use the analyze parameter, followed by the dump file
$ dotnet dump analyze dump_20200417_220807.dmp
This brings up an interactive command processor that accepts SOS commands. SOS is a debugging extension that helps you debug managed programs. A list of all supported SOS commands can be found in the SOSCommand.cs file. See my blog post WinDbg Cheat Sheet for .NET Developers for further details.
On Linux-based operating systems, this is the only supported way. Under Windows, you can use PerfView or Visual Studio in addition to dotnet dump analyze.
The main problem with dotnet-dump is the lack of interobablity between Linux and Windows. That means, it is not possible to analyze memory dumps collected on Linux with Windows or vice versa.
dotnet-gcdump can be used on Linux, Mac, and Windows with runtime versions 3.1 or newer. With the help of dotnet-gcdump a heap dump of running .NET Core processes can be created. The dumps are created by triggering a garbage collection in the target process and turning on special events. Look at the DotNetHeapDumpGraphReader::SetupCallbacks method to find out, how to capture memory dumps in .NET using EventPipes.
Run the sample application again, and use the dotnet-gcdump ps command to find out what .NET processes are running, along with their process ID:
$ dotnet gcdump ps
Now, capture a gcdump with the dotnet-gcdump command with the collect and –process-id parameter. This can take several seconds depending on the size of the application:
$ dotnet gcdump collect --process-id 1480
Writing gcdump to 'D:\dev\20200407_055010_18984.gcdump'...
Finished writing 153799 bytes.
Unlike dotnet-dump, the dumps created with dotnet-gcdump can be analyzed with PerfView or Visual Studio, regardless of whether the process was running on Linux, macOS or Windows. This means, whenever you dump the gc heap with dotnet-gcdump, you must have a running windows machine to analyze the dump.
Note: Opening a .gcdump on non-Windows platforms is currently not supported.
Use dotnet-gcdump if your applications running on runtime versions 3.1 or later and you have a Windows machine available to run PerfView or Visual Studio. Under Linux, you must use dotnet-dump for all other variants. In Windows environments, you always have the possibility to use PerfView or Visual Studio or WinDbg.
One more thing to notice: dotnet-dump generates much bigger dumpfiles compared to dotnet-gcdump. The example program shown above created the following file sizes of the respective memory images under Windows 10 and Fedora 31.
Diagnostic tool | Windows 10 | Fedora 31 |
---|---|---|
dotnet-dump | 4’116’867 KB | 4’265’676 KB |
dotnet-gcdump | 32’708 KB | 35’649 KB |