Add crazy struct flattening code
This commit is contained in:
parent
788b349dd7
commit
ab43e6bd97
3 changed files with 126 additions and 39 deletions
|
@ -29,8 +29,15 @@ func (s *Server) check(w http.ResponseWriter, r *http.Request, p httprouter.Para
|
|||
func (s *Server) stats(w http.ResponseWriter, r *http.Request, p httprouter.Params) (int, error) {
|
||||
w.Header().Set("Content-Type", jsonContentType)
|
||||
|
||||
var err error
|
||||
e := json.NewEncoder(w)
|
||||
err := e.Encode(stats.DefaultStats)
|
||||
|
||||
if _, flatten := r.URL.Query()["flatten"]; flatten {
|
||||
err = e.Encode(stats.DefaultStats.Flattened())
|
||||
} else {
|
||||
err = e.Encode(stats.DefaultStats)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
|
|
@ -43,19 +43,18 @@ const (
|
|||
// line flag.
|
||||
var DefaultStats *Stats
|
||||
|
||||
type PeerStats struct {
|
||||
// Stats for all peers.
|
||||
Current uint64 `json:"current"` // Current total peer count.
|
||||
Joined uint64 `json:"joined"` // Total peers that announced.
|
||||
Left uint64 `json:"left"` // Total peers that paused or stopped.
|
||||
Reaped uint64 `json:"reaped"` // Total peers cleaned up after inactivity.
|
||||
Completed uint64 `json:"completed"` // Number of transitions from leech to seed.
|
||||
type PeerClassStats struct {
|
||||
Current uint64 // Current peer count.
|
||||
Joined uint64 // Peers that announced.
|
||||
Left uint64 // Peers that paused or stopped.
|
||||
Reaped uint64 // Peers cleaned up after inactivity.
|
||||
}
|
||||
|
||||
// Stats for seeds only (subset of total).
|
||||
SeedsCurrent uint64 `json:"seeds_current"` // Current seed count.
|
||||
SeedsJoined uint64 `json:"seeds_joined"` // Seeds that announced (does not included leechers that completed).
|
||||
SeedsLeft uint64 `json:"seeds_left"` // Seeds that paused or stopped.
|
||||
SeedsReaped uint64 `json:"seeds_reaped"` // Seeds cleaned up after inactivity.
|
||||
type PeerStats struct {
|
||||
PeerClassStats `json:"Peers"`
|
||||
Seeds PeerClassStats `json:"Seeds"`
|
||||
|
||||
Completed uint64 // Number of transitions from leech to seed.
|
||||
}
|
||||
|
||||
type PercentileTimes struct {
|
||||
|
@ -65,39 +64,41 @@ type PercentileTimes struct {
|
|||
}
|
||||
|
||||
type Stats struct {
|
||||
Start time.Time `json:"start_time"` // Time at which Chihaya was booted.
|
||||
Started time.Time // Time at which Chihaya was booted.
|
||||
|
||||
Announces uint64 `json:"announces"` // Total number of announces.
|
||||
Scrapes uint64 `json:"scrapes"` // Total number of scrapes.
|
||||
Announces uint64 `json:"Tracker.Announces"` // Total number of announces.
|
||||
Scrapes uint64 `json:"Tracker.Scrapes"` // Total number of scrapes.
|
||||
|
||||
IPv4Peers PeerStats `json:"ipv4_peers"`
|
||||
IPv6Peers PeerStats `json:"ipv6_peers"`
|
||||
IPv4Peers PeerStats `json:"Peers.IPv4"`
|
||||
IPv6Peers PeerStats `json:"Peers.IPv6"`
|
||||
|
||||
TorrentsAdded uint64 `json:"torrents_added"`
|
||||
TorrentsRemoved uint64 `json:"torrents_removed"`
|
||||
TorrentsReaped uint64 `json:"torrents_reaped"`
|
||||
TorrentsAdded uint64 `json:"Torrents.Added"`
|
||||
TorrentsRemoved uint64 `json:"Torrents.Removed"`
|
||||
TorrentsReaped uint64 `json:"Torrents.Reaped"`
|
||||
|
||||
OpenConnections uint64 `json:"open_connections"`
|
||||
ConnectionsAccepted uint64 `json:"connections_accepted"`
|
||||
BytesTransmitted uint64 `json:"bytes_transmitted"`
|
||||
OpenConnections uint64 `json:"Connections.Open"`
|
||||
ConnectionsAccepted uint64 `json:"Connections.Accepted"`
|
||||
BytesTransmitted uint64 `json:"BytesTransmitted"`
|
||||
|
||||
RequestsHandled uint64 `json:"requests_handled"`
|
||||
RequestsErrored uint64 `json:"requests_errored"`
|
||||
ClientErrors uint64 `json:"client_errors"`
|
||||
RequestsHandled uint64 `json:"Requests.Handled"`
|
||||
RequestsErrored uint64 `json:"Requests.Errored"`
|
||||
ClientErrors uint64 `json:"Requests.Bad"`
|
||||
|
||||
ResponseTime PercentileTimes `json:"response_time"`
|
||||
MemStats *MemStatsWrapper `json:"mem,omitempty"`
|
||||
ResponseTime PercentileTimes
|
||||
MemStats *MemStatsWrapper `json:"Memory,omitempty"`
|
||||
|
||||
events chan int
|
||||
ipv4PeerEvents chan int
|
||||
ipv6PeerEvents chan int
|
||||
responseTimeEvents chan time.Duration
|
||||
recordMemStats <-chan time.Time
|
||||
|
||||
flattened FlatMap
|
||||
}
|
||||
|
||||
func New(cfg config.StatsConfig) *Stats {
|
||||
s := &Stats{
|
||||
Start: time.Now(),
|
||||
Started: time.Now(),
|
||||
events: make(chan int, cfg.BufferSize),
|
||||
|
||||
ipv4PeerEvents: make(chan int, cfg.BufferSize),
|
||||
|
@ -116,16 +117,21 @@ func New(cfg config.StatsConfig) *Stats {
|
|||
s.recordMemStats = time.NewTicker(cfg.MemUpdateInterval.Duration).C
|
||||
}
|
||||
|
||||
s.flattened = Flatten(s)
|
||||
go s.handleEvents()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Stats) Flattened() FlatMap {
|
||||
return s.flattened
|
||||
}
|
||||
|
||||
func (s *Stats) Close() {
|
||||
close(s.events)
|
||||
}
|
||||
|
||||
func (s *Stats) Uptime() time.Duration {
|
||||
return time.Since(s.Start)
|
||||
return time.Since(s.Started)
|
||||
}
|
||||
|
||||
func (s *Stats) RecordEvent(event int) {
|
||||
|
@ -215,7 +221,7 @@ func (s *Stats) handlePeerEvent(ps *PeerStats, event int) {
|
|||
switch event {
|
||||
case Completed:
|
||||
ps.Completed++
|
||||
ps.SeedsCurrent++
|
||||
ps.Seeds.Current++
|
||||
|
||||
case NewLeech:
|
||||
ps.Joined++
|
||||
|
@ -230,20 +236,20 @@ func (s *Stats) handlePeerEvent(ps *PeerStats, event int) {
|
|||
ps.Current--
|
||||
|
||||
case NewSeed:
|
||||
ps.SeedsJoined++
|
||||
ps.SeedsCurrent++
|
||||
ps.Seeds.Joined++
|
||||
ps.Seeds.Current++
|
||||
ps.Joined++
|
||||
ps.Current++
|
||||
|
||||
case DeletedSeed:
|
||||
ps.SeedsLeft++
|
||||
ps.SeedsCurrent--
|
||||
ps.Seeds.Left++
|
||||
ps.Seeds.Current--
|
||||
ps.Left++
|
||||
ps.Current--
|
||||
|
||||
case ReapedSeed:
|
||||
ps.SeedsReaped++
|
||||
ps.SeedsCurrent--
|
||||
ps.Seeds.Reaped++
|
||||
ps.Seeds.Current--
|
||||
ps.Reaped++
|
||||
ps.Current--
|
||||
|
||||
|
|
74
stats/struct_flattener.go
Normal file
74
stats/struct_flattener.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package stats
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type FlatMap map[string]interface{}
|
||||
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
return v.Interface() == reflect.Zero(v.Type()).Interface()
|
||||
}
|
||||
|
||||
func keyForField(field reflect.StructField, v reflect.Value) string {
|
||||
if tag := field.Tag.Get("json"); tag != "" {
|
||||
tokens := strings.SplitN(tag, ",", 2)
|
||||
name := tokens[0]
|
||||
opts := ""
|
||||
|
||||
if len(tokens) > 1 {
|
||||
opts = tokens[1]
|
||||
}
|
||||
|
||||
if name == "-" || strings.Contains(opts, "omitempty") && isEmptyValue(v) {
|
||||
return ""
|
||||
} else if name != "" {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
return field.Name
|
||||
}
|
||||
|
||||
func recursiveFlatten(val reflect.Value, prefix string, output FlatMap) int {
|
||||
valType := val.Type()
|
||||
added := 0
|
||||
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
child := val.Field(i)
|
||||
childType := valType.Field(i)
|
||||
key := prefix + keyForField(childType, child)
|
||||
|
||||
if childType.PkgPath != "" || key == "" {
|
||||
continue
|
||||
} else if child.Kind() == reflect.Struct {
|
||||
if recursiveFlatten(child, key+".", output) != 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
output[key] = child.Addr().Interface()
|
||||
added++
|
||||
}
|
||||
|
||||
return added
|
||||
}
|
||||
|
||||
func flattenPointer(val reflect.Value) FlatMap {
|
||||
if val.Kind() == reflect.Ptr {
|
||||
return flattenPointer(val.Elem())
|
||||
}
|
||||
|
||||
if val.Kind() != reflect.Struct {
|
||||
panic("must be called with a struct type")
|
||||
}
|
||||
|
||||
m := FlatMap{}
|
||||
recursiveFlatten(val, "", m)
|
||||
return m
|
||||
}
|
||||
|
||||
func Flatten(val interface{}) FlatMap {
|
||||
return flattenPointer(reflect.ValueOf(val))
|
||||
}
|
Loading…
Reference in a new issue