From 11b88b8caf86e77c300dfd06dda9beaab0a9103f Mon Sep 17 00:00:00 2001 From: Evgenii Shuvalov Date: Mon, 1 Jul 2024 15:00:42 +0300 Subject: [PATCH 1/8] added basic iter1 features --- cmd/agent/README.md | 3 - cmd/server/README.md | 3 - cmd/server/main.go | 16 +++- go.mod | 11 +++ go.sum | 10 +++ internal/metrics/types.go | 17 ++++ internal/metrics/types_test.go | 33 ++++++++ internal/server/handlers.go | 76 ++++++++++++++++++ internal/server/router.go | 17 ++++ internal/server/server.go | 27 +++++++ internal/storage/memory.go | 43 ++++++++++ internal/storage/memory_test.go | 103 ++++++++++++++++++++++++ internal/storage/record.go | 20 +++++ internal/storage/record_test.go | 29 +++++++ internal/storage/storage.go | 7 ++ internal/validators/validators.go | 34 ++++++++ internal/validators/validators_test.go | 105 +++++++++++++++++++++++++ 17 files changed, 547 insertions(+), 7 deletions(-) delete mode 100644 cmd/agent/README.md delete mode 100644 cmd/server/README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/metrics/types.go create mode 100644 internal/metrics/types_test.go create mode 100644 internal/server/handlers.go create mode 100644 internal/server/router.go create mode 100644 internal/server/server.go create mode 100644 internal/storage/memory.go create mode 100644 internal/storage/memory_test.go create mode 100644 internal/storage/record.go create mode 100644 internal/storage/record_test.go create mode 100644 internal/storage/storage.go create mode 100644 internal/validators/validators.go create mode 100644 internal/validators/validators_test.go diff --git a/cmd/agent/README.md b/cmd/agent/README.md deleted file mode 100644 index 5d500a4..0000000 --- a/cmd/agent/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# cmd/agent - -В данной директории будет содержаться код Агента, который скомпилируется в бинарное приложение diff --git a/cmd/server/README.md b/cmd/server/README.md deleted file mode 100644 index cf02a69..0000000 --- a/cmd/server/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# cmd/agent - -В данной директории будет содержаться код Сервера, который скомпилируется в бинарное приложение diff --git a/cmd/server/main.go b/cmd/server/main.go index 38dd16d..2c3b94b 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,3 +1,17 @@ package main -func main() {} +import ( + "github.com/ex0rcist/metflix/internal/server" +) + +func main() { + srv, err := server.New() + if err != nil { + panic(err) + } + + err2 := srv.Run() + if err2 != nil { + panic(err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b6e2f4b --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/ex0rcist/metflix + +go 1.22.4 + +require github.com/stretchr/testify v1.9.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..60ce688 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/metrics/types.go b/internal/metrics/types.go new file mode 100644 index 0000000..c2b3ed1 --- /dev/null +++ b/internal/metrics/types.go @@ -0,0 +1,17 @@ +package metrics + +type Metric interface { + Kind() string +} + +type Counter int64 + +func (c Counter) Kind() string { + return "counter" +} + +type Gauge float64 + +func (g Gauge) Kind() string { + return "gauge" +} diff --git a/internal/metrics/types_test.go b/internal/metrics/types_test.go new file mode 100644 index 0000000..d872b4c --- /dev/null +++ b/internal/metrics/types_test.go @@ -0,0 +1,33 @@ +package metrics_test + +import ( + "testing" + + "github.com/ex0rcist/metflix/internal/metrics" +) + +func TestKind(t *testing.T) { + tests := []struct { + name string + metric metrics.Metric + want string + }{ + { + name: "kind = counter", + metric: metrics.Counter(1), + want: "counter", + }, + { + name: "kind = gauge", + metric: metrics.Gauge(1.01), + want: "gauge", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.metric.Kind(); got != tt.want { + t.Errorf("Metric.Kind() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/server/handlers.go b/internal/server/handlers.go new file mode 100644 index 0000000..dd3bb35 --- /dev/null +++ b/internal/server/handlers.go @@ -0,0 +1,76 @@ +package server + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/ex0rcist/metflix/internal/metrics" + "github.com/ex0rcist/metflix/internal/storage" + "github.com/ex0rcist/metflix/internal/validators" +) + +type Resource struct { + storage storage.Storage +} + +func (r Resource) Homepage(res http.ResponseWriter, _ *http.Request) { + body := fmt.Sprintln("mainpage here.", "metrics list: ") + + // todo: errors + records, _ := r.storage.GetAll() + for _, record := range records { + body += fmt.Sprintf("%s => %s: %v\r\n", record.Name, record.Value.Kind(), record.Value) + } + + res.Write([]byte(body)) +} + +func (r Resource) UpdateMetric(res http.ResponseWriter, req *http.Request) { + metricName := req.PathValue("metricName") + metricType := req.PathValue("metricType") + metricValue := req.PathValue("metricValue") + + if err := validators.EnsureNamePresent(metricName); err != nil { + res.WriteHeader(http.StatusOK) + return + } + + //При попытке передать запрос без имени метрики возвращать http.StatusNotFound. + if err := validators.EnsureNamePresent(metricName); err != nil { + res.WriteHeader(http.StatusNotFound) + return + } + + //При попытке передать запрос с некорректным типом метрики или значением возвращать http.StatusBadRequest. + if err := validators.ValidateKind(metricType); err != nil { + res.WriteHeader(http.StatusBadRequest) + return + } + + var record storage.Record + + switch metricType { + case "counter": + var value int64 = 0 + + current, err := r.storage.Get(metricName) + if err == nil { + value = int64(current.Value.(metrics.Counter)) + } + + incr, _ := strconv.ParseInt(metricValue, 10, 64) + value += incr + + record = storage.Record{Name: metricName, Value: metrics.Counter(value)} + case "gauge": + current, _ := strconv.ParseFloat(metricValue, 64) + record = storage.Record{Name: metricName, Value: metrics.Gauge(current)} + default: + panic("why") // todo + } + + r.storage.Push(record) + + res.WriteHeader(http.StatusOK) +} diff --git a/internal/server/router.go b/internal/server/router.go new file mode 100644 index 0000000..c8f33d0 --- /dev/null +++ b/internal/server/router.go @@ -0,0 +1,17 @@ +package server + +import ( + "net/http" + + "github.com/ex0rcist/metflix/internal/storage" +) + +func NewRouter(storage storage.Storage) http.Handler { + mux := http.NewServeMux() + resource := Resource{storage: storage} + + mux.HandleFunc("GET /update/{metricType}/{metricName}/{metricValue}", resource.UpdateMetric) + mux.HandleFunc(`/`, resource.Homepage) + + return mux +} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..d70e45c --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,27 @@ +package server + +import ( + "net/http" + + "github.com/ex0rcist/metflix/internal/storage" +) + +type Server struct { + Storage storage.Storage + Router http.Handler +} + +func New() (*Server, error) { + storage := storage.NewMemStorage() + router := NewRouter(storage) + + return &Server{ + Storage: storage, + Router: router, + }, nil +} + +func (s *Server) Run() error { + err := http.ListenAndServe(`:8080`, s.Router) + return err +} diff --git a/internal/storage/memory.go b/internal/storage/memory.go new file mode 100644 index 0000000..9275c06 --- /dev/null +++ b/internal/storage/memory.go @@ -0,0 +1,43 @@ +package storage + +import ( + "errors" +) + +type MemStorage struct { + Data map[string]Record +} + +func (strg *MemStorage) Push(record Record) error { + id := RecordId(record.Name, record.Value.Kind()) + strg.Data[id] = record + + return nil +} + +func (strg MemStorage) Get(id string) (Record, error) { + record, ok := strg.Data[id] + if !ok { + return Record{}, errors.New("no value") + } + + return record, nil +} + +func (strg MemStorage) GetAll() ([]Record, error) { + arr := make([]Record, len(strg.Data)) + i := 0 + + for _, v := range strg.Data { + arr[i] = v + i++ + } + + return arr, nil +} + +func NewMemStorage() *MemStorage { + return &MemStorage{ + Data: make(map[string]Record), + } +} diff --git a/internal/storage/memory_test.go b/internal/storage/memory_test.go new file mode 100644 index 0000000..0891178 --- /dev/null +++ b/internal/storage/memory_test.go @@ -0,0 +1,103 @@ +package storage_test + +import ( + "testing" + + "github.com/ex0rcist/metflix/internal/metrics" + "github.com/ex0rcist/metflix/internal/storage" + + "github.com/stretchr/testify/require" +) + +func TestPushCounter(t *testing.T) { + require := require.New(t) + + strg := storage.NewMemStorage() + name := "test" + + value := metrics.Counter(42) + record := storage.Record{Name: name, Value: value} + err := strg.Push(record) + + require.NoError(err) + require.Equal(value, strg.Data[record.RecordId()].Value) +} + +func TestPushGauge(t *testing.T) { + require := require.New(t) + + strg := storage.NewMemStorage() + name := "test" + + value := metrics.Gauge(42.42) + record := storage.Record{Name: name, Value: value} + err := strg.Push(record) + + require.NoError(err) + require.Equal(value, strg.Data[record.RecordId()].Value) +} + +func TestPushWithSameName(t *testing.T) { + require := require.New(t) + + strg := storage.NewMemStorage() + + counterValue := metrics.Counter(42) + gaugeValue := metrics.Gauge(42.42) + + record1 := storage.Record{Name: "test", Value: counterValue} + err1 := strg.Push(record1) + require.NoError(err1) + + record2 := storage.Record{Name: "test", Value: gaugeValue} + err2 := strg.Push(record2) + require.NoError(err2) + + require.Equal(counterValue, strg.Data[record1.RecordId()].Value) + require.Equal(gaugeValue, strg.Data[record2.RecordId()].Value) +} + +func TestGet(t *testing.T) { + require := require.New(t) + + strg := storage.NewMemStorage() + + value := metrics.Counter(6) + record := storage.Record{Name: "test", Value: value} + err := strg.Push(record) + require.NoError(err) + + gotRecord, err := strg.Get(record.RecordId()) + require.NoError(err) + require.Equal(value, gotRecord.Value) +} + +func TestGetNonExistantKey(t *testing.T) { + require := require.New(t) + + strg := storage.NewMemStorage() + + _, err := strg.Get("none") + require.Error(err) +} + +func TestGetAll(t *testing.T) { + require := require.New(t) + + strg := storage.NewMemStorage() + + records := []storage.Record{ + {Name: "test1", Value: metrics.Counter(1)}, + {Name: "test2", Value: metrics.Counter(2)}, + {Name: "test3", Value: metrics.Gauge(3.4)}, + } + + for _, r := range records { + err := strg.Push(r) + require.NoError(err) + } + + allRecords, err := strg.GetAll() + require.NoError(err) + require.ElementsMatch(records, allRecords) +} diff --git a/internal/storage/record.go b/internal/storage/record.go new file mode 100644 index 0000000..d030267 --- /dev/null +++ b/internal/storage/record.go @@ -0,0 +1,20 @@ +package storage + +import "github.com/ex0rcist/metflix/internal/metrics" + +type Record struct { + Name string + Value metrics.Metric +} + +func RecordId(name, kind string) string { + if len(name) == 0 || len(kind) == 0 { + return "" + } + + return name + "_" + kind +} + +func (r Record) RecordId() string { + return RecordId(r.Name, r.Value.Kind()) +} diff --git a/internal/storage/record_test.go b/internal/storage/record_test.go new file mode 100644 index 0000000..b24dad1 --- /dev/null +++ b/internal/storage/record_test.go @@ -0,0 +1,29 @@ +package storage_test + +import ( + "testing" + + "github.com/ex0rcist/metflix/internal/metrics" + "github.com/ex0rcist/metflix/internal/storage" + "github.com/stretchr/testify/require" +) + +func TestRecordId(t *testing.T) { + require := require.New(t) + + record1 := storage.Record{Name: "test", Value: metrics.Counter(42)} + record2 := storage.Record{Name: "test", Value: metrics.Gauge(42.42)} + + require.Equal("test_counter", record1.RecordId()) + require.Equal("test_gauge", record2.RecordId()) +} + +func TestRecordIdWithEmptyName(t *testing.T) { + require := require.New(t) + require.Equal("", storage.RecordId("", "counter")) +} + +func TestRecordIdWithEmptyKind(t *testing.T) { + require := require.New(t) + require.Equal("", storage.RecordId("test", "")) +} diff --git a/internal/storage/storage.go b/internal/storage/storage.go new file mode 100644 index 0000000..7b09267 --- /dev/null +++ b/internal/storage/storage.go @@ -0,0 +1,7 @@ +package storage + +type Storage interface { + Push(record Record) error + Get(key string) (Record, error) + GetAll() ([]Record, error) +} diff --git a/internal/validators/validators.go b/internal/validators/validators.go new file mode 100644 index 0000000..278fcde --- /dev/null +++ b/internal/validators/validators.go @@ -0,0 +1,34 @@ +package validators + +import ( + "errors" + "regexp" +) + +var nameRegexp = regexp.MustCompile(`^[A-Za-z\d]+$`) + +func EnsureNamePresent(name string) error { + if len(name) == 0 { + return errors.New("missing name") + } + + return nil +} + +func ValidateName(name, kind string) error { + if !nameRegexp.MatchString(name) { + return errors.New("invalid name") + } + + return nil +} + +func ValidateKind(kind string) error { + switch kind { + case "counter", "gauge": + return nil + + default: + return errors.New("invalid metric type") + } +} diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go new file mode 100644 index 0000000..8ba8d87 --- /dev/null +++ b/internal/validators/validators_test.go @@ -0,0 +1,105 @@ +package validators_test + +import ( + "testing" + + "github.com/ex0rcist/metflix/internal/validators" +) + +func TestEnsureNamePresent(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "no error when name present", + args: args{name: "testname"}, + wantErr: false, + }, + { + name: "has error when name is not present", + args: args{name: ""}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validators.EnsureNamePresent(tt.args.name); (err != nil) != tt.wantErr { + t.Errorf("EnsureNamePresent() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestValidateName(t *testing.T) { + type args struct { + name string + kind string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "correct name", + args: args{name: "correctname1"}, + wantErr: false, + }, + { + name: "incorrect name", + args: args{name: "некорректноеимя"}, + wantErr: true, + }, + { + name: "incorrect name", + args: args{name: "incorrect name"}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validators.ValidateName(tt.args.name, tt.args.kind); (err != nil) != tt.wantErr { + t.Errorf("ValidateName() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestValidateKind(t *testing.T) { + type args struct { + kind string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "correct kind = counter", + args: args{kind: "counter"}, + wantErr: false, + }, + { + name: "correct kind = gauge", + args: args{kind: "gauge"}, + wantErr: false, + }, + { + name: "incorrect kind = gauger", + args: args{kind: "gauger"}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validators.ValidateKind(tt.args.kind); (err != nil) != tt.wantErr { + t.Errorf("ValidateKind() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From d1aaf1f6971d2fe2207151d0f5088e886a618076 Mon Sep 17 00:00:00 2001 From: Evgenii Shuvalov Date: Tue, 2 Jul 2024 16:07:15 +0300 Subject: [PATCH 2/8] iter1 fixes --- cmd/server/main.go | 2 +- go.mod | 5 +- go.sum | 2 + internal/server/handlers.go | 39 +++++-- internal/server/handlers_test.go | 155 +++++++++++++++++++++++++ internal/server/router.go | 10 +- internal/validators/validators.go | 2 +- internal/validators/validators_test.go | 2 +- 8 files changed, 200 insertions(+), 17 deletions(-) create mode 100644 internal/server/handlers_test.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 2c3b94b..00c755c 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -12,6 +12,6 @@ func main() { err2 := srv.Run() if err2 != nil { - panic(err) + panic(err2) } } diff --git a/go.mod b/go.mod index b6e2f4b..853db6b 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module github.com/ex0rcist/metflix go 1.22.4 -require github.com/stretchr/testify v1.9.0 +require ( + github.com/go-chi/chi/v5 v5.1.0 + github.com/stretchr/testify v1.9.0 +) require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 60ce688..c4d1e3e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= diff --git a/internal/server/handlers.go b/internal/server/handlers.go index dd3bb35..299ca42 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -14,13 +14,23 @@ type Resource struct { storage storage.Storage } +func NewResource(s storage.Storage) Resource { + return Resource{ + storage: s, + } +} + func (r Resource) Homepage(res http.ResponseWriter, _ *http.Request) { - body := fmt.Sprintln("mainpage here.", "metrics list: ") + body := fmt.Sprintln("mainpage here.") // todo: errors records, _ := r.storage.GetAll() - for _, record := range records { - body += fmt.Sprintf("%s => %s: %v\r\n", record.Name, record.Value.Kind(), record.Value) + if len(records) > 0 { + body += fmt.Sprintln("metrics list:") + + for _, record := range records { + body += fmt.Sprintf("%s => %s: %v\n", record.Name, record.Value.Kind(), record.Value) + } } res.Write([]byte(body)) @@ -28,17 +38,19 @@ func (r Resource) Homepage(res http.ResponseWriter, _ *http.Request) { func (r Resource) UpdateMetric(res http.ResponseWriter, req *http.Request) { metricName := req.PathValue("metricName") + metricType := req.PathValue("metricType") metricValue := req.PathValue("metricValue") + //При попытке передать запрос без имени метрики возвращать http.StatusNotFound. if err := validators.EnsureNamePresent(metricName); err != nil { - res.WriteHeader(http.StatusOK) + res.WriteHeader(http.StatusNotFound) return } - //При попытке передать запрос без имени метрики возвращать http.StatusNotFound. - if err := validators.EnsureNamePresent(metricName); err != nil { - res.WriteHeader(http.StatusNotFound) + //При попытке передать запрос с некорректным именем метрики возвращать http.StatusBadRequest. + if err := validators.ValidateName(metricName); err != nil { + res.WriteHeader(http.StatusBadRequest) return } @@ -59,12 +71,21 @@ func (r Resource) UpdateMetric(res http.ResponseWriter, req *http.Request) { value = int64(current.Value.(metrics.Counter)) } - incr, _ := strconv.ParseInt(metricValue, 10, 64) + incr, err := strconv.ParseInt(metricValue, 10, 64) + if err != nil { + res.WriteHeader(http.StatusBadRequest) + return + } value += incr record = storage.Record{Name: metricName, Value: metrics.Counter(value)} case "gauge": - current, _ := strconv.ParseFloat(metricValue, 64) + current, err := strconv.ParseFloat(metricValue, 64) + if err != nil { + res.WriteHeader(http.StatusBadRequest) + return + } + record = storage.Record{Name: metricName, Value: metrics.Gauge(current)} default: panic("why") // todo diff --git a/internal/server/handlers_test.go b/internal/server/handlers_test.go new file mode 100644 index 0000000..f746e0e --- /dev/null +++ b/internal/server/handlers_test.go @@ -0,0 +1,155 @@ +package server_test + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/ex0rcist/metflix/internal/metrics" + "github.com/ex0rcist/metflix/internal/server" + "github.com/ex0rcist/metflix/internal/storage" + "github.com/stretchr/testify/require" +) + +// https://github.com/go-chi/chi/blob/cca4135d8dddff765463feaf1118047a9e506b4a/middleware/get_head_test.go +func testRequest(t *testing.T, ts *httptest.Server, method, path string, body io.Reader) (*http.Response, string) { + req, err := http.NewRequest(method, ts.URL+path, body) + if err != nil { + t.Fatal(err) + return nil, "" + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + return nil, "" + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + return nil, "" + } + defer resp.Body.Close() + + return resp, string(respBody) +} + +func TestHomepage(t *testing.T) { + type result struct { + code int + body string + } + + require := require.New(t) + tests := []struct { + name string + path string + metrics []storage.Record + want result + }{ + { + name: "default homepage", + path: "/", + want: result{code: http.StatusOK, body: "mainpage here.\n"}, + }, + { + name: "has some metrics", + path: "/", + metrics: []storage.Record{ + {Name: "test1", Value: metrics.Counter(1)}, + {Name: "test2", Value: metrics.Gauge(2.3)}, + }, + want: result{code: http.StatusOK, body: "mainpage here.\nmetrics list:\ntest1 => counter: 1\ntest2 => gauge: 2.3\n"}, + }, + } + + for _, tt := range tests { + storage := storage.NewMemStorage() + router := server.NewRouter(storage) + testServer := httptest.NewServer(router) + defer testServer.Close() + + if len(tt.metrics) > 0 { + for _, r := range tt.metrics { + storage.Push(r) + } + } + + t.Run(tt.name, func(t *testing.T) { + req, body := testRequest(t, testServer, http.MethodGet, tt.path, nil) + + require.Equal(tt.want.code, req.StatusCode) + require.Equal(tt.want.body, string(body)) + }) + } +} + +func TestUpdateMetric(t *testing.T) { + type result struct { + code int + body string + } + + require := require.New(t) + tests := []struct { + name string + path string + want result + }{ + { + name: "push counter", + path: "/update/counter/test/42", + want: result{code: http.StatusOK, body: ""}, + }, + { + name: "push gauge", + path: "/update/gauge/test/42.42", + want: result{code: http.StatusOK, body: ""}, + }, + { + name: "fail on invalid kind", + path: "/update/xxx/test/1", + want: result{code: http.StatusBadRequest}, + }, + { + name: "fail on empty metric name", + path: "/update/counter//1", + want: result{code: http.StatusNotFound}, + }, + { + name: "fail on counter with invalid name", + path: "/update/counter/inva!id/10", + want: result{code: http.StatusBadRequest}, + }, + { + name: "fail on counter with invalid value", + path: "/update/counter/test/10.0", + want: result{code: http.StatusBadRequest}, + }, + { + name: "fail on gauge with invalid name", + path: "/update/gauge/inval!d/42.42", + want: result{code: http.StatusBadRequest}, + }, + { + name: "fail on gauge with invalid value", + path: "/update/gauge/test/42.42!", + want: result{code: http.StatusBadRequest}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router := server.NewRouter(storage.NewMemStorage()) + testServer := httptest.NewServer(router) + defer testServer.Close() + + req, body := testRequest(t, testServer, http.MethodPost, tt.path, nil) + + require.Equal(tt.want.code, req.StatusCode) + require.Equal(tt.want.body, string(body)) + }) + } +} diff --git a/internal/server/router.go b/internal/server/router.go index c8f33d0..f8cac41 100644 --- a/internal/server/router.go +++ b/internal/server/router.go @@ -3,15 +3,17 @@ package server import ( "net/http" + "github.com/go-chi/chi/v5" + "github.com/ex0rcist/metflix/internal/storage" ) func NewRouter(storage storage.Storage) http.Handler { - mux := http.NewServeMux() + router := chi.NewRouter() resource := Resource{storage: storage} - mux.HandleFunc("GET /update/{metricType}/{metricName}/{metricValue}", resource.UpdateMetric) - mux.HandleFunc(`/`, resource.Homepage) + router.Get("/", resource.Homepage) // TODO: resource? + router.Post("/update/{metricType}/{metricName}/{metricValue}", resource.UpdateMetric) - return mux + return router } diff --git a/internal/validators/validators.go b/internal/validators/validators.go index 278fcde..ff21773 100644 --- a/internal/validators/validators.go +++ b/internal/validators/validators.go @@ -15,7 +15,7 @@ func EnsureNamePresent(name string) error { return nil } -func ValidateName(name, kind string) error { +func ValidateName(name string) error { if !nameRegexp.MatchString(name) { return errors.New("invalid name") } diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go index 8ba8d87..f1ee287 100644 --- a/internal/validators/validators_test.go +++ b/internal/validators/validators_test.go @@ -63,7 +63,7 @@ func TestValidateName(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := validators.ValidateName(tt.args.name, tt.args.kind); (err != nil) != tt.wantErr { + if err := validators.ValidateName(tt.args.name); (err != nil) != tt.wantErr { t.Errorf("ValidateName() error = %v, wantErr %v", err, tt.wantErr) } }) From 9576f0d187e4ed4ceec6e49eb0b3622341d39952 Mon Sep 17 00:00:00 2001 From: Evgenii Shuvalov Date: Wed, 3 Jul 2024 13:17:51 +0300 Subject: [PATCH 3/8] iter1 fixes --- .DS_Store | Bin 0 -> 6148 bytes .golangci.yml | 4 ++ go.mod | 10 +++ go.sum | 92 +++++++++++++++++++++++++ internal/server/handlers.go | 10 ++- internal/server/handlers_test.go | 53 +++++++------- internal/storage/memory.go | 2 +- internal/storage/memory_test.go | 10 +-- internal/storage/record.go | 6 +- internal/storage/record_test.go | 8 +-- internal/validators/validators_test.go | 1 - 11 files changed, 156 insertions(+), 40 deletions(-) create mode 100644 .DS_Store create mode 100644 .golangci.yml diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9dd95b1cbe9427140f471242c09dec42604f7a83 GIT binary patch literal 6148 zcmeHKOHRWu5Pfc|(vOfTR&0=R0V;8WP}&6xHgEy@Q&lN#P(KKE*>eZ3z-c%PD|jCOXXYz!*eKO%NxoOLdGmH^GxsTVT9G$a#irtm z`Bh#$R$VpPGB@T_jD`PM9OPrME=HH~Z{ivQeoy+iB8sw}u9caWOG~pJvIePO!)i+T zmv%YY#X&@V%4{F^e)f5#nZ~~~p4noJw#DFs0zrYGz?TB@eu&rvV~3eVeRa_36@Xad zv>N;JmqIv^!`NYFkxyvGQi+yo+!e!EI_D!77dy->S~`rod>FU0aW@pByL0_W!(n2J z!3PC`0!0O?cG!{gf3W}lUnGT5P#`GqrxY-?{!PEflHA!^Do)PYi0zh5OyV+&N}+Sd hv3BGrK4w$nvq(C`*kNXoGqmtWKxGInDDa~Sd;q7D-R}SZ literal 0 HcmV?d00001 diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..2666698 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,4 @@ +linters-settings: + custom: + statictest: + path: ./plugin/statictest.so diff --git a/go.mod b/go.mod index 853db6b..b45f4d2 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,21 @@ go 1.22.4 require ( github.com/go-chi/chi/v5 v5.1.0 + github.com/jingyugao/rowserrcheck v1.1.1 github.com/stretchr/testify v1.9.0 + github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 + golang.org/x/tools v0.22.0 + honnef.co/go/tools v0.4.7 ) require ( + github.com/BurntSushi/toml v1.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gostaticanalysis/analysisutil v0.7.1 // indirect + github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/sync v0.7.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c4d1e3e..160b5b3 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,104 @@ +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4 h1:d2/eIbH9XjD1fFwD5SHv8x168fjbQ9PB8hvs8DSEC08= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= +github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= +github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= +golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= +honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 299ca42..bef3725 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -33,7 +33,10 @@ func (r Resource) Homepage(res http.ResponseWriter, _ *http.Request) { } } - res.Write([]byte(body)) + _, err := res.Write([]byte(body)) + if err != nil { + panic("unable to write") // todo + } } func (r Resource) UpdateMetric(res http.ResponseWriter, req *http.Request) { @@ -91,7 +94,10 @@ func (r Resource) UpdateMetric(res http.ResponseWriter, req *http.Request) { panic("why") // todo } - r.storage.Push(record) + err := r.storage.Push(record) + if err != nil { + panic("cannot push") // todo + } res.WriteHeader(http.StatusOK) } diff --git a/internal/server/handlers_test.go b/internal/server/handlers_test.go index f746e0e..4a52d35 100644 --- a/internal/server/handlers_test.go +++ b/internal/server/handlers_test.go @@ -13,33 +13,28 @@ import ( ) // https://github.com/go-chi/chi/blob/cca4135d8dddff765463feaf1118047a9e506b4a/middleware/get_head_test.go -func testRequest(t *testing.T, ts *httptest.Server, method, path string, body io.Reader) (*http.Response, string) { +func testRequest(t *testing.T, ts *httptest.Server, method, path string, body io.Reader) (int, string, []byte) { req, err := http.NewRequest(method, ts.URL+path, body) - if err != nil { - t.Fatal(err) - return nil, "" - } + require.NoError(t, err) resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatal(err) - return nil, "" - } + require.NoError(t, err) - respBody, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - return nil, "" - } defer resp.Body.Close() - return resp, string(respBody) + contentType := resp.Header.Get("Content-Type") + + respBody, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + return resp.StatusCode, contentType, respBody } func TestHomepage(t *testing.T) { type result struct { - code int - body string + code int + body string + contains []string } require := require.New(t) @@ -61,7 +56,7 @@ func TestHomepage(t *testing.T) { {Name: "test1", Value: metrics.Counter(1)}, {Name: "test2", Value: metrics.Gauge(2.3)}, }, - want: result{code: http.StatusOK, body: "mainpage here.\nmetrics list:\ntest1 => counter: 1\ntest2 => gauge: 2.3\n"}, + want: result{code: http.StatusOK, contains: []string{"mainpage here.", "metrics list", "test1 => counter: 1", "test2 => gauge: 2.3"}}, }, } @@ -73,15 +68,25 @@ func TestHomepage(t *testing.T) { if len(tt.metrics) > 0 { for _, r := range tt.metrics { - storage.Push(r) + err := storage.Push(r) + require.NoError(err) } } t.Run(tt.name, func(t *testing.T) { - req, body := testRequest(t, testServer, http.MethodGet, tt.path, nil) + code, _, body := testRequest(t, testServer, http.MethodGet, tt.path, nil) - require.Equal(tt.want.code, req.StatusCode) - require.Equal(tt.want.body, string(body)) + require.Equal(tt.want.code, code) + + if len(tt.want.body) > 0 { + require.Equal(tt.want.body, string(body)) + } + + if len(tt.want.contains) > 0 { + for _, v := range tt.want.contains { + require.Contains(string(body), v) + } + } }) } } @@ -146,9 +151,9 @@ func TestUpdateMetric(t *testing.T) { testServer := httptest.NewServer(router) defer testServer.Close() - req, body := testRequest(t, testServer, http.MethodPost, tt.path, nil) + code, _, body := testRequest(t, testServer, http.MethodPost, tt.path, nil) - require.Equal(tt.want.code, req.StatusCode) + require.Equal(tt.want.code, code) require.Equal(tt.want.body, string(body)) }) } diff --git a/internal/storage/memory.go b/internal/storage/memory.go index 9275c06..d7a073c 100644 --- a/internal/storage/memory.go +++ b/internal/storage/memory.go @@ -9,7 +9,7 @@ type MemStorage struct { } func (strg *MemStorage) Push(record Record) error { - id := RecordId(record.Name, record.Value.Kind()) + id := RecordID(record.Name, record.Value.Kind()) strg.Data[id] = record return nil diff --git a/internal/storage/memory_test.go b/internal/storage/memory_test.go index 0891178..0e06575 100644 --- a/internal/storage/memory_test.go +++ b/internal/storage/memory_test.go @@ -20,7 +20,7 @@ func TestPushCounter(t *testing.T) { err := strg.Push(record) require.NoError(err) - require.Equal(value, strg.Data[record.RecordId()].Value) + require.Equal(value, strg.Data[record.RecordID()].Value) } func TestPushGauge(t *testing.T) { @@ -34,7 +34,7 @@ func TestPushGauge(t *testing.T) { err := strg.Push(record) require.NoError(err) - require.Equal(value, strg.Data[record.RecordId()].Value) + require.Equal(value, strg.Data[record.RecordID()].Value) } func TestPushWithSameName(t *testing.T) { @@ -53,8 +53,8 @@ func TestPushWithSameName(t *testing.T) { err2 := strg.Push(record2) require.NoError(err2) - require.Equal(counterValue, strg.Data[record1.RecordId()].Value) - require.Equal(gaugeValue, strg.Data[record2.RecordId()].Value) + require.Equal(counterValue, strg.Data[record1.RecordID()].Value) + require.Equal(gaugeValue, strg.Data[record2.RecordID()].Value) } func TestGet(t *testing.T) { @@ -67,7 +67,7 @@ func TestGet(t *testing.T) { err := strg.Push(record) require.NoError(err) - gotRecord, err := strg.Get(record.RecordId()) + gotRecord, err := strg.Get(record.RecordID()) require.NoError(err) require.Equal(value, gotRecord.Value) } diff --git a/internal/storage/record.go b/internal/storage/record.go index d030267..e4bf4c4 100644 --- a/internal/storage/record.go +++ b/internal/storage/record.go @@ -7,7 +7,7 @@ type Record struct { Value metrics.Metric } -func RecordId(name, kind string) string { +func RecordID(name, kind string) string { if len(name) == 0 || len(kind) == 0 { return "" } @@ -15,6 +15,6 @@ func RecordId(name, kind string) string { return name + "_" + kind } -func (r Record) RecordId() string { - return RecordId(r.Name, r.Value.Kind()) +func (r Record) RecordID() string { + return RecordID(r.Name, r.Value.Kind()) } diff --git a/internal/storage/record_test.go b/internal/storage/record_test.go index b24dad1..3a98258 100644 --- a/internal/storage/record_test.go +++ b/internal/storage/record_test.go @@ -14,16 +14,16 @@ func TestRecordId(t *testing.T) { record1 := storage.Record{Name: "test", Value: metrics.Counter(42)} record2 := storage.Record{Name: "test", Value: metrics.Gauge(42.42)} - require.Equal("test_counter", record1.RecordId()) - require.Equal("test_gauge", record2.RecordId()) + require.Equal("test_counter", record1.RecordID()) + require.Equal("test_gauge", record2.RecordID()) } func TestRecordIdWithEmptyName(t *testing.T) { require := require.New(t) - require.Equal("", storage.RecordId("", "counter")) + require.Equal("", storage.RecordID("", "counter")) } func TestRecordIdWithEmptyKind(t *testing.T) { require := require.New(t) - require.Equal("", storage.RecordId("test", "")) + require.Equal("", storage.RecordID("test", "")) } diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go index f1ee287..bffd861 100644 --- a/internal/validators/validators_test.go +++ b/internal/validators/validators_test.go @@ -38,7 +38,6 @@ func TestEnsureNamePresent(t *testing.T) { func TestValidateName(t *testing.T) { type args struct { name string - kind string } tests := []struct { name string From a5d1fc2d707d0597803abcf3157e1b1645796ef2 Mon Sep 17 00:00:00 2001 From: Evgenii Shuvalov Date: Thu, 4 Jul 2024 19:30:42 +0300 Subject: [PATCH 4/8] added basic iter2 features --- cmd/agent/main.go | 33 ++++++++- cmd/server/main.go | 8 +++ go.mod | 16 ++--- go.sum | 112 ++++++------------------------- internal/agent/agent.go | 91 +++++++++++++++++++++++++ internal/agent/agent_test.go | 26 +++++++ internal/agent/api.go | 67 ++++++++++++++++++ internal/agent/api_test.go | 40 +++++++++++ internal/logging/logging.go | 19 ++++++ internal/logging/logging_test.go | 13 ++++ internal/metrics/types.go | 10 +++ internal/metrics/types_test.go | 31 +++++++++ internal/stats/runtime.go | 70 +++++++++++++++++++ internal/stats/runtime_test.go | 23 +++++++ internal/stats/stats.go | 34 ++++++++++ internal/stats/stats_test.go | 31 +++++++++ 16 files changed, 521 insertions(+), 103 deletions(-) create mode 100644 internal/agent/agent.go create mode 100644 internal/agent/agent_test.go create mode 100644 internal/agent/api.go create mode 100644 internal/agent/api_test.go create mode 100644 internal/logging/logging.go create mode 100644 internal/logging/logging_test.go create mode 100644 internal/stats/runtime.go create mode 100644 internal/stats/runtime_test.go create mode 100644 internal/stats/stats.go create mode 100644 internal/stats/stats_test.go diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 38dd16d..b51d111 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -1,3 +1,34 @@ package main -func main() {} +import ( + "time" + + "github.com/ex0rcist/metflix/internal/agent" + "github.com/ex0rcist/metflix/internal/logging" + "github.com/rs/zerolog/log" +) + +func main() { + logging.Setup() + + conf := agent.Config{ + Address: "http://0.0.0.0:8080", + ReportInterval: 10 * time.Second, + PollInterval: 2 * time.Second, + PollTimeout: 2 * time.Second, + ExportTimeout: 4 * time.Second, + } // todo: yml? + + log.Info().Msg("starting agent...") + + agnt := agent.New(conf) + agnt.Run() + + log.Info().Msg("agent ready") + + for { + // fixme: tmp hack for goroutine + time.Sleep(time.Second * 1) + } + +} diff --git a/cmd/server/main.go b/cmd/server/main.go index 00c755c..07924c3 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,15 +1,23 @@ package main import ( + "github.com/ex0rcist/metflix/internal/logging" "github.com/ex0rcist/metflix/internal/server" + "github.com/rs/zerolog/log" ) func main() { + logging.Setup() + + log.Info().Msg("starting server...") + srv, err := server.New() if err != nil { panic(err) } + log.Info().Msg("server ready") + err2 := srv.Run() if err2 != nil { panic(err2) diff --git a/go.mod b/go.mod index b45f4d2..d90b327 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,18 @@ module github.com/ex0rcist/metflix go 1.22.4 require ( + github.com/go-chi/chi v1.5.5 github.com/go-chi/chi/v5 v5.1.0 - github.com/jingyugao/rowserrcheck v1.1.1 + github.com/rs/zerolog v1.33.0 + github.com/satori/go.uuid v1.2.0 github.com/stretchr/testify v1.9.0 - github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 - golang.org/x/tools v0.22.0 - honnef.co/go/tools v0.4.7 ) require ( - github.com/BurntSushi/toml v1.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gostaticanalysis/analysisutil v0.7.1 // indirect - github.com/gostaticanalysis/comment v1.4.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect - golang.org/x/mod v0.18.0 // indirect - golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 160b5b3..8358a24 100644 --- a/go.sum +++ b/go.sum @@ -1,104 +1,32 @@ -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= +github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= -github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= -github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= -github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4 h1:d2/eIbH9XjD1fFwD5SHv8x168fjbQ9PB8hvs8DSEC08= -github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= -github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= -github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= -github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= -github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= -golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= -honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= diff --git a/internal/agent/agent.go b/internal/agent/agent.go new file mode 100644 index 0000000..c824e3b --- /dev/null +++ b/internal/agent/agent.go @@ -0,0 +1,91 @@ +package agent + +import ( + "time" + + "github.com/ex0rcist/metflix/internal/stats" +) + +type Config struct { + Address string + PollInterval time.Duration + ReportInterval time.Duration + PollTimeout time.Duration + ExportTimeout time.Duration +} + +type Agent struct { + config Config + stats *stats.Stats + api *API +} + +func New(cfg Config) *Agent { + stts := stats.NewStats() + api := NewAPI(cfg.Address, nil) + + agnt := &Agent{config: cfg, stats: stts, api: api} + + return agnt +} + +func (app *Agent) Run() { + go app.startPolling() + go app.startReporting() +} + +func (app *Agent) startPolling() { + for { + err := app.stats.Poll() + if err != nil { + return // todo: handle errors + } + + time.Sleep(app.config.PollInterval) + } +} + +func (app *Agent) startReporting() { + for { + time.Sleep(app.config.ReportInterval) + + app.reportStats() // todo: handle errors + } +} + +func (app *Agent) reportStats() { + app.api. + Report("Alloc", app.stats.Runtime.Alloc). + Report("BuckHashSys", app.stats.Runtime.BuckHashSys). + Report("Frees", app.stats.Runtime.Frees). + Report("GCCPUFraction", app.stats.Runtime.GCCPUFraction). + Report("GCSys", app.stats.Runtime.GCSys). + Report("HeapAlloc", app.stats.Runtime.HeapAlloc). + Report("HeapIdle", app.stats.Runtime.HeapIdle). + Report("HeapInuse", app.stats.Runtime.HeapInuse). + Report("HeapObjects", app.stats.Runtime.HeapObjects). + Report("HeapReleased", app.stats.Runtime.HeapReleased). + Report("HeapSys", app.stats.Runtime.HeapSys). + Report("LastGC", app.stats.Runtime.LastGC). + Report("Lookups", app.stats.Runtime.Lookups). + Report("MCacheInuse", app.stats.Runtime.MCacheInuse). + Report("MCacheSys", app.stats.Runtime.MCacheSys). + Report("MSpanInuse", app.stats.Runtime.MSpanInuse). + Report("MSpanSys", app.stats.Runtime.MSpanSys). + Report("Mallocs", app.stats.Runtime.Mallocs). + Report("NextGC", app.stats.Runtime.NextGC). + Report("NumForcedGC", app.stats.Runtime.NumForcedGC). + Report("NumGC", app.stats.Runtime.NumGC). + Report("OtherSys", app.stats.Runtime.OtherSys). + Report("PauseTotalNs", app.stats.Runtime.PauseTotalNs). + Report("StackInuse", app.stats.Runtime.StackInuse). + Report("StackSys", app.stats.Runtime.StackSys). + Report("Sys", app.stats.Runtime.Sys). + Report("TotalAlloc", app.stats.Runtime.TotalAlloc) + + app.api. + Report("RandomValue", app.stats.RandomValue) + + app.api. + Report("PollCount", app.stats.PollCount) +} diff --git a/internal/agent/agent_test.go b/internal/agent/agent_test.go new file mode 100644 index 0000000..8af7e33 --- /dev/null +++ b/internal/agent/agent_test.go @@ -0,0 +1,26 @@ +package agent_test + +import ( + "testing" + "time" + + "github.com/ex0rcist/metflix/internal/agent" + "github.com/stretchr/testify/require" +) + +func TestRun(t *testing.T) { + cfg := agent.Config{ + Address: "http://0.0.0.0:8080", + ReportInterval: 10 * time.Second, + PollInterval: 2 * time.Second, + PollTimeout: 2 * time.Second, + ExportTimeout: 4 * time.Second, + } + + agnt := agent.New(cfg) + require.NotPanics(t, agnt.Run) +} + +func TestReportStats(t *testing.T) { + // todo: как тестировать приватные? +} diff --git a/internal/agent/api.go b/internal/agent/api.go new file mode 100644 index 0000000..ef761f2 --- /dev/null +++ b/internal/agent/api.go @@ -0,0 +1,67 @@ +package agent + +import ( + "fmt" + "io" + "net/http" + "time" + + "github.com/ex0rcist/metflix/internal/metrics" + "github.com/rs/zerolog/log" +) + +type API struct { + baseURL string + httpClient *http.Client + err error +} + +func NewAPI(serverAddr string, httpTransport http.RoundTripper) *API { + if httpTransport == nil { + httpTransport = http.DefaultTransport + } + + client := &http.Client{ + Timeout: 2 * time.Second, + Transport: httpTransport, + } + + return &API{ + baseURL: serverAddr, + httpClient: client, + err: nil, + } +} + +func (c *API) Report(name string, metric metrics.Metric) *API { + log.Info().Msg("reporting stats ... ") + + url := c.baseURL + fmt.Sprintf("/update/%s/%s/%s", metric.Kind(), name, metric) + + req, err := http.NewRequest(http.MethodPost, url, http.NoBody) + if err != nil { + panic(err) + } + + req.Header.Set("Content-Type", "text/plain") + + log.Info().Msg(fmt.Sprintf("sending POST to %v", url)) + + resp, err := c.httpClient.Do(req) + if err != nil { + c.err = err + } + + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) // нужно прочитать ответ для keepalive? + if err != nil { + c.err = err + } + + if resp.StatusCode != http.StatusOK { + fmt.Println(respBody) // todo: log + } + + return c +} diff --git a/internal/agent/api_test.go b/internal/agent/api_test.go new file mode 100644 index 0000000..1089518 --- /dev/null +++ b/internal/agent/api_test.go @@ -0,0 +1,40 @@ +package agent_test + +import ( + "net/http" + "testing" + + "github.com/ex0rcist/metflix/internal/agent" + "github.com/ex0rcist/metflix/internal/metrics" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// http://hassansin.github.io/Unit-Testing-http-client-in-Go +type RoundTripFunc func(req *http.Request) *http.Response + +// todo: wtf... +func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req), nil +} + +func TestNewApi(t *testing.T) { + require := require.New(t) + require.NotPanics(func() { agent.NewAPI("someaddr", nil) }) +} + +func TestApiClientReport(t *testing.T) { + rtf := func(req *http.Request) *http.Response { + assert.Equal(t, "http://localhost:8080/update/counter/Test/0", req.URL.String()) + assert.Equal(t, http.MethodPost, req.Method) + + return &http.Response{ + StatusCode: 200, + Body: http.NoBody, + Header: make(http.Header), + } + } + + api := agent.NewAPI("http://localhost:8080", RoundTripFunc(rtf)) + api.Report("Test", metrics.Counter(0)) +} diff --git a/internal/logging/logging.go b/internal/logging/logging.go new file mode 100644 index 0000000..d9a1adf --- /dev/null +++ b/internal/logging/logging.go @@ -0,0 +1,19 @@ +package logging + +import ( + "os" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func Setup() { + output := zerolog.ConsoleWriter{ + Out: os.Stdout, + TimeFormat: time.RFC3339, // time.RFC822 + } + + l := zerolog.New(output).With().Timestamp() + log.Logger = l.Logger() +} diff --git a/internal/logging/logging_test.go b/internal/logging/logging_test.go new file mode 100644 index 0000000..ea0bf4e --- /dev/null +++ b/internal/logging/logging_test.go @@ -0,0 +1,13 @@ +package logging_test + +import ( + "testing" + + "github.com/ex0rcist/metflix/internal/logging" + "github.com/stretchr/testify/require" +) + +func TestSetup(t *testing.T) { + require := require.New(t) + require.NotPanics(logging.Setup) +} diff --git a/internal/metrics/types.go b/internal/metrics/types.go index c2b3ed1..be1dbd8 100644 --- a/internal/metrics/types.go +++ b/internal/metrics/types.go @@ -1,5 +1,7 @@ package metrics +import "strconv" + type Metric interface { Kind() string } @@ -10,8 +12,16 @@ func (c Counter) Kind() string { return "counter" } +func (c Counter) String() string { + return strconv.FormatInt(int64(c), 10) +} + type Gauge float64 func (g Gauge) Kind() string { return "gauge" } + +func (g Gauge) String() string { + return strconv.FormatFloat(float64(g), 'f', -1, 64) +} diff --git a/internal/metrics/types_test.go b/internal/metrics/types_test.go index d872b4c..51e4727 100644 --- a/internal/metrics/types_test.go +++ b/internal/metrics/types_test.go @@ -31,3 +31,34 @@ func TestKind(t *testing.T) { }) } } + +func TesString(t *testing.T) { + tests := []struct { + name string + metric metrics.Metric + want string + }{ + { + name: "String() for counter", + metric: metrics.Counter(42), + want: "42", + }, + { + name: "String() for gauge", + metric: metrics.Gauge(42.01), + want: "42.01", + }, + { + name: "String() for small gauge", + metric: metrics.Gauge(0.42), + want: "0.42", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.metric.Kind(); got != tt.want { + t.Errorf("Metric.String() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/stats/runtime.go b/internal/stats/runtime.go new file mode 100644 index 0000000..ce27c60 --- /dev/null +++ b/internal/stats/runtime.go @@ -0,0 +1,70 @@ +package stats + +import ( + "runtime" + + "github.com/ex0rcist/metflix/internal/metrics" +) + +type RuntimeStats struct { + Alloc metrics.Gauge + BuckHashSys metrics.Gauge + Frees metrics.Gauge + GCCPUFraction metrics.Gauge + GCSys metrics.Gauge + HeapAlloc metrics.Gauge + HeapIdle metrics.Gauge + HeapInuse metrics.Gauge + HeapObjects metrics.Gauge + HeapReleased metrics.Gauge + HeapSys metrics.Gauge + LastGC metrics.Gauge + Lookups metrics.Gauge + MCacheInuse metrics.Gauge + MCacheSys metrics.Gauge + MSpanInuse metrics.Gauge + MSpanSys metrics.Gauge + Mallocs metrics.Gauge + NextGC metrics.Gauge + NumForcedGC metrics.Gauge + NumGC metrics.Gauge + OtherSys metrics.Gauge + PauseTotalNs metrics.Gauge + StackInuse metrics.Gauge + StackSys metrics.Gauge + Sys metrics.Gauge + TotalAlloc metrics.Gauge +} + +func (m *RuntimeStats) Poll() { + stats := runtime.MemStats{} + runtime.ReadMemStats(&stats) + + m.Alloc = metrics.Gauge(stats.Alloc) + m.BuckHashSys = metrics.Gauge(stats.BuckHashSys) + m.Frees = metrics.Gauge(stats.Frees) + m.GCCPUFraction = metrics.Gauge(stats.GCCPUFraction) + m.GCSys = metrics.Gauge(stats.GCSys) + m.HeapAlloc = metrics.Gauge(stats.HeapAlloc) + m.HeapIdle = metrics.Gauge(stats.HeapIdle) + m.HeapInuse = metrics.Gauge(stats.HeapInuse) + m.HeapObjects = metrics.Gauge(stats.HeapObjects) + m.HeapReleased = metrics.Gauge(stats.HeapReleased) + m.HeapSys = metrics.Gauge(stats.HeapSys) + m.LastGC = metrics.Gauge(stats.LastGC) + m.Lookups = metrics.Gauge(stats.Lookups) + m.MCacheInuse = metrics.Gauge(stats.MCacheInuse) + m.MCacheSys = metrics.Gauge(stats.MCacheSys) + m.MSpanInuse = metrics.Gauge(stats.MSpanInuse) + m.MSpanSys = metrics.Gauge(stats.MSpanSys) + m.Mallocs = metrics.Gauge(stats.Mallocs) + m.NextGC = metrics.Gauge(stats.NextGC) + m.NumForcedGC = metrics.Gauge(stats.NumForcedGC) + m.NumGC = metrics.Gauge(stats.NumGC) + m.OtherSys = metrics.Gauge(stats.OtherSys) + m.PauseTotalNs = metrics.Gauge(stats.PauseTotalNs) + m.StackInuse = metrics.Gauge(stats.StackInuse) + m.StackSys = metrics.Gauge(stats.StackSys) + m.Sys = metrics.Gauge(stats.Sys) + m.TotalAlloc = metrics.Gauge(stats.TotalAlloc) +} diff --git a/internal/stats/runtime_test.go b/internal/stats/runtime_test.go new file mode 100644 index 0000000..6fbd309 --- /dev/null +++ b/internal/stats/runtime_test.go @@ -0,0 +1,23 @@ +package stats_test + +import ( + "testing" + + "github.com/ex0rcist/metflix/internal/stats" + "github.com/stretchr/testify/require" +) + +func TestRuntimePoll(t *testing.T) { + require := require.New(t) + + rs := stats.RuntimeStats{} + require.Zero(rs.TotalAlloc) + + rs.Poll() + require.NotZero(rs.TotalAlloc) + + newRs := stats.RuntimeStats{} + newRs.Poll() + + require.NotEqual(newRs.TotalAlloc, rs.TotalAlloc) +} diff --git a/internal/stats/stats.go b/internal/stats/stats.go new file mode 100644 index 0000000..a9f31f6 --- /dev/null +++ b/internal/stats/stats.go @@ -0,0 +1,34 @@ +package stats + +import ( + "math/rand" + "time" + + "github.com/ex0rcist/metflix/internal/metrics" + "github.com/rs/zerolog/log" +) + +type Stats struct { + Runtime RuntimeStats + PollCount metrics.Counter + RandomValue metrics.Gauge + + generator *rand.Rand +} + +func NewStats() *Stats { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return &Stats{generator: r} +} + +func (m *Stats) Poll() error { + log.Info().Msg("polling stats ... ") + + m.PollCount++ + m.RandomValue = metrics.Gauge(m.generator.Float64()) + m.Runtime.Poll() + + // todo: errors? + + return nil +} diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go new file mode 100644 index 0000000..d672fef --- /dev/null +++ b/internal/stats/stats_test.go @@ -0,0 +1,31 @@ +package stats_test + +import ( + "testing" + + "github.com/ex0rcist/metflix/internal/metrics" + "github.com/ex0rcist/metflix/internal/stats" + "github.com/stretchr/testify/require" +) + +func TestStatsPoll(t *testing.T) { + require := require.New(t) + + s := stats.NewStats() + require.Zero(s.Runtime.Alloc) + require.Zero(s.PollCount) + require.Zero(s.RandomValue) + + err := s.Poll() + require.NoError(err) + require.Equal(s.PollCount, metrics.Counter(1)) + require.NotZero(s.Runtime.Alloc) + require.NotZero(s.RandomValue) + + prev := *s + err = s.Poll() + require.NoError(err) + require.Equal(s.PollCount, metrics.Counter(2)) + require.NotEqual(prev.RandomValue, s.RandomValue) + require.NotEqual(prev.Runtime.Alloc, s.Runtime.Alloc) +} From 454d326d3d8aacd64e5a10f6e2feb2c35187191b Mon Sep 17 00:00:00 2001 From: Evgenii Shuvalov Date: Fri, 5 Jul 2024 15:20:38 +0300 Subject: [PATCH 5/8] added basic iter3 features --- internal/agent/agent.go | 64 ++++++++++-------- internal/agent/api.go | 2 - internal/metrics/types.go | 1 + internal/metrics/types_test.go | 4 +- internal/server/handlers.go | 57 +++++++++++++--- internal/server/handlers_test.go | 112 +++++++++++++++++++++++++++---- internal/server/router.go | 8 ++- internal/stats/stats.go | 2 +- internal/stats/stats_test.go | 8 ++- internal/storage/memory.go | 30 ++++++--- internal/storage/memory_test.go | 10 +-- internal/storage/record.go | 11 +-- internal/storage/record_test.go | 8 +-- internal/storage/storage.go | 2 +- 14 files changed, 236 insertions(+), 83 deletions(-) diff --git a/internal/agent/agent.go b/internal/agent/agent.go index c824e3b..01743b3 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -4,6 +4,7 @@ import ( "time" "github.com/ex0rcist/metflix/internal/stats" + "github.com/rs/zerolog/log" ) type Config struct { @@ -54,38 +55,43 @@ func (app *Agent) startReporting() { } func (app *Agent) reportStats() { + log.Info().Msg("reporting stats ... ") + + // agent continues polling while repor is in progress, take snapshot + snapshot := *app.stats + app.api. - Report("Alloc", app.stats.Runtime.Alloc). - Report("BuckHashSys", app.stats.Runtime.BuckHashSys). - Report("Frees", app.stats.Runtime.Frees). - Report("GCCPUFraction", app.stats.Runtime.GCCPUFraction). - Report("GCSys", app.stats.Runtime.GCSys). - Report("HeapAlloc", app.stats.Runtime.HeapAlloc). - Report("HeapIdle", app.stats.Runtime.HeapIdle). - Report("HeapInuse", app.stats.Runtime.HeapInuse). - Report("HeapObjects", app.stats.Runtime.HeapObjects). - Report("HeapReleased", app.stats.Runtime.HeapReleased). - Report("HeapSys", app.stats.Runtime.HeapSys). - Report("LastGC", app.stats.Runtime.LastGC). - Report("Lookups", app.stats.Runtime.Lookups). - Report("MCacheInuse", app.stats.Runtime.MCacheInuse). - Report("MCacheSys", app.stats.Runtime.MCacheSys). - Report("MSpanInuse", app.stats.Runtime.MSpanInuse). - Report("MSpanSys", app.stats.Runtime.MSpanSys). - Report("Mallocs", app.stats.Runtime.Mallocs). - Report("NextGC", app.stats.Runtime.NextGC). - Report("NumForcedGC", app.stats.Runtime.NumForcedGC). - Report("NumGC", app.stats.Runtime.NumGC). - Report("OtherSys", app.stats.Runtime.OtherSys). - Report("PauseTotalNs", app.stats.Runtime.PauseTotalNs). - Report("StackInuse", app.stats.Runtime.StackInuse). - Report("StackSys", app.stats.Runtime.StackSys). - Report("Sys", app.stats.Runtime.Sys). - Report("TotalAlloc", app.stats.Runtime.TotalAlloc) + Report("Alloc", snapshot.Runtime.Alloc). + Report("BuckHashSys", snapshot.Runtime.BuckHashSys). + Report("Frees", snapshot.Runtime.Frees). + Report("GCCPUFraction", snapshot.Runtime.GCCPUFraction). + Report("GCSys", snapshot.Runtime.GCSys). + Report("HeapAlloc", snapshot.Runtime.HeapAlloc). + Report("HeapIdle", snapshot.Runtime.HeapIdle). + Report("HeapInuse", snapshot.Runtime.HeapInuse). + Report("HeapObjects", snapshot.Runtime.HeapObjects). + Report("HeapReleased", snapshot.Runtime.HeapReleased). + Report("HeapSys", snapshot.Runtime.HeapSys). + Report("LastGC", snapshot.Runtime.LastGC). + Report("Lookups", snapshot.Runtime.Lookups). + Report("MCacheInuse", snapshot.Runtime.MCacheInuse). + Report("MCacheSys", snapshot.Runtime.MCacheSys). + Report("MSpanInuse", snapshot.Runtime.MSpanInuse). + Report("MSpanSys", snapshot.Runtime.MSpanSys). + Report("Mallocs", snapshot.Runtime.Mallocs). + Report("NextGC", snapshot.Runtime.NextGC). + Report("NumForcedGC", snapshot.Runtime.NumForcedGC). + Report("NumGC", snapshot.Runtime.NumGC). + Report("OtherSys", snapshot.Runtime.OtherSys). + Report("PauseTotalNs", snapshot.Runtime.PauseTotalNs). + Report("StackInuse", snapshot.Runtime.StackInuse). + Report("StackSys", snapshot.Runtime.StackSys). + Report("Sys", snapshot.Runtime.Sys). + Report("TotalAlloc", snapshot.Runtime.TotalAlloc) app.api. - Report("RandomValue", app.stats.RandomValue) + Report("RandomValue", snapshot.RandomValue) app.api. - Report("PollCount", app.stats.PollCount) + Report("PollCount", snapshot.PollCount) } diff --git a/internal/agent/api.go b/internal/agent/api.go index ef761f2..4bbf976 100644 --- a/internal/agent/api.go +++ b/internal/agent/api.go @@ -34,8 +34,6 @@ func NewAPI(serverAddr string, httpTransport http.RoundTripper) *API { } func (c *API) Report(name string, metric metrics.Metric) *API { - log.Info().Msg("reporting stats ... ") - url := c.baseURL + fmt.Sprintf("/update/%s/%s/%s", metric.Kind(), name, metric) req, err := http.NewRequest(http.MethodPost, url, http.NoBody) diff --git a/internal/metrics/types.go b/internal/metrics/types.go index be1dbd8..a25eb24 100644 --- a/internal/metrics/types.go +++ b/internal/metrics/types.go @@ -4,6 +4,7 @@ import "strconv" type Metric interface { Kind() string + String() string } type Counter int64 diff --git a/internal/metrics/types_test.go b/internal/metrics/types_test.go index 51e4727..f1845b8 100644 --- a/internal/metrics/types_test.go +++ b/internal/metrics/types_test.go @@ -32,7 +32,7 @@ func TestKind(t *testing.T) { } } -func TesString(t *testing.T) { +func TestString(t *testing.T) { tests := []struct { name string metric metrics.Metric @@ -56,7 +56,7 @@ func TesString(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := tt.metric.Kind(); got != tt.want { + if got := tt.metric.String(); got != tt.want { t.Errorf("Metric.String() = %v, want %v", got, tt.want) } }) diff --git a/internal/server/handlers.go b/internal/server/handlers.go index bef3725..3e9fc9f 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -41,8 +41,7 @@ func (r Resource) Homepage(res http.ResponseWriter, _ *http.Request) { func (r Resource) UpdateMetric(res http.ResponseWriter, req *http.Request) { metricName := req.PathValue("metricName") - - metricType := req.PathValue("metricType") + metricKind := req.PathValue("metricKind") metricValue := req.PathValue("metricValue") //При попытке передать запрос без имени метрики возвращать http.StatusNotFound. @@ -58,18 +57,19 @@ func (r Resource) UpdateMetric(res http.ResponseWriter, req *http.Request) { } //При попытке передать запрос с некорректным типом метрики или значением возвращать http.StatusBadRequest. - if err := validators.ValidateKind(metricType); err != nil { + if err := validators.ValidateKind(metricKind); err != nil { res.WriteHeader(http.StatusBadRequest) return } - var record storage.Record + var rec storage.Record - switch metricType { + switch metricKind { case "counter": var value int64 = 0 - current, err := r.storage.Get(metricName) + recordID := storage.CalculateRecordID(metricName, metricKind) + current, err := r.storage.Get(recordID) if err == nil { value = int64(current.Value.(metrics.Counter)) } @@ -81,7 +81,7 @@ func (r Resource) UpdateMetric(res http.ResponseWriter, req *http.Request) { } value += incr - record = storage.Record{Name: metricName, Value: metrics.Counter(value)} + rec = storage.Record{Name: metricName, Value: metrics.Counter(value)} case "gauge": current, err := strconv.ParseFloat(metricValue, 64) if err != nil { @@ -89,15 +89,54 @@ func (r Resource) UpdateMetric(res http.ResponseWriter, req *http.Request) { return } - record = storage.Record{Name: metricName, Value: metrics.Gauge(current)} + rec = storage.Record{Name: metricName, Value: metrics.Gauge(current)} default: panic("why") // todo } - err := r.storage.Push(record) + err := r.storage.Push(rec) if err != nil { panic("cannot push") // todo } res.WriteHeader(http.StatusOK) } + +func (r Resource) ShowMetric(res http.ResponseWriter, req *http.Request) { + metricName := req.PathValue("metricName") + metricKind := req.PathValue("metricKind") + + //При попытке передать запрос без имени метрики возвращать http.StatusNotFound. + if err := validators.EnsureNamePresent(metricName); err != nil { + res.WriteHeader(http.StatusNotFound) + return + } + + //При попытке передать запрос с некорректным именем метрики возвращать http.StatusBadRequest. + if err := validators.ValidateName(metricName); err != nil { + res.WriteHeader(http.StatusBadRequest) + return + } + + //При попытке передать запрос с некорректным типом метрики или значением возвращать http.StatusBadRequest. + if err := validators.ValidateKind(metricKind); err != nil { + res.WriteHeader(http.StatusBadRequest) + return + } + + var record storage.Record + + recordID := storage.CalculateRecordID(metricName, metricKind) + record, err := r.storage.Get(recordID) + if err != nil { + + } + + body := record.Value.String() + + res.WriteHeader(http.StatusOK) + _, wErr := res.Write([]byte(body)) + if wErr != nil { + + } +} diff --git a/internal/server/handlers_test.go b/internal/server/handlers_test.go index 4a52d35..7424cda 100644 --- a/internal/server/handlers_test.go +++ b/internal/server/handlers_test.go @@ -1,6 +1,7 @@ package server_test import ( + "fmt" "io" "net/http" "net/http/httptest" @@ -9,6 +10,7 @@ import ( "github.com/ex0rcist/metflix/internal/metrics" "github.com/ex0rcist/metflix/internal/server" "github.com/ex0rcist/metflix/internal/storage" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -60,12 +62,12 @@ func TestHomepage(t *testing.T) { }, } - for _, tt := range tests { - storage := storage.NewMemStorage() - router := server.NewRouter(storage) - testServer := httptest.NewServer(router) - defer testServer.Close() + storage := storage.NewMemStorage() + router := server.NewRouter(storage) + testServer := httptest.NewServer(router) + defer testServer.Close() + for _, tt := range tests { if len(tt.metrics) > 0 { for _, r := range tt.metrics { err := storage.Push(r) @@ -99,15 +101,24 @@ func TestUpdateMetric(t *testing.T) { require := require.New(t) tests := []struct { - name string - path string - want result + name string + path string + metrics []storage.Record + want result }{ { name: "push counter", path: "/update/counter/test/42", want: result{code: http.StatusOK, body: ""}, }, + { + name: "push counter with existing value", + path: "/update/counter/test/42", + metrics: []storage.Record{ + {Name: "test", Value: metrics.Counter(42)}, + }, + want: result{code: http.StatusOK, body: ""}, + }, { name: "push gauge", path: "/update/gauge/test/42.42", @@ -145,12 +156,20 @@ func TestUpdateMetric(t *testing.T) { }, } + strg := storage.NewMemStorage() + router := server.NewRouter(strg) + testServer := httptest.NewServer(router) + defer testServer.Close() + for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - router := server.NewRouter(storage.NewMemStorage()) - testServer := httptest.NewServer(router) - defer testServer.Close() + if len(tt.metrics) > 0 { + for _, r := range tt.metrics { + err := strg.Push(r) + require.NoError(err) + } + } + t.Run(tt.name, func(t *testing.T) { code, _, body := testRequest(t, testServer, http.MethodPost, tt.path, nil) require.Equal(tt.want.code, code) @@ -158,3 +177,72 @@ func TestUpdateMetric(t *testing.T) { }) } } + +func TestShowMetric(t *testing.T) { + type result struct { + code int + body string + } + + //require := require.New(t) + tests := []struct { + name string + path string + want result + }{ + { + name: "get counter", + path: "/value/counter/test", + want: result{code: http.StatusOK, body: "42"}, + }, + { + name: "get gauge", + path: "/value/gauge/test", + want: result{code: http.StatusOK, body: "42.42"}, + }, + { + name: "fail on invalid kind", + path: "/value/xxx/test", + want: result{code: http.StatusBadRequest}, + }, + { + name: "fail on empty metric name", + path: "/value/counter", + want: result{code: http.StatusNotFound}, + }, + { + name: "fail on counter with invalid name", + path: "/value/counter/inva!id", + want: result{code: http.StatusBadRequest}, + }, + { + name: "fail on gauge with invalid name", + path: "/value/gauge/inval!d", + want: result{code: http.StatusBadRequest}, + }, + } + + strg := storage.NewMemStorage() + router := server.NewRouter(strg) + testServer := httptest.NewServer(router) + defer testServer.Close() + + err := strg.Push(storage.Record{Name: "test", Value: metrics.Counter(42)}) + if err != nil { + fmt.Println(err) + } + + err = strg.Push(storage.Record{Name: "test", Value: metrics.Gauge(42.42)}) + if err != nil { + fmt.Println(err) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + code, _, body := testRequest(t, testServer, http.MethodGet, tt.path, nil) + + assert.Equal(t, tt.want.code, code) + assert.Equal(t, tt.want.body, string(body)) + }) + } +} diff --git a/internal/server/router.go b/internal/server/router.go index f8cac41..fb3cb02 100644 --- a/internal/server/router.go +++ b/internal/server/router.go @@ -12,8 +12,14 @@ func NewRouter(storage storage.Storage) http.Handler { router := chi.NewRouter() resource := Resource{storage: storage} + router.NotFound(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // no default body + w.WriteHeader(http.StatusNotFound) + })) + router.Get("/", resource.Homepage) // TODO: resource? - router.Post("/update/{metricType}/{metricName}/{metricValue}", resource.UpdateMetric) + router.Post("/update/{metricKind}/{metricName}/{metricValue}", resource.UpdateMetric) + router.Get("/value/{metricKind}/{metricName}", resource.ShowMetric) return router } diff --git a/internal/stats/stats.go b/internal/stats/stats.go index a9f31f6..d1ee901 100644 --- a/internal/stats/stats.go +++ b/internal/stats/stats.go @@ -10,7 +10,7 @@ import ( type Stats struct { Runtime RuntimeStats - PollCount metrics.Counter + PollCount metrics.Gauge RandomValue metrics.Gauge generator *rand.Rand diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go index d672fef..b7d3ac2 100644 --- a/internal/stats/stats_test.go +++ b/internal/stats/stats_test.go @@ -18,14 +18,18 @@ func TestStatsPoll(t *testing.T) { err := s.Poll() require.NoError(err) - require.Equal(s.PollCount, metrics.Counter(1)) + require.Equal(s.PollCount, metrics.Gauge(1)) require.NotZero(s.Runtime.Alloc) require.NotZero(s.RandomValue) prev := *s err = s.Poll() require.NoError(err) - require.Equal(s.PollCount, metrics.Counter(2)) + require.Equal(s.PollCount, metrics.Gauge(2)) require.NotEqual(prev.RandomValue, s.RandomValue) require.NotEqual(prev.Runtime.Alloc, s.Runtime.Alloc) + + err = s.Poll() + require.NoError(err) + require.Equal(s.PollCount, metrics.Gauge(3)) } diff --git a/internal/storage/memory.go b/internal/storage/memory.go index d7a073c..e52c7fb 100644 --- a/internal/storage/memory.go +++ b/internal/storage/memory.go @@ -2,21 +2,24 @@ package storage import ( "errors" + "sort" ) type MemStorage struct { - Data map[string]Record + Data map[RecordID]Record } func (strg *MemStorage) Push(record Record) error { - id := RecordID(record.Name, record.Value.Kind()) - strg.Data[id] = record + recordID := CalculateRecordID(record.Name, record.Value.Kind()) + strg.Data[recordID] = record return nil } -func (strg MemStorage) Get(id string) (Record, error) { - record, ok := strg.Data[id] +type By func(r1, r2 *Record) bool + +func (strg MemStorage) Get(recordID RecordID) (Record, error) { + record, ok := strg.Data[recordID] if !ok { return Record{}, errors.New("no value") } @@ -24,13 +27,20 @@ func (strg MemStorage) Get(id string) (Record, error) { return record, nil } +// Sorted by name +// Too complicated? mb easy way? func (strg MemStorage) GetAll() ([]Record, error) { + names := make([]string, 0, len(strg.Data)) + + for k := range strg.Data { + names = append(names, string(k)) + } + arr := make([]Record, len(strg.Data)) - i := 0 + sort.Strings(names) - for _, v := range strg.Data { - arr[i] = v - i++ + for i, v := range names { + arr[i] = strg.Data[RecordID(v)] } return arr, nil @@ -38,6 +48,6 @@ func (strg MemStorage) GetAll() ([]Record, error) { func NewMemStorage() *MemStorage { return &MemStorage{ - Data: make(map[string]Record), + Data: make(map[RecordID]Record), } } diff --git a/internal/storage/memory_test.go b/internal/storage/memory_test.go index 0e06575..8f016f8 100644 --- a/internal/storage/memory_test.go +++ b/internal/storage/memory_test.go @@ -20,7 +20,7 @@ func TestPushCounter(t *testing.T) { err := strg.Push(record) require.NoError(err) - require.Equal(value, strg.Data[record.RecordID()].Value) + require.Equal(value, strg.Data[record.CalculateRecordID()].Value) } func TestPushGauge(t *testing.T) { @@ -34,7 +34,7 @@ func TestPushGauge(t *testing.T) { err := strg.Push(record) require.NoError(err) - require.Equal(value, strg.Data[record.RecordID()].Value) + require.Equal(value, strg.Data[record.CalculateRecordID()].Value) } func TestPushWithSameName(t *testing.T) { @@ -53,8 +53,8 @@ func TestPushWithSameName(t *testing.T) { err2 := strg.Push(record2) require.NoError(err2) - require.Equal(counterValue, strg.Data[record1.RecordID()].Value) - require.Equal(gaugeValue, strg.Data[record2.RecordID()].Value) + require.Equal(counterValue, strg.Data[record1.CalculateRecordID()].Value) + require.Equal(gaugeValue, strg.Data[record2.CalculateRecordID()].Value) } func TestGet(t *testing.T) { @@ -67,7 +67,7 @@ func TestGet(t *testing.T) { err := strg.Push(record) require.NoError(err) - gotRecord, err := strg.Get(record.RecordID()) + gotRecord, err := strg.Get(record.CalculateRecordID()) require.NoError(err) require.Equal(value, gotRecord.Value) } diff --git a/internal/storage/record.go b/internal/storage/record.go index e4bf4c4..243137a 100644 --- a/internal/storage/record.go +++ b/internal/storage/record.go @@ -2,19 +2,20 @@ package storage import "github.com/ex0rcist/metflix/internal/metrics" +type RecordID string type Record struct { Name string Value metrics.Metric } -func RecordID(name, kind string) string { +func CalculateRecordID(name, kind string) RecordID { if len(name) == 0 || len(kind) == 0 { - return "" + return RecordID("") } - return name + "_" + kind + return RecordID(name + "_" + kind) } -func (r Record) RecordID() string { - return RecordID(r.Name, r.Value.Kind()) +func (r Record) CalculateRecordID() RecordID { + return CalculateRecordID(r.Name, r.Value.Kind()) } diff --git a/internal/storage/record_test.go b/internal/storage/record_test.go index 3a98258..5177ad8 100644 --- a/internal/storage/record_test.go +++ b/internal/storage/record_test.go @@ -14,16 +14,16 @@ func TestRecordId(t *testing.T) { record1 := storage.Record{Name: "test", Value: metrics.Counter(42)} record2 := storage.Record{Name: "test", Value: metrics.Gauge(42.42)} - require.Equal("test_counter", record1.RecordID()) - require.Equal("test_gauge", record2.RecordID()) + require.Equal(storage.RecordID("test_counter"), record1.CalculateRecordID()) + require.Equal(storage.RecordID("test_gauge"), record2.CalculateRecordID()) } func TestRecordIdWithEmptyName(t *testing.T) { require := require.New(t) - require.Equal("", storage.RecordID("", "counter")) + require.Equal(storage.RecordID(""), storage.CalculateRecordID("", "counter")) } func TestRecordIdWithEmptyKind(t *testing.T) { require := require.New(t) - require.Equal("", storage.RecordID("test", "")) + require.Equal(storage.RecordID(""), storage.CalculateRecordID("test", "")) } diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 7b09267..6656e5c 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -2,6 +2,6 @@ package storage type Storage interface { Push(record Record) error - Get(key string) (Record, error) + Get(recordID RecordID) (Record, error) GetAll() ([]Record, error) } From 4cac6ad24ba3e9f1db9d043874075f1b26f2d933 Mon Sep 17 00:00:00 2001 From: Evgenii Shuvalov Date: Fri, 5 Jul 2024 15:34:17 +0300 Subject: [PATCH 6/8] iter3 fixes --- go.mod | 2 -- go.sum | 4 ---- internal/server/handlers.go | 25 +++++++++++++++++++------ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index d90b327..51ec017 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,8 @@ module github.com/ex0rcist/metflix go 1.22.4 require ( - github.com/go-chi/chi v1.5.5 github.com/go-chi/chi/v5 v5.1.0 github.com/rs/zerolog v1.33.0 - github.com/satori/go.uuid v1.2.0 github.com/stretchr/testify v1.9.0 ) diff --git a/go.sum b/go.sum index 8358a24..ea89f77 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= -github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -17,8 +15,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 3e9fc9f..d724f47 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -8,6 +8,7 @@ import ( "github.com/ex0rcist/metflix/internal/metrics" "github.com/ex0rcist/metflix/internal/storage" "github.com/ex0rcist/metflix/internal/validators" + "github.com/rs/zerolog/log" ) type Resource struct { @@ -20,6 +21,15 @@ func NewResource(s storage.Storage) Resource { } } +func writeErrorResponse(w http.ResponseWriter, code int, err error) { + log.Error().Err(err).Msg("") + + w.WriteHeader(code) // only header for now + + // resp := fmt.Sprintf("%d %v", code, err) + // http.Error(w, resp, code) +} + func (r Resource) Homepage(res http.ResponseWriter, _ *http.Request) { body := fmt.Sprintln("mainpage here.") @@ -108,19 +118,19 @@ func (r Resource) ShowMetric(res http.ResponseWriter, req *http.Request) { //При попытке передать запрос без имени метрики возвращать http.StatusNotFound. if err := validators.EnsureNamePresent(metricName); err != nil { - res.WriteHeader(http.StatusNotFound) + writeErrorResponse(res, http.StatusNotFound, err) return } //При попытке передать запрос с некорректным именем метрики возвращать http.StatusBadRequest. if err := validators.ValidateName(metricName); err != nil { - res.WriteHeader(http.StatusBadRequest) + writeErrorResponse(res, http.StatusBadRequest, err) return } //При попытке передать запрос с некорректным типом метрики или значением возвращать http.StatusBadRequest. if err := validators.ValidateKind(metricKind); err != nil { - res.WriteHeader(http.StatusBadRequest) + writeErrorResponse(res, http.StatusBadRequest, err) return } @@ -129,14 +139,17 @@ func (r Resource) ShowMetric(res http.ResponseWriter, req *http.Request) { recordID := storage.CalculateRecordID(metricName, metricKind) record, err := r.storage.Get(recordID) if err != nil { - + writeErrorResponse(res, http.StatusNotFound, err) + return } body := record.Value.String() res.WriteHeader(http.StatusOK) - _, wErr := res.Write([]byte(body)) - if wErr != nil { + _, err = res.Write([]byte(body)) + if err != nil { + writeErrorResponse(res, http.StatusInternalServerError, err) + return } } From 25c99986d828434d935e4f3d71f11dc0cfa7f080 Mon Sep 17 00:00:00 2001 From: Evgenii Shuvalov Date: Fri, 5 Jul 2024 19:27:11 +0300 Subject: [PATCH 7/8] added basic iter4 features --- cmd/agent/main.go | 21 ++++--- cmd/server/main.go | 8 ++- go.mod | 1 + go.sum | 2 + internal/agent/agent.go | 67 ++++++++++++++++------- internal/agent/agent_test.go | 16 +----- internal/agent/api.go | 13 +++-- internal/agent/api_test.go | 14 +++-- internal/{stats => agent}/runtime.go | 2 +- internal/{stats => agent}/runtime_test.go | 8 +-- internal/{stats => agent}/stats.go | 2 +- internal/{stats => agent}/stats_test.go | 6 +- internal/entities/address.go | 37 +++++++++++++ internal/entities/address_test.go | 49 +++++++++++++++++ internal/server/server.go | 31 ++++++++++- internal/storage/memory.go | 14 ++--- 16 files changed, 219 insertions(+), 72 deletions(-) rename internal/{stats => agent}/runtime.go (99%) rename internal/{stats => agent}/runtime_test.go (68%) rename internal/{stats => agent}/stats.go (97%) rename internal/{stats => agent}/stats_test.go (88%) create mode 100644 internal/entities/address.go create mode 100644 internal/entities/address_test.go diff --git a/cmd/agent/main.go b/cmd/agent/main.go index b51d111..10509d3 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -11,24 +11,23 @@ import ( func main() { logging.Setup() - conf := agent.Config{ - Address: "http://0.0.0.0:8080", - ReportInterval: 10 * time.Second, - PollInterval: 2 * time.Second, - PollTimeout: 2 * time.Second, - ExportTimeout: 4 * time.Second, - } // todo: yml? - log.Info().Msg("starting agent...") - agnt := agent.New(conf) + agnt := agent.New() + + err := agnt.ParseFlags() + if err != nil { + panic(err) + } + + log.Info().Msgf("agent args: a: %v, p: %v, r: %v", agnt.Config.Address, agnt.Config.PollInterval, agnt.Config.ReportInterval) + agnt.Run() - log.Info().Msg("agent ready") + log.Info().Msgf("agent ready") for { // fixme: tmp hack for goroutine time.Sleep(time.Second * 1) } - } diff --git a/cmd/server/main.go b/cmd/server/main.go index 07924c3..7294f49 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -16,7 +16,13 @@ func main() { panic(err) } - log.Info().Msg("server ready") + err = srv.ParseFlags() + if err != nil { + panic(err) + } + + log.Info().Msgf("server flags: address=%v", srv.Config.Address) + log.Info().Msg("server ready") // TODO: must be after run? err2 := srv.Run() if err2 != nil { diff --git a/go.mod b/go.mod index 51ec017..26a4544 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.4 require ( github.com/go-chi/chi/v5 v5.1.0 github.com/rs/zerolog v1.33.0 + github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 ) diff --git a/go.sum b/go.sum index ea89f77..8534f95 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 01743b3..6891919 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -3,31 +3,58 @@ package agent import ( "time" - "github.com/ex0rcist/metflix/internal/stats" + "github.com/ex0rcist/metflix/internal/entities" "github.com/rs/zerolog/log" + "github.com/spf13/pflag" ) +type Agent struct { + Config *Config + Stats *Stats + API *API +} + type Config struct { - Address string + Address entities.Address PollInterval time.Duration ReportInterval time.Duration - PollTimeout time.Duration - ExportTimeout time.Duration } -type Agent struct { - config Config - stats *stats.Stats - api *API +func New() *Agent { + config := &Config{ + Address: "0.0.0.0:8080", + PollInterval: 2 * time.Second, + ReportInterval: 10 * time.Second, + } + + stats := NewStats() + api := NewAPI(&config.Address, nil) + + return &Agent{ + Config: config, + Stats: stats, + API: api, + } } -func New(cfg Config) *Agent { - stts := stats.NewStats() - api := NewAPI(cfg.Address, nil) +func (app *Agent) ParseFlags() error { + address := app.Config.Address + + pflag.VarP(&address, "address", "a", "address:port for HTTP API requests") // HELP: "&"" because Set() has pointer receiver? + pflag.DurationVarP(&app.Config.PollInterval, "poll-interval", "p", app.Config.PollInterval, "interval (s) for polling stats") + pflag.DurationVarP(&app.Config.ReportInterval, "report-interval", "r", app.Config.ReportInterval, "interval (s) for polling stats") - agnt := &Agent{config: cfg, stats: stts, api: api} + pflag.Parse() + + // because VarP gets non-pointer value, set it manually + pflag.Visit(func(f *pflag.Flag) { + switch f.Name { + case "address": + app.Config.Address = address + } + }) - return agnt + return nil } func (app *Agent) Run() { @@ -37,18 +64,18 @@ func (app *Agent) Run() { func (app *Agent) startPolling() { for { - err := app.stats.Poll() + err := app.Stats.Poll() if err != nil { return // todo: handle errors } - time.Sleep(app.config.PollInterval) + time.Sleep(app.Config.PollInterval) } } func (app *Agent) startReporting() { for { - time.Sleep(app.config.ReportInterval) + time.Sleep(app.Config.ReportInterval) app.reportStats() // todo: handle errors } @@ -58,9 +85,9 @@ func (app *Agent) reportStats() { log.Info().Msg("reporting stats ... ") // agent continues polling while repor is in progress, take snapshot - snapshot := *app.stats + snapshot := *app.Stats - app.api. + app.API. Report("Alloc", snapshot.Runtime.Alloc). Report("BuckHashSys", snapshot.Runtime.BuckHashSys). Report("Frees", snapshot.Runtime.Frees). @@ -89,9 +116,9 @@ func (app *Agent) reportStats() { Report("Sys", snapshot.Runtime.Sys). Report("TotalAlloc", snapshot.Runtime.TotalAlloc) - app.api. + app.API. Report("RandomValue", snapshot.RandomValue) - app.api. + app.API. Report("PollCount", snapshot.PollCount) } diff --git a/internal/agent/agent_test.go b/internal/agent/agent_test.go index 8af7e33..0eda959 100644 --- a/internal/agent/agent_test.go +++ b/internal/agent/agent_test.go @@ -1,26 +1,16 @@ -package agent_test +package agent import ( "testing" - "time" - "github.com/ex0rcist/metflix/internal/agent" "github.com/stretchr/testify/require" ) func TestRun(t *testing.T) { - cfg := agent.Config{ - Address: "http://0.0.0.0:8080", - ReportInterval: 10 * time.Second, - PollInterval: 2 * time.Second, - PollTimeout: 2 * time.Second, - ExportTimeout: 4 * time.Second, - } - - agnt := agent.New(cfg) + agnt := New() require.NotPanics(t, agnt.Run) } func TestReportStats(t *testing.T) { - // todo: как тестировать приватные? + // HELP: как тестировать приватные? } diff --git a/internal/agent/api.go b/internal/agent/api.go index 4bbf976..f4a2423 100644 --- a/internal/agent/api.go +++ b/internal/agent/api.go @@ -6,17 +6,18 @@ import ( "net/http" "time" + "github.com/ex0rcist/metflix/internal/entities" "github.com/ex0rcist/metflix/internal/metrics" "github.com/rs/zerolog/log" ) type API struct { - baseURL string + address *entities.Address httpClient *http.Client err error } -func NewAPI(serverAddr string, httpTransport http.RoundTripper) *API { +func NewAPI(address *entities.Address, httpTransport http.RoundTripper) *API { if httpTransport == nil { httpTransport = http.DefaultTransport } @@ -27,14 +28,15 @@ func NewAPI(serverAddr string, httpTransport http.RoundTripper) *API { } return &API{ - baseURL: serverAddr, + address: address, httpClient: client, err: nil, } } func (c *API) Report(name string, metric metrics.Metric) *API { - url := c.baseURL + fmt.Sprintf("/update/%s/%s/%s", metric.Kind(), name, metric) + // todo: another transport? + url := "http://" + c.address.String() + fmt.Sprintf("/update/%s/%s/%s", metric.Kind(), name, metric) req, err := http.NewRequest(http.MethodPost, url, http.NoBody) if err != nil { @@ -47,7 +49,8 @@ func (c *API) Report(name string, metric metrics.Metric) *API { resp, err := c.httpClient.Do(req) if err != nil { - c.err = err + panic(err) + //c.err = err } defer resp.Body.Close() diff --git a/internal/agent/api_test.go b/internal/agent/api_test.go index 1089518..45da750 100644 --- a/internal/agent/api_test.go +++ b/internal/agent/api_test.go @@ -1,10 +1,10 @@ -package agent_test +package agent import ( "net/http" "testing" - "github.com/ex0rcist/metflix/internal/agent" + "github.com/ex0rcist/metflix/internal/entities" "github.com/ex0rcist/metflix/internal/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,7 +20,11 @@ func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { func TestNewApi(t *testing.T) { require := require.New(t) - require.NotPanics(func() { agent.NewAPI("someaddr", nil) }) + + require.NotPanics(func() { + address := entities.Address("localhost") + NewAPI(&address, nil) + }) } func TestApiClientReport(t *testing.T) { @@ -35,6 +39,8 @@ func TestApiClientReport(t *testing.T) { } } - api := agent.NewAPI("http://localhost:8080", RoundTripFunc(rtf)) + address := entities.Address("localhost:8080") + + api := NewAPI(&address, RoundTripFunc(rtf)) api.Report("Test", metrics.Counter(0)) } diff --git a/internal/stats/runtime.go b/internal/agent/runtime.go similarity index 99% rename from internal/stats/runtime.go rename to internal/agent/runtime.go index ce27c60..f153cd4 100644 --- a/internal/stats/runtime.go +++ b/internal/agent/runtime.go @@ -1,4 +1,4 @@ -package stats +package agent import ( "runtime" diff --git a/internal/stats/runtime_test.go b/internal/agent/runtime_test.go similarity index 68% rename from internal/stats/runtime_test.go rename to internal/agent/runtime_test.go index 6fbd309..48cfcd7 100644 --- a/internal/stats/runtime_test.go +++ b/internal/agent/runtime_test.go @@ -1,22 +1,22 @@ -package stats_test +package agent_test import ( "testing" - "github.com/ex0rcist/metflix/internal/stats" + "github.com/ex0rcist/metflix/internal/agent" "github.com/stretchr/testify/require" ) func TestRuntimePoll(t *testing.T) { require := require.New(t) - rs := stats.RuntimeStats{} + rs := agent.RuntimeStats{} require.Zero(rs.TotalAlloc) rs.Poll() require.NotZero(rs.TotalAlloc) - newRs := stats.RuntimeStats{} + newRs := agent.RuntimeStats{} newRs.Poll() require.NotEqual(newRs.TotalAlloc, rs.TotalAlloc) diff --git a/internal/stats/stats.go b/internal/agent/stats.go similarity index 97% rename from internal/stats/stats.go rename to internal/agent/stats.go index d1ee901..ae34fec 100644 --- a/internal/stats/stats.go +++ b/internal/agent/stats.go @@ -1,4 +1,4 @@ -package stats +package agent import ( "math/rand" diff --git a/internal/stats/stats_test.go b/internal/agent/stats_test.go similarity index 88% rename from internal/stats/stats_test.go rename to internal/agent/stats_test.go index b7d3ac2..cb265a8 100644 --- a/internal/stats/stats_test.go +++ b/internal/agent/stats_test.go @@ -1,17 +1,17 @@ -package stats_test +package agent_test import ( "testing" + "github.com/ex0rcist/metflix/internal/agent" "github.com/ex0rcist/metflix/internal/metrics" - "github.com/ex0rcist/metflix/internal/stats" "github.com/stretchr/testify/require" ) func TestStatsPoll(t *testing.T) { require := require.New(t) - s := stats.NewStats() + s := agent.NewStats() require.Zero(s.Runtime.Alloc) require.Zero(s.PollCount) require.Zero(s.RandomValue) diff --git a/internal/entities/address.go b/internal/entities/address.go new file mode 100644 index 0000000..64e1bf8 --- /dev/null +++ b/internal/entities/address.go @@ -0,0 +1,37 @@ +package entities + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +// https://github.com/spf13/pflag/blob/master/README.md#usage +// must comply with https://pkg.go.dev/github.com/spf13/pflag@v1.0.5#Value +type Address string + +func (a Address) String() string { + return string(a) +} + +func (a *Address) Set(src string) error { + chunks := strings.Split(src, ":") + if len(chunks) != 2 { + return fmt.Errorf("set address failed: %w", errors.New("bad address")) + } + + port := chunks[1] + + if _, err := strconv.Atoi(port); err != nil { + return fmt.Errorf("set address failed: %w", errors.New("bad port")) + } + + *a = Address(src) + + return nil +} + +func (a Address) Type() string { + return "string" +} diff --git a/internal/entities/address_test.go b/internal/entities/address_test.go new file mode 100644 index 0000000..ef1a568 --- /dev/null +++ b/internal/entities/address_test.go @@ -0,0 +1,49 @@ +package entities_test + +import ( + "testing" + + "github.com/ex0rcist/metflix/internal/entities" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAddressType(t *testing.T) { + addr := entities.Address("0.0.0.0:8080") + + require.Equal(t, "string", addr.Type()) +} + +func TestAddressSet(t *testing.T) { + tt := []struct { + name string + src string + want bool + }{ + {name: "correct interface", src: "0.0.0.0:8080", want: true}, + {name: "with ip", src: "42.42.42.42:4242", want: true}, + {name: "localhost and port", src: "localhost:8080", want: true}, + {name: "no colon", src: "localhost8080", want: false}, + {name: "no port", src: "localhost", want: false}, + {name: "invalid format", src: "localhost/42", want: false}, + {name: "invalid port ", src: "localhost:abc", want: false}, + {name: "no host", src: "8080", want: false}, + {name: "stolen", src: "", want: false}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + assert := assert.New(t) + addr := new(entities.Address) + + err := addr.Set(tc.src) + + if !tc.want { + assert.Error(err) + } else { + assert.NoError(err) + assert.Equal(tc.src, addr.String()) + } + }) + } +} diff --git a/internal/server/server.go b/internal/server/server.go index d70e45c..6889c71 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -3,25 +3,54 @@ package server import ( "net/http" + "github.com/ex0rcist/metflix/internal/entities" "github.com/ex0rcist/metflix/internal/storage" + "github.com/spf13/pflag" ) type Server struct { + Config *Config Storage storage.Storage Router http.Handler } +type Config struct { + Address entities.Address +} + func New() (*Server, error) { + config := &Config{ + Address: "0.0.0.0:8080", + } + storage := storage.NewMemStorage() router := NewRouter(storage) return &Server{ + Config: config, Storage: storage, Router: router, }, nil } +func (s *Server) ParseFlags() error { + address := s.Config.Address + + pflag.VarP(&address, "address", "a", "address:port for HTTP API requests") // HELP: "&"" because Set() has pointer receiver? + pflag.Parse() + + // because VarP gets non-pointer value, set it manually + pflag.Visit(func(f *pflag.Flag) { + switch f.Name { + case "address": + s.Config.Address = address + } + }) + + return nil +} + func (s *Server) Run() error { - err := http.ListenAndServe(`:8080`, s.Router) + err := http.ListenAndServe(s.Config.Address.String(), s.Router) // HELP: почему тип, реализующий String() не приводится к строке автоматически? return err } diff --git a/internal/storage/memory.go b/internal/storage/memory.go index e52c7fb..40aa8ba 100644 --- a/internal/storage/memory.go +++ b/internal/storage/memory.go @@ -9,6 +9,12 @@ type MemStorage struct { Data map[RecordID]Record } +func NewMemStorage() *MemStorage { + return &MemStorage{ + Data: make(map[RecordID]Record), + } +} + func (strg *MemStorage) Push(record Record) error { recordID := CalculateRecordID(record.Name, record.Value.Kind()) strg.Data[recordID] = record @@ -16,8 +22,6 @@ func (strg *MemStorage) Push(record Record) error { return nil } -type By func(r1, r2 *Record) bool - func (strg MemStorage) Get(recordID RecordID) (Record, error) { record, ok := strg.Data[recordID] if !ok { @@ -45,9 +49,3 @@ func (strg MemStorage) GetAll() ([]Record, error) { return arr, nil } - -func NewMemStorage() *MemStorage { - return &MemStorage{ - Data: make(map[RecordID]Record), - } -} From 7bdaa66536c109b29e0b16e3cf317e8232ba3f2a Mon Sep 17 00:00:00 2001 From: Evgenii Shuvalov Date: Fri, 5 Jul 2024 20:23:58 +0300 Subject: [PATCH 8/8] iter4 fixes --- internal/agent/agent.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 6891919..5c9c5b8 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -41,11 +41,16 @@ func (app *Agent) ParseFlags() error { address := app.Config.Address pflag.VarP(&address, "address", "a", "address:port for HTTP API requests") // HELP: "&"" because Set() has pointer receiver? - pflag.DurationVarP(&app.Config.PollInterval, "poll-interval", "p", app.Config.PollInterval, "interval (s) for polling stats") - pflag.DurationVarP(&app.Config.ReportInterval, "report-interval", "r", app.Config.ReportInterval, "interval (s) for polling stats") + + // Task requires us to receive intervals in seconds, not duration, so we have to do it dirty + pollFlag := pflag.IntP("poll-interval", "p", durationToInt(app.Config.PollInterval), "interval (s) for polling stats") + reportFlag := pflag.IntP("report-interval", "r", durationToInt(app.Config.ReportInterval), "interval (s) for polling stats") pflag.Parse() + app.Config.PollInterval = intToDuration(*pollFlag) + app.Config.ReportInterval = intToDuration(*reportFlag) + // because VarP gets non-pointer value, set it manually pflag.Visit(func(f *pflag.Flag) { switch f.Name { @@ -84,7 +89,7 @@ func (app *Agent) startReporting() { func (app *Agent) reportStats() { log.Info().Msg("reporting stats ... ") - // agent continues polling while repor is in progress, take snapshot + // agent continues polling while report is in progress, take snapshot? snapshot := *app.Stats app.API. @@ -122,3 +127,11 @@ func (app *Agent) reportStats() { app.API. Report("PollCount", snapshot.PollCount) } + +func intToDuration(s int) time.Duration { + return time.Duration(s) * time.Second +} + +func durationToInt(d time.Duration) int { + return int(d.Seconds()) +}