Skip to content

Commit 5611299

Browse files
committed
feat: add metrics support to mock server
1 parent 664451e commit 5611299

File tree

7 files changed

+116
-10
lines changed

7 files changed

+116
-10
lines changed

cmd/mock.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ import (
2828
)
2929

3030
type mockOption struct {
31-
port int
32-
prefix string
31+
port int
32+
prefix string
33+
metrics bool
3334
}
3435

3536
func createMockCmd() (c *cobra.Command) {
@@ -45,19 +46,24 @@ func createMockCmd() (c *cobra.Command) {
4546
flags := c.Flags()
4647
flags.IntVarP(&opt.port, "port", "", 6060, "The mock server port")
4748
flags.StringVarP(&opt.prefix, "prefix", "", "/mock", "The mock server API prefix")
49+
flags.BoolVarP(&opt.metrics, "metrics", "m", true, "Enable request metrics collection")
4850
return
4951
}
5052

5153
func (o *mockOption) runE(c *cobra.Command, args []string) (err error) {
5254
reader := mock.NewLocalFileReader(args[0])
53-
server := mock.NewInMemoryServer(o.port)
55+
server := mock.NewInMemoryServer(c.Context(), o.port)
5456
if err = server.Start(reader, o.prefix); err != nil {
5557
return
5658
}
5759

5860
clean := make(chan os.Signal, 1)
5961
signal.Notify(clean, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
6062
printLocalIPs(c, o.port)
63+
if o.metrics {
64+
server.EnableMetrics()
65+
c.Printf("Metrics available at http://localhost:%d%s/metrics\n", o.port, o.prefix)
66+
}
6167

6268
select {
6369
case <-c.Context().Done():

cmd/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
275275
mockWriter = mock.NewInMemoryReader("")
276276
}
277277

278-
dynamicMockServer := mock.NewInMemoryServer(0)
278+
dynamicMockServer := mock.NewInMemoryServer(cmd.Context(), 0)
279279
mockServerController := server.NewMockServerController(mockWriter, dynamicMockServer, o.httpPort)
280280

281281
clean := make(chan os.Signal, 1)
-1 Bytes
Binary file not shown.

pkg/mock/in_memory.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,17 @@ type inMemoryServer struct {
5353
ctx context.Context
5454
cancelFunc context.CancelFunc
5555
reader Reader
56+
metrics RequestMetrics
5657
}
5758

58-
func NewInMemoryServer(port int) DynamicServer {
59-
ctx, cancel := context.WithCancel(context.TODO())
59+
func NewInMemoryServer(ctx context.Context, port int) DynamicServer {
60+
ctx, cancel := context.WithCancel(ctx)
6061
return &inMemoryServer{
6162
port: port,
6263
wg: sync.WaitGroup{},
6364
ctx: ctx,
6465
cancelFunc: cancel,
66+
metrics: NewNoopMetrics(),
6567
}
6668
}
6769

@@ -148,10 +150,15 @@ func (s *inMemoryServer) Start(reader Reader, prefix string) (err error) {
148150
return
149151
}
150152

153+
func (s *inMemoryServer) EnableMetrics() {
154+
s.metrics = NewInMemoryMetrics()
155+
}
156+
151157
func (s *inMemoryServer) startObject(obj Object) {
152158
// create a simple CRUD server
153159
s.mux.HandleFunc("/"+obj.Name, func(w http.ResponseWriter, req *http.Request) {
154160
fmt.Println("mock server received request", req.URL.Path)
161+
s.metrics.RecordRequest(req.URL.Path)
155162
method := req.Method
156163
w.Header().Set(util.ContentType, util.JSON)
157164

@@ -210,6 +217,7 @@ func (s *inMemoryServer) startObject(obj Object) {
210217

211218
// handle a single object
212219
s.mux.HandleFunc(fmt.Sprintf("/%s/{name}", obj.Name), func(w http.ResponseWriter, req *http.Request) {
220+
s.metrics.RecordRequest(req.URL.Path)
213221
w.Header().Set(util.ContentType, util.JSON)
214222
objects := s.data[obj.Name]
215223
if objects != nil {
@@ -278,15 +286,17 @@ func (s *inMemoryServer) startItem(item Item) {
278286
headerSlices = append(headerSlices, k, v)
279287
}
280288

281-
adHandler := &advanceHandler{item: &item}
289+
adHandler := &advanceHandler{item: &item, metrics: s.metrics}
282290
s.mux.HandleFunc(item.Request.Path, adHandler.handle).Methods(strings.Split(method, ",")...).Headers(headerSlices...)
283291
}
284292

285293
type advanceHandler struct {
286-
item *Item
294+
item *Item
295+
metrics RequestMetrics
287296
}
288297

289298
func (h *advanceHandler) handle(w http.ResponseWriter, req *http.Request) {
299+
h.metrics.RecordRequest(req.URL.Path)
290300
memLogger.Info("receiving mock request", "name", h.item.Name, "method", req.Method, "path", req.URL.Path,
291301
"encoder", h.item.Response.Encoder)
292302

pkg/mock/metrics.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
Copyright 2025 API Testing Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package mock
17+
18+
import (
19+
"net/http"
20+
"sync"
21+
)
22+
23+
// NoopMetrics implements RequestMetrics but does nothing
24+
type NoopMetrics struct{}
25+
26+
// NewNoopMetrics creates a new NoopMetrics instance
27+
func NewNoopMetrics() *NoopMetrics {
28+
return &NoopMetrics{}
29+
}
30+
31+
// RecordRequest implements RequestMetrics but does nothing
32+
func (m *NoopMetrics) RecordRequest(path string) {}
33+
34+
// GetMetrics implements RequestMetrics but returns empty map
35+
func (m *NoopMetrics) GetMetrics() map[string]int {
36+
return make(map[string]int)
37+
}
38+
39+
// AddMetricsHandler implements RequestMetrics but does nothing
40+
func (m *NoopMetrics) AddMetricsHandler(mux *http.ServeMux, prefix string) {}
41+
42+
// RequestMetrics represents an interface for collecting request metrics
43+
type RequestMetrics interface {
44+
RecordRequest(path string)
45+
GetMetrics() map[string]int
46+
AddMetricsHandler(mux *http.ServeMux, prefix string)
47+
}
48+
49+
// InMemoryMetrics implements RequestMetrics with in-memory storage
50+
type InMemoryMetrics struct {
51+
requests map[string]int
52+
mu sync.RWMutex
53+
}
54+
55+
// NewInMemoryMetrics creates a new InMemoryMetrics instance
56+
func NewInMemoryMetrics() *InMemoryMetrics {
57+
return &InMemoryMetrics{
58+
requests: make(map[string]int),
59+
}
60+
}
61+
62+
// RecordRequest records a request for the given path
63+
func (m *InMemoryMetrics) RecordRequest(path string) {
64+
m.mu.Lock()
65+
defer m.mu.Unlock()
66+
m.requests[path]++
67+
}
68+
69+
// GetMetrics returns a copy of the current metrics
70+
func (m *InMemoryMetrics) GetMetrics() map[string]int {
71+
m.mu.RLock()
72+
defer m.mu.RUnlock()
73+
74+
// Return a copy to avoid map races
75+
result := make(map[string]int)
76+
for k, v := range m.requests {
77+
result[k] = v
78+
}
79+
return result
80+
}
81+
82+
func (m *InMemoryMetrics) AddMetricsHandler(mux *http.ServeMux, prefix string) {
83+
// Add metrics endpoint
84+
mux.HandleFunc(prefix+"/metrics", func(w http.ResponseWriter, r *http.Request) {
85+
// metrics handling code
86+
})
87+
}

pkg/mock/server.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ limitations under the License.
1515
*/
1616
package mock
1717

18-
import "net/http"
18+
import (
19+
"net/http"
20+
)
1921

2022
type Loadable interface {
2123
Load() error
@@ -26,6 +28,7 @@ type DynamicServer interface {
2628
SetupHandler(reader Reader, prefix string) (http.Handler, error)
2729
Stop() error
2830
GetPort() string
31+
EnableMetrics()
2932
Loadable
3033
}
3134

pkg/server/remote_server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1274,7 +1274,7 @@ func (s *mockServerController) Reload(ctx context.Context, in *MockConfig) (repl
12741274
}
12751275
}
12761276

1277-
server := mock.NewInMemoryServer(int(in.GetPort()))
1277+
server := mock.NewInMemoryServer(ctx, int(in.GetPort()))
12781278
server.Start(s.mockWriter, in.Prefix)
12791279
s.loader = server
12801280
}

0 commit comments

Comments
 (0)