Split global statistics into per-bot RemoteConnection (#2505)

* Split global statistics into per-bot RemoteConnection

* Add migration for existing statistics setting
This commit is contained in:
Łukasz Domeradzki
2022-02-03 17:33:04 +01:00
committed by GitHub
parent e03734ef8f
commit c3c5f33289
4 changed files with 87 additions and 30 deletions

View File

@@ -41,7 +41,7 @@ using ArchiSteamFarm.Web;
namespace ArchiSteamFarm.Core;
internal sealed class Statistics : IAsyncDisposable {
internal sealed class RemoteCommunication : IAsyncDisposable {
private const ushort MaxItemsForFairBots = ArchiWebHandler.MaxItemsInSingleInventoryRequest * WebBrowser.MaxTries; // Determines which fair bots we'll deprioritize when matching due to excessive number of inventory requests they need to make, which are likely to fail in the process or cause excessive delays
private const byte MaxMatchedBotsHard = 40; // Determines how many bots we can attempt to match in total, where match attempt is equal to analyzing bot's inventory
private const byte MaxMatchingRounds = 10; // Determines maximum amount of matching rounds we're going to consider before leaving the rest of work for the next batch
@@ -61,7 +61,7 @@ internal sealed class Statistics : IAsyncDisposable {
private readonly SemaphoreSlim MatchActivelySemaphore = new(1, 1);
#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync()
private readonly Timer MatchActivelyTimer;
private readonly Timer? MatchActivelyTimer;
#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync()
private readonly SemaphoreSlim RequestsSemaphore = new(1, 1);
@@ -71,25 +71,33 @@ internal sealed class Statistics : IAsyncDisposable {
private DateTime LastPersonaStateRequest;
private bool ShouldSendHeartBeats;
internal Statistics(Bot bot) {
internal RemoteCommunication(Bot bot) {
Bot = bot ?? throw new ArgumentNullException(nameof(bot));
MatchActivelyTimer = new Timer(
MatchActively,
null,
TimeSpan.FromHours(1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0), // Delay
TimeSpan.FromHours(8) // Period
);
if (Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.TradeMatcher)) {
MatchActivelyTimer = new Timer(
MatchActively,
null,
TimeSpan.FromHours(1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0), // Delay
TimeSpan.FromHours(8) // Period
);
}
}
public async ValueTask DisposeAsync() {
MatchActivelySemaphore.Dispose();
RequestsSemaphore.Dispose();
await MatchActivelyTimer.DisposeAsync().ConfigureAwait(false);
if (MatchActivelyTimer != null) {
await MatchActivelyTimer.DisposeAsync().ConfigureAwait(false);
}
}
internal async Task OnHeartBeat() {
if (!Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.PublicListing)) {
return;
}
// Request persona update if needed
if ((DateTime.UtcNow > LastPersonaStateRequest.AddHours(MinPersonaStateTTL)) && (DateTime.UtcNow > LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL))) {
LastPersonaStateRequest = DateTime.UtcNow;
@@ -125,12 +133,20 @@ internal sealed class Statistics : IAsyncDisposable {
}
internal async Task OnLoggedOn() {
if (!Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.SteamGroup)) {
return;
}
if (!await Bot.ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(ArchiWebHandler.JoinGroup)));
}
}
internal async Task OnPersonaState(string? nickname = null, string? avatarHash = null) {
if (!Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.PublicListing)) {
return;
}
if ((DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL)) && (ShouldSendHeartBeats || (LastHeartBeat == DateTime.MinValue))) {
return;
}
@@ -282,6 +298,10 @@ internal sealed class Statistics : IAsyncDisposable {
}
private async void MatchActively(object? state = null) {
if (!Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.TradeMatcher)) {
return;
}
if (!Bot.IsConnectedAndLoggedOn || Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything) || !Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchActively) || (await IsEligibleForMatching().ConfigureAwait(false) != true)) {
Bot.ArchiLogger.LogGenericTrace(Strings.ErrorAborted);

View File

@@ -154,7 +154,6 @@ public sealed class Bot : IAsyncDisposable {
private readonly SemaphoreSlim MessagingSemaphore = new(1, 1);
private readonly ConcurrentDictionary<UserNotificationsCallback.EUserNotification, uint> PastNotifications = new();
private readonly SemaphoreSlim SendCompleteTypesSemaphore = new(1, 1);
private readonly Statistics? Statistics;
private readonly SteamClient SteamClient;
private readonly ConcurrentHashSet<ulong> SteamFamilySharingIDs = new();
private readonly SteamUser SteamUser;
@@ -250,6 +249,7 @@ public sealed class Bot : IAsyncDisposable {
#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync()
private bool ReconnectOnUserInitiated;
private RemoteCommunication? RemoteCommunication;
private bool SendCompleteTypesScheduled;
#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync()
@@ -339,10 +339,6 @@ public sealed class Bot : IAsyncDisposable {
Commands = new Commands(this);
Trading = new Trading(this);
if (!Debugging.IsDebugBuild && (ASF.GlobalConfig?.Statistics ?? GlobalConfig.DefaultStatistics)) {
Statistics = new Statistics(this);
}
HeartBeatTimer = new Timer(
HeartBeat,
null,
@@ -383,8 +379,8 @@ public sealed class Bot : IAsyncDisposable {
await SendItemsTimer.DisposeAsync().ConfigureAwait(false);
}
if (Statistics != null) {
await Statistics.DisposeAsync().ConfigureAwait(false);
if (RemoteCommunication != null) {
await RemoteCommunication.DisposeAsync().ConfigureAwait(false);
}
if (SteamSaleEvent != null) {
@@ -1962,8 +1958,8 @@ public sealed class Bot : IAsyncDisposable {
HeartBeatFailures = 0;
if (Statistics != null) {
Utilities.InBackground(Statistics.OnHeartBeat);
if (RemoteCommunication != null) {
Utilities.InBackground(RemoteCommunication.OnHeartBeat);
}
} catch (Exception e) {
ArchiLogger.LogGenericDebuggingException(e);
@@ -2112,6 +2108,16 @@ public sealed class Bot : IAsyncDisposable {
SteamSaleEvent = new SteamSaleEvent(this);
}
if (RemoteCommunication != null) {
await RemoteCommunication.DisposeAsync().ConfigureAwait(false);
RemoteCommunication = null;
}
if (!Debugging.IsDebugBuild && (BotConfig.RemoteCommunication > BotConfig.ERemoteCommunication.None)) {
RemoteCommunication = new RemoteCommunication(this);
}
await PluginsCore.OnBotInitModules(this, BotConfig.AdditionalProperties).ConfigureAwait(false);
}
@@ -2866,8 +2872,8 @@ public sealed class Bot : IAsyncDisposable {
Utilities.InBackground(InitializeFamilySharing);
if (Statistics != null) {
Utilities.InBackground(Statistics.OnLoggedOn);
if (RemoteCommunication != null) {
Utilities.InBackground(RemoteCommunication.OnLoggedOn);
}
if (BotConfig.OnlineStatus != EPersonaState.Offline) {
@@ -3042,8 +3048,8 @@ public sealed class Bot : IAsyncDisposable {
AvatarHash = avatarHash;
Nickname = callback.Name;
if (Statistics != null) {
Utilities.InBackground(() => Statistics.OnPersonaState(callback.Name, avatarHash));
if (RemoteCommunication != null) {
Utilities.InBackground(() => RemoteCommunication.OnPersonaState(callback.Name, avatarHash));
}
}

View File

@@ -35,6 +35,7 @@ using ArchiSteamFarm.IPC.Integration;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.Steam.Data;
using ArchiSteamFarm.Steam.Integration;
using ArchiSteamFarm.Storage;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -80,6 +81,9 @@ public sealed class BotConfig {
[PublicAPI]
public const ERedeemingPreferences DefaultRedeemingPreferences = ERedeemingPreferences.None;
[PublicAPI]
public const ERemoteCommunication DefaultRemoteCommunication = ERemoteCommunication.All;
[PublicAPI]
public const bool DefaultSendOnFarmingFinished = false;
@@ -195,6 +199,9 @@ public sealed class BotConfig {
[JsonProperty(Required = Required.DisallowNull)]
public ERedeemingPreferences RedeemingPreferences { get; private set; } = DefaultRedeemingPreferences;
[JsonProperty(Required = Required.DisallowNull)]
public ERemoteCommunication RemoteCommunication { get; private set; } = DefaultRemoteCommunication;
[JsonProperty(Required = Required.DisallowNull)]
public bool SendOnFarmingFinished { get; private set; } = DefaultSendOnFarmingFinished;
@@ -351,6 +358,9 @@ public sealed class BotConfig {
[UsedImplicitly]
public bool ShouldSerializeRedeemingPreferences() => !Saving || (RedeemingPreferences != DefaultRedeemingPreferences);
[UsedImplicitly]
public bool ShouldSerializeRemoteCommunication() => !Saving || (RemoteCommunication != DefaultRemoteCommunication);
[UsedImplicitly]
public bool ShouldSerializeSendOnFarmingFinished() => !Saving || (SendOnFarmingFinished != DefaultSendOnFarmingFinished);
@@ -594,7 +604,18 @@ public sealed class BotConfig {
break;
}
// TODO: Pending removal, Statistics -> RemoteCommunication migration
if ((ASF.GlobalConfig?.Statistics == false) && (botConfig.RemoteCommunication == DefaultRemoteCommunication)) {
botConfig.RemoteCommunication = ERemoteCommunication.None;
}
if (!Program.ConfigMigrate) {
// TODO: Pending removal, warning for people that disabled config migrate, they need to migrate themselves
if (ASF.GlobalConfig?.Statistics == false) {
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(Program.ConfigMigrate)));
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningDeprecated, nameof(GlobalConfig.Statistics), nameof(RemoteCommunication)));
}
return (botConfig, null);
}
@@ -662,6 +683,15 @@ public sealed class BotConfig {
All = Forwarding | Distributing | KeepMissingGames | AssumeWalletKeyOnBadActivationCode
}
[Flags]
public enum ERemoteCommunication : byte {
None = 0,
SteamGroup = 1,
TradeMatcher = 2,
PublicListing = 4,
All = SteamGroup | TradeMatcher | PublicListing
}
[Flags]
public enum ETradingPreferences : byte {
None = 0,

View File

@@ -99,9 +99,6 @@ public sealed class GlobalConfig {
[PublicAPI]
public const EOptimizationMode DefaultOptimizationMode = EOptimizationMode.MaxPerformance;
[PublicAPI]
public const bool DefaultStatistics = true;
[PublicAPI]
public const string? DefaultSteamMessagePrefix = "/me ";
@@ -259,9 +256,6 @@ public sealed class GlobalConfig {
[JsonProperty(Required = Required.DisallowNull)]
public EOptimizationMode OptimizationMode { get; private set; } = DefaultOptimizationMode;
[JsonProperty(Required = Required.DisallowNull)]
public bool Statistics { get; private set; } = DefaultStatistics;
[JsonProperty]
[MaxLength(SteamChatMessage.MaxMessagePrefixBytes / SteamChatMessage.ReservedEscapeMessageBytes)]
public string? SteamMessagePrefix { get; private set; } = DefaultSteamMessagePrefix;
@@ -303,6 +297,10 @@ public sealed class GlobalConfig {
internal bool Saving { get; set; }
// TODO: Pending removal, Statistics property which got changed into RemoteConnection
[JsonProperty(Required = Required.DisallowNull)]
internal bool Statistics { get; private set; } = true;
[JsonProperty]
internal string? WebProxyPassword {
get => BackingWebProxyPassword;
@@ -398,8 +396,11 @@ public sealed class GlobalConfig {
[UsedImplicitly]
public bool ShouldSerializeSSteamOwnerID() => !Saving;
// TODO: Pending removal, Statistics property which got changed into RemoteConnection, we never serialize it after update
#pragma warning disable CA1822 // We can't mark it as static
[UsedImplicitly]
public bool ShouldSerializeStatistics() => !Saving || (Statistics != DefaultStatistics);
public bool ShouldSerializeStatistics() => false;
#pragma warning restore CA1822 // We can't mark it as static
[UsedImplicitly]
public bool ShouldSerializeSteamMessagePrefix() => !Saving || (SteamMessagePrefix != DefaultSteamMessagePrefix);