Skip to content

Commit 59d93c5

Browse files
committed
add export to CSV function
1 parent b63aacb commit 59d93c5

4 files changed

Lines changed: 188 additions & 98 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using Microsoft.Win32;
2+
using System.Diagnostics;
3+
using System.Linq;
4+
using System.Runtime.InteropServices;
5+
6+
namespace ProcessorLatencyTool.Helpers;
7+
8+
public static class SystemInfoHelper
9+
{
10+
public static string GetProcessorName()
11+
{
12+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
13+
{
14+
using var key = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0");
15+
var processorName = key?.GetValue("ProcessorNameString")?.ToString()?.Trim();
16+
17+
return processorName ?? "Unknown CPU";
18+
}
19+
20+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
21+
{
22+
try
23+
{
24+
var startInfo = new ProcessStartInfo
25+
{
26+
FileName = "cat",
27+
Arguments = "/proc/cpuinfo",
28+
RedirectStandardOutput = true,
29+
UseShellExecute = false,
30+
CreateNoWindow = true
31+
};
32+
33+
using var process = Process.Start(startInfo);
34+
if (process != null)
35+
{
36+
var output = process.StandardOutput.ReadToEnd();
37+
process.WaitForExit();
38+
var modelName = output.Split('\n')
39+
.FirstOrDefault(line => line.StartsWith("model name"))
40+
?.Split(':')
41+
.LastOrDefault()
42+
?.Trim();
43+
return modelName ?? "Unknown CPU";
44+
}
45+
}
46+
catch
47+
{
48+
return "Unknown CPU";
49+
}
50+
}
51+
52+
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
53+
{
54+
try
55+
{
56+
var startInfo = new ProcessStartInfo
57+
{
58+
FileName = "sysctl",
59+
Arguments = "-n machdep.cpu.brand_string",
60+
RedirectStandardOutput = true,
61+
UseShellExecute = false,
62+
CreateNoWindow = true
63+
};
64+
65+
using var process = Process.Start(startInfo);
66+
if (process != null)
67+
{
68+
var output = process.StandardOutput.ReadToEnd();
69+
process.WaitForExit();
70+
return output.Trim();
71+
}
72+
}
73+
catch
74+
{
75+
return "Unknown CPU";
76+
}
77+
}
78+
79+
return "Unknown CPU";
80+
}
81+
}

ProcessorLatencyTool/ProcessorLatencyTool.csproj

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project Sdk="Microsoft.NET.Sdk">
33
<PropertyGroup>
4+
<LangVersion>preview</LangVersion>
45
<OutputType>WinExe</OutputType>
56
<TargetFramework>net9.0</TargetFramework>
67
<Nullable>enable</Nullable>
@@ -48,19 +49,15 @@
4849
<MakeDir Directories="$(MSBuildProjectDirectory)/Native/build" />
4950

5051
<!-- Run CMake -->
51-
<Exec Command="cmake -DCMAKE_BUILD_TYPE=$(Configuration) -DCMAKE_INSTALL_PREFIX=$(NativeOutputDir) .."
52-
WorkingDirectory="$(MSBuildProjectDirectory)/Native/build" />
53-
<Exec Command="cmake --build . --config $(Configuration)"
54-
WorkingDirectory="$(MSBuildProjectDirectory)/Native/build" />
55-
<Exec Command="cmake --install ."
56-
WorkingDirectory="$(MSBuildProjectDirectory)/Native/build" />
52+
<Exec Command="cmake -DCMAKE_BUILD_TYPE=$(Configuration) -DCMAKE_INSTALL_PREFIX=$(NativeOutputDir) .." WorkingDirectory="$(MSBuildProjectDirectory)/Native/build" />
53+
<Exec Command="cmake --build . --config $(Configuration)" WorkingDirectory="$(MSBuildProjectDirectory)/Native/build" />
54+
<Exec Command="cmake --install ." WorkingDirectory="$(MSBuildProjectDirectory)/Native/build" />
5755

5856
<!-- Copy to output directory -->
5957
<ItemGroup>
6058
<NativeLibraries Include="$(NativeOutputDir)/*arm64_registers.*" />
6159
</ItemGroup>
62-
<Copy SourceFiles="@(NativeLibraries)"
63-
DestinationFolder="$(OutputPath)/runtimes/osx/native" />
60+
<Copy SourceFiles="@(NativeLibraries)" DestinationFolder="$(OutputPath)/runtimes/osx/native" />
6461
</Target>
6562

