feat:android show performance waterfall

This commit is contained in:
pengfei.zhou 2021-07-20 17:26:14 +08:00 committed by osborn
parent a021aae3de
commit 8e2ebaf833
6 changed files with 246 additions and 36 deletions

View File

@ -17,6 +17,13 @@ package pub.doric.devkit;
import android.util.Log; import android.util.Log;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import pub.doric.performance.DoricPerformanceProfile; import pub.doric.performance.DoricPerformanceProfile;
/** /**
@ -25,16 +32,57 @@ import pub.doric.performance.DoricPerformanceProfile;
* @CreateDate: 2021/7/20 * @CreateDate: 2021/7/20
*/ */
public class DoricDevPerformanceAnchorHook implements DoricPerformanceProfile.GlobalAnchorHook { public class DoricDevPerformanceAnchorHook implements DoricPerformanceProfile.GlobalAnchorHook {
public static class AnchorNode {
public String name;
public long prepare;
public long start;
public long end;
AnchorNode(String name, long prepare, long start, long end) {
this.name = name;
this.prepare = prepare;
this.start = start;
this.end = end;
}
}
private static final String TAG = "DoricPerformance"; private static final String TAG = "DoricPerformance";
private final Map<String, List<AnchorNode>> nodeMap = new HashMap<>();
private final Comparator<AnchorNode> comparator = new Comparator<AnchorNode>() {
@Override
public int compare(AnchorNode o1, AnchorNode o2) {
return (int) (o1.prepare - o2.prepare);
}
};
@Override @Override
public void onAnchor(DoricPerformanceProfile profile, String name, long prepare, long start, long end) { public void onAnchor(DoricPerformanceProfile profile, String name, long prepare, long start, long end) {
Log.d(TAG, String.format("%s: %s prepared %dms, cost %dms.", Log.d(TAG, String.format("%s: %s prepared %dms, cost %dms.",
profile.getName(), name, start - prepare, end - start)); profile.getName(), name, start - prepare, end - start));
List<AnchorNode> list = nodeMap.get(profile.getName());
if (list == null) {
list = new ArrayList<>();
nodeMap.put(profile.getName(), list);
}
list.add(new AnchorNode(name, prepare, start, end));
Collections.sort(list, comparator);
if (name.equals(DoricPerformanceProfile.STEP_DESTROY)) {
nodeMap.remove(profile.getName());
}
} }
@Override @Override
public void onAnchor(String name, long prepare, long start, long end) { public void onAnchor(String name, long prepare, long start, long end) {
//DO nothing //DO nothing
} }
public List<AnchorNode> getAnchorNodeList(String name) {
List<AnchorNode> ret = nodeMap.get(name);
if (ret == null) {
ret = new ArrayList<>();
}
return ret;
}
} }

View File

