There is a reason for this — Why should Flutter performance be monitored

In fact, MOBILE APM is already a mature proposition. In the development of Native world over the years, there have been many SDKS for monitoring online performance data. However, due to the many revolutionary changes made by Flutter relative to Native, the performance monitoring of Native is basically completely invalid on the Flutter page. With this background, we launched a project called the Flutter High Availability SDK last year to make Flutter pages measurable like Native pages.

Targeted — What kind of SDK do we need

Performance monitoring is a mature proposition, which means we have plenty of resources to learn from. We learned from performance monitoring SDKS including EMAS high availability of Taobao, Martix of wechat and Hertz of Meituan, and combined with the actual situation of Flutter, we determined two questions: what performance indicators should be collected, and what features should be included in the SDK.

Performance indicators

1 page sliding fluency: traditional sliding fluency mainly through Fps, but there is a problem is unable to distinguish between a lot of Fps mild caton and a small amount of severe caton, but apparently, body feeling for users difference is very big, so we at the same time introduced the Fps, sliding length, frame drop length measured whether smooth.

2. Page loading time: Page loading time We selected the interactive time that better reflects the user’s sense of body. The interactive time refers to the period from the time when the user clicks to initiate a route jump to the time when the page content is loaded until the interaction can take place.

3. Exception: This indicator should not require much explanation.

The SDK features

1. Accuracy: Accuracy is a basic requirement of the performance monitoring SDK. False or false positives will cause the developer to spend a lot of unnecessary troubleshooting time.

2. Online monitoring: Online monitoring means that the cost of data collection should not be too high and the monitoring should not affect the original performance of the App.

3. Easy to expand: As an open source project, the fundamental goal is that everyone can participate in and contribute to the community, so the SDK itself should be easy to expand, and a series of specifications are needed to help everyone develop.

See the small, see the whole design from a single indicator

On April 25, 2019, we published an article about how to upgrade the Flutter experience by data. In this article, we detailed the performance metrics of Flutter and how to collect them. You can take a quick review of the previous articles on Flutter. Let’s take a look at the SDK design as a whole by taking a typical collection of instantaneous Fps implementations.

First you need to implement a FpsRecorder and inherit from BaseRecorder. The purpose of this class is to get the timing of the page Pop/Push in the business layer and the timing of the page start rendering, end rendering, and click events provided by FlutterBinding, and calculate the source data from these times. For instantaneous Fps, the source data is the duration of each frame.

                                
     

    class FpsRecorder extends BaseRecorder {

    / / /...

    @override

    void onReceivedEvent( BaseEvent event ) {

    if ( event is RouterEvent) {

    / / /...

    } else if (event is RenderEvent ) {

    switch ( event.eventType ) {

    case RenderEventType .beginFrame:

    _frameStopwatch .reset();

    _frameStopwatch .start();

    break;

    case RenderEventType .endFrame:

    _frameStopwatch .stop();

    PerformanceDataCenter().push (FrameData( _frameStopwatch.elapsedMicroseconds ));

    break;

    }

    } else if (event is UserInputEvent ) {

    / / /...

    }

    }

    @override

    List subscribedEventList() {

    return < Type>[RenderEvent , RouterEvent , UserInputEvent ];

    }

    }

Copy the code

We put the start point in beginFrame and the end point in endFrame, and we get the duration of each frame. As you can see, after we collected each frame duration, we encapsulated it into a FrameData and pushed it into the PerformanceDataCenter. PerformanceDataCenter will distribute this data to processors subscribed to FrameData, so we need to create a new FpsProcessor subscription and process the source data.


     

    class FpsProcessor extends BaseProcessor {

    / / /...

    @override

    void process(BaseData data) {

    if (data is FrameData) {

    / / /...

    if (isFinishedWithSample(currentTime)) {

    /// If the interval is greater than 1s, an FPS is calculated

    _startSampleTime = currentTime;

    collectSample(currentTime);

    }

    }

    }

    @override

    List<Type> subscribedDataList() {

    return [FrameData];

    }

    void collectSample(int finishSampleTime) {

    / / /...

    PerformanceDataCenter().push(FpsUploadData(avgFps: fps));

    }

    / / /...

    }

Copy the code

FpsProcessor collects the obtained frame duration and calculates the instantaneous Fps value within 1s (the specific statistical method can be referred to the implementation of the previous article mentioned above, which will not be described here in more detail). Similarly, after calculating the Fps value, we encapsulated it into an FpsUploadData and pushed it into PerformanceDataCenter again. PerformanceDataCenter gives FpsUploadData to the Uploader that subscribed to it for processing, so we need to create a new MyUploader subscription and process the data.


     

    class MyUploader extends BaseUploader {

    @override

    List<Type> subscribedDataList() {

    return <Type>[

    FpsUploadData, //TimeUploadData, ScrollUploadData, ExceptionUploadData,

    ];

    }

    @override

    void upload(BaseUploadData data) {

    if (data is FpsUploadData) {

    _sendFPS(data.pageInfoData.pageName, data.avgFps);

    }

    / / /...

    }

    }

Copy the code

Uploader can select the UploadData to be subscribed by subscribedDataList(), and upload() receives notify and reports it. In theory, a Uploader corresponds to an upload channel, users can achieve as needed, such as LocalLogUploader, NetworkUploader and other data to report to different places.

Overall situation — overall structure design

SDK can be divided into 4 layers in general, and a large number of publish-subscribe mode is used to connect with 2 centers. The advantage of this mode is that it can achieve complete decoupling between layers and make data processing more flexible.

API

