0 联邦学习端侧交接文档_独立建仓部分
zhangzhaoju edited this page 1 year ago

联邦学习从Mindspore 1.8版本开始拉取分支进行独立建仓,因此Mindspore 1.8之前联邦学习代码和Mindspore共用mindspore代码仓库,端侧代码存放在fl_client目录, 联邦学习独立建仓后代码迁移到联邦独立仓。本文档主要讲解独立建仓后联邦学习端侧的代码结构、以及如何进行代码编译、UT测试、ST测试、手机测试。

端侧代码结构

端侧代码包含联邦端侧框架、开源二次定制客户端样例、内源二次定制客户端样例。 开源和内源代码开发样例结构一致,只是针对不同模型进行了不同定制,本文以开源二次定制客户端为例进行说明。

联邦端侧框架结构

.
├── build.gradle   # 端侧gradle工程配置文件,用于编译、打包构建
├── cli_build.sh   # 端侧编译入口脚本,进行编译依赖下载并调用gradle完成构建
├── README_CN.md   # 端侧编译和测试指导说明
├── settings.gradle # 端侧gradle工程配置文件,声明了端侧工程名字
├── src             # 端侧源码路径
│   ├── main
│   │   └── java
│   │       └── com
│   │           └── mindspore
│   │               └── flclient
│   │                   ├── BindMode.java  # 手机CPU绑定方式(绑定大核、中核),由于绑核需要特殊权限,该功能并没有启用
│   │                   ├── cipher         # 网络安全加解密相关代码
│   │                   │   ├── AESEncrypt.java  # AES加解密类定义
│   │                   │   ├── BaseUtil.java    # 基本的字节流和String互转实现
│   │                   │   ├── CertVerify.java  # 证书检查类
│   │                   │   ├── CipherConsts.java  # 证书相关的常量定义类
│   │                   │   ├── ClientListReq.java # PW加密算法专用,用于从Server获取客户端列表
│   │                   │   ├── KEYAgreement.java  # 生成公私玥对和DH Keys供PW加密算法使用
│   │                   │   ├── Masking.java       # 生成生成mask供PW加密算法使用
│   │                   │   ├── ReconstructSecretReq.java  
│   │                   │   ├── ShareSecrets.java
│   │                   │   ├── SignAndVerify.java
│   │                   │   └── struct
│   │                   │       ├── ClientPublicKey.java
│   │                   │       ├── DecryptShareSecrets.java
│   │                   │       ├── EncryptShare.java
│   │                   │       ├── NewArray.java
│   │                   │       └── ShareSecret.java
│   │                   ├── CipherClient.java
│   │                   ├── common
│   │                   │   ├── FLLoggerGenerater.java # 日志匿名化适配类,用于生成Logger对象
│   │                   │   └── MsgAnonymous.java      # 日志匿名化具体实现类
│   │                   ├── Common.java                # 通用公共接口定义
│   │                   ├── compression                # 压缩相关类定义
│   │                   │   ├── CompressMode.java      # 压缩类型定义
│   │                   │   ├── CompressWeight.java    # 压缩权重信息定义
│   │                   │   ├── DecodeExecutor.java    # 加压缩类
│   │                   │   └── EncodeExecutor.java    # 压缩类
│   │                   ├── EarlyStopMod.java          # 支持EarlyStop预留接口,当前未使用
│   │                   ├── EncryptLevel.java          # 加密方式枚举类
│   │                   ├── FLClientStatus.java        # FLClient状态定义枚举类
│   │                   ├── FLCommunication.java       # 基于okhttp的通信接口类
│   │                   ├── FLJobResultCallback.java   # FLJob执行结果检查回调类,用于复写检查执行结果,当前在UT中使用
│   │                   ├── FLLiteClient.java          # 端侧功能实现的主体类,提供训练、推理、模型更新、下载等功能
│   │                   ├── FLParameter.java           # 联邦相关的参数配置类,该类会暴露给外部,外部通过该类进行联邦端侧配置
│   │                   ├── GetModel.java              # 构建GetModel FlatBuffer请求消息,解析GetModel FlatBuffer响应消息
│   │                   ├── LocalFLParameter.java      # 联邦内部参数配置类,该类不需要暴露给外部,用于端侧各实体对象间参数传递
│   │                   ├── model
│   │                   │   ├── Callback.java          # 二次开发定制Client时,数据预处理、精度评估、Loss计算的回调基类
│   │                   │   ├── Client.java            # 二次开发定制Client基类,提供了基本的Client能力,用户可以继承该类实现自己的数据预处理、模型评估等。
│   │                   │   ├── ClientManager.java     # Client管理类,用户自定义Client注册到该类,并根据服务名选择具体的Client类
│   │                   │   ├── CommonUtils.java       # 供二次开发定制的通用功能接口
│   │                   │   ├── DataSet.java           # 二次开发数据预处理基类
│   │                   │   ├── LossCallback.java      # 二次开发loss获取基类
│   │                   │   ├── ModelProxy.java        # Model代理类用于接口统一,对Client屏蔽训练和推理差异
│   │                   │   └── Status.java            # 执行结果码定义
│   │                   ├── pki                        # 从华为KeyStore获取签名和证书信息
│   │                   │   ├── PkiBean.java
│   │                   │   ├── PkiConsts.java
│   │                   │   └── PkiUtil.java
│   │                   ├── SecureProtocol.java        # 联邦权重加解密实现类
│   │                   ├── SSLSocketFactoryTools.java # SSL通信证书解析和检查类,支撑https通信
│   │                   ├── StartFLJob.java            # 构建StartFLJob(训练启动) FlatBuffer请求消息,解析StartFLJob FlatBuffer响应消息
│   │                   ├── SyncFLJob.java             # 联邦端侧对外统一接口类,提供训练、推理、模型更新接口
│   │                   └── UpdateModel.java           # 构建UpdateModel(模型更新) FlatBuffer请求消息,解析UpdateModel FlatBuffer响应消息
│   └── test        # 端侧单元测试代码路径
│       └── java
│           └── com
│               └── mindspore
│                   └── flclient
│                       ├── FLFrameTestCase.java         # 测试用例元数据类
│                       ├── FLFrameTestCaseParser.java   # 测试用例元数据解析类
│                       ├── FLHttpRes.java               # 测试用例定义中的http消息元数据类
│                       ├── FLInferTest.java             # 推理测试用例执行类
│                       ├── FLMockServer.java            # http server mock类,用于模拟server接收和响应消息
│                       ├── FLSingleCaseTest.java        # 读取特定文件特定用例执行,方便问题分析和定位
│                       ├── FLTrainTest.java             # 训练测试用例执行类
│                       ├── FLUTCommon.java              # UT测试通用功能定义类
│                       └── FLUTResultCheck.java         # UT测试结果检查类,继承自IFLJobResultCallback
└── ut_data         # 端侧单元测试数据路径     
    ├── gen_lenet_data.py                # 生成test_data的python脚本
    ├── test_case
    │   ├── fl_frame_ut_case_infer.json  # 推理测试用例定义文件
    │   └── fl_frame_ut_case.json        # 训练测试用例定义文件
    └── test_data
        └── lenet
            └── f0178_39                 # 由于CI服务器python执行存在问题,预置的测试数据
                ├── f0178_39_bn_1_test_data.bin
                ├── f0178_39_bn_1_test_label.bin
                ├── f0178_39_bn_9_train_data.bin
                └── f0178_39_bn_9_train_label.bin

