misc: bug fixes and improvements for stats Fuse fs 41/32541/3
authorArthur de Kerhor <arthurdekerhor@gmail.com>
Wed, 24 Mar 2021 14:32:07 +0000 (07:32 -0700)
committerBeno�t Ganne <bganne@cisco.com>
Thu, 3 Jun 2021 07:25:04 +0000 (07:25 +0000)
Added syslogs
Added support for symlinks
Relocated make commands in a local Makefile
Dumping stats on index instead of paths
Updated README
Added go.mod and go.sum with relevant dependencies for the module

Type: fix
Change-Id: I2c91317939b2f4d765771ab7038372ae27d3109d
Signed-off-by: Arthur de Kerhor <arthurdekerhor@gmail.com>
Makefile
extras/vpp_stats_fs/Makefile [new file with mode: 0644]
extras/vpp_stats_fs/README.md
extras/vpp_stats_fs/cmd.go
extras/vpp_stats_fs/go.mod [new file with mode: 0644]
extras/vpp_stats_fs/go.sum [new file with mode: 0644]
extras/vpp_stats_fs/install.sh
extras/vpp_stats_fs/stats_fs.go

index b57ed5c..2bc236c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -220,7 +220,6 @@ help:
        @echo " docs                 - Build the Sphinx documentation"
        @echo " docs-venv            - Build the virtual environment for 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 "Make Arguments:"
        @echo " V=[0|1]                  - set build verbosity level"
@@ -669,33 +668,6 @@ featurelist: centos-pyyaml
 checkfeaturelist: centos-pyyaml
        @extras/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
 #
diff --git a/extras/vpp_stats_fs/Makefile b/extras/vpp_stats_fs/Makefile
new file mode 100644 (file)
index 0000000..0aded0c
--- /dev/null
@@ -0,0 +1,26 @@
+
+# Build vpp_stats_fs
+
+.PHONY: install
+install:
+       @./install.sh install
+
+.PHONY: start
+start:
+       @./install.sh start
+
+.PHONY: clean
+clean:
+       @./install.sh clean
+
+.PHONY: help
+help:
+       @./install.sh help
+
+.PHONY: force-unmount
+force-unmount:
+       @./install.sh unmount
+
+.PHONY: stop
+stop:
+       @./install.sh stop
\ No newline at end of file
index c5ec812..52610fe 100755 (executable)
 # VPP stats segment FUSE filesystem  {#stats_fs_doc}
 
 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.
+It relies 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.
+Directories update their contents on epoch changes so that new counters get added to the filesystem.
 
-## Prerequisites (for building)
+The script `install.sh` is responsible for buildiing and installing the filesystem.
 
-**GoVPP** library (master branch)
-**Go-FUSE** library
-vpp, vppapi
+## Usage
 
-## Building
+The local Makefile contains targets for all the possible intercations with the stats_f binary.
 
-Here, we add the Go librairies before building the binary
+### Help
+A basic help menu
 ```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
+make help
 ```
 
-## Usage
+### Install
+Building the binary
+```bash
+make install
+```
 
-The basic usage is:
+### Start
+Starts the filesystem. Requires a running VPP instance using the default socket /run/vpp/stats.sock.
+
+May require a privileged user (sudo)
 ```bash
-sudo ./statfs <MOUNT_POINT> &
+make start
+```
+
+### Stop
+Stops and unmounts the filesystem if it is not busy.
+
+May require a privileged user (sudo)
+```bash
+make stop
+```
+
+### Force unmount
+Forces the unmount of the filesystem even if it is busy.
+
+May require a privileged user (sudo)
+```bash
+make force-unmount
+```
+
+### Cleanup
+Cleaning stats_fs binary.
+
+May require a privileged user (sudo).
+```bash
+make clean
 ```
-**Options:**
- - debug \<true|false\> (default is false)
- - socket \<statSocket\> (default is /run/vpp/stats.sock)
 
 ## Browsing the filesystem
 
+The default mountpoint is /run/vpp/stats_fs_dir.
 You can browse the filesystem as a regular user.
 Example:
 
 ```bash
-cd /path/to/mountpoint
+cd /run/vpp/stats_fs_dir
 cd sys/node
 ls -al
 cat names
 ```
 
-## Unmounting the file system
+## Building and mounting the filesystem manually
+
+For more modularity, you can build and mount the filesystem manually.
+
+### Building
+Inside the local directory, you can build the go binary:
+```bash
+go build
+```
+
+### Mounting
+Then, ou can mount the filesystem with the local binary.
+
+May require a privileged user (sudo).
+
+The basic usage is:
+```bash
+./stats_fs <MOUNT_POINT>
+```
+
+**Options:**
+ - debug \<true|false\> (default is false)
+ - socket \<statSocket\> (default is /run/vpp/stats.sock) : VPP socket for stats
+
+
+### Unmounting the file system
 
 You can unmount the filesystem with the fusermount command.
