go.ligato.io/vpp-agent/v3@v3.5.0/plugins/telemetry/vppcalls/vpp2202/telemetry_vppcalls.go (about) 1 // Copyright (c) 2022 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 vpp2202 16 17 import ( 18 "context" 19 "fmt" 20 "regexp" 21 "strconv" 22 "strings" 23 24 govppapi "go.fd.io/govpp/api" 25 26 "go.ligato.io/vpp-agent/v3/plugins/telemetry/vppcalls" 27 ) 28 29 func (h *TelemetryHandler) GetSystemStats(context.Context) (*govppapi.SystemStats, error) { 30 return nil, nil 31 } 32 33 var ( 34 // Regular expression to parse output from `show memory` 35 memoryRe = regexp.MustCompile( 36 `Thread\s+(\d+)\s+(\w+).?\s+` + 37 `base 0x[0-9a-f]+, size ([\dkmg\.]+), (?:(?:locked|unmap-on-destroy)[,\s]+)*name '[-\w\s]*'\s+` + 38 `page stats: page-size ([\dkmgKMG\.]+), total ([\dkmg\.]+), mapped [\dkmg\.]+, not-mapped [\dkmg\.]+(?:, unknown [\dkmg\.]+)?\s+` + 39 `(?:(?:\s+numa [\d]+: [\dkmg\.]+ pages, [\dkmg\.]+ bytes\s+)*\s+)*` + 40 `\s+total: ([\dkmgKMG\.]+), used: ([\dkmgKMG\.]+), free: ([\dkmgKMG\.]+), trimmable: ([\dkmgKMG\.]+)\s+` + 41 `free chunks (\d+)\s+free fastbin blks (\d+)\s+max total allocated\s+([\dkmgKMG\.]+)`, 42 ) 43 ) 44 45 // GetMemory retrieves `show memory` info. 46 func (h *TelemetryHandler) GetMemory(ctx context.Context) (*vppcalls.MemoryInfo, error) { 47 input, err := h.vpe.RunCli(context.TODO(), "show memory main-heap verbose") 48 if err != nil { 49 return nil, err 50 } 51 52 threadMatches := memoryRe.FindAllStringSubmatch(input, -1) 53 54 if len(threadMatches) == 0 && input != "" { 55 return nil, fmt.Errorf("invalid memory input: %q", input) 56 } 57 58 var threads []vppcalls.MemoryThread 59 for _, matches := range threadMatches { 60 fields := matches[1:] 61 if len(fields) != 12 { 62 return nil, fmt.Errorf("invalid memory data %v for thread: %q", fields, matches[0]) 63 } 64 id, err := strconv.ParseUint(fields[0], 10, 64) 65 if err != nil { 66 return nil, err 67 } 68 thread := &vppcalls.MemoryThread{ 69 ID: uint(id), 70 Name: fields[1], 71 Size: strToUint64(fields[2]), 72 PageSize: strToUint64(fields[3]), 73 Pages: strToUint64(fields[4]), 74 Total: strToUint64(fields[5]), 75 Used: strToUint64(fields[6]), 76 Free: strToUint64(fields[7]), 77 Trimmable: strToUint64(fields[8]), 78 FreeChunks: strToUint64(fields[9]), 79 FreeFastbinBlks: strToUint64(fields[10]), 80 MaxTotalAlloc: strToUint64(fields[11]), 81 } 82 threads = append(threads, *thread) 83 } 84 85 info := &vppcalls.MemoryInfo{ 86 Threads: threads, 87 } 88 89 return info, nil 90 } 91 92 func (h *TelemetryHandler) GetInterfaceStats(context.Context) (*govppapi.InterfaceStats, error) { 93 return nil, nil 94 } 95 96 var ( 97 // Regular expression to parse output from `show node counters` 98 nodeCountersRe = regexp.MustCompile(`^\s+(\d+)\s+([\w-\/]+)\s+(.+)$`) 99 ) 100 101 // GetNodeCounters retrieves node counters info. 102 func (h *TelemetryHandler) GetNodeCounters(ctx context.Context) (*vppcalls.NodeCounterInfo, error) { 103 data, err := h.vpe.RunCli(context.TODO(), "show node counters") 104 if err != nil { 105 return nil, err 106 } 107 108 var counters []vppcalls.NodeCounter 109 110 for i, line := range strings.Split(string(data), "\n") { 111 // Skip empty lines 112 if strings.TrimSpace(line) == "" { 113 continue 114 } 115 // Check first line 116 if i == 0 { 117 fields := strings.Fields(line) 118 // Verify header 119 if len(fields) != 3 || fields[0] != "Count" { 120 return nil, fmt.Errorf("invalid header for `show node counters` received: %q", line) 121 } 122 continue 123 } 124 125 // Parse lines using regexp 126 matches := nodeCountersRe.FindStringSubmatch(line) 127 if len(matches)-1 != 3 { 128 return nil, fmt.Errorf("parsing failed for `show node counters` line: %q", line) 129 } 130 fields := matches[1:] 131 132 counters = append(counters, vppcalls.NodeCounter{ 133 Value: strToUint64(fields[0]), 134 Node: fields[1], 135 Name: fields[2], 136 }) 137 } 138 139 info := &vppcalls.NodeCounterInfo{ 140 Counters: counters, 141 } 142 143 return info, nil 144 } 145 146 var ( 147 // Regular expression to parse output from `show runtime` 148 runtimeRe = regexp.MustCompile(`(?:-+\n)?(?:Thread (\d+) (\w+)(?: \(lcore \d+\))?\n)?` + 149 `Time ([0-9\.e-]+), average vectors/node ([0-9\.e-]+), last (\d+) main loops ([0-9\.e-]+) per node ([0-9\.e-]+)\s+` + 150 `vector rates in ([0-9\.e-]+), out ([0-9\.e-]+), drop ([0-9\.e-]+), punt ([0-9\.e-]+)\n` + 151 `\s+Name\s+State\s+Calls\s+Vectors\s+Suspends\s+Clocks\s+Vectors/Call\s+(?:Perf Ticks\s+)?` + 152 `((?:[\w-:\.]+\s+\w+(?:[ -]\w+)*\s+\d+\s+\d+\s+\d+\s+[0-9\.e-]+\s+[0-9\.e-]+\s+)+)`) 153 runtimeItemsRe = regexp.MustCompile(`([\w-:\.]+)\s+(\w+(?:[ -]\w+)*)\s+(\d+)\s+(\d+)\s+(\d+)\s+([0-9\.e-]+)\s+([0-9\.e-]+)\s+`) 154 ) 155 156 // GetRuntimeInfo retrieves how runtime info. 157 func (h *TelemetryHandler) GetRuntimeInfo(ctx context.Context) (*vppcalls.RuntimeInfo, error) { 158 input, err := h.vpe.RunCli(context.TODO(), "show runtime") 159 if err != nil { 160 return nil, err 161 } 162 163 threadMatches := runtimeRe.FindAllStringSubmatch(input, -1) 164 165 if len(threadMatches) == 0 && input != "" { 166 return nil, fmt.Errorf("invalid runtime input: %q", input) 167 } 168 169 var threads []vppcalls.RuntimeThread 170 for _, matches := range threadMatches { 171 fields := matches[1:] 172 if len(fields) != 12 { 173 return nil, fmt.Errorf("invalid runtime data for thread (len=%v): %q", len(fields), matches[0]) 174 } 175 thread := vppcalls.RuntimeThread{ 176 ID: uint(strToUint64(fields[0])), 177 Name: fields[1], 178 Time: strToFloat64(fields[2]), 179 AvgVectorsPerNode: strToFloat64(fields[3]), 180 LastMainLoops: strToUint64(fields[4]), 181 VectorsPerMainLoop: strToFloat64(fields[5]), 182 VectorLengthPerNode: strToFloat64(fields[6]), 183 VectorRatesIn: strToFloat64(fields[7]), 184 VectorRatesOut: strToFloat64(fields[8]), 185 VectorRatesDrop: strToFloat64(fields[9]), 186 VectorRatesPunt: strToFloat64(fields[10]), 187 } 188 189 itemMatches := runtimeItemsRe.FindAllStringSubmatch(fields[11], -1) 190 for _, matches := range itemMatches { 191 fields := matches[1:] 192 if len(fields) != 7 { 193 return nil, fmt.Errorf("invalid runtime data for thread item: %q", matches[0]) 194 } 195 thread.Items = append(thread.Items, vppcalls.RuntimeItem{ 196 Name: fields[0], 197 State: fields[1], 198 Calls: strToUint64(fields[2]), 199 Vectors: strToUint64(fields[3]), 200 Suspends: strToUint64(fields[4]), 201 Clocks: strToFloat64(fields[5]), 202 VectorsPerCall: strToFloat64(fields[6]), 203 }) 204 } 205 206 threads = append(threads, thread) 207 } 208 209 info := &vppcalls.RuntimeInfo{ 210 Threads: threads, 211 } 212 213 return info, nil 214 } 215 216 var ( 217 // Regular expression to parse output from `show buffers` 218 buffersRe = regexp.MustCompile( 219 `^(\w+(?:[ \-]\w+)*)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+([\dkmg\.]+)\s+([\dkmg\.]+)\s+([\dkmg\.]+)\s+([\dkmg\.]+)(?:\s+)?$`, 220 ) 221 ) 222 223 // GetBuffersInfo retrieves buffers info from VPP. 224 func (h *TelemetryHandler) GetBuffersInfo(ctx context.Context) (*vppcalls.BuffersInfo, error) { 225 data, err := h.vpe.RunCli(context.TODO(), "show buffers") 226 if err != nil { 227 return nil, err 228 } 229 230 var items []vppcalls.BuffersItem 231 232 for i, line := range strings.Split(string(data), "\n") { 233 // Skip empty lines 234 if strings.TrimSpace(line) == "" { 235 continue 236 } 237 // Check first line 238 if i == 0 { 239 fields := strings.Fields(line) 240 // Verify header 241 if len(fields) != 11 || fields[0] != "Pool" { 242 return nil, fmt.Errorf("invalid header for `show buffers` received: %q", line) 243 } 244 continue 245 } 246 247 // Parse lines using regexp 248 matches := buffersRe.FindStringSubmatch(line) 249 if len(matches)-1 != 9 { 250 return nil, fmt.Errorf("parsing failed (%d matches) for `show buffers` line: %q", len(matches), line) 251 } 252 fields := matches[1:] 253 254 items = append(items, vppcalls.BuffersItem{ 255 // ThreadID: uint(strToUint64(fields[0])), 256 Name: fields[0], 257 Index: uint(strToUint64(fields[1])), 258 Size: strToUint64(fields[3]), 259 Alloc: strToUint64(fields[7]), 260 Free: strToUint64(fields[5]), 261 // NumAlloc: strToUint64(fields[6]), 262 // NumFree: strToUint64(fields[7]), 263 }) 264 } 265 266 info := &vppcalls.BuffersInfo{ 267 Items: items, 268 } 269 270 return info, nil 271 } 272 273 // GetThreads retrieves info about the VPP threads 274 func (h *TelemetryHandler) GetThreads(ctx context.Context) (*vppcalls.ThreadsInfo, error) { 275 threads, err := h.vpe.GetThreads(ctx) 276 if err != nil { 277 return nil, err 278 } 279 var items []vppcalls.ThreadsItem 280 for _, thread := range threads { 281 items = append(items, vppcalls.ThreadsItem{ 282 Name: thread.Name, 283 ID: thread.ID, 284 Type: thread.Type, 285 PID: thread.PID, 286 CPUID: thread.CPUID, 287 Core: thread.Core, 288 CPUSocket: thread.CPUSocket, 289 }) 290 } 291 return &vppcalls.ThreadsInfo{ 292 Items: items, 293 }, err 294 } 295 296 func strToFloat64(s string) float64 { 297 // Replace 'k' (thousands) with 'e3' to make it parsable with strconv 298 s = strings.Replace(s, "k", "e3", 1) 299 s = strings.Replace(s, "K", "e3", 1) 300 s = strings.Replace(s, "m", "e6", 1) 301 s = strings.Replace(s, "M", "e6", 1) 302 s = strings.Replace(s, "g", "e9", 1) 303 s = strings.Replace(s, "G", "e9", 1) 304 305 num, err := strconv.ParseFloat(s, 64) 306 if err != nil { 307 return 0 308 } 309 return num 310 } 311 312 func strToUint64(s string) uint64 { 313 return uint64(strToFloat64(s)) 314 }