
The big change is that the GeoJSON package has been completely rewritten to fix a few of geometry calculation bugs, increase performance, and to better follow the GeoJSON spec RFC 7946. GeoJSON updates - A LineString now requires at least two points. - All json members, even foreign, now persist with the object. - The bbox member persists too but is no longer used for geometry calculations. This is change in behavior. Previously Tile38 would treat the bbox as the object's physical rectangle. - Corrections to geometry intersects and within calculations. Faster spatial queries - The performance of Point-in-polygon and object intersect operations are greatly improved for complex polygons and line strings. It went from O(n) to roughly O(log n). - The same for all collection types with many children, including FeatureCollection, GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon. Codebase changes - The pkg directory has been renamed to internal - The GeoJSON internal package has been moved to a seperate repo at https://github.com/tidwall/geojson. It's now vendored. Please look out for higher memory usage for datasets using complex shapes. A complex shape is one that has 64 or more points. For these shapes it's expected that there will be increase of least 54 bytes per point.
374 lines
10 KiB
Go
374 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/tidwall/redbench"
|
|
"github.com/tidwall/tile38/core"
|
|
)
|
|
|
|
var (
|
|
hostname = "127.0.0.1"
|
|
port = 9851
|
|
clients = 50
|
|
requests = 100000
|
|
quiet = false
|
|
pipeline = 1
|
|
csv = false
|
|
json = false
|
|
tests = "PING,SET,GET,SEARCH,EVAL"
|
|
redis = false
|
|
)
|
|
|
|
var addr string
|
|
|
|
func showHelp() bool {
|
|
gitsha := ""
|
|
if core.GitSHA == "" || core.GitSHA == "0000000" {
|
|
gitsha = ""
|
|
} else {
|
|
gitsha = " (git:" + core.GitSHA + ")"
|
|
}
|
|
fmt.Fprintf(os.Stdout, "tile38-benchmark %s%s\n\n", core.Version, gitsha)
|
|
fmt.Fprintf(os.Stdout, "Usage: tile38-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests>]\n")
|
|
|
|
fmt.Fprintf(os.Stdout, " -h <hostname> Server hostname (default: %s)\n", hostname)
|
|
fmt.Fprintf(os.Stdout, " -p <port> Server port (default: %d)\n", port)
|
|
fmt.Fprintf(os.Stdout, " -c <clients> Number of parallel connections (default %d)\n", clients)
|
|
fmt.Fprintf(os.Stdout, " -n <requests> Total number or requests (default %d)\n", requests)
|
|
fmt.Fprintf(os.Stdout, " -q Quiet. Just show query/sec values\n")
|
|
fmt.Fprintf(os.Stdout, " -P <numreq> Pipeline <numreq> requests. Default 1 (no pipeline).\n")
|
|
fmt.Fprintf(os.Stdout, " -t <tests> Only run the comma separated list of tests. The test\n")
|
|
fmt.Fprintf(os.Stdout, " names are the same as the ones produced as output.\n")
|
|
fmt.Fprintf(os.Stdout, " --csv Output in CSV format.\n")
|
|
fmt.Fprintf(os.Stdout, " --json Request JSON responses (default is RESP output)\n")
|
|
fmt.Fprintf(os.Stdout, " --redis Runs against a Redis server\n")
|
|
fmt.Fprintf(os.Stdout, "\n")
|
|
return false
|
|
}
|
|
|
|
func parseArgs() bool {
|
|
defer func() {
|
|
if v := recover(); v != nil {
|
|
if v, ok := v.(string); ok && v == "bad arg" {
|
|
showHelp()
|
|
}
|
|
}
|
|
}()
|
|
|
|
args := os.Args[1:]
|
|
readArg := func(arg string) string {
|
|
if len(args) == 0 {
|
|
panic("bad arg")
|
|
}
|
|
var narg = args[0]
|
|
args = args[1:]
|
|
return narg
|
|
}
|
|
readIntArg := func(arg string) int {
|
|
n, err := strconv.ParseUint(readArg(arg), 10, 64)
|
|
if err != nil {
|
|
panic("bad arg")
|
|
}
|
|
return int(n)
|
|
}
|
|
badArg := func(arg string) bool {
|
|
fmt.Fprintf(os.Stderr, "Unrecognized option or bad number of args for: '%s'\n", arg)
|
|
return false
|
|
}
|
|
|
|
for len(args) > 0 {
|
|
arg := readArg("")
|
|
if arg == "--help" || arg == "-?" {
|
|
return showHelp()
|
|
}
|
|
if !strings.HasPrefix(arg, "-") {
|
|
args = append([]string{arg}, args...)
|
|
break
|
|
}
|
|
switch arg {
|
|
default:
|
|
return badArg(arg)
|
|
case "-h":
|
|
hostname = readArg(arg)
|
|
case "-p":
|
|
port = readIntArg(arg)
|
|
case "-c":
|
|
clients = readIntArg(arg)
|
|
if clients <= 0 {
|
|
clients = 1
|
|
}
|
|
case "-n":
|
|
requests = readIntArg(arg)
|
|
if requests <= 0 {
|
|
requests = 0
|
|
}
|
|
case "-q":
|
|
quiet = true
|
|
case "-P":
|
|
pipeline = readIntArg(arg)
|
|
if pipeline <= 0 {
|
|
pipeline = 1
|
|
}
|
|
case "-t":
|
|
tests = readArg(arg)
|
|
case "--csv":
|
|
csv = true
|
|
case "--json":
|
|
json = true
|
|
case "--redis":
|
|
redis = true
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func fillOpts() *redbench.Options {
|
|
opts := *redbench.DefaultOptions
|
|
opts.CSV = csv
|
|
opts.Clients = clients
|
|
opts.Pipeline = pipeline
|
|
opts.Quiet = quiet
|
|
opts.Requests = requests
|
|
opts.Stderr = os.Stderr
|
|
opts.Stdout = os.Stdout
|
|
return &opts
|
|
}
|
|
|
|
func randPoint() (lat, lon float64) {
|
|
return rand.Float64()*180 - 90, rand.Float64()*360 - 180
|
|
}
|
|
|
|
func randRect() (minlat, minlon, maxlat, maxlon float64) {
|
|
for {
|
|
minlat, minlon = randPoint()
|
|
maxlat, maxlon = minlat+1, minlon+1
|
|
if maxlat <= 180 && maxlon <= 180 {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
func prepFn(conn net.Conn) bool {
|
|
if json {
|
|
conn.Write([]byte("output json\r\n"))
|
|
resp := make([]byte, 100)
|
|
conn.Read(resp)
|
|
}
|
|
return true
|
|
}
|
|
func main() {
|
|
rand.Seed(time.Now().UnixNano())
|
|
if !parseArgs() {
|
|
return
|
|
}
|
|
opts := fillOpts()
|
|
addr = fmt.Sprintf("%s:%d", hostname, port)
|
|
for _, test := range strings.Split(tests, ",") {
|
|
switch strings.ToUpper(strings.TrimSpace(test)) {
|
|
case "PING":
|
|
redbench.Bench("PING", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
return redbench.AppendCommand(buf, "PING")
|
|
},
|
|
)
|
|
case "GEOADD":
|
|
//GEOADD key longitude latitude member
|
|
if redis {
|
|
var i int64
|
|
redbench.Bench("GEOADD", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
lat, lon := randPoint()
|
|
return redbench.AppendCommand(buf, "GEOADD", "key:bench",
|
|
strconv.FormatFloat(lat, 'f', 5, 64),
|
|
strconv.FormatFloat(lon, 'f', 5, 64),
|
|
"id:"+strconv.FormatInt(i, 10),
|
|
)
|
|
},
|
|
)
|
|
}
|
|
|
|
case "SET", "SET-POINT", "SET-RECT", "SET-STRING":
|
|
if redis {
|
|
redbench.Bench("SET", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
return redbench.AppendCommand(buf, "SET", "key:__rand_int__", "xxx")
|
|
},
|
|
)
|
|
} else {
|
|
var i int64
|
|
switch strings.ToUpper(strings.TrimSpace(test)) {
|
|
case "SET", "SET-POINT":
|
|
redbench.Bench("SET (point)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
lat, lon := randPoint()
|
|
return redbench.AppendCommand(buf, "SET", "key:bench", "id:"+strconv.FormatInt(i, 10), "POINT",
|
|
strconv.FormatFloat(lat, 'f', 5, 64),
|
|
strconv.FormatFloat(lon, 'f', 5, 64),
|
|
)
|
|
},
|
|
)
|
|
}
|
|
switch strings.ToUpper(strings.TrimSpace(test)) {
|
|
case "SET", "SET-RECT":
|
|
redbench.Bench("SET (rect)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
minlat, minlon, maxlat, maxlon := randRect()
|
|
return redbench.AppendCommand(buf, "SET", "key:bench", "id:"+strconv.FormatInt(i, 10), "BOUNDS",
|
|
strconv.FormatFloat(minlat, 'f', 5, 64),
|
|
strconv.FormatFloat(minlon, 'f', 5, 64),
|
|
strconv.FormatFloat(maxlat, 'f', 5, 64),
|
|
strconv.FormatFloat(maxlon, 'f', 5, 64),
|
|
)
|
|
},
|
|
)
|
|
}
|
|
switch strings.ToUpper(strings.TrimSpace(test)) {
|
|
case "SET", "SET-STRING":
|
|
redbench.Bench("SET (string)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
return redbench.AppendCommand(buf, "SET", "key:bench", "id:"+strconv.FormatInt(i, 10), "STRING", "xxx")
|
|
},
|
|
)
|
|
}
|
|
}
|
|
case "GET":
|
|
if redis {
|
|
redbench.Bench("GET", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
return redbench.AppendCommand(buf, "GET", "key:__rand_int__")
|
|
},
|
|
)
|
|
} else {
|
|
var i int64
|
|
redbench.Bench("GET (point)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
return redbench.AppendCommand(buf, "GET", "key:bench", "id:"+strconv.FormatInt(i, 10), "POINT")
|
|
},
|
|
)
|
|
redbench.Bench("GET (rect)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
return redbench.AppendCommand(buf, "GET", "key:bench", "id:"+strconv.FormatInt(i, 10), "BOUNDS")
|
|
},
|
|
)
|
|
redbench.Bench("GET (string)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
return redbench.AppendCommand(buf, "GET", "key:bench", "id:"+strconv.FormatInt(i, 10), "OBJECT")
|
|
},
|
|
)
|
|
}
|
|
case "SEARCH":
|
|
if !redis {
|
|
redbench.Bench("SEARCH (nearby 1km)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
lat, lon := randPoint()
|
|
return redbench.AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT",
|
|
strconv.FormatFloat(lat, 'f', 5, 64),
|
|
strconv.FormatFloat(lon, 'f', 5, 64),
|
|
"1000")
|
|
},
|
|
)
|
|
redbench.Bench("SEARCH (nearby 10km)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
lat, lon := randPoint()
|
|
return redbench.AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT",
|
|
strconv.FormatFloat(lat, 'f', 5, 64),
|
|
strconv.FormatFloat(lon, 'f', 5, 64),
|
|
"10000")
|
|
},
|
|
)
|
|
redbench.Bench("SEARCH (nearby 100km)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
lat, lon := randPoint()
|
|
return redbench.AppendCommand(buf, "NEARBY", "key:bench", "COUNT", "POINT",
|
|
strconv.FormatFloat(lat, 'f', 5, 64),
|
|
strconv.FormatFloat(lon, 'f', 5, 64),
|
|
"100000")
|
|
},
|
|
)
|
|
}
|
|
case "EVAL":
|
|
if !redis {
|
|
var i int64
|
|
get_script := "return tile38.call('GET', KEYS[1], ARGV[1], 'point')"
|
|
get4_script :=
|
|
"a = tile38.call('GET', KEYS[1], ARGV[1], 'point');" +
|
|
"b = tile38.call('GET', KEYS[1], ARGV[2], 'point');" +
|
|
"c = tile38.call('GET', KEYS[1], ARGV[3], 'point');" +
|
|
"d = tile38.call('GET', KEYS[1], ARGV[4], 'point');" +
|
|
"return d"
|
|
|
|
set_script := "return tile38.call('SET', KEYS[1], ARGV[1], 'point', ARGV[2], ARGV[3])"
|
|
if !opts.Quiet {
|
|
fmt.Println("Scripts to run:")
|
|
fmt.Println("GET SCRIPT: " + get_script)
|
|
fmt.Println("GET FOUR SCRIPT: " + get4_script)
|
|
fmt.Println("SET SCRIPT: " + set_script)
|
|
}
|
|
|
|
redbench.Bench("EVAL (set point)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
lat, lon := randPoint()
|
|
return redbench.AppendCommand(buf, "EVAL", set_script, "1",
|
|
"key:bench",
|
|
"id:"+strconv.FormatInt(i, 10),
|
|
strconv.FormatFloat(lat, 'f', 5, 64),
|
|
strconv.FormatFloat(lon, 'f', 5, 64),
|
|
)
|
|
},
|
|
)
|
|
redbench.Bench("EVALNA (set point)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
lat, lon := randPoint()
|
|
return redbench.AppendCommand(buf, "EVALNA", set_script, "1",
|
|
"key:bench",
|
|
"id:"+strconv.FormatInt(i, 10),
|
|
strconv.FormatFloat(lat, 'f', 5, 64),
|
|
strconv.FormatFloat(lon, 'f', 5, 64),
|
|
)
|
|
},
|
|
)
|
|
redbench.Bench("EVALRO (get point)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
return redbench.AppendCommand(buf, "EVALRO", get_script, "1", "key:bench", "id:"+strconv.FormatInt(i, 10))
|
|
},
|
|
)
|
|
redbench.Bench("EVALRO (get 4 points)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
return redbench.AppendCommand(buf, "EVALRO", get4_script, "1",
|
|
"key:bench",
|
|
"id:"+strconv.FormatInt(i, 10),
|
|
"id:"+strconv.FormatInt(i+1, 10),
|
|
"id:"+strconv.FormatInt(i+2, 10),
|
|
"id:"+strconv.FormatInt(i+3, 10),
|
|
)
|
|
},
|
|
)
|
|
redbench.Bench("EVALNA (get point)", addr, opts, prepFn,
|
|
func(buf []byte) []byte {
|
|
i := atomic.AddInt64(&i, 1)
|
|
return redbench.AppendCommand(buf, "EVALNA", get_script, "1", "key:bench", "id:"+strconv.FormatInt(i, 10))
|
|
},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|