/*
This file is part of GNUnet.
Copyright (C) 2012 Christian Grothoff
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet 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
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
/**
* @file fs/gnunet-daemon-fsprofiler.c
* @brief daemon that publishes and downloads (random) files
* @author Christian Grothoff
*
* TODO:
* - how to signal driver that we're done?
*/
#include "platform.h"
#include "gnunet_fs_service.h"
#include "gnunet_statistics_service.h"
/**
* We use 'patterns' of the form (x,y,t) to specify desired download/publish
* activities of a peer. They are stored in a DLL.
*/
struct Pattern
{
/**
* Kept in a DLL.
*/
struct Pattern *next;
/**
* Kept in a DLL.
*/
struct Pattern *prev;
/**
* Execution context for the pattern (FS-handle to the operation).
*/
void *ctx;
/**
* Secondary execution context for the pattern (FS-handle to the operation).
*/
void *sctx;
/**
* When did the operation start?
*/
struct GNUNET_TIME_Absolute start_time;
/**
* With how much delay should this operation be started?
*/
struct GNUNET_TIME_Relative delay;
/**
* Task to run the operation.
*/
struct GNUNET_SCHEDULER_Task * task;
/**
* Secondary task to run the operation.
*/
struct GNUNET_SCHEDULER_Task * stask;
/**
* X-value.
*/
unsigned long long x;
/**
* Y-value.
*/
unsigned long long y;
};
/**
* Return value from 'main'.
*/
static int global_ret;
/**
* Configuration we use.
*/
static const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* Handle to the statistics service.
*/
static struct GNUNET_STATISTICS_Handle *stats_handle;
/**
* Peer's FS handle.
*/
static struct GNUNET_FS_Handle *fs_handle;
/**
* Unique number for this peer in the testbed.
*/
static unsigned long long my_peerid;
/**
* Desired anonymity level.
*/
static unsigned long long anonymity_level;
/**
* Desired replication level.
*/
static unsigned long long replication_level;
/**
* String describing which publishing operations this peer should
* perform. The format is "(SIZE,SEED,TIME)*", for example:
* "(1,5,0)(7,3,13)" means to publish a file with 1 byte and
* seed/keyword 5 immediately and another file with 7 bytes and
* seed/keyword 3 after 13 ms.
*/
static char *publish_pattern;
/**
* Head of the DLL of publish patterns.
*/
static struct Pattern *publish_head;
/**
* Tail of the DLL of publish patterns.
*/
static struct Pattern *publish_tail;
/**
* String describing which download operations this peer should
* perform. The format is "(KEYWORD,SIZE,DELAY)*"; for example,
* "(1,7,3)(3,8,8)" means to download one file of 7 bytes under
* keyword "1" starting the search after 3 ms; and another one of 8
* bytes under keyword '3' starting after 8 ms. The file size is
* used to determine which search result(s) should be used or ignored.
*/
static char *download_pattern;
/**
* Head of the DLL of publish patterns.
*/
static struct Pattern *download_head;
/**
* Tail of the DLL of publish patterns.
*/
static struct Pattern *download_tail;
/**
* Parse a pattern string and store the corresponding
* 'struct Pattern' in the given head/tail.
*
* @param head where to store the head
* @param tail where to store the tail
* @param pattern pattern to parse
* @return GNUNET_OK on success
*/
static int
parse_pattern (struct Pattern **head,
struct Pattern **tail,
const char *pattern)
{
struct Pattern *p;
unsigned long long x;
unsigned long long y;
unsigned long long t;
while (3 == sscanf (pattern,
"(%llu,%llu,%llu)",
&x, &y, &t))
{
p = GNUNET_new (struct Pattern);
p->x = x;
p->y = y;
p->delay.rel_value_us = (uint64_t) t;
GNUNET_CONTAINER_DLL_insert (*head, *tail, p);
pattern = strstr (pattern, ")");
GNUNET_assert (NULL != pattern);
pattern++;
}
return (0 == strlen (pattern)) ? GNUNET_OK : GNUNET_SYSERR;
}
/**
* Create a KSK URI from a number.
*
* @param kval the number
* @return corresponding KSK URI
*/
static struct GNUNET_FS_Uri *
make_keywords (uint64_t kval)
{
char kw[128];
GNUNET_snprintf (kw, sizeof (kw),
"%llu", (unsigned long long) kval);
return GNUNET_FS_uri_ksk_create (kw, NULL);
}
/**
* Create a file of the given length with a deterministic amount
* of data to be published under keyword 'kval'.
*
* @param length number of bytes in the file
* @param kval keyword value and seed for the data of the file
* @param ctx context to pass to 'fi'
* @return file information handle for the file
*/
static struct GNUNET_FS_FileInformation *
make_file (uint64_t length,
uint64_t kval,
void *ctx)
{
struct GNUNET_FS_FileInformation *fi;
struct GNUNET_FS_BlockOptions bo;
char *data;
struct GNUNET_FS_Uri *keywords;
unsigned long long i;
uint64_t xor;
data = NULL; /* to make compilers happy */
if ( (0 != length) &&
(NULL == (data = GNUNET_malloc_large ((size_t) length))) )
return NULL;
/* initialize data with 'unique' data only depending on 'kval' and 'size',
making sure that blocks do not repeat */
for (i=0;itask)
GNUNET_SCHEDULER_cancel (p->task);
if (NULL != p->ctx)
GNUNET_FS_publish_stop (p->ctx);
GNUNET_CONTAINER_DLL_remove (publish_head, publish_tail, p);
GNUNET_free (p);
}
while (NULL != (p = download_head))
{
if (NULL != p->task)
GNUNET_SCHEDULER_cancel (p->task);
if (NULL != p->stask)
GNUNET_SCHEDULER_cancel (p->stask);
if (NULL != p->ctx)
GNUNET_FS_download_stop (p->ctx, GNUNET_YES);
if (NULL != p->sctx)
GNUNET_FS_search_stop (p->sctx);
GNUNET_CONTAINER_DLL_remove (download_head, download_tail, p);
GNUNET_free (p);
}
if (NULL != fs_handle)
{
GNUNET_FS_stop (fs_handle);
fs_handle = NULL;
}
if (NULL != stats_handle)
{
GNUNET_STATISTICS_destroy (stats_handle, GNUNET_YES);
stats_handle = NULL;
}
}
/**
* Task run when a publish operation should be stopped.
*
* @param cls the 'struct Pattern' of the publish operation to stop
*/
static void
publish_stop_task (void *cls)
{
struct Pattern *p = cls;
p->task = NULL;
GNUNET_FS_publish_stop (p->ctx);
}
/**
* Task run when a download operation should be stopped.
*
* @param cls the 'struct Pattern' of the download operation to stop
*/
static void
download_stop_task (void *cls)
{
struct Pattern *p = cls;
p->task = NULL;
GNUNET_FS_download_stop (p->ctx, GNUNET_YES);
}
/**
* Task run when a download operation should be stopped.
*
* @param cls the 'struct Pattern' of the download operation to stop
*/
static void
search_stop_task (void *cls)
{
struct Pattern *p = cls;
p->stask = NULL;
GNUNET_FS_search_stop (p->sctx);
}
/**
* Notification of FS to a client about the progress of an
* operation. Callbacks of this type will be used for uploads,
* downloads and searches. Some of the arguments depend a bit
* in their meaning on the context in which the callback is used.
*
* @param cls closure
* @param info details about the event, specifying the event type
* and various bits about the event
* @return client-context (for the next progress call
* for this operation; should be set to NULL for
* SUSPEND and STOPPED events). The value returned
* will be passed to future callbacks in the respective
* field in the GNUNET_FS_ProgressInfo struct.
*/
static void *
progress_cb (void *cls,
const struct GNUNET_FS_ProgressInfo *info)
{
struct Pattern *p;
const struct GNUNET_FS_Uri *uri;
switch (info->status)
{
case GNUNET_FS_STATUS_PUBLISH_START:
case GNUNET_FS_STATUS_PUBLISH_PROGRESS:
p = info->value.publish.cctx;
return p;
case GNUNET_FS_STATUS_PUBLISH_PROGRESS_DIRECTORY:
p = info->value.publish.cctx;
return p;
case GNUNET_FS_STATUS_PUBLISH_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Publishing failed\n");
GNUNET_STATISTICS_update (stats_handle,
"# failed publish operations", 1, GNUNET_NO);
p = info->value.publish.cctx;
p->task = GNUNET_SCHEDULER_add_now (&publish_stop_task, p);
return p;
case GNUNET_FS_STATUS_PUBLISH_COMPLETED:
p = info->value.publish.cctx;
GNUNET_STATISTICS_update (stats_handle,
"# publishing time (ms)",
(long long) GNUNET_TIME_absolute_get_duration (p->start_time).rel_value_us / 1000LL,
GNUNET_NO);
p->task = GNUNET_SCHEDULER_add_now (&publish_stop_task, p);
return p;
case GNUNET_FS_STATUS_PUBLISH_STOPPED:
p = info->value.publish.cctx;
p->ctx = NULL;
GNUNET_CONTAINER_DLL_remove (publish_head, publish_tail, p);
GNUNET_free (p);
return NULL;
case GNUNET_FS_STATUS_DOWNLOAD_START:
case GNUNET_FS_STATUS_DOWNLOAD_PROGRESS:
case GNUNET_FS_STATUS_DOWNLOAD_ACTIVE:
case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE:
p = info->value.download.cctx;
return p;
case GNUNET_FS_STATUS_DOWNLOAD_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Download failed\n");
GNUNET_STATISTICS_update (stats_handle,
"# failed downloads", 1, GNUNET_NO);
p = info->value.download.cctx;
p->task = GNUNET_SCHEDULER_add_now (&download_stop_task, p);
return p;
case GNUNET_FS_STATUS_DOWNLOAD_COMPLETED:
p = info->value.download.cctx;
GNUNET_STATISTICS_update (stats_handle,
"# download time (ms)",
(long long) GNUNET_TIME_absolute_get_duration (p->start_time).rel_value_us / 1000LL,
GNUNET_NO);
p->task = GNUNET_SCHEDULER_add_now (&download_stop_task, p);
return p;
case GNUNET_FS_STATUS_DOWNLOAD_STOPPED:
p = info->value.download.cctx;
p->ctx = NULL;
if (NULL == p->sctx)
{
GNUNET_CONTAINER_DLL_remove (download_head, download_tail, p);
GNUNET_free (p);
}
return NULL;
case GNUNET_FS_STATUS_SEARCH_START:
case GNUNET_FS_STATUS_SEARCH_RESULT_NAMESPACE:
p = info->value.search.cctx;
return p;
case GNUNET_FS_STATUS_SEARCH_RESULT:
p = info->value.search.cctx;
uri = info->value.search.specifics.result.uri;
if (GNUNET_YES != GNUNET_FS_uri_test_chk (uri))
return NULL; /* not what we want */
if (p->y != GNUNET_FS_uri_chk_get_file_size (uri))
return NULL; /* not what we want */
GNUNET_STATISTICS_update (stats_handle,
"# search time (ms)",
(long long) GNUNET_TIME_absolute_get_duration (p->start_time).rel_value_us / 1000LL,
GNUNET_NO);
p->start_time = GNUNET_TIME_absolute_get ();
p->ctx = GNUNET_FS_download_start (fs_handle, uri,
NULL, NULL, NULL,
0, GNUNET_FS_uri_chk_get_file_size (uri),
anonymity_level,
GNUNET_FS_DOWNLOAD_NO_TEMPORARIES,
p,
NULL);
p->stask = GNUNET_SCHEDULER_add_now (&search_stop_task, p);
return NULL;
case GNUNET_FS_STATUS_SEARCH_UPDATE:
case GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED:
return NULL; /* don't care */
case GNUNET_FS_STATUS_SEARCH_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Search failed\n");
GNUNET_STATISTICS_update (stats_handle,
"# failed searches", 1, GNUNET_NO);
p = info->value.search.cctx;
p->stask = GNUNET_SCHEDULER_add_now (&search_stop_task, p);
return p;
case GNUNET_FS_STATUS_SEARCH_STOPPED:
p = info->value.search.cctx;
p->sctx = NULL;
if (NULL == p->ctx)
{
GNUNET_CONTAINER_DLL_remove (download_head, download_tail, p);
GNUNET_free (p);
}
return NULL;
default:
/* unexpected event during profiling */
GNUNET_break (0);
return NULL;
}
}
/**
* Start publish operation.
*
* @param cls the 'struct Pattern' specifying the operation to perform
*/
static void
start_publish (void *cls)
{
struct Pattern *p = cls;
struct GNUNET_FS_FileInformation *fi;
p->task = NULL;
fi = make_file (p->x, p->y, p);
p->start_time = GNUNET_TIME_absolute_get ();
p->ctx = GNUNET_FS_publish_start (fs_handle,
fi,
NULL, NULL, NULL,
GNUNET_FS_PUBLISH_OPTION_NONE);
}
/**
* Start download operation.
*
* @param cls the 'struct Pattern' specifying the operation to perform
*/
static void
start_download (void *cls)
{
struct Pattern *p = cls;
struct GNUNET_FS_Uri *keywords;
p->task = NULL;
keywords = make_keywords (p->x);
p->start_time = GNUNET_TIME_absolute_get ();
p->sctx = GNUNET_FS_search_start (fs_handle, keywords,
anonymity_level,
GNUNET_FS_SEARCH_OPTION_NONE,
p);
}
/**
* @brief Main function that will be run by the scheduler.
*
* @param cls closure
* @param args remaining command-line arguments
* @param cfgfile name of the configuration file used (for saving, can be NULL!)
* @param cfg_ configuration
*/
static void
run (void *cls, char *const *args GNUNET_UNUSED,
const char *cfgfile GNUNET_UNUSED,
const struct GNUNET_CONFIGURATION_Handle *cfg_)
{
char myoptname[128];
struct Pattern *p;
cfg = cfg_;
/* Scheduled the task to clean up when shutdown is called */
GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
NULL);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_number (cfg,
"TESTBED", "PEERID",
&my_peerid))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"TESTBED", "PEERID");
global_ret = GNUNET_SYSERR;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_number (cfg,
"FSPROFILER", "ANONYMITY_LEVEL",
&anonymity_level))
anonymity_level = 1;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_number (cfg,
"FSPROFILER", "REPLICATION_LEVEL",
&replication_level))
replication_level = 1;
GNUNET_snprintf (myoptname, sizeof (myoptname),
"DOWNLOAD-PATTERN-%u", my_peerid);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
"FSPROFILER", myoptname,
&download_pattern))
download_pattern = GNUNET_strdup ("");
GNUNET_snprintf (myoptname, sizeof (myoptname),
"PUBLISH-PATTERN-%u", my_peerid);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
"FSPROFILER", myoptname,
&publish_pattern))
publish_pattern = GNUNET_strdup ("");
if ( (GNUNET_OK !=
parse_pattern (&download_head,
&download_tail,
download_pattern)) ||
(GNUNET_OK !=
parse_pattern (&publish_head,
&publish_tail,
publish_pattern)) )
{
GNUNET_SCHEDULER_shutdown ();
return;
}
stats_handle = GNUNET_STATISTICS_create ("fsprofiler", cfg);
fs_handle =
GNUNET_FS_start (cfg,
"fsprofiler",
&progress_cb, NULL,
GNUNET_FS_FLAGS_NONE,
GNUNET_FS_OPTIONS_DOWNLOAD_PARALLELISM, 1,
GNUNET_FS_OPTIONS_REQUEST_PARALLELISM, 1,
GNUNET_FS_OPTIONS_END);
if (NULL == fs_handle)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not acquire FS handle. Exiting.\n");
global_ret = GNUNET_SYSERR;
GNUNET_SCHEDULER_shutdown ();
return;
}
for (p = publish_head; NULL != p; p = p->next)
p->task = GNUNET_SCHEDULER_add_delayed (p->delay,
&start_publish, p);
for (p = download_head; NULL != p; p = p->next)
p->task = GNUNET_SCHEDULER_add_delayed (p->delay,
&start_download, p);
}
/**
* Program that performs various "random" FS activities.
*
* @param argc number of arguments from the command line
* @param argv command line arguments
* @return 0 ok, 1 on error
*/
int
main (int argc, char *const *argv)
{
static const struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_OPTION_END
};
if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
return 2;
return (GNUNET_OK ==
GNUNET_PROGRAM_run (argc, argv, "gnunet-daemon-fsprofiler",
gettext_noop
("Daemon to use file-sharing to measure its performance."),
options, &run, NULL)) ? global_ret : 1;
}
/* end of gnunet-daemon-fsprofiler.c */