OpenTelemetry搭配aspire-dashboard来监测系统性能

一、概念介绍

1、什么是OpenTelemetry

OpenTelemetry 是一个可观测性框架和工具包,旨在创建和管理遥测数据,如链路、、指标和日志。OpenTelemetry 对供应商和工具是中立的,这意味着它可以与各种可观测性后端一起使用,包括 Jaeger 和 Prometheus 这类开源工具以及商业化产品。

OpenTelemetry 不是像 Jaeger、Prometheus 或其他商业供应商那样的可观测性后端。OpenTelemetry 专注于遥测数据的生成、采集、管理和导出。OpenTelemetry 的一个主要目标是,无论应用程序或系统采用何种编程语言、基础设施或运行时环境,你都可以轻松地将其仪表化。重要的是,遥测数据的存储和可视化是有意留给其他工具处理的。

2、什么是aspire-dashboard

Aspire-dashboard 是一个用于实时监控和调试应用程序的工具,特别适用于 ASP.NETCore 和其他支持 OpenTelemetry 的应用。它提供了一个用户界面来跟踪日志、指标数据,帮助开发者在本地环境中快速诊断问题。

二、安装OpenTelemetry环境

如下图所示安装所需的 nuget 包,如果想监测本项目的 MongoDB 性能,必须安装 MongoDB.Driver.Core.Extensions.DiagnosticSources 包。

三、开启监测项目本身Mongodb请求的性能

本项目使用 MongoDB 来存储数据,所以我们需要在 MongoClient 初始化之后再启用 OpenTelemetry。而且必须订阅 DiagnosticsActivityEventSubscriber 才能监测本项目的 MongoDB 性能。

cs 复制代码
namespace TelemetryConsole
{
    public static class StartupExtensions
    {
        public static void AddConfigurations(this IServiceCollection services, IConfiguration configuration)
        {
            
        }

        public static void AddServices(this IServiceCollection services)
        {
            services.AddSingleton<IAnalyzerService, AnalyzerService>();
            
        }

        public static void AddMongoDbConnection(this IServiceCollection services, IConfiguration configuration)
        {
            services.Configure<DatabaseSettings>(configuration.GetSection(nameof(DatabaseSettings)));
            services.AddSingleton<IDatabaseSettings>(sp => sp.GetRequiredService<IOptions<DatabaseSettings>>().Value);
            //services.AddSingleton<IMongoClient, MongoClient>(x =>
            //    new MongoClient(((DatabaseSettings)x.GetService(typeof(DatabaseSettings)))
            //        .ConnectionString));
            services.AddSingleton<IMongoClient, MongoClient>(x =>
            {
                /* If we have initialized a MongoClient, then its settings will be frozen.
                 * We cannot enable open telemetry at the moment.
                 * So we only enable open telemetry before initializing the MongoClient.
                 */
                var settings = MongoClientSettings
                    .FromConnectionString(((IDatabaseSettings)x.GetService(typeof(IDatabaseSettings)))?.ConnectionString)
                    .EnableOpenTelemetry();

                return new MongoClient(settings);
            });
            services.AddSingleton<IDbConnectionPool, MongoDbConnectionPool>();
            services.AddSingleton<IBaseDbService, BaseDbService>();
            services.AddSingleton<IGuidWrapper, GuidWrapper>();
            services.AddSingleton<IDateTimeWrapper, DateTimeWrapper>();
        }
    }
}
cs 复制代码
namespace TelemetryConsole.Telemetry
{
    public static class MongoClientExtensions
    {
        /// <summary>
        /// 开启监测项目本身Mongodb请求的性能
        /// </summary>
        /// <param name="settings"></param>
        /// <returns></returns>
        public static MongoClientSettings EnableOpenTelemetry(this MongoClientSettings settings)
        {
            if (settings == null)
            {
                return settings;
            }

            var previous = settings.ClusterConfigurator;
            settings.ClusterConfigurator = cb =>
            {
                previous?.Invoke(cb);
                cb.Subscribe(new DiagnosticsActivityEventSubscriber(
                    new InstrumentationOptions
                    {
                        CaptureCommandText = true
                    }));
            };
            return settings;
        }
    }
}

