Commit 7f22bba6 authored by Kirill Smelkov's avatar Kirill Smelkov

X zwrk: New tool to simulate paralell load from multiple clients

Similarly to wrk on HTTP.

Rationale: simulating multiple clients is:

1. noisy - the timings from run to run are changing sometimes up to 50%
2. with significant additional overhead - there are constant OS-level
   process switches in between client processes and this prevents to
   actually create the load.
3. the above load from "2" actually takes resources from the server in
   localhost case.

So let's switch to simlating many requests in lightweight way similarly
to how it is done in wrk - in one process and not so many threads (it
can be just 1) with many connections opened to server and epolly way to
load it with Go providing epoll-goroutine matching.
parent c86ba1b0
/log /log
/var /var
/zhash
/zhash_go
/tcpu /tcpu
/tcpu_go /tcpu_go
/tzodb
/tzodb_go
/ioping.tmp /ioping.tmp
...@@ -432,7 +432,7 @@ GENfs() { ...@@ -432,7 +432,7 @@ GENfs() {
# remember correct hash to later check in benchmarks # remember correct hash to later check in benchmarks
# crc32:1552c530 ; oid=0..2127 nread=8534126 t=0.033s (15.7μs / object) x=zhash.py # crc32:1552c530 ; oid=0..2127 nread=8534126 t=0.033s (15.7μs / object) x=zhash.py
zhash.py --$zhashfunc $fs1/data.fs |awk '{print $1}' >$ds/zhash.ok tzodb.py zhash --$zhashfunc $fs1/data.fs |awk '{print $1}' >$ds/zhash.ok
} }
# generate data in sqlite # generate data in sqlite
...@@ -860,8 +860,9 @@ cpustat() { ...@@ -860,8 +860,9 @@ cpustat() {
return $ret return $ret
} }
Nrun=5 # repeat benchmarks N time Nrun=5 # repeat benchmarks N time
Npar=16 # run so many parallel clients in parallel phase Nparv="1 2 4 8 16" # run parallel zwrk benchmarks with so many clients (XXX +6, +12 ?)
#Npar=16 # run so many parallel clients in parallel phase
#profile= #profile=
profile=cpustat profile=cpustat
...@@ -874,6 +875,7 @@ nrun() { ...@@ -874,6 +875,7 @@ nrun() {
} }
# nrunpar ... - run $Npar ... instances in parallel and wait for completion # nrunpar ... - run $Npar ... instances in parallel and wait for completion
# XXX running processes in parallel is deprecated in favour of zwrk.
nrunpar() { nrunpar() {
$profile _nrunpar "$@" $profile _nrunpar "$@"
} }
...@@ -1047,9 +1049,10 @@ zbench() { ...@@ -1047,9 +1049,10 @@ zbench() {
zhashok=$3 zhashok=$3
# nrun time demo-zbigarray read $url # nrun time demo-zbigarray read $url
nrun zhash.py --check=$zhashok --bench=$topic/%s --$zhashfunc $url nrun tzodb.py zhash --check=$zhashok --bench=$topic/%s --$zhashfunc $url
echo -e "\n# ${Npar} clients in parallel" # XXX running processes in parallel is deprecated in favour of zwrk.
nrunpar zhash.py --check=$zhashok --bench=$topic/%s-P$Npar --$zhashfunc $url # echo -e "\n# ${Npar} clients in parallel"
# nrunpar tzodb.py zhash --check=$zhashok --bench=$topic/%s-P$Npar --$zhashfunc $url
echo echo
zbench_go $url $topic $zhashok zbench_go $url $topic $zhashok
} }
...@@ -1059,11 +1062,17 @@ zbench_go() { ...@@ -1059,11 +1062,17 @@ zbench_go() {
url=$1 url=$1
topic=$2 topic=$2
zhashok=$3 zhashok=$3
nrun zhash_go -check=$zhashok --bench=$topic/%s --log_dir=$log -$zhashfunc $url nrun tzodb_go -log_dir=$log zhash -check=$zhashok -bench=$topic/%s -$zhashfunc $url
nrun zhash_go -check=$zhashok --bench=$topic/%s --log_dir=$log -$zhashfunc -useprefetch $url nrun tzodb_go -log_dir=$log zhash -check=$zhashok -bench=$topic/%s -$zhashfunc -useprefetch $url
echo -e "\n# ${Npar} clients in parallel" # XXX running processes in parallel is deprecated in favour of zwrk.
nrunpar zhash_go -check=$zhashok --bench=$topic/%s-P$Npar --log_dir=$log -$zhashfunc $url # echo -e "\n# ${Npar} clients in parallel"
# nrunpar tzodb_go -log_dir=$log zhash -check=$zhashok -bench=$topic/%s-P$Npar -$zhashfunc $url
for i in ${Nparv}; do
echo -e "\n# $i clients in parallel"
nrun tzodb_go -log_dir=$log zwrk -nclient $i -check=$zhashok -bench=$topic/%s -$zhashfunc $url
done
} }
...@@ -1402,15 +1411,15 @@ cpustat) ...@@ -1402,15 +1411,15 @@ cpustat)
;; ;;
esac esac
# make sure zhash*, tcpu* and zgenprod are on PATH (because we could be invoked from another dir) # make sure tzodb*, tcpu* and zgenprod are on PATH (because we could be invoked from another dir)
X=$(cd `dirname $0` && pwd) X=$(cd `dirname $0` && pwd)
export PATH=$X:$PATH export PATH=$X:$PATH
# rebuild go bits # rebuild go bits
# neo/py, wendelin.core, ... - must be pip install'ed - `neotest deploy` cares about that # neo/py, wendelin.core, ... - must be pip install'ed - `neotest deploy` cares about that
go install -v lab.nexedi.com/kirr/neo/go/... go install -v lab.nexedi.com/kirr/neo/go/...
go build -o $X/zhash_go $X/zhash.go go build -o $X/tzodb_go $X/tzodb.go
#go build -race -o $X/zhash_go $X/zhash.go #go build -race -o $X/tzodb_go $X/tzodb.go
go build -o $X/tcpu_go $X/tcpu.go go build -o $X/tcpu_go $X/tcpu.go
# setup network & fs environment # setup network & fs environment
......
...@@ -69,6 +69,8 @@ func prettyarg(arg string) string { ...@@ -69,6 +69,8 @@ func prettyarg(arg string) string {
// benchit runs the benchmark for benchf // benchit runs the benchmark for benchf
func benchit(benchname string, bencharg string, benchf func(*testing.B, string)) { func benchit(benchname string, bencharg string, benchf func(*testing.B, string)) {
// FIXME testing.Benchmark does not allow to detect whether benchmark failed.
// (use log.Fatal, not {t,b}.Fatal as workaround)
r := testing.Benchmark(func (b *testing.B) { r := testing.Benchmark(func (b *testing.B) {
benchf(b, bencharg) benchf(b, bencharg)
}) })
...@@ -86,7 +88,7 @@ func benchit(benchname string, bencharg string, benchf func(*testing.B, string)) ...@@ -86,7 +88,7 @@ func benchit(benchname string, bencharg string, benchf func(*testing.B, string))
func benchHash(b *testing.B, h hash.Hash, arg string) { func benchHash(b *testing.B, h hash.Hash, arg string) {
blksize, err := strconv.Atoi(arg) blksize, err := strconv.Atoi(arg)
if err != nil { if err != nil {
b.Fatal(err) log.Fatal(err)
} }
data := make([]byte, blksize) data := make([]byte, blksize)
...@@ -101,23 +103,23 @@ func BenchmarkAdler32(b *testing.B, arg string) { benchHash(b, adler32.New(), ar ...@@ -101,23 +103,23 @@ func BenchmarkAdler32(b *testing.B, arg string) { benchHash(b, adler32.New(), ar
func BenchmarkCrc32(b *testing.B, arg string) { benchHash(b, crc32.NewIEEE(), arg) } func BenchmarkCrc32(b *testing.B, arg string) { benchHash(b, crc32.NewIEEE(), arg) }
func BenchmarkSha1(b *testing.B, arg string) { benchHash(b, sha1.New(), arg) } func BenchmarkSha1(b *testing.B, arg string) { benchHash(b, sha1.New(), arg) }
func xreadfile(t testing.TB, path string) []byte { func xreadfile(path string) []byte {
data, err := ioutil.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
t.Fatal(err) log.Fatal(err)
} }
return data return data
} }
func BenchmarkUnzlib(b *testing.B, zfile string) { func BenchmarkUnzlib(b *testing.B, zfile string) {
zdata := xreadfile(b, fmt.Sprintf("testdata/zlib/%s", zfile)) zdata := xreadfile(fmt.Sprintf("testdata/zlib/%s", zfile))
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := xzlib.Decompress(zdata) _, err := xzlib.Decompress(zdata)
if err != nil { if err != nil {
b.Fatal(err) log.Fatal(err)
} }
} }
} }
......
...@@ -24,7 +24,7 @@ from __future__ import print_function ...@@ -24,7 +24,7 @@ from __future__ import print_function
import sys import sys
import hashlib import hashlib
import zhash import tzodb
import zlib import zlib
from time import time from time import time
from math import ceil, log10 from math import ceil, log10
...@@ -125,8 +125,8 @@ def _bench_hasher(b, h, blksize): ...@@ -125,8 +125,8 @@ def _bench_hasher(b, h, blksize):
i += 1 i += 1
def bench_adler32(b, blksize): _bench_hasher(b, zhash.Adler32Hasher(), blksize) def bench_adler32(b, blksize): _bench_hasher(b, tzodb.Adler32Hasher(), blksize)
def bench_crc32(b, blksize): _bench_hasher(b, zhash.CRC32Hasher(), blksize) def bench_crc32(b, blksize): _bench_hasher(b, tzodb.CRC32Hasher(), blksize)
def bench_sha1(b, blksize): _bench_hasher(b, hashlib.sha1(), blksize) def bench_sha1(b, blksize): _bench_hasher(b, hashlib.sha1(), blksize)
......
This diff is collapsed.
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2017 Nexedi SA and Contributors. # Copyright (C) 2017-2018 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com> # Kirill Smelkov <kirr@nexedi.com>
# #
# This program is free software: you can Use, Study, Modify and Redistribute # This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your # it under the terms of the GNU General Public License version 3, or (at your
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
# #
# See COPYING file for full licensing terms. # See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options. # See https://www.nexedi.com/licensing for rationale and options.
"""zhash - compute hash of whole latest objects stream in a ZODB database""" """tzodb - ZODB-related benchmarks"""
from __future__ import print_function from __future__ import print_function
...@@ -81,7 +81,7 @@ hashRegistry = { ...@@ -81,7 +81,7 @@ hashRegistry = {
def usage(w): def usage(w):
print(\ print(\
"""Usage: zhash [options] url """Usage: tzodb zhash [options] url
options: options:
...@@ -97,9 +97,14 @@ options: ...@@ -97,9 +97,14 @@ options:
--bench=<topic> use benchmarking format for output --bench=<topic> use benchmarking format for output
""", file=w) """, file=w)
def main(): def zhash():
"""zhash - compute hash of whole latest objects stream in a ZODB database"""
if len(sys.argv) < 2 or sys.argv[1] != "zhash":
usage(sys.stderr)
exit(1)
try: try:
optv, argv = getopt(sys.argv[1:], "h", ["help", "check=", "bench="] + hashRegistry.keys()) optv, argv = getopt(sys.argv[2:], "h", ["help", "check=", "bench="] + hashRegistry.keys())
except GetoptError as e: except GetoptError as e:
print("E: %s" % e, file=sys.stderr) print("E: %s" % e, file=sys.stderr)
usage(sys.stderr) usage(sys.stderr)
...@@ -164,7 +169,7 @@ def main(): ...@@ -164,7 +169,7 @@ def main():
x = "zhash.py" x = "zhash.py"
hresult = "%s:%s" % (h.name, h.hexdigest()) hresult = "%s:%s" % (h.name, h.hexdigest())
if bench is None: if bench is None:
print('%s ; oid=0..%d nread=%d t=%.3fs (%.1fμs / object) x=%s' % \ print('%s ; oid=0..%d nread=%d t=%.3fs (%.1fµs / object) x=%s' % \
(hresult, oid-1, nread, dt, dt * 1E6 / oid, x)) (hresult, oid-1, nread, dt, dt * 1E6 / oid, x))
else: else:
topic = bench % x topic = bench % x
...@@ -175,5 +180,9 @@ def main(): ...@@ -175,5 +180,9 @@ def main():
print("%s: hash mismatch: expected %s ; got %s\t# x=%s" % (url, check, hresult, x), file=sys.stderr) print("%s: hash mismatch: expected %s ; got %s\t# x=%s" % (url, check, hresult, x), file=sys.stderr)
sys.exit(1) sys.exit(1)
def main():
zhash() # XXX stub
if __name__ == '__main__': if __name__ == '__main__':
main() main()
...@@ -32,6 +32,7 @@ import ( ...@@ -32,6 +32,7 @@ import (
// OpenOptions describes options for OpenStorage // OpenOptions describes options for OpenStorage
type OpenOptions struct { type OpenOptions struct {
ReadOnly bool // whether to open storage as read-only ReadOnly bool // whether to open storage as read-only
NoCache bool // don't use cache for read/write operations
} }
// DriverOpener is a function to open a storage driver // DriverOpener is a function to open a storage driver
...@@ -69,6 +70,7 @@ func OpenStorage(ctx context.Context, storageURL string, opt *OpenOptions) (ISto ...@@ -69,6 +70,7 @@ func OpenStorage(ctx context.Context, storageURL string, opt *OpenOptions) (ISto
// XXX commonly handle some options from url -> opt? // XXX commonly handle some options from url -> opt?
// (e.g. ?readonly=1 -> opt.ReadOnly=true + remove ?readonly=1 from URL) // (e.g. ?readonly=1 -> opt.ReadOnly=true + remove ?readonly=1 from URL)
// ----//---- nocache
opener, ok := driverRegistry[u.Scheme] opener, ok := driverRegistry[u.Scheme]
if !ok { if !ok {
...@@ -80,12 +82,16 @@ func OpenStorage(ctx context.Context, storageURL string, opt *OpenOptions) (ISto ...@@ -80,12 +82,16 @@ func OpenStorage(ctx context.Context, storageURL string, opt *OpenOptions) (ISto
return nil, err return nil, err
} }
return &storage{ var cache *Cache
IStorageDriver: storDriver, if !opt.NoCache {
// small cache so that prefetch can work for loading // small cache so that prefetch can work for loading
// XXX 512K hardcoded (= ~ 128 · 4K-entries) // XXX 512K hardcoded (= ~ 128 · 4K-entries)
l1cache: NewCache(storDriver, 128 * 4*1024), cache = NewCache(storDriver, 128 * 4*1024)
}
return &storage{
IStorageDriver: storDriver,
l1cache: cache,
}, nil }, nil
} }
...@@ -97,7 +103,7 @@ func OpenStorage(ctx context.Context, storageURL string, opt *OpenOptions) (ISto ...@@ -97,7 +103,7 @@ func OpenStorage(ctx context.Context, storageURL string, opt *OpenOptions) (ISto
// and other storage-independed higher-level functionality. // and other storage-independed higher-level functionality.
type storage struct { type storage struct {
IStorageDriver IStorageDriver
l1cache *Cache l1cache *Cache // can be =nil, if opened with NoCache
} }
...@@ -106,9 +112,15 @@ type storage struct { ...@@ -106,9 +112,15 @@ type storage struct {
func (s *storage) Load(ctx context.Context, xid Xid) (*mem.Buf, Tid, error) { func (s *storage) Load(ctx context.Context, xid Xid) (*mem.Buf, Tid, error) {
// XXX here: offload xid validation from cache and driver ? // XXX here: offload xid validation from cache and driver ?
// XXX here: offload wrapping err -> OpError{"load", err} ? // XXX here: offload wrapping err -> OpError{"load", err} ?
return s.l1cache.Load(ctx, xid) if s.l1cache != nil {
return s.l1cache.Load(ctx, xid)
} else {
return s.IStorageDriver.Load(ctx, xid)
}
} }
func (s *storage) Prefetch(ctx context.Context, xid Xid) { func (s *storage) Prefetch(ctx context.Context, xid Xid) {
s.l1cache.Prefetch(ctx, xid) if s.l1cache != nil {
s.l1cache.Prefetch(ctx, xid)
}
} }
...@@ -179,6 +179,7 @@ type IStorage interface { ...@@ -179,6 +179,7 @@ type IStorage interface {
// started, to complete. // started, to complete.
// //
// Prefetch does not return any error. // Prefetch does not return any error.
// Prefetch is noop if storage was opened with NoCache option.
Prefetch(ctx context.Context, xid Xid) Prefetch(ctx context.Context, xid Xid)
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment