-
Notifications
You must be signed in to change notification settings - Fork 0
(feat/server) Collecting runtime metrics #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
632698e
98a85e4
8482628
c37c776
9248f61
57f51d5
ff79d8e
6cf1202
5604cd2
ac8b095
729c191
f310000
110e380
5482420
f825558
9ee0829
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| package main |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,27 @@ | ||
| // Server for metrics collection and alerting service | ||
| package main | ||
|
|
||
| func main() {} | ||
| import ( | ||
| "log" | ||
|
|
||
| "github.com/srg-bnd/observator/internal/handlers" | ||
| "github.com/srg-bnd/observator/internal/server" | ||
| "github.com/srg-bnd/observator/internal/services" | ||
| "github.com/srg-bnd/observator/internal/storage" | ||
| ) | ||
|
|
||
| var memStorage storage.MemStorage | ||
|
|
||
| func init() { | ||
| memStorage = storage.NewMemStorage() | ||
| // HACK to acess memStorage | ||
| server.MemStorage = &memStorage | ||
| handlers.MemStorage = &memStorage | ||
| services.MemStorage = &memStorage | ||
| } | ||
|
|
||
| func main() { | ||
| if err := server.Start(); err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| package main |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // Handlers for server | ||
| package handlers | ||
|
|
||
| import ( | ||
| "net/http" | ||
|
|
||
| "github.com/srg-bnd/observator/internal/services" | ||
| "github.com/srg-bnd/observator/internal/storage" | ||
| ) | ||
|
|
||
| var ( | ||
|
||
| MemStorage *storage.MemStorage | ||
| ) | ||
|
|
||
| func UpdateMetricHandler(res http.ResponseWriter, req *http.Request) { | ||
|
||
| metricType := req.PathValue("metricType") | ||
| metricName := req.PathValue("metricName") | ||
| metricValue := req.PathValue("metricValue") | ||
|
|
||
| data := services.UpdateMetricService(metricType, metricName, metricValue) | ||
|
|
||
| if data.Ok { | ||
| res.Header().Set("content-type", "text/plain; charset=utf-8") | ||
| res.WriteHeader(http.StatusOK) | ||
| } else { | ||
| switch data.Status { | ||
| case "typeError", "valueError": | ||
| res.WriteHeader(http.StatusBadRequest) | ||
| case "nameError": | ||
| res.WriteHeader(http.StatusNotFound) | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package handlers | ||
|
|
||
| import ( | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestUpdateMetricHandler(t *testing.T) { | ||
| // TODO | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // Middleware for server | ||
| package middleware | ||
|
|
||
| import "net/http" | ||
|
|
||
| type Middleware func(http.Handler) http.Handler | ||
|
|
||
| func Conveyor(h http.Handler, middlewares ...Middleware) http.Handler { | ||
|
||
| for _, middleware := range middlewares { | ||
| h = middleware(h) | ||
| } | ||
| return h | ||
| } | ||
|
|
||
| func CheckMethodPost(next http.Handler) http.Handler { | ||
| return http.HandlerFunc(func(resWriter http.ResponseWriter, req *http.Request) { | ||
| if req.Method != http.MethodPost { | ||
| http.Error(resWriter, "Method not allowed", http.StatusMethodNotAllowed) | ||
| return | ||
| } | ||
|
|
||
| next.ServeHTTP(resWriter, req) | ||
| }) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| package middleware |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package server | ||
|
|
||
| import ( | ||
| "net/http" | ||
|
|
||
| "github.com/srg-bnd/observator/internal/handlers" | ||
| "github.com/srg-bnd/observator/internal/middleware" | ||
| "github.com/srg-bnd/observator/internal/storage" | ||
| ) | ||
|
|
||
| const ( | ||
| defaultHost = `:8080` | ||
| ) | ||
|
|
||
| var ( | ||
| MemStorage *storage.MemStorage | ||
|
||
| mux *http.ServeMux | ||
| ) | ||
|
|
||
| func init() { | ||
| // Routes | ||
| mux = http.NewServeMux() | ||
| mux.Handle( | ||
| `/update/{metricType}/{metricName}/{metricValue}`, | ||
| middleware.Conveyor( | ||
| http.HandlerFunc(handlers.UpdateMetricHandler), | ||
| middleware.CheckMethodPost, | ||
| )) | ||
| } | ||
|
|
||
| // Init server dependencies before startup | ||
| func Start() error { | ||
| host, err := getHost() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| http.ListenAndServe(host, mux) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // Selects the server host | ||
| func getHost() (string, error) { | ||
| return defaultHost, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package server | ||
|
|
||
| import ( | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestStart(t *testing.T) { | ||
| // TODO | ||
| } | ||
|
|
||
| func TestGetHost(t *testing.T) { | ||
| got, _ := getHost() | ||
| want := ":8080" | ||
|
|
||
| if got != want { | ||
| t.Errorf(`Incorrect 'getHost()' method behavior, got "%v", want "%v"`, got, want) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // Handlers for server | ||
| package services | ||
|
|
||
| import ( | ||
| "slices" | ||
| "strconv" | ||
|
|
||
| "github.com/srg-bnd/observator/internal/storage" | ||
| ) | ||
|
|
||
| type Data struct { | ||
| Ok bool | ||
| Status string | ||
| } | ||
|
|
||
| func successData(status string) *Data { | ||
| return &Data{Ok: true, Status: status} | ||
| } | ||
|
|
||
| func errorData(status string) *Data { | ||
| return &Data{Ok: false, Status: status} | ||
| } | ||
|
|
||
| var ( | ||
| MemStorage *storage.MemStorage | ||
| ) | ||
|
|
||
| // Services | ||
|
|
||
| func UpdateMetricService(metricType, metricName, metricValue string) *Data { | ||
|
||
| // Check type | ||
| if !slices.Contains([]string{"counter", "gauge"}, metricType) { | ||
| return errorData("typeError") | ||
| } | ||
|
|
||
| // Check name | ||
| if metricName == "" { | ||
| return errorData("nameError") | ||
| } | ||
|
|
||
| switch metricType { | ||
| case "counter": | ||
| // Check and set value | ||
| value, err := strconv.ParseInt(metricValue, 10, 64) | ||
| if err != nil { | ||
| return errorData("valueError") | ||
| } else { | ||
| MemStorage.SetCounter(metricName, value) | ||
| } | ||
| case "gauge": | ||
| // Check and set value | ||
| value, err := strconv.ParseFloat(metricValue, 64) | ||
| if err != nil { | ||
| return errorData("valueError") | ||
| } else { | ||
| MemStorage.SetGauge(metricName, value) | ||
| } | ||
| } | ||
|
|
||
| return successData("Ok") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package services | ||
|
|
||
| import ( | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestUpdateMetricService(t *testing.T) { | ||
| // TODO | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // Storage for metrics | ||
| package storage | ||
|
|
||
| type ( | ||
| gauge float64 | ||
| counter int64 | ||
| ) | ||
|
|
||
| type MemStorage struct { | ||
| gauges map[string]gauge | ||
| counters map[string]counter | ||
| } | ||
|
|
||
| // Create MemStorage instance | ||
| func NewMemStorage() MemStorage { | ||
| return MemStorage{ | ||
| gauges: make(map[string]gauge), | ||
| counters: make(map[string]counter), | ||
| } | ||
| } | ||
|
|
||
| // Change gauge by key | ||
| func (mStore *MemStorage) SetGauge(key string, value float64) error { | ||
| mStore.gauges[key] = gauge(value) | ||
| return nil | ||
| } | ||
|
|
||
| // Return gauge by key | ||
| func (mStore *MemStorage) GetGauge(key string) float64 { | ||
| return float64(mStore.gauges[key]) | ||
| } | ||
|
|
||
| // Change counter by key | ||
| func (mStore *MemStorage) SetCounter(key string, value int64) error { | ||
| mStore.counters[key] += counter(value) | ||
| return nil | ||
| } | ||
|
|
||
| // Return gauge by counter | ||
| func (mStore *MemStorage) GetCounter(key string) int64 { | ||
| return int64(mStore.counters[key]) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package storage | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Отличное покрытие тестами для пакета |
||
|
|
||
| import ( | ||
| "reflect" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestNewMemStorage(t *testing.T) { | ||
| got := NewMemStorage() | ||
| want := MemStorage{} | ||
|
|
||
| if reflect.TypeOf(want) != reflect.TypeOf(got) { | ||
| t.Errorf(`Incorrect structure type for NewMemStorage(), got "%v", want "%v"`, got, want) | ||
| } | ||
| } | ||
|
|
||
| func TestSetGauge(t *testing.T) { | ||
| memStore := NewMemStorage() | ||
| want := float64(1) | ||
| memStore.SetGauge("key", want) | ||
| got := float64(memStore.gauges["key"]) | ||
|
|
||
| if got != want { | ||
| t.Errorf(`Incorrect 'SetGauge("key", %#v)' method behavior, got "%v", want "%v"`, want, got, want) | ||
| } | ||
| } | ||
|
|
||
| func TestGetGauge(t *testing.T) { | ||
| memStore := NewMemStorage() | ||
| want := float64(1) | ||
| memStore.gauges["key"] = gauge(want) | ||
| got := memStore.GetGauge("key") | ||
|
|
||
| if want != float64(got) { | ||
| t.Errorf(`Incorrect 'GetGauge("key")' method behavior, got "%v", want "%v"`, got, want) | ||
| } | ||
| } | ||
|
|
||
| func TestSetCounter(t *testing.T) { | ||
| memStore := NewMemStorage() | ||
| counter := int64(10) | ||
| want := int64(memStore.counters["key"]) + counter | ||
| memStore.SetCounter("key", counter) | ||
| got := int64(memStore.counters["key"]) | ||
|
|
||
| if got != want { | ||
| t.Errorf(`Incorrect 'SetCounter("key", %#v)' method behavior, got "%v", want "%v"`, want, got, want) | ||
| } | ||
| } | ||
|
|
||
| func TestGetCounter(t *testing.T) { | ||
| memStore := NewMemStorage() | ||
| want := int64(1) | ||
| memStore.counters["key"] = counter(want) | ||
| got := int64(memStore.GetCounter("key")) | ||
|
|
||
| if want != got { | ||
| t.Errorf(`Incorrect 'GetCounter("key")' method behavior, got "%v", want "%v"`, got, want) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
И еще тут напишу, вместо глобальных переменных и init() лучше использовать внедрение зависимостей. Вот так примерно
Как обещал на 1:1 вот код