@ -29,13 +29,13 @@ import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList; import java.util.LinkedList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Locale;
import pub.doric.devkit.DoricDevPerformanceAnchorHook;
import pub.doric.devkit.R; import pub.doric.devkit.R;
import pub.doric.performance.DoricPerformanceProfile;
/** /**
* @Description: pub.doric.devkit.ui * @Description: pub.doric.devkit.ui
@ -43,6 +43,9 @@ import pub.doric.devkit.R;
* @CreateDate: 2021/7/19 * @CreateDate: 2021/7/19
*/ */
public class DoricDevPerfActivity extends DoricDevBaseActivity { public class DoricDevPerfActivity extends DoricDevBaseActivity {
private MyAdapter myAdapter;
private TextView tvBtn;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -51,7 +54,33 @@ public class DoricDevPerfActivity extends DoricDevBaseActivity {
textView.setText(String.format("Doric %s <%s>", doricContext.getSource(), doricContext.getContextId())); textView.setText(String.format("Doric %s <%s>", doricContext.getSource(), doricContext.getContextId()));
RecyclerView recyclerView = findViewById(R.id.list); RecyclerView recyclerView = findViewById(R.id.list);
recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new MyAdapter()); myAdapter = new MyAdapter();
recyclerView.setAdapter(myAdapter);
tvBtn = findViewById(R.id.tv_button);
tvBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (myAdapter.isAllExpanded()) {
for (AnchorItem anchorItem : myAdapter.anchorNodes) {
anchorItem.expanded = false;
}
} else {
for (AnchorItem anchorItem : myAdapter.anchorNodes) {
anchorItem.expanded = true;
}
}
myAdapter.notifyDataSetChanged();
updateButton();
}
});
}
private void updateButton() {
if (myAdapter.isAllExpanded()) {
tvBtn.setText("Collapse[-]");
} else {
tvBtn.setText("Expand[+]");
}
} }
private static class PerfCellHolder extends RecyclerView.ViewHolder { private static class PerfCellHolder extends RecyclerView.ViewHolder {
@ -59,43 +88,68 @@ public class DoricDevPerfActivity extends DoricDevBaseActivity {
private LinearLayout layoutWaterfall; private LinearLayout layoutWaterfall;
private View waterfallPrepared; private View waterfallPrepared;
private View waterfallWorked; private View waterfallWorked;
private View waterfallPrefix;
private View waterfallSuffix;
private LinearLayout layoutExpand; private LinearLayout layoutExpand;
private TextView tvFuncName; private TextView tvFuncName;
private TextView tvParameter; private TextView tvParameter;
private TextView tvCost; private TextView tvCost;
public PerfCellHolder(@NonNull View itemView) { private PerfCellHolder(@NonNull View itemView) {
super(itemView); super(itemView);
} }
} }
private static class AnchorNode { private static class AnchorItem {
String name; private String name;
long prepare; private long position;
private long prepared;
private long worked;
private boolean expanded;
} }
private class MyAdapter extends RecyclerView.Adapter<PerfCellHolder> { private class MyAdapter extends RecyclerView.Adapter<PerfCellHolder> {
private List<AnchorNode> anchorNodes = new ArrayList<>(); private final List<AnchorItem> anchorNodes = new LinkedList<>();
private final long duration;
private MyAdapter() { private MyAdapter() {
// Map<String, Long> anchorMap = doricContext.getPerformanceProfile().getAnchorMap(); DoricPerformanceProfile.GlobalAnchorHook anchorHook
// for (String key : anchorMap.keySet()) { = doricContext.getDriver().getRegistry().getGlobalPerformanceAnchorHook();
// if (key.endsWith("#prepare")) { long position = 0;
// Long prepare = anchorMap.get(key); if (anchorHook instanceof DoricDevPerformanceAnchorHook) {
// if (prepare != null) { DoricDevPerformanceAnchorHook.AnchorNode prevNode = null;
// AnchorNode anchorNode = new AnchorNode(); for (DoricDevPerformanceAnchorHook.AnchorNode anchorNode :
// anchorNode.name = key.substring(0, key.lastIndexOf("#prepare")); ((DoricDevPerformanceAnchorHook) anchorHook)
// anchorNode.prepare = prepare; .getAnchorNodeList(doricContext.getContextId())) {
// anchorNodes.add(anchorNode); AnchorItem anchorItem = new AnchorItem();
// } anchorItem.name = anchorNode.name;
// } long gap = 0;
// } if (prevNode != null) {
// Collections.sort(anchorNodes, new Comparator<AnchorNode>() { gap = Math.min(16, anchorNode.prepare - prevNode.end);
// @Override }
// public int compare(AnchorNode o1, AnchorNode o2) { position = position + gap;
// return (int) (o1.prepare - o2.prepare); anchorItem.position = position;
// } anchorItem.prepared = anchorNode.start - anchorNode.prepare;
// }); anchorItem.worked = anchorNode.end - anchorNode.start;
anchorNodes.add(anchorItem);
position = position + anchorItem.prepared + anchorItem.worked;
prevNode = anchorNode;
}
duration = position;
} else {
duration = 0;
}
}
private boolean isAllExpanded() {
boolean allExpanded = true;
for (AnchorItem anchorItem : myAdapter.anchorNodes) {
if (!anchorItem.expanded) {
allExpanded = false;
break;
}
}
return allExpanded;
} }
@Override @Override
@ -106,6 +160,8 @@ public class DoricDevPerfActivity extends DoricDevBaseActivity {
cellHolder.layoutWaterfall = cell.findViewById(R.id.layout_waterfall); cellHolder.layoutWaterfall = cell.findViewById(R.id.layout_waterfall);
cellHolder.waterfallPrepared = cell.findViewById(R.id.waterfall_prepared); cellHolder.waterfallPrepared = cell.findViewById(R.id.waterfall_prepared);
cellHolder.waterfallWorked = cell.findViewById(R.id.waterfall_worked); cellHolder.waterfallWorked = cell.findViewById(R.id.waterfall_worked);
cellHolder.waterfallPrefix = cell.findViewById(R.id.waterfall_prefix);
cellHolder.waterfallSuffix = cell.findViewById(R.id.waterfall_suffix);
cellHolder.layoutExpand = cell.findViewById(R.id.layout_expand); cellHolder.layoutExpand = cell.findViewById(R.id.layout_expand);
cellHolder.tvFuncName = cell.findViewById(R.id.tv_func_name); cellHolder.tvFuncName = cell.findViewById(R.id.tv_func_name);
cellHolder.tvParameter = cell.findViewById(R.id.tv_parameter); cellHolder.tvParameter = cell.findViewById(R.id.tv_parameter);
@ -116,13 +172,56 @@ public class DoricDevPerfActivity extends DoricDevBaseActivity {
@Override @Override
public void onBindViewHolder(@NonNull PerfCellHolder holder, int position) { public void onBindViewHolder(@NonNull PerfCellHolder holder, int position) {
holder.itemView.setBackgroundColor(position % 2 == 0 ? Color.parseColor("#ecf0f1") : Color.WHITE); holder.itemView.setBackgroundColor(position % 2 == 0 ? Color.parseColor("#ecf0f1") : Color.WHITE);
holder.layoutExpand.setVisibility(View.GONE); final AnchorItem anchorItem = anchorNodes.get(position);
AnchorNode anchorNode = anchorNodes.get(position); holder.layoutExpand.setVisibility(anchorItem.expanded ? View.VISIBLE : View.GONE);
if (anchorNode.name.startsWith("Call")) { if (anchorItem.name.startsWith("Call")) {
holder.tvName.setBackgroundColor(0xff3498db);
holder.tvName.setText("Call"); holder.tvName.setText("Call");
String extraInfo = anchorItem.name.substring("Call:".length());
String[] info = extraInfo.split(",");
if (info.length > 1) {
holder.tvFuncName.setVisibility(View.VISIBLE);
holder.tvFuncName.setText(info[0]);
holder.tvParameter.setVisibility(View.VISIBLE);
holder.tvParameter.setText(extraInfo.substring(extraInfo.indexOf(",") + 1));
} else if (info.length > 0) {
holder.tvFuncName.setVisibility(View.VISIBLE);
holder.tvFuncName.setText(info[0]);
holder.tvParameter.setVisibility(View.GONE);
} else {
holder.tvFuncName.setVisibility(View.GONE);
holder.tvParameter.setVisibility(View.GONE);
}
} else { } else {
holder.tvName.setText(anchorNode.name); if (anchorItem.name.equals("Render")) {
holder.tvName.setBackgroundColor(0xffe74c3c);
} else {
holder.tvName.setBackgroundColor(0xff2ecc71);
}
holder.tvName.setText(anchorItem.name);
holder.tvFuncName.setVisibility(View.GONE);
holder.tvParameter.setVisibility(View.GONE);
} }
((LinearLayout.LayoutParams) (holder.waterfallPrefix.getLayoutParams())).weight
= anchorItem.position;
((LinearLayout.LayoutParams) (holder.waterfallPrepared.getLayoutParams())).weight
= anchorItem.prepared;
((LinearLayout.LayoutParams) (holder.waterfallWorked.getLayoutParams())).weight
= Math.max(anchorItem.worked, 1);
((LinearLayout.LayoutParams) (holder.waterfallSuffix.getLayoutParams())).weight
= duration - anchorItem.position - anchorItem.prepared - anchorItem.worked;
holder.layoutWaterfall.requestLayout();
holder.tvCost.setText(String.format(Locale.getDefault(),
"%d ms",
anchorItem.prepared + anchorItem.worked));
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
anchorItem.expanded = !anchorItem.expanded;
notifyDataSetChanged();
updateButton();
}
});
} }
@Override @Override

