Иконка ресурса

FREE ShadowStatusPlugin : 1.2.2 1.2.2

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("ShadowStatus", "Eclipsa", "1.2.2")]
    [Description("Отправка FPS, нагрузки плагинов и ошибок на панель ShadowStatus")]
    public class ShadowStatusPlugin : RustPlugin
    {
        private Configuration _config;
        private Timer _reportTimer;
        private DateTime _lastSend = DateTime.MinValue;
        private string _serverId;
        private readonly Queue<Dictionary<string, object>> _pendingErrors = new Queue<Dictionary<string, object>>();
        private readonly Queue<Dictionary<string, object>> _pendingSlowHooks = new Queue<Dictionary<string, object>>();
        private const int MaxQueue = 50;

        private static readonly Regex SlowHookRegex = new Regex(
            @"Calling\s+'([^']+)'\s+on\s+'([^']+)'\s+took\s+([\d.]+)ms",
            RegexOptions.Compiled | RegexOptions.IgnoreCase);

        private class Configuration
        {
            [JsonProperty("API URL")]
            public string ApiUrl = "http://176.212.127.83:32457";

            [JsonProperty("API Key")]
            public string ApiKey = "shadowstatus-default-key";

            [JsonProperty("Report interval (seconds)")]
            public float ReportIntervalSeconds = 10f;

            [JsonProperty("Server display name (empty = hostname)")]
            public string ServerDisplayName = "";
        }

        protected override void LoadDefaultConfig()
        {
            _config = new Configuration();
            SaveConfig();
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<Configuration>();
                if (_config == null)
                    LoadDefaultConfig();
            }
            catch
            {
                LoadDefaultConfig();
            }
        }

        protected override void SaveConfig() => Config.WriteObject(_config, true);

        private void Init()
        {
            _serverId = $"{ConVar.Server.hostname}_{ConVar.Server.port}".GetHashCode().ToString("X8");
            Application.logMessageReceived += OnUnityLog;
        }

        private void Unload()
        {
            Application.logMessageReceived -= OnUnityLog;
            _reportTimer?.Destroy();
        }

        private void OnServerInitialized()
        {
            _reportTimer?.Destroy();
            var interval = Mathf.Max(5f, _config.ReportIntervalSeconds);
            _reportTimer = timer.Every(interval, SendReport);
            Puts($"ShadowStatus → {_config.ApiUrl} (каждые {interval}s)");
        }

        private void OnUnityLog(string condition, string stackTrace, LogType type)
        {
            if (!string.IsNullOrEmpty(condition) && SlowHookRegex.IsMatch(condition))
            {
                var m = SlowHookRegex.Match(condition);
                EnqueueSlowHook(m.Groups[2].Value, m.Groups[1].Value, float.Parse(m.Groups[3].Value));
            }

            if (type != LogType.Error && type != LogType.Exception && type != LogType.Assert)
                return;

            EnqueueError(type.ToString(), condition, stackTrace);
        }

        private void EnqueueError(string level, string message, string stack)
        {
            if (string.IsNullOrEmpty(message))
                return;
            if (_pendingErrors.Count >= MaxQueue)
                _pendingErrors.Dequeue();
            _pendingErrors.Enqueue(new Dictionary<string, object>
            {
                ["level"] = level,
                ["message"] = message.Length > 2000 ? message.Substring(0, 2000) : message,
                ["stack"] = stack ?? ""
            });
        }

        private void EnqueueSlowHook(string plugin, string hook, float ms)
        {
            if (_pendingSlowHooks.Count >= MaxQueue)
                _pendingSlowHooks.Dequeue();
            _pendingSlowHooks.Enqueue(new Dictionary<string, object>
            {
                ["plugin"] = plugin,
                ["hook"] = hook,
                ["ms"] = ms
            });
        }

        private void SendReport()
        {
            var perf = Performance.current;
            var report = Performance.report;

            var metrics = new Dictionary<string, object>
            {
                ["fps"] = report.frameRate,
                ["fps_avg"] = report.frameRateAverage,
                ["frame_time"] = perf.frameTime,
                ["frame_time_avg"] = perf.frameTimeAverage,
                ["memory_used"] = perf.memoryUsageSystem,
                ["gc_collections"] = perf.memoryCollections,
                ["entities"] = BaseNetworkable.serverEntities.Count,
                ["players"] = BasePlayer.activePlayerList.Count,
                ["sleepers"] = BasePlayer.sleepingPlayerList.Count,
                ["hook_time_total"] = GetTotalHookTimeMs(),
                ["tasks_load_balancer"] = perf.loadBalancerTasks,
                ["tasks_invoke_handler"] = perf.invokeHandlerTasks,
                ["network_in"] = GetNetworkIn(),
                ["network_out"] = GetNetworkOut(),
                ["packet_loss"] = GetPacketLoss()
            };

            var pluginList = new List<Dictionary<string, object>>();
            foreach (var plugin in plugins.GetAll().OrderByDescending(p => p.TotalHookTime).Take(40))
            {
                if (plugin == null || plugin.IsCorePlugin || plugin.Name == Name)
                    continue;

                pluginList.Add(new Dictionary<string, object>
                {
                    ["name"] = plugin.Name,
                    ["version"] = plugin.Version.ToString(),
                    ["hook_time_ms"] = Math.Round(plugin.TotalHookTime, 4),
                    ["hook_memory_bytes"] = plugin.TotalHookMemory
                });
            }

            var errors = DrainQueue(_pendingErrors);
            var slowHooks = DrainQueue(_pendingSlowHooks);

            var payload = new Dictionary<string, object>
            {
                ["server_id"] = _serverId,
                ["server_name"] = GetServerDisplayName(),
                ["timestamp"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                ["metrics"] = metrics,
                ["plugins"] = pluginList,
                ["slow_hooks"] = slowHooks,
                ["errors"] = errors
            };

            var json = JsonConvert.SerializeObject(payload);
            var headers = new Dictionary<string, string>
            {
                ["Content-Type"] = "application/json",
                ["X-Api-Key"] = _config.ApiKey
            };

            var url = _config.ApiUrl.TrimEnd('/') + "/api/ingest";
            webrequest.Enqueue(url, json, (code, response) =>
            {
                if (code == 200)
                    _lastSend = DateTime.UtcNow;
                else if (code != 0)
                    PrintWarning($"ShadowStatus ingest HTTP {code}: {response}");
            }, this, Core.Libraries.RequestMethod.POST, headers, 15f);
        }

        private static List<Dictionary<string, object>> DrainQueue(Queue<Dictionary<string, object>> q)
        {
            var list = new List<Dictionary<string, object>>();
            while (q.Count > 0)
                list.Add(q.Dequeue());
            return list;
        }

        private double GetTotalHookTimeMs()
        {
            double total = 0;
            foreach (var p in plugins.GetAll())
            {
                if (p != null && !p.IsCorePlugin)
                    total += p.TotalHookTime;
            }
            return Math.Round(total, 2);
        }

        private string GetServerDisplayName()
        {
            if (!string.IsNullOrWhiteSpace(_config.ServerDisplayName))
                return _config.ServerDisplayName;
            return $"{ConVar.Server.hostname} :{ConVar.Server.port}";
        }

        // Безопасное получение сетевых метрик (без прямого доступа к Facepunch.Network)
        private long GetNetworkIn()
        {
            try
            {
                // Попытка через рефлексию (если Net.sv существует)
                var netSv = Type.GetType("Network.Net, Assembly-CSharp")?.GetProperty("sv")?.GetValue(null);
                if (netSv != null)
                {
                    var method = netSv.GetType().GetMethod("GetStat");
                    if (method != null)
                    {
                        var statType = Type.GetType("Network.StatTypeLong, Assembly-CSharp");
                        if (statType != null)
                        {
                            var enumValue = Enum.Parse(statType, "BytesReceived_LastSecond");
                            return (long)method.Invoke(netSv, new[] { enumValue });
                        }
                    }
                }
            }
            catch { }
            return 0;
        }

        private long GetNetworkOut()
        {
            try
            {
                var netSv = Type.GetType("Network.Net, Assembly-CSharp")?.GetProperty("sv")?.GetValue(null);
                if (netSv != null)
                {
                    var method = netSv.GetType().GetMethod("GetStat");
                    if (method != null)
                    {
                        var statType = Type.GetType("Network.StatTypeLong, Assembly-CSharp");
                        if (statType != null)
                        {
                            var enumValue = Enum.Parse(statType, "BytesSent_LastSecond");
                            return (long)method.Invoke(netSv, new[] { enumValue });
                        }
                    }
                }
            }
            catch { }
            return 0;
        }

        private long GetPacketLoss()
        {
            try
            {
                var netSv = Type.GetType("Network.Net, Assembly-CSharp")?.GetProperty("sv")?.GetValue(null);
                if (netSv != null)
                {
                    var method = netSv.GetType().GetMethod("GetStat");
                    if (method != null)
                    {
                        var statType = Type.GetType("Network.StatTypeLong, Assembly-CSharp");
                        if (statType != null)
                        {
                            var enumValue = Enum.Parse(statType, "PacketLossLastSecond");
                            return (long)method.Invoke(netSv, new[] { enumValue });
                        }
                    }
                }
            }
            catch { }
            return 0;
        }

        [ConsoleCommand("shadowstatus.status")]
        private void CmdStatus(ConsoleSystem.Arg arg)
        {
            var ago = _lastSend == DateTime.MinValue
                ? "никогда"
                : $"{(DateTime.UtcNow - _lastSend).TotalSeconds:F0}s назад";
            arg.ReplyWith($"ShadowStatus API: {_config.ApiUrl} | последняя отправка: {ago} | id: {_serverId}");
        }
    }
}

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху