xref: /openbmc/qemu/block/throttle-groups.c (revision e2dbca03)
12ff1f2e3SAlberto Garcia /*
22ff1f2e3SAlberto Garcia  * QEMU block throttling group infrastructure
32ff1f2e3SAlberto Garcia  *
42ff1f2e3SAlberto Garcia  * Copyright (C) Nodalink, EURL. 2014
52ff1f2e3SAlberto Garcia  * Copyright (C) Igalia, S.L. 2015
62ff1f2e3SAlberto Garcia  *
72ff1f2e3SAlberto Garcia  * Authors:
82ff1f2e3SAlberto Garcia  *   Benoît Canet <benoit.canet@nodalink.com>
92ff1f2e3SAlberto Garcia  *   Alberto Garcia <berto@igalia.com>
102ff1f2e3SAlberto Garcia  *
112ff1f2e3SAlberto Garcia  * This program is free software; you can redistribute it and/or
122ff1f2e3SAlberto Garcia  * modify it under the terms of the GNU General Public License as
132ff1f2e3SAlberto Garcia  * published by the Free Software Foundation; either version 2 or
142ff1f2e3SAlberto Garcia  * (at your option) version 3 of the License.
152ff1f2e3SAlberto Garcia  *
162ff1f2e3SAlberto Garcia  * This program is distributed in the hope that it will be useful,
172ff1f2e3SAlberto Garcia  * but WITHOUT ANY WARRANTY; without even the implied warranty of
182ff1f2e3SAlberto Garcia  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
192ff1f2e3SAlberto Garcia  * GNU General Public License for more details.
202ff1f2e3SAlberto Garcia  *
212ff1f2e3SAlberto Garcia  * You should have received a copy of the GNU General Public License
222ff1f2e3SAlberto Garcia  * along with this program; if not, see <http://www.gnu.org/licenses/>.
232ff1f2e3SAlberto Garcia  */
242ff1f2e3SAlberto Garcia 
2580c71a24SPeter Maydell #include "qemu/osdep.h"
2631dce3ccSKevin Wolf #include "sysemu/block-backend.h"
272ff1f2e3SAlberto Garcia #include "block/throttle-groups.h"
28432d889eSManos Pitsidianakis #include "qemu/throttle-options.h"
29db725815SMarkus Armbruster #include "qemu/main-loop.h"
3076f4afb4SAlberto Garcia #include "qemu/queue.h"
3176f4afb4SAlberto Garcia #include "qemu/thread.h"
3276f4afb4SAlberto Garcia #include "sysemu/qtest.h"
33432d889eSManos Pitsidianakis #include "qapi/error.h"
349af23989SMarkus Armbruster #include "qapi/qapi-visit-block-core.h"
35432d889eSManos Pitsidianakis #include "qom/object.h"
36432d889eSManos Pitsidianakis #include "qom/object_interfaces.h"
37432d889eSManos Pitsidianakis 
38432d889eSManos Pitsidianakis static void throttle_group_obj_init(Object *obj);
39432d889eSManos Pitsidianakis static void throttle_group_obj_complete(UserCreatable *obj, Error **errp);
403b2337efSzhenwei pi static void timer_cb(ThrottleGroupMember *tgm, ThrottleDirection direction);
412ff1f2e3SAlberto Garcia 
422ff1f2e3SAlberto Garcia /* The ThrottleGroup structure (with its ThrottleState) is shared
43022cdc9fSManos Pitsidianakis  * among different ThrottleGroupMembers and it's independent from
442ff1f2e3SAlberto Garcia  * AioContext, so in order to use it from different threads it needs
452ff1f2e3SAlberto Garcia  * its own locking.
462ff1f2e3SAlberto Garcia  *
472ff1f2e3SAlberto Garcia  * This locking is however handled internally in this file, so it's
48d87d01e1SAlberto Garcia  * transparent to outside users.
492ff1f2e3SAlberto Garcia  *
502ff1f2e3SAlberto Garcia  * The whole ThrottleGroup structure is private and invisible to
512ff1f2e3SAlberto Garcia  * outside users, that only use it through its ThrottleState.
522ff1f2e3SAlberto Garcia  *
53022cdc9fSManos Pitsidianakis  * In addition to the ThrottleGroup structure, ThrottleGroupMember has
542ff1f2e3SAlberto Garcia  * fields that need to be accessed by other members of the group and
5527ccdd52SKevin Wolf  * therefore also need to be protected by this lock. Once a
56022cdc9fSManos Pitsidianakis  * ThrottleGroupMember is registered in a group those fields can be accessed
5727ccdd52SKevin Wolf  * by other threads any time.
582ff1f2e3SAlberto Garcia  *
592ff1f2e3SAlberto Garcia  * Again, all this is handled internally and is mostly transparent to
602ff1f2e3SAlberto Garcia  * the outside. The 'throttle_timers' field however has an additional
612ff1f2e3SAlberto Garcia  * constraint because it may be temporarily invalid (see for example
620d2fac8eSAlberto Garcia  * blk_set_aio_context()). Therefore in this file a thread will
63022cdc9fSManos Pitsidianakis  * access some other ThrottleGroupMember's timers only after verifying that
64022cdc9fSManos Pitsidianakis  * that ThrottleGroupMember has throttled requests in the queue.
652ff1f2e3SAlberto Garcia  */
667c9dcd6cSEduardo Habkost struct ThrottleGroup {
67432d889eSManos Pitsidianakis     Object parent_obj;
68432d889eSManos Pitsidianakis 
69432d889eSManos Pitsidianakis     /* refuse individual property change if initialization is complete */
70432d889eSManos Pitsidianakis     bool is_initialized;
712ff1f2e3SAlberto Garcia     char *name; /* This is constant during the lifetime of the group */
722ff1f2e3SAlberto Garcia 
732ff1f2e3SAlberto Garcia     QemuMutex lock; /* This lock protects the following four fields */
742ff1f2e3SAlberto Garcia     ThrottleState ts;
75022cdc9fSManos Pitsidianakis     QLIST_HEAD(, ThrottleGroupMember) head;
763b2337efSzhenwei pi     ThrottleGroupMember *tokens[THROTTLE_MAX];
773b2337efSzhenwei pi     bool any_timer_armed[THROTTLE_MAX];
78dbe824ccSManos Pitsidianakis     QEMUClockType clock_type;
792ff1f2e3SAlberto Garcia 
80432d889eSManos Pitsidianakis     /* This field is protected by the global QEMU mutex */
812ff1f2e3SAlberto Garcia     QTAILQ_ENTRY(ThrottleGroup) list;
827c9dcd6cSEduardo Habkost };
832ff1f2e3SAlberto Garcia 
84432d889eSManos Pitsidianakis /* This is protected by the global QEMU mutex */
852ff1f2e3SAlberto Garcia static QTAILQ_HEAD(, ThrottleGroup) throttle_groups =
862ff1f2e3SAlberto Garcia     QTAILQ_HEAD_INITIALIZER(throttle_groups);
872ff1f2e3SAlberto Garcia 
88432d889eSManos Pitsidianakis 
89432d889eSManos Pitsidianakis /* This function reads throttle_groups and must be called under the global
90432d889eSManos Pitsidianakis  * mutex.
91432d889eSManos Pitsidianakis  */
throttle_group_by_name(const char * name)92432d889eSManos Pitsidianakis static ThrottleGroup *throttle_group_by_name(const char *name)
93432d889eSManos Pitsidianakis {
94432d889eSManos Pitsidianakis     ThrottleGroup *iter;
95432d889eSManos Pitsidianakis 
96432d889eSManos Pitsidianakis     /* Look for an existing group with that name */
97432d889eSManos Pitsidianakis     QTAILQ_FOREACH(iter, &throttle_groups, list) {
98432d889eSManos Pitsidianakis         if (!g_strcmp0(name, iter->name)) {
99432d889eSManos Pitsidianakis             return iter;
100432d889eSManos Pitsidianakis         }
101432d889eSManos Pitsidianakis     }
102432d889eSManos Pitsidianakis 
103432d889eSManos Pitsidianakis     return NULL;
104432d889eSManos Pitsidianakis }
105432d889eSManos Pitsidianakis 
106d8e7d87eSManos Pitsidianakis /* This function reads throttle_groups and must be called under the global
107d8e7d87eSManos Pitsidianakis  * mutex.
108d8e7d87eSManos Pitsidianakis  */
throttle_group_exists(const char * name)109d8e7d87eSManos Pitsidianakis bool throttle_group_exists(const char *name)
110d8e7d87eSManos Pitsidianakis {
111d8e7d87eSManos Pitsidianakis     return throttle_group_by_name(name) != NULL;
112d8e7d87eSManos Pitsidianakis }
113d8e7d87eSManos Pitsidianakis 
1142ff1f2e3SAlberto Garcia /* Increments the reference count of a ThrottleGroup given its name.
1152ff1f2e3SAlberto Garcia  *
1162ff1f2e3SAlberto Garcia  * If no ThrottleGroup is found with the given name a new one is
1172ff1f2e3SAlberto Garcia  * created.
1182ff1f2e3SAlberto Garcia  *
119432d889eSManos Pitsidianakis  * This function edits throttle_groups and must be called under the global
120432d889eSManos Pitsidianakis  * mutex.
121432d889eSManos Pitsidianakis  *
1222ff1f2e3SAlberto Garcia  * @name: the name of the ThrottleGroup
123973f2ddfSMax Reitz  * @ret:  the ThrottleState member of the ThrottleGroup
1242ff1f2e3SAlberto Garcia  */
throttle_group_incref(const char * name)125973f2ddfSMax Reitz ThrottleState *throttle_group_incref(const char *name)
1262ff1f2e3SAlberto Garcia {
1272ff1f2e3SAlberto Garcia     ThrottleGroup *tg = NULL;
1282ff1f2e3SAlberto Garcia 
1292ff1f2e3SAlberto Garcia     /* Look for an existing group with that name */
130432d889eSManos Pitsidianakis     tg = throttle_group_by_name(name);
1312ff1f2e3SAlberto Garcia 
132432d889eSManos Pitsidianakis     if (tg) {
133432d889eSManos Pitsidianakis         object_ref(OBJECT(tg));
134432d889eSManos Pitsidianakis     } else {
1352ff1f2e3SAlberto Garcia         /* Create a new one if not found */
136432d889eSManos Pitsidianakis         /* new ThrottleGroup obj will have a refcnt = 1 */
137432d889eSManos Pitsidianakis         tg = THROTTLE_GROUP(object_new(TYPE_THROTTLE_GROUP));
1382ff1f2e3SAlberto Garcia         tg->name = g_strdup(name);
139432d889eSManos Pitsidianakis         throttle_group_obj_complete(USER_CREATABLE(tg), &error_abort);
140dbe824ccSManos Pitsidianakis     }
1412ff1f2e3SAlberto Garcia 
142973f2ddfSMax Reitz     return &tg->ts;
1432ff1f2e3SAlberto Garcia }
1442ff1f2e3SAlberto Garcia 
1452ff1f2e3SAlberto Garcia /* Decrease the reference count of a ThrottleGroup.
1462ff1f2e3SAlberto Garcia  *
1472ff1f2e3SAlberto Garcia  * When the reference count reaches zero the ThrottleGroup is
1482ff1f2e3SAlberto Garcia  * destroyed.
1492ff1f2e3SAlberto Garcia  *
150432d889eSManos Pitsidianakis  * This function edits throttle_groups and must be called under the global
151432d889eSManos Pitsidianakis  * mutex.
152432d889eSManos Pitsidianakis  *
153973f2ddfSMax Reitz  * @ts:  The ThrottleGroup to unref, given by its ThrottleState member
1542ff1f2e3SAlberto Garcia  */
throttle_group_unref(ThrottleState * ts)155973f2ddfSMax Reitz void throttle_group_unref(ThrottleState *ts)
1562ff1f2e3SAlberto Garcia {
157973f2ddfSMax Reitz     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
158432d889eSManos Pitsidianakis     object_unref(OBJECT(tg));
1592ff1f2e3SAlberto Garcia }
1602ff1f2e3SAlberto Garcia 
161022cdc9fSManos Pitsidianakis /* Get the name from a ThrottleGroupMember's group. The name (and the pointer)
16249d2165dSKevin Wolf  * is guaranteed to remain constant during the lifetime of the group.
1632ff1f2e3SAlberto Garcia  *
164022cdc9fSManos Pitsidianakis  * @tgm:  a ThrottleGroupMember
1652ff1f2e3SAlberto Garcia  * @ret:  the name of the group.
1662ff1f2e3SAlberto Garcia  */
throttle_group_get_name(ThrottleGroupMember * tgm)167022cdc9fSManos Pitsidianakis const char *throttle_group_get_name(ThrottleGroupMember *tgm)
1682ff1f2e3SAlberto Garcia {
169022cdc9fSManos Pitsidianakis     ThrottleGroup *tg = container_of(tgm->throttle_state, ThrottleGroup, ts);
1702ff1f2e3SAlberto Garcia     return tg->name;
1712ff1f2e3SAlberto Garcia }
1722ff1f2e3SAlberto Garcia 
173022cdc9fSManos Pitsidianakis /* Return the next ThrottleGroupMember in the round-robin sequence, simulating
174022cdc9fSManos Pitsidianakis  * a circular list.
1752ff1f2e3SAlberto Garcia  *
1762ff1f2e3SAlberto Garcia  * This assumes that tg->lock is held.
1772ff1f2e3SAlberto Garcia  *
178022cdc9fSManos Pitsidianakis  * @tgm: the current ThrottleGroupMember
179022cdc9fSManos Pitsidianakis  * @ret: the next ThrottleGroupMember in the sequence
1802ff1f2e3SAlberto Garcia  */
throttle_group_next_tgm(ThrottleGroupMember * tgm)181022cdc9fSManos Pitsidianakis static ThrottleGroupMember *throttle_group_next_tgm(ThrottleGroupMember *tgm)
1822ff1f2e3SAlberto Garcia {
183022cdc9fSManos Pitsidianakis     ThrottleState *ts = tgm->throttle_state;
1842ff1f2e3SAlberto Garcia     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
185022cdc9fSManos Pitsidianakis     ThrottleGroupMember *next = QLIST_NEXT(tgm, round_robin);
1862ff1f2e3SAlberto Garcia 
1872ff1f2e3SAlberto Garcia     if (!next) {
18831dce3ccSKevin Wolf         next = QLIST_FIRST(&tg->head);
1892ff1f2e3SAlberto Garcia     }
1902ff1f2e3SAlberto Garcia 
191022cdc9fSManos Pitsidianakis     return next;
1922ff1f2e3SAlberto Garcia }
1932ff1f2e3SAlberto Garcia 
1946bf77e1cSAlberto Garcia /*
195022cdc9fSManos Pitsidianakis  * Return whether a ThrottleGroupMember has pending requests.
1966bf77e1cSAlberto Garcia  *
1976bf77e1cSAlberto Garcia  * This assumes that tg->lock is held.
1986bf77e1cSAlberto Garcia  *
199022cdc9fSManos Pitsidianakis  * @tgm:        the ThrottleGroupMember
2003b2337efSzhenwei pi  * @direction:  the ThrottleDirection
201022cdc9fSManos Pitsidianakis  * @ret:        whether the ThrottleGroupMember has pending requests.
2026bf77e1cSAlberto Garcia  */
tgm_has_pending_reqs(ThrottleGroupMember * tgm,ThrottleDirection direction)203022cdc9fSManos Pitsidianakis static inline bool tgm_has_pending_reqs(ThrottleGroupMember *tgm,
2043b2337efSzhenwei pi                                         ThrottleDirection direction)
2056bf77e1cSAlberto Garcia {
2063b2337efSzhenwei pi     return tgm->pending_reqs[direction];
2076bf77e1cSAlberto Garcia }
2086bf77e1cSAlberto Garcia 
209022cdc9fSManos Pitsidianakis /* Return the next ThrottleGroupMember in the round-robin sequence with pending
210022cdc9fSManos Pitsidianakis  * I/O requests.
21176f4afb4SAlberto Garcia  *
21276f4afb4SAlberto Garcia  * This assumes that tg->lock is held.
21376f4afb4SAlberto Garcia  *
214022cdc9fSManos Pitsidianakis  * @tgm:       the current ThrottleGroupMember
2153b2337efSzhenwei pi  * @direction: the ThrottleDirection
216022cdc9fSManos Pitsidianakis  * @ret:       the next ThrottleGroupMember with pending requests, or tgm if
217022cdc9fSManos Pitsidianakis  *             there is none.
21876f4afb4SAlberto Garcia  */
next_throttle_token(ThrottleGroupMember * tgm,ThrottleDirection direction)219022cdc9fSManos Pitsidianakis static ThrottleGroupMember *next_throttle_token(ThrottleGroupMember *tgm,
2203b2337efSzhenwei pi                                                 ThrottleDirection direction)
22176f4afb4SAlberto Garcia {
222022cdc9fSManos Pitsidianakis     ThrottleState *ts = tgm->throttle_state;
223022cdc9fSManos Pitsidianakis     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
224022cdc9fSManos Pitsidianakis     ThrottleGroupMember *token, *start;
22576f4afb4SAlberto Garcia 
2265d8e4ca0SAlberto Garcia     /* If this member has its I/O limits disabled then it means that
2275d8e4ca0SAlberto Garcia      * it's being drained. Skip the round-robin search and return tgm
2285d8e4ca0SAlberto Garcia      * immediately if it has pending requests. Otherwise we could be
2295d8e4ca0SAlberto Garcia      * forcing it to wait for other member's throttled requests. */
2303b2337efSzhenwei pi     if (tgm_has_pending_reqs(tgm, direction) &&
231d73415a3SStefan Hajnoczi         qatomic_read(&tgm->io_limits_disabled)) {
2325d8e4ca0SAlberto Garcia         return tgm;
2335d8e4ca0SAlberto Garcia     }
2345d8e4ca0SAlberto Garcia 
2353b2337efSzhenwei pi     start = token = tg->tokens[direction];
23676f4afb4SAlberto Garcia 
23776f4afb4SAlberto Garcia     /* get next bs round in round robin style */
238022cdc9fSManos Pitsidianakis     token = throttle_group_next_tgm(token);
2393b2337efSzhenwei pi     while (token != start && !tgm_has_pending_reqs(token, direction)) {
240022cdc9fSManos Pitsidianakis         token = throttle_group_next_tgm(token);
24176f4afb4SAlberto Garcia     }
24276f4afb4SAlberto Garcia 
24376f4afb4SAlberto Garcia     /* If no IO are queued for scheduling on the next round robin token
244022cdc9fSManos Pitsidianakis      * then decide the token is the current tgm because chances are
245022cdc9fSManos Pitsidianakis      * the current tgm got the current request queued.
24676f4afb4SAlberto Garcia      */
2473b2337efSzhenwei pi     if (token == start && !tgm_has_pending_reqs(token, direction)) {
248022cdc9fSManos Pitsidianakis         token = tgm;
24976f4afb4SAlberto Garcia     }
25076f4afb4SAlberto Garcia 
251022cdc9fSManos Pitsidianakis     /* Either we return the original TGM, or one with pending requests */
2523b2337efSzhenwei pi     assert(token == tgm || tgm_has_pending_reqs(token, direction));
2536bf77e1cSAlberto Garcia 
25476f4afb4SAlberto Garcia     return token;
25576f4afb4SAlberto Garcia }
25676f4afb4SAlberto Garcia 
257022cdc9fSManos Pitsidianakis /* Check if the next I/O request for a ThrottleGroupMember needs to be
258022cdc9fSManos Pitsidianakis  * throttled or not. If there's no timer set in this group, set one and update
259022cdc9fSManos Pitsidianakis  * the token accordingly.
26076f4afb4SAlberto Garcia  *
26176f4afb4SAlberto Garcia  * This assumes that tg->lock is held.
26276f4afb4SAlberto Garcia  *
263022cdc9fSManos Pitsidianakis  * @tgm:        the current ThrottleGroupMember
2643b2337efSzhenwei pi  * @direction:  the ThrottleDirection
26576f4afb4SAlberto Garcia  * @ret:        whether the I/O request needs to be throttled or not
26676f4afb4SAlberto Garcia  */
throttle_group_schedule_timer(ThrottleGroupMember * tgm,ThrottleDirection direction)267022cdc9fSManos Pitsidianakis static bool throttle_group_schedule_timer(ThrottleGroupMember *tgm,
2683b2337efSzhenwei pi                                           ThrottleDirection direction)
26976f4afb4SAlberto Garcia {
270022cdc9fSManos Pitsidianakis     ThrottleState *ts = tgm->throttle_state;
27176f4afb4SAlberto Garcia     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
272022cdc9fSManos Pitsidianakis     ThrottleTimers *tt = &tgm->throttle_timers;
27376f4afb4SAlberto Garcia     bool must_wait;
27476f4afb4SAlberto Garcia 
275d73415a3SStefan Hajnoczi     if (qatomic_read(&tgm->io_limits_disabled)) {
276ce0f1412SPaolo Bonzini         return false;
277ce0f1412SPaolo Bonzini     }
278ce0f1412SPaolo Bonzini 
27976f4afb4SAlberto Garcia     /* Check if any of the timers in this group is already armed */
2803b2337efSzhenwei pi     if (tg->any_timer_armed[direction]) {
28176f4afb4SAlberto Garcia         return true;
28276f4afb4SAlberto Garcia     }
28376f4afb4SAlberto Garcia 
284e76f201fSzhenwei pi     must_wait = throttle_schedule_timer(ts, tt, direction);
28576f4afb4SAlberto Garcia 
286022cdc9fSManos Pitsidianakis     /* If a timer just got armed, set tgm as the current token */
28776f4afb4SAlberto Garcia     if (must_wait) {
2883b2337efSzhenwei pi         tg->tokens[direction] = tgm;
2893b2337efSzhenwei pi         tg->any_timer_armed[direction] = true;
29076f4afb4SAlberto Garcia     }
29176f4afb4SAlberto Garcia 
29276f4afb4SAlberto Garcia     return must_wait;
29376f4afb4SAlberto Garcia }
29476f4afb4SAlberto Garcia 
295022cdc9fSManos Pitsidianakis /* Start the next pending I/O request for a ThrottleGroupMember. Return whether
2963b170dc8SPaolo Bonzini  * any request was actually pending.
2973b170dc8SPaolo Bonzini  *
298022cdc9fSManos Pitsidianakis  * @tgm:       the current ThrottleGroupMember
2993b2337efSzhenwei pi  * @direction: the ThrottleDirection
3003b170dc8SPaolo Bonzini  */
throttle_group_co_restart_queue(ThrottleGroupMember * tgm,ThrottleDirection direction)301022cdc9fSManos Pitsidianakis static bool coroutine_fn throttle_group_co_restart_queue(ThrottleGroupMember *tgm,
3023b2337efSzhenwei pi                                                          ThrottleDirection direction)
3033b170dc8SPaolo Bonzini {
30493001e9dSPaolo Bonzini     bool ret;
3053b170dc8SPaolo Bonzini 
306022cdc9fSManos Pitsidianakis     qemu_co_mutex_lock(&tgm->throttled_reqs_lock);
3073b2337efSzhenwei pi     ret = qemu_co_queue_next(&tgm->throttled_reqs[direction]);
308022cdc9fSManos Pitsidianakis     qemu_co_mutex_unlock(&tgm->throttled_reqs_lock);
30993001e9dSPaolo Bonzini 
31093001e9dSPaolo Bonzini     return ret;
3113b170dc8SPaolo Bonzini }
3123b170dc8SPaolo Bonzini 
31376f4afb4SAlberto Garcia /* Look for the next pending I/O request and schedule it.
31476f4afb4SAlberto Garcia  *
31576f4afb4SAlberto Garcia  * This assumes that tg->lock is held.
31676f4afb4SAlberto Garcia  *
317022cdc9fSManos Pitsidianakis  * @tgm:       the current ThrottleGroupMember
3183b2337efSzhenwei pi  * @direction: the ThrottleDirection
31976f4afb4SAlberto Garcia  */
schedule_next_request(ThrottleGroupMember * tgm,ThrottleDirection direction)320*e2dbca03SPaolo Bonzini static void coroutine_mixed_fn schedule_next_request(ThrottleGroupMember *tgm,
3213b2337efSzhenwei pi                                                      ThrottleDirection direction)
32276f4afb4SAlberto Garcia {
323022cdc9fSManos Pitsidianakis     ThrottleState *ts = tgm->throttle_state;
324022cdc9fSManos Pitsidianakis     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
32576f4afb4SAlberto Garcia     bool must_wait;
326022cdc9fSManos Pitsidianakis     ThrottleGroupMember *token;
32776f4afb4SAlberto Garcia 
32876f4afb4SAlberto Garcia     /* Check if there's any pending request to schedule next */
3293b2337efSzhenwei pi     token = next_throttle_token(tgm, direction);
3303b2337efSzhenwei pi     if (!tgm_has_pending_reqs(token, direction)) {
33176f4afb4SAlberto Garcia         return;
33276f4afb4SAlberto Garcia     }
33376f4afb4SAlberto Garcia 
33476f4afb4SAlberto Garcia     /* Set a timer for the request if it needs to be throttled */
3353b2337efSzhenwei pi     must_wait = throttle_group_schedule_timer(token, direction);
33676f4afb4SAlberto Garcia 
33776f4afb4SAlberto Garcia     /* If it doesn't have to wait, queue it for immediate execution */
33876f4afb4SAlberto Garcia     if (!must_wait) {
339022cdc9fSManos Pitsidianakis         /* Give preference to requests from the current tgm */
34076f4afb4SAlberto Garcia         if (qemu_in_coroutine() &&
3413b2337efSzhenwei pi             throttle_group_co_restart_queue(tgm, direction)) {
342022cdc9fSManos Pitsidianakis             token = tgm;
34376f4afb4SAlberto Garcia         } else {
344022cdc9fSManos Pitsidianakis             ThrottleTimers *tt = &token->throttle_timers;
345dbe824ccSManos Pitsidianakis             int64_t now = qemu_clock_get_ns(tg->clock_type);
3463b2337efSzhenwei pi             timer_mod(tt->timers[direction], now);
3473b2337efSzhenwei pi             tg->any_timer_armed[direction] = true;
34876f4afb4SAlberto Garcia         }
3493b2337efSzhenwei pi         tg->tokens[direction] = token;
35076f4afb4SAlberto Garcia     }
35176f4afb4SAlberto Garcia }
35276f4afb4SAlberto Garcia 
35376f4afb4SAlberto Garcia /* Check if an I/O request needs to be throttled, wait and set a timer
35476f4afb4SAlberto Garcia  * if necessary, and schedule the next request using a round robin
35576f4afb4SAlberto Garcia  * algorithm.
35676f4afb4SAlberto Garcia  *
357022cdc9fSManos Pitsidianakis  * @tgm:       the current ThrottleGroupMember
35876f4afb4SAlberto Garcia  * @bytes:     the number of bytes for this I/O
3593b2337efSzhenwei pi  * @direction: the ThrottleDirection
36076f4afb4SAlberto Garcia  */
throttle_group_co_io_limits_intercept(ThrottleGroupMember * tgm,int64_t bytes,ThrottleDirection direction)361022cdc9fSManos Pitsidianakis void coroutine_fn throttle_group_co_io_limits_intercept(ThrottleGroupMember *tgm,
362801625e6SVladimir Sementsov-Ogievskiy                                                         int64_t bytes,
3633b2337efSzhenwei pi                                                         ThrottleDirection direction)
36476f4afb4SAlberto Garcia {
36576f4afb4SAlberto Garcia     bool must_wait;
366022cdc9fSManos Pitsidianakis     ThrottleGroupMember *token;
367022cdc9fSManos Pitsidianakis     ThrottleGroup *tg = container_of(tgm->throttle_state, ThrottleGroup, ts);
368801625e6SVladimir Sementsov-Ogievskiy 
369801625e6SVladimir Sementsov-Ogievskiy     assert(bytes >= 0);
3703b2337efSzhenwei pi     assert(direction < THROTTLE_MAX);
371801625e6SVladimir Sementsov-Ogievskiy 
37276f4afb4SAlberto Garcia     qemu_mutex_lock(&tg->lock);
37376f4afb4SAlberto Garcia 
37476f4afb4SAlberto Garcia     /* First we check if this I/O has to be throttled. */
3753b2337efSzhenwei pi     token = next_throttle_token(tgm, direction);
3763b2337efSzhenwei pi     must_wait = throttle_group_schedule_timer(token, direction);
37776f4afb4SAlberto Garcia 
37876f4afb4SAlberto Garcia     /* Wait if there's a timer set or queued requests of this type */
3793b2337efSzhenwei pi     if (must_wait || tgm->pending_reqs[direction]) {
3803b2337efSzhenwei pi         tgm->pending_reqs[direction]++;
38176f4afb4SAlberto Garcia         qemu_mutex_unlock(&tg->lock);
382022cdc9fSManos Pitsidianakis         qemu_co_mutex_lock(&tgm->throttled_reqs_lock);
3833b2337efSzhenwei pi         qemu_co_queue_wait(&tgm->throttled_reqs[direction],
384022cdc9fSManos Pitsidianakis                            &tgm->throttled_reqs_lock);
385022cdc9fSManos Pitsidianakis         qemu_co_mutex_unlock(&tgm->throttled_reqs_lock);
38676f4afb4SAlberto Garcia         qemu_mutex_lock(&tg->lock);
3873b2337efSzhenwei pi         tgm->pending_reqs[direction]--;
38876f4afb4SAlberto Garcia     }
38976f4afb4SAlberto Garcia 
39076f4afb4SAlberto Garcia     /* The I/O will be executed, so do the accounting */
391e76f201fSzhenwei pi     throttle_account(tgm->throttle_state, direction, bytes);
39276f4afb4SAlberto Garcia 
39376f4afb4SAlberto Garcia     /* Schedule the next request */
3943b2337efSzhenwei pi     schedule_next_request(tgm, direction);
39576f4afb4SAlberto Garcia 
39676f4afb4SAlberto Garcia     qemu_mutex_unlock(&tg->lock);
39776f4afb4SAlberto Garcia }
39876f4afb4SAlberto Garcia 
3993b170dc8SPaolo Bonzini typedef struct {
400022cdc9fSManos Pitsidianakis     ThrottleGroupMember *tgm;
4013b2337efSzhenwei pi     ThrottleDirection direction;
4023b170dc8SPaolo Bonzini } RestartData;
4033b170dc8SPaolo Bonzini 
throttle_group_restart_queue_entry(void * opaque)4043b170dc8SPaolo Bonzini static void coroutine_fn throttle_group_restart_queue_entry(void *opaque)
4057258ed93SPaolo Bonzini {
4063b170dc8SPaolo Bonzini     RestartData *data = opaque;
407022cdc9fSManos Pitsidianakis     ThrottleGroupMember *tgm = data->tgm;
408022cdc9fSManos Pitsidianakis     ThrottleState *ts = tgm->throttle_state;
409022cdc9fSManos Pitsidianakis     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
4103b2337efSzhenwei pi     ThrottleDirection direction = data->direction;
4117258ed93SPaolo Bonzini     bool empty_queue;
4127258ed93SPaolo Bonzini 
4133b2337efSzhenwei pi     empty_queue = !throttle_group_co_restart_queue(tgm, direction);
4147258ed93SPaolo Bonzini 
4157258ed93SPaolo Bonzini     /* If the request queue was empty then we have to take care of
4167258ed93SPaolo Bonzini      * scheduling the next one */
4177258ed93SPaolo Bonzini     if (empty_queue) {
4187258ed93SPaolo Bonzini         qemu_mutex_lock(&tg->lock);
4193b2337efSzhenwei pi         schedule_next_request(tgm, direction);
4207258ed93SPaolo Bonzini         qemu_mutex_unlock(&tg->lock);
4217258ed93SPaolo Bonzini     }
42243a5dc02SManos Pitsidianakis 
42343a5dc02SManos Pitsidianakis     g_free(data);
424bc19a0a6SStefan Hajnoczi 
425d73415a3SStefan Hajnoczi     qatomic_dec(&tgm->restart_pending);
426bc19a0a6SStefan Hajnoczi     aio_wait_kick();
4277258ed93SPaolo Bonzini }
4287258ed93SPaolo Bonzini 
throttle_group_restart_queue(ThrottleGroupMember * tgm,ThrottleDirection direction)4293b2337efSzhenwei pi static void throttle_group_restart_queue(ThrottleGroupMember *tgm,
4303b2337efSzhenwei pi                                         ThrottleDirection direction)
4313b170dc8SPaolo Bonzini {
4323b170dc8SPaolo Bonzini     Coroutine *co;
43343a5dc02SManos Pitsidianakis     RestartData *rd = g_new0(RestartData, 1);
4343b170dc8SPaolo Bonzini 
43543a5dc02SManos Pitsidianakis     rd->tgm = tgm;
4363b2337efSzhenwei pi     rd->direction = direction;
43743a5dc02SManos Pitsidianakis 
43825b8e4dbSAlberto Garcia     /* This function is called when a timer is fired or when
43925b8e4dbSAlberto Garcia      * throttle_group_restart_tgm() is called. Either way, there can
44025b8e4dbSAlberto Garcia      * be no timer pending on this tgm at this point */
4413b2337efSzhenwei pi     assert(!timer_pending(tgm->throttle_timers.timers[direction]));
44225b8e4dbSAlberto Garcia 
443d73415a3SStefan Hajnoczi     qatomic_inc(&tgm->restart_pending);
444bc19a0a6SStefan Hajnoczi 
44543a5dc02SManos Pitsidianakis     co = qemu_coroutine_create(throttle_group_restart_queue_entry, rd);
446c61791fcSManos Pitsidianakis     aio_co_enter(tgm->aio_context, co);
4473b170dc8SPaolo Bonzini }
4483b170dc8SPaolo Bonzini 
throttle_group_restart_tgm(ThrottleGroupMember * tgm)449022cdc9fSManos Pitsidianakis void throttle_group_restart_tgm(ThrottleGroupMember *tgm)
450a72f6414SPaolo Bonzini {
4513b2337efSzhenwei pi     ThrottleDirection dir;
45225b8e4dbSAlberto Garcia 
453022cdc9fSManos Pitsidianakis     if (tgm->throttle_state) {
4543b2337efSzhenwei pi         for (dir = THROTTLE_READ; dir < THROTTLE_MAX; dir++) {
4553b2337efSzhenwei pi             QEMUTimer *t = tgm->throttle_timers.timers[dir];
45625b8e4dbSAlberto Garcia             if (timer_pending(t)) {
45725b8e4dbSAlberto Garcia                 /* If there's a pending timer on this tgm, fire it now */
45825b8e4dbSAlberto Garcia                 timer_del(t);
4593b2337efSzhenwei pi                 timer_cb(tgm, dir);
46025b8e4dbSAlberto Garcia             } else {
46125b8e4dbSAlberto Garcia                 /* Else run the next request from the queue manually */
4623b2337efSzhenwei pi                 throttle_group_restart_queue(tgm, dir);
46325b8e4dbSAlberto Garcia             }
46425b8e4dbSAlberto Garcia         }
465a72f6414SPaolo Bonzini     }
466a72f6414SPaolo Bonzini }
467a72f6414SPaolo Bonzini 
4682ff1f2e3SAlberto Garcia /* Update the throttle configuration for a particular group. Similar
4692ff1f2e3SAlberto Garcia  * to throttle_config(), but guarantees atomicity within the
4702ff1f2e3SAlberto Garcia  * throttling group.
4712ff1f2e3SAlberto Garcia  *
472022cdc9fSManos Pitsidianakis  * @tgm:    a ThrottleGroupMember that is a member of the group
4732ff1f2e3SAlberto Garcia  * @cfg: the configuration to set
4742ff1f2e3SAlberto Garcia  */
throttle_group_config(ThrottleGroupMember * tgm,ThrottleConfig * cfg)475022cdc9fSManos Pitsidianakis void throttle_group_config(ThrottleGroupMember *tgm, ThrottleConfig *cfg)
4762ff1f2e3SAlberto Garcia {
477022cdc9fSManos Pitsidianakis     ThrottleState *ts = tgm->throttle_state;
4782ff1f2e3SAlberto Garcia     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
4792ff1f2e3SAlberto Garcia     qemu_mutex_lock(&tg->lock);
48027e4cf13SManos Pitsidianakis     throttle_config(ts, tg->clock_type, cfg);
4812ff1f2e3SAlberto Garcia     qemu_mutex_unlock(&tg->lock);
482a72f6414SPaolo Bonzini 
483022cdc9fSManos Pitsidianakis     throttle_group_restart_tgm(tgm);
4842ff1f2e3SAlberto Garcia }
4852ff1f2e3SAlberto Garcia 
4862ff1f2e3SAlberto Garcia /* Get the throttle configuration from a particular group. Similar to
4872ff1f2e3SAlberto Garcia  * throttle_get_config(), but guarantees atomicity within the
4882ff1f2e3SAlberto Garcia  * throttling group.
4892ff1f2e3SAlberto Garcia  *
490022cdc9fSManos Pitsidianakis  * @tgm:    a ThrottleGroupMember that is a member of the group
4912ff1f2e3SAlberto Garcia  * @cfg: the configuration will be written here
4922ff1f2e3SAlberto Garcia  */
throttle_group_get_config(ThrottleGroupMember * tgm,ThrottleConfig * cfg)493022cdc9fSManos Pitsidianakis void throttle_group_get_config(ThrottleGroupMember *tgm, ThrottleConfig *cfg)
4942ff1f2e3SAlberto Garcia {
495022cdc9fSManos Pitsidianakis     ThrottleState *ts = tgm->throttle_state;
4962ff1f2e3SAlberto Garcia     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
4972ff1f2e3SAlberto Garcia     qemu_mutex_lock(&tg->lock);
4982ff1f2e3SAlberto Garcia     throttle_get_config(ts, cfg);
4992ff1f2e3SAlberto Garcia     qemu_mutex_unlock(&tg->lock);
5002ff1f2e3SAlberto Garcia }
5012ff1f2e3SAlberto Garcia 
50276f4afb4SAlberto Garcia /* ThrottleTimers callback. This wakes up a request that was waiting
50376f4afb4SAlberto Garcia  * because it had been throttled.
50476f4afb4SAlberto Garcia  *
505c61791fcSManos Pitsidianakis  * @tgm:       the ThrottleGroupMember whose request had been throttled
5063b2337efSzhenwei pi  * @direction: the ThrottleDirection
50776f4afb4SAlberto Garcia  */
timer_cb(ThrottleGroupMember * tgm,ThrottleDirection direction)5083b2337efSzhenwei pi static void timer_cb(ThrottleGroupMember *tgm, ThrottleDirection direction)
50976f4afb4SAlberto Garcia {
510022cdc9fSManos Pitsidianakis     ThrottleState *ts = tgm->throttle_state;
51176f4afb4SAlberto Garcia     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
51276f4afb4SAlberto Garcia 
51376f4afb4SAlberto Garcia     /* The timer has just been fired, so we can update the flag */
51476f4afb4SAlberto Garcia     qemu_mutex_lock(&tg->lock);
5153b2337efSzhenwei pi     tg->any_timer_armed[direction] = false;
51676f4afb4SAlberto Garcia     qemu_mutex_unlock(&tg->lock);
51776f4afb4SAlberto Garcia 
51876f4afb4SAlberto Garcia     /* Run the request that was waiting for this timer */
5193b2337efSzhenwei pi     throttle_group_restart_queue(tgm, direction);
52076f4afb4SAlberto Garcia }
52176f4afb4SAlberto Garcia 
read_timer_cb(void * opaque)52276f4afb4SAlberto Garcia static void read_timer_cb(void *opaque)
52376f4afb4SAlberto Garcia {
5243b2337efSzhenwei pi     timer_cb(opaque, THROTTLE_READ);
52576f4afb4SAlberto Garcia }
52676f4afb4SAlberto Garcia 
write_timer_cb(void * opaque)52776f4afb4SAlberto Garcia static void write_timer_cb(void *opaque)
52876f4afb4SAlberto Garcia {
5293b2337efSzhenwei pi     timer_cb(opaque, THROTTLE_WRITE);
53076f4afb4SAlberto Garcia }
53176f4afb4SAlberto Garcia 
532022cdc9fSManos Pitsidianakis /* Register a ThrottleGroupMember from the throttling group, also initializing
533022cdc9fSManos Pitsidianakis  * its timers and updating its throttle_state pointer to point to it. If a
53431dce3ccSKevin Wolf  * throttling group with that name does not exist yet, it will be created.
5352ff1f2e3SAlberto Garcia  *
536432d889eSManos Pitsidianakis  * This function edits throttle_groups and must be called under the global
537432d889eSManos Pitsidianakis  * mutex.
538432d889eSManos Pitsidianakis  *
539022cdc9fSManos Pitsidianakis  * @tgm:       the ThrottleGroupMember to insert
5402ff1f2e3SAlberto Garcia  * @groupname: the name of the group
541c61791fcSManos Pitsidianakis  * @ctx:       the AioContext to use
5422ff1f2e3SAlberto Garcia  */
throttle_group_register_tgm(ThrottleGroupMember * tgm,const char * groupname,AioContext * ctx)543022cdc9fSManos Pitsidianakis void throttle_group_register_tgm(ThrottleGroupMember *tgm,
544c61791fcSManos Pitsidianakis                                  const char *groupname,
545c61791fcSManos Pitsidianakis                                  AioContext *ctx)
5462ff1f2e3SAlberto Garcia {
5473b2337efSzhenwei pi     ThrottleDirection dir;
548973f2ddfSMax Reitz     ThrottleState *ts = throttle_group_incref(groupname);
549973f2ddfSMax Reitz     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
550022cdc9fSManos Pitsidianakis 
551022cdc9fSManos Pitsidianakis     tgm->throttle_state = ts;
552c61791fcSManos Pitsidianakis     tgm->aio_context = ctx;
553d73415a3SStefan Hajnoczi     qatomic_set(&tgm->restart_pending, 0);
5542ff1f2e3SAlberto Garcia 
5553af613ebSGan Qixin     QEMU_LOCK_GUARD(&tg->lock);
556022cdc9fSManos Pitsidianakis     /* If the ThrottleGroup is new set this ThrottleGroupMember as the token */
5573b2337efSzhenwei pi     for (dir = THROTTLE_READ; dir < THROTTLE_MAX; dir++) {
5583b2337efSzhenwei pi         if (!tg->tokens[dir]) {
5593b2337efSzhenwei pi             tg->tokens[dir] = tgm;
5602ff1f2e3SAlberto Garcia         }
5613b2337efSzhenwei pi         qemu_co_queue_init(&tgm->throttled_reqs[dir]);
5622ff1f2e3SAlberto Garcia     }
5632ff1f2e3SAlberto Garcia 
564022cdc9fSManos Pitsidianakis     QLIST_INSERT_HEAD(&tg->head, tgm, round_robin);
56576f4afb4SAlberto Garcia 
566022cdc9fSManos Pitsidianakis     throttle_timers_init(&tgm->throttle_timers,
567c61791fcSManos Pitsidianakis                          tgm->aio_context,
568dbe824ccSManos Pitsidianakis                          tg->clock_type,
56976f4afb4SAlberto Garcia                          read_timer_cb,
57076f4afb4SAlberto Garcia                          write_timer_cb,
571c61791fcSManos Pitsidianakis                          tgm);
572f738cfc8SManos Pitsidianakis     qemu_co_mutex_init(&tgm->throttled_reqs_lock);
5732ff1f2e3SAlberto Garcia }
5742ff1f2e3SAlberto Garcia 
575022cdc9fSManos Pitsidianakis /* Unregister a ThrottleGroupMember from its group, removing it from the list,
57631dce3ccSKevin Wolf  * destroying the timers and setting the throttle_state pointer to NULL.
5772ff1f2e3SAlberto Garcia  *
578022cdc9fSManos Pitsidianakis  * The ThrottleGroupMember must not have pending throttled requests, so the
579022cdc9fSManos Pitsidianakis  * caller has to drain them first.
5805ac72418SAlberto Garcia  *
5812ff1f2e3SAlberto Garcia  * The group will be destroyed if it's empty after this operation.
5822ff1f2e3SAlberto Garcia  *
583022cdc9fSManos Pitsidianakis  * @tgm the ThrottleGroupMember to remove
5842ff1f2e3SAlberto Garcia  */
throttle_group_unregister_tgm(ThrottleGroupMember * tgm)585022cdc9fSManos Pitsidianakis void throttle_group_unregister_tgm(ThrottleGroupMember *tgm)
5862ff1f2e3SAlberto Garcia {
587022cdc9fSManos Pitsidianakis     ThrottleState *ts = tgm->throttle_state;
588022cdc9fSManos Pitsidianakis     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
589022cdc9fSManos Pitsidianakis     ThrottleGroupMember *token;
5903b2337efSzhenwei pi     ThrottleDirection dir;
5912ff1f2e3SAlberto Garcia 
592d8e7d87eSManos Pitsidianakis     if (!ts) {
593d8e7d87eSManos Pitsidianakis         /* Discard already unregistered tgm */
594d8e7d87eSManos Pitsidianakis         return;
595d8e7d87eSManos Pitsidianakis     }
596d8e7d87eSManos Pitsidianakis 
597bc19a0a6SStefan Hajnoczi     /* Wait for throttle_group_restart_queue_entry() coroutines to finish */
598d73415a3SStefan Hajnoczi     AIO_WAIT_WHILE(tgm->aio_context, qatomic_read(&tgm->restart_pending) > 0);
599bc19a0a6SStefan Hajnoczi 
6003af613ebSGan Qixin     WITH_QEMU_LOCK_GUARD(&tg->lock) {
6013b2337efSzhenwei pi         for (dir = THROTTLE_READ; dir < THROTTLE_MAX; dir++) {
6023b2337efSzhenwei pi             assert(tgm->pending_reqs[dir] == 0);
6033b2337efSzhenwei pi             assert(qemu_co_queue_empty(&tgm->throttled_reqs[dir]));
6043b2337efSzhenwei pi             assert(!timer_pending(tgm->throttle_timers.timers[dir]));
6053b2337efSzhenwei pi             if (tg->tokens[dir] == tgm) {
606022cdc9fSManos Pitsidianakis                 token = throttle_group_next_tgm(tgm);
607022cdc9fSManos Pitsidianakis                 /* Take care of the case where this is the last tgm in the group */
608022cdc9fSManos Pitsidianakis                 if (token == tgm) {
6092ff1f2e3SAlberto Garcia                     token = NULL;
6102ff1f2e3SAlberto Garcia                 }
6113b2337efSzhenwei pi                 tg->tokens[dir] = token;
6122ff1f2e3SAlberto Garcia             }
6132ff1f2e3SAlberto Garcia         }
6142ff1f2e3SAlberto Garcia 
615022cdc9fSManos Pitsidianakis         /* remove the current tgm from the list */
616022cdc9fSManos Pitsidianakis         QLIST_REMOVE(tgm, round_robin);
617022cdc9fSManos Pitsidianakis         throttle_timers_destroy(&tgm->throttle_timers);
6183af613ebSGan Qixin     }
6192ff1f2e3SAlberto Garcia 
620973f2ddfSMax Reitz     throttle_group_unref(&tg->ts);
621022cdc9fSManos Pitsidianakis     tgm->throttle_state = NULL;
6222ff1f2e3SAlberto Garcia }
6232ff1f2e3SAlberto Garcia 
throttle_group_attach_aio_context(ThrottleGroupMember * tgm,AioContext * new_context)624c61791fcSManos Pitsidianakis void throttle_group_attach_aio_context(ThrottleGroupMember *tgm,
625c61791fcSManos Pitsidianakis                                        AioContext *new_context)
626c61791fcSManos Pitsidianakis {
627c61791fcSManos Pitsidianakis     ThrottleTimers *tt = &tgm->throttle_timers;
628c61791fcSManos Pitsidianakis     throttle_timers_attach_aio_context(tt, new_context);
629c61791fcSManos Pitsidianakis     tgm->aio_context = new_context;
630c61791fcSManos Pitsidianakis }
631c61791fcSManos Pitsidianakis 
throttle_group_detach_aio_context(ThrottleGroupMember * tgm)632c61791fcSManos Pitsidianakis void throttle_group_detach_aio_context(ThrottleGroupMember *tgm)
633c61791fcSManos Pitsidianakis {
634341e0b56SStefan Hajnoczi     ThrottleGroup *tg = container_of(tgm->throttle_state, ThrottleGroup, ts);
635c61791fcSManos Pitsidianakis     ThrottleTimers *tt = &tgm->throttle_timers;
6363b2337efSzhenwei pi     ThrottleDirection dir;
637dc868fb0SStefan Hajnoczi 
638dc868fb0SStefan Hajnoczi     /* Requests must have been drained */
6393b2337efSzhenwei pi     for (dir = THROTTLE_READ; dir < THROTTLE_MAX; dir++) {
6403b2337efSzhenwei pi         assert(tgm->pending_reqs[dir] == 0);
6413b2337efSzhenwei pi         assert(qemu_co_queue_empty(&tgm->throttled_reqs[dir]));
6423b2337efSzhenwei pi     }
643dc868fb0SStefan Hajnoczi 
644341e0b56SStefan Hajnoczi     /* Kick off next ThrottleGroupMember, if necessary */
6453af613ebSGan Qixin     WITH_QEMU_LOCK_GUARD(&tg->lock) {
6463b2337efSzhenwei pi         for (dir = THROTTLE_READ; dir < THROTTLE_MAX; dir++) {
6473b2337efSzhenwei pi             if (timer_pending(tt->timers[dir])) {
6483b2337efSzhenwei pi                 tg->any_timer_armed[dir] = false;
6493b2337efSzhenwei pi                 schedule_next_request(tgm, dir);
650341e0b56SStefan Hajnoczi             }
651341e0b56SStefan Hajnoczi         }
6523af613ebSGan Qixin     }
653341e0b56SStefan Hajnoczi 
654c61791fcSManos Pitsidianakis     throttle_timers_detach_aio_context(tt);
655c61791fcSManos Pitsidianakis     tgm->aio_context = NULL;
656c61791fcSManos Pitsidianakis }
657c61791fcSManos Pitsidianakis 
658432d889eSManos Pitsidianakis #undef THROTTLE_OPT_PREFIX
659432d889eSManos Pitsidianakis #define THROTTLE_OPT_PREFIX "x-"
660432d889eSManos Pitsidianakis 
661432d889eSManos Pitsidianakis /* Helper struct and array for QOM property setter/getter */
662432d889eSManos Pitsidianakis typedef struct {
663432d889eSManos Pitsidianakis     const char *name;
664432d889eSManos Pitsidianakis     BucketType type;
665432d889eSManos Pitsidianakis     enum {
666432d889eSManos Pitsidianakis         AVG,
667432d889eSManos Pitsidianakis         MAX,
668432d889eSManos Pitsidianakis         BURST_LENGTH,
669432d889eSManos Pitsidianakis         IOPS_SIZE,
670432d889eSManos Pitsidianakis     } category;
671432d889eSManos Pitsidianakis } ThrottleParamInfo;
672432d889eSManos Pitsidianakis 
673432d889eSManos Pitsidianakis static ThrottleParamInfo properties[] = {
6742ff1f2e3SAlberto Garcia     {
675432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL,
676432d889eSManos Pitsidianakis         THROTTLE_OPS_TOTAL, AVG,
677432d889eSManos Pitsidianakis     },
678432d889eSManos Pitsidianakis     {
679432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX,
680432d889eSManos Pitsidianakis         THROTTLE_OPS_TOTAL, MAX,
681432d889eSManos Pitsidianakis     },
682432d889eSManos Pitsidianakis     {
683432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX_LENGTH,
684432d889eSManos Pitsidianakis         THROTTLE_OPS_TOTAL, BURST_LENGTH,
685432d889eSManos Pitsidianakis     },
686432d889eSManos Pitsidianakis     {
687432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ,
688432d889eSManos Pitsidianakis         THROTTLE_OPS_READ, AVG,
689432d889eSManos Pitsidianakis     },
690432d889eSManos Pitsidianakis     {
691432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX,
692432d889eSManos Pitsidianakis         THROTTLE_OPS_READ, MAX,
693432d889eSManos Pitsidianakis     },
694432d889eSManos Pitsidianakis     {
695432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX_LENGTH,
696432d889eSManos Pitsidianakis         THROTTLE_OPS_READ, BURST_LENGTH,
697432d889eSManos Pitsidianakis     },
698432d889eSManos Pitsidianakis     {
699432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE,
700432d889eSManos Pitsidianakis         THROTTLE_OPS_WRITE, AVG,
701432d889eSManos Pitsidianakis     },
702432d889eSManos Pitsidianakis     {
703432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX,
704432d889eSManos Pitsidianakis         THROTTLE_OPS_WRITE, MAX,
705432d889eSManos Pitsidianakis     },
706432d889eSManos Pitsidianakis     {
707432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX_LENGTH,
708432d889eSManos Pitsidianakis         THROTTLE_OPS_WRITE, BURST_LENGTH,
709432d889eSManos Pitsidianakis     },
710432d889eSManos Pitsidianakis     {
711432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL,
712432d889eSManos Pitsidianakis         THROTTLE_BPS_TOTAL, AVG,
713432d889eSManos Pitsidianakis     },
714432d889eSManos Pitsidianakis     {
715432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX,
716432d889eSManos Pitsidianakis         THROTTLE_BPS_TOTAL, MAX,
717432d889eSManos Pitsidianakis     },
718432d889eSManos Pitsidianakis     {
719432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX_LENGTH,
720432d889eSManos Pitsidianakis         THROTTLE_BPS_TOTAL, BURST_LENGTH,
721432d889eSManos Pitsidianakis     },
722432d889eSManos Pitsidianakis     {
723432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ,
724432d889eSManos Pitsidianakis         THROTTLE_BPS_READ, AVG,
725432d889eSManos Pitsidianakis     },
726432d889eSManos Pitsidianakis     {
727432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX,
728432d889eSManos Pitsidianakis         THROTTLE_BPS_READ, MAX,
729432d889eSManos Pitsidianakis     },
730432d889eSManos Pitsidianakis     {
731432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX_LENGTH,
732432d889eSManos Pitsidianakis         THROTTLE_BPS_READ, BURST_LENGTH,
733432d889eSManos Pitsidianakis     },
734432d889eSManos Pitsidianakis     {
735432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE,
736432d889eSManos Pitsidianakis         THROTTLE_BPS_WRITE, AVG,
737432d889eSManos Pitsidianakis     },
738432d889eSManos Pitsidianakis     {
739432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX,
740432d889eSManos Pitsidianakis         THROTTLE_BPS_WRITE, MAX,
741432d889eSManos Pitsidianakis     },
742432d889eSManos Pitsidianakis     {
743432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX_LENGTH,
744432d889eSManos Pitsidianakis         THROTTLE_BPS_WRITE, BURST_LENGTH,
745432d889eSManos Pitsidianakis     },
746432d889eSManos Pitsidianakis     {
747432d889eSManos Pitsidianakis         THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_SIZE,
748432d889eSManos Pitsidianakis         0, IOPS_SIZE,
749432d889eSManos Pitsidianakis     }
750432d889eSManos Pitsidianakis };
751432d889eSManos Pitsidianakis 
752432d889eSManos Pitsidianakis /* This function edits throttle_groups and must be called under the global
753432d889eSManos Pitsidianakis  * mutex */
throttle_group_obj_init(Object * obj)754432d889eSManos Pitsidianakis static void throttle_group_obj_init(Object *obj)
755432d889eSManos Pitsidianakis {
756432d889eSManos Pitsidianakis     ThrottleGroup *tg = THROTTLE_GROUP(obj);
757432d889eSManos Pitsidianakis 
758432d889eSManos Pitsidianakis     tg->clock_type = QEMU_CLOCK_REALTIME;
759432d889eSManos Pitsidianakis     if (qtest_enabled()) {
760432d889eSManos Pitsidianakis         /* For testing block IO throttling only */
761432d889eSManos Pitsidianakis         tg->clock_type = QEMU_CLOCK_VIRTUAL;
762432d889eSManos Pitsidianakis     }
763432d889eSManos Pitsidianakis     tg->is_initialized = false;
764432d889eSManos Pitsidianakis     qemu_mutex_init(&tg->lock);
765432d889eSManos Pitsidianakis     throttle_init(&tg->ts);
766432d889eSManos Pitsidianakis     QLIST_INIT(&tg->head);
7672ff1f2e3SAlberto Garcia }
7682ff1f2e3SAlberto Garcia 
769432d889eSManos Pitsidianakis /* This function edits throttle_groups and must be called under the global
770432d889eSManos Pitsidianakis  * mutex */
throttle_group_obj_complete(UserCreatable * obj,Error ** errp)771432d889eSManos Pitsidianakis static void throttle_group_obj_complete(UserCreatable *obj, Error **errp)
772432d889eSManos Pitsidianakis {
773432d889eSManos Pitsidianakis     ThrottleGroup *tg = THROTTLE_GROUP(obj);
774432d889eSManos Pitsidianakis     ThrottleConfig cfg;
775432d889eSManos Pitsidianakis 
776432d889eSManos Pitsidianakis     /* set group name to object id if it exists */
777432d889eSManos Pitsidianakis     if (!tg->name && tg->parent_obj.parent) {
7787a309cc9SMarkus Armbruster         tg->name = g_strdup(object_get_canonical_path_component(OBJECT(obj)));
779432d889eSManos Pitsidianakis     }
780432d889eSManos Pitsidianakis     /* We must have a group name at this point */
781432d889eSManos Pitsidianakis     assert(tg->name);
782432d889eSManos Pitsidianakis 
783432d889eSManos Pitsidianakis     /* error if name is duplicate */
784d8e7d87eSManos Pitsidianakis     if (throttle_group_exists(tg->name)) {
785432d889eSManos Pitsidianakis         error_setg(errp, "A group with this name already exists");
786432d889eSManos Pitsidianakis         return;
787432d889eSManos Pitsidianakis     }
788432d889eSManos Pitsidianakis 
789432d889eSManos Pitsidianakis     /* check validity */
790432d889eSManos Pitsidianakis     throttle_get_config(&tg->ts, &cfg);
791432d889eSManos Pitsidianakis     if (!throttle_is_valid(&cfg, errp)) {
792432d889eSManos Pitsidianakis         return;
793432d889eSManos Pitsidianakis     }
794432d889eSManos Pitsidianakis     throttle_config(&tg->ts, tg->clock_type, &cfg);
795432d889eSManos Pitsidianakis     QTAILQ_INSERT_TAIL(&throttle_groups, tg, list);
796432d889eSManos Pitsidianakis     tg->is_initialized = true;
797432d889eSManos Pitsidianakis }
798432d889eSManos Pitsidianakis 
799432d889eSManos Pitsidianakis /* This function edits throttle_groups and must be called under the global
800432d889eSManos Pitsidianakis  * mutex */
throttle_group_obj_finalize(Object * obj)801432d889eSManos Pitsidianakis static void throttle_group_obj_finalize(Object *obj)
802432d889eSManos Pitsidianakis {
803432d889eSManos Pitsidianakis     ThrottleGroup *tg = THROTTLE_GROUP(obj);
804432d889eSManos Pitsidianakis     if (tg->is_initialized) {
805432d889eSManos Pitsidianakis         QTAILQ_REMOVE(&throttle_groups, tg, list);
806432d889eSManos Pitsidianakis     }
807432d889eSManos Pitsidianakis     qemu_mutex_destroy(&tg->lock);
808432d889eSManos Pitsidianakis     g_free(tg->name);
809432d889eSManos Pitsidianakis }
810432d889eSManos Pitsidianakis 
throttle_group_set(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)811432d889eSManos Pitsidianakis static void throttle_group_set(Object *obj, Visitor *v, const char * name,
812432d889eSManos Pitsidianakis                                void *opaque, Error **errp)
813432d889eSManos Pitsidianakis 
814432d889eSManos Pitsidianakis {
815432d889eSManos Pitsidianakis     ThrottleGroup *tg = THROTTLE_GROUP(obj);
816432d889eSManos Pitsidianakis     ThrottleConfig *cfg;
817432d889eSManos Pitsidianakis     ThrottleParamInfo *info = opaque;
818432d889eSManos Pitsidianakis     int64_t value;
819432d889eSManos Pitsidianakis 
820432d889eSManos Pitsidianakis     /* If we have finished initialization, don't accept individual property
821432d889eSManos Pitsidianakis      * changes through QOM. Throttle configuration limits must be set in one
822432d889eSManos Pitsidianakis      * transaction, as certain combinations are invalid.
823432d889eSManos Pitsidianakis      */
824432d889eSManos Pitsidianakis     if (tg->is_initialized) {
825dcfe4805SMarkus Armbruster         error_setg(errp, "Property cannot be set after initialization");
826dcfe4805SMarkus Armbruster         return;
827432d889eSManos Pitsidianakis     }
828432d889eSManos Pitsidianakis 
829668f62ecSMarkus Armbruster     if (!visit_type_int64(v, name, &value, errp)) {
830dcfe4805SMarkus Armbruster         return;
831432d889eSManos Pitsidianakis     }
832432d889eSManos Pitsidianakis     if (value < 0) {
833dcfe4805SMarkus Armbruster         error_setg(errp, "Property values cannot be negative");
834dcfe4805SMarkus Armbruster         return;
835432d889eSManos Pitsidianakis     }
836432d889eSManos Pitsidianakis 
837432d889eSManos Pitsidianakis     cfg = &tg->ts.cfg;
838432d889eSManos Pitsidianakis     switch (info->category) {
839432d889eSManos Pitsidianakis     case AVG:
840432d889eSManos Pitsidianakis         cfg->buckets[info->type].avg = value;
841432d889eSManos Pitsidianakis         break;
842432d889eSManos Pitsidianakis     case MAX:
843432d889eSManos Pitsidianakis         cfg->buckets[info->type].max = value;
844432d889eSManos Pitsidianakis         break;
845432d889eSManos Pitsidianakis     case BURST_LENGTH:
846432d889eSManos Pitsidianakis         if (value > UINT_MAX) {
847dcfe4805SMarkus Armbruster             error_setg(errp, "%s value must be in the" "range [0, %u]",
848dcfe4805SMarkus Armbruster                        info->name, UINT_MAX);
849dcfe4805SMarkus Armbruster             return;
850432d889eSManos Pitsidianakis         }
851432d889eSManos Pitsidianakis         cfg->buckets[info->type].burst_length = value;
852432d889eSManos Pitsidianakis         break;
853432d889eSManos Pitsidianakis     case IOPS_SIZE:
854432d889eSManos Pitsidianakis         cfg->op_size = value;
855432d889eSManos Pitsidianakis         break;
856432d889eSManos Pitsidianakis     }
857432d889eSManos Pitsidianakis }
858432d889eSManos Pitsidianakis 
throttle_group_get(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)859432d889eSManos Pitsidianakis static void throttle_group_get(Object *obj, Visitor *v, const char *name,
860432d889eSManos Pitsidianakis                                void *opaque, Error **errp)
861432d889eSManos Pitsidianakis {
862432d889eSManos Pitsidianakis     ThrottleGroup *tg = THROTTLE_GROUP(obj);
863432d889eSManos Pitsidianakis     ThrottleConfig cfg;
864432d889eSManos Pitsidianakis     ThrottleParamInfo *info = opaque;
865432d889eSManos Pitsidianakis     int64_t value;
866432d889eSManos Pitsidianakis 
867432d889eSManos Pitsidianakis     throttle_get_config(&tg->ts, &cfg);
868432d889eSManos Pitsidianakis     switch (info->category) {
869432d889eSManos Pitsidianakis     case AVG:
870432d889eSManos Pitsidianakis         value = cfg.buckets[info->type].avg;
871432d889eSManos Pitsidianakis         break;
872432d889eSManos Pitsidianakis     case MAX:
873432d889eSManos Pitsidianakis         value = cfg.buckets[info->type].max;
874432d889eSManos Pitsidianakis         break;
875432d889eSManos Pitsidianakis     case BURST_LENGTH:
876432d889eSManos Pitsidianakis         value = cfg.buckets[info->type].burst_length;
877432d889eSManos Pitsidianakis         break;
878432d889eSManos Pitsidianakis     case IOPS_SIZE:
879432d889eSManos Pitsidianakis         value = cfg.op_size;
880432d889eSManos Pitsidianakis         break;
881432d889eSManos Pitsidianakis     }
882432d889eSManos Pitsidianakis 
883432d889eSManos Pitsidianakis     visit_type_int64(v, name, &value, errp);
884432d889eSManos Pitsidianakis }
885432d889eSManos Pitsidianakis 
throttle_group_set_limits(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)886432d889eSManos Pitsidianakis static void throttle_group_set_limits(Object *obj, Visitor *v,
887432d889eSManos Pitsidianakis                                       const char *name, void *opaque,
888432d889eSManos Pitsidianakis                                       Error **errp)
889432d889eSManos Pitsidianakis 
890432d889eSManos Pitsidianakis {
891432d889eSManos Pitsidianakis     ThrottleGroup *tg = THROTTLE_GROUP(obj);
892432d889eSManos Pitsidianakis     ThrottleConfig cfg;
89388be15a9SPanNengyuan     ThrottleLimits *argp;
894432d889eSManos Pitsidianakis     Error *local_err = NULL;
895432d889eSManos Pitsidianakis 
89614217038SMarkus Armbruster     if (!visit_type_ThrottleLimits(v, name, &argp, errp)) {
89714217038SMarkus Armbruster         return;
898432d889eSManos Pitsidianakis     }
899432d889eSManos Pitsidianakis     qemu_mutex_lock(&tg->lock);
900432d889eSManos Pitsidianakis     throttle_get_config(&tg->ts, &cfg);
901432d889eSManos Pitsidianakis     throttle_limits_to_config(argp, &cfg, &local_err);
902432d889eSManos Pitsidianakis     if (local_err) {
903432d889eSManos Pitsidianakis         goto unlock;
904432d889eSManos Pitsidianakis     }
905432d889eSManos Pitsidianakis     throttle_config(&tg->ts, tg->clock_type, &cfg);
906432d889eSManos Pitsidianakis 
907432d889eSManos Pitsidianakis unlock:
908432d889eSManos Pitsidianakis     qemu_mutex_unlock(&tg->lock);
90988be15a9SPanNengyuan     qapi_free_ThrottleLimits(argp);
910432d889eSManos Pitsidianakis     error_propagate(errp, local_err);
911432d889eSManos Pitsidianakis     return;
912432d889eSManos Pitsidianakis }
913432d889eSManos Pitsidianakis 
throttle_group_get_limits(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)914432d889eSManos Pitsidianakis static void throttle_group_get_limits(Object *obj, Visitor *v,
915432d889eSManos Pitsidianakis                                       const char *name, void *opaque,
916432d889eSManos Pitsidianakis                                       Error **errp)
917432d889eSManos Pitsidianakis {
918432d889eSManos Pitsidianakis     ThrottleGroup *tg = THROTTLE_GROUP(obj);
919432d889eSManos Pitsidianakis     ThrottleConfig cfg;
920432d889eSManos Pitsidianakis     ThrottleLimits arg = { 0 };
921432d889eSManos Pitsidianakis     ThrottleLimits *argp = &arg;
922432d889eSManos Pitsidianakis 
923432d889eSManos Pitsidianakis     qemu_mutex_lock(&tg->lock);
924432d889eSManos Pitsidianakis     throttle_get_config(&tg->ts, &cfg);
925432d889eSManos Pitsidianakis     qemu_mutex_unlock(&tg->lock);
926432d889eSManos Pitsidianakis 
927432d889eSManos Pitsidianakis     throttle_config_to_limits(&cfg, argp);
928432d889eSManos Pitsidianakis 
929432d889eSManos Pitsidianakis     visit_type_ThrottleLimits(v, name, &argp, errp);
930432d889eSManos Pitsidianakis }
931432d889eSManos Pitsidianakis 
throttle_group_can_be_deleted(UserCreatable * uc)932432d889eSManos Pitsidianakis static bool throttle_group_can_be_deleted(UserCreatable *uc)
933432d889eSManos Pitsidianakis {
934432d889eSManos Pitsidianakis     return OBJECT(uc)->ref == 1;
935432d889eSManos Pitsidianakis }
936432d889eSManos Pitsidianakis 
throttle_group_obj_class_init(ObjectClass * klass,void * class_data)937432d889eSManos Pitsidianakis static void throttle_group_obj_class_init(ObjectClass *klass, void *class_data)
938432d889eSManos Pitsidianakis {
939432d889eSManos Pitsidianakis     size_t i = 0;
940432d889eSManos Pitsidianakis     UserCreatableClass *ucc = USER_CREATABLE_CLASS(klass);
941432d889eSManos Pitsidianakis 
942432d889eSManos Pitsidianakis     ucc->complete = throttle_group_obj_complete;
943432d889eSManos Pitsidianakis     ucc->can_be_deleted = throttle_group_can_be_deleted;
944432d889eSManos Pitsidianakis 
945432d889eSManos Pitsidianakis     /* individual properties */
946432d889eSManos Pitsidianakis     for (i = 0; i < sizeof(properties) / sizeof(ThrottleParamInfo); i++) {
947432d889eSManos Pitsidianakis         object_class_property_add(klass,
948432d889eSManos Pitsidianakis                                   properties[i].name,
949432d889eSManos Pitsidianakis                                   "int",
950432d889eSManos Pitsidianakis                                   throttle_group_get,
951432d889eSManos Pitsidianakis                                   throttle_group_set,
952d2623129SMarkus Armbruster                                   NULL, &properties[i]);
953432d889eSManos Pitsidianakis     }
954432d889eSManos Pitsidianakis 
955432d889eSManos Pitsidianakis     /* ThrottleLimits */
956432d889eSManos Pitsidianakis     object_class_property_add(klass,
957432d889eSManos Pitsidianakis                               "limits", "ThrottleLimits",
958432d889eSManos Pitsidianakis                               throttle_group_get_limits,
959432d889eSManos Pitsidianakis                               throttle_group_set_limits,
960d2623129SMarkus Armbruster                               NULL, NULL);
961432d889eSManos Pitsidianakis }
962432d889eSManos Pitsidianakis 
963432d889eSManos Pitsidianakis static const TypeInfo throttle_group_info = {
964432d889eSManos Pitsidianakis     .name = TYPE_THROTTLE_GROUP,
965432d889eSManos Pitsidianakis     .parent = TYPE_OBJECT,
966432d889eSManos Pitsidianakis     .class_init = throttle_group_obj_class_init,
967432d889eSManos Pitsidianakis     .instance_size = sizeof(ThrottleGroup),
968432d889eSManos Pitsidianakis     .instance_init = throttle_group_obj_init,
969432d889eSManos Pitsidianakis     .instance_finalize = throttle_group_obj_finalize,
970432d889eSManos Pitsidianakis     .interfaces = (InterfaceInfo[]) {
971432d889eSManos Pitsidianakis         { TYPE_USER_CREATABLE },
972432d889eSManos Pitsidianakis         { }
973432d889eSManos Pitsidianakis     },
974432d889eSManos Pitsidianakis };
975432d889eSManos Pitsidianakis 
throttle_groups_init(void)976432d889eSManos Pitsidianakis static void throttle_groups_init(void)
977432d889eSManos Pitsidianakis {
978432d889eSManos Pitsidianakis     type_register_static(&throttle_group_info);
979432d889eSManos Pitsidianakis }
980432d889eSManos Pitsidianakis 
981432d889eSManos Pitsidianakis type_init(throttle_groups_init);
982