1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright(c) 2016-2017 Intel Corporation 3 */ 4 5 #include <rte_event_ring.h> 6 #include "sw_evdev.h" 7 #include "iq_chunk.h" 8 9 enum xstats_type { 10 /* common stats */ 11 rx, 12 tx, 13 dropped, 14 inflight, 15 calls, 16 credits, 17 /* device instance specific */ 18 no_iq_enq, 19 no_cq_enq, 20 /* port_specific */ 21 rx_used, 22 rx_free, 23 tx_used, 24 tx_free, 25 pkt_cycles, 26 poll_return, /* for zero-count and used also for port bucket loop */ 27 /* qid_specific */ 28 iq_used, 29 /* qid port mapping specific */ 30 pinned, 31 pkts, /* note: qid-to-port pkts */ 32 }; 33 34 typedef uint64_t (*xstats_fn)(const struct sw_evdev *dev, 35 uint16_t obj_idx, /* port or queue id */ 36 enum xstats_type stat, int extra_arg); 37 38 struct sw_xstats_entry { 39 struct rte_event_dev_xstats_name name; 40 xstats_fn fn; 41 uint16_t obj_idx; 42 enum xstats_type stat; 43 enum rte_event_dev_xstats_mode mode; 44 int extra_arg; 45 uint8_t reset_allowed; /* when set, this value can be reset */ 46 uint64_t reset_value; /* an offset to be taken away to emulate resets */ 47 }; 48 49 static uint64_t 50 get_dev_stat(const struct sw_evdev *sw, uint16_t obj_idx __rte_unused, 51 enum xstats_type type, int extra_arg __rte_unused) 52 { 53 switch (type) { 54 case rx: return sw->stats.rx_pkts; 55 case tx: return sw->stats.tx_pkts; 56 case dropped: return sw->stats.rx_dropped; 57 case calls: return sw->sched_called; 58 case no_iq_enq: return sw->sched_no_iq_enqueues; 59 case no_cq_enq: return sw->sched_no_cq_enqueues; 60 default: return -1; 61 } 62 } 63 64 static uint64_t 65 get_port_stat(const struct sw_evdev *sw, uint16_t obj_idx, 66 enum xstats_type type, int extra_arg __rte_unused) 67 { 68 const struct sw_port *p = &sw->ports[obj_idx]; 69 70 switch (type) { 71 case rx: return p->stats.rx_pkts; 72 case tx: return p->stats.tx_pkts; 73 case dropped: return p->stats.rx_dropped; 74 case inflight: return p->inflights; 75 case pkt_cycles: return p->avg_pkt_ticks; 76 case calls: return p->total_polls; 77 case credits: return p->inflight_credits; 78 case poll_return: return p->zero_polls; 79 case rx_used: return rte_event_ring_count(p->rx_worker_ring); 80 case rx_free: return rte_event_ring_free_count(p->rx_worker_ring); 81 case tx_used: return rte_event_ring_count(p->cq_worker_ring); 82 case tx_free: return rte_event_ring_free_count(p->cq_worker_ring); 83 default: return -1; 84 } 85 } 86 87 static uint64_t 88 get_port_bucket_stat(const struct sw_evdev *sw, uint16_t obj_idx, 89 enum xstats_type type, int extra_arg) 90 { 91 const struct sw_port *p = &sw->ports[obj_idx]; 92 93 switch (type) { 94 case poll_return: return p->poll_buckets[extra_arg]; 95 default: return -1; 96 } 97 } 98 99 static uint64_t 100 get_qid_stat(const struct sw_evdev *sw, uint16_t obj_idx, 101 enum xstats_type type, int extra_arg __rte_unused) 102 { 103 const struct sw_qid *qid = &sw->qids[obj_idx]; 104 105 switch (type) { 106 case rx: return qid->stats.rx_pkts; 107 case tx: return qid->stats.tx_pkts; 108 case dropped: return qid->stats.rx_dropped; 109 case inflight: 110 do { 111 uint64_t infl = 0; 112 unsigned int i; 113 for (i = 0; i < RTE_DIM(qid->fids); i++) 114 infl += qid->fids[i].pcount; 115 return infl; 116 } while (0); 117 break; 118 default: return -1; 119 } 120 } 121 122 static uint64_t 123 get_qid_iq_stat(const struct sw_evdev *sw, uint16_t obj_idx, 124 enum xstats_type type, int extra_arg) 125 { 126 const struct sw_qid *qid = &sw->qids[obj_idx]; 127 const int iq_idx = extra_arg; 128 129 switch (type) { 130 case iq_used: return iq_count(&qid->iq[iq_idx]); 131 default: return -1; 132 } 133 } 134 135 static uint64_t 136 get_qid_port_stat(const struct sw_evdev *sw, uint16_t obj_idx, 137 enum xstats_type type, int extra_arg) 138 { 139 const struct sw_qid *qid = &sw->qids[obj_idx]; 140 uint16_t port = extra_arg; 141 142 switch (type) { 143 case pinned: 144 do { 145 uint64_t pin = 0; 146 unsigned int i; 147 for (i = 0; i < RTE_DIM(qid->fids); i++) 148 if (qid->fids[i].cq == port) 149 pin++; 150 return pin; 151 } while (0); 152 break; 153 case pkts: 154 return qid->to_port[port]; 155 default: return -1; 156 } 157 } 158 159 int 160 sw_xstats_init(struct sw_evdev *sw) 161 { 162 /* 163 * define the stats names and types. Used to build up the device 164 * xstats array 165 * There are multiple set of stats: 166 * - device-level, 167 * - per-port, 168 * - per-port-dequeue-burst-sizes 169 * - per-qid, 170 * - per-iq 171 * - per-port-per-qid 172 * 173 * For each of these sets, we have three parallel arrays, one for the 174 * names, the other for the stat type parameter to be passed in the fn 175 * call to get that stat. The third array allows resetting or not. 176 * All these arrays must be kept in sync 177 */ 178 static const char * const dev_stats[] = { "rx", "tx", "drop", 179 "sched_calls", "sched_no_iq_enq", "sched_no_cq_enq", 180 }; 181 static const enum xstats_type dev_types[] = { rx, tx, dropped, 182 calls, no_iq_enq, no_cq_enq, 183 }; 184 /* all device stats are allowed to be reset */ 185 186 static const char * const port_stats[] = {"rx", "tx", "drop", 187 "inflight", "avg_pkt_cycles", "credits", 188 "rx_ring_used", "rx_ring_free", 189 "cq_ring_used", "cq_ring_free", 190 "dequeue_calls", "dequeues_returning_0", 191 }; 192 static const enum xstats_type port_types[] = { rx, tx, dropped, 193 inflight, pkt_cycles, credits, 194 rx_used, rx_free, tx_used, tx_free, 195 calls, poll_return, 196 }; 197 static const uint8_t port_reset_allowed[] = {1, 1, 1, 198 0, 1, 0, 199 0, 0, 0, 0, 200 1, 1, 201 }; 202 203 static const char * const port_bucket_stats[] = { 204 "dequeues_returning" }; 205 static const enum xstats_type port_bucket_types[] = { poll_return }; 206 /* all bucket dequeues are allowed to be reset, handled in loop below */ 207 208 static const char * const qid_stats[] = {"rx", "tx", "drop", 209 "inflight" 210 }; 211 static const enum xstats_type qid_types[] = { rx, tx, dropped, 212 inflight 213 }; 214 static const uint8_t qid_reset_allowed[] = {1, 1, 1, 215 0 216 }; 217 218 static const char * const qid_iq_stats[] = { "used" }; 219 static const enum xstats_type qid_iq_types[] = { iq_used }; 220 /* reset allowed */ 221 222 static const char * const qid_port_stats[] = { "pinned_flows", 223 "packets" 224 }; 225 static const enum xstats_type qid_port_types[] = { pinned, pkts }; 226 static const uint8_t qid_port_reset_allowed[] = {0, 1}; 227 /* reset allowed */ 228 /* ---- end of stat definitions ---- */ 229 230 /* check sizes, since a missed comma can lead to strings being 231 * joined by the compiler. 232 */ 233 RTE_BUILD_BUG_ON(RTE_DIM(dev_stats) != RTE_DIM(dev_types)); 234 RTE_BUILD_BUG_ON(RTE_DIM(port_stats) != RTE_DIM(port_types)); 235 RTE_BUILD_BUG_ON(RTE_DIM(qid_stats) != RTE_DIM(qid_types)); 236 RTE_BUILD_BUG_ON(RTE_DIM(qid_iq_stats) != RTE_DIM(qid_iq_types)); 237 RTE_BUILD_BUG_ON(RTE_DIM(qid_port_stats) != RTE_DIM(qid_port_types)); 238 RTE_BUILD_BUG_ON(RTE_DIM(port_bucket_stats) != 239 RTE_DIM(port_bucket_types)); 240 241 RTE_BUILD_BUG_ON(RTE_DIM(port_stats) != RTE_DIM(port_reset_allowed)); 242 RTE_BUILD_BUG_ON(RTE_DIM(qid_stats) != RTE_DIM(qid_reset_allowed)); 243 244 /* other vars */ 245 const uint32_t cons_bkt_shift = 246 (MAX_SW_CONS_Q_DEPTH >> SW_DEQ_STAT_BUCKET_SHIFT); 247 const unsigned int count = RTE_DIM(dev_stats) + 248 sw->port_count * RTE_DIM(port_stats) + 249 sw->port_count * RTE_DIM(port_bucket_stats) * 250 (cons_bkt_shift + 1) + 251 sw->qid_count * RTE_DIM(qid_stats) + 252 sw->qid_count * SW_IQS_MAX * RTE_DIM(qid_iq_stats) + 253 sw->qid_count * sw->port_count * 254 RTE_DIM(qid_port_stats); 255 unsigned int i, port, qid, iq, bkt, stat = 0; 256 257 sw->xstats = rte_zmalloc_socket(NULL, sizeof(sw->xstats[0]) * count, 0, 258 sw->data->socket_id); 259 if (sw->xstats == NULL) 260 return -ENOMEM; 261 262 #define sname sw->xstats[stat].name.name 263 for (i = 0; i < RTE_DIM(dev_stats); i++, stat++) { 264 sw->xstats[stat] = (struct sw_xstats_entry){ 265 .fn = get_dev_stat, 266 .stat = dev_types[i], 267 .mode = RTE_EVENT_DEV_XSTATS_DEVICE, 268 .reset_allowed = 1, 269 }; 270 snprintf(sname, sizeof(sname), "dev_%s", dev_stats[i]); 271 } 272 sw->xstats_count_mode_dev = stat; 273 274 for (port = 0; port < sw->port_count; port++) { 275 sw->xstats_offset_for_port[port] = stat; 276 277 uint32_t count_offset = stat; 278 279 for (i = 0; i < RTE_DIM(port_stats); i++, stat++) { 280 sw->xstats[stat] = (struct sw_xstats_entry){ 281 .fn = get_port_stat, 282 .obj_idx = port, 283 .stat = port_types[i], 284 .mode = RTE_EVENT_DEV_XSTATS_PORT, 285 .reset_allowed = port_reset_allowed[i], 286 }; 287 snprintf(sname, sizeof(sname), "port_%u_%s", 288 port, port_stats[i]); 289 } 290 291 for (bkt = 0; bkt < (rte_event_ring_get_capacity( 292 sw->ports[port].cq_worker_ring) >> 293 SW_DEQ_STAT_BUCKET_SHIFT) + 1; bkt++) { 294 for (i = 0; i < RTE_DIM(port_bucket_stats); i++) { 295 sw->xstats[stat] = (struct sw_xstats_entry){ 296 .fn = get_port_bucket_stat, 297 .obj_idx = port, 298 .stat = port_bucket_types[i], 299 .mode = RTE_EVENT_DEV_XSTATS_PORT, 300 .extra_arg = bkt, 301 .reset_allowed = 1, 302 }; 303 snprintf(sname, sizeof(sname), 304 "port_%u_%s_%u-%u", 305 port, port_bucket_stats[i], 306 (bkt << SW_DEQ_STAT_BUCKET_SHIFT) + 1, 307 (bkt + 1) << SW_DEQ_STAT_BUCKET_SHIFT); 308 stat++; 309 } 310 } 311 312 sw->xstats_count_per_port[port] = stat - count_offset; 313 } 314 315 sw->xstats_count_mode_port = stat - sw->xstats_count_mode_dev; 316 317 for (qid = 0; qid < sw->qid_count; qid++) { 318 uint32_t count_offset = stat; 319 sw->xstats_offset_for_qid[qid] = stat; 320 321 for (i = 0; i < RTE_DIM(qid_stats); i++, stat++) { 322 sw->xstats[stat] = (struct sw_xstats_entry){ 323 .fn = get_qid_stat, 324 .obj_idx = qid, 325 .stat = qid_types[i], 326 .mode = RTE_EVENT_DEV_XSTATS_QUEUE, 327 .reset_allowed = qid_reset_allowed[i], 328 }; 329 snprintf(sname, sizeof(sname), "qid_%u_%s", 330 qid, qid_stats[i]); 331 } 332 for (iq = 0; iq < SW_IQS_MAX; iq++) 333 for (i = 0; i < RTE_DIM(qid_iq_stats); i++, stat++) { 334 sw->xstats[stat] = (struct sw_xstats_entry){ 335 .fn = get_qid_iq_stat, 336 .obj_idx = qid, 337 .stat = qid_iq_types[i], 338 .mode = RTE_EVENT_DEV_XSTATS_QUEUE, 339 .extra_arg = iq, 340 .reset_allowed = 0, 341 }; 342 snprintf(sname, sizeof(sname), 343 "qid_%u_iq_%u_%s", 344 qid, iq, 345 qid_iq_stats[i]); 346 } 347 348 for (port = 0; port < sw->port_count; port++) 349 for (i = 0; i < RTE_DIM(qid_port_stats); i++, stat++) { 350 sw->xstats[stat] = (struct sw_xstats_entry){ 351 .fn = get_qid_port_stat, 352 .obj_idx = qid, 353 .stat = qid_port_types[i], 354 .mode = RTE_EVENT_DEV_XSTATS_QUEUE, 355 .extra_arg = port, 356 .reset_allowed = 357 qid_port_reset_allowed[i], 358 }; 359 snprintf(sname, sizeof(sname), 360 "qid_%u_port_%u_%s", 361 qid, port, 362 qid_port_stats[i]); 363 } 364 365 sw->xstats_count_per_qid[qid] = stat - count_offset; 366 } 367 368 sw->xstats_count_mode_queue = stat - 369 (sw->xstats_count_mode_dev + sw->xstats_count_mode_port); 370 #undef sname 371 372 sw->xstats_count = stat; 373 374 return stat; 375 } 376 377 int 378 sw_xstats_uninit(struct sw_evdev *sw) 379 { 380 rte_free(sw->xstats); 381 sw->xstats_count = 0; 382 return 0; 383 } 384 385 int 386 sw_xstats_get_names(const struct rte_eventdev *dev, 387 enum rte_event_dev_xstats_mode mode, uint8_t queue_port_id, 388 struct rte_event_dev_xstats_name *xstats_names, 389 unsigned int *ids, unsigned int size) 390 { 391 const struct sw_evdev *sw = sw_pmd_priv_const(dev); 392 unsigned int i; 393 unsigned int xidx = 0; 394 RTE_SET_USED(mode); 395 RTE_SET_USED(queue_port_id); 396 397 uint32_t xstats_mode_count = 0; 398 uint32_t start_offset = 0; 399 400 switch (mode) { 401 case RTE_EVENT_DEV_XSTATS_DEVICE: 402 xstats_mode_count = sw->xstats_count_mode_dev; 403 break; 404 case RTE_EVENT_DEV_XSTATS_PORT: 405 if (queue_port_id >= (signed int)sw->port_count) 406 break; 407 xstats_mode_count = sw->xstats_count_per_port[queue_port_id]; 408 start_offset = sw->xstats_offset_for_port[queue_port_id]; 409 break; 410 case RTE_EVENT_DEV_XSTATS_QUEUE: 411 if (queue_port_id >= (signed int)sw->qid_count) 412 break; 413 xstats_mode_count = sw->xstats_count_per_qid[queue_port_id]; 414 start_offset = sw->xstats_offset_for_qid[queue_port_id]; 415 break; 416 default: 417 SW_LOG_ERR("Invalid mode received in sw_xstats_get_names()\n"); 418 return -EINVAL; 419 }; 420 421 if (xstats_mode_count > size || !ids || !xstats_names) 422 return xstats_mode_count; 423 424 for (i = 0; i < sw->xstats_count && xidx < size; i++) { 425 if (sw->xstats[i].mode != mode) 426 continue; 427 428 if (mode != RTE_EVENT_DEV_XSTATS_DEVICE && 429 queue_port_id != sw->xstats[i].obj_idx) 430 continue; 431 432 xstats_names[xidx] = sw->xstats[i].name; 433 if (ids) 434 ids[xidx] = start_offset + xidx; 435 xidx++; 436 } 437 return xidx; 438 } 439 440 static int 441 sw_xstats_update(struct sw_evdev *sw, enum rte_event_dev_xstats_mode mode, 442 uint8_t queue_port_id, const unsigned int ids[], 443 uint64_t values[], unsigned int n, const uint32_t reset, 444 const uint32_t ret_if_n_lt_nstats) 445 { 446 unsigned int i; 447 unsigned int xidx = 0; 448 RTE_SET_USED(mode); 449 RTE_SET_USED(queue_port_id); 450 451 uint32_t xstats_mode_count = 0; 452 453 switch (mode) { 454 case RTE_EVENT_DEV_XSTATS_DEVICE: 455 xstats_mode_count = sw->xstats_count_mode_dev; 456 break; 457 case RTE_EVENT_DEV_XSTATS_PORT: 458 if (queue_port_id >= (signed int)sw->port_count) 459 goto invalid_value; 460 xstats_mode_count = sw->xstats_count_per_port[queue_port_id]; 461 break; 462 case RTE_EVENT_DEV_XSTATS_QUEUE: 463 if (queue_port_id >= (signed int)sw->qid_count) 464 goto invalid_value; 465 xstats_mode_count = sw->xstats_count_per_qid[queue_port_id]; 466 break; 467 default: 468 SW_LOG_ERR("Invalid mode received in sw_xstats_get()\n"); 469 goto invalid_value; 470 }; 471 472 /* this function can check num stats and return them (xstats_get() style 473 * behaviour) or ignore n for reset() of a single stat style behaviour. 474 */ 475 if (ret_if_n_lt_nstats && xstats_mode_count > n) 476 return xstats_mode_count; 477 478 for (i = 0; i < n && xidx < xstats_mode_count; i++) { 479 struct sw_xstats_entry *xs = &sw->xstats[ids[i]]; 480 if (ids[i] > sw->xstats_count || xs->mode != mode) 481 continue; 482 483 if (mode != RTE_EVENT_DEV_XSTATS_DEVICE && 484 queue_port_id != xs->obj_idx) 485 continue; 486 487 uint64_t val = xs->fn(sw, xs->obj_idx, xs->stat, xs->extra_arg) 488 - xs->reset_value; 489 490 if (values) 491 values[xidx] = val; 492 493 if (xs->reset_allowed && reset) 494 xs->reset_value = val; 495 496 xidx++; 497 } 498 499 return xidx; 500 invalid_value: 501 return -EINVAL; 502 } 503 504 int 505 sw_xstats_get(const struct rte_eventdev *dev, 506 enum rte_event_dev_xstats_mode mode, uint8_t queue_port_id, 507 const unsigned int ids[], uint64_t values[], unsigned int n) 508 { 509 struct sw_evdev *sw = sw_pmd_priv(dev); 510 const uint32_t reset = 0; 511 const uint32_t ret_n_lt_stats = 0; 512 return sw_xstats_update(sw, mode, queue_port_id, ids, values, n, 513 reset, ret_n_lt_stats); 514 } 515 516 uint64_t 517 sw_xstats_get_by_name(const struct rte_eventdev *dev, 518 const char *name, unsigned int *id) 519 { 520 const struct sw_evdev *sw = sw_pmd_priv_const(dev); 521 unsigned int i; 522 523 for (i = 0; i < sw->xstats_count; i++) { 524 struct sw_xstats_entry *xs = &sw->xstats[i]; 525 if (strncmp(xs->name.name, name, 526 RTE_EVENT_DEV_XSTATS_NAME_SIZE) == 0){ 527 if (id != NULL) 528 *id = i; 529 return xs->fn(sw, xs->obj_idx, xs->stat, xs->extra_arg) 530 - xs->reset_value; 531 } 532 } 533 if (id != NULL) 534 *id = (uint32_t)-1; 535 return (uint64_t)-1; 536 } 537 538 static void 539 sw_xstats_reset_range(struct sw_evdev *sw, uint32_t start, uint32_t num) 540 { 541 uint32_t i; 542 for (i = start; i < start + num; i++) { 543 struct sw_xstats_entry *xs = &sw->xstats[i]; 544 if (!xs->reset_allowed) 545 continue; 546 547 uint64_t val = xs->fn(sw, xs->obj_idx, xs->stat, xs->extra_arg) 548 - xs->reset_value; 549 xs->reset_value = val; 550 } 551 } 552 553 static int 554 sw_xstats_reset_queue(struct sw_evdev *sw, uint8_t queue_id, 555 const uint32_t ids[], uint32_t nb_ids) 556 { 557 const uint32_t reset = 1; 558 const uint32_t ret_n_lt_stats = 0; 559 if (ids) { 560 uint32_t nb_reset = sw_xstats_update(sw, 561 RTE_EVENT_DEV_XSTATS_QUEUE, 562 queue_id, ids, NULL, nb_ids, 563 reset, ret_n_lt_stats); 564 return nb_reset == nb_ids ? 0 : -EINVAL; 565 } 566 567 if (ids == NULL) 568 sw_xstats_reset_range(sw, sw->xstats_offset_for_qid[queue_id], 569 sw->xstats_count_per_qid[queue_id]); 570 571 return 0; 572 } 573 574 static int 575 sw_xstats_reset_port(struct sw_evdev *sw, uint8_t port_id, 576 const uint32_t ids[], uint32_t nb_ids) 577 { 578 const uint32_t reset = 1; 579 const uint32_t ret_n_lt_stats = 0; 580 int offset = sw->xstats_offset_for_port[port_id]; 581 int nb_stat = sw->xstats_count_per_port[port_id]; 582 583 if (ids) { 584 uint32_t nb_reset = sw_xstats_update(sw, 585 RTE_EVENT_DEV_XSTATS_PORT, port_id, 586 ids, NULL, nb_ids, 587 reset, ret_n_lt_stats); 588 return nb_reset == nb_ids ? 0 : -EINVAL; 589 } 590 591 sw_xstats_reset_range(sw, offset, nb_stat); 592 return 0; 593 } 594 595 static int 596 sw_xstats_reset_dev(struct sw_evdev *sw, const uint32_t ids[], uint32_t nb_ids) 597 { 598 uint32_t i; 599 if (ids) { 600 for (i = 0; i < nb_ids; i++) { 601 uint32_t id = ids[i]; 602 if (id >= sw->xstats_count_mode_dev) 603 return -EINVAL; 604 sw_xstats_reset_range(sw, id, 1); 605 } 606 } else { 607 for (i = 0; i < sw->xstats_count_mode_dev; i++) 608 sw_xstats_reset_range(sw, i, 1); 609 } 610 611 return 0; 612 } 613 614 int 615 sw_xstats_reset(struct rte_eventdev *dev, 616 enum rte_event_dev_xstats_mode mode, 617 int16_t queue_port_id, 618 const uint32_t ids[], 619 uint32_t nb_ids) 620 { 621 struct sw_evdev *sw = sw_pmd_priv(dev); 622 uint32_t i, err; 623 624 /* handle -1 for queue_port_id here, looping over all ports/queues */ 625 switch (mode) { 626 case RTE_EVENT_DEV_XSTATS_DEVICE: 627 sw_xstats_reset_dev(sw, ids, nb_ids); 628 break; 629 case RTE_EVENT_DEV_XSTATS_PORT: 630 if (queue_port_id == -1) { 631 for (i = 0; i < sw->port_count; i++) { 632 err = sw_xstats_reset_port(sw, i, ids, nb_ids); 633 if (err) 634 return -EINVAL; 635 } 636 } else if (queue_port_id < (int16_t)sw->port_count) 637 sw_xstats_reset_port(sw, queue_port_id, ids, nb_ids); 638 break; 639 case RTE_EVENT_DEV_XSTATS_QUEUE: 640 if (queue_port_id == -1) { 641 for (i = 0; i < sw->qid_count; i++) { 642 err = sw_xstats_reset_queue(sw, i, ids, nb_ids); 643 if (err) 644 return -EINVAL; 645 } 646 } else if (queue_port_id < (int16_t)sw->qid_count) 647 sw_xstats_reset_queue(sw, queue_port_id, ids, nb_ids); 648 break; 649 }; 650 651 return 0; 652 } 653