Add crazy struct flattening code

This commit is contained in:
Justin Li 2014-07-24 19:35:15 -04:00
parent 788b349dd7
commit ab43e6bd97
3 changed files with 126 additions and 39 deletions

View file

@ -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
}

View file

@ -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
View 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))
}