[fix]GetNext
大石头 编写于 2024-06-25 16:49:36
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; }

    /// <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)
    {
        Tracer = tracer;
        Name = name;
        MinCost = -1;

        StartTime = DateTime.UtcNow.ToLong();
    }
    #endregion

    #region 方法
    /// <summary>开始一个Span,开始计时</summary>
    /// <returns></returns>
    public virtual ISpan Start()
    {
        var span = new DefaultSpan(this);
        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 total = Interlocked.Increment(ref _Total);

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

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

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

        var tracer = Tracer;
        if (tracer == null) return;

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

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

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