四、配置启动项

使用OpenTelemetry 最重要的有两点:一是在 appsetting.json 中添加配置Otlp:Endpoint,二是 builder.Services.AddDefaultOpenTelemetry() 配置OpenTelemetry 组件。若跟下面的示例代码类似配置了 o.Tracing.Endpoint = "http://localhost:4317",那不在 appsetting.json 中添加配置也行,配置这个 URL 是为了把生成的遥测数据发过去,让其他组件为我们生成可观测的性能报告。

cs 复制代码
namespace TelemetryConsole
{
    [ExcludeFromCodeCoverage]
    class Program
    {
        public static async Task<int> Main(string[] args)
        {
            try
            {
                var builder = Host.CreateApplicationBuilder();
                var conventionPack = new ConventionPack { new CamelCaseElementNameConvention() };
                ConventionRegistry.Register("camelCase", conventionPack, t => true);

                builder.Services.AddHttpClient();
                builder.Services.AddHostedService<Entry>();
                builder.Services.AddConfigurations(builder.Configuration);
                builder.Services.AddServices();
                builder.Services.AddMongoDbConnection(builder.Configuration);
                
                builder.Services.AddDefaultOpenTelemetry(
                    builder.Configuration,
                    o =>
                    {
                        o.Resource.Namespace = "MyNamespace"; // From configuration 'ServiceNamespace'
                        o.Resource.Version = "0.0.1";         // From configuration 'ServiceVersion'
                        o.Resource.InstanceId = "MyInstance"; // From configuration 'HOSTNAME'
                        o.Tracing.Endpoint = "http://localhost:4317";
                    });

                var host = builder.Build();
                await host.RunAsync();
                return 0;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error occured when running Maxwell Perf Analyzer : {ex}");
                return 1;
            }
        }
    }
}

五、改造待监测的类

AnalyzerService() 是我们要监测的主要类,构造函数中的 ITracerFactory 是改造时添加的,只要是已启动监测的类型(MongoDB、Redis、Http请求),都会生成一条同等级的记录。但我们的代码是嵌套结构,若我们想更清晰的了解某个方法及其内部子方法的性能情况,就要用到ITracerFactory。using var span = _traceFactory.CreateSpan("Main point") 会生成一条目录,using 范围内的监测类型都会在这个目录之下,若子方法内部还有 _traceFactory.CreateSpan("Sub point"),那本目录就会缩进变成子目录,这样有了层级结构报告会更清晰。

cs 复制代码
namespace TelemetryConsole.Services.Impls
{
    public class AnalyzerService : IAnalyzerService
    {
        private const string DefaultUser = "test@slb.com";
        private readonly IBaseDbService _baseDbService;
        private IHttpClientFactory _httpFactory;
        private ITracerFactory _traceFactory;

        public AnalyzerService(IBaseDbService baseDbService, IHttpClientFactory httpFactory, ITracerFactory traceFactory)
        {
            _baseDbService = baseDbService;
            _httpFactory = httpFactory;
            _traceFactory = traceFactory;
        }

        public async Task AnalyzeAsync()
        {
			try
			{
                using var span = _traceFactory.CreateSpan("Main point");

                await QuerySomeData();

                await InsertSomeData();

                await ExecuteSomeHttpRequests();
            }
			catch (Exception EX)
			{
				throw;
			}
        }

        async Task QuerySomeData()
        {
            using var span = _traceFactory.CreateSpan("Query some data");

            var item01 = await _baseDbService.GetAsync<ModificationItem>("6a89034a-6e31-4aeb-a4f0-b36494fe7b18", "wireline-bgc-drillplan-bgc-bil", "modification");
            var item02 = await _baseDbService.GetAsync<ModificationItem>("10208e50-e4aa-447f-893d-dd8a4b29c8e4", "wireline-bgc-drillplan-bgc-bil", "modification");
            string str = "";
            for (int i = 0; i < 10000; i++)
            {
                str += i;
            }
        }

