[Stats API] Active device endpoint and exit country code (#6265)
* active_device endpoint and exit_cc in report * bump stats API version * stats API version in lockflie * migration changes
This commit is contained in:
Generated
+1
-1
@@ -7248,7 +7248,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-statistics-api"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
|
||||
@@ -6,6 +6,7 @@ use time::Date;
|
||||
|
||||
const BASIC_REPORT_KIND: &str = "vpn_client_stats_report";
|
||||
const SESSION_REPORT_KIND: &str = "vpn_client_session_report";
|
||||
const ACTIVE_DEVICE_REPORT_KIND: &str = "vpn_client_active_device";
|
||||
const VERSION_1: &str = "v1";
|
||||
const VERSION_2: &str = "v2";
|
||||
|
||||
@@ -64,6 +65,27 @@ impl VpnClientStatsReportV2 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ActiveDeviceReport {
|
||||
pub kind: String,
|
||||
pub api_version: String,
|
||||
pub stats_id: String,
|
||||
|
||||
pub static_information: StaticInformationReport,
|
||||
}
|
||||
|
||||
impl ActiveDeviceReport {
|
||||
pub fn new(stats_id: String, static_information: StaticInformationReport) -> Self {
|
||||
ActiveDeviceReport {
|
||||
kind: ACTIVE_DEVICE_REPORT_KIND.into(),
|
||||
api_version: VERSION_2.into(),
|
||||
stats_id,
|
||||
static_information,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StaticInformationReport {
|
||||
@@ -90,6 +112,7 @@ pub struct SessionReport {
|
||||
pub session_duration_min: i32,
|
||||
pub disconnection_time_ms: i32,
|
||||
pub exit_id: String,
|
||||
pub exit_cc: Option<String>,
|
||||
pub follow_up_id: Option<String>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO report_v2(\n received_at,\n source_ip,\n from_mixnet,\n country_code,\n report_version,\n device_id,\n os_type,\n os_version,\n architecture,\n app_version,\n user_agent,\n start_day_utc,\n connection_time_ms,\n tunnel_type,\n retry_attempt,\n session_duration_min,\n disconnection_time_ms,\n exit_id,\n follow_up_id,\n error)\n VALUES ($1::timestamptz, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)",
|
||||
"query": "INSERT INTO report_v2(\n received_at,\n source_ip,\n from_mixnet,\n country_code,\n report_version,\n device_id,\n os_type,\n os_version,\n architecture,\n app_version,\n user_agent,\n start_day_utc,\n connection_time_ms,\n tunnel_type,\n retry_attempt,\n session_duration_min,\n disconnection_time_ms,\n exit_id,\n exit_cc,\n follow_up_id,\n error)\n VALUES ($1::timestamptz, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -24,10 +24,11 @@
|
||||
"Int4",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "14d75cdd34201313e34ae7f0b931c9df43603232e3be42b0573013cd74226518"
|
||||
"hash": "5a6025e1b0d55dbfae098fdaa564e5a59642cec59947fabcb6f514d24423553c"
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-statistics-api"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
-- IMPORTANT : At the time of writing this, there are no instances of the Stats API with data in that table. Dropping it to modify is therefore fine
|
||||
DROP TABLE report_v2;
|
||||
|
||||
CREATE TABLE report_v2 (
|
||||
-- some info about the report, inferred from when/from where we got it
|
||||
received_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
source_ip TEXT,
|
||||
from_mixnet BOOLEAN,
|
||||
country_code TEXT,
|
||||
report_version TEXT,
|
||||
|
||||
-- some infos about the device sending the report
|
||||
device_id TEXT NOT NULL,
|
||||
os_type TEXT,
|
||||
os_version TEXT,
|
||||
architecture TEXT,
|
||||
app_version TEXT,
|
||||
user_agent TEXT,
|
||||
|
||||
-- session info
|
||||
start_day_utc DATE,
|
||||
connection_time_ms INTEGER,
|
||||
tunnel_type TEXT,
|
||||
retry_attempt INTEGER,
|
||||
session_duration_min INTEGER,
|
||||
disconnection_time_ms INTEGER,
|
||||
exit_id TEXT,
|
||||
exit_cc TEXT, -- new column
|
||||
follow_up_id TEXT,
|
||||
error TEXT
|
||||
);
|
||||
@@ -1,7 +1,9 @@
|
||||
use axum::{Json, Router, extract::State};
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use axum_extra::{TypedHeader, headers::UserAgent};
|
||||
use nym_statistics_common::report::vpn_client::{VpnClientStatsReport, VpnClientStatsReportV2};
|
||||
use nym_statistics_common::report::vpn_client::{
|
||||
ActiveDeviceReport, StaticInformationReport, VpnClientStatsReport, VpnClientStatsReportV2,
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
@@ -15,6 +17,7 @@ use crate::{
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/report", axum::routing::post(submit_stats_report))
|
||||
.route("/active_device", axum::routing::post(submit_active_device))
|
||||
.route("/session", axum::routing::post(submit_session_report))
|
||||
}
|
||||
|
||||
@@ -76,6 +79,53 @@ async fn submit_stats_report(
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
request_body = StaticInformationReport,
|
||||
tag = "Stats",
|
||||
path = "/active_device",
|
||||
context_path = "/v1/stats",
|
||||
responses(
|
||||
(status = 200)
|
||||
)
|
||||
)]
|
||||
#[tracing::instrument(level = "info", skip_all)]
|
||||
async fn submit_active_device(
|
||||
State(mut state): State<AppState>,
|
||||
TypedHeader(user_agent): TypedHeader<UserAgent>,
|
||||
insecure_ip_addr: InsecureClientIp,
|
||||
Json(report): Json<ActiveDeviceReport>,
|
||||
) -> HttpResult<Json<()>> {
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
|
||||
let gateway_record = state
|
||||
.network_view()
|
||||
.get_country_by_ip(&insecure_ip_addr.0)
|
||||
.await;
|
||||
|
||||
let from_mixnet = gateway_record.is_some();
|
||||
|
||||
if from_mixnet {
|
||||
debug!("Received an active device ping from the network");
|
||||
} else {
|
||||
debug!("Received an active device ping from outside of the network");
|
||||
}
|
||||
let active_device = DailyActiveDeviceDto::from_active_device_report(
|
||||
now,
|
||||
&report,
|
||||
user_agent.clone(),
|
||||
from_mixnet,
|
||||
);
|
||||
|
||||
state
|
||||
.storage()
|
||||
.store_active_device(active_device)
|
||||
.await
|
||||
.map_err(HttpError::internal_with_logging)?;
|
||||
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
request_body = VpnClientStatsReportV2,
|
||||
|
||||
@@ -152,9 +152,10 @@ impl StatisticsStorage {
|
||||
session_duration_min,
|
||||
disconnection_time_ms,
|
||||
exit_id,
|
||||
exit_cc,
|
||||
follow_up_id,
|
||||
error)
|
||||
VALUES ($1::timestamptz, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)"#,
|
||||
VALUES ($1::timestamptz, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)"#,
|
||||
report_v2.received_at as time::OffsetDateTime,
|
||||
report_v2.received_from,
|
||||
report_v2.from_mixnet,
|
||||
@@ -173,6 +174,7 @@ impl StatisticsStorage {
|
||||
report_v2.session_duration_min,
|
||||
report_v2.disconnection_time_ms,
|
||||
report_v2.exit_id,
|
||||
report_v2.exit_cc,
|
||||
report_v2.follow_up_id,
|
||||
report_v2.error
|
||||
)
|
||||
|
||||
@@ -2,7 +2,9 @@ use std::net::IpAddr;
|
||||
|
||||
use axum_extra::headers::UserAgent;
|
||||
use celes::Country;
|
||||
use nym_statistics_common::report::vpn_client::{VpnClientStatsReport, VpnClientStatsReportV2};
|
||||
use nym_statistics_common::report::vpn_client::{
|
||||
ActiveDeviceReport, VpnClientStatsReport, VpnClientStatsReportV2,
|
||||
};
|
||||
use time::{Date, OffsetDateTime};
|
||||
|
||||
pub type StatsId = String;
|
||||
@@ -54,6 +56,24 @@ impl DailyActiveDeviceDto {
|
||||
from_mixnet,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_active_device_report(
|
||||
received_at: OffsetDateTime,
|
||||
report: &ActiveDeviceReport,
|
||||
user_agent: UserAgent,
|
||||
from_mixnet: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
day: received_at.date(),
|
||||
stats_id: report.stats_id.clone(),
|
||||
os_type: report.static_information.os_type.clone(),
|
||||
os_version: report.static_information.os_version.clone(),
|
||||
os_arch: report.static_information.os_arch.clone(),
|
||||
app_version: report.static_information.app_version.clone(),
|
||||
user_agent: user_agent.to_string(),
|
||||
from_mixnet,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
@@ -129,6 +149,7 @@ pub(crate) struct StatsReportV2Dto {
|
||||
pub(crate) session_duration_min: i32,
|
||||
pub(crate) disconnection_time_ms: i32,
|
||||
pub(crate) exit_id: String,
|
||||
pub(crate) exit_cc: Option<String>,
|
||||
pub(crate) follow_up_id: Option<String>,
|
||||
pub(crate) error: Option<String>,
|
||||
}
|
||||
@@ -161,6 +182,7 @@ impl StatsReportV2Dto {
|
||||
session_duration_min: stats_report.session_report.session_duration_min,
|
||||
disconnection_time_ms: stats_report.session_report.disconnection_time_ms,
|
||||
exit_id: stats_report.session_report.exit_id.clone(),
|
||||
exit_cc: stats_report.session_report.exit_cc.clone(),
|
||||
follow_up_id: stats_report.session_report.follow_up_id.clone(),
|
||||
error: stats_report.session_report.error.clone(),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user