.NET をIBM Powerで動かしてみた話 #2

本".NET をIBM Powerで動かしてみた話"シリーズは、.NET 7がLinux on Power環境で利用可能となった([IBM社のブログ : .NET 7 now includes support for Linux on Power!](https://community.ibm.com/community/user/powerdeveloper/blogs/janani-janakiraman/2022/11/07/net7-support-linux-on-power))ということで、以下のテーマで3つのブログを掲載予定としています。いずれのテーマも、Linux on Powerをご存知の方、もしくは触ったことがある方は今回のニュースを聞いて、抱かれた疑問ではないでしょうか。

<b>1. 「本当に.NET 7は問題なく動くのか」
2. 「x86環境で作成された.NET アプリケーションはppc64le環境でもそのまま動くのか」
3. 「コンテナ環境でも利用できるのか」</b>


今回は、２つ目のテーマである"「x86環境で作成された.NET アプリケーションはppc64le環境でもそのまま動くのか」" について記載していこうと思います。
なお、本記事で使用している環境は、上記の"1. 「本当に.NET 7は問題なく動くのか」"の記事で作成したものをそのまま使用しています。
まだ読まれていない方は、よろしければ本記事に入る前に以下の内容もご覧ください。

https://qiita.com/Nakaji1106/items/f0761c4d1dce58c0b8d5

それでは、本題に入っていきたいと思います。

# 2. 「x86環境で作成された.NET アプリケーションはppc64le環境でもそのまま動くのか」

このテーマでは実際に.NETのサンプルコードを利用して、.NETアプリケーションの利用においてCPUアーキテクチャ(x86環境とPower環境)を意識することがあるのかを確認していきたいと思います。.NETの仕組み的に、サンプルコードレベルでの互換性はあると理解していますが、やはり実際に動かしてみたいと思ってしまいますよね。

ちなみにこれは参考情報ですが、.NET環境においても.NETアプリケーション内部で使用している.NETコンポーネントなどは、x86環境とPower環境とでの違いはあります。こちらはPower環境に対応した、対象の.NETコンポーネントが提供されているかどうかという部分になってきます。
このような形で、既存の.NETアプリケーションのコードが"全部・そのまま・必ず"動くかというとそうではありませんのでご注意ください。一度、評価環境などで動作をご確認いただくことをお勧めいたします。

### (1) 作業環境の確認

今回使用しているLinux on Power環境は以下となります。
RHEL8.7 相当となるAlmaLinux 8.7 をインストールしています。

```bash
[root@dotnet-sample-alma87 ~]# uname -a
Linux dotnet-sample-alma87 4.18.0-425.3.1.el8.ppc64le #1 SMP Tue Nov 8 14:07:43 EST 2022 ppc64le ppc64le ppc64le GNU/Linux
[root@dotnet-sample-alma87 ~]# cat /etc/redhat-release 
AlmaLinux release 8.7 (Stone Smilodon)
```

### (2) 使用するサンプルコード

以下のGitHubの内容をもとに動作を確認しています。
このGitHubの内容は、Blazorフレームワークをベースとしたサンプルコードとなっています。

https://github.com/dotnet/blazor-samples.git

この中から、以下のコンテンツの動作を確認していきます。
問題なく動作すれば、チャット機能やカウンター機能、天気情報の確認が可能なWebUIが起動してきます。

https://github.com/dotnet/blazor-samples/tree/main/7.0/BlazorServerSignalRApp


### (3) サンプルコードの実行

まず初めに任意のディレクトリで上記のGitHubレポジトリをcloneします。
今回は、/rootディレクトリ配下にworkディレクトリを作成し、そちらにgit clone したいと思います。

```bash:/root/work ディレクトリの作成
[root@dotnet-sample-alma87 ~]# mkdir /root/work
[root@dotnet-sample-alma87 ~]# cd /root/work/
[root@dotnet-sample-alma87 work]# pwd
/root/work
```

```bash:git cloneの実施
[root@dotnet-sample-alma87 work]# git clone https://github.com/dotnet/blazor-samples.git
Cloning into 'blazor-samples'...
remote: Enumerating objects: 1939, done.
remote: Counting objects: 100% (139/139), done.
remote: Compressing objects: 100% (55/55), done.
remote: Total 1939 (delta 101), reused 95 (delta 84), pack-reused 1800
Receiving objects: 100% (1939/1939), 627.37 KiB | 169.00 KiB/s, done.
Resolving deltas: 100% (1270/1270), done.
[root@dotnet-sample-alma87 work]# ls
blazor-samples
```

続いて、今回利用する"BlazorServerSignalRApp"のディレクトリの中身を確認してみます。

```bash: blazor-samples/7.0/BlazorServerSignalRApp
[root@dotnet-sample-alma87 work]# cd blazor-samples/7.0/BlazorServerSignalRApp/
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# ls
App.razor  BlazorServerSignalRApp.csproj  Data  Hubs  Pages  Program.cs  Properties  Shared  _Imports.razor  appsettings.Development.json  appsettings.json  wwwroot
```

次に、.NETアプリケーション起動時のアクセスIPアドレスの設定を変更します。デフォルトではlocalhostとなっていますが、今回は"Any IP"(対象のAlmaLinuxがもつ全てのIPアドレスでアクセス可能)の設定に変更したいと思います。

```bash:デフォルトの設定 : Properties/launchSettings.json 
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# cat Properties/launchSettings.json 
{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:55347",
      "sslPort": 44312
    }
  },
  "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "http://localhost:5098",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "https://localhost:7033;http://localhost:5098",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}
```

```bash:変更後 : Properties/launchSettings.json( "localhost -> * " に変更)
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# cat Properties/launchSettings.json 
{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://*:55347",
      "sslPort": 44312
    }
  },
  "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "http://*:5098",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "https://*:7033;http://*:5098",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}
```

それでは早速、起動してみましょう！

```bash:起動 : dotnet run
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# pwd
/root/work/blazor-samples/7.0/BlazorServerSignalRApp
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# dotnet run
ビルドしています...
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://[::]:5098
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /root/work/blazor-samples/7.0/BlazorServerSignalRApp
```

エラーなく、起動してきました。
また先ほどの設定変更によって、.NETアプリケーションがListenしているURLが"`http://[::]:5098`" となっています。もし設定を実施しない場合は、"`http://localhost:5098`" となり、localhostからしかアクセスできなくなります。
今回は、"http://[::]:5098"としていますので、.NETアプリケーションが起動している環境がもつIPアドレスであれば、どのIPでもアクセスが可能となっています。

それでは実際にWebUIにアクセスしてみます。正常にアプリケーションが起動していれば、以下のような画面が表示されるはずです。
そしてここまで動作を確認ができれば、.NETのアプリケーションコードがCPUアーキテクチャに依存せず、そのまま利用できていることがわかります。

 + アクセスURL : `http://<IPアドレス>:5098`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613078/bf3d5484-1446-4988-fa89-97d359319d97.png)

WebUI画面左側のタブをそれぞれクリックすると、各ページごとに作成された機能を確認できます。

+ Counter

"Click me"ボタンを押すと、Current countの値が増えていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613078/ca4b4d59-1070-dfdc-a5bd-bcbd6e8e4b23.png)

