test commit

main
SavelyG 2025-02-03 15:39:59 +00:00
parent 7663f27ae2
commit 909c1151d4
9 changed files with 218 additions and 2 deletions

10
.air.toml Normal file
View File

@ -0,0 +1,10 @@
root = "." # Корень проекта
tmp_dir = "tmp"
[build]
binary = "app" # Имя исполняемого файла
cmd = "go build -o app main.go"
include_ext = ["go", "tpl", "tmpl", "html"]
[run]
cmd = "./app"

2
.gitignore vendored Executable file
View File

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

View File

@ -1,2 +1 @@
# Exporter
# Exporter

15
docker-compose.yml Normal file
View File

@ -0,0 +1,15 @@
services:
exporter:
image: cosmtrek/air:v1.61.7
volumes:
- .:/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"

17
go.mod Normal file
View File

@ -0,0 +1,17 @@
module prometheus_exporter
go 1.23.5
require github.com/prometheus/client_golang v1.20.5
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
golang.org/x/sys v0.22.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)

24
go.sum Normal file
View File

@ -0,0 +1,24 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=

131
main.go Normal file
View File

@ -0,0 +1,131 @@
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Создаём кастомный реестр
var customRegistry = prometheus.NewRegistry()
// Структура JSON
type MetricRequest struct {
ID string `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Method string `json:"method"`
Type string `json:"type"` // "gauge", "counter" и т.д.
Metrics map[string]any `json:"metrics"`
}
// Экспортёр метрик
type MetricsExporter struct {
gaugeMetrics map[string]*prometheus.GaugeVec
counterMetrics map[string]*prometheus.CounterVec
mu sync.Mutex // Защита от одновременного доступа
}
// Создаём новый экспортёр
func NewMetricsExporter() *MetricsExporter {
return &MetricsExporter{
gaugeMetrics: make(map[string]*prometheus.GaugeVec),
counterMetrics: make(map[string]*prometheus.CounterVec),
}
}
// Обновление или создание метрики
func (me *MetricsExporter) UpdateMetric(name string, value interface{}) {
me.mu.Lock()
defer me.mu.Unlock()
for metricName, value := range request.Metrics {
// Уникальное имя метрики
fullMetricName := fmt.Sprintf("vks_%s_%s", request.ID, metricName)
// Лейблы
labels := []string{"name", "url", "method"}
labelValues := []string{request.Name, request.URL, request.Method}
if request.Type == "gauge" {
// Регистрируем Gauge, если он ещё не существует
if _, exists := me.gaugeMetrics[fullMetricName]; !exists {
gaugeVec := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: fullMetricName,
Help: fmt.Sprintf("Metric %s from %s", metricName, request.ID),
},
labels,
)
customRegistry.MustRegister(gaugeVec)
me.gaugeMetrics[fullMetricName] = gaugeVec
}
// Обновляем значение Gauge
me.gaugeMetrics[fullMetricName].WithLabelValues(labelValues...).Set(floatVal)
} else if request.Type == "counter" {
// Регистрируем Counter, если он ещё не существует
if _, exists := me.counterMetrics[fullMetricName]; !exists {
counterVec := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: fullMetricName,
Help: fmt.Sprintf("Metric %s from %s", metricName, request.ID),
},
labels,
)
customRegistry.MustRegister(counterVec)
me.counterMetrics[fullMetricName] = counterVec
}
// Инкрементируем Counter
me.counterMetrics[fullMetricName].WithLabelValues(labelValues...).Add(floatVal)
}
}
}
// Обработчик для приёма JSON
func (me *MetricsExporter) JSONHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
return
}
// Читаем тело запроса
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
return
}
defer r.Body.Close()
// Парсим JSON
var data MetricRequest
if err := json.Unmarshal(body, &data); err != nil {
http.Error(w, "Invalid JSON format", http.StatusBadRequest)
return
}
// Обновляем метрики
me.UpdateMetric(name, value)
w.WriteHeader(http.StatusOK)
w.Write([]byte("Metrics updated"))
}
func main() {
exporter := NewMetricsExporter()
// Используем кастомный реестр в обработчике /metrics
http.Handle("/metrics", promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{}))
http.HandleFunc("/update", exporter.JSONHandler) // Обработчик для приёма JSON
port := ":9101"
fmt.Println("Starting server on port", port)
if err := http.ListenAndServe(port, nil); err != nil {
fmt.Println("Error starting server:", err)
}
}

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"] # Сервис экспортёра

11
test_data.txt Normal file
View File

@ -0,0 +1,11 @@
curl -X POST -H "Content-Type: application/json" -d '{
"id": "mock_api_2",
"name": "Mock /ping",
"url": "http://127.0.0.1:8081/ping",
"method": "GET",
"type": "gauge",
"metrics": {
"response_time": 120.5,
"status_code": 200,
}
}' http://localhost:9101/update