tracker/stats/stats.go

286 lines
5.9 KiB
Go
Raw Normal View History

// 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
2014-07-23 23:25:01 +02:00
import (
"time"
2014-07-28 19:25:48 +02:00
"github.com/pushrax/faststats"
2014-07-25 02:41:12 +02:00
"github.com/pushrax/flatjson"
2014-07-28 19:25:48 +02:00
"github.com/chihaya/chihaya/config"
2014-07-23 23:25:01 +02:00
)
const (
Announce = iota
Scrape
2014-07-21 17:42:05 +02:00
Completed
NewLeech
DeletedLeech
ReapedLeech
NewSeed
DeletedSeed
ReapedSeed
2014-07-21 17:42:05 +02:00
NewTorrent
DeletedTorrent
2014-07-21 17:42:05 +02:00
ReapedTorrent
AcceptedConnection
ClosedConnection
HandledRequest
ErroredRequest
ClientError
2014-07-22 20:57:36 +02:00
ResponseTime
)
2014-07-22 01:08:08 +02:00
// DefaultStats is a default instance of stats tracking that uses an unbuffered
2014-07-23 09:59:23 +02:00
// channel for broadcasting events unless specified otherwise via a command
// line flag.
2014-07-23 23:25:01 +02:00
var DefaultStats *Stats
2014-07-22 01:08:08 +02:00
2014-07-25 01:35:15 +02:00
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.
}
2014-07-21 17:42:05 +02:00
type PeerStats struct {
2014-07-25 02:30:11 +02:00
PeerClassStats `json:"Peers"` // Stats for all peers.
2014-07-25 01:35:15 +02:00
2014-07-25 02:30:11 +02:00
Seeds PeerClassStats // Stats for seeds only.
Completed uint64 // Number of transitions from leech to seed.
2014-07-21 17:42:05 +02:00
}
2014-07-22 20:57:36 +02:00
type PercentileTimes struct {
2014-07-28 19:25:48 +02:00
P50 *faststats.Percentile
P90 *faststats.Percentile
P95 *faststats.Percentile
2014-07-22 20:57:36 +02:00
}
type Stats struct {
2014-07-25 01:35:15 +02:00
Started time.Time // Time at which Chihaya was booted.
2014-07-21 17:42:05 +02:00
2014-07-25 01:35:15 +02:00
OpenConnections uint64 `json:"Connections.Open"`
ConnectionsAccepted uint64 `json:"Connections.Accepted"`
BytesTransmitted uint64 `json:"BytesTransmitted"`
2014-07-21 17:42:05 +02:00
2014-09-25 17:34:27 +02:00
GoRoutines int `json:"Runtime.GoRoutines"`
2014-07-25 01:35:15 +02:00
RequestsHandled uint64 `json:"Requests.Handled"`
RequestsErrored uint64 `json:"Requests.Errored"`
ClientErrors uint64 `json:"Requests.Bad"`
2014-07-25 01:36:57 +02:00
ResponseTime PercentileTimes
Announces uint64 `json:"Tracker.Announces"`
Scrapes uint64 `json:"Tracker.Scrapes"`
TorrentsSize uint64 `json:"Torrents.Size"`
2014-07-25 01:36:57 +02:00
TorrentsAdded uint64 `json:"Torrents.Added"`
TorrentsRemoved uint64 `json:"Torrents.Removed"`
TorrentsReaped uint64 `json:"Torrents.Reaped"`
IPv4Peers PeerStats `json:"Peers.IPv4"`
IPv6Peers PeerStats `json:"Peers.IPv6"`
2014-07-25 02:27:20 +02:00
*MemStatsWrapper `json:",omitempty"`
2014-07-22 20:57:36 +02:00
events chan int
ipv4PeerEvents chan int
ipv6PeerEvents chan int
2014-07-22 20:57:36 +02:00
responseTimeEvents chan time.Duration
2014-07-23 23:08:06 +02:00
recordMemStats <-chan time.Time
2014-07-25 01:35:15 +02:00
2014-07-25 06:47:29 +02:00
flattened flatjson.Map
}
2014-07-23 23:25:01 +02:00
func New(cfg config.StatsConfig) *Stats {
s := &Stats{
2014-07-25 01:35:15 +02:00
Started: time.Now(),
events: make(chan int, cfg.BufferSize),
2014-07-22 20:57:36 +02:00
2014-09-25 17:34:27 +02:00
GoRoutines: 0,
2014-07-23 23:25:01 +02:00
ipv4PeerEvents: make(chan int, cfg.BufferSize),
ipv6PeerEvents: make(chan int, cfg.BufferSize),
responseTimeEvents: make(chan time.Duration, cfg.BufferSize),
2014-07-22 20:57:36 +02:00
ResponseTime: PercentileTimes{
2014-07-28 19:25:48 +02:00
P50: faststats.NewPercentile(0.5),
P90: faststats.NewPercentile(0.9),
P95: faststats.NewPercentile(0.95),
2014-07-22 20:57:36 +02:00
},
}
2014-07-23 23:25:01 +02:00
if cfg.IncludeMem {
2014-07-25 02:27:20 +02:00
s.MemStatsWrapper = NewMemStatsWrapper(cfg.VerboseMem)
2014-07-23 23:25:01 +02:00
s.recordMemStats = time.NewTicker(cfg.MemUpdateInterval.Duration).C
2014-07-23 23:08:06 +02:00
}
2014-07-25 02:41:12 +02:00
s.flattened = flatjson.Flatten(s)
go s.handleEvents()
return s
}
2014-07-25 06:47:29 +02:00
func (s *Stats) Flattened() flatjson.Map {
2014-07-25 01:35:15 +02:00
return s.flattened
}
func (s *Stats) Close() {
close(s.events)
}
func (s *Stats) Uptime() time.Duration {
2014-07-25 01:35:15 +02:00
return time.Since(s.Started)
}
func (s *Stats) RecordEvent(event int) {
s.events <- event
}
2014-08-01 18:37:35 +02:00
func (s *Stats) RecordPeerEvent(event int, ipv6 bool) {
if ipv6 {
s.ipv6PeerEvents <- event
2014-08-01 18:37:35 +02:00
} else {
s.ipv4PeerEvents <- event
}
}
2014-07-22 18:42:56 +02:00
func (s *Stats) RecordTiming(event int, duration time.Duration) {
2014-07-22 20:57:36 +02:00
switch event {
case ResponseTime:
s.responseTimeEvents <- duration
default:
panic("stats: RecordTiming called with an unknown event")
}
2014-07-22 18:42:56 +02:00
}
func (s *Stats) handleEvents() {
for {
select {
case event := <-s.events:
s.handleEvent(event)
case event := <-s.ipv4PeerEvents:
s.handlePeerEvent(&s.IPv4Peers, event)
case event := <-s.ipv6PeerEvents:
s.handlePeerEvent(&s.IPv6Peers, event)
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)
2014-07-23 23:08:06 +02:00
case <-s.recordMemStats:
2014-07-25 02:27:20 +02:00
s.MemStatsWrapper.Update()
}
}
}
func (s *Stats) handleEvent(event int) {
switch event {
case Announce:
s.Announces++
case Scrape:
s.Scrapes++
case NewTorrent:
s.TorrentsAdded++
s.TorrentsSize++
case DeletedTorrent:
s.TorrentsRemoved++
s.TorrentsSize--
case ReapedTorrent:
s.TorrentsReaped++
s.TorrentsSize--
case AcceptedConnection:
s.ConnectionsAccepted++
s.OpenConnections++
2014-07-22 01:08:08 +02:00
case ClosedConnection:
s.OpenConnections--
case HandledRequest:
s.RequestsHandled++
case ClientError:
s.ClientErrors++
2014-07-24 23:48:03 +02:00
case ErroredRequest:
s.RequestsErrored++
default:
panic("stats: RecordEvent called with an unknown event")
}
}
func (s *Stats) handlePeerEvent(ps *PeerStats, event int) {
switch event {
case Completed:
ps.Completed++
2014-07-25 01:35:15 +02:00
ps.Seeds.Current++
case NewLeech:
ps.Joined++
ps.Current++
case DeletedLeech:
ps.Left++
ps.Current--
case ReapedLeech:
ps.Reaped++
ps.Current--
case NewSeed:
2014-07-25 01:35:15 +02:00
ps.Seeds.Joined++
ps.Seeds.Current++
ps.Joined++
ps.Current++
case DeletedSeed:
2014-07-25 01:35:15 +02:00
ps.Seeds.Left++
ps.Seeds.Current--
ps.Left++
ps.Current--
case ReapedSeed:
2014-07-25 01:35:15 +02:00
ps.Seeds.Reaped++
ps.Seeds.Current--
ps.Reaped++
ps.Current--
default:
panic("stats: RecordPeerEvent called with an unknown event")
2014-07-22 20:57:36 +02:00
}
}
2014-07-22 18:42:56 +02:00
// RecordEvent broadcasts an event to the default stats queue.
2014-07-22 01:08:08 +02:00
func RecordEvent(event int) {
DefaultStats.RecordEvent(event)
}
2014-07-22 18:42:56 +02:00
// RecordPeerEvent broadcasts a peer event to the default stats queue.
2014-08-01 18:37:35 +02:00
func RecordPeerEvent(event int, ipv6 bool) {
DefaultStats.RecordPeerEvent(event, ipv6)
}
2014-07-22 18:42:56 +02:00
// RecordTiming broadcasts a timing event to the default stats queue.
func RecordTiming(event int, duration time.Duration) {
DefaultStats.RecordTiming(event, duration)
}