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 }