use constant size arrays for storage functions

This commit is contained in:
Michał Adamski
2025-01-26 01:39:46 +01:00
parent f5081d64d8
commit 57ba81575d
6 changed files with 85 additions and 49 deletions

View File

@@ -3,3 +3,6 @@ build:
deploy: build
rsync -avzL --exclude '*.fiber.gz' docs privtracker privtracker:web/
test:
go test -bench . -benchmem

View File

@@ -1,7 +1,9 @@
package main
import (
"crypto/sha1"
"net"
"time"
"github.com/gofiber/fiber/v2"
"github.com/jackpal/bencode-go"
@@ -40,29 +42,32 @@ func announce(c *fiber.Ctx) error {
if err != nil {
return err
}
room := c.Params("room")
ip := net.ParseIP(c.IP())
if ip == nil {
ip = c.Context().RemoteIP()
}
if req.Numwant == 0 {
if req.Numwant < 1 {
req.Numwant = 30
}
swarmHash := sha1.Sum([]byte(c.Params("room") + req.InfoHash))
peer := NewPeer(ip, req.Port)
switch req.Event {
case "stopped":
DeletePeer(room, req.InfoHash, peer)
DeletePeer(swarmHash, peer)
case "completed":
GraduateLeecher(room, req.InfoHash, peer)
GraduateLeecher(swarmHash, peer)
default:
PutPeer(room, req.InfoHash, peer, req.IsSeeding())
PutPeer(swarmHash, peer, req.IsSeeding())
}
peersIPv4, peersIPv6, numSeeders, numLeechers := GetPeers(room, req.InfoHash, peer, req.IsSeeding(), req.Numwant)
interval := 120
if numSeeders == 0 {
interval /= 2
} else if numLeechers == 0 {
interval *= 4
peersIPv4, peersIPv6, numSeeders, numLeechers := GetPeers(swarmHash, peer, req.IsSeeding(), req.Numwant)
interval := int(time.Now().Unix()+int64(swarmHash[0]))%256 + 60
switch {
// case numSeeders == 0:
// interval -= 30
case numLeechers == 0:
interval += 240
case numSeeders+numLeechers > 10:
interval += 480
}
resp := AnnounceResponse{
Interval: interval,

19
main.go
View File

@@ -16,20 +16,21 @@ import (
"golang.org/x/crypto/acme/autocert"
)
var port = os.Getenv("PORT")
func main() {
port := os.Getenv("PORT")
tlsEnabled := port == "443"
if port == "" {
port = "1337"
}
config := fiber.Config{
AppName: "PrivTracker",
ServerHeader: "PrivTracker",
ReadTimeout: time.Second * 245,
WriteTimeout: time.Second * 30,
Network: fiber.NetworkTCP,
GETOnly: true,
AppName: "PrivTracker",
ServerHeader: "PrivTracker",
ReadTimeout: time.Second * 245,
WriteTimeout: time.Second * 30,
Network: fiber.NetworkTCP,
GETOnly: true,
DisableKeepalive: true,
Immutable: true,
}
// if you disable TLS, then I guess you want to use existing proxy
if !tlsEnabled {
@@ -38,7 +39,7 @@ func main() {
config.ProxyHeader = fiber.HeaderXForwardedFor
}
go Cleanup(time.Second * 600)
go Cleanup(time.Minute * 16)
app := fiber.New(config)
app.Use(recover.New())

View File

@@ -1,6 +1,8 @@
package main
import (
"crypto/sha1"
"github.com/gofiber/fiber/v2"
"github.com/jackpal/bencode-go"
)
@@ -25,7 +27,8 @@ func scrape(c *fiber.Ctx) error {
if err != nil {
return err
}
numSeeders, numLeechers := GetStats(c.Params("room"), req.InfoHash)
swarmHash := sha1.Sum([]byte(c.Params("room") + req.InfoHash))
numSeeders, numLeechers := GetStats(swarmHash)
resp := ScrapeResponse{
Files: map[string]Stat{
req.InfoHash: {

View File

@@ -5,24 +5,18 @@ import (
"crypto/sha1"
"encoding/binary"
"fmt"
"log"
"net"
"runtime"
"sync"
"time"
)
type Hash [20]byte // we use sha1 and we are not affraid of hash collisions
type Peer [18]byte // 16 bytes for IP and 2 bytes for port number
type Hash [sha1.Size]byte // we use sha1 and we are not affraid of hash collisions
type Peer [18]byte // 16 bytes for IP and 2 bytes for port number
func NewPeer(ip net.IP, port uint16) (peer Peer) {
copy(peer[:], ip)
peer[16] = byte(port >> 8)
peer[17] = byte(port)
return
}
func (peer Peer) String() string {
return fmt.Sprintf("%s:%d", net.IP(peer[:16]), binary.BigEndian.Uint16(peer[16:]))
}
var shards = NewShards(256)
var v4InV6Prefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}
type shard struct {
swarms map[Hash]Swarm
@@ -41,10 +35,19 @@ func NewSwarm() Swarm {
}
}
var shards = NewShards(512)
var v4InV6Prefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}
func NewPeer(ip net.IP, port uint16) (peer Peer) {
copy(peer[:16], ip)
binary.BigEndian.PutUint16(peer[16:18], port)
return
}
func shardIndex(hash [20]byte) int {
func (peer Peer) String() string {
ip := net.IP(peer[:16])
port := binary.BigEndian.Uint16(peer[16:18])
return fmt.Sprintf("%s:%d", ip, port)
}
func shardIndex(hash Hash) int {
return int(binary.BigEndian.Uint16(hash[:2])) % len(shards)
}
@@ -58,8 +61,7 @@ func NewShards(size int) []*shard {
return shards
}
func PutPeer(room, infoHash string, peer Peer, seeding bool) {
h := sha1.Sum([]byte(room + infoHash))
func PutPeer(h Hash, peer Peer, seeding bool) {
shard := shards[shardIndex(h)]
shard.Lock()
if _, ok := shard.swarms[h]; !ok {
@@ -73,8 +75,7 @@ func PutPeer(room, infoHash string, peer Peer, seeding bool) {
shard.Unlock()
}
func DeletePeer(room, infoHash string, peer Peer) {
h := sha1.Sum([]byte(room + infoHash))
func DeletePeer(h Hash, peer Peer) {
shard := shards[shardIndex(h)]
shard.Lock()
if _, ok := shard.swarms[h]; !ok {
@@ -85,8 +86,7 @@ func DeletePeer(room, infoHash string, peer Peer) {
shard.Unlock()
}
func GraduateLeecher(room, infoHash string, peer Peer) {
h := sha1.Sum([]byte(room + infoHash))
func GraduateLeecher(h Hash, peer Peer) {
shard := shards[shardIndex(h)]
shard.Lock()
if _, ok := shard.swarms[h]; !ok {
@@ -97,10 +97,10 @@ func GraduateLeecher(room, infoHash string, peer Peer) {
shard.Unlock()
}
func GetPeers(room, infoHash string, client Peer, seeding bool, numWant uint) (peersIPv4, peersIPv6 []byte, numSeeders, numLeechers int) {
h := sha1.Sum([]byte(room + infoHash))
func GetPeers(h Hash, client Peer, seeding bool, numWant uint) (peersIPv4, peersIPv6 []byte, numSeeders, numLeechers int) {
shard := shards[shardIndex(h)]
shard.RLock()
// seeders don't need other seeders
if !seeding {
for peer := range shard.swarms[h].seeders {
@@ -135,8 +135,7 @@ func GetPeers(room, infoHash string, client Peer, seeding bool, numWant uint) (p
return
}
func GetStats(room, infoHash string) (numSeeders, numLeechers int) {
h := sha1.Sum([]byte(room + infoHash))
func GetStats(h Hash) (numSeeders, numLeechers int) {
shard := shards[shardIndex(h)]
shard.RLock()
numSeeders = len(shard.swarms[h].seeders)
@@ -148,25 +147,35 @@ func GetStats(room, infoHash string) (numSeeders, numLeechers int) {
func Cleanup(duration time.Duration) {
ticker := time.NewTicker(duration)
for range ticker.C {
var seeders, leechers, swarms, seedersDeleted, leechersDeleted, swarmsDeleted int
expiration := time.Now().Unix() - int64(duration.Seconds())
for _, shard := range shards {
shard.Lock()
swarms += len(shard.swarms)
for h, swarm := range shard.swarms {
seeders += len(swarm.seeders)
leechers += len(swarm.leechers)
for peer, lastSeen := range swarm.seeders {
if lastSeen < expiration {
seedersDeleted++
delete(swarm.seeders, peer)
}
}
for peer, lastSeen := range swarm.leechers {
if lastSeen < expiration {
leechersDeleted++
delete(swarm.leechers, peer)
}
}
if len(swarm.leechers) == 0 && len(swarm.seeders) == 0 {
swarmsDeleted++
delete(shard.swarms, h)
}
}
shard.Unlock()
}
log.Printf("seeders: %d (%d deleted), leechers: %d (%d deleted), swarms: %d (%d deleted)",
seeders, seedersDeleted, leechers, leechersDeleted, swarms, swarmsDeleted)
runtime.GC()
}
}

View File

@@ -1,14 +1,29 @@
package main
import (
"crypto/rand"
mrand "math/rand"
"net"
"testing"
)
func BenchmarkPutPeerGetPeers(b *testing.B) {
peer := NewPeer(net.ParseIP("127.0.0.1"), 6881)
var swarmHash Hash
for i := 0; i < b.N; i++ {
PutPeer("room", "infoHash", peer, true)
GetPeers("room", "infoHash", peer, true, 99)
for j := 0; j < 100; j++ {
rand.Read(swarmHash[:])
for k := 0; k < 10; k++ {
peer := NewPeer(net.ParseIP("127.0.0.1"), uint16(mrand.Uint32()))
PutPeer(swarmHash, peer, true)
GetPeers(swarmHash, peer, true, 99)
GraduateLeecher(swarmHash, peer)
GetPeers(swarmHash, peer, true, 99)
DeletePeer(swarmHash, peer)
GetPeers(swarmHash, peer, true, 99)
}
}
}
}