交互模式直接运行
大石头 authored at 2024-11-25 09:58:04
3.73 KiB
Stardust
using System.Collections.Concurrent;
using NewLife.Log;
using NewLife.Threading;
using Stardust.Data.Monitors;
using XCode;

namespace Stardust.Server.Services;

/// <summary>应用统计服务</summary>
public interface IAppDayStatService
{
    /// <summary>添加需要统计的应用,去重</summary>
    /// <param name="date"></param>
    void Add(DateTime date);
}

/// <summary>应用统计服务</summary>
public class AppDayStatService : IAppDayStatService
{
    /// <summary>批计算周期。默认30秒</summary>
    public Int32 BatchPeriod { get; set; } = 30;

    private TimerX _timer;
    private readonly ConcurrentBag<DateTime> _bag = new();
    private readonly ITracer _tracer;

    public AppDayStatService(ITracer tracer) => _tracer = tracer;

    /// <summary>添加需要统计的应用,去重</summary>
    /// <param name="date"></param>
    public void Add(DateTime date)
    {
        if (!_bag.Contains(date)) _bag.Add(date);

        // 初始化定时器
        if (_timer == null && BatchPeriod > 0)
        {
            lock (this)
            {
                if (_timer == null && BatchPeriod > 0) _timer = new TimerX(DoTraceStat, null, 5_000, BatchPeriod * 1000) { Async = true };
            }
        }
    }

    private void DoTraceStat(Object state)
    {
        while (_bag.TryTake(out var time))
        {
            using var span = _tracer?.NewSpan("AppDayStat-Process", time.Date);
            try
            {
                Process(time.Date);
            }
            catch (Exception ex)
            {
                span.SetError(ex, null);
                throw;
            }
        }
    }

    private void Process(DateTime date)
    {
        // 统计数据,每日追踪根据应用和类型分组
        var list = TraceDayStat.SearchGroupAppAndType(date);
        if (list.Count == 0) return;

        var ns = TraceDayStat.SearchGroupAppAndItem(date);

        // 统计对象
        var sts = AppDayStat.Search(date, null);
        var sts2 = AppDayStat.Search(date.AddDays(-1), null);

        // 聚合,按应用分组,每一组内每个类型一行
        var dic = list.GroupBy(e => e.AppId);
        foreach (var item in dic)
        {
            var appId = item.Key;
            if (appId <= 0) continue;

            var ds = item.ToList();
            var st = sts.FirstOrDefault(e => e.AppId == appId);
            if (st == null)
            {
                st = new AppDayStat { StatDate = date, AppId = appId };
                sts.Add(st);
            }

            st.Total = ds.Sum(e => e.Total);
            st.Errors = ds.Sum(e => e.Errors);
            st.TotalCost = ds.Sum(e => e.TotalCost);
            st.MaxCost = ds.Max(e => e.MaxCost);
            //st.MinCost = ds.Min(e => e.MinCost);
            var vs2 = ds.Where(e => e.MinCost > 0).ToList();
            if (vs2.Count > 0) st.MinCost = vs2.Min(e => e.MinCost);

            // 分类统计,应用有可能缺失某些类别
            st.Apis = ds.Where(e => e.Type == "api").Sum(e => e.Total);
            st.Https = ds.Where(e => e.Type == "http").Sum(e => e.Total);
            st.Dbs = ds.Where(e => e.Type == "db").Sum(e => e.Total);
            st.Mqs = ds.Where(e => e.Type == "mq").Sum(e => e.Total);
            st.Redis = ds.Where(e => e.Type == "redis").Sum(e => e.Total);
            st.Others = ds.Where(e => e.Type == "other").Sum(e => e.Total);

            // 计算埋点个数
            st.Names = ns.Where(e => e.AppId == appId).Count();

            // 计算环比
            var st2 = sts2.FirstOrDefault(e => e.AppId == appId);
            if (st2 != null) st.RingRate = st2.Total <= 0 ? 1 : (Double)st.Total / st2.Total;

            st.Save();
        }

        //// 保存统计
        //sts.Save(false);
    }
}