remove creator/bagent/bspawn

This commit is contained in:
Matt Jaffee 2017-02-09 16:51:45 -06:00
parent 64db5e0f23
commit 77372fe3cc
31 changed files with 0 additions and 2825 deletions

View file

@ -1,30 +0,0 @@
The following applies to the files errgroup.go and errgroup_test.go
in the same directory this file resides:
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,41 +0,0 @@
package bench
import "context"
// Benchmark is an interface to guide the creation of new pilosa benchmarks or
// benchmark components. It defines 2 methods, Init, and Run. These are separate
// methods so that benchmark running code can time only the running of the
// benchmark, and not any setup.
type Benchmark interface {
// Init takes a list of hosts and an agent number. It is generally expected
// to set up a connection to pilosa using whatever client it chooses. The
// agentNum should be used to parameterize the benchmark's configuration if
// it is being run simultaneously on multiple "agents". E.G. the agentNum
// might be used to make a random seed different for each agent, or have
// each agent set a different set of bits. Init's doc string should document
// how the agentNum affects it. Every benchmark should have a 'Name' field
// set by init, which appears when the benchmark is marshalled to json as
// "name".
Init(hosts []string, agentNum int) error
// Run runs the benchmark. The return value of Run is kept generic so that
// any relevant statistics or metrics that may be specific to the benchmark
// in question can be reported. TODO guidelines for what gets included in
// results and what will get added by other stuff. Run does not need to
// report total run time in `results`, as that will be added by calling
// code.
Run(ctx context.Context) map[string]interface{}
}
// Command extends Benchmark by adding methods for configuring via command line flags and returning usage information.
type Command interface {
Benchmark
// ConsumeFlags sets and parses flags, and then returns flagSet.Args(). This
// is so that multiple benchmarks can be specified at the command line.
ConsumeFlags(args []string) ([]string, error)
// Usage returns information on how to use this benchmark. The usage string
// should explain how the agent num affects the benchmark's operation.
Usage() string
}

View file

@ -1,67 +0,0 @@
package bench
import (
"context"
"errors"
"fmt"
"github.com/pilosa/pilosa"
)
func firstHostClient(hosts []string) (*pilosa.Client, error) {
client, err := pilosa.NewClient(hosts[0])
if err != nil {
return nil, err
}
return client, nil
}
func roundRobinClient(hosts []string, agentNum int) (*pilosa.Client, error) {
clientNum := agentNum % len(hosts)
return firstHostClient(hosts[clientNum:])
}
// HasClient provides a reusable component for Benchmark implementations which
// provides the Init method, a ClientType argument and a cli internal variable.
type HasClient struct {
client *pilosa.Client
ClientType string `json:"client-type"`
ContentType string `json:"content-type"`
}
// Init for HasClient looks at the ClientType field and creates a pilosa client
// either using the first host in the list of hosts or based on the agent
// number mod len(hosts)
func (h *HasClient) Init(hosts []string, agentNum int) error {
var err error
switch h.ClientType {
case "single":
h.client, err = firstHostClient(hosts)
case "round_robin":
h.client, err = roundRobinClient(hosts, agentNum)
default:
err = fmt.Errorf("Unsupported ClientType: %v", h.ClientType)
}
if err != nil {
return err
}
switch h.ContentType {
case "protobuf":
return nil
case "pql":
return nil
default:
return fmt.Errorf("Unsupported ContentType: %v", h.ContentType)
}
}
func (h *HasClient) ExecuteQuery(contentType, db, query string, ctx context.Context) (interface{}, error) {
if contentType == "protobuf" {
return h.client.ExecuteQuery(ctx, db, query, true)
} else if contentType == "pql" {
return h.client.ExecutePQL(ctx, db, query)
} else {
return nil, errors.New("unsupport content type")
}
}

View file

@ -1,105 +0,0 @@
package bench
import (
"fmt"
"flag"
"io/ioutil"
"context"
"time"
)
// DiagonalSetBits sets bits with increasing profile id and bitmap id.
type DiagonalSetBits struct {
HasClient
Name string `json:"name"`
BaseBitmapID int `json:"base-bitmap-id"`
BaseProfileID int `json:"base-profile-id"`
Iterations int `json:"iterations"`
DB string `json:"db"`
}
// Init sets up the pilosa client and modifies the configured values based on
// the agent num.
func (b *DiagonalSetBits) Init(hosts []string, agentNum int) error {
b.Name = "diagonal-set-bits"
b.BaseBitmapID = b.BaseBitmapID + (agentNum * b.Iterations)
b.BaseProfileID = b.BaseProfileID + (agentNum * b.Iterations)
return b.HasClient.Init(hosts, agentNum)
}
// Usage returns the usage message to be printed.
func (b *DiagonalSetBits) Usage() string {
return `
diagonal-set-bits sets bits with increasing profile id and bitmap id.
Agent num offsets both the base profile id and base bitmap id by the number of
iterations, so that only bits on the main diagonal are set, and agents don't
overlap at all.
Usage: diagonal-set-bits [arguments]
The following arguments are available:
-base-bitmap-id int
bits being set will all be greater than BaseBitmapID
-base-profile-id int
profile id num to start from
-iterations int
number of bits to set
-db string
pilosa db to use
-client-type string
Can be 'single' (all agents hitting one host) or 'round_robin'
-content-type string
protobuf or pql
`[1:]
}
// ConsumeFlags parses all flags up to the next non flag argument (argument does
// not start with "-" and isn't the value of a flag). It returns the remaining
// args.
func (b *DiagonalSetBits) ConsumeFlags(args []string) ([]string, error) {
fs := flag.NewFlagSet("DiagonalSetBits", flag.ContinueOnError)
fs.SetOutput(ioutil.Discard)
fs.IntVar(&b.BaseBitmapID, "base-bitmap-id", 0, "")
fs.IntVar(&b.BaseProfileID, "base-profile-id", 0, "")
fs.IntVar(&b.Iterations, "iterations", 100, "")
fs.StringVar(&b.DB, "db", "benchdb", "")
fs.StringVar(&b.ClientType, "client-type", "single", "")
fs.StringVar(&b.ContentType, "content-type", "protobuf", "")
if err := fs.Parse(args); err != nil {
return nil, err
}
return fs.Args(), nil
}
// Run runs the DiagonalSetBits benchmark
func (b *DiagonalSetBits) Run(ctx context.Context) map[string]interface{} {
results := make(map[string]interface{})
if b.client == nil {
results["error"] = fmt.Errorf("No client set for DiagonalSetBits")
return results
}
s := NewStats()
var start time.Time
for n := 0; n < b.Iterations; n++ {
query := fmt.Sprintf("SetBit(%d, 'frame.n', %d)", b.BaseBitmapID+n, b.BaseProfileID+n)
start = time.Now()
_, err := b.client.ExecuteQuery(ctx, b.DB, query, true)
if err != nil {
results["error"] = err
return results
}
s.Add(time.Now().Sub(start))
}
AddToResults(s, results)
return results
}

View file

@ -1,39 +0,0 @@
// bench contains benchmarks and common utilities useful to benchmarks
//
// In order to write new benchmarks, one must satisfy the Benchmark and Command
// interfaces in bench.go. In order to use the benchmark from pilosactl, it
// needs to be wired in in two places. The first is BagentCommand.ParseFlags,
// where a case statement needs to be added, and the second is just adding the
// benchmark to the BagentCommand.Usage usage string.
//
// When writing a new benchmark, there are a few things to keep in mind other
// than just implementing the interface:
//
// 1. The benchmark should modify its own configuration in its Init method based
// on the agentNum it is given. How it modifies is specific to the benchmark,
// but the idea is that it should make sense to call the benchmark with the same
// configuration, but multiple different agent numbers, and it should do useful
// work each time (i.e. not just setting the same bits, or running the same
// queries).
//
// 2. The Init method should do everything that needs to be done to get the
// benchmark to a runnable state - all code in run should be the stuff that we
// actually want to time.
//
// 3. The Run method does not need to report the total runtime - that is collected
// by calling code.
//
// 4. Usage should follow the format in other benchmarks, and explain how the
// benchmark uses agentNum to modify its behavior
//
//
// Files:
//
// 1. client.go contains pilosa client code which is shared by many benchmarks
//
// 2. errgroup.go contains the ErrGroup implementation copied from golang.org/x/
// so as not to pull in a bunch of useless deps.
//
// 3. stats.go contains useful code for gathering stats about a series of timed
// operations.
package bench

View file

@ -1,67 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package errgroup provides synchronization, error propagation, and Context
// cancelation for groups of goroutines working on subtasks of a common task.
package bench
import (
"sync"
"context"
)
// An ErrGroup is a collection of goroutines working on subtasks that are part of
// the same overall task.
//
// A zero ErrGroup is valid and does not cancel on error.
type ErrGroup struct {
cancel func()
wg sync.WaitGroup
errOnce sync.Once
err error
}
// WithContext returns a new ErrGroup and an associated Context derived from ctx.
//
// The derived Context is canceled the first time a function passed to Go
// returns a non-nil error or the first time Wait returns, whichever occurs
// first.
func WithContext(ctx context.Context) (*ErrGroup, context.Context) {
ctx, cancel := context.WithCancel(ctx)
return &ErrGroup{cancel: cancel}, ctx
}
// Wait blocks until all function calls from the Go method have returned, then
// returns the first non-nil error (if any) from them.
func (g *ErrGroup) Wait() error {
g.wg.Wait()
if g.cancel != nil {
g.cancel()
}
return g.err
}
// Go calls the given function in a new goroutine.
//
// The first call to return a non-nil error cancels the group; its error will be
// returned by Wait.
func (g *ErrGroup) Go(f func() error) {
g.wg.Add(1)
go func() {
defer g.wg.Done()
if err := f(); err != nil {
g.errOnce.Do(func() {
g.err = err
if g.cancel != nil {
g.cancel()
}
})
}
}()
}

View file

