Mini Program performance monitoring

更新时间:
复制 MD 格式

Monitor mini program performance events on Android and iOS, including startup time, white screens, JS exceptions, and network errors.

Android

API

Mriver.setProxy(PrepareNotifyProxy.class, new PrepareNotifyProxy() {
            @Override
            public void notify(String s, PrepareStatus prepareStatus) {

            }

            @Override
            public void apmEvent(final String event, final String param1, final String param2, final String param3, final String param4) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (logs == null) {
                            logs = new StringBuilder();
                        }
                        logs.append(s).append(" ").append(s1).append(" ").append(s2).append(" ").append(s3).append(" ").append(s4).append("\n");
                    }
                });

            }
});

Supported events:

MINI_APP_PREPARE: An application opening exception occurs if the errc parameter is not 1.
MINI_PAGE_ABNORMAL: White screen.
MINI_APP_REQUEST: An application package pulling exception occurs if the step parameter is 'fail'.
MINI_AL_NETWORK_PERMISSON_ERROR: Page access is restricted.
MINI_AL_JSAPI_RESULT_ERROR: A request exception occurs if jsapiName is 'httpRequest' or 'request'. Otherwise, a JSAPI exception occurs.
MINI_CUSTOM_JS_ERROR: JS exception.
MINI_AL_NETWORK_PERFORMANCE_ERROR: Resource request exception.
MiniAppStart: Startup time. The startCost parameter indicates the time in milliseconds.

iOS

API

Develop a plugin to intercept system events. The Plist configuration is as follows:

image.png

// Plist initialization code:
- (void)application:(UIApplication *)application beforeDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//    [MPNebulaAdapterInterface initNebula];
    NSString* h5Json = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"MPCustomPresetApps.bundle/h5_json.json"]ofType:nil];
    NSString* amrBundle = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"MPCustomPresetApps.bundle"] ofType:nil];
    NSString* jsPath = [NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@"MPCustomPluginsJsapis.bundle/Poseidon-UserDefine-Extra-Config.plist"];
    [MPNebulaAdapterInterface initNebulaWithCustomPresetApplistPath:h5Json
                                         customPresetAppPackagePath:amrBundle
                                            customPluginsJsapisPath:jsPath];
}

Plugin implementation code:

// .h file
#import <AriverApp/RVAPluginBase.h>

NS_ASSUME_NONNULL_BEGIN

@interface DemoPlugin4APM : RVAPluginBase

@end

NS_ASSUME_NONNULL_END


// .m file
#import "DemoPlugin4APM.h"
#import <NebulaPoseidon/PSDMonitorEvent.h>

@implementation DemoPlugin4APM


- (void)pluginDidLoad
{
    self.scope = kPSDScope_Service;
    [self.target addEventListener:kEvent_Monitor_Log_Before withListener:self useCapture:NO];
    [super pluginDidLoad];
}

