| 1 |
/* |
| 2 |
* ircd-hybrid: an advanced Internet Relay Chat Daemon(ircd). |
| 3 |
* |
| 4 |
* Copyright (C) 2000 Kevin L. Mitchell <klmitch@mit.edu> |
| 5 |
* Copyright (C) 2013 by the Hybrid Development Team. |
| 6 |
* |
| 7 |
* This program is free software; you can redistribute it and/or modify |
| 8 |
* it under the terms of the GNU General Public License as published by |
| 9 |
* the Free Software Foundation; either version 2 of the License, or |
| 10 |
* (at your option) any later version. |
| 11 |
* |
| 12 |
* This program is distributed in the hope that it will be useful, |
| 13 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 |
* GNU General Public License for more details. |
| 16 |
* |
| 17 |
* You should have received a copy of the GNU General Public License |
| 18 |
* along with this program; if not, write to the Free Software |
| 19 |
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
| 20 |
* USA |
| 21 |
*/ |
| 22 |
|
| 23 |
/*! \file msgq.c |
| 24 |
* \brief Outbound message queue implementation. |
| 25 |
* \version $Id: msgq.c 2386 2013-07-06 20:47:09Z michael $ |
| 26 |
*/ |
| 27 |
|
| 28 |
#include "stdinc.h" |
| 29 |
#include "msgq.h" |
| 30 |
#include "list.h" |
| 31 |
#include "log.h" |
| 32 |
#include "send.h" |
| 33 |
#include "memory.h" |
| 34 |
#include "restart.h" |
| 35 |
#include "client.h" |
| 36 |
#include "ircd.h" |
| 37 |
#include "numeric.h" |
| 38 |
#include "xsnprintf.h" |
| 39 |
|
| 40 |
|
| 41 |
#include <sys/uio.h> |
| 42 |
|
| 43 |
#define FEAT_BUFFERPOOL 27000000 /* XXX */ |
| 44 |
|
| 45 |
#define MB_BASE_SHIFT 5 /**< Log2 of smallest message body to allocate. */ |
| 46 |
#define MB_MAX_SHIFT 9 /**< Log2 of largest message body to allocate. */ |
| 47 |
|
| 48 |
/** Buffer for a single message. */ |
| 49 |
struct MsgBuf |
| 50 |
{ |
| 51 |
struct MsgBuf *next; /**< next msg in global queue */ |
| 52 |
struct MsgBuf **prev_p; /**< what points to us in linked list */ |
| 53 |
struct MsgBuf *real; /**< the actual MsgBuf we're attaching */ |
| 54 |
unsigned int ref; /**< reference count */ |
| 55 |
unsigned int length; /**< length of message */ |
| 56 |
unsigned int power; /**< size of buffer (power of 2) */ |
| 57 |
char msg[1]; /**< the message */ |
| 58 |
}; |
| 59 |
|
| 60 |
/** Return allocated length of the buffer of \a buf. */ |
| 61 |
#define bufsize(buf) (1 << (buf)->power) |
| 62 |
|
| 63 |
/** Message body for a particular destination. */ |
| 64 |
struct Msg |
| 65 |
{ |
| 66 |
struct Msg *next; /**< next msg */ |
| 67 |
unsigned int sent; /**< bytes in msg that have already been sent */ |
| 68 |
struct MsgBuf *msg; /**< actual message in queue */ |
| 69 |
}; |
| 70 |
|
| 71 |
/** Statistics tracking for message sizes. */ |
| 72 |
struct MsgSizes |
| 73 |
{ |
| 74 |
unsigned int msgs; /**< total number of messages */ |
| 75 |
unsigned int sizes[IRCD_BUFSIZE]; /**< histogram of message sizes */ |
| 76 |
}; |
| 77 |
|
| 78 |
/** Global tracking data for message buffers. */ |
| 79 |
static struct |
| 80 |
{ |
| 81 |
struct MsgBuf *msglist; /**< list of in-use MsgBuf's */ |
| 82 |
|
| 83 |
struct |
| 84 |
{ |
| 85 |
unsigned int alloc; /**< number of Msg's allocated */ |
| 86 |
unsigned int used; /**< number of Msg's in use */ |
| 87 |
struct Msg *free; /**< freelist of Msg's */ |
| 88 |
} msgs; /**< tracking info for Msg structs */ |
| 89 |
|
| 90 |
size_t tot_bufsize; /**< total amount of memory in buffers */ |
| 91 |
|
| 92 |
/** Array of MsgBuf information, one entry for each used bucket size. */ |
| 93 |
struct |
| 94 |
{ |
| 95 |
unsigned int alloc; /**< total MsgBuf's of this size */ |
| 96 |
unsigned int used; /**< number of MsgBuf's of this size in use */ |
| 97 |
struct MsgBuf *free; /**< list of free MsgBuf's */ |
| 98 |
} msgBufs[MB_MAX_SHIFT - MB_BASE_SHIFT + 1]; |
| 99 |
|
| 100 |
struct MsgSizes sizes; /**< histogram of message sizes */ |
| 101 |
} MQData; |
| 102 |
|
| 103 |
/* |
| 104 |
* This routine is used to remove a certain amount of data from a given |
| 105 |
* queue and release the Msg (and MsgBuf) structure if needed |
| 106 |
*/ |
| 107 |
/** Remove some data from a list within a message queue. |
| 108 |
* @param[in,out] mq Message queue to remove from. |
| 109 |
* @param[in,out] qlist Particular list within queue to remove from. |
| 110 |
* @param[in,out] length_p Number of bytes left to remove. |
| 111 |
*/ |
| 112 |
static void |
| 113 |
msgq_delmsg(struct MsgQ *mq, struct MsgQList *qlist, unsigned int *length_p) |
| 114 |
{ |
| 115 |
struct Msg *m; |
| 116 |
unsigned int msglen; |
| 117 |
|
| 118 |
assert(mq); |
| 119 |
assert(qlist); |
| 120 |
assert(qlist->head); |
| 121 |
assert(length_p); |
| 122 |
|
| 123 |
m = qlist->head; /* find the msg we're deleting from */ |
| 124 |
|
| 125 |
msglen = m->msg->length - m->sent; /* calculate how much is left */ |
| 126 |
|
| 127 |
if (*length_p >= msglen) |
| 128 |
{ |
| 129 |
/* deleted it all? */ |
| 130 |
mq->length -= msglen; /* decrement length */ |
| 131 |
mq->count--; /* decrement the message count */ |
| 132 |
*length_p -= msglen; |
| 133 |
|
| 134 |
msgq_clean(m->msg); /* free up the struct MsgBuf */ |
| 135 |
m->msg = 0; /* don't let it point anywhere nasty, please */ |
| 136 |
|
| 137 |
if (qlist->head == qlist->tail) /* figure out if we emptied the queue */ |
| 138 |
qlist->head = qlist->tail = 0; |
| 139 |
else |
| 140 |
qlist->head = m->next; /* just shift the list down some */ |
| 141 |
|
| 142 |
MQData.msgs.used--; /* struct Msg is not in use anymore */ |
| 143 |
|
| 144 |
m->next = MQData.msgs.free; /* throw it onto the free list */ |
| 145 |
MQData.msgs.free = m; |
| 146 |
} |
| 147 |
else |
| 148 |
{ |
| 149 |
mq->length -= *length_p; /* decrement queue length */ |
| 150 |
m->sent += *length_p; /* this much of the message has been sent */ |
| 151 |
*length_p = 0; /* we've dealt with it all */ |
| 152 |
} |
| 153 |
} |
| 154 |
|
| 155 |
/** Initialize \a mq. |
| 156 |
* @param[in] mq MsgQ to initialize. |
| 157 |
*/ |
| 158 |
void |
| 159 |
msgq_init(struct MsgQ *mq) |
| 160 |
{ |
| 161 |
assert(mq); |
| 162 |
|
| 163 |
mq->length = 0; |
| 164 |
mq->count = 0; |
| 165 |
mq->queue.head = 0; |
| 166 |
mq->queue.tail = 0; |
| 167 |
mq->prio.head = 0; |
| 168 |
mq->prio.tail = 0; |
| 169 |
} |
| 170 |
|
| 171 |
/** Delete bytes from the front of a message queue. |
| 172 |
* @param[in] mq Queue to drop data from. |
| 173 |
* @param[in] length Number of bytes to drop. |
| 174 |
*/ |
| 175 |
void |
| 176 |
msgq_delete(struct MsgQ *mq, unsigned int length) |
| 177 |
{ |
| 178 |
assert(0 != mq); |
| 179 |
|
| 180 |
while (length > 0) |
| 181 |
{ |
| 182 |
if (mq->queue.head && mq->queue.head->sent > 0) /* partial msg on norm q */ |
| 183 |
msgq_delmsg(mq, &mq->queue, &length); |
| 184 |
else if (mq->prio.head) /* message (partial or complete) on prio queue */ |
| 185 |
msgq_delmsg(mq, &mq->prio, &length); |
| 186 |
else if (mq->queue.head) /* message on normal queue */ |
| 187 |
msgq_delmsg(mq, &mq->queue, &length); |
| 188 |
else |
| 189 |
break; |
| 190 |
} |
| 191 |
} |
| 192 |
|
| 193 |
/** Map data from a message queue to an I/O vector. |
| 194 |
* @param[in] mq Message queue to send from. |
| 195 |
* @param[out] iov Output vector. |
| 196 |
* @param[in] count Number of elements in \a iov. |
| 197 |
* @param[out] len Number of bytes mapped from \a mq to \a iov. |
| 198 |
* @return Number of elements filled in \a iov. |
| 199 |
*/ |
| 200 |
int |
| 201 |
msgq_mapiov(const struct MsgQ *mq, struct iovec *iov, int count, |
| 202 |
unsigned int *len) |
| 203 |
{ |
| 204 |
struct Msg *queue; |
| 205 |
struct Msg *prio; |
| 206 |
int i = 0; |
| 207 |
|
| 208 |
assert(mq); |
| 209 |
assert(iov); |
| 210 |
assert(count); |
| 211 |
assert(len); |
| 212 |
|
| 213 |
if (mq->length <= 0) /* no data to map */ |
| 214 |
return 0; |
| 215 |
|
| 216 |
if (mq->queue.head && mq->queue.head->sent > 0) |
| 217 |
{ |
| 218 |
/* partial msg on norm q */ |
| 219 |
iov[i].iov_base = mq->queue.head->msg->msg + mq->queue.head->sent; |
| 220 |
iov[i].iov_len = mq->queue.head->msg->length - mq->queue.head->sent; |
| 221 |
*len += iov[i].iov_len; |
| 222 |
|
| 223 |
queue = mq->queue.head->next; /* where we start later... */ |
| 224 |
|
| 225 |
i++; /* filled an iovec... */ |
| 226 |
|
| 227 |
if (!--count) /* check for space */ |
| 228 |
return i; |
| 229 |
} |
| 230 |
else |
| 231 |
queue = mq->queue.head; /* start at head of queue */ |
| 232 |
|
| 233 |
if (mq->prio.head && mq->prio.head->sent > 0) |
| 234 |
{ |
| 235 |
/* partial msg on prio q */ |
| 236 |
iov[i].iov_base = mq->prio.head->msg->msg + mq->prio.head->sent; |
| 237 |
iov[i].iov_len = mq->prio.head->msg->length - mq->prio.head->sent; |
| 238 |
*len += iov[i].iov_len; |
| 239 |
|
| 240 |
prio = mq->prio.head->next; /* where we start later... */ |
| 241 |
|
| 242 |
i++; /* filled an iovec... */ |
| 243 |
|
| 244 |
if (!--count) /* check for space */ |
| 245 |
return i; |
| 246 |
} |
| 247 |
else |
| 248 |
prio = mq->prio.head; /* start at head of prio */ |
| 249 |
|
| 250 |
for (; prio; prio = prio->next) |
| 251 |
{ |
| 252 |
/* go through prio queue */ |
| 253 |
iov[i].iov_base = prio->msg->msg; /* store message */ |
| 254 |
iov[i].iov_len = prio->msg->length; |
| 255 |
*len += iov[i].iov_len; |
| 256 |
|
| 257 |
i++; /* filled an iovec... */ |
| 258 |
|
| 259 |
if (!--count) /* check for space */ |
| 260 |
return i; |
| 261 |
} |
| 262 |
|
| 263 |
for (; queue; queue = queue->next) |
| 264 |
{ |
| 265 |
/* go through normal queue */ |
| 266 |
iov[i].iov_base = queue->msg->msg; |
| 267 |
iov[i].iov_len = queue->msg->length; |
| 268 |
*len += iov[i].iov_len; |
| 269 |
|
| 270 |
i++; /* filled an iovec... */ |
| 271 |
|
| 272 |
if (!--count) /* check for space */ |
| 273 |
return i; |
| 274 |
} |
| 275 |
|
| 276 |
return i; |
| 277 |
} |
| 278 |
|
| 279 |
/** Allocate a message buffer large enough to hold \a length bytes. |
| 280 |
* TODO: \a in_mb needs better documentation. |
| 281 |
* @param[in] in_mb Some other message buffer(?). |
| 282 |
* @param[in] length Number of bytes of space to reserve in output. |
| 283 |
* @return Pointer to some usable message buffer. |
| 284 |
*/ |
| 285 |
static struct MsgBuf * |
| 286 |
msgq_alloc(struct MsgBuf *in_mb, unsigned int length) |
| 287 |
{ |
| 288 |
struct MsgBuf *mb; |
| 289 |
unsigned int power; |
| 290 |
|
| 291 |
/* Find the power of two size that will accommodate the message */ |
| 292 |
for (power = MB_BASE_SHIFT; power < MB_MAX_SHIFT + 1; power++) |
| 293 |
if ((length - 1) >> power == 0) |
| 294 |
break; |
| 295 |
|
| 296 |
assert((1 << power) >= length); |
| 297 |
assert((1 << power) <= 512); |
| 298 |
length = 1 << power; /* reset the length */ |
| 299 |
|
| 300 |
/* If the message needs a buffer of exactly the existing size, just use it */ |
| 301 |
if (in_mb && in_mb->power == power) |
| 302 |
{ |
| 303 |
in_mb->real = in_mb; /* real buffer is this buffer */ |
| 304 |
return in_mb; |
| 305 |
} |
| 306 |
|
| 307 |
/* Try popping one off the freelist first */ |
| 308 |
if ((mb = MQData.msgBufs[power - MB_BASE_SHIFT].free)) |
| 309 |
MQData.msgBufs[power - MB_BASE_SHIFT].free = mb->next; |
| 310 |
else if (MQData.tot_bufsize < FEAT_BUFFERPOOL) |
| 311 |
{ |
| 312 |
/* Allocate another if we won't bust the BUFFERPOOL */ |
| 313 |
ilog(LOG_TYPE_DEBUG, "Allocating MsgBuf of length %d (total size %zu)", |
| 314 |
length, sizeof(struct MsgBuf) + length); |
| 315 |
|
| 316 |
mb = MyMalloc(sizeof(struct MsgBuf) + length); |
| 317 |
MQData.msgBufs[power - MB_BASE_SHIFT].alloc++; |
| 318 |
mb->power = power; /* remember size */ |
| 319 |
MQData.tot_bufsize += length; |
| 320 |
} |
| 321 |
|
| 322 |
if (mb) |
| 323 |
{ |
| 324 |
MQData.msgBufs[power - MB_BASE_SHIFT].used++; /* how many are we using? */ |
| 325 |
|
| 326 |
mb->real = 0; /* essential initializations */ |
| 327 |
mb->ref = 1; |
| 328 |
|
| 329 |
if (in_mb) /* remember who's the *real* buffer */ |
| 330 |
in_mb->real = mb; |
| 331 |
} |
| 332 |
else if (in_mb) /* just use the input buffer */ |
| 333 |
mb = in_mb->real = in_mb; |
| 334 |
|
| 335 |
return mb; /* return the buffer */ |
| 336 |
} |
| 337 |
|
| 338 |
/** Deallocate unused message buffers. |
| 339 |
*/ |
| 340 |
static void |
| 341 |
msgq_clear_freembs(void) |
| 342 |
{ |
| 343 |
struct MsgBuf *mb; |
| 344 |
int i; |
| 345 |
|
| 346 |
/* Walk through the various size classes */ |
| 347 |
for (i = MB_BASE_SHIFT; i < MB_MAX_SHIFT + 1; i++) |
| 348 |
{ |
| 349 |
/* walk down the free list */ |
| 350 |
while ((mb = MQData.msgBufs[i - MB_BASE_SHIFT].free)) |
| 351 |
{ |
| 352 |
MQData.msgBufs[i - MB_BASE_SHIFT].free = mb->next; /* shift free list */ |
| 353 |
MQData.msgBufs[i - MB_BASE_SHIFT].alloc--; /* reduce allocation count */ |
| 354 |
MQData.tot_bufsize -= 1 << i; /* reduce total buffer allocation count */ |
| 355 |
MyFree(mb); /* and free the buffer */ |
| 356 |
} |
| 357 |
} |
| 358 |
} |
| 359 |
|
| 360 |
/** Format a message buffer for a client from a format string. |
| 361 |
* @param[in] dest %Client that receives the data (may be NULL). |
| 362 |
* @param[in] format Format string for message. |
| 363 |
* @param[in] vl Argument list for \a format. |
| 364 |
* @return Allocated MsgBuf. |
| 365 |
*/ |
| 366 |
struct MsgBuf * |
| 367 |
msgq_vmake(struct Client *dest, const char *format, va_list vl) |
| 368 |
{ |
| 369 |
struct MsgBuf *mb; |
| 370 |
|
| 371 |
assert(format); |
| 372 |
|
| 373 |
if (!(mb = msgq_alloc(0, IRCD_BUFSIZE))) |
| 374 |
{ |
| 375 |
/* |
| 376 |
* from "Married With Children" episode were Al bought a REAL toilet |
| 377 |
* on the black market because he was tired of the wimpy water |
| 378 |
* conserving toilets they make these days --Bleep |
| 379 |
*/ |
| 380 |
/* |
| 381 |
* Apparently this doesn't work, the server _has_ to |
| 382 |
* dump a few clients to handle the load. A fully loaded |
| 383 |
* server cannot handle a net break without dumping some |
| 384 |
* clients. If we flush the connections here under a full |
| 385 |
* load we may end up starving the kernel for mbufs and |
| 386 |
* crash the machine |
| 387 |
*/ |
| 388 |
/* |
| 389 |
* attempt to recover from buffer starvation before |
| 390 |
* bailing this may help servers running out of memory |
| 391 |
*/ |
| 392 |
send_queued_all(); |
| 393 |
mb = msgq_alloc(0, IRCD_BUFSIZE); |
| 394 |
|
| 395 |
if (!mb) |
| 396 |
{ /* OK, try clearing the buffer free list */ |
| 397 |
msgq_clear_freembs(); |
| 398 |
mb = msgq_alloc(0, IRCD_BUFSIZE); |
| 399 |
} |
| 400 |
#if 0 |
| 401 |
if (!mb) |
| 402 |
{ |
| 403 |
/* OK, try killing a client */ |
| 404 |
kill_highest_sendq(0); /* Don't kill any server connections */ |
| 405 |
msgq_clear_freembs(); /* Release whatever was just freelisted */ |
| 406 |
mb = msgq_alloc(0, IRCD_BUFSIZE); |
| 407 |
} |
| 408 |
|
| 409 |
if (!mb) |
| 410 |
{ |
| 411 |
kill_highest_sendq(1); /* Try killing a server connection now */ |
| 412 |
msgq_clear_freembs(); /* Clear freelist again */ |
| 413 |
mb = msgq_alloc(0, IRCD_BUFSIZE); |
| 414 |
} |
| 415 |
#endif |
| 416 |
if (!mb) /* AIEEEE! */ |
| 417 |
restart("Unable to allocate buffers!"); |
| 418 |
} |
| 419 |
|
| 420 |
mb->next = MQData.msglist; /* initialize the msgbuf */ |
| 421 |
mb->prev_p = &MQData.msglist; |
| 422 |
|
| 423 |
/* fill the buffer */ |
| 424 |
mb->length = xvsnprintf(dest, mb->msg, bufsize(mb) - 1, format, vl); |
| 425 |
|
| 426 |
if (mb->length > bufsize(mb) - 2) |
| 427 |
mb->length = bufsize(mb) - 2; |
| 428 |
|
| 429 |
mb->msg[mb->length++] = '\r'; /* add \r\n to buffer */ |
| 430 |
mb->msg[mb->length++] = '\n'; |
| 431 |
mb->msg[mb->length] = '\0'; /* not strictly necessary */ |
| 432 |
|
| 433 |
assert(mb->length <= bufsize(mb)); |
| 434 |
|
| 435 |
if (MQData.msglist) /* link it into the list */ |
| 436 |
MQData.msglist->prev_p = &mb->next; |
| 437 |
MQData.msglist = mb; |
| 438 |
|
| 439 |
return mb; |
| 440 |
} |
| 441 |
|
| 442 |
/** Format a message buffer for a client from a format string. |
| 443 |
* @param[in] dest %Client that receives the data (may be NULL). |
| 444 |
* @param[in] format Format string for message. |
| 445 |
* @return Allocated MsgBuf. |
| 446 |
*/ |
| 447 |
struct MsgBuf * |
| 448 |
msgq_make(struct Client *dest, const char *format, ...) |
| 449 |
{ |
| 450 |
va_list vl; |
| 451 |
struct MsgBuf *mb; |
| 452 |
|
| 453 |
va_start(vl, format); |
| 454 |
mb = msgq_vmake(dest, format, vl); |
| 455 |
va_end(vl); |
| 456 |
|
| 457 |
return mb; |
| 458 |
} |
| 459 |
|
| 460 |
/** Append text to an existing message buffer. |
| 461 |
* @param[in] dest %Client for whom to format the message. |
| 462 |
* @param[in] mb Message buffer to append to. |
| 463 |
* @param[in] format Format string of what to append. |
| 464 |
*/ |
| 465 |
void |
| 466 |
msgq_append(struct Client *dest, struct MsgBuf *mb, const char *format, ...) |
| 467 |
{ |
| 468 |
va_list vl; |
| 469 |
|
| 470 |
assert(mb); |
| 471 |
assert(format); |
| 472 |
assert(0 == mb->real); |
| 473 |
|
| 474 |
assert(2 < mb->length); |
| 475 |
assert(bufsize(mb) >= mb->length); |
| 476 |
|
| 477 |
mb->length -= 2; /* back up to before \r\n */ |
| 478 |
|
| 479 |
va_start(vl, format); /* append to the buffer */ |
| 480 |
|
| 481 |
mb->length += xvsnprintf(dest, mb->msg + mb->length, |
| 482 |
bufsize(mb) - mb->length - 1, format, vl); |
| 483 |
|
| 484 |
va_end(vl); |
| 485 |
|
| 486 |
if (mb->length > bufsize(mb) - 2) |
| 487 |
mb->length = bufsize(mb) - 2; |
| 488 |
|
| 489 |
mb->msg[mb->length++] = '\r'; /* add \r\n to buffer */ |
| 490 |
mb->msg[mb->length++] = '\n'; |
| 491 |
mb->msg[mb->length] = '\0'; /* not strictly necessary */ |
| 492 |
|
| 493 |
assert(mb->length <= bufsize(mb)); |
| 494 |
} |
| 495 |
|
| 496 |
/** Decrement the reference count on \a mb, freeing it if needed. |
| 497 |
* @param[in] mb MsgBuf to release. |
| 498 |
*/ |
| 499 |
void |
| 500 |
msgq_clean(struct MsgBuf *mb) |
| 501 |
{ |
| 502 |
assert(mb); |
| 503 |
assert(0 < mb->ref); |
| 504 |
|
| 505 |
if (!--mb->ref) |
| 506 |
{ |
| 507 |
/* deallocate the message */ |
| 508 |
if (mb->prev_p) |
| 509 |
{ |
| 510 |
*mb->prev_p = mb->next; /* clip it out of active MsgBuf's list */ |
| 511 |
|
| 512 |
if (mb->next) |
| 513 |
mb->next->prev_p = mb->prev_p; |
| 514 |
} |
| 515 |
|
| 516 |
if (mb->real && mb->real != mb) /* clean up the real buffer */ |
| 517 |
msgq_clean(mb->real); |
| 518 |
|
| 519 |
mb->next = MQData.msgBufs[mb->power - MB_BASE_SHIFT].free; |
| 520 |
MQData.msgBufs[mb->power - MB_BASE_SHIFT].free = mb; |
| 521 |
MQData.msgBufs[mb->power - MB_BASE_SHIFT].used--; |
| 522 |
|
| 523 |
mb->prev_p = 0; |
| 524 |
} |
| 525 |
} |
| 526 |
|
| 527 |
/** Append a message to a peer's message queue. |
| 528 |
* @param[in] mq Message queue to append to. |
| 529 |
* @param[in] mb Message to append. |
| 530 |
* @param[in] prio If non-zero, use the high-priority (lag-busting) message list; else use the normal list. |
| 531 |
*/ |
| 532 |
void |
| 533 |
msgq_add(struct MsgQ *mq, struct MsgBuf *mb, int prio) |
| 534 |
{ |
| 535 |
struct MsgQList *qlist; |
| 536 |
struct Msg *msg; |
| 537 |
|
| 538 |
assert(mq); |
| 539 |
assert(mb); |
| 540 |
assert(0 < mb->ref); |
| 541 |
assert(0 < mb->length); |
| 542 |
|
| 543 |
ilog(LOG_TYPE_DEBUG, "Adding buffer %p [%.*s] length %u to %s queue", mb, |
| 544 |
mb->length - 2, mb->msg, mb->length, prio ? "priority" : "normal"); |
| 545 |
|
| 546 |
qlist = prio ? &mq->prio : &mq->queue; |
| 547 |
|
| 548 |
if (!(msg = MQData.msgs.free)) |
| 549 |
{ |
| 550 |
/* do I need to allocate one? */ |
| 551 |
msg = MyMalloc(sizeof(struct Msg)); |
| 552 |
MQData.msgs.alloc++; /* we allocated another */ |
| 553 |
} |
| 554 |
else /* shift the free list */ |
| 555 |
MQData.msgs.free = MQData.msgs.free->next; |
| 556 |
|
| 557 |
MQData.msgs.used++; /* we're using another */ |
| 558 |
|
| 559 |
msg->next = 0; /* initialize the msg */ |
| 560 |
msg->sent = 0; |
| 561 |
|
| 562 |
/* Get the real buffer, allocating one if necessary */ |
| 563 |
if (!mb->real) |
| 564 |
{ |
| 565 |
struct MsgBuf *tmp; |
| 566 |
|
| 567 |
MQData.sizes.msgs++; /* update histogram counts */ |
| 568 |
MQData.sizes.sizes[mb->length - 1]++; |
| 569 |
|
| 570 |
tmp = msgq_alloc(mb, mb->length); /* allocate a close-fitting buffer */ |
| 571 |
|
| 572 |
if (tmp != mb) |
| 573 |
{ |
| 574 |
/* OK, prepare the new "real" buffer */ |
| 575 |
ilog(LOG_TYPE_DEBUG, "Copying old buffer %p [%.*s] length %u into new " |
| 576 |
"buffer %p size %u", mb, mb->length - 2, mb->msg, mb->length, |
| 577 |
tmp, bufsize(tmp)); |
| 578 |
memcpy(tmp->msg, mb->msg, mb->length + 1); /* copy string over */ |
| 579 |
tmp->length = mb->length; |
| 580 |
|
| 581 |
tmp->next = mb->next; /* replace it in the list, now */ |
| 582 |
|
| 583 |
if (tmp->next) |
| 584 |
tmp->next->prev_p = &tmp->next; |
| 585 |
tmp->prev_p = mb->prev_p; |
| 586 |
*tmp->prev_p = tmp; |
| 587 |
|
| 588 |
mb->next = 0; /* this one's no longer in the list */ |
| 589 |
mb->prev_p = 0; |
| 590 |
} |
| 591 |
} |
| 592 |
|
| 593 |
mb = mb->real; /* work with the real buffer */ |
| 594 |
mb->ref++; /* increment the ref count on the buffer */ |
| 595 |
|
| 596 |
msg->msg = mb; /* point at the real message buffer now */ |
| 597 |
|
| 598 |
if (!qlist->head) /* queue list was empty; head and tail point to msg */ |
| 599 |
qlist->head = qlist->tail = msg; |
| 600 |
else |
| 601 |
{ |
| 602 |
assert(qlist->tail); |
| 603 |
|
| 604 |
qlist->tail->next = msg; /* queue had something in it; add to end */ |
| 605 |
qlist->tail = msg; |
| 606 |
} |
| 607 |
|
| 608 |
mq->length += mb->length; /* update the queue length */ |
| 609 |
mq->count++; /* and the queue count */ |
| 610 |
} |
| 611 |
|
| 612 |
/** Report memory statistics for message buffers. |
| 613 |
* @param[in] cptr Client requesting information. |
| 614 |
* @param[out] msg_alloc Receives number of bytes allocated in Msg structs. |
| 615 |
* @param[out] msgbuf_alloc Receives number of bytes allocated in MsgBuf structs. |
| 616 |
*/ |
| 617 |
void |
| 618 |
msgq_count_memory(struct Client *source_p, size_t *msg_alloc, size_t *msgbuf_alloc) |
| 619 |
{ |
| 620 |
int i; |
| 621 |
size_t total = 0, size; |
| 622 |
|
| 623 |
assert(client_p); |
| 624 |
assert(msg_alloc); |
| 625 |
assert(msgbuf_alloc); |
| 626 |
|
| 627 |
/* Data for Msg's is simple, so just send it */ |
| 628 |
sendto_one(source_p, ":%s %d %s :Msgs allocated %d(%zu) used %d(%zu) text %zu", |
| 629 |
me.name, RPL_STATSDEBUG, source_p->name, |
| 630 |
MQData.msgs.alloc, MQData.msgs.alloc * sizeof(struct Msg), |
| 631 |
MQData.msgs.used, MQData.msgs.used * sizeof(struct Msg), |
| 632 |
MQData.tot_bufsize); |
| 633 |
/* count_memory() wants to know the total */ |
| 634 |
*msg_alloc = MQData.msgs.alloc * sizeof(struct Msg); |
| 635 |
|
| 636 |
/* Ok, now walk through each size class */ |
| 637 |
for (i = MB_BASE_SHIFT; i < MB_MAX_SHIFT + 1; i++) |
| 638 |
{ |
| 639 |
size = sizeof(struct MsgBuf) + (1 << i); /* total size of a buffer */ |
| 640 |
|
| 641 |
/* Send information for this buffer size class */ |
| 642 |
sendto_one(source_p, ":%s %d %s :MsgBufs of size %zu allocated %d(%zu) used %d(%zu)", |
| 643 |
me.name, RPL_STATSDEBUG, source_p->name, 1 << i, |
| 644 |
MQData.msgBufs[i - MB_BASE_SHIFT].alloc, |
| 645 |
MQData.msgBufs[i - MB_BASE_SHIFT].alloc * size, |
| 646 |
MQData.msgBufs[i - MB_BASE_SHIFT].used, |
| 647 |
MQData.msgBufs[i - MB_BASE_SHIFT].used * size); |
| 648 |
|
| 649 |
/* count_memory() wants to know the total */ |
| 650 |
total += MQData.msgBufs[i - MB_BASE_SHIFT].alloc * size; |
| 651 |
} |
| 652 |
|
| 653 |
*msgbuf_alloc = total; |
| 654 |
} |
| 655 |
|
| 656 |
/** Report remaining space in a MsgBuf. |
| 657 |
* @param[in] mb Message buffer to check. |
| 658 |
* @return Number of additional bytes that can be appended to the message. |
| 659 |
*/ |
| 660 |
unsigned int |
| 661 |
msgq_bufleft(struct MsgBuf *mb) |
| 662 |
{ |
| 663 |
assert(mb); |
| 664 |
|
| 665 |
return bufsize(mb) - mb->length; /* \r\n counted in mb->length */ |
| 666 |
} |
| 667 |
|
| 668 |
/** Send histogram of message lengths to a client. |
| 669 |
* @param[in] cptr Client requesting statistics. |
| 670 |
* @param[in] sd Stats descriptor for request (ignored). |
| 671 |
* @param[in] param Extra parameter from user (ignored). |
| 672 |
*/ |
| 673 |
void |
| 674 |
msgq_histogram(struct Client *source_p) |
| 675 |
{ |
| 676 |
struct MsgSizes tmp = MQData.sizes; /* All hail structure copy! */ |
| 677 |
int i; |
| 678 |
|
| 679 |
sendto_one(source_p, ":%s %d %s :Histogram of message lengths (%lu messages)", |
| 680 |
me.name, RPL_STATSDEBUG, source_p->name, tmp.msgs); |
| 681 |
|
| 682 |
for (i = 0; i + 16 <= IRCD_BUFSIZE; i += 16) |
| 683 |
sendto_one(source_p, ":%s %d %s :% 4d: %u %u %u %u " |
| 684 |
"%u %u %u %u %u %u %u %u %u %u %u %u", |
| 685 |
me.name, RPL_STATSDEBUG, source_p->name, i + 1, |
| 686 |
tmp.sizes[i + 0], tmp.sizes[i + 1], tmp.sizes[i + 2], |
| 687 |
tmp.sizes[i + 3], tmp.sizes[i + 4], tmp.sizes[i + 5], |
| 688 |
tmp.sizes[i + 6], tmp.sizes[i + 7], tmp.sizes[i + 8], |
| 689 |
tmp.sizes[i + 9], tmp.sizes[i + 10], tmp.sizes[i + 11], |
| 690 |
tmp.sizes[i + 12], tmp.sizes[i + 13], tmp.sizes[i + 14], |
| 691 |
tmp.sizes[i + 15]); |
| 692 |
} |