38

使用 Hyperledger Caliper 对 VS Code 中开发的智能合约进行性能测试

 4 years ago
source link: http://www.ibm.com/developerworks/cn/cloud/library/blockchain-performance-testing-smart-contracts-vscode-caliper/index.html?ca=drs-
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

IBM Blockchain Platform Extension for VS Code 可帮助开发者创建、测试和调试智能合约。但对于开发人员来说,还有一个关键问题: 开发的智能合约的性能如何?

Hyperledger Caliper 是一种区块链基准测试工具,旨在对已部署的智能合约执行基准测试,从而能够分析在使用智能合约时区块链网络的吞吐量、延迟和资源消耗情况。本教程向您展示如何通过 Hyperledger Caliper 对使用 IBM Blockchain Platform VS Code 扩展开发和部署的 Hyperledger Fabric 智能合约进行性能测试。

前提条件

要完成本教程,您将需要:

预估时间

一旦完成 VS Code 教程(大约需要 20 到 30 分钟),您就可以开始以下构建过程:

  1. 获取并配置 Caliper
  2. 创建 Caliper 测试资产
  3. 运行性能基准测试

这些后续步骤大约需要 30 到 40 分钟。

步骤

要完成本教程,您需要完成三个主要步骤,详细说明如下:

  1. 获取 Caliper
  2. 创建 Caliper 测试资产
  3. 运行基准测试

第 1 步. 获取 Caliper

在本教程中,您将使用 Caliper CLI,这是一个可以通过 npm 获取并安装的节点模块。一旦安装了 Caliper CLI,您就需要将该工具绑定到目标区块链的指定 SDK。本教程便是使用 Caliper-CLI 版本 0.1.0 和 Fabric-SDK-Node 版本 1.4.4 制作的。

任务:

  1. 使用以下终端命令全局安装 Caliper CLI 模块:
    npm install -g --only=prod @hyperledger/[email protected]
  2. 使用终端命令绑定到所需的 SDK:
    caliper bind --caliper-bind-sut fabric --caliper-bind-sdk 1.4.4 --caliper-bind-args=-g

现在,您可以使用 Caliper,利用配置为使用指定 1.4.4 SDK 的客户端对 Fabric 网络进行基准测试。

第 2 步. 创建 Caliper 测试资产

Caliper 需要两个配置文件:

  • 网络配置文件,用于描述受测系统并提供网络连接要求
  • 基准测试配置,用于描述性能基准测试工作负载并引用用户指定的测试文件

所有必需的测试资产都将在一个新目录中创建,该目录充当工作区。

任务:

  1. 在 VS Code 中,创建一个名为 benchmarks 的新文件夹。
  2. 右键单击此文件夹,然后选择 Open in Terminal

现在,您将使用网络配置文件填充此目录,并对 Caliper 所需的资产进行基准测试。

网络配置文件

网络配置文件用于描述受测系统,并提供与网络交互的客户端的连接要求。可以使用 YAML 或 JSON 格式来指定此文件。

对于 Fabric 网络,网络配置文件是扩展的通用连接配置文件,通过 Caliper 所需的元素进行了扩充。

任务:

  1. 切换到 IBM Blockchain Platform 扩展窗口,并断开与任何网关的连接。
  2. 从 VS Code 导出连接配置文件:
    • Fabric Gateways -> Local Fabric 下,右键单击并选择以导出连接配置文件。
    • 将此配置文件保存在 benchmarks 文件夹下,并且另存为 network_config.json
  3. 从 VS Code 中导出本地 Fabric 钱包:
    • Fabric Wallets -> Local Fabric Wallet 下,右键单击并导出钱包。
    • 将钱包保存在 benchmarks 文件夹下,并且另存为 myWallet

接下来,编辑导出的连接配置文件,添加以下必需属性:

{
    "caliper": {},
    "wallet": "",
    "clients": {},
    "channels": {}
}

