Skip to content

Commit

Permalink
feat: add metrics (#419)
Browse files Browse the repository at this point in the history
* wip

* working
  • Loading branch information
leanmendoza authored May 27, 2024
1 parent 8aa5b3c commit adc86ff
Show file tree
Hide file tree
Showing 13 changed files with 541 additions and 2 deletions.
11 changes: 11 additions & 0 deletions godot/src/config/config_data.gd
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ var audio_mic_amplification: float = 100.0:
set(value):
audio_mic_amplification = value

var analytics_user_id: String = "":
set(value):
analytics_user_id = value


func fix_last_places_duplicates(place_dict: Dictionary, _last_places: Array):
var realm = place_dict.get("realm")
Expand Down Expand Up @@ -219,6 +223,8 @@ func load_from_default():
self.last_realm_joined = "https://sdk-team-cdn.decentraland.org/ipfs/goerli-plaza-test-psquad-demo-latest"
self.last_parcel_position = Vector2i(72, -10)

self.analytics_user_id = DclConfig.generate_uuid_v4()


func load_from_settings_file():
var data_default := ConfigData.new()
Expand Down Expand Up @@ -297,6 +303,10 @@ func load_from_settings_file():
"user", "last_realm_joined", data_default.last_realm_joined
)

self.analytics_user_id = settings_file.get_value(
"analytics", "user_id", DclConfig.generate_uuid_v4()
)

self.last_places = settings_file.get_value("user", "last_places", data_default.last_places)


Expand Down Expand Up @@ -333,4 +343,5 @@ func save_to_settings_file():
settings_file.set_value("user", "last_parcel_position", self.last_parcel_position)
settings_file.set_value("user", "last_realm_joined", self.last_realm_joined)
settings_file.set_value("user", "last_places", self.last_places)
settings_file.set_value("analytics", "user_id", self.analytics_user_id)
settings_file.save(DclConfig.get_settings_file_path())
6 changes: 6 additions & 0 deletions godot/src/global.gd
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ func _ready():
if Engine.has_singleton("DclAndroidPlugin"):
dcl_android_plugin = Engine.get_singleton("DclAndroidPlugin")

self.metrics = Metrics.create_metrics(
self.config.analytics_user_id, DclConfig.generate_uuid_v4()
)
self.metrics.set_name("metrics")

self.realm = Realm.new()
self.realm.set_name("realm")

Expand Down Expand Up @@ -103,6 +108,7 @@ func _ready():
get_tree().root.add_child.call_deferred(self.avatars)
get_tree().root.add_child.call_deferred(self.portable_experience_controller)
get_tree().root.add_child.call_deferred(self.testing_tools)
get_tree().root.add_child.call_deferred(self.metrics)

var custom_importer = load("res://src/logic/custom_gltf_importer.gd").new()
GLTFDocument.register_gltf_document_extension(custom_importer)
Expand Down
2 changes: 2 additions & 0 deletions godot/src/logic/realm.gd
Original file line number Diff line number Diff line change
Expand Up @@ -197,5 +197,7 @@ func async_set_realm(new_realm_string: String) -> void:
Global.get_config().last_realm_joined = realm_url
Global.get_config().save_to_settings_file()

Global.metrics.update_realm(realm_url)

_has_realm = true
emit_signal("realm_changed")
5 changes: 5 additions & 0 deletions godot/src/ui/explorer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func _process(_dt):
Global.get_config().last_parcel_position = parcel_position
dirty_save_position = true
Global.change_parcel.emit(parcel_position)
Global.metrics.update_position("%d,%d" % [parcel_position.x, parcel_position.y])


func _on_parcels_procesed(parcels, empty):
Expand Down Expand Up @@ -159,6 +160,10 @@ func _ready():
if Global.testing_scene_mode:
Global.player_identity.create_guest_account()

Global.metrics.update_identity(
Global.player_identity.get_address_str(), Global.player_identity.is_guest
)

# last
ui_root.grab_focus.call_deferred()

Expand Down
5 changes: 3 additions & 2 deletions rust/decentraland-godot-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ log-panics = { version = "2", features = ["with-backtrace"]}