端侧编译&&部署

参考:https://www.mindspore.cn/federated/docs/zh-CN/master/deploy_federated_client.html

开源二次定制客户端样例结构

二次客户端开发的代码样例根据保密级别要求分别放在了开源仓quick_start_flclient和内源仓flclient_models,其代码结构类似。

├── build.sh
├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── mindspore
                    └── flclient
                        └── demo
                            ├── albert   # albert客户端定制代码
                            │   ├── AlbertClient.java
                            │   ├── AlbertDataSet.java
                            │   ├── CustomTokenizer.java
                            │   └── Feature.java
                            ├── common
                            │   ├── ClassifierAccuracyCallback.java
                            │   └── PredictCallback.java
                            └── lenet    # lenet客户端定制定制代码
                                ├── LenetClient.java
                                └── LenetDataSet.java

二次客户端定制,主要完成三种类的定制开发:

xxxClient.java: 主要是为客户端挂载自定义的CallBack和DataSet,并基于CallBack获取训练推理结果。代码相对简单,参考样例实现即可。

xxxDataSet.java:进行数据预处理的定制,读取原始数据文件将训练/推理数据缓存到程序中,并通过fillInputBuffer接口将数据传递给Lite侧完成训练、推理

xxxCallback.java:定制训练的Loss获取Callback、推理结果Callback、验证精度Callback

