From 809b69ea4a90455445c34bbad7d8e5fea5cf3462 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Tue, 17 Sep 2019 12:41:47 +0200 Subject: Optimizations for statsclient - this dramatically improves performance for stats data collection - memory allocation is now done only when stat dirs change - updating prepared stat dir does not need to allocate memory - created integration test for testing stats client - added NumWorkerThreads and VectorRatePerWorker to SystemStats - added ReduceSimpleCounterStatIndex, ReduceCombinedCounterStatIndex for aggregating specific index Change-Id: I702731a69024ab5dd0832bb5cfe2773a987359e5 Signed-off-by: Ondrej Fabry --- examples/stats-client/README.md | 150 +++++++++++++++++++++ examples/stats-client/stats_api.go | 260 +++++++++++++++++++++++++++++++++++++ 2 files changed, 410 insertions(+) create mode 100644 examples/stats-client/README.md create mode 100644 examples/stats-client/stats_api.go (limited to 'examples/stats-client') diff --git a/examples/stats-client/README.md b/examples/stats-client/README.md new file mode 100644 index 0000000..0a44a55 --- /dev/null +++ b/examples/stats-client/README.md @@ -0,0 +1,150 @@ +# Stats Client Example + +This example demonstrates how to retrieve statistics from VPP using [the new Stats API](https://github.com/FDio/vpp/blob/master/src/vpp/stats/stats.md). + +## Requirements + +The following requirements are required to run this example: + +- install VPP **18.10+** (VPP 19.04+ for statsclient) +- enable stats in VPP + +To enable stats add following section to you VPP config: + + ```sh + statseg { + default + per-node-counters on + } + ``` + > The [default socket](https://wiki.fd.io/view/VPP/Command-line_Arguments#.22statseg.22_parameters) is located at `/run/vpp/stats.sock`. + +## Running example + +First build the example: `go build git.fd.io/govpp.git/examples/stats-api`. + +### Higher-level access to stats + +Use commands following commands to retrieve stats that are aggregated and +processed into logical structures from [api package](../../api). + +- `system` to retrieve system statistics +- `nodes` to retrieve per node statistics +- `interfaces` to retrieve per interface statistics +- `errors` to retrieve error statistics (you can use patterns to filter the errors) + +#### System stats + +Following command will retrieve system stats. +``` +$ ./stats-api system +System stats: &{VectorRate:0 InputRate:0 LastUpdate:32560 LastStatsClear:0 Heartbeat:3255} +``` + +#### Node stats + +Following command will retrieve per node stats. +``` +$ ./stats-api nodes +Listing node stats.. +... + - {NodeIndex:554 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:555 Clocks:189609 Vectors:15 Calls:15 Suspends:0} + - {NodeIndex:556 Clocks:2281847 Vectors:0 Calls:0 Suspends:21} + - {NodeIndex:557 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:558 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:559 Clocks:7094 Vectors:0 Calls:1 Suspends:1} + - {NodeIndex:560 Clocks:88159323916601 Vectors:0 Calls:14066116 Suspends:0} + - {NodeIndex:561 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:562 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:563 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:564 Clocks:447894125 Vectors:0 Calls:0 Suspends:32395} + - {NodeIndex:565 Clocks:1099655497824612 Vectors:0 Calls:40 Suspends:117} + - {NodeIndex:566 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:567 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:568 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:569 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:570 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:571 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:572 Clocks:0 Vectors:0 Calls:0 Suspends:0} +Listed 573 node counters +``` + +#### Interface stats + +Following command will retrieve per interface stats. +``` +$ ./stats-api interfaces +Listing interface stats.. + - {InterfaceIndex:0 RxPackets:0 RxBytes:0 RxErrors:0 TxPackets:0 TxBytes:0 TxErrors:0 RxUnicast:[0 0] RxMulticast:[0 0] RxBroadcast:[0 0] TxUnicastMiss:[0 0] TxMulticast:[0 0] TxBroadcast:[0 0] Drops:0 Punts:0 IP4:0 IP6:0 RxNoBuf:0 RxMiss:0} + - {InterfaceIndex:1 RxPackets:0 RxBytes:0 RxErrors:0 TxPackets:0 TxBytes:0 TxErrors:0 RxUnicast:[0 0] RxMulticast:[0 0] RxBroadcast:[0 0] TxUnicastMiss:[0 0] TxMulticast:[0 0] TxBroadcast:[0 0] Drops:5 Punts:0 IP4:0 IP6:0 RxNoBuf:0 RxMiss:0} + - {InterfaceIndex:2 RxPackets:0 RxBytes:0 RxErrors:0 TxPackets:0 TxBytes:0 TxErrors:0 RxUnicast:[0 0] RxMulticast:[0 0] RxBroadcast:[0 0] TxUnicastMiss:[0 0] TxMulticast:[0 0] TxBroadcast:[0 0] Drops:0 Punts:0 IP4:0 IP6:0 RxNoBuf:0 RxMiss:0} + - {InterfaceIndex:3 RxPackets:0 RxBytes:0 RxErrors:0 TxPackets:0 TxBytes:0 TxErrors:0 RxUnicast:[0 0] RxMulticast:[0 0] RxBroadcast:[0 0] TxUnicastMiss:[0 0] TxMulticast:[0 0] TxBroadcast:[0 0] Drops:0 Punts:0 IP4:0 IP6:0 RxNoBuf:0 RxMiss:0} +Listed 4 interface counters +``` + +#### Error stats + +Following command will retrieve error stats. +Use flag `-all` to include stats with zero values. +``` +$ ./stats-api errors ip +Listing error stats.. ip + - {ip4-input/ip4 spoofed local-address packet drops 15} +Listed 1 (825) error counters +``` + +### Low-level access to stats + +Use commands `ls` and `dump` to list and dump statistics in their raw format +from [adapter package](../../adapter). +Optionally, patterns can be used to filter the results. + +#### List stats + +Following command will list stats matching patterns `/sys/` and `/if/`. +``` +$ ./stats-api ls /sys/ /if/ +Listing stats.. /sys/ /if/ + - /sys/vector_rate + - /sys/input_rate + - /sys/last_update + - /sys/last_stats_clear + - /sys/heartbeat + - /sys/node/clocks + - /sys/node/vectors + - /sys/node/calls + - /sys/node/suspends + - /if/drops + - /if/punt + - /if/ip4 + - /if/ip6 + - /if/rx-no-buf + - /if/rx-miss + - /if/rx-error + - /if/tx-error + - /if/rx + - /if/rx-unicast + - /if/rx-multicast + - /if/rx-broadcast + - /if/tx + - /if/tx-unicast-miss + - /if/tx-multicast + - /if/tx-broadcast +Listed 25 stats +``` + +#### Dump stats + +Following command will dump stats with their types and actual values. +Use flag `-all` to include stats with zero values. +``` +$ ./stats-api dump +Dumping stats.. + - /sys/last_update ScalarIndex 10408 + - /sys/heartbeat ScalarIndex 1041 + - /err/ip4-icmp-error/unknown type ErrorIndex 5 + - /net/route/to CombinedCounterVector [[{Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:0 Bytes:0} {Packets:5 Bytes:420}]] + - /if/drops SimpleCounterVector [[0 5 5]] +Dumped 5 (2798) stats +``` diff --git a/examples/stats-client/stats_api.go b/examples/stats-client/stats_api.go new file mode 100644 index 0000000..288caea --- /dev/null +++ b/examples/stats-client/stats_api.go @@ -0,0 +1,260 @@ +// 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 ( + "flag" + "fmt" + "log" + "os" + "strings" + "time" + + "git.fd.io/govpp.git/adapter" + "git.fd.io/govpp.git/adapter/statsclient" + "git.fd.io/govpp.git/api" + "git.fd.io/govpp.git/core" +) + +// ------------------------------------------------------------------ +// Example - Stats API +// ------------------------------------------------------------------ +// The example stats_api demonstrates how to retrieve stats +// from the VPP using the new stats API. +// ------------------------------------------------------------------ + +var ( + statsSocket = flag.String("socket", statsclient.DefaultSocketName, "Path to VPP stats socket") + dumpAll = flag.Bool("all", false, "Dump all stats including ones with zero values") + pollPeriod = flag.Duration("period", time.Second*5, "Polling interval period") +) + +func init() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "%s: usage [ls|dump|poll|errors|interfaces|nodes|system|buffers] ...\n", os.Args[0]) + flag.PrintDefaults() + os.Exit(1) + } +} + +func main() { + flag.Parse() + skipZeros := !*dumpAll + + var patterns []string + if flag.NArg() > 0 { + patterns = flag.Args()[1:] + } + + client := statsclient.NewStatsClient(*statsSocket) + + c, err := core.ConnectStats(client) + if err != nil { + log.Fatalln("Connecting failed:", err) + } + defer c.Disconnect() + + switch cmd := flag.Arg(0); cmd { + case "system": + stats := new(api.SystemStats) + if err := c.GetSystemStats(stats); err != nil { + log.Fatalln("getting system stats failed:", err) + } + fmt.Printf("System stats: %+v\n", stats) + + case "poll-system": + pollSystem(c) + + case "nodes": + fmt.Println("Listing node stats..") + stats := new(api.NodeStats) + if err := c.GetNodeStats(stats); err != nil { + log.Fatalln("getting node stats failed:", err) + } + + for _, node := range stats.Nodes { + if skipZeros && node.Calls == 0 && node.Suspends == 0 && node.Clocks == 0 && node.Vectors == 0 { + continue + } + fmt.Printf(" - %+v\n", node) + } + fmt.Printf("Listed %d node counters\n", len(stats.Nodes)) + + case "interfaces": + fmt.Println("Listing interface stats..") + stats := new(api.InterfaceStats) + if err := c.GetInterfaceStats(stats); err != nil { + log.Fatalln("getting interface stats failed:", err) + } + for _, iface := range stats.Interfaces { + fmt.Printf(" - %+v\n", iface) + } + fmt.Printf("Listed %d interface counters\n", len(stats.Interfaces)) + + case "poll-interfaces": + pollInterfaces(c) + + case "errors": + fmt.Printf("Listing error stats.. %s\n", strings.Join(patterns, " ")) + stats := new(api.ErrorStats) + if err := c.GetErrorStats(stats); err != nil { + log.Fatalln("getting error stats failed:", err) + } + n := 0 + for _, counter := range stats.Errors { + if skipZeros && counter.Value == 0 { + continue + } + fmt.Printf(" - %v\n", counter) + n++ + } + fmt.Printf("Listed %d (%d) error counters\n", n, len(stats.Errors)) + + case "buffers": + stats := new(api.BufferStats) + if err := c.GetBufferStats(stats); err != nil { + log.Fatalln("getting buffer stats failed:", err) + } + fmt.Printf("Buffer stats: %+v\n", stats) + + case "dump": + fmt.Printf("Dumping stats.. %s\n", strings.Join(patterns, " ")) + + dumpStats(client, patterns, skipZeros) + + case "poll": + fmt.Printf("Polling stats.. %s\n", strings.Join(patterns, " ")) + + pollStats(client, patterns, skipZeros) + + case "list", "ls", "": + fmt.Printf("Listing stats.. %s\n", strings.Join(patterns, " ")) + + listStats(client, patterns) + + default: + fmt.Printf("invalid command: %q\n", cmd) + } +} + +func listStats(client adapter.StatsAPI, patterns []string) { + list, err := client.ListStats(patterns...) + 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)) +} + +func dumpStats(client adapter.StatsAPI, patterns []string, skipZeros bool) { + stats, err := client.DumpStats(patterns...) + if err != nil { + log.Fatalln("dumping stats failed:", err) + } + + n := 0 + for _, stat := range stats { + if skipZeros && (stat.Data == nil || stat.Data.IsZero()) { + continue + } + fmt.Printf(" - %-50s %25v %+v\n", stat.Name, stat.Type, stat.Data) + n++ + } + + fmt.Printf("Dumped %d (%d) stats\n", n, len(stats)) +} + +func pollStats(client adapter.StatsAPI, patterns []string, skipZeros bool) { + dir, err := client.PrepareDir(patterns...) + if err != nil { + log.Fatalln("preparing dir failed:", err) + } + + tick := time.Tick(*pollPeriod) + for { + n := 0 + fmt.Println(time.Now().Format(time.Stamp)) + for _, stat := range dir.Entries { + if skipZeros && (stat.Data == nil || stat.Data.IsZero()) { + continue + } + fmt.Printf("%-50s %+v\n", stat.Name, stat.Data) + n++ + } + fmt.Println() + + select { + case <-tick: + if err := client.UpdateDir(dir); err != nil { + if err == adapter.ErrStatsDirStale { + if dir, err = client.PrepareDir(patterns...); err != nil { + log.Fatalln("preparing dir failed:", err) + } + continue + } + log.Fatalln("updating dir failed:", err) + } + } + } +} + +func pollSystem(client api.StatsProvider) { + stats := new(api.SystemStats) + + if err := client.GetSystemStats(stats); err != nil { + log.Fatalln("updating system stats failed:", err) + } + + tick := time.Tick(*pollPeriod) + for { + fmt.Printf("System stats: %+v\n", stats) + fmt.Println() + + select { + case <-tick: + if err := client.GetSystemStats(stats); err != nil { + log.Println("updating system stats failed:", err) + } + } + } +} + +func pollInterfaces(client api.StatsProvider) { + stats := new(api.InterfaceStats) + + if err := client.GetInterfaceStats(stats); err != nil { + log.Fatalln("updating system stats failed:", err) + } + + tick := time.Tick(*pollPeriod) + for { + fmt.Printf("Interface stats (%d interfaces)\n", len(stats.Interfaces)) + for i := range stats.Interfaces { + fmt.Printf(" - %+v\n", stats.Interfaces[i]) + } + fmt.Println() + + select { + case <-tick: + if err := client.GetInterfaceStats(stats); err != nil { + log.Println("updating system stats failed:", err) + } + } + } +} -- cgit 1.2.3-korg