v8 = { version = "0.74.3", optional = true }
deno_core = { version = "0.197", optional = true }
uuid = { version = "1.3.0", features = ["v4"], optional = true }
uuid = { version = "1.3.0", features = ["v4"] }
fastwebsockets = { version = "0.3.1", features = ["upgrade"], optional = true }
hyper1 = { package = "hyper", version = "0.14.26", features = ["server","runtime", "http1"], optional = true }
num-traits = "0.2"
Expand Down Expand Up @@ -79,11 +79,12 @@ default = ["use_ffmpeg", "use_livekit", "use_deno"]
use_ffmpeg = ["dep:ffmpeg-next", "dep:cpal"]
use_livekit = ["use_ffmpeg", "dep:livekit"]
use_deno = ["dep:deno_core", "dep:v8"]
enable_inspector = ["use_deno", "dep:uuid", "dep:fastwebsockets", "dep:hyper1"]
enable_inspector = ["use_deno", "dep:fastwebsockets", "dep:hyper1"]

[build-dependencies]
webrtc-sys-build = "0.2.0"
prost-build = "0.11.8"
chrono = "0.4.31"

[patch."https://github.com/godot-rust/godot4-prebuilt".godot4-prebuilt]
git = "https://github.com//godot-rust/godot4-prebuilt"
Expand Down
24 changes: 24 additions & 0 deletions rust/decentraland-godot-lib/build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use chrono::prelude::*;
use std::process::Command;
use std::{
env,
fs::{self, File},
Expand Down Expand Up @@ -331,10 +333,32 @@ fn main() -> io::Result<()> {
println!("cargo:rerun-if-changed={value}");
}

set_godot_explorer_version();

Ok(())
}

fn generate_file<P: AsRef<Path>>(path: P, text: &[u8]) {
let mut f = File::create(path).unwrap();
f.write_all(text).unwrap()
}

fn set_godot_explorer_version() {
let snapshot = if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() {
let long_hash = String::from_utf8(output.stdout).unwrap();
format!("commit-{}", &long_hash[..8])
} else {
Utc::now()
.to_rfc3339()
.replace(|c: char| !c.is_ascii_digit(), "")
};

// get the CARGO_PKG_VERSION env var
let version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.0.0".to_string());
let snapshot_version = format!("{}-{}", version, snapshot);

println!(
"cargo:rustc-env=GODOT_EXPLORER_VERSION={}",
snapshot_version
);
}
192 changes: 192 additions & 0 deletions rust/decentraland-godot-lib/src/analytics/data_definition.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
use serde::Serialize;

#[derive(Serialize)]
pub struct SegmentMetricEventBody {
#[serde(rename = "type")]
r#type: String,
event: String,
#[serde(rename = "userId")]
user_id: String,
properties: serde_json::Value,
}

#[derive(Serialize)]
// Same for all events sent from the explorer
pub struct SegmentEventCommonExplorerFields {
// User’s wallet id, even for guests.
pub dcl_eth_address: String,
// If the user is a guest or not.
pub dcl_is_guest: bool,
// Realm where the user is connected.
pub realm: String,
// Current user position.
pub position: String,
// What type of client was used to render the world (Web/Native/VR)
pub dcl_renderer_type: String,
// Explorer’s unique session id.
pub session_id: String,
// Explorer’s release used.
pub renderer_version: String,
}

impl SegmentEventCommonExplorerFields {
pub fn new(session_id: String) -> Self {
let dcl_renderer_type = format!("dao-godot-{}", godot::engine::Os::singleton().get_name());

Self {
dcl_eth_address: "unauthenticated".into(),
dcl_is_guest: false,
realm: "no-realm".into(),
position: "no-position".into(),
dcl_renderer_type,
session_id,
renderer_version: env!("GODOT_EXPLORER_VERSION").into(),
}
}
}

pub enum SegmentEvent {
PerformanceMetrics(SegmentEventPerformanceMetrics),
ExplorerError(SegmentEventExplorerError),
ExplorerSceneLoadTimes(SegmentEventExplorerSceneLoadTimes),
ExplorerMoveToParcel(String, SegmentEventExplorerMoveToParcel),
SystemInfoReport(SegmentEventSystemInfoReport),
}

