交互模式直接运行
大石头 authored at 2024-11-25 09:58:04
19.61 KiB
Stardust
using System.Xml.Linq;
using NewLife;
using NewLife.Log;
using NewLife.Threading;
using Stardust.Data.Nodes;
using XCode;
using XCode.DataAccessLayer;
using XCode.Membership;
using XCode.Model;
using static Stardust.Data.Nodes.Node;

namespace Stardust.Server.Services;

public class NodeStatService : IHostedService
{
    private readonly ITracer _tracer;
    private TimerX _timer;
    public NodeStatService(ITracer tracer) => _tracer = tracer;

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _timer = new TimerX(DoNodeStat, null, 5_000, 600 * 1000) { Async = true };

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _timer.TryDispose();

        return Task.CompletedTask;
    }

    private void DoNodeStat(Object state)
    {
        // 无在线则不执行
        //if (_onlines == 0) return;

        using var span = _tracer?.NewSpan("NodeStat");

        // 减少Sql日志
        var dal = NodeStat.Meta.Session.Dal;
        var oldSql = dal.Session.ShowSQL;
#if !DEBUG
        dal.Session.ShowSQL = false;
#endif
        try
        {
            // 每天0点,补偿跑T-1
            var now = DateTime.Now;
            var start = now.Hour == 0 && now.Minute <= 10 ? now.Date.AddDays(-1) : now.Date;
            for (var dt = start; dt <= DateTime.Today; dt = dt.AddDays(1))
            {
                OSKindStat(dt, GetSelects(dt));
                ProductCodeStat(dt, GetSelects(dt));
                VersionStat(dt, GetSelects(dt));
                RuntimeStat(dt, GetSelects(dt));
                FrameworkStat(dt, GetSelects(dt));
                CityStat(dt, GetSelects(dt));
                ArchStat(dt, GetSelects(dt));
                VendorStat(dt, GetSelects(dt));
                ProductStat(dt, GetSelects(dt));
                BoardStat(dt, GetSelects(dt));
                ProcessorStat(dt, GetSelects(dt));
                ProjectStat(dt, GetSelects(dt));
            }
        }
        catch (Exception ex)
        {
            span?.SetError(ex, null);
            throw;
        }
        finally
        {
            dal.Session.ShowSQL = oldSql;
        }
    }

    ConcatExpression GetSelects(DateTime dt)
    {
        var t1 = dt.AddDays(-0);
        var t7 = dt.AddDays(-7);
        var t30 = dt.AddDays(-30);

        var selects = _.ID.Count();
        selects &= _.LastActive.SumLarge($"'{t1:yyyy-MM-dd}'", "activeT1");
        selects &= _.LastActive.SumLarge($"'{t7:yyyy-MM-dd}'", "activeT7");
        selects &= _.LastActive.SumLarge($"'{t30:yyyy-MM-dd}'", "activeT30");
        selects &= _.CreateTime.SumLarge($"'{t1:yyyy-MM-dd}'", "newT1");
        selects &= _.CreateTime.SumLarge($"'{t7:yyyy-MM-dd}'", "newT7");
        selects &= _.CreateTime.SumLarge($"'{t30:yyyy-MM-dd}'", "newT30");

        return selects;
    }

    private void OSKindStat(DateTime date, ConcatExpression selects)
    {
        var category = "操作系统";
        var list = SearchGroup(date.AddYears(-1), selects & _.OSKind, _.OSKind);
        var sts = NodeStat.FindAllByDate(category, date);
        foreach (var node in list)
        {
            var key = node.OSKind + "";
            if (key.Length > 50) key = key[..50];
            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = (Int32)node.OSKind + "";
            st.Total = node.ID;
            st.Actives = node["activeT1"].ToInt();
            st.ActivesT7 = node["activeT7"].ToInt();
            st.ActivesT30 = node["activeT30"].ToInt();
            st.News = node["newT1"].ToInt();
            st.NewsT7 = node["newT7"].ToInt();
            st.NewsT30 = node["newT30"].ToInt();

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }

    private void ProductCodeStat(DateTime date, ConcatExpression selects)
    {
        var category = "产品种类";
        var list = SearchGroup(date.AddYears(-1), selects & _.ProductCode, _.ProductCode);
        var sts = NodeStat.FindAllByDate(category, date);
        foreach (var node in list)
        {
            var key = node.ProductCode + "";
            if (key.Length > 50) key = key[..50];
            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = node.ProductCode + "";
            st.Total = node.ID;
            st.Actives = node["activeT1"].ToInt();
            st.ActivesT7 = node["activeT7"].ToInt();
            st.ActivesT30 = node["activeT30"].ToInt();
            st.News = node["newT1"].ToInt();
            st.NewsT7 = node["newT7"].ToInt();
            st.NewsT30 = node["newT30"].ToInt();

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }

    private void VersionStat(DateTime date, ConcatExpression selects)
    {
        var category = "版本";
        var list = SearchGroup(date.AddYears(-1), selects & _.Version, _.Version);
        var sts = NodeStat.FindAllByDate(category, date);
        foreach (var node in list)
        {
            var key = node.Version + "";
            if (key.Length > 50) key = key[..50];
            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = node.Version + "";
            st.Total = node.ID;
            st.Actives = node["activeT1"].ToInt();
            st.ActivesT7 = node["activeT7"].ToInt();
            st.ActivesT30 = node["activeT30"].ToInt();
            st.News = node["newT1"].ToInt();
            st.NewsT7 = node["newT7"].ToInt();
            st.NewsT30 = node["newT30"].ToInt();

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }

    private void RuntimeStat(DateTime date, ConcatExpression selects)
    {
        // 运行时的戏份版本比较多,需要取前三个字符
        var category = "运行时";
        //var func = NodeStat.Meta.Session.Dal.DbType switch
        //{
        //    DatabaseType.SqlServer => "substring",
        //    DatabaseType.Oracle => "substr",
        //    DatabaseType.MySql => "substr",
        //    DatabaseType.SQLite => "substr",
        //    _ => "substr",
        //};
        //var group = NodeStat.Meta.Session.Dal.DbType switch
        //{
        //    DatabaseType.SqlServer => "(Runtime, 1, 3)",
        //    _ => "Rt",
        //};
        //var list = SearchGroup(date.AddYears(-1), selects & $"{func}(Runtime, 1, 3) Rt", group);
        var list = SearchGroup(date.AddYears(-1), selects & _.Runtime, _.Runtime);
        var sts = NodeStat.FindAllByDate(category, date);
        // 先安装运行时版本全部取出来,再把截取版本然后二次聚合。避免了复杂的SQL兼容问题
        foreach (var item in list.GroupBy(e => (e.Runtime.IsNullOrEmpty() || e.Runtime.Length < 3) ? e.Runtime + "" : e.Runtime[..3]))
        {
            var key = item.Key;
            if (key.Length > 50) key = key[..50];
            var datas = item.ToList();
            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = key + "";
            st.Total = datas.Sum(e => e.ID);
            st.Actives = datas.Sum(e => e["activeT1"].ToInt());
            st.ActivesT7 = datas.Sum(e => e["activeT7"].ToInt());
            st.ActivesT30 = datas.Sum(e => e["activeT30"].ToInt());
            st.News = datas.Sum(e => e["newT1"].ToInt());
            st.NewsT7 = datas.Sum(e => e["newT7"].ToInt());
            st.NewsT30 = datas.Sum(e => e["newT30"].ToInt());

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }

    private void FrameworkStat(DateTime date, ConcatExpression selects)
    {
        var category = "最高框架";
        var list = SearchGroup(date.AddYears(-1), selects & _.Framework, _.Framework);
        var sts = NodeStat.FindAllByDate(category, date);
        foreach (var node in list)
        {
            var key = node.Framework + "";
            if (key.Length > 50) key = key[..50];
            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = node.Framework + "";
            st.Total = node.ID;
            st.Actives = node["activeT1"].ToInt();
            st.ActivesT7 = node["activeT7"].ToInt();
            st.ActivesT30 = node["activeT30"].ToInt();
            st.News = node["newT1"].ToInt();
            st.NewsT7 = node["newT7"].ToInt();
            st.NewsT30 = node["newT30"].ToInt();

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }

    private void CityStat(DateTime date, ConcatExpression selects)
    {
        var category = "城市";
        var list = SearchGroup(date.AddYears(-1), selects & _.CityID, _.CityID);
        var finder = new BatchFinder<Int32, Area>(list.Select(e => e.CityID));
        //finder.Add(list.Select(e => e.CityID));
        var sts = NodeStat.FindAllByDate(category, date);
        foreach (var node in list)
        {
            //if (node.CityID == 0) continue;

            //var key = Area.FindByID(node.CityID)?.Path;
            var key = finder[node.CityID]?.Path + "";
            if (key.Length > 50) key = key[..50];
            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = node.CityID + "";
            st.Total = node.ID;
            st.Actives = node["activeT1"].ToInt();
            st.ActivesT7 = node["activeT7"].ToInt();
            st.ActivesT30 = node["activeT30"].ToInt();
            st.News = node["newT1"].ToInt();
            st.NewsT7 = node["newT7"].ToInt();
            st.NewsT30 = node["newT30"].ToInt();

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }

    private void ArchStat(DateTime date, ConcatExpression selects)
    {
        var category = "芯片架构";
        var list = SearchGroup(date.AddYears(-1), selects & _.Architecture, _.Architecture);
        var sts = NodeStat.FindAllByDate(category, date);
        foreach (var node in list)
        {
            var key = node.Architecture + "";
            if (key.Length > 50) key = key[..50];
            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = node.Architecture + "";
            st.Total = node.ID;
            st.Actives = node["activeT1"].ToInt();
            st.ActivesT7 = node["activeT7"].ToInt();
            st.ActivesT30 = node["activeT30"].ToInt();
            st.News = node["newT1"].ToInt();
            st.NewsT7 = node["newT7"].ToInt();
            st.NewsT30 = node["newT30"].ToInt();

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }

    private void VendorStat(DateTime date, ConcatExpression selects)
    {
        var category = "制造商";
        var list = SearchGroup(date.AddYears(-1), selects & _.Vendor, _.Vendor);
        var sts = NodeStat.FindAllByDate(category, date);

        // 太少数据的分类合并
        var count = 0;
        foreach (var node in list.OrderByDescending(e => e.ID))
        {
            if (node.Vendor.IsNullOrEmpty() || node.ID < 3 || count++ >= 200)
                node.Vendor = "Other";
        }

        foreach (var item in list.GroupBy(e => e.Vendor))
        {
            var key = item.Key + "";
            if (key.Length > 50) key = key[..50];
            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = key;

            var nodes = item.ToList();
            st.Total = nodes.Sum(e => e.ID);
            st.Actives = nodes.Sum(e => e["activeT1"].ToInt());
            st.ActivesT7 = nodes.Sum(e => e["activeT7"].ToInt());
            st.ActivesT30 = nodes.Sum(e => e["activeT30"].ToInt());
            st.News = nodes.Sum(e => e["newT1"].ToInt());
            st.NewsT7 = nodes.Sum(e => e["newT7"].ToInt());
            st.NewsT30 = nodes.Sum(e => e["newT30"].ToInt());

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }

    private void ProductStat(DateTime date, ConcatExpression selects)
    {
        var category = "产品";
        var list = SearchGroup(date.AddYears(-1), selects & _.Product, _.Product);
        var sts = NodeStat.FindAllByDate(category, date);

        // 太少数据的分类合并
        var count = 0;
        foreach (var node in list.OrderByDescending(e => e.ID))
        {
            if (node.Product.IsNullOrEmpty() || node.ID < 3 || count++ >= 200)
                node.Product = "Other";
        }

        foreach (var item in list.GroupBy(e => e.Product))
        {
            var key = item.Key + "";
            if (key.Length > 50) key = key[..50];
            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = key;

            var nodes = item.ToList();
            st.Total = nodes.Sum(e => e.ID);
            st.Actives = nodes.Sum(e => e["activeT1"].ToInt());
            st.ActivesT7 = nodes.Sum(e => e["activeT7"].ToInt());
            st.ActivesT30 = nodes.Sum(e => e["activeT30"].ToInt());
            st.News = nodes.Sum(e => e["newT1"].ToInt());
            st.NewsT7 = nodes.Sum(e => e["newT7"].ToInt());
            st.NewsT30 = nodes.Sum(e => e["newT30"].ToInt());

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }

    private void BoardStat(DateTime date, ConcatExpression selects)
    {
        var category = "型号";
        var list = SearchGroup(date.AddYears(-1), selects & _.Board, _.Board);
        var sts = NodeStat.FindAllByDate(category, date);

        // 太少数据的分类合并
        var count = 0;
        foreach (var node in list.OrderByDescending(e => e.ID))
        {
            if (node.Board.IsNullOrEmpty() || node.ID < 3 || count++ >= 200)
                node.Board = "Other";
        }

        foreach (var item in list.GroupBy(e => e.Board))
        {
            var key = item.Key + "";
            if (key.Length > 50) key = key[..50];
            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = key;

            var nodes = item.ToList();
            st.Total = nodes.Sum(e => e.ID);
            st.Actives = nodes.Sum(e => e["activeT1"].ToInt());
            st.ActivesT7 = nodes.Sum(e => e["activeT7"].ToInt());
            st.ActivesT30 = nodes.Sum(e => e["activeT30"].ToInt());
            st.News = nodes.Sum(e => e["newT1"].ToInt());
            st.NewsT7 = nodes.Sum(e => e["newT7"].ToInt());
            st.NewsT30 = nodes.Sum(e => e["newT30"].ToInt());

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }

    private void ProcessorStat(DateTime date, ConcatExpression selects)
    {
        var category = "处理器";
        var list = SearchGroup(date.AddYears(-1), selects & _.Processor, _.Processor);
        var sts = NodeStat.FindAllByDate(category, date);

        // 处理器做二次合并,因为处理器太多,需要截取中间一部分
        var count = 0;
        foreach (var node in list.OrderByDescending(e => e.ID))
        {
#if !DEBUG
            if (node.Processor.IsNullOrEmpty() || node.ID < 3 || count++ >= 200)
            {
                node.Processor = "Other";
                continue;
            }
#endif

            var name = (node.Processor + "").Trim();
            var p = name.IndexOf('@');
            if (p > 0) name = name[..p].Trim().TrimEnd("CPU").Trim();

            // 双处理器
            p = name.IndexOf(',');
            if (p > 0) name = name[..p].Trim().Trim();

            p = name.IndexOf("with");
            if (p > 0) name = name[..p].Trim();

            p = name.IndexOf("w/");
            if (p > 0) name = name[..p].Trim();

            p = name.IndexOf("Core(TM)");
            if (p > 0) name = name.Substring(p + "Core(TM)".Length).Trim();

            p = name.IndexOf("CoreT");
            if (p > 0) name = name.Substring(p + "CoreT".Length).Trim();

            p = name.IndexOf("Platinum");
            if (p > 0) name = name[p..].Trim();

            // AMD Ryzen 7 2700 Eight-Core Processor
            p = name.IndexOf("-Core");
            if (p > 0) p = name.LastIndexOf(' ', p);
            if (p > 0) name = name[..p].Trim();

            var name2 = name
                  .TrimStart("AMD ", /*"Ryzen ",*/ /*"EPYC",*/ "Genuine ", "Intel(R) ", "Xeon(R) ", "Pentium(R) ", "Celeron(R) ", "CPU")
                  .TrimEnd(" Processor", /*"-Core",*/ " v2", " v3", " v4", " 0", " (Device Tree)")
                  .Trim();
            //if (name2.Contains("Ryzen"))
            //    XTrace.WriteLine("{0} -> {1}", name, name2);

            node.Processor = name2;
        }

        foreach (var item in list.GroupBy(e => e.Processor))
        {
            var key = item.Key + "";
            if (key.Length > 50) key = key[..50];

            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = key;

            var nodes = item.ToList();
            st.Total = nodes.Sum(e => e.ID);
            st.Actives = nodes.Sum(e => e["activeT1"].ToInt());
            st.ActivesT7 = nodes.Sum(e => e["activeT7"].ToInt());
            st.ActivesT30 = nodes.Sum(e => e["activeT30"].ToInt());
            st.News = nodes.Sum(e => e["newT1"].ToInt());
            st.NewsT7 = nodes.Sum(e => e["newT7"].ToInt());
            st.NewsT30 = nodes.Sum(e => e["newT30"].ToInt());

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }

    private void ProjectStat(DateTime date, ConcatExpression selects)
    {
        var category = "项目";
        var list = SearchGroup(date.AddYears(-1), selects & _.ProjectId, _.ProjectId);
        var sts = NodeStat.FindAllByDate(category, date);
        foreach (var node in list)
        {
            var key = node.ProjectName + "";
            if (key.Length > 50) key = key[..50];
            var st = sts.FirstOrDefault(e => e.Key == key);
            if (st == null)
                st = NodeStat.GetOrAdd(category, date, key);
            else
                sts.Remove(st);

            st.LinkItem = node.ProjectId + "";
            st.Total = node.ID;
            st.Actives = node["activeT1"].ToInt();
            st.ActivesT7 = node["activeT7"].ToInt();
            st.ActivesT30 = node["activeT30"].ToInt();
            st.News = node["newT1"].ToInt();
            st.NewsT7 = node["newT7"].ToInt();
            st.NewsT30 = node["newT30"].ToInt();

            st.Update();
        }

        // 删除多余统计项
        sts.Delete();
    }
}