Skywalking自定义拓展线程池监控grpc线程池

1,579 阅读3分钟

背景

SkyWalking Java Agent 8.10.0 增加了对database连接池和tomcat线程池监控的plugin:skywalking.apache.org/events/rele…

如果我们自己想增加一些针对于自己业务的线程池的监控,Skywalking也是提供了拓展点的。

实现

客户端实现

这篇官方文档描述了客户端(也就是应用端)如何上传数据到SkyWalking服务端。

skywalking.apache.org/docs/skywal…

image.png

通过查看官方对tomcat线程池的实现,可以看到主要使用了 MeterFactory.gauge API,我们可以在我们自己的组件中也用类似的写法进行模拟一下。

image.png

通过对tomcat线程池的拦截源码来看,其实核心原理就是在合适的地方拦截到Java线程池对象,然后通过调用线程池对象的getXxxSize来获取到相应的状态,然后上报,

因为我这里的需求是监控grpc运行时的业务线程池,所以首先我需要先编写我的客户端插件,用于取到grpc业务线程池,并对其进行状态监控调用,我的源代码如下:

定义拦截类,我这里是在ServerImpl.start,也就是grpc服务启动的时候进行拦截,获取线程池:

public class GrpcThreadPoolInstrumentation extends ClassEnhancePluginDefine {

    // 代理类
    public static final String INTERCEPT_CLASS =
            "org.apache.skywalking.apm.plugin.grpc.v1.server.GrpcDefaultThreadExecutorInterceptor";
    // 需要拦截的方法
    public static final String ENHANCE_METHOD = "start";
    // 需要拦截的类
    public static final String ENHANCE_CLASS = "io.grpc.internal.ServerImpl";

    @Override
    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
        return new ConstructorInterceptPoint[0];
    }

    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[]{new InstanceMethodsInterceptPoint() {
            @Override
            public ElementMatcher<MethodDescription> getMethodsMatcher() {
                return named(ENHANCE_METHOD);
            }

            @Override
            public String getMethodsInterceptor() {
                return INTERCEPT_CLASS;
            }

            @Override
            public boolean isOverrideArgs() {
                return false;
            }
        }};
    }

    @Override
    public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() {
        return new StaticMethodsInterceptPoint[0];
    }

    @Override
    protected ClassMatch enhanceClass() {
        return byName(ENHANCE_CLASS);
    }

}

我的拦截类里通过反射获取到ServerImpl类中的 executor 属性,该属性就是业务线程池,然后将其注册到 MeterFactory.gauge API中,

注意 METER_NAMETHREAD_POOL_NAME 这两个变量,前者代表你上报到Skywalking中的名字,后者代表tag标签的名字,这里要处理好,不要和之前默认的tomcat重名,

定义好这个后,注意将plugin打包放到agent的plugin目录下,启动项目即可。

public class GrpcDefaultThreadExecutorInterceptor implements InstanceMethodsAroundInterceptor {

    private static final String METER_NAME = "grpc_thread_pool";
    private static final String METRIC_POOL_NAME_TAG_NAME = "pool_name";
    private static final String THREAD_POOL_NAME = "grpc_default_executor";
    private static final String METRIC_TYPE_TAG_NAME = "metric_type";

    @Override
    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
                             MethodInterceptResult result) throws Throwable {
    }

    @Override
    public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
                              Object ret) throws Throwable {
        Field field = ret.getClass().getDeclaredField("executor");
        field.setAccessible(true);
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) field.get(ret);
        MeterFactory.gauge(METER_NAME, () -> (double) threadPoolExecutor.getCorePoolSize())
                .tag(METRIC_POOL_NAME_TAG_NAME, THREAD_POOL_NAME)
                .tag(METRIC_TYPE_TAG_NAME, "core_pool_size")
                .build();
        MeterFactory.gauge(METER_NAME, () -> (double) threadPoolExecutor.getMaximumPoolSize())
                .tag(METRIC_POOL_NAME_TAG_NAME, THREAD_POOL_NAME)
                .tag(METRIC_TYPE_TAG_NAME, "max_pool_size")
                .build();
        MeterFactory.gauge(METER_NAME, () -> (double) threadPoolExecutor.getPoolSize())
                .tag(METRIC_POOL_NAME_TAG_NAME, THREAD_POOL_NAME)
                .tag(METRIC_TYPE_TAG_NAME, "pool_size")
                .build();
        MeterFactory.gauge(METER_NAME, () -> (double) threadPoolExecutor.getQueue().size())
                .tag(METRIC_POOL_NAME_TAG_NAME, THREAD_POOL_NAME)
                .tag(METRIC_TYPE_TAG_NAME, "queue_size")
                .build();
        MeterFactory.gauge(METER_NAME, () -> (double) threadPoolExecutor.getActiveCount())
                .tag(METRIC_POOL_NAME_TAG_NAME, THREAD_POOL_NAME)
                .tag(METRIC_TYPE_TAG_NAME, "active_size")
                .build();
        return ret;
    }

    @Override
    public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
                                      Class<?>[] argumentsTypes, Throwable t) {
    }
}

SkyWalking服务端实现

注意,plugin中写好了自己的上报程序后,服务端还是需要做出配置的,否则上报上去的数据无法被加载到。

skywalking.apache.org/docs/main/l…

image.png

意思就是,如果需要SkyWalking收集更多的信息,需要在 meterAnalyzerActiveFiles 中配置,SkyWalking在启动时会加载其中的内容:

SkyWalking内置了下面4个收集,grpc是我后面根据thread pool加的。

image.png

thread pool内容如下:

image.png

我定义的grpc thread pool内容如下,其实就是拷贝了一份,然后修改了 metricsRules 中的 name:

image.png

最后需要在截图中的application.yaml的meterAnalyzerActiveFiles中添加上我们自定义的grpcthreadpool.yaml

image.png

image.png

(其实这一步不用新加文件也可以,我们可以将我们自己的线程信息也定义在threadpool.yaml中):

image.png

最后重启你的SkyWalking后端,以及启动的你的应用程序,在SkyWalking提供的界面中做出配置:

点击普通服务,然后点击你的服务名字进入下一个页面:

image.png

进入instance页面后,点击相应的instance:

image.png

然后进入JVM的Tab,开启编辑模式,即右上角那个蓝色的按钮,默认是灰色的,点击一下就成了蓝色,然后点击左边按钮,再点击Add Widget,就会出现大箭头的无图表:

image.png

再点击无图标右上方的三个点,再点击弹出的编辑按钮,在弹出的页面中,指标该项,输入grpc关键字,就可以看到我们之前定义的名字:

image.png

选中指标,然后点击选择图形样式,再输入面板的自定义名称,最后点击确认即可:

image.png

最后客户端上报数据后,该面板将实时展示数据:

image.png

image.png

本文参考了:developpaper.com/skywalking-…