- (void)handleEvent:(RVKEvent *)event
{
    [super handleEvent:event];
    
    if ([kEvent_Monitor_Log_Before isEqualToString:event.eventType]){
        
        PSDMonitorEvent *mEvent = (PSDMonitorEvent *)event;
        NSArray *params = [mEvent.params isKindOfClass:[NSArray class]]? mEvent.params : @[];
        NSString *bizType = [NSString stringWithFormat:@"%@", mEvent.bizType];
        NSString *seedId = [NSString stringWithFormat:@"%@", mEvent.seedId];
        if ([params count] != 4 || ![bizType length]) {
            return;
        }
        
        NSString *aplogstr = [NSString stringWithFormat:@"%@\nbizType=%@,param1=%@,param2=%@,param4=%@",params[2],bizType,params[0],params[1],params[3]];
        NSLog(@"seed seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        // Application opening exception: To reproduce, open the demo and tap "Open Exception H5_APP_PREPARE".
        if ([seedId isEqualToString:@"H5_APP_PREPARE"]) {
            NSString *currentStep = [self mp_valueFromLogStr:params[2] key:@"step"];
            // An application opening exception occurs if the step parameter is 'noexistForce'.
            if ([currentStep isEqualToString:@"noexistForce"]) {
                NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
            }
        }
        // JS exception: To reproduce, open the demo and tap "APM Mini Program -> js error".
        else if ([seedId isEqualToString:@"H5_CUSTOM_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // White screen: To reproduce, disconnect from the network (enable airplane mode, disconnect from Wi-Fi), then open the demo and tap "Open Exception H5_APP_PREPARE".
        else if ([seedId isEqualToString:@"H5_PAGE_ABNORMAL"]) {
            NSLog(@"Caught white screen:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // Package pulling exception: No reproduction path is available. This can only be reproduced when a mini program package pulling API exception occurs.
        else if ([seedId isEqualToString:@"H5_APP_REQUEST"]) {
            NSString *currentStep = [self mp_valueFromLogStr:params[2] key:@"step"];
            NSLog(@"Package pulling:[%@]\nlog[%@]:[%@]", seedId, currentStep, aplogstr);
            if ([currentStep containsString:@"fail"]) {
                NSLog(@"Package pulling exception:[%@]\nlog[%@]:[%@]", seedId, currentStep, aplogstr);
            }
        }
        // Page access restricted H5_AL_NETWORK_PERMISSON_ERROR
        // To reproduce, open the demo and tap "APM Mini Program -> Page Access Restricted".
        else if ([seedId isEqualToString:@"H5_AL_PAGE_UNAUTHORIZED"] || [seedId isEqualToString:@"H5_AL_NETWORK_PERMISSON_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // Request exception / JSAPI exception H5_AL_JSAPI_RESULT_ERROR
        // To reproduce, open the demo and tap "APM Mini Program -> js api error".
        else if ([seedId isEqualToString:@"H5_AL_JSAPI_RESULT_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // Resource request exception H5_AL_NETWORK_PERFORMANCE_ERROR
        // To reproduce, disconnect from the network, open the demo, tap "Go to Mini Program", and then tap "Refresh".
        else if ([seedId isEqualToString:@"H5_AL_NETWORK_PERFORMANCE_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
    }
    
    // Startup time
    // Calculation rule: The time elapsed from AppStart to PageLoad, in seconds.
    // For business metrics, calculate only the first callback, which is when the home page finishes loading. The difference can be seen in the h5WebVC.url log below.
    if ([event.eventType isEqualToString:kEvent_Session_Create]) {
        // Record the timestamp:
        CFTimeInterval tm = CACurrentMediaTime(); // CACurrentMediaTime() is based on the internal clock. It provides more precise and atomic measurements and is not affected by external time changes, such as time zone changes, daylight saving time, or leap seconds. However, it is related to the system uptime and is reset after a system restart. CACurrentMediaTime() is often used to test code efficiency.
        self.appStartTimestamp = tm;
        NSLog(@"kEvent_Session_Create: AppStart [%f]", tm);
    }
    
    if ([event.eventType isEqualToString:kEvent_Page_Load_Complete]) {
        PSDEvent *oldEvent = (PSDEvent *)event;
        H5WebViewController *h5WebVC = (H5WebViewController *)[oldEvent.context currentViewController];
        NSString* currentUrl = [h5WebVC.url absoluteString];
        NSLog(@"kEvent_Session_Create: page[%@]", currentUrl);
        if ([currentUrl containsString:@"#"]) { //
            // Timestamp:
            CFTimeInterval tm = CACurrentMediaTime();
            NSLog(@"kEvent_Session_Create: PageLoad [%f]", tm);
            CFTimeInterval appStartTime = tm - self.appStartTimestamp;
            NSLog(@"kEvent_Session_Create: AppStart - PageLoad = Startup time[%f]", appStartTime);
        }
    }
}

@end

Android and iOS event comparison table

Event

Android

iOS

Opening exception

MINI_APP_PREPARE

H5_APP_PREPARE

White screen

MINI_PAGE_ABNORMAL

H5_PAGE_ABNORMAL

Package pulling exception

MINI_APP_REQUEST

H5_APP_REQUEST

Page access restricted

MINI_AL_NETWORK_PERMISSON_ERROR

H5_AL_NETWORK_PERMISSON_ERROR / H5_AL_PAGE_UNAUTHORIZED

Request exception / JSAPI exception

MINI_AL_JSAPI_RESULT_ERROR

H5_AL_JSAPI_RESULT_ERROR

JS exception

MINI_CUSTOM_JS_ERROR

H5_CUSTOM_ERROR

Resource request exception

MINI_AL_NETWORK_PERFORMANCE_ERROR

H5_AL_NETWORK_PERFORMANCE_ERROR

Startup time

MiniAppStart

Mini Program

my.onError(Function listener)

Description

Listens for mini program error events.

Input parameters

Function listener

Parameters

Property

Type

Compatibility

Description

error

String

-

The exception description, typically the message field of the Error object.

stack

String

Base libraries: 2.7.4

The exception stack, typically the stack field of the Error object.

Code example

my.onError(Function listener)

Page({
 onLoad() {
 my.onError(this.errorHandler);
 },
 errorHandler(error, stack) {
 console.log('onError error', error);
 console.log('onError stack', stack);
 }
})
Note
  • Errors monitored by my.onError are also caught by the onError method in app.js.

  • If you use my.onError on multiple pages, a page error triggers multiple listener events unless the listener is disabled. Call my.offError to disable the listener when the page is closed.

my.onUnhandledRejection(Function listener)

Description

Listens for unhandled Promise rejection events.

Input parameters

Function listener

The callback function for unhandled Promise rejection events.

Parameters

Object res

Property

Type

Description

reason

any

The rejection reason, typically an Error object passed to reject().

promise

Promise

The rejected Promise object.

Note: Supported only on iOS.

Code example

my.onUnhandledRejection(Function listener)

Page({
 onLoad() {
 my.onUnhandledRejection(this.unhandledRejectionHandler);
 },
 unhandledRejectionHandler(res) {
 console.log('onUnhandledRejection reason', res.reason);
 console.log('onUnhandledRejection promise', res.promise);
 }
})
Note
  • Triggering a Promise unhandledrejection event within the my.onUnhandledRejection callback can cause a loop. Avoid this scenario.

  • This listener can catch all unhandledRejection events, but only those of the Error type will trigger an alarm in the mini program console.