项目性能总结--性能系统 《二》

280 阅读4分钟

Perf-patronus

Perf-patronus 会默认监控以下性能指标:

  • FCP

  • Total Blocking Time

  • First CPU Idle

  • TTI

  • Speed Index

  • LCP

工作架构和流程

  • 特定环境完成 MR 部署后,开始进行性能体检服务。

  • 性能体检服务由消息队列 worker 消费完成。

  • 每一次性能体检产出体检数据,根据数据内容是否达标,进行后续消息提醒;体检数据内容同时被性能守卫系统平台所消费,展现相关页面的性能情况。

  • 性能数据由 Redis 存储。

  • 性能体检相关富媒体资源(页面截图等)可以由容器持久化目录存储,或上传到 OSS 服务

如何真实反映用户情况?

  • **页面不确定性:**比如 A/B 实验情况。这种情况性能体检服务无法进行处理,需要接入者保证页面性能的可对比性。

  • **用户侧网络情况不确定性:**性能体检服务应该设计有可靠的 Throttling 机制,以及较合理的请求等待时间。

  • **终端设备不确定性:**性能体检服务应该设计有可靠的 CPU Simulating 能力,并统一 CPU 能力测试范围标准。

  • 性能体检服务的稳定性

整体流程

async run(runOptions: RunOptions) {
    // 检查相关数据
    const results = {};
    // 使用 Puppeteer 创建一个无头浏览器
    const context = await this.createPuppeteer(runOptions);

    try {
        // 执行必要的登录流程
        await this.Login(context);
        // 页面打开前的钩子函数
        await this.before(context);
        // 打开页面,获取 lighthouse 数据
        await this.getLighthouseResult(context);
        // 页面打开后的钩子函数
        await this.after(context, results);
        // 收集页面性能数据
        return await this.collectArtifact(context, results);

    } catch (error) {
        throw error;
    } finally {
        // 关闭页面和无头浏览器
        await this.disposeDriver(context);
    }

}

其中,创建一个 Puppeteer 无头浏览器的逻辑,如下代码:

async createPuppeteer(runOptions: RunOptions) {
    // 启动配置项可以参考 puppeteerlaunchoptions
    const launchOptions: puppeteer.LaunchOptions = {
        headless: true, // 是否采用无头模式
        defaultViewport: {
            width: 1440,
            height: 960
        }, // 指定页面视口宽高
        args: ['--no-sandbox', '--disable-dev-shm-usage'],
        // Chromium 安装路径

        executablePath: 'xxx',

    };
    // 创建一个浏览器对象
    const browser = await puppeteer.launch(launchOptions);
    const page = (await browser.pages())[0];
    // 返回浏览器和页面对象

    return {
        browser,
        page
    };

}

打开相关页面,并执行 Lighthouse 模块,如下代码所示:

async getLighthouseResult(context: Context) {

    // 获取上下文信息,包括 browser 和页面地址
    const {
        browser,
        url
    } = context;

    // 使用 lighthouse 模块进行性能采集
    const {
        artifacts,
        lhr
    } = await lighthouse(url, {
        port: new URL(browser.wsEndpoint()).port,
        output: 'json',
        logLevel: 'info',
        emulatedFormFactor: 'desktop',

        throttling: {
            rttMs: 40,
            throughputKbps: 10 * 1024,
            cpuSlowdownMultiplier: 1,
            requestLatencyMs: 0,
            downloadThroughputKbps: 0,
            uploadThroughputKbps: 0,

        },
        disableDeviceEmulation: true,
        // 只检测 performance 模块
        onlyCategories: ['performance'],
    });

    // 回填数据
    context.lhr = lhr;
    context.artifacts = artifacts;

}

上述流程都是常规启用 Lighthouse 模块,在 Node.js 环境中对相关页面执行 Lighthouse 的逻辑。

我们自定义的逻辑往往可以通过 Lighthouse 插件实现,一个 Lighthouse 插件就是一个 Node.js 模块,在插件中我们可以定义 Lighthouse 的检查项,并在产出报告中以一个新的 category 呈现。

举个例子,我们想要实现“检查页面中是否含有大小超过 5MB 的 GIF 图片”的任务,如以下代码

module.exports = {
    // 对应 audits
    audits: [{
        path: 'lighthouse-plugin-cinememe/audits/cinememe.js',
  }],

    // 该 plugin 对应的 category
    category: {
        title: 'Obligatory Cinememes',
        description: 'Modern webapps should have cinememes to ensure a positive ' +
            'user experience.',
        auditRefs: [
            {
                id: 'cinememe',
                weight: 1
            },
    ],

    },
};

对应自定义 Audits,如下代码:

'use strict';

const Audit = require('lighthouse').Audit;

// 继承 Audit 类

class CinememeAudit extends Audit {

    static get meta() {

        return {

            id: 'cinememe',

            title: 'Has cinememes',

            failureTitle: 'Does not have cinememes',

            description: 'This page should have a cinememe in order to be a modern ' +

                'webapp.',

            requiredArtifacts: ['ImageElements'],

        };

    }

    static audit(artifacts) {

        // 默认 hasCinememe 为 false(大小超过 5MB 的 GIF 图片)

        let hasCinememe = false;

        // 非 Cinememe 图片结果

        const results = [];

        // 过滤筛选相关图片

        artifacts.ImageElements.filter(image => {

            return !image.isCss &&

                image.mimeType &&

                image.mimeType !== 'image/svg+xml' &&

                image.naturalHeight > 5 &&

                image.naturalWidth > 5 &&

                image.displayedWidth &&

                image.displayedHeight;

        }).forEach(image => {

            if (image.mimeType === 'image/gif' && image.resourceSize >= 5000000) {

                hasCinememe = true;

            } else {

                results.push(image);

            }

        });

        const headings = [

            {
                key: 'src',
                itemType: 'thumbnail',
                text: ''
            },

            {
                key: 'src',
                itemType: 'url',
                text: 'url'
            },

            {
                key: 'mimeType',
                itemType: 'text',
                text: 'MIME type'
            },

            {
                key: 'resourceSize',
                itemType: 'text',
                text: 'Resource Size'
            },

    ];

        return {

            score: hasCinememe > 0 ? 1 : 0,

            details: Audit.makeTableDetails(headings, results),

        };

    }

}

module.exports = CinememeAudit;

通过上面插件,我们就可以在 Node.js 环境中,结合 CI/CD 流程,找出页面中大小超过 5MB 的 GIF 图片了。

由插件原理可知,一个性能守卫系统,是通过常规插件和自定义插件集合而成的,具有良好的扩展性。

文章参考:www.baidu.com/link?url=N3…