e1a78635929efb3dfdf8c49ac147a3984afd1293
[oweals/gnunet.git] / src / ats / gnunet-service-ats_preferences.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2011-2015 GNUnet e.V.
4
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.
9
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.
14     
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/>.
17 */
18 /**
19  * @file ats/gnunet-service-ats_preferences.c
20  * @brief manage preferences expressed by clients
21  * @author Matthias Wachs
22  * @author Christian Grothoff
23  */
24 #include "platform.h"
25 #include "gnunet-service-ats.h"
26 #include "gnunet-service-ats_addresses.h"
27 #include "gnunet-service-ats_performance.h"
28 #include "gnunet-service-ats_plugins.h"
29 #include "gnunet-service-ats_preferences.h"
30 #include "gnunet-service-ats_reservations.h"
31 #include "ats.h"
32
33 #define LOG(kind,...) GNUNET_log_from (kind, "ats-preferences",__VA_ARGS__)
34
35 /**
36  * How frequently do we age preference values?
37  */
38 #define PREF_AGING_INTERVAL GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10)
39
40 /**
41  * By which factor do we age preferences expressed during
42  * each #PREF_AGING_INTERVAL?
43  */
44 #define PREF_AGING_FACTOR 0.95
45
46 /**
47  * What is the lowest threshold up to which prefernce values
48  * are aged, and below which we consider them zero and thus
49  * no longer subject to aging?
50  */
51 #define PREF_EPSILON 0.01
52
53
54 /**
55  * Relative preferences for a peer.
56  */
57 struct PeerRelative
58 {
59   /**
60    * Array of relative preference values, to be indexed by
61    * an `enum GNUNET_ATS_PreferenceKind`.
62    */
63   double f_rel[GNUNET_ATS_PREFERENCE_END];
64
65   /**
66    * Number of clients that are expressing a preference for
67    * this peer. When this counter reaches zero, this entry
68    * is freed.
69    */
70   unsigned int num_clients;
71 };
72
73
74 /**
75  * Default values, returned as our preferences if we do not
76  * have any preferences expressed for a peer.
77  */
78 static struct PeerRelative defvalues;
79
80
81 /**
82  * Preference information per peer and client.
83  */
84 struct PreferencePeer
85 {
86   /**
87    * Next in DLL of preference entries for the same client.
88    */
89   struct PreferencePeer *next;
90
91   /**
92    * Previous in DLL of preference entries for the same client.
93    */
94   struct PreferencePeer *prev;
95
96   /**
97    * Absolute preference values for all preference types
98    * as expressed by this client for this peer.
99    */
100   double f_abs[GNUNET_ATS_PREFERENCE_END];
101
102   /**
103    * Relative preference values for all preference types,
104    * normalized in [0..1] based on how the respective
105    * client scored other peers.
106    */
107   double f_rel[GNUNET_ATS_PREFERENCE_END];
108
109 };
110
111
112 /**
113  * Preference client, as in a client that expressed preferences
114  * for peers.  This is the information we keep track of for each
115  * such client.
116  */
117 struct PreferenceClient
118 {
119
120   /**
121    * Next in client list
122    */
123   struct PreferenceClient *next;
124
125   /**
126    * Previous in client peer list
127    */
128   struct PreferenceClient *prev;
129
130   /**
131    * Client handle
132    */
133   struct GNUNET_SERVICE_Client *client;
134
135   /**
136    * Mapping peer identities to `struct PreferencePeer` entry
137    * for the respective peer.
138    */
139   struct GNUNET_CONTAINER_MultiPeerMap *peer2pref;
140
141   /**
142    * Array of sums of absolute preferences for all
143    * peers as expressed by this client.
144    */
145   double f_abs_sum[GNUNET_ATS_PREFERENCE_END];
146
147 };
148
149
150 /**
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.
154  */
155 static struct GNUNET_CONTAINER_MultiPeerMap *preference_peers;
156
157 /**
158  * Clients in DLL: head
159  */
160 static struct PreferenceClient *pc_head;
161
162 /**
163  * Clients in DLL: tail
164  */
165 static struct PreferenceClient *pc_tail;
166
167 /**
168  * Handle for task we run periodically to age preferences over time.
169  */
170 static struct GNUNET_SCHEDULER_Task *aging_task;
171
172
173 /**
174  * Closure for #sum_relative_preferences().
175  */
176 struct SumContext
177 {
178   /**
179    * Where to accumulate the result.
180    */
181   double f_rel_total;
182
183   /**
184    * Which kind of preference value are we adding up?
185    */
186   enum GNUNET_ATS_PreferenceKind kind;
187 };
188
189
190 /**
191  * Add the relative preference for the kind given to the
192  * closure.
193  *
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.
198  * @return #GNUNET_OK
199  */
200 static int
201 sum_relative_preferences (void *cls,
202                           const struct GNUNET_PeerIdentity *peer,
203                           void *value)
204 {
205   struct SumContext *sum_ctx = cls;
206   struct PreferencePeer *p_cur = value;
207
208   sum_ctx->f_rel_total += p_cur->f_rel[sum_ctx->kind];
209   return GNUNET_OK;
210 }
211
212
213 /**
214  * Update the total releative preference for a peer by summing
215  * up the relative preferences all clients have for this peer.
216  *
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
220  */
221 static void
222 update_relative_values_for_peer (const struct GNUNET_PeerIdentity *id,
223                                  enum GNUNET_ATS_PreferenceKind kind)
224 {
225   struct PreferenceClient *c_cur;
226   struct SumContext sum_ctx;
227   struct PeerRelative *rp;
228
229   sum_ctx.f_rel_total = 0.0;
230   sum_ctx.kind = kind;
231   for (c_cur = pc_head; NULL != c_cur; c_cur = c_cur->next)
232     GNUNET_CONTAINER_multipeermap_get_multiple (c_cur->peer2pref,
233                                                 id,
234                                                 &sum_relative_preferences,
235                                                 &sum_ctx);
236   LOG (GNUNET_ERROR_TYPE_DEBUG,
237        "Total relative preference for peer `%s' for `%s' is %.3f\n",
238        GNUNET_i2s (id),
239        GNUNET_ATS_print_preference_type (kind),
240        sum_ctx.f_rel_total);
241   rp = GNUNET_CONTAINER_multipeermap_get (preference_peers,
242                                           id);
243   GNUNET_assert (NULL != rp);
244   if (rp->f_rel[kind] != sum_ctx.f_rel_total)
245   {
246     rp->f_rel[kind] = sum_ctx.f_rel_total;
247     GAS_plugin_notify_preference_changed (id,
248                                           kind,
249                                           rp->f_rel[kind]);
250   }
251 }
252
253
254 /**
255  * Free a peer's `struct PeerRelative`.
256  *
257  * @param cls unused
258  * @param key the key
259  * @param value the `struct PeerRelative` to free.
260  * @return #GNUNET_OK to continue
261  */
262 static int
263 free_peer (void *cls,
264            const struct GNUNET_PeerIdentity *key,
265            void *value)
266 {
267   struct PeerRelative *rp = value;
268
269   GNUNET_assert (GNUNET_YES ==
270                  GNUNET_CONTAINER_multipeermap_remove (preference_peers,
271                                                        key,
272                                                        value));
273   GNUNET_free (rp);
274   return GNUNET_OK;
275 }
276
277
278 /**
279  * Free `struct PreferencePeer` entry in map.
280  *
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)
285  */
286 static int
287 free_preference (void *cls,
288                  const struct GNUNET_PeerIdentity *key,
289                  void *value)
290 {
291   struct PreferenceClient *pc = cls;
292   struct PreferencePeer *p = value;
293   struct PeerRelative *pr;
294
295   GNUNET_assert (GNUNET_OK ==
296                  GNUNET_CONTAINER_multipeermap_remove (pc->peer2pref,
297                                                        key,
298                                                        p));
299   GNUNET_free (p);
300   pr = GNUNET_CONTAINER_multipeermap_get (preference_peers,
301                                           key);
302   GNUNET_assert (NULL != pr);
303   GNUNET_assert (pr->num_clients > 0);
304   pr->num_clients--;
305   if (0 == pr->num_clients)
306   {
307     free_peer (NULL,
308                key,
309                pr);
310   }
311   return GNUNET_OK;
312 }
313
314
315 /**
316  * Closure for #age_values().
317  */
318 struct AgeContext
319 {
320   /**
321    * Counter of values remaining to update, incremented for each value
322    * changed (to a new non-zero value).
323    */
324   unsigned int values_to_update;
325
326   /**
327    * Client we are currently aging values for.
328    */
329   struct PreferenceClient *cur_client;
330
331 };
332
333
334 /**
335  * Age preference values of the given peer.
336  *
337  * @param cls a `
338  * @param peer peer being aged
339  * @param value the `struct PreferencePeer` for the peer
340  * @return #GNUNET_OK (continue to iterate)
341  */
342 static int
343 age_values (void *cls,
344             const struct GNUNET_PeerIdentity *peer,
345             void *value)
346 {
347   struct AgeContext *ac = cls;
348   struct PreferencePeer *p = value;
349   unsigned int i;
350   int dead;
351
352   dead = GNUNET_YES;
353   for (i = 0; i < GNUNET_ATS_PREFERENCE_END; i++)
354   {
355     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
356                 "Aging preference for peer `%s'\n",
357                 GNUNET_i2s (peer));
358     if (p->f_abs[i] > DEFAULT_ABS_PREFERENCE)
359       p->f_abs[i] *= PREF_AGING_FACTOR;
360     if (p->f_abs[i] <= DEFAULT_ABS_PREFERENCE + PREF_EPSILON)
361     {
362       p->f_abs[i] = DEFAULT_ABS_PREFERENCE;
363       p->f_rel[i] = DEFAULT_REL_PREFERENCE;
364       update_relative_values_for_peer (peer,
365                                        i);
366     }
367     else
368     {
369       ac->values_to_update++;
370       dead = GNUNET_NO;
371     }
372   }
373   if (GNUNET_YES == dead)
374   {
375     /* all preferences are zero, remove this entry */
376     free_preference (ac->cur_client,
377                      peer,
378                      p);
379   }
380   return GNUNET_OK;
381 }
382
383
384 /**
385  * Reduce absolute preferences since they got old.
386  *
387  * @param cls unused
388  */
389 static void
390 preference_aging (void *cls)
391 {
392   struct AgeContext ac;
393
394   aging_task = NULL;
395   GAS_plugin_solver_lock ();
396   ac.values_to_update = 0;
397   for (ac.cur_client = pc_head; NULL != ac.cur_client; ac.cur_client = ac.cur_client->next)
398     GNUNET_CONTAINER_multipeermap_iterate (ac.cur_client->peer2pref,
399                                            &age_values,
400                                            &ac);
401   GAS_plugin_solver_unlock ();
402   if (ac.values_to_update > 0)
403   {
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,
409                                                  &preference_aging,
410                                                  NULL);
411   }
412   else
413   {
414     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
415                 "No values to age left, not rescheduling aging task\n");
416   }
417 }
418
419
420 /**
421  * Closure for #update_rel_sum() and #update_abs_sum().
422  */
423 struct UpdateContext
424 {
425   /**
426    * Preference client with the sum of all absolute scores.
427    */
428   struct PreferenceClient *pc;
429
430   /**
431    * Which kind are we updating?
432    */
433   enum GNUNET_ATS_PreferenceKind kind;
434
435 };
436
437
438 /**
439  * Compute updated absolute score for the client based on the
440  * current absolute scores for each peer.
441  *
442  * @param cls a `struct UpdateContext`
443  * @param peer peer being updated
444  * @param value the `struct PreferencePeer` for the peer
445  * @return #GNUNET_OK (continue to iterate)
446  */
447 static int
448 update_abs_sum (void *cls,
449                 const struct GNUNET_PeerIdentity *peer,
450                 void *value)
451 {
452   struct UpdateContext *uc = cls;
453   struct PreferencePeer *p_cur = value;
454
455   uc->pc->f_abs_sum[uc->kind] += p_cur->f_abs[uc->kind];
456   return GNUNET_OK;
457 }
458
459
460 /**
461  * Compute updated relative score for each peer based on the
462  * current absolute score given by this client.
463  *
464  * @param cls a `struct UpdateContext`
465  * @param peer peer being updated
466  * @param value the `struct PreferencePeer` for the peer (updated)
467  * @return #GNUNET_OK (continue to iterate)
468  */
469 static int
470 update_rel_sum (void *cls,
471                 const struct GNUNET_PeerIdentity *peer,
472                 void *value)
473 {
474   struct UpdateContext *uc = cls;
475   struct PreferencePeer *p_cur = value;
476
477   p_cur->f_rel[uc->kind] = p_cur->f_abs[uc->kind] / uc->pc->f_abs_sum[uc->kind];
478   LOG (GNUNET_ERROR_TYPE_DEBUG,
479        "Client has relative preference for %s for peer `%s' of %.3f\n",
480        GNUNET_ATS_print_preference_type (uc->kind),
481        GNUNET_i2s (peer),
482        p_cur->f_rel[uc->kind]);
483   return GNUNET_OK;
484 }
485
486
487 /**
488  * Recalculate preference for a specific ATS property
489  *
490  * @param c the preference client
491  * @param kind the preference kind
492  * @return the result
493  */
494 static void
495 recalculate_relative_preferences (struct PreferenceClient *c,
496                                   enum GNUNET_ATS_PreferenceKind kind)
497 {
498   struct UpdateContext uc;
499
500   /* For this client: sum of absolute preference values for this preference */
501   uc.kind = kind;
502   uc.pc = c;
503   c->f_abs_sum[kind] = 0.0;
504
505   /* For all peers: calculate sum of absolute preferences */
506   GNUNET_CONTAINER_multipeermap_iterate (c->peer2pref,
507                                          &update_abs_sum,
508                                          &uc);
509   LOG (GNUNET_ERROR_TYPE_DEBUG,
510        "Client has sum of total preferences for %s of %.3f\n",
511        GNUNET_ATS_print_preference_type (kind),
512        c->f_abs_sum[kind]);
513
514   /* For all peers: calculate relative preference */
515   GNUNET_CONTAINER_multipeermap_iterate (c->peer2pref,
516                                          &update_rel_sum,
517                                          &uc);
518 }
519
520
521 /**
522  * The relative preferences of one of the clients have
523  * changed, update the global preferences for the given
524  * peer and notify the plugin.
525  *
526  * @param cls the kind of preference to calculate the
527  *        new global relative preference values for
528  * @param key the peer to update relative preference values for
529  * @param value a `struct PeerRelative`, unused
530  */
531 static int
532 update_iterator (void *cls,
533                  const struct GNUNET_PeerIdentity *key,
534                  void *value)
535 {
536   enum GNUNET_ATS_PreferenceKind *kind = cls;
537
538   update_relative_values_for_peer (key,
539                                    *kind);
540   return GNUNET_OK;
541 }
542
543
544 /**
545  * Update the absolute preference and calculate the
546  * new relative preference value.
547  *
548  * @param client the client with this preference
549  * @param peer the peer to change the preference for
550  * @param kind the kind to change the preference
551  * @param score_abs the normalized score
552  */
553 static void
554 update_preference (struct GNUNET_SERVICE_Client *client,
555                    const struct GNUNET_PeerIdentity *peer,
556                    enum GNUNET_ATS_PreferenceKind kind,
557                    float score_abs)
558 {
559   struct PreferenceClient *c_cur;
560   struct PreferencePeer *p_cur;
561   struct PeerRelative *r_cur;
562   unsigned int i;
563
564   if (kind >= GNUNET_ATS_PREFERENCE_END)
565   {
566     GNUNET_break(0);
567     return;
568   }
569   LOG (GNUNET_ERROR_TYPE_DEBUG,
570        "Client changes preference for peer `%s' for `%s' to %.2f\n",
571        GNUNET_i2s (peer),
572        GNUNET_ATS_print_preference_type (kind),
573        score_abs);
574
575   /* Find preference client */
576   for (c_cur = pc_head; NULL != c_cur; c_cur = c_cur->next)
577     if (client == c_cur->client)
578       break;
579   /* Not found: create new preference client */
580   if (NULL == c_cur)
581   {
582     c_cur = GNUNET_new (struct PreferenceClient);
583     c_cur->client = client;
584     c_cur->peer2pref = GNUNET_CONTAINER_multipeermap_create (16,
585                                                              GNUNET_NO);
586     for (i = 0; i < GNUNET_ATS_PREFERENCE_END; i++)
587       c_cur->f_abs_sum[i] = DEFAULT_ABS_PREFERENCE;
588     GNUNET_CONTAINER_DLL_insert (pc_head,
589                                  pc_tail,
590                                  c_cur);
591   }
592
593   /* check global peer entry exists */
594   if (NULL ==
595       (r_cur = GNUNET_CONTAINER_multipeermap_get (preference_peers,
596                                                   peer)))
597   {
598     /* Create struct for peer */
599     r_cur = GNUNET_new (struct PeerRelative);
600     for (i = 0; i < GNUNET_ATS_PREFERENCE_END; i++)
601       r_cur->f_rel[i] = DEFAULT_REL_PREFERENCE;
602     GNUNET_assert (GNUNET_OK ==
603                    GNUNET_CONTAINER_multipeermap_put (preference_peers,
604                                                       peer,
605                                                       r_cur,
606                                                       GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
607   }
608
609   /* Find entry for peer */
610   p_cur = GNUNET_CONTAINER_multipeermap_get (c_cur->peer2pref,
611                                              peer);
612   if (NULL == p_cur)
613   {
614     /* Not found: create new peer entry */
615     p_cur = GNUNET_new (struct PreferencePeer);
616     for (i = 0; i < GNUNET_ATS_PREFERENCE_END; i++)
617     {
618       /* Default value per peer absolute preference for a preference*/
619       p_cur->f_abs[i] = DEFAULT_ABS_PREFERENCE;
620       /* Default value per peer relative preference for a quality */
621       p_cur->f_rel[i] = DEFAULT_REL_PREFERENCE;
622     }
623     GNUNET_assert (GNUNET_YES ==
624                    GNUNET_CONTAINER_multipeermap_put (c_cur->peer2pref,
625                                                       peer,
626                                                       p_cur,
627                                                       GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
628     r_cur->num_clients++;
629   }
630
631   p_cur->f_abs[kind] += score_abs;
632   recalculate_relative_preferences (c_cur, kind);
633   GNUNET_CONTAINER_multipeermap_iterate (preference_peers,
634                                          &update_iterator,
635                                          &kind);
636
637   if (NULL == aging_task)
638     aging_task = GNUNET_SCHEDULER_add_delayed (PREF_AGING_INTERVAL,
639                                                &preference_aging,
640                                                NULL);
641 }
642
643
644 /**
645  * Handle 'preference change' messages from clients.
646  *
647  * @param client the client that sent the request
648  * @param msg the request message
649  */
650 void
651 GAS_handle_preference_change (struct GNUNET_SERVICE_Client *client,
652                               const struct ChangePreferenceMessage *msg)
653 {
654   const struct PreferenceInformation *pi;
655   uint32_t nump;
656
657   nump = ntohl (msg->num_preferences);
658   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
659               "Received PREFERENCE_CHANGE message for peer `%s'\n",
660               GNUNET_i2s (&msg->peer));
661   GNUNET_STATISTICS_update (GSA_stats,
662                             "# preference change requests processed",
663                             1,
664                             GNUNET_NO);
665   pi = (const struct PreferenceInformation *) &msg[1];
666   GAS_plugin_solver_lock ();
667   for (uint32_t i = 0; i < nump; i++)
668     update_preference (client,
669                        &msg->peer,
670                        (enum GNUNET_ATS_PreferenceKind) ntohl (pi[i].preference_kind),
671                        pi[i].preference_value);
672   GAS_plugin_solver_unlock ();
673 }
674
675
676 /**
677  * Initialize preferences subsystem.
678  */
679 void
680 GAS_preference_init ()
681 {
682   unsigned int i;
683
684   preference_peers = GNUNET_CONTAINER_multipeermap_create (16,
685                                                            GNUNET_NO);
686   for (i = 0; i < GNUNET_ATS_PREFERENCE_END; i++)
687     defvalues.f_rel[i] = DEFAULT_REL_PREFERENCE;
688 }
689
690
691 /**
692  * Shutdown preferences subsystem.
693  */
694 void
695 GAS_preference_done ()
696 {
697   struct PreferenceClient *pc;
698   struct PreferenceClient *next_pc;
699
700   if (NULL != aging_task)
701   {
702     GNUNET_SCHEDULER_cancel (aging_task);
703     aging_task = NULL;
704   }
705   next_pc = pc_head;
706   while (NULL != (pc = next_pc))
707   {
708     next_pc = pc->next;
709     GNUNET_CONTAINER_DLL_remove (pc_head,
710                                  pc_tail,
711                                  pc);
712     GNUNET_CONTAINER_multipeermap_iterate (pc->peer2pref,
713                                            &free_preference,
714                                            pc);
715     GNUNET_CONTAINER_multipeermap_destroy (pc->peer2pref);
716     GNUNET_free (pc);
717   }
718   GNUNET_CONTAINER_multipeermap_iterate (preference_peers,
719                                          &free_peer,
720                                          NULL);
721   GNUNET_CONTAINER_multipeermap_destroy (preference_peers);
722
723 }
724
725
726 /**
727  * Get the normalized preference values for a specific peer or
728  * the default values if
729  *
730  * @param cls ignored
731  * @param id the peer
732  * @return pointer to the values, can be indexed with GNUNET_ATS_PreferenceKind,
733  * default preferences if peer does not exist
734  */
735 const double *
736 GAS_preference_get_by_peer (void *cls,
737                             const struct GNUNET_PeerIdentity *id)
738 {
739   struct PeerRelative *rp;
740
741   if (NULL ==
742       (rp = GNUNET_CONTAINER_multipeermap_get (preference_peers,
743                                                id)))
744   {
745     return defvalues.f_rel;
746   }
747   return rp->f_rel;
748 }
749
750
751 /**
752  * A performance client disconnected
753  *
754  * @param client the client
755  */
756 void
757 GAS_preference_client_disconnect (struct GNUNET_SERVICE_Client *client)
758 {
759   struct PreferenceClient *c_cur;
760
761   for (c_cur = pc_head; NULL != c_cur; c_cur = c_cur->next)
762     if (client == c_cur->client)
763       break;
764   if (NULL == c_cur)
765     return;
766   GNUNET_CONTAINER_DLL_remove (pc_head,
767                                pc_tail,
768                                c_cur);
769   GNUNET_CONTAINER_multipeermap_iterate (c_cur->peer2pref,
770                                          &free_preference,
771                                          c_cur);
772   GNUNET_CONTAINER_multipeermap_destroy (c_cur->peer2pref);
773   GNUNET_free (c_cur);
774 }
775
776
777 /* end of gnunet-service-ats_preferences.c */