流量监控

This commit is contained in:
陈国伟 2021-10-28 17:43:26 +08:00
parent 29a5e39a60
commit 7c2e717eda
26 changed files with 35204 additions and 141 deletions

View File

@ -0,0 +1,167 @@
using System;
using System.Buffers;
using System.IO;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.FlowAnalyze
{
class DuplexPipeStream : Stream
{
private readonly PipeReader _input;
private readonly PipeWriter _output;
private readonly bool _throwOnCancelled;
private volatile bool _cancelCalled;
public DuplexPipeStream(PipeReader input, PipeWriter output, bool throwOnCancelled = false)
{
_input = input;
_output = output;
_throwOnCancelled = throwOnCancelled;
}
public void CancelPendingRead()
{
_cancelCalled = true;
_input.CancelPendingRead();
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length
{
get
{
throw new NotSupportedException();
}
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
ValueTask<int> vt = ReadAsyncInternal(new Memory<byte>(buffer, offset, count), default);
return vt.IsCompleted ?
vt.Result :
vt.AsTask().GetAwaiter().GetResult();
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default)
{
return ReadAsyncInternal(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
}
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
return ReadAsyncInternal(destination, cancellationToken);
}
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override async Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
{
await _output.WriteAsync(buffer.AsMemory(offset, count), cancellationToken) ;
}
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
await _output.WriteAsync(source, cancellationToken);
}
public override void Flush()
{
FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
}
public override async Task FlushAsync(CancellationToken cancellationToken)
{
await _output.FlushAsync(cancellationToken);
}
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
private async ValueTask<int> ReadAsyncInternal(Memory<byte> destination, CancellationToken cancellationToken)
{
while (true)
{
var result = await _input.ReadAsync(cancellationToken);
var readableBuffer = result.Buffer;
try
{
if (_throwOnCancelled && result.IsCanceled && _cancelCalled)
{
// Reset the bool
_cancelCalled = false;
throw new OperationCanceledException();
}
if (!readableBuffer.IsEmpty)
{
// buffer.Count is int
var count = (int)Math.Min(readableBuffer.Length, destination.Length);
readableBuffer = readableBuffer.Slice(0, count);
readableBuffer.CopyTo(destination.Span);
return count;
}
if (result.IsCompleted)
{
return 0;
}
}
finally
{
_input.AdvanceTo(readableBuffer.End, readableBuffer.End);
}
}
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
return TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
return TaskToApm.End<int>(asyncResult);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
return TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
TaskToApm.End(asyncResult);
}
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.IO;
using System.IO.Pipelines;
using System.Threading.Tasks;
namespace FastGithub.FlowAnalyze
{
class DuplexPipeStreamAdapter<TStream> : DuplexPipeStream, IDuplexPipe where TStream : Stream
{
private bool _disposed;
private readonly object _disposeLock = new object();
public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, Func<Stream, TStream> createStream) :
this(duplexPipe, new StreamPipeReaderOptions(leaveOpen: true), new StreamPipeWriterOptions(leaveOpen: true), createStream)
{
}
public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func<Stream, TStream> createStream) :
base(duplexPipe.Input, duplexPipe.Output)
{
var stream = createStream(this);
Stream = stream;
Input = PipeReader.Create(stream, readerOptions);
Output = PipeWriter.Create(stream, writerOptions);
}
public TStream Stream { get; }
public PipeReader Input { get; }
public PipeWriter Output { get; }
public override async ValueTask DisposeAsync()
{
lock (_disposeLock)
{
if (_disposed)
{
return;
}
_disposed = true;
}
await Input.CompleteAsync();
await Output.CompleteAsync();
}
protected override void Dispose(bool disposing)
{
throw new NotSupportedException();
}
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,12 @@
using System.IO.Pipelines;
namespace FastGithub.FlowAnalyze
{
sealed class FlowAnalyzeDuplexPipe : DuplexPipeStreamAdapter<FlowAnalyzeStream>
{
public FlowAnalyzeDuplexPipe(IDuplexPipe transport, IFlowAnalyzer flowAnalyzer) :
base(transport, stream => new FlowAnalyzeStream(stream, flowAnalyzer))
{
}
}
}

View File

@ -0,0 +1,159 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.FlowAnalyze
{
sealed class FlowAnalyzeStream : Stream
{
private readonly Stream inner;
private readonly IFlowAnalyzer flowAnalyzer;
public FlowAnalyzeStream(Stream inner, IFlowAnalyzer flowAnalyzer)
{
this.inner = inner;
this.flowAnalyzer = flowAnalyzer;
}
public override bool CanRead
{
get
{
return inner.CanRead;
}
}
public override bool CanSeek
{
get
{
return inner.CanSeek;
}
}
public override bool CanWrite
{
get
{
return inner.CanWrite;
}
}
public override long Length
{
get
{
return inner.Length;
}
}
public override long Position
{
get
{
return inner.Position;
}
set
{
inner.Position = value;
}
}
public override void Flush()
{
inner.Flush();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return inner.FlushAsync(cancellationToken);
}
public override int Read(byte[] buffer, int offset, int count)
{
int read = inner.Read(buffer, offset, count);
this.flowAnalyzer.OnFlow(FlowType.Read, read);
return read;
}
public override int Read(Span<byte> destination)
{
int read = inner.Read(destination);
this.flowAnalyzer.OnFlow(FlowType.Read, read);
return read;
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
int read = await inner.ReadAsync(buffer.AsMemory(offset, count), cancellationToken);
this.flowAnalyzer.OnFlow(FlowType.Read, read);
return read;
}
public override async ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
int read = await inner.ReadAsync(destination, cancellationToken);
this.flowAnalyzer.OnFlow(FlowType.Read, read);
return read;
}
public override long Seek(long offset, SeekOrigin origin)
{
return inner.Seek(offset, origin);
}
public override void SetLength(long value)
{
inner.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
this.flowAnalyzer.OnFlow(FlowType.Wirte, count);
inner.Write(buffer, offset, count);
}
public override void Write(ReadOnlySpan<byte> source)
{
this.flowAnalyzer.OnFlow(FlowType.Wirte, source.Length);
inner.Write(source);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
this.flowAnalyzer.OnFlow(FlowType.Wirte, count);
return inner.WriteAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
this.flowAnalyzer.OnFlow(FlowType.Wirte, source.Length);
return inner.WriteAsync(source, cancellationToken);
}
// The below APM methods call the underlying Read/WriteAsync methods which will still be logged.
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
return TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
return TaskToApm.End<int>(asyncResult);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
return TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
TaskToApm.End(asyncResult);
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
namespace FastGithub.FlowAnalyze
{
sealed class FlowAnalyzer : IFlowAnalyzer
{
private const int INTERVAL_SECONDS = 5;
private readonly ConcurrentQueue<QueueItem> readQueue = new();
private readonly ConcurrentQueue<QueueItem> writeQueue = new();
private record QueueItem(long Ticks, int Length);
/// <summary>
/// 收到数据
/// </summary>
/// <param name="flowType"></param>
/// <param name="length"></param>
public void OnFlow(FlowType flowType, int length)
{
if (flowType == FlowType.Read)
{
Add(this.readQueue, length);
}
else
{
Add(this.writeQueue, length);
}
}
private static void Add(ConcurrentQueue<QueueItem> quques, int length)
{
var ticks = Environment.TickCount64;
while (quques.TryPeek(out var item))
{
if (ticks - item.Ticks < INTERVAL_SECONDS * 1000)
{
break;
}
else
{
quques.TryDequeue(out _);
}
}
quques.Enqueue(new QueueItem(ticks, length));
}
/// <summary>
/// 获取速率
/// </summary>
/// <returns></returns>
public FlowRate GetFlowRate()
{
var readRate = (double)this.readQueue.Sum(item => item.Length) / INTERVAL_SECONDS;
var writeRate = (double)this.writeQueue.Sum(item => item.Length) / INTERVAL_SECONDS;
return new FlowRate { ReadRate = readRate, WriteRate = writeRate };
}
}
}

View File

@ -0,0 +1,9 @@
namespace FastGithub.FlowAnalyze
{
public record FlowRate
{
public double ReadRate { get; init; }
public double WriteRate { get; init; }
}
}

View File

@ -0,0 +1,18 @@
namespace FastGithub.FlowAnalyze
{
/// <summary>
/// 流量类型
/// </summary>
public enum FlowType
{
/// <summary>
/// 读取
/// </summary>
Read,
/// <summary>
/// 写入
/// </summary>
Wirte
}
}

View File

@ -0,0 +1,21 @@
namespace FastGithub.FlowAnalyze
{
/// <summary>
/// 流量分析器
/// </summary>
public interface IFlowAnalyzer
{
/// <summary>
/// 收到数据
/// </summary>
/// <param name="flowType"></param>
/// <param name="length"></param>
void OnFlow(FlowType flowType, int length);
/// <summary>
/// 获取速率
/// </summary>
/// <returns></returns>
FlowRate GetFlowRate();
}
}

View File

@ -0,0 +1,37 @@
using FastGithub.FlowAnalyze;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
namespace FastGithub
{
/// <summary>
/// ListenOptions扩展
/// </summary>
public static class ListenOptionsExtensions
{
/// <summary>
/// 使用流量分析中间件
/// </summary>
/// <param name="listen"></param>
/// <returns></returns>
public static ListenOptions UseFlowAnalyze(this ListenOptions listen)
{
var flowAnalyzer = listen.ApplicationServices.GetRequiredService<IFlowAnalyzer>();
listen.Use(next => async context =>
{
var oldTransport = context.Transport;
try
{
await using var loggingDuplexPipe = new FlowAnalyzeDuplexPipe(context.Transport, flowAnalyzer);
context.Transport = loggingDuplexPipe;
await next(context);
}
finally
{
context.Transport = oldTransport;
}
});
return listen;
}
}
}

View File

@ -0,0 +1,21 @@
using FastGithub.FlowAnalyze;
using Microsoft.Extensions.DependencyInjection;
namespace FastGithub
{
/// <summary>
/// ServiceCollection扩展
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// 添加流量分析
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddFlowAnalyze(this IServiceCollection services)
{
return services.AddSingleton<IFlowAnalyzer, FlowAnalyzer>();
}
}
}

View File

@ -0,0 +1,106 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.FlowAnalyze
{
static class TaskToApm
{
/// <summary>
/// Marshals the Task as an IAsyncResult, using the supplied callback and state
/// to implement the APM pattern.
/// </summary>
/// <param name="task">The Task to be marshaled.</param>
/// <param name="callback">The callback to be invoked upon completion.</param>
/// <param name="state">The state to be stored in the IAsyncResult.</param>
/// <returns>An IAsyncResult to represent the task's asynchronous operation.</returns>
public static IAsyncResult Begin(Task task, AsyncCallback? callback, object? state) =>
new TaskAsyncResult(task, state, callback);
/// <summary>Processes an IAsyncResult returned by Begin.</summary>
/// <param name="asyncResult">The IAsyncResult to unwrap.</param>
public static void End(IAsyncResult asyncResult)
{
if (asyncResult is TaskAsyncResult twar)
{
twar._task.GetAwaiter().GetResult();
return;
}
throw new ArgumentNullException();
}
/// <summary>Processes an IAsyncResult returned by Begin.</summary>
/// <param name="asyncResult">The IAsyncResult to unwrap.</param>
public static TResult End<TResult>(IAsyncResult asyncResult)
{
if (asyncResult is TaskAsyncResult twar && twar._task is Task<TResult> task)
{
return task.GetAwaiter().GetResult();
}
throw new ArgumentNullException();
}
/// <summary>Provides a simple IAsyncResult that wraps a Task.</summary>
/// <remarks>
/// We could use the Task as the IAsyncResult if the Task's AsyncState is the same as the object state,
/// but that's very rare, in particular in a situation where someone cares about allocation, and always
/// using TaskAsyncResult simplifies things and enables additional optimizations.
/// </remarks>
internal sealed class TaskAsyncResult : IAsyncResult
{
/// <summary>The wrapped Task.</summary>
internal readonly Task _task;
/// <summary>Callback to invoke when the wrapped task completes.</summary>
private readonly AsyncCallback? _callback;
/// <summary>Initializes the IAsyncResult with the Task to wrap and the associated object state.</summary>
/// <param name="task">The Task to wrap.</param>
/// <param name="state">The new AsyncState value.</param>
/// <param name="callback">Callback to invoke when the wrapped task completes.</param>
internal TaskAsyncResult(Task task, object? state, AsyncCallback? callback)
{
Debug.Assert(task != null);
_task = task;
AsyncState = state;
if (task.IsCompleted)
{
// Synchronous completion. Invoke the callback. No need to store it.
CompletedSynchronously = true;
callback?.Invoke(this);
}
else if (callback != null)
{
// Asynchronous completion, and we have a callback; schedule it. We use OnCompleted rather than ContinueWith in
// order to avoid running synchronously if the task has already completed by the time we get here but still run
// synchronously as part of the task's completion if the task completes after (the more common case).
_callback = callback;
_task.ConfigureAwait(continueOnCapturedContext: false)
.GetAwaiter()
.OnCompleted(InvokeCallback); // allocates a delegate, but avoids a closure
}
}
/// <summary>Invokes the callback.</summary>
private void InvokeCallback()
{
Debug.Assert(!CompletedSynchronously);
Debug.Assert(_callback != null);
_callback.Invoke(this);
}
/// <summary>Gets a user-defined object that qualifies or contains information about an asynchronous operation.</summary>
public object? AsyncState { get; }
/// <summary>Gets a value that indicates whether the asynchronous operation completed synchronously.</summary>
/// <remarks>This is set lazily based on whether the <see cref="_task"/> has completed by the time this object is created.</remarks>
public bool CompletedSynchronously { get; }
/// <summary>Gets a value that indicates whether the asynchronous operation has completed.</summary>
public bool IsCompleted => _task.IsCompleted;
/// <summary>Gets a <see cref="WaitHandle"/> that is used to wait for an asynchronous operation to complete.</summary>
public WaitHandle AsyncWaitHandle => ((IAsyncResult)_task).AsyncWaitHandle;
}
}
}

View File

@ -12,6 +12,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FastGithub.FlowAnalyze\FastGithub.FlowAnalyze.csproj" />
<ProjectReference Include="..\FastGithub.Http\FastGithub.Http.csproj" />
</ItemGroup>

View File

@ -91,10 +91,14 @@ namespace FastGithub
certService.InstallAndTrustCaCert();
var httpsPort = ReverseProxyPort.Https;
kestrel.Listen(IPAddress.Loopback, httpsPort,
listen => listen.UseHttps(https =>
https.ServerCertificateSelector = (ctx, domain) =>
certService.GetOrCreateServerCert(domain)));
kestrel.Listen(IPAddress.Loopback, httpsPort, listen =>
{
listen.UseFlowAnalyze();
listen.UseHttps(https =>
{
https.ServerCertificateSelector = (ctx, domain) => certService.GetOrCreateServerCert(domain);
});
});
if (OperatingSystem.IsWindows())
{

View File

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows;
namespace FastGithub.UI
{
@ -12,6 +6,6 @@ namespace FastGithub.UI
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
{
}
}

View File

@ -9,94 +9,6 @@
<Window.Resources>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="DataGrid">
<!--网格线颜色-->
<Setter Property="CanUserResizeColumns" Value="false"/>
<Setter Property="Background" Value="#ddd" />
<Setter Property="BorderBrush" Value="#ddd" />
<Setter Property="Margin" Value="1 0 1 1" />
<Setter Property="HorizontalGridLinesBrush">
<Setter.Value>
<SolidColorBrush Color="#eee"/>
</Setter.Value>
</Setter>
<Setter Property="VerticalGridLinesBrush">
<Setter.Value>
<SolidColorBrush Color="#eee"/>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="DataGridColumnHeader">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="MinHeight" Value="28" />
<Setter Property="Foreground" Value="#000" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridColumnHeader">
<Border x:Name="BackgroundBorder" BorderThickness="0,0,0,1" BorderBrush="#eee" Width="Auto">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentPresenter Margin="0,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<Path x:Name="SortArrow" Visibility="Collapsed" Data="M0,0 L1,0 0.5,1 z" Stretch="Fill" Grid.Column="2" Width="8" Height="6" Fill="White" Margin="0,0,50,0" VerticalAlignment="Center" RenderTransformOrigin="1,1" />
<Rectangle Width="1" Fill="#eee" HorizontalAlignment="Right" Grid.ColumnSpan="1" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Height" Value="30"/>
</Style>
<!--行样式触发-->
<!--背景色改变必须先设置cellStyle 因为cellStyle会覆盖rowStyle样式-->
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="#F2F2F2" />
<Setter Property="MinHeight" Value="25"/>
<Setter Property="Foreground" Value="Black" />
<Style.Triggers>
<!--隔行换色-->
<Trigger Property="AlternationIndex" Value="0" >
<Setter Property="Background" Value="White" />
</Trigger>
<Trigger Property="AlternationIndex" Value="1" >
<Setter Property="Background" Value="#FFEEEEEE" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightGray"/>
</Trigger>
<Trigger Property= "IsSelected" Value="True">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Background" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
<!--单元格样式触发-->
<Style TargetType="DataGridCell">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<TextBlock TextAlignment="Left" VerticalAlignment="Center" TextWrapping="Wrap" >
<ContentPresenter />
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Black"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="TabControlWithUnderLineStyle" TargetType="{x:Type TabControl}">
<Setter Property="Padding" Value="2"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
@ -230,21 +142,9 @@
</Window.Resources>
<Grid Background="#00ffffff">
<TabControl Style="{StaticResource TabControlWithUnderLineStyle}" Foreground="Black" Background="Transparent" BorderBrush="Transparent" BorderThickness="0">
<TabItem Style="{StaticResource TabItemExWithUnderLineStyle}" Cursor="Hand" Header="日志消息" Height="40" Width="100" Margin="5 0" FontSize="18">
<TabItem Style="{StaticResource TabItemExWithUnderLineStyle}" Cursor="Hand" Header="流量监控" Height="40" Width="100" Margin="5 0" FontSize="18">
<Grid Background="#ddd">
<DataGrid Name="dataGrid" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="False" AlternationCount="2">
<DataGrid.Columns>
<DataGridTextColumn Header="时间" Width="160" Binding="{Binding Time}"/>
<DataGridTextColumn Header="消息来源" Width="250" Binding="{Binding Source}"/>
<DataGridTextColumn Header="消息内容" Width="*" Binding="{Binding Message}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<WebBrowser Source="http://127.0.0.1/flow.html" />
</Grid>
</TabItem>
@ -256,13 +156,13 @@
<TabItem Style="{StaticResource TabItemExWithUnderLineStyle}" Cursor="Hand" Header="问题反馈" Height="40" Width="100" Margin="5 0" FontSize="18">
<Grid Background="#ddd">
<TextBlock Text="Mv电台" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</TabItem>
<TabItem Style="{StaticResource TabItemExWithUnderLineStyle}" Cursor="Hand" Header="一支华子" Height="40" Width="100" Margin="5 0" FontSize="18">
<Grid Background="#ddd">
<TextBlock Text="Mv电台" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</TabItem>
</TabControl>

View File

@ -1,17 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows;
namespace FastGithub.UI
{

View File

@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.PacketIntercept"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastGithub.UI", "FastGithub.UI\FastGithub.UI.csproj", "{5082061F-38D5-4F50-945E-791C85B9BDB5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastGithub.FlowAnalyze", "FastGithub.FlowAnalyze\FastGithub.FlowAnalyze.csproj", "{93478EAF-739C-47DA-B8FE-AEBA78A75E11}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -51,6 +53,10 @@ Global
{5082061F-38D5-4F50-945E-791C85B9BDB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5082061F-38D5-4F50-945E-791C85B9BDB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5082061F-38D5-4F50-945E-791C85B9BDB5}.Release|Any CPU.Build.0 = Release|Any CPU
{93478EAF-739C-47DA-B8FE-AEBA78A75E11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{93478EAF-739C-47DA-B8FE-AEBA78A75E11}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93478EAF-739C-47DA-B8FE-AEBA78A75E11}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93478EAF-739C-47DA-B8FE-AEBA78A75E11}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -19,8 +19,7 @@
<ItemGroup>
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Network" Version="2.0.2.68" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.2.0" />
</ItemGroup>
@ -46,6 +45,9 @@
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\flow.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\cert.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View File

@ -2,10 +2,8 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Sinks.Network;
using System;
using System.IO;
using System.Net;
namespace FastGithub
{
@ -72,7 +70,6 @@ namespace FastGithub
.ReadFrom.Configuration(hosting.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: template)
.WriteTo.UDPSink(IPAddress.Loopback, 38457)
.WriteTo.File(Path.Combine("logs", @"log.txt"), rollingInterval: RollingInterval.Day, outputTemplate: template);
});
});

