// Copyright 2014 The Chihaya Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. // Package stats implements a means of tracking processing statistics for a // BitTorrent tracker. package stats import "time" const ( Announce = iota Scrape CompletedIPv4 NewLeechIPv4 DeletedLeechIPv4 ReapedLeechIPv4 NewSeedIPv4 DeletedSeedIPv4 ReapedSeedIPv4 CompletedIPv6 NewLeechIPv6 DeletedLeechIPv6 ReapedLeechIPv6 NewSeedIPv6 DeletedSeedIPv6 ReapedSeedIPv6 NewTorrent DeletedTorrent ReapedTorrent AcceptedConnection ClosedConnection HandledRequest ErroredRequest ResponseTime ) // DefaultStats is a default instance of stats tracking that uses an unbuffered // channel for broadcasting events. var DefaultStats *Stats func init() { DefaultStats = New(0) } type PeerStats struct { // Stats for all peers. Completed uint64 `json:"completed"` Joined uint64 `json:"joined"` Left uint64 `json:"left"` Reaped uint64 `json:"reaped"` // Stats for seeds only (subset of total). SeedsJoined uint64 `json:"seeds_joined"` SeedsLeft uint64 `json:"seeds_left"` SeedsReaped uint64 `json:"seeds_reaped"` } type PercentileTimes struct { P50 *Percentile P90 *Percentile P95 *Percentile } type Stats struct { Start time.Time `json:"start_time"` Announces uint64 `json:"announces"` Scrapes uint64 `json:"scrapes"` IPv4Peers PeerStats `json:"ipv4_peers"` IPv6Peers PeerStats `json:"ipv6_peers"` 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"` RequestsHandled uint64 `json:"requests_handled"` RequestsErrored uint64 `json:"requests_errored"` ResponseTime PercentileTimes `json:"response_time"` events chan int responseTimeEvents chan time.Duration } func New(chanSize int) *Stats { s := &Stats{ Start: time.Now(), events: make(chan int, chanSize), responseTimeEvents: make(chan time.Duration, chanSize), ResponseTime: PercentileTimes{ P50: NewPercentile(0.5, 128), P90: NewPercentile(0.9, 128), P95: NewPercentile(0.95, 128), }, } go s.handleEvents() go s.handleTimings() return s } func (s *Stats) Close() { close(s.events) } func (s *Stats) Uptime() time.Duration { return time.Since(s.Start) } func (s *Stats) RecordEvent(event int) { s.events <- event } func (s *Stats) RecordTiming(event int, duration time.Duration) { switch event { case ResponseTime: s.responseTimeEvents <- duration default: panic("stats: RecordTiming called with an unknown event") } } func (s *Stats) handleEvents() { for event := range s.events { switch event { case Announce: s.Announces++ case Scrape: s.Scrapes++ case CompletedIPv4: s.IPv4Peers.Completed++ case NewLeechIPv4: s.IPv4Peers.Joined++ case DeletedLeechIPv4: s.IPv4Peers.Left++ case ReapedLeechIPv4: s.IPv4Peers.Reaped++ case NewSeedIPv4: s.IPv4Peers.SeedsJoined++ s.IPv4Peers.Joined++ case DeletedSeedIPv4: s.IPv4Peers.SeedsLeft++ s.IPv4Peers.Left++ case ReapedSeedIPv4: s.IPv4Peers.SeedsReaped++ s.IPv4Peers.Reaped++ case CompletedIPv6: s.IPv6Peers.Completed++ case NewLeechIPv6: s.IPv6Peers.Joined++ case DeletedLeechIPv6: s.IPv6Peers.Left++ case ReapedLeechIPv6: s.IPv6Peers.Reaped++ case NewSeedIPv6: s.IPv6Peers.SeedsJoined++ s.IPv6Peers.Joined++ case DeletedSeedIPv6: s.IPv6Peers.SeedsLeft++ s.IPv6Peers.Left++ case ReapedSeedIPv6: s.IPv6Peers.SeedsReaped++ s.IPv6Peers.Reaped++ case NewTorrent: s.TorrentsAdded++ case DeletedTorrent: s.TorrentsRemoved++ case ReapedTorrent: s.TorrentsReaped++ case AcceptedConnection: s.ConnectionsAccepted++ s.OpenConnections++ case ClosedConnection: s.OpenConnections-- case HandledRequest: s.RequestsHandled++ case ErroredRequest: s.RequestsErrored++ default: panic("stats: RecordEvent called with an unknown event") } } } func (s *Stats) handleTimings() { for { select { case duration := <-s.responseTimeEvents: f := float64(duration) / float64(time.Millisecond) s.ResponseTime.P50.AddSample(f) s.ResponseTime.P90.AddSample(f) s.ResponseTime.P95.AddSample(f) } } } // RecordEvent broadcasts an event to the default stats queue. func RecordEvent(event int) { DefaultStats.RecordEvent(event) } // RecordTiming broadcasts a timing event to the default stats queue. func RecordTiming(event int, duration time.Duration) { DefaultStats.RecordTiming(event, duration) }