mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2026-08-06 20:27:43 +00:00
Added meson build option for enabling MeasurementFramework usage.
This commit is contained in:
parent
1af9ad8035
commit
74f4aa67af
117
include/spice-server/measurement.h
Normal file
117
include/spice-server/measurement.h
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* This file is part of the SPICE measurement timestamp
|
||||
*
|
||||
* Copyright(c) 2026 Sebastian Vater <sebastian.vater@rz.uni-freiburg.de>
|
||||
*
|
||||
* This file may be licensed under the terms of the
|
||||
* GNU General Public License Version 2 (the ``GPL'').
|
||||
*
|
||||
* Software distributed under the License is distributed
|
||||
* on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
|
||||
* express or implied. See the GPL for the specific language
|
||||
* governing rights and limitations.
|
||||
*
|
||||
* You should have received a copy of the GPL along with this
|
||||
* program. If not, go to http://www.gnu.org/licenses/gpl.html
|
||||
* or write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file measurement.h
|
||||
* @author Sebastian Vater
|
||||
* @date 29 Jan 2026
|
||||
* @brief SPICE measurement timestamp header.
|
||||
*
|
||||
* This file contains the SPICE measurement header code
|
||||
* for roundtrip data points and statistic calculations.
|
||||
*
|
||||
* @see https://bwsyncandshare.kit.edu/apps/user_saml/saml/selectUserBackEnd?redirectUrl=/apps/files/files/5088142741?dir%3D/OpenSourceVDI%26editing%3Dfalse%26openfile%3Dtrue
|
||||
*/
|
||||
|
||||
#ifndef SPICE_MEASUREMENT_H
|
||||
#define SPICE_MEASUREMENT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_SPICE_MEASUREMENT
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define VARIADIC_FUNC(fmt_idx, arg_idx) __attribute__((format(printf, fmt_idx, arg_idx)))
|
||||
#else
|
||||
#define VARIADIC_FUNC(fmt_idx, arg_idx)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Measurement context for measurement data points.
|
||||
*
|
||||
* This structure is used for measurement data point
|
||||
* storage by every function which is measurement related.
|
||||
*/
|
||||
typedef struct spice_ms_context {
|
||||
/// Pointer to measurement data.
|
||||
uint64_t *values;
|
||||
|
||||
/// Number of measurement data points.
|
||||
size_t count;
|
||||
|
||||
/// Capacity of measurement data points.
|
||||
size_t capacity;
|
||||
|
||||
/// Merge window size, amount of measurement data points to calculate mean and standard deviance for.
|
||||
size_t merge_window_size;
|
||||
|
||||
/// Last measured timestamp.
|
||||
struct timespec last;
|
||||
|
||||
/// Measurement log file path.
|
||||
char *path;
|
||||
|
||||
/// Measurement context has been initialized.
|
||||
bool init;
|
||||
} spice_ms_context_t;
|
||||
|
||||
int spice_ms_create(const char *spice_ms_log_file, const size_t capacity); // Creates and initializes a log buffer for timestamp recording
|
||||
|
||||
int spice_ms_log(const char *format, ...) VARIADIC_FUNC(1, 2); // Opens log file, appends a formatted string and closes the log file again
|
||||
|
||||
void spice_ms_set_merge_window_size(size_t win_size); // Sets the merge window size which determines mean and standard deviation calculation interval
|
||||
|
||||
int spice_ms_start_measure(void); // Retrieves the initial measurement timestamp used for delta calculation
|
||||
int spice_ms_calc_delta(void); // Calculates the delta value of two measurement timestamp data points and stores the result into the log buffer
|
||||
|
||||
int spice_ms_flush(void); // Flushes all measurement timestamp data from memory to disk by appending to log file
|
||||
|
||||
double spice_ms_calc_mean(const size_t pos, const size_t size); // Calculates the mean of all measurement timestamp data
|
||||
double spice_ms_calc_std_dev(const size_t pos, const size_t size); // Calculates the standard deviation of all measurement timestamp data
|
||||
|
||||
spice_ms_context_t *spice_ms_context_create(const char *spice_ms_log_file, const size_t capacity); // Creates and initializes a context based log buffer for timestamp recording
|
||||
void spice_ms_context_destroy(spice_ms_context_t *ctx); // Deallocates a measurement context allocated by spice_ms_context_create
|
||||
|
||||
int spice_ms_context_log(const spice_ms_context_t *ctx, const char *format, ...) VARIADIC_FUNC(2, 3); // Opens context log file, appends a formatted string and closes the log file again
|
||||
|
||||
void spice_ms_context_set_merge_window_size(spice_ms_context_t *ctx, size_t win_size); // Sets the merge window size for a context which determines mean and standard deviation calculation interval
|
||||
|
||||
int spice_ms_context_start_measure(spice_ms_context_t *ctx); // Retrieves the context based initial measurement timestamp used for delta calculation
|
||||
int spice_ms_context_calc_delta(spice_ms_context_t *ctx); // Calculates the context based delta value of two measurement timestamp data points and stores the result into the log buffer
|
||||
|
||||
int spice_ms_context_flush(spice_ms_context_t *ctx); // Flushes all context based measurement timestamp data from memory to disk by appending to log file
|
||||
|
||||
double spice_ms_context_calc_mean(const spice_ms_context_t *ctx, const size_t pos, const size_t size); // Calculates the context based mean of all measurement timestamp data
|
||||
double spice_ms_context_calc_std_dev(const spice_ms_context_t *ctx, const size_t pos, const size_t size); // Calculates the standard deviation of all measurement timestamp data
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SPICE_MEASUREMENT_H */
|
||||
@ -54,6 +54,12 @@ spice_server_link_args = []
|
||||
spice_server_requires = ''
|
||||
spice_protocol_version='0.14.3'
|
||||
|
||||
if get_option('enable_spice_measurement')
|
||||
spice_server_config_data.set('ENABLE_SPICE_MEASUREMENT', '1')
|
||||
add_project_arguments('-DENABLE_SPICE_MEASUREMENT', language: 'c')
|
||||
add_project_arguments('-DENABLE_SPICE_MEASUREMENT', language: 'cpp')
|
||||
endif
|
||||
|
||||
#
|
||||
# Spice common subproject
|
||||
#
|
||||
|
||||
@ -51,3 +51,9 @@ option('tests',
|
||||
type : 'boolean',
|
||||
value : true,
|
||||
description : 'Build the test binaries')
|
||||
|
||||
# Usage of MeasurementFramework
|
||||
option('enable_spice_measurement',
|
||||
type : 'boolean',
|
||||
value : false,
|
||||
description : 'Enable SPICE measurement support using MeasurementFramework')
|
||||
|
||||
@ -23,6 +23,11 @@
|
||||
|
||||
#include "image-encoders.h"
|
||||
#include "image-cache.h"
|
||||
|
||||
#ifdef ENABLE_SPICE_MEASUREMENT
|
||||
#include "measurement.h"
|
||||
#endif
|
||||
|
||||
#include "pixmap-cache.h"
|
||||
#include "display-limits.h"
|
||||
#include "common-graphics-channel.h"
|
||||
@ -65,8 +70,10 @@ protected:
|
||||
|
||||
public:
|
||||
red::unique_link<DisplayChannelClientPrivate> priv;
|
||||
|
||||
int is_low_bandwidth;
|
||||
#ifdef ENABLE_SPICE_MEASUREMENT
|
||||
spice_ms_context_t *measurement_ctx;
|
||||
#endif
|
||||
};
|
||||
|
||||
#define PALETTE_CACHE_HASH_SHIFT 8
|
||||
|
||||
697
server/measurement.c
Normal file
697
server/measurement.c
Normal file
@ -0,0 +1,697 @@
|
||||
/*
|
||||
* This file is part of the SPICE measurement timestamp
|
||||
*
|
||||
* Copyright(c) 2026 Sebastian Vater <sebastian.vater@rz.uni-freiburg.de>
|
||||
*
|
||||
* This file may be licensed under the terms of the
|
||||
* GNU General Public License Version 2 (the ``GPL'').
|
||||
*
|
||||
* Software distributed under the License is distributed
|
||||
* on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
|
||||
* express or implied. See the GPL for the specific language
|
||||
* governing rights and limitations.
|
||||
*
|
||||
* You should have received a copy of the GPL along with this
|
||||
* program. If not, go to http://www.gnu.org/licenses/gpl.html
|
||||
* or write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
#define __STDC_FORMAT_MACROS 1
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "measurement.h"
|
||||
|
||||
/**
|
||||
* @file measurement.c
|
||||
* @author Sebastian Vater
|
||||
* @date 29 Jan 2026
|
||||
* @brief SPICE measurement timestamp implementation.
|
||||
*
|
||||
* This file contains the SPICE measurement implementation
|
||||
* for roundtrip data points and statistic calculations.
|
||||
*
|
||||
* @see https://bwsyncandshare.kit.edu/apps/user_saml/saml/selectUserBackEnd?redirectUrl=/apps/files/files/5088142741?dir%3D/OpenSourceVDI%26editing%3Dfalse%26openfile%3Dtrue
|
||||
*/
|
||||
|
||||
|
||||
#ifdef ENABLE_SPICE_MEASUREMENT
|
||||
static uint64_t *spice_ms_values = NULL;
|
||||
static size_t spice_ms_count = 0;
|
||||
static size_t spice_ms_capacity = 0;
|
||||
static bool spice_ms_init = 0;
|
||||
|
||||
static struct timespec spice_ms_last;
|
||||
static char spice_ms_path[PATH_MAX];
|
||||
|
||||
static size_t spice_ms_merge_window_size = 0;
|
||||
|
||||
/**
|
||||
* @brief Converts a timespec to 64-bit integer representing timestamp in nanoseconds.
|
||||
*
|
||||
* This function converts a timespec as returned by
|
||||
* clock_gettime to nanoseconds represented by an
|
||||
* 64-bit integer.
|
||||
*
|
||||
* @param[in] ts Pointer to timespec to convert.
|
||||
* @return Timestamp as 64-bit integer in nanoseconds.
|
||||
*/
|
||||
static inline uint64_t spice_ms_ts_to_ns(const struct timespec *ts)
|
||||
{
|
||||
return (((uint64_t) ts->tv_sec * 1000000000ULL) + (uint64_t) ts->tv_nsec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles abnormal program termination cases for flushing buffers not yet written to disk.
|
||||
*
|
||||
* This function just flushes all already recorded
|
||||
* timestamps to disk.
|
||||
*/
|
||||
static void spice_ms_atexit(void)
|
||||
{
|
||||
spice_ms_flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates and initializes a log buffer for timestamp recording.
|
||||
*
|
||||
* This function allocates memory for a fixed
|
||||
* capacity and initializes the necessary
|
||||
* structures for doing the measurement task.
|
||||
*
|
||||
* @param[in] spice_ms_log_file Pointer to log file name to write
|
||||
* the measurement timestamp data to. May NOT
|
||||
* be NULL, so be careful.
|
||||
* @param[in] capacity Maximum number of timestamp values to
|
||||
* record. May NOT be zero.
|
||||
* @retval -1 An error occured during initialization.
|
||||
* @retval 0 Initialization successfully completed.
|
||||
*/
|
||||
int spice_ms_create(const char *spice_ms_log_file, const size_t capacity)
|
||||
{
|
||||
if ( (spice_ms_log_file == NULL) || (capacity == 0) || (strlen( spice_ms_log_file ) >= PATH_MAX) )
|
||||
return -1;
|
||||
|
||||
strncpy( spice_ms_path, spice_ms_log_file, sizeof(spice_ms_path) - 1 );
|
||||
|
||||
spice_ms_values = malloc( capacity * sizeof(uint64_t) );
|
||||
|
||||
if ( spice_ms_values == NULL )
|
||||
return -1;
|
||||
|
||||
spice_ms_capacity = capacity;
|
||||
spice_ms_count = 0;
|
||||
|
||||
if ( atexit( spice_ms_atexit ) != 0 )
|
||||
return -1;
|
||||
|
||||
spice_ms_init = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Opens log file, appends a formatted string and closes the log file again.
|
||||
*
|
||||
* This function is used to store meta
|
||||
* information about the measurement
|
||||
* process, e.g. bitrate of a measured
|
||||
* video encoding process.
|
||||
*
|
||||
* @param[in] format Format string to write to log file.
|
||||
* @param[in] ... Values to fill in the format string with.
|
||||
|
||||
* @return Non-negative number on success, a
|
||||
* negative error code otherwise.
|
||||
*/
|
||||
int spice_ms_log(const char *format, ...)
|
||||
{
|
||||
if ( !spice_ms_init || (format == NULL) )
|
||||
return -1;
|
||||
|
||||
FILE *fh = fopen( spice_ms_path, "a" );
|
||||
|
||||
if ( fh == NULL )
|
||||
return -1;
|
||||
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
const int rc = vfprintf( fh, format, args );
|
||||
va_end( args );
|
||||
fclose( fh );
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the merge window size which determines mean and standard deviation calculation interval.
|
||||
*
|
||||
* This function allows to set the merge window
|
||||
* interval in order to calculate mean and standard
|
||||
* deviation values.\n
|
||||
* Zero is allowed, indicating only calculating
|
||||
* mean and standard deviation over the total
|
||||
* set.
|
||||
*
|
||||
* @param[in] win_size Size of merge window to be set.
|
||||
*/
|
||||
void spice_ms_set_merge_window_size(size_t win_size)
|
||||
{
|
||||
spice_ms_merge_window_size = win_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves the initial measurement timestamp used for delta calculation.
|
||||
*
|
||||
* THis function gets the current monotonic clock
|
||||
* time and stores it for using it for delta
|
||||
* calculation.
|
||||
*
|
||||
* @retval -1 An error occured during getting the data.
|
||||
* @retval 0 Current timestamp retrieved successfully.
|
||||
*/
|
||||
int spice_ms_start_measure(void)
|
||||
{
|
||||
if ( !spice_ms_init )
|
||||
return -1;
|
||||
|
||||
clock_gettime( CLOCK_MONOTONIC, &spice_ms_last );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the delta value of two measurement timestamp data points and stores the result into the log buffer.
|
||||
*
|
||||
* This function calculates the delta value of the
|
||||
* previously stored delta value and the current
|
||||
* timestamp upon calling and stores the result
|
||||
* into the log buffer.\n
|
||||
* If the log buffer is full, a larger buffer of
|
||||
* twice the current size will be allocated.
|
||||
*
|
||||
* @retval -1 An error occured during allocation of the
|
||||
* new log buffer in case current buffer was full.
|
||||
* @retval 0 Measurement timestamp delta stored
|
||||
* successfully into the log buffer.
|
||||
*/
|
||||
int spice_ms_calc_delta(void)
|
||||
{
|
||||
if ( !spice_ms_init )
|
||||
return -1;
|
||||
|
||||
struct timespec spice_ms_now;
|
||||
clock_gettime( CLOCK_MONOTONIC, &spice_ms_now );
|
||||
|
||||
const uint64_t delta = spice_ms_ts_to_ns( &spice_ms_now ) - spice_ms_ts_to_ns( &spice_ms_last );
|
||||
spice_ms_last = spice_ms_now;
|
||||
|
||||
if ( spice_ms_count == spice_ms_capacity ) {
|
||||
size_t new_capacity = (spice_ms_capacity << 1);
|
||||
uint64_t *new_buf = realloc( spice_ms_values, new_capacity * sizeof(uint64_t) );
|
||||
|
||||
if ( new_buf == NULL )
|
||||
return -1;
|
||||
|
||||
spice_ms_values = new_buf;
|
||||
spice_ms_capacity = new_capacity;
|
||||
}
|
||||
|
||||
spice_ms_values[spice_ms_count++] = delta;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Flushes all measurement timestamp data from memory to disk by appending to log file.
|
||||
*
|
||||
* This function appends all measurement timestamp
|
||||
* data currently recorded in memory to the
|
||||
* specified log file at creation and frees the
|
||||
* memory log buffer afterwards.
|
||||
*
|
||||
* @retval -1 An error occured during data flush.
|
||||
* @retval 0 Everything was flushed to disk successfully.
|
||||
*/
|
||||
int spice_ms_flush(void)
|
||||
{
|
||||
if ( !spice_ms_init || (spice_ms_values == NULL) || (spice_ms_count == 0) )
|
||||
return -1;
|
||||
|
||||
FILE *fh = fopen( spice_ms_path, "a" );
|
||||
|
||||
if ( fh == NULL )
|
||||
return -1;
|
||||
|
||||
size_t merge_win_pos = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
for ( size_t i = 0; i < spice_ms_count; i++ ) {
|
||||
fprintf( fh, "%" PRIu64 "\n", (uint64_t) spice_ms_values[i] );
|
||||
|
||||
if ( (spice_ms_merge_window_size > 0) && ((++merge_win_pos == spice_ms_merge_window_size) || ((i + 1) == spice_ms_count))) {
|
||||
const double mean = spice_ms_calc_mean( pos, merge_win_pos );
|
||||
const double std_dev = spice_ms_calc_std_dev( pos, merge_win_pos );
|
||||
|
||||
fprintf( fh, "# count=%d, mean=%f, std_dev=%f\n", (int) merge_win_pos, mean, std_dev );
|
||||
|
||||
pos += merge_win_pos;
|
||||
merge_win_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const double mean = spice_ms_calc_mean( 0, spice_ms_count );
|
||||
const double std_dev = spice_ms_calc_std_dev( 0, spice_ms_count );
|
||||
|
||||
fprintf( fh, "# count=%d, mean=%f, std_dev=%f\n", (int) spice_ms_count, mean, std_dev );
|
||||
|
||||
fclose( fh );
|
||||
free( spice_ms_values );
|
||||
|
||||
spice_ms_values = NULL;
|
||||
spice_ms_count = 0;
|
||||
spice_ms_capacity = 0;
|
||||
spice_ms_init = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the mean of all measurement timestamp data.
|
||||
*
|
||||
* This function calculates the mean of all
|
||||
* measurement timestamp values by adding
|
||||
* them together and dividing through the
|
||||
* number of measurement timestamp data
|
||||
* points.\n
|
||||
* Optimized for speed, addition is done
|
||||
* using 64-bit integers and only the
|
||||
* division part is done using floating
|
||||
* point arithmetic.\n
|
||||
* This might cause integer overflows in
|
||||
* very large measurement delta values
|
||||
* and extreme long measurement phases,
|
||||
* but is fine for short deltas.
|
||||
*
|
||||
* @param[in] pos First position of measurement timestamp data.
|
||||
* @param[in] size Number of measurement timestamp data entries,
|
||||
* 0 means all remaining entries starting from pos.
|
||||
* @return The mean value of all measurement timestamp
|
||||
* points or NaN in case of an error.
|
||||
*/
|
||||
double spice_ms_calc_mean(const size_t pos, const size_t size)
|
||||
{
|
||||
if ( !spice_ms_init || (spice_ms_count == 0) )
|
||||
return NAN;
|
||||
|
||||
const size_t count = ((size != 0) ? size : (spice_ms_count - pos));
|
||||
uint64_t sum = 0;
|
||||
|
||||
for ( size_t i = pos; i < (pos + count); i++ ) {
|
||||
sum += spice_ms_values[i];
|
||||
}
|
||||
|
||||
return ((double) sum / (double) count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the standard deviation of all measurement timestamp data.
|
||||
*
|
||||
* This function calculates the standard deviation
|
||||
* of all measurement timestamp values by first
|
||||
* calculating the mean and then squaring the
|
||||
* subtracting of the individual timestamp values
|
||||
* by the the mean in order to retrieve the
|
||||
* variance.
|
||||
*
|
||||
* @param[in] pos First position of measurement timestamp data.
|
||||
* @param[in] size Number of measurement timestamp data entries,
|
||||
* 0 means all remaining entries starting from pos.
|
||||
* @return The standard deviation value of all measurement
|
||||
* timestamp points or NaN in case of an error.
|
||||
*/
|
||||
double spice_ms_calc_std_dev(const size_t pos, const size_t size)
|
||||
{
|
||||
if ( !spice_ms_init || (spice_ms_count == 0) )
|
||||
return NAN;
|
||||
|
||||
const size_t count = ((size != 0) ? size : (spice_ms_count - pos));
|
||||
const double mean = spice_ms_calc_mean( pos, size );
|
||||
double square_sums = 0.0;
|
||||
|
||||
for ( size_t i = pos; i < (pos + count); i++ ) {
|
||||
const double delta = ((double) spice_ms_values[i] - mean);
|
||||
|
||||
square_sums += (delta * delta);
|
||||
}
|
||||
|
||||
return sqrt( square_sums / (double) count );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates and initializes a context based log buffer for timestamp recording.
|
||||
*
|
||||
* This function allocates memory for a
|
||||
* context, fixed capacity and initializes
|
||||
* the context for doing the measurement
|
||||
* task.\n
|
||||
* The context based approach allows running
|
||||
* multiple measurements in parallel in one
|
||||
* single exxecutable or library.
|
||||
*
|
||||
* @param[in] spice_ms_log_file Pointer to log file name to write
|
||||
* the measurement timestamp data to. May NOT
|
||||
* be NULL, so be careful.
|
||||
* @param[in] capacity Maximum number of timestamp values to
|
||||
* record. May NOT be zero.
|
||||
* @return Pointer to created and initialized
|
||||
* context or NULL upon failure.
|
||||
*/
|
||||
spice_ms_context_t *spice_ms_context_create(const char *spice_ms_log_file, const size_t capacity)
|
||||
{
|
||||
const size_t len = strlen( spice_ms_log_file );
|
||||
|
||||
if ( (spice_ms_log_file == NULL) || (capacity == 0) || (len >= PATH_MAX) )
|
||||
return NULL;
|
||||
|
||||
spice_ms_context_t *ctx = (spice_ms_context_t *) malloc( sizeof(struct spice_ms_context) );
|
||||
|
||||
if ( ctx == NULL )
|
||||
return NULL;
|
||||
|
||||
ctx->path = malloc( (len + 1) );
|
||||
|
||||
if ( ctx->path == NULL ) {
|
||||
free( ctx );
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy( ctx->path, spice_ms_log_file, (len + 1) );
|
||||
|
||||
ctx->values = malloc( capacity * sizeof(uint64_t) );
|
||||
|
||||
if ( ctx->values == NULL ) {
|
||||
free( ctx->path );
|
||||
free( ctx );
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->capacity = capacity;
|
||||
ctx->merge_window_size = 0;
|
||||
ctx->count = 0;
|
||||
ctx->init = true;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deallocates a measurement context allocated by spice_ms_context_create.
|
||||
*
|
||||
* All associated resources within this
|
||||
* context are deallocated, too.
|
||||
*
|
||||
* @param[in] ctx Pointer to measurement
|
||||
* context to deallocate. If NULL is passed,
|
||||
* this function does nothing.
|
||||
*/
|
||||
void spice_ms_context_destroy(spice_ms_context_t *ctx)
|
||||
{
|
||||
if ( ctx != NULL ) {
|
||||
if ( ctx->values != NULL )
|
||||
free( ctx->values );
|
||||
|
||||
if ( ctx->path != NULL )
|
||||
free( ctx->path );
|
||||
|
||||
free( ctx );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Opens context log file, appends a formatted string and closes the log file again.
|
||||
*
|
||||
* This function is used to store meta
|
||||
* information about the measurement
|
||||
* process, e.g. bitrate of a measured
|
||||
* video encoding process.
|
||||
*
|
||||
* @param[in] ctx Pointer to context log file.
|
||||
* @param[in] format Format string to write to log file.
|
||||
* @param[in] ... Values to fill in the format string with.
|
||||
|
||||
* @return Non-negative number on success, a
|
||||
* negative error code otherwise.
|
||||
*/
|
||||
int spice_ms_context_log(const spice_ms_context_t *ctx, const char *format, ...)
|
||||
{
|
||||
if ( (ctx == NULL) || !ctx->init || (format == NULL) )
|
||||
return -1;
|
||||
|
||||
FILE *fh = fopen( ctx->path, "a" );
|
||||
|
||||
if ( fh == NULL )
|
||||
return -1;
|
||||
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
const int rc = vfprintf( fh, format, args );
|
||||
va_end( args );
|
||||
fclose( fh );
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the merge window size for a context which determines mean and standard deviation calculation interval.
|
||||
*
|
||||
* This function allows to set the merge window
|
||||
* interval in order to calculate mean and standard
|
||||
* deviation values.\n
|
||||
* Zero is allowed, indicating only calculating
|
||||
* mean and standard deviation over the total
|
||||
* set.
|
||||
*
|
||||
* @param[in] ctx Pointer to merge window context.
|
||||
* @param[in] win_size Size of merge window to be set.
|
||||
*/
|
||||
void spice_ms_context_set_merge_window_size(spice_ms_context_t *ctx, size_t win_size)
|
||||
{
|
||||
if ( ctx == NULL )
|
||||
return;
|
||||
|
||||
ctx->merge_window_size = win_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves the context based initial measurement timestamp used for delta calculation.
|
||||
*
|
||||
* THis function gets the current monotonic clock
|
||||
* time and stores it for using it for delta
|
||||
* calculation.
|
||||
*
|
||||
* @param[in] ctx Pointer to context for which to measure.
|
||||
*
|
||||
* @retval -1 An error occured during getting the data.
|
||||
* @retval 0 Current timestamp retrieved successfully.
|
||||
*/
|
||||
int spice_ms_context_start_measure(spice_ms_context_t *ctx)
|
||||
{
|
||||
if ( (ctx == NULL) || !ctx->init )
|
||||
return -1;
|
||||
|
||||
clock_gettime( CLOCK_MONOTONIC, &ctx->last );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the context based delta value of two measurement timestamp data points and stores the result into the log buffer.
|
||||
*
|
||||
* This function calculates the delta value of the
|
||||
* previously stored delta value and the current
|
||||
* timestamp upon calling and stores the result
|
||||
* into the log buffer.\n
|
||||
* If the log buffer is full, a larger buffer of
|
||||
* twice the current size will be allocated.
|
||||
*
|
||||
* @param[in] ctx Pointer to context for which to calculate
|
||||
* the delta for.
|
||||
*
|
||||
* @retval -1 An error occured during allocation of the
|
||||
* new log buffer in case current buffer was full.
|
||||
* @retval 0 Measurement timestamp delta stored
|
||||
* successfully into the log buffer.
|
||||
*/
|
||||
int spice_ms_context_calc_delta(spice_ms_context_t *ctx)
|
||||
{
|
||||
if ( (ctx == NULL) || !ctx->init )
|
||||
return -1;
|
||||
|
||||
struct timespec spice_ms_now;
|
||||
clock_gettime( CLOCK_MONOTONIC, &spice_ms_now );
|
||||
|
||||
const uint64_t delta = spice_ms_ts_to_ns( &spice_ms_now ) - spice_ms_ts_to_ns( &ctx->last );
|
||||
ctx->last = spice_ms_now;
|
||||
|
||||
if ( ctx->count == ctx->capacity ) {
|
||||
size_t new_capacity = (ctx->capacity << 1);
|
||||
uint64_t *new_buf = realloc( ctx->values, new_capacity * sizeof(uint64_t) );
|
||||
|
||||
if ( new_buf == NULL )
|
||||
return -1;
|
||||
|
||||
ctx->values = new_buf;
|
||||
ctx->capacity = new_capacity;
|
||||
}
|
||||
|
||||
ctx->values[ctx->count++] = delta;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Flushes all context based measurement timestamp data from memory to disk by appending to log file.
|
||||
*
|
||||
* This function appends all measurement timestamp
|
||||
* data currently recorded in memory to the
|
||||
* specified log file at creation and frees the
|
||||
* memory log buffer afterwards.
|
||||
*
|
||||
* @param[in] ctx Pointer to context for which to flush
|
||||
* all measurement timestamp data for.
|
||||
* @retval -1 An error occured during data flush.
|
||||
* @retval 0 Everything was flushed to disk successfully.
|
||||
*/
|
||||
int spice_ms_context_flush(spice_ms_context_t *ctx)
|
||||
{
|
||||
if ( (ctx == NULL) || !ctx->init || (ctx->values == NULL) || (ctx->count == 0) )
|
||||
return -1;
|
||||
|
||||
FILE *fh = fopen( ctx->path, "a" );
|
||||
|
||||
if ( fh == NULL )
|
||||
return -1;
|
||||
|
||||
size_t merge_win_pos = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
for ( size_t i = 0; i < ctx->count; i++ ) {
|
||||
fprintf( fh, "%" PRIu64 "\n", (uint64_t) ctx->values[i] );
|
||||
|
||||
if ( (ctx->merge_window_size > 0) && ((++merge_win_pos == ctx->merge_window_size) || ((i + 1) == ctx->count))) {
|
||||
const double mean = spice_ms_context_calc_mean( ctx, pos, merge_win_pos );
|
||||
const double std_dev = spice_ms_context_calc_std_dev( ctx, pos, merge_win_pos );
|
||||
|
||||
fprintf( fh, "# count=%d, mean=%f, std_dev=%f\n", (int) merge_win_pos, mean, std_dev );
|
||||
|
||||
pos += merge_win_pos;
|
||||
merge_win_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const double mean = spice_ms_context_calc_mean( ctx, 0, ctx->count );
|
||||
const double std_dev = spice_ms_context_calc_std_dev( ctx, 0, ctx->count );
|
||||
|
||||
fprintf( fh, "# count=%d, mean=%f, std_dev=%f\n", (int) ctx->count, mean, std_dev );
|
||||
|
||||
fclose( fh );
|
||||
free( ctx->values );
|
||||
|
||||
ctx->values = NULL;
|
||||
ctx->count = 0;
|
||||
ctx->capacity = 0;
|
||||
ctx->init = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the context based mean of all measurement timestamp data.
|
||||
*
|
||||
* This function calculates the mean of all
|
||||
* measurement timestamp values by adding
|
||||
* them together and dividing through the
|
||||
* number of measurement timestamp data
|
||||
* points.\n
|
||||
* Optimized for speed, addition is done
|
||||
* using 64-bit integers and only the
|
||||
* division part is done using floating
|
||||
* point arithmetic.\n
|
||||
* This might cause integer overflows in
|
||||
* very large measurement delta values
|
||||
* and extreme long measurement phases,
|
||||
* but is fine for short deltas.
|
||||
*
|
||||
* @param[in] ctx Pointer to context for which to
|
||||
* calculatze the mean for.
|
||||
* @param[in] pos First position of measurement timestamp data.
|
||||
* @param[in] size Number of measurement timestamp data entries,
|
||||
* 0 means all remaining entries starting from pos.
|
||||
* @return The mean value of all measurement timestamp
|
||||
* points or NaN in case of an error.
|
||||
*/
|
||||
double spice_ms_context_calc_mean(const spice_ms_context_t *ctx, const size_t pos, const size_t size)
|
||||
{
|
||||
if ( (ctx == NULL) || !ctx->init || (ctx->count == 0) )
|
||||
return NAN;
|
||||
|
||||
const size_t count = ((size != 0) ? size : (ctx->count - pos));
|
||||
uint64_t sum = 0;
|
||||
|
||||
for ( size_t i = pos; i < (pos + count); i++ ) {
|
||||
sum += ctx->values[i];
|
||||
}
|
||||
|
||||
return ((double) sum / (double) count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the standard deviation of all measurement timestamp data.
|
||||
*
|
||||
* This function calculates the standard deviation
|
||||
* of all measurement timestamp values by first
|
||||
* calculating the mean and then squaring the
|
||||
* subtracting of the individual timestamp values
|
||||
* by the the mean in order to retrieve the
|
||||
* variance.
|
||||
*
|
||||
* @param[in] pos First position of measurement timestamp data.
|
||||
* @param[in] size Number of measurement timestamp data entries,
|
||||
* 0 means all remaining entries starting from pos.
|
||||
* @return The standard deviation value of all measurement
|
||||
* timestamp points or NaN in case of an error.
|
||||
*/
|
||||
double spice_ms_context_calc_std_dev(const spice_ms_context_t *ctx, const size_t pos, const size_t size)
|
||||
{
|
||||
if ( (ctx == NULL) || !ctx->init || (ctx->count == 0) )
|
||||
return NAN;
|
||||
|
||||
const size_t count = ((size != 0) ? size : (ctx->count - pos));
|
||||
const double mean = spice_ms_context_calc_mean( ctx, pos, size );
|
||||
double square_sums = 0.0;
|
||||
|
||||
for ( size_t i = pos; i < (pos + count); i++ ) {
|
||||
const double delta = ((double) ctx->values[i] - mean);
|
||||
|
||||
square_sums += (delta * delta);
|
||||
}
|
||||
|
||||
return sqrt( square_sums / (double) count );
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -42,6 +42,7 @@ spice_version_h = configure_file(input : 'spice-version.h.in',
|
||||
# libspice-server.so
|
||||
#
|
||||
spice_server_headers = [
|
||||
'measurement.h',
|
||||
spice_version_h,
|
||||
'spice-audio.h',
|
||||
'spice-char.h',
|
||||
@ -113,6 +114,7 @@ spice_server_sources = [
|
||||
'main-channel.h',
|
||||
'main-dispatcher.cpp',
|
||||
'main-dispatcher.h',
|
||||
'measurement.c',
|
||||
'memslot.c',
|
||||
'memslot.h',
|
||||
'migration-protocol.h',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user