torrent stats
This commit is contained in:
parent
d5aa78cb39
commit
f9311284fc
16 changed files with 1541 additions and 987 deletions
src/delivery
|
@ -1,126 +0,0 @@
|
|||
package delivery
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"git.kmsign.ru/royalcat/tstor/src/sources/torrent"
|
||||
"github.com/anacrolix/missinggo/v2/filecache"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var apiStatusHandler = func(fc *filecache.Cache, ss *torrent.Stats) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
stat := gin.H{
|
||||
"torrentStats": ss.GlobalStats(),
|
||||
}
|
||||
|
||||
if fc != nil {
|
||||
stat["cacheItems"] = fc.Info().NumItems
|
||||
stat["cacheFilled"] = fc.Info().Filled / 1024 / 1024
|
||||
stat["cacheCapacity"] = fc.Info().Capacity / 1024 / 1024
|
||||
}
|
||||
|
||||
// TODO move to a struct
|
||||
ctx.JSON(http.StatusOK, stat)
|
||||
}
|
||||
}
|
||||
|
||||
// var apiServersHandler = func(ss []*service.Server) gin.HandlerFunc {
|
||||
// return func(ctx *gin.Context) {
|
||||
// var infos []*torrent.ServerInfo
|
||||
// for _, s := range ss {
|
||||
// infos = append(infos, s.Info())
|
||||
// }
|
||||
// ctx.JSON(http.StatusOK, infos)
|
||||
// }
|
||||
// }
|
||||
|
||||
// var apiRoutesHandler = func(ss *service.Stats) gin.HandlerFunc {
|
||||
// return func(ctx *gin.Context) {
|
||||
// s := ss.RoutesStats()
|
||||
// sort.Sort(torrent.ByName(s))
|
||||
// ctx.JSON(http.StatusOK, s)
|
||||
// }
|
||||
// }
|
||||
|
||||
// var apiAddTorrentHandler = func(s *service.Service) gin.HandlerFunc {
|
||||
// return func(ctx *gin.Context) {
|
||||
// route := ctx.Param("route")
|
||||
|
||||
// var json RouteAdd
|
||||
// if err := ctx.ShouldBindJSON(&json); err != nil {
|
||||
// ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
// return
|
||||
// }
|
||||
|
||||
// if err := s.AddMagnet(route, json.Magnet); err != nil {
|
||||
// ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
// return
|
||||
// }
|
||||
|
||||
// ctx.JSON(http.StatusOK, nil)
|
||||
// }
|
||||
// }
|
||||
|
||||
// var apiDelTorrentHandler = func(s *service.Service) gin.HandlerFunc {
|
||||
// return func(ctx *gin.Context) {
|
||||
// route := ctx.Param("route")
|
||||
// hash := ctx.Param("torrent_hash")
|
||||
|
||||
// if err := s.RemoveFromHash(route, hash); err != nil {
|
||||
// ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
// return
|
||||
// }
|
||||
|
||||
// ctx.JSON(http.StatusOK, nil)
|
||||
// }
|
||||
// }
|
||||
|
||||
var apiLogHandler = func(path string) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
max := math.Max(float64(-fi.Size()), -1024*8*8)
|
||||
_, err = f.Seek(int64(max), io.SeekEnd)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
ctx.Stream(func(w io.Writer) bool {
|
||||
_, err := b.ReadFrom(f)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = b.WriteTo(w)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -7,6 +7,14 @@ import (
|
|||
atorrent "github.com/anacrolix/torrent"
|
||||
)
|
||||
|
||||
func Apply[I any, O any](in []I, f func(I) O) []O {
|
||||
out := make([]O, len(in))
|
||||
for i, v := range in {
|
||||
out[i] = f(v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func MapPeerSource(source atorrent.PeerSource) string {
|
||||
switch source {
|
||||
case atorrent.PeerSourceDirect:
|
||||
|
@ -43,3 +51,14 @@ func MapTorrent(ctx context.Context, t *torrent.Controller) (*Torrent, error) {
|
|||
T: t,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func MapTorrentStats(s torrent.TorrentStats) *TorrentStats {
|
||||
return &TorrentStats{
|
||||
Timestamp: s.Timestamp,
|
||||
DownloadedBytes: uint(s.DownloadedBytes),
|
||||
UploadedBytes: uint(s.UploadedBytes),
|
||||
TotalPeers: uint(s.TotalPeers),
|
||||
ActivePeers: uint(s.ActivePeers),
|
||||
ConnectedSeeders: uint(s.ConnectedSeeders),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,6 +184,22 @@ type Torrent struct {
|
|||
T *torrent.Controller `json:"-"`
|
||||
}
|
||||
|
||||
type TorrentClientStats struct {
|
||||
BytesWritten int64 `json:"bytesWritten"`
|
||||
BytesWrittenData int64 `json:"bytesWrittenData"`
|
||||
BytesRead int64 `json:"bytesRead"`
|
||||
BytesReadData int64 `json:"bytesReadData"`
|
||||
BytesReadUsefulData int64 `json:"bytesReadUsefulData"`
|
||||
BytesReadUsefulIntendedData int64 `json:"bytesReadUsefulIntendedData"`
|
||||
ChunksWritten int64 `json:"chunksWritten"`
|
||||
ChunksRead int64 `json:"chunksRead"`
|
||||
ChunksReadUseful int64 `json:"chunksReadUseful"`
|
||||
ChunksReadWasted int64 `json:"chunksReadWasted"`
|
||||
MetadataChunksRead int64 `json:"metadataChunksRead"`
|
||||
PiecesDirtiedGood int64 `json:"piecesDirtiedGood"`
|
||||
PiecesDirtiedBad int64 `json:"piecesDirtiedBad"`
|
||||
}
|
||||
|
||||
type TorrentDaemonMutation struct {
|
||||
ValidateTorrent bool `json:"validateTorrent"`
|
||||
SetTorrentPriority bool `json:"setTorrentPriority"`
|
||||
|
@ -191,8 +207,9 @@ type TorrentDaemonMutation struct {
|
|||
}
|
||||
|
||||
type TorrentDaemonQuery struct {
|
||||
Torrents []*Torrent `json:"torrents"`
|
||||
Stats *TorrentStats `json:"stats"`
|
||||
Torrents []*Torrent `json:"torrents"`
|
||||
ClientStats *TorrentClientStats `json:"clientStats"`
|
||||
StatsHistory []*TorrentStats `json:"statsHistory"`
|
||||
}
|
||||
|
||||
type TorrentFs struct {
|
||||
|
@ -271,19 +288,12 @@ func (this TorrentProgress) GetCurrent() int64 { return this.Current }
|
|||
func (this TorrentProgress) GetTotal() int64 { return this.Total }
|
||||
|
||||
type TorrentStats struct {
|
||||
BytesWritten int64 `json:"bytesWritten"`
|
||||
BytesWrittenData int64 `json:"bytesWrittenData"`
|
||||
BytesRead int64 `json:"bytesRead"`
|
||||
BytesReadData int64 `json:"bytesReadData"`
|
||||
BytesReadUsefulData int64 `json:"bytesReadUsefulData"`
|
||||
BytesReadUsefulIntendedData int64 `json:"bytesReadUsefulIntendedData"`
|
||||
ChunksWritten int64 `json:"chunksWritten"`
|
||||
ChunksRead int64 `json:"chunksRead"`
|
||||
ChunksReadUseful int64 `json:"chunksReadUseful"`
|
||||
ChunksReadWasted int64 `json:"chunksReadWasted"`
|
||||
MetadataChunksRead int64 `json:"metadataChunksRead"`
|
||||
PiecesDirtiedGood int64 `json:"piecesDirtiedGood"`
|
||||
PiecesDirtiedBad int64 `json:"piecesDirtiedBad"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
DownloadedBytes uint `json:"downloadedBytes"`
|
||||
UploadedBytes uint `json:"uploadedBytes"`
|
||||
TotalPeers uint `json:"totalPeers"`
|
||||
ActivePeers uint `json:"activePeers"`
|
||||
ConnectedSeeders uint `json:"connectedSeeders"`
|
||||
}
|
||||
|
||||
type TorrentsFilter struct {
|
||||
|
|
|
@ -8,10 +8,13 @@ import (
|
|||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
||||
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
||||
"git.kmsign.ru/royalcat/tstor/src/sources/torrent"
|
||||
|
||||
tinfohash "github.com/anacrolix/torrent/types/infohash"
|
||||
)
|
||||
|
||||
// Torrents is the resolver for the torrents field.
|
||||
|
@ -86,10 +89,10 @@ func (r *torrentDaemonQueryResolver) Torrents(ctx context.Context, obj *model.To
|
|||
return tr, nil
|
||||
}
|
||||
|
||||
// Stats is the resolver for the stats field.
|
||||
func (r *torrentDaemonQueryResolver) Stats(ctx context.Context, obj *model.TorrentDaemonQuery) (*model.TorrentStats, error) {
|
||||
// ClientStats is the resolver for the clientStats field.
|
||||
func (r *torrentDaemonQueryResolver) ClientStats(ctx context.Context, obj *model.TorrentDaemonQuery) (*model.TorrentClientStats, error) {
|
||||
stats := r.Service.Stats()
|
||||
return &model.TorrentStats{
|
||||
return &model.TorrentClientStats{
|
||||
BytesWritten: stats.BytesWritten.Int64(),
|
||||
BytesRead: stats.BytesRead.Int64(),
|
||||
BytesWrittenData: stats.BytesWrittenData.Int64(),
|
||||
|
@ -106,6 +109,33 @@ func (r *torrentDaemonQueryResolver) Stats(ctx context.Context, obj *model.Torre
|
|||
}, nil
|
||||
}
|
||||
|
||||
// StatsHistory is the resolver for the statsHistory field.
|
||||
func (r *torrentDaemonQueryResolver) StatsHistory(ctx context.Context, obj *model.TorrentDaemonQuery, since time.Time, infohash *string) ([]*model.TorrentStats, error) {
|
||||
var stats []torrent.TorrentStats
|
||||
if infohash == nil {
|
||||
stats, err := r.Service.StatsHistory(ctx, since)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return model.Apply(stats, model.MapTorrentStats), nil
|
||||
} else if *infohash == "total" {
|
||||
var err error
|
||||
stats, err = r.Service.TotalStatsHistory(ctx, since)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
ih := tinfohash.FromHexString(*infohash)
|
||||
var err error
|
||||
stats, err = r.Service.TorrentStatsHistory(ctx, since, ih)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return model.Apply(stats, model.MapTorrentStats), nil
|
||||
}
|
||||
|
||||
// TorrentDaemonQuery returns graph.TorrentDaemonQueryResolver implementation.
|
||||
func (r *Resolver) TorrentDaemonQuery() graph.TorrentDaemonQueryResolver {
|
||||
return &torrentDaemonQueryResolver{r}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue