| 1 | |
| 2 | |
| 3 | * net/core/gen_stats.c
|
| 4 | *
|
| 5 | * Authors: Thomas Graf <[email protected]>
|
| 6 | * Jamal Hadi Salim
|
| 7 | * Alexey Kuznetsov, <[email protected]>
|
| 8 | *
|
| 9 | * See Documentation/networking/gen_stats.rst
|
| 10 |
|
| 11 |
|
| 12 | #include <linux/types.h>
|
| 13 | #include <linux/kernel.h>
|
| 14 | #include <linux/module.h>
|
| 15 | #include <linux/interrupt.h>
|
| 16 | #include <linux/socket.h>
|
| 17 | #include <linux/rtnetlink.h>
|
| 18 | #include <linux/gen_stats.h>
|
| 19 | #include <net/netlink.h>
|
| 20 | #include <net/gen_stats.h>
|
| 21 | #include <net/sch_generic.h>
|
| 22 |
|
| 23 | static inline int
|
| 24 | gnet_stats_copy(struct gnet_dump *d, int type, void *buf, int size, int padattr)
|
| 25 | {
|
| 26 | if (nla_put_64bit(d->skb, type, size, buf, padattr))
|
| 27 | goto nla_put_failure;
|
| 28 | return 0;
|
| 29 |
|
| 30 | nla_put_failure:
|
| 31 | if (d->lock)
|
| 32 | spin_unlock_bh(d->lock);
|
| 33 | kfree(d->xstats);
|
| 34 | d->xstats = NULL;
|
| 35 | d->xstats_len = 0;
|
| 36 | return -1;
|
| 37 | }
|
| 38 |
|
| 39 | |
| 40 | * gnet_stats_start_copy_compat - start dumping procedure in compatibility mode
|
| 41 | * @skb: socket buffer to put statistics TLVs into
|
| 42 | * @type: TLV type for top level statistic TLV
|
| 43 | * @tc_stats_type: TLV type for backward compatibility struct tc_stats TLV
|
| 44 | * @xstats_type: TLV type for backward compatibility xstats TLV
|
| 45 | * @lock: statistics lock
|
| 46 | * @d: dumping handle
|
| 47 | * @padattr: padding attribute
|
| 48 | *
|
| 49 | * Initializes the dumping handle, grabs the statistic lock and appends
|
| 50 | * an empty TLV header to the socket buffer for use a container for all
|
| 51 | * other statistic TLVS.
|
| 52 | *
|
| 53 | * The dumping handle is marked to be in backward compatibility mode telling
|
| 54 | * all gnet_stats_copy_XXX() functions to fill a local copy of struct tc_stats.
|
| 55 | *
|
| 56 | * Returns 0 on success or -1 if the room in the socket buffer was not sufficient.
|
| 57 |
|
| 58 | int
|
| 59 | gnet_stats_start_copy_compat(struct sk_buff *skb, int type, int tc_stats_type,
|
| 60 | int xstats_type, spinlock_t *lock,
|
| 61 | struct gnet_dump *d, int padattr)
|
| 62 | __acquires(lock)
|
| 63 | {
|
| 64 | memset(d, 0, sizeof(*d));
|
| 65 |
|
| 66 | if (type)
|
| 67 | d->tail = (struct nlattr *)skb_tail_pointer(skb);
|
| 68 | d->skb = skb;
|
| 69 | d->compat_tc_stats = tc_stats_type;
|
| 70 | d->compat_xstats = xstats_type;
|
| 71 | d->padattr = padattr;
|
| 72 | if (lock) {
|
| 73 | d->lock = lock;
|
| 74 | spin_lock_bh(lock);
|
| 75 | }
|
| 76 | if (d->tail) {
|
| 77 | int ret = gnet_stats_copy(d, type, NULL, 0, padattr);
|
| 78 |
|
| 79 | |
| 80 | * preceded by a padding attribute, in which case d->tail will
|
| 81 | * end up pointing at the padding instead of the real attribute.
|
| 82 | * Fix this so gnet_stats_finish_copy() adjusts the length of
|
| 83 | * the right attribute.
|
| 84 |
|
| 85 | if (ret == 0 && d->tail->nla_type == padattr)
|
| 86 | d->tail = (struct nlattr *)((char *)d->tail +
|
| 87 | NLA_ALIGN(d->tail->nla_len));
|
| 88 | return ret;
|
| 89 | }
|
| 90 |
|
| 91 | return 0;
|
| 92 | }
|
| 93 | EXPORT_SYMBOL(gnet_stats_start_copy_compat);
|
| 94 |
|
| 95 | |
| 96 | * gnet_stats_start_copy - start dumping procedure in compatibility mode
|
| 97 | * @skb: socket buffer to put statistics TLVs into
|
| 98 | * @type: TLV type for top level statistic TLV
|
| 99 | * @lock: statistics lock
|
| 100 | * @d: dumping handle
|
| 101 | * @padattr: padding attribute
|
| 102 | *
|
| 103 | * Initializes the dumping handle, grabs the statistic lock and appends
|
| 104 | * an empty TLV header to the socket buffer for use a container for all
|
| 105 | * other statistic TLVS.
|
| 106 | *
|
| 107 | * Returns 0 on success or -1 if the room in the socket buffer was not sufficient.
|
| 108 |
|
| 109 | int
|
| 110 | gnet_stats_start_copy(struct sk_buff *skb, int type, spinlock_t *lock,
|
| 111 | struct gnet_dump *d, int padattr)
|
| 112 | {
|
| 113 | return gnet_stats_start_copy_compat(skb, type, 0, 0, lock, d, padattr);
|
| 114 | }
|
| 115 | EXPORT_SYMBOL(gnet_stats_start_copy);
|
| 116 |
|
| 117 |
|
| 118 | void gnet_stats_basic_sync_init(struct gnet_stats_basic_sync *b)
|
| 119 | {
|
| 120 | u64_stats_set(&b->bytes, 0);
|
| 121 | u64_stats_set(&b->packets, 0);
|
| 122 | u64_stats_init(&b->syncp);
|
| 123 | }
|
| 124 | EXPORT_SYMBOL(gnet_stats_basic_sync_init);
|
| 125 |
|
| 126 | static void gnet_stats_add_basic_cpu(struct gnet_stats_basic_sync *bstats,
|
| 127 | const struct gnet_stats_basic_sync __percpu *cpu)
|
| 128 | {
|
| 129 | u64 t_bytes = 0, t_packets = 0;
|
| 130 | int i;
|
| 131 |
|
| 132 | for_each_possible_cpu(i) {
|
| 133 | const struct gnet_stats_basic_sync *bcpu = per_cpu_ptr(cpu, i);
|
| 134 | unsigned int start;
|
| 135 | u64 bytes, packets;
|
| 136 |
|
| 137 | do {
|
| 138 | start = u64_stats_fetch_begin(&bcpu->syncp);
|
| 139 | bytes = u64_stats_read(&bcpu->bytes);
|
| 140 | packets = u64_stats_read(&bcpu->packets);
|
| 141 | } while (u64_stats_fetch_retry(&bcpu->syncp, start));
|
| 142 |
|
| 143 | t_bytes += bytes;
|
| 144 | t_packets += packets;
|
| 145 | }
|
| 146 | _bstats_update(bstats, t_bytes, t_packets);
|
| 147 | }
|
| 148 |
|
| 149 | void gnet_stats_add_basic(struct gnet_stats_basic_sync *bstats,
|
| 150 | const struct gnet_stats_basic_sync __percpu *cpu,
|
| 151 | const struct gnet_stats_basic_sync *b, bool running)
|
| 152 | {
|
| 153 | unsigned int start;
|
| 154 | u64 bytes = 0;
|
| 155 | u64 packets = 0;
|
| 156 |
|
| 157 | WARN_ON_ONCE((cpu || running) && in_hardirq());
|
| 158 |
|
| 159 | if (cpu) {
|
| 160 | gnet_stats_add_basic_cpu(bstats, cpu);
|
| 161 | return;
|
| 162 | }
|
| 163 | do {
|
| 164 | if (running)
|
| 165 | start = u64_stats_fetch_begin(&b->syncp);
|
| 166 | bytes = u64_stats_read(&b->bytes);
|
| 167 | packets = u64_stats_read(&b->packets);
|
| 168 | } while (running && u64_stats_fetch_retry(&b->syncp, start));
|
| 169 |
|
| 170 | _bstats_update(bstats, bytes, packets);
|
| 171 | }
|
| 172 | EXPORT_SYMBOL(gnet_stats_add_basic);
|
| 173 |
|
| 174 | static void gnet_stats_read_basic(u64 *ret_bytes, u64 *ret_packets,
|
| 175 | const struct gnet_stats_basic_sync __percpu *cpu,
|
| 176 | const struct gnet_stats_basic_sync *b, bool running)
|
| 177 | {
|
| 178 | unsigned int start;
|
| 179 |
|
| 180 | if (cpu) {
|
| 181 | u64 t_bytes = 0, t_packets = 0;
|
| 182 | int i;
|
| 183 |
|
| 184 | for_each_possible_cpu(i) {
|
| 185 | const struct gnet_stats_basic_sync *bcpu = per_cpu_ptr(cpu, i);
|
| 186 | u64 bytes, packets;
|
| 187 |
|
| 188 | do {
|
| 189 | start = u64_stats_fetch_begin(&bcpu->syncp);
|
| 190 | bytes = u64_stats_read(&bcpu->bytes);
|
| 191 | packets = u64_stats_read(&bcpu->packets);
|
| 192 | } while (u64_stats_fetch_retry(&bcpu->syncp, start));
|
| 193 |
|
| 194 | t_bytes += bytes;
|
| 195 | t_packets += packets;
|
| 196 | }
|
| 197 | *ret_bytes = t_bytes;
|
| 198 | *ret_packets = t_packets;
|
| 199 | return;
|
| 200 | }
|
| 201 | do {
|
| 202 | if (running)
|
| 203 | start = u64_stats_fetch_begin(&b->syncp);
|
| 204 | *ret_bytes = u64_stats_read(&b->bytes);
|
| 205 | *ret_packets = u64_stats_read(&b->packets);
|
| 206 | } while (running && u64_stats_fetch_retry(&b->syncp, start));
|
| 207 | }
|
| 208 |
|
| 209 | static int
|
| 210 | ___gnet_stats_copy_basic(struct gnet_dump *d,
|
| 211 | const struct gnet_stats_basic_sync __percpu *cpu,
|
| 212 | const struct gnet_stats_basic_sync *b,
|
| 213 | int type, bool running)
|
| 214 | {
|
| 215 | u64 bstats_bytes, bstats_packets;
|
| 216 |
|
| 217 | gnet_stats_read_basic(&bstats_bytes, &bstats_packets, cpu, b, running);
|
| 218 |
|
| 219 | if (d->compat_tc_stats && type == TCA_STATS_BASIC) {
|
| 220 | d->tc_stats.bytes = bstats_bytes;
|
| 221 | d->tc_stats.packets = bstats_packets;
|
| 222 | }
|
| 223 |
|
| 224 | if (d->tail) {
|
| 225 | struct gnet_stats_basic sb;
|
| 226 | int res;
|
| 227 |
|
| 228 | memset(&sb, 0, sizeof(sb));
|
| 229 | sb.bytes = bstats_bytes;
|
| 230 | sb.packets = bstats_packets;
|
| 231 | res = gnet_stats_copy(d, type, &sb, sizeof(sb), TCA_STATS_PAD);
|
| 232 | if (res < 0 || sb.packets == bstats_packets)
|
| 233 | return res;
|
| 234 |
|
| 235 | return gnet_stats_copy(d, TCA_STATS_PKT64, &bstats_packets,
|
| 236 | sizeof(bstats_packets), TCA_STATS_PAD);
|
| 237 | }
|
| 238 | return 0;
|
| 239 | }
|
| 240 |
|
| 241 | |
| 242 | * gnet_stats_copy_basic - copy basic statistics into statistic TLV
|
| 243 | * @d: dumping handle
|
| 244 | * @cpu: copy statistic per cpu
|
| 245 | * @b: basic statistics
|
| 246 | * @running: true if @b represents a running qdisc, thus @b's
|
| 247 | * internal values might change during basic reads.
|
| 248 | * Only used if @cpu is NULL
|
| 249 | *
|
| 250 | * Context: task; must not be run from IRQ or BH contexts
|
| 251 | *
|
| 252 | * Appends the basic statistics to the top level TLV created by
|
| 253 | * gnet_stats_start_copy().
|
| 254 | *
|
| 255 | * Returns 0 on success or -1 with the statistic lock released
|
| 256 | * if the room in the socket buffer was not sufficient.
|
| 257 |
|
| 258 | int
|
| 259 | gnet_stats_copy_basic(struct gnet_dump *d,
|
| 260 | const struct gnet_stats_basic_sync __percpu *cpu,
|
| 261 | const struct gnet_stats_basic_sync *b,
|
| 262 | bool running)
|
| 263 | {
|
| 264 | return ___gnet_stats_copy_basic(d, cpu, b, TCA_STATS_BASIC, running);
|
| 265 | }
|
| 266 | EXPORT_SYMBOL(gnet_stats_copy_basic);
|
| 267 |
|
| 268 | |
| 269 | * gnet_stats_copy_basic_hw - copy basic hw statistics into statistic TLV
|
| 270 | * @d: dumping handle
|
| 271 | * @cpu: copy statistic per cpu
|
| 272 | * @b: basic statistics
|
| 273 | * @running: true if @b represents a running qdisc, thus @b's
|
| 274 | * internal values might change during basic reads.
|
| 275 | * Only used if @cpu is NULL
|
| 276 | *
|
| 277 | * Context: task; must not be run from IRQ or BH contexts
|
| 278 | *
|
| 279 | * Appends the basic statistics to the top level TLV created by
|
| 280 | * gnet_stats_start_copy().
|
| 281 | *
|
| 282 | * Returns 0 on success or -1 with the statistic lock released
|
| 283 | * if the room in the socket buffer was not sufficient.
|
| 284 |
|
| 285 | int
|
| 286 | gnet_stats_copy_basic_hw(struct gnet_dump *d,
|
| 287 | const struct gnet_stats_basic_sync __percpu *cpu,
|
| 288 | const struct gnet_stats_basic_sync *b,
|
| 289 | bool running)
|
| 290 | {
|
| 291 | return ___gnet_stats_copy_basic(d, cpu, b, TCA_STATS_BASIC_HW, running);
|
| 292 | }
|
| 293 | EXPORT_SYMBOL(gnet_stats_copy_basic_hw);
|
| 294 |
|
| 295 | |
| 296 | * gnet_stats_copy_rate_est - copy rate estimator statistics into statistics TLV
|
| 297 | * @d: dumping handle
|
| 298 | * @rate_est: rate estimator
|
| 299 | *
|
| 300 | * Appends the rate estimator statistics to the top level TLV created by
|
| 301 | * gnet_stats_start_copy().
|
| 302 | *
|
| 303 | * Returns 0 on success or -1 with the statistic lock released
|
| 304 | * if the room in the socket buffer was not sufficient.
|
| 305 |
|
| 306 | int
|
| 307 | gnet_stats_copy_rate_est(struct gnet_dump *d,
|
| 308 | struct net_rate_estimator __rcu **rate_est)
|
| 309 | {
|
| 310 | struct gnet_stats_rate_est64 sample;
|
| 311 | struct gnet_stats_rate_est est;
|
| 312 | int res;
|
| 313 |
|
| 314 | if (!gen_estimator_read(rate_est, &sample))
|
| 315 | return 0;
|
| 316 | est.bps = min_t(u64, UINT_MAX, sample.bps);
|
| 317 |
|
| 318 | est.pps = sample.pps;
|
| 319 |
|
| 320 | if (d->compat_tc_stats) {
|
| 321 | d->tc_stats.bps = est.bps;
|
| 322 | d->tc_stats.pps = est.pps;
|
| 323 | }
|
| 324 |
|
| 325 | if (d->tail) {
|
| 326 | res = gnet_stats_copy(d, TCA_STATS_RATE_EST, &est, sizeof(est),
|
| 327 | TCA_STATS_PAD);
|
| 328 | if (res < 0 || est.bps == sample.bps)
|
| 329 | return res;
|
| 330 |
|
| 331 | return gnet_stats_copy(d, TCA_STATS_RATE_EST64, &sample,
|
| 332 | sizeof(sample), TCA_STATS_PAD);
|
| 333 | }
|
| 334 |
|
| 335 | return 0;
|
| 336 | }
|
| 337 | EXPORT_SYMBOL(gnet_stats_copy_rate_est);
|
| 338 |
|
| 339 | static void gnet_stats_add_queue_cpu(struct gnet_stats_queue *qstats,
|
| 340 | const struct gnet_stats_queue __percpu *q)
|
| 341 | {
|
| 342 | int i;
|
| 343 |
|
| 344 | for_each_possible_cpu(i) {
|
| 345 | const struct gnet_stats_queue *qcpu = per_cpu_ptr(q, i);
|
| 346 |
|
| 347 | qstats->qlen += READ_ONCE(qcpu->qlen);
|
| 348 | qstats->backlog += READ_ONCE(qcpu->backlog);
|
| 349 | qstats->drops += READ_ONCE(qcpu->drops);
|
| 350 | qstats->requeues += READ_ONCE(qcpu->requeues);
|
| 351 | qstats->overlimits += READ_ONCE(qcpu->overlimits);
|
| 352 | }
|
| 353 | }
|
| 354 |
|
| 355 | void gnet_stats_add_queue(struct gnet_stats_queue *qstats,
|
| 356 | const struct gnet_stats_queue __percpu *cpu,
|
| 357 | const struct gnet_stats_queue *q)
|
| 358 | {
|
| 359 | if (cpu) {
|
| 360 | gnet_stats_add_queue_cpu(qstats, cpu);
|
| 361 | } else {
|
| 362 | qstats->qlen += READ_ONCE(q->qlen);
|
| 363 | qstats->backlog += READ_ONCE(q->backlog);
|
| 364 | qstats->drops += READ_ONCE(q->drops);
|
| 365 | qstats->requeues += READ_ONCE(q->requeues);
|
| 366 | qstats->overlimits += READ_ONCE(q->overlimits);
|
| 367 | }
|
| 368 | }
|
| 369 | EXPORT_SYMBOL(gnet_stats_add_queue);
|
| 370 |
|
| 371 | |
| 372 | * gnet_stats_copy_queue - copy queue statistics into statistics TLV
|
| 373 | * @d: dumping handle
|
| 374 | * @cpu_q: per cpu queue statistics
|
| 375 | * @q: queue statistics
|
| 376 | * @qlen: queue length statistics
|
| 377 | *
|
| 378 | * Appends the queue statistics to the top level TLV created by
|
| 379 | * gnet_stats_start_copy(). Using per cpu queue statistics if
|
| 380 | * they are available.
|
| 381 | *
|
| 382 | * Returns 0 on success or -1 with the statistic lock released
|
| 383 | * if the room in the socket buffer was not sufficient.
|
| 384 |
|
| 385 | int
|
| 386 | gnet_stats_copy_queue(struct gnet_dump *d,
|
| 387 | const struct gnet_stats_queue __percpu *cpu_q,
|
| 388 | const struct gnet_stats_queue *q, __u32 qlen)
|
| 389 | {
|
| 390 | struct gnet_stats_queue qstats = {0};
|
| 391 |
|
| 392 | gnet_stats_add_queue(&qstats, cpu_q, q);
|
| 393 | qstats.qlen = qlen;
|
| 394 |
|
| 395 | if (d->compat_tc_stats) {
|
| 396 | d->tc_stats.drops = qstats.drops;
|
| 397 | d->tc_stats.qlen = qstats.qlen;
|
| 398 | d->tc_stats.backlog = qstats.backlog;
|
| 399 | d->tc_stats.overlimits = qstats.overlimits;
|
| 400 | }
|
| 401 |
|
| 402 | if (d->tail)
|
| 403 | return gnet_stats_copy(d, TCA_STATS_QUEUE,
|
| 404 | &qstats, sizeof(qstats),
|
| 405 | TCA_STATS_PAD);
|
| 406 |
|
| 407 | return 0;
|
| 408 | }
|
| 409 | EXPORT_SYMBOL(gnet_stats_copy_queue);
|
| 410 |
|
| 411 | |
| 412 | * gnet_stats_copy_app - copy application specific statistics into statistics TLV
|
| 413 | * @d: dumping handle
|
| 414 | * @st: application specific statistics data
|
| 415 | * @len: length of data
|
| 416 | *
|
| 417 | * Appends the application specific statistics to the top level TLV created by
|
| 418 | * gnet_stats_start_copy() and remembers the data for XSTATS if the dumping
|
| 419 | * handle is in backward compatibility mode.
|
| 420 | *
|
| 421 | * Returns 0 on success or -1 with the statistic lock released
|
| 422 | * if the room in the socket buffer was not sufficient.
|
| 423 |
|
| 424 | int
|
| 425 | gnet_stats_copy_app(struct gnet_dump *d, void *st, int len)
|
| 426 | {
|
| 427 | if (d->compat_xstats) {
|
| 428 | d->xstats = kmemdup(st, len, GFP_ATOMIC);
|
| 429 | if (!d->xstats)
|
| 430 | goto err_out;
|
| 431 | d->xstats_len = len;
|
| 432 | }
|
| 433 |
|
| 434 | if (d->tail)
|
| 435 | return gnet_stats_copy(d, TCA_STATS_APP, st, len,
|
| 436 | TCA_STATS_PAD);
|
| 437 |
|
| 438 | return 0;
|
| 439 |
|
| 440 | err_out:
|
| 441 | if (d->lock)
|
| 442 | spin_unlock_bh(d->lock);
|
| 443 | d->xstats_len = 0;
|
| 444 | return -1;
|
| 445 | }
|
| 446 | EXPORT_SYMBOL(gnet_stats_copy_app);
|
| 447 |
|
| 448 | |
| 449 | * gnet_stats_finish_copy - finish dumping procedure
|
| 450 | * @d: dumping handle
|
| 451 | *
|
| 452 | * Corrects the length of the top level TLV to include all TLVs added
|
| 453 | * by gnet_stats_copy_XXX() calls. Adds the backward compatibility TLVs
|
| 454 | * if gnet_stats_start_copy_compat() was used and releases the statistics
|
| 455 | * lock.
|
| 456 | *
|
| 457 | * Returns 0 on success or -1 with the statistic lock released
|
| 458 | * if the room in the socket buffer was not sufficient.
|
| 459 |
|
| 460 | int
|
| 461 | gnet_stats_finish_copy(struct gnet_dump *d)
|
| 462 | {
|
| 463 | if (d->tail)
|
| 464 | d->tail->nla_len = skb_tail_pointer(d->skb) - (u8 *)d->tail;
|
| 465 |
|
| 466 | if (d->compat_tc_stats)
|
| 467 | if (gnet_stats_copy(d, d->compat_tc_stats, &d->tc_stats,
|
| 468 | sizeof(d->tc_stats), d->padattr) < 0)
|
| 469 | return -1;
|
| 470 |
|
| 471 | if (d->compat_xstats && d->xstats) {
|
| 472 | if (gnet_stats_copy(d, d->compat_xstats, d->xstats,
|
| 473 | d->xstats_len, d->padattr) < 0)
|
| 474 | return -1;
|
| 475 | }
|
| 476 |
|
| 477 | if (d->lock)
|
| 478 | spin_unlock_bh(d->lock);
|
| 479 | kfree(d->xstats);
|
| 480 | d->xstats = NULL;
|
| 481 | d->xstats_len = 0;
|
| 482 | return 0;
|
| 483 | }
|
| 484 | EXPORT_SYMBOL(gnet_stats_finish_copy);
|