diff --git a/CHANGELOG.md b/CHANGELOG.md index 3143d79..2f71b98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [v1.1.0] - 2026-02-17 + +### Added +- Both English and Russian monitoring point names in metrics (as `mp_name` and `mp_name_ru` labels) and tasks JSON logs (as `MpName` and `MPNameRu` fields) + +### Removed +- All translation logic (`translator` module, `locations.json` file, and `ENG_MP_NAMES` ENV variable) + ## [v1.0.0] - 2025-12-03 ### Added diff --git a/README.md b/README.md index 330f67e..8739e4f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ style="text-align: justify"> - 🔄 **Automatic Metrics Collection**: Periodically fetches metrics from Ping-Admin API for multiple tasks - 📊 **Prometheus Integration**: Exposes metrics in standard Prometheus format at `/metrics` - 📈 **JSON Stats API**: Provides additional JSON endpoints for task statistics -- 🌍 **Location Translation**: Supports translation of location names via `locations.json` - 🚀 **Concurrent Processing**: Efficiently processes multiple tasks in parallel - 🔁 **Automatic Cleanup**: Removes stale metrics when monitoring points are no longer available - 🐳 **Docker Support**: Ready-to-use Docker image @@ -79,7 +78,6 @@ The exporter can be configured via command-line flags or environment variables. | `--listen-address` | `LISTEN_ADDRESS` | HTTP server listen address | `:8080` | | `--log-level` | `LOG_LEVEL` | Log level (debug, info, warn, error) | `info` | | `--locations-file` | `LOCATIONS_FILE` | Path to locations.json file | `locations.json` | -| `--eng-mp-names` | `ENG_MP_NAMES` | Translate MP names to English | `true` | | `--refresh-interval` | `REFRESH_INTERVAL` | Metrics refresh interval | `3m` | | `--api-update-delay` | `API_UPDATE_DELAY` | Ping-Admin API data update delay | `4m` | | `--api-data-time-step` | `API_DATA_TIME_STEP` | Time between API data points | `3m` | @@ -147,7 +145,7 @@ The exporter exposes the following Prometheus metrics: ### Monitoring Point Metrics -All MP metrics include labels: `task_id`, `task_name`, `mp_id`, `mp_name`, `mp_ip`, `mp_gps` +All MP metrics include labels: `task_id`, `task_name`, `mp_id`, `mp_name`, `mp_name_ru`,`mp_ip`, `mp_gps` - `apatit_mp_status` - Status of monitoring point (1 = up, 0 = down/stale) - `apatit_mp_data_status` - Status of the data for the monitoring point (1 = has data, 0 = no data) @@ -175,13 +173,11 @@ apatit/ │ ├── log/ # Logging setup │ ├── scheduler/ # Metrics and stats schedulers │ ├── server/ # HTTP server -│ ├── translator/ # Location name translation │ ├── utils/ # Utility functions │ └── version/ # Version information ├── deploy/ │ └── docker-compose.yaml # Docker Compose configuration ├── Dockerfile # Container image definition -├── locations.json # Location translation mappings └── go.mod # Go module definition ``` diff --git a/cmd/apatit/main.go b/cmd/apatit/main.go index 0b5ddfd..4fc93f5 100644 --- a/cmd/apatit/main.go +++ b/cmd/apatit/main.go @@ -15,7 +15,6 @@ import ( "apatit/internal/log" "apatit/internal/scheduler" "apatit/internal/server" - "apatit/internal/translator" ) // createExporters creates and returns a list of exporters for the specified tasks. @@ -42,7 +41,6 @@ func createExporters(apiClient *client.Client, cfg *config.Config) ([]*exporter. expConfig := &exporter.Config{ TaskID: taskID, - EngMPNames: cfg.EngMPNames, ApiUpdateDelay: cfg.ApiUpdateDelay, ApiDataTimeStep: cfg.ApiDataTimeStep, } @@ -92,11 +90,6 @@ func newApp(cfg *config.Config) (*application, error) { // Set logger for components log.Init(cfg.LogLevel) - // Set translator - if err := translator.Init(cfg.LocationsFilePath); err != nil { - logrus.Warnf("Failed to initialize translator, location names will not be translated: %v", err) - } - // Create API client apiClient := client.New(cfg.APIKey, nil, cfg.RequestDelay, cfg.RequestRetries, cfg.MaxRequestsPerSecond) diff --git a/deploy/grafana/monitoring-dashboard.json b/deploy/grafana/monitoring-dashboard.json index 14fedf7..3343c32 100644 --- a/deploy/grafana/monitoring-dashboard.json +++ b/deploy/grafana/monitoring-dashboard.json @@ -2170,7 +2170,7 @@ "showLegend": false }, "mergeValues": true, - "perPage": 20, + "perPage": 10, "rowHeight": 0.9, "showValue": "never", "tooltip": { diff --git a/internal/client/models.go b/internal/client/models.go index 0c33d38..cbb34a3 100644 --- a/internal/client/models.go +++ b/internal/client/models.go @@ -14,9 +14,10 @@ import ( // It contains TM (tochka monitoringa) info with TmID; TmName and TmRes (results with time, speed). // P.S. TM will be used as MP (Monitoring Point) after processing. type EntryRaw struct { - TmID string `json:"tm_id"` - TmName string `json:"tm_name"` - TmRes []*TmResRaw `json:"tm_res"` + TmID string `json:"tm_id"` + TmName string `json:"tm_name"` + TmNameEn string `json:"tm_name_en"` + TmRes []*TmResRaw `json:"tm_res"` } // TmResRaw @@ -90,6 +91,8 @@ type MonitoringPointRaw struct { ID string `json:"id"` // Monitoring Point Name Name string `json:"name"` + // Monitoring Point NameEn + NameEn string `json:"name_en"` // Monitoring Point IP IP string `json:"ip"` // Monitoring Point GPS @@ -116,6 +119,7 @@ type TasksLogsRaw struct { Descr *string `json:"descr"` Status *int `json:"status"` Tm *string `json:"tm"` + TmEn *string `json:"tm_en"` TmID *string `json:"tm_id"` Traceroute *string `json:"traceroute"` } @@ -140,6 +144,7 @@ type TaskInfo struct { type MonitoringPointInfo struct { ID string Name string + NameEn string IP string GPS string Status int64 @@ -150,6 +155,7 @@ type MonitoringPointInfo struct { type MonitoringPointEntry struct { ID string Name string + NameEn string Status int Result []*MonitoringPointConnectionResult } @@ -180,6 +186,7 @@ type TaskLog struct { Data string Description string Status int64 + MPNameRu string MPName string MPID string Traceroute string @@ -191,6 +198,7 @@ func (mp *MonitoringPointRaw) ProcessMonitoringPointInfo() *MonitoringPointInfo return &MonitoringPointInfo{ ID: mp.ID, Name: mp.Name, + NameEn: mp.NameEn, IP: mp.IP, GPS: mp.GPS, Status: parseInt(&mp.Status, "status"), @@ -218,6 +226,7 @@ func (e *EntryRaw) ProcessMonitoringPointEntry() *MonitoringPointEntry { entry := &MonitoringPointEntry{ ID: e.TmID, Name: e.TmName, + NameEn: e.TmNameEn, Result: make([]*MonitoringPointConnectionResult, 0, len(e.TmRes)), } @@ -247,7 +256,8 @@ func (t *TaskStatRaw) ProcessTaskEntry() *TaskStatEntry { Data: *resRaw.Data, Description: *resRaw.Descr, Status: int64(*resRaw.Status), - MPName: *resRaw.Tm, + MPName: *resRaw.TmEn, + MPNameRu: *resRaw.Tm, MPID: *resRaw.TmID, Traceroute: *resRaw.Traceroute, } @@ -283,79 +293,3 @@ func parseInt(s *string, fieldName string) int64 { } return i } - -//// Transpose -//// TransposedTaskLogs -//// is just a transposed TaskLog structure. -//type TransposedTaskLogs struct { -// Data []string `json:"Data"` -// Description []string `json:"Description"` -// Status []int64 `json:"Status"` -// MPName []string `json:"MPName"` -// MPID []string `json:"MPID"` -// Traceroute []string `json:"Traceroute"` -//} -// -//// TransposedTaskStatEntry -//// is a transposed 'TaskStatEntry'. -//type TransposedTaskStatEntry struct { -// TaskID string `json:"TaskID"` -// TaskName string `json:"TaskName"` -// TaskLogs TransposedTaskLogs `json:"TaskLogs"` -//} -// -//// transposes TaskLogs. -//func (entry *TaskStatEntry) Transpose() *TransposedTaskStatEntry { -// logCount := len(entry.TaskLogs) -// -// data := make([]string, 0, logCount) -// description := make([]string, 0, logCount) -// status := make([]int64, 0, logCount) -// mpName := make([]string, 0, logCount) -// mpID := make([]string, 0, logCount) -// traceroute := make([]string, 0, logCount) -// -// for _, log := range entry.TaskLogs { -// data = append(data, log.Data) -// description = append(description, log.Description) -// status = append(status, log.Status) -// mpName = append(mpName, log.MPName) -// mpID = append(mpID, log.MPID) -// traceroute = append(traceroute, log.Traceroute) -// } -// -// transposedEntry := &TransposedTaskStatEntry{ -// TaskID: entry.TaskID, -// TaskName: entry.TaskName, -// TaskLogs: TransposedTaskLogs{ -// Data: data, -// Description: description, -// Status: status, -// MPName: mpName, -// MPID: mpID, -// Traceroute: traceroute, -// }, -// } -// -// return transposedEntry -//} -// -//// --- Formatting Helpers --- -// -//// FormatMonitoringPointSliceToMap gets MonitoringPoint slice and returns map[ID]MonitoringPoint -//func FormatMonitoringPointSliceToMap(items []*MonitoringPointRaw) map[string]*MonitoringPointRaw { -// result := make(map[string]*MonitoringPointRaw, len(items)) -// for _, value := range items { -// result[value.ID] = value -// } -// return result -//} -// -//// FormatTaskSliceToMap gets TaskRaw slice and returns map[TaskID]Task -//func FormatTaskSliceToMap(items []*TaskRaw) map[string]*TaskRaw { -// result := make(map[string]*TaskRaw, len(items)) -// for _, value := range items { -// result[strconv.Itoa(value.ID)] = value -// } -// return result -//} diff --git a/internal/config/config.go b/internal/config/config.go index ee42af3..ee1861e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -13,7 +13,6 @@ import ( type Config struct { APIKey string TaskIDs []int - EngMPNames bool ApiUpdateDelay time.Duration ApiDataTimeStep time.Duration RefreshInterval time.Duration @@ -32,7 +31,6 @@ func New() (*Config, error) { flag.StringVar(&cfg.APIKey, "api-key", envString("API_KEY", ""), "API key for Ping-Admin") taskIDsStr := flag.String("task-ids", envString("TASK_IDS", ""), "Comma-separated list of task IDs") - flag.BoolVar(&cfg.EngMPNames, "eng-mp-names", envBool("ENG_MP_NAMES", true), "Translate monitoring points (MP) names to English") flag.DurationVar(&cfg.ApiUpdateDelay, "api-update-delay", envDuration("API_UPDATE_DELAY", 4*time.Minute), "Fixed Ping-Admin API delay for new data update") flag.DurationVar(&cfg.ApiDataTimeStep, "api-data-time-step", envDuration("API_DATA_TIME_STEP", 3*time.Minute), "Fixed Ping-Admin API time between data points") flag.DurationVar(&cfg.RefreshInterval, "refresh-interval", envDuration("REFRESH_INTERVAL", 3*time.Minute), "Exporter's refresh interval") @@ -102,16 +100,6 @@ func envDuration(key string, def time.Duration) time.Duration { return def } -// envBool bool env variables helper. -func envBool(key string, def bool) bool { - if v := os.Getenv(key); v != "" { - if b, err := strconv.ParseBool(v); err == nil { - return b - } - } - return def -} - // envInt int env variables helper. func envInt(env string, def int) int { if v, ok := os.LookupEnv(env); ok { diff --git a/internal/exporter/exporter.go b/internal/exporter/exporter.go index a6ca2b9..8b3ab54 100644 --- a/internal/exporter/exporter.go +++ b/internal/exporter/exporter.go @@ -11,7 +11,6 @@ import ( "github.com/sirupsen/logrus" "apatit/internal/client" - "apatit/internal/translator" ) // Exporter collects metrics for a single task. @@ -159,9 +158,9 @@ func (e *Exporter) RefreshMetrics() ([]prometheus.Labels, error) { if item.Status > 1 || item.Status < 0 { e.log.WithFields( logrus.Fields{ - "mp_id": item.ID, + "mp_id": item.ID, "mp_name": item.Name, - "status": item.Status, + "status": item.Status, }).Errorf("incorrect monitoring points status: %d", item.Status) item.Status = 0 } @@ -183,28 +182,24 @@ func (e *Exporter) processTaskStatResults(taskStatResults *client.TaskStatEntry) for _, entry := range taskStatResults.TaskLogs { entry.Traceroute = strings.ReplaceAll(entry.Traceroute, "\\n", "\n") - entry.MPName = translator.GetEngLocation(entry.MPName) } } // processTaskStatGraphResultItem processes one record (monitoring point) and updates metrics. func (e *Exporter) processTaskStatGraphResultItem(item *client.MonitoringPointEntry, refreshStartTime time.Time) []prometheus.Labels { if len(item.Result) == 0 { - locationName := item.Name - if e.Config.EngMPNames { - locationName = translator.GetEngLocation(item.Name) - } MPDataStatus.WithLabelValues( - strconv.Itoa(e.taskInfo.ID), + strconv.Itoa(e.taskInfo.ID), e.taskInfo.ServiceName, item.ID, - locationName, - ).Set(0) + item.NameEn, + item.Name, + ).Set(0) e.log.WithFields( logrus.Fields{ - "mp_id": item.ID, - "mp_name": item.Name}).Warn("No results found for MP") + "mp_id": item.ID, + "mp_name": item.NameEn}).Warn("No results found for MP") return nil } @@ -217,22 +212,18 @@ func (e *Exporter) processTaskStatGraphResultItem(item *client.MonitoringPointEn } MPDataStatus.WithLabelValues( - strconv.Itoa(e.taskInfo.ID), + strconv.Itoa(e.taskInfo.ID), e.taskInfo.ServiceName, item.ID, processedLabels[0][LabelMPName], - ).Set(1) + processedLabels[0][LabelMPNameRu], + ).Set(1) return processedLabels } // buildLabels creates a set of Prometheus labels for a monitoring point. func (e *Exporter) buildLabels(item *client.MonitoringPointEntry) prometheus.Labels { - locationName := item.Name - if e.Config.EngMPNames { - locationName = translator.GetEngLocation(item.Name) - } - ipAddress := "unknown" gpsCoordinates := "unknown" for _, mp := range e.monitoringPoints { @@ -247,7 +238,8 @@ func (e *Exporter) buildLabels(item *client.MonitoringPointEntry) prometheus.Lab LabelTaskID: strconv.Itoa(e.taskInfo.ID), LabelTaskName: e.taskInfo.ServiceName, LabelMPID: item.ID, - LabelMPName: locationName, + LabelMPName: item.NameEn, + LabelMPNameRu: item.Name, LabelMPIP: ipAddress, LabelMPGPS: gpsCoordinates, } diff --git a/internal/exporter/labels.go b/internal/exporter/labels.go index 3b07928..8d4f9d7 100644 --- a/internal/exporter/labels.go +++ b/internal/exporter/labels.go @@ -11,6 +11,7 @@ const ( LabelTaskName = "task_name" LabelMPID = "mp_id" LabelMPName = "mp_name" + LabelMPNameRu = "mp_name_ru" LabelMPIP = "mp_ip" LabelMPGPS = "mp_gps" ) diff --git a/internal/exporter/metrics.go b/internal/exporter/metrics.go index 796bfd5..41cee5e 100644 --- a/internal/exporter/metrics.go +++ b/internal/exporter/metrics.go @@ -19,6 +19,7 @@ var ( LabelTaskName, LabelMPID, LabelMPName, + LabelMPNameRu, LabelMPIP, LabelMPGPS, } @@ -109,7 +110,7 @@ var ( Name: "data_status", Help: "Status of the data for the monitoring point (1 = has data, 0 = no data).", }, - []string{LabelTaskID, LabelTaskName, LabelMPID, LabelMPName}, + []string{LabelTaskID, LabelTaskName, LabelMPID, LabelMPName, LabelMPNameRu}, ) MPConnectSeconds = prometheus.NewGaugeVec( diff --git a/internal/scheduler/stats.go b/internal/scheduler/stats.go index 2c2c6ac..4ace0bc 100644 --- a/internal/scheduler/stats.go +++ b/internal/scheduler/stats.go @@ -14,7 +14,7 @@ import ( "apatit/internal/utils" ) -// runStatsScheduler starts a loop that periodically updates task stats and publish them +// RunStatsScheduler starts a loop that periodically updates task stats and publish them func RunStatsScheduler(exporters []*exporter.Exporter, cfg *config.Config, stop <-chan struct{}) { statsLog := logrus.WithField("component", "stats_scheduler") @@ -74,12 +74,6 @@ func RunStatsScheduler(exporters []*exporter.Exporter, cfg *config.Config, stop wg.Wait() statsLog.Infof("All exporters finished stats refresh cycle in %s.", time.Since(cycleStartTime)) - //// transpose stats - //transposedStats := make([]*client.TransposedTaskStatEntry, 0, len(allStats)) - //for _, originalStat := range allStats { - // transposedStats = append(transposedStats, originalStat.Transpose()) - //} - finalJSON, err := json.Marshal(allStats) if err != nil { statsLog.Errorf("Failed to marshal aggregated transposed stats to JSON: %v", err)