diff --git a/BililiveRecorder.Core/Event/IOStatsEventArgs.cs b/BililiveRecorder.Core/Event/IOStatsEventArgs.cs
index 428510a..11fa4a2 100644
--- a/BililiveRecorder.Core/Event/IOStatsEventArgs.cs
+++ b/BililiveRecorder.Core/Event/IOStatsEventArgs.cs
@@ -4,25 +4,43 @@ namespace BililiveRecorder.Core.Event
{
public class IOStatsEventArgs : EventArgs
{
+ ///
+ /// 当前统计区间的开始时间
+ ///
public DateTimeOffset StartTime { get; set; }
+ ///
+ /// 当前统计区间的结束时间
+ ///
public DateTimeOffset EndTime { get; set; }
+ ///
+ /// 当前统计区间的时长
+ ///
public TimeSpan Duration { get; set; }
+ ///
+ /// 下载了的数据量
+ ///
public int NetworkBytesDownloaded { get; set; }
///
- /// mibi-bits per seconds
+ /// 平均下载速度,mibi-bits per second
///
public double NetworkMbps { get; set; }
- public TimeSpan DiskWriteTime { get; set; }
+ ///
+ /// 统计区间内的磁盘写入耗时
+ ///
+ public TimeSpan DiskWriteDuration { get; set; }
+ ///
+ /// 统计区间内写入磁盘的数据量
+ ///
public int DiskBytesWritten { get; set; }
///
- /// mibi-bytes per seconds
+ /// 平均写入速度,mibi-bytes per second
///
public double DiskMBps { get; set; }
}
diff --git a/BililiveRecorder.Core/Event/RecordingStatsEventArgs.cs b/BililiveRecorder.Core/Event/RecordingStatsEventArgs.cs
index 439b964..3107b6f 100644
--- a/BililiveRecorder.Core/Event/RecordingStatsEventArgs.cs
+++ b/BililiveRecorder.Core/Event/RecordingStatsEventArgs.cs
@@ -4,28 +4,103 @@ namespace BililiveRecorder.Core.Event
{
public class RecordingStatsEventArgs : EventArgs
{
- public long InputVideoByteCount { get; set; }
- public long InputAudioByteCount { get; set; }
+ ///
+ /// 从录制开始到现在一共经过的时间,毫秒
+ ///
+ public double SessionDuration { get; set; }
- public int OutputVideoFrameCount { get; set; }
- public int OutputAudioFrameCount { get; set; }
- public long OutputVideoByteCount { get; set; }
- public long OutputAudioByteCount { get; set; }
+ ///
+ /// 总接受字节数
+ ///
+ public long TotalInputBytes { get; set; }
- public long TotalInputVideoByteCount { get; set; }
- public long TotalInputAudioByteCount { get; set; }
-
- public int TotalOutputVideoFrameCount { get; set; }
- public int TotalOutputAudioFrameCount { get; set; }
- public long TotalOutputVideoByteCount { get; set; }
- public long TotalOutputAudioByteCount { get; set; }
+ ///
+ /// 总写入字节数
+ ///
+ public long TotalOutputBytes { get; set; }
+ ///
+ /// 当前文件的大小
+ ///
public long CurrentFileSize { get; set; }
- public double AddedDuration { get; set; }
- public double PassedTime { get; set; }
- public double DurationRatio { get; set; }
+ ///
+ /// 本次直播流收到的最大时间戳(已修复过,相当于总时长,毫秒)
+ ///
public int SessionMaxTimestamp { get; set; }
+
+ ///
+ /// 当前文件的最大时间戳(相当于总时长,毫秒)
+ ///
public int FileMaxTimestamp { get; set; }
+
+ ///
+ /// 当前这一个统计区间的直播数据时长,毫秒
+ ///
+ public double AddedDuration { get; set; }
+
+ ///
+ /// 当前这一个统计区间所经过的时间长度,毫秒
+ ///
+ public double PassedTime { get; set; }
+
+ ///
+ /// 录制速度比例
+ ///
+ public double DurationRatio { get; set; }
+
+ //----------------------------------------
+
+ ///
+ /// 当前统计区间新收到的视频数据大小
+ ///
+ public long InputVideoBytes { get; set; }
+ ///
+ /// 当前统计区间新收到的音频数据大小
+ ///
+ public long InputAudioBytes { get; set; }
+
+ ///
+ /// 当前统计区间新写入的视频帧数量
+ ///
+ public int OutputVideoFrames { get; set; }
+ ///
+ /// 当前统计区间新写入的音频帧数量
+ ///
+ public int OutputAudioFrames { get; set; }
+ ///
+ /// 当前统计区间新写入的视频数据大小
+ ///
+ public long OutputVideoBytes { get; set; }
+ ///
+ /// 当前统计区间新写入的音频数据大小
+ ///
+ public long OutputAudioBytes { get; set; }
+
+ ///
+ /// 总共收到的视频数据大小
+ ///
+ public long TotalInputVideoBytes { get; set; }
+ ///
+ /// 总共收到的音频数据大小
+ ///
+ public long TotalInputAudioBytes { get; set; }
+
+ ///
+ /// 总共写入的视频帧数量
+ ///
+ public int TotalOutputVideoFrames { get; set; }
+ ///
+ /// 总共写入的音频帧数量
+ ///
+ public int TotalOutputAudioFrames { get; set; }
+ ///
+ /// 总共写入的视频数据大小
+ ///
+ public long TotalOutputVideoBytes { get; set; }
+ ///
+ /// 总共写入的音频数据大小
+ ///
+ public long TotalOutputAudioBytes { get; set; }
}
}
diff --git a/BililiveRecorder.Core/IRoom.cs b/BililiveRecorder.Core/IRoom.cs
index fb44ce0..3f75644 100644
--- a/BililiveRecorder.Core/IRoom.cs
+++ b/BililiveRecorder.Core/IRoom.cs
@@ -22,7 +22,7 @@ namespace BililiveRecorder.Core
bool Streaming { get; }
bool DanmakuConnected { get; }
bool AutoRecordForThisSession { get; }
- RecordingStats Stats { get; }
+ RoomStats Stats { get; }
event EventHandler? RecordSessionStarted;
event EventHandler? RecordSessionEnded;
diff --git a/BililiveRecorder.Core/ProcessingRules/StatsRule.cs b/BililiveRecorder.Core/ProcessingRules/StatsRule.cs
index 35ef9d4..1fbfb72 100644
--- a/BililiveRecorder.Core/ProcessingRules/StatsRule.cs
+++ b/BililiveRecorder.Core/ProcessingRules/StatsRule.cs
@@ -13,6 +13,11 @@ namespace BililiveRecorder.Core.ProcessingRules
{
public const string SkipStatsKey = nameof(SkipStatsKey);
+ public StatsRule(DateTimeOffset? RecordingStart = null)
+ {
+ this.RecordingStart = RecordingStart ?? DateTimeOffset.Now;
+ }
+
public event EventHandler? StatsUpdated;
public long TotalInputVideoByteCount { get; private set; }
@@ -29,10 +34,14 @@ namespace BililiveRecorder.Core.ProcessingRules
public int CurrentFileMaxTimestamp { get; private set; }
public DateTimeOffset LastWriteTime { get; private set; }
+ public DateTimeOffset RecordingStart { get; }
public void Run(FlvProcessingContext context, Action next)
{
- var e = new RecordingStatsEventArgs();
+ var e = new RecordingStatsEventArgs
+ {
+ SessionDuration = (DateTimeOffset.Now - this.RecordingStart).TotalMilliseconds
+ };
{
static IEnumerable FilterDataActions(IEnumerable actions)
@@ -42,11 +51,13 @@ namespace BililiveRecorder.Core.ProcessingRules
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);
- e.TotalInputAudioByteCount = this.TotalInputAudioByteCount += e.InputAudioByteCount =
+ e.TotalInputAudioBytes = this.TotalInputAudioByteCount += e.InputAudioBytes =
FilterDataActions(context.Actions).ToStructEnumerable().Sum(ref LinqFunctions.SumSizeOfAudioData, x => x, x => x);
+
+ e.TotalInputBytes = e.TotalInputVideoBytes + e.TotalInputAudioBytes;
}
next();
@@ -82,7 +93,7 @@ namespace BililiveRecorder.Core.ProcessingRules
}
var now = DateTimeOffset.UtcNow;
- e.PassedTime = (now - this.LastWriteTime).TotalSeconds;
+ e.PassedTime = (now - this.LastWriteTime).TotalMilliseconds;
this.LastWriteTime = now;
e.DurationRatio = e.AddedDuration / e.PassedTime;
@@ -93,26 +104,28 @@ namespace BililiveRecorder.Core.ProcessingRules
{
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);
- e.TotalOutputAudioFrameCount = this.TotalOutputAudioFrameCount += e.OutputAudioFrameCount =
+ e.TotalOutputAudioFrames = this.TotalOutputAudioFrameCount += e.OutputAudioFrames =
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);
- e.TotalOutputAudioByteCount = this.TotalOutputAudioByteCount += e.OutputAudioByteCount =
+ e.TotalOutputAudioBytes = this.TotalOutputAudioByteCount += e.OutputAudioBytes =
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)
{
var tags = action.Tags;
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;
}
}
diff --git a/BililiveRecorder.Core/Recording/RawDataRecordTask.cs b/BililiveRecorder.Core/Recording/RawDataRecordTask.cs
index 4517b88..37aa620 100644
--- a/BililiveRecorder.Core/Recording/RawDataRecordTask.cs
+++ b/BililiveRecorder.Core/Recording/RawDataRecordTask.cs
@@ -71,7 +71,7 @@ namespace BililiveRecorder.Core.Recording
lock (this.ioDiskStatsLock)
{
- this.ioDiskWriteTime += this.ioDiskStopwatch.Elapsed;
+ this.ioDiskWriteDuration += this.ioDiskStopwatch.Elapsed;
this.ioDiskWrittenBytes += bytesRead;
}
this.ioDiskStopwatch.Reset();
diff --git a/BililiveRecorder.Core/Recording/RecordTaskBase.cs b/BililiveRecorder.Core/Recording/RecordTaskBase.cs
index a19f16f..a666c11 100644
--- a/BililiveRecorder.Core/Recording/RecordTaskBase.cs
+++ b/BililiveRecorder.Core/Recording/RecordTaskBase.cs
@@ -42,7 +42,7 @@ namespace BililiveRecorder.Core.Recording
protected Stopwatch ioDiskStopwatch = new();
protected object ioDiskStatsLock = new();
- protected TimeSpan ioDiskWriteTime;
+ protected TimeSpan ioDiskWriteDuration;
protected int ioDiskWrittenBytes;
private DateTimeOffset ioDiskWarningTimeout;
@@ -130,7 +130,7 @@ namespace BililiveRecorder.Core.Recording
private void Timer_Elapsed_TriggerIOStats(object sender, ElapsedEventArgs e)
{
int networkDownloadBytes, diskWriteBytes;
- TimeSpan durationDiff, diskWriteTime;
+ TimeSpan durationDiff, diskWriteDuration;
DateTimeOffset startTime, endTime;
@@ -148,15 +148,15 @@ namespace BililiveRecorder.Core.Recording
// disks
lock (this.ioDiskStatsLock) // 锁硬盘统计
{
- diskWriteTime = this.ioDiskWriteTime;
+ diskWriteDuration = this.ioDiskWriteDuration;
diskWriteBytes = this.ioDiskWrittenBytes;
- this.ioDiskWriteTime = TimeSpan.Zero;
+ this.ioDiskWriteDuration = TimeSpan.Zero;
this.ioDiskWrittenBytes = 0;
}
}
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
{
@@ -166,12 +166,12 @@ namespace BililiveRecorder.Core.Recording
EndTime = endTime,
NetworkMbps = netMbps,
DiskBytesWritten = diskWriteBytes,
- DiskWriteTime = diskWriteTime,
+ DiskWriteDuration = diskWriteDuration,
DiskMBps = diskMBps,
});
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 可能不能满足录播
this.ioDiskWarningTimeout = now + TimeSpan.FromMinutes(2); // 最多每 2 分钟提醒一次
diff --git a/BililiveRecorder.Core/Recording/StandardRecordTask.cs b/BililiveRecorder.Core/Recording/StandardRecordTask.cs
index d180700..8348efc 100644
--- a/BililiveRecorder.Core/Recording/StandardRecordTask.cs
+++ b/BililiveRecorder.Core/Recording/StandardRecordTask.cs
@@ -177,7 +177,7 @@ namespace BililiveRecorder.Core.Recording
lock (this.ioDiskStatsLock)
{
- this.ioDiskWriteTime += this.ioDiskStopwatch.Elapsed;
+ this.ioDiskWriteDuration += this.ioDiskStopwatch.Elapsed;
this.ioDiskWrittenBytes += bytesWritten;
}
this.ioDiskStopwatch.Reset();
@@ -257,7 +257,7 @@ namespace BililiveRecorder.Core.Recording
this.splitFileRule.SetSplitBeforeFlag();
break;
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();
break;
}
diff --git a/BililiveRecorder.Core/RecordingStats.cs b/BililiveRecorder.Core/RecordingStats.cs
deleted file mode 100644
index c238b49..0000000
--- a/BililiveRecorder.Core/RecordingStats.cs
+++ /dev/null
@@ -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(ref T location, T value, [CallerMemberName] string propertyName = "")
- {
- if (EqualityComparer.Default.Equals(location, value))
- return false;
- location = value;
- this.OnPropertyChanged(propertyName);
- return true;
- }
- }
-}
diff --git a/BililiveRecorder.Core/Room.cs b/BililiveRecorder.Core/Room.cs
index 9a9e2a2..6fd1e23 100644
--- a/BililiveRecorder.Core/Room.cs
+++ b/BililiveRecorder.Core/Room.cs
@@ -94,7 +94,7 @@ namespace BililiveRecorder.Core
public bool Recording => this.recordTask != null;
public RoomConfig RoomConfig { get; }
- public RecordingStats Stats { get; } = new RecordingStats();
+ public RoomStats Stats { get; } = new RoomStats();
public Guid ObjectId { get; } = Guid.NewGuid();
@@ -353,7 +353,14 @@ namespace BililiveRecorder.Core
{
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.DiskWriteDuration = e.DiskWriteDuration;
+ this.Stats.DiskBytesWritten = e.DiskBytesWritten;
+ this.Stats.DiskMBps = e.DiskMBps;
IOStats?.Invoke(this, e);
}
@@ -363,14 +370,31 @@ namespace BililiveRecorder.Core
{
this.logger.Verbose("Recording stats: {@stats}", e);
- var diff = DateTimeOffset.UtcNow - this.recordTaskStartTime;
- this.Stats.SessionDuration = TimeSpan.FromSeconds(Math.Round(diff.TotalSeconds));
- this.Stats.FileMaxTimestamp = TimeSpan.FromMilliseconds(e.FileMaxTimestamp);
+ this.Stats.SessionDuration = TimeSpan.FromMilliseconds(e.SessionDuration);
+ this.Stats.TotalInputBytes = e.TotalInputBytes;
+ this.Stats.TotalOutputBytes = e.TotalOutputBytes;
+ this.Stats.CurrentFileSize = e.CurrentFileSize;
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.TotalInputBytes = e.TotalInputVideoByteCount + e.TotalInputAudioByteCount;
- this.Stats.TotalOutputBytes = e.TotalOutputVideoByteCount + e.TotalOutputAudioByteCount;
+ this.Stats.InputVideoBytes = e.InputVideoBytes;
+ 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);
}
diff --git a/BililiveRecorder.Core/RoomStats.cs b/BililiveRecorder.Core/RoomStats.cs
new file mode 100644
index 0000000..0a79ad6
--- /dev/null
+++ b/BililiveRecorder.Core/RoomStats.cs
@@ -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
+
+ ///
+ /// 当前统计区间的开始时间
+ ///
+ public DateTimeOffset StartTime { get => this.___StartTime; set => this.SetField(ref this.___StartTime, value); }
+
+ ///
+ /// 当前统计区间的结束时间
+ ///
+ public DateTimeOffset EndTime { get => this.___EndTime; set => this.SetField(ref this.___EndTime, value); }
+
+ ///
+ /// 当前统计区间的时长
+ ///
+ public TimeSpan Duration { get => this.___Duration; set => this.SetField(ref this.___Duration, value); }
+
+ ///
+ /// 下载了的数据量
+ ///
+ public int NetworkBytesDownloaded { get => this.___NetworkBytesDownloaded; set => this.SetField(ref this.___NetworkBytesDownloaded, value); }
+
+ ///
+ /// 平均下载速度,mibi-bits per second
+ ///
+ public double NetworkMbps { get => this.___NetworkMbps; set => this.SetField(ref this.___NetworkMbps, value); }
+
+ ///
+ /// 统计区间内的磁盘写入耗时
+ ///
+ public TimeSpan DiskWriteDuration { get => this.___DiskWriteDuration; set => this.SetField(ref this.___DiskWriteDuration, value); }
+
+ ///
+ /// 统计区间内写入磁盘的数据量
+ ///
+ public int DiskBytesWritten { get => this.___DiskBytesWritten; set => this.SetField(ref this.___DiskBytesWritten, value); }
+
+ ///
+ /// 平均写入速度,mibi-bytes per second
+ ///
+ public double DiskMBps { get => this.___DiskMBps; set => this.SetField(ref this.___DiskMBps, value); }
+
+ #endregion
+ #region Recording Stats Properties
+
+ ///
+ /// 从录制开始到现在一共经过的时间
+ ///
+ public TimeSpan SessionDuration { get => this.___SessionDuration; set => this.SetField(ref this.___SessionDuration, 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 long CurrentFileSize { get => this.___CurrentFileSize; set => this.SetField(ref this.___CurrentFileSize, 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 AddedDuration { get => this.___AddedDuration; set => this.SetField(ref this.___AddedDuration, value); }
+
+ ///
+ /// 当前这一个统计区间所经过的时间长度
+ ///
+ public double PassedTime { get => this.___PassedTime; set => this.SetField(ref this.___PassedTime, value); }
+
+ ///
+ /// 录制速度比例
+ ///
+ public double DurationRatio { get => this.___DurationRatio; set => this.SetField(ref this.___DurationRatio, value); }
+
+ //----------------------------------------
+
+ ///
+ /// 当前统计区间新收到的视频数据大小
+ ///
+ public long InputVideoBytes { get => this.___InputVideoBytes; set => this.SetField(ref this.___InputVideoBytes, value); }
+ ///
+ /// 当前统计区间新收到的音频数据大小
+ ///
+ public long InputAudioBytes { get => this.___InputAudioBytes; set => this.SetField(ref this.___InputAudioBytes, value); }
+
+ ///
+ /// 当前统计区间新写入的视频帧数量
+ ///
+ public int OutputVideoFrames { get => this.___OutputVideoFrames; set => this.SetField(ref this.___OutputVideoFrames, value); }
+ ///
+ /// 当前统计区间新写入的音频帧数量
+ ///
+ public int OutputAudioFrames { get => this.___OutputAudioFrames; set => this.SetField(ref this.___OutputAudioFrames, value); }
+ ///
+ /// 当前统计区间新写入的视频数据大小
+ ///
+ public long OutputVideoBytes { get => this.___OutputVideoBytes; set => this.SetField(ref this.___OutputVideoBytes, value); }
+ ///
+ /// 当前统计区间新写入的音频数据大小
+ ///
+ public long OutputAudioBytes { get => this.___OutputAudioBytes; set => this.SetField(ref this.___OutputAudioBytes, value); }
+
+ ///
+ /// 总共收到的视频数据大小
+ ///
+ public long TotalInputVideoBytes { get => this.___TotalInputVideoBytes; set => this.SetField(ref this.___TotalInputVideoBytes, value); }
+ ///
+ /// 总共收到的音频数据大小
+ ///
+ public long TotalInputAudioBytes { get => this.___TotalInputAudioBytes; set => this.SetField(ref this.___TotalInputAudioBytes, value); }
+
+ ///
+ /// 总共写入的视频帧数量
+ ///
+ public int TotalOutputVideoFrames { get => this.___TotalOutputVideoFrames; set => this.SetField(ref this.___TotalOutputVideoFrames, value); }
+ ///
+ /// 总共写入的音频帧数量
+ ///
+ public int TotalOutputAudioFrames { get => this.___TotalOutputAudioFrames; set => this.SetField(ref this.___TotalOutputAudioFrames, value); }
+ ///
+ /// 总共写入的视频数据大小
+ ///
+ public long TotalOutputVideoBytes { get => this.___TotalOutputVideoBytes; set => this.SetField(ref this.___TotalOutputVideoBytes, value); }
+ ///
+ /// 总共写入的音频数据大小
+ ///
+ 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(ref T location, T value, [CallerMemberName] string propertyName = "")
+ {
+ if (EqualityComparer.Default.Equals(location, value))
+ return false;
+ location = value;
+ this.OnPropertyChanged(propertyName);
+ return true;
+ }
+ }
+}
diff --git a/BililiveRecorder.Web/Api/RoomController.cs b/BililiveRecorder.Web/Api/RoomController.cs
index b7ba1df..ffca155 100644
--- a/BililiveRecorder.Web/Api/RoomController.cs
+++ b/BililiveRecorder.Web/Api/RoomController.cs
@@ -152,12 +152,12 @@ namespace BililiveRecorder.Web.Api
[HttpGet("{roomId:int}/stats")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
- public ActionResult GetRoomStats(int roomId)
+ public ActionResult GetRoomStats(int roomId)
{
var room = this.FetchRoom(roomId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
- return this.mapper.Map(room.Stats);
+ return this.mapper.Map(room.Stats);
}
///
@@ -168,12 +168,12 @@ namespace BililiveRecorder.Web.Api
[HttpGet("{objectId:guid}/stats")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RestApiError), StatusCodes.Status404NotFound)]
- public ActionResult GetRoomStats(Guid objectId)
+ public ActionResult GetRoomStats(Guid objectId)
{
var room = this.FetchRoom(objectId);
if (room is null)
return this.NotFound(new RestApiError { Code = RestApiErrorCode.RoomNotFound, Message = "Room not found" });
- return this.mapper.Map(room.Stats);
+ return this.mapper.Map(room.Stats);
}
#endregion
diff --git a/BililiveRecorder.Web/FakeRecorderForWeb.cs b/BililiveRecorder.Web/FakeRecorderForWeb.cs
index 32edc8a..637ef88 100644
--- a/BililiveRecorder.Web/FakeRecorderForWeb.cs
+++ b/BililiveRecorder.Web/FakeRecorderForWeb.cs
@@ -20,7 +20,7 @@ namespace BililiveRecorder.Web
public event EventHandler>? RecordSessionEnded;
public event EventHandler>? RecordFileOpening;
public event EventHandler>? RecordFileClosed;
- public event EventHandler>? NetworkingStats;
+ public event EventHandler>? IOStats;
public event EventHandler>? RecordingStats;
public event PropertyChangedEventHandler? PropertyChanged;
#pragma warning restore CS0067
diff --git a/BililiveRecorder.Web/Models/Graphql/IOStatsType.cs b/BililiveRecorder.Web/Models/Graphql/IOStatsType.cs
new file mode 100644
index 0000000..7453883
--- /dev/null
+++ b/BililiveRecorder.Web/Models/Graphql/IOStatsType.cs
@@ -0,0 +1,20 @@
+using BililiveRecorder.Core;
+using GraphQL.Types;
+
+namespace BililiveRecorder.Web.Models.Graphql
+{
+ public class IOStatsType : ObjectGraphType
+ {
+ 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);
+ }
+ }
+}
diff --git a/BililiveRecorder.Web/Models/Graphql/RecordingStatsType.cs b/BililiveRecorder.Web/Models/Graphql/RecordingStatsType.cs
index 71409bb..22b7c00 100644
--- a/BililiveRecorder.Web/Models/Graphql/RecordingStatsType.cs
+++ b/BililiveRecorder.Web/Models/Graphql/RecordingStatsType.cs
@@ -3,17 +3,31 @@ using GraphQL.Types;
namespace BililiveRecorder.Web.Models.Graphql
{
- public class RecordingStatsType : ObjectGraphType
+ public class RecordingStatsType : ObjectGraphType
{
public RecordingStatsType()
{
- this.Field(x => x.NetworkMbps);
- this.Field(x => x.SessionDuration);
- this.Field(x => x.FileMaxTimestamp);
- this.Field(x => x.SessionMaxTimestamp);
- this.Field(x => x.DurationRatio);
+ this.Field(x => x.SessionDuration, type: typeof(TimeSpanMillisecondsGraphType));
this.Field(x => x.TotalInputBytes);
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);
}
}
}
diff --git a/BililiveRecorder.Web/Models/Graphql/RoomType.cs b/BililiveRecorder.Web/Models/Graphql/RoomType.cs
index e627793..812e848 100644
--- a/BililiveRecorder.Web/Models/Graphql/RoomType.cs
+++ b/BililiveRecorder.Web/Models/Graphql/RoomType.cs
@@ -18,7 +18,8 @@ namespace BililiveRecorder.Web.Models.Graphql
this.Field(x => x.Streaming);
this.Field(x => x.AutoRecordForThisSession);
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));
}
}
}
diff --git a/BililiveRecorder.Web/Models/Rest/DataMappingProfile.cs b/BililiveRecorder.Web/Models/Rest/DataMappingProfile.cs
index 77c8c3c..276de87 100644
--- a/BililiveRecorder.Web/Models/Rest/DataMappingProfile.cs
+++ b/BililiveRecorder.Web/Models/Rest/DataMappingProfile.cs
@@ -1,6 +1,7 @@
using AutoMapper;
using BililiveRecorder.Core;
using BililiveRecorder.Core.Config.V3;
+using BililiveRecorder.Core.Event;
namespace BililiveRecorder.Web.Models.Rest
{
@@ -11,14 +12,23 @@ namespace BililiveRecorder.Web.Models.Rest
this.CreateMap()
.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.IoStats, x => x.MapFrom(s => s.Stats))
+ .ForMember(x => x.RecordingStats, x => x.MapFrom(s => s.Stats))
;
- this.CreateMap()
+ this.CreateMap()
.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.FileMaxTimestamp, x => x.MapFrom(s => s.FileMaxTimestamp.TotalMilliseconds))
;
+ this.CreateMap()
+ .ForMember(x => x.Duration, x => x.MapFrom(s => s.Duration.TotalMilliseconds))
+ .ForMember(x => x.DiskWriteDuration, x => x.MapFrom(s => s.DiskWriteDuration.TotalMilliseconds))
+ ;
+
+ this.CreateMap();
+
this.CreateMap();
this.CreateMap();
diff --git a/BililiveRecorder.Web/Models/Rest/RoomDto.cs b/BililiveRecorder.Web/Models/Rest/RoomDto.cs
index cda3476..7df11c9 100644
--- a/BililiveRecorder.Web/Models/Rest/RoomDto.cs
+++ b/BililiveRecorder.Web/Models/Rest/RoomDto.cs
@@ -16,6 +16,7 @@ namespace BililiveRecorder.Web.Models.Rest
public bool Streaming { get; set; }
public bool DanmakuConnected { 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!;
}
}
diff --git a/BililiveRecorder.Web/Models/Rest/RoomIOStatsDto.cs b/BililiveRecorder.Web/Models/Rest/RoomIOStatsDto.cs
new file mode 100644
index 0000000..32f194a
--- /dev/null
+++ b/BililiveRecorder.Web/Models/Rest/RoomIOStatsDto.cs
@@ -0,0 +1,47 @@
+using System;
+
+namespace BililiveRecorder.Web.Models.Rest
+{
+ public class RoomIOStatsDto
+ {
+ ///
+ /// 当前统计区间的开始时间
+ ///
+ public DateTimeOffset StartTime { get; set; }
+
+ ///
+ /// 当前统计区间的结束时间
+ ///
+ public DateTimeOffset EndTime { get; set; }
+
+ ///
+ /// 当前统计区间的时长,毫秒
+ ///
+ public double Duration { get; set; }
+
+ ///
+ /// 下载了的数据量
+ ///
+ public int NetworkBytesDownloaded { get; set; }
+
+ ///
+ /// 平均下载速度,mibi-bits per second
+ ///
+ public double NetworkMbps { get; set; }
+
+ ///
+ /// 统计区间内的磁盘写入耗时,毫秒
+ ///
+ public double DiskWriteDuration { get; set; }
+
+ ///
+ /// 统计区间内写入磁盘的数据量
+ ///
+ public int DiskBytesWritten { get; set; }
+
+ ///
+ /// 平均写入速度,mibi-bytes per second
+ ///
+ public double DiskMBps { get; set; }
+ }
+}
diff --git a/BililiveRecorder.Web/Models/Rest/RoomRecordingStatsDto.cs b/BililiveRecorder.Web/Models/Rest/RoomRecordingStatsDto.cs
new file mode 100644
index 0000000..5f39107
--- /dev/null
+++ b/BililiveRecorder.Web/Models/Rest/RoomRecordingStatsDto.cs
@@ -0,0 +1,104 @@
+namespace BililiveRecorder.Web.Models.Rest
+{
+ public class RoomRecordingStatsDto
+ {
+ ///
+ /// 从录制开始到现在一共经过的时间,毫秒
+ ///
+ public double SessionDuration { get; set; }
+
+ ///
+ /// 总接受字节数
+ ///
+ public long TotalInputBytes { get; set; }
+
+ ///
+ /// 总写入字节数
+ ///
+ public long TotalOutputBytes { get; set; }
+
+ ///
+ /// 当前文件的大小
+ ///
+ public long CurrentFileSize { get; set; }
+
+ ///
+ /// 本次直播流收到的最大时间戳(已修复过,相当于总时长,毫秒)
+ ///
+ public int SessionMaxTimestamp { get; set; }
+
+ ///
+ /// 当前文件的最大时间戳(相当于总时长,毫秒)
+ ///
+ public int FileMaxTimestamp { get; set; }
+
+ ///
+ /// 当前这一个统计区间的直播数据时长,毫秒
+ ///
+ public double AddedDuration { get; set; }
+
+ ///
+ /// 当前这一个统计区间所经过的时间长度,毫秒
+ ///
+ public double PassedTime { get; set; }
+
+ ///
+ /// 录制速度比例
+ ///
+ public double DurationRatio { get; set; }
+
+ //----------------------------------------
+
+ ///
+ /// 当前统计区间新收到的视频数据大小
+ ///
+ public long InputVideoBytes { get; set; }
+ ///
+ /// 当前统计区间新收到的音频数据大小
+ ///
+ public long InputAudioBytes { get; set; }
+
+ ///
+ /// 当前统计区间新写入的视频帧数量
+ ///
+ public int OutputVideoFrames { get; set; }
+ ///
+ /// 当前统计区间新写入的音频帧数量
+ ///
+ public int OutputAudioFrames { get; set; }
+ ///
+ /// 当前统计区间新写入的视频数据大小
+ ///
+ public long OutputVideoBytes { get; set; }
+ ///
+ /// 当前统计区间新写入的音频数据大小
+ ///
+ public long OutputAudioBytes { get; set; }
+
+ ///
+ /// 总共收到的视频数据大小
+ ///
+ public long TotalInputVideoBytes { get; set; }
+ ///
+ /// 总共收到的音频数据大小
+ ///
+ public long TotalInputAudioBytes { get; set; }
+
+ ///
+ /// 总共写入的视频帧数量
+ ///
+ public int TotalOutputVideoFrames { get; set; }
+ ///
+ /// 总共写入的音频帧数量
+ ///
+ public int TotalOutputAudioFrames { get; set; }
+ ///
+ /// 总共写入的视频数据大小
+ ///
+ public long TotalOutputVideoBytes { get; set; }
+ ///
+ /// 总共写入的音频数据大小
+ ///
+ public long TotalOutputAudioBytes { get; set; }
+ }
+}
diff --git a/BililiveRecorder.Web/Models/Rest/RoomStatsDto.cs b/BililiveRecorder.Web/Models/Rest/RoomStatsDto.cs
deleted file mode 100644
index bed1ec4..0000000
--- a/BililiveRecorder.Web/Models/Rest/RoomStatsDto.cs
+++ /dev/null
@@ -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; }
- }
-}
diff --git a/BililiveRecorder.Web/Startup.cs b/BililiveRecorder.Web/Startup.cs
index 7156bcc..27fb480 100644
--- a/BililiveRecorder.Web/Startup.cs
+++ b/BililiveRecorder.Web/Startup.cs
@@ -2,6 +2,7 @@ using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
+using AutoMapper;
using BililiveRecorder.Core;
using BililiveRecorder.Web.Graphql;
using BililiveRecorder.Web.Models.Rest;
@@ -53,6 +54,15 @@ namespace BililiveRecorder.Web
// 实际运行时在 BililiveRecorder.Web.Program 里会加上真的 IRecorder
services.TryAddSingleton(new FakeRecorderForWeb());
+#if DEBUG
+ // TODO 移动到一个单独的测试项目里
+ if (Debugger.IsAttached)
+ {
+ var configuration = new MapperConfiguration(cfg => cfg.AddProfile());
+ configuration.AssertConfigurationIsValid();
+ }
+#endif
+
services
.AddAutoMapper(c => c.AddProfile())
.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()))