使用全局时间提供者,规避运行环境时间偏差问题,在注入星尘以后,自动屏蔽本地和服务器之间的时间差,所得到的当前时间接近于星尘平台服务器。
智能大石头 authored at 2025-01-07 19:20:49
7.56 KiB
X
using System.Runtime.Serialization;
using System.Web.Script.Serialization;
using System.Xml.Serialization;

namespace NewLife.Log;

/// <summary>跟踪片段构建器</summary>
public interface ISpanBuilder
{
    #region 属性
    /// <summary>跟踪器</summary>
    [XmlIgnore, ScriptIgnore, IgnoreDataMember]
    ITracer? Tracer { get; }

    /// <summary>操作名</summary>
    String Name { get; set; }

    /// <summary>开始时间。Unix毫秒</summary>
    Int64 StartTime { get; set; }

    /// <summary>结束时间。Unix毫秒</summary>
    Int64 EndTime { get; set; }

    /// <summary>采样总数</summary>
    Int32 Total { get; }

    /// <summary>错误次数</summary>
    Int32 Errors { get; }

    /// <summary>总耗时。所有请求耗时累加,单位ms</summary>
    Int64 Cost { get; }

    /// <summary>最大耗时。单位ms</summary>
    Int32 MaxCost { get; }

    /// <summary>最小耗时。单位ms</summary>
    Int32 MinCost { get; }

    /// <summary>用户数值。记录数字型标量,如每次数据库操作行数,星尘平台汇总统计</summary>
    Int64 Value { get; set; }

    /// <summary>正常采样</summary>
    IList<ISpan>? Samples { get; set; }

    /// <summary>异常采样</summary>
    IList<ISpan>? ErrorSamples { get; set; }
    #endregion

    #region 方法
    /// <summary>开始一个Span</summary>
    /// <returns></returns>
    ISpan Start();

    /// <summary>完成Span</summary>
    /// <param name="span"></param>
    void Finish(ISpan span);
    #endregion
}

/// <summary>跟踪片段构建器</summary>
public class DefaultSpanBuilder : ISpanBuilder
{
    #region 属性
    /// <summary>跟踪器</summary>
    [XmlIgnore, ScriptIgnore, IgnoreDataMember]
    public ITracer? Tracer { get; set; }

    /// <summary>埋点名</summary>
    public String Name { get; set; } = null!;

    /// <summary>开始时间。Unix毫秒</summary>
    public Int64 StartTime { get; set; }

    /// <summary>结束时间。Unix毫秒</summary>
    public Int64 EndTime { get; set; }

    private Int32 _Total;
    /// <summary>采样总数</summary>
    public Int32 Total { get => _Total; set => _Total = value; }

    private Int32 _Errors;
    /// <summary>错误次数</summary>
    public Int32 Errors { get => _Errors; set => _Errors = value; }

    private Int64 _Cost;
    /// <summary>总耗时。所有请求耗时累加,单位ms</summary>
    public Int64 Cost { get => _Cost; set => _Cost = value; }

    /// <summary>最大耗时。单位ms</summary>
    public Int32 MaxCost { get; set; }

    /// <summary>最小耗时。单位ms</summary>
    public Int32 MinCost { get; set; }

    private Int64 _Value;
    /// <summary>用户数值。记录数字型标量,如每次数据库操作行数,星尘平台汇总统计</summary>
    public Int64 Value { get => _Value; set => _Value = value; }

    /// <summary>正常采样</summary>
    public IList<ISpan>? Samples { get; set; }

    /// <summary>异常采样</summary>
    public IList<ISpan>? ErrorSamples { get; set; }
    #endregion

    #region 构造函数
    /// <summary>实例化</summary>
    public DefaultSpanBuilder() { }

    /// <summary>实例化</summary>
    /// <param name="tracer"></param>
    /// <param name="name"></param>
    public DefaultSpanBuilder(ITracer tracer, String name)
    {
        Init(tracer, name);
    }
    #endregion

