Preface For iOS overall ecosystem is relatively closed, compared to Android, there is no such command as ADB can view memory, CPU. In daily performance tests, you need to use Xcode instruments to check memory and CPU data. But instruments is cumbersome and does not provide a command line. In continuous integration, it is difficult to monitor app performance indicators all the time. And now the release of app usually takes about 2 weeks, leaving less time for special testing, so it is certainly too late to do the performance test of core scenes. Therefore, some automation tools are needed to reduce the workload of manual collection of performance indicators. Performance collection items Basic performance collection items of APP include memory, CPU, FPS and electric quantity. In automatic collection, electric quantity data cannot be collected because the mobile device is plugged into the computer to charge. The existing tools

  • Instruments are officially provided and cannot be automated
  • Tencent GT needs to integrate SDK into APP, which has a certain access cost
  • The third SDK, similar to Tencent GT, needs to be integrated in app, which may have the risk of data leakage

The above existing tools for script development do not meet the requirements. To automate the collection of performance data in continuous integration, the expected performance testing tools are as follows:

  • Convenient access to
  • Generates a performance report
  • sustainable
  • Accurate data collection

Therefore, you need to develop a performance collection script based on these points. Use official apis to collect performance data

Obtain memory, CPU, etc

#import <mach/mach.h>/** * get memory */ - (NSString *)get_memory {int64_t memoryUsageInByte = 0; task_vm_info_data_t vmInfo; mach_msg_type_number_t count = TASK_VM_INFO_COUNT; kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);if(kernelReturn == KERN_SUCCESS) {
        memoryUsageInByte = (int64_t) vmInfo.phys_footprint;
        NSLog(@"Memory in use (in bytes): %lld", memoryUsageInByte);
    } else {
        NSLog(@"Error with task_info(): %s", mach_error_string(kernelReturn)); } double mem = memoryUsageInByte/(1024.0 * 1024.0); NSString *memtostring ; memtostring = [NSString stringWithFormat:@"%.1lf",mem];

    returnmemtostring; } /** * get CPU */ - (NSString *) get_cpu{kern_return_t kr; task_info_data_t tinfo; mach_msg_type_number_t task_info_count; task_info_count = TASK_INFO_MAX; kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);if(kr ! = KERN_SUCCESS) {return [ NSString stringWithFormat: @"%f" ,-1];
    }

    task_basic_info_t      basic_info;
    thread_array_t         thread_list;
    mach_msg_type_number_t thread_count;

    thread_info_data_t     thinfo;
    mach_msg_type_number_t thread_info_count;

    thread_basic_info_t basic_info_th;
    uint32_t stat_thread = 0; // Mach threads

    basic_info = (task_basic_info_t)tinfo;

    // get threads in the task
    kr = task_threads(mach_task_self(), &thread_list, &thread_count);
    if(kr ! = KERN_SUCCESS) {return [ NSString stringWithFormat: @"%f" ,-1];
    }
    if (thread_count > 0)
        stat_thread += thread_count;

    long tot_sec = 0;
    long tot_usec = 0;
    float tot_cpu = 0;
    int j;

    for (j = 0; j < thread_count; j++)
    {
        thread_info_count = THREAD_INFO_MAX;
        kr = thread_info(thread_list[j], THREAD_BASIC_INFO,
                         (thread_info_t)thinfo, &thread_info_count);
        if(kr ! = KERN_SUCCESS) { tot_cpu = -1; //return- 1; } basic_info_th = (thread_basic_info_t)thinfo;if(! (basic_info_th->flags & TH_FLAGS_IDLE)) { tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds; tot_usec = tot_usec + basic_info_th->user_time.microseconds + basic_info_th->system_time.microseconds; tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float) TH_USAGE_SCALE * 100.0; / /}}for each thread

    kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
    assert(kr == KERN_SUCCESS);

    NSString *tostring = nil ;
    tostring = [ NSString stringWithFormat: @"%.1f" ,tot_cpu];
    NSLog (@"performance cpu:%@",tostring);

    return tostring;
}Copy the code

Obtain page VC

It collects memory and CPU, and it also needs to be mapped to the page while collecting data. This is clear is the current page memory and CPU status.

/** * get_vc {UIWindow *keyWindow = [UIApplication sharedApplication].keywindow; __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{if([keyWindow.rootViewController isKindOfClass:[UITabBarController class]]) { UITabBarController *tab = (UITabBarController *)keyWindow.rootViewController; UINavigationController *nav = tab.childViewControllers[tab.selectedIndex]; DDContainerController *content = [nav topViewController]; weakSelf.vc = [content contentViewController]; }});return self.vc;
}Copy the code

Obtaining Device Information

- (NSString *) get_devicesName {NSString *devicesName = [UIDevice currentDevice]. // Device name NSLog(@"performance devicesName:%@", devicesName);
    returndevicesName; } /* * get the systemVersion */ - (NSString *) get_systemVersion{NSString *systemVersion = [UIDevice currentDevice].systemversion; // System version NSLog(@"performance version:%@", systemVersion);
    returnsystemVersion; } / access equipment idf * / * * - (nsstrings *) get_idf {nsstrings * idf = [UIDevice currentDevice]. IdentifierForVendor. UUIDString; NSLog(@"performance idf:%@", idf);
    return idf;

}Copy the code

Data splicing

Finally, the memory, CPU and other data should be spliced into a dictionary form, convenient output view

The outputlogLog data format {"cpu": "0.4"."fps": "60 FPS"."version": "11.2"."appname": "xxxxxx"."battery": "100.0"."appversion": "5.0.4"."time": "The 2018-09-07 11:45:24"."memory": "141.9"."devicesName": "xxxxxx"."vcClass": "DDAlreadPaidTabListVC"."idf": "8863F83E-70CB-43D5-B6C7-EAB85F3A2AAD"/* * PerformanceThread */ - (void) PerformanceThread {NSThread *thread = [[NSThread alloc] initWithBlock:^{ NSLog(@"performance ======get performance======");

        [self get_fps];

        while (true) {
            DDPerformanceModel *model = [DDPerformanceModel new];
            model.time=[self get_time];
            model.appname=[self get_appname];
            model.appversion=[self get_appversion];
            model.idf =[self get_idf];
            model.devicesName =[self get_devicesName];
            model.version = [self get_systemVersion ];
            model.vcClass = NSStringFromClass([self get_vc].class);
            model.memory = [self get_memory];
            model.battery = [self get_battery];
            model.cpu = [self get_cpu];
            model.fps = self.percount;

            NSString *json = [model modelToJSONString];

//            printf(" getperformance %s\r\n", [json UTF8String]);
            NSLog(@"getperformance model %@", json); sleep(5); }}]; [thread start]; NSLog(@"performance ======continue mainblock======");
}
Copy the code

Example Initialize performance collection

AppDelegate. M users in various initialization file didFinishLaunchingWithOptions method, the acquisition performance can be in the first row is initialized, So the app starts can timing acquisition data - (BOOL) application: (UIApplication *) application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions { [[getperformance new] performancethread]; // Get performance data}Copy the code

Performance collection log storage

Log storage is typically written to a local log and then read. But there are two problems

  • The need to read and write file code can be difficult for those unfamiliar with oc
  • Due to periodic collection, file I/O operations are frequent

Therefore, regardless of the way of storing local log logs, you can print data in the code and obtain data by intercepting the current device running logs.

The simulator can use the xcrun simctl command to get the current device run logs, and the real machine can use the libiMobiledevice to get the logs

xcrun simctl spawn booted log stream –level=debug | grep getperformance

The data format of the output log, which is beautified with JSON, is printed every few seconds on the console

{
    "cpu": "0.4"."fps": "60 FPS"."version": "11.2"."appname": "xxxxxx"."battery": "100.0"."appversion": "5.0.4"."time": "The 2018-09-07 11:45:24"."memory": "141.9"."devicesName": "xxxxxx"."vcClass": "DDAlreadPaidTabListVC"."idf": "8863F83E-70CB-43D5-B6C7-EAB85F3A2AAD"
}Copy the code

If you get data multiple times, you can use a shell script to put the command in the background and periodically write it to the logPath

nohupxcrunsimctlspawnbootedlogstream–level=debug>${logpath}&

Code is inserted into the project

Because in continuous integration, each fetch is code with no performance tests, it is written to a separate file. Before compiling the project, shell code is inserted into the project so that the output package can collect performance data.

scriptrootpath=The ${2}
AddFiles=The ${2}"/GetPerformance/performancefiles"
localDDPerformanceModelh=${scriptrootpath}"/GetPerformance/performancefiles/DDPerformanceModel.h"
localDDPerformanceModelm=${scriptrootpath}"/GetPerformance/performancefiles/DDPerformanceModel.m"
localgetperformanceh=${scriptrootpath}"/GetPerformance/performancefiles/getperformance.h"
localgetperformancem=${scriptrootpath}"/GetPerformance/performancefiles/getperformance.m"

addfiles() {echo "Delete${projectaddpath}Original performance collection file in"

    rm -rf ${DDPerformanceModelh}
    rm -rf ${DDPerformanceModelm}
    rm -rf ${getperformanceh}
    rm -rf ${getperformancem}

    echo "Copy files to${projectaddpath}The path"

    cp  ${localDDPerformanceModelh} ${projectaddpath}
    cp  ${localDDPerformanceModelm} ${projectaddpath}
    cp  ${localgetperformanceh} ${projectaddpath}
    cp  ${localgetperformancem} ${projectaddpath}

}Copy the code

Performance Data Drawing

In manual and automated use of the app that inserts performance test code, if the performance data is captured, the data can be drawn for performance data.

Use Higcharts or Echarts to chart performance

How can it be used in continuous integration

Monkey and UI automation, which eventually sends a performance report.

The Demo code

The performance code has been removed from the main project, can be compiled in the Demo code,github address :github.com/xinxi1990/i…

The last

While the iOS ecosystem is closed, there is some room for developers and testers to take advantage of it.

IOS testing has always been a difficult point to understand oc syntax and the overall iOS framework. IOS testing isn’t that hard if you start getting into it. It just takes a little patience and concentration.