package restapi import ( "bytes" "encoding/json" "net/http" "reflect" "time" "github.com/go-chi/render" "github.com/gorilla/websocket" "gvisor.dev/gvisor/pkg/tcpip" ) var _statsFunc func() tcpip.Stats func SetStatsFunc(s func() tcpip.Stats) { _statsFunc = s } func getNetStats(w http.ResponseWriter, r *http.Request) { if _statsFunc == nil { render.Status(r, http.StatusInternalServerError) render.JSON(w, r, ErrUninitialized) return } snapshot := func() any { s := _statsFunc() return dump(reflect.ValueOf(&s).Elem()) } if !websocket.IsWebSocketUpgrade(r) { render.JSON(w, r, snapshot()) return } conn, err := _upgrader.Upgrade(w, r, nil) if err != nil { return } tick := time.NewTicker(time.Second) defer tick.Stop() buf := &bytes.Buffer{} for range tick.C { buf.Reset() if err = json.NewEncoder(buf).Encode(snapshot()); err != nil { break } if err = conn.WriteMessage(websocket.TextMessage, buf.Bytes()); err != nil { break } } } func dump(value reflect.Value) map[string]any { numField := value.NumField() structure := make(map[string]any, numField) for i := 0; i < numField; i++ { field := value.Type().Field(i) value := value.Field(i) switch v := value.Addr().Interface().(type) { case **tcpip.StatCounter: structure[field.Name] = (*v).Value() case **tcpip.IntegralStatCounterMap: counterMap := make(map[uint64]uint64) for _, k := range (*v).Keys() { if counter, ok := (*v).Get(k); ok { counterMap[k] = counter.Value() } } structure[field.Name] = counterMap default: structure[field.Name] = dump(value) } } return structure }