-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2d057f6
Showing
11 changed files
with
526 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
bin/* | ||
tmp/* | ||
.idea/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
APPNAME = noisia | ||
|
||
COMMIT=$(shell git rev-parse --short HEAD) | ||
BRANCH=$(shell git rev-parse --abbrev-ref HEAD) | ||
|
||
LDFLAGS = -a -installsuffix cgo -ldflags "-X main.appName=${APPNAME} -X main.gitCommit=${COMMIT} -X main.gitBranch=${BRANCH}" | ||
|
||
.PHONY: help clean lint test race build | ||
|
||
.DEFAULT_GOAL := help | ||
|
||
help: ## Display this help screen | ||
@echo "Makefile available targets:" | ||
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " * \033[36m%-15s\033[0m %s\n", $$1, $$2}' | ||
|
||
clean: ## Clean | ||
rm -f ./bin/${APPNAME} | ||
rmdir ./bin | ||
|
||
dep: ## Get the dependencies | ||
go mod download | ||
|
||
lint: ## Lint the source files | ||
golangci-lint run --timeout 5m -E golint -e '(method|func) [a-zA-Z]+ should be [a-zA-Z]+' | ||
|
||
test: dep ## Run unittests | ||
go test -short -timeout 300s -p 1 ./... | ||
|
||
race: dep ## Run data race detector | ||
go test -race -short -timeout 300s -p 1 ./... | ||
|
||
build: dep ## Build | ||
mkdir -p ./bin | ||
CGO_ENABLED=0 GOOS=linux GOARCH=${GOARCH} go build ${LDFLAGS} -o bin/${APPNAME} ./app/cmd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Noisia | ||
Harmful workload generator for PostgreSQL. | ||
- idle transactions | ||
- see built-in help for more runtime options. | ||
|
||
**ATTENTION: Use only for testing purposes, don't execute against production, reckless usage might cause problems.** | ||
|
||
--- | ||
#### TODO | ||
- rollbacks | ||
- waiting transactions | ||
- deadlocks | ||
- temporary files |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package app | ||
|
||
import ( | ||
"context" | ||
"github.com/lesovsky/noisia/app/internal/log" | ||
) | ||
|
||
func Start(ctx context.Context, c *Config) error { | ||
if c.IdleXact { | ||
err := runIdleXactWorkload(ctx, c) | ||
if err != nil { | ||
log.Errorf("idle transactions workload failed: %s", err) | ||
} | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/lesovsky/noisia/app" | ||
"github.com/lesovsky/noisia/app/internal/log" | ||
"gopkg.in/alecthomas/kingpin.v2" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
) | ||
|
||
var ( | ||
appName, gitCommit, gitBranch string | ||
) | ||
|
||
func main() { | ||
var ( | ||
showVersion = kingpin.Flag("version", "show version and exit").Default().Bool() | ||
logLevel = kingpin.Flag("log-level", "set log level: debug, info, warn, error").Default("warn").Envar("NOISIA_LOG_LEVEL").String() | ||
postgresConninfo = kingpin.Flag("conninfo", "Postgres connection string (DSN or URL), must be specified explicitly").Default("").Envar("NOISIA_POSTGRES_CONNINFO").String() | ||
jobs = kingpin.Flag("jobs", "Run workload with specified number of workers").Default("1").Envar("NOISIA_JOBS").Uint16() | ||
idleXact = kingpin.Flag("idle-xact", "Run idle transactions workload").Default("false").Envar("NOISIA_IDLE_XACT").Bool() | ||
idleXactNaptimeMin = kingpin.Flag("idle-xact.naptime-min", "Min transactions naptime, in seconds").Default("5").Envar("NOISIA_IDLE_XACT_NAPTIME_MIN").Int() | ||
idleXactNaptimeMax = kingpin.Flag("idle-xact.naptime-max", "Max transactions naptime, in seconds").Default("20").Envar("NOISIA_IDLE_XACT_NAPTIME_MAX").Int() | ||
) | ||
kingpin.Parse() | ||
log.SetLevel(*logLevel) | ||
|
||
if *showVersion { | ||
fmt.Printf("%s %s-%s\n", appName, gitCommit, gitBranch) | ||
os.Exit(0) | ||
} | ||
|
||
config := &app.Config{ | ||
PostgresConninfo: *postgresConninfo, | ||
Jobs: *jobs, | ||
IdleXact: *idleXact, | ||
IdleXactNaptimeMin: *idleXactNaptimeMin, | ||
IdleXactNaptimeMax: *idleXactNaptimeMax, | ||
} | ||
|
||
if err := config.Validate(); err != nil { | ||
log.Errorln(err) | ||
os.Exit(1) | ||
} | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
|
||
var doExit = make(chan error, 2) | ||
go func() { | ||
doExit <- listenSignals() | ||
cancel() | ||
}() | ||
|
||
go func() { | ||
doExit <- app.Start(ctx, config) | ||
cancel() | ||
}() | ||
|
||
log.Warnf("shutdown: %s", <-doExit) | ||
} | ||
|
||
func listenSignals() error { | ||
c := make(chan os.Signal, 1) | ||
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) | ||
return fmt.Errorf("got %s", <-c) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package app | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
type Config struct { | ||
PostgresConninfo string | ||
Jobs uint16 // max 65535 | ||
IdleXact bool | ||
IdleXactNaptimeMin int | ||
IdleXactNaptimeMax int | ||
} | ||
|
||
func (c *Config) Validate() error { | ||
if c.PostgresConninfo == "" { | ||
return errors.New("'conninfo' is not specified") | ||
} | ||
|
||
if c.IdleXactNaptimeMin < 1 || c.IdleXactNaptimeMin > c.IdleXactNaptimeMax { | ||
return errors.New("wrong 'idle-xact.naptime-min' or 'idle-xact.naptime-max' specified") | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package app | ||
|
||
import ( | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
) | ||
|
||
func TestConfig_Validate(t *testing.T) { | ||
testcases := []struct { | ||
config Config | ||
valid bool | ||
}{ | ||
{config: Config{PostgresConninfo: "127.0.0.1", IdleXactNaptimeMin: 10, IdleXactNaptimeMax: 20}, valid: true}, | ||
{config: Config{}, valid: false}, | ||
{config: Config{PostgresConninfo: "127.0.0.1", IdleXactNaptimeMin: 0, IdleXactNaptimeMax: 10}, valid: false}, | ||
{config: Config{PostgresConninfo: "127.0.0.1", IdleXactNaptimeMin: 10, IdleXactNaptimeMax: 5}, valid: false}, | ||
} | ||
for _, tc := range testcases { | ||
if tc.valid { | ||
assert.NoError(t, tc.config.Validate()) | ||
} else { | ||
assert.Error(t, tc.config.Validate()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package app | ||
|
||
import ( | ||
"context" | ||
"github.com/jackc/pgx/v4/pgxpool" | ||
"github.com/lesovsky/noisia/app/internal/log" | ||
"math/rand" | ||
"time" | ||
) | ||
|
||
func runIdleXactWorkload(ctx context.Context, config *Config) error { | ||
log.Infoln("Starting idle transactions workload") | ||
|
||
pool, err := pgxpool.Connect(context.Background(), config.PostgresConninfo) | ||
if err != nil { | ||
return err | ||
} | ||
defer pool.Close() | ||
|
||
// keep specified number of workers using channel - run new workers until there is any free slot | ||
guard := make(chan struct{}, config.Jobs) | ||
for { | ||
select { | ||
// run workers only when it's possible to write into channel (channel is limited by number of jobs) | ||
case guard <- struct{}{}: | ||
go func() { | ||
naptime := time.Duration(rand.Intn(config.IdleXactNaptimeMax-config.IdleXactNaptimeMin)+config.IdleXactNaptimeMin) * time.Second | ||
|
||
log.Infof("starting xact with naptime %s", naptime) | ||
err := startSingleIdleXact(context.Background(), pool, naptime) | ||
if err != nil { | ||
log.Errorln(err) | ||
} | ||
|
||
// when worker finished, read from the channel to allow starting another workers | ||
<-guard | ||
}() | ||
case <-ctx.Done(): | ||
log.Info("exit signaled, stop idle transaction workload") | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
func startSingleIdleXact(ctx context.Context, pool *pgxpool.Pool, naptime time.Duration) error { | ||
tx, err := pool.Begin(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
rows, err := tx.Query(ctx, "SELECT * FROM pg_stat_database") | ||
if err != nil { | ||
return err | ||
} | ||
rows.Close() | ||
time.Sleep(naptime) | ||
|
||
if err := tx.Commit(ctx); err != nil { | ||
log.Warnln(err) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package log | ||
|
||
import ( | ||
"fmt" | ||
"github.com/rs/zerolog" | ||
"os" | ||
) | ||
|
||
// Logger is the global logger with predefined settings | ||
var Logger = zerolog.New(os.Stdout).With().Timestamp().Logger() | ||
|
||
// KV is a simple key-value store | ||
type KV map[string]string | ||
|
||
// SetLevel sets logging level | ||
func SetLevel(level string) { | ||
switch level { | ||
case "debug": | ||
zerolog.SetGlobalLevel(zerolog.DebugLevel) | ||
case "info": | ||
zerolog.SetGlobalLevel(zerolog.InfoLevel) | ||
case "warn": | ||
zerolog.SetGlobalLevel(zerolog.WarnLevel) | ||
case "error": | ||
zerolog.SetGlobalLevel(zerolog.ErrorLevel) | ||
default: | ||
zerolog.SetGlobalLevel(zerolog.InfoLevel) | ||
} | ||
} | ||
|
||
func New() zerolog.Logger { | ||
var logger = Logger | ||
return logger | ||
} | ||
|
||
// SetApplication appends application name string to log messages | ||
func SetApplication(app string) { | ||
Logger = Logger.With().Str("service", app).Logger() | ||
} | ||
|
||
// Debug prints message with DEBUG severity | ||
func Debug(msg string) { | ||
Logger.Debug().Msg(msg) | ||
} | ||
|
||
// Debugf prints formatted message with DEBUG severity | ||
func Debugf(format string, v ...interface{}) { | ||
Logger.Debug().Msgf(format, v...) | ||
} | ||
|
||
// Debugln concatenates arguments and prints them with DEBUG severity | ||
func Debugln(v ...interface{}) { | ||
Logger.Debug().Msg(fmt.Sprint(v...)) | ||
} | ||
|
||
// Info prints message with INFO severity | ||
func Info(msg string) { | ||
Logger.Info().Msg(msg) | ||
} | ||
|
||
// Infof prints formatted message with INFO severity | ||
func Infof(format string, v ...interface{}) { | ||
Logger.Info().Msgf(format, v...) | ||
} | ||
|
||
// Infoln concatenates arguments and prints them with INFO severity | ||
func Infoln(v ...interface{}) { | ||
Logger.Info().Msg(fmt.Sprint(v...)) | ||
} | ||
|
||
// Warn prints message with WARNING severity | ||
func Warn(msg string) { | ||
Logger.Warn().Msg(msg) | ||
} | ||
|
||
// Warnf prints formatted message with WARNING severity | ||
func Warnf(format string, v ...interface{}) { | ||
Logger.Warn().Msgf(format, v...) | ||
} | ||
|
||
// Warnln concatenates arguments and prints them with WARNING severity | ||
func Warnln(v ...interface{}) { | ||
Logger.Warn().Msg(fmt.Sprint(v...)) | ||
} | ||
|
||
// Error prints message with ERROR severity | ||
func Error(msg string) { | ||
Logger.Error().Msg(msg) | ||
} | ||
|
||
// Errorf prints formatted message with ERROR severity | ||
func Errorf(format string, v ...interface{}) { | ||
Logger.Error().Msgf(format, v...) | ||
} | ||
|
||
// Errorln concatenates arguments and prints them with ERROR severity | ||
func Errorln(v ...interface{}) { | ||
Logger.Error().Msg(fmt.Sprint(v...)) | ||
} | ||
|
||
// KVError prints message with ERROR severity with attached KV map | ||
func KVError(kv KV, msg string) { | ||
log := Logger.Error() | ||
for k, v := range kv { | ||
log.Str(k, v) | ||
} | ||
log.Msg(msg) | ||
} | ||
|
||
// KVErrorf prints formatted message with ERROR severity with attached KV map | ||
func KVErrorf(kv KV, format string, v ...interface{}) { | ||
log := Logger.Error() | ||
for k, v := range kv { | ||
log.Str(k, v) | ||
} | ||
log.Msgf(format, v...) | ||
} | ||
|
||
// KVErrorln concatenates arguments and prints them with ERROR severity with attached KV map | ||
func KVErrorln(kv KV, v ...interface{}) { | ||
log := Logger.Error() | ||
for k, v := range kv { | ||
log.Str(k, v) | ||
} | ||
log.Msg(fmt.Sprint(v...)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module github.com/lesovsky/noisia | ||
|
||
go 1.14 | ||
|
||
require ( | ||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect | ||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect | ||
github.com/jackc/pgx/v4 v4.6.0 | ||
github.com/rs/zerolog v1.19.0 | ||
github.com/stretchr/testify v1.5.1 | ||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 | ||
) |
Oops, something went wrong.