#[derive(Serialize)]
pub struct SegmentEventPerformanceMetrics {
// Total number of frames measured for this event.
pub samples: u32,
// Total length of the performance report.
pub total_time: f32,
// Amount of hiccups in 1000 frames.
pub hiccups_in_thousand_frames: u32,
// Total time length of hiccups measured in seconds.
pub hiccups_time: f32,
// Minimum delta (difference) between frames in milliseconds
pub min_frame_time: f32,
// Maximum delta (difference) between frames in milliseconds
pub max_frame_time: f32,
// Average delta (difference) between frames in milliseconds
pub mean_frame_time: f32,
// Median delta (difference) between frames in milliseconds
pub median_frame_time: f32,
// Percentile 1 of the delta (difference) between frames in milliseconds
pub p1_frame_time: f32,
// Percentile 5 of the delta (difference) between frames in milliseconds
pub p5_frame_time: f32,
// Percentile 10 of the delta (difference) between frames in milliseconds
pub p10_frame_time: f32,
// Percentile 20 of the delta (difference) between frames in milliseconds
pub p20_frame_time: f32,
// Percentile 50 of the delta (difference) between frames in milliseconds
pub p50_frame_time: f32,
// Percentile 75 of the delta (difference) between frames in milliseconds
pub p75_frame_time: f32,
// Percentile 80 of the delta (difference) between frames in milliseconds
pub p80_frame_time: f32,
// Percentile 90 of the delta (difference) between frames in milliseconds
pub p90_frame_time: f32,
// Percentile 95 of the delta (difference) between frames in milliseconds
pub p95_frame_time: f32,
// Percentile 99 of the delta (difference) between frames in milliseconds
pub p99_frame_time: f32,
// How many users where nearby the current user
pub player_count: i32,
// Javascript heap memory used by the scenes in kilo bytes
pub used_jsheap_size: i32,
// Memory used only by the explorer in kilo bytes
pub memory_usage: i32,
}

#[derive(Serialize)]
pub struct SegmentEventExplorerError {
// Generic or Fatal.
error_type: String,
// Error description.
error_message: String,
// Error’s stack
error_stack: String,
}

#[derive(Serialize)]
pub struct SegmentEventExplorerSceneLoadTimes {
// Unique hash for the scene.
scene_hash: String,
// Time to load in seconds.
elapsed: f32,
// Boolean flag indicating wether the scene loaded without errors.
success: bool,
}

// TODO: maybe important what realm?
#[derive(Serialize)]
pub struct SegmentEventExplorerMoveToParcel {
// Parcel where the user is coming from.
pub old_parcel: String,
}

#[derive(Serialize)]
pub struct SegmentEventSystemInfoReport {
// Processor used by the user.
processor_type: String,
// How many processors are available in user’s device.
processor_count: u32,
// Graphic Device used by the user.
graphics_device_name: String,
// Graphic device memory in mb.
graphics_memory_mb: u32,
// RAM memory in mb.
system_memory_size_mb: u32,
}

pub fn build_segment_event_batch_item(
user_id: String,
common: &SegmentEventCommonExplorerFields,
event_data: SegmentEvent,
) -> SegmentMetricEventBody {
let (event_name, event_properties, override_position) = match event_data {
SegmentEvent::PerformanceMetrics(event) => (
"Performance Metrics".to_string(),
serde_json::to_value(event).unwrap(),
None,
),
SegmentEvent::ExplorerError(event) => (
"Explorer Error".to_string(),
serde_json::to_value(event).unwrap(),
None,
),
SegmentEvent::ExplorerSceneLoadTimes(event) => (
"Explorer Scene Load Times".to_string(),
serde_json::to_value(event).unwrap(),
None,
),
SegmentEvent::ExplorerMoveToParcel(current_position, event) => (
"Explorer Move To Parcel".to_string(),
serde_json::to_value(event).unwrap(),
Some(current_position),
),
SegmentEvent::SystemInfoReport(event) => (
"System Info Report".to_string(),
serde_json::to_value(event).unwrap(),
None,
),
};

let mut properties = serde_json::to_value(common).unwrap();
// merge specific event properties with common properties
for (k, v) in event_properties.as_object().unwrap().iter() {
properties[k] = v.clone();
}

if let Some(position) = override_position {
properties["position"] = serde_json::Value::String(position);
}

SegmentMetricEventBody {
r#type: "track".to_string(),
event: event_name,
user_id,
properties,
}
}
Loading

0 comments on commit adc86ff

Please sign in to comment.