diff --git a/CHANGES.md b/CHANGES.md index c9c7448..8f209b9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ # CHANGES +## develop + +- [ADD] `sora_license_expired_at_timestamp_seconds` メトリクスを追加する + - Sora のライセンス期限を epoch 秒に変換したものを返す + - 仮にライセンスの期限が 2024 年 1 月の場合は、`2024-01-31T23:59:59Z` の epoch 秒になる + - @tnamao +- [ADD] `sora_time_seconds` メトリクスを追加する + - これは `Node exporter` の `node_time_seconds` と同じもので、exporter が起動しているサーバーのシステム時間を epoch 秒で返す + - `sora_license_expired_at_timestamp_seconds` と組み合わせてライセンスの期限を監視することを想定している + - @tnamao + ## 2024.1.0 - [FIX] Sora 2023.2.0 で `ListClusterNodes` API の `include_all_known_nodes` のデフォルト値が変更で panic が起こす問題に対応する diff --git a/collector/collector.go b/collector/collector.go index 1a99b56..ccdb70c 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -14,12 +14,18 @@ import ( "github.com/prometheus/client_golang/prometheus" ) +var ( + // for testing + freezedTimeSeconds = float64(time.Date(2024, 1, 7, 17, 41, 31, 312389, time.UTC).UnixNano()) / 1e9 +) + type Collector struct { mutex sync.RWMutex logger log.Logger timeout time.Duration URI string skipSslVerify bool + freezeTimeSeconds bool enableSoraClientMetrics bool enableSoraConnectionErrorMetrics bool enableErlangVMMetrics bool @@ -27,6 +33,8 @@ type Collector struct { soraUp *prometheus.Desc soraVersionInfo *prometheus.Desc + soraTimeSeconds *prometheus.Desc + ConnectionMetrics WebhookMetrics ClientMetrics @@ -40,6 +48,7 @@ type CollectorOptions struct { URI string SkipSslVerify bool Timeout time.Duration + FreezeTimeSeconds bool Logger log.Logger EnableSoraClientMetrics bool EnableSoraConnectionErrorMetrics bool @@ -62,13 +71,19 @@ func NewCollector(options *CollectorOptions) *Collector { skipSslVerify: options.SkipSslVerify, logger: options.Logger, + // for testing + freezeTimeSeconds: options.FreezeTimeSeconds, + enableSoraClientMetrics: options.EnableSoraClientMetrics, enableSoraConnectionErrorMetrics: options.EnableSoraConnectionErrorMetrics, enableErlangVMMetrics: options.EnableErlangVMMetrics, EnableSoraClusterMetrics: options.EnableSoraClusterMetrics, - soraUp: newDesc("up", "Whether the last scrape of metrics from Sora was able to connect to the server (1 for yes, 0 for no)."), - soraVersionInfo: newDescWithLabel("version_info", "sora version info.", []string{"version"}), + soraUp: newDesc("up", "Whether the last scrape of metrics from Sora was able to connect to the server (1 for yes, 0 for no)."), + soraVersionInfo: newDescWithLabel("version_info", "sora version info.", []string{"version"}), + // same as node expoter's node_time_seconds + soraTimeSeconds: newDesc("time_seconds", "System time in seconds since epoch."), + ConnectionMetrics: connectionMetrics, WebhookMetrics: webhookMetrics, ClientMetrics: clientMetrics, @@ -175,6 +190,14 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) { ch <- newGauge(c.soraUp, 1) ch <- newGauge(c.soraVersionInfo, 1, report.SoraVersion) + + if c.freezeTimeSeconds { + ch <- newGauge(c.soraTimeSeconds, freezedTimeSeconds) + } else { + nowSec := float64(time.Now().UnixNano()) / 1e9 + ch <- newGauge(c.soraTimeSeconds, nowSec) + } + c.LicenseMetrics.Collect(ch, licenseInfo) c.ConnectionMetrics.Collect(ch, report.soraConnectionReport) c.WebhookMetrics.Collect(ch, report.soraWebhookReport) @@ -196,6 +219,7 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) { func (c *Collector) Describe(ch chan<- *prometheus.Desc) { ch <- c.soraUp ch <- c.soraVersionInfo + ch <- c.soraTimeSeconds c.LicenseMetrics.Describe(ch) c.ConnectionMetrics.Describe(ch) c.WebhookMetrics.Describe(ch) diff --git a/collector/license.go b/collector/license.go index 0606b35..8cdfaa0 100644 --- a/collector/license.go +++ b/collector/license.go @@ -1,25 +1,32 @@ package collector -import "github.com/prometheus/client_golang/prometheus" +import ( + "time" + + "github.com/prometheus/client_golang/prometheus" +) var ( licenseMetrics = LicenseMetrics{ - licenseInfo: newDescWithLabel("license_info", "sora license info.", []string{"expired_at", "product_name", "serial_code", "type"}), - licenseMaxConnections: newDesc("license_max_connections", "sora license file's max_connections."), - licenseMaxNodes: newDesc("license_max_nodes", "sora license file's max_nodes."), + licenseInfo: newDescWithLabel("license_info", "sora license info.", []string{"expired_at", "product_name", "serial_code", "type"}), + licenseMaxConnections: newDesc("license_max_connections", "sora license file's max_connections."), + licenseMaxNodes: newDesc("license_max_nodes", "sora license file's max_nodes."), + licenseExpiredAtTimestampSeconds: newDesc("license_expired_at_timestamp_seconds", "sora license file's expire seconds since epoch."), } ) type LicenseMetrics struct { - licenseInfo *prometheus.Desc - licenseMaxConnections *prometheus.Desc - licenseMaxNodes *prometheus.Desc + licenseInfo *prometheus.Desc + licenseMaxConnections *prometheus.Desc + licenseMaxNodes *prometheus.Desc + licenseExpiredAtTimestampSeconds *prometheus.Desc } func (m *LicenseMetrics) Describe(ch chan<- *prometheus.Desc) { ch <- m.licenseInfo ch <- m.licenseMaxConnections ch <- m.licenseMaxNodes + ch <- m.licenseExpiredAtTimestampSeconds } func (m *LicenseMetrics) Collect(ch chan<- prometheus.Metric, info soraLicenseInfo) { @@ -28,4 +35,19 @@ func (m *LicenseMetrics) Collect(ch chan<- prometheus.Metric, info soraLicenseIn if info.MaxNodes != nil { ch <- newGauge(m.licenseMaxNodes, float64(*info.MaxNodes)) } + + expiredAtSec := expiredAtToSecondSinceEpoch(info.ExpiredAt) + ch <- newGauge(m.licenseExpiredAtTimestampSeconds, expiredAtSec) +} + +func expiredAtToSecondSinceEpoch(expiredAt string) float64 { + // expiredAt: "2024-12" + expiredAtTime, err := time.Parse("2006-01", expiredAt) + if err != nil { + return 0 + } + + // 期限翌月の 1 日 0 時 0 分0 秒の 1 秒前 + expiredTimestamp := time.Date(expiredAtTime.Year(), expiredAtTime.Month()+1, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second) + return float64(expiredTimestamp.UnixNano()) / 1e9 } diff --git a/main.go b/main.go index ea3c774..c6be469 100644 --- a/main.go +++ b/main.go @@ -86,6 +86,7 @@ type handler struct { soraAPIURL string soraSkipSslVeirfy bool soraTimeout time.Duration + soraFreezeTimeSeconds bool enableSoraClientMetrics bool enableSoraConnectionErrorMetrics bool enableErlangVMMetrics bool @@ -94,7 +95,7 @@ type handler struct { func newHandler( includeExporterMetrics bool, maxRequests int, logger log.Logger, - soraAPIURL string, soraSkipSslVeirfy bool, soraTimeout time.Duration, + soraAPIURL string, soraSkipSslVeirfy bool, soraTimeout time.Duration, soraFreezeTimeSeconds bool, enableSoraClientMetrics bool, enableSoraConnectionErrorMetrics bool, enableErlangVMMetrics bool, enableSoraClusterMetrics bool) *handler { h := &handler{ @@ -105,6 +106,7 @@ func newHandler( soraAPIURL: soraAPIURL, soraSkipSslVeirfy: soraSkipSslVeirfy, soraTimeout: soraTimeout, + soraFreezeTimeSeconds: soraFreezeTimeSeconds, enableSoraClientMetrics: enableSoraClientMetrics, enableSoraConnectionErrorMetrics: enableSoraConnectionErrorMetrics, enableErlangVMMetrics: enableErlangVMMetrics, @@ -132,6 +134,7 @@ func (h *handler) innerHandler() http.Handler { URI: h.soraAPIURL, SkipSslVerify: h.soraSkipSslVeirfy, Timeout: h.soraTimeout, + FreezeTimeSeconds: h.soraFreezeTimeSeconds, Logger: h.logger, EnableSoraClientMetrics: h.enableSoraClientMetrics, EnableSoraConnectionErrorMetrics: h.enableSoraConnectionErrorMetrics, @@ -185,7 +188,7 @@ func main() { }) soraHandler := newHandler( !*disableExporterMetrics, *maxRequests, logger, - *soraAPIURL, *soraSkipSslVeirfy, *soraTimeout, + *soraAPIURL, *soraSkipSslVeirfy, *soraTimeout, false, *enableSoraClientMetrics, *enableSoraConnectionErrorMetrics, *enableErlangVMMetrics, *enableSoraClusterMetrics) http.Handle(*metricsPath, soraHandler) diff --git a/main_test.go b/main_test.go index 1516b87..a43c6e5 100644 --- a/main_test.go +++ b/main_test.go @@ -280,6 +280,7 @@ func TestInvalidConfig(t *testing.T) { URI: s.URL, SkipSslVerify: true, Timeout: timeout, + FreezeTimeSeconds: true, Logger: log.NewNopLogger(), EnableSoraClientMetrics: true, EnableSoraConnectionErrorMetrics: true, @@ -298,6 +299,7 @@ func TestMaximumMetrics(t *testing.T) { URI: s.URL, SkipSslVerify: true, Timeout: timeout, + FreezeTimeSeconds: true, Logger: log.NewNopLogger(), EnableSoraClientMetrics: true, EnableSoraConnectionErrorMetrics: true, @@ -316,6 +318,7 @@ func TestSoraErlangVMEnabledMetrics(t *testing.T) { URI: s.URL, SkipSslVerify: true, Timeout: timeout, + FreezeTimeSeconds: true, Logger: log.NewNopLogger(), EnableSoraClientMetrics: false, EnableSoraConnectionErrorMetrics: false, @@ -334,6 +337,7 @@ func TestSoraClientEnabledMetrics(t *testing.T) { URI: s.URL, SkipSslVerify: true, Timeout: timeout, + FreezeTimeSeconds: true, Logger: log.NewNopLogger(), EnableSoraClientMetrics: true, EnableSoraConnectionErrorMetrics: false, @@ -352,6 +356,7 @@ func TestSoraConnectionErrorEnabledMetrics(t *testing.T) { URI: s.URL, SkipSslVerify: true, Timeout: timeout, + FreezeTimeSeconds: true, Logger: log.NewNopLogger(), EnableSoraClientMetrics: false, EnableSoraConnectionErrorMetrics: true, @@ -395,6 +400,7 @@ func TestMinimumMetrics(t *testing.T) { URI: s.URL, SkipSslVerify: true, Timeout: timeout, + FreezeTimeSeconds: true, Logger: log.NewNopLogger(), EnableSoraClientMetrics: false, EnableSoraConnectionErrorMetrics: false, @@ -413,6 +419,7 @@ func TestSoraClusterEnabledMetrics(t *testing.T) { URI: s.URL, SkipSslVerify: true, Timeout: timeout, + FreezeTimeSeconds: true, Logger: log.NewNopLogger(), EnableSoraClientMetrics: false, EnableSoraConnectionErrorMetrics: false, @@ -432,6 +439,7 @@ func TestSoraClusterEnabledMetricsCurrentJsonData(t *testing.T) { URI: s.URL, SkipSslVerify: true, Timeout: timeout, + FreezeTimeSeconds: true, Logger: log.NewNopLogger(), EnableSoraClientMetrics: false, EnableSoraConnectionErrorMetrics: false, diff --git a/test/maximum.metrics b/test/maximum.metrics index fc7d4fa..8361fee 100644 --- a/test/maximum.metrics +++ b/test/maximum.metrics @@ -147,6 +147,9 @@ sora_erlang_vm_wall_clock_wallclock_time_since_last_call 9090 # TYPE sora_event_webhook_total counter sora_event_webhook_total{state="failed"} 94 sora_event_webhook_total{state="successful"} 97 +# HELP sora_license_expired_at_timestamp_seconds sora license file's expire seconds since epoch. +# TYPE sora_license_expired_at_timestamp_seconds gauge +sora_license_expired_at_timestamp_seconds 1.759276799e+09 # HELP sora_license_info sora license info. # TYPE sora_license_info gauge sora_license_info{expired_at="2025-09",product_name="Sora",serial_code="EXPORTER-SRA-E001-202509-N10-100",type="Experimental"} 1 @@ -174,6 +177,9 @@ sora_session_webhook_total{state="successful"} 98 # TYPE sora_successful_auth_webhook_total counter sora_successful_auth_webhook_total{state="allowed"} 91 sora_successful_auth_webhook_total{state="denied"} 92 +# HELP sora_time_seconds System time in seconds since epoch. +# TYPE sora_time_seconds gauge +sora_time_seconds 1.7046492910003123e+09 # HELP sora_turn_connections_total The total number of connections with TURN. # TYPE sora_turn_connections_total counter sora_turn_connections_total{proto="tcp"} 2 diff --git a/test/minimum.metrics b/test/minimum.metrics index 4a1a988..6be8558 100644 --- a/test/minimum.metrics +++ b/test/minimum.metrics @@ -22,6 +22,9 @@ sora_duration_seconds_total 1412 # TYPE sora_event_webhook_total counter sora_event_webhook_total{state="failed"} 94 sora_event_webhook_total{state="successful"} 97 +# HELP sora_license_expired_at_timestamp_seconds sora license file's expire seconds since epoch. +# TYPE sora_license_expired_at_timestamp_seconds gauge +sora_license_expired_at_timestamp_seconds 1.759276799e+09 # HELP sora_license_info sora license info. # TYPE sora_license_info gauge sora_license_info{expired_at="2025-09",product_name="Sora",serial_code="EXPORTER-SRA-E001-202509-N10-100",type="Experimental"} 1 @@ -46,6 +49,9 @@ sora_session_webhook_total{state="successful"} 98 # TYPE sora_successful_auth_webhook_total counter sora_successful_auth_webhook_total{state="allowed"} 91 sora_successful_auth_webhook_total{state="denied"} 92 +# HELP sora_time_seconds System time in seconds since epoch. +# TYPE sora_time_seconds gauge +sora_time_seconds 1.7046492910003123e+09 # HELP sora_turn_connections_total The total number of connections with TURN. # TYPE sora_turn_connections_total counter sora_turn_connections_total{proto="tcp"} 444 diff --git a/test/sora_client_enabled.metrics b/test/sora_client_enabled.metrics index 34fb87e..d49c2b5 100644 --- a/test/sora_client_enabled.metrics +++ b/test/sora_client_enabled.metrics @@ -48,6 +48,9 @@ sora_duration_seconds_total 1412 # TYPE sora_event_webhook_total counter sora_event_webhook_total{state="failed"} 94 sora_event_webhook_total{state="successful"} 97 +# HELP sora_license_expired_at_timestamp_seconds sora license file's expire seconds since epoch. +# TYPE sora_license_expired_at_timestamp_seconds gauge +sora_license_expired_at_timestamp_seconds 1.759276799e+09 # HELP sora_license_info sora license info. # TYPE sora_license_info gauge sora_license_info{expired_at="2025-09",product_name="Sora",serial_code="EXPORTER-SRA-E001-202509-N10-100",type="Experimental"} 1 @@ -75,6 +78,9 @@ sora_session_webhook_total{state="successful"} 98 # TYPE sora_successful_auth_webhook_total counter sora_successful_auth_webhook_total{state="allowed"} 91 sora_successful_auth_webhook_total{state="denied"} 92 +# HELP sora_time_seconds System time in seconds since epoch. +# TYPE sora_time_seconds gauge +sora_time_seconds 1.7046492910003123e+09 # HELP sora_turn_connections_total The total number of connections with TURN. # TYPE sora_turn_connections_total counter sora_turn_connections_total{proto="tcp"} 2 diff --git a/test/sora_cluster_metrics_enabled.metrics b/test/sora_cluster_metrics_enabled.metrics index 757e071..03d77e5 100644 --- a/test/sora_cluster_metrics_enabled.metrics +++ b/test/sora_cluster_metrics_enabled.metrics @@ -36,6 +36,9 @@ sora_duration_seconds_total 1412 # TYPE sora_event_webhook_total counter sora_event_webhook_total{state="failed"} 94 sora_event_webhook_total{state="successful"} 97 +# HELP sora_license_expired_at_timestamp_seconds sora license file's expire seconds since epoch. +# TYPE sora_license_expired_at_timestamp_seconds gauge +sora_license_expired_at_timestamp_seconds 1.759276799e+09 # HELP sora_license_info sora license info. # TYPE sora_license_info gauge sora_license_info{expired_at="2025-09",product_name="Sora",serial_code="EXPORTER-SRA-E001-202509-N10-100",type="Experimental"} 1 @@ -63,6 +66,9 @@ sora_session_webhook_total{state="successful"} 98 # TYPE sora_successful_auth_webhook_total counter sora_successful_auth_webhook_total{state="allowed"} 91 sora_successful_auth_webhook_total{state="denied"} 92 +# HELP sora_time_seconds System time in seconds since epoch. +# TYPE sora_time_seconds gauge +sora_time_seconds 1.7046492910003123e+09 # HELP sora_turn_connections_total The total number of connections with TURN. # TYPE sora_turn_connections_total counter sora_turn_connections_total{proto="tcp"} 2 diff --git a/test/sora_connection_error_enabled.metrics b/test/sora_connection_error_enabled.metrics index 99ac654..0b816d8 100644 --- a/test/sora_connection_error_enabled.metrics +++ b/test/sora_connection_error_enabled.metrics @@ -26,6 +26,9 @@ sora_duration_seconds_total 1412 # TYPE sora_event_webhook_total counter sora_event_webhook_total{state="failed"} 94 sora_event_webhook_total{state="successful"} 97 +# HELP sora_license_expired_at_timestamp_seconds sora license file's expire seconds since epoch. +# TYPE sora_license_expired_at_timestamp_seconds gauge +sora_license_expired_at_timestamp_seconds 1.759276799e+09 # HELP sora_license_info sora license info. # TYPE sora_license_info gauge sora_license_info{expired_at="2025-09",product_name="Sora",serial_code="EXPORTER-SRA-E001-202509-N10-100",type="Experimental"} 1 @@ -53,6 +56,9 @@ sora_session_webhook_total{state="successful"} 98 # TYPE sora_successful_auth_webhook_total counter sora_successful_auth_webhook_total{state="allowed"} 91 sora_successful_auth_webhook_total{state="denied"} 92 +# HELP sora_time_seconds System time in seconds since epoch. +# TYPE sora_time_seconds gauge +sora_time_seconds 1.7046492910003123e+09 # HELP sora_turn_connections_total The total number of connections with TURN. # TYPE sora_turn_connections_total counter sora_turn_connections_total{proto="tcp"} 2 diff --git a/test/sora_erlang_vm_enabled.metrics b/test/sora_erlang_vm_enabled.metrics index 9b4e0df..45b94e5 100644 --- a/test/sora_erlang_vm_enabled.metrics +++ b/test/sora_erlang_vm_enabled.metrics @@ -103,6 +103,9 @@ sora_erlang_vm_wall_clock_wallclock_time_since_last_call 9090 # TYPE sora_event_webhook_total counter sora_event_webhook_total{state="failed"} 94 sora_event_webhook_total{state="successful"} 97 +# HELP sora_license_expired_at_timestamp_seconds sora license file's expire seconds since epoch. +# TYPE sora_license_expired_at_timestamp_seconds gauge +sora_license_expired_at_timestamp_seconds 1.759276799e+09 # HELP sora_license_info sora license info. # TYPE sora_license_info gauge sora_license_info{expired_at="2025-09",product_name="Sora",serial_code="EXPORTER-SRA-E001-202509-N10-100",type="Experimental"} 1 @@ -130,6 +133,9 @@ sora_session_webhook_total{state="successful"} 98 # TYPE sora_successful_auth_webhook_total counter sora_successful_auth_webhook_total{state="allowed"} 91 sora_successful_auth_webhook_total{state="denied"} 92 +# HELP sora_time_seconds System time in seconds since epoch. +# TYPE sora_time_seconds gauge +sora_time_seconds 1.7046492910003123e+09 # HELP sora_turn_connections_total The total number of connections with TURN. # TYPE sora_turn_connections_total counter sora_turn_connections_total{proto="tcp"} 2