func (rn *Runner) Cleanup() error {
	var err error
	if rn.peek {
		return err
	}
	if !rn.simOnly && rn.dbName == mdb.KeyholeDB {
		ctx := context.Background()
		if rn.collectionName == mdb.ExamplesCollection {
			rn.Logger.Info("dropping collection", mdb.KeyholeDB, mdb.ExamplesCollection)
			if err = rn.client.Database(mdb.KeyholeDB).Collection(mdb.ExamplesCollection).Drop(ctx); err != nil {
				rn.Logger.Info(err)
			}
		}
		rn.Logger.Info("dropping temp database", mdb.KeyholeDB)
		if err = rn.client.Database(rn.dbName).Drop(ctx); err != nil {
			rn.Logger.Info(err)
		}
	}
	time.Sleep(time.Second)
	return err
}
Cleanup drops the temp database
package sim
import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"sync"
	"syscall"
	"time"
	"github.com/simagix/gox"
	anly "github.com/simagix/keyhole/analytics"
	"github.com/simagix/keyhole/mdb"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/x/mongo/driver/connstring"
)
const (
	outdir = "./out"
)
type Runner struct {
	Logger  *gox.Logger         `bson:"keyhole"`
	Metrics map[string][]bson.M `bson:"metrics"`
	Results []string            `bson:"results"`
	auto           bool
	channel        chan string
	client         *mongo.Client
	clusterType    string
	collectionName string
	conns          int
	connString     connstring.ConnString
	dbName         string
	drop           bool
	duration       int
	filename       string
	mutex          sync.RWMutex
	peek           bool
	simOnly        bool
	tps            int
	txFilename     string
	uri            string
	uriList        []string
	verbose        bool
}
func NewRunner(connString connstring.ConnString) (*Runner, error) {
	var err error
	runner := Runner{Logger: gox.GetLogger("keyhole"), connString: connString, conns: runtime.NumCPU(),
		channel: make(chan string), collectionName: mdb.ExamplesCollection, Metrics: map[string][]bson.M{},
		mutex: sync.RWMutex{}}
	runner.dbName = connString.Database
	if runner.dbName == "" {
		runner.dbName = mdb.KeyholeDB
	}
	if runner.client, err = mdb.NewMongoClient(connString.String()); err != nil {
		return &runner, err
	}
	stats := mdb.NewClusterStats("")
	stats.GetClusterStatsSummary(runner.client)
	runner.clusterType = stats.Cluster
	if runner.clusterType == "" {
		runner.Logger.Warn("unable to retrieve cluster type")
	}
	runner.uriList = []string{connString.String()}
	if runner.clusterType == mdb.Sharded {
		if shards, err := mdb.GetShards(runner.client); err != nil {
			return &runner, err
		} else if runner.uriList, err = mdb.GetAllShardURIs(shards, connString); err != nil {
			return &runner, err
		}
	}
	runner.uri = runner.uriList[len(runner.uriList)-1]
	return &runner, nil
}
func (rn *Runner) SetCollection(collectionName string) {
	if collectionName != "" {
		rn.collectionName = collectionName
	} else {
		rn.collectionName = mdb.ExamplesCollection
	}
}
func (rn *Runner) SetTPS(tps int) {
	rn.tps = tps
}
func (rn *Runner) SetAutoMode(auto bool) { rn.auto = auto }
func (rn *Runner) SetTemplateFilename(filename string) {
	rn.filename = filename
}
func (rn *Runner) SetVerbose(verbose bool) {
	rn.verbose = verbose
}
func (rn *Runner) SetPeekingMode(mode bool) {
	rn.peek = mode
	if rn.peek {
		go func(x int) {
			time.Sleep(time.Duration(x) * time.Minute)
			rn.terminate()
		}(rn.duration)
	}
}
func (rn *Runner) SetDuration(duration int) {
	rn.duration = duration
}
func (rn *Runner) SetDropFirstMode(mode bool) {
	rn.drop = mode
}
func (rn *Runner) SetNumberConnections(num int) {
	if num == 0 {
		return
	}
	rn.conns = num
}
func (rn *Runner) SetTransactionTemplate(filename string) {
	rn.txFilename = filename
}
func (rn *Runner) SetSimOnlyMode(mode bool) {
	rn.simOnly = mode
}
func (rn *Runner) Start() error {
	var err error
	if rn.peek {
		return nil
	}
	if !rn.auto {
		reader := bufio.NewReader(os.Stdin)
		fmt.Print("Begin a load test [y/N]: ")
		text, _ := reader.ReadString('\n')
		text = strings.Replace(text, "\n", "", -1)
		if text != "y" && text != "Y" {
			os.Exit(0)
		}
	}
	rn.Logger.Info("Duration in minute(s):", rn.duration)
	if rn.dbName == "" || rn.dbName == "admin" || rn.dbName == "config" || rn.dbName == "local" {
		rn.dbName = mdb.KeyholeDB  
	}
	if rn.drop {
		rn.Cleanup()
	}
	rn.initSimDocs()
	tdoc := GetTransactions(rn.txFilename)
	rn.Logger.Info(fmt.Sprintf("Total TPS: %d (%d tps/conn * %d conns), duration: %d (mins)", rn.tps*rn.conns, rn.tps, rn.conns, rn.duration))
	simTime := rn.duration
	if !rn.simOnly {
		simTime--
		rn.createIndexes(tdoc.Indexes)
	}
	for i := 0; i < rn.conns; i++ {
		go func(thread int) {
			if !rn.simOnly && rn.duration > 0 {
				if err = rn.PopulateData(); err != nil {
					rn.Logger.Info("Thread", thread, "existing with", err)
					return
				}
				time.Sleep(10 * time.Millisecond)
			}
			if err = rn.Simulate(simTime, tdoc.Transactions, thread); err != nil {
				rn.Logger.Info("Thread", thread, "existing with", err)
				return
			}
		}(i)
	}
	return nil
}
func (rn *Runner) terminate() {
	var err error
	var client *mongo.Client
	var filename string
	var filenames []string
	var result string
	os.Mkdir(outdir, 0755)
	rn.Cleanup()
	rn.Results = []string{}
	for _, uri := range rn.uriList {
		if client, err = mdb.NewMongoClient(uri); err != nil {
			rn.Logger.Info(err)
			continue
		}
		var err error
		var stat anly.ServerStatusDoc
		serverStatus, _ := mdb.RunAdminCommand(client, "serverStatus")
		buf, _ := bson.Marshal(serverStatus)
		bson.Unmarshal(buf, &stat)
		serverStatusDocs[uri] = append(serverStatusDocs[uri], stat)
		var data []byte
		if data, err = getServerStatusData(uri); err != nil {
			rn.Logger.Error(err)
			break
		}
		filename := outdir + keyholeStatsDataFile + "-" + getReplicaSetName(uri) + ".gz"
		filenames = append(filenames, filename)
		gox.OutputGzipped(data, filename)
		d := anly.NewDiagnosticData()
		reader := bufio.NewReader(strings.NewReader(string(data)))
		if err = d.AnalyzeServerStatus(reader); err != nil {
			rn.Logger.Error(err)
			break
		}
		strs := []string{}
		if d.ServerInfo != nil {
			var p mdb.ClusterStats
			data, _ := json.Marshal(d.ServerInfo)
			json.Unmarshal(data, &p)
			result := fmt.Sprintf("\n* MongoDB v%v %v (%v) %v %v %v cores %v mem",
				p.BuildInfo.Version, p.HostInfo.System.Hostname, p.HostInfo.OS.Name,
				p.ServerStatus.Process, p.Cluster, p.HostInfo.System.NumCores, p.HostInfo.System.MemSizeMB)
			strs = append(strs, result)
		}
		strs = append(strs, anly.PrintAllStats(d.ServerStatusList, -1))
		result = strings.Join(strs, "\n")
		fmt.Println(result)
		rn.Results = append(rn.Results, result)
	}
	hostname, _ := os.Hostname()
	filename = fmt.Sprintf(`%s%s.%s-perf.bson.gz`, outdir, hostname, fileTimestamp)
	var buf []byte
	if buf, err = bson.Marshal(rn); err != nil {
		rn.Logger.Info("marshal error:", err)
	}
	gox.OutputGzipped(buf, filename)
	filenames = append(filenames, filename)
	zipFile := fmt.Sprintf(`%s%s.%s-perf.zip`, outdir, hostname, fileTimestamp)
	gox.ZipFiles(zipFile, filenames)
	rn.Logger.Info("stats written to ", zipFile)
	for _, f := range filenames {
		os.Remove(f)
	}
	os.Exit(0)
}
func (rn *Runner) CollectAllStatus() error {
	var err error
	for i, uri := range rn.uriList {
		var client *mongo.Client
		if client, err = mdb.NewMongoClient(uri); err != nil {
			rn.Logger.Info(err)
			continue
		}
		stats := NewServerStats(uri, rn.channel)
		stats.SetVerbose(rn.verbose)
		stats.SetPeekingMode(rn.peek)
		go stats.getDBStats(client, rn.dbName)
		go stats.getReplSetGetStatus(client)
		go stats.getServerStatus(client)
		go stats.getMongoConfig(client)
		if i == 0 {
			go stats.collectMetrics(client, uri)
		}
	}
	quit := make(chan os.Signal, 2)
	signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
	timer := time.NewTimer(time.Duration(rn.duration) * time.Minute)
	for {
		select {
		case <-quit:
			rn.terminate()
		case <-timer.C:
			rn.terminate()
		default:
			rn.Logger.Info(<-rn.channel)
		}
		time.Sleep(50 * time.Millisecond)
	}
}
func (rn *Runner) createIndexes(docs []bson.M) error {
	var err error
	var ctx = context.Background()
	c := rn.client.Database(rn.dbName).Collection(rn.collectionName)
	indexView := c.Indexes()
	idx := mongo.IndexModel{Keys: bson.D{{Key: "_search", Value: 1}}}
	if _, err = indexView.CreateOne(ctx, idx); err != nil {
		return err
	}
	if len(docs) == 0 {
		idx = mongo.IndexModel{Keys: bson.D{{Key: "email", Value: 1}}}
		if _, err = indexView.CreateOne(ctx, idx); err != nil {
			return err
		}
		if rn.clusterType == mdb.Sharded {
			if err = rn.splitChunks(); err != nil {
				fmt.Println(err)
			}
		}
	}
	for _, doc := range docs {
		keys := bson.D{}
		for k, v := range doc {
			x := int32(1)
			switch v := v.(type) {
			case int:
				if v < 0 {
					x = -1
				}
			case float64:
				if v < 0 {
					x = -1
				}
			}
			keys = append(keys, bson.E{Key: k, Value: x})
		}
		idx := mongo.IndexModel{
			Keys: keys,
		}
		if _, err = indexView.CreateOne(ctx, idx); err != nil {
			return err
		}
	}
	return err
}