View File

@ -1,5 +1,7 @@
using FastGithub.Configuration;
using FastGithub.FlowAnalyze;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -36,6 +38,7 @@ namespace FastGithub
services.AddDomainResolve();
services.AddHttpClient();
services.AddReverseProxy();
services.AddFlowAnalyze();
services.AddHostedService<AppHostedService>();
if (OperatingSystem.IsWindows())
@ -62,12 +65,20 @@ namespace FastGithub
appBuilder.UseHttpReverseProxy();
app.UseStaticFiles();
appBuilder.UseRouting();
appBuilder.UseEndpoints(endpoint => endpoint.MapFallback(context =>
appBuilder.UseRouting();
appBuilder.UseEndpoints(endpoint =>
{
context.Response.Redirect("https://github.com/dotnetcore/fastgithub");
return Task.CompletedTask;
}));
endpoint.MapGet("/flowRates", context =>
{
var flowRate = context.RequestServices.GetRequiredService<IFlowAnalyzer>().GetFlowRate();
return context.Response.WriteAsJsonAsync(flowRate);
});
endpoint.MapFallback(context =>
{
context.Response.Redirect("https://github.com/dotnetcore/fastgithub");
return Task.CompletedTask;
});
});
});
}
}

View File

@ -4,7 +4,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" crossorigin="anonymous">
<link href="libs/bootstrap.min.css" rel="stylesheet" />
<title>证书验证</title>
<style type="text/css">
body {

View File

@ -0,0 +1,95 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<script src="libs/echarts.js"></script>
<script src="libs/jquery.min.js"></script>
<title>流量监控</title>
<style type="text/css">
html, body, #chart {
height: 100%;
}
body {
overflow: hidden;
background-color: #ddd;
}
</style>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript">
var timeCategory = [];
var readCategory = [];
var wirteCategory = [];
var option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['上行速率(KB/s)', '下行速率(KB/s)']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: timeCategory
},
yAxis: {
type: 'value'
},
series: [
{
name: '上行速率(KB/s)',
type: 'line',
stack: 'Total',
smooth: true,
data: readCategory
},
{
name: '下行速率(KB/s)',
type: 'line',
stack: 'Total',
smooth: true,
data: wirteCategory
}
]
};
var flowChart = echarts.init(document.getElementById('chart'));
flowChart.setOption(option);
window.onresize = function () {
flowChart.resize();
}
setInterval(function () {
$.getJSON('/flowRates?r=' + Math.random(), function (data) {
var time = new Date().toLocaleTimeString().replace(/^\D*/, '');
timeCategory.push(time);
readCategory.push((data.readRate / 1024).toFixed(2));
wirteCategory.push((data.writeRate / 1024).toFixed(2));
if (timeCategory.length > 60) {
timeCategory.shift();
readCategory.shift();
wirteCategory.shift();
}
flowChart.setOption(option);
});
}, 1000);
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

5
FastGithub/wwwroot/libs/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long