+ Fetch Data

dotnet run を実行した翌日以降の日付で天気予報が表示されます。今回は、12月20日に実行していますので、21日からの表示になっていますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613078/62716b0c-b92e-a87b-ede3-38c09b81831b.png)

### (4) サンプルコードを変更してみよう！

ここからは、.NETアプリケーションのソースコード変更して、どこをどう変更するとWebUIに反映されるのかを確認してみました。
実施してみたことは以下の4点です。

(a) WebUIアクセス時の"Home"画面として、起動している環境のシステム情報を表示したい
(b) 左側のタブの"BlazorServerSignalRApp"を変更したい
(c) 左側のタブの表示順を変更したい
(d) 左側のタブのアイコンを変更したい

各項目を設定するための変更対象ファイルと内容は以下です。
変更後の"BlazorServerSignalRApp"アプリケーションコードは、以下のGitHubに公開しました。

https://github.com/nakaji1106/dotnet-demo/tree/main/BlazorServerSignalRApp

それでは早速変更箇所を見ていきます。

まずは、以下のように"/root/work/blazor-samples/7.0/BlazorServerSignalRApp/Pages/"ディレクトリ配下に"Info.razor"というファイルを作成し、(a)のやりたいことであるHome画面として表示するWebページ用のコードを作ります。

