流量监控
This commit is contained in:
parent
29a5e39a60
commit
7c2e717eda
167
FastGithub.FlowAnalyze/DuplexPipeStream.cs
Normal file
167
FastGithub.FlowAnalyze/DuplexPipeStream.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
FastGithub.FlowAnalyze/DuplexPipeStreamAdapter.cs
Normal file
53
FastGithub.FlowAnalyze/DuplexPipeStreamAdapter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
FastGithub.FlowAnalyze/FastGithub.FlowAnalyze.csproj
Normal file
11
FastGithub.FlowAnalyze/FastGithub.FlowAnalyze.csproj
Normal 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>
|
||||
12
FastGithub.FlowAnalyze/FlowAnalyzeDuplexPipe.cs
Normal file
12
FastGithub.FlowAnalyze/FlowAnalyzeDuplexPipe.cs
Normal 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))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
159
FastGithub.FlowAnalyze/FlowAnalyzeStream.cs
Normal file
159
FastGithub.FlowAnalyze/FlowAnalyzeStream.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
FastGithub.FlowAnalyze/FlowAnalyzer.cs
Normal file
60
FastGithub.FlowAnalyze/FlowAnalyzer.cs
Normal 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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
9
FastGithub.FlowAnalyze/FlowRate.cs
Normal file
9
FastGithub.FlowAnalyze/FlowRate.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace FastGithub.FlowAnalyze
|
||||
{
|
||||
public record FlowRate
|
||||
{
|
||||
public double ReadRate { get; init; }
|
||||
|
||||
public double WriteRate { get; init; }
|
||||
}
|
||||
}
|
||||
18
FastGithub.FlowAnalyze/FlowType.cs
Normal file
18
FastGithub.FlowAnalyze/FlowType.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace FastGithub.FlowAnalyze
|
||||
{
|
||||
/// <summary>
|
||||
/// 流量类型
|
||||
/// </summary>
|
||||
public enum FlowType
|
||||
{
|
||||
/// <summary>
|
||||
/// 读取
|
||||
/// </summary>
|
||||
Read,
|
||||
|
||||
/// <summary>
|
||||
/// 写入
|
||||
/// </summary>
|
||||
Wirte
|
||||
}
|
||||
}
|
||||
21
FastGithub.FlowAnalyze/IFlowAnalyzer.cs
Normal file
21
FastGithub.FlowAnalyze/IFlowAnalyzer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
37
FastGithub.FlowAnalyze/ListenOptionsExtensions.cs
Normal file
37
FastGithub.FlowAnalyze/ListenOptionsExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
FastGithub.FlowAnalyze/ServiceCollectionExtensions.cs
Normal file
21
FastGithub.FlowAnalyze/ServiceCollectionExtensions.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
106
FastGithub.FlowAnalyze/TaskToApm.cs
Normal file
106
FastGithub.FlowAnalyze/TaskToApm.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FastGithub.FlowAnalyze\FastGithub.FlowAnalyze.csproj" />
|
||||
<ProjectReference Include="..\FastGithub.Http\FastGithub.Http.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -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())
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -20,7 +20,6 @@
|
||||
<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.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>
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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())
|
||||
@ -63,11 +66,19 @@ namespace FastGithub
|
||||
|
||||
app.UseStaticFiles();
|
||||
appBuilder.UseRouting();
|
||||
appBuilder.UseEndpoints(endpoint => endpoint.MapFallback(context =>
|
||||
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;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
95
FastGithub/wwwroot/flow.html
Normal file
95
FastGithub/wwwroot/flow.html
Normal 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>
|
||||
7
FastGithub/wwwroot/libs/bootstrap.min.css
vendored
Normal file
7
FastGithub/wwwroot/libs/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
34380
FastGithub/wwwroot/libs/echarts.js
Normal file
34380
FastGithub/wwwroot/libs/echarts.js
Normal file
File diff suppressed because it is too large
Load Diff
5
FastGithub/wwwroot/libs/jquery.min.js
vendored
Normal file
5
FastGithub/wwwroot/libs/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user