任务:

  1. 打开导出的连接配置文件 network_config.json
  2. 标识正在测试的分布式账本技术 (DLT) 引擎。在文件的顶部,将 caliper 对象添加到模式,该模式包含单个名为 blockchain 的属性,其字符串值为 fabric
    "caliper": {
           "blockchain": "fabric"
    },
  3. 标识其中包含与网络交互所需身份的钱包:
    • 添加名为 wallet 的密钥。
    • 在 VS Code 中,右键单击 wallet 文件夹,选择 Copy Path ,然后将其提供为 wallet 属性的值:
      "wallet": "<fully-qualified-path-wallet>",
  4. 通过提供客户端身份到客户端对象的映射,指定 Caliper 可用于与网络交互的客户端:
    • 将现有的 client 对象嵌套在一个新的 JSON 对象中,该对象使用所导出钱包中某个身份的名称。在此场景中,名为 admin
    • 将上面创建的映射身份嵌套在名为 clients 的新 JSON 对象中。
      "channels": {
           "mychannel": {
               "chaincodes": [
                   {
                       "id": "demoContract",
                       "version": "0.0.1"
                   }
               ]
           }
       },
  5. 标识可用的通道以及部署到这些通道的智能合约:
    • clients 对象下的模式中添加一个 channels 对象。
    • channels 对象中,添加另一个名为 mychannel 的对象,这是由 VS Code 创建且在其中部署了智能合约的默认通道的名称。
    • mychannel 对象中,添加一个名为 chaincodes 的数组对象。在此数组中,添加一个对象,其中包含已部署智能合约的 ID 和版本的键/值对,它们分别为 demoContract0.0.1
    "channels": {
         "mychannel": {
             "chaincodes": [
                 {
                     "id": "demoContract",
                     "version": "0.0.1"
                 }
             ]
         }
     },
  6. 保存修改后的文件。

现在,您已具有了可供 Caliper 使用的网络配置文件。

基准测试配置

基准测试包括在几轮测试中重复执行指定的测试回调文件,每一轮的持续时间均受到控制,并且在这几轮测试中,负载由客户端(可能是多个)驱动,这些客户端本身通过速率控制机制进行控制。

基准测试配置需要:

  • 一个或多个测试回调文件,这些文件与已部署的智能合约交互并定义要调查的操作
  • 一个配置文件,用于定义基准测试轮次并引用已定义的回调

接下来,您将创建一个用于与部署的智能合约进行交互的测试回调文件,以及一个在单轮测试中引用该测试回调文件的配置文件。

测试回调文件

测试回调文件是在每轮基准测试期间与已部署智能合约的交互点。每个测试回调文件都必须导出以下函数:

  • init 函数 -- 用于初始化要在 run 部分中使用的任何必需项目
  • run 函数 -- 用于在监控的基准测试阶段与智能合约方法进行交互
  • end 函数 -- 用于在运行阶段完成后执行清理操作

部署的智能合约包含资产的完整 CRUD 操作集;为简洁起见,我们仅研究 "readMyAsset" 智能合约方法。

Caliper 区块链对象使用以下方法与已部署的智能合约进行交互:

  • invokeSmartContract (ctx, contractId, contractVersion, args)
  • querySmartContract (ctx, contractId, contractVersion, args)

    其中:

    • ctx 是用户上下文
    • contractId 是智能合约名称
    • contractVersion 是智能合约版本
    • args 是包含以下内容的对象:
      chaincodeFunction
      invokerIdentity
      chaincodeArguments
      

下面是一个基本测试回调模板,该模板与版本为 0.0.1 的已部署智能合约 demoContract 进行交互:

'use strict';

module.exports.info  = 'Template callback';

const contractID = 'demoContract';
const version = '0.0.1';

let bc, ctx, clientArgs, clientIdx;

module.exports.init = async function(blockchain, context, args) {
};

module.exports.run = function() {
    return Promise.resolve();
};

