mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-16 11:42:22 +08:00
Refactor room stats
This commit is contained in:
parent
95e4f1d5dd
commit
42cbf55fe2
|
@ -4,25 +4,43 @@ namespace BililiveRecorder.Core.Event
|
||||||
{
|
{
|
||||||
public class IOStatsEventArgs : EventArgs
|
public class IOStatsEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间的开始时间
|
||||||
|
/// </summary>
|
||||||
public DateTimeOffset StartTime { get; set; }
|
public DateTimeOffset StartTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间的结束时间
|
||||||
|
/// </summary>
|
||||||
public DateTimeOffset EndTime { get; set; }
|
public DateTimeOffset EndTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间的时长
|
||||||
|
/// </summary>
|
||||||
public TimeSpan Duration { get; set; }
|
public TimeSpan Duration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载了的数据量
|
||||||
|
/// </summary>
|
||||||
public int NetworkBytesDownloaded { get; set; }
|
public int NetworkBytesDownloaded { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// mibi-bits per seconds
|
/// 平均下载速度,mibi-bits per second
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double NetworkMbps { get; set; }
|
public double NetworkMbps { get; set; }
|
||||||
|
|
||||||
public TimeSpan DiskWriteTime { get; set; }
|
/// <summary>
|
||||||
|
/// 统计区间内的磁盘写入耗时
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan DiskWriteDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统计区间内写入磁盘的数据量
|
||||||
|
/// </summary>
|
||||||
public int DiskBytesWritten { get; set; }
|
public int DiskBytesWritten { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// mibi-bytes per seconds
|
/// 平均写入速度,mibi-bytes per second
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double DiskMBps { get; set; }
|
public double DiskMBps { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,28 +4,103 @@ namespace BililiveRecorder.Core.Event
|
||||||
{
|
{
|
||||||
public class RecordingStatsEventArgs : EventArgs
|
public class RecordingStatsEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public long InputVideoByteCount { get; set; }
|
/// <summary>
|
||||||
public long InputAudioByteCount { get; set; }
|
/// 从录制开始到现在一共经过的时间,毫秒
|
||||||
|
/// </summary>
|
||||||
|
public double SessionDuration { get; set; }
|
||||||
|
|
||||||
public int OutputVideoFrameCount { get; set; }
|
/// <summary>
|
||||||
public int OutputAudioFrameCount { get; set; }
|
/// 总接受字节数
|
||||||
public long OutputVideoByteCount { get; set; }
|
/// </summary>
|
||||||
public long OutputAudioByteCount { get; set; }
|
public long TotalInputBytes { get; set; }
|
||||||
|
|
||||||
public long TotalInputVideoByteCount { get; set; }
|
/// <summary>
|
||||||
public long TotalInputAudioByteCount { get; set; }
|
/// 总写入字节数
|
||||||
|
/// </summary>
|
||||||
public int TotalOutputVideoFrameCount { get; set; }
|
public long TotalOutputBytes { get; set; }
|
||||||
public int TotalOutputAudioFrameCount { get; set; }
|
|
||||||
public long TotalOutputVideoByteCount { get; set; }
|
|
||||||
public long TotalOutputAudioByteCount { get; set; }
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前文件的大小
|
||||||
|
/// </summary>
|
||||||
public long CurrentFileSize { get; set; }
|
public long CurrentFileSize { get; set; }
|
||||||
|
|
||||||
public double AddedDuration { get; set; }
|
/// <summary>
|
||||||
public double PassedTime { get; set; }
|
/// 本次直播流收到的最大时间戳(已修复过,相当于总时长,毫秒)
|
||||||
public double DurationRatio { get; set; }
|
/// </summary>
|
||||||
public int SessionMaxTimestamp { get; set; }
|
public int SessionMaxTimestamp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前文件的最大时间戳(相当于总时长,毫秒)
|
||||||
|
/// </summary>
|
||||||
public int FileMaxTimestamp { get; set; }
|
public int FileMaxTimestamp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前这一个统计区间的直播数据时长,毫秒
|
||||||
|
/// </summary>
|
||||||
|
public double AddedDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前这一个统计区间所经过的时间长度,毫秒
|
||||||
|
/// </summary>
|
||||||
|
public double PassedTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 录制速度比例
|
||||||
|
/// </summary>
|
||||||
|
public double DurationRatio { get; set; }
|
||||||
|
|
||||||
|
//----------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新收到的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long InputVideoBytes { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新收到的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long InputAudioBytes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的视频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int OutputVideoFrames { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的音频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int OutputAudioFrames { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long OutputVideoBytes { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long OutputAudioBytes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总共收到的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalInputVideoBytes { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共收到的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalInputAudioBytes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的视频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int TotalOutputVideoFrames { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的音频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int TotalOutputAudioFrames { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalOutputVideoBytes { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalOutputAudioBytes { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace BililiveRecorder.Core
|
||||||
bool Streaming { get; }
|
bool Streaming { get; }
|
||||||
bool DanmakuConnected { get; }
|
bool DanmakuConnected { get; }
|
||||||
bool AutoRecordForThisSession { get; }
|
bool AutoRecordForThisSession { get; }
|
||||||
RecordingStats Stats { get; }
|
RoomStats Stats { get; }
|
||||||
|
|
||||||
event EventHandler<RecordSessionStartedEventArgs>? RecordSessionStarted;
|
event EventHandler<RecordSessionStartedEventArgs>? RecordSessionStarted;
|
||||||
event EventHandler<RecordSessionEndedEventArgs>? RecordSessionEnded;
|
event EventHandler<RecordSessionEndedEventArgs>? RecordSessionEnded;
|
||||||
|
|
|
@ -13,6 +13,11 @@ namespace BililiveRecorder.Core.ProcessingRules
|
||||||
{
|
{
|
||||||
public const string SkipStatsKey = nameof(SkipStatsKey);
|
public const string SkipStatsKey = nameof(SkipStatsKey);
|
||||||
|
|
||||||
|
public StatsRule(DateTimeOffset? RecordingStart = null)
|
||||||
|
{
|
||||||
|
this.RecordingStart = RecordingStart ?? DateTimeOffset.Now;
|
||||||
|
}
|
||||||
|
|
||||||
public event EventHandler<RecordingStatsEventArgs>? StatsUpdated;
|
public event EventHandler<RecordingStatsEventArgs>? StatsUpdated;
|
||||||
|
|
||||||
public long TotalInputVideoByteCount { get; private set; }
|
public long TotalInputVideoByteCount { get; private set; }
|
||||||
|
@ -29,10 +34,14 @@ namespace BililiveRecorder.Core.ProcessingRules
|
||||||
public int CurrentFileMaxTimestamp { get; private set; }
|
public int CurrentFileMaxTimestamp { get; private set; }
|
||||||
|
|
||||||
public DateTimeOffset LastWriteTime { get; private set; }
|
public DateTimeOffset LastWriteTime { get; private set; }
|
||||||
|
public DateTimeOffset RecordingStart { get; }
|
||||||
|
|
||||||
public void Run(FlvProcessingContext context, Action next)
|
public void Run(FlvProcessingContext context, Action next)
|
||||||
{
|
{
|
||||||
var e = new RecordingStatsEventArgs();
|
var e = new RecordingStatsEventArgs
|
||||||
|
{
|
||||||
|
SessionDuration = (DateTimeOffset.Now - this.RecordingStart).TotalMilliseconds
|
||||||
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
static IEnumerable<PipelineDataAction> FilterDataActions(IEnumerable<PipelineAction> actions)
|
static IEnumerable<PipelineDataAction> FilterDataActions(IEnumerable<PipelineAction> actions)
|
||||||
|
@ -42,11 +51,13 @@ namespace BililiveRecorder.Core.ProcessingRules
|
||||||
yield return dataAction;
|
yield return dataAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.TotalInputVideoByteCount = this.TotalInputVideoByteCount += e.InputVideoByteCount =
|
e.TotalInputVideoBytes = this.TotalInputVideoByteCount += e.InputVideoBytes =
|
||||||
FilterDataActions(context.Actions).ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfVideoData, x => x, x => x);
|
FilterDataActions(context.Actions).ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfVideoData, x => x, x => x);
|
||||||
|
|
||||||
e.TotalInputAudioByteCount = this.TotalInputAudioByteCount += e.InputAudioByteCount =
|
e.TotalInputAudioBytes = this.TotalInputAudioByteCount += e.InputAudioBytes =
|
||||||
FilterDataActions(context.Actions).ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfAudioData, x => x, x => x);
|
FilterDataActions(context.Actions).ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfAudioData, x => x, x => x);
|
||||||
|
|
||||||
|
e.TotalInputBytes = e.TotalInputVideoBytes + e.TotalInputAudioBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
|
@ -82,7 +93,7 @@ namespace BililiveRecorder.Core.ProcessingRules
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
e.PassedTime = (now - this.LastWriteTime).TotalSeconds;
|
e.PassedTime = (now - this.LastWriteTime).TotalMilliseconds;
|
||||||
this.LastWriteTime = now;
|
this.LastWriteTime = now;
|
||||||
e.DurationRatio = e.AddedDuration / e.PassedTime;
|
e.DurationRatio = e.AddedDuration / e.PassedTime;
|
||||||
|
|
||||||
|
@ -93,26 +104,28 @@ namespace BililiveRecorder.Core.ProcessingRules
|
||||||
{
|
{
|
||||||
if (dataActions.Count > 0)
|
if (dataActions.Count > 0)
|
||||||
{
|
{
|
||||||
e.TotalOutputVideoFrameCount = this.TotalOutputVideoFrameCount += e.OutputVideoFrameCount =
|
e.TotalOutputVideoFrames = this.TotalOutputVideoFrameCount += e.OutputVideoFrames =
|
||||||
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.CountVideoTags, x => x, x => x);
|
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.CountVideoTags, x => x, x => x);
|
||||||
|
|
||||||
e.TotalOutputAudioFrameCount = this.TotalOutputAudioFrameCount += e.OutputAudioFrameCount =
|
e.TotalOutputAudioFrames = this.TotalOutputAudioFrameCount += e.OutputAudioFrames =
|
||||||
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.CountAudioTags, x => x, x => x);
|
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.CountAudioTags, x => x, x => x);
|
||||||
|
|
||||||
e.TotalOutputVideoByteCount = this.TotalOutputVideoByteCount += e.OutputVideoByteCount =
|
e.TotalOutputVideoBytes = this.TotalOutputVideoByteCount += e.OutputVideoBytes =
|
||||||
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfVideoDataByNalu, x => x, x => x);
|
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfVideoDataByNalu, x => x, x => x);
|
||||||
|
|
||||||
e.TotalOutputAudioByteCount = this.TotalOutputAudioByteCount += e.OutputAudioByteCount =
|
e.TotalOutputAudioBytes = this.TotalOutputAudioByteCount += e.OutputAudioBytes =
|
||||||
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfAudioData, x => x, x => x);
|
dataActions.ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfAudioData, x => x, x => x);
|
||||||
|
|
||||||
e.CurrentFileSize = this.CurrentFileSize += e.OutputVideoByteCount + e.OutputAudioByteCount;
|
e.TotalOutputBytes = e.TotalOutputAudioBytes + e.TotalOutputVideoBytes;
|
||||||
|
|
||||||
|
e.CurrentFileSize = this.CurrentFileSize += e.OutputVideoBytes + e.OutputAudioBytes;
|
||||||
|
|
||||||
foreach (var action in dataActions)
|
foreach (var action in dataActions)
|
||||||
{
|
{
|
||||||
var tags = action.Tags;
|
var tags = action.Tags;
|
||||||
if (tags.Count > 0)
|
if (tags.Count > 0)
|
||||||
{
|
{
|
||||||
e.AddedDuration += (tags[tags.Count - 1].Timestamp - tags[0].Timestamp) / 1000d;
|
e.AddedDuration += (tags[tags.Count - 1].Timestamp - tags[0].Timestamp);
|
||||||
this.CurrentFileMaxTimestamp = e.FileMaxTimestamp = tags[tags.Count - 1].Timestamp;
|
this.CurrentFileMaxTimestamp = e.FileMaxTimestamp = tags[tags.Count - 1].Timestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ namespace BililiveRecorder.Core.Recording
|
||||||
|
|
||||||
lock (this.ioDiskStatsLock)
|
lock (this.ioDiskStatsLock)
|
||||||
{
|
{
|
||||||
this.ioDiskWriteTime += this.ioDiskStopwatch.Elapsed;
|
this.ioDiskWriteDuration += this.ioDiskStopwatch.Elapsed;
|
||||||
this.ioDiskWrittenBytes += bytesRead;
|
this.ioDiskWrittenBytes += bytesRead;
|
||||||
}
|
}
|
||||||
this.ioDiskStopwatch.Reset();
|
this.ioDiskStopwatch.Reset();
|
||||||
|
|
|
@ -42,7 +42,7 @@ namespace BililiveRecorder.Core.Recording
|
||||||
|
|
||||||
protected Stopwatch ioDiskStopwatch = new();
|
protected Stopwatch ioDiskStopwatch = new();
|
||||||
protected object ioDiskStatsLock = new();
|
protected object ioDiskStatsLock = new();
|
||||||
protected TimeSpan ioDiskWriteTime;
|
protected TimeSpan ioDiskWriteDuration;
|
||||||
protected int ioDiskWrittenBytes;
|
protected int ioDiskWrittenBytes;
|
||||||
private DateTimeOffset ioDiskWarningTimeout;
|
private DateTimeOffset ioDiskWarningTimeout;
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ namespace BililiveRecorder.Core.Recording
|
||||||
private void Timer_Elapsed_TriggerIOStats(object sender, ElapsedEventArgs e)
|
private void Timer_Elapsed_TriggerIOStats(object sender, ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
int networkDownloadBytes, diskWriteBytes;
|
int networkDownloadBytes, diskWriteBytes;
|
||||||
TimeSpan durationDiff, diskWriteTime;
|
TimeSpan durationDiff, diskWriteDuration;
|
||||||
DateTimeOffset startTime, endTime;
|
DateTimeOffset startTime, endTime;
|
||||||
|
|
||||||
|
|
||||||
|
@ -148,15 +148,15 @@ namespace BililiveRecorder.Core.Recording
|
||||||
// disks
|
// disks
|
||||||
lock (this.ioDiskStatsLock) // 锁硬盘统计
|
lock (this.ioDiskStatsLock) // 锁硬盘统计
|
||||||
{
|
{
|
||||||
diskWriteTime = this.ioDiskWriteTime;
|
diskWriteDuration = this.ioDiskWriteDuration;
|
||||||
diskWriteBytes = this.ioDiskWrittenBytes;
|
diskWriteBytes = this.ioDiskWrittenBytes;
|
||||||
this.ioDiskWriteTime = TimeSpan.Zero;
|
this.ioDiskWriteDuration = TimeSpan.Zero;
|
||||||
this.ioDiskWrittenBytes = 0;
|
this.ioDiskWrittenBytes = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var netMbps = networkDownloadBytes * (8d / 1024d / 1024d) / durationDiff.TotalSeconds;
|
var netMbps = networkDownloadBytes * (8d / 1024d / 1024d) / durationDiff.TotalSeconds;
|
||||||
var diskMBps = diskWriteBytes / (1024d * 1024d) / diskWriteTime.TotalSeconds;
|
var diskMBps = diskWriteBytes / (1024d * 1024d) / diskWriteDuration.TotalSeconds;
|
||||||
|
|
||||||
this.OnIOStats(new IOStatsEventArgs
|
this.OnIOStats(new IOStatsEventArgs
|
||||||
{
|
{
|
||||||
|
@ -166,12 +166,12 @@ namespace BililiveRecorder.Core.Recording
|
||||||
EndTime = endTime,
|
EndTime = endTime,
|
||||||
NetworkMbps = netMbps,
|
NetworkMbps = netMbps,
|
||||||
DiskBytesWritten = diskWriteBytes,
|
DiskBytesWritten = diskWriteBytes,
|
||||||
DiskWriteTime = diskWriteTime,
|
DiskWriteDuration = diskWriteDuration,
|
||||||
DiskMBps = diskMBps,
|
DiskMBps = diskMBps,
|
||||||
});
|
});
|
||||||
|
|
||||||
var now = DateTimeOffset.Now;
|
var now = DateTimeOffset.Now;
|
||||||
if (diskWriteBytes > 0 && this.ioDiskWarningTimeout < now && (diskWriteTime.TotalSeconds > 1d || diskMBps < 2d))
|
if (diskWriteBytes > 0 && this.ioDiskWarningTimeout < now && (diskWriteDuration.TotalSeconds > 1d || diskMBps < 2d))
|
||||||
{
|
{
|
||||||
// 硬盘 IO 可能不能满足录播
|
// 硬盘 IO 可能不能满足录播
|
||||||
this.ioDiskWarningTimeout = now + TimeSpan.FromMinutes(2); // 最多每 2 分钟提醒一次
|
this.ioDiskWarningTimeout = now + TimeSpan.FromMinutes(2); // 最多每 2 分钟提醒一次
|
||||||
|
|
|
@ -177,7 +177,7 @@ namespace BililiveRecorder.Core.Recording
|
||||||
|
|
||||||
lock (this.ioDiskStatsLock)
|
lock (this.ioDiskStatsLock)
|
||||||
{
|
{
|
||||||
this.ioDiskWriteTime += this.ioDiskStopwatch.Elapsed;
|
this.ioDiskWriteDuration += this.ioDiskStopwatch.Elapsed;
|
||||||
this.ioDiskWrittenBytes += bytesWritten;
|
this.ioDiskWrittenBytes += bytesWritten;
|
||||||
}
|
}
|
||||||
this.ioDiskStopwatch.Reset();
|
this.ioDiskStopwatch.Reset();
|
||||||
|
@ -257,7 +257,7 @@ namespace BililiveRecorder.Core.Recording
|
||||||
this.splitFileRule.SetSplitBeforeFlag();
|
this.splitFileRule.SetSplitBeforeFlag();
|
||||||
break;
|
break;
|
||||||
case CuttingMode.BySize:
|
case CuttingMode.BySize:
|
||||||
if ((e.CurrentFileSize + (e.OutputVideoByteCount * 1.1) + e.OutputAudioByteCount) / (1024d * 1024d) > this.room.RoomConfig.CuttingNumber)
|
if ((e.CurrentFileSize + (e.OutputVideoBytes * 1.1) + e.OutputAudioBytes) / (1024d * 1024d) > this.room.RoomConfig.CuttingNumber)
|
||||||
this.splitFileRule.SetSplitBeforeFlag();
|
this.splitFileRule.SetSplitBeforeFlag();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
namespace BililiveRecorder.Core
|
|
||||||
{
|
|
||||||
public class RecordingStats : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
private TimeSpan sessionMaxTimestamp;
|
|
||||||
private TimeSpan fileMaxTimestamp;
|
|
||||||
private TimeSpan sessionDuration;
|
|
||||||
private double networkMbps;
|
|
||||||
private long totalInputBytes;
|
|
||||||
private long totalOutputBytes;
|
|
||||||
private double durationRatio;
|
|
||||||
|
|
||||||
public RecordingStats()
|
|
||||||
{
|
|
||||||
this.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TimeSpan SessionDuration { get => this.sessionDuration; set => this.SetField(ref this.sessionDuration, value); }
|
|
||||||
public TimeSpan SessionMaxTimestamp { get => this.sessionMaxTimestamp; set => this.SetField(ref this.sessionMaxTimestamp, value); }
|
|
||||||
public TimeSpan FileMaxTimestamp { get => this.fileMaxTimestamp; set => this.SetField(ref this.fileMaxTimestamp, value); }
|
|
||||||
|
|
||||||
public double DurationRatio { get => this.durationRatio; set => this.SetField(ref this.durationRatio, value); }
|
|
||||||
|
|
||||||
public long TotalInputBytes { get => this.totalInputBytes; set => this.SetField(ref this.totalInputBytes, value); }
|
|
||||||
public long TotalOutputBytes { get => this.totalOutputBytes; set => this.SetField(ref this.totalOutputBytes, value); }
|
|
||||||
|
|
||||||
public double NetworkMbps { get => this.networkMbps; set => this.SetField(ref this.networkMbps, value); }
|
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
this.SessionDuration = TimeSpan.Zero;
|
|
||||||
this.SessionMaxTimestamp = TimeSpan.Zero;
|
|
||||||
this.FileMaxTimestamp = TimeSpan.Zero;
|
|
||||||
this.DurationRatio = double.NaN;
|
|
||||||
this.TotalInputBytes = 0;
|
|
||||||
this.TotalOutputBytes = 0;
|
|
||||||
this.NetworkMbps = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
|
||||||
protected virtual void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
protected bool SetField<T>(ref T location, T value, [CallerMemberName] string propertyName = "")
|
|
||||||
{
|
|
||||||
if (EqualityComparer<T>.Default.Equals(location, value))
|
|
||||||
return false;
|
|
||||||
location = value;
|
|
||||||
this.OnPropertyChanged(propertyName);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -94,7 +94,7 @@ namespace BililiveRecorder.Core
|
||||||
public bool Recording => this.recordTask != null;
|
public bool Recording => this.recordTask != null;
|
||||||
|
|
||||||
public RoomConfig RoomConfig { get; }
|
public RoomConfig RoomConfig { get; }
|
||||||
public RecordingStats Stats { get; } = new RecordingStats();
|
public RoomStats Stats { get; } = new RoomStats();
|
||||||
|
|
||||||
public Guid ObjectId { get; } = Guid.NewGuid();
|
public Guid ObjectId { get; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
@ -353,7 +353,14 @@ namespace BililiveRecorder.Core
|
||||||
{
|
{
|
||||||
this.logger.Verbose("IO stats: {@stats}", e);
|
this.logger.Verbose("IO stats: {@stats}", e);
|
||||||
|
|
||||||
|
this.Stats.StartTime = e.StartTime;
|
||||||
|
this.Stats.EndTime = e.EndTime;
|
||||||
|
this.Stats.Duration = e.Duration;
|
||||||
|
this.Stats.NetworkBytesDownloaded = e.NetworkBytesDownloaded;
|
||||||
this.Stats.NetworkMbps = e.NetworkMbps;
|
this.Stats.NetworkMbps = e.NetworkMbps;
|
||||||
|
this.Stats.DiskWriteDuration = e.DiskWriteDuration;
|
||||||
|
this.Stats.DiskBytesWritten = e.DiskBytesWritten;
|
||||||
|
this.Stats.DiskMBps = e.DiskMBps;
|
||||||
|
|
||||||
IOStats?.Invoke(this, e);
|
IOStats?.Invoke(this, e);
|
||||||
}
|
}
|
||||||
|
@ -363,14 +370,31 @@ namespace BililiveRecorder.Core
|
||||||
{
|
{
|
||||||
this.logger.Verbose("Recording stats: {@stats}", e);
|
this.logger.Verbose("Recording stats: {@stats}", e);
|
||||||
|
|
||||||
var diff = DateTimeOffset.UtcNow - this.recordTaskStartTime;
|
this.Stats.SessionDuration = TimeSpan.FromMilliseconds(e.SessionDuration);
|
||||||
this.Stats.SessionDuration = TimeSpan.FromSeconds(Math.Round(diff.TotalSeconds));
|
this.Stats.TotalInputBytes = e.TotalInputBytes;
|
||||||
this.Stats.FileMaxTimestamp = TimeSpan.FromMilliseconds(e.FileMaxTimestamp);
|
this.Stats.TotalOutputBytes = e.TotalOutputBytes;
|
||||||
|
this.Stats.CurrentFileSize = e.CurrentFileSize;
|
||||||
this.Stats.SessionMaxTimestamp = TimeSpan.FromMilliseconds(e.SessionMaxTimestamp);
|
this.Stats.SessionMaxTimestamp = TimeSpan.FromMilliseconds(e.SessionMaxTimestamp);
|
||||||
|
this.Stats.FileMaxTimestamp = TimeSpan.FromMilliseconds(e.FileMaxTimestamp);
|
||||||
|
this.Stats.AddedDuration = e.AddedDuration;
|
||||||
|
this.Stats.PassedTime = e.PassedTime;
|
||||||
this.Stats.DurationRatio = e.DurationRatio;
|
this.Stats.DurationRatio = e.DurationRatio;
|
||||||
|
|
||||||
this.Stats.TotalInputBytes = e.TotalInputVideoByteCount + e.TotalInputAudioByteCount;
|
this.Stats.InputVideoBytes = e.InputVideoBytes;
|
||||||
this.Stats.TotalOutputBytes = e.TotalOutputVideoByteCount + e.TotalOutputAudioByteCount;
|
this.Stats.InputAudioBytes = e.InputAudioBytes;
|
||||||
|
|
||||||
|
this.Stats.OutputVideoFrames = e.OutputVideoFrames;
|
||||||
|
this.Stats.OutputAudioFrames = e.OutputAudioFrames;
|
||||||
|
this.Stats.OutputVideoBytes = e.OutputVideoBytes;
|
||||||
|
this.Stats.OutputAudioBytes = e.OutputAudioBytes;
|
||||||
|
|
||||||
|
this.Stats.TotalInputVideoBytes = e.TotalInputVideoBytes;
|
||||||
|
this.Stats.TotalInputAudioBytes = e.TotalInputAudioBytes;
|
||||||
|
|
||||||
|
this.Stats.TotalOutputVideoFrames = e.TotalOutputVideoFrames;
|
||||||
|
this.Stats.TotalOutputAudioFrames = e.TotalOutputAudioFrames;
|
||||||
|
this.Stats.TotalOutputVideoBytes = e.TotalOutputVideoBytes;
|
||||||
|
this.Stats.TotalOutputAudioBytes = e.TotalOutputAudioBytes;
|
||||||
|
|
||||||
RecordingStats?.Invoke(this, e);
|
RecordingStats?.Invoke(this, e);
|
||||||
}
|
}
|
||||||
|
|
260
BililiveRecorder.Core/RoomStats.cs
Normal file
260
BililiveRecorder.Core/RoomStats.cs
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
namespace BililiveRecorder.Core
|
||||||
|
{
|
||||||
|
public class RoomStats : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
#region IO Stats Fields
|
||||||
|
private DateTimeOffset ___StartTime;
|
||||||
|
private DateTimeOffset ___EndTime;
|
||||||
|
private TimeSpan ___Duration;
|
||||||
|
private int ___NetworkBytesDownloaded;
|
||||||
|
private double ___NetworkMbps;
|
||||||
|
private TimeSpan ___DiskWriteDuration;
|
||||||
|
private int ___DiskBytesWritten;
|
||||||
|
private double ___DiskMBps;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Recording Stats Fields
|
||||||
|
private TimeSpan ___SessionDuration;
|
||||||
|
private long ___TotalInputBytes;
|
||||||
|
private long ___TotalOutputBytes;
|
||||||
|
private long ___CurrentFileSize;
|
||||||
|
private TimeSpan ___SessionMaxTimestamp;
|
||||||
|
private TimeSpan ___FileMaxTimestamp;
|
||||||
|
private double ___AddedDuration;
|
||||||
|
private double ___PassedTime;
|
||||||
|
private double ___DurationRatio;
|
||||||
|
|
||||||
|
private long ___InputVideoBytes;
|
||||||
|
private long ___InputAudioBytes;
|
||||||
|
|
||||||
|
private int ___OutputVideoFrames;
|
||||||
|
private int ___OutputAudioFrames;
|
||||||
|
private long ___OutputVideoBytes;
|
||||||
|
private long ___OutputAudioBytes;
|
||||||
|
|
||||||
|
private long ___TotalInputVideoBytes;
|
||||||
|
private long ___TotalInputAudioBytes;
|
||||||
|
|
||||||
|
private int ___TotalOutputVideoFrames;
|
||||||
|
private int ___TotalOutputAudioFrames;
|
||||||
|
private long ___TotalOutputVideoBytes;
|
||||||
|
private long ___TotalOutputAudioBytes;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public RoomStats()
|
||||||
|
{
|
||||||
|
this.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IO Stats Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间的开始时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset StartTime { get => this.___StartTime; set => this.SetField(ref this.___StartTime, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间的结束时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset EndTime { get => this.___EndTime; set => this.SetField(ref this.___EndTime, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间的时长
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan Duration { get => this.___Duration; set => this.SetField(ref this.___Duration, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载了的数据量
|
||||||
|
/// </summary>
|
||||||
|
public int NetworkBytesDownloaded { get => this.___NetworkBytesDownloaded; set => this.SetField(ref this.___NetworkBytesDownloaded, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 平均下载速度,mibi-bits per second
|
||||||
|
/// </summary>
|
||||||
|
public double NetworkMbps { get => this.___NetworkMbps; set => this.SetField(ref this.___NetworkMbps, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统计区间内的磁盘写入耗时
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan DiskWriteDuration { get => this.___DiskWriteDuration; set => this.SetField(ref this.___DiskWriteDuration, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统计区间内写入磁盘的数据量
|
||||||
|
/// </summary>
|
||||||
|
public int DiskBytesWritten { get => this.___DiskBytesWritten; set => this.SetField(ref this.___DiskBytesWritten, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 平均写入速度,mibi-bytes per second
|
||||||
|
/// </summary>
|
||||||
|
public double DiskMBps { get => this.___DiskMBps; set => this.SetField(ref this.___DiskMBps, value); }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Recording Stats Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从录制开始到现在一共经过的时间
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan SessionDuration { get => this.___SessionDuration; set => this.SetField(ref this.___SessionDuration, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总接受字节数
|
||||||
|
/// </summary>
|
||||||
|
public long TotalInputBytes { get => this.___TotalInputBytes; set => this.SetField(ref this.___TotalInputBytes, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总写入字节数
|
||||||
|
/// </summary>
|
||||||
|
public long TotalOutputBytes { get => this.___TotalOutputBytes; set => this.SetField(ref this.___TotalOutputBytes, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前文件的大小
|
||||||
|
/// </summary>
|
||||||
|
public long CurrentFileSize { get => this.___CurrentFileSize; set => this.SetField(ref this.___CurrentFileSize, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 本次直播流收到的最大时间戳(已修复过,相当于总时长)
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan SessionMaxTimestamp { get => this.___SessionMaxTimestamp; set => this.SetField(ref this.___SessionMaxTimestamp, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前文件的最大时间戳(相当于总时长)
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan FileMaxTimestamp { get => this.___FileMaxTimestamp; set => this.SetField(ref this.___FileMaxTimestamp, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前这一个统计区间的直播数据时长
|
||||||
|
/// </summary>
|
||||||
|
public double AddedDuration { get => this.___AddedDuration; set => this.SetField(ref this.___AddedDuration, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前这一个统计区间所经过的时间长度
|
||||||
|
/// </summary>
|
||||||
|
public double PassedTime { get => this.___PassedTime; set => this.SetField(ref this.___PassedTime, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 录制速度比例
|
||||||
|
/// </summary>
|
||||||
|
public double DurationRatio { get => this.___DurationRatio; set => this.SetField(ref this.___DurationRatio, value); }
|
||||||
|
|
||||||
|
//----------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新收到的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long InputVideoBytes { get => this.___InputVideoBytes; set => this.SetField(ref this.___InputVideoBytes, value); }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新收到的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long InputAudioBytes { get => this.___InputAudioBytes; set => this.SetField(ref this.___InputAudioBytes, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的视频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int OutputVideoFrames { get => this.___OutputVideoFrames; set => this.SetField(ref this.___OutputVideoFrames, value); }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的音频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int OutputAudioFrames { get => this.___OutputAudioFrames; set => this.SetField(ref this.___OutputAudioFrames, value); }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long OutputVideoBytes { get => this.___OutputVideoBytes; set => this.SetField(ref this.___OutputVideoBytes, value); }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long OutputAudioBytes { get => this.___OutputAudioBytes; set => this.SetField(ref this.___OutputAudioBytes, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总共收到的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalInputVideoBytes { get => this.___TotalInputVideoBytes; set => this.SetField(ref this.___TotalInputVideoBytes, value); }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共收到的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalInputAudioBytes { get => this.___TotalInputAudioBytes; set => this.SetField(ref this.___TotalInputAudioBytes, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的视频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int TotalOutputVideoFrames { get => this.___TotalOutputVideoFrames; set => this.SetField(ref this.___TotalOutputVideoFrames, value); }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的音频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int TotalOutputAudioFrames { get => this.___TotalOutputAudioFrames; set => this.SetField(ref this.___TotalOutputAudioFrames, value); }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalOutputVideoBytes { get => this.___TotalOutputVideoBytes; set => this.SetField(ref this.___TotalOutputVideoBytes, value); }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalOutputAudioBytes { get => this.___TotalOutputAudioBytes; set => this.SetField(ref this.___TotalOutputAudioBytes, value); }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
this.SessionDuration = TimeSpan.Zero;
|
||||||
|
this.SessionMaxTimestamp = TimeSpan.Zero;
|
||||||
|
this.FileMaxTimestamp = TimeSpan.Zero;
|
||||||
|
//this.DurationRatio = double.NaN;
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
this.StartTime = default;
|
||||||
|
this.EndTime = default;
|
||||||
|
this.Duration = default;
|
||||||
|
this.NetworkBytesDownloaded = default;
|
||||||
|
this.NetworkMbps = default;
|
||||||
|
this.DiskWriteDuration = default;
|
||||||
|
this.DiskBytesWritten = default;
|
||||||
|
this.DiskMBps = default;
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
this.SessionDuration = default;
|
||||||
|
this.TotalInputBytes = default;
|
||||||
|
this.TotalOutputBytes = default;
|
||||||
|
this.CurrentFileSize = default;
|
||||||
|
this.SessionMaxTimestamp = default;
|
||||||
|
this.FileMaxTimestamp = default;
|
||||||
|
this.AddedDuration = default;
|
||||||
|
this.PassedTime = default;
|
||||||
|
this.DurationRatio = double.NaN;
|
||||||
|
|
||||||
|
this.InputVideoBytes = default;
|
||||||
|
this.InputAudioBytes = default;
|
||||||
|
|
||||||
|
this.OutputVideoFrames = default;
|
||||||
|
this.OutputAudioFrames = default;
|
||||||
|
this.OutputVideoBytes = default;
|
||||||
|
this.OutputAudioBytes = default;
|
||||||
|
|
||||||
|
this.TotalInputVideoBytes = default;
|
||||||
|
this.TotalInputAudioBytes = default;
|
||||||
|
|
||||||
|
this.TotalOutputVideoFrames = default;
|
||||||
|
this.TotalOutputAudioFrames = default;
|
||||||
|
this.TotalOutputVideoBytes = default;
|
||||||
|
this.TotalOutputAudioBytes = default;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
protected virtual void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
protected bool SetField<T>(ref T location, T value, [CallerMemberName] string propertyName = "")
|
||||||
|
{
|
||||||
|
if (EqualityComparer<T>.Default.Equals(location, value))
|
||||||
|
return false;
|
||||||
|
location = value;
|
||||||
|
this.OnPropertyChanged(propertyName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -152,12 +152,12 @@ namespace BililiveRecorder.Web.Api
|
||||||
[HttpGet("{roomId:int}/stats")]
|
[HttpGet("{roomId:int}/stats")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
|
||||||
public ActionResult<RoomStatsDto> GetRoomStats(int roomId)
|
public ActionResult<RoomRecordingStatsDto> GetRoomStats(int roomId)
|
||||||
{
|
{
|
||||||
var room = this.FetchRoom(roomId);
|
var room = this.FetchRoom(roomId);
|
||||||
if (room is null)
|
if (room is null)
|
||||||
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
|
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
|
||||||
return this.mapper.Map<RoomStatsDto>(room.Stats);
|
return this.mapper.Map<RoomRecordingStatsDto>(room.Stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -168,12 +168,12 @@ namespace BililiveRecorder.Web.Api
|
||||||
[HttpGet("{objectId:guid}/stats")]
|
[HttpGet("{objectId:guid}/stats")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
|
||||||
public ActionResult<RoomStatsDto> GetRoomStats(Guid objectId)
|
public ActionResult<RoomRecordingStatsDto> GetRoomStats(Guid objectId)
|
||||||
{
|
{
|
||||||
var room = this.FetchRoom(objectId);
|
var room = this.FetchRoom(objectId);
|
||||||
if (room is null)
|
if (room is null)
|
||||||
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
|
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
|
||||||
return this.mapper.Map<RoomStatsDto>(room.Stats);
|
return this.mapper.Map<RoomRecordingStatsDto>(room.Stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace BililiveRecorder.Web
|
||||||
public event EventHandler<AggregatedRoomEventArgs<RecordSessionEndedEventArgs>>? RecordSessionEnded;
|
public event EventHandler<AggregatedRoomEventArgs<RecordSessionEndedEventArgs>>? RecordSessionEnded;
|
||||||
public event EventHandler<AggregatedRoomEventArgs<RecordFileOpeningEventArgs>>? RecordFileOpening;
|
public event EventHandler<AggregatedRoomEventArgs<RecordFileOpeningEventArgs>>? RecordFileOpening;
|
||||||
public event EventHandler<AggregatedRoomEventArgs<RecordFileClosedEventArgs>>? RecordFileClosed;
|
public event EventHandler<AggregatedRoomEventArgs<RecordFileClosedEventArgs>>? RecordFileClosed;
|
||||||
public event EventHandler<AggregatedRoomEventArgs<NetworkingStatsEventArgs>>? NetworkingStats;
|
public event EventHandler<AggregatedRoomEventArgs<IOStatsEventArgs>>? IOStats;
|
||||||
public event EventHandler<AggregatedRoomEventArgs<RecordingStatsEventArgs>>? RecordingStats;
|
public event EventHandler<AggregatedRoomEventArgs<RecordingStatsEventArgs>>? RecordingStats;
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
#pragma warning restore CS0067
|
#pragma warning restore CS0067
|
||||||
|
|
20
BililiveRecorder.Web/Models/Graphql/IOStatsType.cs
Normal file
20
BililiveRecorder.Web/Models/Graphql/IOStatsType.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using BililiveRecorder.Core;
|
||||||
|
using GraphQL.Types;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Models.Graphql
|
||||||
|
{
|
||||||
|
public class IOStatsType : ObjectGraphType<RoomStats>
|
||||||
|
{
|
||||||
|
public IOStatsType()
|
||||||
|
{
|
||||||
|
this.Field(x => x.StartTime);
|
||||||
|
this.Field(x => x.EndTime);
|
||||||
|
this.Field(x => x.Duration, type: typeof(TimeSpanMillisecondsGraphType));
|
||||||
|
this.Field(x => x.NetworkBytesDownloaded);
|
||||||
|
this.Field(x => x.NetworkMbps);
|
||||||
|
this.Field(x => x.DiskWriteDuration, type: typeof(TimeSpanMillisecondsGraphType));
|
||||||
|
this.Field(x => x.DiskBytesWritten);
|
||||||
|
this.Field(x => x.DiskMBps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,17 +3,31 @@ using GraphQL.Types;
|
||||||
|
|
||||||
namespace BililiveRecorder.Web.Models.Graphql
|
namespace BililiveRecorder.Web.Models.Graphql
|
||||||
{
|
{
|
||||||
public class RecordingStatsType : ObjectGraphType<RecordingStats>
|
public class RecordingStatsType : ObjectGraphType<RoomStats>
|
||||||
{
|
{
|
||||||
public RecordingStatsType()
|
public RecordingStatsType()
|
||||||
{
|
{
|
||||||
this.Field(x => x.NetworkMbps);
|
this.Field(x => x.SessionDuration, type: typeof(TimeSpanMillisecondsGraphType));
|
||||||
this.Field(x => x.SessionDuration);
|
|
||||||
this.Field(x => x.FileMaxTimestamp);
|
|
||||||
this.Field(x => x.SessionMaxTimestamp);
|
|
||||||
this.Field(x => x.DurationRatio);
|
|
||||||
this.Field(x => x.TotalInputBytes);
|
this.Field(x => x.TotalInputBytes);
|
||||||
this.Field(x => x.TotalOutputBytes);
|
this.Field(x => x.TotalOutputBytes);
|
||||||
|
this.Field(x => x.CurrentFileSize);
|
||||||
|
this.Field(x => x.SessionMaxTimestamp, type: typeof(TimeSpanMillisecondsGraphType));
|
||||||
|
this.Field(x => x.FileMaxTimestamp, type: typeof(TimeSpanMillisecondsGraphType));
|
||||||
|
this.Field(x => x.AddedDuration);
|
||||||
|
this.Field(x => x.PassedTime);
|
||||||
|
this.Field(x => x.DurationRatio);
|
||||||
|
this.Field(x => x.InputVideoBytes);
|
||||||
|
this.Field(x => x.InputAudioBytes);
|
||||||
|
this.Field(x => x.OutputVideoFrames);
|
||||||
|
this.Field(x => x.OutputAudioFrames);
|
||||||
|
this.Field(x => x.OutputVideoBytes);
|
||||||
|
this.Field(x => x.OutputAudioBytes);
|
||||||
|
this.Field(x => x.TotalInputVideoBytes);
|
||||||
|
this.Field(x => x.TotalInputAudioBytes);
|
||||||
|
this.Field(x => x.TotalOutputVideoFrames);
|
||||||
|
this.Field(x => x.TotalOutputAudioFrames);
|
||||||
|
this.Field(x => x.TotalOutputVideoBytes);
|
||||||
|
this.Field(x => x.TotalOutputAudioBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,8 @@ namespace BililiveRecorder.Web.Models.Graphql
|
||||||
this.Field(x => x.Streaming);
|
this.Field(x => x.Streaming);
|
||||||
this.Field(x => x.AutoRecordForThisSession);
|
this.Field(x => x.AutoRecordForThisSession);
|
||||||
this.Field(x => x.DanmakuConnected);
|
this.Field(x => x.DanmakuConnected);
|
||||||
this.Field(x => x.Stats, type: typeof(RecordingStatsType));
|
this.Field("ioStats", x => x.Stats, type: typeof(IOStatsType));
|
||||||
|
this.Field("recordingStats", x => x.Stats, type: typeof(RecordingStatsType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using BililiveRecorder.Core;
|
using BililiveRecorder.Core;
|
||||||
using BililiveRecorder.Core.Config.V3;
|
using BililiveRecorder.Core.Config.V3;
|
||||||
|
using BililiveRecorder.Core.Event;
|
||||||
|
|
||||||
namespace BililiveRecorder.Web.Models.Rest
|
namespace BililiveRecorder.Web.Models.Rest
|
||||||
{
|
{
|
||||||
|
@ -11,14 +12,23 @@ namespace BililiveRecorder.Web.Models.Rest
|
||||||
this.CreateMap<IRoom, RoomDto>()
|
this.CreateMap<IRoom, RoomDto>()
|
||||||
.ForMember(x => x.RoomId, x => x.MapFrom(s => s.RoomConfig.RoomId))
|
.ForMember(x => x.RoomId, x => x.MapFrom(s => s.RoomConfig.RoomId))
|
||||||
.ForMember(x => x.AutoRecord, x => x.MapFrom(s => s.RoomConfig.AutoRecord))
|
.ForMember(x => x.AutoRecord, x => x.MapFrom(s => s.RoomConfig.AutoRecord))
|
||||||
|
.ForMember(x => x.IoStats, x => x.MapFrom(s => s.Stats))
|
||||||
|
.ForMember(x => x.RecordingStats, x => x.MapFrom(s => s.Stats))
|
||||||
;
|
;
|
||||||
|
|
||||||
this.CreateMap<RecordingStats, RoomStatsDto>()
|
this.CreateMap<RoomStats, RoomRecordingStatsDto>()
|
||||||
.ForMember(x => x.SessionDuration, x => x.MapFrom(s => s.SessionDuration.TotalMilliseconds))
|
.ForMember(x => x.SessionDuration, x => x.MapFrom(s => s.SessionDuration.TotalMilliseconds))
|
||||||
.ForMember(x => x.SessionMaxTimestamp, x => x.MapFrom(s => s.SessionMaxTimestamp.TotalMilliseconds))
|
.ForMember(x => x.SessionMaxTimestamp, x => x.MapFrom(s => s.SessionMaxTimestamp.TotalMilliseconds))
|
||||||
.ForMember(x => x.FileMaxTimestamp, x => x.MapFrom(s => s.FileMaxTimestamp.TotalMilliseconds))
|
.ForMember(x => x.FileMaxTimestamp, x => x.MapFrom(s => s.FileMaxTimestamp.TotalMilliseconds))
|
||||||
;
|
;
|
||||||
|
|
||||||
|
this.CreateMap<RoomStats, RoomIOStatsDto>()
|
||||||
|
.ForMember(x => x.Duration, x => x.MapFrom(s => s.Duration.TotalMilliseconds))
|
||||||
|
.ForMember(x => x.DiskWriteDuration, x => x.MapFrom(s => s.DiskWriteDuration.TotalMilliseconds))
|
||||||
|
;
|
||||||
|
|
||||||
|
this.CreateMap<RecordingStatsEventArgs, RoomRecordingStatsDto>();
|
||||||
|
|
||||||
this.CreateMap<RoomConfig, RoomConfigDto>();
|
this.CreateMap<RoomConfig, RoomConfigDto>();
|
||||||
|
|
||||||
this.CreateMap<GlobalConfig, GlobalConfigDto>();
|
this.CreateMap<GlobalConfig, GlobalConfigDto>();
|
||||||
|
|
|
@ -16,6 +16,7 @@ namespace BililiveRecorder.Web.Models.Rest
|
||||||
public bool Streaming { get; set; }
|
public bool Streaming { get; set; }
|
||||||
public bool DanmakuConnected { get; set; }
|
public bool DanmakuConnected { get; set; }
|
||||||
public bool AutoRecordForThisSession { get; set; }
|
public bool AutoRecordForThisSession { get; set; }
|
||||||
public RoomStatsDto Stats { get; set; } = default!;
|
public RoomRecordingStatsDto RecordingStats { get; set; } = default!;
|
||||||
|
public RoomIOStatsDto IoStats { get; set; } = default!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
47
BililiveRecorder.Web/Models/Rest/RoomIOStatsDto.cs
Normal file
47
BililiveRecorder.Web/Models/Rest/RoomIOStatsDto.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Web.Models.Rest
|
||||||
|
{
|
||||||
|
public class RoomIOStatsDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间的开始时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset StartTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间的结束时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset EndTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间的时长,毫秒
|
||||||
|
/// </summary>
|
||||||
|
public double Duration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载了的数据量
|
||||||
|
/// </summary>
|
||||||
|
public int NetworkBytesDownloaded { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 平均下载速度,mibi-bits per second
|
||||||
|
/// </summary>
|
||||||
|
public double NetworkMbps { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统计区间内的磁盘写入耗时,毫秒
|
||||||
|
/// </summary>
|
||||||
|
public double DiskWriteDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统计区间内写入磁盘的数据量
|
||||||
|
/// </summary>
|
||||||
|
public int DiskBytesWritten { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 平均写入速度,mibi-bytes per second
|
||||||
|
/// </summary>
|
||||||
|
public double DiskMBps { get; set; }
|
||||||
|
}
|
||||||
|
}
|
104
BililiveRecorder.Web/Models/Rest/RoomRecordingStatsDto.cs
Normal file
104
BililiveRecorder.Web/Models/Rest/RoomRecordingStatsDto.cs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
namespace BililiveRecorder.Web.Models.Rest
|
||||||
|
{
|
||||||
|
public class RoomRecordingStatsDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 从录制开始到现在一共经过的时间,毫秒
|
||||||
|
/// </summary>
|
||||||
|
public double SessionDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总接受字节数
|
||||||
|
/// </summary>
|
||||||
|
public long TotalInputBytes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总写入字节数
|
||||||
|
/// </summary>
|
||||||
|
public long TotalOutputBytes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前文件的大小
|
||||||
|
/// </summary>
|
||||||
|
public long CurrentFileSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 本次直播流收到的最大时间戳(已修复过,相当于总时长,毫秒)
|
||||||
|
/// </summary>
|
||||||
|
public int SessionMaxTimestamp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前文件的最大时间戳(相当于总时长,毫秒)
|
||||||
|
/// </summary>
|
||||||
|
public int FileMaxTimestamp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前这一个统计区间的直播数据时长,毫秒
|
||||||
|
/// </summary>
|
||||||
|
public double AddedDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前这一个统计区间所经过的时间长度,毫秒
|
||||||
|
/// </summary>
|
||||||
|
public double PassedTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 录制速度比例
|
||||||
|
/// </summary>
|
||||||
|
public double DurationRatio { get; set; }
|
||||||
|
|
||||||
|
//----------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新收到的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long InputVideoBytes { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新收到的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long InputAudioBytes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的视频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int OutputVideoFrames { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的音频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int OutputAudioFrames { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long OutputVideoBytes { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 当前统计区间新写入的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long OutputAudioBytes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总共收到的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalInputVideoBytes { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共收到的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalInputAudioBytes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的视频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int TotalOutputVideoFrames { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的音频帧数量
|
||||||
|
/// </summary>
|
||||||
|
public int TotalOutputAudioFrames { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的视频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalOutputVideoBytes { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 总共写入的音频数据大小
|
||||||
|
/// </summary>
|
||||||
|
public long TotalOutputAudioBytes { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
namespace BililiveRecorder.Web.Models.Rest
|
|
||||||
{
|
|
||||||
public class RoomStatsDto
|
|
||||||
{
|
|
||||||
public double SessionDuration { get; set; }
|
|
||||||
public double SessionMaxTimestamp { get; set; }
|
|
||||||
public double FileMaxTimestamp { get; set; }
|
|
||||||
public double DurationRatio { get; set; }
|
|
||||||
public long TotalInputBytes { get; set; }
|
|
||||||
public long TotalOutputBytes { get; set; }
|
|
||||||
public double NetworkMbps { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using AutoMapper;
|
||||||
using BililiveRecorder.Core;
|
using BililiveRecorder.Core;
|
||||||
using BililiveRecorder.Web.Graphql;
|
using BililiveRecorder.Web.Graphql;
|
||||||
using BililiveRecorder.Web.Models.Rest;
|
using BililiveRecorder.Web.Models.Rest;
|
||||||
|
@ -53,6 +54,15 @@ namespace BililiveRecorder.Web
|
||||||
// 实际运行时在 BililiveRecorder.Web.Program 里会加上真的 IRecorder
|
// 实际运行时在 BililiveRecorder.Web.Program 里会加上真的 IRecorder
|
||||||
services.TryAddSingleton<IRecorder>(new FakeRecorderForWeb());
|
services.TryAddSingleton<IRecorder>(new FakeRecorderForWeb());
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
// TODO 移动到一个单独的测试项目里
|
||||||
|
if (Debugger.IsAttached)
|
||||||
|
{
|
||||||
|
var configuration = new MapperConfiguration(cfg => cfg.AddProfile<DataMappingProfile>());
|
||||||
|
configuration.AssertConfigurationIsValid();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
services
|
services
|
||||||
.AddAutoMapper(c => c.AddProfile<DataMappingProfile>())
|
.AddAutoMapper(c => c.AddProfile<DataMappingProfile>())
|
||||||
.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()))
|
.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user