There are already a lot of frames on the market, so why do we have to reinvent the wheel? As business development is changing all the time, we have to build a wheel to adapt to the business. Before building the wheel, we first list several popular frameworks for comparison, list their advantages and disadvantages, and then summarize our own wheels
Scheme comparison
The framework | performance | The technical implementation | Method Parameter acquisition | confusion | The scope of |
---|---|---|---|---|---|
hugo | poor | aspectJ | support | Does not support | Java classes |
costTime | good | asm | Does not support | support | App module |
matrix | good | asm | Does not support | support | All modules |
Mamba | good | asm | support | support | All modules |
1, Hugo
Hugo is using aspectJ implementation of method staking, use is very simple, just need to add @debuglog annotation to the method to obtain the execution time of the method. Because aspectJ is used, it only works on Java files, not AAR files, and the whole process of getting methods and parameters is time-consuming, as can be seen in the enterMethod and exitMethod methods. Hugo also does not support confusion. Codesignatment. getName got the confused method, but could not get the original function name, so it could not collect statistics. Maybe Hugo’s location is just debug statistics, as can be seen from the annotation DebugLog.
2, costTime
The plugin will scan all the methods under the class to see if there is @COST annotation. If there is, the method will be staked. The effect of staking is as follows:
System.out.println("========start=========");
TimeUtil.setsStartTime("newFunc", System.nanoTime());
// The implementation of the original method
TimeUtil.setEndTime("newFunc", System.nanoTime());
System.out.println(TimeCache.getCostTime("newFunc"));
System.out.println("========end=========");
Copy the code
CostTime only supports the current App Module. See transfrom for details. The jar part generated by the library is not staked.
3, Matrix
Matrix is an APM framework of Tencent, which implements the piling of methods in matrix-Gradle-Plugin module. The specific principle can be referred to my article “TraceCanary source code analysis of Matrix”. Matrix does not record the name of the method, but generates a unique methodId for each method of piling, and generates a configuration file of method and methodId mapping. When reporting data, only uploading methodId is required. Cloud need only through a configuration file parsing out methodId corresponding method, can see the whole method invocation chain, advantage is obvious, of course, data size and memory optimization do very extreme, faults have, methodId will change as the change of version, need to maintain each version of the configuration file, when doing data analysis, Need to adjust according to the version number.
4, Mamba
The implementation of Mamba is similar to Matrix, but the content of piling is not methodId, but the current class, method name and method parameters. The effect of piling is as follows:
public void test(a) {
long start = System.currentTimeMillis();
Class<Test> cls = Test.class;
Mamba.i(cls, "test");
// The original method body
Mamba.i(cls, "test");
}
Copy the code
Mamba does no logic of its own, leaving the start and end of the method body to the class that implements IMambaLoader. Mamba also provides the function of using @track annotation to capture method information, which is used to assist in obtaining parameter values of traceless burying point scheme. The effect of pile insertion is as follows:
/ / the original method
@Track
private void open(String t, float a, double b, long c) {
Toast.makeText(this."What can I say? Mamba out", Toast.LENGTH_SHORT).show();
}
// Insert the pile after the effect
private void open(String str, float f, double d, long j) {
Class<TrackActivity> cls = TrackActivity.class;
Mamba.i(cls, "open", str, Float.valueOf(f), Double.valueOf(d), Long.valueOf(j));
Toast.makeText(this."What can I say? Mamba out".0).show();
}
Copy the code
The downside is that the base type needs to be boxed as a reference type and stored in an Object array
Mamba implementation
Mamba uses gradle-Plugin and ASM implementations to insert methods. Mamba iterates through the class of full Project and uses ASM to insert bytecode at the beginning and end of the method. Why is the bytecode inserted by Mamba Class, MethodName, Method Params?
- The main purpose of inserting a Class is to better locate the method execution process, because each Class will have the same method name, resulting in unclear call chain
- MethodName is necessary because the MethodName is logged when the peg is inserted so that the call chain can be logged normally even if the application package is confused
- Method Params records are mainly for further capture of methods
Tell me more about Method Params
In business practice, it is impossible to achieve a traceless burying point scheme. Some burying points are context-dependent and record the current value of variables, so we have to hardcode the burying point in business code.
One solution I came up with to solve the hardknitting problem was: Write the places that need to be buried into function calls, and then take the variables that need to be recorded as the parameters of the function, and mark @track for the function. Then Mamba will automatically implement the plugging of methods and parameters according to @track annotation. We only need to bury the data in Mamba’s implementation class.
Here is an example of how to do this: Log the userName variable in the click event
public class MyActivity{
public void onClick(View view){
String userName = editUserName.getText().toString();
updateUser(userName);
// in general, we might hardcode directly, such as trackmanager.get ().logevent (" get userName ",userName)
}
/* * Update the user name */
private void updateUser(String userName){... }}Copy the code
Let’s change it:
public class MyActivity{
public void onClick(View view){
String userName = editUserName.getText().toString();
updateUser(userName)
}
// Add a Track annotation to the update user
@Track
private void updateUser(String userName){... }}Copy the code
The bytecode generation results in:
public class MyActivity{
public void onClick(View view){
String userName = editUserName.getText().toString();
updateUser(userName)
}
@Track
private void updateUser(String userName){
Class<MyActivity> cls = MyActivity.class;
Mamba.i(cls, "updateUser", userName); . }}Copy the code
Class = MyActivity; method = updateUser; params = MyActivity; method = updateUser;
override fun methodEnter(clazz: Class<*>? , methodName: String? , args: Array
?)
{
when (clazz) {
MyActivity::class.java -> {
trackMyActivity(methodName, args)
}
}
}
private fun trackMyActivity(methodName: String? , args: Array
?)
{
when(methodName){
"updateUser"- > {// Get the userName valueval userName = args!! [0] as String
// Use TrackManager to bury points}}}Copy the code
Although @track still needs to be edited in the business code, it is already a minimal intrusion into the business code. Even if the user name does not need to be recorded in the future, we do not need to delete the @track annotation, just remove the judgment of updateUser in the Mamba implementation class. The reader may ask, why don’t you just automate collecting method parameters instead of using annotations to invade the business? In fact, I also thought of this solution, but for the base type parameter is very unfriendly, if I want to collect uniform method parameters, you must use a everyone some parent container to save, so here defines the Object array to store the parameters, but the problem again, the base type what would you do without a parent class, only the base type packing into a reference type, That is, wrapping float as float.valueof () and storing it in an Object array consumes memory. Imagine wrapping all method arguments and collecting them, and performance becomes a problem. So, use @track to annotate what you think you need to collect.
performance
You may be concerned about the performance after piling. Here are the test cases and results:
1. Method Pile insertion, which takes 0 ms for multiple tests
2. Method parameter staking, multiple tests, about 2 milliseconds
Pay attention to
- Method parameter collection currently supports a maximum of five parameters.
- When pegging, you also need to configure exclude for the Mamba implementation class to avoid method circular calls due to pegging
conclusion
Overall, the solution implementations are similar, with slight differences in business implementations.
Mamba also provides two default implementation classes:
- CostTimeLoader: Statistics method time
- TrackLoader: Captures method information
See Mamba README: github.com/MRwangqi/Ma…