feat: 使用 cobra 框架构建命令行
This commit is contained in:
parent
6ed8bf0118
commit
e6dda458ad
|
|
@ -97,20 +97,24 @@ make
|
|||
# 查询测试
|
||||
|
||||
通过 `xdb_searcher search` 命令来测试 ip2region.xdb 的查询:
|
||||
```
|
||||
➜ golang git:(v2.0_xdb) ./xdb_searcher search
|
||||
./xdb_searcher search [command options]
|
||||
options:
|
||||
--db string ip2region binary xdb file path
|
||||
--cache-policy string cache policy: file/vectorIndex/content
|
||||
```bash
|
||||
$ ./xdb_searcher search
|
||||
Usage:
|
||||
xdb_searcher search [flags]
|
||||
|
||||
Flags:
|
||||
-c, --cache-policy string cache policy (file|vectorIndex|content) (default "vectorIndex")
|
||||
-d, --db string ip2region binary xdb file path (required)
|
||||
-h, --help help for search
|
||||
```
|
||||
|
||||
例如:使用默认的 data/ip2region.xdb 进行查询测试
|
||||
```bash
|
||||
➜ golang git:(v2.0_xdb) ✗ ./xdb_searcher search --db=../../data/ip2region.xdb
|
||||
ip2region xdb searcher test program, type `quit` to exit
|
||||
ip2region>> 1.2.3.4
|
||||
{region:美国|0|华盛顿|0|谷歌, took:101.57µs}
|
||||
$ ./xdb_searcher search --db=../../data/ip2region.xdb
|
||||
ip2region xdb searcher test program, cachePolicy: vectorIndex
|
||||
type 'quit' to exit
|
||||
ip2region>> 1.2.3.4
|
||||
{region: 美国|0|华盛顿|0|谷歌, ioCount: 7, took: 776.292µs}
|
||||
```
|
||||
|
||||
输入 ip 地址进行查询即可,输入 quit 退出测试程序。可以设置 `cache-policy` 为 file/vectorIndex/content 来测试不同的查询缓存机制。
|
||||
|
|
@ -120,17 +124,20 @@ ip2region>> 1.2.3.4
|
|||
|
||||
通过 `xdb_searcher bench` 命令来进行自动 bench 测试,一方面确保程序和 `xdb` 文件都没有错误,另一方面通过大量的查询得到平均查询性能:
|
||||
```bash
|
||||
➜ golang git:(v2.0_xdb) ./xdb_searcher bench
|
||||
./xdb_searcher bench [command options]
|
||||
options:
|
||||
--db string ip2region binary xdb file path
|
||||
--src string source ip text file path
|
||||
--cache-policy string cache policy: file/vectorIndex/content
|
||||
$ ./xdb_searcher bench
|
||||
Usage:
|
||||
xdb_searcher bench [flags]
|
||||
|
||||
Flags:
|
||||
-c, --cache-policy string cache policy (file|vectorIndex|content) (default "vectorIndex")
|
||||
-d, --db string ip2region binary xdb file path (required)
|
||||
-h, --help help for bench
|
||||
-s, --src string source ip text file path (required)
|
||||
```
|
||||
|
||||
例如:通过 data/ip2region.xdb 和 data/ip.merge.txt 进行 bench 测试:
|
||||
```bash
|
||||
➜ golang git:(v2.0_xdb) ✗ ./xdb_searcher bench --db=../../data/ip2region.xdb --src=../../data/ip.merge.txt
|
||||
$ ./xdb_searcher bench --db=../../data/ip2region.xdb --src=../../data/ip.merge.txt
|
||||
Bench finished, {total: 3417955, took: 28.211578339s, cost: 8253 ns/op}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
cachePolicyBench string
|
||||
dbFileBench string
|
||||
srcFileBench string
|
||||
)
|
||||
|
||||
// benchCmd represents the bench command
|
||||
var benchCmd = &cobra.Command{
|
||||
Use: "bench",
|
||||
Short: "search bench test",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
bench()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(benchCmd)
|
||||
|
||||
benchCmd.Flags().StringVarP(&dbFileBench, "db", "d", "", "ip2region binary xdb file path (required)")
|
||||
benchCmd.Flags().StringVarP(&srcFileBench, "src", "s", "", "source ip text file path (required)")
|
||||
benchCmd.Flags().StringVarP(&cachePolicyBench, "cache-policy", "c", "vectorIndex", "cache policy (file|vectorIndex|content)")
|
||||
|
||||
// Required flags
|
||||
_ = benchCmd.MarkFlagRequired("db")
|
||||
_ = benchCmd.MarkFlagRequired("src")
|
||||
}
|
||||
|
||||
func bench() {
|
||||
dbPath, err := homedir.Expand(dbFileBench)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid xdb file path `%s`: %s", dbFileBench, err)
|
||||
return
|
||||
}
|
||||
|
||||
searcher, err := createSearcher(dbPath, cachePolicyBench)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
searcher.Close()
|
||||
}()
|
||||
|
||||
handle, err := os.OpenFile(srcFileBench, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to open source text file: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var count, tStart, costs = int64(0), time.Now(), int64(0)
|
||||
var scanner = bufio.NewScanner(handle)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
var l = strings.TrimSpace(strings.TrimSuffix(scanner.Text(), "\n"))
|
||||
var ps = strings.SplitN(l, "|", 3)
|
||||
if len(ps) != 3 {
|
||||
fmt.Printf("invalid ip segment line `%s`\n", l)
|
||||
return
|
||||
}
|
||||
|
||||
sip, err := xdb.CheckIP(ps[0])
|
||||
if err != nil {
|
||||
fmt.Printf("check start ip `%s`: %s\n", ps[0], err)
|
||||
return
|
||||
}
|
||||
|
||||
eip, err := xdb.CheckIP(ps[1])
|
||||
if err != nil {
|
||||
fmt.Printf("check end ip `%s`: %s\n", ps[1], err)
|
||||
return
|
||||
}
|
||||
|
||||
if sip > eip {
|
||||
fmt.Printf("start ip(%s) should not be greater than end ip(%s)\n", ps[0], ps[1])
|
||||
return
|
||||
}
|
||||
|
||||
mip := xdb.MidIP(sip, eip)
|
||||
for _, ip := range []uint32{sip, xdb.MidIP(sip, mip), mip, xdb.MidIP(mip, eip), eip} {
|
||||
sTime := time.Now()
|
||||
region, err := searcher.Search(ip)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to search ip '%s': %s\n", xdb.Long2IP(ip), err)
|
||||
return
|
||||
}
|
||||
|
||||
costs += time.Since(sTime).Nanoseconds()
|
||||
|
||||
// check the region info
|
||||
if region != ps[2] {
|
||||
fmt.Printf("failed Search(%s) with (%s != %s)\n", xdb.Long2IP(ip), region, ps[2])
|
||||
return
|
||||
}
|
||||
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
cost := time.Since(tStart)
|
||||
fmt.Printf("Bench finished, {cachePolicy: %s, total: %d, took: %s, cost: %d μs/op}\n",
|
||||
cachePolicyBench, count, cost, costs/count/1000)
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "xdb_searcher",
|
||||
Short: "Go Implementation For Ip2region",
|
||||
Long: `Ip2region (2.0 - xdb) is a offline IP address manager framework and locator,
|
||||
support billions of data segments, ten microsecond searching performance.`,
|
||||
}
|
||||
|
||||
func createSearcher(dbPath string, cachePolicy string) (*xdb.Searcher, error) {
|
||||
switch cachePolicy {
|
||||
case "nil", "file":
|
||||
return xdb.NewWithFileOnly(dbPath)
|
||||
case "vectorIndex":
|
||||
vIndex, err := xdb.LoadVectorIndexFromFile(dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load vector index from `%s`: %w", dbPath, err)
|
||||
}
|
||||
|
||||
return xdb.NewWithVectorIndex(dbPath, vIndex)
|
||||
case "content":
|
||||
cBuff, err := xdb.LoadContentFromFile(dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load content from '%s': %w", dbPath, err)
|
||||
}
|
||||
|
||||
return xdb.NewWithBuffer(cBuff)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid cache policy `%s`, options: file/vectorIndex/content", cachePolicy)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
dbFileSearch string
|
||||
cachePolicySearch string
|
||||
)
|
||||
|
||||
// searchCmd represents the search command
|
||||
var searchCmd = &cobra.Command{
|
||||
Use: "search",
|
||||
Short: "search input test",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
searcher()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(searchCmd)
|
||||
|
||||
searchCmd.Flags().StringVarP(&dbFileSearch, "db", "d", "", "ip2region binary xdb file path (required)")
|
||||
searchCmd.Flags().StringVarP(&cachePolicySearch, "cache-policy", "c", "vectorIndex", "cache policy (file|vectorIndex|content)")
|
||||
// Required flags
|
||||
_ = searchCmd.MarkFlagRequired("db")
|
||||
|
||||
}
|
||||
|
||||
func searcher() {
|
||||
dbPath, err := homedir.Expand(dbFileSearch)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid xdb file path `%s`: %s", dbFileSearch, err)
|
||||
return
|
||||
}
|
||||
// create the searcher with the cache policy setting
|
||||
searcher, err := createSearcher(dbPath, "vectorIndex")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
searcher.Close()
|
||||
fmt.Println("searcher test program exited, thanks for trying")
|
||||
}()
|
||||
|
||||
fmt.Printf("ip2region xdb searcher test program, cachePolicy: %s \n type 'quit' to exit \n ", cachePolicySearch)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Print("ip2region>> ")
|
||||
str, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read string: %s", err)
|
||||
}
|
||||
|
||||
line := strings.TrimSpace(strings.TrimSuffix(str, "\n"))
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line == "quit" {
|
||||
break
|
||||
}
|
||||
|
||||
tStart := time.Now()
|
||||
region, err := searcher.SearchByStr(line)
|
||||
if err != nil {
|
||||
fmt.Printf("\x1b[0;31m{err: %s, ioCount: %d}\x1b[0m\n", err.Error(), searcher.GetIOCount())
|
||||
} else {
|
||||
fmt.Printf("\x1b[0;32m{region: %s, ioCount: %d, took: %s}\x1b[0m\n", region, searcher.GetIOCount(), time.Since(tStart))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,7 +1,13 @@
|
|||
module github.com/lionsoul2014/ip2region/binding/golang
|
||||
|
||||
go 1.17
|
||||
go 1.19
|
||||
|
||||
require github.com/mitchellh/go-homedir v1.1.0
|
||||
require (
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
)
|
||||
|
||||
require github.com/yookoala/realpath v1.0.0 // indirect
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/yookoala/realpath v1.0.0 h1:7OA9pj4FZd+oZDsyvXWQvjn5oBdcHRTV44PpdMSuImQ=
|
||||
github.com/yookoala/realpath v1.0.0/go.mod h1:gJJMA9wuX7AcqLy1+ffPatSCySA1FQ2S8Ya9AIoYBpE=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
|
|
@ -1,270 +1,10 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/16
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func printHelp() {
|
||||
fmt.Printf("ip2region xdb searcher\n")
|
||||
fmt.Printf("%s [command] [command options]\n", os.Args[0])
|
||||
fmt.Printf("Command: \n")
|
||||
fmt.Printf(" search search input test\n")
|
||||
fmt.Printf(" bench search bench test\n")
|
||||
}
|
||||
|
||||
func testSearch() {
|
||||
var err error
|
||||
var dbFile, cachePolicy = "", "vectorIndex"
|
||||
for i := 2; i < len(os.Args); i++ {
|
||||
r := os.Args[i]
|
||||
if len(r) < 5 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Index(r, "--") != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var sIdx = strings.Index(r, "=")
|
||||
if sIdx < 0 {
|
||||
fmt.Printf("missing = for args pair '%s'\n", r)
|
||||
return
|
||||
}
|
||||
|
||||
switch r[2:sIdx] {
|
||||
case "db":
|
||||
dbFile = r[sIdx+1:]
|
||||
case "cache-policy":
|
||||
cachePolicy = r[sIdx+1:]
|
||||
default:
|
||||
fmt.Printf("undefined option `%s`\n", r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if dbFile == "" {
|
||||
fmt.Printf("%s search [command options]\n", os.Args[0])
|
||||
fmt.Printf("options:\n")
|
||||
fmt.Printf(" --db string ip2region binary xdb file path\n")
|
||||
fmt.Printf(" --cache-policy string cache policy: file/vectorIndex/content\n")
|
||||
return
|
||||
}
|
||||
|
||||
dbPath, err := homedir.Expand(dbFile)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid xdb file path `%s`: %s", dbFile, err)
|
||||
return
|
||||
}
|
||||
|
||||
// create the searcher with the cache policy setting
|
||||
searcher, err := createSearcher(dbPath, cachePolicy)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
searcher.Close()
|
||||
fmt.Printf("searcher test program exited, thanks for trying\n")
|
||||
}()
|
||||
|
||||
fmt.Printf(`ip2region xdb searcher test program, cachePolicy: %s
|
||||
type 'quit' to exit
|
||||
`, cachePolicy)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Print("ip2region>> ")
|
||||
str, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read string: %s", err)
|
||||
}
|
||||
|
||||
line := strings.TrimSpace(strings.TrimSuffix(str, "\n"))
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line == "quit" {
|
||||
break
|
||||
}
|
||||
|
||||
tStart := time.Now()
|
||||
region, err := searcher.SearchByStr(line)
|
||||
if err != nil {
|
||||
fmt.Printf("\x1b[0;31m{err: %s, ioCount: %d}\x1b[0m\n", err.Error(), searcher.GetIOCount())
|
||||
} else {
|
||||
fmt.Printf("\x1b[0;32m{region: %s, ioCount: %d, took: %s}\x1b[0m\n", region, searcher.GetIOCount(), time.Since(tStart))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBench() {
|
||||
var err error
|
||||
var dbFile, srcFile, cachePolicy = "", "", "vectorIndex"
|
||||
for i := 2; i < len(os.Args); i++ {
|
||||
r := os.Args[i]
|
||||
if len(r) < 5 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Index(r, "--") != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var sIdx = strings.Index(r, "=")
|
||||
if sIdx < 0 {
|
||||
fmt.Printf("missing = for args pair '%s'\n", r)
|
||||
return
|
||||
}
|
||||
|
||||
switch r[2:sIdx] {
|
||||
case "db":
|
||||
dbFile = r[sIdx+1:]
|
||||
case "src":
|
||||
srcFile = r[sIdx+1:]
|
||||
case "cache-policy":
|
||||
cachePolicy = r[sIdx+1:]
|
||||
default:
|
||||
fmt.Printf("undefined option `%s`\n", r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if dbFile == "" || srcFile == "" {
|
||||
fmt.Printf("%s bench [command options]\n", os.Args[0])
|
||||
fmt.Printf("options:\n")
|
||||
fmt.Printf(" --db string ip2region binary xdb file path\n")
|
||||
fmt.Printf(" --src string source ip text file path\n")
|
||||
fmt.Printf(" --cache-policy string cache policy: file/vectorIndex/content\n")
|
||||
return
|
||||
}
|
||||
|
||||
dbPath, err := homedir.Expand(dbFile)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid xdb file path `%s`: %s", dbFile, err)
|
||||
return
|
||||
}
|
||||
|
||||
searcher, err := createSearcher(dbPath, cachePolicy)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
searcher.Close()
|
||||
}()
|
||||
|
||||
handle, err := os.OpenFile(srcFile, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to open source text file: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var count, tStart, costs = int64(0), time.Now(), int64(0)
|
||||
var scanner = bufio.NewScanner(handle)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
var l = strings.TrimSpace(strings.TrimSuffix(scanner.Text(), "\n"))
|
||||
var ps = strings.SplitN(l, "|", 3)
|
||||
if len(ps) != 3 {
|
||||
fmt.Printf("invalid ip segment line `%s`\n", l)
|
||||
return
|
||||
}
|
||||
|
||||
sip, err := xdb.CheckIP(ps[0])
|
||||
if err != nil {
|
||||
fmt.Printf("check start ip `%s`: %s\n", ps[0], err)
|
||||
return
|
||||
}
|
||||
|
||||
eip, err := xdb.CheckIP(ps[1])
|
||||
if err != nil {
|
||||
fmt.Printf("check end ip `%s`: %s\n", ps[1], err)
|
||||
return
|
||||
}
|
||||
|
||||
if sip > eip {
|
||||
fmt.Printf("start ip(%s) should not be greater than end ip(%s)\n", ps[0], ps[1])
|
||||
return
|
||||
}
|
||||
|
||||
mip := xdb.MidIP(sip, eip)
|
||||
for _, ip := range []uint32{sip, xdb.MidIP(sip, mip), mip, xdb.MidIP(mip, eip), eip} {
|
||||
sTime := time.Now()
|
||||
region, err := searcher.Search(ip)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to search ip '%s': %s\n", xdb.Long2IP(ip), err)
|
||||
return
|
||||
}
|
||||
|
||||
costs += time.Since(sTime).Nanoseconds()
|
||||
|
||||
// check the region info
|
||||
if region != ps[2] {
|
||||
fmt.Printf("failed Search(%s) with (%s != %s)\n", xdb.Long2IP(ip), region, ps[2])
|
||||
return
|
||||
}
|
||||
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
cost := time.Since(tStart)
|
||||
fmt.Printf("Bench finished, {cachePolicy: %s, total: %d, took: %s, cost: %d μs/op}\n",
|
||||
cachePolicy, count, cost, costs/count/1000)
|
||||
}
|
||||
|
||||
func createSearcher(dbPath string, cachePolicy string) (*xdb.Searcher, error) {
|
||||
switch cachePolicy {
|
||||
case "nil", "file":
|
||||
return xdb.NewWithFileOnly(dbPath)
|
||||
case "vectorIndex":
|
||||
vIndex, err := xdb.LoadVectorIndexFromFile(dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load vector index from `%s`: %w", dbPath, err)
|
||||
}
|
||||
|
||||
return xdb.NewWithVectorIndex(dbPath, vIndex)
|
||||
case "content":
|
||||
cBuff, err := xdb.LoadContentFromFile(dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load content from '%s': %w", dbPath, err)
|
||||
}
|
||||
|
||||
return xdb.NewWithBuffer(cBuff)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid cache policy `%s`, options: file/vectorIndex/content", cachePolicy)
|
||||
}
|
||||
}
|
||||
import "github.com/lionsoul2014/ip2region/binding/golang/cmd"
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
printHelp()
|
||||
return
|
||||
}
|
||||
|
||||
// set the log flag
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||||
switch strings.ToLower(os.Args[1]) {
|
||||
case "search":
|
||||
testSearch()
|
||||
case "bench":
|
||||
testBench()
|
||||
default:
|
||||
printHelp()
|
||||
}
|
||||
cmd.Execute()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,17 +14,21 @@ make
|
|||
# `xdb` 数据生成
|
||||
|
||||
通过 `xdb_maker gen` 命令生成 ip2region.xdb 二进制文件:
|
||||
```
|
||||
➜ golang git:(v2.0_xdb) ✗ ./xdb_maker gen
|
||||
./xdb_maker gen [command options]
|
||||
options:
|
||||
--src string source ip text file path
|
||||
--dst string destination binary xdb file path
|
||||
```bash
|
||||
$ ./xdb_maker gen
|
||||
Usage:
|
||||
xdb_maker gen [flags]
|
||||
|
||||
Flags:
|
||||
-d, --dst string destination binary xdb file path (required)
|
||||
-h, --help help for gen
|
||||
-i, --index-policy string generate index policy (vector|btree) (default "vector")
|
||||
-s, --src string source ip text file path (required)
|
||||
```
|
||||
|
||||
例如,使用默认的 data/ip.merge.txt 作为源数据,生成一个 ip2region.xdb 到当前目录:
|
||||
```bash
|
||||
➜ golang git:(v2.0_xdb) ✗ ./xdb_maker gen --src=../../data/ip.merge.txt --dst=./ip2region.xdb
|
||||
$ ./xdb_maker gen --src=../../data/ip.merge.txt --dst=./ip2region.xdb
|
||||
# 会看到一堆输出,最终会看到类似如下输出表示运行结束
|
||||
...
|
||||
2022/06/16 16:38:48 maker.go:317: write done, with 13804 data blocks and (683591, 720221) index blocks
|
||||
|
|
@ -35,54 +39,58 @@ options:
|
|||
# `xdb` 数据查询
|
||||
|
||||
通过 `xdb_maker search` 命令来测试查询输入的 ip:
|
||||
```
|
||||
➜ golang git:(v2.0_xdb) ✗ ./xdb_maker search
|
||||
./xdb_maker search [command options]
|
||||
options:
|
||||
--db string ip2region binary xdb file path
|
||||
```bash
|
||||
$ ./xdb_maker search
|
||||
Usage:
|
||||
xdb_maker search [flags]
|
||||
|
||||
Flags:
|
||||
-d, --db string ip2region binary xdb file path (required)
|
||||
-h, --help help for search
|
||||
```
|
||||
|
||||
例如,使用自带的 xdb 文件来运行查询测试:
|
||||
```bash
|
||||
➜ golang git:(v2.0_xdb) ✗ ./xdb_maker search --db=../../data/ip2region.xdb
|
||||
$ ./xdb_maker search --db=../../data/ip2region.xdb
|
||||
ip2region xdb search test program, commands:
|
||||
loadIndex : load the vector index for search speedup.
|
||||
clearIndex: clear the vector index.
|
||||
quit : exit the test program
|
||||
quit : exit the test program.
|
||||
ip2region>> 103.192.227.215
|
||||
{region:中国|0|香港|0|0, iocount:8, took:87.151µs}
|
||||
ip2region>> loadIndex
|
||||
vector index cached
|
||||
{region:中国|0|香港|0|0, iocount:8, took:1.390702ms}
|
||||
ip2region>> 39.114.2.16
|
||||
{region:韩国|0|0|0|SK宽带, iocount:2, took:50.005µs}
|
||||
{region:韩国|0|0|0|SK宽带, iocount:3, took:1.337932ms}
|
||||
ip2region>> 120.24.130.96
|
||||
{region:中国|0|广东省|深圳市|阿里云, iocount:2, took:32.805µs}
|
||||
{region:中国|0|广东省|深圳市|阿里云, iocount:3, took:1.387141ms}
|
||||
ip2region>>
|
||||
```
|
||||
|
||||
# `xdb` 数据编辑
|
||||
|
||||
通过 `xdb_maker edit` 命令来编辑原始的 IP 数据:
|
||||
```
|
||||
➜ golang git:(fr_editor) ✗ ./xdb_maker edit
|
||||
./xdb_maker edit [command options]
|
||||
options:
|
||||
--src string source ip text file path
|
||||
```bash
|
||||
$ ./xdb_maker edit
|
||||
Usage:
|
||||
xdb_maker edit [flags]
|
||||
|
||||
Flags:
|
||||
-h, --help help for edit
|
||||
-s, --src string source ip text file path (required)
|
||||
```
|
||||
|
||||
例如,使用编辑器打开 `./data/ip.merge.txt` 会看到如下的操作面板:
|
||||
```bash
|
||||
➜ golang git:(fr_editor) ✗ ./xdb_maker edit --src=../../data/ip.merge.txt
|
||||
$ ./xdb_maker edit --src=../../data/ip.merge.txt
|
||||
init the editor from source @ `../../data/ip.merge.txt` ...
|
||||
all segments loaded, length: 683591, elapsed: 479.73743ms
|
||||
command list:
|
||||
all segments loaded, length: 683843, elapsed: 503.631945ms
|
||||
command list:
|
||||
put [segment] : put the specifield $segment
|
||||
put_file [file] : put all the segments from the specified $file
|
||||
list [offset] [size] : list the first $size segments start from $offset
|
||||
save : save all the changes to the destination source file
|
||||
quit : exit the program
|
||||
help : print this help menu
|
||||
editor>>
|
||||
editor>>
|
||||
```
|
||||
|
||||
通过 `put` 命令修改指定 IP 段的定位信息,例如:
|
||||
|
|
@ -109,18 +117,22 @@ editor>>
|
|||
# bench 测试
|
||||
|
||||
如果你自主生成了 `xdb` 文件,请确保运行如下的 `xdb_maker bench` 命令来确保生成的的 `xdb` 文件的正确性:
|
||||
```
|
||||
➜ golang git:(v2.0_xdb) ✗ ./xdb_maker bench
|
||||
./xdb_maker bench [command options]
|
||||
options:
|
||||
--db string ip2region binary xdb file path
|
||||
--src string source ip text file path
|
||||
--ignore-error bool keep going if bench failed
|
||||
```bash
|
||||
$ ./xdb_maker bench
|
||||
Usage:
|
||||
xdb_maker bench [flags]
|
||||
|
||||
Flags:
|
||||
-d, --db string ip2region binary xdb file path (required)
|
||||
-h, --help help for bench
|
||||
-i, --ignore-error keep going if bench failed (default "false")
|
||||
-s, --src string source ip text file path (required)
|
||||
|
||||
```
|
||||
|
||||
例如:使用 data/ip.merge.txt 源文件来 bench 测试 data/ip2region.xdb 这个 xdb 文件:
|
||||
```bash
|
||||
➜ golang git:(v2.0_xdb) ✗ ./xdb_maker bench --db=../../data/ip2region.xdb --src=../../data/ip.merge.txt
|
||||
$ ./xdb_maker bench --db=../../data/ip2region.xdb --src=../../data/ip.merge.txt
|
||||
# 会看到一堆输出,看到类似如下的数据表示 bench 测试通过了,否则就会报错
|
||||
...
|
||||
try to bench segment: `224.0.0.0|255.255.255.255|0|0|0|内网IP|内网IP`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lionsoul2014/ip2region/maker/golang/xdb"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
ignoreErrorBench bool
|
||||
dbFileBench string
|
||||
srcFileBench string
|
||||
)
|
||||
|
||||
// benchCmd represents the bench command
|
||||
var benchCmd = &cobra.Command{
|
||||
Use: "bench",
|
||||
Short: "binary xdb bench test",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
bench()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(benchCmd)
|
||||
|
||||
benchCmd.Flags().StringVarP(&dbFileBench, "db", "d", "", "ip2region binary xdb file path (required)")
|
||||
benchCmd.Flags().StringVarP(&srcFileBench, "src", "s", "", "source ip text file path (required)")
|
||||
benchCmd.Flags().BoolVarP(&ignoreErrorBench, "ignore-error", "i", false, "keep going if bench failed (default \"false\")")
|
||||
|
||||
// Required flags
|
||||
_ = benchCmd.MarkFlagRequired("db")
|
||||
_ = benchCmd.MarkFlagRequired("src")
|
||||
|
||||
}
|
||||
|
||||
func bench() {
|
||||
searcher, err := xdb.NewSearcher(dbFileBench)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher with `%s`: %s\n", dbFileBench, err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
searcher.Close()
|
||||
}()
|
||||
|
||||
handle, err := os.OpenFile(srcFileBench, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to open source text file: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var count, errCount, tStart = 0, 0, time.Now()
|
||||
var iErr = xdb.IterateSegments(handle, nil, func(seg *xdb.Segment) error {
|
||||
var l = fmt.Sprintf("%d|%d|%s", seg.StartIP, seg.EndIP, seg.Region)
|
||||
fmt.Printf("try to bench segment: `%s`\n", l)
|
||||
mip := xdb.MidIP(seg.StartIP, seg.EndIP)
|
||||
for _, ip := range []uint32{seg.StartIP, xdb.MidIP(seg.EndIP, mip), mip, xdb.MidIP(mip, seg.EndIP), seg.EndIP} {
|
||||
fmt.Printf("|-try to bench ip '%s' ... ", xdb.Long2IP(ip))
|
||||
r, _, err := searcher.Search(ip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to search ip '%s': %s\n", xdb.Long2IP(ip), err)
|
||||
}
|
||||
// check the region info
|
||||
count++
|
||||
if r != seg.Region {
|
||||
errCount++
|
||||
fmt.Printf(" --[Failed] (%s != %s)\n", r, seg.Region)
|
||||
if !ignoreErrorBench {
|
||||
return fmt.Errorf("")
|
||||
}
|
||||
} else {
|
||||
fmt.Println(" --[Ok]")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if iErr != nil {
|
||||
fmt.Printf("%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Bench finished, {count: %d, failed: %d, took: %s}\n", count, errCount, time.Since(tStart))
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/lionsoul2014/ip2region/maker/golang/xdb"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var srcFileEdit string
|
||||
|
||||
// editCmd represents the edit command
|
||||
var editCmd = &cobra.Command{
|
||||
Use: "edit",
|
||||
Short: "edit the source ip data",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
edit()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(editCmd)
|
||||
|
||||
editCmd.Flags().StringVarP(&srcFileEdit, "src", "s", "", "source ip text file path (required)")
|
||||
// Required flags
|
||||
_ = editCmd.MarkFlagRequired("src")
|
||||
}
|
||||
|
||||
func edit() {
|
||||
rExp, err := regexp.Compile("\\s+")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to compile regexp: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("init the editor from source @ `%s` ... \n", srcFileEdit)
|
||||
var tStart = time.Now()
|
||||
editor, err := xdb.NewEditor(srcFileEdit)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to init editor: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("all segments loaded, length: %d, elapsed: %s\n", editor.SegLen(), time.Since(tStart))
|
||||
var help = func() {
|
||||
fmt.Println("command list:")
|
||||
fmt.Println(" put [segment] : put the specifield $segment")
|
||||
fmt.Println(" put_file [file] : put all the segments from the specified $file")
|
||||
fmt.Println(" list [offset] [size] : list the first $size segments start from $offset")
|
||||
fmt.Println(" save : save all the changes to the destination source file")
|
||||
fmt.Println(" quit : exit the program")
|
||||
fmt.Println(" help : print this help menu")
|
||||
}
|
||||
|
||||
help()
|
||||
var sTip = ""
|
||||
var reader = bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
if editor.NeedSave() {
|
||||
sTip = "*"
|
||||
} else {
|
||||
sTip = ""
|
||||
}
|
||||
|
||||
fmt.Printf("%seditor>> ", sTip)
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
fmt.Printf("failed to read line from cli: %s\n", err)
|
||||
break
|
||||
}
|
||||
|
||||
cmd := strings.TrimSpace(line)
|
||||
if cmd == "help" {
|
||||
help()
|
||||
} else if cmd == "quit" {
|
||||
if editor.NeedSave() {
|
||||
fmt.Printf("there are changes that need to save, type 'quit!' to force quit\n")
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else if cmd == "quit!" {
|
||||
// quit directly
|
||||
break
|
||||
} else if cmd == "save" {
|
||||
err = editor.Save()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to save the changes: %s\n", err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("all segments saved to %s\n", srcFileEdit)
|
||||
} else if strings.HasPrefix(cmd, "list") {
|
||||
var sErr error
|
||||
off, size, l := 0, 10, len("list")
|
||||
str := strings.TrimSpace(cmd)
|
||||
if len(str) > l {
|
||||
sets := rExp.Split(cmd, 3)
|
||||
switch len(sets) {
|
||||
case 2:
|
||||
_, sErr = fmt.Sscanf(cmd, "%s %d", &str, &off)
|
||||
case 3:
|
||||
_, sErr = fmt.Sscanf(cmd, "%s %d %d", &str, &off, &size)
|
||||
}
|
||||
}
|
||||
|
||||
if sErr != nil {
|
||||
fmt.Printf("failed to parse the offset and size: %s\n", sErr)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("+-slice(%d,%d): \n", off, size)
|
||||
for _, s := range editor.Slice(off, size) {
|
||||
fmt.Printf("%s\n", s)
|
||||
}
|
||||
} else if strings.HasPrefix(cmd, "put ") {
|
||||
seg := strings.TrimSpace(cmd[len("put "):])
|
||||
o, n, err := editor.Put(seg)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to Put(%s): %s\n", seg, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Put(%s): Ok, with %d deletes and %d additions\n", seg, o, n)
|
||||
} else if strings.HasPrefix(cmd, "put_file ") {
|
||||
file := strings.TrimSpace(cmd[len("put_file "):])
|
||||
o, n, err := editor.PutFile(file)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to PutFile(%s): %s\n", file, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("PutFile(%s): Ok, with %d deletes and %d additions\n", file, o, n)
|
||||
} else if len(cmd) > 0 {
|
||||
help()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lionsoul2014/ip2region/maker/golang/xdb"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
dstFileGen string
|
||||
srcFileGen string
|
||||
indexPolicyGen string
|
||||
)
|
||||
|
||||
// genCmd represents the gen command
|
||||
var genCmd = &cobra.Command{
|
||||
Use: "gen",
|
||||
Short: "generate the binary db file",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
genDb()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(genCmd)
|
||||
|
||||
genCmd.Flags().StringVarP(&dstFileGen, "dst", "d", "", "destination binary xdb file path (required)")
|
||||
genCmd.Flags().StringVarP(&srcFileGen, "src", "s", "", "source ip text file path (required)")
|
||||
genCmd.Flags().StringVarP(&indexPolicyGen, "index-policy", "i", "vector", "generate index policy (vector|btree)")
|
||||
|
||||
// Required flags
|
||||
_ = genCmd.MarkFlagRequired("dst")
|
||||
_ = genCmd.MarkFlagRequired("src")
|
||||
|
||||
}
|
||||
|
||||
func genDb() {
|
||||
indexPolicy, err := xdb.IndexPolicyFromString(indexPolicyGen)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
// make the binary file
|
||||
tStart := time.Now()
|
||||
maker, err := xdb.NewMaker(indexPolicy, srcFileGen, dstFileGen)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = maker.Init()
|
||||
if err != nil {
|
||||
fmt.Printf("failed Init: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = maker.Start()
|
||||
if err != nil {
|
||||
fmt.Printf("failed Start: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = maker.End()
|
||||
if err != nil {
|
||||
fmt.Printf("failed End: %s\n", err)
|
||||
}
|
||||
|
||||
log.Printf("Done, elapsed: %s\n", time.Since(tStart))
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "xdb_maker",
|
||||
Short: "Go Implementation For Ip2region",
|
||||
Long: `Ip2region (2.0 - xdb) is a offline IP address manager framework and locator,
|
||||
support billions of data segments, ten microsecond searching performance.`,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/lionsoul2014/ip2region/maker/golang/xdb"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var dbFileSearch string
|
||||
|
||||
// searchCmd represents the search command
|
||||
var searchCmd = &cobra.Command{
|
||||
Use: "search",
|
||||
Short: "binary xdb search test",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
search()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(searchCmd)
|
||||
|
||||
searchCmd.Flags().StringVarP(&dbFileSearch, "db", "d", "", "ip2region binary xdb file path (required)")
|
||||
// Required flags
|
||||
_ = searchCmd.MarkFlagRequired("db")
|
||||
}
|
||||
|
||||
func search() {
|
||||
searcher, err := xdb.NewSearcher(dbFileSearch)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher with `%s`: %s\n", dbFileSearch, err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
searcher.Close()
|
||||
fmt.Println("test program exited, thanks for trying")
|
||||
}()
|
||||
|
||||
fmt.Println("ip2region xdb search test program, commands:")
|
||||
fmt.Println("loadIndex : load the vector index for search speedup.")
|
||||
fmt.Println("clearIndex: clear the vector index.")
|
||||
fmt.Println("quit : exit the test program.")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Print("ip2region>> ")
|
||||
str, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read string: %s", err)
|
||||
}
|
||||
|
||||
line := strings.TrimSpace(strings.TrimSuffix(str, "\n"))
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
// command interception and execution
|
||||
if line == "loadIndex" {
|
||||
err = searcher.LoadVectorIndex()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load vector index: %s", err)
|
||||
}
|
||||
fmt.Printf("vector index cached\n")
|
||||
continue
|
||||
} else if line == "clearIndex" {
|
||||
searcher.ClearVectorIndex()
|
||||
fmt.Printf("vector index cleared\n")
|
||||
continue
|
||||
} else if line == "quit" {
|
||||
break
|
||||
}
|
||||
|
||||
ip, err := xdb.CheckIP(line)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid ip address `%s`\n", line)
|
||||
continue
|
||||
}
|
||||
|
||||
tStart := time.Now()
|
||||
region, ioCount, err := searcher.Search(ip)
|
||||
if err != nil {
|
||||
fmt.Printf("\x1b[0;31m{err:%s, iocount:%d}\x1b[0m\n", err.Error(), ioCount)
|
||||
} else {
|
||||
fmt.Printf("\x1b[0;32m{region:%s, iocount:%d, took:%s}\x1b[0m\n", region, ioCount, time.Since(tStart))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,10 @@
|
|||
module github.com/lionsoul2014/ip2region/maker/golang
|
||||
|
||||
go 1.17
|
||||
go 1.19
|
||||
|
||||
require github.com/spf13/cobra v1.7.0
|
||||
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
@ -4,430 +4,8 @@
|
|||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/lionsoul2014/ip2region/maker/golang/xdb"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func printHelp() {
|
||||
fmt.Printf("ip2region xdb maker\n")
|
||||
fmt.Printf("%s [command] [command options]\n", os.Args[0])
|
||||
fmt.Printf("Command: \n")
|
||||
fmt.Printf(" gen generate the binary db file\n")
|
||||
fmt.Printf(" search binary xdb search test\n")
|
||||
fmt.Printf(" bench binary xdb bench test\n")
|
||||
fmt.Printf(" edit edit the source ip data\n")
|
||||
}
|
||||
|
||||
// Iterate the cli flags
|
||||
func iterateFlags(cb func(key string, val string) error) error {
|
||||
for i := 2; i < len(os.Args); i++ {
|
||||
r := os.Args[i]
|
||||
if len(r) < 5 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Index(r, "--") != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var sIdx = strings.Index(r, "=")
|
||||
if sIdx < 0 {
|
||||
return fmt.Errorf("missing = for args pair '%s'", r)
|
||||
}
|
||||
|
||||
if err := cb(r[2:sIdx], r[sIdx+1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func genDb() {
|
||||
var err error
|
||||
var srcFile, dstFile = "", ""
|
||||
var indexPolicy = xdb.VectorIndexPolicy
|
||||
var fErr = iterateFlags(func(key string, val string) error {
|
||||
switch key {
|
||||
case "src":
|
||||
srcFile = val
|
||||
case "dst":
|
||||
dstFile = val
|
||||
case "index":
|
||||
indexPolicy, err = xdb.IndexPolicyFromString(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse policy: %w", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("undefine option `%s=%s`\n", key, val)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if fErr != nil {
|
||||
fmt.Printf("failed to parse flags: %s", fErr)
|
||||
return
|
||||
}
|
||||
|
||||
if srcFile == "" || dstFile == "" {
|
||||
fmt.Printf("%s gen [command options]\n", os.Args[0])
|
||||
fmt.Printf("options:\n")
|
||||
fmt.Printf(" --src string source ip text file path\n")
|
||||
fmt.Printf(" --dst string destination binary xdb file path\n")
|
||||
return
|
||||
}
|
||||
|
||||
// make the binary file
|
||||
tStart := time.Now()
|
||||
maker, err := xdb.NewMaker(indexPolicy, srcFile, dstFile)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = maker.Init()
|
||||
if err != nil {
|
||||
fmt.Printf("failed Init: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = maker.Start()
|
||||
if err != nil {
|
||||
fmt.Printf("failed Start: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = maker.End()
|
||||
if err != nil {
|
||||
fmt.Printf("failed End: %s\n", err)
|
||||
}
|
||||
|
||||
log.Printf("Done, elapsed: %s\n", time.Since(tStart))
|
||||
}
|
||||
|
||||
func testSearch() {
|
||||
var err error
|
||||
var dbFile = ""
|
||||
var fErr = iterateFlags(func(key string, val string) error {
|
||||
if key == "db" {
|
||||
dbFile = val
|
||||
} else {
|
||||
return fmt.Errorf("undefined option '%s=%s'\n", key, val)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if fErr != nil {
|
||||
fmt.Printf("failed to parse flags: %s", fErr)
|
||||
return
|
||||
}
|
||||
|
||||
if dbFile == "" {
|
||||
fmt.Printf("%s search [command options]\n", os.Args[0])
|
||||
fmt.Printf("options:\n")
|
||||
fmt.Printf(" --db string ip2region binary xdb file path\n")
|
||||
return
|
||||
}
|
||||
|
||||
searcher, err := xdb.NewSearcher(dbFile)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher with `%s`: %s\n", dbFile, err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
searcher.Close()
|
||||
fmt.Printf("test program exited, thanks for trying\n")
|
||||
}()
|
||||
|
||||
fmt.Println(`ip2region xdb search test program, commands:
|
||||
loadIndex : load the vector index for search speedup.
|
||||
clearIndex: clear the vector index.
|
||||
quit : exit the test program`)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Print("ip2region>> ")
|
||||
str, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read string: %s", err)
|
||||
}
|
||||
|
||||
line := strings.TrimSpace(strings.TrimSuffix(str, "\n"))
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// command interception and execution
|
||||
if line == "loadIndex" {
|
||||
err = searcher.LoadVectorIndex()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load vector index: %s", err)
|
||||
}
|
||||
fmt.Printf("vector index cached\n")
|
||||
continue
|
||||
} else if line == "clearIndex" {
|
||||
searcher.ClearVectorIndex()
|
||||
fmt.Printf("vector index cleared\n")
|
||||
continue
|
||||
} else if line == "quit" {
|
||||
break
|
||||
}
|
||||
|
||||
ip, err := xdb.CheckIP(line)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid ip address `%s`\n", line)
|
||||
continue
|
||||
}
|
||||
|
||||
tStart := time.Now()
|
||||
region, ioCount, err := searcher.Search(ip)
|
||||
if err != nil {
|
||||
fmt.Printf("\x1b[0;31m{err:%s, iocount:%d}\x1b[0m\n", err.Error(), ioCount)
|
||||
} else {
|
||||
fmt.Printf("\x1b[0;32m{region:%s, iocount:%d, took:%s}\x1b[0m\n", region, ioCount, time.Since(tStart))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBench() {
|
||||
var err error
|
||||
var dbFile, srcFile = "", ""
|
||||
var ignoreError = false
|
||||
var fErr = iterateFlags(func(key string, val string) error {
|
||||
switch key {
|
||||
case "db":
|
||||
dbFile = val
|
||||
case "src":
|
||||
srcFile = val
|
||||
case "ignore-error":
|
||||
if val == "true" || val == "1" {
|
||||
ignoreError = true
|
||||
} else if val == "false" || val == "0" {
|
||||
ignoreError = false
|
||||
} else {
|
||||
return fmt.Errorf("invalid value for ignore-error option, could be false/0 or true/1\n")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("undefined option '%s=%s'\n", key, val)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if fErr != nil {
|
||||
fmt.Printf("failed to parse flags: %s", fErr)
|
||||
return
|
||||
}
|
||||
|
||||
if dbFile == "" || srcFile == "" {
|
||||
fmt.Printf("%s bench [command options]\n", os.Args[0])
|
||||
fmt.Printf("options:\n")
|
||||
fmt.Printf(" --db string ip2region binary xdb file path\n")
|
||||
fmt.Printf(" --src string source ip text file path\n")
|
||||
fmt.Printf(" --ignore-error bool keep going if bench failed\n")
|
||||
return
|
||||
}
|
||||
|
||||
searcher, err := xdb.NewSearcher(dbFile)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher with `%s`: %s\n", dbFile, err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
searcher.Close()
|
||||
}()
|
||||
|
||||
handle, err := os.OpenFile(srcFile, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to open source text file: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var count, errCount, tStart = 0, 0, time.Now()
|
||||
var iErr = xdb.IterateSegments(handle, nil, func(seg *xdb.Segment) error {
|
||||
var l = fmt.Sprintf("%d|%d|%s", seg.StartIP, seg.EndIP, seg.Region)
|
||||
fmt.Printf("try to bench segment: `%s`\n", l)
|
||||
mip := xdb.MidIP(seg.StartIP, seg.EndIP)
|
||||
for _, ip := range []uint32{seg.StartIP, xdb.MidIP(seg.EndIP, mip), mip, xdb.MidIP(mip, seg.EndIP), seg.EndIP} {
|
||||
fmt.Printf("|-try to bench ip '%s' ... ", xdb.Long2IP(ip))
|
||||
r, _, err := searcher.Search(ip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to search ip '%s': %s\n", xdb.Long2IP(ip), err)
|
||||
}
|
||||
|
||||
// check the region info
|
||||
count++
|
||||
if r != seg.Region {
|
||||
errCount++
|
||||
fmt.Printf(" --[Failed] (%s != %s)\n", r, seg.Region)
|
||||
if ignoreError == false {
|
||||
return fmt.Errorf("")
|
||||
}
|
||||
} else {
|
||||
fmt.Printf(" --[Ok]\n")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if iErr != nil {
|
||||
fmt.Printf("%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Bench finished, {count: %d, failed: %d, took: %s}\n", count, errCount, time.Since(tStart))
|
||||
}
|
||||
|
||||
func edit() {
|
||||
var err error
|
||||
var srcFile = ""
|
||||
var fErr = iterateFlags(func(key string, val string) error {
|
||||
switch key {
|
||||
case "src":
|
||||
srcFile = val
|
||||
default:
|
||||
return fmt.Errorf("undefined option '%s=%s'\n", key, val)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if fErr != nil {
|
||||
fmt.Printf("failed to parse flags: %s", fErr)
|
||||
return
|
||||
}
|
||||
|
||||
if srcFile == "" {
|
||||
fmt.Printf("%s edit [command options]\n", os.Args[0])
|
||||
fmt.Printf("options:\n")
|
||||
fmt.Printf(" --src string source ip text file path\n")
|
||||
return
|
||||
}
|
||||
|
||||
rExp, err := regexp.Compile("\\s+")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to compile regexp: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("init the editor from source @ `%s` ... \n", srcFile)
|
||||
var tStart = time.Now()
|
||||
editor, err := xdb.NewEditor(srcFile)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to init editor: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("all segments loaded, length: %d, elapsed: %s\n", editor.SegLen(), time.Since(tStart))
|
||||
var help = func() {
|
||||
fmt.Printf("command list: \n")
|
||||
fmt.Printf(" put [segment] : put the specifield $segment\n")
|
||||
fmt.Printf(" put_file [file] : put all the segments from the specified $file\n")
|
||||
fmt.Printf(" list [offset] [size] : list the first $size segments start from $offset\n")
|
||||
fmt.Printf(" save : save all the changes to the destination source file\n")
|
||||
fmt.Printf(" quit : exit the program\n")
|
||||
fmt.Printf(" help : print this help menu\n")
|
||||
}
|
||||
|
||||
help()
|
||||
var sTip = ""
|
||||
var reader = bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
if editor.NeedSave() {
|
||||
sTip = "*"
|
||||
} else {
|
||||
sTip = ""
|
||||
}
|
||||
|
||||
fmt.Printf("%seditor>> ", sTip)
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
fmt.Printf("failed to read line from cli: %s\n", err)
|
||||
break
|
||||
}
|
||||
|
||||
cmd := strings.TrimSpace(line)
|
||||
if cmd == "help" {
|
||||
help()
|
||||
} else if cmd == "quit" {
|
||||
if editor.NeedSave() {
|
||||
fmt.Printf("there are changes that need to save, type 'quit!' to force quit\n")
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else if cmd == "quit!" {
|
||||
// quit directly
|
||||
break
|
||||
} else if cmd == "save" {
|
||||
err = editor.Save()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to save the changes: %s\n", err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("all segments saved to %s\n", srcFile)
|
||||
} else if strings.HasPrefix(cmd, "list") {
|
||||
var sErr error
|
||||
off, size, l := 0, 10, len("list")
|
||||
str := strings.TrimSpace(cmd)
|
||||
if len(str) > l {
|
||||
sets := rExp.Split(cmd, 3)
|
||||
switch len(sets) {
|
||||
case 2:
|
||||
_, sErr = fmt.Sscanf(cmd, "%s %d", &str, &off)
|
||||
case 3:
|
||||
_, sErr = fmt.Sscanf(cmd, "%s %d %d", &str, &off, &size)
|
||||
}
|
||||
}
|
||||
|
||||
if sErr != nil {
|
||||
fmt.Printf("failed to parse the offset and size: %s\n", sErr)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("+-slice(%d,%d): \n", off, size)
|
||||
for _, s := range editor.Slice(off, size) {
|
||||
fmt.Printf("%s\n", s)
|
||||
}
|
||||
} else if strings.HasPrefix(cmd, "put ") {
|
||||
seg := strings.TrimSpace(cmd[len("put "):])
|
||||
o, n, err := editor.Put(seg)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to Put(%s): %s\n", seg, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Put(%s): Ok, with %d deletes and %d additions\n", seg, o, n)
|
||||
} else if strings.HasPrefix(cmd, "put_file ") {
|
||||
file := strings.TrimSpace(cmd[len("put_file "):])
|
||||
o, n, err := editor.PutFile(file)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to PutFile(%s): %s\n", file, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("PutFile(%s): Ok, with %d deletes and %d additions\n", file, o, n)
|
||||
} else if len(cmd) > 0 {
|
||||
help()
|
||||
}
|
||||
}
|
||||
}
|
||||
import "github.com/lionsoul2014/ip2region/maker/golang/cmd"
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
printHelp()
|
||||
return
|
||||
}
|
||||
|
||||
// set the log flag
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||||
switch strings.ToLower(os.Args[1]) {
|
||||
case "gen":
|
||||
genDb()
|
||||
case "search":
|
||||
testSearch()
|
||||
case "bench":
|
||||
testBench()
|
||||
case "edit":
|
||||
edit()
|
||||
default:
|
||||
printHelp()
|
||||
}
|
||||
cmd.Execute()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue