1 /*
2  * %CopyrightBegin%
3  *
4  * Copyright Ericsson AB 2019. All Rights Reserved.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * %CopyrightEnd%
19  */
20 
21 /**
22  * @file erl_flxctr.h
23  *
24  * @brief This file contains the API of a flexible counter. The
25  * counter can be configured during its initialization to be
26  * centralized or decentralized. The centralized configuration makes
27  * it possible to read the counter value extremely efficiently, but
28  * updates of the counter value can easily cause contention. The
29  * decentralized configuration has the reverse trade-off (i.e.,
30  * updates are efficient and scalable but reading the counter value is
31  * slow and may cause contention).
32  *
33  * @author Kjell Winblad
34  */
35 
36 #ifndef ERL_FLXCTR_H__
37 #define ERL_FLXCTR_H__
38 
39 #ifdef HAVE_CONFIG_H
40 #  include "config.h"
41 #endif
42 #include "sys.h"
43 #include "erl_vm.h"
44 #include "global.h"
45 #include "error.h"
46 #include "bif.h"
47 #include "big.h"
48 #include "erl_binary.h"
49 #include "bif.h"
50 #include <stddef.h>
51 
52 /* Public Interface */
53 
54 #define ERTS_MAX_FLXCTR_GROUPS 256
55 #define ERTS_FLXCTR_ATOMICS_PER_CACHE_LINE (ERTS_CACHE_LINE_SIZE / sizeof(erts_atomic_t))
56 
57 typedef struct {
58     int nr_of_counters;
59     int is_decentralized;
60     union {
61         erts_atomic_t counters_ptr;
62         erts_atomic_t counters[1];
63     } u;
64 } ErtsFlxCtr;
65 
66 #define ERTS_FLXCTR_NR_OF_EXTRA_BYTES(NR_OF_COUNTERS)   \
67     ((NR_OF_COUNTERS-1) * sizeof(erts_atomic_t))
68 
69 /* Called by early_init */
70 void erts_flxctr_setup(int decentralized_counter_groups);
71 
72 /**
73  * @brief Initializes an ErtsFlxCtr. The macro
74  * ERTS_FLXCTR_NR_OF_EXTRA_BYTES should be used to determine how much
75  * extra space that needs to be allocated directly after the
76  * ErtsFlxCtr when is_decentralized is set to zero.  Each ErtsFlxCtr
77  * instance may contain up to ERTS_FLXCTR_ATOMICS_PER_CACHE_LINE
78  * counters. These counters are numbered from zero to
79  * (ERTS_FLXCTR_ATOMICS_PER_CACHE_LINE-1). Most of the functions in
80  * this module take a parameter named counter_nr that controls which
81  * of the ERTS_FLXCTR_ATOMICS_PER_CACHE_LINE counters in the given
82  * ErtsFlxCtr that should be operated on.
83  *
84  * @param c The counter to initialize
85  * @param is_decentralized Non-zero value to make c decentralized
86  * @param nr_of_counters The number of counters included in c
87  *                       (max ERTS_FLXCTR_ATOMICS_PER_CACHE_LINE)
88  * @param alloc_type
89  */
90 void erts_flxctr_init(ErtsFlxCtr* c,
91                       int is_decentralized,
92                       Uint nr_of_counters,
93                       ErtsAlcType_t alloc_type);
94 
95 /**
96  * @brief Destroys an initialized counter.
97  *
98  * @param c The counter that should be destroyed
99  * @param alloc_type The allocation type (needs to be the same as the
100  *                   one passed to erts_flxctr_init when c was
101  *                   initialized)
102  */
103 void erts_flxctr_destroy(ErtsFlxCtr* c, ErtsAlcType_t alloc_type);
104 
105 /**
106  * @brief Adds to_add to the counter with counter_nr in c
107  *
108  * @param c the ErtsFlxCtr to operate on
109  * @param counter_nr The number of the counter in c to modify
110  * @param to_add The amount that should be added to the specified counter
111  */
112 ERTS_GLB_INLINE
113 void erts_flxctr_add(ErtsFlxCtr* c,
114                      Uint counter_nr,
115                      int to_add);
116 
117 /**
118  * @brief Increases the specified counter by 1
119  *
120  * @param c The ErtsFlxCtr instance to operate on
121  * @param counter_nr The number of the counter within c to operate on
122  */
123 ERTS_GLB_INLINE
124 void erts_flxctr_inc(ErtsFlxCtr* c,
125                      Uint counter_nr);
126 
127 /**
128  * @brief Decreases the specified counter by 1
129  */
130 ERTS_GLB_INLINE
131 void erts_flxctr_dec(ErtsFlxCtr* c,
132                      Uint counter_nr);
133 
134 /**
135  * @brief This function tries to return the current value of the
136  * specified counter but may return an incorrect result if the counter
137  * is decentralized and other threads are accessing the counter
138  * concurrently.
139  *
140  * @param c The ErtsFlxCtr instance to operate on
141  * @param counter_nr The number of the counter within c to operate on
142  *
143  * @return A snapshot of the specifed counter if c is centralized or a
144  *         possibly incorrect estimate of the counter value if c is
145  *         decentralized
146  */
147 Sint erts_flxctr_read_approx(ErtsFlxCtr* c,
148                              Uint counter_nr);
149 
150 /**
151  * @brief This function can only be used together with an ErtsFlxCtr
152  * that is configured to be centralized. The function increments the
153  * specified counter by 1 and returns the value of the counter after
154  * the increment.
155  */
156 ERTS_GLB_INLINE
157 Sint erts_flxctr_inc_read_centralized(ErtsFlxCtr* c,
158                                       Uint counter_nr);
159 
160 /**
161  * @brief This function can only be used together with a ErtsFlxCtr
162  * that is configured to be centralized. The function decrements the
163  * specified counter by 1 and returns the value of the counter after
164  * the operation.
165  */
166 ERTS_GLB_INLINE
167 Sint erts_flxctr_dec_read_centralized(ErtsFlxCtr* c,
168                                       Uint counter_nr);
169 
170 /**
171  * @brief This function can only be used together with an ErtsFlxCtr
172  * that is configured to be centralized. The function returns the
173  * current value of the specified counter.
174  */
175 ERTS_GLB_INLINE
176 Sint erts_flxctr_read_centralized(ErtsFlxCtr* c,
177                                   Uint counter_nr);
178 
179 
180 typedef enum {
181     ERTS_FLXCTR_TRY_AGAIN_AFTER_TRAP,
182     ERTS_FLXCTR_DONE,
183     ERTS_FLXCTR_GET_RESULT_AFTER_TRAP
184 } ErtsFlxctrSnapshotResultType;
185 
186 typedef struct {
187     ErtsFlxctrSnapshotResultType type;
188     Eterm trap_resume_state;
189     Sint result[ERTS_FLXCTR_ATOMICS_PER_CACHE_LINE];
190 } ErtsFlxCtrSnapshotResult;
191 
192 /**
193  * @brief This function initiates an atomic snapshot of an ErtsFlxCtr
194  * to read out the values of one or more of the counters that are
195  * stored in the given ErtsFlxCtr. The caller needs to perform
196  * different actions after the return of this function depending on
197  * the value of the type field in the returned struct:
198  *
199  * - The caller needs to trap and try again after the trap if the
200  *   return value has the type ERTS_FLXCTR_TRY_AGAIN_AFTER_TRAP.
201  *
202  * - The caller can get the result directly from the result field of
203  *   the returned struct if the return value has the type
204  *   ERTS_FLXCTR_DONE. The value at index i in the result field
205  *   correspond to counter number i.
206  *
207  * - Finally, if the return value has the type
208  *   ERTS_FLXCTR_GET_RESULT_AFTER_TRAP, then the caller needs to save
209  *   the value of the field trap_resume_state from the returned struct
210  *   and trap. After the trap, the values of the counters can be
211  *   obtained by using the function
212  *   erts_flxctr_get_snapshot_result_after_trap. Note that the
213  *   function erts_flxctr_is_snapshot_result can be used to check if a
214  *   value is obtained from the trap_resume_state field in the
215  *   returned struct (this can be useful when the calling function
216  *   wakes up again after the trap).
217  *
218  * The snapshot operation that is initiated by this function should be
219  * considered to be ongoing from the issuing of this function until a
220  * struct with the type field set to ERTS_FLXCTR_DONE has been
221  * returned from the function or until the caller of this function has
222  * woken up after trapping.
223  *
224  * @param c The ErtsFlxCtr that the snapshot shall be taken from
225  * @param alloc_type The allocation type (needs to be the same as the
226  *                   type passed to erts_flxctr_init when c was
227  *                   initialized)
228  * @param p The Erlang process that is doing the call
229  *
230  * @return See the description above
231  *
232  */
233 ErtsFlxCtrSnapshotResult
234 erts_flxctr_snapshot(ErtsFlxCtr* c,
235                      ErtsAlcType_t alloc_type,
236                      Process* p);
237 
238 /**
239  * @brief Checks if the parameter term is a snapshot result (i.e.,
240  * something obtained from the trap_resume_state field of an
241  * ErtsFlxCtrSnapshotResult struct that has been returned from
242  * erts_flxctr_snapshot).
243  *
244  * @param term The term to check
245  *
246  * @return A nonzero value iff the term is a snapshot result
247  */
248 int erts_flxctr_is_snapshot_result(Eterm term);
249 
250 /**
251  * @brief Returns the result of a snapshot for a counter given a
252  * snapshot result returned by a call to erts_flxctr_snapshot (i.e.,
253  * the value stored in the trap_resume_state field of a struct
254  * returned by erts_flxctr_snapshot). The caller needs to trap between
255  * the return of erts_flxctr_snapshot and the call to this function.
256  */
257 Sint erts_flxctr_get_snapshot_result_after_trap(Eterm trap_resume_state,
258                                                 Uint counter_nr);
259 
260 /**
261  * @brief Resets the specified counter to 0. This function is unsafe
262  * to call while a snapshot operation may be active (initiated with
263  * the erts_flxctr_snapshot function).
264  */
265 void erts_flxctr_reset(ErtsFlxCtr* c,
266                        Uint counter_nr);
267 
268 /**
269  * @brief Checks if a snapshot operation is active (snapshots are
270  * initiated with the erts_flxctr_snapshot function).
271  *
272  * @return nonzero value iff a snapshot was active at some point
273  * between the invocation and return of the function
274  */
275 int erts_flxctr_is_snapshot_ongoing(ErtsFlxCtr* c);
276 
277 /**
278  * @brief This function checks if a snapshot operation is ongoing
279  * (snapshots are initiated with the erts_flxctr_snapshot function)
280  * and suspend the given process until thread progress has happened if
281  * it detected an ongoing snapshot operation. The caller needs to trap
282  * if a non-zero value is returned.
283  *
284  * @param c The ErtsFlxCtr to check
285  * @param p The calling process
286  *
287  * @return nonzero value if the given process has got suspended
288  */
289 int erts_flxctr_suspend_until_thr_prg_if_snapshot_ongoing(ErtsFlxCtr* c, Process* p);
290 
291 /**
292  * @brief This function returns the number of bytes that are allocated
293  * for for the given FlxCtr.
294  *
295  * @return nr of bytes allocated for the FlxCtr
296  */
297 size_t erts_flxctr_nr_of_allocated_bytes(ErtsFlxCtr* c);
298 
299 /**
300  * @brief This debug function returns the amount of memory allocated
301  * for decentralized counter arrays when compiled with the DEBUG
302  * macro. The function returns -1 if the DEBUG macro is undefined.
303  *
304  * @return number of bytes allocated for decentralized counter arrays
305  * if in debug mode and otherwise -1
306  */
307 Sint erts_flxctr_debug_memory_usage(void);
308 
309 /* End: Public Interface */
310 
311 /* Internal Declarations */
312 
313 #define ERTS_FLXCTR_GET_CTR_ARRAY_PTR(C)                                \
314     ((ErtsFlxCtrDecentralizedCtrArray*) erts_atomic_read_acqb(&(C)->u.counters_ptr))
315 #define ERTS_FLXCTR_GET_CTR_PTR(C, SCHEDULER_ID, COUNTER_ID)            \
316     &(ERTS_FLXCTR_GET_CTR_ARRAY_PTR(C))->array[SCHEDULER_ID].counters[COUNTER_ID]
317 
318 
319 typedef union {
320     erts_atomic_t counters[ERTS_FLXCTR_ATOMICS_PER_CACHE_LINE];
321     char pad[ERTS_CACHE_LINE_SIZE];
322 } ErtsFlxCtrDecentralizedCtrArrayElem;
323 
324 typedef struct ErtsFlxCtrDecentralizedCtrArray {
325     void* block_start;
326     erts_atomic_t snapshot_status;
327     ErtsFlxCtrDecentralizedCtrArrayElem array[];
328 } ErtsFlxCtrDecentralizedCtrArray;
329 
330 void erts_flxctr_set_slot(int group);
331 
332 ERTS_GLB_INLINE
333 int erts_flxctr_get_slot_index(void);
334 
335 /* End: Internal Declarations */
336 
337 
338 /* Implementation of inlined functions */
339 
340 #if ERTS_GLB_INLINE_INCL_FUNC_DEF
341 
342 ERTS_GLB_INLINE
erts_flxctr_get_slot_index(void)343 int erts_flxctr_get_slot_index(void)
344 {
345     ErtsSchedulerData *esdp = erts_get_scheduler_data();
346     ASSERT(esdp && !ERTS_SCHEDULER_IS_DIRTY(esdp));
347     ASSERT(esdp->flxctr_slot_no > 0);
348     return esdp->flxctr_slot_no;
349 }
350 
351 ERTS_GLB_INLINE
erts_flxctr_add(ErtsFlxCtr * c,Uint counter_nr,int to_add)352 void erts_flxctr_add(ErtsFlxCtr* c,
353                      Uint counter_nr,
354                      int to_add)
355 {
356     ASSERT(counter_nr < c->nr_of_counters);
357     if (c->is_decentralized) {
358         erts_atomic_add_nob(ERTS_FLXCTR_GET_CTR_PTR(c,
359                                                     erts_flxctr_get_slot_index(),
360                                                     counter_nr),
361                             to_add);
362     } else {
363         erts_atomic_add_nob(&c->u.counters[counter_nr], to_add);
364     }
365 }
366 
367 ERTS_GLB_INLINE
erts_flxctr_inc(ErtsFlxCtr * c,Uint counter_nr)368 void erts_flxctr_inc(ErtsFlxCtr* c,
369                      Uint counter_nr)
370 {
371     ASSERT(counter_nr < c->nr_of_counters);
372     if (c->is_decentralized) {
373         erts_atomic_inc_nob(ERTS_FLXCTR_GET_CTR_PTR(c,
374                                                     erts_flxctr_get_slot_index(),
375                                                     counter_nr));
376     } else {
377         erts_atomic_inc_read_nob(&c->u.counters[counter_nr]);
378     }
379 }
380 
381 ERTS_GLB_INLINE
erts_flxctr_dec(ErtsFlxCtr * c,Uint counter_nr)382 void erts_flxctr_dec(ErtsFlxCtr* c,
383                      Uint counter_nr)
384 {
385     ASSERT(counter_nr < c->nr_of_counters);
386     if (c->is_decentralized) {
387         erts_atomic_dec_nob(ERTS_FLXCTR_GET_CTR_PTR(c,
388                                                     erts_flxctr_get_slot_index(),
389                                                     counter_nr));
390     } else {
391         erts_atomic_dec_nob(&c->u.counters[counter_nr]);
392     }
393 }
394 
395 ERTS_GLB_INLINE
erts_flxctr_inc_read_centralized(ErtsFlxCtr * c,Uint counter_nr)396 Sint erts_flxctr_inc_read_centralized(ErtsFlxCtr* c,
397                                       Uint counter_nr)
398 {
399     ASSERT(counter_nr < c->nr_of_counters);
400     ASSERT(!c->is_decentralized);
401     return erts_atomic_inc_read_nob(&c->u.counters[counter_nr]);
402 }
403 
404 ERTS_GLB_INLINE
erts_flxctr_dec_read_centralized(ErtsFlxCtr * c,Uint counter_nr)405 Sint erts_flxctr_dec_read_centralized(ErtsFlxCtr* c,
406                                       Uint counter_nr)
407 {
408     ASSERT(counter_nr < c->nr_of_counters);
409     ASSERT(!c->is_decentralized);
410     return erts_atomic_dec_read_nob(&c->u.counters[counter_nr]);
411 }
412 
413 ERTS_GLB_INLINE
erts_flxctr_read_centralized(ErtsFlxCtr * c,Uint counter_nr)414 Sint erts_flxctr_read_centralized(ErtsFlxCtr* c,
415                                   Uint counter_nr)
416 {
417     ASSERT(counter_nr < c->nr_of_counters);
418     ASSERT(!c->is_decentralized);
419     return erts_atomic_read_nob(&((erts_atomic_t*)(c->u.counters))[counter_nr]);
420 }
421 
422 #endif /* #if ERTS_GLB_INLINE_INCL_FUNC_DEF */
423 
424 #endif /* ERL_FLXCTR_H__ */
425