Documentation
¶
Overview ¶
Package stats records owner analytics from observed playback and provider data. It never reconstructs device or bandwidth information from watch counts.
Index ¶
- type AccountRef
- type BandwidthRow
- type Bucket
- type Cost
- type Dashboard
- func (d *Dashboard) Bandwidth(q Query) Page
- func (d *Dashboard) Device(id int64) (Device, bool)
- func (d *Dashboard) Devices() []Device
- func (d *Dashboard) Media(q Query) Page
- func (d *Dashboard) RecordBytes(accountID int64, accountName, clientID, deviceName, platform string, lan bool, ...)
- func (d *Dashboard) RecordPlay(accountID int64, accountName, clientID string, metadataType int, ...)
- func (d *Dashboard) SeeDevice(clientID, name, platform string) Device
- type DashboardOptions
- type DecisionCounts
- type Device
- type DeviceCount
- type ExperienceCause
- type ExperienceDay
- type ExperienceMetrics
- type ExperienceOverview
- type ExperiencePlay
- type ExperienceViewer
- type ExperienceViewerRow
- type IndexerUsage
- type MediaRow
- type NewsletterSettings
- type Options
- type Page
- type Play
- type Query
- type ReasonCount
- type Service
- func (s *Service) Close(ctx context.Context) error
- func (s *Service) Dashboard() *Dashboard
- func (s *Service) Experience(ctx context.Context, days int) (ExperienceOverview, error)
- func (s *Service) ExperienceForViewer(ctx context.Context, user int64, days int) (ExperienceViewer, error)
- func (s *Service) ExperienceHandler() http.Handler
- func (s *Service) Handler() http.Handler
- func (s *Service) History(ctx context.Context, since, until time.Time, user int64, limit int) ([]Play, error)
- func (s *Service) IndexerMonth(ctx context.Context) ([]IndexerUsage, error)
- func (s *Service) MaybeNewsletter(ctx context.Context) error
- func (s *Service) NewsletterSettings(ctx context.Context) (NewsletterSettings, error)
- func (s *Service) ObserveIndexer(e newznab.Event)
- func (s *Service) ObservePlayback(ev playback.Event)
- func (s *Service) ObserveState(ev state.Event)
- func (s *Service) OutboxStatus() (st store.OutboxStatus, ok bool)
- func (s *Service) PlayByID(ctx context.Context, id string) (map[string]any, error)
- func (s *Service) Prune(ctx context.Context) (int64, error)
- func (s *Service) Record(ctx context.Context, p *core.PlaySession, ended bool, problem string) error
- func (s *Service) RecordIndexer(ctx context.Context, e newznab.Event) error
- func (s *Service) RenderNewsletter(ctx context.Context, userID int64) (string, error)
- func (s *Service) Run(ctx context.Context)
- func (s *Service) Sample(ctx context.Context)
- func (s *Service) SaveNewsletter(ctx context.Context, v NewsletterSettings) error
- func (s *Service) SetCost(ctx context.Context, c Cost) error
- func (s *Service) SnapshotProviders(ctx context.Context, ps []usenet.ProviderStats) error
- func (s *Service) Summary(ctx context.Context, since, until time.Time, user int64) (Summary, error)
- type Summary
- type TranscodeReason
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AccountRef ¶ added in v0.3.0
AccountRef labels an account id on a statistics page.
type BandwidthRow ¶ added in v0.3.0
type BandwidthRow struct {
AccountID int64
DeviceID int64
Timespan int
At int64
Lan int
Bytes int64
}
BandwidthRow is one StatisticsBandwidth series point.
type Dashboard ¶ added in v0.3.0
type Dashboard struct {
// contains filtered or unexported fields
}
Dashboard is this node's in-memory Plex Dashboard store: devices, per-second bandwidth, and completed-play counts. Clustered nodes each keep their own rings; there is no request pin, so a client reads the node it connected to. Request paths only walk the requested buckets.
func NewDashboard ¶ added in v0.3.0
func NewDashboard(opts DashboardOptions) *Dashboard
NewDashboard creates the anonymous device (id 1) the way a PMS does at start.
func (*Dashboard) Bandwidth ¶ added in v0.3.0
Bandwidth returns the filtered, paged bandwidth series.
func (*Dashboard) Devices ¶ added in v0.3.0
Devices lists every device this process has seen, id order.
func (*Dashboard) RecordBytes ¶ added in v0.3.0
func (d *Dashboard) RecordBytes(accountID int64, accountName, clientID, deviceName, platform string, lan bool, n int64)
RecordBytes adds client-facing playback bytes to the per-second series.
func (*Dashboard) RecordPlay ¶ added in v0.3.0
func (d *Dashboard) RecordPlay(accountID int64, accountName, clientID string, metadataType int, durationSec int64)
RecordPlay counts one completed play into the media series. durationSec is the item's length in seconds (PMS StatisticsMedia duration), summed per bucket.
type DashboardOptions ¶ added in v0.3.0
DashboardOptions configure the clock (tests).
type DecisionCounts ¶ added in v0.3.3
type DeviceCount ¶ added in v0.3.3
type ExperienceCause ¶ added in v0.3.3
type ExperienceCause struct {
Kind string `json:"kind"`
Reason string `json:"reason,omitempty"`
Count int `json:"count,omitempty"`
RemotePct float64 `json:"remotePct,omitempty"`
SourceKbps int `json:"sourceKbps,omitempty"`
Device string `json:"device,omitempty"`
ReasonKind string `json:"reasonKind,omitempty"`
ReasonDetail string `json:"reasonDetail,omitempty"`
Switches int `json:"switches,omitempty"`
Plays int `json:"plays,omitempty"`
MedianMS int64 `json:"medianMs,omitempty"`
PerHour *float64 `json:"perHour,omitempty"`
}
ExperienceCause is the most likely cause of a viewer's experience, picked by simple rules; the console words it.
type ExperienceDay ¶ added in v0.3.3
type ExperienceMetrics ¶ added in v0.3.3
type ExperienceMetrics struct {
Plays int `json:"plays"`
MeasuredPlays int `json:"measuredPlays"`
StartMedianMS *int64 `json:"startMedianMs"`
StartP90MS *int64 `json:"startP90Ms"`
BufferEventsPerHour *float64 `json:"bufferEventsPerHour"`
BufferMS int64 `json:"bufferMs"`
MeasuredWatchedMS int64 `json:"measuredWatchedMs"`
FailedStarts int `json:"failedStarts"`
FailedReasons []ReasonCount `json:"failedReasons"`
Decisions DecisionCounts `json:"decisions"`
TopTranscodeReason *TranscodeReason `json:"topTranscodeReason"`
SourceKbps *int `json:"sourceKbps"`
DeliveredKbps *int `json:"deliveredKbps"`
UHDPct *float64 `json:"uhdPct"`
HDRPct *float64 `json:"hdrPct"`
Switches int `json:"switches"`
DownSwitches int `json:"downSwitches"`
Finished int `json:"finished"`
Abandoned int `json:"abandoned"`
RemotePct *float64 `json:"remotePct"`
WatchedMS int64 `json:"watchedMs"`
// TopDevice is the device with the most plays, as recorded.
TopDevice *DeviceCount `json:"topDevice"`
}
ExperienceMetrics summarises one viewer's (or the household's) plays.
type ExperienceOverview ¶ added in v0.3.3
type ExperienceOverview struct {
Days int `json:"days"`
Since int64 `json:"since"`
MeasuredSince int64 `json:"measuredSince"`
Household ExperienceMetrics `json:"household"`
Viewers []ExperienceViewerRow `json:"viewers"`
}
type ExperiencePlay ¶ added in v0.3.3
type ExperiencePlay struct {
ID string `json:"id"`
UserID int64 `json:"userId"`
Username string `json:"-"`
Title string `json:"title"`
Device string `json:"device"`
Platform string `json:"platform"`
Started int64 `json:"started"`
Ended int64 `json:"-"`
Measured bool `json:"measured"`
StartMS *int64 `json:"startMs"`
BufferEvents *int `json:"bufferEvents"`
BufferMS *int64 `json:"bufferMs"`
Failed bool `json:"failed"`
Error string `json:"error"`
Decision string `json:"decision"`
ReasonKind string `json:"reasonKind"`
ReasonDetail string `json:"reasonDetail"`
Origin string `json:"origin"`
Release string `json:"release"`
Remote bool `json:"remote"`
WatchedMS int64 `json:"watchedMs"`
DurationMS int64 `json:"durationMs"`
Switches int `json:"switches"`
Completed bool `json:"-"`
// contains filtered or unexported fields
}
ExperiencePlay is one recorded play with its experience.
type ExperienceViewer ¶ added in v0.3.3
type ExperienceViewer struct {
UserID int64 `json:"userId"`
Username string `json:"username"`
Days int `json:"days"`
Since int64 `json:"since"`
MeasuredSince int64 `json:"measuredSince"`
Metrics ExperienceMetrics `json:"metrics"`
Trend []ExperienceDay `json:"trend"`
Plays []ExperiencePlay `json:"plays"`
Cause ExperienceCause `json:"cause"`
}
type ExperienceViewerRow ¶ added in v0.3.3
type ExperienceViewerRow struct {
UserID int64 `json:"userId"`
Username string `json:"username"`
Metrics ExperienceMetrics `json:"metrics"`
}
type IndexerUsage ¶
type IndexerUsage struct {
Indexer string `json:"indexer"`
Searches int64 `json:"searches"`
Successes int64 `json:"successes"`
Grabs int64 `json:"grabs"`
GrabSuccess int64 `json:"grabSuccess"`
Budgets int64 `json:"budgets"`
LatencyMS int64 `json:"latencyMs"`
AgeSeconds int64 `json:"ageSeconds"`
AgeSamples int64 `json:"ageSamples"`
}
type MediaRow ¶ added in v0.3.0
type MediaRow struct {
AccountID int64
DeviceID int64
Timespan int
At int64
MetadataType int
Count int64
Duration int64 // sum of item lengths in seconds
}
MediaRow is one StatisticsMedia series point.
type NewsletterSettings ¶
type Options ¶
type Options struct {
Newsletter func(context.Context, string) error
StopSession func(string) error
SessionExists func(string) bool
Playback core.Playback
Providers func() []usenet.ProviderStats
Indexers func(context.Context) ([]newznab.Remaining, error)
Logger *slog.Logger
Now func() time.Time
// NodeID identifies this node in a cluster. Plays are tagged with it and
// New closes only this node's unfinished plays. Empty for a standalone
// server, which therefore closes every unfinished play as before.
NodeID string
// Availability, when set (clustered nodes), holds play records the
// database cannot take through an outage (the latest per play) and
// skips periodic provider snapshots while the database is down; their
// counters are cumulative, so nothing is lost. Nil writes synchronously.
Availability *store.Availability
// MetadataType maps a rating key to a Plex metadata type number for
// Dashboard media rows. Zero or nil skips the play.
MetadataType func(core.RatingKey) int
// AccountName labels a user on Dashboard media rows.
AccountName func(int64) string
}
type Page ¶ added in v0.3.0
type Page struct {
Rows []BandwidthRow
Media []MediaRow
Devices []Device
Accounts []AccountRef
TotalSize int
}
Page is one statistics answer: the series window and the Device/Account lookup for ids that window names.
type Play ¶
type Play struct {
ID string `json:"id"`
UserID int64 `json:"userId"`
Key int64 `json:"ratingKey"`
Username string `json:"username"`
Title string `json:"title"`
Platform string `json:"platform"`
Device string `json:"device"`
Decision string `json:"decision"`
Started int64 `json:"started"`
Updated int64 `json:"updated"`
Ended int64 `json:"ended"`
Bytes int64 `json:"bytes"`
WatchedMS int64 `json:"watchedMs"`
DurationMS int64 `json:"durationMs"`
Completed bool `json:"completed"`
Failovers int `json:"failovers"`
Error string `json:"error"`
}
type Query ¶ added in v0.3.0
type Query struct {
Timespans []int
AccountID int64
DeviceID int64
HasAccount, HasDevice, HasLan bool
Lan int
AtMin, AtMax int64
ExactAt int64
HasExact, HasAtMin, HasAtMax bool
Offset int
Limit int
HasLimit bool
Sort string
Desc bool
}
Query is the parsed Dashboard filter. Timespans empty means "every kept rollup except per-second" for bandwidth/media (0..4).
type ReasonCount ¶ added in v0.3.3
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
func (*Service) Close ¶
Close writes play records still held from a database outage (bounded by ctx); a no-op on a standalone server.
func (*Service) Dashboard ¶ added in v0.3.0
Dashboard is the in-memory Plex Dashboard store for this node.
func (*Service) Experience ¶ added in v0.3.3
Experience returns the household and per-viewer experience for the last days (7 or 30).
func (*Service) ExperienceForViewer ¶ added in v0.3.3
func (s *Service) ExperienceForViewer(ctx context.Context, user int64, days int) (ExperienceViewer, error)
ExperienceForViewer returns one viewer's metrics, daily trend, last 20 plays and the most likely cause of a poor experience.
func (*Service) ExperienceHandler ¶ added in v0.3.3
ExperienceHandler serves Activity › Experience; mount it behind owner authentication with /experience stripped.
func (*Service) IndexerMonth ¶
func (s *Service) IndexerMonth(ctx context.Context) ([]IndexerUsage, error)
func (*Service) MaybeNewsletter ¶
MaybeNewsletter claims each weekly slot in a durable ledger before enqueueing. Queue errors remain visible and are not retried automatically, preventing duplicates after an ambiguous process interruption.
func (*Service) NewsletterSettings ¶
func (s *Service) NewsletterSettings(ctx context.Context) (NewsletterSettings, error)
func (*Service) ObserveIndexer ¶
func (*Service) ObservePlayback ¶
ObservePlayback is suitable for playback.Manager.Subscribe.
func (*Service) ObserveState ¶
ObserveState correlates timeline data only when exactly one active session matches the user/title. Plex state events do not identify the client.
func (*Service) OutboxStatus ¶
func (s *Service) OutboxStatus() (st store.OutboxStatus, ok bool)
OutboxStatus reports play records held through a database outage; ok is false on a standalone server.
func (*Service) PlayByID ¶ added in v0.3.3
PlayByID returns one recorded play with its experience fields merged into the History play's; nil when unknown.
func (*Service) Prune ¶
Prune deletes per-day indexer/provider counters older than dailyRetentionDays. It is meant to be called periodically.
func (*Service) Record ¶
func (s *Service) Record(ctx context.Context, p *core.PlaySession, ended bool, problem string) error
Record observes a playback session and upserts the history row of the viewing it belongs to (viewing.go): sessions of one viewer, client and title within the play-session window share a row, and a row is written only once media was delivered or the attempt failed. On a clustered node a record the database cannot take is held and written when it is back.
func (*Service) RecordIndexer ¶
func (*Service) RenderNewsletter ¶
func (*Service) SaveNewsletter ¶
func (s *Service) SaveNewsletter(ctx context.Context, v NewsletterSettings) error
func (*Service) SnapshotProviders ¶
type Summary ¶
type Summary struct {
Plays int64 `json:"plays"`
WatchedMS int64 `json:"watchedMs"`
Bytes int64 `json:"bytes"`
Completed int64 `json:"completed"`
Daily []Bucket `json:"daily"`
Hourly []Bucket `json:"hourly"`
Platforms []Bucket `json:"platforms"`
Titles []Bucket `json:"titles"`
Users []Bucket `json:"users"`
Since time.Time `json:"since"`
Until time.Time `json:"until"`
}