2 This file is part of GNUnet.
3 Copyright (C) 2011-2015 GNUnet e.V.
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
21 * @file ats/gnunet-service-ats_preferences.c
22 * @brief manage preferences expressed by clients
23 * @author Matthias Wachs
24 * @author Christian Grothoff
27 #include "gnunet-service-ats.h"
28 #include "gnunet-service-ats_addresses.h"
29 #include "gnunet-service-ats_performance.h"
30 #include "gnunet-service-ats_plugins.h"
31 #include "gnunet-service-ats_preferences.h"
32 #include "gnunet-service-ats_reservations.h"
35 #define LOG(kind, ...) GNUNET_log_from (kind, "ats-preferences", __VA_ARGS__)
38 * How frequently do we age preference values?
40 #define PREF_AGING_INTERVAL GNUNET_TIME_relative_multiply ( \
41 GNUNET_TIME_UNIT_SECONDS, 10)
44 * By which factor do we age preferences expressed during
45 * each #PREF_AGING_INTERVAL?
47 #define PREF_AGING_FACTOR 0.95
50 * What is the lowest threshold up to which prefernce values
51 * are aged, and below which we consider them zero and thus
52 * no longer subject to aging?
54 #define PREF_EPSILON 0.01
58 * Relative preferences for a peer.
63 * Array of relative preference values, to be indexed by
64 * an `enum GNUNET_ATS_PreferenceKind`.
66 double f_rel[GNUNET_ATS_PREFERENCE_END];
69 * Number of clients that are expressing a preference for
70 * this peer. When this counter reaches zero, this entry
73 unsigned int num_clients;
78 * Default values, returned as our preferences if we do not
79 * have any preferences expressed for a peer.
81 static struct PeerRelative defvalues;
85 * Preference information per peer and client.
90 * Next in DLL of preference entries for the same client.
92 struct PreferencePeer *next;
95 * Previous in DLL of preference entries for the same client.
97 struct PreferencePeer *prev;
100 * Absolute preference values for all preference types
101 * as expressed by this client for this peer.
103 double f_abs[GNUNET_ATS_PREFERENCE_END];
106 * Relative preference values for all preference types,
107 * normalized in [0..1] based on how the respective
108 * client scored other peers.
110 double f_rel[GNUNET_ATS_PREFERENCE_END];
115 * Preference client, as in a client that expressed preferences
116 * for peers. This is the information we keep track of for each
119 struct PreferenceClient
122 * Next in client list
124 struct PreferenceClient *next;
127 * Previous in client peer list
129 struct PreferenceClient *prev;
134 struct GNUNET_SERVICE_Client *client;
137 * Mapping peer identities to `struct PreferencePeer` entry
138 * for the respective peer.
140 struct GNUNET_CONTAINER_MultiPeerMap *peer2pref;
143 * Array of sums of absolute preferences for all
144 * peers as expressed by this client.
146 double f_abs_sum[GNUNET_ATS_PREFERENCE_END];
151 * Hashmap to store peer information for preference normalization.
152 * Maps the identity of a peer to a `struct PeerRelative` containing
153 * the current relative preference values for that peer.
155 static struct GNUNET_CONTAINER_MultiPeerMap *preference_peers;
158 * Clients in DLL: head
160 static struct PreferenceClient *pc_head;
163 * Clients in DLL: tail
165 static struct PreferenceClient *pc_tail;
168 * Handle for task we run periodically to age preferences over time.
170 static struct GNUNET_SCHEDULER_Task *aging_task;
174 * Closure for #sum_relative_preferences().
179 * Where to accumulate the result.
184 * Which kind of preference value are we adding up?
186 enum GNUNET_ATS_PreferenceKind kind;
191 * Add the relative preference for the kind given to the
194 * @param cls the `struct SumContext` with the kind and place
195 * to store the result
196 * @param peer ignored
197 * @param value the `struct PreferencePeer` for getting the rel pref.
201 sum_relative_preferences (void *cls,
202 const struct GNUNET_PeerIdentity *peer,
205 struct SumContext *sum_ctx = cls;
206 struct PreferencePeer *p_cur = value;
208 sum_ctx->f_rel_total += p_cur->f_rel[sum_ctx->kind];
214 * Update the total releative preference for a peer by summing
215 * up the relative preferences all clients have for this peer.
217 * @param id peer id of the peer for which we should do the update
218 * @param kind the kind of preference value to update
219 * @return the new relative preference
222 update_relative_values_for_peer (const struct GNUNET_PeerIdentity *id,
223 enum GNUNET_ATS_PreferenceKind kind)
225 struct PreferenceClient *c_cur;
226 struct SumContext sum_ctx;
227 struct PeerRelative *rp;
229 sum_ctx.f_rel_total = 0.0;
231 for (c_cur = pc_head; NULL != c_cur; c_cur = c_cur->next)
232 GNUNET_CONTAINER_multipeermap_get_multiple (c_cur->peer2pref,
234 &sum_relative_preferences,
236 LOG (GNUNET_ERROR_TYPE_DEBUG,
237 "Total relative preference for peer `%s' for `%s' is %.3f\n",
239 GNUNET_ATS_print_preference_type (kind),
240 sum_ctx.f_rel_total);
241 rp = GNUNET_CONTAINER_multipeermap_get (preference_peers,
243 GNUNET_assert (NULL != rp);
244 if (rp->f_rel[kind] != sum_ctx.f_rel_total)
246 rp->f_rel[kind] = sum_ctx.f_rel_total;
247 GAS_plugin_notify_preference_changed (id,
255 * Free a peer's `struct PeerRelative`.
259 * @param value the `struct PeerRelative` to free.
260 * @return #GNUNET_OK to continue
263 free_peer (void *cls,
264 const struct GNUNET_PeerIdentity *key,
267 struct PeerRelative *rp = value;
269 GNUNET_assert (GNUNET_YES ==
270 GNUNET_CONTAINER_multipeermap_remove (preference_peers,
279 * Free `struct PreferencePeer` entry in map.
281 * @param cls the `struct PreferenceClient` with the map
282 * @param key the peer the entry is for
283 * @param value the `struct PreferencePeer` entry to free
284 * @return #GNUNET_OK (continue to iterate)
287 free_preference (void *cls,
288 const struct GNUNET_PeerIdentity *key,
291 struct PreferenceClient *pc = cls;
292 struct PreferencePeer *p = value;
293 struct PeerRelative *pr;
295 GNUNET_assert (GNUNET_OK ==
296 GNUNET_CONTAINER_multipeermap_remove (pc->peer2pref,
300 pr = GNUNET_CONTAINER_multipeermap_get (preference_peers,
302 GNUNET_assert (NULL != pr);
303 GNUNET_assert (pr->num_clients > 0);
305 if (0 == pr->num_clients)
316 * Closure for #age_values().
321 * Counter of values remaining to update, incremented for each value
322 * changed (to a new non-zero value).
324 unsigned int values_to_update;
327 * Client we are currently aging values for.
329 struct PreferenceClient *cur_client;
334 * Age preference values of the given peer.
337 * @param peer peer being aged
338 * @param value the `struct PreferencePeer` for the peer
339 * @return #GNUNET_OK (continue to iterate)
342 age_values (void *cls,
343 const struct GNUNET_PeerIdentity *peer,
346 struct AgeContext *ac = cls;
347 struct PreferencePeer *p = value;
352 for (i = 0; i < GNUNET_ATS_PREFERENCE_END; i++)
354 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
355 "Aging preference for peer `%s'\n",
357 if (p->f_abs[i] > DEFAULT_ABS_PREFERENCE)
358 p->f_abs[i] *= PREF_AGING_FACTOR;
359 if (p->f_abs[i] <= DEFAULT_ABS_PREFERENCE + PREF_EPSILON)
361 p->f_abs[i] = DEFAULT_ABS_PREFERENCE;
362 p->f_rel[i] = DEFAULT_REL_PREFERENCE;
363 update_relative_values_for_peer (peer,
368 ac->values_to_update++;
372 if (GNUNET_YES == dead)
374 /* all preferences are zero, remove this entry */
375 free_preference (ac->cur_client,
384 * Reduce absolute preferences since they got old.
389 preference_aging (void *cls)
391 struct AgeContext ac;
394 GAS_plugin_solver_lock ();
395 ac.values_to_update = 0;
396 for (ac.cur_client = pc_head; NULL != ac.cur_client; ac.cur_client =
398 GNUNET_CONTAINER_multipeermap_iterate (ac.cur_client->peer2pref,
401 GAS_plugin_solver_unlock ();
402 if (ac.values_to_update > 0)
404 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
405 "Rescheduling aging task due to %u elements remaining to age\n",
406 ac.values_to_update);
407 if (NULL == aging_task)
408 aging_task = GNUNET_SCHEDULER_add_delayed (PREF_AGING_INTERVAL,
414 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
415 "No values to age left, not rescheduling aging task\n");
421 * Closure for #update_rel_sum() and #update_abs_sum().
426 * Preference client with the sum of all absolute scores.
428 struct PreferenceClient *pc;
431 * Which kind are we updating?
433 enum GNUNET_ATS_PreferenceKind kind;
438 * Compute updated absolute score for the client based on the
439 * current absolute scores for each peer.
441 * @param cls a `struct UpdateContext`
442 * @param peer peer being updated
443 * @param value the `struct PreferencePeer` for the peer
444 * @return #GNUNET_OK (continue to iterate)
447 update_abs_sum (void *cls,
448 const struct GNUNET_PeerIdentity *peer,
451 struct UpdateContext *uc = cls;
452 struct PreferencePeer *p_cur = value;
454 uc->pc->f_abs_sum[uc->kind] += p_cur->f_abs[uc->kind];
460 * Compute updated relative score for each peer based on the
461 * current absolute score given by this client.
463 * @param cls a `struct UpdateContext`
464 * @param peer peer being updated
465 * @param value the `struct PreferencePeer` for the peer (updated)
466 * @return #GNUNET_OK (continue to iterate)
469 update_rel_sum (void *cls,
470 const struct GNUNET_PeerIdentity *peer,
473 struct UpdateContext *uc = cls;
474 struct PreferencePeer *p_cur = value;
476 p_cur->f_rel[uc->kind] = p_cur->f_abs[uc->kind] / uc->pc->f_abs_sum[uc->kind];
477 LOG (GNUNET_ERROR_TYPE_DEBUG,
478 "Client has relative preference for %s for peer `%s' of %.3f\n",
479 GNUNET_ATS_print_preference_type (uc->kind),
481 p_cur->f_rel[uc->kind]);
487 * Recalculate preference for a specific ATS property
489 * @param c the preference client
490 * @param kind the preference kind
494 recalculate_relative_preferences (struct PreferenceClient *c,
495 enum GNUNET_ATS_PreferenceKind kind)
497 struct UpdateContext uc;
499 /* For this client: sum of absolute preference values for this preference */
502 c->f_abs_sum[kind] = 0.0;
504 /* For all peers: calculate sum of absolute preferences */
505 GNUNET_CONTAINER_multipeermap_iterate (c->peer2pref,
508 LOG (GNUNET_ERROR_TYPE_DEBUG,
509 "Client has sum of total preferences for %s of %.3f\n",
510 GNUNET_ATS_print_preference_type (kind),
513 /* For all peers: calculate relative preference */
514 GNUNET_CONTAINER_multipeermap_iterate (c->peer2pref,
521 * The relative preferences of one of the clients have
522 * changed, update the global preferences for the given
523 * peer and notify the plugin.
525 * @param cls the kind of preference to calculate the
526 * new global relative preference values for
527 * @param key the peer to update relative preference values for
528 * @param value a `struct PeerRelative`, unused
531 update_iterator (void *cls,
532 const struct GNUNET_PeerIdentity *key,
535 enum GNUNET_ATS_PreferenceKind *kind = cls;
537 update_relative_values_for_peer (key,
544 * Update the absolute preference and calculate the
545 * new relative preference value.
547 * @param client the client with this preference
548 * @param peer the peer to change the preference for
549 * @param kind the kind to change the preference
550 * @param score_abs the normalized score
553 update_preference (struct GNUNET_SERVICE_Client *client,
554 const struct GNUNET_PeerIdentity *peer,
555 enum GNUNET_ATS_PreferenceKind kind,
558 struct PreferenceClient *c_cur;
559 struct PreferencePeer *p_cur;
560 struct PeerRelative *r_cur;
563 if (kind >= GNUNET_ATS_PREFERENCE_END)
568 LOG (GNUNET_ERROR_TYPE_DEBUG,
569 "Client changes preference for peer `%s' for `%s' to %.2f\n",
571 GNUNET_ATS_print_preference_type (kind),
574 /* Find preference client */
575 for (c_cur = pc_head; NULL != c_cur; c_cur = c_cur->next)
576 if (client == c_cur->client)
578 /* Not found: create new preference client */
581 c_cur = GNUNET_new (struct PreferenceClient);
582 c_cur->client = client;
583 c_cur->peer2pref = GNUNET_CONTAINER_multipeermap_create (16,
585 for (i = 0; i < GNUNET_ATS_PREFERENCE_END; i++)
586 c_cur->f_abs_sum[i] = DEFAULT_ABS_PREFERENCE;
587 GNUNET_CONTAINER_DLL_insert (pc_head,
592 /* check global peer entry exists */
594 (r_cur = GNUNET_CONTAINER_multipeermap_get (preference_peers,
597 /* Create struct for peer */
598 r_cur = GNUNET_new (struct PeerRelative);
599 for (i = 0; i < GNUNET_ATS_PREFERENCE_END; i++)
600 r_cur->f_rel[i] = DEFAULT_REL_PREFERENCE;
601 GNUNET_assert (GNUNET_OK ==
602 GNUNET_CONTAINER_multipeermap_put (preference_peers,
605 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
608 /* Find entry for peer */
609 p_cur = GNUNET_CONTAINER_multipeermap_get (c_cur->peer2pref,
613 /* Not found: create new peer entry */
614 p_cur = GNUNET_new (struct PreferencePeer);
615 for (i = 0; i < GNUNET_ATS_PREFERENCE_END; i++)
617 /* Default value per peer absolute preference for a preference*/
618 p_cur->f_abs[i] = DEFAULT_ABS_PREFERENCE;
619 /* Default value per peer relative preference for a quality */
620 p_cur->f_rel[i] = DEFAULT_REL_PREFERENCE;
622 GNUNET_assert (GNUNET_YES ==
623 GNUNET_CONTAINER_multipeermap_put (c_cur->peer2pref,
626 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
627 r_cur->num_clients++;
630 p_cur->f_abs[kind] += score_abs;
631 recalculate_relative_preferences (c_cur, kind);
632 GNUNET_CONTAINER_multipeermap_iterate (preference_peers,
636 if (NULL == aging_task)
637 aging_task = GNUNET_SCHEDULER_add_delayed (PREF_AGING_INTERVAL,
644 * Handle 'preference change' messages from clients.
646 * @param client the client that sent the request
647 * @param msg the request message
650 GAS_handle_preference_change (struct GNUNET_SERVICE_Client *client,
651 const struct ChangePreferenceMessage *msg)
653 const struct PreferenceInformation *pi;
656 nump = ntohl (msg->num_preferences);
657 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
658 "Received PREFERENCE_CHANGE message for peer `%s'\n",
659 GNUNET_i2s (&msg->peer));
660 GNUNET_STATISTICS_update (GSA_stats,
661 "# preference change requests processed",
664 pi = (const struct PreferenceInformation *) &msg[1];
665 GAS_plugin_solver_lock ();
666 for (uint32_t i = 0; i < nump; i++)
667 update_preference (client,
669 (enum GNUNET_ATS_PreferenceKind) ntohl (
670 pi[i].preference_kind),
671 pi[i].preference_value);
672 GAS_plugin_solver_unlock ();
677 * Initialize preferences subsystem.
680 GAS_preference_init ()
684 preference_peers = GNUNET_CONTAINER_multipeermap_create (16,
686 for (i = 0; i < GNUNET_ATS_PREFERENCE_END; i++)
687 defvalues.f_rel[i] = DEFAULT_REL_PREFERENCE;
692 * Shutdown preferences subsystem.
695 GAS_preference_done ()
697 struct PreferenceClient *pc;
698 struct PreferenceClient *next_pc;
700 if (NULL != aging_task)
702 GNUNET_SCHEDULER_cancel (aging_task);
706 while (NULL != (pc = next_pc))
709 GNUNET_CONTAINER_DLL_remove (pc_head,
712 GNUNET_CONTAINER_multipeermap_iterate (pc->peer2pref,
715 GNUNET_CONTAINER_multipeermap_destroy (pc->peer2pref);
718 GNUNET_CONTAINER_multipeermap_iterate (preference_peers,
721 GNUNET_CONTAINER_multipeermap_destroy (preference_peers);
726 * Get the normalized preference values for a specific peer or
727 * the default values if
731 * @return pointer to the values, can be indexed with GNUNET_ATS_PreferenceKind,
732 * default preferences if peer does not exist
735 GAS_preference_get_by_peer (void *cls,
736 const struct GNUNET_PeerIdentity *id)
738 struct PeerRelative *rp;
741 (rp = GNUNET_CONTAINER_multipeermap_get (preference_peers,
744 return defvalues.f_rel;
751 * A performance client disconnected
753 * @param client the client
756 GAS_preference_client_disconnect (struct GNUNET_SERVICE_Client *client)
758 struct PreferenceClient *c_cur;
760 for (c_cur = pc_head; NULL != c_cur; c_cur = c_cur->next)
761 if (client == c_cur->client)
765 GNUNET_CONTAINER_DLL_remove (pc_head,
768 GNUNET_CONTAINER_multipeermap_iterate (c_cur->peer2pref,
771 GNUNET_CONTAINER_multipeermap_destroy (c_cur->peer2pref);
776 /* end of gnunet-service-ats_preferences.c */