需求:模拟马拉松比赛时,多人使用跑步软件上传轨迹数据,另有多人下载跑步者的轨迹数据,达到实时观看马拉松跑步轨迹的目的。
说明:
php 脚本生成的网页内容:
runnerName,runnerPassword,runnerNickname,watcher1Name,watcher1Password,watcher1Nickname,watcher2Name,watcher2Password,watcher2Nickname,watcher3Name,watcher3Password,watcher3Nickname,watcher4Name,watcher4Password,watcher4Nickname
1231231000,123456,ucr0,1231231001,123456,ucr0w1,1231231002,123456,ucr0w2,1231231003,123456,ucr0w3,1231231004,123456,ucr0w4
1231231005,123456,ucr1,1231231006,123456,ucr1w1,1231231007,123456,ucr1w2,1231231008,123456,ucr1w3,1231231009,123456,ucr1w4
1231231010,123456,ucr2,1231231011,123456,ucr2w1,1231231012,123456,ucr2w2,1231231013,123456,ucr2w3,1231231014,123456,ucr2w4
1231231015,123456,ucr3,1231231016,123456,ucr3w1,1231231017,123456,ucr3w2,1231231018,123456,ucr3w3,1231231019,123456,ucr3w4
1231231020,123456,ucr4,1231231021,123456,ucr4w1,1231231022,123456,ucr4w2,1231231023,123456,ucr4w3,1231231024,123456,ucr4w4
1231231025,123456,ucr5,1231231026,123456,ucr5w1,1231231027,123456,ucr5w2,1231231028,123456,ucr5w3,1231231029,123456,ucr5w4
1231231030,123456,ucr6,1231231031,123456,ucr6w1,1231231032,123456,ucr6w2,1231231033,123456,ucr6w3,1231231034,123456,ucr6w4
1231231035,123456,ucr7,1231231036,123456,ucr7w1,1231231037,123456,ucr7w2,1231231038,123456,ucr7w3,1231231039,123456,ucr7w4
1231231040,123456,ucr8,1231231041,123456,ucr8w1,1231231042,123456,ucr8w2,1231231043,123456,ucr8w3,1231231044,123456,ucr8w4
1231231045,123456,ucr9,1231231046,123456,ucr9w1,1231231047,123456,ucr9w2,1231231048,123456,ucr9w3,1231231049,123456
内容是 excel 格式的每组帐号的手机号、密码、昵称
此网页内容保存到压力测试服务器的 ~/gatling-charts-highcharts-bundle-2.1.7/user-files/data/runner-watcher-10.csv 中
浏览器中打开网页:
http://12.34.56.78/marathon/dump/locations.php?starttime=2015-12-22%2015:38:54&userid=12345
可以导出帐号 12345 的已结束跑步的轨迹数据,示例如下:
toff1,lat1,lng1,toff2,lat2,lng2,toff3,lat3,lng3,toff4,lat4,lng4,toff5,lat5,lng5
2,31.328442,120.432391,4,31.328441034483,120.43249606897,6,31.328440068966,120.43260113793,8,31.328439103448,120.4327062069,10,31.328438137931,120.43281127586,
12,31.328437172414,120.43291634483,14,31.328436206897,120.43302141379,16,31.328435241379,120.43312648276,18,31.328434275862,120.43323155172,20,31.328433310345,120.43333662069,
22,31.328432344828,120.43344168966,24,31.32843137931,120.43354675862,26,31.328430413793,120.43365182759,28,31.328429448276,120.43375689655,30,31.328428482759,120.43386196552,
32,31.328427517241,120.43396703448,34,31.328426551724,120.43407210345,36,31.328425586207,120.43417717241,38,31.32842462069,120.43428224138,40,31.328423655172,120.43438731034,
42,31.328422689655,120.43449237931,44,31.328421724138,120.43459744828,46,31.328420758621,120.43470251724,48,31.328419793103,120.43480758621,50,31.328418827586,120.43491265517,
52,31.328417862069,120.43501772414,54,31.328416896552,120.4351227931,56,31.328415931034,120.43522786207,58,31.328414965517,120.43533293103,60,31.328414,120.435438,
62,31.328413228571,120.435541,64,31.328412457143,120.435644,66,31.328411685714,120.435747,68,31.328410914286,120.43585,70,31.328410142857,120.435953,
72,31.328409371429,120.436056,74,31.3284086,120.436159,76,31.328407828571,120.436262,78,31.328407057143,120.436365,80,31.328406285714,120.436468,
82,31.328405514286,120.436571,84,31.328404742857,120.436674,86,31.328403971429,120.436777,88,31.3284032,120.43688,90,31.328402428571,120.436983,
92,31.328401657143,120.437086,94,31.328400885714,120.437189,96,31.328400114286,120.437292,98,31.328399342857,120.437395,100,31.328398571429,120.437498,
内容为 excel 格式的轨迹数据,每行数据为每 10 秒上传的 5 个点。
此网页内容保存到压力测试服务器的~/gatling-charts-highcharts-bundle-2.1.7/user-files/data/location-taihu-42km.csv 中
截取 location-taihu-42km.csv 的 229 行,可以另存为 location-taihu-10km.csv。
压力测试服务器上,输入 ~/gatling-charts-highcharts-bundle-2.1.7/bin$ ./gatling.sh
,
输入 0 选择第一个测试用例 Marathon.RunningSimulation,再按两次回车,就开始压力测试了
shen@debian:~/gatling-charts-highcharts-bundle-2.1.7/bin$ ./gatling.sh
GATLING_HOME is set to /home/shen/gatling-charts-highcharts-bundle-2.1.7
Choose a simulation number:
[0] Marathon.RunningSimulation
[1] computerdatabase.BasicSimulation
[2] computerdatabase.advanced.AdvancedSimulationStep01
[3] computerdatabase.advanced.AdvancedSimulationStep02
[4] computerdatabase.advanced.AdvancedSimulationStep03
[5] computerdatabase.advanced.AdvancedSimulationStep04
[6] computerdatabase.advanced.AdvancedSimulationStep05
0
Select simulation id (default is 'runningsimulation'). Accepted characters are a-z, A-Z, 0-9, - and _
Select run description (optional)
运行过程中的输出:
================================================================================
2015-12-23 13:51:38 170s elapsed
---- running -------------------------------------------------------------------
[--------------------------------------------------------------------------] 0%
waiting: 0 / active: 1 / done:0
---- Requests ------------------------------------------------------------------
> Global (OK=198 KO=0 )
> login (OK=5 KO=0 )
> start_workout (OK=1 KO=0 )
> start_session (OK=1 KO=0 )
> upload_location (OK=15 KO=0 )
> get_last_location (OK=176 KO=0 )
================================================================================
Gatling 源代码摘要:
package Marathon
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
import java.text.SimpleDateFormat
import java.util.Date
class RunningSimulation extends Simulation {
val httpConf = http
.baseURL("http://12.34.56.78/marathon/api")
val userFeeder = csv("runner-watcher-10.csv").circular
object RunAndWatch {
val records = csv("location-taihu-10km.csv").records
val runAndWatch = foreach(records, "record") {
exec(flattenMapIntoAttributes("${record}"))
.exec(
http("upload_location")
.post("/Workout/saveWorkoutSegment")
.headers(headers_urlencoded)
.formParam("userid", "${runner_uid}")
.formParam("sessionid", "abcd")
.formParam("workout", """{
"contenttype": "sessiondata",
"users_id": "${runner_uid}",
"starttime": "${w_starttime}",
"lap": [
{
"starttime": "${s_starttime}",
"locationdata": [
{
"timeoffset": "${toff1}",
"latitude": "${lat1}",
"longitude": "${lng1}",
},
{
"timeoffset": "${toff2}",
"latitude": "${lat2}",
"longitude": "${lng2}",
},
{
"timeoffset": "${toff3}",
"latitude": "${lat3}",
"longitude": "${lng3}",
},
{
"timeoffset": "${toff4}",
"latitude": "${lat4}",
"longitude": "${lng4}",
},
{
"timeoffset": "${toff5}",
"latitude": "${lat5}",
"longitude": "${lng5}",
}
]
}
]
}""")
)
.pause(3)
.exec(
http("get_all_location")
.post("/Workout/getLatestData")
.headers(headers_urlencoded)
.formParam("userid", "${watcher1_uid}")
.formParam("sessionid", "abcd")
.formParam("id", "${runner_uid}")
.formParam("startsplit", "0")
//.check(bodyString.saveAs("watcher1_location_body"))
)
.exec{ session =>
val df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val now = df.format(new Date())
session.set("watcher1_lasttime", now)
}
.pause(3)
.exec(
http("get_all_location")
.post("/Workout/getLatestData")
.headers(headers_urlencoded)
.formParam("userid", "${watcher1_uid}")
.formParam("sessionid", "abcd")
.formParam("id", "${runner_uid}")
.formParam("startsplit", "0")
//.check(bodyString.saveAs("watcher1_location_body"))
)
.exec{ session =>
val df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val now = df.format(new Date())
session.set("watcher1_lasttime", now)
}
.pause(3)
.exec(
http("get_all_location")
.post("/Workout/getLatestData")
.headers(headers_urlencoded)
.formParam("userid", "${watcher1_uid}")
.formParam("sessionid", "abcd")
.formParam("id", "${runner_uid}")
.formParam("startsplit", "0")
//.check(bodyString.saveAs("watcher1_location_body"))
)
.exec{ session =>
val df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val now = df.format(new Date())
session.set("watcher1_lasttime", now)
}
.pause(1)
}
}
val headers_urlencoded = Map("Content-Type" -> "application/x-www-form-urlencoded")
val scn = scenario("running")
.feed(userFeeder)
.exec{ session =>
//println(session)
session
}
.exec(
http("login")
.post("/User/login")
.headers(headers_urlencoded)
.formParam("name", "${runnerName}")
.formParam("password", "${runnerPassword}")
.check(jsonPath("$.result.id").saveAs("runner_uid"))
)
.pause(1)
.exec(
http("login")
.post("/User/login")
.headers(headers_urlencoded)
.formParam("name", "${watcher1Name}")
.formParam("password", "${watcher1Password}")
.check(jsonPath("$.result.id").saveAs("watcher1_uid"))
)
.pause(60)
.exec{ session =>
//println(session)
val df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val now = df.format(new Date())
session.set("w_starttime", now)
}
.exec(
http("start_workout")
.post("/Workout/saveWorkoutSegment")
.headers(headers_urlencoded)
.formParam("userid", "${runner_uid}")
.formParam("sessionid", "abcd")
.formParam("workout", """{
"contenttype": "workouthead",
"users_id": "${runner_uid}",
"starttime": "${w_starttime}",
"type": "1"
}""")
.check(bodyString.saveAs("start_workout_body"))
.check(jsonPath("$.result.id").saveAs("workout_id"))
)
.pause(1)
)
.exec{ session =>
val df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val now = df.format(new Date())
session.set("watcher1_lasttime", now)
}
.exec(RunAndWatch.runAndWatch)
.exec{ session =>
//println(session)
session
}
)
.pause(1)
.exec(
http("end_workout")
.post("/Workout/saveWorkoutSegment")
.headers(headers_urlencoded)
.formParam("userid", "${runner_uid}")
.formParam("sessionid", "abcd")
.formParam("workout", """{
"contenttype": "workoutend",
"users_id": "${runner_uid}",
"starttime": "${w_starttime}",
}""")
)
setUp(scn.inject(rampUsers(10) over(60 seconds)).protocols(httpConf))
//setUp(scn.inject(atOnceUsers(1)).protocols(httpConf))
}
10 个跑步者和 10 个观看者在 60 秒内登录完成,然后开始跑步和观看。1 人跑,1 人看,共 10 组用户。
最主要指标:95th percentile 1436
表示 95% upload_location 请求的响应时间低于 1436 毫秒,上图是 800 组用户的情况,已经到达性能瓶颈。
如果总共 100 个请求,那么从快到慢的第 95 个请求的响应时间为 1436 毫秒。