增加单元测试及基准测试
This commit is contained in:
parent
4efa1b36b2
commit
de4f5c0c22
|
|
@ -84,3 +84,4 @@ target
|
||||||
|
|
||||||
#erlang
|
#erlang
|
||||||
/binding/erlang/_build
|
/binding/erlang/_build
|
||||||
|
/binding/erlang/doc
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,78 @@ $ rebar3 shell
|
||||||
region => <<>>}
|
region => <<>>}
|
||||||
2>
|
2>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
* 在rebar.config中引入依赖
|
||||||
|
```
|
||||||
|
{deps, [
|
||||||
|
ip2region
|
||||||
|
]}.
|
||||||
|
|
||||||
|
```
|
||||||
|
* 启动ip2region Application
|
||||||
|
```
|
||||||
|
......
|
||||||
|
|
||||||
|
application:ensure_started(ip2region),
|
||||||
|
|
||||||
|
......
|
||||||
|
```
|
||||||
|
|
||||||
|
* 调用ip2region:search/1接口查询IP信息
|
||||||
|
```
|
||||||
|
......
|
||||||
|
|
||||||
|
ip2region:search("1.0.8.0"),
|
||||||
|
|
||||||
|
......
|
||||||
|
```
|
||||||
|
|
||||||
|
### 单元测试
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rebar3 eunit
|
||||||
|
===> Verifying dependencies...
|
||||||
|
===> Analyzing applications...
|
||||||
|
===> Compiling ip2region
|
||||||
|
===> Performing EUnit tests...
|
||||||
|
=INFO REPORT==== 13-Jan-2023::21:26:15.137021 ===
|
||||||
|
XdbFile:/home/admin/erl-workspace/ip2region/binding/erlang/_build/test/lib/ip2region/priv/ip2region.xdb
|
||||||
|
...
|
||||||
|
Finished in 0.085 seconds
|
||||||
|
3 tests, 0 failures
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 基准测试
|
||||||
|
```
|
||||||
|
$ cd benchmarks/
|
||||||
|
$ sh ip2region-benchmark.sh
|
||||||
|
CPU info:
|
||||||
|
model name : AMD EPYC 7K62 48-Core Processor
|
||||||
|
cache size : 512 KB
|
||||||
|
cpu MHz : 2595.124
|
||||||
|
bogomips : 5190.24
|
||||||
|
cores/threads : 2
|
||||||
|
|
||||||
|
Erlang info:
|
||||||
|
system_version:Erlang/OTP 24 [erts-12.3.2.2] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit]
|
||||||
|
load test data use 2.724344s
|
||||||
|
|
||||||
|
start run benchmark tests
|
||||||
|
|
||||||
|
search from file:
|
||||||
|
ip count:683844,
|
||||||
|
total time: 25.56304s,
|
||||||
|
search 26751.27840820184 times per second,
|
||||||
|
use 37.381391077497206 micro second per search
|
||||||
|
|
||||||
|
search from cache:
|
||||||
|
ip count:683844,
|
||||||
|
total time: 0.670307s,
|
||||||
|
search 1020195.2239794602 times per second,
|
||||||
|
use 0.9802045495756342 micro second per search
|
||||||
|
|
||||||
|
benchmark test finish
|
||||||
|
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
awk -v FS='|' '{print $1}' ../../../data/ip.merge.txt > test_data.txt
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
rebar3 shell --eval="ip2region_benchmark:main(\"./benchmarks/test_data.txt\"), init:stop()."
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,13 @@
|
||||||
{erl_opts, [debug_info, export_all, nowarn_export_all]}.
|
{erl_opts, [
|
||||||
|
debug_info,
|
||||||
|
export_all,
|
||||||
|
nowarn_export_all
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{plugins, [rebar3_hex, rebar3_ex_doc]}.
|
||||||
|
|
||||||
{deps, [
|
{deps, [
|
||||||
poolboy
|
poolboy
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{shell, [
|
{shell, [
|
||||||
|
|
@ -8,3 +15,10 @@
|
||||||
{apps, [ip2region]}
|
{apps, [ip2region]}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
{ex_doc, [
|
||||||
|
{extras, ["README.md"]},
|
||||||
|
{main, "README.md"},
|
||||||
|
{source_url, "https://github.com/leihua996/ip2region/tree/master/binding/erlang"}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{hex, [{doc, ex_doc}]}.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{application, ip2region,
|
{application, ip2region,
|
||||||
[{description, "An OTP application"},
|
[{description, "ip2region xdb client application"},
|
||||||
{vsn, "0.1.0"},
|
{vsn, "0.1.0"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{mod, {ip2region_app, []}},
|
{mod, {ip2region_app, []}},
|
||||||
|
|
@ -16,5 +16,5 @@
|
||||||
{modules, []},
|
{modules, []},
|
||||||
|
|
||||||
{licenses, ["Apache-2.0"]},
|
{licenses, ["Apache-2.0"]},
|
||||||
{links, []}
|
{links, [{"Github", "https://github.com/leihua996/ip2region/tree/master/binding/erlang"}]}
|
||||||
]}.
|
]}.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
|
|
||||||
|
%%%===============================================================
|
||||||
|
%%% @author leihua <leihua918@sina.com>
|
||||||
|
%%% @doc
|
||||||
|
%%% ip2region xdb 查询客户端
|
||||||
|
%%% Created: 2023-1-13 16:53
|
||||||
|
%%% @end
|
||||||
|
%%%===============================================================
|
||||||
-module(ip2region).
|
-module(ip2region).
|
||||||
-include("ip2region.hrl").
|
-include("ip2region.hrl").
|
||||||
|
|
||||||
|
|
@ -5,23 +13,18 @@
|
||||||
|
|
||||||
-spec search(Ip :: tuple() | list() | binary()) -> Result :: {error, Reason::atom()} | map().
|
-spec search(Ip :: tuple() | list() | binary()) -> Result :: {error, Reason::atom()} | map().
|
||||||
search(Ip) ->
|
search(Ip) ->
|
||||||
case check_ip(Ip) of
|
case util:ipv4_to_n(Ip) of
|
||||||
{false, Reason} -> {error, Reason};
|
IntIp when is_integer(IntIp) ->
|
||||||
_ ->
|
case ets:lookup(?IP2REGION_CACHE, IntIp) of
|
||||||
Worker = poolboy:checkout(?IP2REGION_POOL, true, infinity),
|
[{_IntIp, Region}] -> Region;
|
||||||
try
|
|
||||||
ip2region_worker:search(Worker, Ip)
|
|
||||||
after
|
|
||||||
poolboy:checkin(?IP2REGION_POOL, Worker)
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
check_ip({_A, _B, _C, _D}) -> true;
|
|
||||||
check_ip(Ip) when is_list(Ip); is_binary(Ip) ->
|
|
||||||
IpRegx = "^((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])$",
|
|
||||||
case re:run(Ip, IpRegx) of
|
|
||||||
{match, _Captured} ->
|
|
||||||
ok;
|
|
||||||
_ ->
|
_ ->
|
||||||
{false, bad_ip_format}
|
Worker = poolboy:checkout(?IP2REGION_POOL, true, infinity),
|
||||||
|
try
|
||||||
|
ip2region_worker:search(Worker, IntIp)
|
||||||
|
after
|
||||||
|
poolboy:checkin(?IP2REGION_POOL, Worker)
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
Ret ->
|
||||||
|
Ret
|
||||||
end.
|
end.
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
%%%===============================================================
|
||||||
|
%%% @author leihua <leihua918@sina.com>
|
||||||
|
%%% @doc
|
||||||
|
%%% ip2region性能基准测试
|
||||||
|
%%% Created: 2023-1-13 17:46
|
||||||
|
%%% @end
|
||||||
|
%%%===============================================================
|
||||||
|
-module(ip2region_benchmark).
|
||||||
|
-export([main/1]).
|
||||||
|
|
||||||
|
main(DataFile) ->
|
||||||
|
application:ensure_started(ip2region),
|
||||||
|
show_hw_sw_info(),
|
||||||
|
IpList = load_test_data(DataFile),
|
||||||
|
run(IpList).
|
||||||
|
|
||||||
|
show_hw_sw_info() ->
|
||||||
|
io:format("CPU info:~n", []),
|
||||||
|
io:format("~s", [os:cmd("egrep '^model name' /proc/cpuinfo | head -1")]),
|
||||||
|
io:format("~s", [os:cmd("egrep '^cache' /proc/cpuinfo | head -1")]),
|
||||||
|
io:format("~s", [os:cmd("egrep '^cpu MHz' /proc/cpuinfo | head -1")]),
|
||||||
|
io:format("~s", [os:cmd("egrep '^bogomips' /proc/cpuinfo | head -1")]),
|
||||||
|
io:format("cores/threads : ~s~n", [os:cmd("egrep -c '^processor' /proc/cpuinfo")]),
|
||||||
|
io:format("Erlang info:~n", []),
|
||||||
|
io:format("system_version:~s", [erlang:system_info(system_version)]),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
load_test_data(DataFile) ->
|
||||||
|
{ok, Fd} = file:open(DataFile, [read]),
|
||||||
|
T0 = os:timestamp(),
|
||||||
|
IpList = load_test_data(Fd, []),
|
||||||
|
T1 = os:timestamp(),
|
||||||
|
Sec = timer:now_diff(T1, T0) / 1000000,
|
||||||
|
io:format("load test data use ~ps~n", [Sec]),
|
||||||
|
IpList.
|
||||||
|
|
||||||
|
load_test_data(Fd, IpList) ->
|
||||||
|
case file:read_line(Fd) of
|
||||||
|
{ok, Ip} ->
|
||||||
|
load_test_data(Fd, [string:trim(Ip)| IpList]);
|
||||||
|
_ ->
|
||||||
|
file:close(Fd),
|
||||||
|
IpList
|
||||||
|
end.
|
||||||
|
|
||||||
|
run(IpList) ->
|
||||||
|
garbage_collect(),
|
||||||
|
io:format("~nstart run benchmark tests~n", []),
|
||||||
|
io:format("~nsearch from file:~n", []),
|
||||||
|
run_test(IpList),
|
||||||
|
io:format("~nsearch from cache:~n", []),
|
||||||
|
run_test(IpList),
|
||||||
|
io:format("~nbenchmark test finish~n", []).
|
||||||
|
|
||||||
|
run_test(IpList) ->
|
||||||
|
T0 = os:timestamp(),
|
||||||
|
run_test_aux(IpList),
|
||||||
|
T1 = os:timestamp(),
|
||||||
|
Sec = timer:now_diff(T1, T0) / 1000000,
|
||||||
|
IpCount = length(IpList),
|
||||||
|
io:format("ip count:~p,~ntotal time: ~ps,~nsearch ~p times per second,~nuse ~p micro second per search~n",
|
||||||
|
[IpCount, Sec, IpCount / Sec, Sec * 1000000/IpCount]).
|
||||||
|
|
||||||
|
run_test_aux([]) -> ok;
|
||||||
|
run_test_aux([Ip | Tail]) ->
|
||||||
|
#{} = ip2region:search(Ip),
|
||||||
|
run_test_aux(Tail).
|
||||||
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
|
%%%===============================================================
|
||||||
|
%%% @author leihua <leihua918@sina.com>
|
||||||
|
%%% @doc
|
||||||
|
%%% ip2region工作进程
|
||||||
|
%%% Created: 2023-1-13 16:53
|
||||||
|
%%% @end
|
||||||
|
%%%===============================================================
|
||||||
-module(ip2region_worker).
|
-module(ip2region_worker).
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
-include("ip2region.hrl").
|
-include("ip2region.hrl").
|
||||||
|
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([start/1, stop/1, start_link/1]).
|
-export([start/1, stop/1, start_link/1]).
|
||||||
-export([search/2]).
|
-export([search/2]).
|
||||||
|
|
@ -31,7 +37,12 @@ search(Pid, Ip) ->
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
%% =========================================
|
%% =========================================
|
||||||
init(_Args) ->
|
init(_Args) ->
|
||||||
PrivDir = code:priv_dir(?APP_NAME),
|
AppName =
|
||||||
|
case application:get_application() of
|
||||||
|
{ok, AName} -> AName;
|
||||||
|
_ -> ?APP_NAME
|
||||||
|
end,
|
||||||
|
PrivDir = code:priv_dir(AppName),
|
||||||
XdbFileName = filename:join([PrivDir, "ip2region.xdb"]),
|
XdbFileName = filename:join([PrivDir, "ip2region.xdb"]),
|
||||||
error_logger:info_report(io_lib:format("XdbFile:~s~n", [XdbFileName])),
|
error_logger:info_report(io_lib:format("XdbFile:~s~n", [XdbFileName])),
|
||||||
{ok, IoDevice} = file:open(XdbFileName, [read, binary]),
|
{ok, IoDevice} = file:open(XdbFileName, [read, binary]),
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,16 @@
|
||||||
-export([ipv4_to_n/1]).
|
-export([ipv4_to_n/1]).
|
||||||
|
|
||||||
|
|
||||||
ip_aton(Ip) ->
|
ipv4_to_n(IntIp) when is_integer(IntIp) -> IntIp;
|
||||||
{ok, Addr} = inet_parse:address(Ip),
|
|
||||||
Addr.
|
|
||||||
|
|
||||||
ipv4_to_n({A, B, C, D}) ->
|
ipv4_to_n({A, B, C, D}) ->
|
||||||
<<N:32>> = <<A, B, C, D>>,
|
<<N:32>> = <<A, B, C, D>>,
|
||||||
N;
|
N;
|
||||||
ipv4_to_n(Ip) when is_binary(Ip) ->
|
ipv4_to_n(Ip) when is_binary(Ip) ->
|
||||||
ipv4_to_n(binary_to_list(Ip));
|
ipv4_to_n(binary_to_list(Ip));
|
||||||
ipv4_to_n(Ip) when is_list(Ip) ->
|
ipv4_to_n(Ip) when is_list(Ip) ->
|
||||||
Addr = ip_aton(Ip),
|
case inet_parse:address(Ip) of
|
||||||
ipv4_to_n(Addr).
|
{ok, Addr} ->
|
||||||
|
ipv4_to_n(Addr);
|
||||||
|
_ ->
|
||||||
|
{error, bad_ip_format}
|
||||||
|
end.
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
-module(ip2region_test).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
search_test_() ->
|
||||||
|
application:ensure_started(ip2region),
|
||||||
|
A = #{
|
||||||
|
city => <<"广州市"/utf8>>,
|
||||||
|
country => <<"中国"/utf8>>,
|
||||||
|
isp => <<"电信"/utf8>>,
|
||||||
|
province => <<"广东省"/utf8>>,
|
||||||
|
region => <<>>
|
||||||
|
},
|
||||||
|
Region1 = ip2region:search("1.0.8.0"),
|
||||||
|
Region2 = ip2region:search({1,0,8,0}),
|
||||||
|
Region3 = ip2region:search("xxx.0.8.0"),
|
||||||
|
[
|
||||||
|
?_assert(A =:= Region1),
|
||||||
|
?_assert(A =:= Region2),
|
||||||
|
?_assert({error, bad_ip_format} =:= Region3)
|
||||||
|
|
||||||
|
].
|
||||||
Loading…
Reference in New Issue