From 8e2ebaf83313f6928bb71af4e5cfb234457eeb82 Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Tue, 20 Jul 2021 17:26:14 +0800 Subject: [PATCH] feat:android show performance waterfall --- .../devkit/DoricDevPerformanceAnchorHook.java | 48 ++++++ .../doric/devkit/ui/DoricDevPerfActivity.java | 163 ++++++++++++++---- .../res/layout/activity_doricdev_perf.xml | 11 ++ .../main/res/layout/item_doricdev_perf.xml | 25 ++- .../Classes/DoricDevPerformanceAnchorHook.h | 15 ++ .../Classes/DoricDevPerformanceAnchorHook.m | 20 +++ 6 files changed, 246 insertions(+), 36 deletions(-) diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDevPerformanceAnchorHook.java b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDevPerformanceAnchorHook.java index b85e97b9..212e60ef 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDevPerformanceAnchorHook.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDevPerformanceAnchorHook.java @@ -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> nodeMap = new HashMap<>(); + private final Comparator comparator = new Comparator() { + @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 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 getAnchorNodeList(String name) { + List ret = nodeMap.get(name); + if (ret == null) { + ret = new ArrayList<>(); + } + return ret; + } } diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevPerfActivity.java b/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevPerfActivity.java index 6a01bc28..3ad4aef0 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevPerfActivity.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevPerfActivity.java @@ -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 { - private List anchorNodes = new ArrayList<>(); + private final List anchorNodes = new LinkedList<>(); + private final long duration; private MyAdapter() { -// Map 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() { -// @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.tvFuncName.setVisibility(View.GONE); + holder.tvParameter.setVisibility(View.GONE); + } } 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 diff --git a/doric-android/devkit/src/main/res/layout/activity_doricdev_perf.xml b/doric-android/devkit/src/main/res/layout/activity_doricdev_perf.xml index b0818566..5943b60e 100644 --- a/doric-android/devkit/src/main/res/layout/activity_doricdev_perf.xml +++ b/doric-android/devkit/src/main/res/layout/activity_doricdev_perf.xml @@ -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" /> + + + android:paddingLeft="10dp" + android:paddingRight="10dp"> + + + + diff --git a/doric-iOS/Devkit/Classes/DoricDevPerformanceAnchorHook.h b/doric-iOS/Devkit/Classes/DoricDevPerformanceAnchorHook.h index b4421582..e22d6621 100644 --- a/doric-iOS/Devkit/Classes/DoricDevPerformanceAnchorHook.h +++ b/doric-iOS/Devkit/Classes/DoricDevPerformanceAnchorHook.h @@ -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. // diff --git a/doric-iOS/Devkit/Classes/DoricDevPerformanceAnchorHook.m b/doric-iOS/Devkit/Classes/DoricDevPerformanceAnchorHook.m index 4e89958c..5417460c 100644 --- a/doric-iOS/Devkit/Classes/DoricDevPerformanceAnchorHook.m +++ b/doric-iOS/Devkit/Classes/DoricDevPerformanceAnchorHook.m @@ -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 \ No newline at end of file