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