基于端侧编译&&部署完成端侧编译和部署后,可以通过脚本build.sh 完成quick_start_flclient编译。

需要注意的是:quick_start_flclient编译依赖依赖mindspore-lite-java-flclient-0.1.0.jar 和 mindspore端侧lite包, mindspore-lite-java-flclient-0.1.0.jar在端侧编译时生成, mindspore端侧lite包使用wget从开源发布仓获取。

UT测试指南

联邦学习UT测试已经自动化到端侧的编译脚本中cli_build.sh中, 具体如何启动请参考cli_build.sh;

同时也可以参考cli_build.sh 完成环境变量设置后通过Intellidea 工具进程单元测试; 单元测试需要将quick_start_flclient.jar(二次定制开发包编译生成)和mindspore-lite-java.jar(mindspore端侧发布包中携带)两个jar包放置到libs目录,同时设置MS_FL_UT_BASE_PATH为ut_data对应的目录

ut_data目录结构如下:

.
├── gen_lenet_data.py  # 使用该脚本生成lenet测试数据
├── test_case
│   ├── fl_frame_ut_case_infer.json   # infer类用例撰写脚本
│   └── fl_frame_ut_case.json         # 训练类用例撰写脚本
└── test_data
    └── lenet                         # lenet测试相关数据
        ├── f0178_39                  # 通过gen_lenet_data.py脚本生成的测试数据
        │   ├── f0178_39_bn_1_test_data.bin
        │   ├── f0178_39_bn_1_test_label.bin
        │   ├── f0178_39_bn_9_train_data.bin
        │   └── f0178_39_bn_9_train_label.bin
        └── lenet_train.ms            # wget从网上下载的lenet模型文件

fl_frame_ut_case.json中测试用例展示:

[
{
  'caseName':'New_Frame_Lenet_FlJobRun',    # 测试用例名
  'trainDataPath':'/test_data/lenet/f0178_39/f0178_39_bn_9_train_data.bin,/test_data/lenet/f0178_39/f0178_39_bn_9_train_label.bin',            # 测试用例需要的文件列表(训练和标签),和MS_FL_UT_BASE_PATH 拼接为绝对路径,需要保证和真实文件名一致
  'evalDataPath':'/test_data/lenet/f0178_39/f0178_39_bn_1_test_data.bin,/test_data/lenet/f0178_39/f0178_39_bn_1_test_label.bin', # 验证集数据和标签, 和MS_FL_UT_BASE_PATH 拼接为绝对路径,需要保证和真实文件名一致
  'pathRegex':',',  # trainDataPath和evalDataPath中的文件分割符
  'flName':'com.mindspore.flclient.demo.lenet.LenetClient',   # 完整的二次定制开发联邦学习客户端名字
  'trainModelPath':'/test_data/lenet/lenet_train.ms',         # 训练模型文件,和MS_FL_UT_BASE_PATH 拼接为绝对路径
  'inferModelPath':'/test_data/lenet/lenet_train.ms',         # 推理模型文件,和MS_FL_UT_BASE_PATH 拼接为绝对路径
  'deployEnv':'x86',                                          # 测试平台
  'domainName':'http://127.0.0.1:6668',                       # 联邦server的服务域名,UT测试通过MockWebServer 模拟 
  'sslProtocol':'TLSv1.2',                                    # 使用的SSL写
  'useSSL':'false',                                           # 是否开启SSL
  'serverNum':'2',                                            # 远端SSL的个数
  'certPath':'CARoot.pem',                                    # SSL的证书,UT并不支持开启SSL
  'task':'train',                                             # 任务类型,分为train、inference、getModel三类;对应SyncFLJob.java的三种对外接口
  'cpuBindMode':'NOT_BINDING_CORE',                           # CPU绑核定义,没有意义
  'httpRes':[{'resName':'startFLJob', 'resCode':200, 'contendMode':0, 'contentData':'0'},   # startFLJob消息的http响应
             {'resName':'updateModel', 'resCode':200, 'contendMode':0, 'contentData':'1'},  # updateModel消息的http响应
             {'resName':'getModel', 'resCode':200, 'contendMode':0, 'contentData':'8'}      # getModel消息的http响应
            ],
  'resultCode':200
},
...
]

