Skip to content

Commit 12682a4

Browse files
ondrej-fabryVladoLavor
authored andcommitted
govppmux stats REST handler (#1396)
* Add govppmux stats handler for REST and improve stats Signed-off-by: Ondrej Fabry <[email protected]> * Allow disabling status publishing and interface stats collection Signed-off-by: Ondrej Fabry <[email protected]>
1 parent bb5566c commit 12682a4

File tree

7 files changed

+156
-39
lines changed

7 files changed

+156
-39
lines changed

pkg/metrics/metrics.go

+33-26
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ package metrics
1616

1717
import (
1818
"encoding/json"
19+
"math"
1920
"sort"
2021
"time"
2122
)
2223

2324
// RoundDuration is the default value used for rounding durations.
24-
var RoundDuration = time.Microsecond * 10
25+
var RoundDuration = time.Millisecond * 1
2526

2627
type Calls map[string]*CallStats
2728

@@ -32,27 +33,39 @@ func (m Calls) MarshalJSON() ([]byte, error) {
3233
calls = append(calls, s)
3334
}
3435
sort.Slice(calls, func(i, j int) bool {
35-
return calls[i].Total > calls[j].Total
36+
return calls[i].Count > calls[j].Count
3637
})
38+
39+
/*var buf bytes.Buffer
40+
buf.WriteByte('{')
41+
for i, c := range calls {
42+
if i > 0 {
43+
buf.WriteByte(',')
44+
}
45+
buf.WriteString(fmt.Sprintf(`"%s":{"count":%d}`, c.Name, c.Count))
46+
}
47+
buf.WriteByte('}')
48+
return buf.Bytes(), nil*/
49+
3750
return json.Marshal(calls)
3851
}
3952

4053
// CallStats represents generic stats for call metrics.
4154
type CallStats struct {
42-
Name string `json:",omitempty"`
43-
Count uint64
44-
Total Duration
45-
Avg Duration
46-
Min Duration
47-
Max Duration
55+
Name string `json:"name,omitempty"`
56+
Count uint64 `json:"count"`
57+
Total float64 `json:"total,omitempty"`
58+
Avg float64 `json:"avg,omitempty"`
59+
Min float64 `json:"min,omitempty"`
60+
Max float64 `json:"max,omitempty"`
4861
}
4962

5063
// Increment increments call count and recalculates durations
5164
func (m *CallStats) Increment(d time.Duration) {
52-
took := Duration(d)
65+
took := d.Round(RoundDuration).Seconds()
5366
m.Count++
54-
m.Total += took
55-
m.Avg = m.Total / Duration(m.Count)
67+
m.Total = round(m.Total + took)
68+
m.Avg = round(m.Total / float64(m.Count))
5669
if took > m.Max {
5770
m.Max = took
5871
}
@@ -61,23 +74,17 @@ func (m *CallStats) Increment(d time.Duration) {
6174
}
6275
}
6376

64-
/*
77+
func round(n float64) float64 {
78+
return math.Round(n*1000) / 1000
79+
}
80+
6581
// MarshalJSON implements json.Marshaler interface
66-
func (m *CallStats) MarshalJSON() ([]byte, error) {
82+
/*func (m *CallStats) MarshalJSON() ([]byte, error) {
6783
var d string
6884
d = fmt.Sprintf(
69-
"count: %d, total: %s (avg/min/max: %s/%s/%s)",
70-
m.Count, durStr(m.TotalDur),
71-
durStr(m.AvgDur), durStr(m.MinDur), durStr(m.MaxDur),
85+
"%s - count: %d, total: %s (avg/min/max: %s/%s/%s)",
86+
m.Name, m.Count, durStr(m.Total),
87+
durStr(m.Avg), durStr(m.Min), durStr(m.Max),
7288
)
7389
return json.Marshal(d)
74-
}
75-
*/
76-
77-
type Duration time.Duration
78-
79-
// MarshalJSON implements json.Marshaler interface
80-
func (m *Duration) MarshalJSON() ([]byte, error) {
81-
s := time.Duration(*m).Round(RoundDuration).String()
82-
return json.Marshal(s)
83-
}
90+
}*/

plugins/govppmux/govpp_channel.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,10 @@ func (r *govppRequestCtx) ReceiveReply(reply govppapi.Message) error {
148148
err = r.sendRequest(r.requestMsg).ReceiveReply(reply)
149149
}
150150