View File

@ -16,9 +16,20 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="15dp" android:layout_marginLeft="15dp"
android:layout_weight="1"
android:text="Context" android:text="Context"
android:textColor="#fff" android:textColor="#fff"
android:textSize="16dp" /> android:textSize="16dp" />
<TextView
android:id="@+id/tv_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="15dp"
android:gravity="center_vertical"
android:text="Expand[+]"
android:textColor="#fff"
android:textSize="16dp" />
</LinearLayout> </LinearLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView

View File

@ -5,7 +5,8 @@
android:background="#ecf0f1" android:background="#ecf0f1"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="10dp"> android:paddingLeft="10dp"
android:paddingRight="10dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -31,16 +32,32 @@
android:layout_weight="1"> android:layout_weight="1">
<View <View
android:id="@+id/waterfall_prepared" android:id="@+id/waterfall_prefix"
android:layout_width="30dp" android:layout_width="0dp"
android:layout_height="20dp" android:layout_height="20dp"
android:layout_weight="1" />
<View
android:id="@+id/waterfall_prepared"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_weight="1"
android:background="#1abc9c" /> android:background="#1abc9c" />
<View <View
android:id="@+id/waterfall_worked" android:id="@+id/waterfall_worked"
android:layout_width="60dp" android:layout_width="0dp"
android:layout_height="20dp" android:layout_height="20dp"
android:layout_weight="1"
android:background="#f1c40f" /> android:background="#f1c40f" />
<View
android:id="@+id/waterfall_suffix"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_weight="1" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -1,3 +1,18 @@
/*
* Copyright [2021] [Doric.Pub]
*
* Licensed 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.
*/
// //
// Created by pengfei.zhou on 2021/7/20. // Created by pengfei.zhou on 2021/7/20.
// //

View File

@ -1,3 +1,18 @@
/*
* Copyright [2021] [Doric.Pub]
*
* Licensed 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.
*/
// //
// Created by pengfei.zhou on 2021/7/20. // Created by pengfei.zhou on 2021/7/20.
// //
@ -14,4 +29,9 @@ - (void)onAnchorName:(NSString *)name prepare:(NSNumber *)prepare start:(NSNumbe
@(end.integerValue - start.integerValue) @(end.integerValue - start.integerValue)
); );
} }
- (void)onAnchorName:(NSString *)name prepare:(NSNumber *)prepare start:(NSNumber *)start end:(NSNumber *)end {
//Do nothing
}
@end @end