Skip to content

Commit

Permalink
Iter2 (#4)
Browse files Browse the repository at this point in the history
* отрефакторил проект

* iter2

* исправления по результатам работы статического анализатора

* ещё исправление
  • Loading branch information
StasMerzlyakov authored Feb 4, 2024
1 parent e056a6c commit 20345c1
Show file tree
Hide file tree
Showing 14 changed files with 783 additions and 400 deletions.
31 changes: 30 additions & 1 deletion cmd/agent/agent.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
package main

func main() {}
import (
"context"
"github.com/StasMerzlyakov/go-metrics/internal/agent"
"os"
"os/signal"
"syscall"
)

func main() {
configuration := agent.Configuration{
ServerAddr: "http://localhost:8080",
ContentType: "text/plain",
PollIntervalSec: 2,
ReportIntervalSec: 10,
}

// Взято отсюда: "Реализация Graceful Shutdown в Go"(https://habr.com/ru/articles/771626/)
// скорее на будущее для сервера
ctx, cancel := context.WithCancel(context.Background())
exit := make(chan os.Signal, 1)
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)

if agent, err := agent.CreateAgent(ctx, configuration); err != nil {
panic(err)
} else {
<-exit
cancel()
agent.Wait() // ожидаение завершения go-рутин в агенте
}
}
5 changes: 4 additions & 1 deletion cmd/server/server.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package main

import (
"context"
"github.com/StasMerzlyakov/go-metrics/internal/server"
)

