229 lines
4.7 KiB
Go
229 lines
4.7 KiB
Go
// 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),
|
|
P90: NewPercentile(0.9),
|
|
P95: NewPercentile(0.95),
|
|
},
|
|
}
|
|
|
|
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)
|
|
}
|