+
+May require a privileged user (sudo)
+
 ```bash
-sudo fusermount -u /path/to/mountpoint
+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
+fusermount -uz /path/to/mountpoint
 ```
index 826b011..38d1b3d 100644 (file)
@@ -25,6 +25,8 @@ package main
 import (
        "flag"
        "fmt"
+       "log"
+       "log/syslog"
        "os"
        "os/signal"
        "runtime"
@@ -36,20 +38,32 @@ import (
        "github.com/hanwen/go-fuse/v2/fs"
 )
 
+func LogMsg(msg string) {
+       fmt.Fprint(os.Stderr, msg)
+       log.Print(msg)
+}
+
 func main() {
+       syslogger, err := syslog.New(syslog.LOG_ERR|syslog.LOG_DAEMON, "statsfs")
+       if err != nil {
+               log.Fatalln(err)
+       }
+       log.SetOutput(syslogger)
+
        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])
+               LogMsg(fmt.Sprintf("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")
+       fmt.Println("Waiting for the VPP socket to be available. Be sure a VPP instance is running.")
        c, err := core.ConnectStats(sc)
        if err != nil {
-               fmt.Fprintf(os.Stderr, "Failed to connect to the stats socket: %v\n", err)
+               LogMsg(fmt.Sprintf("Failed to connect to the stats socket: %v\n", err))
                os.Exit(1)
        }
        defer c.Disconnect()
@@ -57,7 +71,7 @@ func main() {
        //Creating the filesystem instance
        root, err := NewStatsFileSystem(sc)
        if err != nil {
-               fmt.Fprintf(os.Stderr, "NewStatsFileSystem failed: %v\n", err)
+               LogMsg(fmt.Sprintf("NewStatsFileSystem failed: %v\n", err))
                os.Exit(1)
        }
 
@@ -67,7 +81,7 @@ func main() {
        opts.AllowOther = true
        server, err := fs.Mount(flag.Arg(0), root, opts)
        if err != nil {
-               fmt.Fprintf(os.Stderr, "Mount fail: %v\n", err)
+               LogMsg(fmt.Sprintf("Mount fail: %v\n", err))
                os.Exit(1)
        }
 
@@ -86,6 +100,6 @@ func main() {
                if err == nil || !strings.Contains(err.Error(), "Device or resource busy") {
                        break
                }
-               fmt.Fprintf(os.Stderr, "Unmount fail: %v\n", err)
+               LogMsg(fmt.Sprintf("Unmount fail: %v\n", err))
        }
 }
diff --git a/extras/vpp_stats_fs/go.mod b/extras/vpp_stats_fs/go.mod
new file mode 100644 (file)
index 0000000..0a284bb
--- /dev/null
@@ -0,0 +1,8 @@
+module stats_fs
+
+go 1.16
+
+require (
+       git.fd.io/govpp.git v0.3.6-0.20210601140839-da95997338b7 // indirect
+       github.com/hanwen/go-fuse/v2 v2.1.0 // indirect
+)
diff --git a/extras/vpp_stats_fs/go.sum b/extras/vpp_stats_fs/go.sum
new file mode 100644 (file)
index 0000000..05bcc7a
--- /dev/null
@@ -0,0 +1,42 @@
+git.fd.io/govpp.git v0.3.6-0.20210601140839-da95997338b7 h1:IPy+QyEmQxFbVRFolJ4ofP+ZLN4HfzmK+QCPycmaINc=
+git.fd.io/govpp.git v0.3.6-0.20210601140839-da95997338b7/go.mod h1:OCVd4W8SH+666KRQoMj6PM+oipLDZAHhqMz9B1TGbgI=
+github.com/bennyscetbun/jsongo v1.1.0/go.mod h1:suxbVmjBV8+A2BBAM5EYVh6Uj8j3rqJhzWf3hv7Ff8U=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff h1:zk1wwii7uXmI0znwU+lqg+wFL9G5+vm5I+9rv2let60=
+github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff/go.mod h1:yUhRXHewUVJ1k89wHKP68xfzk7kwXUx/DV1nx4EBMbw=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc=
+github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
+github.com/hanwen/go-fuse/v2 v2.1.0 h1:+32ffteETaLYClUj0a3aHjZ1hOPxxaNEHiZiujuDaek=
+github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
+github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe h1:ewr1srjRCmcQogPQ/NCx6XCk6LGVmsVCc9Y3vvPZj+Y=
+github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
+github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.1.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
+golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
index 6249e63..c800ad3 100755 (executable)
@@ -1,3 +1,5 @@
+#!/bin/bash
+
 # 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.
@@ -11,8 +13,6 @@
 # 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
 
@@ -21,6 +21,7 @@ set -eo pipefail
 OPT_ARG=${1:-}
 
 STATS_FS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"/
+cd "${STATS_FS_DIR}"/../../
 VPP_DIR=$(pwd)/
 BUILD_ROOT=${VPP_DIR}build-root/
 BINARY_DIR=${BUILD_ROOT}install-vpp-native/vpp/bin/
@@ -75,6 +76,12 @@ function install_fuse() {
   apt-get install fuse -y
 }
 
+function install_nohup() {
+  echo "Installing nohup"
+  apt-get update
+  apt-get install nohup -y
+}
+
 function install_go_dep() {
   echo "Installing Go dependencies"
   if [[ ! -x "$(command -v go)" ]]; then
@@ -84,11 +91,11 @@ function install_go_dep() {
 
   if [ ! -e "go.mod" ]; then
     go mod init stats_fs
+    # We require a specific govpp commit for compatibility
+    go get git.fd.io/govpp.git@da95997338b77811bc2ea850db393c652b3bd18e
+    go get git.fd.io/govpp.git/adapter/statsclient@da95997338b77811bc2ea850db393c652b3bd18e
+    go get github.com/hanwen/go-fuse/v2
   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
@@ -114,6 +121,10 @@ function install_statfs() {
     install_fuse
   fi
 
+  if [[ ! -x "$(command -v nohup)" ]]; then
+    install_nohup
+  fi
+
   if [ ! -d "${STATS_FS_DIR}" ]; then
     echo "${STATS_FS_DIR} directory does not exist"
     exit 1
@@ -137,6 +148,11 @@ function start_statfs() {
     EXE_DIR=$DEBUG_DIR
   fi
 
+  if [[ $(pidof "${EXE_DIR}"stats_fs) ]]; then
+    echo "The service stats_fs has already been launched"
+    exit 1
+  fi
+
   mountpoint="${RUN_DIR}stats_fs_dir"
 
   if [[ -x "$(command -v ${EXE_DIR}stats_fs)" ]] ; then
@@ -148,6 +164,7 @@ function start_statfs() {
   fi
 
   echo "stats_fs is not installed, use 'make stats-fs-install' first"
+  exit 1
 }
 
 function stop_statfs() {
@@ -165,7 +182,8 @@ function stop_statfs() {
   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"
+    echo "Check your syslog file (default is /var/log/syslog)."
+    echo "It may be that the file system is busy."
     exit 1
   fi
 
@@ -196,12 +214,6 @@ function cleanup() {
 
   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
@@ -224,14 +236,14 @@ function cleanup() {
 # Show available commands
 function help() {
   cat <<__EOF__
-  Stats_fs installer
+  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
+  install        - Installs requirements (Go, GoVPP, GoFUSE) and builds stats_fs
+  start          - Launches the stats_fs binary and creates a mountpoint
+  clean          - Removes stats_fs binary
+  stop           - Stops the executable, unmounts the file system
+                   and removes the mountpoint directory
+  force-unmount  - Forces the unmount of the filesystem even if it is busy
 
 __EOF__
 }
@@ -246,7 +258,7 @@ function resolve_option() {
   "install")
     install_statfs
     ;;
-  "cleanup")
+  "clean")
     cleanup
     ;;
   "unmount")
index a9b8ae7..80c1509 100644 (file)
@@ -22,7 +22,7 @@ package main
 import (
        "context"
        "fmt"
-       "log"
+       "path"
        "path/filepath"
        "strings"
        "syscall"
@@ -36,57 +36,58 @@ import (
 )
 
 func updateDir(ctx context.Context, n *fs.Inode, cl *statsclient.StatsClient, dirPath string) syscall.Errno {
-       list, err := cl.ListStats(dirPath)
+       stats, err := cl.PrepareDir(dirPath)
        if err != nil {
-               log.Println("list stats failed:", err)
+               LogMsg(fmt.Sprintf("Listing stats index failed: %v\n", err))
                return syscall.EAGAIN
        }
 
-       if list == nil {
-               n.ForgetPersistent()
-               return syscall.ENOENT
-       }
+       n.Operations().(*dirNode).epoch = stats.Epoch
+
+       n.RmAllChildren()
 
-       for _, path := range list {
-               localPath := strings.TrimPrefix(path, dirPath)
-               dir, base := filepath.Split(localPath)
+       for _, entry := range stats.Entries {
+               localPath := strings.TrimPrefix(string(entry.Name), dirPath)
+               dirPath, base := filepath.Split(localPath)
 
                parent := n
-               for _, component := range strings.Split(dir, "/") {
+               for _, component := range strings.Split(dirPath, "/") {
                        if len(component) == 0 {
                                continue
                        }
                        child := parent.GetChild(component)
                        if child == nil {
-                               child = parent.NewPersistentInode(ctx, &dirNode{client: cl, lastUpdate: time.Now()},
+                               child = parent.NewInode(ctx, &dirNode{client: cl, epoch: stats.Epoch},
                                        fs.StableAttr{Mode: fuse.S_IFDIR})
                                parent.AddChild(component, child, true)
+                       } else {
+                               child.Operations().(*dirNode).epoch = stats.Epoch
                        }
 
                        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{})
+                       child := parent.NewPersistentInode(ctx, &statNode{client: cl, index: entry.Index}, fs.StableAttr{})
                        parent.AddChild(filename, child, true)
                }
        }
        return 0
 }
 
-func getCounterContent(path string, client *statsclient.StatsClient) (content string, status syscall.Errno) {
+func getCounterContent(index uint32, client *statsclient.StatsClient) (content string, status syscall.Errno) {
        content = ""
-       //We add '$' because we deal with regexp here
-       res, err := client.DumpStats(path + "$")
+       statsDir, err := client.PrepareDirOnIndex(index)
        if err != nil {
+               LogMsg(fmt.Sprintf("Dumping stats on index failed: %v\n", err))
                return content, syscall.EAGAIN
        }
-       if res == nil {
+       if len(statsDir.Entries) != 1 {
                return content, syscall.ENOENT
        }
-
-       result := res[0]
+       result := statsDir.Entries[0]
        if result.Data == nil {
                return content, 0
        }
@@ -131,38 +132,46 @@ func getCounterContent(path string, client *statsclient.StatsClient) (content st
        return content, fs.OK
 }
 
-type rootNode struct {
+//The dirNode structure represents directories
+type dirNode struct {
        fs.Inode
-       client     *statsclient.StatsClient
-       lastUpdate time.Time
+       client *statsclient.StatsClient
+       epoch  int64
 }
 
-var _ = (fs.NodeOnAdder)((*rootNode)(nil))
+var _ = (fs.NodeOpendirer)((*dirNode)(nil))
+var _ = (fs.NodeGetattrer)((*dirNode)(nil))
+var _ = (fs.NodeOnAdder)((*dirNode)(nil))
 
-func (root *rootNode) OnAdd(ctx context.Context) {
-       updateDir(ctx, &root.Inode, root.client, "/")
-       root.lastUpdate = time.Now()
+func (dn *dirNode) OnAdd(ctx context.Context) {
+       if dn.Inode.IsRoot() {
+               updateDir(ctx, &dn.Inode, dn.client, "/")
+       }
 }
 
-//The dirNode structure represents directories
-type dirNode struct {
-       fs.Inode
-       client     *statsclient.StatsClient
-       lastUpdate time.Time
+func (dn *dirNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
+       out.Mtime = uint64(time.Now().Unix())
+       out.Atime = out.Mtime
+       out.Ctime = out.Mtime
+       return 0
 }
 
-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
+       var status syscall.Errno = syscall.F_OK
+       var sleepTime time.Duration = 10 * time.Millisecond
+       newEpoch, inProgress := dn.client.GetEpoch()
+       for inProgress {
+               newEpoch, inProgress = dn.client.GetEpoch()
+               time.Sleep(sleepTime)
+               sleepTime = sleepTime * 2
        }
 
-       //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()
+       //We check that the directory epoch is up to date
+       if dn.epoch != newEpoch {
+               //directoryPath is the path to the current directory from root
+               directoryPath := path.Clean("/" + dn.Inode.Path(nil) + "/")
+               status = updateDir(ctx, &dn.Inode, dn.client, directoryPath)
+       }
        return status
 }
 
@@ -170,16 +179,26 @@ func (dn *dirNode) Opendir(ctx context.Context) syscall.Errno {
 type statNode struct {
        fs.Inode
        client *statsclient.StatsClient
-       path   string
+       index  uint32
 }
 
 var _ = (fs.NodeOpener)((*statNode)(nil))
+var _ = (fs.NodeGetattrer)((*statNode)(nil))
+
+func (fh *statNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
+       out.Mtime = uint64(time.Now().Unix())
+       out.Atime = out.Mtime
+       out.Ctime = out.Mtime
+       return 0
+}
 
 //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)
+       content, status := getCounterContent(sn.index, sn.client)
        if status == syscall.ENOENT {
-               sn.Inode.ForgetPersistent()
+               _, parent := sn.Inode.Parent()
+               parent.RmChild(sn.Inode.Path(parent))
+
        }
        return &statFH{data: []byte(content)}, fuse.FOPEN_DIRECT_IO, status
 }
@@ -203,5 +222,5 @@ func (fh *statFH) Read(ctx context.Context, data []byte, off int64) (fuse.ReadRe
 
 //NewStatsFileSystem creates the fs for the stat segment.
 func NewStatsFileSystem(sc *statsclient.StatsClient) (root fs.InodeEmbedder, err error) {
-       return &rootNode{client: sc}, nil
+       return &dirNode{client: sc}, nil
 }