Compare commits

..

No commits in common. "rc" and "main" have entirely different histories.
rc ... main

10 changed files with 103 additions and 97 deletions

7
.air.toml Normal file
View File

@ -0,0 +1,7 @@
root = "."
tmp_dir = "tmp"
[build]
cmd = "go build -o ./tmp/app ."
bin = "./tmp/app"
include_ext = ["go"]

View File

@ -1,7 +0,0 @@
.devcontainer/
tests/
*.md
Dockerfile
.dockerignore
.gitignore
.git

3
.gitignore vendored
View File

@ -1 +1,2 @@
.devcontainer/
.devcontainer/
tmp/

View File

@ -1,21 +0,0 @@
FROM golang:1.23.5 as builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /app/main ./cmd
FROM golang:1.23.5-alpine
WORKDIR /app
COPY --from=builder /app/main .
EXPOSE 9101
CMD ["./main"]

View File

@ -1,29 +0,0 @@
package main
import (
"exporter/internal/app"
"log"
"net/http"
"os"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
log.SetOutput(os.Stdout) // Логируем в стандартный вывод
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
exporter := app.NewMetricsExporter()
// Используем кастомный реестр в обработчике /metrics
http.Handle("/metrics", promhttp.HandlerFor(app.CustomRegistry, promhttp.HandlerOpts{}))
http.HandleFunc("/update", exporter.JSONHandler) // Обработчик для приёма JSON
port := ":9101"
log.Printf("Starting server on port %s\n", port)
if err := http.ListenAndServe(port, nil); err != nil {
log.Fatalf("Error starting server: %s\n", err)
}
}
//TODO: сделать переменные окружения, настроить канал, дописать юнит тесты, добавить интеграционные тесты

16
docker-compose.yml Normal file
View File

@ -0,0 +1,16 @@
services:
exporter:
image: cosmtrek/air:v1.61.7
volumes:
- .:/app
working_dir: /app
ports:
- "9101:9101"
command: ["air", "-c", ".air.toml"]
prometheus:
image: prom/prometheus:v3.1.0
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"

View File