    #region 方法
    /// <summary>初始化。重用已有对象</summary>
    /// <param name="tracer"></param>
    /// <param name="name"></param>
    public void Init(ITracer tracer, String name)
    {
        Tracer = tracer;
        Name = name;
        MinCost = -1;
        StartTime = Runtime.UtcNow.ToLong();
    }

    /// <summary>开始一个Span,开始计时</summary>
    /// <returns></returns>
    public virtual ISpan Start()
    {
        DefaultSpan? span = null;
        var tracer = Tracer;
        if (tracer is DefaultTracer tracer2)
        {
            span = tracer2.SpanPool.Get() as DefaultSpan;
            if (span != null)
            {
                span.Tracer = tracer2;
                span.Name = Name;
#pragma warning disable CS0612 // 类型或成员已过时
                span.Builder = this;
#pragma warning restore CS0612 // 类型或成员已过时
            }
        }

        span ??= new DefaultSpan(tracer!);
        span.Start();

        // 指示当前节点开始的后续节点强制采样
        if (span.TraceFlag == 0 && tracer != null && Total < tracer.MaxSamples) span.TraceFlag = 1;

        return span;
    }

    /// <summary>完成Span,每一个埋点结束都进入这里</summary>
    /// <param name="span"></param>
    public virtual void Finish(ISpan span)
    {
        var tracer = Tracer;
        if (tracer == null) return;

        // 累计耗时。时间回退的耗时,一律清零,避免出现负数耗时
        var cost = (Int32)(span.EndTime - span.StartTime);
        if (cost < 0) cost = 0;

        // 抛弃耗时过大的埋点,避免污染统计数据
        if (cost > 3600_000) return;

        Interlocked.Add(ref _Cost, cost);

        // 总次数
        var total = Interlocked.Increment(ref _Total);

        // 累加用户数值
        if (span.Value != 0) Interlocked.Add(ref _Value, span.Value);

        // 最大最小耗时
        if (MaxCost < cost) MaxCost = cost;
        if (MinCost > cost || MinCost < 0) MinCost = cost;

        // 检查跟踪标识,上游指示强制采样,确保链路采样完整
        var force = false;
        var ds = span as DefaultSpan;
        if (ds != null && ds.TraceFlag > 0) force = true;

        // 处理采样
        var flag = false;
        if (span.Error != null)
        {
            if (Interlocked.Increment(ref _Errors) <= tracer.MaxErrors || force && _Errors <= tracer.MaxErrors * 10)
            {
                var ss = ErrorSamples ??= [];
                lock (ss)
                {
                    ss.Add(span);
                    flag = true;
                }
            }
        }
        // 未达最大数采样,超时采样,强制采样
        else if (total <= tracer.MaxSamples || (tracer.Timeout > 0 && cost > tracer.Timeout || force) && total <= tracer.MaxSamples * 10)
        {
            var ss = Samples ??= [];
            lock (ss)
            {
                ss.Add(span);
                flag = true;
            }
        }

        // 如果埋点没有加入链表,则归还对象池
        if (!flag && Tracer is DefaultTracer tracer2)
        {
            ds?.Clear();
            tracer2.SpanPool.Return(span);
        }
    }

    /// <summary>把集合中的ISpan归还到池里</summary>
    /// <param name="spans"></param>
    internal void Return(IList<ISpan>? spans)
    {
        if (spans == null) return;
        if (Tracer is not DefaultTracer tracer) return;

        foreach (var elm in spans)
        {
            if (elm is DefaultSpan ds)
            {
                ds.Clear();
                tracer.SpanPool.Return(ds);
            }
        }
    }

    /// <summary>清空已有数据</summary>
    public void Clear()
    {
        //!!! 不能清空Tracer,否则长时间Span完成时,Builder已被处理,Span无法创建新的Builder来统计埋点数据
        //Tracer = null;

        Name = null!;
        StartTime = 0;
        EndTime = 0;
        Value = 0;
        Samples = null;
        ErrorSamples = null;

        _Total = 0;
        _Errors = 0;
        _Cost = 0;
        MaxCost = 0;
        MinCost = 0;
    }

    /// <summary>已重载。</summary>
    /// <returns></returns>
    public override String? ToString() => Name;
    #endregion
}