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 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;
/**
@ -25,16 +32,57 @@ import pub.doric.performance.DoricPerformanceProfile;
* @CreateDate: 2021/7/20
*/
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 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
public void onAnchor(DoricPerformanceProfile profile, String name, long prepare, long start, long end) {
Log.d(TAG, String.format("%s: %s prepared %dms, cost %dms.",
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
public void onAnchor(String name, long prepare, long start, long end) {
//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.RecyclerView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
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.performance.DoricPerformanceProfile;
/**
* @Description: pub.doric.devkit.ui
@ -43,6 +43,9 @@ import pub.doric.devkit.R;
* @CreateDate: 2021/7/19
*/
public class DoricDevPerfActivity extends DoricDevBaseActivity {
private MyAdapter myAdapter;
private TextView tvBtn;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -51,7 +54,33 @@ public class DoricDevPerfActivity extends DoricDevBaseActivity {
textView.setText(String.format("Doric %s <%s>", doricContext.getSource(), doricContext.getContextId()));
RecyclerView recyclerView = findViewById(R.id.list);
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 {
@ -59,43 +88,68 @@ public class DoricDevPerfActivity extends DoricDevBaseActivity {
private LinearLayout layoutWaterfall;
private View waterfallPrepared;
private View waterfallWorked;
private View waterfallPrefix;
private View waterfallSuffix;
private LinearLayout layoutExpand;
private TextView tvFuncName;
private TextView tvParameter;
private TextView tvCost;
public PerfCellHolder(@NonNull View itemView) {
private PerfCellHolder(@NonNull View itemView) {
super(itemView);
}
}
private static class AnchorNode {
String name;
long prepare;
private static class AnchorItem {
private String name;
private long position;
private long prepared;
private long worked;
private boolean expanded;
}
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() {
// Map<String, Long> anchorMap = doricContext.getPerformanceProfile().getAnchorMap();
// for (String key : anchorMap.keySet()) {
// if (key.endsWith("#prepare")) {
// Long prepare = anchorMap.get(key);
// if (prepare != null) {
// AnchorNode anchorNode = new AnchorNode();
// anchorNode.name = key.substring(0, key.lastIndexOf("#prepare"));
// anchorNode.prepare = prepare;
// anchorNodes.add(anchorNode);
// }
// }
// }
// Collections.sort(anchorNodes, new Comparator<AnchorNode>() {
// @Override
// public int compare(AnchorNode o1, AnchorNode o2) {
// return (int) (o1.prepare - o2.prepare);
// }
// });
DoricPerformanceProfile.GlobalAnchorHook anchorHook
= doricContext.getDriver().getRegistry().getGlobalPerformanceAnchorHook();
long position = 0;
if (anchorHook instanceof DoricDevPerformanceAnchorHook) {
DoricDevPerformanceAnchorHook.AnchorNode prevNode = null;
for (DoricDevPerformanceAnchorHook.AnchorNode anchorNode :
((DoricDevPerformanceAnchorHook) anchorHook)
.getAnchorNodeList(doricContext.getContextId())) {
AnchorItem anchorItem = new AnchorItem();
anchorItem.name = anchorNode.name;
long gap = 0;
if (prevNode != null) {
gap = Math.min(16, anchorNode.prepare - prevNode.end);
}
position = position + gap;
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
@ -106,6 +160,8 @@ public class DoricDevPerfActivity extends DoricDevBaseActivity {
cellHolder.layoutWaterfall = cell.findViewById(R.id.layout_waterfall);
cellHolder.waterfallPrepared = cell.findViewById(R.id.waterfall_prepared);
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.tvFuncName = cell.findViewById(R.id.tv_func_name);
cellHolder.tvParameter = cell.findViewById(R.id.tv_parameter);
@ -116,13 +172,56 @@ public class DoricDevPerfActivity extends DoricDevBaseActivity {
@Override
public void onBindViewHolder(@NonNull PerfCellHolder holder, int position) {
holder.itemView.setBackgroundColor(position % 2 == 0 ? Color.parseColor("#ecf0f1") : Color.WHITE);
holder.layoutExpand.setVisibility(View.GONE);
AnchorNode anchorNode = anchorNodes.get(position);
if (anchorNode.name.startsWith("Call")) {
final AnchorItem anchorItem = anchorNodes.get(position);
holder.layoutExpand.setVisibility(anchorItem.expanded ? View.VISIBLE : View.GONE);
if (anchorItem.name.startsWith("Call")) {
holder.tvName.setBackgroundColor(0xff3498db);
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.tvName.setText(anchorNode.name);
holder.tvFuncName.setVisibility(View.GONE);
holder.tvParameter.setVisibility(View.GONE);
}
} else {
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

View File

@ -16,9 +16,20 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_weight="1"
android:text="Context"
android:textColor="#fff"
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>
<androidx.recyclerview.widget.RecyclerView

View File

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

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.
//
@ -14,4 +29,9 @@ - (void)onAnchorName:(NSString *)name prepare:(NSNumber *)prepare start:(NSNumbe
@(end.integerValue - start.integerValue)
);
}
- (void)onAnchorName:(NSString *)name prepare:(NSNumber *)prepare start:(NSNumber *)start end:(NSNumber *)end {
//Do nothing
}
@end