module.exports.end = async function() {
};

任务:

  1. benchmarks 文件夹中创建一个名为 callbacks 的子文件夹。
  2. 在 callbacks 文件夹中创建一个名为 queryAssetBenchmark.js 的文件。
  3. 打开该文件并插入上面的模板代码。
  4. 保存该文件。

现在应该填充 initrunend 模板函数了。

init 函数

该函数用于持久化传递的参数,并准备 run 函数中所需的所有项目。至少,您需要持久化 blockchaincontext 参数,但是由于 readMyAsset 函数需要一组要查询的资产,因此您还需要创建这些资产。您可以将用户指定的一组参数传递给 init 函数,这意味着您可以指定一个变量来表示要在测试期间创建的资产数量。应该注意的是,由于在基准测试期间可能会使用多个客户端,并且这些客户端都将调用同一测试回调,因此务必要避免客户端之间出现歧义。最简单的方法就是使用唯一客户端标识符,这也是上下文的属性。

任务:

  1. 将区块链、上下文和参数分别作为全局变量 bcctxclientArgs 来持久化。
  2. 假设要创建的所需资产数量以 clientArgs.assets 形式给出,然后创建一个介于 0 到要创建的资产数量之间的 for 循环。
  3. 您将使用智能合约方法 createMyAssetfor 循环内创建资产。如果发生错误,该方法可能会抛出错误,因此您应该在 try-catch 函数块中为此设定条件,并将错误打印输出到控制台以便于调试。
    • for 循环中创建一个 try-catch 函数块。
    • 在 catch 中,添加一个用于报告错误的信息语句。
    • 在 try 中,等待对区块链对象调用 invokeSmartContract 完成,并传递已知的上下文、合约名称、合约版本以及包含以下内容的对象:
      • 设置为 createMyAssetchaincodeFunction
      • 设置为 admininvokerIdentity ,这是导出的钱包中的身份
      • 带有数组的 chaincodeArguments ,该数组包含:
        for
        
    module.exports.init = async function(blockchain, context, args) {
        bc = blockchain;
        ctx = context;
        clientArgs = args;
        clientIdx = context.clientIdx.toString();
        for (let i=0; i<clientArgs.assets; i++) {
            try {
                const assetID = `${clientIdx}_${i}`;
                console.log(`Client ${clientIdx}: Creating asset ${assetID}`);
                const myArgs = {
                    chaincodeFunction: 'createMyAsset',
                    invokerIdentity: 'admin',
                    chaincodeArguments: [assetID, `UUID: ${assetID}`]
                };
                await bc.bcObj.invokeSmartContract(ctx, contractID, version, myArgs);
            } catch (error) {
                console.log(`Client ${clientIdx}: Smart Contract threw with error: ${error}` );
            }
        }
    };

run 函数

这是在记录的基准测试阶段中反复运行的函数,因此应尽可能简洁。您的目标是评估 readMyAsset 智能合约函数,对 init 阶段中创建的其中一个资产执行查询。函数 必须 返回一个未解析的 promise,而不阻塞,这样驱动客户端便可以进行多个并发的 run 调用。

任务:

  1. 创建要查询的资产的字符串身份,这一身份是通过将测试客户端索引与 0 到创建的资产数量之间的随机整数相连接而构成。
  2. 返回对 querySmartContract 的调用,并传递已知的上下文、合约名称、合约版本以及包含以下内容的对象:
    • 设置为 readMyAssetchaincodeFunction
    • 设置为 admininvokerIdentity ,这是导出的钱包中的身份
    • 含数组的 chaincodeArguments ,该数组包含要在这个调用中查询的资产
    module.exports.run = function() {
        const randomId = Math.floor(Math.random()*clientArgs.assets);
        const myArgs = {
            chaincodeFunction: 'readMyAsset',
            invokerIdentity: 'admin',
            chaincodeArguments: [`${clientIdx}_${randomId}`]
        };
        return bc.bcObj.querySmartContract(ctx, contractID, version, myArgs);
    };