注:

  1. 出于安全性考虑,测试数据自行生成,模型文件从开源仓获取。开发自测可以使用FL-models提供的真实数据模型
  2. 可以参考fl_frame_ut_case.json中的现有配置追加新的用例

ST测试指南

联邦ST执行

联邦学习ST的执行主要有两个依赖 fl_resource 和 安装包pkg。

fl_resource 资源包如下:

fl_resources
├── certs   # 获取地址 https://codehub-y.huawei.com/Geek/FL-models/files?ref=master&filePath=certs
│   ├── Huawei_CBG_MobileEquCa_2021.pem
│   ├── RootCaG2Ecdsa.cer
│   └── root.cer
├── ci_jar  # 通过编译quick_start_flclient和flclient_models 获取
│   ├── flclient_models.jar
│   └── quick_start_flclient.jar
├── client  
│   ├── cert # 端侧https证书文件, 当前ST没有开启https,自测https证书可以通过genFlCert.sh生成
│   │   └── CARoot.pem
│   ├── data # 获取地址 https://codehub-y.huawei.com/Geek/FL-models/files?ref=master&filePath=client%2Fdatasets
│   │   ├── adstag
│   │   │   ├── n_samples_42_id_1535.csv
│   │   │   └── n_samples_42_id_1691.csv
│   │   ├── albert
│   │   │   ├── eval
│   │   │   │   └── eval.txt
│   │   │   ├── eval_tiny
│   │   │   │   └── eval.txt
│   │   │   ├── train
│   │   │   │   └── 0.txt
│   │   │   ├── vocab_map_ids.txt
│   │   │   └── vocab.txt
│   │   ├── lenet
│   │   │   ├── f0049_32_bn_11_train_data.bin
│   │   │   ├── f0049_32_bn_11_train_label.bin
│   │   │   ├── f0049_32_bn_1_test_data.bin
│   │   │   └── f0049_32_bn_1_test_label.bin
│   │   └── vae
│   │       └── flatten_ca801543-a7e8-4090-9210-9b5af63be892_3.csv
│   └── ms # 获取地址 https://codehub-y.huawei.com/Geek/FL-models/files?ref=master&filePath=client%2Fms_files
│       ├── albert_supervise.mindir.ms
│       ├── albert_train.mindir.ms
│       ├── lenet_train.ms
│       ├── tag_infer_bs_1_ms_170_convert_170_date_220711.ms
│       ├── tag_ms_170_convert_170_date_220704.ms
│       ├── test
│       └── vae_train_2022.0411.ms
└── server
    └── models # 获取地址 https://codehub-y.huawei.com/Geek/FL-models/files?ref=master&filePath=server%2Fmodels
        ├── adbert
        │   ├── albert_iteration_0.ckpt
        │   ├── fl_ckpt
        │   │   └── Adbert_recovery_iteration_0_220726_164030.ckpt
        │   ├── vocab_map_ids.txt
        │   └── vocab.txt
        ├── adstag
        │   └── fl_ckpt
        │       └── Adstag_recovery_iteration_0_220726_164030.ckpt
        ├── albert
        │   └── fl_ckpt
        │       └── Albert_recovery_iteration_0_220726_164030.ckpt
        ├── lenet
        │   └── fl_ckpt
        │       ├── Lenet_recovery_iteration_0_220726_164030.ckpt
        │       ├── Lenet_recovery_iteration_1_20220922_185317.ckpt
        │       └── Lenet_recovery_iteration_2_20220922_185328.ckpt
        └── vae
            └── fl_ckpt
                └── Vae_recovery_iteration_0_20220726_164030.ckpt

安装包pkg如下:

pkg
├── mindspore-2.0.0-cp37-cp37m-linux_x86_64.whl      # MindSpore安装包,MindSpore官网或自行编译获取
├── mindspore_federated-0.1.0-cp37-cp37m-linux_x86_64.whl # MindSpore FL 云侧安装包,自行编译获取
├── mindspore-lite-2.0.0-linux-x64.tar.gz  # MindSpore Lite安装包,MindSpore官网或自行编译获取
└── mindspore-lite-java-flclient-0.1.0.jar # MindSpore FL端侧X86包,自行编译获取

