Skip to content

Commit

Permalink
Store ranges
Browse files Browse the repository at this point in the history
  • Loading branch information
davidvanleeuwen committed Mar 12, 2024
1 parent 793917d commit 86cb0a2
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 155 deletions.
40 changes: 14 additions & 26 deletions client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ export class Metrics {
bufferTimeInterval = 50;
bufferOffset = (this.bufferTimeInterval - 40) / 1000;

#playing = false;

selectedLanguageVTT?: string;
fullscreen = false;

Expand Down Expand Up @@ -150,7 +148,7 @@ export class Metrics {
} else {
this.identifier = args[1] as string;
}

if (args[2]) {
this.metadata = args[2] as object;
}
Expand Down Expand Up @@ -196,10 +194,10 @@ export class Metrics {

demonitor(): void {
if(!this.#monitoring) return;

this.#resizeObserver?.unobserve(this.#video);
this.#unrecordSession();

if (this.#session && this.#video) {
Data.stopSession(this.#video);
}
Expand Down Expand Up @@ -234,7 +232,6 @@ export class Metrics {

switch (event.type) {
case NativeEvents.PLAYING:
this.#playing = true;
this.#session?.push(
'event',
{
Expand All @@ -247,36 +244,27 @@ export class Metrics {

break;
case NativeEvents.PAUSE:
this.#playing = false;

this.#session?.push(
'event',
{
...params,
to: this.#video?.currentTime,
},
this.timeout
);

break;
case NativeEvents.SEEKED:
// ignore time shift
if(this.#playing && Math.abs(this.#lastLastLastCurrentTime - this.#lastLastCurrentTime) > 0.5) {
if (this.#video?.readyState === 4) {
this.#session?.push(
'event',
{
...params,
name: 'pause',
to: this.#lastLastLastCurrentTime,
to: this.#video?.currentTime,
},
this.timeout
);
}

break;
case NativeEvents.SEEKED:
// ignore time shift
if (Math.abs(this.#lastLastLastCurrentTime - this.#lastLastCurrentTime) > 0.5) {
this.#session?.push(
'event',
{
...params,
name: 'play',
to: this.#video?.currentTime,
name: 'pause',
to: this.#lastLastLastCurrentTime,
},
this.timeout
);
Expand All @@ -287,7 +275,7 @@ export class Metrics {
this.#lastLastCurrentTime = this.#lastCurrentTime;
this.#lastCurrentTime = this.#video?.currentTime;
break;

}
}
}
2 changes: 1 addition & 1 deletion server/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ services:
- db
db:
hostname: mave-db.internal
image: timescale/timescaledb:latest-pg14
image: timescale/timescaledb:latest-pg15
ports:
- "5432:5432"
environment:
Expand Down
101 changes: 48 additions & 53 deletions server/lib/mave_metrics/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule MaveMetrics.API do

import Ecto.Query, warn: false
import EctoCase
alias MaveMetrics.{Repo, Video, Event}
alias MaveMetrics.{Repo, Video, Event, Duration}

@default_timeframe "7 days"
@default_interval "12 months"
Expand Down Expand Up @@ -65,46 +65,49 @@ defmodule MaveMetrics.API do
end

def query_aggregated_video_metrics(video_ids, timeframe, min_watched_seconds, interval) do
"daily_session_aggregation"
Duration
|> apply_timeframe(timeframe)
|> where([d], d.video_id in ^video_ids)
|> where([d], d.session_watched_seconds >= ^min_watched_seconds)
|> where([d], d.duration >= ^min_watched_seconds)
|> group_by([d], [
fragment(~s|time_bucket('?', ?)|, literal(^interval), d.session_date),
fragment(~s|time_bucket('?', ?)|, literal(^interval), d.timestamp),
d.timestamp,
d.session_id,
d.platform,
d.device,
d.browser
])
|> select([d], %{
session_id: d.session_id,
interval: fragment(~s|time_bucket('?', ?)|, literal(^interval), d.session_date),
total_view_time: sum(d.session_watched_seconds),
platform_mac: case_when(d.platform == "mac", 1, 0),
platform_ios: case_when(d.platform == "ios", 1, 0),
platform_android: case_when(d.platform == "android", 1, 0),
platform_windows: case_when(d.platform == "windows", 1, 0),
platform_linux: case_when(d.platform == "linux", 1, 0),
platform_other: case_when(d.platform == "other", 1, 0),
device_mobile: case_when(d.device == "mobile", 1, 0),
device_desktop: case_when(d.device == "desktop", 1, 0),
device_tablet: case_when(d.device == "tablet", 1, 0),
device_other: case_when(d.device == "other", 1, 0),
browser_edge: case_when(d.browser == "edge", 1, 0),
browser_ie: case_when(d.browser == "ie", 1, 0),
browser_chrome: case_when(d.browser == "chrome", 1, 0),
browser_firefox: case_when(d.browser == "firefox", 1, 0),
browser_opera: case_when(d.browser == "opera", 1, 0),
browser_safari: case_when(d.browser == "safari", 1, 0),
browser_brave: case_when(d.browser == "brave", 1, 0),
browser_other: case_when(d.browser == "other", 1, 0)
interval: fragment(~s|time_bucket('?', ?)|, literal(^interval), d.timestamp),
total_view_time: sum(d.duration),
unique_total_view_time: sum(fragment("? * ?", d.duration, d.uniqueness)),
platform_mac: case_when(d.platform == :mac, 1, 0),
platform_ios: case_when(d.platform == :ios, 1, 0),
platform_android: case_when(d.platform == :android, 1, 0),
platform_windows: case_when(d.platform == :windows, 1, 0),
platform_linux: case_when(d.platform == :linux, 1, 0),
platform_other: case_when(d.platform == :other, 1, 0),
device_mobile: case_when(d.device == :mobile, 1, 0),
device_desktop: case_when(d.device == :desktop, 1, 0),
device_tablet: case_when(d.device == :tablet, 1, 0),
device_other: case_when(d.device == :other, 1, 0),
browser_edge: case_when(d.browser == :edge, 1, 0),
browser_ie: case_when(d.browser == :ie, 1, 0),
browser_chrome: case_when(d.browser == :chrome, 1, 0),
browser_firefox: case_when(d.browser == :firefox, 1, 0),
browser_opera: case_when(d.browser == :opera, 1, 0),
browser_safari: case_when(d.browser == :safari, 1, 0),
browser_brave: case_when(d.browser == :brave, 1, 0),
browser_other: case_when(d.browser == :other, 1, 0)
})
|> subquery()
|> group_by([d], [d.interval])
|> select([e], %{
interval: e.interval,
views: count(e.session_id),
total_view_time: sum(e.total_view_time),
unique_total_view_time: sum(e.unique_total_view_time),
platform: %{
mac: sum(e.platform_mac),
ios: sum(e.platform_ios),
Expand Down Expand Up @@ -134,17 +137,17 @@ defmodule MaveMetrics.API do
end

def query_individual_video_by_url(video_id, timeframe, min_watched_seconds, interval) do
"daily_session_aggregation"
Duration
|> apply_timeframe(timeframe)
|> where([d], d.video_id == ^video_id)
|> where([d], d.session_watched_seconds >= ^min_watched_seconds)
|> where([d], d.duration >= ^min_watched_seconds)
|> group_by([d], [
fragment(~s|time_bucket('?', ?)|, literal(^interval), d.session_date),
fragment(~s|time_bucket('?', ?)|, literal(^interval), d.timestamp),
d.uri_host,
d.uri_path
])
|> select([d], %{
interval: fragment(~s|time_bucket('?', ?)|, literal(^interval), d.session_date),
interval: fragment(~s|time_bucket('?', ?)|, literal(^interval), d.timestamp),
host: d.uri_host,
path: d.uri_path,
views: count(d.session_id)
Expand All @@ -153,38 +156,30 @@ defmodule MaveMetrics.API do
end

def query_individual_video_engagement(video_id, timeframe, interval) do
Event
|> where([e], e.type == ^:play and e.video_id == ^video_id)
|> apply_timeframe(timeframe, :timestamp)
|> join(:inner, [e], e_next in Event,
on:
e_next.session_id == e.session_id and e_next.type == ^:pause and
e_next.timestamp > e.timestamp
)
Duration
|> where([d], d.video_id == ^video_id)
|> apply_timeframe(timeframe)
|> join(
:inner_lateral,
[e, e_next],
[d],
gs in fragment(
"SELECT generate_series(
floor(?)::int,
ceil(?)::int - 1
) AS second",
e.video_time,
e_next.video_time
"SELECT generate_series(lower(?), upper(?) - 1) AS second",
d.watched_seconds,
d.watched_seconds
),
on: true
)
|> group_by([e, e_next, s], [
fragment(~s|time_bucket('?', ?)|, literal(^interval), e.timestamp),
s.second
|> group_by([d, gs], [
fragment(~s|time_bucket('?', ?)|, literal(^interval), d.timestamp),
gs.second
])
|> order_by([e, e_next, s],
asc: fragment(~s|time_bucket('?', ?)|, literal(^interval), e.timestamp),
asc: s.second
|> order_by([d, gs],
asc: fragment(~s|time_bucket('?', ?)|, literal(^interval), d.timestamp),
asc: gs.second
)
|> select([e, e_next, s], %{
interval: fragment(~s|time_bucket('?', ?)|, literal(^interval), e.timestamp),
second: s.second,
|> select([d, gs], %{
interval: fragment(~s|time_bucket('?', ?)|, literal(^interval), d.timestamp),
second: gs.second,
views: count()
})
|> Repo.all()
Expand All @@ -205,7 +200,7 @@ defmodule MaveMetrics.API do
end)
end

defp apply_timeframe(query, timeframe, date_field \\ :session_date)
defp apply_timeframe(query, timeframe, date_field \\ :timestamp)

defp apply_timeframe(
query,
Expand Down
43 changes: 43 additions & 0 deletions server/lib/mave_metrics/models/duration.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
defmodule MaveMetrics.Duration do
use Ecto.Schema
import Ecto.Changeset
alias PgRanges.Int4Range

@primary_key false

@required_fields ~w(timestamp duration uniqueness watched_seconds session_id video_id browser platform device uri_host uri_path)a
@optional_fields ~w()a

schema "durations" do
field(:timestamp, :utc_datetime_usec)

field(:duration, :float)
field(:uniqueness, :float)
field(:watched_seconds, Int4Range)

field(:browser, Ecto.Enum,
values: [:edge, :ie, :chrome, :firefox, :opera, :safari, :brave, :other],
default: :other
)

field(:platform, Ecto.Enum,
values: [:ios, :android, :mac, :windows, :linux, :other],
default: :other
)

field(:device, Ecto.Enum, values: [:mobile, :tablet, :desktop, :other], default: :other)

field(:uri_host, :string)
field(:uri_path, :string)

belongs_to(:session, MaveMetrics.Session)
belongs_to(:video, MaveMetrics.Video)
end

@doc false
def changeset(model, attrs) do
model
|> cast(attrs, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
end
end
Loading

0 comments on commit 86cb0a2

Please sign in to comment.