end 函数

end 函数用于在测试完成之后执行清理操作。为了确保测试的可重复性,您需要删除在 init 阶段创建的所有资产。您可以使用 init 阶段中使用的同一个 for 循环,但将其修改为调用智能合约函数 deleteMyAsset ,并且仅传递要删除的资产身份。

module.exports.end = async function() {
    for (let i=0; i<clientArgs.assets; i++) {
        try {
            const assetID = `${clientIdx}_${i}`;
            console.log(`Client ${clientIdx}: Deleting asset ${assetID}`);
            const myArgs = {
                chaincodeFunction: 'deleteMyAsset',
                invokerIdentity: 'admin',
                chaincodeArguments: [assetID]
            };
            await bc.bcObj.invokeSmartContract(ctx, contractID, version, myArgs);
        } catch (error) {
            console.log(`Client ${clientIdx}: Smart Contract threw with error: ${error}` );
        }
    }
};

现在,您已经指定了一项测试回调,该测试回调在 init 阶段创建测试资产,在 run 阶段查询所创建的资产,并在 end 阶段删除测试资产。

基准测试配置文件

基准测试配置文件是一个 YAML 文件,此文件通过指定以下内容来定义根据部署的智能合约要运行的完整性能测试:

  • 生成测试负载时要使用的测试客户端数量
  • 测试轮数
  • 每轮持续时间
  • 每轮的负载生成方法
  • 每轮中要使用的回调(测试交互)

现在,您将开始构建一个使用 queryAsssetBenchmark.js 测试回调的 YAML 基准测试配置文件。请注意,YAM 文件区分大小写。所有标签均应以小写格式指定。

任务:

  1. benchmarks 文件夹中创建一个名为 myAssetBenchmark.yaml 的新文件,然后打开该文件进行编辑。
  2. 添加一个名为 test 的根级字段,用于描述要运行的测试,其中包含:
    • name 键,其值为 my-asset-benchmark
    • description 键,值是简短描述
    • 一个名为 clients 的字段,定义要使用的测试客户端的类型和数量。现在,添加以下键/值对:
      • type: local
      • number: 2
    • 一个名为 rounds 的字段,留为空白
  3. 添加一个名为 monitor 的根级字段,其中包含一个名为 type 的键,并以单个数组项 none 作为值。这表明在基准测试期间,您将不会执行任何资源监视。
---
test:
  name: my-asset-benchmark
  description: Benchmarking for VS Code sample
  clients:
    type: local
    number: 2
  rounds:

monitor:
  type:
  - none

rounds 字段包含将以序列格式运行的每轮基准测试,并以唯一的轮次标签作为标题。轮次可用于对不同的智能合约方法进行基准测试,或以不同方式对同一方法进行基准测试。每一轮测试块都包含以下内容:

  • label -- 该轮次使用的唯一标签
  • description -- 正在运行的轮次的描述
  • chaincodeId -- 正在测试的链码(智能合约)的 ID
  • [txDuration | txNumber] -- 轮次时间长度的说明符,可以是持续时间,也可以基于事务
  • rateControl -- 含选项的速率控制方法
  • callback -- 正在调查的智能合约的用户定义测试文件的相对路径
  • arguments -- 可选的参数数组,在调用时将传递到用户测试文件 ( callback )

您现在将填充这些内容。