联邦ST执行通过调用脚本run_benchmark_net.sh来完成,具体如下:

 bash run_benchmark_net.sh -r $PATH/fl_resources -p $PATH/pkg

联邦学习CI环境上的ST 执行安装包pkg 由自动化脚本从每日构建中获取是最新的, fl_resource由联邦开发提供给CI 环境维护工程师(杨浩然 00471439、刘若涛 30029782)上传到ST服务器(非实时获取), 这种机制决定了如果更改、新增用例或代码变更需要修复fl_resource则需要联系(杨浩然 00471439、刘若涛 30029782)进行更新。

联邦ST架构

client_script  # 端侧通用启动脚本,各模型具体启动指令可以启动ST通过日志查看
├── fl_client_finish.py
└── fl_client_run.py
cross_device_cloud # 云侧通用启动脚本,各模型具体启动指令可以启动ST通过日志查看
├── finish_cloud.py
├── run_cloud.py
├── run_sched.py
├── run_server_disaster_recovery.py
├── run_server.py
└── yamls  # 各用例云侧启动定制配置文件
    ├── adbert.yaml
    ├── adstag
    │   ├── adstag.yaml
    │   ├── compress_ne_config.yaml
    │   ├── nc_dp_config.yaml
    │   ├── nc_ne_config.yaml
    │   ├── nc_pw_config.yaml
    │   └── nc_signds_config.yaml
    ├── albert
    │   ├── albert.yaml
    │   ├── compress_ne_config.yaml
    │   ├── nc_dp_config.yaml
    │   ├── nc_ne_config.yaml
    │   ├── nc_pw_config.yaml
    │   └── nc_signds_config.yaml
    ├── lenet
    │   ├── batchsize_16_config.yaml
    │   ├── batchsize_64_config.yaml
    │   ├── compress_ne_config.yaml
    │   ├── default_yaml_config.yaml
    │   ├── nc_dp_config.yaml
    │   ├── nc_ne_config.yaml
    │   ├── nc_pw_config.yaml
    │   └── nc_signds_config.yaml
    ├── vae
    │   ├── compress_ne_config.yaml
    │   ├── nc_dp_config.yaml
    │   ├── nc_ne_config.yaml
    │   ├── nc_pw_config.yaml
    │   └── nc_signds_config.yaml
    └── vae_v2
        └── compress_signds_config.yaml
st_script  # 端侧用例构建脚本
├── base_case.py # ST基类脚本, 用于安装Mindspore、联邦学习、环境准备等
├── test_adstag.py # adstag用例文件,继承自base_case
├── test_albert.py # albert用例文件,继承自base_case
├── test_lenet.py  # lenet用例文件,继承自base_case
└── test_vae.py    # vae用例文件,继承自base_case

手机测试指南

启动联邦学习FL_Server

安装MindSpore

MindSpore横向联邦学习云侧集群支持在x86 CPU和GPU CUDA硬件平台上部署。可参考MindSpore安装指南安装MindSpore最新版本。

安装MindSpore Federated

通过源码编译安装。

git clone https://gitee.com/mindspore/federated.git -b master
cd federated
bash build.sh

对于bash build.sh,可通过例如-jn选项,例如-j16,加速编译;可通过-S on选项,从gitee而不是github下载第三方依赖。

编译完成后,在build/package/目录下找到Federated的whl安装包进行安装:

pip install mindspore_federated-{version}-{python_version}-linux_{arch}.whl

验证是否成功安装

执行以下命令,验证安装结果。导入Python模块不报错即安装成功:

from mindspore_federated import FLServerJob

安装和启动Redis服务器

联邦学习默认依赖Redis服务器作为缓存数据中间件,运行联邦学习业务,需要安装和运行Redis服务器。

安装Redis服务器:

sudo apt-get install redis

运行Redis服务器,配置端口号为:8113:

redis-server --port 8113 --save ""

将预先准被好的模文件
Vae_recovery_iteration_0_20220726_164030.ckpt
放入联邦学习源码的tests/st/cross_device_cloud/fl_ckpt 目录,

