Add per-cgroup local event counters to track RDMA resource limit exhaustion from the perspective of individual cgroups. The rdma.events.local file reports two per-resource counters: - max: number of times this cgroup's limit was the one that blocked an allocation in the subtree - alloc_fail: number of allocation attempts originating from this cgroup that failed due to an ancestor's limit This mirrors the design of pids.events.local, where events are attributed to the cgroup that imposed the limit, not necessarily the cgroup where the allocation was attempted. Also extend rdma.events with a hierarchical alloc_fail counter that tracks allocation failures propagating upward from the requesting cgroup, complementing the existing max counter, so that rdma.events and rdma.events.local share the same output format.
Showing the changes to kernel (1 of 2 files). Show all
Unified · Split
| @@ -82,8 +82,11 @@ struct rdmacg_resource_pool { · 81 unmodified lines | |||
| 82 | /* total number counts which are set to max */ | 82 | /* total number counts which are set to max */ |
| 83 | int num_max_cnt; | 83 | int num_max_cnt; |
| 84 | 84 | ||
| 85 | /* per-resource hierarchical max event counters */ | 85 | /* per-resource event counters */ |
| 86 | u64 events_max[RDMACG_RESOURCE_MAX]; | 86 | u64 events_max[RDMACG_RESOURCE_MAX]; |
| 87 | u64 events_alloc_fail[RDMACG_RESOURCE_MAX]; | ||
| 88 | u64 events_local_max[RDMACG_RESOURCE_MAX]; | ||
| 89 | u64 events_local_alloc_fail[RDMACG_RESOURCE_MAX]; | ||
| 87 | }; | 90 | }; |
| 88 | 91 | ||
| 89 | static struct rdma_cgroup *css_rdmacg(struct cgroup_subsys_state *css) | 92 | static struct rdma_cgroup *css_rdmacg(struct cgroup_subsys_state *css) |
| @@ -131,6 +134,26 @@ static void free_cg_rpool_locked(struct rdmacg_resource_pool *rpool) · 41 unmodified lines | |||
| 131 | kfree(rpool); | 134 | kfree(rpool); |
| 132 | } | 135 | } |
| 133 | 136 | ||
| 137 | static bool rpool_has_persistent_state(struct rdmacg_resource_pool *rpool) | ||
| 138 | { | ||
| 139 | int i; | ||
| 140 | |||
| 141 | /* | ||
| 142 | * Keep the rpool alive if any peak value is non-zero, | ||
| 143 | * so that rdma.peak persists as a historical high- | ||
| 144 | * watermark even after all resources are freed. | ||
| 145 | */ | ||
| 146 | for (i = 0; i < RDMACG_RESOURCE_MAX; i++) { | ||
| 147 | if (rpool->resources[i].peak || | ||
| 148 | READ_ONCE(rpool->events_max[i]) || | ||
| 149 | READ_ONCE(rpool->events_local_max[i]) || | ||
| 150 | READ_ONCE(rpool->events_alloc_fail[i]) || | ||
| 151 | READ_ONCE(rpool->events_local_alloc_fail[i])) | ||
| 152 | return true; | ||
| 153 | } | ||
| 154 | return false; | ||
| 155 | } | ||
| 156 | |||
| 134 | static struct rdmacg_resource_pool * | 157 | static struct rdmacg_resource_pool * |
| 135 | find_cg_rpool_locked(struct rdma_cgroup *cg, | 158 | find_cg_rpool_locked(struct rdma_cgroup *cg, |
| 136 | struct rdmacg_device *device) | 159 | struct rdmacg_device *device) |
| @@ -209,37 +232,30 @@ uncharge_cg_locked(struct rdma_cgroup *cg, · 72 unmodified lines | |||
| 209 | rpool->usage_sum--; | 232 | rpool->usage_sum--; |
| 210 | if (rpool->usage_sum == 0 && | 233 | if (rpool->usage_sum == 0 && |
| 211 | rpool->num_max_cnt == RDMACG_RESOURCE_MAX) { | 234 | rpool->num_max_cnt == RDMACG_RESOURCE_MAX) { |
| 212 | int i; | 235 | if (!rpool_has_persistent_state(rpool)) { |
| 213 | 236 | /* | |
| 214 | /* | 237 | * No user of the rpool and all entries are set to max, so |
| 215 | * Keep the rpool alive if any peak value is non-zero, | 238 | * safe to delete this rpool. |
| 216 | * so that rdma.peak persists as a historical high- | 239 | */ |
| 217 | * watermark even after all resources are freed. | 240 | free_cg_rpool_locked(rpool); |
| 218 | */ | ||
| 219 | for (i = 0; i < RDMACG_RESOURCE_MAX; i++) { | ||
| 220 | if (rpool->resources[i].peak || | ||
| 221 | READ_ONCE(rpool->events_max[i])) | ||
| 222 | return; | ||
| 223 | } | 241 | } |
| 224 | /* | ||
| 225 | * No user of the rpool and all entries are set to max, so | ||
| 226 | * safe to delete this rpool. | ||
| 227 | */ | ||
| 228 | free_cg_rpool_locked(rpool); | ||
| 229 | } | 242 | } |
| 230 | } | 243 | } |
| 231 | 244 | ||
| 232 | /** | 245 | /** |
| 233 | * rdmacg_event_locked - fire hierarchical max event when resource limit is hit | 246 | * rdmacg_event_locked - fire event when resource allocation exceeds limit |
| 247 | * @cg: requesting cgroup | ||
| 234 | * @over_cg: cgroup whose limit was exceeded | 248 | * @over_cg: cgroup whose limit was exceeded |
| 235 | * @device: rdma device | 249 | * @device: rdma device |
| 236 | * @index: resource type index | 250 | * @index: resource type index |
| 237 | * | 251 | * |
| 238 | * Must be called under rdmacg_mutex. Propagates max event counts | 252 | * Must be called under rdmacg_mutex. Updates event counters in the |
| 239 | * from @over_cg (including itself) upward to all ancestors with | 253 | * resource pools of @cg and @over_cg, propagates hierarchical max |
| 240 | * an rpool and notifies userspace. | 254 | * events from @over_cg (including itself) upward, and notifies |
| 255 | * userspace via cgroup_file_notify(). | ||
| 241 | */ | 256 | */ |
| 242 | static void rdmacg_event_locked(struct rdma_cgroup *over_cg, | 257 | static void rdmacg_event_locked(struct rdma_cgroup *cg, |
| 258 | struct rdma_cgroup *over_cg, | ||
| 243 | struct rdmacg_device *device, | 259 | struct rdmacg_device *device, |
| 244 | enum rdmacg_resource_type index) | 260 | enum rdmacg_resource_type index) |
| 245 | { | 261 | { |
| @@ -248,6 +264,21 @@ static void rdmacg_event_locked(struct rdma_cgroup *over_cg, · 2 unmodified lines | |||
| 248 | 264 | ||
| 249 | lockdep_assert_held(&rdmacg_mutex); | 265 | lockdep_assert_held(&rdmacg_mutex); |
| 250 | 266 | ||
| 267 | /* Increment local alloc_fail in requesting cgroup */ | ||
| 268 | rpool = find_cg_rpool_locked(cg, device); | ||
| 269 | if (rpool) { | ||
| 270 | rpool->events_local_alloc_fail[index]++; | ||
| 271 | cgroup_file_notify(&cg->events_local_file); | ||
| 272 | } | ||
| 273 | |||
| 274 | /* Increment local max in the over-limit cgroup */ | ||
| 275 | rpool = find_cg_rpool_locked(over_cg, device); | ||
| 276 | if (rpool) { | ||
| 277 | rpool->events_local_max[index]++; | ||
| 278 | cgroup_file_notify(&over_cg->events_local_file); | ||
| 279 | } | ||
| 280 | |||
| 281 | /* Propagate hierarchical max events upward */ | ||
| 251 | for (p = over_cg; parent_rdmacg(p); p = parent_rdmacg(p)) { | 282 | for (p = over_cg; parent_rdmacg(p); p = parent_rdmacg(p)) { |
| 252 | rpool = get_cg_rpool_locked(p, device); | 283 | rpool = get_cg_rpool_locked(p, device); |
| 253 | if (!IS_ERR(rpool)) { | 284 | if (!IS_ERR(rpool)) { |
| @@ -255,6 +286,14 @@ static void rdmacg_event_locked(struct rdma_cgroup *over_cg, · 1 unmodified line | |||
| 255 | cgroup_file_notify(&p->events_file); | 286 | cgroup_file_notify(&p->events_file); |
| 256 | } | 287 | } |
| 257 | } | 288 | } |
| 289 | /* Propagate hierarchical alloc_fail from requesting cgroup upward */ | ||
| 290 | for (p = cg; parent_rdmacg(p); p = parent_rdmacg(p)) { | ||
| 291 | rpool = get_cg_rpool_locked(p, device); | ||
| 292 | if (!IS_ERR(rpool)) { | ||
| 293 | rpool->events_alloc_fail[index]++; | ||
| 294 | cgroup_file_notify(&p->events_file); | ||
| 295 | } | ||
| 296 | } | ||
| 258 | } | 297 | } |
| 259 | 298 | ||
| 260 | /** | 299 | /** |
| @@ -368,7 +407,7 @@ int rdmacg_try_charge(struct rdma_cgroup **rdmacg, · 107 unmodified lines | |||
| 368 | 407 | ||
| 369 | err: | 408 | err: |
| 370 | if (ret == -EAGAIN) | 409 | if (ret == -EAGAIN) |
| 371 | rdmacg_event_locked(p, device, index); | 410 | rdmacg_event_locked(cg, p, device, index); |
| 372 | mutex_unlock(&rdmacg_mutex); | 411 | mutex_unlock(&rdmacg_mutex); |
| 373 | rdmacg_uncharge_hierarchy(cg, device, p, index); | 412 | rdmacg_uncharge_hierarchy(cg, device, p, index); |
| 374 | return ret; | 413 | return ret; |
| @@ -525,18 +564,13 @@ static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of, · 150 unmodified lines | |||
| 525 | 564 | ||
| 526 | if (rpool->usage_sum == 0 && | 565 | if (rpool->usage_sum == 0 && |
| 527 | rpool->num_max_cnt == RDMACG_RESOURCE_MAX) { | 566 | rpool->num_max_cnt == RDMACG_RESOURCE_MAX) { |
| 528 | int i; | 567 | if (!rpool_has_persistent_state(rpool)) { |
| 529 | 568 | /* | |
| 530 | for (i = 0; i < RDMACG_RESOURCE_MAX; i++) { | 569 | * No user of the rpool and all entries are set to max, so |
| 531 | if (rpool->resources[i].peak || | 570 | * safe to delete this rpool. |
| 532 | READ_ONCE(rpool->events_max[i])) | 571 | */ |
| 533 | goto dev_err; | 572 | free_cg_rpool_locked(rpool); |
| 534 | } | 573 | } |
| 535 | /* | ||
| 536 | * No user of the rpool and all entries are set to max, so | ||
| 537 | * safe to delete this rpool. | ||
| 538 | */ | ||
| 539 | free_cg_rpool_locked(rpool); | ||
| 540 | } | 574 | } |
| 541 | 575 | ||
| 542 | dev_err: | 576 | dev_err: |
| @@ -618,9 +652,40 @@ static int rdmacg_events_show(struct seq_file *sf, void *v) · 75 unmodified lines | |||
| 618 | 652 | ||
| 619 | seq_printf(sf, "%s ", device->name); | 653 | seq_printf(sf, "%s ", device->name); |
| 620 | for (i = 0; i < RDMACG_RESOURCE_MAX; i++) { | 654 | for (i = 0; i < RDMACG_RESOURCE_MAX; i++) { |
| 621 | seq_printf(sf, "%s.max=%llu", | 655 | seq_printf(sf, "%s.max=%llu %s.alloc_fail=%llu", |
| 656 | rdmacg_resource_names[i], | ||
| 657 | rpool ? READ_ONCE(rpool->events_max[i]) : 0ULL, | ||
| 658 | rdmacg_resource_names[i], | ||
| 659 | rpool ? READ_ONCE(rpool->events_alloc_fail[i]) : 0ULL); | ||
| 660 | if (i < RDMACG_RESOURCE_MAX - 1) | ||
| 661 | seq_putc(sf, ' '); | ||
| 662 | } | ||
| 663 | seq_putc(sf, '\n'); | ||
| 664 | } | ||
| 665 | |||
| 666 | mutex_unlock(&rdmacg_mutex); | ||
| 667 | return 0; | ||
| 668 | } | ||
| 669 | |||
| 670 | static int rdmacg_events_local_show(struct seq_file *sf, void *v) | ||
| 671 | { | ||
| 672 | struct rdma_cgroup *cg = css_rdmacg(seq_css(sf)); | ||
| 673 | struct rdmacg_resource_pool *rpool; | ||
| 674 | struct rdmacg_device *device; | ||
| 675 | int i; | ||
| 676 | |||
| 677 | mutex_lock(&rdmacg_mutex); | ||
| 678 | |||
| 679 | list_for_each_entry(device, &rdmacg_devices, dev_node) { | ||
| 680 | rpool = find_cg_rpool_locked(cg, device); | ||
| 681 | |||
| 682 | seq_printf(sf, "%s ", device->name); | ||
| 683 | for (i = 0; i < RDMACG_RESOURCE_MAX; i++) { | ||
| 684 | seq_printf(sf, "%s.max=%llu %s.alloc_fail=%llu", | ||
| 685 | rdmacg_resource_names[i], | ||
| 686 | rpool ? READ_ONCE(rpool->events_local_max[i]) : 0ULL, | ||
| 622 | rdmacg_resource_names[i], | 687 | rdmacg_resource_names[i], |
| 623 | rpool ? READ_ONCE(rpool->events_max[i]) : 0ULL); | 688 | rpool ? READ_ONCE(rpool->events_local_alloc_fail[i]) : 0ULL); |
| 624 | if (i < RDMACG_RESOURCE_MAX - 1) | 689 | if (i < RDMACG_RESOURCE_MAX - 1) |
| 625 | seq_putc(sf, ' '); | 690 | seq_putc(sf, ' '); |
| 626 | } | 691 | } |
| @@ -657,6 +722,12 @@ static struct cftype rdmacg_files[] = { · 30 unmodified lines | |||
| 657 | .file_offset = offsetof(struct rdma_cgroup, events_file), | 722 | .file_offset = offsetof(struct rdma_cgroup, events_file), |
| 658 | .flags = CFTYPE_NOT_ON_ROOT, | 723 | .flags = CFTYPE_NOT_ON_ROOT, |
| 659 | }, | 724 | }, |
| 725 | { | ||
| 726 | .name = "events.local", | ||
| 727 | .seq_show = rdmacg_events_local_show, | ||
| 728 | .file_offset = offsetof(struct rdma_cgroup, events_local_file), | ||
| 729 | .flags = CFTYPE_NOT_ON_ROOT, | ||
| 730 | }, | ||
| 660 | { } /* terminate */ | 731 | { } /* terminate */ |
| 661 | }; | 732 | }; |
| 662 | 733 | ||