diff --git a/Makefile b/Makefile index 466cd7e..174663a 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ MIGRATE_DIR := ./migrations SERVICE := $(PROJECT).server .PHONY: install migrate migrate-create clean build release -.PHONY: test test-one test-fuzz test-js lint-js typecheck-js +.PHONY: test test-one test-fuzz test-conn test-js lint-js typecheck-js .PHONY: reset-db setup-local server server-profile js-install .PHONY: client-update client-large-update client-get client-rebuild client-pack .PHONY: client-gc-contents client-gc-project client-gc-random-projects @@ -123,6 +123,11 @@ test-fuzz: export DL_SKIP_SSL_VERIFICATION=1 test-fuzz: reset-db go run cmd/fuzz-test/main.go --server $(GRPC_SERVER) --iterations 1000 --projects 5 +test-conn: export DL_TOKEN=$(DEV_TOKEN_ADMIN) +test-conn: export DL_SKIP_SSL_VERIFICATION=1 +test-conn: + go run cmd/conn-test/main.go --server $(GRPC_SERVER) + test-js: js-install cd js && npm run test @@ -141,7 +146,7 @@ setup-local: reset-db server: export DL_ENV=dev server: internal/pb/fs.pb.go internal/pb/fs_grpc.pb.go go run cmd/server/main.go --dburi $(DB_URI) --port $(GRPC_PORT) - + server-profile: export DL_ENV=dev server-profile: internal/pb/fs.pb.go internal/pb/fs_grpc.pb.go go run cmd/server/main.go --dburi $(DB_URI) --port $(GRPC_PORT) --profile cpu.prof --log-level info @@ -243,4 +248,4 @@ load-test-get: $(call load-test,Get,get.json,100000,40,5000) load-test-get-compress: - $(call load-test,GetCompress,get-compress.json,100000,40,5000) \ No newline at end of file + $(call load-test,GetCompress,get-compress.json,100000,40,5000) diff --git a/cmd/conn-test/main.go b/cmd/conn-test/main.go new file mode 100644 index 0000000..ba2435d --- /dev/null +++ b/cmd/conn-test/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/gadget-inc/dateilager/pkg/cli" + +func main() { + cli.ConnTestExecute() +} diff --git a/internal/testutil/connection.go b/internal/testutil/connection.go new file mode 100644 index 0000000..19002b9 --- /dev/null +++ b/internal/testutil/connection.go @@ -0,0 +1,89 @@ +package testutil + +import ( + "context" + "fmt" + "os" + "time" + + dlc "github.com/gadget-inc/dateilager/pkg/client" +) + +type AttemptResult int + +const ( + Ok AttemptResult = iota + TimedOut + Error +) + +type Results struct { + count int + failCount int + downtimeStart *time.Time + downtimes []time.Duration +} + +func (r *Results) Add(attempt AttemptResult) { + now := time.Now() + + r.count += 1 + if attempt != Ok { + r.failCount += 1 + } + + switch { + case r.downtimeStart == nil && attempt != Ok: + r.downtimeStart = &now + case r.downtimeStart != nil && attempt == Ok: + r.downtimes = append(r.downtimes, now.Sub(*r.downtimeStart)) + r.downtimeStart = nil + } +} + +func (r *Results) Summarize() { + var max time.Duration + for _, duration := range r.downtimes { + if duration > max { + max = duration + } + } + + fmt.Println("--- Result ---") + fmt.Printf("request count: %d\n", r.count) + fmt.Printf("success rate: %2.f%%\n", float32(r.count-r.failCount)/float32(r.count)*100) + fmt.Printf("max downtime: %s\n", max.String()) +} + +func tryConnect(ctx context.Context, client *dlc.Client, timeout time.Duration) AttemptResult { + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + _, err := client.Get(ctx, 1, "a", nil, dlc.VersionRange{}) + if os.IsTimeout(err) { + fmt.Printf("conn timed out: %v\n", err) + return TimedOut + } + if err != nil { + fmt.Printf("conn error: %v\n", err) + return Error + } + return Ok +} + +func TestConnection(ctx context.Context, client *dlc.Client) error { + results := Results{} + + clock := time.NewTicker(100 * time.Millisecond) + defer clock.Stop() + + for { + select { + case <-ctx.Done(): + results.Summarize() + return nil + case <-clock.C: + results.Add(tryConnect(ctx, client, 50*time.Millisecond)) + } + } +} diff --git a/pkg/cli/connTest.go b/pkg/cli/connTest.go new file mode 100644 index 0000000..c0a3700 --- /dev/null +++ b/pkg/cli/connTest.go @@ -0,0 +1,87 @@ +package cli + +import ( + "context" + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/gadget-inc/dateilager/internal/logger" + "github.com/gadget-inc/dateilager/internal/testutil" + dlc "github.com/gadget-inc/dateilager/pkg/client" + "github.com/gadget-inc/dateilager/pkg/version" + "github.com/spf13/cobra" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func NewConnTestCommand() *cobra.Command { + var client *dlc.Client + + var ( + server string + ) + + cmd := &cobra.Command{ + Use: "conn-test", + Short: "DateiLager connection test", + DisableAutoGenTag: true, + Version: version.Version, + RunE: func(cmd *cobra.Command, _ []string) error { + cmd.SilenceUsage = true // silence usage when an error occurs after flags have been parsed + + config := zap.NewDevelopmentConfig() + config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + + err := logger.Init(config) + if err != nil { + return fmt.Errorf("could not initialize logger: %w", err) + } + + ctx, cancel := context.WithCancel(cmd.Context()) + + client, err = dlc.NewClient(ctx, server) + if err != nil { + cancel() + return fmt.Errorf("could not connect to server %s: %w", server, err) + } + + osSignals := make(chan os.Signal, 1) + signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM) + go func() { + <-osSignals + cancel() + }() + + return testutil.TestConnection(ctx, client) + }, + PersistentPostRunE: func(cmd *cobra.Command, _ []string) error { + if client != nil { + client.Close() + } + + return nil + }, + } + + flags := cmd.PersistentFlags() + + flags.StringVar(&server, "server", "", "Server GRPC address") + + return cmd +} + +func ConnTestExecute() { + ctx := context.Background() + cmd := NewConnTestCommand() + + err := cmd.ExecuteContext(ctx) + + if err != nil { + logger.Fatal(ctx, "connection test failed", zap.Error(err)) + } + + logger.Info(ctx, "connection test complete") + _ = logger.Sync() +}