misc: fuse fs for the stats segment
This extra allows to mount a FUSE filesystem reflecting the state of the stats segment. Type: feature Signed-off-by: Arthur de Kerhor <arthurdekerhor@gmail.com> Change-Id: I692f9ca5a65c1123b3cf28c761455eec36049791
This commit is contained in:

committed by
Dave Barach

parent
4d2726ece8
commit
1f13f8fd8a
28
Makefile
28
Makefile
@ -218,6 +218,7 @@ help:
|
|||||||
@echo " docs - Build the Sphinx documentation"
|
@echo " docs - Build the Sphinx documentation"
|
||||||
@echo " docs-venv - Build the virtual environment for the Sphinx docs"
|
@echo " docs-venv - Build the virtual environment for the Sphinx docs"
|
||||||
@echo " docs-clean - Remove the generated files from the Sphinx docs"
|
@echo " docs-clean - Remove the generated files from the Sphinx docs"
|
||||||
|
@echo " stats-fs-help - Help to build the stats segment file system"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Make Arguments:"
|
@echo "Make Arguments:"
|
||||||
@echo " V=[0|1] - set build verbosity level"
|
@echo " V=[0|1] - set build verbosity level"
|
||||||
@ -657,6 +658,33 @@ featurelist: centos-pyyaml
|
|||||||
checkfeaturelist: centos-pyyaml
|
checkfeaturelist: centos-pyyaml
|
||||||
@build-root/scripts/fts.py --validate --all
|
@build-root/scripts/fts.py --validate --all
|
||||||
|
|
||||||
|
|
||||||
|
# Build vpp_stats_fs
|
||||||
|
|
||||||
|
.PHONY: stats-fs-install
|
||||||
|
stats-fs-install:
|
||||||
|
@extras/vpp_stats_fs/install.sh install
|
||||||
|
|
||||||
|
.PHONY: stats-fs-start
|
||||||
|
stats-fs-start:
|
||||||
|
@extras/vpp_stats_fs/install.sh start
|
||||||
|
|
||||||
|
.PHONY: stats-fs-cleanup
|
||||||
|
stats-fs-cleanup:
|
||||||
|
@extras/vpp_stats_fs/install.sh cleanup
|
||||||
|
|
||||||
|
.PHONY: stats-fs-help
|
||||||
|
stats-fs-help:
|
||||||
|
@extras/vpp_stats_fs/install.sh help
|
||||||
|
|
||||||
|
.PHONY: stats-fs-force-unmount
|
||||||
|
stats-fs-force-unmount:
|
||||||
|
@extras/vpp_stats_fs/install.sh unmount
|
||||||
|
|
||||||
|
.PHONY: stats-fs-stop
|
||||||
|
stats-fs-stop:
|
||||||
|
@extras/vpp_stats_fs/install.sh stop
|
||||||
|
|
||||||
#
|
#
|
||||||
# Build the documentation
|
# Build the documentation
|
||||||
#
|
#
|
||||||
|
61
extras/vpp_stats_fs/README.md
Executable file
61
extras/vpp_stats_fs/README.md
Executable file
@ -0,0 +1,61 @@
|
|||||||
|
# VPP stats segment FUSE filesystem
|
||||||
|
|
||||||
|
The statfs binary allows to create a FUSE filesystem to expose and to browse the stats segment.
|
||||||
|
Is is leaned on the Go-FUSE library and requires Go-VPP stats bindings to work.
|
||||||
|
|
||||||
|
The binary mounts a filesystem on the local machine whith the data from the stats segments.
|
||||||
|
The counters can be opened and read as files (e.g. in a Unix shell).
|
||||||
|
Note that the value of a counter is determined when the corresponding file is opened (as for /proc/interrupts).
|
||||||
|
|
||||||
|
Directories regularly update their contents so that new counters get added to the filesystem.
|
||||||
|
|
||||||
|
## Prerequisites (for building)
|
||||||
|
|
||||||
|
**GoVPP** library (master branch)
|
||||||
|
**Go-FUSE** library
|
||||||
|
vpp, vppapi
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Here, we add the Go librairies before building the binary
|
||||||
|
```bash
|
||||||
|
go mod init stats_fs
|
||||||
|
go get git.fd.io/govpp.git@master
|
||||||
|
go get git.fd.io/govpp.git/adapter/statsclient@master
|
||||||
|
go get github.com/hanwen/go-fuse/v2
|
||||||
|
go build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The basic usage is:
|
||||||
|
```bash
|
||||||
|
sudo ./statfs <MOUNT_POINT> &
|
||||||
|
```
|
||||||
|
**Options:**
|
||||||
|
- debug \<true|false\> (default is false)
|
||||||
|
- socket \<statSocket\> (default is /run/vpp/stats.sock)
|
||||||
|
|
||||||
|
## Browsing the filesystem
|
||||||
|
|
||||||
|
You can browse the filesystem as a regular user.
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/mountpoint
|
||||||
|
cd sys/node
|
||||||
|
ls -al
|
||||||
|
cat names
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unmounting the file system
|
||||||
|
|
||||||
|
You can unmount the filesystem with the fusermount command.
|
||||||
|
```bash
|
||||||
|
sudo fusermount -u /path/to/mountpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
To force the unmount even if the resource is busy, add the -z option:
|
||||||
|
```bash
|
||||||
|
sudo fusermount -uz /path/to/mountpoint
|
||||||
|
```
|
91
extras/vpp_stats_fs/cmd.go
Normal file
91
extras/vpp_stats_fs/cmd.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Cisco Systems 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2016 the Go-FUSE Authors. All rights reserved.
|
||||||
|
* Use of this source code is governed by a BSD-style
|
||||||
|
* license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file is the main program driver to mount the stats segment filesystem.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.fd.io/govpp.git/adapter/statsclient"
|
||||||
|
"git.fd.io/govpp.git/core"
|
||||||
|
"github.com/hanwen/go-fuse/v2/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
statsSocket := flag.String("socket", statsclient.DefaultSocketName, "Path to VPP stats socket")
|
||||||
|
debug := flag.Bool("debug", false, "print debugging messages.")
|
||||||
|
flag.Parse()
|
||||||
|
if flag.NArg() < 1 {
|
||||||
|
fmt.Fprintf(os.Stderr, "usage: %s MOUNTPOINT\n", os.Args[0])
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
//Conection to the stat segment socket.
|
||||||
|
sc := statsclient.NewStatsClient(*statsSocket)
|
||||||
|
fmt.Printf("Waiting for the VPP socket to be available. Be sure a VPP instance is running.\n")
|
||||||
|
c, err := core.ConnectStats(sc)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to connect to the stats socket: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer c.Disconnect()
|
||||||
|
fmt.Printf("Connected to the socket\n")
|
||||||
|
//Creating the filesystem instance
|
||||||
|
root, err := NewStatsFileSystem(sc)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "NewStatsFileSystem failed: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Mounting the filesystem.
|
||||||
|
opts := &fs.Options{}
|
||||||
|
opts.Debug = *debug
|
||||||
|
opts.AllowOther = true
|
||||||
|
server, err := fs.Mount(flag.Arg(0), root, opts)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Mount fail: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sigs := make(chan os.Signal)
|
||||||
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
fmt.Printf("Successfully mounted the file system in directory: %s\n", flag.Arg(0))
|
||||||
|
runtime.GC()
|
||||||
|
|
||||||
|
for {
|
||||||
|
go server.Wait()
|
||||||
|
|
||||||
|
<-sigs
|
||||||
|
fmt.Println("Unmounting...")
|
||||||
|
err := server.Unmount()
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "Device or resource busy") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Unmount fail: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
274
extras/vpp_stats_fs/install.sh
Executable file
274
extras/vpp_stats_fs/install.sh
Executable file
@ -0,0 +1,274 @@
|
|||||||
|
# Copyright (c) 2021 Cisco Systems 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.
|
||||||
|
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# A simple script that installs stats_fs, a Fuse file system
|
||||||
|
# for the stats segment
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
OPT_ARG=${1:-}
|
||||||
|
|
||||||
|
STATS_FS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"/
|
||||||
|
VPP_DIR=$(pwd)/
|
||||||
|
BUILD_ROOT=${VPP_DIR}build-root/
|
||||||
|
BINARY_DIR=${BUILD_ROOT}install-vpp-native/vpp/bin/
|
||||||
|
DEBUG_DIR=${BUILD_ROOT}install-vpp_debug-native/vpp/bin/
|
||||||
|
RUN_DIR=/run/vpp/
|
||||||
|
|
||||||
|
GOROOT=${GOROOT:-}
|
||||||
|
GOPATH=${GOPATH:-}
|
||||||
|
|
||||||
|
[ -z "${GOROOT}" ] && GOROOT="${HOME}/.go" && PATH=$GOROOT/bin:$PATH
|
||||||
|
[ -z "${GOPATH}" ] && GOPATH="${HOME}/go" && PATH=$GOPATH/bin:$PATH
|
||||||
|
|
||||||
|
function install_tools() {
|
||||||
|
echo "Installing downloading tools"
|
||||||
|
apt-get update
|
||||||
|
apt-get install git wget curl -y
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install latest GO version
|
||||||
|
function install_go() {
|
||||||
|
local TMP="/tmp"
|
||||||
|
|
||||||
|
echo "Installing latest GO"
|
||||||
|
if [[ -x "$(command -v go)" ]]; then
|
||||||
|
local installed_ver installed_ver_fmt
|
||||||
|
installed_ver=$(go version)
|
||||||
|
installed_ver_fmt=${installed_ver#"go version go"}
|
||||||
|
echo "Found installed version ${installed_ver_fmt}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${GOROOT}"
|
||||||
|
mkdir -p "${GOPATH}/"{src,pkg,bin}
|
||||||
|
|
||||||
|
wget "https://dl.google.com/go/$(curl https://golang.org/VERSION?m=text).linux-amd64.tar.gz" -O "${TMP}/go.tar.gz"
|
||||||
|
tar -C "$GOROOT" --strip-components=1 -xzf "${TMP}/go.tar.gz"
|
||||||
|
|
||||||
|
rm -f "${TMP}/go.tar.gz"
|
||||||
|
|
||||||
|
# export path for current session to install vpp_stast_fs
|
||||||
|
export GOROOT=${GOROOT}
|
||||||
|
export PATH=$GOROOT/bin:$PATH
|
||||||
|
export GOPATH=$GOPATH
|
||||||
|
export PATH=$GOPATH/bin:$PATH
|
||||||
|
|
||||||
|
echo "Installed $(go version)"
|
||||||
|
}
|
||||||
|
|
||||||
|
function install_fuse() {
|
||||||
|
echo "Installing Fuse"
|
||||||
|
apt-get update
|
||||||
|
apt-get install fuse -y
|
||||||
|
}
|
||||||
|
|
||||||
|
function install_go_dep() {
|
||||||
|
echo "Installing Go dependencies"
|
||||||
|
if [[ ! -x "$(command -v go)" ]]; then
|
||||||
|
echo "GO is not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e "go.mod" ]; then
|
||||||
|
go mod init stats_fs
|
||||||
|
fi
|
||||||
|
# master required
|
||||||
|
go get git.fd.io/govpp.git@master
|
||||||
|
go get git.fd.io/govpp.git/adapter/statsclient@master
|
||||||
|
go get github.com/hanwen/go-fuse/v2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve stats_fs dependencies and builds the binary
|
||||||
|
function build_statfs() {
|
||||||
|
echo "Installing statfs"
|
||||||
|
go build
|
||||||
|
if [ -d "${BINARY_DIR}" ]; then
|
||||||
|
mv stats_fs "${BINARY_DIR}"/stats_fs
|
||||||
|
elif [ -d "${DEBUG_DIR}" ]; then
|
||||||
|
mv stats_fs "${DEBUG_DIR}"/stats_fs
|
||||||
|
else
|
||||||
|
echo "${BINARY_DIR} and ${DEBUG_DIR} directories does not exist, the binary is installed at ${STATS_FS_DIR}stats_fs instead"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function install_statfs() {
|
||||||
|
if [[ ! -x "$(command -v go)" ]]; then
|
||||||
|
install_tools
|
||||||
|
install_go
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -x "$(command -v fusermount)" ]]; then
|
||||||
|
install_fuse
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "${STATS_FS_DIR}" ]; then
|
||||||
|
echo "${STATS_FS_DIR} directory does not exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cd "${STATS_FS_DIR}"
|
||||||
|
|
||||||
|
if [[ ! -x "$(command -v ${STATS_FS_DIR}stats_fs)" ]]; then
|
||||||
|
install_go_dep
|
||||||
|
build_statfs
|
||||||
|
else
|
||||||
|
echo "stats_fs already installed at path ${STATS_FS_DIR}stats_fs"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Starts the statfs binary
|
||||||
|
function start_statfs() {
|
||||||
|
EXE_DIR=$STATS_FS_DIR
|
||||||
|
if [ -d "${BINARY_DIR}" ]; then
|
||||||
|
EXE_DIR=$BINARY_DIR
|
||||||
|
elif [ -d "${DEBUG_DIR}" ]; then
|
||||||
|
EXE_DIR=$DEBUG_DIR
|
||||||
|
fi
|
||||||
|
|
||||||
|
mountpoint="${RUN_DIR}stats_fs_dir"
|
||||||
|
|
||||||
|
if [[ -x "$(command -v ${EXE_DIR}stats_fs)" ]] ; then
|
||||||
|
if [ ! -d "$mountpoint" ] ; then
|
||||||
|
mkdir "$mountpoint"
|
||||||
|
fi
|
||||||
|
nohup "${EXE_DIR}"stats_fs $mountpoint 0<&- &>/dev/null &
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "stats_fs is not installed, use 'make stats-fs-install' first"
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop_statfs() {
|
||||||
|
EXE_DIR=$STATS_FS_DIR
|
||||||
|
if [ -d "${BINARY_DIR}" ]; then
|
||||||
|
EXE_DIR=$BINARY_DIR
|
||||||
|
elif [ -d "${DEBUG_DIR}" ]; then
|
||||||
|
EXE_DIR=$DEBUG_DIR
|
||||||
|
fi
|
||||||
|
if [[ ! $(pidof "${EXE_DIR}"stats_fs) ]]; then
|
||||||
|
echo "The service stats_fs is not running"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PID=$(pidof "${EXE_DIR}"stats_fs)
|
||||||
|
kill "$PID"
|
||||||
|
if [[ $(pidof "${EXE_DIR}"stats_fs) ]]; then
|
||||||
|
echo "Can't unmount the file system: Device or resource busy"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "${RUN_DIR}stats_fs_dir" ] ; then
|
||||||
|
rm -df "${RUN_DIR}stats_fs_dir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function force_unmount() {
|
||||||
|
if (( $(mount | grep "${RUN_DIR}stats_fs_dir" | wc -l) == 1 )) ; then
|
||||||
|
fusermount -uz "${RUN_DIR}stats_fs_dir"
|
||||||
|
else
|
||||||
|
echo "The default directory ${RUN_DIR}stats_fs_dir is not mounted."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "${RUN_DIR}stats_fs_dir" ] ; then
|
||||||
|
rm -df "${RUN_DIR}stats_fs_dir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove stats_fs Go module
|
||||||
|
function cleanup() {
|
||||||
|
echo "Cleaning up stats_fs"
|
||||||
|
if [ ! -d "${STATS_FS_DIR}" ]; then
|
||||||
|
echo "${STATS_FS_DIR} directory does not exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "${STATS_FS_DIR}"
|
||||||
|
|
||||||
|
if [ -e "go.mod" ]; then
|
||||||
|
rm -f go.mod
|
||||||
|
fi
|
||||||
|
if [ -e "go.sum" ]; then
|
||||||
|
rm -f go.sum
|
||||||
|
fi
|
||||||
|
if [ -e "stats_fs" ]; then
|
||||||
|
rm -f stats_fs
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "${BINARY_DIR}" ]; then
|
||||||
|
if [ -e "${BINARY_DIR}stats_fs" ]; then
|
||||||
|
rm -f ${BINARY_DIR}stats_fs
|
||||||
|
fi
|
||||||
|
elif [ -d "${DEBUG_DIR}" ]; then
|
||||||
|
if [ -e "${DEBUG_DIR}stats_fs" ]; then
|
||||||
|
rm -f ${DEBUG_DIR}stats_fs
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "${RUN_DIR}stats_fs_dir" ] ; then
|
||||||
|
rm -df "${RUN_DIR}stats_fs_dir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show available commands
|
||||||
|
function help() {
|
||||||
|
cat <<__EOF__
|
||||||
|
Stats_fs installer
|
||||||
|
|
||||||
|
stats-fs-install - Installs requirements (Go, GoVPP, GoFUSE) and builds stats_fs
|
||||||
|
stats-fs-start - Launches the stats_fs binary and creates a mountpoint
|
||||||
|
stats-fs-cleanup - Removes stats_fs binary and deletes go module
|
||||||
|
stats-fs-stop - Stops the executable, unmounts the file system
|
||||||
|
and removes the mountpoint directory
|
||||||
|
stats-fs-force-unmount - Forces the unmount of the filesystem even if it is busy
|
||||||
|
|
||||||
|
__EOF__
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve chosen option and call appropriate functions
|
||||||
|
function resolve_option() {
|
||||||
|
local option=$1
|
||||||
|
case ${option} in
|
||||||
|
"start")
|
||||||
|
start_statfs
|
||||||
|
;;
|
||||||
|
"install")
|
||||||
|
install_statfs
|
||||||
|
;;
|
||||||
|
"cleanup")
|
||||||
|
cleanup
|
||||||
|
;;
|
||||||
|
"unmount")
|
||||||
|
force_unmount
|
||||||
|
;;
|
||||||
|
"stop")
|
||||||
|
stop_statfs
|
||||||
|
;;
|
||||||
|
"help")
|
||||||
|
help
|
||||||
|
;;
|
||||||
|
*) echo invalid option ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -n ${OPT_ARG} ]]; then
|
||||||
|
resolve_option "${OPT_ARG}"
|
||||||
|
else
|
||||||
|
PS3="--> "
|
||||||
|
options=("install" "cleanup" "help" "start" "unmount")
|
||||||
|
select option in "${options[@]}"; do
|
||||||
|
resolve_option "${option}"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
fi
|
207
extras/vpp_stats_fs/stats_fs.go
Normal file
207
extras/vpp_stats_fs/stats_fs.go
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Cisco Systems 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*Go-FUSE allows us to define the behaviour of our filesystem by recoding any primitive function we need.
|
||||||
|
*The structure of the filesystem is constructed as a tree.
|
||||||
|
*Each type of nodes (root, directory, file) follows its own prmitives.
|
||||||
|
*/
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hanwen/go-fuse/v2/fs"
|
||||||
|
"github.com/hanwen/go-fuse/v2/fuse"
|
||||||
|
|
||||||
|
"git.fd.io/govpp.git/adapter"
|
||||||
|
"git.fd.io/govpp.git/adapter/statsclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
func updateDir(ctx context.Context, n *fs.Inode, cl *statsclient.StatsClient, dirPath string) syscall.Errno {
|
||||||
|
list, err := cl.ListStats(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("list stats failed:", err)
|
||||||
|
return syscall.EAGAIN
|
||||||
|
}
|
||||||
|
|
||||||
|
if list == nil {
|
||||||
|
n.ForgetPersistent()
|
||||||
|
return syscall.ENOENT
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range list {
|
||||||
|
localPath := strings.TrimPrefix(path, dirPath)
|
||||||
|
dir, base := filepath.Split(localPath)
|
||||||
|
|
||||||
|
parent := n
|
||||||
|
for _, component := range strings.Split(dir, "/") {
|
||||||
|
if len(component) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
child := parent.GetChild(component)
|
||||||
|
if child == nil {
|
||||||
|
child = parent.NewPersistentInode(ctx, &dirNode{client: cl, lastUpdate: time.Now()},
|
||||||
|
fs.StableAttr{Mode: fuse.S_IFDIR})
|
||||||
|
parent.AddChild(component, child, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = child
|
||||||
|
}
|
||||||
|
filename := strings.Replace(base, " ", "_", -1)
|
||||||
|
child := parent.GetChild(filename)
|
||||||
|
if child == nil {
|
||||||
|
child := parent.NewPersistentInode(ctx, &statNode{client: cl, path: path}, fs.StableAttr{})
|
||||||
|
parent.AddChild(filename, child, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCounterContent(path string, client *statsclient.StatsClient) (content string, status syscall.Errno) {
|
||||||
|
content = ""
|
||||||
|
//We add '$' because we deal with regexp here
|
||||||
|
res, err := client.DumpStats(path + "$")
|
||||||
|
if err != nil {
|
||||||
|
return content, syscall.EAGAIN
|
||||||
|
}
|
||||||
|
if res == nil {
|
||||||
|
return content, syscall.ENOENT
|
||||||
|
}
|
||||||
|
|
||||||
|
result := res[0]
|
||||||
|
if result.Data == nil {
|
||||||
|
return content, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
switch result.Type {
|
||||||
|
case adapter.ScalarIndex:
|
||||||
|
stats := result.Data.(adapter.ScalarStat)
|
||||||
|
content = fmt.Sprintf("%.2f\n", stats)
|
||||||
|
case adapter.ErrorIndex:
|
||||||
|
stats := result.Data.(adapter.ErrorStat)
|
||||||
|
content = fmt.Sprintf("%-16s%s\n", "Index", "Count")
|
||||||
|
for i, value := range stats {
|
||||||
|
content += fmt.Sprintf("%-16d%d\n", i, value)
|
||||||
|
}
|
||||||
|
case adapter.SimpleCounterVector:
|
||||||
|
stats := result.Data.(adapter.SimpleCounterStat)
|
||||||
|
content = fmt.Sprintf("%-16s%-16s%s\n", "Thread", "Index", "Packets")
|
||||||
|
for i, vector := range stats {
|
||||||
|
for j, value := range vector {
|
||||||
|
content += fmt.Sprintf("%-16d%-16d%d\n", i, j, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case adapter.CombinedCounterVector:
|
||||||
|
stats := result.Data.(adapter.CombinedCounterStat)
|
||||||
|
content = fmt.Sprintf("%-16s%-16s%-16s%s\n", "Thread", "Index", "Packets", "Bytes")
|
||||||
|
for i, vector := range stats {
|
||||||
|
for j, value := range vector {
|
||||||
|
content += fmt.Sprintf("%-16d%-16d%-16d%d\n", i, j, value[0], value[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case adapter.NameVector:
|
||||||
|
stats := result.Data.(adapter.NameStat)
|
||||||
|
content = fmt.Sprintf("%-16s%s\n", "Index", "Name")
|
||||||
|
for i, value := range stats {
|
||||||
|
content += fmt.Sprintf("%-16d%s\n", i, string(value))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
content = fmt.Sprintf("Unknown stat type: %d\n", result.Type)
|
||||||
|
//For now, the empty type (file deleted) is not implemented in GoVPP
|
||||||
|
return content, syscall.ENOENT
|
||||||
|
}
|
||||||
|
return content, fs.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
type rootNode struct {
|
||||||
|
fs.Inode
|
||||||
|
client *statsclient.StatsClient
|
||||||
|
lastUpdate time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = (fs.NodeOnAdder)((*rootNode)(nil))
|
||||||
|
|
||||||
|
func (root *rootNode) OnAdd(ctx context.Context) {
|
||||||
|
updateDir(ctx, &root.Inode, root.client, "/")
|
||||||
|
root.lastUpdate = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
//The dirNode structure represents directories
|
||||||
|
type dirNode struct {
|
||||||
|
fs.Inode
|
||||||
|
client *statsclient.StatsClient
|
||||||
|
lastUpdate time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = (fs.NodeOpendirer)((*dirNode)(nil))
|
||||||
|
|
||||||
|
func (dn *dirNode) Opendir(ctx context.Context) syscall.Errno {
|
||||||
|
//We do not update a directory more than once a second, as counters are rarely added/deleted.
|
||||||
|
if time.Now().Sub(dn.lastUpdate) < time.Second {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//directoryPath is the path to the current directory from root
|
||||||
|
directoryPath := "/" + dn.Inode.Path(nil) + "/"
|
||||||
|
status := updateDir(ctx, &dn.Inode, dn.client, directoryPath)
|
||||||
|
dn.lastUpdate = time.Now()
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
//The statNode structure represents counters
|
||||||
|
type statNode struct {
|
||||||
|
fs.Inode
|
||||||
|
client *statsclient.StatsClient
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = (fs.NodeOpener)((*statNode)(nil))
|
||||||
|
|
||||||
|
//When a file is opened, the correpsonding counter value is dumped and a file handle is created
|
||||||
|
func (sn *statNode) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
|
||||||
|
content, status := getCounterContent(sn.path, sn.client)
|
||||||
|
if status == syscall.ENOENT {
|
||||||
|
sn.Inode.ForgetPersistent()
|
||||||
|
}
|
||||||
|
return &statFH{data: []byte(content)}, fuse.FOPEN_DIRECT_IO, status
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The statFH structure aims at dislaying the counters dynamically.
|
||||||
|
* It allows the Kernel to read data as I/O without having to specify files sizes, as they may evolve dynamically.
|
||||||
|
*/
|
||||||
|
type statFH struct {
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = (fs.FileReader)((*statFH)(nil))
|
||||||
|
|
||||||
|
func (fh *statFH) Read(ctx context.Context, data []byte, off int64) (fuse.ReadResult, syscall.Errno) {
|
||||||
|
end := int(off) + len(data)
|
||||||
|
if end > len(fh.data) {
|
||||||
|
end = len(fh.data)
|
||||||
|
}
|
||||||
|
return fuse.ReadResultData(fh.data[off:end]), fs.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewStatsFileSystem creates the fs for the stat segment.
|
||||||
|
func NewStatsFileSystem(sc *statsclient.StatsClient) (root fs.InodeEmbedder, err error) {
|
||||||
|
return &rootNode{client: sc}, nil
|
||||||
|
}
|
Reference in New Issue
Block a user