203 lines
6.9 KiB
Go
203 lines
6.9 KiB
Go
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net/http"
|
||
"os"
|
||
"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"`
|
||
Metrics map[string]float64 `json:"metrics"`
|
||
}
|
||
|
||
// Экспортёр метрик
|
||
type MetricsExporter struct {
|
||
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),
|
||
}
|
||
}
|
||
|
||
// Обновление или создание метрики
|
||
func (me *MetricsExporter) UpdateMetric(request MetricRequest) {
|
||
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}
|
||
|
||
// Обработка метрик в зависимости от типа
|
||
switch request.Type {
|
||
case "gauge":
|
||
me.updateGauge(fullMetricName, labels, labelValues, value)
|
||
case "counter":
|
||
me.updateCounter(fullMetricName, labels, labelValues, value)
|
||
case "histogram":
|
||
me.updateHistogram(fullMetricName, labels, labelValues, value)
|
||
case "summary":
|
||
me.updateSummary(fullMetricName, labels, labelValues, value)
|
||
default:
|
||
log.Printf("Неподдерживаемый тип метрики: %s\n", request.Type)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Обновление `Gauge` метрик
|
||
func (me *MetricsExporter) updateGauge(name string, labels []string, labelValues []string, value float64) {
|
||
if _, exists := me.gaugeMetrics[name]; !exists {
|
||
gaugeVec := prometheus.NewGaugeVec(
|
||
prometheus.GaugeOpts{
|
||
Name: name,
|
||
Help: fmt.Sprintf("Gauge metric %s", name),
|
||
},
|
||
labels,
|
||
)
|
||
customRegistry.MustRegister(gaugeVec)
|
||
me.gaugeMetrics[name] = gaugeVec
|
||
}
|
||
me.gaugeMetrics[name].WithLabelValues(labelValues...).Set(value)
|
||
log.Printf("Gauge обновлён: %s = %f\n", name, value)
|
||
}
|
||
|
||
// Обновление `Counter` метрик
|
||
func (me *MetricsExporter) updateCounter(name string, labels []string, labelValues []string, value float64) {
|
||
if value < 0 {
|
||
log.Printf("Ошибка: Counter %s не может быть отрицательным\n", name)
|
||
return
|
||
}
|
||
if _, exists := me.counterMetrics[name]; !exists {
|
||
counterVec := prometheus.NewCounterVec(
|
||
prometheus.CounterOpts{
|
||
Name: name,
|
||
Help: fmt.Sprintf("Counter metric %s", name),
|
||
},
|
||
labels,
|
||
)
|
||
customRegistry.MustRegister(counterVec)
|
||
me.counterMetrics[name] = counterVec
|
||
}
|
||
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 {
|
||
histogramVec := prometheus.NewHistogramVec(
|
||
prometheus.HistogramOpts{
|
||
Name: name,
|
||
Help: fmt.Sprintf("Histogram metric %s", name),
|
||
Buckets: prometheus.LinearBuckets(10, 10, 10), // Пример диапазонов
|
||
},
|
||
labels,
|
||
)
|
||
customRegistry.MustRegister(histogramVec)
|
||
me.histogramMetrics[name] = histogramVec
|
||
}
|
||
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 {
|
||
summaryVec := prometheus.NewSummaryVec(
|
||
prometheus.SummaryOpts{
|
||
Name: name,
|
||
Help: fmt.Sprintf("Summary metric %s", name),
|
||
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, // Пример целей
|
||
},
|
||
labels,
|
||
)
|
||
customRegistry.MustRegister(summaryVec)
|
||
me.summaryMetrics[name] = summaryVec
|
||
}
|
||
me.summaryMetrics[name].WithLabelValues(labelValues...).Observe(value)
|
||
log.Printf("Summary обновлён: %s = %f\n", name, value)
|
||
}
|
||
|
||
// Обработчик для приёма JSON
|
||
func (me *MetricsExporter) JSONHandler(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodPost {
|
||
log.Printf("Неверный метод: %s (ожидался POST)", r.Method)
|
||
http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
// Читаем тело запроса
|
||
body, err := io.ReadAll(r.Body)
|
||
if err != nil {
|
||
log.Printf("Ошибка чтения тела запроса: %s\n", err)
|
||
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer r.Body.Close()
|
||
|
||
// Парсим JSON
|
||
var request MetricRequest
|
||
if err := json.Unmarshal(body, &request); err != nil {
|
||
log.Printf("Ошибка парсинга JSON: %s\nТело запроса: %s\n", err, string(body))
|
||
http.Error(w, "Invalid JSON format", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// Обновляем метрики
|
||
log.Printf("Обновление метрик для запроса ID: %s\n", request.ID)
|
||
me.UpdateMetric(request)
|
||
|
||
w.WriteHeader(http.StatusOK)
|
||
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: сделать переменные окружения, настроить канал, дописать тесты
|