任务:

  1. 使用键名称 label 和值 "queryAsset" 创建一个新序列。
  2. queryAsset 序列中,添加一个名为 description 的键,其值为"Query asset benchmark test"。
  3. queryAsset 序列中,添加一个名为 chaincodeId 的键,其值为 demoContract
  4. queryAsset 序列中,添加一个名为 txDuration 的字段,其单个序列条目为 30。这表明基准测试将运行一次,持续时间为 30 秒。
  5. queryAsset 序列中,添加一个名为 rateControl 的字段,其中包含一个具有以下内容的序列条目:
    • 名为 type 的键,其字符串值为 fixed-backlog 。这表明对于暂挂事务,您将使基准测试保持固定的事务积压量。
    • 名为 ops 的字段,其键为 unfinished_per_client ,值为 2。这表明将以一定速度驱动每个客户端,以保持 2 个暂挂事务。
  6. queryAsset 序列中,向 queryAssetBenchmark.js 文件中添加含有相对路径的回调。相对路径介于要创建的基准测试文件和回调文件之间。
  7. queryAsset 序列中,添加一个名为 arguments 的字段。添加一个名为 assets 的键,其值为 10。这将在 init 阶段传递给测试回调。
---
test:
  name: my-asset-benchmark
  description: Benchmarking for VS Code sample
  clients:
    type: local
    number: 2
  rounds:
    - label: queryAsset
      description: Query asset benchmark test
      chaincodeId: demoContract
      txDuration:
      - 30
      rateControl:
      - type: fixed-backlog
        opts:
          unfinished_per_client: 2
      callback: callbacks/queryAssetBenchmark.js
      arguments:
        assets: 10
monitor:
  type:
  - none

现在,您已有了一个基准测试配置文件,以及可由 Caliper 使用的配套测试回调文件。

第 3 步. 运行基准测试

您现在将使用在前述步骤中创建的资源,通过 Caliper CLI 对默认的 IBM Blockchain Platform VS Code 网络完成性能基准测试。要发出的命令是 caliper benchmark run ,必须在该命令中提供 网络配置 文件、 基准测试配置 文件和正在使用的 工作区 的详细信息。根据您已创建的资源,必须提供以下参数对:

network_config.json
myAssetBenchmark.yaml
./

由于已经配置了网络,并且链码也已安装和实例化,因此,Caliper 唯一要执行的操作就是 测试 阶段(使用启用了 发现Fabric 网关 )。为指定这些选项,您需要将以下附加标志传递给 CLI 命令:

caliper-flow-only-test
caliper-fabric-usegateway
caliper-fabric-discovery

任务:

  1. 确保您位于步骤 2 中创建的 benchmarks 目录中,该目录中现在应存在以下资源:
    .
    ├── callbacks
    │   └── queryAssetBenchmark.js
    ├── myAssetBenchmark.yaml
    ├── myWallet
    │   └── admin
    │       ├── <UUID>-priv
    │       ├── <UUID>-pub
    │       └── admin
    └── network_config.json
  2. 运行 Caliper CLI 命令
    caliper benchmark run --caliper-benchconfig myAssetBenchmark.yaml --caliper-networkconfig network_config.json --caliper-workspace ./ --caliper-flow-only-test --caliper-fabric-usegateway --caliper-fabric-discovery

随着测试的进行,您将在控制台上看到 Caliper 的操作,最终会显示基准测试的输出摘要。同时还将生成 HTML 报告,其中包含在基准测试过程中打印到控制台的相同信息。

该报告将详细介绍每一轮基准测试的以下信息:

  • Name -- 轮次名称,这与基准测试配置文件中的测试轮次标签相关
  • Succ/Fail -- 成功/失败事务的数量
  • Send Rate -- Caliper 发出事务的速率
  • Latency (max/min/avg) -- 关于发出事务到收到响应之间所用秒数的统计信息
  • Throughput -- 平均每秒处理的事务数

结束语

现在,您已经在 IBM Blockchain Platform VS Code 扩展提供的默认本地网络上成功地对已部署的智能合约进行了基准测试。您可以通过更改基准测试参数来重复进行测试:有关完整参数集的信息,请参见 Caliper 官方文档

特别感谢 IBM 袁怿对本文的翻译建议!

参考资源

本文翻译自: Performance testing smart contracts developed within VS Code using Hyperledger Caliper (2019-10-14)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK