Skip to content

Commit 3e4b58b

Browse files
author
Saad Karim
committed
[FABC-785] Add metrics for server APIs
Added rate, error, and duration metrics for server APIs Change-Id: Ia7ec7860ac4b8b177d49f84e1290000b53a67813 Signed-off-by: Saad Karim <skarim@us.ibm.com>
1 parent 6fc2cf5 commit 3e4b58b

File tree

18 files changed

+1834
-2
lines changed

18 files changed

+1834
-2
lines changed

lib/server.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,17 @@ import (
2525
"github.com/cloudflare/cfssl/log"
2626
"github.com/cloudflare/cfssl/revoke"
2727
"github.com/cloudflare/cfssl/signer"
28+
"github.com/felixge/httpsnoop"
2829
gmux "github.com/gorilla/mux"
2930
"github.com/hyperledger/fabric-ca/lib/attr"
3031
"github.com/hyperledger/fabric-ca/lib/caerrors"
3132
calog "github.com/hyperledger/fabric-ca/lib/common/log"
3233
"github.com/hyperledger/fabric-ca/lib/dbutil"
3334
"github.com/hyperledger/fabric-ca/lib/metadata"
35+
"github.com/hyperledger/fabric-ca/lib/server/metrics"
3436
stls "github.com/hyperledger/fabric-ca/lib/tls"
3537
"github.com/hyperledger/fabric-ca/util"
38+
"github.com/hyperledger/fabric/common/metrics/disabled"
3639
"github.com/pkg/errors"
3740
"github.com/spf13/viper"
3841
)
@@ -53,6 +56,8 @@ type Server struct {
5356
BlockingStart bool
5457
// The server's configuration
5558
Config *ServerConfig
59+
// Metrics are the metrics that the server tracks
60+
Metrics metrics.Metrics
5661
// The server mux
5762
mux *gmux.Router
5863
// The current listener for this server
@@ -107,10 +112,17 @@ func (s *Server) init(renew bool) (err error) {
107112
if err != nil {
108113
return err
109114
}
115+
s.initMetrics()
110116
// Successful initialization
111117
return nil
112118
}
113119

120+
func (s *Server) initMetrics() {
121+
provider := disabled.Provider{} // This is temporary, will eventually be replaced by actual provider
122+
s.Metrics.APICounter = provider.NewCounter(metrics.APICounterOpts)
123+
s.Metrics.APIDuration = provider.NewHistogram(metrics.APIDurationOpts)
124+
}
125+
114126
// Start the fabric-ca server
115127
func (s *Server) Start() (err error) {
116128
log.Infof("Starting server in home directory: %s", s.HomeDir)
@@ -489,10 +501,31 @@ func (s *Server) registerHandler(se *serverEndpoint) {
489501

490502
func (s *Server) middleware(next http.Handler) http.Handler {
491503
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
492-
next.ServeHTTP(w, r)
504+
metrics := httpsnoop.CaptureMetrics(next, w, r)
505+
apiName := s.getAPIName(r)
506+
caName := s.getCAName()
507+
s.recordMetrics(metrics.Duration, caName, apiName, strconv.Itoa(metrics.Code))
493508
})
494509
}
495510

511+
func (s *Server) getAPIName(r *http.Request) string {
512+
var apiName string
513+
var match gmux.RouteMatch
514+
if s.mux.Match(r, &match) {
515+
apiName = match.Route.GetName()
516+
}
517+
return apiName
518+
}
519+
520+
func (s *Server) getCAName() string {
521+
return s.CA.Config.CA.Name
522+
}
523+
524+
func (s *Server) recordMetrics(duration time.Duration, caName, apiName, statusCode string) {
525+
s.Metrics.APICounter.With("ca_name", caName, "api_name", apiName, "status_code", statusCode).Add(1)
526+
s.Metrics.APIDuration.With("ca_name", caName, "api_name", apiName, "status_code", statusCode).Observe(duration.Seconds())
527+
}
528+
496529
// Starting listening and serving
497530
func (s *Server) listenAndServe() (err error) {
498531

@@ -611,6 +644,7 @@ func (s *Server) serve() error {
611644
// in https://jira.hyperledger.org/browse/FAB-3100.
612645
return nil
613646
}
647+
614648
s.serveError = http.Serve(listener, s.mux)
615649
log.Errorf("Server has stopped serving: %s", s.serveError)
616650
s.closeListener()

lib/server/metrics/metrics.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package metrics
8+
9+
import "github.com/hyperledger/fabric/common/metrics"
10+
11+
var (
12+
// APICounterOpts define the counter opts for server APIs
13+
APICounterOpts = metrics.CounterOpts{
14+
Namespace: "api_request",
15+
Subsystem: "",
16+
Name: "count",
17+
Help: "Number of requests made to an API",
18+
LabelNames: []string{"ca_name", "api_name", "status_code"},
19+
StatsdFormat: "%{#fqname}.%{ca_name}.%{api_name}.%{status_code}",
20+
}
21+
22+
// APIDurationOpts define the duration opts for server APIs
23+
APIDurationOpts = metrics.HistogramOpts{
24+
Namespace: "api_request",
25+
Subsystem: "",
26+
Name: "duration",
27+
Help: "Time taken in seconds for the request to an API to be completed",
28+
LabelNames: []string{"ca_name", "api_name", "status_code"},
29+
StatsdFormat: "%{#fqname}.%{ca_name}.%{api_name}.%{status_code}",
30+
}
31+
)
32+
33+
// Metrics are the metrics tracked by server
34+
type Metrics struct {
35+
// APICounter keeps track of number of times an API endpoint is called
36+
APICounter metrics.Counter
37+
// APIDuration keeps track of time taken for request to complete for an API
38+
APIDuration metrics.Histogram
39+
}

lib/server_whitebox_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@ limitations under the License.
1616
package lib
1717

1818
import (
19+
"net/http"
20+
"net/http/httptest"
1921
"os"
2022
"testing"
2123

2224
"github.com/cloudflare/cfssl/log"
25+
"github.com/gorilla/mux"
26+
"github.com/hyperledger/fabric-ca/lib/server/metrics"
2327
"github.com/hyperledger/fabric-ca/util"
28+
"github.com/hyperledger/fabric/common/metrics/metricsfakes"
29+
. "github.com/onsi/gomega"
2430
"github.com/stretchr/testify/assert"
2531
)
2632

@@ -118,3 +124,49 @@ func TestServerLogLevel(t *testing.T) {
118124
err = srv.Init(false)
119125
assert.Error(t, err, "Should fail, can't specify a log level and set debug true at same time")
120126
}
127+
128+
func TestServerMetrics(t *testing.T) {
129+
gt := NewGomegaWithT(t)
130+
131+
se := &serverEndpoint{
132+
Path: "/test",
133+
}
134+
135+
router := mux.NewRouter()
136+
router.Handle(se.Path, se).Name(se.Path)
137+
138+
fakeCounter := &metricsfakes.Counter{}
139+
fakeCounter.WithReturns(fakeCounter)
140+
fakeHist := &metricsfakes.Histogram{}
141+
fakeHist.WithReturns(fakeHist)
142+
server := &Server{
143+
CA: CA{
144+
Config: &CAConfig{
145+
CA: CAInfo{
146+
Name: "ca1",
147+
},
148+
},
149+
},
150+
Metrics: metrics.Metrics{
151+
APICounter: fakeCounter,
152+
APIDuration: fakeHist,
153+
},
154+
mux: router,
155+
}
156+
157+
server.mux.Use(server.middleware)
158+
se.Server = server
159+
160+
req, err := http.NewRequest("GET", "/test", nil)
161+
gt.Expect(err).NotTo(HaveOccurred())
162+
163+
rr := httptest.NewRecorder()
164+
router.ServeHTTP(rr, req)
165+
gt.Expect(fakeCounter.AddCallCount()).To(Equal(1))
166+
gt.Expect(fakeCounter.WithArgsForCall(0)).NotTo(BeZero())
167+
gt.Expect(fakeCounter.WithArgsForCall(0)).To(Equal([]string{"ca_name", "ca1", "api_name", "/test", "status_code", "405"}))
168+
169+
gt.Expect(fakeHist.ObserveCallCount()).To(Equal(1))
170+
gt.Expect(fakeHist.WithArgsForCall(0)).NotTo(BeZero())
171+
gt.Expect(fakeHist.WithArgsForCall(0)).To(Equal([]string{"ca_name", "ca1", "api_name", "/test", "status_code", "405"}))
172+
}

scripts/run_unit_tests

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ go get github.com/AlekSi/gocov-xml
1616
# Skipping /lib/common package as there is only one file that contains request/response structs used by both client and server. It needs to be removed from exclude package list
1717
# when code is added to this package
1818
# Skipping credential package as there is only one file that contains Credential interface definition. It needs to be removed from exclude package list when code is added to this package
19-
PKGS=`go list github.com/hyperledger/fabric-ca/... | grep -Ev '/vendor/|/api|/dbutil|/ldap|/mocks|/test/fabric-ca-load-tester|/integration|/fabric-ca-client$|/credential$|/lib/common$'`
19+
PKGS=`go list github.com/hyperledger/fabric-ca/... | grep -Ev '/vendor/|/api|/dbutil|/ldap|/mocks|/test/fabric-ca-load-tester|/integration|/fabric-ca-client$|/credential$|/lib/common$|/metrics'`
2020

2121
gocov test -timeout 15m $PKGS | gocov-xml > coverage.xml
2222

vendor/github.com/felixge/httpsnoop/LICENSE.txt

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/felixge/httpsnoop/Makefile

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/felixge/httpsnoop/README.md

Lines changed: 94 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)