1 /*  Copyright 2017 Facebook.
2  *
3  *  Use and distribution licensed under the BSD license.  See
4  *  the LICENSE file for full text.
5  */
6 
7 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
8 #include "memcached.h"
9 #include "slab_automove.h"
10 #include <stdlib.h>
11 #include <string.h>
12 
13 #define MIN_PAGES_FOR_SOURCE 2
14 #define MIN_PAGES_FOR_RECLAIM 2.5
15 
16 struct window_data {
17     uint64_t age;
18     uint64_t dirty;
19     float evicted_ratio;
20     uint64_t evicted_seen; // if evictions were seen at all this window
21 };
22 
23 typedef struct {
24     struct window_data *window_data;
25     uint32_t window_size;
26     uint32_t window_cur;
27     double max_age_ratio;
28     item_stats_automove iam_before[MAX_NUMBER_OF_SLAB_CLASSES];
29     item_stats_automove iam_after[MAX_NUMBER_OF_SLAB_CLASSES];
30     slab_stats_automove sam_before[MAX_NUMBER_OF_SLAB_CLASSES];
31     slab_stats_automove sam_after[MAX_NUMBER_OF_SLAB_CLASSES];
32 } slab_automove;
33 
slab_automove_init(struct settings * settings)34 void *slab_automove_init(struct settings *settings) {
35     uint32_t window_size = settings->slab_automove_window;
36     double max_age_ratio = settings->slab_automove_ratio;
37     slab_automove *a = calloc(1, sizeof(slab_automove));
38     if (a == NULL)
39         return NULL;
40     a->window_data = calloc(window_size * MAX_NUMBER_OF_SLAB_CLASSES, sizeof(struct window_data));
41     a->window_size = window_size;
42     a->max_age_ratio = max_age_ratio;
43     if (a->window_data == NULL) {
44         free(a);
45         return NULL;
46     }
47 
48     // do a dry run to fill the before structs
49     fill_item_stats_automove(a->iam_before);
50     fill_slab_stats_automove(a->sam_before);
51 
52     return (void *)a;
53 }
54 
slab_automove_free(void * arg)55 void slab_automove_free(void *arg) {
56     slab_automove *a = (slab_automove *)arg;
57     free(a->window_data);
58     free(a);
59 }
60 
window_sum(struct window_data * wd,struct window_data * w,uint32_t size)61 static void window_sum(struct window_data *wd, struct window_data *w, uint32_t size) {
62     int x;
63     for (x = 0; x < size; x++) {
64         struct window_data *d = &wd[x];
65         w->age += d->age;
66         w->dirty += d->dirty;
67         w->evicted_ratio += d->evicted_ratio;
68         w->evicted_seen += d->evicted_seen;
69     }
70 }
71 
72 // TODO: if oldest is dirty, find next oldest.
73 // still need to base ratio off of absolute age
slab_automove_run(void * arg,int * src,int * dst)74 void slab_automove_run(void *arg, int *src, int *dst) {
75     slab_automove *a = (slab_automove *)arg;
76     int n;
77     struct window_data w_sum;
78     int oldest = -1;
79     uint64_t oldest_age = 0;
80     int youngest = -1;
81     uint64_t youngest_age = ~0;
82     bool youngest_evicting = false;
83     *src = -1;
84     *dst = -1;
85 
86     // fill after structs
87     fill_item_stats_automove(a->iam_after);
88     fill_slab_stats_automove(a->sam_after);
89     // Loop once to get total_evicted for this window.
90     uint64_t evicted_total = 0;
91     for (n = POWER_SMALLEST; n < MAX_NUMBER_OF_SLAB_CLASSES; n++) {
92         evicted_total += a->iam_after[n].evicted - a->iam_before[n].evicted;
93     }
94     a->window_cur++;
95 
96     // iterate slabs
97     for (n = POWER_SMALLEST; n < MAX_NUMBER_OF_SLAB_CLASSES; n++) {
98         int w_offset = n * a->window_size;
99         struct window_data *wd = &a->window_data[w_offset + (a->window_cur % a->window_size)];
100         memset(wd, 0, sizeof(struct window_data));
101 
102         // if page delta, or evicted delta, mark window dirty
103         // (or outofmemory)
104         uint64_t evicted_delta = a->iam_after[n].evicted - a->iam_before[n].evicted;
105         if (evicted_delta > 0) {
106             // FIXME: the python script is using floats. we have ints.
107             wd->evicted_ratio = (float) evicted_delta / evicted_total;
108             wd->evicted_seen = 1;
109             wd->dirty = 1;
110         }
111 
112         if (a->iam_after[n].outofmemory - a->iam_before[n].outofmemory > 0) {
113             wd->dirty = 1;
114         }
115         if (a->sam_after[n].total_pages - a->sam_before[n].total_pages > 0) {
116             wd->dirty = 1;
117         }
118 
119         // set age into window
120         wd->age = a->iam_after[n].age;
121 
122         // summarize the window-up-to-now.
123         memset(&w_sum, 0, sizeof(struct window_data));
124         window_sum(&a->window_data[w_offset], &w_sum, a->window_size);
125 
126         // grab age as average of window total
127         uint64_t age = w_sum.age / a->window_size;
128 
129         // if > N free chunks and not dirty, make decision.
130         if (a->sam_after[n].free_chunks > a->sam_after[n].chunks_per_page * MIN_PAGES_FOR_RECLAIM) {
131             if (w_sum.dirty == 0) {
132                 *src = n;
133                 *dst = 0;
134                 youngest = oldest = -1;
135                 break;
136             }
137         }
138 
139         // if oldest and have enough pages, is oldest
140         if (age > oldest_age && a->sam_after[n].total_pages > MIN_PAGES_FOR_SOURCE) {
141             oldest = n;
142             oldest_age = age;
143         }
144 
145         // grab evicted count from window
146         // if > half the window and youngest, mark as youngest
147         // or, if more than 25% of total evictions in the window.
148         if (age < youngest_age && (w_sum.evicted_seen > a->window_size / 2
149                     || w_sum.evicted_ratio / a->window_size > 0.25)) {
150             youngest = n;
151             youngest_age = age;
152             youngest_evicting = wd->evicted_seen ? true : false;
153         }
154     }
155 
156     memcpy(a->iam_before, a->iam_after,
157             sizeof(item_stats_automove) * MAX_NUMBER_OF_SLAB_CLASSES);
158     memcpy(a->sam_before, a->sam_after,
159             sizeof(slab_stats_automove) * MAX_NUMBER_OF_SLAB_CLASSES);
160     // if we have a youngest and oldest, and oldest is outside the ratio,
161     // also, only make decisions if window has filled once.
162     if (youngest != -1 && oldest != -1 && a->window_cur > a->window_size) {
163         if (youngest_age < ((double)oldest_age * a->max_age_ratio) && youngest_evicting) {
164             *src = oldest;
165             *dst = youngest;
166         }
167     }
168     return;
169 }
170