From ab43e6bd97c40773028cee7779c2eb7c3be2e1bc Mon Sep 17 00:00:00 2001 From: Justin Li Date: Thu, 24 Jul 2014 19:35:15 -0400 Subject: [PATCH] Add crazy struct flattening code --- http/routes.go | 9 ++++- stats/stats.go | 82 +++++++++++++++++++++------------------ stats/struct_flattener.go | 74 +++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 39 deletions(-) create mode 100644 stats/struct_flattener.go diff --git a/http/routes.go b/http/routes.go index 8f615a8..c2db8b8 100644 --- a/http/routes.go +++ b/http/routes.go @@ -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 } diff --git a/stats/stats.go b/stats/stats.go index ebfb63f..32442af 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -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,40 +64,42 @@ 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(), - events: make(chan int, cfg.BufferSize), + Started: time.Now(), + events: make(chan int, cfg.BufferSize), ipv4PeerEvents: make(chan int, cfg.BufferSize), ipv6PeerEvents: 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-- diff --git a/stats/struct_flattener.go b/stats/struct_flattener.go new file mode 100644 index 0000000..957f92c --- /dev/null +++ b/stats/struct_flattener.go @@ -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)) +}