├── 3rdparty
├── http-parser-2.1.zip
└── st-1.9.zip
├── README.md
├── auto
├── apps.sh
└── modules.sh
├── configure
├── src
├── app
│ ├── htl_app_hls_load.cpp
│ ├── htl_app_hls_load.hpp
│ ├── htl_app_http_client.cpp
│ ├── htl_app_http_client.hpp
│ ├── htl_app_http_load.cpp
│ ├── htl_app_http_load.hpp
│ ├── htl_app_m3u8_parser.cpp
│ ├── htl_app_m3u8_parser.hpp
│ ├── htl_app_rtmp_client.cpp
│ ├── htl_app_rtmp_client.hpp
│ ├── htl_app_rtmp_load.cpp
│ ├── htl_app_rtmp_load.hpp
│ ├── htl_app_rtmp_protocol.cpp
│ ├── htl_app_rtmp_protocol.hpp
│ ├── htl_app_task_base.cpp
│ └── htl_app_task_base.hpp
├── core
│ ├── htl_core_aggregate_ret.cpp
│ ├── htl_core_aggregate_ret.hpp
│ ├── htl_core_error.cpp
│ ├── htl_core_error.hpp
│ ├── htl_core_log.cpp
│ ├── htl_core_log.hpp
│ ├── htl_core_uri.cpp
│ ├── htl_core_uri.hpp
│ └── htl_stdinc.hpp
├── main
│ ├── htl_main_hls_load.cpp
│ ├── htl_main_http_load.cpp
│ ├── htl_main_rtmp_load.cpp
│ ├── htl_main_utility.cpp
│ └── htl_main_utility.hpp
├── os
│ ├── htl_os_st.cpp
│ └── htl_os_st.hpp
└── st-load
│ ├── init
│ └── st-load.upp
├── start_hls_live.sh
├── start_hls_vod.sh
├── start_http.sh
└── start_rtmp.sh
/3rdparty/http-parser-2.1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leanhd/st-load/da79647f01bb8c4b13bef7002857a87108896511/3rdparty/http-parser-2.1.zip
--------------------------------------------------------------------------------
/3rdparty/st-1.9.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leanhd/st-load/da79647f01bb8c4b13bef7002857a87108896511/3rdparty/st-1.9.zip
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | st-load
2 | ===========
3 |
4 | hls/http/rtmp load test tool base on st(state-threads), support huge concurrency
5 |
6 | 服务器负载测试工具(st-load):
7 | 1. 模拟huge并发:2G内存就可以开300k连接。基于states-threads的协程。
8 | 2. 支持HLS解析和测试,下载ts片后等待一个切片长度,模拟客户端。支持HLS点播和直播。
9 | 3. 支持HTTP负载测试,所有并发重复下载一个http文件。可将80Gbps带宽测试的72Gbps。
10 | 4. 支持RTMP流测试,一个进程支持5k并发。使用nginx-rtmp的协议直接将chunk流解析为messgae。
11 | 5. RTMP协议使用高性能服务器SRS([SimpleRtmpServer](https://github.com/winlinvip/simple-rtmp-server))的协议栈,1000个客户端只需要使用30%CPU。
12 |
13 | TestEnvironment: 24CPU, 80Gbps Network, 16GB Memory
14 | Server: NGINX HLS
15 | Result: 90% bandwith, 72Gbps
16 |
17 |
18 | [root@dell-server ~]# dstat
19 | ----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
20 | usr sys idl wai hiq siq| read writ| recv send | in out | int csw
21 | 1 1 95 0 0 3|4091B 369k| 0 0 | 0 0 | 100k 9545
22 | 3 8 66 0 0 23| 0 0 | 40MB 6114MB| 0 0 | 681k 46k
23 | 3 8 63 0 0 25| 0 100k| 41MB 6223MB| 0 0 | 692k 46k
24 | 3 8 64 0 0 25| 0 0 | 41MB 6190MB| 0 0 | 694k 45k
25 | 3 8 66 0 0 23| 0 0 | 40MB 6272MB| 0 0 | 694k 48k
26 | 3 8 64 0 0 25| 0 20k| 40MB 6161MB| 0 0 | 687k 46k
27 | 3 8 65 0 0 24| 0 0 | 40MB 6198MB| 0 0 | 687k 46k
28 | 3 8 66 0 0 23| 0 0 | 40MB 6231MB| 0 0 | 688k 47k
29 | 3 7 70 0 0 20| 0 68k| 40MB 6159MB| 0 0 | 675k 49k
30 | 3 9 62 0 0 26| 0 4096B| 42MB 6283MB| 0 0 | 702k 44k
31 | 4 8 62 0 0 25| 0 2472k| 40MB 6122MB| 0 0 | 698k 44k
32 | 3 8 67 0 0 22| 0 0 | 39MB 6066MB| 0 0 | 671k 46k
33 | 3 8 64 0 0 25| 0 0 | 41MB 6263MB| 0 0 | 695k 46k
34 | 3 8 64 0 0 25|4096B 132k| 41MB 6161MB| 0 0 | 687k 45k
35 | 3 11 60 0 0 26| 0 0 | 42MB 6822MB| 0 0 | 714k 36k
36 | 3 10 62 0 0 25| 0 0 | 40MB 6734MB| 0 0 | 703k 38k
37 | 3 11 60 0 0 26| 0 0 | 43MB 7019MB| 0 0 | 724k 38k
38 | 3 11 60 0 0 26| 0 24k| 45MB 7436MB| 0 0 | 746k 41k
39 | 3 11 60 0 0 27| 0 24k| 47MB 7736MB| 0 0 | 766k 42k
40 | 3 11 59 0 0 28| 0 0 | 52MB 8283MB| 0 0 | 806k 45k
41 | 2 10 61 0 0 27| 0 0 | 54MB 8359MB| 0 0 | 806k 47k
42 | 3 12 53 0 0 32| 0 16k| 58MB 8565MB| 0 0 | 850k 42k
43 | 2 10 62 0 0 26| 0 1212k| 51MB 8140MB| 0 0 | 783k 47k
44 | 2 10 64 0 0 24| 0 0 | 42MB 7033MB| 0 0 | 703k 40k
45 | 2 11 62 0 0 25| 0 0 | 43MB 7203MB| 0 0 | 703k 40k
46 | 2 12 57 0 0 29| 0 0 | 50MB 7970MB| 0 0 | 774k 40k
47 | 2 11 54 0 0 33| 0 0 | 72MB 8943MB| 0 0 | 912k 47k
48 | 3 13 65 0 0 20| 0 0 | 36MB 7247MB| 0 0 | 552k 29k
49 | 3 14 61 0 0 23| 0 1492k| 42MB 8091MB| 0 0 | 613k 32k
50 | 3 13 54 0 0 30| 0 0 | 57MB 9144MB| 0 0 | 760k 34k
51 | 2 10 55 0 0 32| 0 84k| 69MB 9292MB| 0 0 | 861k 38k
52 | 2 9 58 0 0 31| 0 92k| 71MB 9083MB| 0 0 | 860k 39k
53 | 2 9 56 0 0 33| 0 0 | 78MB 9098MB| 0 0 | 914k 39k
54 | 2 8 61 0 0 30| 0 0 | 73MB 8860MB| 0 0 | 876k 39k
55 |
56 |
57 | RTMP load test:
58 |
59 | top - 17:57:24 up 7:10, 7 users, load average: 0.20, 0.20, 0.09
60 | Tasks: 154 total, 1 running, 153 sleeping, 0 stopped, 0 zombie
61 | Cpu(s): 7.4%us, 7.2%sy, 0.0%ni, 78.8%id, 0.0%wa, 0.1%hi, 6.5%si, 0.0%st
62 | Mem: 2055440k total, 1304528k used, 750912k free, 182336k buffers
63 | Swap: 2064376k total, 0k used, 2064376k free, 613848k cached
64 |
65 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
66 | 13091 winlin 20 0 186m 110m 1404 S 29.6 5.5 1:55.35 ./objs/st_rtmp_load -c 1000
67 | 12544 winlin 20 0 124m 22m 2080 S 20.3 1.1 1:51.51 ./objs/simple_rtmp_server
68 |
69 | ----total-cpu-usage---- -dsk/total- ---net/lo-- ---paging-- ---system--
70 | usr sys idl wai hiq siq| read writ| recv send| in out | int csw
71 | 7 7 82 0 0 4| 0 0 | 158M 158M| 0 0 |2962 353
72 | 6 5 83 0 0 6| 0 0 | 74M 74M| 0 0 |2849 291
73 | 7 6 81 0 0 6| 0 0 | 102M 102M| 0 0 |2966 360
74 | 7 8 79 0 0 6| 0 0 | 168M 168M| 0 0 |2889 321
75 | 7 7 79 0 0 7| 0 0 | 83M 83M| 0 0 |2862 364
76 | 5 6 83 0 0 6| 0 0 | 106M 106M| 0 0 |2967 296
77 | 5 6 83 0 0 6| 0 0 | 54M 54M| 0 0 |2907 355
78 | 6 6 84 0 0 4| 0 0 | 58M 58M| 0 0 |2986 353
79 | 6 6 83 0 0 4| 0 0 | 117M 117M| 0 0 |2863 326
80 | 7 6 82 0 0 5| 0 0 | 97M 97M| 0 0 |2954 321
81 | 5 7 78 2 0 8| 0 40k| 82M 82M| 0 0 |2909 357
82 | 5 5 84 0 0 6| 0 0 | 57M 57M| 0 0 |2937 307
83 | 8 8 78 0 0 6| 0 0 | 190M 190M| 0 0 |3024 413
84 | 5 7 82 0 0 7| 0 0 | 75M 75M| 0 0 |2940 310
85 | 8 8 80 0 0 4| 0 0 | 136M 136M| 0 0 |3000 436
86 | 8 8 74 0 0 10| 0 0 | 116M 116M| 0 0 |2816 356
87 | 7 8 78 0 0 6| 0 0 | 128M 128M| 0 0 |2972 424
88 | 6 8 80 0 0 7| 0 4096B| 123M 123M| 0 0 |2981 395
89 | 6 6 83 0 0 5| 0 0 | 50M 50M| 0 0 |2984 367
90 | 7 6 81 2 0 4| 0 92k| 49M 49M| 0 0 |3010 445
91 | 5 6 84 0 0 6| 0 0 | 22M 22M| 0 0 |2912 364
92 | 5 5 85 0 0 4| 0 0 | 34M 34M| 0 0 |3001 429
93 | 6 6 81 0 0 7| 0 0 | 45M 45M| 0 0 |2996 468
94 | 5 5 84 0 0 6| 0 0 | 18M 18M| 0 0 |2923 338
95 | 8 8 77 0 0 7| 0 0 | 158M 158M| 0 0 |2971 351
96 | 7 7 80 0 0 5| 0 0 | 167M 167M| 0 0 |2860 334
97 | 6 5 83 0 0 6| 0 60k| 61M 61M| 0 0 |2988 424
98 | 7 8 79 0 0 6| 0 0 | 140M 140M| 0 0 |2916 391
99 | 8 8 78 0 0 6| 0 0 | 172M 172M| 0 0 |2961 348
100 | 7 8 78 0 0 7| 0 0 | 127M 127M| 0 0 |2865 347
101 | 5 6 84 0 0 5| 0 0 | 73M 73M| 0 0 |2972 344
102 | 6 8 78 0 0 8| 0 0 | 115M 115M| 0 0 |2942 314
103 | 7 8 79 0 0 6| 0 0 | 147M 147M| 0 0 |2966 366
104 |
105 |
106 |
--------------------------------------------------------------------------------
/auto/apps.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # params:
4 | # $GLOBAL_DIR_OBJS the objs directory. ie. objs
5 | # $GLOBAL_FILE_MAKEFILE the makefile name. ie. Makefile
6 | # $MAIN_ENTRANCES array, all main entrance, disable all except the $APP_MAIN itself
7 | # $MODULE_OBJS array, the objects to compile the app.
8 | # $BUILD_KEY a string indicates the build key for Makefile. ie. dump
9 | # $APP_MAIN the object file that contains main function. ie. your_app_main
10 | # $APP_NAME the app name to output. ie. your_app
11 | # $ModuleLibFiles array, the 3rdpart library file to link with. ie. (objs/st-1.9/obj/libst.a objs/libx264/obj/libx264.a)
12 | # $LINK_OPTIONS the linker options.
13 | # $SO_PATH the libssl.so.10 and other so file path.
14 |
15 | FILE=${GLOBAL_DIR_OBJS}/${GLOBAL_FILE_MAKEFILE}
16 |
17 | APP_TARGET="${GLOBAL_DIR_OBJS}/${APP_NAME}"
18 |
19 | echo "generate app ${APP_NAME} depends...";
20 |
21 | echo "# build ${APP_TARGET}" >> ${FILE}
22 | echo "${BUILD_KEY}: ${APP_TARGET}" >> ${FILE}
23 |
24 | echo -n "${APP_TARGET}: " >> ${FILE}
25 | for item in ${MODULE_OBJS[*]}; do
26 | FILE_NAME=`basename $item`
27 | FILE_NAME=${FILE_NAME%.*}
28 |
29 | ignored=0
30 | for disabled_item in ${MAIN_ENTRANCES[*]}; do
31 | if [[ ${FILE_NAME} == ${disabled_item} && ${FILE_NAME} != ${APP_MAIN} ]]; then
32 | ignored=1
33 | continue;
34 | fi
35 | done
36 |
37 | if [ ! -f ${item} ]; then
38 | ignored=1
39 | fi
40 |
41 | if [ ${ignored} == 1 ]; then
42 | continue;
43 | fi
44 |
45 | OBJ_FILE=${GLOBAL_DIR_OBJS}/$item
46 | OBJ_FILE="${OBJ_FILE%.*}.o"
47 | echo -n "${OBJ_FILE} " >> ${FILE}
48 | done
49 | echo "" >> ${FILE}
50 |
51 | echo "generate app ${APP_NAME} link...";
52 |
53 | echo -n " \$(LINK) ${PerformanceLink} -o ${APP_TARGET} " >> ${FILE}
54 | for item in ${MODULE_OBJS[*]}; do
55 | FILE_NAME=`basename $item`
56 | FILE_NAME=${FILE_NAME%.*}
57 |
58 | ignored=0
59 | for disabled_item in ${MAIN_ENTRANCES[*]}; do
60 | if [[ ${FILE_NAME} == ${disabled_item} && ${FILE_NAME} != ${APP_MAIN} ]]; then
61 | ignored=1
62 | continue;
63 | fi
64 | done
65 |
66 | if [ ! -f ${item} ]; then
67 | ignored=1
68 | fi
69 |
70 | if [ ${ignored} == 1 ]; then
71 | continue;
72 | fi
73 |
74 | OBJ_FILE=${GLOBAL_DIR_OBJS}/$item
75 | OBJ_FILE="${OBJ_FILE%.*}.o"
76 | echo -n "${OBJ_FILE} " >> ${FILE}
77 | done
78 | # 3rdpart library static link.
79 | for item in ${ModuleLibFiles[*]}; do
80 | echo -n "$item " >> ${FILE}
81 | done
82 | # link options.
83 | echo -n "${LINK_OPTIONS}" >> ${FILE}
84 | echo "" >> ${FILE}
85 |
86 | # set the so reference path.
87 | if [[ ! -z ${SO_PATH} ]]; then
88 | echo -n " @bash auto/set_so_rpath.sh ${SOPathTool} ${APP_TARGET} ${SO_PATH}" >> ${FILE}
89 | echo "" >> ${FILE}
90 | fi
91 |
92 | echo -n "generate app ${APP_NAME} ok"; echo '!';
93 |
--------------------------------------------------------------------------------
/auto/modules.sh:
--------------------------------------------------------------------------------
1 | # params:
2 | # $GLOBAL_DIR_OBJS the objs directory. ie. objs
3 | # $GLOBAL_FILE_MAKEFILE the makefile name. ie. Makefile
4 | # $MODULE_DIR the module dir. ie. src/os/linux
5 | # $MODULE_ID the id of module. ie. CORE
6 | # $MODULE_DEPENDS array, the denpend MODULEs id. ie. (CORE OS)
7 | # $ModuleLibIncs array, the depend 3rdpart library includes. ie. (objs/st-1.9/obj objs/libx264/obj)
8 | # $MODULE_FILES array, the head/cpp files of modules. ie. (public log)
9 | #
10 | # returns:
11 | # $MODULE_OBJS array, the objects of the modules.
12 |
13 | FILE=${GLOBAL_DIR_OBJS}/${GLOBAL_FILE_MAKEFILE}
14 |
15 | # INCS
16 | INCS_NAME="${MODULE_ID}_INCS"
17 | echo "# the ${MODULE_ID} module." >> ${FILE}
18 | echo "${MODULE_ID}_MODULE_INCS = -I ${MODULE_DIR} " >> ${FILE}
19 | echo -n "${INCS_NAME} = -I ${MODULE_DIR} " >> ${FILE}
20 | for item in ${MODULE_DEPENDS[*]}; do
21 | DEP_INCS_NAME="${item}_INCS"do
22 | DEP_INCS_NAME="${item}_MODULE_INCS"
23 | echo -n "\$(${DEP_INCS_NAME}) " >> ${FILE}
24 | done
25 | for item in ${ModuleLibIncs[*]}; do
26 | echo -n "-I ${item} " >> ${FILE}
27 | done
28 | echo "" >> ${FILE}
29 |
30 | # DEPS
31 | DEPS_NAME="${MODULE_ID}_DEPS"
32 | echo -n "${DEPS_NAME} = " >> ${FILE}
33 | for item in ${MODULE_FILES[*]}; do
34 | HEADER_FILE="${MODULE_DIR}/${item}.hpp"
35 | if [ -f ${HEADER_FILE} ]; then
36 | echo -n " ${HEADER_FILE}" >> ${FILE}
37 | fi
38 | done
39 | for item in ${MODULE_DEPENDS[*]}; do
40 | DEP_DEPS_NAME="${item}_DEPS"
41 | echo -n " \$(${DEP_DEPS_NAME}) " >> ${FILE}
42 | done
43 | echo "" >> ${FILE}; echo "" >> ${FILE}
44 |
45 | # OBJ
46 | MODULE_OBJS=()
47 | for item in ${MODULE_FILES[*]}; do
48 | CPP_FILE="${MODULE_DIR}/${item}.cpp"
49 | OBJ_FILE="${GLOBAL_DIR_OBJS}/${MODULE_DIR}/${item}.o"
50 | MODULE_OBJS="${MODULE_OBJS[@]} ${CPP_FILE}"
51 | if [ -f ${CPP_FILE} ]; then
52 | echo "${OBJ_FILE}: \$(${DEPS_NAME}) ${CPP_FILE} " >> ${FILE}
53 | echo " \$(GCC) -c \$(CXXFLAGS) \$(${INCS_NAME}) -o ${OBJ_FILE} ${CPP_FILE}" >> ${FILE}
54 | fi
55 | done
56 | echo "" >> ${FILE}
57 |
58 | # Makefile
59 | echo " mkdir -p ${GLOBAL_DIR_OBJS}/${MODULE_DIR}" >> ${GLOBAL_FILE_MAKEFILE}
60 |
61 | echo -n "generate module ${MODULE_ID} ok"; echo '!';
62 |
--------------------------------------------------------------------------------
/configure:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | GLOBAL_FILE_MAKEFILE="Makefile"
4 | GLOBAL_DIR_OBJS="objs"
5 |
6 | mkdir -p ${GLOBAL_DIR_OBJS}
7 |
8 | #####################################################################################
9 | # prepare the depends tools
10 | #####################################################################################
11 | # st-1.9
12 | if [[ -f ${GLOBAL_DIR_OBJS}/st-1.9/obj/libst.a && -f ${GLOBAL_DIR_OBJS}/st-1.9/obj/libst.so ]]; then
13 | echo "st-1.9t is ok.";
14 | else
15 | echo "build st-1.9t";
16 | (rm -rf ${GLOBAL_DIR_OBJS}/st-1.9 && cd ${GLOBAL_DIR_OBJS} && unzip ../3rdparty/st-1.9.zip && cd st-1.9 && make linux-debug)
17 | fi
18 | # check status
19 | ret=$?; if [[ $ret -ne 0 ]]; then echo "build st-1.9 failed, ret=$ret"; exit $ret; fi
20 | if [ ! -f ${GLOBAL_DIR_OBJS}/st-1.9/obj/libst.a ]; then echo "build st-1.9 failed."; exit -1; fi
21 | if [ ! -f ${GLOBAL_DIR_OBJS}/st-1.9/obj/libst.so ]; then echo "build st-1.9 failed."; exit -1; fi
22 |
23 | # http-parser-2.1
24 | if [[ -f ${GLOBAL_DIR_OBJS}/http-parser-2.1/http_parser.h && -f ${GLOBAL_DIR_OBJS}/http-parser-2.1/libhttp_parser.a ]]; then
25 | echo "http-parser-2.1 is ok.";
26 | else
27 | echo "build http-parser-2.1";
28 | (
29 | rm -rf ${GLOBAL_DIR_OBJS}/http-parser-2.1 && cd ${GLOBAL_DIR_OBJS} && unzip ../3rdparty/http-parser-2.1.zip &&
30 | cd http-parser-2.1 &&
31 | sed -i "s/CPPFLAGS_FAST +=.*$/CPPFLAGS_FAST = \$\(CPPFLAGS_DEBUG\)/g" Makefile &&
32 | sed -i "s/CFLAGS_FAST =.*$/CFLAGS_FAST = \$\(CFLAGS_DEBUG\)/g" Makefile &&
33 | make package
34 | )
35 | fi
36 | # check status
37 | ret=$?; if [[ $ret -ne 0 ]]; then echo "build http-parser-2.1 failed, ret=$ret"; exit $ret; fi
38 | if [[ ! -f ${GLOBAL_DIR_OBJS}/http-parser-2.1/http_parser.h ]]; then echo "build http-parser-2.1 failed"; exit -1; fi
39 | if [[ ! -f ${GLOBAL_DIR_OBJS}/http-parser-2.1/libhttp_parser.a ]]; then echo "build http-parser-2.1 failed"; exit -1; fi
40 |
41 | #####################################################################################
42 | # generate Makefile.
43 | #####################################################################################
44 | echo "generate Makefile"
45 |
46 | cat << END > ${GLOBAL_FILE_MAKEFILE}
47 | .PHONY: default help clean http hls all _prepare_dir
48 | default: all
49 |
50 | help:
51 | @echo "Usage: make |||||"
52 | @echo " help display this help menu"
53 | @echo " clean cleanup project"
54 | @echo " http build the http load test tool over st(state-threads)"
55 | @echo " hls build the hls load test tool over st(state-threads)"
56 | @echo " rtmp build the rtmp load test tool over st(state-threads)"
57 | @echo " all build the http/hls load test tool over st(state-threads)"
58 |
59 | clean:
60 | (cd ${GLOBAL_DIR_OBJS}; rm -rf src st_*_load)
61 |
62 | http: _prepare_dir
63 | @echo "build the http load test tool over st(state-threads)"
64 | \$(MAKE) -f ${GLOBAL_DIR_OBJS}/${GLOBAL_FILE_MAKEFILE} st_http_load
65 |
66 | rtmp: _prepare_dir
67 | @echo "build the http load test tool over st(state-threads)"
68 | \$(MAKE) -f ${GLOBAL_DIR_OBJS}/${GLOBAL_FILE_MAKEFILE} st_rtmp_load
69 |
70 | hls: _prepare_dir
71 | @echo "build the HLS load test tool over st(state-threads)"
72 | \$(MAKE) -f ${GLOBAL_DIR_OBJS}/${GLOBAL_FILE_MAKEFILE} st_hls_load
73 |
74 | all: _prepare_dir
75 | @echo "build the http/hls/rtmp load test tool over st(state-threads)"
76 | \$(MAKE) -f ${GLOBAL_DIR_OBJS}/${GLOBAL_FILE_MAKEFILE} st_http_load
77 | \$(MAKE) -f ${GLOBAL_DIR_OBJS}/${GLOBAL_FILE_MAKEFILE} st_hls_load
78 | \$(MAKE) -f ${GLOBAL_DIR_OBJS}/${GLOBAL_FILE_MAKEFILE} st_rtmp_load
79 |
80 | # the ./configure will generate it.
81 | _prepare_dir:
82 | END
83 |
84 | echo 'generate Makefile ok!'
85 |
86 | # the performance analysis, uncomments the following when use gperf to analysis the performance. see third-party/readme.txt
87 | #Performance="-pg"
88 | #PerformanceLink="-pg"
89 | # enable gdb debug
90 | GDBDebug="-g -O0"
91 | # the warning level.
92 | WarnLevel="-Wall -Wextra"
93 | # the compile standard.
94 | CppStd="-std=c++98"
95 | # other macros defined
96 | UserMacros=""
97 | # the cxx flag generated.
98 | CXXFLAGS="${CppStd} ${WarnLevel} ${GDBDebug} ${Performance} ${UserMacros}"
99 | cat << END > ${GLOBAL_DIR_OBJS}/${GLOBAL_FILE_MAKEFILE}
100 | CXXFLAGS = ${CXXFLAGS}
101 | GCC = g++
102 | LINK = \$(GCC)
103 | AR = ar
104 |
105 | .PHONY: default st_http_load st_rtmp_load st_hls_load
106 |
107 | default:
108 |
109 | END
110 |
111 | # Libraries
112 | LibSTRoot="${GLOBAL_DIR_OBJS}/st-1.9/obj"
113 | LibSTfile="${LibSTRoot}/libst.a"
114 | LibHttpParserRoot="${GLOBAL_DIR_OBJS}/http-parser-2.1"
115 | LibHttpParserfile="${LibHttpParserRoot}/libhttp_parser.a"
116 |
117 | #Core Module
118 | MODULE_ID="CORE"
119 | MODULE_DEPENDS=()
120 | ModuleLibIncs=(${LibHttpParserRoot})
121 | MODULE_FILES=("htl_core_log" "htl_core_error" "htl_core_uri" "htl_core_aggregate_ret")
122 | MODULE_DIR="src/core" . auto/modules.sh
123 | CORE_OBJS="${MODULE_OBJS[@]}"
124 |
125 | #OS Module
126 | MODULE_ID="OS"
127 | MODULE_DEPENDS=("CORE")
128 | ModuleLibIncs=(${LibSTRoot})
129 | MODULE_FILES=("htl_os_st")
130 | MODULE_DIR="src/os" . auto/modules.sh
131 | OS_OBJS="${MODULE_OBJS[@]}"
132 |
133 | #APP Module
134 | MODULE_ID="APP"
135 | MODULE_DEPENDS=("CORE" "OS")
136 | ModuleLibIncs=(${LibSTRoot} ${LibHttpParserRoot})
137 | MODULE_FILES=("htl_app_hls_load" "htl_app_http_load" "htl_app_http_client" "htl_app_rtmp_client"
138 | "htl_app_m3u8_parser" "htl_app_task_base" "htl_app_rtmp_load" "htl_app_rtmp_protocol")
139 | MODULE_DIR="src/app" . auto/modules.sh
140 | APP_OBJS="${MODULE_OBJS[@]}"
141 |
142 | #Main Module
143 | MODULE_ID="MAIN"
144 | MODULE_DEPENDS=("CORE" "OS" "APP")
145 | ModuleLibIncs=(${LibSTRoot} ${LibHttpParserRoot})
146 | MODULE_FILES=("htl_main_hls_load" "htl_main_http_load" "htl_main_rtmp_load" "htl_main_utility")
147 | MODULE_DIR="src/main" . auto/modules.sh
148 | MAIN_OBJS="${MODULE_OBJS[@].o}"
149 |
150 | # all main entrances
151 | MAIN_ENTRANCES=("htl_main_hls_load" "htl_main_http_load" "htl_main_rtmp_load")
152 |
153 | # http load test tool over st(state-threads)
154 | ModuleLibFiles=(${LibSTfile} ${LibHttpParserfile})
155 | MODULE_OBJS="${CORE_OBJS[@]} ${OS_OBJS[@]} ${APP_OBJS[@]} ${MAIN_OBJS[@]}"
156 | BUILD_KEY="st_http_load" APP_MAIN="htl_main_http_load" APP_NAME="st_http_load" LINK_OPTIONS="-ldl" SO_PATH="" . auto/apps.sh
157 |
158 | # rtmp load test tool over st(state-threads)
159 | ModuleLibFiles=(${LibSTfile} ${LibHttpParserfile})
160 | MODULE_OBJS="${CORE_OBJS[@]} ${OS_OBJS[@]} ${APP_OBJS[@]} ${MAIN_OBJS[@]}"
161 | BUILD_KEY="st_rtmp_load" APP_MAIN="htl_main_rtmp_load" APP_NAME="st_rtmp_load" LINK_OPTIONS="-ldl" SO_PATH="" . auto/apps.sh
162 |
163 | # hls load test tool over direct TCP.
164 | ModuleLibFiles=(${LibSTfile} ${LibHttpParserfile})
165 | MODULE_OBJS="${CORE_OBJS[@]} ${OS_OBJS[@]} ${APP_OBJS[@]} ${MAIN_OBJS[@]}"
166 | BUILD_KEY="st_hls_load" APP_MAIN="htl_main_hls_load" APP_NAME="st_hls_load" LINK_OPTIONS="-ldl" SO_PATH="" . auto/apps.sh
167 |
168 | echo 'configure ok! '
169 |
170 | # next step.
171 | echo "you can:"
172 | echo "\" make \" to build the http/hls load test tools."
173 | echo "\" make help \" to get the usage of make"
174 |
--------------------------------------------------------------------------------
/src/app/htl_app_hls_load.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 | using namespace std;
10 |
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #include
17 |
18 | #include
19 |
20 | #define DEFAULT_TS_DURATION 10
21 |
22 | StHlsTask::StHlsTask(){
23 | target_duration = DEFAULT_TS_DURATION;
24 | }
25 |
26 | StHlsTask::~StHlsTask(){
27 | }
28 |
29 | int StHlsTask::Initialize(std::string http_url, bool vod, double startup, double delay, double error, int count){
30 | int ret = ERROR_SUCCESS;
31 |
32 | is_vod = vod;
33 |
34 | if((ret = InitializeBase(http_url, startup, delay, error, count)) != ERROR_SUCCESS){
35 | return ret;
36 | }
37 |
38 | return ret;
39 | }
40 |
41 | Uri* StHlsTask::GetUri(){
42 | return &url;
43 | }
44 |
45 | int StHlsTask::ProcessTask(){
46 | int ret = ERROR_SUCCESS;
47 |
48 | Trace("start to process HLS task #%d, schema=%s, host=%s, port=%d, path=%s, startup=%.2f, delay=%.2f, error=%.2f, count=%d",
49 | GetId(), url.GetSchema(), url.GetHost(), url.GetPort(), url.GetPath(), startup_seconds, delay_seconds, error_seconds, count);
50 |
51 | StHttpClient client;
52 |
53 | // if count is zero, infinity loop.
54 | for(int i = 0; count == 0 || i < count; i++){
55 | statistic->OnTaskStart(GetId(), url.GetUrl());
56 |
57 | if((ret = ProcessM3u8(client)) != ERROR_SUCCESS){
58 | statistic->OnTaskError(GetId(), 0);
59 |
60 | Error("http client process m3u8 failed. ret=%d", ret);
61 | st_usleep((st_utime_t)(error_seconds * 1000 * 1000));
62 | continue;
63 | }
64 |
65 | Info("[HLS] %s download completed.", url.GetUrl());
66 | }
67 |
68 | return ret;
69 | }
70 |
71 | int StHlsTask::ProcessM3u8(StHttpClient& client){
72 | int ret = ERROR_SUCCESS;
73 |
74 | string m3u8;
75 | if((ret = client.DownloadString(&url, &m3u8)) != ERROR_SUCCESS){
76 | Error("http client get m3u8 failed. ret=%d", ret);
77 | return ret;
78 | }
79 | Trace("[HLS] get m3u8 %s get success, length=%"PRId64, url.GetUrl(), (int64_t)m3u8.length());
80 |
81 | vector ts_objects;
82 | if((ret = HlsM3u8Parser::ParseM3u8Data(&url, m3u8, ts_objects, target_duration)) != ERROR_SUCCESS){
83 | Error("http client parse m3u8 content failed. ret=%d", ret);
84 | return ret;
85 | }
86 |
87 | if((ret = ProcessTS(client, ts_objects)) != ERROR_SUCCESS){
88 | Error("http client download m3u8 ts file failed. ret=%d", ret);
89 | return ret;
90 | }
91 |
92 | return ret;
93 | }
94 |
95 | int StHlsTask::ProcessTS(StHttpClient& client, vector& ts_objects){
96 | int ret = ERROR_SUCCESS;
97 |
98 | vector::iterator ite = ts_objects.begin();
99 |
100 | // if live(not vod), remember the last download ts object.
101 | // if vod(not live), always access from the frist ts.
102 | if(!is_vod){
103 | ite = find(ts_objects.begin(), ts_objects.end(), last_downloaded_ts);
104 |
105 | // not found, reset to begin to process all.
106 | if(ite == ts_objects.end()){
107 | ite = ts_objects.begin();
108 | }
109 | // fount, skip it.
110 | else{
111 | ite++;
112 | }
113 |
114 | // no ts now, wait for a segment
115 | if(ite == ts_objects.end()){
116 | int sleep_ms = StUtility::BuildRandomMTime((target_duration > 0)? target_duration:DEFAULT_TS_DURATION);
117 | Trace("[TS] no fresh ts, wait for a while. sleep %dms", sleep_ms);
118 | st_usleep(sleep_ms * 1000);
119 |
120 | return ret;
121 | }
122 | }
123 |
124 | AggregateRet aggregate_ret;
125 |
126 | // to process from the specified ite
127 | for(; ite != ts_objects.end(); ++ite){
128 | M3u8TS ts_object = *ite;
129 |
130 | if(!is_vod){
131 | last_downloaded_ts = ts_object;
132 | }
133 |
134 | Info("start to process ts %s", ts_object.ts_url.c_str());
135 |
136 | aggregate_ret.Add(DownloadTS(client, ts_object));
137 | }
138 |
139 | return aggregate_ret.GetReturnValue();
140 | }
141 |
142 | int StHlsTask::DownloadTS(StHttpClient& client, M3u8TS& ts){
143 | int ret = ERROR_SUCCESS;
144 |
145 | HttpUrl url;
146 |
147 | if((ret = url.Initialize(ts.ts_url)) != ERROR_SUCCESS){
148 | Error("initialize ts url failed. ret=%d", ret);
149 | return ret;
150 | }
151 |
152 | Info("[TS] url=%s, duration=%.2f, delay=%.2f", url.GetUrl(), ts.duration, delay_seconds);
153 | statistic->OnSubTaskStart(GetId(), ts.ts_url);
154 |
155 | if((ret = client.DownloadString(&url, NULL)) != ERROR_SUCCESS){
156 | statistic->OnSubTaskError(GetId(), (int)ts.duration);
157 |
158 | Error("http client download ts file %s failed. ret=%d", url.GetUrl(), ret);
159 | return ret;
160 | }
161 |
162 | int sleep_ms = StUtility::BuildRandomMTime((delay_seconds >= 0)? delay_seconds:ts.duration);
163 | Trace("[TS] url=%s download, duration=%.2f, delay=%.2f, size=%"PRId64", sleep %dms",
164 | url.GetUrl(), ts.duration, delay_seconds, client.GetResponseHeader()->content_length, sleep_ms);
165 | st_usleep(sleep_ms * 1000);
166 |
167 | statistic->OnSubTaskEnd(GetId(), (int)ts.duration);
168 |
169 | return ret;
170 | }
171 |
172 |
--------------------------------------------------------------------------------
/src/app/htl_app_hls_load.hpp:
--------------------------------------------------------------------------------
1 | #ifndef _htl_app_hls_load_hpp
2 | #define _htl_app_hls_load_hpp
3 |
4 | /*
5 | #include
6 | */
7 | #include
8 | #include
9 | #include
10 |
11 | // for http task.
12 | class StHlsTask : public StBaseTask
13 | {
14 | private:
15 | HttpUrl url;
16 | // the last downloaded ts url to prevent download multile times.
17 | M3u8TS last_downloaded_ts;
18 | int target_duration;
19 | bool is_vod;
20 | public:
21 | StHlsTask();
22 | virtual ~StHlsTask();
23 | public:
24 | virtual int Initialize(std::string http_url, bool vod, double startup, double delay, double error, int count);
25 | protected:
26 | virtual Uri* GetUri();
27 | virtual int ProcessTask();
28 | private:
29 | virtual int ProcessM3u8(StHttpClient& client);
30 | virtual int ProcessTS(StHttpClient& client, std::vector& ts_objects);
31 | virtual int DownloadTS(StHttpClient& client, M3u8TS& ts);
32 | };
33 |
34 | #endif
35 |
--------------------------------------------------------------------------------
/src/app/htl_app_http_client.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 | using namespace std;
9 |
10 | #include
11 | #include
12 |
13 | #include
14 |
15 | StHttpClient::StHttpClient(){
16 | socket = new StSocket();
17 | connected_url = NULL;
18 | }
19 |
20 | StHttpClient::~StHttpClient(){
21 | delete socket;
22 | socket = NULL;
23 |
24 | delete connected_url;
25 | connected_url = NULL;
26 | }
27 |
28 | int StHttpClient::DownloadString(HttpUrl* url, std::string* response){
29 | int ret = ERROR_SUCCESS;
30 |
31 | if((ret = CheckUrl(url)) != ERROR_SUCCESS){
32 | Error("http client check url failed. ret=%d", ret);
33 | return ret;
34 | }
35 |
36 | if((ret = Connect(url)) != ERROR_SUCCESS){
37 | Error("http client connect url failed. ret=%d", ret);
38 | return ret;
39 | }
40 |
41 | // send GET request to read content
42 | // GET %s HTTP/1.1\r\nHost: %s\r\n\r\n
43 | stringstream ss;
44 | ss << "GET " << url->GetPath() << " "
45 | << "HTTP/1.1\r\n"
46 | << "Host: " << url->GetHost() << "\r\n"
47 | << "Connection: Keep-Alive" << "\r\n"
48 | << "User-Agent: " << ProductHTTPName << "\r\n"
49 | << "\r\n";
50 |
51 | ssize_t nwrite;
52 | if((ret = socket->Write(ss.str().c_str(), ss.str().length(), &nwrite)) != ERROR_SUCCESS){
53 | Error("write to server failed. ret=%d", ret);
54 | return ret;
55 | }
56 |
57 | if((ret = ParseResponse(url, response)) != ERROR_SUCCESS){
58 | Error("http client parse response failed. ret=%d", ret);
59 | return ret;
60 | }
61 |
62 | return ret;
63 | }
64 |
65 | http_parser* StHttpClient::GetResponseHeader(){
66 | return &http_header;
67 | }
68 |
69 | int StHttpClient::on_headers_complete(http_parser* parser){
70 | StHttpClient* obj = (StHttpClient*)parser->data;
71 | obj->OnHeaderCompleted(parser);
72 |
73 | // see http_parser.c:1570, return 1 to skip body.
74 | return 1;
75 | }
76 |
77 | void StHttpClient::OnHeaderCompleted(http_parser* parser){
78 | // save the parser status when header parse completed.
79 | memcpy(&http_header, parser, sizeof(http_header));
80 | }
81 |
82 | int StHttpClient::ParseResponse(HttpUrl* url, string* response){
83 | int ret = ERROR_SUCCESS;
84 |
85 | int body_received = 0;
86 | if((ret = ParseResponseHeader(response, body_received)) != ERROR_SUCCESS){
87 | Error("parse response header failed. ret=%d", ret);
88 | return ret;
89 | }
90 |
91 | if((ret = ParseResponseBody(url, response, body_received)) != ERROR_SUCCESS){
92 | Error("parse response body failed. ret=%d", ret);
93 | return ret;
94 | }
95 |
96 | Info("url %s download, body size=%"PRId64, url->GetUrl(), http_header.content_length);
97 |
98 | return ret;
99 | }
100 |
101 | int StHttpClient::ParseResponseBody(HttpUrl* url, string* response, int body_received){
102 | int ret = ERROR_SUCCESS;
103 |
104 | assert(url != NULL);
105 |
106 | uint64_t body_left = http_header.content_length - body_received;
107 |
108 | if(response != NULL){
109 | char buf[HTTP_BODY_BUFFER];
110 | return ParseResponseBodyData(url, response, (size_t)body_left, (const void*)buf, (size_t)HTTP_BODY_BUFFER);
111 | }
112 | else{
113 | // if ignore response, use shared fast memory.
114 | static char buf[HTTP_BODY_BUFFER];
115 | return ParseResponseBodyData(url, response, (size_t)body_left, (const void*)buf, (size_t)HTTP_BODY_BUFFER);
116 | }
117 |
118 | return ret;
119 | }
120 |
121 | int StHttpClient::ParseResponseBodyData(HttpUrl* url, string* response, size_t body_left, const void* buf, size_t size){
122 | int ret = ERROR_SUCCESS;
123 |
124 | assert(url != NULL);
125 |
126 | while(body_left > 0){
127 | ssize_t nread;
128 | if((ret = socket->Read(buf, (size < body_left)? size:body_left, &nread)) != ERROR_SUCCESS){
129 | Error("read header from server failed. ret=%d", ret);
130 | return ret;
131 | }
132 |
133 | if(response != NULL && nread > 0){
134 | response->append((char*)buf, nread);
135 | }
136 |
137 | body_left -= nread;
138 | Info("read url(%s) content partial %"PRId64"/%"PRId64"",
139 | url->GetUrl(), http_header.content_length - body_left, http_header.content_length);
140 | }
141 |
142 | return ret;
143 | }
144 |
145 | int StHttpClient::ParseResponseHeader(string* response, int& body_received){
146 | int ret = ERROR_SUCCESS;
147 |
148 | http_parser_settings settings;
149 |
150 | memset(&settings, 0, sizeof(settings));
151 | settings.on_headers_complete = on_headers_complete;
152 |
153 | http_parser parser;
154 | http_parser_init(&parser, HTTP_RESPONSE);
155 | // callback object ptr.
156 | parser.data = (void*)this;
157 |
158 | // reset response header.
159 | memset(&http_header, 0, sizeof(http_header));
160 |
161 | // parser header.
162 | char buf[HTTP_HEADER_BUFFER];
163 | for(;;){
164 | ssize_t nread;
165 | if((ret = socket->Read((const void*)buf, (size_t)sizeof(buf), &nread)) != ERROR_SUCCESS){
166 | Error("read body from server failed. ret=%d", ret);
167 | return ret;
168 | }
169 |
170 | ssize_t nparsed = http_parser_execute(&parser, &settings, buf, nread);
171 | Info("read_size=%d, nparsed=%d", (int)nread, (int)nparsed);
172 |
173 | // check header size.
174 | if(http_header.nread != 0){
175 | body_received = nread - nparsed;
176 |
177 | Info("http header parsed, size=%d, content-length=%"PRId64", body-received=%d",
178 | http_header.nread, http_header.content_length, body_received);
179 |
180 | if(response != NULL && body_received > 0){
181 | response->append(buf + nparsed, body_received);
182 | }
183 |
184 | return ret;
185 | }
186 |
187 | if(nparsed != nread){
188 | ret = ERROR_HP_PARSE_RESPONSE;
189 | Error("parse response error, parsed(%d)!=read(%d), ret=%d", (int)nparsed, (int)nread, ret);
190 | return ret;
191 | }
192 | }
193 |
194 | return ret;
195 | }
196 |
197 | int StHttpClient::Connect(HttpUrl* url){
198 | int ret = ERROR_SUCCESS;
199 |
200 | if(socket->Status() == SocketConnected){
201 | return ret;
202 | }
203 |
204 | string ip;
205 | if((ret = StUtility::DnsResolve(url->GetHost(), ip)) != ERROR_SUCCESS){
206 | Error("dns resolve failed. ret=%d", ret);
207 | return ret;
208 | }
209 |
210 | if((ret = socket->Connect(ip.c_str(), url->GetPort())) != ERROR_SUCCESS){
211 | Error("connect to server failed. ret=%d", ret);
212 | return ret;
213 | }
214 |
215 | Info("socket connected on url %s", url->GetUrl());
216 |
217 | return ret;
218 | }
219 |
220 | int StHttpClient::CheckUrl(HttpUrl* url){
221 | int ret = ERROR_SUCCESS;
222 |
223 | if(connected_url == NULL){
224 | connected_url = url->Copy();
225 |
226 | if((ret = StUtility::DnsResolve(connected_url->GetHost(), connected_ip)) != ERROR_SUCCESS){
227 | return ret;
228 | }
229 | }
230 |
231 | string ip;
232 | if((ret = StUtility::DnsResolve(url->GetHost(), ip)) != ERROR_SUCCESS){
233 | return ret;
234 | }
235 |
236 | if(ip != connected_ip || connected_url->GetPort() != url->GetPort()){
237 | ret = ERROR_HP_EP_CHNAGED;
238 | Error("invalid url=%s, endpoint change from %s:%d to %s:%d",
239 | url->GetUrl(), connected_ip.c_str(), connected_url->GetPort(), ip.c_str(), url->GetPort());
240 |
241 | return ret;
242 | }
243 |
244 | return ret;
245 | }
246 |
247 |
--------------------------------------------------------------------------------
/src/app/htl_app_http_client.hpp:
--------------------------------------------------------------------------------
1 | #ifndef _htl_app_http_client_hpp
2 | #define _htl_app_http_client_hpp
3 |
4 | /*
5 | #include
6 | */
7 | #include
8 |
9 | #include
10 | #include
11 |
12 | class StHttpClient
13 | {
14 | private:
15 | std::string connected_ip;
16 | HttpUrl* connected_url;
17 | StSocket* socket;
18 | private:
19 | http_parser http_header;
20 | public:
21 | StHttpClient();
22 | virtual ~StHttpClient();
23 | public:
24 | /**
25 | * download the content specified by url using HTTP GET method.
26 | * @response the string pointer which store the content. ignore content if set to NULL.
27 | */
28 | virtual int DownloadString(HttpUrl* url, std::string* response);
29 | public:
30 | /**
31 | * when get response and parse header completed, return the header.
32 | * if not parsed, header set to zero.
33 | */
34 | virtual http_parser* GetResponseHeader();
35 | private:
36 | static int on_headers_complete(http_parser* parser);
37 | virtual void OnHeaderCompleted(http_parser* parser);
38 | private:
39 | virtual int ParseResponse(HttpUrl* url, string* response);
40 | virtual int ParseResponseBody(HttpUrl* url, string* response, int body_received);
41 | virtual int ParseResponseBodyData(HttpUrl* url, string* response, size_t body_left, const void* buf, size_t size);
42 | virtual int ParseResponseHeader(string* response, int& body_received);
43 | virtual int Connect(HttpUrl* url);
44 | virtual int CheckUrl(HttpUrl* url);
45 | };
46 |
47 | #endif
48 |
--------------------------------------------------------------------------------
/src/app/htl_app_http_load.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 | using namespace std;
9 |
10 | #include
11 | #include
12 | #include
13 |
14 | #include
15 |
16 | StHttpTask::StHttpTask(){
17 | }
18 |
19 | StHttpTask::~StHttpTask(){
20 | }
21 |
22 | int StHttpTask::Initialize(std::string http_url, double startup, double delay, double error, int count){
23 | int ret = ERROR_SUCCESS;
24 |
25 | if((ret = InitializeBase(http_url, startup, delay, error, count)) != ERROR_SUCCESS){
26 | return ret;
27 | }
28 |
29 | return ret;
30 | }
31 |
32 | Uri* StHttpTask::GetUri(){
33 | return &url;
34 | }
35 |
36 | int StHttpTask::ProcessTask(){
37 | int ret = ERROR_SUCCESS;
38 |
39 | Trace("start to process HTTP task #%d, schema=%s, host=%s, port=%d, path=%s, startup=%.2f, delay=%.2f, error=%.2f, count=%d",
40 | GetId(), url.GetSchema(), url.GetHost(), url.GetPort(), url.GetPath(), startup_seconds, delay_seconds, error_seconds, count);
41 |
42 | StHttpClient client;
43 |
44 | // if count is zero, infinity loop.
45 | for(int i = 0; count == 0 || i < count; i++){
46 | statistic->OnTaskStart(GetId(), url.GetUrl());
47 |
48 | if((ret = client.DownloadString(&url, NULL)) != ERROR_SUCCESS){
49 | statistic->OnTaskError(GetId(), 0);
50 |
51 | Error("http client get url failed. ret=%d", ret);
52 | st_usleep((st_utime_t)(error_seconds * 1000 * 1000));
53 | continue;
54 | }
55 |
56 | int sleep_ms = StUtility::BuildRandomMTime((delay_seconds >= 0)? delay_seconds:0);
57 | Trace("[HTTP] %s download, size=%"PRId64", sleep %dms", url.GetUrl(), client.GetResponseHeader()->content_length, sleep_ms);
58 | st_usleep(sleep_ms * 1000);
59 |
60 | statistic->OnTaskEnd(GetId(), 0);
61 | }
62 |
63 | return ret;
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/src/app/htl_app_http_load.hpp:
--------------------------------------------------------------------------------
1 | #ifndef _htl_app_http_load_hpp
2 | #define _htl_app_http_load_hpp
3 |
4 | /*
5 | #include
6 | */
7 | #include
8 |
9 | // for http task.
10 | class StHttpTask : public StBaseTask
11 | {
12 | private:
13 | HttpUrl url;
14 | public:
15 | StHttpTask();
16 | virtual ~StHttpTask();
17 | public:
18 | virtual int Initialize(std::string http_url, double startup, double delay, double error, int count);
19 | protected:
20 | virtual Uri* GetUri();
21 | virtual int ProcessTask();
22 | };
23 |
24 | #endif
25 |
--------------------------------------------------------------------------------
/src/app/htl_app_m3u8_parser.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 | using namespace std;
10 |
11 | #include
12 | #include
13 | #include
14 |
15 | #include
16 |
17 | class String
18 | {
19 | private:
20 | string value;
21 | public:
22 | String(string str = ""){
23 | value = str;
24 | }
25 | public:
26 | String& set_str(string str){
27 | value = str;
28 | return *this;
29 | }
30 | int length(){
31 | return (int)value.length();
32 | }
33 | bool startswith(string key, String* left = NULL){
34 | size_t pos = value.find(key);
35 |
36 | if(pos == 0 && left != NULL){
37 | left->set_str(value.substr(pos + key.length()));
38 | left->strip();
39 | }
40 |
41 | return pos == 0;
42 | }
43 | bool endswith(string key, String* left = NULL){
44 | size_t pos = value.rfind(key);
45 |
46 | if(pos == value.length() - key.length() && left != NULL){
47 | left->set_str(value.substr(pos));
48 | left->strip();
49 | }
50 |
51 | return pos == value.length() - key.length();
52 | }
53 | String& strip(){
54 | while(value.length() > 0){
55 | if(startswith("\n")){
56 | value = value.substr(1);
57 | continue;
58 | }
59 |
60 | if(startswith("\r")){
61 | value = value.substr(1);
62 | continue;
63 | }
64 |
65 | if(startswith(" ")){
66 | value = value.substr(1);
67 | continue;
68 | }
69 |
70 | if(endswith("\n")){
71 | value = value.substr(0, value.length() - 1);
72 | continue;
73 | }
74 |
75 | if(endswith("\r")){
76 | value = value.substr(0, value.length() - 1);
77 | continue;
78 | }
79 |
80 | if(endswith(" ")){
81 | value = value.substr(0, value.length() - 1);
82 | continue;
83 | }
84 |
85 | break;
86 | }
87 |
88 | return *this;
89 | }
90 | string getline(){
91 | size_t pos = string::npos;
92 |
93 | if((pos = value.find("\n")) != string::npos){
94 | return value.substr(0, pos);
95 | }
96 |
97 | return value;
98 | }
99 | String& remove(int size){
100 | if(size >= (int)value.length()){
101 | value = "";
102 | return *this;
103 | }
104 |
105 | value = value.substr(size);
106 | return *this;
107 | }
108 | const char* c_str(){
109 | return value.c_str();
110 | }
111 | };
112 |
113 | HlsM3u8Parser::HlsM3u8Parser(){
114 | }
115 |
116 | HlsM3u8Parser::~HlsM3u8Parser(){
117 | }
118 |
119 | int HlsM3u8Parser::ParseM3u8Data(HttpUrl* url, string m3u8, vector& ts_objects, int& target_duration){
120 | int ret = ERROR_SUCCESS;
121 |
122 | String data(m3u8);
123 |
124 | // http://tools.ietf.org/html/draft-pantos-http-live-streaming-08#section-3.3.1
125 | // An Extended M3U file is distinguished from a basic M3U file by its
126 | // first line, which MUST be the tag #EXTM3U.
127 | if(!data.startswith("#EXTM3U")){
128 | ret = ERROR_HLS_INVALID;
129 | Error("invalid hls, #EXTM3U not found. ret=%d", ret);
130 | return ret;
131 | }
132 |
133 | String value;
134 |
135 | M3u8TS ts_object;
136 | while(data.length() > 0){
137 | String line;
138 | data.remove(line.set_str(data.strip().getline()).strip().length()).strip();
139 |
140 | // http://tools.ietf.org/html/draft-pantos-http-live-streaming-08#section-3.4.2
141 | // #EXT-X-TARGETDURATION:
142 | // where s is an integer indicating the target duration in seconds.
143 | if(line.startswith("#EXT-X-TARGETDURATION:", &value)){
144 | target_duration = atoi(value.c_str());
145 | ts_object.duration = target_duration;
146 | continue;
147 | }
148 |
149 | // http://tools.ietf.org/html/draft-pantos-http-live-streaming-08#section-3.3.2
150 | // #EXTINF:,
151 | // "duration" is an integer or floating-point number in decimal
152 | // positional notation that specifies the duration of the media segment
153 | // in seconds. Durations that are reported as integers SHOULD be
154 | // rounded to the nearest integer. Durations MUST be integers if the
155 | // protocol version of the Playlist file is less than 3. The remainder
156 | // of the line following the comma is an optional human-readable
157 | // informative title of the media segment.
158 | // ignore others util EXTINF
159 | if(line.startswith("#EXTINF:", &value)){
160 | ts_object.duration = atof(value.c_str());
161 | continue;
162 | }
163 |
164 | if(!line.startswith("#")){
165 | ts_object.ts_url = url->Resolve(line.c_str());
166 | ts_objects.push_back(ts_object);
167 | continue;
168 | }
169 | }
170 |
171 | return ret;
172 | }
173 |
174 |
--------------------------------------------------------------------------------
/src/app/htl_app_m3u8_parser.hpp:
--------------------------------------------------------------------------------
1 | #ifndef _htl_app_m3u8_parser_hpp
2 | #define _htl_app_m3u8_parser_hpp
3 |
4 | /*
5 | #include
6 | */
7 | #include
8 | #include
9 |
10 | #include
11 |
12 | struct M3u8TS
13 | {
14 | std::string ts_url;
15 | double duration;
16 |
17 | bool operator== (const M3u8TS& b)const{
18 | return ts_url == b.ts_url;
19 | }
20 | };
21 |
22 | class HlsM3u8Parser
23 | {
24 | public:
25 | HlsM3u8Parser();
26 | virtual ~HlsM3u8Parser();
27 | public:
28 | static int ParseM3u8Data(HttpUrl* url, std::string m3u8, std::vector& ts_objects, int& target_duration);
29 | };
30 |
31 | #endif
32 |
--------------------------------------------------------------------------------
/src/app/htl_app_rtmp_client.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 | using namespace std;
9 |
10 | #include
11 | #include
12 |
13 | #include
14 | #include
15 |
16 | StRtmpClient::StRtmpClient(){
17 | socket = new StSocket();
18 | stream_id = 0;
19 | srs = NULL;
20 | }
21 |
22 | StRtmpClient::~StRtmpClient(){
23 | delete socket;
24 | delete srs;
25 | socket = NULL;
26 | }
27 |
28 | int StRtmpClient::Dump(RtmpUrl* url){
29 | int ret = ERROR_SUCCESS;
30 |
31 | if((ret = Connect(url)) != ERROR_SUCCESS){
32 | Error("rtmp client connect server failed. ret=%d", ret);
33 | return ret;
34 | }
35 |
36 | if((ret = Handshake()) != ERROR_SUCCESS){
37 | Error("rtmp client handshake failed. ret=%d", ret);
38 | return ret;
39 | }
40 | Info("rtmp client handshake success");
41 |
42 | if((ret = ConnectApp(url)) != ERROR_SUCCESS){
43 | Error("rtmp client connect tcUrl failed. ret=%d", ret);
44 | return ret;
45 | }
46 | Info("rtmp client connect tcUrl(%s) success", url->GetTcUrl());
47 |
48 | if((ret = CreateStream()) != ERROR_SUCCESS){
49 | Error("rtmp client create stream failed. ret=%d", ret);
50 | return ret;
51 | }
52 | Info("rtmp client create stream(%d) success", stream_id);
53 |
54 | if((ret = PlayStram(url)) != ERROR_SUCCESS){
55 | Error("rtmp client play stream failed. ret=%d", ret);
56 | return ret;
57 | }
58 | Info("rtmp client play stream(%s) success", url->GetUrl());
59 |
60 | if((ret = DumpAV()) != ERROR_SUCCESS){
61 | Error("rtmp client dump av failed. ret=%d", ret);
62 | return ret;
63 | }
64 | Info("rtmp client dump av success");
65 |
66 | return ret;
67 | }
68 |
69 | int StRtmpClient::Connect(RtmpUrl* url){
70 | int ret = ERROR_SUCCESS;
71 |
72 | if(socket->Status() == SocketConnected){
73 | return ret;
74 | }
75 |
76 | string ip;
77 | if((ret = StUtility::DnsResolve(url->GetHost(), ip)) != ERROR_SUCCESS){
78 | Error("dns resolve failed. ret=%d", ret);
79 | return ret;
80 | }
81 |
82 | if((ret = socket->Connect(ip.c_str(), url->GetPort())) != ERROR_SUCCESS){
83 | Error("connect to server failed. ret=%d", ret);
84 | return ret;
85 | }
86 |
87 | Info("socket connected on url %s", url->GetUrl());
88 |
89 | delete srs;
90 | srs = new SrsRtmpClient(socket->GetStfd());
91 |
92 | return ret;
93 | }
94 |
95 | int StRtmpClient::Handshake(){
96 | return srs->handshake();
97 | }
98 |
99 | int StRtmpClient::ConnectApp(RtmpUrl* url){
100 | return srs->connect_app(url->GetApp(), url->GetTcUrl());
101 | }
102 |
103 | int StRtmpClient::CreateStream(){
104 | return srs->create_stream(stream_id);
105 | }
106 |
107 | int StRtmpClient::PlayStram(RtmpUrl* url){
108 | return srs->play(url->GetStream(), stream_id);
109 | }
110 |
111 | int StRtmpClient::DumpAV(){
112 | int ret = ERROR_SUCCESS;
113 |
114 | // recv response
115 | while(true){
116 | SrsCommonMessage* msg = NULL;
117 | if ((ret = srs->recv_message(&msg)) != ERROR_SUCCESS){
118 | return ret;
119 | }
120 | SrsAutoFree(SrsCommonMessage, msg, false);
121 |
122 | Info("get message type=%d, size=%d",
123 | msg->header.message_type, msg->header.payload_length);
124 | }
125 |
126 | return ret;
127 | }
128 |
--------------------------------------------------------------------------------
/src/app/htl_app_rtmp_client.hpp:
--------------------------------------------------------------------------------
1 | #ifndef _htl_app_rtmp_client_hpp
2 | #define _htl_app_rtmp_client_hpp
3 |
4 | /*
5 | #include
6 | */
7 | #include
8 |
9 | #include
10 | #include
11 |
12 | class SrsRtmpClient;
13 |
14 | class StRtmpClient
15 | {
16 | private:
17 | SrsRtmpClient* srs;
18 | StSocket* socket;
19 | int stream_id;
20 | public:
21 | StRtmpClient();
22 | virtual ~StRtmpClient();
23 | public:
24 | virtual int Dump(RtmpUrl* url);
25 | private:
26 | virtual int Connect(RtmpUrl* url);
27 | virtual int Handshake();
28 | virtual int ConnectApp(RtmpUrl* url);
29 | virtual int CreateStream();
30 | virtual int PlayStram(RtmpUrl* url);
31 | virtual int DumpAV();
32 | };
33 |
34 | #endif
35 |
--------------------------------------------------------------------------------
/src/app/htl_app_rtmp_load.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 | using namespace std;
9 |
10 | #include
11 | #include
12 | #include
13 |
14 | #include
15 |
16 | StRtmpTask::StRtmpTask(){
17 | }
18 |
19 | StRtmpTask::~StRtmpTask(){
20 | }
21 |
22 | int StRtmpTask::Initialize(std::string http_url, double startup, double delay, double error, int count){
23 | int ret = ERROR_SUCCESS;
24 |
25 | if((ret = InitializeBase(http_url, startup, delay, error, count)) != ERROR_SUCCESS){
26 | return ret;
27 | }
28 |
29 | return ret;
30 | }
31 |
32 | Uri* StRtmpTask::GetUri(){
33 | return &url;
34 | }
35 |
36 | int StRtmpTask::ProcessTask(){
37 | int ret = ERROR_SUCCESS;
38 |
39 | Trace("start to process RTMP task #%d, schema=%s, host=%s, port=%d, tcUrl=%s, stream=%s, startup=%.2f, delay=%.2f, error=%.2f, count=%d",
40 | GetId(), url.GetSchema(), url.GetHost(), url.GetPort(), url.GetTcUrl(), url.GetStream(), startup_seconds, delay_seconds, error_seconds, count);
41 |
42 | StRtmpClient client;
43 |
44 | // if count is zero, infinity loop.
45 | for(int i = 0; count == 0 || i < count; i++){
46 | statistic->OnTaskStart(GetId(), url.GetUrl());
47 |
48 | if((ret = client.Dump(&url)) != ERROR_SUCCESS){
49 | statistic->OnTaskError(GetId(), 0);
50 |
51 | Error("rtmp client dump url failed. ret=%d", ret);
52 | st_usleep((st_utime_t)(error_seconds * 1000 * 1000));
53 | continue;
54 | }
55 |
56 | int sleep_ms = StUtility::BuildRandomMTime((delay_seconds >= 0)? delay_seconds:0);
57 | Trace("[RTMP] %s dump success, sleep %dms", url.GetUrl(), sleep_ms);
58 | st_usleep(sleep_ms * 1000);
59 |
60 | statistic->OnTaskEnd(GetId(), 0);
61 | }
62 |
63 | return ret;
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/src/app/htl_app_rtmp_load.hpp:
--------------------------------------------------------------------------------
1 | #ifndef _htl_app_rtmp_load_hpp
2 | #define _htl_app_rtmp_load_hpp
3 |
4 | /*
5 | #include
6 | */
7 | #include
8 |
9 | // for rtmp task.
10 | class StRtmpTask : public StBaseTask
11 | {
12 | private:
13 | RtmpUrl url;
14 | public:
15 | StRtmpTask();
16 | virtual ~StRtmpTask();
17 | public:
18 | virtual int Initialize(std::string http_url, double startup, double delay, double error, int count);
19 | protected:
20 | virtual Uri* GetUri();
21 | virtual int ProcessTask();
22 | };
23 |
24 | #endif
25 |
--------------------------------------------------------------------------------
/src/app/htl_app_rtmp_protocol.hpp:
--------------------------------------------------------------------------------
1 | #ifndef _htl_app_rtmp_protocol_hpp
2 | #define _htl_app_rtmp_protocol_hpp
3 |
4 | /*
5 | #include
6 | */
7 |
8 | #include
9 | #include
10 | #include