Introduce StatsAPI and it's initial implementation 54/15454/7
authorOndrej Fabry <ofabry@cisco.com>
Mon, 22 Oct 2018 12:45:21 +0000 (14:45 +0200)
committerOndrej Fabry <ofabry@cisco.com>
Mon, 22 Oct 2018 12:56:09 +0000 (14:56 +0200)
- this implementation is basically Go wrapper around VPP's vppapiclient C library

Change-Id: I6f53dc3e228868834bf3a8a00c686ad05e22f3dd
Signed-off-by: Ondrej Fabry <ofabry@cisco.com>
14 files changed:
.gitignore
adapter/mock/mock_adapter.go
adapter/stats_api.go [new file with mode: 0644]
adapter/vpp_api.go [moved from adapter/adapter.go with 84% similarity]
adapter/vppapiclient/doc.go [new file with mode: 0644]
adapter/vppapiclient/stat_client.go [new file with mode: 0644]
adapter/vppapiclient/stat_client_stub.go [new file with mode: 0644]
adapter/vppapiclient/vppapiclient.go [moved from adapter/vppapiclient/vppapiclient_adapter.go with 56% similarity]
adapter/vppapiclient/vppapiclient_stub.go [moved from adapter/vppapiclient/empty_adapter.go with 52% similarity]
core/connection.go
core/request_handler.go
examples/cmd/stats-api/stats_api.go [new file with mode: 0644]
examples/cmd/stats-client/stats_client.go
govpp.go

index c10a9ca..7ec04f8 100644 (file)
@@ -7,6 +7,7 @@ cmd/binapi-generator/binapi-generator
 examples/cmd/perf-bench/perf-bench
 examples/cmd/simple-client/simple-client
 examples/cmd/stats-client/stats-client
+examples/cmd/stats-api/stats-api
 
 # extras
 extras/libmemif/examples/gopacket/gopacket
index cdf2081..9783651 100644 (file)
@@ -37,7 +37,7 @@ const (
        useReplyHandlers           // use reply handler
 )
 