This layer consists mainly of exposed interfaces. For example, init() requires the user to call before runApp(), and the business layer needs to call the pushEvent() method to provide some opportunities for the SDK.

Recorder

The primary responsibility of this layer is to collect the corresponding source data using the timing provided by Evnet and hand it to the Processor that subscribed to the data for processing. For example, the duration of each frame in FPS capture is the source data. This layer is designed so that the source data can be used in many different ways, such as the time per frame can be used to calculate the number of stuck seconds as well as the FPS.

To use it, you need to inherit BaseRecoder, select subscribed events by subscribedEventList(), and process received events in onReceivedEvent()


     

    abstract class BaseRecorder with TimingObserver {

    BaseRecorder() {

    PerformanceEventCenter().subscribe(this, subscribedEventList());

    }

    }

    mixin TimingObserver {

    void onReceivedEvent(BaseEvent event);

    List<Type> subscribedEventList();

Copy the code

Processor

This layer processes the source data into data that can eventually be reported, and gives it to the Uploader that subscribed to the data for reporting. For example, the FPS value in FPS collection is calculated according to the time of each frame collected.

When using it, you need to inherit BaseProcessor, select the Data type to subscribe by subscribedDataList(), and process the received Data in process().


     

    abstract class BaseProcessor{

    void process(BaseData data);

    List<Type> subscribedDataList();

    BaseProcessor(){

    PerformanceDataCenter().registerProcessor(this, subscribedDataList());

    }

    }

Copy the code

Uploader

This layer is mainly implemented by the user, because each user wants to report the data to different places, so the SDK provides the corresponding base class, just need to follow the specification of the base class, can obtain the subscribed data.

When using BaseUploader, select the Data type to subscribe by subscribedDataList() and process the UploadData received in Upload ().


     

    abstract class BaseUploader{

    void upload(BaseUploadData data);

    List<Type> subscribedDataList();

    BaseUploader(){

    PerformanceDataCenter().registerUploader(this, subscribedDataList());

    }

    }

Copy the code

PerformanceDataCenter

Singleton that receives BaseData (source data) and UploadData (processed data) and distributes these opportunities to the Processor and Uploader that subscribed to them for processing.

The BaseProcessor and BaseUploader constructors call the PerformanceDataCenter register method to subscribe, which stores the corresponding instance in the two PerformanceDataCenter maps. This data structure allows a single DataType to correspond to multiple subscribers.


     

    final Map<Type, Set<BaseProcessor>> _processorMap = <Type, Set<BaseProcessor>>{};

    final Map<Type, Set<BaseUploader>> _uploaderMap = <Type, Set<BaseUploader>>{};

Copy the code

As shown in the figure, when calling PerformanceDataCenter. Push () method to push the Data, can according to the type of Data distribution, to all subscribed to the Data types Proceesor/Uploader.

PerforanceEventCenter

Singleton, similar in design to PerformanceDataCenter, but used to receive events (corresponding timing) provided by the business layer and distribute these timing to the Recorder that subscribed to them for processing. The main types of events are as follows :(the business state needs to be provided by the user, and the collection has been completed in SDK at other times)

  • App status: Switch between App front and background

  • Page state: frame rendering starts and frame rendering ends

  • Business status: Pop/Push occurs on the page, slide occurs on the page, Exception occurs in the business

Opinion depends — how the SDK opens

If you are an SDK user, then you only need to pay attention to the API layer and Uploader layer, you only need to do the following steps:

1. Reference the highly available SDK in Pubspec;

2. Call init() to initialize the SDK before the runApp() method is called;

3. In your business code, use the pushEvent() method to provide the SDK with the necessary timing, such as routing Pop and Push;

4. Create a custom Uploader class that reports data to the data collection platform you want.

If you want to contribute to the high availability SDK, please follow the following design specifications and send us a Push Request. We will Review it and get back to you.

1. In the publish-subscribe mode, the publisher first delivers data to the corresponding data center, and the data center distributes data to the corresponding subscribers.

2. Data flow from Recorder to Processor and then to Uploader, driven by data, API to drive Recorder through Event, Recorder to drive Processor through BaseData, Processor Uses UploadData to drive the Uploader.

Down to earth — landing application of SDK

We have made several data accuracy tweaks to the Flutter high availability SDK, a number of BadCase fixes, and even a radical refactoring. So far, SDK has been running stably in Xianyu for nearly half a year. Since the first access, there has never been any stability problem caused by highly available SDK, and the accuracy of data collection has been nearly stable after repeated tuning.

We utilized the backstage data processing and foreground data display capabilities of Mobile EMAS to report and display the data collected online by the HIGHLY available SDK, enabling Flutter page to compete with Native page.

Look up at the stars — our journey is a sea of stars

Features added
There are still two major issues with the SDK that the community needs but has not addressed:
  • Flutter memory analysis

  • Snap stack grab

We will continue to work on this, but we also hope that students with ideas will join us and submit code to us.

Open source project

At present, the HIGH availability SDK has been open source within the group. We have supplemented and modified the document according to the feedback of students in the group, but it is still not comprehensive enough. Meanwhile, test cases are also under intense preparation. After that we will open source, which we expect to see in about two months.

The Idle Fish Team is the industry leader in the new technology of Flutter+Dart FaaS integration, right now! Client/server Java/architecture/front-end/quality engineer recruitment for the society, base Hangzhou Alibaba Xixi Park, together to create creative space community products, do the depth of the top open source projects, together to expand the technical boundary achievement!

* Send resumes to small idle fish →[email protected]


More series of articles, open source projects, key insights, in-depth interpretation

Please look for the idle fish technology