Skip to content

Commit cc805a5

Browse files
QinYuuuuDev Agent
andauthored
add metrics for runner webhook (#647)
Co-authored-by: Dev Agent <[email protected]>
1 parent 18a0562 commit cc805a5

File tree

4 files changed

+184
-1
lines changed

4 files changed

+184
-1
lines changed

api/middleware/webhook_metrics.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package middleware
2+
3+
import (
4+
"time"
5+
6+
"github.com/gin-gonic/gin"
7+
bldprometheus "opencsg.com/csghub-server/builder/prometheus"
8+
)
9+
10+
// WebhookMetrics returns a middleware that collects metrics for webhook requests
11+
func WebhookMetrics() gin.HandlerFunc {
12+
return func(c *gin.Context) {
13+
startTime := time.Now()
14+
15+
// Increment the total webhook requests counter
16+
if bldprometheus.WebhookRequestsTotal != nil {
17+
bldprometheus.WebhookRequestsTotal.Inc()
18+
}
19+
20+
// Process the request
21+
c.Next()
22+
23+
// Record the duration
24+
duration := time.Since(startTime).Seconds()
25+
if bldprometheus.WebhookRequestDuration != nil {
26+
bldprometheus.WebhookRequestDuration.WithLabelValues(
27+
c.Request.Method,
28+
c.FullPath(),
29+
string(rune(c.Writer.Status())),
30+
).Observe(duration)
31+
}
32+
}
33+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package middleware
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
8+
"github.com/gin-gonic/gin"
9+
"github.com/prometheus/client_golang/prometheus"
10+
dto "github.com/prometheus/client_model/go"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
bldprometheus "opencsg.com/csghub-server/builder/prometheus"
14+
)
15+
16+
func TestWebhookMetrics(t *testing.T) {
17+
// Initialize metrics for testing
18+
bldprometheus.InitMetrics()
19+
20+
gin.SetMode(gin.TestMode)
21+
22+
tests := []struct {
23+
name string
24+
method string
25+
path string
26+
expectedStatus int
27+
setupHandler func(*gin.RouterGroup)
28+
}{
29+
{
30+
name: "Successful POST request",
31+
method: "POST",
32+
path: "/webhook/runner",
33+
expectedStatus: http.StatusOK,
34+
setupHandler: func(rg *gin.RouterGroup) {
35+
rg.POST("/runner", func(c *gin.Context) {
36+
c.JSON(http.StatusOK, gin.H{"message": "webhook received"})
37+
})
38+
},
39+
},
40+
{
41+
name: "Failed request with 500 status",
42+
method: "POST",
43+
path: "/webhook/runner",
44+
expectedStatus: http.StatusInternalServerError,
45+
setupHandler: func(rg *gin.RouterGroup) {
46+
rg.POST("/runner", func(c *gin.Context) {
47+
c.AbortWithStatus(http.StatusInternalServerError)
48+
})
49+
},
50+
},
51+
{
52+
name: "GET request (should still work)",
53+
method: "GET",
54+
path: "/webhook/runner",
55+
expectedStatus: http.StatusOK,
56+
setupHandler: func(rg *gin.RouterGroup) {
57+
rg.GET("/runner", func(c *gin.Context) {
58+
c.JSON(http.StatusOK, gin.H{"message": "ok"})
59+
})
60+
},
61+
},
62+
}
63+
64+
for _, tt := range tests {
65+
t.Run(tt.name, func(t *testing.T) {
66+
// Create a new router with webhook metrics middleware
67+
router := gin.New()
68+
router.Use(WebhookMetrics())
69+
70+
webhookGroup := router.Group("/webhook")
71+
72+
tt.setupHandler(webhookGroup)
73+
74+
// Get initial metric values
75+
initialRequestsCount := getMetricValue(t, bldprometheus.WebhookRequestsTotal)
76+
// Create and execute request
77+
req, _ := http.NewRequest(tt.method, tt.path, nil)
78+
w := httptest.NewRecorder()
79+
router.ServeHTTP(w, req)
80+
81+
// Verify response status
82+
assert.Equal(t, tt.expectedStatus, w.Code)
83+
84+
// Verify metrics were incremented
85+
finalRequestsCount := getMetricValue(t, bldprometheus.WebhookRequestsTotal)
86+
87+
assert.Greater(t, finalRequestsCount, initialRequestsCount, "WebhookRequestsTotal should be incremented")
88+
})
89+
}
90+
}
91+
92+
func TestWebhookMetricsWithNilMetrics(t *testing.T) {
93+
// Test behavior when metrics are nil (before InitMetrics)
94+
// Store original values
95+
originalRequestsTotal := bldprometheus.WebhookRequestsTotal
96+
originalRequestDuration := bldprometheus.WebhookRequestDuration
97+
98+
// Set metrics to nil
99+
defer func() {
100+
bldprometheus.WebhookRequestsTotal = originalRequestsTotal
101+
bldprometheus.WebhookRequestDuration = originalRequestDuration
102+
}()
103+
104+
bldprometheus.WebhookRequestsTotal = nil
105+
bldprometheus.WebhookRequestDuration = nil
106+
107+
gin.SetMode(gin.TestMode)
108+
router := gin.New()
109+
router.Use(WebhookMetrics())
110+
111+
router.POST("/webhook/runner", func(c *gin.Context) {
112+
c.JSON(http.StatusOK, gin.H{"message": "webhook received"})
113+
})
114+
115+
req, _ := http.NewRequest("POST", "/webhook/runner", nil)
116+
w := httptest.NewRecorder()
117+
118+
// Should not panic when metrics are nil
119+
assert.NotPanics(t, func() {
120+
router.ServeHTTP(w, req)
121+
})
122+
123+
assert.Equal(t, http.StatusOK, w.Code)
124+
}
125+
126+
// Helper function to get metric value
127+
func getMetricValue(t *testing.T, counter prometheus.Counter) float64 {
128+
if counter == nil {
129+
return 0
130+
}
131+
132+
dto := &dto.Metric{}
133+
err := counter.Write(dto)
134+
require.NoError(t, err)
135+
return dto.Counter.GetValue()
136+
}

api/router/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1324,7 +1324,7 @@ func createWebHookRoutes(apiGroup *gin.RouterGroup, middlewareCollection middlew
13241324
{
13251325
webhookGrp := apiGroup.Group("/webhook")
13261326
runnerHookGrp := webhookGrp.Group("/runner")
1327-
runnerHookGrp.POST("", middlewareCollection.Auth.NeedAPIKey, webhookHandler.ReceiveRunnerWebHook)
1327+
runnerHookGrp.POST("", middlewareCollection.Auth.NeedAPIKey, middleware.WebhookMetrics(), webhookHandler.ReceiveRunnerWebHook)
13281328
}
13291329
return nil
13301330
}

builder/prometheus/metrics.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,25 @@ import (
77

88
var (
99
HttpPanicsTotal prometheus.Counter
10+
11+
WebhookRequestsTotal prometheus.Counter
12+
WebhookRequestDuration *prometheus.HistogramVec
1013
)
1114

1215
func InitMetrics() {
1316
HttpPanicsTotal = promauto.NewCounter(prometheus.CounterOpts{
1417
Name: "csghub_http_panics_total",
1518
Help: "Total number of HTTP panics",
1619
})
20+
21+
WebhookRequestsTotal = promauto.NewCounter(prometheus.CounterOpts{
22+
Name: "csghub_webhook_requests_total",
23+
Help: "Total number of webhook requests from runner server",
24+
})
25+
26+
WebhookRequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
27+
Name: "csghub_webhook_request_duration_seconds",
28+
Help: "Duration of webhook requests in seconds",
29+
Buckets: prometheus.DefBuckets,
30+
}, []string{"method", "endpoint", "status"})
1731
}

0 commit comments

Comments
 (0)