```bash: /root/work/blazor-samples/7.0/BlazorServerSignalRApp/Pages/Info.razor を作成
@page "/"
@using System.Runtime.InteropServices
@using System.IO
@using System.Diagnostics
@{
    var hostName = System.Net.Dns.GetHostName();
    System.Diagnostics.Debug.WriteLine(hostName);

    const long Mebi = 1024 * 1024;
    const long Gibi = Mebi * 1024;

    GCMemoryInfo gcInfo = GC.GetGCMemoryInfo();
    string totalAvailableMemory = GetInBestUnit(gcInfo.TotalAvailableMemoryBytes);

    bool cgroup = RuntimeInformation.OSDescription.StartsWith("Linux") && Directory.Exists("/sys/fs/cgroup/memory");
    string memoryUsage = string.Empty;
    string memoryLimit = string.Empty;

    if (cgroup)
    {
        string usage = System.IO.File.ReadAllLines("/sys/fs/cgroup/memory/memory.usage_in_bytes")[0];
        string limit = System.IO.File.ReadAllLines("/sys/fs/cgroup/memory/memory.limit_in_bytes")[0];
        memoryUsage = GetInBestUnit(long.Parse(usage));
        memoryLimit = GetInBestUnit(long.Parse(limit));
    }

}

<div class="text-center">
    <h1>Hello! .NET on Power!</h1>
</div>
<br>

<font color="#0000cd"><b>今回の検証環境は、以下となります。</b></font><br>
<font color="#0000cd"><b>x86で作成されたサンプルコードをそのまま使用して、本日のセッション用にカスタマイズしています。</b></font><br>
<br>

<div align="center">
    <style>
	table td {
		background: #eee;
	}
    	table tr:nth-child(odd) td {
		background: #fff;
    	}
	table {
		border:3px solid #333;
	}
    </style>
    
    <table class="table table-striped table-hover">
        <tr>
            <td>.NET version</td>
            <td>@RuntimeInformation.FrameworkDescription</td>
        </tr>
        <tr>
            <td>Operating system</td>
            <td>@RuntimeInformation.OSDescription</td>
        </tr>
        <tr>
            <td>Processor architecture</td>
            <td>@RuntimeInformation.OSArchitecture</td>
        </tr>
        <tr>
            <td>CPU cores</td>
            <td>@Environment.ProcessorCount</td>
        </tr>
        <tr>
            <td>Containerized</td>
            <td>@(Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") is null ? "false" : "true")</td>
        </tr>
        <tr>
            <td>Memory, total available GC memory</td>
            <td>@totalAvailableMemory</td>
        </tr>
        @if (cgroup)
        {
            <tr>
                <td>cgroup memory usage</td>
                <td>@memoryUsage</td>
            </tr>
            <tr>
                <td>cgroup memory limit</td>
                <td>@memoryLimit</td>
            </tr>
        }
        <tr>
            <td>Host name</td>
            <td>@hostName</td>
        </tr>
    </table>
</div>

@{
    string GetInBestUnit(long size)
    {
        if (size < Mebi)
        {
            return $"{size} bytes";
        }
        else if (size < Gibi)
        {
            decimal mebibytes = Decimal.Divide(size, Mebi);
            return $"{mebibytes:F} MiB";
        }
        else
        {
            decimal gibibytes = Decimal.Divide(size, Gibi);
            return $"{gibibytes:F} GiB";
        }
    }
}
```
続いて、WebUIにアクセスした際に"Home"として、上記のWebページが表示されるように "BlazorServerSignalRApp/Shared/NavMenu.razor"ファイルを編集します。
またこのファイルでは、WebUI左側タブに表示されている文字やコンテンツの表示順、アイコンの変更(やりたいことの(b)-(d))も可能なため、併せて変更してしまいます。