6663
<!-- Include native libraries in build output -->
@@ -72,10 +69,10 @@
7269
</ItemGroup>
7370

7471
<ItemGroup>
75-
<PackageReference Include="Avalonia" Version="11.0.6" />
76-
<PackageReference Include="Avalonia.Desktop" Version="11.0.6" />
77-
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.6" />
78-
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.6" />
79-
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
72+
<PackageReference Include="Avalonia" Version="11.3.0" />
73+
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
74+
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0" />
75+
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.0" />
76+
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
8077
</ItemGroup>
8178
</Project>

ProcessorLatencyTool/ViewModels/MainWindowViewModel.cs

Lines changed: 84 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Avalonia.Controls;
22
using Avalonia.Layout;
33
using Avalonia.Media;
4-
using System.Runtime.InteropServices;
54
using System;
65
using CommunityToolkit.Mvvm.Input;
76
using System.Linq;
@@ -10,96 +9,29 @@
109
using Avalonia;
1110
using Avalonia.Threading;
1211
using CommunityToolkit.Mvvm.ComponentModel;
13-
using System.Diagnostics;
14-
using Microsoft.Win32;
1512
using ProcessorLatencyTool.Helpers.LatencyTester;
1613
using ProcessorLatencyTool.Models;
14+
using System.IO;
15+
using System.Text;
16+
using Avalonia.Controls.ApplicationLifetimes;
17+
using Avalonia.Platform.Storage;
1718

1819
namespace ProcessorLatencyTool.ViewModels
1920
{
2021
public partial class MainWindowViewModel : ViewModelBase
2122
{
22-
public static string CpuModel
23-
{
24-
get
25-
{
26-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
27-
{
28-
using var key = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0");
29-
var processorName = key?.GetValue("ProcessorNameString")?.ToString()?.Trim();
30-
31-
return processorName ?? "Unknown CPU";
32-
}
33-
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
34-
{
35-
try
36-
{
37-
var startInfo = new ProcessStartInfo
38-
{
39-
FileName = "cat",
40-
Arguments = "/proc/cpuinfo",
41-
RedirectStandardOutput = true,
42-
UseShellExecute = false,
43-
CreateNoWindow = true
44-
};
45-
46-
using var process = Process.Start(startInfo);
47-
if (process != null)
48-
{
49-
var output = process.StandardOutput.ReadToEnd();
50-
process.WaitForExit();
51-
var modelName = output.Split('\n')
52-
.FirstOrDefault(line => line.StartsWith("model name"))
53-
?.Split(':')
54-
.LastOrDefault()
55-
?.Trim();
56-
return modelName ?? "Unknown CPU";
57-
}
58-
}
59-
catch
60-
{
61-
// Ignore
62-
}
63-
}
64-
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
65-
{
66-
try
67-
{
68-
var startInfo = new ProcessStartInfo
69-
{
70-
FileName = "sysctl",
71-
Arguments = "-n machdep.cpu.brand_string",
72-
RedirectStandardOutput = true,
73-
UseShellExecute = false,
74-
CreateNoWindow = true
75-
};
76-
77-
using var process = Process.Start(startInfo);
78-
if (process != null)
79-
{
80-
var output = process.StandardOutput.ReadToEnd();
81-
process.WaitForExit();
82-
return output.Trim();
83-
}
84-
}
85-
catch
86-
{
87-
// Ignore
88-
}
89-
}
90-
91-
return "Unknown CPU";
92-
}
93-
}
23+
public static string CpuModel => SystemInfoHelper.GetProcessorName();
9424

9525
public static string CoreThreadInfo => $"Cores: {Environment.ProcessorCount}";
9626

97-
[ObservableProperty] private string _progressText = "Testing latency...";
98-
[ObservableProperty] private string _statusText = "Ready";
99-
[ObservableProperty] private double _progressValue;
100-
[ObservableProperty] private string _progressPercentage = "0%";
27+
[ObservableProperty] public partial string ProgressText { get; private set; } = "Testing latency...";
28+
[ObservableProperty] public partial string StatusText { get; private set; } = "Ready";
29+
[ObservableProperty] public partial double ProgressValue { get; private set; }
30+
[ObservableProperty] public partial string ProgressPercentage { get; private set; } = "0%";
31+
[ObservableProperty] public partial bool HasResults { get; private set; }
10132

10233
private StackPanel? _mainPanel;
34+
private LatencyResult[][]? _currentMatrix;
10335

10436
public void SetMainPanel(StackPanel panel)
10537
{
@@ -132,6 +64,8 @@ private void DisplayInitialGrid()
13264
}
13365
}
13466

67+
_currentMatrix = matrix;
68+
HasResults = false;
13569
BuildLatencyGrid(matrix, _mainPanel, true);
13670
}
13771