-// VppAdapter represents a mock VPP adapter that can be used for unit/integration testing instead of the vppapiclient adapter.
+// VppAPI represents a mock VPP adapter that can be used for unit/integration testing instead of the vppapiclient adapter.
 type VppAdapter struct {
        callback adapter.MsgCallback
 
@@ -107,8 +107,8 @@ func (a *VppAdapter) Connect() error {
 }
 
 // Disconnect emulates disconnecting the process from VPP.
-func (a *VppAdapter) Disconnect() {
-       // no op
+func (a *VppAdapter) Disconnect() error {
+       return nil
 }
 
 // GetMsgNameByID returns message name for specified message ID.
diff --git a/adapter/stats_api.go b/adapter/stats_api.go
new file mode 100644 (file)
index 0000000..8815aae
--- /dev/null
@@ -0,0 +1,86 @@
+package adapter
+
+// StatsAPI provides connection to VPP stats API.
+type StatsAPI interface {
+       // Connect establishes client connection to the stats API.
+       Connect() error
+
+       // Disconnect terminates client connection.
+       Disconnect() error
+
+       // ListStats lists names for all stats.
+       ListStats(patterns ...string) (statNames []string, err error)
+
+       // DumpStats dumps all stat entries.
+       DumpStats(patterns ...string) ([]*StatEntry, error)
+}
+
+// StatType represents type of stat directory and simply
+// defines what type of stat data is stored in the stat entry.
+type StatType int
+
+const (
+       _ StatType = iota
+       ScalarIndex
+       SimpleCounterVector
+       CombinedCounterVector
+       ErrorIndex
+)
+
+func (d StatType) String() string {
+       switch d {
+       case ScalarIndex:
+               return "ScalarIndex"
+       case SimpleCounterVector:
+               return "SimpleCounterVector"
+       case CombinedCounterVector:
+               return "CombinedCounterVector"
+       case ErrorIndex:
+               return "ErrorIndex"
+       }
+       return "UnknownStatType"
+}
+
+// StatEntry represents single stat entry. The type of stat stored in Data
+// is defined by Type.
+type StatEntry struct {
+       Name string
+       Type StatType
+       Data Stat
+}
+
+// Counter represents simple counter with single value.
+type Counter uint64
+
+// CombinedCounter represents counter with two values, for packet count and bytes count.
+type CombinedCounter struct {
+       Packets Counter
+       Bytes   Counter
+}
+
+// ScalarStat represents stat for ScalarIndex.
+type ScalarStat float64
+
+// ErrorStat represents stat for ErrorIndex.
+type ErrorStat uint64
+
+// SimpleCounterStat represents stat for SimpleCounterVector.
+// The outer array represents workers and the inner array represents sw_if_index.
+// Values should be aggregated per interface for every worker.
+type SimpleCounterStat [][]Counter
+
+// CombinedCounterStat represents stat for CombinedCounterVector.
+// The outer array represents workers and the inner array represents sw_if_index.
+// Values should be aggregated per interface for every worker.
+type CombinedCounterStat [][]CombinedCounter
+
+// Data represents some type of stat which is usually defined by StatType.
+type Stat interface {
+       // isStat is unexported to limit implementations of Data interface to this package,
+       isStat()
+}
+
+func (ScalarStat) isStat()          {}
+func (ErrorStat) isStat()           {}
+func (SimpleCounterStat) isStat()   {}
+func (CombinedCounterStat) isStat() {}
similarity index 84%
rename from adapter/adapter.go
rename to adapter/vpp_api.go
index aa34329..7d14633 100644 (file)
@@ -24,16 +24,17 @@ var ErrNotImplemented = errors.New("not implemented for this OS")
 // MsgCallback defines func signature for message callback.
 type MsgCallback func(msgID uint16, data []byte)
 
-// VppAdapter provides connection to VPP. It is responsible for sending and receiving of binary-encoded messages to/from VPP.
-type VppAdapter interface {
+// VppAPI provides connection to VPP binary API.
+// It is responsible for sending and receiving of binary-encoded messages to/from VPP.
+type VppAPI interface {
        // Connect connects the process to VPP.
        Connect() error
 
        // Disconnect disconnects the process from VPP.
-       Disconnect()
+       Disconnect() error
 
        // GetMsgID returns a runtime message ID for the given message name and CRC.
-       GetMsgID(msgName string, msgCrc string) (uint16, error)
+       GetMsgID(msgName string, msgCrc string) (msgID uint16, err error)
 
        // SendMsg sends a binary-encoded message to VPP.
        SendMsg(context uint32, data []byte) error
diff --git a/adapter/vppapiclient/doc.go b/adapter/vppapiclient/doc.go
new file mode 100644 (file)
index 0000000..6505498
--- /dev/null
@@ -0,0 +1,18 @@
+// Copyright (c) 2018 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package vppapiclient is the default VPP adapter being used for
+// the connection to VPP binary & stats API via shared memory.
+// It is essentially Go wrapper for the VPP vppapiclient library written in C.
+package vppapiclient
diff --git a/adapter/vppapiclient/stat_client.go b/adapter/vppapiclient/stat_client.go
new file mode 100644 (file)
index 0000000..5f3b932
--- /dev/null
@@ -0,0 +1,279 @@
+// Copyright (c) 2018 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build !windows,!darwin
+
+package vppapiclient
+
+/*
+#cgo CFLAGS: -DPNG_DEBUG=1
+#cgo LDFLAGS: -lvppapiclient
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <vpp-api/client/vppapiclient.h>
+#include <vpp-api/client/stat_client.h>
+
+static int
+govpp_stat_connect(char *socket_name)
+{
+       return stat_segment_connect(socket_name);
+}
+
+static void
+govpp_stat_disconnect()
+{
+    stat_segment_disconnect();
+}
+
+static uint32_t*
+govpp_stat_segment_ls(uint8_t ** pattern)
+{
+       return stat_segment_ls(pattern);
+}
+
+static int
+govpp_stat_segment_vec_len(void *vec)
+{
+       return stat_segment_vec_len(vec);
+}
+
+static void
+govpp_stat_segment_vec_free(void *vec)
+{
+       stat_segment_vec_free(vec);
+}
+
+static char*
+govpp_stat_segment_dir_index_to_name(uint32_t *dir, uint32_t index)
+{
+       return stat_segment_index_to_name(dir[index]);
+}
+
+static stat_segment_data_t*
+govpp_stat_segment_dump(uint32_t *counter_vec)
+{
+       return stat_segment_dump(counter_vec);
+}
+
+static stat_segment_data_t
+govpp_stat_segment_dump_index(stat_segment_data_t *data, int index)
+{
+       return data[index];
+}
+
+static int
+govpp_stat_segment_data_type(stat_segment_data_t *data)
+{
+       return data->type;
+}
+
+static double
+govpp_stat_segment_data_get_scalar_value(stat_segment_data_t *data)
+{
+       return data->scalar_value;
+}
+
+static double
+govpp_stat_segment_data_get_error_value(stat_segment_data_t *data)
+{
+       return data->error_value;
+}
+
+static uint64_t**
+govpp_stat_segment_data_get_simple_counter(stat_segment_data_t *data)
+{
+       return data->simple_counter_vec;
+}
+
+static uint64_t*
+govpp_stat_segment_data_get_simple_counter_index(stat_segment_data_t *data, int index)
+{
+       return data->simple_counter_vec[index];
+}
+
+static uint64_t
+govpp_stat_segment_data_get_simple_counter_index_value(stat_segment_data_t *data, int index, int index2)
+{
+       return data->simple_counter_vec[index][index2];
+}
+
+static vlib_counter_t**
+govpp_stat_segment_data_get_combined_counter(stat_segment_data_t *data)
+{
+       return data->combined_counter_vec;
+}
+
+static vlib_counter_t*
+govpp_stat_segment_data_get_combined_counter_index(stat_segment_data_t *data, int index)
+{
+       return data->combined_counter_vec[index];
+}
+
+static uint64_t
+govpp_stat_segment_data_get_combined_counter_index_packets(stat_segment_data_t *data, int index, int index2)
+{
+       return data->combined_counter_vec[index][index2].packets;
+}
+
+static uint64_t
+govpp_stat_segment_data_get_combined_counter_index_bytes(stat_segment_data_t *data, int index, int index2)
+{
+       return data->combined_counter_vec[index][index2].bytes;
+}
+
+static void
+govpp_stat_segment_data_free(stat_segment_data_t *data)
+{
+       stat_segment_data_free(data);
+}
+
+static uint8_t**
+govpp_stat_segment_string_vector(uint8_t ** string_vector, char *string)
+{
+       return stat_segment_string_vector(string_vector, string);
+}
+*/
+import "C"
+import (
+       "fmt"
+       "os"
+       "unsafe"
+
+       "git.fd.io/govpp.git/adapter"
+)
+
+var (
+       // DefaultStatSocket is the default path for the VPP stat socket file.
+       DefaultStatSocket = "/run/vpp/stats.sock"
+)
+
+// StatClient is the default implementation of StatsAPI.
+type StatClient struct {
+       socketName string
+}
+
+// NewStatClient returns new VPP stats API client.
+func NewStatClient(socketName string) *StatClient {
+       return &StatClient{
+               socketName: socketName,
+       }
+}
+
+func (c *StatClient) Connect() error {
+       var sockName string
+
+       if c.socketName == "" {
+               sockName = DefaultStatSocket
+       } else {
+               sockName = c.socketName
+       }
+
+       rc := C.govpp_stat_connect(C.CString(sockName))
+       if rc != 0 {
+               return fmt.Errorf("connecting to VPP stats API failed (rc=%v)", rc)
+       }
+
+       return nil
+}
+
+func (c *StatClient) Disconnect() error {
+       C.govpp_stat_disconnect()
+       return nil
+}
+
+func (c *StatClient) ListStats(patterns ...string) (stats []string, err error) {
+       dir := C.govpp_stat_segment_ls(convertStringSlice(patterns))
+       defer C.govpp_stat_segment_vec_free(unsafe.Pointer(dir))
+
+       l := C.govpp_stat_segment_vec_len(unsafe.Pointer(dir))
+       for i := 0; i < int(l); i++ {
+               nameChar := C.govpp_stat_segment_dir_index_to_name(dir, C.uint32_t(i))
+               stats = append(stats, C.GoString(nameChar))
+               C.free(unsafe.Pointer(nameChar))
+       }
+
+       return stats, nil
+}
+
+func (c *StatClient) DumpStats(patterns ...string) (stats []*adapter.StatEntry, err error) {
+       dir := C.govpp_stat_segment_ls(convertStringSlice(patterns))
+       defer C.govpp_stat_segment_vec_free(unsafe.Pointer(dir))
+
+       dump := C.govpp_stat_segment_dump(dir)
+       defer C.govpp_stat_segment_data_free(dump)
+
+       l := C.govpp_stat_segment_vec_len(unsafe.Pointer(dump))
+       for i := 0; i < int(l); i++ {
+               v := C.govpp_stat_segment_dump_index(dump, C.int(i))
+               nameChar := v.name
+               name := C.GoString(nameChar)
+               typ := adapter.StatType(C.govpp_stat_segment_data_type(&v))
+
+               stat := &adapter.StatEntry{
+                       Name: name,
+                       Type: typ,
+               }
+
+               switch typ {
+               case adapter.ScalarIndex:
+                       stat.Data = adapter.ScalarStat(C.govpp_stat_segment_data_get_scalar_value(&v))
+
+               case adapter.ErrorIndex:
+                       stat.Data = adapter.ErrorStat(C.govpp_stat_segment_data_get_error_value(&v))
+
+               case adapter.SimpleCounterVector:
+                       length := int(C.govpp_stat_segment_vec_len(unsafe.Pointer(C.govpp_stat_segment_data_get_simple_counter(&v))))
+                       vector := make([][]adapter.Counter, length)
+                       for k := 0; k < length; k++ {
+                               for j := 0; j < int(C.govpp_stat_segment_vec_len(unsafe.Pointer(C.govpp_stat_segment_data_get_simple_counter_index(&v, _Ctype_int(k))))); j++ {
+                                       vector[k] = append(vector[k], adapter.Counter(C.govpp_stat_segment_data_get_simple_counter_index_value(&v, _Ctype_int(k), _Ctype_int(j))))
+                               }
+                       }
+                       stat.Data = adapter.SimpleCounterStat(vector)
+
+               case adapter.CombinedCounterVector:
+                       length := int(C.govpp_stat_segment_vec_len(unsafe.Pointer(C.govpp_stat_segment_data_get_combined_counter(&v))))
+                       vector := make([][]adapter.CombinedCounter, length)
+                       for k := 0; k < length; k++ {
+                               for j := 0; j < int(C.govpp_stat_segment_vec_len(unsafe.Pointer(C.govpp_stat_segment_data_get_combined_counter_index(&v, _Ctype_int(k))))); j++ {
+                                       vector[k] = append(vector[k], adapter.CombinedCounter{
+                                               Packets: adapter.Counter(C.govpp_stat_segment_data_get_combined_counter_index_packets(&v, _Ctype_int(k), _Ctype_int(j))),
+                                               Bytes:   adapter.Counter(C.govpp_stat_segment_data_get_combined_counter_index_bytes(&v, _Ctype_int(k), _Ctype_int(j))),
+                                       })
+                               }
+                       }
+                       stat.Data = adapter.CombinedCounterStat(vector)
+
+               default:
+                       fmt.Fprintf(os.Stderr, "invalid stat type: %v (%d)", typ, typ)
+                       continue
+
+               }
+
+               stats = append(stats, stat)
+       }
+
+       return stats, nil
+}
+
+func convertStringSlice(strs []string) **C.uint8_t {
+       var arr **C.uint8_t
+       for _, str := range strs {
+               arr = C.govpp_stat_segment_string_vector(arr, C.CString(str))
+       }
+       return arr
+}
diff --git a/adapter/vppapiclient/stat_client_stub.go b/adapter/vppapiclient/stat_client_stub.go
new file mode 100644 (file)
index 0000000..24f7e13
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright (c) 2018 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build windows darwin
+
+package vppapiclient
+
+import (
+       "git.fd.io/govpp.git/adapter"
+)
+
+// StatClient is just an stub adapter that does nothing. It builds only on Windows and OSX, where the real
+// VPP stats API client adapter does not build. Its sole purpose is to make the compiler happy on Windows and OSX.
+type StatClient struct{}
+
+func NewStatClient(socketName string) *StatClient {
+       return new(StatClient)
+}
+
+func (*StatClient) Connect() error {
+       return adapter.ErrNotImplemented
+}
+
+func (*StatClient) Disconnect() error {
+       return nil
+}
+
+func (*StatClient) ListStats(patterns ...string) (statNames []string, err error) {
+       return nil, adapter.ErrNotImplemented
+}
+
+func (*StatClient) DumpStats(patterns ...string) ([]*adapter.StatEntry, error) {
+       return nil, adapter.ErrNotImplemented
+}
similarity index 56%
rename from adapter/vppapiclient/vppapiclient_adapter.go
rename to adapter/vppapiclient/vppapiclient.go
index e62bccd..34ad199 100644 (file)
@@ -14,8 +14,6 @@
 
 // +build !windows,!darwin
 
-// Package vppapiclient is the default VPP adapter being used for the connection with VPP via shared memory.
-// It is based on the communication with the vppapiclient VPP library written in C via CGO.
 package vppapiclient
 
 /*
@@ -41,7 +39,7 @@ typedef struct __attribute__((__packed__)) _reply_header {
 } reply_header_t;
 
 static void
-govpp_msg_callback (unsigned char *data, int size)
+govpp_msg_callback(unsigned char *data, int size)
 {
     reply_header_t *header = ((reply_header_t *)data);
     go_msg_callback(ntohs(header->msg_id), data, size);
@@ -56,13 +54,13 @@ govpp_send(uint32_t context, void *data, size_t size)
 }
 
 static int
-govpp_connect (char *shm)
+govpp_connect(char *shm)
 {
     return vac_connect("govpp", shm, govpp_msg_callback, 32);
 }
 
 static int
-govvp_disconnect()
+govpp_disconnect()
 {
     return vac_disconnect();
 }
@@ -87,32 +85,34 @@ import (
 )
 
 const (
-       // watchedFolder is a folder where vpp's shared memory is supposed to be created.
-       // File system events are monitored in this folder.
-       watchedFolder = "/dev/shm/"
-       // watchedFile is a default name of the file in the watchedFolder. Once the file is present,
-       // the vpp is ready to accept a new connection.
-       watchedFile = "vpe-api"
+       // shmDir is a directory where shared memory is supposed to be created.
+       shmDir = "/dev/shm/"
+       // vppShmFile is a default name of the file in the shmDir.
+       vppShmFile = "vpe-api"
 )
 
-// vppAPIClientAdapter is the opaque context of the adapter.
-type vppAPIClientAdapter struct {
-       shmPrefix string
-       callback  adapter.MsgCallback
-}
+// global VPP binary API client adapter context
+var client *VppClient
 
-var vppClient *vppAPIClientAdapter // global vpp API client adapter context
+// VppClient is the default implementation of the VppAPI.
+type VppClient struct {
+       shmPrefix   string
+       msgCallback adapter.MsgCallback
+}
 
-// NewVppAdapter returns a new vpp API client adapter.
-func NewVppAdapter(shmPrefix string) adapter.VppAdapter {
-       return &vppAPIClientAdapter{
+// NewVppClient returns a new VPP binary API client.
+func NewVppClient(shmPrefix string) *VppClient {
+       return &VppClient{
                shmPrefix: shmPrefix,
        }
 }
 
 // Connect connects the process to VPP.
-func (a *vppAPIClientAdapter) Connect() error {
-       vppClient = a
+func (a *VppClient) Connect() error {
+       if client != nil {
+               return fmt.Errorf("already connected to binary API, disconnect first")
+       }
+
        var rc _Ctype_int
        if a.shmPrefix == "" {
                rc = C.govpp_connect(nil)
@@ -121,18 +121,27 @@ func (a *vppAPIClientAdapter) Connect() error {
                rc = C.govpp_connect(shm)
        }
        if rc != 0 {
-               return fmt.Errorf("unable to connect to VPP (error=%d)", rc)
+               return fmt.Errorf("connecting to VPP binary API failed (rc=%v)", rc)
        }
+
+       client = a
        return nil
 }
 
 // Disconnect disconnects the process from VPP.
-func (a *vppAPIClientAdapter) Disconnect() {
-       C.govvp_disconnect()
+func (a *VppClient) Disconnect() error {
+       client = nil
+
+       rc := C.govpp_disconnect()
+       if rc != 0 {
+               return fmt.Errorf("disconnecting from VPP binary API failed (rc=%v)", rc)
+       }
+
+       return nil
 }
 
 // GetMsgID returns a runtime message ID for the given message name and CRC.
-func (a *vppAPIClientAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
+func (a *VppClient) GetMsgID(msgName string, msgCrc string) (uint16, error) {
        nameAndCrc := C.CString(msgName + "_" + msgCrc)
        defer C.free(unsafe.Pointer(nameAndCrc))
 
@@ -146,45 +155,56 @@ func (a *vppAPIClientAdapter) GetMsgID(msgName string, msgCrc string) (uint16, e
 }
 
 // SendMsg sends a binary-encoded message to VPP.
-func (a *vppAPIClientAdapter) SendMsg(context uint32, data []byte) error {
+func (a *VppClient) SendMsg(context uint32, data []byte) error {
        rc := C.govpp_send(C.uint32_t(context), unsafe.Pointer(&data[0]), C.size_t(len(data)))
        if rc != 0 {
-               return fmt.Errorf("unable to send the message (error=%d)", rc)
+               return fmt.Errorf("unable to send the message (rc=%v)", rc)
        }
        return nil
 }
 
-// SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from VPP.
-func (a *vppAPIClientAdapter) SetMsgCallback(cb adapter.MsgCallback) {
-       a.callback = cb
+// SetMsgCallback sets a callback function that will be called by the adapter
+// whenever a message comes from VPP.
+func (a *VppClient) SetMsgCallback(cb adapter.MsgCallback) {
+       a.msgCallback = cb
 }
 
 // WaitReady blocks until shared memory for sending
 // binary api calls is present on the file system.
-func (a *vppAPIClientAdapter) WaitReady() error {
-       // Path to the shared memory segment
+func (a *VppClient) WaitReady() error {
        var path string
+
+       // join the path to the shared memory segment
        if a.shmPrefix == "" {
-               path = filepath.Join(watchedFolder, watchedFile)
+               path = filepath.Join(shmDir, vppShmFile)
        } else {
-               path = filepath.Join(watchedFolder, a.shmPrefix+"-"+watchedFile)
+               path = filepath.Join(shmDir, a.shmPrefix+"-"+vppShmFile)
        }
 
-       // Watch folder if file does not exist yet
-       if !fileExists(path) {
-               watcher, err := fsnotify.NewWatcher()
-               if err != nil {
-                       return err
-               }
-               defer watcher.Close()
+       // check if file at the path exists
+       if _, err := os.Stat(path); err == nil {
+               // file exists, we are ready
+               return nil
+       } else if !os.IsNotExist(err) {
+               return err
+       }
 
-               if err := watcher.Add(watchedFolder); err != nil {
-                       return err
-               }
+       // file does not exist, start watching folder
+       watcher, err := fsnotify.NewWatcher()
+       if err != nil {
+               return err
+       }
+       defer watcher.Close()
+
+       if err := watcher.Add(shmDir); err != nil {
+               return err
+       }
 
-               for {
-                       ev := <-watcher.Events
-                       if ev.Name == path && (ev.Op&fsnotify.Create) == fsnotify.Create {
+       for {
+               ev := <-watcher.Events
+               if ev.Name == path {
+                       if (ev.Op & fsnotify.Create) == fsnotify.Create {
+                               // file was created, we are ready
                                break
                        }
                }
@@ -193,20 +213,11 @@ func (a *vppAPIClientAdapter) WaitReady() error {
        return nil
 }
 
-func fileExists(name string) bool {
-       if _, err := os.Stat(name); err != nil {
-               if os.IsNotExist(err) {
-                       return false
-               }
-       }
-       return true
-}
-
 //export go_msg_callback
 func go_msg_callback(msgID C.uint16_t, data unsafe.Pointer, size C.size_t) {
        // convert unsafe.Pointer to byte slice
-       slice := &reflect.SliceHeader{Data: uintptr(data), Len: int(size), Cap: int(size)}
-       byteArr := *(*[]byte)(unsafe.Pointer(slice))
+       sliceHeader := &reflect.SliceHeader{Data: uintptr(data), Len: int(size), Cap: int(size)}
+       byteSlice := *(*[]byte)(unsafe.Pointer(sliceHeader))
 
-       vppClient.callback(uint16(msgID), byteArr)
+       client.msgCallback(uint16(msgID), byteSlice)
 }
similarity index 52%
rename from adapter/vppapiclient/empty_adapter.go
rename to adapter/vppapiclient/vppapiclient_stub.go
index 7514048..6de89a7 100644 (file)
 
 // +build windows darwin
 
-/*
-       This is just an empty adapter that does nothing. It builds only on Windows and OSX, where the real
-       VPP API client adapter does not build. Its sole purpose is to make the compiler happy on Windows and OSX.
-*/
-
 package vppapiclient
 
 import (
        "git.fd.io/govpp.git/adapter"
 )
 
-type vppAPIClientAdapter struct{}
+// VppClient is just an stub adapter that does nothing. It builds only on Windows and OSX, where the real
+// VPP binary API client adapter does not build. Its sole purpose is to make the compiler happy on Windows and OSX.
+type VppClient struct{}
 
-func NewVppAdapter(string) adapter.VppAdapter {
-       return &vppAPIClientAdapter{}
+func NewVppAdapter(string) *VppClient {
+       return &VppClient{}
 }
 
-func (a *vppAPIClientAdapter) Connect() error {
+func (a *VppClient) Connect() error {
        return adapter.ErrNotImplemented
 }
 
-func (a *vppAPIClientAdapter) Disconnect() {
-       // no op
+func (a *VppClient) Disconnect() error {
+       return nil
 }
 
-func (a *vppAPIClientAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) {
+func (a *VppClient) GetMsgID(msgName string, msgCrc string) (uint16, error) {
        return 0, nil
 }
 
-func (a *vppAPIClientAdapter) SendMsg(clientID uint32, data []byte) error {
+func (a *VppClient) SendMsg(clientID uint32, data []byte) error {
        return nil
 }
 
-func (a *vppAPIClientAdapter) SetMsgCallback(cb adapter.MsgCallback) {
+func (a *VppClient) SetMsgCallback(cb adapter.MsgCallback) {
        // no op
 }
 
-func (a *vppAPIClientAdapter) WaitReady() error {
-       return nil
+func (a *VppClient) WaitReady() error {
+       return adapter.ErrNotImplemented
 }
index 7d014ce..c4048f0 100644 (file)
@@ -72,8 +72,9 @@ var (
 
 // Connection represents a shared memory connection to VPP via vppAdapter.
 type Connection struct {
-       vpp       adapter.VppAdapter // VPP adapter
-       connected uint32             // non-zero if the adapter is connected to VPP
+       vppClient adapter.VppAPI // VPP binary API client adapter
+
+       vppConnected uint32 // non-zero if the adapter is connected to VPP
 
        codec  *codec.MsgCodec        // message codec
        msgIDs map[string]uint16      // map of message IDs indexed by message name + CRC
@@ -93,24 +94,24 @@ type Connection struct {
        lastReply     time.Time  // time of the last received reply from VPP
 }
 
-func newConnection(vpp adapter.VppAdapter) *Connection {
+func newConnection(binapi adapter.VppAPI) *Connection {
        c := &Connection{
-               vpp:           vpp,
+               vppClient:     binapi,
                codec:         &codec.MsgCodec{},
                msgIDs:        make(map[string]uint16),
                msgMap:        make(map[uint16]api.Message),
                channels:      make(map[uint16]*Channel),
                subscriptions: make(map[uint16][]*subscriptionCtx),
        }
-       vpp.SetMsgCallback(c.msgCallback)
+       binapi.SetMsgCallback(c.msgCallback)
        return c
 }
 
 // Connect connects to VPP using specified VPP adapter and returns the connection handle.
 // This call blocks until VPP is connected, or an error occurs. Only one connection attempt will be performed.
-func Connect(vppAdapter adapter.VppAdapter) (*Connection, error) {
+func Connect(binapi adapter.VppAPI) (*Connection, error) {
        // create new connection handle
-       c, err := createConnection(vppAdapter)
+       c, err := createConnection(binapi)
        if err != nil {
                return nil, err
        }
@@ -127,9 +128,9 @@ func Connect(vppAdapter adapter.VppAdapter) (*Connection, error) {
 // and ConnectionState channel. This call does not block until connection is established, it
 // returns immediately. The caller is supposed to watch the returned ConnectionState channel for
 // Connected/Disconnected events. In case of disconnect, the library will asynchronously try to reconnect.
-func AsyncConnect(vppAdapter adapter.VppAdapter) (*Connection, chan ConnectionEvent, error) {
+func AsyncConnect(binapi adapter.VppAPI) (*Connection, chan ConnectionEvent, error) {
        // create new connection handle
-       c, err := createConnection(vppAdapter)
+       c, err := createConnection(binapi)
        if err != nil {
                return nil, nil, err
        }
@@ -150,14 +151,14 @@ func (c *Connection) Disconnect() {
        connLock.Lock()
        defer connLock.Unlock()
 
-       if c.vpp != nil {
+       if c.vppClient != nil {
                c.disconnectVPP()
        }
        conn = nil
 }
 
 // newConnection returns new connection handle.
-func createConnection(vppAdapter adapter.VppAdapter) (*Connection, error) {
+func createConnection(binapi adapter.VppAPI) (*Connection, error) {
        connLock.Lock()
        defer connLock.Unlock()
 
@@ -165,7 +166,7 @@ func createConnection(vppAdapter adapter.VppAdapter) (*Connection, error) {
                return nil, errors.New("only one connection per process is supported")
        }
 
-       conn = newConnection(vppAdapter)
+       conn = newConnection(binapi)
 
        return conn, nil
 }
@@ -175,19 +176,19 @@ func (c *Connection) connectVPP() error {
        log.Debug("Connecting to VPP..")
 
        // blocking connect
-       if err := c.vpp.Connect(); err != nil {
+       if err := c.vppClient.Connect(); err != nil {
                return err
        }
 
        log.Debugf("Connected to VPP.")
 
        if err := c.retrieveMessageIDs(); err != nil {
-               c.vpp.Disconnect()
+               c.vppClient.Disconnect()
                return fmt.Errorf("VPP is incompatible: %v", err)
        }
 
        // store connected state
-       atomic.StoreUint32(&c.connected, 1)
+       atomic.StoreUint32(&c.vppConnected, 1)
 
        return nil
 }
@@ -272,7 +273,7 @@ func (c *Connection) retrieveMessageIDs() (err error) {
        msgs := api.GetAllMessages()
 
        for name, msg := range msgs {
-               msgID, err := c.vpp.GetMsgID(msg.GetMessageName(), msg.GetCrcString())
+               msgID, err := c.vppClient.GetMsgID(msg.GetMessageName(), msg.GetCrcString())
                if err != nil {
                        return err
                }
@@ -296,14 +297,14 @@ func (c *Connection) retrieveMessageIDs() (err error) {
 
        // fallback for control ping when vpe package is not imported
        if c.pingReqID == 0 {
-               c.pingReqID, err = c.vpp.GetMsgID(msgControlPing.GetMessageName(), msgControlPing.GetCrcString())
+               c.pingReqID, err = c.vppClient.GetMsgID(msgControlPing.GetMessageName(), msgControlPing.GetCrcString())
                if err != nil {
                        return err
                }
                addMsg(c.pingReqID, msgControlPing)
        }
        if c.pingReplyID == 0 {
-               c.pingReplyID, err = c.vpp.GetMsgID(msgControlPingReply.GetMessageName(), msgControlPingReply.GetCrcString())
+               c.pingReplyID, err = c.vppClient.GetMsgID(msgControlPingReply.GetMessageName(), msgControlPingReply.GetCrcString())
                if err != nil {
                        return err
                }
@@ -315,8 +316,8 @@ func (c *Connection) retrieveMessageIDs() (err error) {
 
 // disconnectVPP disconnects from VPP in case it is connected.
 func (c *Connection) disconnectVPP() {
-       if atomic.CompareAndSwapUint32(&c.connected, 1, 0) {
-               c.vpp.Disconnect()
+       if atomic.CompareAndSwapUint32(&c.vppConnected, 1, 0) {
+               c.vppClient.Disconnect()
        }
 }
 
@@ -325,7 +326,7 @@ func (c *Connection) disconnectVPP() {
 func (c *Connection) connectLoop(connChan chan ConnectionEvent) {
        // loop until connected
        for {
-               if err := c.vpp.WaitReady(); err != nil {
+               if err := c.vppClient.WaitReady(); err != nil {
                        log.Warnf("wait ready failed: %v", err)
                }
                if err := c.connectVPP(); err == nil {
@@ -362,7 +363,7 @@ func (c *Connection) healthCheckLoop(connChan chan ConnectionEvent) {
                // sleep until next health check probe period
                time.Sleep(HealthCheckProbeInterval)
 
-               if atomic.LoadUint32(&c.connected) == 0 {
+               if atomic.LoadUint32(&c.vppConnected) == 0 {
                        // Disconnect has been called in the meantime, return the healthcheck - reconnect loop
                        log.Debug("Disconnected on request, exiting health check loop.")
                        return
index e52e262..545f235 100644 (file)
@@ -49,7 +49,7 @@ func (c *Connection) watchRequests(ch *Channel) {
 // processRequest processes a single request received on the request channel.
 func (c *Connection) processRequest(ch *Channel, req *vppRequest) error {
        // check whether we are connected to VPP
-       if atomic.LoadUint32(&c.connected) == 0 {
+       if atomic.LoadUint32(&c.vppConnected) == 0 {
                err := ErrNotConnected
                log.Errorf("processing request failed: %v", err)
                return err
@@ -95,7 +95,7 @@ func (c *Connection) processRequest(ch *Channel, req *vppRequest) error {
        }
 
        // send the request to VPP
-       err = c.vpp.SendMsg(context, data)
+       err = c.vppClient.SendMsg(context, data)
        if err != nil {
                err = fmt.Errorf("unable to send the message: %v", err)
                log.WithFields(logger.Fields{
@@ -118,7 +118,7 @@ func (c *Connection) processRequest(ch *Channel, req *vppRequest) error {
                        "seq_num":  req.seqNum,
                }).Debug(" -> Sending a control ping to VPP.")
 
-               if err := c.vpp.SendMsg(context, pingData); err != nil {
+               if err := c.vppClient.SendMsg(context, pingData); err != nil {
                        log.WithFields(logger.Fields{
                                "context": context,
                                "msg_id":  msgID,
diff --git a/examples/cmd/stats-api/stats_api.go b/examples/cmd/stats-api/stats_api.go
new file mode 100644 (file)
index 0000000..74454ab
--- /dev/null
@@ -0,0 +1,66 @@
+// Copyright (c) 2018 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+       "fmt"
+       "log"
+
+       "git.fd.io/govpp.git/adapter"
+       "git.fd.io/govpp.git/adapter/vppapiclient"
+)
+
+// This example shows how to work with VPP's new stats API.
+
+func main() {
+       fmt.Println("Starting VPP stats API example..")
+
+       client := vppapiclient.NewStatClient(vppapiclient.DefaultStatSocket)
+
+       // connect to stats API
+       if err := client.Connect(); err != nil {
+               log.Fatalln("connecting client failed:", err)
+       }
+       defer client.Disconnect()
+
+       // list stats by patterns
+       // you can omit parameters to list all stats
+       list, err := client.ListStats("/if", "/sys")
+       if err != nil {
+               log.Fatalln("listing stats failed:", err)
+       }
+
+       for _, stat := range list {
+               fmt.Printf(" - %v\n", stat)
+       }
+       fmt.Printf("listed %d stats\n", len(list))
+
+       // dump stats by patterns to retrieve stats with the stats data
+       stats, err := client.DumpStats()
+       if err != nil {
+               log.Fatalln("dumping stats failed:", err)
+       }
+
+       for _, stat := range stats {
+               switch data := stat.Data.(type) {
+               case adapter.ErrorStat:
+                       if data == 0 {
+                               // skip printing errors with 0 value
+                               continue
+                       }
+               }
+               fmt.Printf(" - %-25s %25v %+v\n", stat.Name, stat.Type, stat.Data)
+       }
+}
index f61f975..7a2c313 100644 (file)
@@ -28,6 +28,15 @@ import (
        "git.fd.io/govpp.git/examples/bin_api/stats"
 )
 
+/*
+
+       IMPORTANT NOTICE!
+
+       The binary API module stats used in this example will be deprecated in VPP 19.01.
+       VPP's new stats API should be used, you can find basic usage of new stats API in example stats-api.
+
+*/
+
 func main() {
        fmt.Println("Starting stats VPP client..")
 
index ff45b78..f679242 100644 (file)
--- a/govpp.go
+++ b/govpp.go
@@ -20,16 +20,28 @@ import (
        "git.fd.io/govpp.git/core"
 )
 
-var vppAdapter adapter.VppAdapter // VPP Adapter that will be used in the subsequent Connect calls
+var (
+       // VPP binary API adapter that will be used in the subsequent Connect calls
+       vppAdapter adapter.VppAPI
+)
+
+func getVppAdapter(shm string) adapter.VppAPI {
+       if vppAdapter == nil {
+               vppAdapter = vppapiclient.NewVppClient(shm)
+       }
+       return vppAdapter
+}
+
+// SetVppAdapter sets the adapter that will be used for connections to VPP in the subsequent `Connect` calls.
+func SetVppAdapter(a adapter.VppAPI) {
+       vppAdapter = a
+}
 
 // Connect connects the govpp core to VPP either using the default VPP Adapter, or using the adapter previously
 // set by SetAdapter (useful mostly just for unit/integration tests with mocked VPP adapter).
 // This call blocks until VPP is connected, or an error occurs. Only one connection attempt will be performed.
 func Connect(shm string) (*core.Connection, error) {
-       if vppAdapter == nil {
-               vppAdapter = vppapiclient.NewVppAdapter(shm)
-       }
-       return core.Connect(vppAdapter)
+       return core.Connect(getVppAdapter(shm))
 }
 
 // AsyncConnect asynchronously connects the govpp core to VPP either using the default VPP Adapter,
@@ -38,13 +50,5 @@ func Connect(shm string) (*core.Connection, error) {
 // supposed to watch the returned ConnectionState channel for Connected/Disconnected events.
 // In case of disconnect, the library will asynchronously try to reconnect.
 func AsyncConnect(shm string) (*core.Connection, chan core.ConnectionEvent, error) {
-       if vppAdapter == nil {
-               vppAdapter = vppapiclient.NewVppAdapter(shm)
-       }
-       return core.AsyncConnect(vppAdapter)
+       return core.AsyncConnect(getVppAdapter(shm))
 }
-
-// SetAdapter sets the adapter that will be used for connections to VPP in the subsequent `Connect` calls.
-func SetAdapter(ad adapter.VppAdapter) {
-       vppAdapter = ad
-}
\ No newline at end of file