(b) 左側のタブの"BlazorServerSignalRApp"を変更したい
(c) 左側のタブの表示順を変更したい
(d) 左側のタブのアイコンを変更したい

```bash:/root/work/blazor-samples/7.0/BlazorServerSignalRApp/Shared/NavMenu.razor を編集
<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">.NET on IBM Power!!</a>
        <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
            <span class="navbar-toggler-icon"></span>
        </button>
    </div>
</div>

<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="chat" Match="NavLinkMatch.All">
                <span class="oi oi-chat" aria-hidden="true"></span> Communication
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-cloudy" aria-hidden="true"></span> WeatherForecast
            </NavLink>
        </div>
    </nav>
</div>

@code {
    private bool collapseNavMenu = true;

    private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}
```

最後に、デフォルトのWebUIで"Home"画面として使用されていた"チャット画面"を"Communication"タブ(/chat)に変更するため、BlazorServerSignalRApp/Pages/Index.razor ファイルを変更します。

```bash: /root/work/blazor-samples/7.0/BlazorServerSignalRApp/Pages/Index.razor を編集
@page "/chat"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable

<PageTitle>Index</PageTitle>

<div class="form-group">
    <label>
        User:
        <input @bind="userInput" />
    </label>
</div>
<div class="form-group">
    <label>
        Message:
        <input @bind="messageInput" size="50" />
    </label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>

<hr>

<ul id="messagesList">
    @foreach (var message in messages)
    {
        <li>@message</li>
    }
</ul>

@code {
    private HubConnection? hubConnection;
    private List<string> messages = new List<string>();
    private string? userInput;
    private string? messageInput;

    protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder()
            .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
            .Build();

        hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
        {
            var encodedMsg = $"{user}: {message}";
            messages.Add(encodedMsg);
            InvokeAsync(StateHasChanged);
        });

        await hubConnection.StartAsync();
    }

    private async Task Send()
	{
	    if (hubConnection is not null)
            {
                await hubConnection.SendAsync("SendMessage", userInput, messageInput);
            }
	}

    public bool IsConnected =>
        hubConnection?.State == HubConnectionState.Connected;

    public async ValueTask DisposeAsync()
    {
        if (hubConnection is not null)
        {
            await hubConnection.DisposeAsync();
        }
    }
}
```

以上で、サンプルコードの変更は終了です。
今一度アプリケーションを起動し、想定した内容となっているか確認してみましょう。また"dotnet run"実行のタイミングでエラーなどが表示されないかも併せて確認します。(もし表示された場合には、そのエラー箇所を修正しましょう。)

```bash: dotnet run 実行
[root@dotnet-sample-alma87 BlazorServerSignalRApp]# dotnet run
ビルドしています...
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://[::]:5098
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /root/work/blazor-samples/7.0/BlazorServerSignalRApp
```

正常に上記の変更がされている場合、以下のようなWebページが表示されるはずです。
ちゃんと"Home"のページコンテンツとして、システム情報が表示されていますし、左側のタブ欄も変更が反映されていますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613078/d583ecbf-75a0-6bc8-8d0f-65899dfdcfb1.png)

# まとめ

今回は、前回の内容につづき、IBM Power上での.NET 7アプリケーションの稼働確認を行いました。この結果から、アプリケーションのソースコードは基本的にはCPUアーキテクチャに依存しないことが分かりました。
しかしながら冒頭に記載しましたように、.NETアプリケーション内部で使用している.NETコンポーネントなどは、x86環境とPower環境とでの違いはあります。これはPower環境に対応した、対象の.NETコンポーネントが提供されているかどうかという部分になってきます。
その為、既存の.NETアプリケーションのコードが"全部・そのまま・必ず"動くかというとそうではありませんのでご注意ください。一度、評価環境などで動作をご確認いただくことをお勧めいたします。

次回は最後のコンテンツとなる"3. 「コンテナ環境でも利用できるのか」" について記載しています。宜しければご覧ください。

https://qiita.com/Nakaji1106/items/d6360a44b52ab360f76d


今回の内容は以上となります。本記事が何かの参考となれば幸いです。