@ -1,176 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package bench_test
import (
"errors"
"fmt"
"net/http"
"os"
"testing"
"context"
"github.com/pilosa/pilosa/bench"
)
var (
Web = fakeSearch("web")
Image = fakeSearch("image")
Video = fakeSearch("video")
)
type Result string
type Search func(ctx context.Context, query string) (Result, error)
func fakeSearch(kind string) Search {
return func(_ context.Context, query string) (Result, error) {
return Result(fmt.Sprintf("%s result for %q", kind, query)), nil
}
}
// JustErrors illustrates the use of a ErrGroup in place of a sync.WaitGroup to
// simplify goroutine counting and error handling. This example is derived from
// the sync.WaitGroup example at https://golang.org/pkg/sync/#example_WaitGroup.
func ExampleGroup_justErrors() {
var g bench.ErrGroup
var urls = []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
}
for _, url := range urls {
// Launch a goroutine to fetch the URL.
url := url // https://golang.org/doc/faq#closures_and_goroutines
g.Go(func() error {
// Fetch the URL.
resp, err := http.Get(url)
if err == nil {
resp.Body.Close()
}
return err
})
}
// Wait for all HTTP fetches to complete.
if err := g.Wait(); err == nil {
fmt.Println("Successfully fetched all URLs.")
}
}
// Parallel illustrates the use of a ErrGroup for synchronizing a simple parallel
// task: the "Google Search 2.0" function from
// https://talks.golang.org/2012/concurrency.slide#46, augmented with a Context
// and error-handling.
func ExampleGroup_parallel() {
Google := func(ctx context.Context, query string) ([]Result, error) {
g, ctx := bench.WithContext(ctx)
searches := []Search{Web, Image, Video}
results := make([]Result, len(searches))
for i, search := range searches {
i, search := i, search // https://golang.org/doc/faq#closures_and_goroutines
g.Go(func() error {
result, err := search(ctx, query)
if err == nil {
results[i] = result
}
return err
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
results, err := Google(context.Background(), "golang")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
for _, result := range results {
fmt.Println(result)
}
// Output:
// web result for "golang"
// image result for "golang"
// video result for "golang"
}
func TestZeroGroup(t *testing.T) {
err1 := errors.New("errgroup_test: 1")
err2 := errors.New("errgroup_test: 2")
cases := []struct {
errs []error
}{
{errs: []error{}},
{errs: []error{nil}},
{errs: []error{err1}},
{errs: []error{err1, nil}},
{errs: []error{err1, nil, err2}},
}
for _, tc := range cases {
var g bench.ErrGroup
var firstErr error
for i, err := range tc.errs {
err := err
g.Go(func() error { return err })
if firstErr == nil && err != nil {
firstErr = err
}
if gErr := g.Wait(); gErr != firstErr {
t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+
"g.Wait() = %v; want %v",
g, tc.errs[:i+1], err, firstErr)
}
}
}
}
func TestWithContext(t *testing.T) {
errDoom := errors.New("group_test: doomed")
cases := []struct {
errs []error
want error
}{
{want: nil},
{errs: []error{nil}, want: nil},
{errs: []error{errDoom}, want: errDoom},
{errs: []error{errDoom, nil}, want: errDoom},
}
for _, tc := range cases {
g, ctx := bench.WithContext(context.Background())
for _, err := range tc.errs {
err := err
g.Go(func() error { return err })
}
if err := g.Wait(); err != tc.want {
t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+
"g.Wait() = %v; want %v",
g, tc.errs, err, tc.want)
}
canceled := false
select {
case <-ctx.Done():
canceled = true
default:
}
if !canceled {
t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+
"ctx.Done() was not closed",
g, tc.errs)
}
}
}

View file

@ -1,203 +0,0 @@
package bench
import (
"context"
"flag"
"fmt"
"io"
"io/ioutil"
"math/rand"
"sort"
"github.com/pilosa/pilosa/pilosactl"
)
// NewImport returns an Import Benchmark which pilosactl importer configured.
func NewImport(stdin io.Reader, stdout, stderr io.Writer) *Import {
return &Import{
ImportCommand: pilosactl.NewImportCommand(stdin, stdout, stderr),
}
}
// Import sets bits with increasing profile id and bitmap id.
type Import struct {
Name string `json:"name"`
BaseBitmapID int64 `json:"base-bitmap-id"`
MaxBitmapID int64 `json:"max-bitmap-id"`
BaseProfileID int64 `json:"base-profile-id"`
MaxProfileID int64 `json:"max-profile-id"`
RandomBitmapOrder bool `json:"random-bitmap-order"`
MinBitsPerMap int64 `json:"min-bits-per-map"`
MaxBitsPerMap int64 `json:"max-bits-per-map"`
AgentControls string `json:"agent-controls"`
Seed int64 `json:"seed"`
numbits int
*pilosactl.ImportCommand
}
// Usage returns the usage message to be printed.
func (b *Import) Usage() string {
return `
import generates an import file and imports using pilosa's bulk import interface
Agent num can have various effects - see -agent-controls flag.
Usage: import [arguments]
The following arguments are available:
-base-bitmap-id int
bits being set will all be greater than this
-max-bitmap-id int
bits being set will all be less than this
-base-profile-id int
profile id num to start from
-max-profile-id int
maximum profile id to generate
-random-bitmap-order
if this option is set, the import file will not be sorted by bitmap id
-min-bits-per-map int
minimum number of bits set per bitmap
-max-bits-per-map int
maximum number of bits set per bitmap
-agent-controls string
can be 'height', 'width', or empty (TODO or square?)- increasing
number of agents modulates bitmap id range, profile id range,
or just sets more bits in the same range.
-seed int
seed for RNG
-db string
pilosa db to use
-frame string
frame to import into
`[1:]
}
// ConsumeFlags parses all flags up to the next non flag argument (argument does
// not start with "-" and isn't the value of a flag). It returns the remaining
// args.
func (b *Import) ConsumeFlags(args []string) ([]string, error) {
fs := flag.NewFlagSet("Import", flag.ContinueOnError)
fs.SetOutput(ioutil.Discard)
fs.Int64Var(&b.BaseBitmapID, "base-bitmap-id", 0, "")
fs.Int64Var(&b.MaxBitmapID, "max-bitmap-id", 1000, "")
fs.Int64Var(&b.BaseProfileID, "base-profile-id", 0, "")
fs.Int64Var(&b.MaxProfileID, "max-profile-id", 1000, "")
fs.BoolVar(&b.RandomBitmapOrder, "random-bitmap-order", false, "")
fs.Int64Var(&b.MinBitsPerMap, "min-bits-per-map", 0, "")
fs.Int64Var(&b.MaxBitsPerMap, "max-bits-per-map", 10, "")
fs.StringVar(&b.AgentControls, "agent-controls", "", "")
fs.Int64Var(&b.Seed, "seed", 0, "")
fs.StringVar(&b.Database, "db", "benchdb", "")
fs.StringVar(&b.Frame, "frame", "testframe", "")
fs.IntVar(&b.BufferSize, "buffer-size", 10000000, "")
if err := fs.Parse(args); err != nil {
return nil, err
}
return fs.Args(), nil
}
// Init generates import data based on the agent num and fields of 'b'.
func (b *Import) Init(hosts []string, agentNum int) error {
if len(hosts) == 0 {
return fmt.Errorf("Need at least one host")
}
b.Name = "import"
b.Host = hosts[0]
// generate csv data
b.Seed = b.Seed + int64(agentNum)
switch b.AgentControls {
case "height":
numBitmapIDs := (b.MaxBitmapID - b.BaseBitmapID)
b.BaseBitmapID = b.BaseBitmapID + (numBitmapIDs * int64(agentNum))
b.MaxBitmapID = b.BaseBitmapID + numBitmapIDs
case "width":
numProfileIDs := (b.MaxProfileID - b.BaseProfileID)
b.BaseProfileID = b.BaseProfileID + (numProfileIDs * int64(agentNum))
b.MaxProfileID = b.BaseProfileID + numProfileIDs
case "":
break
default:
return fmt.Errorf("agent-controls: '%v' is not supported", b.AgentControls)
}
f, err := ioutil.TempFile("", "")
if err != nil {
return err
}
// set b.Paths)
num := GenerateImportCSV(f, b.BaseBitmapID, b.MaxBitmapID, b.BaseProfileID, b.MaxProfileID,
b.MinBitsPerMap, b.MaxBitsPerMap, b.Seed, b.RandomBitmapOrder)
b.numbits = num
// set b.Paths
b.Paths = []string{f.Name()}
return f.Close()
}
// Run runs the Import benchmark
func (b *Import) Run(ctx context.Context) map[string]interface{} {
results := make(map[string]interface{})
results["numbits"] = b.numbits
results["db"] = b.Database
err := b.ImportCommand.Run(ctx)
if err != nil {
results["error"] = err.Error()
}
return results
}
// Int64Slice is a sortable slice of 64 bit signed ints
type Int64Slice []int64
func (s Int64Slice) Len() int { return len(s) }
func (s Int64Slice) Less(i, j int) bool { return s[i] < s[j] }
func (s Int64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// GenerateImportCSV writes a generated csv to 'w' which is in the form pilosactl expects for imports.
func GenerateImportCSV(w io.Writer, baseBitmapID, maxBitmapID, baseProfileID, maxProfileID, minBitsPerMap, maxBitsPerMap, seed int64, randomOrder bool) int {
src := rand.NewSource(seed)
rng := rand.New(src)
var bitmapIDs []int
if randomOrder {
bitmapIDs = rng.Perm(int(maxBitmapID - baseBitmapID))
}
numrows := 0
profileIDs := make(Int64Slice, maxBitsPerMap)
for i := baseBitmapID; i < maxBitmapID; i++ {
var bitmapID int64
if randomOrder {
bitmapID = int64(bitmapIDs[i-baseBitmapID])
} else {
bitmapID = int64(i)
}
numBitsToSet := rng.Int63n(maxBitsPerMap-minBitsPerMap) + minBitsPerMap
numrows += int(numBitsToSet)
for j := int64(0); j < numBitsToSet; j++ {
profileIDs[j] = rng.Int63n(maxProfileID-baseProfileID) + baseProfileID
}
profIDs := profileIDs[:numBitsToSet]
if !randomOrder {
sort.Sort(profIDs)
}
for j := int64(0); j < numBitsToSet; j++ {
fmt.Fprintf(w, "%d,%d\n", bitmapID, profIDs[j])
}
}
return numrows
}

View file

@ -1,136 +0,0 @@
package bench_test
import (
"bytes"
"testing"
"io/ioutil"
"os"
"github.com/pilosa/pilosa/bench"
)
func TestImportInit(t *testing.T) {
imp := bench.NewImport(os.Stdin, os.Stdout, os.Stderr)
imp.BaseBitmapID = 0
imp.MaxBitmapID = 10
imp.BaseProfileID = 0
imp.MaxProfileID = 10
imp.RandomBitmapOrder = false
imp.MinBitsPerMap = 2
imp.MaxBitsPerMap = 3
imp.AgentControls = "width"
imp.Seed = 0
imp.Init([]string{"blah"}, 2)
f, err := os.Open(imp.Paths[0])
if err != nil {
t.Fatalf("Couldn't open file: %v, err: %v", imp.Paths[0], err)
}
bytes, err := ioutil.ReadAll(f)
if err != nil {
t.Fatalf("error reading file: %v", err)
}
expected := `
0,21
0,22
1,20
1,22
2,22
2,26
3,21
3,23
4,21
4,22
5,20
5,28
6,23
6,27
7,20
7,20
8,23
8,29
9,23
9,29
`[1:]
if string(bytes) != expected {
t.Fatalf("unexpected result: %v", string(bytes))
}
}
func TestGenerateImportCSVNonRand(t *testing.T) {
b := bytes.NewBuffer(make([]byte, 0))
bench.GenerateImportCSV(b, 0, 10, 20, 30, 2, 3, 2, false)
bytes, err := ioutil.ReadAll(b)
if err != nil {
t.Fatalf("Error reading buffer: %v", err)
}
expected := `
0,21
0,22
1,20
1,22
2,22
2,26
3,21
3,23
4,21
4,22
5,20
5,28
6,23
6,27
7,20
7,20
8,23
8,29
9,23
9,29
`[1:]
if string(bytes) != expected {
t.Fatalf("unexpected value for generated csv: \n%v", string(bytes))
}
}
func TestGenerateImportCSVRand(t *testing.T) {
b := bytes.NewBuffer(make([]byte, 0))
bench.GenerateImportCSV(b, 0, 10, 21, 29, 1, 4, 0, true)
bytes, err := ioutil.ReadAll(b)
if err != nil {
t.Fatalf("Error reading buffer: %v", err)
}
expected := `
8,25
2,23
3,22
3,28
3,28
0,25
0,23
5,26
5,23
7,21
7,23
1,25
6,23
6,27
6,25
9,26
4,22
4,24
`[1:]
if string(bytes) != expected {
t.Fatalf("unexpected value for generated csv: \n%v", string(bytes))
}
}

View file

@ -1,97 +0,0 @@
package bench
import (
"context"
"flag"
"fmt"
"io/ioutil"
"strconv"
"time"
)
// MultiDBSetBits sets bits with increasing profile id and bitmap id.
type MultiDBSetBits struct {
HasClient
Name string `json:"name"`
BaseBitmapID int `json:"base-bitmap-id"`
BaseProfileID int `json:"base-profile-id"`
Iterations int `json:"iterations"`
Database string `json:"database"`
}
// Init sets up the db name based on the agentNum and sets up the pilosa client.
func (b *MultiDBSetBits) Init(hosts []string, agentNum int) error {
b.Name = "multi-db-set-bits"
b.Database = b.Database + strconv.Itoa(agentNum)
return b.HasClient.Init(hosts, agentNum)
}
// Usage returns the usage message to be printed.
func (b *MultiDBSetBits) Usage() string {
return `
multi-db-set-bits sets bits with increasing profile id and bitmap id using a different DB for each agent.
Agent num changes the database being written to.
Usage: multi-db-set-bits [arguments]
The following arguments are available:
-base-bitmap-id int
bits being set will all be greater than base-bitmap-id
-base-profile-id int
profile id num to start from
-iterations int
number of bits to set
-client-type string
Can be 'single' (all agents hitting one host) or 'round_robin'
-content-type string
protobuf or pql
`[1:]
}
// ConsumeFlags parses all flags up to the next non flag argument (argument does
// not start with "-" and isn't the value of a flag). It returns the remaining
// args.
func (b *MultiDBSetBits) ConsumeFlags(args []string) ([]string, error) {
fs := flag.NewFlagSet("MultiDBSetBits", flag.ContinueOnError)
fs.SetOutput(ioutil.Discard)
fs.IntVar(&b.BaseBitmapID, "base-bitmap-id", 0, "")
fs.IntVar(&b.BaseProfileID, "base-profile-id", 0, "")
fs.IntVar(&b.Iterations, "iterations", 100, "")
fs.StringVar(&b.ClientType, "client-type", "single", "")
fs.StringVar(&b.ContentType, "content-type", "protobuf", "")
if err := fs.Parse(args); err != nil {
return nil, err
}
return fs.Args(), nil
}
// Run runs the MultiDBSetBits benchmark
func (b *MultiDBSetBits) Run(ctx context.Context) map[string]interface{} {
results := make(map[string]interface{})
if b.client == nil {
results["error"] = fmt.Errorf("No client set for MultiDBSetBits")
return results
}
s := NewStats()
var start time.Time
for n := 0; n < b.Iterations; n++ {
query := fmt.Sprintf("SetBit(%d, 'frame.n', %d)", b.BaseBitmapID+n, b.BaseProfileID+n)
start = time.Now()
_, err := b.client.ExecuteQuery(ctx, b.Database, query, true)
if err != nil {
results["error"] = err
return results
}
s.Add(time.Now().Sub(start))
}
AddToResults(s, results)
return results
}

View file

@ -1,64 +0,0 @@
package bench
// PermutationGenerator provides a way to pass integer IDs through a permutation
// map that is pseudorandom but repeatable. This could be done with rand.Perm,
// but that would require storing a [Iterations]int64 array, which we want to avoid
// for large values of Iterations.
// It works by using a Linear Congruence Generator (https://en.wikipedia.org/wiki/Linear_congruential_generator)
// with modulus m = Iterations,
// c = an arbitrary prime,
// a = computed to ensure the full period.
// relevant stackoverflow: http://cs.stackexchange.com/questions/29822/lazily-computing-a-random-permutation-of-the-positive-integers
type PermutationGenerator struct {
a int64
c int64
m int64
}
func NewPermutationGenerator(m int64, seed int64) *PermutationGenerator {
// figure out 'a' and 'c', return PermutationGenerator
a := LCGmultiplierFromModulus(m, seed)
c := int64(22695479)
return &PermutationGenerator{a, c, m}
}
func (p *PermutationGenerator) Next(n int64) int64 {
// run one step of the LCG
return (n*p.a + p.c) % p.m
}
// LCG parameters must satisfy three conditions:
// 1. m and c are relatively prime (satisfied for prime c != m)
// 2. a-1 is divisible by all prime factors of m
// 3. a-1 is divisible by 4 if m is divisible by 4
// Additionally, a seed can be used to select between different permutations
func LCGmultiplierFromModulus(m int64, seed int64) int64 {
factors := primeFactors(m)
product := int64(1)
for p := range factors {
// satisfy condition 2
product *= p
}
if m%4 == 0 {
// satisfy condition 3
product *= 2
}
return product*seed + 1
}
// Returns map of {integerFactor: count, ...}
// This is a naive algorithm that will not work well for large prime n.
func primeFactors(n int64) map[int64]int {
factors := make(map[int64]int)
for i := int64(2); i <= n; i++ {
div, mod := n/i, n%i
for mod == 0 {
factors[i] += 1
n = div
div, mod = n/i, n%i
}
}
return factors
}

View file

@ -1,41 +0,0 @@
package bench
import "time"
// wrapper type to force human-readable JSON output
type PrettyDuration time.Duration
// MarshalJSON returns a nicely formatted duration, instead of it just being
// treated like an int.
func (d PrettyDuration) MarshalJSON() ([]byte, error) {
s := time.Duration(d).String()
return []byte("\"" + s + "\""), nil
}
// Recursively replaces elements of ugly types with their pretty wrappers
func Prettify(m map[string]interface{}) map[string]interface{} {
newmap := make(map[string]interface{})
for k, v := range m {
switch v.(type) {
case map[string]interface{}:
newmap[k] = Prettify(v.(map[string]interface{}))
case []time.Duration:
newslice := make([]PrettyDuration, len(v.([]time.Duration)))
slice := v.([]time.Duration)
for n, e := range slice {
newslice[n] = PrettyDuration(e)
}
newmap[k] = newslice
case time.Duration:
newmap[k] = PrettyDuration(v.(time.Duration))
default:
if interv, ok := v.([]map[string]interface{}); ok {
for i, iv := range interv {
interv[i] = Prettify(iv)
}
}
newmap[k] = v
}
}
return newmap
}

View file

@ -1,93 +0,0 @@
package bench_test
import (
"bytes"
"encoding/json"
"fmt"
"os"
"testing"
"time"
"github.com/pilosa/pilosa/bench"
)
func prettyEncode(data map[string]interface{}) string {
pretty := bench.Prettify(data)
jsonString := new(bytes.Buffer)
enc := json.NewEncoder(jsonString)
enc.SetIndent("", " ")
err := enc.Encode(pretty)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
return jsonString.String()
}
func TestPrettifyString(t *testing.T) {
res := make(map[string]interface{}, 1)
res["0"] = "foobar"
pretty := prettyEncode(res)
expected := `
{
"0": "foobar"
}
`[1:]
if pretty != expected {
t.Fatalf("Pretty string doesn't match")
}
}
func TestPrettifyInt(t *testing.T) {
res := make(map[string]interface{}, 1)
res["0"] = 234567
pretty := prettyEncode(res)
expected := `
{
"0": 234567
}
`[1:]
if pretty != expected {
t.Fatalf("Pretty int doesn't match")
}
}
func TestPrettifyDuration(t *testing.T) {
res := make(map[string]interface{}, 1)
res["0"] = time.Duration(234567)
pretty := prettyEncode(res)
expected := `
{
"0": "234.567µs"
}
`[1:]
if pretty != expected {
t.Fatalf("Pretty duration doesn't match")
}
}
func TestPrettifyDurationSlice(t *testing.T) {
res := make(map[string]interface{}, 1)
res["0"] = []time.Duration{123, 234567, 34567890}
pretty := prettyEncode(res)
expected := `
{
"0": [
"123ns",
"234.567µs",
"34.56789ms"
]
}
`[1:]
if pretty != expected {
t.Fatalf("Pretty duration slice doesn't match")
}
}

View file

@ -1,20 +0,0 @@
package bench_test
import (
"github.com/pilosa/pilosa/bench"
"testing"
)
func TestRandomBitmapCall(t *testing.T) {
qm := bench.NewQueryGenerator(5)
bmc := qm.RandomBitmapCall(4, 3, 0, 1000)
t.Log(bmc.String())
}
func TestRandom(t *testing.T) {
for i := 99; i < 120; i++ {
qm := bench.NewQueryGenerator(int64(i))
call := qm.Random(10, 4, 3, 0, 1000)
t.Log(call)
}
}

View file

@ -1,117 +0,0 @@
package bench
import (
"fmt"
"flag"
"io/ioutil"
"context"
"math/rand"
"time"
)
// RandomSetBits sets bits randomly and deterministically based on a seed.
type RandomSetBits struct {
HasClient
Name string `json:"name"`
BaseBitmapID int64 `json:"base-bitmap-id"`
BaseProfileID int64 `json:"base-profile-id"`
BitmapIDRange int64 `json:"bitmap-id-range"`
ProfileIDRange int64 `json:"profile-id-range"`
Iterations int `json:"iterations"`
Seed int64 `json:"seed"`
DB string `json:"db"`
}
// Init adds the agent num to the random seed and initializes the client.
func (b *RandomSetBits) Init(hosts []string, agentNum int) error {
b.Name = "random-set-bits"
b.Seed = b.Seed + int64(agentNum)
return b.HasClient.Init(hosts, agentNum)
}
// Usage returns the usage message to be printed.
func (b *RandomSetBits) Usage() string {
return `
random-set-bits sets random bits
Agent number modifies the random seed.
Usage: random-set-bits [arguments]
The following arguments are available:
-base-bitmap-id int
bits being set will all be greater than BaseBitmapID
-bitmap-id-range int
number of possible bitmap ids that can be set
-base-profile-id int
profile id num to start from
-profile-id-range int
number of possible profile ids that can be set
-iterations int
number of bits to set
-seed int
Seed for RNG
-db string
pilosa db to use
-client-type string
Can be 'single' (all agents hitting one host) or 'round_robin'
-content-type string
protobuf or pql
`[1:]
}
// ConsumeFlags parses all flags up to the next non flag argument (argument does
// not start with "-" and isn't the value of a flag). It returns the remaining
// args.
func (b *RandomSetBits) ConsumeFlags(args []string) ([]string, error) {
fs := flag.NewFlagSet("RandomSetBits", flag.ContinueOnError)
fs.SetOutput(ioutil.Discard)
fs.Int64Var(&b.BaseBitmapID, "base-bitmap-id", 0, "")
fs.Int64Var(&b.BitmapIDRange, "bitmap-id-range", 100000, "")
fs.Int64Var(&b.BaseProfileID, "base-profile-id", 0, "")
fs.Int64Var(&b.ProfileIDRange, "profile-id-range", 100000, "")
fs.Int64Var(&b.Seed, "seed", 1, "")
fs.IntVar(&b.Iterations, "iterations", 100, "")
fs.StringVar(&b.DB, "db", "benchdb", "")
fs.StringVar(&b.ClientType, "client-type", "single", "")
fs.StringVar(&b.ContentType, "content-type", "protobuf", "")
if err := fs.Parse(args); err != nil {
return nil, err
}
return fs.Args(), nil
}
// Run runs the RandomSetBits benchmark
func (b *RandomSetBits) Run(ctx context.Context) map[string]interface{} {
src := rand.NewSource(b.Seed)
rng := rand.New(src)
results := make(map[string]interface{})
if b.client == nil {
results["error"] = fmt.Errorf("No client set for RandomSetBits")
return results
}
s := NewStats()
var start time.Time
for n := 0; n < b.Iterations; n++ {
bitmapID := rng.Int63n(b.BitmapIDRange)
profID := rng.Int63n(b.ProfileIDRange)
query := fmt.Sprintf("SetBit(%d, 'frame.n', %d)", b.BaseBitmapID+bitmapID, b.BaseProfileID+profID)
start = time.Now()
b.client.ExecuteQuery(ctx, b.DB, query, true)
s.Add(time.Now().Sub(start))
}
AddToResults(s, results)
return results
}

View file

@ -1,151 +0,0 @@
package bench
import (
"context"
"flag"
"io"
"io/ioutil"
"strconv"
"time"
"github.com/pilosa/pilosa"
"github.com/pilosa/pilosa/pql"
)
// NewSliceHeight creates a new slice height benchmark with stdin/out/err
// initialized.
func NewSliceHeight(stdin io.Reader, stdout, stderr io.Writer) *SliceHeight {
return &SliceHeight{
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
}
}
// SliceHeight benchmark tests the effect of an increasing number of bitmaps in
// a single slice on query time.
type SliceHeight struct {
MaxTime time.Duration `json:"max-time"`
hosts []string
Name string `json:"name"`
MinBitsPerMap int64 `json:"min-bits-per-map"`
MaxBitsPerMap int64 `json:"max-bits-per-map"`
Seed int64 `json:"seed"`
Database string `json:"database"`
Frame string `json:"frame"`
Stdin io.Reader `json:"-"`
Stdout io.Writer `json:"-"`
Stderr io.Writer `json:"-"`
}
// Usage returns the usage message to be printed.
func (b *SliceHeight) Usage() string {
return `
slice-height repeatedly imports more bitmaps into a single slice and tests query times in between.
Agent number has no effect on this benchmark.
Usage: slice-height [arguments]
The following arguments are available:
-max-time int
stop benchmark after this many seconds
-min-bits-per-map int
minimum number of bits set per bitmap
-max-bits-per-map int
maximum number of bits set per bitmap
-seed int
seed for RNG
-db string
pilosa db to use
-frame string
frame to import into
`[1:]
}
// ConsumeFlags parses all flags up to the next non flag argument (argument does
// not start with "-" and isn't the value of a flag). It returns the remaining
// args.
func (b *SliceHeight) ConsumeFlags(args []string) ([]string, error) {
fs := flag.NewFlagSet("SliceHeight", flag.ContinueOnError)
fs.SetOutput(ioutil.Discard)
maxTime := fs.Int("max-time", 30, "")
fs.Int64Var(&b.MinBitsPerMap, "min-bits-per-map", 0, "")
fs.Int64Var(&b.MaxBitsPerMap, "max-bits-per-map", 10, "")
fs.Int64Var(&b.Seed, "seed", 0, "")
fs.StringVar(&b.Database, "db", "benchdb", "")
fs.StringVar(&b.Frame, "frame", "testframe", "")
if err := fs.Parse(args); err != nil {
return nil, err
}
b.MaxTime = time.Duration(*maxTime) * time.Second
return fs.Args(), nil
}
// Init sets up the slice height benchmark.
func (b *SliceHeight) Init(hosts []string, agentNum int) error {
b.Name = "slice-height"
b.hosts = hosts
return nil
}
// Run runs the SliceHeight benchmark
func (b *SliceHeight) Run(ctx context.Context) map[string]interface{} {
results := make(map[string]interface{})
imp := NewImport(b.Stdin, b.Stdout, b.Stderr)
imp.MaxBitmapID = 100
imp.MaxProfileID = pilosa.SliceWidth
imp.MinBitsPerMap = b.MinBitsPerMap
imp.MaxBitsPerMap = b.MaxBitsPerMap
imp.Database = b.Database
imp.Frame = b.Frame
start := time.Now()
for i := 0; i > -1; i++ {
iresults := make(map[string]interface{})
results["iteration"+strconv.Itoa(i)] = iresults
genstart := time.Now()
imp.Init(b.hosts, 0)
gendur := time.Now().Sub(genstart)
iresults["csvgen"] = gendur
iresults["import"] = imp.Run(ctx)
qstart := time.Now()
q := &pql.Call{
Name: "TopN",
Args: map[string]interface{}{
"frame": b.Frame,
"n": 50,
},
}
_, err := imp.Client.ExecuteQuery(ctx, b.Database, q.String(), true)
if err != nil {
iresults["query_error"] = err.Error()
} else {
qdur := time.Now().Sub(qstart)
iresults["query"] = qdur
}
imp.BaseBitmapID = imp.MaxBitmapID
imp.MaxBitmapID = imp.MaxBitmapID * 10
if time.Now().Sub(start) > b.MaxTime {
break
}
}
return results
}

View file

@ -1,65 +0,0 @@
package bench
import (
"math"
"time"
)
// Stats object helps track timing stats.
type Stats struct {
Min time.Duration
Max time.Duration
Mean time.Duration
sumSquareDelta float64
Total time.Duration
Num int64
All []time.Duration
SaveAll bool
}
// NewStats gets a Stats object.
func NewStats() *Stats {
return &Stats{
Min: 1<<63 - 1,
All: make([]time.Duration, 0),
}
}
// Add adds a new time to the stats object.
func (s *Stats) Add(td time.Duration) {
if s.SaveAll {
s.All = append(s.All, td)
}
s.Num += 1
s.Total += td
if td < s.Min {
s.Min = td
}
if td > s.Max {
s.Max = td
}
// online variance calculation
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
delta := td - s.Mean
s.Mean += delta / time.Duration(s.Num)
s.sumSquareDelta += float64(delta * (td - s.Mean))
}
// Avg returns the average of all durations Added to the Stats object.
func (s *Stats) Avg() time.Duration {
return s.Total / time.Duration(s.Num)
}
// AddToResults serializes the summary of Stats and adds them to the results
// map.
func AddToResults(s *Stats, results map[string]interface{}) {
results["min"] = s.Min
results["max"] = s.Max
results["avg"] = s.Mean
variance := s.sumSquareDelta / float64(s.Num)
results["std"] = time.Duration(math.Sqrt(variance))
if s.SaveAll {
results["all"] = s.All
}
}

View file

@ -1,39 +0,0 @@
package bench
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
// NewS3Uploader creates an S3Uploader with specified bucket and key
func NewS3Uploader(bucket string, key string) *S3Uploader {
return &S3Uploader{
bucket,
s3.New(session.New(&aws.Config{})),
key,
}
}
// S3Uploader is an io.Writer for sending output to AWS S3 storage.
type S3Uploader struct {
bucket string
service *s3.S3
key string
}
// Write writes data to the uploader's bucket/key
func (u *S3Uploader) Write(data []byte) (int, error) {
// first return value of PutObject contains an ETag (hash) of the uploaded object, not needed here
fmt.Println(string(data))
_, err := u.service.PutObject(&s3.PutObjectInput{
Body: strings.NewReader(string(data)),
Bucket: &u.bucket,
Key: &u.key,
})
return 0, err
}

View file

@ -1,191 +0,0 @@
package bench
import (
"fmt"
"flag"
"io/ioutil"
"context"
"math"
"math/rand"
"time"
)
// Zipf sets random bits according to the Zipf-Mandelbrot distribution.
// This distribution accepts two parameters, Exponent and Ratio, for both bitmaps and profiles.
// It also uses PermutationGenerator to permute IDs randomly.
type Zipf struct {
HasClient
Name string `json:"name"`
BaseBitmapID int64 `json:"base-bitmap-id"`
BaseProfileID int64 `json:"base-profile-id"`
BitmapIDRange int64 `json:"bitmap-id-range"`
ProfileIDRange int64 `json:"profile-id-range"`
Iterations int `json:"iterations"`
Seed int64 `json:"seed"`
DB string `json:"db"`
BitmapExponent float64 `json:"bitmap-exponent"`
BitmapRatio float64 `json:"bitmap-ratio"`
ProfileExponent float64 `json:"profile-exponent"`
ProfileRatio float64 `json:"profile-ratio"`
Operation string `json:"operation"`
bitmapRng *rand.Zipf
profileRng *rand.Zipf
bitmapPerm *PermutationGenerator
profilePerm *PermutationGenerator
}
// Usage returns the usage message to be printed.
func (b *Zipf) Usage() string {
return `
zipf sets random bits according to the Zipf distribution.
This is a power-law distribution controlled by two parameters.
Exponent, in the range (1, inf), with a default value of 1.001, controls
the "sharpness" of the distribution, with higher exponent being sharper.
Ratio, in the range (0, 1), with a default value of 0.25, controls the
maximum variation of the distribution, with higher ratio being more uniform.
Agent number modifies random seed.
Usage: zipf [arguments]
The following arguments are available:
-base-bitmap-id int
bits being set will all be greater than BaseBitmapID
-bitmap-id-range int
number of possible bitmap ids that can be set
-base-profile-id int
profile id num to start from
-profile-id-range int
number of possible profile ids that can be set
-iterations int
number of bits to set
-seed int
Seed for RNG
-db string
pilosa db to use
-bitmap-exponent float64
zipf exponent parameter for bitmap IDs
-bitmap-ratio float64
zipf probability ratio parameter for bitmap IDs
-profile-exponent float64
zipf exponent parameter for profile IDs
-profile-ratio float64
zipf probability ratio parameter for profile IDs
-client-type string
Can be 'single' (all agents hitting one host) or 'round_robin'
-operation string
Can be 'set' or 'clear'
-content-type string
protobuf or pql
`[1:]
}
// ConsumeFlags parses all flags up to the next non flag argument (argument does
// not start with "-" and isn't the value of a flag). It returns the remaining
// args.
func (b *Zipf) ConsumeFlags(args []string) ([]string, error) {
fs := flag.NewFlagSet("Zipf", flag.ContinueOnError)
fs.SetOutput(ioutil.Discard)
fs.Int64Var(&b.BaseBitmapID, "base-bitmap-id", 0, "")
fs.Int64Var(&b.BitmapIDRange, "bitmap-id-range", 100000, "")
fs.Int64Var(&b.BaseProfileID, "base-profile-id", 0, "")
fs.Int64Var(&b.ProfileIDRange, "profile-id-range", 100000, "")
fs.Int64Var(&b.Seed, "seed", 1, "")
fs.IntVar(&b.Iterations, "iterations", 100, "")
fs.StringVar(&b.DB, "db", "benchdb", "")
fs.Float64Var(&b.BitmapExponent, "bitmap-exponent", 1.01, "")
fs.Float64Var(&b.BitmapRatio, "bitmap-ratio", 0.25, "")
fs.Float64Var(&b.ProfileExponent, "profile-exponent", 1.01, "")
fs.Float64Var(&b.ProfileRatio, "profile-ratio", 0.25, "")
fs.StringVar(&b.ClientType, "client-type", "single", "")
fs.StringVar(&b.Operation, "operation", "set", "")
fs.StringVar(&b.ContentType, "content-type", "protobuf", "")
if err := fs.Parse(args); err != nil {
return nil, err
}
return fs.Args(), nil
}
// Offset is the true parameter used by the Zipf distribution, but the ratio,
// as defined here, is a simpler, readable way to define the distribution.
// Offset is in [1, inf), and its meaning depends on N (a pain for updating benchmark configs)
// ratio is in (0, 1), and its meaning does not depend on N.
// it is the ratio of the lowest probability in the distribution to the highest.
// ratio=0.01 corresponds to a very small offset - the most skewed distribution for a given pair (N, exp)
// ratio=0.99 corresponds to a very large offset - the most nearly uniform distribution for a given (N, exp)
func getZipfOffset(N int64, exp, ratio float64) float64 {
z := math.Pow(ratio, 1/exp)
return z * float64(N-1) / (1 - z)
}
// Init sets up the benchmark based on the agent number and initializes the
// client.
func (b *Zipf) Init(hosts []string, agentNum int) error {
b.Name = "zipf"
b.Seed = b.Seed + int64(agentNum)
rnd := rand.New(rand.NewSource(b.Seed))
bitmapOffset := getZipfOffset(b.BitmapIDRange, b.BitmapExponent, b.BitmapRatio)
b.bitmapRng = rand.NewZipf(rnd, b.BitmapExponent, bitmapOffset, uint64(b.BitmapIDRange-1))
profileOffset := getZipfOffset(b.ProfileIDRange, b.ProfileExponent, b.ProfileRatio)
b.profileRng = rand.NewZipf(rnd, b.ProfileExponent, profileOffset, uint64(b.ProfileIDRange-1))
b.bitmapPerm = NewPermutationGenerator(b.BitmapIDRange, b.Seed)
b.profilePerm = NewPermutationGenerator(b.ProfileIDRange, b.Seed+1)
if b.Operation != "set" && b.Operation != "clear" {
return fmt.Errorf("Unsupported operation: \"%s\" (must be \"set\" or \"clear\")", b.Operation)
}
return b.HasClient.Init(hosts, agentNum)
}
// Run runs the Zipf benchmark
func (b *Zipf) Run(ctx context.Context) map[string]interface{} {
results := make(map[string]interface{})
if b.client == nil {
results["error"] = fmt.Errorf("No client set for Zipf")
return results
}
operation := "SetBit"
if b.Operation == "clear" {
operation = "ClearBit"
}
s := NewStats()
var start time.Time
for n := 0; n < b.Iterations; n++ {
// generate IDs from Zipf distribution
bitmapIDOriginal := b.bitmapRng.Uint64()
profIDOriginal := b.profileRng.Uint64()
// permute IDs randomly, but repeatably
bitmapID := b.bitmapPerm.Next(int64(bitmapIDOriginal))
profID := b.profilePerm.Next(int64(profIDOriginal))
query := fmt.Sprintf("%s(%d, 'frame.n', %d)", operation, b.BaseBitmapID+int64(bitmapID), b.BaseProfileID+int64(profID))
start = time.Now()
_, err := b.client.ExecuteQuery(ctx, b.DB, query, true)
if err != nil {
results["error"] = fmt.Sprintf("Error executing query in zipf: %v", err)
return results
}
s.Add(time.Now().Sub(start))
}
AddToResults(s, results)
return results
}

View file

@ -1,29 +0,0 @@
package build
import (
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
)
func Binary(pkg, goos, goarch string) (io.Reader, error) {
binFile, err := ioutil.TempFile("", "pilosactl")
if err != nil {
return nil, fmt.Errorf("build binary: %v", err)
}
com := exec.Command("go", "build", "-o", binFile.Name(), pkg)
com.Env = append([]string{"GOOS=" + goos, "GOARCH=" + goarch}, os.Environ()...)
err = com.Run()
if err != nil {
return nil, err
}
f, err := os.Open(binFile.Name())
if err != nil {
return nil, err
}
return f, nil
}

View file

@ -1,9 +0,0 @@
{
"benchmarks": [
{
"num": 1,
"name": "import",
"args": ["import", "-max-bitmap-id", "100000", "-max-profile-id", "10000", "-max-bits-per-map", "100", "-seed", "0", "-agent-controls", "width", "import", "-max-bitmap-id", "100000", "-max-profile-id", "10000", "-max-bits-per-map", "100", "-seed", "0", "-agent-controls", "width", "-random-bitmap-order", "-db", "randoload"]
}
]
}

View file

@ -4,7 +4,6 @@ import (
"bufio"
"context"
"encoding/csv"
"encoding/json"
"errors"
"flag"
"fmt"
@ -13,27 +12,18 @@ import (
"log"
"math/rand"
"os"
"os/signal"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"syscall"
"text/tabwriter"
"time"
"unsafe"
"github.com/pilosa/pilosa"
"github.com/pilosa/pilosa/bench"
"github.com/pilosa/pilosa/build"
"github.com/pilosa/pilosa/creator"
"github.com/pilosa/pilosa/pilosactl"
"github.com/pilosa/pilosa/roaring"
pssh "github.com/pilosa/pilosa/ssh"
"github.com/satori/go.uuid"
"golang.org/x/crypto/ssh"
)
var (
@ -115,9 +105,6 @@ The commands are:
inspect inspects fragment data files
check performs a consistency check of data files
bench benchmarks operations
create create pilosa clusters
bagent run a benchmarking agent
bspawn create a cluster and agents and run benchmarks based on config file
Use the "-h" flag with any command for more information.
`)
@ -157,12 +144,6 @@ func (m *Main) ParseFlags(args []string) error {
m.Cmd = NewCheckCommand(m.Stdin, m.Stdout, m.Stderr)
case "bench":
m.Cmd = NewBenchCommand(m.Stdin, m.Stdout, m.Stderr)
case "create":
m.Cmd = NewCreateCommand(m.Stdin, m.Stdout, m.Stderr)
case "bagent":
m.Cmd = NewBagentCommand(m.Stdin, m.Stdout, m.Stderr)
case "bspawn":
m.Cmd = NewBspawnCommand(m.Stdin, m.Stdout, m.Stderr)
default:
return ErrUnknownCommand
}
@ -1004,672 +985,6 @@ func (cmd *BenchCommand) runSetBit(ctx context.Context, client *pilosa.Client) e
return nil
}
// CreateCommand represents a command for creating a pilosa cluster.
type CreateCommand struct {
ServerN int `json:"serverN"`
ReplicaN int `json:"replicaN"`
LogFilePrefix string `json:"log-file-prefix"`
Hosts []string `json:"hosts"`
GoMaxProcs int `json:"gomaxprocs"`
SSHUser string `json:"ssh-user"`
CopyBinary bool `json:"copy-binary"`
GOOS string `json:"goos"`
GOARCH string `json:"goarch"`
// The following are set by CreateCommand.Run once they are known and exist
// to appear in the output
LogFiles []string `json:"log-files"`
FinalHosts []string `json:"final-hosts"`
// Standard input/output
Stdin io.Reader `json:"-"`
Stdout io.Writer `json:"-"`
Stderr io.Writer `json:"-"`
}
// NewCreateCommand returns a new instance of CreateCommand.
func NewCreateCommand(stdin io.Reader, stdout, stderr io.Writer) *CreateCommand {
return &CreateCommand{
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
}
}
// ParseFlags parses command line flags from args.
func (cmd *CreateCommand) ParseFlags(args []string) error {
fs := flag.NewFlagSet("pilosactl", flag.ContinueOnError)
fs.SetOutput(ioutil.Discard)
fs.IntVar(&cmd.ServerN, "serverN", 3, "")
fs.IntVar(&cmd.ReplicaN, "replicaN", 1, "")
fs.StringVar(&cmd.LogFilePrefix, "log-file-prefix", "", "")
var hosts string
fs.IntVar(&cmd.GoMaxProcs, "gomaxprocs", 0, "")
fs.StringVar(&hosts, "hosts", "", "")
fs.StringVar(&cmd.SSHUser, "ssh-user", "", "")
fs.BoolVar(&cmd.CopyBinary, "copy-binary", false, "")
fs.StringVar(&cmd.GOOS, "goos", "linux", "")
fs.StringVar(&cmd.GOARCH, "goarch", "amd64", "")
if err := fs.Parse(args); err != nil {
return err
}
if len(fs.Args()) > 0 {
fmt.Fprintf(cmd.Stderr, "Uknown args: %v\n", strings.Join(fs.Args(), " "))
return flag.ErrHelp
}
cmd.Hosts = customSplit(hosts, ",")
return nil
}
// Usage returns the usage message to be printed.
func (cmd *CreateCommand) Usage() string {
return strings.TrimSpace(`
usage: pilosactl create [args]
Creates a pilosa cluster. Defaults to a 3-node in-process cluster.
The following flags are allowed:
-serverN
number of hosts in cluster. Disregarded if 'hosts' is set
-replicaN
replication factor for cluster
-hosts
comma separated host:port list. If hosts is set, create will start
pilosa on these pre-existing hosts. The same host may be listed multiple
times with different ports
-log-file-prefix
output from the started cluster will go into files with
this prefix (one per node)
-ssh-user
username to use when contacting remote hosts
-gomaxprocs
when starting a cluster on remote hosts, this will set the value
of GOMAXPROCS.
-copy-binary
controls whether or not to build and copy pilosa to hosts
-goos
when using copy-binary, GOOS to use while building binary
-goarch
when using copy-binary, GOARCH to use while building binary
`)
}
// Run executes cluster creation.
func (cmd *CreateCommand) Run(ctx context.Context) error {
var clus creator.Cluster
if len(cmd.Hosts) == 0 {
fmt.Fprintf(cmd.Stderr, "create: no hosts specified - creating cluster in-process\n")
clus = &creator.LocalCluster{
ReplicaN: cmd.ReplicaN,
ServerN: cmd.ServerN,
}
} else {
if cmd.ServerN != 0 {
fmt.Fprintf(cmd.Stderr, "create: hosts were specified, so ignoring serverN\n")
}
clus = &creator.RemoteCluster{
ClusterHosts: cmd.Hosts,
ReplicaN: cmd.ReplicaN,
SSHUser: cmd.SSHUser,
Stderr: cmd.Stderr,
GoMaxProcs: cmd.GoMaxProcs,
CopyBinary: cmd.CopyBinary,
GOOS: cmd.GOOS,
GOARCH: cmd.GOARCH,
}
}
err := clus.Start()
if err != nil {
return fmt.Errorf("starting cluster: %v", err)
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for range c {
fmt.Fprintf(cmd.Stderr, "\ncreate: caught signal - shutting down\n")
err := clus.Shutdown()
code := 0
if err != nil {
code = 1
}
os.Exit(code)
}
}()
defer clus.Shutdown()
cmd.FinalHosts = clus.Hosts()
logReaders := clus.Logs()
if cmd.LogFilePrefix != "" {
cmd.LogFiles = make([]string, len(clus.Hosts()))
}
for i, _ := range clus.Hosts() {
var f io.Writer = cmd.Stderr
var err error
if cmd.LogFilePrefix != "" {
f, err = os.Create(cmd.LogFilePrefix + strconv.Itoa(i))
if err != nil {
return err
}
cmd.LogFiles[i] = f.(*os.File).Name()
}
go func(i int, f io.Writer) {
_, err := io.Copy(f, logReaders[i])
if err != nil {
fmt.Fprintf(cmd.Stderr, "create: error copying cluster logs: '%v'\n", err)
}
}(i, f)
}
enc := json.NewEncoder(cmd.Stdout)
enc.SetIndent("", " ")
err = enc.Encode(cmd)
if err != nil {
return err
}
fmt.Fprintf(cmd.Stderr, "create: cluster started.\n")
select {}
}
// BagentCommand represents a command for running a benchmark agent. A benchmark
// agent runs multiple benchmarks in series in the order that they are specified
// on the command line.
type BagentCommand struct {
// Slice of Benchmarks which will be run serially.
Benchmarks []bench.Benchmark `json:"benchmarks"`
// AgentNum will be passed to each benchmark's Run method so that it can
// parameterize its behavior.
AgentNum int `json:"agent-num"`
// Enable pretty printing of results, for human consumption.
HumanReadable bool `json:"human-readable"`
// Slice of pilosa hosts to run the Benchmarks against.
Hosts []string `json:"hosts"`
Stdin io.Reader `json:"-"`
Stdout io.Writer `json:"-"`
Stderr io.Writer `json:"-"`
}
// NewBagentCommand returns a new instance of BagentCommand.
func NewBagentCommand(stdin io.Reader, stdout, stderr io.Writer) *BagentCommand {
return &BagentCommand{
Benchmarks: []bench.Benchmark{},
Hosts: []string{},
AgentNum: 0,
HumanReadable: false,
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
}
}
// ParseFlags parses command line flags for the BagentCommand. First the command
// wide flags `hosts` and `agent-num` are parsed. The rest of the flags should be
// a series of subcommands along with their flags. ParseFlags runs each
// subcommand's `ConsumeFlags` method which parses the flags for that command
// and returns the rest of the argument slice which should contain further
// subcommands.
func (cmd *BagentCommand) ParseFlags(args []string) error {
fs := flag.NewFlagSet("pilosactl", flag.ContinueOnError)
fs.SetOutput(ioutil.Discard)
var pilosaHosts string
fs.StringVar(&pilosaHosts, "hosts", "localhost:15000", "")
fs.IntVar(&cmd.AgentNum, "agent-num", 0, "")
fs.BoolVar(&cmd.HumanReadable, "human", true, "")
if err := fs.Parse(args); err != nil {
return err
}
remArgs := fs.Args()
if len(remArgs) == 0 {
return flag.ErrHelp
}
for len(remArgs) > 0 {
var bm bench.Command
var err error
switch remArgs[0] {
case "-help", "-h":
return flag.ErrHelp
case "diagonal-set-bits":
bm = &bench.DiagonalSetBits{}
case "random-set-bits":
bm = &bench.RandomSetBits{}
case "zipf":
bm = &bench.Zipf{}
case "multi-db-set-bits":
bm = &bench.MultiDBSetBits{}
case "random-query":
bm = &bench.RandomQuery{}
case "import":
bm = bench.NewImport(cmd.Stdin, cmd.Stdout, cmd.Stderr)
case "slice-height":
bm = bench.NewSliceHeight(cmd.Stdin, cmd.Stdout, cmd.Stderr)
default:
return fmt.Errorf("Unknown benchmark cmd: %v", remArgs[0])
}
remArgs, err = bm.ConsumeFlags(remArgs[1:])
cmd.Benchmarks = append(cmd.Benchmarks, bm)
if err != nil {
if err == flag.ErrHelp {
fmt.Fprintln(cmd.Stderr, bm.Usage())
return fmt.Errorf("")
}
return fmt.Errorf("BagentCommand.ParseFlags: %v", err)
}
}
cmd.Hosts = customSplit(pilosaHosts, ",")
return nil
}
// Usage returns the usage message to be printed.
func (cmd *BagentCommand) Usage() string {
return strings.TrimSpace(`
usage: pilosactl bagent [options] <subcommand [options]>...
Runs benchmarks against a pilosa cluster.
The following flags are allowed:
-hosts ("localhost:15000")
comma separated list of host:port describing all hosts in the cluster
-agent-num (0)
an integer differentiating this agent from others in the fleet
-human (true)
boolean to enable human-readable format
subcommands:
diagonal-set-bits
random-set-bits
zipf
multi-db-set-bits
random-query
import
slice-height
`)
}
// Run executes the benchmark agent.
func (cmd *BagentCommand) Run(ctx context.Context) error {
sbm := serial(cmd.Benchmarks...)
err := sbm.Init(cmd.Hosts, cmd.AgentNum)
if err != nil {
return fmt.Errorf("in cmd.Run initialization: %v", err)
}
res := sbm.Run(ctx)
res["agent-num"] = cmd.AgentNum
enc := json.NewEncoder(cmd.Stdout)
if cmd.HumanReadable {
enc.SetIndent("", " ")
res = bench.Prettify(res)
}
err = enc.Encode(res)
if err != nil {
fmt.Fprintln(cmd.Stderr, err)
}
return nil
}
// BspawnCommand represents a command for spawning complex benchmarks. This
// includes cluster creation and teardown, agent creation and teardown, running
// multiple benchmarks in series and/or parallel, and collecting all the
// results.
type BspawnCommand struct {
// If PilosaHosts is specified, CreatorArgs is ignored and the existing
// cluster specified here is used.
PilosaHosts []string `json:"pilosa-hosts"`
// CreateCommand will be used with these arguments to create a cluster -
// the cluster will be used to populate the PilosaHosts field. This
// should include everything that comes after `pilosactl create`
CreatorArgs []string `json:"creator-args"`
// List of hosts to run agents on. If this is empty, agents will be run
// locally.
AgentHosts []string `json:"agent-hosts"`
// Makes output human readable
HumanReadable bool `json:"human-readable"`
// Result destination, ["stdout", "s3"]
Output string `json:"output"`
// If this is true, build and copy pilosactl binary to agent hosts.
CopyBinary bool `json:"copy-binary"`
GOOS string `json:"goos"`
GOARCH string `json:"goarch"`
// Benchmarks is a slice of Spawns which specifies all of the bagent
// commands to run. These will all be run in parallel, started on each
// of the agents in a round robin fashion.
Benchmarks []Spawn `json:"benchmarks"`
SSHUser string `json:"ssh-user"`
Stdin io.Reader `json:"-"`
Stdout io.Writer `json:"-"`
Stderr io.Writer `json:"-"`
}
// Spawn represents a bagent command run in parallel across Num agents. The
// bagent command can run multiple Benchmarks serially within itself.
type Spawn struct {
Num int `json:"num"` // number of agents to run
Name string `json:"name"` // Should describe what this Spawn does
Args []string `json:"args"` // everything that comes after `pilosactl bagent [arguments]`
}
// NewBspawnCommand returns a new instance of BspawnCommand.
func NewBspawnCommand(stdin io.Reader, stdout, stderr io.Writer) *BspawnCommand {
return &BspawnCommand{
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
}
}
// ParseFlags parses command line flags from args.
func (cmd *BspawnCommand) ParseFlags(args []string) error {
fs := flag.NewFlagSet("pilosactl", flag.ContinueOnError)
fs.SetOutput(ioutil.Discard)
creatorHosts := fs.String("creator.hosts", "", "")
creatorLFP := fs.String("creator.log-file-prefix", "", "")
creatorCopyBinary := fs.Bool("creator.copy-binary", false, "")
pilosaHosts := fs.String("pilosa-hosts", "", "")
agentHosts := fs.String("agent-hosts", "", "")
sshUser := fs.String("ssh-user", "", "")
fs.BoolVar(&cmd.HumanReadable, "human", true, "")
fs.StringVar(&cmd.Output, "output", "stdout", "")
fs.BoolVar(&cmd.CopyBinary, "copy-binary", false, "")
fs.StringVar(&cmd.GOOS, "goos", "linux", "")
fs.StringVar(&cmd.GOARCH, "goarch", "amd64", "")
err := fs.Parse(args)
if err != nil {
return err
}
if len(fs.Args()) != 1 {
return flag.ErrHelp
}
f, err := os.Open(fs.Args()[0])
if err != nil {
return err
}
dec := json.NewDecoder(f)
err = dec.Decode(cmd)
if err != nil {
return err
}
if *pilosaHosts != "" {
cmd.PilosaHosts = customSplit(*pilosaHosts, ",")
}
if *agentHosts != "" {
cmd.AgentHosts = customSplit(*agentHosts, ",")
}
if *sshUser != "" {
cmd.SSHUser = *sshUser
}
if *pilosaHosts == "" {
cmd.CreatorArgs = []string{"-hosts=" + *creatorHosts, "-log-file-prefix=" + *creatorLFP, "-ssh-user=" + cmd.SSHUser, "-goos=" + cmd.GOOS, "-goarch=" + cmd.GOARCH}
if *creatorCopyBinary {
cmd.CreatorArgs = append(cmd.CreatorArgs, "-copy-binary")
}
}
return nil
}
// Usage returns the usage message to be printed.
func (cmd *BspawnCommand) Usage() string {
return strings.TrimSpace(`
usage: pilosactl spawn [flags] <configfile>
Benchmark orchestration tool - runs 'create' and potentially multiple instances
of 'bagent' spread across a number of hosts.
The following flags are allowed and will override the values in the config file:
-creator.hosts ()
hosts argument for pilosactl create
-creator.log-file-prefix ()
log-file-prefix argument for pilosactl create. If empty, log to stderr.
-creator.copy-binary (false)
pilosactl create should build and copy pilosa binary to cluster
-pilosa-hosts ([])
pilosa hosts to run against (will ignore creator args)
-agent-hosts ("localhost")
hosts to use for benchmark agents
-ssh-user (current username)
username to use when contacting remote hosts
-human (true)
toggle human readable output (indented json)
-output ("stdout")
string to select output destination, "stdout" or "s3"
-copy-binary (false)
controls whether or not to build and copy pilosactl to agents
-goos (linux)
when using copy-binary, GOOS to use while building binary
-goarch (amd64)
when using copy-binary, GOARCH to use while building binary
`)
}
// Run executes the main program execution.
func (cmd *BspawnCommand) Run(ctx context.Context) error {
runUUID := uuid.NewV1()
output := make(map[string]interface{})
output["run-uuid"] = runUUID.String()
if len(cmd.PilosaHosts) == 0 {
fmt.Fprintln(cmd.Stderr, "bspawn: pilosa-hosts not specified - using create command to build cluster")
r, w := io.Pipe()
createCmd := NewCreateCommand(cmd.Stdin, w, cmd.Stderr)
err := createCmd.ParseFlags(cmd.CreatorArgs)
if err != nil {
return err
}
go func() {
err := createCmd.Run(ctx)
if err != nil {
fmt.Fprintf(cmd.Stderr, "bspawn: cluster creation error while spawning: %v\n", err)
}
}()
clus := &CreateCommand{}
dec := json.NewDecoder(r)
err = dec.Decode(clus)
if err != nil {
return err
}
cmd.PilosaHosts = clus.FinalHosts
output["cluster"] = clus
}
if len(cmd.AgentHosts) == 0 {
fmt.Fprintln(cmd.Stderr, "bpspawn: no agent-hosts specified; all agents will be spawned on localhost")
cmd.AgentHosts = []string{"localhost"}
}
output["spawn"] = cmd
res, err := cmd.spawnRemote(ctx)
if err != nil {
return err
}
output["results"] = res
var writer io.Writer
if cmd.Output == "s3" {
writer = bench.NewS3Uploader("benchmarks-pilosa", runUUID.String()+".json")
} else if cmd.Output == "stdout" {
writer = cmd.Stdout
} else {
return fmt.Errorf("invalid bspawn output destination")
}
enc := json.NewEncoder(writer)
if cmd.HumanReadable {
enc.SetIndent("", " ")
output = bench.Prettify(output)
}
return enc.Encode(output)
}
func (cmd *BspawnCommand) spawnRemote(ctx context.Context) (map[string]interface{}, error) {
agentIdx := 0
agentFleet, err := pssh.NewFleet(cmd.AgentHosts, cmd.SSHUser, "", cmd.Stderr)
if err != nil {
return nil, err
}
cmdName := "pilosactl"
if cmd.CopyBinary {
cmdName = "/tmp/pilosactl" + strconv.Itoa(rand.Int())
fmt.Fprintf(cmd.Stderr, "bspawn: building pilosactl binary with GOOS=%v and GOARCH=%v to copy to agents at %v\n", cmd.GOOS, cmd.GOARCH, cmdName)
pkg := "github.com/pilosa/pilosa/cmd/pilosactl"
bin, err := build.Binary(pkg, cmd.GOOS, cmd.GOARCH)
if err != nil {
return nil, err
}
err = agentFleet.WriteFile(cmdName, "+x", bin)
if err != nil {
return nil, err
}
}
sessions := make([]*ssh.Session, 0)
results := make(map[string]interface{})
resLock := sync.Mutex{}
wg := sync.WaitGroup{}
fmt.Fprintln(cmd.Stderr, "bspawn: running benchmarks")
for _, sp := range cmd.Benchmarks {
results[sp.Name] = make(map[int]interface{})
for i := 0; i < sp.Num; i++ {
agentIdx %= len(cmd.AgentHosts)
sess, err := agentFleet[cmd.AgentHosts[agentIdx]].NewSession()
if err != nil {
return nil, err
}
agentIdx += 1
sessions = append(sessions, sess)
stdout, err := sess.StdoutPipe()
if err != nil {
return nil, err
}
wg.Add(1)
go func(stdout io.Reader, name string, num int) {
defer wg.Done()
dec := json.NewDecoder(stdout)
var v interface{}
err := dec.Decode(&v)
if err != nil {
fmt.Fprintf(cmd.Stderr, "error decoding json: %v, spawn: %v\n", err, name)
}
resLock.Lock()
results[name].(map[int]interface{})[num] = v
resLock.Unlock()
}(stdout, sp.Name, i)
sess.Stderr = cmd.Stderr
err = sess.Start(cmdName + " bagent -agent-num=" + strconv.Itoa(i) + " -hosts=" + strings.Join(cmd.PilosaHosts, ",") + " " + strings.Join(sp.Args, " "))
if err != nil {
return nil, err
}
}
}
for _, sess := range sessions {
err = sess.Wait()
if err != nil {
return nil, fmt.Errorf("error waiting for remote bagent: %v", err)
}
}
wg.Wait()
return results, nil
}
type serialBenchmark struct {
benchmarkers []bench.Benchmark
}
// Init calls Init for each benchmark. If there are any errors, it will return a
// non-nil error value.
func (sb *serialBenchmark) Init(hosts []string, agentNum int) error {
errors := make([]error, len(sb.benchmarkers))
hadErr := false
for i, b := range sb.benchmarkers {
errors[i] = b.Init(hosts, agentNum)
if errors[i] != nil {
hadErr = true
}
}
if hadErr {
return fmt.Errorf("Had errs in serialBenchmark.Init: %v", errors)
}
return nil
}
// Run runs the serial benchmark and returns it's results in a nested map - the
// top level keys are the indices of each benchmark in the list of benchmarks,
// and the values are the results of each benchmark's Run method.
func (sb *serialBenchmark) Run(ctx context.Context) map[string]interface{} {
benchmarks := make([]map[string]interface{}, len(sb.benchmarkers))
results := map[string]interface{}{"benchmarks": benchmarks}
total_start := time.Now()
for i, b := range sb.benchmarkers {
start := time.Now()
output := b.Run(ctx)
if _, ok := output["runtime"]; ok {
panic(fmt.Sprintf("Benchmark %v added 'runtime' to its results", b))
}
output["runtime"] = time.Now().Sub(start)
ret := map[string]interface{}{"output": output, "metadata": b}
benchmarks[i] = ret
}
results["total_runtime"] = time.Now().Sub(total_start)
return results
}
// serial takes a variable number of Benchmarks and returns a Benchmark
// which combines then and will run each serially.
func serial(bs ...bench.Benchmark) bench.Benchmark {
return &serialBenchmark{
benchmarkers: bs,
}
}
// readCSVRow reads a bitmap/profile pair from a CSV row.
func readCSVRow(r *csv.Reader) (bitmapID, profileID uint64, timestamp int64, err error) {
// Read CSV row.
@ -1709,12 +1024,5 @@ func readCSVRow(r *csv.Reader) (bitmapID, profileID uint64, timestamp int64, err
return bitmapID, profileID, timestamp, nil
}
func customSplit(s string, sep string) []string {
if s == "" {
return []string{}
}
return strings.Split(s, sep)
}
// errBlank indicates a blank row in a CSV file.
var errBlank = errors.New("blank row")

View file

@ -1,8 +0,0 @@
{
"benchmarks": [
{
"num": 3,
"args": ["multi-db-set-bits", "-iterations", "30000", "-client-type", "round_robin"]
}
]
}

View file

@ -1,11 +0,0 @@
{
"benchmarks": [
{
"num": 3,
"args": [
"random-set-bits", "-iterations", "30000", "-profile-id-range", "1000000", "-bitmap-id-range", "1000000", "-seed", "2345", "-client-type", "round_robin", "-db", "randsetandquery",
"random-query", "-iterations", "1000", "-bitmap-id-range", "1000000", "-seed", "1239", "-dbs", "randsetandquery"
]
}
]
}

View file

@ -1,8 +0,0 @@
{
"benchmarks": [
{
"num": 3,
"args": ["random-set-bits", "-iterations", "30000", "-profile-id-range", "1000000", "-bitmap-id-range", "1000000", "-seed", "2345", "-client-type", "round_robin"]
}
]
}

View file

@ -1,8 +0,0 @@
{
"benchmarks": [
{
"num": 1,
"args": ["-human", "slice-height", "-max-time", "1", "-max-bits-per-map", "100"]
}
]
}

View file

@ -1,14 +0,0 @@
{
"benchmarks": [
{
"num": 3,
"name": "set-diags",
"args": ["diagonal-set-bits", "-iterations", "30000", "-client-type", "round_robin"]
},
{
"num": 2,
"name": "rand-plus-zipf",
"args": ["random-set-bits", "-iterations", "20000", "zipf", "-iterations", "100"]
}
]
}

View file

@ -1,12 +0,0 @@
{
"benchmarks": [
{
"num": 1,
"args": ["zipf", "-iterations", "10000", "-profile-id-range", "100", "-bitmap-id-range", "100", "-seed", "2345", "-client-type", "round_robin", "-bitmap-exponent", "1.001", "-bitmap-ratio", ".9", "-profile-exponent", "1.001", "-profile-ratio", ".3"]
},
{
"num": 1,
"args": ["zipf", "-iterations", "10000", "-profile-id-range", "100", "-bitmap-id-range", "100", "-seed", "2345", "-client-type", "round_robin", "-bitmap-exponent", "1.001", "-bitmap-ratio", ".9", "-profile-exponent", "1.001", "-profile-ratio", ".3", "-operation", "clear"]
}
]
}

View file

@ -1,11 +0,0 @@
// creator contains code for standing up pilosa clusters
package creator
import "io"
type Cluster interface {
Start() error
Hosts() []string
Shutdown() error
Logs() []io.Reader
}

View file

@ -1,94 +0,0 @@
package creator
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"github.com/pilosa/pilosa"
)
type LocalCluster struct {
ReplicaN int
ServerN int
hosts []string
logs []io.Reader
servers []*pilosa.Server
cluster *pilosa.Cluster
path string
}
func (localCluster *LocalCluster) Start() error {
BasePort := 19327
localCluster.hosts = make([]string, localCluster.ServerN)
localCluster.servers = make([]*pilosa.Server, localCluster.ServerN)
localCluster.logs = make([]io.Reader, localCluster.ServerN)
path, err := ioutil.TempDir("", "pilosa-bench-")
if err != nil {
return err
}
localCluster.path = path
// Build cluster configuration.
cluster := pilosa.NewCluster()
cluster.ReplicaN = localCluster.ReplicaN
for i := 0; i < localCluster.ServerN; i++ {
cluster.Nodes = append(cluster.Nodes, &pilosa.Node{
Host: fmt.Sprintf("localhost:%d", BasePort+i),
})
}
localCluster.cluster = cluster
// Build servers.
for i := range localCluster.servers {
// Make server work directory.
if err := os.MkdirAll(filepath.Join(path, strconv.Itoa(i)), 0777); err != nil {
return err
}
// Build server.
s := pilosa.NewServer()
s.Host = fmt.Sprintf("localhost:%d", BasePort+i)
s.Cluster = cluster
s.Index.Path = filepath.Join(path, strconv.Itoa(i), "data")
// Create log stream
localCluster.logs[i], s.LogOutput = io.Pipe()
localCluster.servers[i] = s
}
// Open all servers.
for i, s := range localCluster.servers {
if err := s.Open(); err != nil {
return err
}
localCluster.hosts[i] = s.Host
}
return nil
}
func (c *LocalCluster) Hosts() []string { return c.hosts }
func (c *LocalCluster) Logs() []io.Reader { return c.logs }
func (c *LocalCluster) Shutdown() error {
errs := ""
for _, s := range c.servers {
if err := s.Close(); err != nil {
errs = errs + err.Error() + "; "
}
}
if err := os.RemoveAll(c.path); err != nil {
errs = errs + err.Error() + ";"
}
if errs != "" {
return fmt.Errorf(errs)
}
return nil
}

View file

@ -1,187 +0,0 @@
package creator
import (
"fmt"
"io"
"math/rand"
"net"
"strconv"
"sync"
"time"
"github.com/BurntSushi/toml"
"github.com/pilosa/pilosa"
"github.com/pilosa/pilosa/build"
pssh "github.com/pilosa/pilosa/ssh"
"golang.org/x/crypto/ssh"
)
type RemoteCluster struct {
ClusterHosts []string
ReplicaN int
SSHUser string
Keyfile string
Key []byte
GoMaxProcs int
CopyBinary bool
GOOS string
GOARCH string
Stderr io.Writer
wg *sync.WaitGroup
logs []io.Reader
sessions []*ssh.Session
pipeRs []*io.PipeReader
pipeWs []*io.PipeWriter
stdins []io.WriteCloser
}
// Start creates a configuration for each host in the cluster, copies it to the
// node, and starts the pilosa process on the remote host.
func (c *RemoteCluster) Start() error {
c.logs = make([]io.Reader, 0)
if len(c.ClusterHosts) == 0 {
return fmt.Errorf("no type or hosts specified - cannot continue")
}
fleet, err := pssh.NewFleet(c.ClusterHosts, c.SSHUser, c.Keyfile, c.Stderr)
if err != nil {
return fmt.Errorf("connecting to cluster hosts: %v", err)
}
cmdName := "pilosa"
if c.CopyBinary {
cmdName = "/tmp/pilosa" + strconv.Itoa(rand.Int())
fmt.Fprintf(c.Stderr, "create: building pilosa binary with GOOS=%v and GOARCH=%v to copy to hosts at %v", c.GOOS, c.GOARCH, cmdName)
pkg := "github.com/pilosa/pilosa/cmd/pilosa"
bin, err := build.Binary(pkg, c.GOOS, c.GOARCH)
if err != nil {
return fmt.Errorf("building binary: %v", err)
}
err = fleet.WriteFile(cmdName, "+x", bin)
if err != nil {
return fmt.Errorf("writing binary to fleet: %v", err)
}
}
// build config
conf := pilosa.NewConfigForHosts(c.ClusterHosts)
conf.Cluster.ReplicaN = c.ReplicaN
// copy config to remote hosts and start pilosa
c.wg = &sync.WaitGroup{}
for _, hostport := range c.ClusterHosts {
// Set up config for this host
host, port, err := net.SplitHostPort(hostport)
if err != nil {
return fmt.Errorf("splitting hostport: %v", err)
}
conf.Host = hostport
conf.DataDir = "~/.pilosa" + port
// Get client for host
client, err := fleet.Get(host)
if err != nil {
return fmt.Errorf("connecting to host: %v", err)
}
configname := "pilosa" + port + ".conf"
w, err := client.OpenFile(configname, "")
if err != nil {
return fmt.Errorf("opening remote config file: %v", err)
}
enc := toml.NewEncoder(w)
err = enc.Encode(conf)
if err != nil {
return fmt.Errorf("encoding config: %v", err)
}
err = w.Close()
if err != nil {
return fmt.Errorf("closing config writer: %v", err)
}
// Start pilosa on remote host
sess, err := client.NewSession()
if err != nil {
return err
}
// Have to request pty in order to be able to kill remote process
// reliably.
modes := ssh.TerminalModes{
ssh.ISIG: 1,
ssh.ECHO: 0,
}
err = sess.RequestPty("vt100", 40, 80, modes)
if err != nil {
return fmt.Errorf("request pty error: %v", err)
}
pipeR, pipeW := io.Pipe()
sess.Stdout = pipeW
sess.Stderr = pipeW
inpipe, err := sess.StdinPipe()
if err != nil {
return err
}
c.logs = append(c.logs, pipeR)
c.sessions = append(c.sessions, sess)
c.pipeRs = append(c.pipeRs, pipeR)
c.pipeWs = append(c.pipeWs, pipeW)
c.stdins = append(c.stdins, inpipe)
gomaxprocsString := ""
if c.GoMaxProcs != 0 {
gomaxprocsString = "GOMAXPROCS=" + strconv.Itoa(c.GoMaxProcs) + " "
}
err = sess.Start(gomaxprocsString + cmdName + " -config " + configname)
if err != nil {
return err
}
c.wg.Add(1)
go func() {
defer c.wg.Done()
err = sess.Wait()
if err != nil {
fmt.Fprintf(c.Stderr, "problem with remote pilosa process: %v\n", err)
}
}()
}
return nil
}
func (c *RemoteCluster) Hosts() []string { return c.ClusterHosts }
func (c *RemoteCluster) Logs() []io.Reader { return c.logs }
func (c *RemoteCluster) Shutdown() error {
for i, sess := range c.sessions {
var err error
_, err = c.stdins[i].Write([]byte{3}) // Send Control C
if err != nil {
fmt.Fprintf(c.Stderr, "Error write-signaling remote process: %v\n", err)
}
// signaling isn't supported by many ssh servers - hence the hack above
err = sess.Signal(ssh.SIGINT)
if err != nil {
fmt.Fprintf(c.Stderr, "Error signaling remote process: %v\n", err)
}
}
done := make(chan struct{}, 1)
go func() {
c.wg.Wait()
close(done)
}()
select {
case <-done:
return nil
case <-time.After(time.Second * 5):
for _, sess := range c.sessions {
err := sess.Close()
if err != nil {
fmt.Fprintf(c.Stderr, "Error closing remote session: %v\n", err)
}
}
return fmt.Errorf("timed out waiting for remote processes to exit")
}
}