func main() {
if err := server.CreateServer(); err != nil {
ctx := context.Background() // TODO
configuration := server.Configuration{} // TODO
if err := server.CreateServer(ctx, configuration); err != nil {
panic(err)
}
}
93 changes: 93 additions & 0 deletions internal/agent/agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package agent

import (
"context"
"fmt"
"github.com/StasMerzlyakov/go-metrics/internal/storage"
"sync"
"time"
)

type Configuration struct {
ServerAddr string
ContentType string
PollIntervalSec int
ReportIntervalSec int
// TODO - http timeout
}

type Agent interface {
Wait()
}

func CreateAgent(ctx context.Context, config Configuration) (Agent, error) {
agent := &agent{
metricsSource: NewRuntimeMetricsSource(),
resultSender: NewHTTPResultSender(config.ServerAddr, config.ContentType),
gaugeStorage: storage.NewMemoryFloat64Storage(),
poolCounter: 0,
}
go agent.PoolMetrics(ctx, config.PollIntervalSec)
go agent.ReportMetrics(ctx, config.ReportIntervalSec)
agent.wg.Add(2)
return agent, nil
}

type agent struct {
metricsSource MetricsSource
resultSender ResultSender
gaugeStorage storage.MetricsStorage[float64]
poolCounter int64
wg sync.WaitGroup
}

func (a *agent) Wait() {
a.wg.Wait()
}

func (a *agent) PoolMetrics(ctx context.Context, pollIntervalSec int) {
counter := 0
for {
select {
case <-ctx.Done():
fmt.Printf("[%v] PoolMetrics DONE\n", time.Now())
a.wg.Done()
return
default:
time.Sleep(1 * time.Second) // Будем просыпаться каждую секунду для проверки ctx
counter++
if counter == pollIntervalSec {
counter = 0
for k, v := range a.metricsSource.PollMetrics() {
a.gaugeStorage.Set(k, v)
}
a.poolCounter = a.metricsSource.PollCount()
fmt.Printf("[%v] PoolMetrics - %v\n", time.Now(), a.poolCounter)
}
}
}
}

func (a *agent) ReportMetrics(ctx context.Context, reportIntervalSec int) {
counter := 0
for {
select {
case <-ctx.Done():
fmt.Printf("[%v] ReportMetrics DONE\n", time.Now())
a.wg.Done()
return
default:
time.Sleep(1 * time.Second) // Будем просыпаться каждую секунду для проверки ctx
counter++
if counter == reportIntervalSec {
counter = 0
for _, key := range a.gaugeStorage.Keys() {
val, _ := a.gaugeStorage.Get(key)
_ = a.resultSender.SendGauge(key, val)
}
_ = a.resultSender.SendCounter("PoolCount", a.poolCounter)
fmt.Printf("[%v] ReportMetrics success - %v\n", time.Now(), a.poolCounter)
}
}
}
}
15 changes: 15 additions & 0 deletions internal/agent/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package agent

import "errors"

type MetricsSource interface {
PollMetrics() map[string]float64
PollCount() int64
}

var ErrServerInteraction = errors.New("server interaction error")

type ResultSender interface {
SendGauge(name string, value float64) error
SendCounter(name string, value int64) error
}
71 changes: 71 additions & 0 deletions internal/agent/http_result_sender.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package agent

import (
"fmt"
"io"
"net/http"
"strings"
"sync"
)

func NewHTTPResultSender(serverAdd string, contentType string) ResultSender {
return &httpResultSender{
serverAdd: serverAdd,
contentType: contentType,
urlPattern: "/update/%v/%v/%v",
}
}

type httpResultSender struct {
serverAdd string
contentType string
urlPattern string
client *http.Client
sm sync.Mutex
}

func (h *httpResultSender) initIfNecessary() error {
if h.client == nil {
h.sm.Lock()
defer h.sm.Unlock()
if h.client == nil {
h.client = &http.Client{}
h.serverAdd = strings.TrimSuffix(h.serverAdd, "/")
}
}
return nil
}

func (h *httpResultSender) store(url string) error {
if err := h.initIfNecessary(); err != nil {
return err
}
fullURL := h.serverAdd + url
res, err := h.client.Post(fullURL, h.contentType, nil)
if err != nil {
fmt.Printf("server interation error: %v\n", err.Error()) // log error
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
resBody, err := io.ReadAll(res.Body)
// log error
if err == nil {
fmt.Printf("server error: \n status: %v\n content: %v\n", res.StatusCode, string(resBody))
} else {
fmt.Printf("server error: \n status: %v\n content read error: %v\n", res.StatusCode, err.Error())
}
return ErrServerInteraction
}
return nil
}

func (h *httpResultSender) SendGauge(name string, value float64) error {
url := fmt.Sprintf(h.urlPattern, "gauge", name, value)
return h.store(url)
}

func (h *httpResultSender) SendCounter(name string, value int64) error {
url := fmt.Sprintf(h.urlPattern, "counter", name, value)
return h.store(url)
}
54 changes: 54 additions & 0 deletions internal/agent/runtime_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package agent

import (
"math/rand"
"runtime"
"sync/atomic"
)

func NewRuntimeMetricsSource() MetricsSource {
return &runtimeMetrics{}
}

type runtimeMetrics struct {
counter int64
}

func (rm *runtimeMetrics) PollCount() int64 {
return rm.counter
}

func (rm *runtimeMetrics) PollMetrics() map[string]float64 {
defer atomic.AddInt64(&rm.counter, 1)
var memStats runtime.MemStats
return map[string]float64{
"Alloc": float64(memStats.Alloc),
"BuckHashSys": float64(memStats.BuckHashSys),
"Frees": float64(memStats.Frees),
"GCCPUFraction": memStats.GCCPUFraction,
"GCSys": float64(memStats.GCSys),
"HeapAlloc": float64(memStats.HeapAlloc),
"HeapIdle": float64(memStats.HeapIdle),
"HeapInuse": float64(memStats.HeapInuse),
"HeapObjects": float64(memStats.HeapObjects),
"HeapReleased": float64(memStats.HeapReleased),
"HeapSys": float64(memStats.HeapSys),
"LastGC": float64(memStats.LastGC),
"Lookups": float64(memStats.Lookups),
"MCacheInuse": float64(memStats.MCacheInuse),
"MCacheSys": float64(memStats.MCacheSys),
"MSpanInuse": float64(memStats.MSpanInuse),
"MSpanSys": float64(memStats.MSpanSys),
"Mallocs": float64(memStats.Mallocs),
"NextGC": float64(memStats.NextGC),
"NumForcedGC": float64(memStats.NumForcedGC),
"NumGC": float64(memStats.NumGC),
"OtherSys": float64(memStats.OtherSys),
"PauseTotalNs": float64(memStats.PauseTotalNs),
"StackInuse": float64(memStats.StackInuse),
"StackSys": float64(memStats.StackSys),
"Sys": float64(memStats.Sys),
"TotalAlloc": float64(memStats.TotalAlloc),
"RandomValue": rand.Float64(),
}
}
52 changes: 52 additions & 0 deletions internal/agent/runtime_metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package agent

import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func TestRuntimeMetrics(t *testing.T) {
rm := &runtimeMetrics{}
expectedKeys := []string{
"Alloc",
"BuckHashSys",
"Frees",
"GCCPUFraction",
"GCSys",
"HeapAlloc",
"HeapIdle",
"HeapInuse",
"HeapObjects",
"HeapReleased",
"HeapSys",
"LastGC",
"Lookups",
"MCacheInuse",
"MCacheSys",
"MSpanInuse",
"MSpanSys",
"Mallocs",
"NextGC",
"NumForcedGC",
"NumGC",
"OtherSys",
"PauseTotalNs",
"StackInuse",
"StackSys",
"Sys",
"TotalAlloc",
"RandomValue",
}
result := rm.PollMetrics()
assert.Equal(t, len(expectedKeys), len(result))
for _, expectedKey := range expectedKeys {
_, ok := result[expectedKey]
require.Truef(t, ok, "pollMetrics doesn't contain key %v", expectedKey)
}

rm.PollMetrics()
rm.PollMetrics()
rm.PollMetrics()
assert.Equal(t, int64(4), rm.PollCount())
}
Loading

0 comments on commit 20345c1

Please sign in to comment.