Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
03d2bfc
[Feature] Support collecting database connection pool metric,support …
Jan 24, 2022
5e89096
[Feature] Support collecting database connection pool metric,support …
Jan 24, 2022
e15380f
Merge branch 'apache:main' into main
sunOnly Jan 28, 2022
5f6aabe
[Feature] Support collecting database connection pool metric,support …
Jan 28, 2022
63e68b5
Merge branch 'main' of github.com:sunOnly/skywalking-java into main
Jan 28, 2022
8ba0cac
Merge branch 'apache:main' into main
sunOnly Jan 28, 2022
13b1aa9
:wq
Jan 28, 2022
6ca6867
modify format#8450
Jan 28, 2022
5b2f044
druid metric monitor #8450
Jan 28, 2022
1a4da64
Merge branch 'apache:main' into main
sunOnly Jan 29, 2022
6ab8cf7
add support config params collect #96
Feb 7, 2022
b83653f
Merge remote-tracking branch 'origin/main' into main
Feb 7, 2022
499cf50
Merge branch 'main' into main
wu-sheng Feb 7, 2022
a2f3d9c
static field static field Enhanced class changed to interface,add tes…
Feb 8, 2022
c6adcf8
remove static field and log #96
Feb 9, 2022
15aa2ab
format #96
Feb 9, 2022
c08204b
optimization #96
Feb 9, 2022
fd9b150
remove space key #96
Feb 9, 2022
0e142fc
Merge branch 'main' into main
wu-sheng Feb 10, 2022
1ec6f11
add tomcat metric #96
Feb 10, 2022
bbcfb63
Merge branch 'main' of github.com:sunOnly/skywalking-java into main
Feb 10, 2022
b6ee8a0
validate value change #96
Feb 10, 2022
820cd0f
remove datasource metric #96
Feb 10, 2022
aa9f446
Merge branch 'main' into main
wu-sheng Feb 11, 2022
8ae7517
optimize interception point #96
Feb 11, 2022
25c9f46
Merge branch 'main' of github.com:sunOnly/skywalking-java into main
Feb 11, 2022
bee196f
Optimize interception point #96
Feb 11, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Release Notes.
* Support Tomcat thread pool metric collect.
* Remove plugin for ServiceComb Java Chassis 0.x
* Add Guava EventBus plugin.
* Support Druid Connection pool metrics collecting.
* Support HikariCP Connection pool metrics collecting.
* Support Dbcp2 Connection pool metrics collecting.

#### Documentation

