github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/pfsagentd/http_server.go (about)

     1  // Copyright (c) 2015-2021, NVIDIA CORPORATION.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package main
     5  
     6  import (
     7  	"bytes"
     8  	"container/list"
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"log"
    13  	"net"
    14  	"net/http"
    15  	"net/http/pprof"
    16  	"reflect"
    17  	"runtime"
    18  	"strconv"
    19  	"strings"
    20  	"sync/atomic"
    21  
    22  	"github.com/swiftstack/sortedmap"
    23  
    24  	"github.com/swiftstack/ProxyFS/bucketstats"
    25  	"github.com/swiftstack/ProxyFS/version"
    26  )
    27  
    28  type leaseReportStruct struct {
    29  	MountID            string
    30  	None               []string // inode.InodeNumber in 16-digit Hex (no leading "0x")
    31  	SharedRequested    []string
    32  	SharedGranted      []string
    33  	SharedPromoting    []string
    34  	SharedReleasing    []string
    35  	ExclusiveRequested []string
    36  	ExclusiveGranted   []string
    37  	ExclusiveDemoting  []string
    38  	ExclusiveReleasing []string
    39  }
    40  
    41  func serveHTTP() {
    42  	var (
    43  		ipAddrTCPPort string
    44  	)
    45  
    46  	ipAddrTCPPort = net.JoinHostPort(globals.config.HTTPServerIPAddr, strconv.Itoa(int(globals.config.HTTPServerTCPPort)))
    47  
    48  	globals.httpServer = &http.Server{
    49  		Addr:    ipAddrTCPPort,
    50  		Handler: &globals,
    51  	}
    52  
    53  	globals.httpServerWG.Add(1)
    54  
    55  	go func() {
    56  		var (
    57  			err error
    58  		)
    59  
    60  		err = globals.httpServer.ListenAndServe()
    61  		if http.ErrServerClosed != err {
    62  			log.Fatalf("httpServer.ListenAndServe() exited unexpectedly: %v", err)
    63  		}
    64  
    65  		globals.httpServerWG.Done()
    66  	}()
    67  }
    68  
    69  func unserveHTTP() {
    70  	var (
    71  		err error
    72  	)
    73  
    74  	err = globals.httpServer.Shutdown(context.TODO())
    75  	if nil != err {
    76  		log.Fatalf("httpServer.Shutdown() returned with an error: %v", err)
    77  	}
    78  
    79  	globals.httpServerWG.Wait()
    80  }
    81  
    82  func (dummy *globalsStruct) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) {
    83  	switch request.Method {
    84  	case http.MethodGet:
    85  		serveGet(responseWriter, request)
    86  	default:
    87  		responseWriter.WriteHeader(http.StatusMethodNotAllowed)
    88  	}
    89  }
    90  
    91  func serveGet(responseWriter http.ResponseWriter, request *http.Request) {
    92  	var (
    93  		path string
    94  	)
    95  
    96  	path = strings.TrimRight(request.URL.Path, "/")
    97  
    98  	switch {
    99  	case "" == path:
   100  		serveGetOfIndexDotHTML(responseWriter, request)
   101  	case "/config" == path:
   102  		serveGetOfConfig(responseWriter, request)
   103  	case "/debug/pprof/cmdline" == path:
   104  		pprof.Cmdline(responseWriter, request)
   105  	case "/debug/pprof/profile" == path:
   106  		pprof.Profile(responseWriter, request)
   107  	case "/debug/pprof/symbol" == path:
   108  		pprof.Symbol(responseWriter, request)
   109  	case "/debug/pprof/trace" == path:
   110  		pprof.Trace(responseWriter, request)
   111  	case strings.HasPrefix(path, "/debug/pprof"):
   112  		pprof.Index(responseWriter, request)
   113  	case "index.html" == path:
   114  		serveGetOfIndexDotHTML(responseWriter, request)
   115  	case "/leases" == path:
   116  		serveGetOfLeases(responseWriter, request)
   117  	case "/metrics" == path:
   118  		serveGetOfMetrics(responseWriter, request)
   119  	case "/stats" == path:
   120  		serveGetOfStats(responseWriter, request)
   121  	case "/version" == path:
   122  		serveGetOfVersion(responseWriter, request)
   123  	default:
   124  		responseWriter.WriteHeader(http.StatusNotFound)
   125  	}
   126  }
   127  
   128  func serveGetOfConfig(responseWriter http.ResponseWriter, request *http.Request) {
   129  	var (
   130  		confMapJSON       bytes.Buffer
   131  		confMapJSONPacked []byte
   132  		ok                bool
   133  		paramList         []string
   134  		sendPackedConfig  bool
   135  	)
   136  
   137  	paramList, ok = request.URL.Query()["compact"]
   138  	if ok {
   139  		if 0 == len(paramList) {
   140  			sendPackedConfig = false
   141  		} else {
   142  			sendPackedConfig = !((paramList[0] == "") || (paramList[0] == "0") || (paramList[0] == "false"))
   143  		}
   144  	} else {
   145  		sendPackedConfig = false
   146  	}
   147  
   148  	confMapJSONPacked, _ = json.Marshal(globals.config)
   149  
   150  	responseWriter.Header().Set("Content-Type", "application/json")
   151  	responseWriter.WriteHeader(http.StatusOK)
   152  
   153  	if sendPackedConfig {
   154  		_, _ = responseWriter.Write(confMapJSONPacked)
   155  	} else {
   156  		json.Indent(&confMapJSON, confMapJSONPacked, "", "\t")
   157  		_, _ = responseWriter.Write(confMapJSON.Bytes())
   158  		_, _ = responseWriter.Write([]byte("\n"))
   159  	}
   160  }
   161  
   162  func serveGetOfIndexDotHTML(responseWriter http.ResponseWriter, request *http.Request) {
   163  	responseWriter.Header().Set("Content-Type", "text/html")
   164  	responseWriter.WriteHeader(http.StatusOK)
   165  	_, _ = responseWriter.Write([]byte(fmt.Sprintf(indexDotHTMLTemplate, net.JoinHostPort(globals.config.HTTPServerIPAddr, strconv.Itoa(int(globals.config.HTTPServerTCPPort))))))
   166  }
   167  
   168  func serveGetOfLeases(responseWriter http.ResponseWriter, request *http.Request) {
   169  	var (
   170  		fileInode             *fileInodeStruct
   171  		leaseListElement      *list.Element
   172  		leaseReport           *leaseReportStruct
   173  		leaseReportJSON       bytes.Buffer
   174  		leaseReportJSONPacked []byte
   175  		ok                    bool
   176  		paramList             []string
   177  		sendPackedLeaseReport bool
   178  	)
   179  
   180  	leaseReport = &leaseReportStruct{
   181  		MountID:            fmt.Sprintf("%s", globals.mountID),
   182  		None:               make([]string, 0),
   183  		SharedRequested:    make([]string, 0),
   184  		SharedGranted:      make([]string, 0),
   185  		SharedPromoting:    make([]string, 0),
   186  		SharedReleasing:    make([]string, 0),
   187  		ExclusiveRequested: make([]string, 0),
   188  		ExclusiveGranted:   make([]string, 0),
   189  		ExclusiveDemoting:  make([]string, 0),
   190  		ExclusiveReleasing: make([]string, 0),
   191  	}
   192  
   193  	globals.Lock()
   194  
   195  	for leaseListElement = globals.unleasedFileInodeCacheLRU.Front(); leaseListElement != nil; leaseListElement = leaseListElement.Next() {
   196  		fileInode = leaseListElement.Value.(*fileInodeStruct)
   197  		switch fileInode.leaseState {
   198  		case fileInodeLeaseStateNone:
   199  			leaseReport.None = append(leaseReport.None, fmt.Sprintf("%016X", fileInode.InodeNumber))
   200  		case fileInodeLeaseStateSharedReleasing:
   201  			leaseReport.SharedReleasing = append(leaseReport.SharedReleasing, fmt.Sprintf("%016X", fileInode.InodeNumber))
   202  		case fileInodeLeaseStateExclusiveReleasing:
   203  			leaseReport.ExclusiveReleasing = append(leaseReport.ExclusiveReleasing, fmt.Sprintf("%016X", fileInode.InodeNumber))
   204  		default:
   205  			logFatalf("serveGetOfLeases() found unexpected fileInode.leaseState %v on globals.unleasedFileInodeCacheLRU", fileInode.leaseState)
   206  		}
   207  	}
   208  
   209  	for leaseListElement = globals.sharedLeaseFileInodeCacheLRU.Front(); leaseListElement != nil; leaseListElement = leaseListElement.Next() {
   210  		fileInode = leaseListElement.Value.(*fileInodeStruct)
   211  		switch fileInode.leaseState {
   212  		case fileInodeLeaseStateSharedRequested:
   213  			leaseReport.SharedRequested = append(leaseReport.SharedRequested, fmt.Sprintf("%016X", fileInode.InodeNumber))
   214  		case fileInodeLeaseStateSharedGranted:
   215  			leaseReport.SharedGranted = append(leaseReport.SharedGranted, fmt.Sprintf("%016X", fileInode.InodeNumber))
   216  		case fileInodeLeaseStateExclusiveDemoting:
   217  			leaseReport.ExclusiveDemoting = append(leaseReport.ExclusiveDemoting, fmt.Sprintf("%016X", fileInode.InodeNumber))
   218  		default:
   219  			logFatalf("serveGetOfLeases() found unexpected fileInode.leaseState %v on globals.sharedLeaseFileInodeCacheLRU", fileInode.leaseState)
   220  		}
   221  	}
   222  
   223  	for leaseListElement = globals.exclusiveLeaseFileInodeCacheLRU.Front(); leaseListElement != nil; leaseListElement = leaseListElement.Next() {
   224  		fileInode = leaseListElement.Value.(*fileInodeStruct)
   225  		switch fileInode.leaseState {
   226  		case fileInodeLeaseStateSharedPromoting:
   227  			leaseReport.SharedPromoting = append(leaseReport.SharedPromoting, fmt.Sprintf("%016X", fileInode.InodeNumber))
   228  		case fileInodeLeaseStateExclusiveRequested:
   229  			leaseReport.ExclusiveRequested = append(leaseReport.ExclusiveRequested, fmt.Sprintf("%016X", fileInode.InodeNumber))
   230  		case fileInodeLeaseStateExclusiveGranted:
   231  			leaseReport.ExclusiveGranted = append(leaseReport.ExclusiveGranted, fmt.Sprintf("%016X", fileInode.InodeNumber))
   232  		default:
   233  			logFatalf("serveGetOfLeases() found unexpected fileInode.leaseState %v on globals.exclusiveLeaseFileInodeCacheLRU", fileInode.leaseState)
   234  		}
   235  	}
   236  
   237  	globals.Unlock()
   238  
   239  	paramList, ok = request.URL.Query()["compact"]
   240  	if ok {
   241  		if 0 == len(paramList) {
   242  			sendPackedLeaseReport = false
   243  		} else {
   244  			sendPackedLeaseReport = !((paramList[0] == "") || (paramList[0] == "0") || (paramList[0] == "false"))
   245  		}
   246  	} else {
   247  		sendPackedLeaseReport = false
   248  	}
   249  
   250  	leaseReportJSONPacked, _ = json.Marshal(leaseReport)
   251  
   252  	responseWriter.Header().Set("Content-Type", "application/json")
   253  	responseWriter.WriteHeader(http.StatusOK)
   254  
   255  	if sendPackedLeaseReport {
   256  		_, _ = responseWriter.Write(leaseReportJSONPacked)
   257  	} else {
   258  		json.Indent(&leaseReportJSON, leaseReportJSONPacked, "", "\t")
   259  		_, _ = responseWriter.Write(leaseReportJSON.Bytes())
   260  		_, _ = responseWriter.Write([]byte("\n"))
   261  	}
   262  }
   263  
   264  func serveGetOfMetrics(responseWriter http.ResponseWriter, request *http.Request) {
   265  	var (
   266  		err                  error
   267  		format               string
   268  		i                    int
   269  		keyAsKey             sortedmap.Key
   270  		keyAsString          string
   271  		line                 string
   272  		longestKeyAsString   int
   273  		longestValueAsString int
   274  		memStats             runtime.MemStats
   275  		metricsFieldName     string
   276  		metricsFieldValuePtr *uint64
   277  		metricsLLRB          sortedmap.LLRBTree
   278  		metricsLLRBLen       int
   279  		metricsStructValue   reflect.Value
   280  		metricsValue         reflect.Value
   281  		ok                   bool
   282  		pauseNsAccumulator   uint64
   283  		valueAsString        string
   284  		valueAsValue         sortedmap.Value
   285  	)
   286  
   287  	runtime.ReadMemStats(&memStats)
   288  
   289  	metricsLLRB = sortedmap.NewLLRBTree(sortedmap.CompareString, nil)
   290  
   291  	// General statistics.
   292  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_Alloc", memStats.Alloc)
   293  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_TotalAlloc", memStats.TotalAlloc)
   294  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_Sys", memStats.Sys)
   295  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_Lookups", memStats.Lookups)
   296  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_Mallocs", memStats.Mallocs)
   297  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_Frees", memStats.Frees)
   298  
   299  	// Main allocation heap statistics.
   300  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_HeapAlloc", memStats.HeapAlloc)
   301  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_HeapSys", memStats.HeapSys)
   302  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_HeapIdle", memStats.HeapIdle)
   303  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_HeapInuse", memStats.HeapInuse)
   304  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_HeapReleased", memStats.HeapReleased)
   305  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_HeapObjects", memStats.HeapObjects)
   306  
   307  	// Low-level fixed-size structure allocator statistics.
   308  	//	Inuse is bytes used now.
   309  	//	Sys is bytes obtained from system.
   310  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_StackInuse", memStats.StackInuse)
   311  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_StackSys", memStats.StackSys)
   312  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_MSpanInuse", memStats.MSpanInuse)
   313  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_MSpanSys", memStats.MSpanSys)
   314  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_MCacheInuse", memStats.MCacheInuse)
   315  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_MCacheSys", memStats.MCacheSys)
   316  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_BuckHashSys", memStats.BuckHashSys)
   317  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_GCSys", memStats.GCSys)
   318  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_OtherSys", memStats.OtherSys)
   319  
   320  	// Garbage collector statistics (fixed portion).
   321  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_LastGC", memStats.LastGC)
   322  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_PauseTotalNs", memStats.PauseTotalNs)
   323  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_NumGC", uint64(memStats.NumGC))
   324  	insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_GCCPUPercentage", uint64(100.0*memStats.GCCPUFraction))
   325  
   326  	// Garbage collector statistics (go_runtime_MemStats_PauseAverageNs).
   327  	if 0 == memStats.NumGC {
   328  		insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_PauseAverageNs", 0)
   329  	} else {
   330  		pauseNsAccumulator = 0
   331  		if memStats.NumGC < 255 {
   332  			for i = 0; i < int(memStats.NumGC); i++ {
   333  				pauseNsAccumulator += memStats.PauseNs[i]
   334  			}
   335  			insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_PauseAverageNs", pauseNsAccumulator/uint64(memStats.NumGC))
   336  		} else {
   337  			for i = 0; i < 256; i++ {
   338  				pauseNsAccumulator += memStats.PauseNs[i]
   339  			}
   340  			insertInMetricsLLRB(metricsLLRB, "go_runtime_MemStats_PauseAverageNs", pauseNsAccumulator/256)
   341  		}
   342  	}
   343  
   344  	// Add in locally generated metrics
   345  
   346  	metricsStructValue = reflect.Indirect(reflect.ValueOf(globals.metrics))
   347  	metricsValue = reflect.ValueOf(globals.metrics).Elem()
   348  
   349  	for i = 0; i < metricsStructValue.NumField(); i++ {
   350  		metricsFieldName = metricsStructValue.Type().Field(i).Name
   351  		metricsFieldValuePtr = metricsValue.Field(i).Addr().Interface().(*uint64)
   352  		insertInMetricsLLRB(metricsLLRB, metricsFieldName, atomic.LoadUint64(metricsFieldValuePtr))
   353  	}
   354  
   355  	// Produce sorted and column-aligned response
   356  
   357  	responseWriter.Header().Set("Content-Type", "text/plain")
   358  	responseWriter.WriteHeader(http.StatusOK)
   359  
   360  	metricsLLRBLen, err = metricsLLRB.Len()
   361  	if nil != err {
   362  		logFatalf("metricsLLRB.Len() failed: %v", err)
   363  	}
   364  
   365  	longestKeyAsString = 0
   366  	longestValueAsString = 0
   367  
   368  	for i = 0; i < metricsLLRBLen; i++ {
   369  		keyAsKey, valueAsValue, ok, err = metricsLLRB.GetByIndex(i)
   370  		if nil != err {
   371  			logFatalf("llrb.GetByIndex(%v) failed: %v", i, err)
   372  		}
   373  		if !ok {
   374  			logFatalf("llrb.GetByIndex(%v) returned ok == false", i)
   375  		}
   376  		keyAsString = keyAsKey.(string)
   377  		valueAsString = valueAsValue.(string)
   378  		if len(keyAsString) > longestKeyAsString {
   379  			longestKeyAsString = len(keyAsString)
   380  		}
   381  		if len(valueAsString) > longestValueAsString {
   382  			longestValueAsString = len(valueAsString)
   383  		}
   384  	}
   385  
   386  	format = fmt.Sprintf("%%-%vs %%%vs\n", longestKeyAsString, longestValueAsString)
   387  
   388  	for i = 0; i < metricsLLRBLen; i++ {
   389  		keyAsKey, valueAsValue, ok, err = metricsLLRB.GetByIndex(i)
   390  		if nil != err {
   391  			logFatalf("llrb.GetByIndex(%v) failed: %v", i, err)
   392  		}
   393  		if !ok {
   394  			logFatalf("llrb.GetByIndex(%v) returned ok == false", i)
   395  		}
   396  		keyAsString = keyAsKey.(string)
   397  		valueAsString = valueAsValue.(string)
   398  		line = fmt.Sprintf(format, keyAsString, valueAsString)
   399  		_, _ = responseWriter.Write([]byte(line))
   400  	}
   401  }
   402  
   403  func serveGetOfStats(responseWriter http.ResponseWriter, request *http.Request) {
   404  	responseWriter.Header().Set("Content-Type", "text/plain")
   405  	responseWriter.WriteHeader(http.StatusOK)
   406  	_, _ = responseWriter.Write([]byte(bucketstats.SprintStats(bucketstats.StatFormatParsable1, "*", "*")))
   407  }
   408  
   409  func insertInMetricsLLRB(metricsLLRB sortedmap.LLRBTree, metricKey string, metricValueAsUint64 uint64) {
   410  	var (
   411  		err                 error
   412  		metricValueAsString string
   413  		ok                  bool
   414  	)
   415  
   416  	metricValueAsString = fmt.Sprintf("%v", metricValueAsUint64)
   417  
   418  	ok, err = metricsLLRB.Put(metricKey, metricValueAsString)
   419  	if nil != err {
   420  		logFatalf("metricsLLRB.Put(%v, %v) failed: %v", metricKey, metricValueAsString, err)
   421  	}
   422  	if !ok {
   423  		logFatalf("metricsLLRB.Put(%v, %v) returned ok == false", metricKey, metricValueAsString)
   424  	}
   425  }
   426  
   427  func serveGetOfVersion(responseWriter http.ResponseWriter, request *http.Request) {
   428  	responseWriter.Header().Set("Content-Type", "text/plain")
   429  	responseWriter.WriteHeader(http.StatusOK)
   430  	_, _ = responseWriter.Write([]byte(version.ProxyFSVersion))
   431  }