exporter/main.go

203 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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: сделать переменные окружения, настроить канал, сделать тесты