Skip to content

Commit 97dcaaf

Browse files
committed
feat: add prometheus metric
1 parent 0d1c3ab commit 97dcaaf

File tree

13 files changed

+583
-11
lines changed

13 files changed

+583
-11
lines changed

go.mod

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ require (
1212
github.com/hashicorp/go-version v1.7.0
1313
github.com/langgenius/dify-cloud-kit v0.1.1
1414
github.com/panjf2000/ants/v2 v2.10.0
15+
github.com/prometheus/client_golang v1.23.2
1516
github.com/redis/go-redis/v9 v9.6.3
1617
github.com/spf13/cobra v1.8.1
1718
github.com/spf13/viper v1.19.0
18-
github.com/stretchr/testify v1.10.0
19+
github.com/stretchr/testify v1.11.1
1920
github.com/xeipuuv/gojsonschema v1.2.0
2021
golang.org/x/tools v0.38.0
2122
gorm.io/driver/mysql v1.5.7
@@ -58,6 +59,7 @@ require (
5859
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
5960
github.com/aws/smithy-go v1.22.2 // indirect
6061
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
62+
github.com/beorn7/perks v1.0.1 // indirect
6163
github.com/cespare/xxhash/v2 v2.3.0 // indirect
6264
github.com/charmbracelet/lipgloss v0.13.0 // indirect
6365
github.com/charmbracelet/x/ansi v0.2.3 // indirect
@@ -99,8 +101,12 @@ require (
99101
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
100102
github.com/muesli/cancelreader v0.2.2 // indirect
101103
github.com/muesli/termenv v0.15.2 // indirect
104+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
102105
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
103106
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
107+
github.com/prometheus/client_model v0.6.2 // indirect
108+
github.com/prometheus/common v0.66.1 // indirect
109+
github.com/prometheus/procfs v0.16.1 // indirect
104110
github.com/rivo/uniseg v0.4.7 // indirect
105111
github.com/sagikazarmark/locafero v0.4.0 // indirect
106112
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
@@ -125,6 +131,7 @@ require (
125131
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
126132
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
127133
go.opentelemetry.io/otel/trace v1.35.0 // indirect
134+
go.yaml.in/yaml/v2 v2.4.2 // indirect
128135
golang.org/x/mod v0.29.0 // indirect
129136
golang.org/x/oauth2 v0.30.0 // indirect
130137
golang.org/x/time v0.11.0 // indirect
@@ -175,7 +182,7 @@ require (
175182
golang.org/x/sync v0.18.0 // indirect
176183
golang.org/x/sys v0.38.0 // indirect
177184
golang.org/x/text v0.31.0 // indirect
178-
google.golang.org/protobuf v1.36.6 // indirect
185+
google.golang.org/protobuf v1.36.8 // indirect
179186
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
180187
gopkg.in/yaml.v3 v3.0.1
181188
gorm.io/driver/postgres v1.5.9

go.sum

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
8383
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
8484
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
8585
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
86+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
87+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
8688
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
8789
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
8890
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -227,6 +229,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
227229
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
228230
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
229231
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
232+
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
233+
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
230234
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
231235
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
232236
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
@@ -270,6 +274,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
270274
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
271275
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
272276
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
277+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
278+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
273279
github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8=
274280
github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
275281
github.com/panjf2000/gnet/v2 v2.5.5 h1:H+LqGgCHs2mGJq/4n6YELhMjZ027bNgd5Qb8Wj5nbrM=
@@ -287,6 +293,14 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
287293
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
288294
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
289295
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
296+
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
297+
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
298+
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
299+
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
300+
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
301+
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
302+
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
303+
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
290304
github.com/redis/go-redis/v9 v9.6.3 h1:8Dr5ygF1QFXRxIH/m3Xg9MMG1rS8YCtAgosrsewT6i0=
291305
github.com/redis/go-redis/v9 v9.6.3/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
292306
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -328,8 +342,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
328342
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
329343
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
330344
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
331-
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
332-
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
345+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
346+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
333347
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
334348
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
335349
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
@@ -380,6 +394,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
380394
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
381395
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
382396
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
397+
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
398+
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
383399
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
384400
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
385401
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
@@ -421,8 +437,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:
421437
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
422438
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
423439
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
424-
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
425-
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
440+
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
441+
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
426442
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
427443
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
428444
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package controlpanel
2+
3+
import (
4+
"github.com/langgenius/dify-plugin-daemon/internal/core/debugging_runtime"
5+
"github.com/langgenius/dify-plugin-daemon/internal/core/local_runtime"
6+
"github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
7+
"github.com/langgenius/dify-plugin-daemon/pkg/utils/metrics"
8+
)
9+
10+
type MetricsNotifier struct{}
11+
12+
func NewMetricsNotifier() *MetricsNotifier {
13+
return &MetricsNotifier{}
14+
}
15+
16+
// identifiableRuntime is an interface for types that can provide their identity
17+
type identifiableRuntime interface {
18+
Identity() (plugin_entities.PluginUniqueIdentifier, error)
19+
}
20+
21+
func (m *MetricsNotifier) OnLocalRuntimeStarting(pluginUniqueIdentifier plugin_entities.PluginUniqueIdentifier) {
22+
pluginID := pluginUniqueIdentifier.PluginID()
23+
metrics.PluginRuntimeStatus.WithLabelValues(
24+
pluginID,
25+
"local",
26+
).Set(0.5)
27+
}
28+
29+
func (m *MetricsNotifier) OnLocalRuntimeReady(runtime *local_runtime.LocalPluginRuntime) {
30+
pluginID := pluginIDFromRuntime(runtime)
31+
metrics.PluginRuntimeStatus.WithLabelValues(
32+
pluginID,
33+
"local",
34+
).Set(1)
35+
metrics.ActivePluginsTotal.WithLabelValues("local").Inc()
36+
}
37+
38+
func (m *MetricsNotifier) OnLocalRuntimeStartFailed(pluginUniqueIdentifier plugin_entities.PluginUniqueIdentifier, err error) {
39+
pluginID := pluginUniqueIdentifier.PluginID()
40+
metrics.PluginRuntimeStatus.WithLabelValues(
41+
pluginID,
42+
"local",
43+
).Set(0)
44+
metrics.PluginInstallationsTotal.WithLabelValues(
45+
pluginID,
46+
"failed",
47+
).Inc()
48+
}
49+
50+
func (m *MetricsNotifier) OnLocalRuntimeStopped(runtime *local_runtime.LocalPluginRuntime) {
51+
pluginID := pluginIDFromRuntime(runtime)
52+
metrics.PluginRuntimeStatus.WithLabelValues(
53+
pluginID,
54+
"local",
55+
).Set(0)
56+
metrics.ActivePluginsTotal.WithLabelValues("local").Dec()
57+
}
58+
59+
func (m *MetricsNotifier) OnLocalRuntimeStop(runtime *local_runtime.LocalPluginRuntime) {
60+
pluginID := pluginIDFromRuntime(runtime)
61+
metrics.PluginRuntimeStatus.WithLabelValues(
62+
pluginID,
63+
"local",
64+
).Set(0)
65+
}
66+
67+
func (m *MetricsNotifier) OnLocalRuntimeScaleUp(runtime *local_runtime.LocalPluginRuntime, i int32) {
68+
}
69+
70+
func (m *MetricsNotifier) OnLocalRuntimeScaleDown(runtime *local_runtime.LocalPluginRuntime, i int32) {
71+
}
72+
73+
func (m *MetricsNotifier) OnLocalRuntimeInstanceLog(
74+
runtime *local_runtime.LocalPluginRuntime,
75+
instance *local_runtime.PluginInstance,
76+
event plugin_entities.PluginLogEvent,
77+
) {
78+
}
79+
80+
func (m *MetricsNotifier) OnDebuggingRuntimeConnected(runtime *debugging_runtime.RemotePluginRuntime) {
81+
pluginID := pluginIDFromRuntime(runtime)
82+
metrics.PluginRuntimeStatus.WithLabelValues(
83+
pluginID,
84+
"remote",
85+
).Set(1)
86+
metrics.ActivePluginsTotal.WithLabelValues("remote").Inc()
87+
}
88+
89+
func (m *MetricsNotifier) OnDebuggingRuntimeDisconnected(runtime *debugging_runtime.RemotePluginRuntime) {
90+
pluginID := pluginIDFromRuntime(runtime)
91+
metrics.PluginRuntimeStatus.WithLabelValues(
92+
pluginID,
93+
"remote",
94+
).Set(0)
95+
metrics.ActivePluginsTotal.WithLabelValues("remote").Dec()
96+
}
97+
98+
// pluginIDFromRuntime extracts the plugin ID from any runtime that implements identifiableRuntime
99+
func pluginIDFromRuntime(runtime identifiableRuntime) string {
100+
if runtime == nil {
101+
return "unknown"
102+
}
103+
if identity, err := runtime.Identity(); err == nil {
104+
return identity.PluginID()
105+
}
106+
return "unknown"
107+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package controlpanel
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestPluginIDFromIdentifier(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
identifier string
15+
expected string
16+
}{
17+
{
18+
name: "standard plugin identifier",
19+
identifier: "langgenius/openai:1.0.0@abc123def456789abc123def456789abcd",
20+
expected: "langgenius/openai",
21+
},
22+
{
23+
name: "plugin without author",
24+
identifier: "openai:1.0.0@abc123def456789abc123def456789abcd",
25+
expected: "openai",
26+
},
27+
{
28+
name: "complex plugin identifier",
29+
identifier: "author/my-plugin:2.1.3-beta@1234567890abcdef1234567890abcdef",
30+
expected: "author/my-plugin",
31+
},
32+
}
33+
34+
for _, tt := range tests {
35+
t.Run(tt.name, func(t *testing.T) {
36+
identifier, err := plugin_entities.NewPluginUniqueIdentifier(tt.identifier)
37+
assert.NoError(t, err)
38+
39+
result := identifier.PluginID()
40+
assert.Equal(t, tt.expected, result)
41+
})
42+
}
43+
}
44+
45+
func TestNewMetricsNotifier(t *testing.T) {
46+
notifier := NewMetricsNotifier()
47+
assert.NotNil(t, notifier)
48+
49+
assert.IsType(t, &MetricsNotifier{}, notifier, "NewMetricsNotifier should return *MetricsNotifier type")
50+
}
51+
52+
func TestMetricsNotifier_OnLocalRuntimeStarting(t *testing.T) {
53+
notifier := NewMetricsNotifier()
54+
55+
identifier, err := plugin_entities.NewPluginUniqueIdentifier("langgenius/openai:1.0.0@abc123def456789abc123def456789abcd")
56+
assert.NoError(t, err)
57+
58+
// This should not panic
59+
assert.NotPanics(t, func() {
60+
notifier.OnLocalRuntimeStarting(identifier)
61+
})
62+
}
63+
64+
func TestMetricsNotifier_OnLocalRuntimeStartFailed(t *testing.T) {
65+
notifier := NewMetricsNotifier()
66+
67+
identifier, err := plugin_entities.NewPluginUniqueIdentifier("langgenius/openai:1.0.0@abc123def456789abc123def456789abcd")
68+
assert.NoError(t, err)
69+
70+
// This should not panic
71+
assert.NotPanics(t, func() {
72+
notifier.OnLocalRuntimeStartFailed(identifier, assert.AnError)
73+
})
74+
}
75+
76+
func TestMetricsNotifier_ScaleEvents(t *testing.T) {
77+
notifier := NewMetricsNotifier()
78+
79+
// Create a mock runtime (we can't easily create a real one in tests)
80+
// but we can test that the methods don't panic
81+
assert.NotPanics(t, func() {
82+
// These methods have empty implementations currently
83+
// but we test they exist and don't panic
84+
type mockRuntime struct{}
85+
notifier.OnLocalRuntimeScaleUp(nil, 1)
86+
notifier.OnLocalRuntimeScaleDown(nil, 1)
87+
})
88+
}
89+
90+
func TestPluginIDFromRuntime(t *testing.T) {
91+
t.Run("nil runtime", func(t *testing.T) {
92+
result := pluginIDFromRuntime(nil)
93+
assert.Equal(t, "unknown", result)
94+
})
95+
96+
t.Run("mock runtime with successful identity", func(t *testing.T) {
97+
identifier, _ := plugin_entities.NewPluginUniqueIdentifier("test-plugin:1.0.0@abc123def456789abc123def456789abcd")
98+
99+
mockRuntime := &mockIdentifiableRuntime{
100+
identity: identifier,
101+
err: nil,
102+
}
103+
104+
result := pluginIDFromRuntime(mockRuntime)
105+
assert.Equal(t, "test-plugin", result)
106+
})
107+
108+
t.Run("mock runtime with error", func(t *testing.T) {
109+
mockRuntime := &mockIdentifiableRuntime{
110+
identity: "",
111+
err: errors.New("identity error"),
112+
}
113+
114+
result := pluginIDFromRuntime(mockRuntime)
115+
assert.Equal(t, "unknown", result)
116+
})
117+
}
118+
119+
// mockIdentifiableRuntime is a mock implementation of identifiableRuntime for testing
120+
type mockIdentifiableRuntime struct {
121+
identity plugin_entities.PluginUniqueIdentifier
122+
err error
123+
}
124+
125+
func (m *mockIdentifiableRuntime) Identity() (plugin_entities.PluginUniqueIdentifier, error) {
126+
return m.identity, m.err
127+
}

internal/core/plugin_manager/manager.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func InitGlobalManager(oss oss.OSS, config *app.Config) *PluginManager {
8787
// mount control panel notifiers
8888
manager.controlPanel.AddNotifier(&controlpanel.StandardLogger{})
8989
manager.controlPanel.AddNotifier(&install_service.InstallListener{})
90+
manager.controlPanel.AddNotifier(controlpanel.NewMetricsNotifier())
9091

9192
return manager
9293
}

internal/server/http_server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/langgenius/dify-plugin-daemon/internal/service"
1313
"github.com/langgenius/dify-plugin-daemon/internal/types/app"
1414
"github.com/langgenius/dify-plugin-daemon/pkg/utils/log"
15+
"github.com/prometheus/client_golang/prometheus/promhttp"
1516

1617
sentrygin "github.com/getsentry/sentry-go/gin"
1718
)
@@ -32,13 +33,17 @@ func (app *App) server(config *app.Config) func() {
3233
engine.NoRoute(func(c *gin.Context) {
3334
c.JSON(http.StatusNotFound, gin.H{"code": "not_found", "message": "route not found"})
3435
})
36+
engine.Use(PrometheusMiddleware())
3537
engine.GET("/health/check", controllers.HealthCheck(config))
3638

3739
endpointGroup := engine.Group("/e")
3840
serverlessTransactionGroup := engine.Group("/backwards-invocation")
3941
pluginGroup := engine.Group("/plugin/:tenant_id")
4042
pprofGroup := engine.Group("/debug/pprof")
4143

44+
metricsGroup := engine.Group("/metrics")
45+
metricsGroup.GET("/", gin.WrapH(promhttp.Handler()))
46+
4247
if config.AdminApiEnabled {
4348
if len(config.AdminApiKey) < 10 {
4449
log.Panic("length of admin api key must be greater than 10")

0 commit comments

Comments
 (0)