@@ -141,6 +75,7 @@ private async Task StartMeasurementAsync()
14175
StatusText = "Starting measurements...";
14276
ProgressValue = 0;
14377
ProgressPercentage = "0%";
78+
HasResults = false;
14479

14580
var progress = new Progress<(int, int)>(onProgress =>
14681
{
@@ -168,9 +103,78 @@ private async Task StartMeasurementAsync()
168103
}
169104

170105
StatusText = "Measurements completed";
106+
HasResults = true;
171107
});
172108
}
173109

110+
[RelayCommand]
111+
private async Task ExportToCsvAsync()
112+
{
113+
if (_currentMatrix == null)
114+
{
115+
StatusText = "No data to export";
116+
return;
117+
}
118+
119+
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
120+
{
121+
StatusText = "Cannot access application window";
122+
return;
123+
}
124+
125+
var mainWindow = desktop.MainWindow;
126+
if (mainWindow == null)
127+
{
128+
StatusText = "Cannot access main window";
129+
return;
130+
}
131+
132+
var file = await mainWindow.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
133+
{
134+
Title = "Save Latency Data",
135+
DefaultExtension = "csv",
136+
FileTypeChoices =
137+
[
138+
new FilePickerFileType("CSV Files")
139+
{
140+
Patterns = ["*.csv"]
141+
}
142+
]
143+
});
144+
145+
if (file == null)
146+
return;
147+
148+
try
149+
{
150+
var csv = new StringBuilder();
151+
var coreCount = _currentMatrix.Length;
152+
153+
// Add header row
154+
csv.AppendLine("Source Core,Target Core,Mean Latency (ns),Standard Deviation (ns),Min Latency (ns),Max Latency (ns),Sample Count");
155+
156+
// Add data rows
157+
for (var i = 0; i < coreCount; i++)
158+
{
159+
for (var j = 0; j < coreCount; j++)
160+
{
161+
var result = _currentMatrix[i][j];
162+
csv.AppendLine($"{i},{j},{result.MeanLatency:F2},{result.StandardDeviation:F2},{result.MinLatency:F2},{result.MaxLatency:F2},{result.SampleCount}");
163+
}
164+
}
165+
166+
await using var stream = await file.OpenWriteAsync();
167+
await using var writer = new StreamWriter(stream);
168+
await writer.WriteAsync(csv.ToString());
169+
170+
StatusText = "Data exported successfully";
171+
}
172+
catch (Exception ex)
173+
{
174+
StatusText = $"Export failed: {ex.Message}";
175+
}
176+
}
177+
174178
private static void BuildLatencyGrid(LatencyResult[][] matrix, StackPanel panel, bool isInit = false)
175179
{
176180
var grid = new Grid
@@ -329,6 +333,7 @@ private async Task<LatencyResult[][]> MeasureLatencyMatrixAsync(
329333
}
330334
}
331335

336+
_currentMatrix = matrix;
332337
return matrix;
333338
}
334339
}

ProcessorLatencyTool/Views/MainWindow.axaml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,19 @@
3333
Foreground="White" />
3434
</StackPanel>
3535

36-
<Button Command="{Binding StartMeasurementCommand}"
37-
Background="SpringGreen"
38-
HorizontalAlignment="Right"
39-
VerticalAlignment="Center"
40-
Content="Start Test"
41-
Margin="0, 0, 40, 0"/>
36+
<StackPanel Orientation="Horizontal"
37+
HorizontalAlignment="Right"
38+
VerticalAlignment="Center"
39+
Spacing="10"
40+
Margin="0, 0, 40, 0">
41+
<Button Command="{Binding ExportToCsvCommand}"
42+
Background="DodgerBlue"
43+
Content="Export CSV"
44+
IsEnabled="{Binding HasResults}"/>
45+
<Button Command="{Binding StartMeasurementCommand}"
46+
Background="SpringGreen"
47+
Content="Start Test"/>
48+
</StackPanel>
4249
</Grid>
4350

4451
<ScrollViewer Grid.Row="1"

0 commit comments

Comments
 (0)