进入目录联邦学习源码的如下目录:tests/st/cross_device_cloud, 执行如下指令分别启动联邦学习的shedule和server

python run_sched.py --yaml_config=yamls/vae/nc_ne_config.yaml --scheduler_manage_address=127.0.0.1:6000

python run_server.py --yaml_config=yamls/vae/nc_ne_config.yaml --http_server_address=10.175.99.117:60002 --checkpoint_dir=./fl_ckpt/ --local_server_num=1 --tcp_server_ip=10.175.99.117
# python finish_cloud.py --redis_port=8113   #停掉环境指令

FAQ:

  1. server 启动失败,报错ImportError: libpython3.7m.so.1.0: cannot open shared object file: No such file or directory
    设置环境变量 LD_LIBRARY_PATH添加conda 虚拟环境的libs目录, export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:~miniconda3/lib
  2. server 启动失败, 报错ValueError: attempted relative import beyond top-level package
    取消环境变量PYTHONPATH, unset PYTHONPATH
  3. 需要保证fl_server的服务IP 手机能够访问得到,例如将 PC和手机接入同一局域网。

启动联邦学习端侧

安装android-studio

  1. 下载android-studio安装包,下载地址:https://developer.android.google.cn/studio
  2. 将上一步骤下载的android-studio-${date}-linux.tar.gz解压,得到本地android-studio版本
  3. 进入目录android-studio/bin 执行 bash ./studio.sh启动android-studio
    首次启动需要配置proxy下载android-sdk和其他组件,建议proxy配置为:mirrors.neusoft.edu.cn

编译flclienttest, 并安装到手机

  1. flclienttest是联邦学习的手机端测试app,有联邦学习测试团队维护, 需联系丁蓓 wx1159044或李磊 30009791获取。

  2. 获取MindSpore lite的aar包和联邦学习的mindspore-lite-java-flclient-0.1.0.jar、flclient_models.jar包,将其放置到flclienttest工程的libs目录

  3. 将待测试的模型文件vae_train_2022.0411.ms 放到flclienttest工程的assets路径

  4. 使用android-studio 打开flclienttest 工程, 配置flclienttest/app/build.gradle,根据提示修改compileSdkVersion和targetSdkVersion

  5. 执行build apk生成apk安装包

  6. 执行如下指令完成flclienttest的安装

    cd flclienttest/app/build/outputs/apk/debug
    adb install app-debug.apk
    

安装并启动flclienttest

  1. 选取一部版本大于Android 9.0 的手机, 设置开启开发人员选项 打开USB调试开关

  2. 将手机使用USB和本地电脑链接

  3. 使用如下指令将vae测试数据文件上传到手机:

    adb push flatten_ca801543-a7e8-4090-9210-9b5af63be892_3.csv /data/local/tmp/fl/
    
  4. 手机通过Wifi接入网络, 保证手机和FL Server在同一局域网内, 启动客户端

    adb shell am start -n "com.mindlitetest.flclienttest/com.mindlitetest.flclienttest.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -e testcase testRunTrain -e fl_name com.mindspore.flclient.demo.vae.VaeClient -e train_dataset /data/local/tmp/fl/flatten_0a2ec5b4-3194-45f9-a0be-ed42af75930c_791_0208.csv -e vocab_file null -e ids_file null -e test_dataset /data/local/tmp/fl/flatten_0a2ec5b4-3194-45f9-a0be-ed42af75930c_791_0208.csv -e eval_dataset /data/local/tmp/fl/flatten_0a2ec5b4-3194-45f9-a0be-ed42af75930c_791_0208.csv -e batch_size 32 -e input_shape 1:4:1,1,1,1 -e client_id null -e model_path vae_train_2022.0411.ms -e infer_model_path vae_train_2022.0411.ms -e deploy_env android -e domain_name http://10.175.99.117:60002 -e server_num 1 -e use_elb false -e use_pki_verify false -e ssl false -e ssl_protocol TLSv1.2 -e equip_cert_path Huawei_CBG_MobileEquCa_2017.crl -e thread_num -1 -e bind_mode NOT_BINDING_CORE -e server_mod FEDERATED_LEARNING -e valid_interval -1 -e sleep_time -1 -e time_out -1 -e is_test false -e stop_time 60