Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
7667d1b
add project v1.0.0
willowweevil Nov 27, 2025
3d132c3
ad CHANGELOG.md
willowweevil Dec 3, 2025
e5f6bd7
add LICENSE
willowweevil Dec 3, 2025
7d67a4e
add --rm flag to docker run
willowweevil Dec 3, 2025
7ab539a
add build-binary stage
willowweevil Dec 3, 2025
c05406f
update ci to run by pushing tag in main
willowweevil Dec 4, 2025
735a1f4
set build by tag from main/develop branches
willowweevil Dec 4, 2025
8263db4
update locations
willowweevil Dec 4, 2025
83eb9b8
add rate limits logic
willowweevil Dec 4, 2025
615f5ac
remove build-binary from needs in build stage
willowweevil Dec 4, 2025
0a35cbe
initialize v1.0.0
willowweevil Dec 5, 2025
0f25323
add branding materials
willowweevil Dec 5, 2025
31bd389
update metrics
willowweevil Dec 9, 2025
03fb871
update error metric
willowweevil Dec 9, 2025
d7349c3
add metrics labels
willowweevil Dec 9, 2025
d2e8951
update metrics
willowweevil Dec 9, 2025
6b057fc
update README.md
willowweevil Dec 9, 2025
07ffe44
add grafana dashboards
willowweevil Dec 9, 2025
8fa8e24
update README
willowweevil Dec 12, 2025
c43e1fe
update dashboards
willowweevil Dec 12, 2025
33fc8f9
add 'apatit_mp_data_status' metric
willowweevil Dec 12, 2025
dbb9ae5
add data status stat panel
willowweevil Dec 12, 2025
919781d
update ci
willowweevil Dec 12, 2025
66d6110
add helm-chart
willowweevil Dec 12, 2025
f3f0533
change version in arg
willowweevil Dec 12, 2025
a1c68db
add locations.json to release
willowweevil Dec 12, 2025
4453daf
update locations
willowweevil Dec 12, 2025
81b1839
fix 'status' metric name
willowweevil Dec 12, 2025
4e3a72f
update monitoring dashboard
willowweevil Dec 12, 2025
075169b
fix typo in REQUEST_DELAY env var; extend default delay up to 3s
willowweevil Dec 16, 2025
a354396
update README
willowweevil Dec 16, 2025
dbc234d
update goland and libaries
willowweevil Dec 18, 2025
31fd8b3
Remove "EngMPNames" from Config struct
willowweevil Feb 17, 2026
39c4f47
Remove "EngMPNames" from exporter.Config
willowweevil Feb 17, 2026
6dba5cf
Remove "translator" module from import and translator initialization …
willowweevil Feb 17, 2026
e710e18
Remove "translator" module
willowweevil Feb 17, 2026
30c6c04
Use both EN and RU monitoring point names as labels in MP-related met…
willowweevil Feb 17, 2026
c8e5790
Add EN monitoring point names in models.
willowweevil Feb 17, 2026
c1668a6
Remove "translator" module from project.
willowweevil Feb 17, 2026
3fb85f7
Remove transpose-related commented code.
willowweevil Feb 17, 2026
8709865
Fix formatting issues.
willowweevil Feb 17, 2026
9ef0aa0
Remove unused function 'envBool'.
willowweevil Feb 17, 2026
46ee2db
update "Unavailable Monitoring Points" panel
willowweevil Feb 19, 2026
8f098a3
Remove 'locations.json'
willowweevil Feb 19, 2026
33bbd3b
Update README.md
willowweevil Feb 19, 2026
999e5f4
Update CHANGELOG.md
willowweevil Feb 19, 2026
7976055
Update README.md
willowweevil Feb 19, 2026
78c8bd8
Merge branch 'main' into develop
willowweevil Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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` |
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
```

Expand Down
7 changes: 0 additions & 7 deletions cmd/apatit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
}
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion deploy/grafana/monitoring-dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -2170,7 +2170,7 @@
"showLegend": false
},
"mergeValues": true,
"perPage": 20,
"perPage": 10,
"rowHeight": 0.9,
"showValue": "never",
"tooltip": {
Expand Down
94 changes: 14 additions & 80 deletions internal/client/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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"`
}
Expand All @@ -140,6 +144,7 @@ type TaskInfo struct {
type MonitoringPointInfo struct {
ID string
Name string
NameEn string
IP string
GPS string
Status int64
Expand All @@ -150,6 +155,7 @@ type MonitoringPointInfo struct {
type MonitoringPointEntry struct {
ID string
Name string
NameEn string
Status int
Result []*MonitoringPointConnectionResult
}
Expand Down Expand Up @@ -180,6 +186,7 @@ type TaskLog struct {
Data string
Description string
Status int64
MPNameRu string
MPName string
MPID string
Traceroute string
Expand All @@ -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"),
Expand Down Expand Up @@ -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)),
}

Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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
//}
12 changes: 0 additions & 12 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
type Config struct {
APIKey string
TaskIDs []int
EngMPNames bool
ApiUpdateDelay time.Duration
ApiDataTimeStep time.Duration
RefreshInterval time.Duration
Expand All @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down
34 changes: 13 additions & 21 deletions internal/exporter/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/sirupsen/logrus"

"apatit/internal/client"
"apatit/internal/translator"
)

// Exporter collects metrics for a single task.
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}

Expand All @@ -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 {
Expand All @@ -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,
}
Expand Down
1 change: 1 addition & 0 deletions internal/exporter/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const (
LabelTaskName = "task_name"
LabelMPID = "mp_id"
LabelMPName = "mp_name"
LabelMPNameRu = "mp_name_ru"
LabelMPIP = "mp_ip"
LabelMPGPS = "mp_gps"
)
Loading