@ -1,9 +1,8 @@
package test
package main
import (
"bytes"
"encoding/json"
"exporter/internal/app"
"io"
"net/http"
"net/http/httptest"
@ -16,12 +15,12 @@ import (
// Тест: успешное обновление метрик через /update
func TestJSONHandler_ValidRequest(t *testing.T) {
exporter := app.NewMetricsExporter()
exporter := NewMetricsExporter()
server := httptest.NewServer(http.HandlerFunc(exporter.JSONHandler))
defer server.Close()
// JSON-запрос
requestData := app.MetricRequest{
requestData := MetricRequest{
ID: "test_api",
Name: "Test API",
URL: "http://localhost/api",
@ -49,7 +48,7 @@ func TestJSONHandler_ValidRequest(t *testing.T) {
// Тест: успешная обработка некорректного JSON
func TestJSONHandler_InvalidJSON(t *testing.T) {
exporter := app.NewMetricsExporter()
exporter := NewMetricsExporter()
server := httptest.NewServer(http.HandlerFunc(exporter.JSONHandler))
defer server.Close()
@ -61,9 +60,9 @@ func TestJSONHandler_InvalidJSON(t *testing.T) {
// Тест: проверка создания и обновления Gauge метрики
func TestUpdateMetric_Gauge(t *testing.T) {
exporter := app.NewMetricsExporter()
exporter := NewMetricsExporter()
request := app.MetricRequest{
request := MetricRequest{
ID: "test",
Name: "Test Gauge",
URL: "http://test.com",
@ -78,16 +77,16 @@ func TestUpdateMetric_Gauge(t *testing.T) {
exporter.UpdateMetric(request)
// Проверяем, что метрика существует
metric, exists := exporter.GaugeMetrics["vks_test_load"]
metric, exists := exporter.gaugeMetrics["vks_test_load"]
assert.True(t, exists, "Метрика должна быть зарегистрирована")
assert.NotNil(t, metric, "Метрика не должна быть nil")
}
// Тест: проверка создания и увеличения Counter метрики
func TestUpdateMetric_Counter(t *testing.T) {
exporter := app.NewMetricsExporter()
exporter := NewMetricsExporter()
request := app.MetricRequest{
request := MetricRequest{
ID: "test",
Name: "Test Counter",
URL: "http://test.com",
@ -103,7 +102,7 @@ func TestUpdateMetric_Counter(t *testing.T) {
exporter.UpdateMetric(request)
// Проверяем, что метрика существует
metric, exists := exporter.CounterMetrics["vks_test_requests"]
metric, exists := exporter.counterMetrics["vks_test_requests"]
assert.True(t, exists, "Метрика должна быть зарегистрирована")
assert.NotNil(t, metric, "Метрика не должна быть nil")
}
@ -115,10 +114,10 @@ func TestMetricsEndpoint(t *testing.T) {
Name: "test_metric",
Help: "Test metric for /metrics endpoint",
})
app.CustomRegistry.MustRegister(gauge)
customRegistry.MustRegister(gauge)
gauge.Set(50.5)
server := httptest.NewServer(promhttp.HandlerFor(app.CustomRegistry, promhttp.HandlerOpts{}))
server := httptest.NewServer(promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{}))
defer server.Close()
// Делаем GET-запрос к /metrics

View File

@ -1,4 +1,4 @@
package app
package main
import (
"encoding/json"
@ -6,13 +6,15 @@ import (
"io"
"log"
"net/http"
"os"
"sync"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Создаём кастомный реестр
var CustomRegistry = prometheus.NewRegistry()
var customRegistry = prometheus.NewRegistry()
// Структура JSON
type MetricRequest struct {
@ -26,20 +28,20 @@ type MetricRequest struct {
// Экспортёр метрик
type MetricsExporter struct {
GaugeMetrics map[string]*prometheus.GaugeVec
CounterMetrics map[string]*prometheus.CounterVec
HistogramMetrics map[string]*prometheus.HistogramVec
SummaryMetrics map[string]*prometheus.SummaryVec
gaugeMetrics map[string]*prometheus.GaugeVec
counterMetrics map[string]*prometheus.CounterVec
histogramMetrics map[string]*prometheus.HistogramVec
summaryMetrics map[string]*prometheus.SummaryVec
mu sync.Mutex // Защита от одновременного доступа
}
// Создаём новый экспортёр
func NewMetricsExporter() *MetricsExporter {
return &MetricsExporter{
GaugeMetrics: make(map[string]*prometheus.GaugeVec),
CounterMetrics: make(map[string]*prometheus.CounterVec),
HistogramMetrics: make(map[string]*prometheus.HistogramVec),
SummaryMetrics: make(map[string]*prometheus.SummaryVec),
gaugeMetrics: make(map[string]*prometheus.GaugeVec),
counterMetrics: make(map[string]*prometheus.CounterVec),
histogramMetrics: make(map[string]*prometheus.HistogramVec),
summaryMetrics: make(map[string]*prometheus.SummaryVec),
}
}
@ -74,7 +76,7 @@ func (me *MetricsExporter) UpdateMetric(request MetricRequest) {
// Обновление `Gauge` метрик
func (me *MetricsExporter) updateGauge(name string, labels []string, labelValues []string, value float64) {
if _, exists := me.GaugeMetrics[name]; !exists {
if _, exists := me.gaugeMetrics[name]; !exists {
gaugeVec := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: name,
@ -82,10 +84,10 @@ func (me *MetricsExporter) updateGauge(name string, labels []string, labelValues
},
labels,
)
CustomRegistry.MustRegister(gaugeVec)
me.GaugeMetrics[name] = gaugeVec
customRegistry.MustRegister(gaugeVec)
me.gaugeMetrics[name] = gaugeVec
}
me.GaugeMetrics[name].WithLabelValues(labelValues...).Set(value)
me.gaugeMetrics[name].WithLabelValues(labelValues...).Set(value)
log.Printf("Gauge обновлён: %s = %f\n", name, value)
}
@ -95,7 +97,7 @@ func (me *MetricsExporter) updateCounter(name string, labels []string, labelValu
log.Printf("Ошибка: Counter %s не может быть отрицательным\n", name)
return
}
if _, exists := me.CounterMetrics[name]; !exists {
if _, exists := me.counterMetrics[name]; !exists {
counterVec := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: name,
@ -103,16 +105,16 @@ func (me *MetricsExporter) updateCounter(name string, labels []string, labelValu
},
labels,
)
CustomRegistry.MustRegister(counterVec)
me.CounterMetrics[name] = counterVec
customRegistry.MustRegister(counterVec)
me.counterMetrics[name] = counterVec
}
me.CounterMetrics[name].WithLabelValues(labelValues...).Add(value)
me.counterMetrics[name].WithLabelValues(labelValues...).Add(value)
log.Printf("Counter обновлён: %s += %f\n", name, value)
}
// Обновление `Histogram` метрик
func (me *MetricsExporter) updateHistogram(name string, labels []string, labelValues []string, value float64) {
if _, exists := me.HistogramMetrics[name]; !exists {
if _, exists := me.histogramMetrics[name]; !exists {
histogramVec := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: name,
@ -121,16 +123,16 @@ func (me *MetricsExporter) updateHistogram(name string, labels []string, labelVa
},
labels,
)
CustomRegistry.MustRegister(histogramVec)
me.HistogramMetrics[name] = histogramVec
customRegistry.MustRegister(histogramVec)
me.histogramMetrics[name] = histogramVec
}
me.HistogramMetrics[name].WithLabelValues(labelValues...).Observe(value)
me.histogramMetrics[name].WithLabelValues(labelValues...).Observe(value)
log.Printf("Histogram обновлён: %s = %f\n", name, value)
}
// Обновление `Summary` метрик
func (me *MetricsExporter) updateSummary(name string, labels []string, labelValues []string, value float64) {
if _, exists := me.SummaryMetrics[name]; !exists {
if _, exists := me.summaryMetrics[name]; !exists {
summaryVec := prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Name: name,
@ -139,10 +141,10 @@ func (me *MetricsExporter) updateSummary(name string, labels []string, labelValu
},
labels,
)
CustomRegistry.MustRegister(summaryVec)
me.SummaryMetrics[name] = summaryVec
customRegistry.MustRegister(summaryVec)
me.summaryMetrics[name] = summaryVec
}
me.SummaryMetrics[name].WithLabelValues(labelValues...).Observe(value)
me.summaryMetrics[name].WithLabelValues(labelValues...).Observe(value)
log.Printf("Summary обновлён: %s = %f\n", name, value)
}
@ -179,3 +181,22 @@ func (me *MetricsExporter) JSONHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Metrics updated"))
log.Printf("Метрики обновлены успешно для ID: %s\n", request.ID)
}
func main() {
log.SetOutput(os.Stdout) // Логируем в стандартный вывод
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
exporter := NewMetricsExporter()
// Используем кастомный реестр в обработчике /metrics
http.Handle("/metrics", promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{}))
http.HandleFunc("/update", exporter.JSONHandler) // Обработчик для приёма JSON
port := ":9101"
log.Printf("Starting server on port %s\n", port)
if err := http.ListenAndServe(port, nil); err != nil {
log.Fatalf("Error starting server: %s\n", err)
}
}
//TODO: сделать переменные окружения, настроить канал, дописать тесты

7
prometheus.yml Normal file
View File

@ -0,0 +1,7 @@
global:
scrape_interval: 1s # Интервал сбора метрик
scrape_configs:
- job_name: "exporter"
static_configs:
- targets: ["exporter:9101"] # Сервис экспортёра

12
test_api.txt Normal file
View File

@ -0,0 +1,12 @@
curl -X POST -H "Content-Type: application/json" -d '{
"id": "gauge_test",
"name": "Gauge Metric",
"url": "http://127.0.0.1:8081/gauge",
"method": "GET",
"type": "gauge",
"metrics": {
"temperature": 36.6
}
}' "http://localhost:9101/update"
curl http://localhost:9101/metrics