151+
atomic.AddUint64(&stats.RequestsDone, 1)
151152
if err != nil {
152-
atomic.AddUint64(&stats.RequestsFailed, 1)
153+
trackError(err.Error())
154+
atomic.AddUint64(&stats.RequestsErrors, 1)
153155
}
154156

155157
took := time.Since(r.start)
@@ -184,18 +186,25 @@ func (c *goVppChan) SendMultiRequest(request govppapi.Message) govppapi.MultiReq
184186
func (r *govppMultirequestCtx) ReceiveReply(reply govppapi.Message) (bool, error) {
185187
// Receive reply from original send
186188
last, err := r.requestCtx.ReceiveReply(reply)
187-
if last {
189+
if last || err != nil {
188190
took := time.Since(r.start)
189191
trackMsgRequestDur(r.requestMsg.GetMessageName(), took)
192+
193+
atomic.AddUint64(&stats.RequestsDone, 1)
190194
if err != nil {
191-
atomic.AddUint64(&stats.RequestsFailed, 1)
195+
trackError(err.Error())
196+
atomic.AddUint64(&stats.RequestsErrors, 1)
192197
}
198+
193199
defer func() {
194200
r.task.End()
195201
if r.tracer != nil {
196202
r.tracer.LogTime(r.requestMsg.GetMessageName(), r.start)
197203
}
198204
}()
205+
} else {
206+
atomic.AddUint64(&stats.RequestReplies, 1)
207+
trackMsgReply(reply.GetMessageName())
199208
}
200209
return last, err
201210
}

plugins/govppmux/options.go

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package govppmux
1717
import (
1818
"github.com/ligato/cn-infra/datasync/resync"
1919
"github.com/ligato/cn-infra/health/statuscheck"
20+
"github.com/ligato/cn-infra/rpc/rest"
2021
)
2122

2223
// DefaultPlugin is default instance of Plugin
@@ -27,6 +28,7 @@ func NewPlugin(opts ...Option) *Plugin {
2728
p := &Plugin{}
2829

2930
p.PluginName = "govpp"
31+
p.HTTPHandlers = &rest.DefaultPlugin
3032
p.StatusCheck = &statuscheck.DefaultPlugin
3133
p.Resync = &resync.DefaultPlugin
3234

plugins/govppmux/plugin_impl_govppmux.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/ligato/cn-infra/logging"
3232
"github.com/ligato/cn-infra/logging/measure"
3333
"github.com/ligato/cn-infra/logging/measure/model/apitrace"
34+
"github.com/ligato/cn-infra/rpc/rest"
3435
"github.com/pkg/errors"
3536

3637
"github.com/ligato/vpp-agent/plugins/govppmux/vppcalls"
@@ -79,8 +80,9 @@ type Plugin struct {
7980
// so that they do not mix with other plugin fields.
8081
type Deps struct {
8182
infra.PluginDeps
82-
StatusCheck statuscheck.PluginStatusWriter
83-
Resync *resync.Plugin
83+
HTTPHandlers rest.HTTPHandlers
84+
StatusCheck statuscheck.PluginStatusWriter
85+
Resync *resync.Plugin
8486
}
8587

8688
// Config groups the configurable parameter of GoVpp.
@@ -152,6 +154,9 @@ func (p *Plugin) Init() error {
152154
p.Log.Info("VPP API trace enabled")
153155
}
154156

157+
// register REST API handlers
158+
p.registerHandlers(p.HTTPHandlers)
159+
155160
if p.vppAdapter == nil {
156161
address := p.config.BinAPISocketPath
157162
useShm := disabledSocketClient || p.config.ConnectViaShm

plugins/govppmux/rest.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) 2019 Cisco and/or its affiliates.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at:
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package govppmux
16+
17+
import (
18+
"net/http"
19+
20+
"github.com/ligato/cn-infra/rpc/rest"
21+
"github.com/unrolled/render"
22+
)
23+
24+
// registerHandlers registers all supported REST APIs.
25+
func (s *Plugin) registerHandlers(http rest.HTTPHandlers) {
26+
if http == nil {
27+
s.Log.Debug("No http handler provided, skipping registration of REST handlers")
28+
return
29+
}
30+
http.RegisterHTTPHandler("/govppmux/stats", s.statsHandler, "GET")
31+
}
32+
33+
func (s *Plugin) statsHandler(formatter *render.Render) http.HandlerFunc {
34+
return func(w http.ResponseWriter, req *http.Request) {
35+
if err := formatter.JSON(w, http.StatusOK, GetStats()); err != nil {
36+
s.Log.Warnf("stats handler errored: %v", err)
37+
}
38+
}
39+
}

plugins/govppmux/stats.go

+54-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ var (
2828
)
2929

3030
func init() {
31+
stats.Errors = make(metrics.Calls)
3132
stats.Messages = make(metrics.Calls)
33+
stats.Replies = make(metrics.Calls)
3234
}
3335

3436
func GetStats() *Stats {
@@ -42,10 +44,18 @@ func GetStats() *Stats {
4244
type Stats struct {
4345
ChannelsCreated uint64
4446
ChannelsOpen uint64
45-
RequestsSent uint64
46-
RequestsFailed uint64
47-
AllMessages metrics.CallStats
48-
Messages metrics.Calls
47+
48+
RequestsSent uint64
49+
RequestsDone uint64
50+
RequestsErrors uint64
51+
RequestReplies uint64
52+
53+
Errors metrics.Calls
54+
55+
AllMessages metrics.CallStats
56+
Messages metrics.Calls
57+
58+
Replies metrics.Calls
4959
}
5060

5161
func (s *Stats) getOrCreateMessage(msg string) *metrics.CallStats {
@@ -69,6 +79,46 @@ func trackMsgRequestDur(m string, d time.Duration) {
6979
statsMu.Unlock()
7080
}
7181

82+
func (s *Stats) getOrCreateReply(msg string) *metrics.CallStats {
83+
statsMu.RLock()
84+
ms, ok := s.Replies[msg]
85+
statsMu.RUnlock()
86+
if !ok {
87+
ms = &metrics.CallStats{Name: msg}
88+
statsMu.Lock()
89+
s.Replies[msg] = ms
90+
statsMu.Unlock()
91+
}
92+
return ms
93+
}
94+
95+
func trackMsgReply(m string) {
96+
ms := stats.getOrCreateReply(m)
97+
statsMu.Lock()
98+
ms.Increment(0)
99+
statsMu.Unlock()
100+
}
101+
102+
func (s *Stats) getOrCreateError(msg string) *metrics.CallStats {
103+
statsMu.RLock()
104+
ms, ok := s.Errors[msg]
105+
statsMu.RUnlock()
106+
if !ok {
107+
ms = &metrics.CallStats{Name: msg}
108+
statsMu.Lock()
109+
s.Errors[msg] = ms
110+
statsMu.Unlock()
111+
}
112+
return ms
113+
}
114+
115+
func trackError(m string) {
116+
ms := stats.getOrCreateError(m)
117+
statsMu.Lock()
118+
ms.Increment(0)
119+
statsMu.Unlock()
120+
}
121+
72122
func init() {
73123
expvar.Publish("govppstats", expvar.Func(func() interface{} {
74124
return GetStats()

plugins/vpp/ifplugin/interface_state.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ var (
3939
// StateUpdateDelay defines delay before dumping states
4040
StateUpdateDelay = time.Second * 3
4141

42-
disableInterfaceStats = os.Getenv("DISABLE_INTERFACE_STATS") != ""
42+
disableInterfaceStats = os.Getenv("DISABLE_INTERFACE_STATS") != ""
43+
disableStatusPublishing = os.Getenv("DISABLE_STATUS_PUBLISHING") != ""
4344
)
4445

4546
// InterfaceStateUpdater holds state data of all VPP interfaces.
@@ -113,14 +114,18 @@ func (c *InterfaceStateUpdater) Init(ctx context.Context, logger logging.PluginL
113114

114115
// Periodically read VPP counters and combined counters for VPP statistics
115116
if disableInterfaceStats {
116-
c.log.Warnf("reading interface stats is disabled!")
117+
c.log.Warnf("reading interface stats is DISABLED!")
117118
} else if readCounters {
118119
c.wg.Add(1)
119120
go c.startReadingCounters(childCtx)
120121
}
121122

122-
c.wg.Add(1)
123-
go c.startUpdatingIfStateDetails(childCtx)
123+
if disableStatusPublishing {
124+
c.log.Warnf("publishing interface status is DISABLED!")
125+
} else {
126+
c.wg.Add(1)
127+
go c.startUpdatingIfStateDetails(childCtx)
128+
}
124129

125130
return nil
126131
}

0 commit comments

Comments
 (0)