        async Task InsertSomeData()
        {
            using var activity = _traceFactory.CreateSpan("Insert Some Data");

            for (var i = 0; i < 10; i++)
            {
                await _baseDbService.CreateAsync(new ModificationItem
                {
                    Source = ParsingDataSource.Job,
                    ItemId = Guid.NewGuid().ToString()
                }, DefaultUser, null, "modification_wireline-bgc-drillplan-bgc-bil");
            }
            string str = "";
            for (int i = 0; i < 10000; i++)
            {
                str += i;
            }

            activity?.SetTag("RecordInserted", 10);
        }

        async Task ExecuteSomeHttpRequests()
        {
            using (var activity01 = _traceFactory.CreateSpan("Call HTTP API", ActivityKind.Client))
            {
                var client = _httpFactory.CreateClient();
                var response = await client.GetAsync("https://httpbin.org/get");
                var ret = await response.Content.ReadAsStringAsync();
            }

            using (var activity02 = _traceFactory.CreateSpan("Call TelemetryApi", ActivityKind.Client))
            {
                var client = _httpFactory.CreateClient();
                var response01 = await client.GetAsync("https://localhost:7094/ParsingStep?id=d9fff557-1e08-4850-97d7-5cdb73e809e4");
                var ret01 = await response01.Content.ReadAsStringAsync();

                var response02 = await client.GetAsync("https://localhost:7094/ParsingStep?id=e18031b9-1ff4-4320-bfd2-e0f618356ae0");
                var ret02 = await response01.Content.ReadAsStringAsync();
            }
        }
    }
}

六、利用podman启动aspire-dashboard

我在 Windows 系统中安装了WSL2(Ubuntu22.04.5 LTS)、Podman,不熟悉可参考:安装WSL2。启动 aspire-dashboard 之前要先检查 podman 环境是否正常,然后再启动。

bash 复制代码
--检查podman环境是否开启
wsl -l -v

--开启podman环境
podman machine start

--关闭podman环境
podman machine stop

--运行aspire-dashboard
podman run --rm -it -p 18888:18888 -p 4317:18889 -p 4318:18890 -d --name aspire-dashboard -e DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true mcr.microsoft.com/dotnet/aspire-dashboard:9.0

七、启动aspire-dashboard

当我们启动配置了 OpenTelemetry 的项目之后,就会生成遥测数据,访问 http://localhost:18888/traces 即可浏览性能报告。

1、层级结构日志

2、数据库处理请求

3、Http请求

4、调用的API中又请求了数据库

若配置了 OpenTelemetry 的项目,调用的 API 所属的项目也配置了 OpenTelemetry,那性能监测可以穿透到 API 项目。如下图所示,api 内部调用了数据库请求,在这里也能显示出来。

相关推荐
SuniaWang12 天前
《AgentX 专栏》07-全链路可观测:用OpenTelemetry+Jaeger让每次AI对话都可追踪可复盘
java·人工智能·spring·架构·langchain·opentelemetry·agenx
不懂的浪漫1 个月前
OpenTelemetry 和 SkyWalking Agent 怎么选?一次讲清 OTel、SkyWalking Agent 的相同点与区别
wpf·skywalking·链路追踪·opentelemetry·otel
还是码字踏实2 个月前
开源项目解读:Microsoft Multi-Modal Customer Service Agent
microsoft·opentelemetry·pcm16 实时帧·acs bridge·rag 数据摄入·意图路由机制·意图分类器
低调的JVM3 个月前
Golang下kafka可观测数据采集组件Otelsarama详解
golang·kafka·可观测·opentelemetry
低调的JVM4 个月前
EasyTelemetry:让OpenTelemetry拥抱Arthas Trace的强大功能
apm·可观测·javaagent·opentelemetry
我的golang之路果然有问题5 个月前
OpenTelemet 实习中了解到的部分
运维·服务器·opentelemetry
雪雁6 个月前
CodeSpirit・码灵:以 AI 赋能,重构业务智能边界
aspire·.net 10·codespirit
许泽宇的技术分享6 个月前
当 AI Agent 遇上可观测性:AgentOpenTelemetry 让你的智能体不再“黑盒“
人工智能·可观测性·opentelemetry·agentframework
xiangji7 个月前
Aspire+.NET10+手搓线程池打造抓不死的云应用
高并发·aspire·.net10·手搓线程池