Expand Down
10 changes: 7 additions & 3 deletions apm-sniffer/apm-sdk-plugin/dbcp-2.x-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@
<version>${dbcp.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-jdbc-commons</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>


</project>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.skywalking.apm.plugin.dbcp.v2;

import org.apache.commons.dbcp2.BasicDataSourceMXBean;
import org.apache.skywalking.apm.agent.core.meter.MeterFactory;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* {@link PoolingJmxRegisterInterceptor} intercepted the method of DBCP jmxRegister register metric monitor.
*/
public class PoolingJmxRegisterInterceptor implements InstanceMethodsAroundInterceptor {
private static final String METER_NAME = "datasource";

@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 {
if (objInst.getSkyWalkingDynamicField() != null) {
BasicDataSourceMXBean basicDataSource = (BasicDataSourceMXBean) objInst;
String tagValue = (String) objInst.getSkyWalkingDynamicField();
Map<String, Function<BasicDataSourceMXBean, Supplier<Double>>> metricMap = getMetrics();
metricMap.forEach((key, value) -> MeterFactory.gauge(METER_NAME, value.apply(basicDataSource))
.tag("name", tagValue).tag("status", key).build());
}
return ret;
}

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

}

private Map<String, Function<BasicDataSourceMXBean, Supplier<Double>>> getMetrics() {
Map<String, Function<BasicDataSourceMXBean, Supplier<Double>>> metricMap = new HashMap();
metricMap.put("numActive", (BasicDataSourceMXBean basicDataSource) -> () -> (double) basicDataSource.getNumActive());
metricMap.put("maxTotal", (BasicDataSourceMXBean basicDataSource) -> () -> (double) basicDataSource.getMaxTotal());
metricMap.put("numIdle", (BasicDataSourceMXBean basicDataSource) -> () -> (double) (basicDataSource.getNumIdle()));
metricMap.put("maxWaitMillis", (BasicDataSourceMXBean basicDataSource) -> () -> (double) basicDataSource.getMaxWaitMillis());
metricMap.put("maxIdle", (BasicDataSourceMXBean basicDataSource) -> () -> (double) basicDataSource.getMaxIdle());
metricMap.put("minIdle", (BasicDataSourceMXBean basicDataSource) -> () -> (double) basicDataSource.getMinIdle());
metricMap.put("initialSize", (BasicDataSourceMXBean basicDataSource) -> () -> (double) basicDataSource.getInitialSize());
return metricMap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.skywalking.apm.plugin.dbcp.v2;

import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
import org.apache.skywalking.apm.plugin.jdbc.connectionurl.parser.URLParser;
import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo;

import java.lang.reflect.Method;

/**
* {@link PoolingSetUrlInterceptor} intercepted the method of DBCP set url Get parameters.
*/
public class PoolingSetUrlInterceptor implements InstanceMethodsAroundInterceptor {
private static final String METER_NAME = "datasource";

@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 {
ConnectionInfo connectionInfo = URLParser.parser((String) allArguments[0]);
String tagValue = connectionInfo.getDatabaseName() + "_" + connectionInfo.getDatabasePeer();
objInst.setSkyWalkingDynamicField(tagValue);
return ret;
}

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

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@
/**
* BasicDataSource provides a "one stop shopping" solution for database connection pool solution
* basic requirements. BasicDataSource#getConnection() creates (if necessary) and return a connection.
* BasicDataSource#setUrl(String) set url
*/
public class BasicDataSourceInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
private static final String ENHANCE_CLASS = "org.apache.commons.dbcp2.BasicDataSource";
private static final String CONNECT_GET_INTERCEPTOR = "org.apache.skywalking.apm.plugin.dbcp.v2.PoolingGetConnectInterceptor";
private static final String INTERCEPTOR_URL_CLASS = "org.apache.skywalking.apm.plugin.dbcp.v2.PoolingSetUrlInterceptor";
private static final String INTERCEPTOR_CREATE_CLASS = "org.apache.skywalking.apm.plugin.dbcp.v2.PoolingJmxRegisterInterceptor";

@Override
protected ClassMatch enhanceClass() {
Expand All @@ -48,7 +51,7 @@ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {

@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
return new InstanceMethodsInterceptPoint[]{
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
Expand All @@ -60,6 +63,38 @@ public String getMethodsInterceptor() {
return CONNECT_GET_INTERCEPTOR;
}

@Override
public boolean isOverrideArgs() {
return false;
}
},
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named("setUrl");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a final comment:

As I've stated before, we have to be careful with the interceptor points for Alibaba/Druid and HikariCP. But I'am not quite familiar with this dbcp2 DataSource.

So, could you please justify the choice of this interceptor point?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sunOnly First of all, good to see the tests passed. Let's follow this one. #setUrl is not a good point to begin the monitoring, because in the runtime, a datasource could be not-created at this moment, even never(code bug).

I was looking through the codes, it is likely createDataSource is a better method to begin all metrics registration and reporting. Meanwhile, I understand you intercept #setUrl is to get the URL parameter, I noticed there is getUrl method in the class, but with a synchronized, which I have concerns about deadlock. So, it is better you keep this setUrl method intercepting, but keep the value of URL into the EnhancedInstance#setSkyWalkingDynamicField. Then in new createDataSource interceptor, you could use #getSkyWalkingDynamicField to read this.

This would be more robust.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sunOnly First of all, good to see the tests passed. Let's follow this one. #setUrl is not a good point to begin the monitoring, because in the runtime, a datasource could be not-created at this moment, even never(code bug).

I was looking through the codes, it is likely createDataSource is a better method to begin all metrics registration and reporting. Meanwhile, I understand you intercept #setUrl is to get the URL parameter, I noticed there is getUrl method in the class, but with a synchronized, which I have concerns about deadlock. So, it is better you keep this setUrl method intercepting, but keep the value of URL into the EnhancedInstance#setSkyWalkingDynamicField. Then in new createDataSource interceptor, you could use #getSkyWalkingDynamicField to read this.

This would be more robust.
get

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking through the codes, it is likely createDataSource is a better method to begin all metrics registration and reporting. Meanwhile, I understand you intercept #setUrl is to get the URL parameter, I noticed there is getUrl method in the class, but with a synchronized, which I have concerns about deadlock. So, it is better you keep this setUrl method intercepting, but keep the value of URL into the EnhancedInstance#setSkyWalkingDynamicField. Then in new createDataSource interceptor, you could use #getSkyWalkingDynamicField to read this.

What about using jmxRegister() called within createDataSource() (if dataSource is not created) as the interception point?

Since every call to getConnection() will invoke createDataSource(), the performance may be impacted. It also exists from 2.0.1 to 2.9.0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since every call to getConnection() will invoke createDataSource()

Really? This doesn't seem reasonable.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using jmxRegister() called within createDataSource() (if dataSource is not created) as the interception point?

@sunOnly Could you try this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since every call to getConnection() will invoke createDataSource()

Really? This doesn't seem reasonable.

https://github.com/apache/commons-dbcp/blob/466091e51d875d2a4499adbbb63058e32ad5a7c7/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java#L732

Seems so.

}

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

@Override
public boolean isOverrideArgs() {
return false;
}
},
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named("jmxRegister");
}

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

@Override
public boolean isOverrideArgs() {
return false;
Expand Down
10 changes: 7 additions & 3 deletions apm-sniffer/apm-sdk-plugin/druid-1.x-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@
<version>${druid.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-jdbc-commons</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>


</project>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.skywalking.apm.plugin.druid.v1;

import com.alibaba.druid.pool.DruidDataSourceMBean;
import org.apache.skywalking.apm.agent.core.meter.MeterFactory;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.StaticMethodsAroundInterceptor;
import org.apache.skywalking.apm.plugin.jdbc.connectionurl.parser.URLParser;
import org.apache.skywalking.apm.plugin.jdbc.trace.ConnectionInfo;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* {@link PoolingAddDruidDataSourceInterceptor} intercepted the method of druid addDataSource and register metric monitor.
*/
public class PoolingAddDruidDataSourceInterceptor implements StaticMethodsAroundInterceptor {
private static final String METER_NAME = "datasource";

@Override
public void beforeMethod(Class clazz, Method method, Object[] allArguments, Class<?>[] parameterTypes, MethodInterceptResult result) {

}

@Override
public Object afterMethod(Class clazz, Method method, Object[] allArguments, Class<?>[] parameterTypes, Object ret) {
DruidDataSourceMBean druidDataSource = (DruidDataSourceMBean) allArguments[0];
ConnectionInfo connectionInfo = URLParser.parser(druidDataSource.getUrl());
String tagValue = connectionInfo.getDatabaseName() + "_" + connectionInfo.getDatabasePeer();
Map<String, Function<DruidDataSourceMBean, Supplier<Double>>> metricMap = getMetrics();
metricMap.forEach((key, value) -> MeterFactory.gauge(METER_NAME, value.apply(druidDataSource))
.tag("name", tagValue).tag("status", key).build());
return ret;
}

@Override
public void handleMethodException(Class clazz, Method method, Object[] allArguments, Class<?>[] parameterTypes, Throwable t) {

}

private Map<String, Function<DruidDataSourceMBean, Supplier<Double>>> getMetrics() {
Map<String, Function<DruidDataSourceMBean, Supplier<Double>>> metricMap = new HashMap();
metricMap.put("activeCount", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getActiveCount());
metricMap.put("poolingCount", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getPoolingCount());
metricMap.put("idleCount", (DruidDataSourceMBean druidDataSource) -> () -> (double) (druidDataSource.getPoolingCount() - druidDataSource.getActiveCount()));
metricMap.put("lockQueueLength", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getLockQueueLength());
metricMap.put("maxWaitThreadCount", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getMaxWaitThreadCount());
metricMap.put("commitCount", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getCommitCount());
metricMap.put("connectCount", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getConnectCount());
metricMap.put("connectError", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getConnectErrorCount());
metricMap.put("createError", (DruidDataSourceMBean druidDataSource) -> () -> (double) druidDataSource.getCreateErrorCount());
return metricMap;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;

/**
Expand Down Expand Up @@ -94,4 +94,5 @@ public boolean isOverrideArgs() {
public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() {
return new StaticMethodsInterceptPoint[0];
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.skywalking.apm.plugin.druid.v1.define;

import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.StaticMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassStaticMethodsEnhancePluginDefine;
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;

import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;

/**
* Druid is a database connection pool from Alibaba inc.
* <p>
* DruidDataSource provides a "one stop" solution for database connection pool solution
* basic requirements. DruidDataSourceStatManager.addDataSource(Object dataSource, String name)
*/
public class DruidDataSourceStatManagerInstrumentation extends ClassStaticMethodsEnhancePluginDefine {
private static final String ENHANCE_CLASS = "com.alibaba.druid.stat.DruidDataSourceStatManager";
private static final String ENHANCE_METHOD = "addDataSource";
private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.druid.v1.PoolingAddDruidDataSourceInterceptor";

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

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

@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[0];
}

@Override
public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() {
return new StaticMethodsInterceptPoint[]{
new StaticMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named(ENHANCE_METHOD).and(takesArguments(Object.class, String.class));
}

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

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