This commit is contained in:
2026-02-13 13:06:50 +09:00
commit b54066842b
249 changed files with 69547 additions and 0 deletions

291
grd-rdp-session-metrics.c Normal file
View File

@@ -0,0 +1,291 @@
/*
* Copyright (C) 2023 Pascal Nowack
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include "config.h"
#include "grd-rdp-session-metrics.h"
#include "grd-rdp-surface.h"
typedef struct
{
gboolean pending_frame_reception;
gboolean pending_frame_transmission;
int64_t first_frame_reception;
int64_t first_frame_transmission;
uint32_t skipped_frames;
} SurfaceMetrics;
struct _GrdRdpSessionMetrics
{
GObject parent;
GrdRdpPhase phase;
int64_t rd_session_init_us;
int64_t rd_session_post_connect_us;
int64_t rd_session_ready_us;
int64_t rd_session_started_us;
gboolean pending_layout_change;
int64_t layout_change_notification;
GMutex metrics_mutex;
GHashTable *surface_metrics_table;
uint32_t n_pending_surface_metrics;
gboolean pending_output;
};
G_DEFINE_TYPE (GrdRdpSessionMetrics, grd_rdp_session_metrics, G_TYPE_OBJECT)
void
grd_rdp_session_metrics_notify_phase_completion (GrdRdpSessionMetrics *session_metrics,
GrdRdpPhase phase)
{
g_assert (session_metrics->pending_output);
g_assert (phase > session_metrics->phase);
session_metrics->phase = phase;
switch (phase)
{
case GRD_RDP_PHASE_SESSION_INITIALIZATION:
g_assert_not_reached ();
break;
case GRD_RDP_PHASE_POST_CONNECT:
session_metrics->rd_session_post_connect_us = g_get_monotonic_time ();
break;
case GRD_RDP_PHASE_SESSION_READY:
session_metrics->rd_session_ready_us = g_get_monotonic_time ();
break;
case GRD_RDP_PHASE_SESSION_STARTED:
session_metrics->rd_session_started_us = g_get_monotonic_time ();
break;
}
}
void
grd_rdp_session_metrics_notify_frame_reception (GrdRdpSessionMetrics *session_metrics,
GrdRdpSurface *rdp_surface)
{
SurfaceMetrics *surface_metrics = NULL;
g_autoptr (GMutexLocker) locker = NULL;
g_assert (session_metrics->phase == GRD_RDP_PHASE_SESSION_STARTED);
locker = g_mutex_locker_new (&session_metrics->metrics_mutex);
if (session_metrics->pending_layout_change)
return;
if (!session_metrics->pending_output)
return;
if (!g_hash_table_lookup_extended (session_metrics->surface_metrics_table,
rdp_surface,
NULL, (gpointer *) &surface_metrics))
g_assert_not_reached ();
if (surface_metrics->pending_frame_reception)
{
g_assert (surface_metrics->pending_frame_transmission);
surface_metrics->first_frame_reception = g_get_monotonic_time ();
surface_metrics->pending_frame_reception = FALSE;
}
else if (surface_metrics->pending_frame_transmission)
{
++surface_metrics->skipped_frames;
}
}
static void
print_session_metrics (GrdRdpSessionMetrics *session_metrics)
{
GrdRdpSurface *rdp_surface = NULL;
SurfaceMetrics *surface_metrics = NULL;
GHashTableIter iter;
g_debug ("[RDP] Remote Desktop session metrics: _post_connect: %lims, "
"_ready: %lims, _started: %lims, whole startup: %lims",
(session_metrics->rd_session_post_connect_us -
session_metrics->rd_session_init_us) / 1000,
(session_metrics->rd_session_ready_us -
session_metrics->rd_session_post_connect_us) / 1000,
(session_metrics->rd_session_started_us -
session_metrics->rd_session_ready_us) / 1000,
(session_metrics->rd_session_started_us -
session_metrics->rd_session_init_us) / 1000);
g_hash_table_iter_init (&iter, session_metrics->surface_metrics_table);
while (g_hash_table_iter_next (&iter, (gpointer *) &rdp_surface,
(gpointer *) &surface_metrics))
{
GrdRdpSurfaceMapping *surface_mapping =
grd_rdp_surface_get_mapping (rdp_surface);
GrdRdpSurfaceMappingType mapping_type = surface_mapping->mapping_type;
g_assert (!surface_metrics->pending_frame_reception);
g_assert (!surface_metrics->pending_frame_transmission);
g_assert (mapping_type == GRD_RDP_SURFACE_MAPPING_TYPE_MAP_TO_OUTPUT);
g_debug ("[RDP] Surface %ux%u (%i, %i) Metrics: First frame: "
"Reception: %lims, Transmission: %lims, Skipped frames: %u",
grd_rdp_surface_get_width (rdp_surface),
grd_rdp_surface_get_height (rdp_surface),
surface_mapping->output_origin_x,
surface_mapping->output_origin_y,
(surface_metrics->first_frame_reception -
session_metrics->layout_change_notification) / 1000,
(surface_metrics->first_frame_transmission -
surface_metrics->first_frame_reception) / 1000,
surface_metrics->skipped_frames);
}
}
void
grd_rdp_session_metrics_notify_frame_transmission (GrdRdpSessionMetrics *session_metrics,
GrdRdpSurface *rdp_surface)
{
SurfaceMetrics *surface_metrics = NULL;
g_autoptr (GMutexLocker) locker = NULL;
g_assert (session_metrics->phase == GRD_RDP_PHASE_SESSION_STARTED);
locker = g_mutex_locker_new (&session_metrics->metrics_mutex);
g_assert (!session_metrics->pending_layout_change);
if (!session_metrics->pending_output)
return;
if (!g_hash_table_lookup_extended (session_metrics->surface_metrics_table,
rdp_surface,
NULL, (gpointer *) &surface_metrics))
g_assert_not_reached ();
g_assert (!surface_metrics->pending_frame_reception);
if (surface_metrics->pending_frame_transmission)
{
surface_metrics->first_frame_transmission = g_get_monotonic_time ();
surface_metrics->pending_frame_transmission = FALSE;
g_assert (session_metrics->n_pending_surface_metrics > 0);
--session_metrics->n_pending_surface_metrics;
}
if (session_metrics->n_pending_surface_metrics == 0)
{
print_session_metrics (session_metrics);
session_metrics->pending_output = FALSE;
}
}
void
grd_rdp_session_metrics_notify_layout_change (GrdRdpSessionMetrics *session_metrics)
{
g_autoptr (GMutexLocker) locker = NULL;
g_assert (session_metrics->phase == GRD_RDP_PHASE_SESSION_STARTED);
locker = g_mutex_locker_new (&session_metrics->metrics_mutex);
session_metrics->layout_change_notification = g_get_monotonic_time ();
session_metrics->pending_layout_change = TRUE;
}
void
grd_rdp_session_metrics_prepare_surface_metrics (GrdRdpSessionMetrics *session_metrics,
GList *surfaces)
{
g_autoptr (GMutexLocker) locker = NULL;
GList *l;
g_assert (session_metrics->phase == GRD_RDP_PHASE_SESSION_STARTED);
locker = g_mutex_locker_new (&session_metrics->metrics_mutex);
g_hash_table_remove_all (session_metrics->surface_metrics_table);
session_metrics->n_pending_surface_metrics = 0;
for (l = surfaces; l; l = l->next)
{
GrdRdpSurface *rdp_surface = l->data;
SurfaceMetrics *surface_metrics;
surface_metrics = g_new0 (SurfaceMetrics, 1);
surface_metrics->pending_frame_reception = TRUE;
surface_metrics->pending_frame_transmission = TRUE;
++session_metrics->n_pending_surface_metrics;
g_hash_table_insert (session_metrics->surface_metrics_table,
rdp_surface, surface_metrics);
}
session_metrics->pending_layout_change = FALSE;
}
GrdRdpSessionMetrics *
grd_rdp_session_metrics_new (void)
{
return g_object_new (GRD_TYPE_RDP_SESSION_METRICS, NULL);
}
static void
grd_rdp_session_metrics_dispose (GObject *object)
{
GrdRdpSessionMetrics *session_metrics = GRD_RDP_SESSION_METRICS (object);
g_clear_pointer (&session_metrics->surface_metrics_table, g_hash_table_unref);
G_OBJECT_CLASS (grd_rdp_session_metrics_parent_class)->dispose (object);
}
static void
grd_rdp_session_metrics_finalize (GObject *object)
{
GrdRdpSessionMetrics *session_metrics = GRD_RDP_SESSION_METRICS (object);
g_mutex_clear (&session_metrics->metrics_mutex);
G_OBJECT_CLASS (grd_rdp_session_metrics_parent_class)->finalize (object);
}
static void
grd_rdp_session_metrics_init (GrdRdpSessionMetrics *session_metrics)
{
session_metrics->pending_layout_change = TRUE;
session_metrics->pending_output = TRUE;
session_metrics->phase = GRD_RDP_PHASE_SESSION_INITIALIZATION;
session_metrics->rd_session_init_us = g_get_monotonic_time ();
session_metrics->surface_metrics_table = g_hash_table_new_full (NULL, NULL,
NULL, g_free);
g_mutex_init (&session_metrics->metrics_mutex);
}
static void
grd_rdp_session_metrics_class_init (GrdRdpSessionMetricsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_rdp_session_metrics_dispose;
object_class->finalize = grd_rdp_session_metrics_finalize;
}