go.ligato.io/vpp-agent/v3@v3.5.0/plugins/telemetry/vppcalls/vpp2210/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 vpp2210
    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  }