]> git.ipfire.org Git - thirdparty/squid.git/blob - src/MemBuf.cc
Add profiling points for Membuf::grow, ::append and ::consume
[thirdparty/squid.git] / src / MemBuf.cc
1
2 /*
3 * $Id: MemBuf.cc,v 1.42 2006/09/20 08:13:38 adrian Exp $
4 *
5 * DEBUG: section 59 auto-growing Memory Buffer with printf
6 * AUTHOR: Alex Rousskov
7 *
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
33 *
34 */
35
36 /*
37 * To-Do: use memory pools for .buf recycling @?@ @?@
38 */
39
40 /*
41 * Rationale:
42 * ----------
43 *
44 * Here is how one would comm_write an object without MemBuffer:
45 *
46 * {
47 * -- allocate:
48 * buf = malloc(big_enough);
49 *
50 * -- "pack":
51 * snprintf object(s) piece-by-piece constantly checking for overflows
52 * and maintaining (buf+offset);
53 * ...
54 *
55 * -- write
56 * comm_write(buf, free, ...);
57 * }
58 *
59 * The whole "packing" idea is quite messy: We are given a buffer of fixed
60 * size and we have to check all the time that we still fit. Sounds logical.
61 *
62 * However, what happens if we have more data? If we are lucky to stop before
63 * we overrun any buffers, we still may have garbage (e.g. half of ETag) in
64 * the buffer.
65 *
66 * MemBuffer:
67 * ----------
68 *
69 * MemBuffer is a memory-resident buffer with printf()-like interface. It
70 * hides all offest handling and overflow checking. Moreover, it has a
71 * build-in control that no partial data has been written.
72 *
73 * MemBuffer is designed to handle relatively small data. It starts with a
74 * small buffer of configurable size to avoid allocating huge buffers all the
75 * time. MemBuffer doubles the buffer when needed. It assert()s that it will
76 * not grow larger than a configurable limit. MemBuffer has virtually no
77 * overhead (and can even reduce memory consumption) compared to old
78 * "packing" approach.
79 *
80 * MemBuffer eliminates both "packing" mess and truncated data:
81 *
82 * {
83 * -- setup
84 * MemBuf buf;
85 *
86 * -- required init with optional size tuning (see #defines for defaults)
87 * buf.init(initial-size, absolute-maximum);
88 *
89 * -- "pack" (no need to handle offsets or check for overflows)
90 * buf.Printf(...);
91 * ...
92 *
93 * -- write
94 * comm_write_mbuf(fd, buf, handler, data);
95 *
96 * -- *iff* you did not give the buffer away, free it yourself
97 * -- buf.clean();
98 * }
99 */
100 /* if you have configure you can use this */
101 #if defined(HAVE_CONFIG_H)
102 #include "config.h"
103 #endif
104
105 #ifdef VA_COPY
106 #undef VA_COPY
107 #endif
108 #if defined HAVE_VA_COPY
109 #define VA_COPY va_copy
110 #elif defined HAVE___VA_COPY
111 #define VA_COPY __va_copy
112 #endif
113
114 #include "squid.h"
115 #include "MemBuf.h"
116
117 /* local constants */
118
119 /* default values for buffer sizes, used by memBufDefInit */
120 #define MEM_BUF_INIT_SIZE (2*1024)
121 #define MEM_BUF_MAX_SIZE (2*1000*1024*1024)
122
123 CBDATA_CLASS_INIT(MemBuf);
124
125 /* init with defaults */
126 void
127 MemBuf::init()
128 {
129 init(MEM_BUF_INIT_SIZE, MEM_BUF_MAX_SIZE);
130 }
131
132
133 /* init with specific sizes */
134 void
135 MemBuf::init(mb_size_t szInit, mb_size_t szMax)
136 {
137 assert(szInit > 0 && szMax > 0);
138 buf = NULL;
139 size = 0;
140 max_capacity = szMax;
141 capacity = 0;
142 stolen = 0;
143 grow(szInit);
144 }
145
146 /*
147 * cleans the mb; last function to call if you do not give .buf away with
148 * memBufFreeFunc
149 */
150 void
151 MemBuf::clean()
152 {
153 if (isNull()) {
154 // nothing to do
155 } else {
156 assert(buf);
157 assert(!stolen); /* not frozen */
158
159 memFreeBuf(capacity, buf);
160 buf = NULL;
161 size = capacity = max_capacity = 0;
162 }
163 }
164
165 /* cleans the buffer without changing its capacity
166 * if called with a Null buffer, calls memBufDefInit() */
167 void
168 MemBuf::reset()
169 {
170 if (isNull()) {
171 init();
172 } else {
173 assert(!stolen); /* not frozen */
174 /* reset */
175 memset(buf, 0, capacity);
176 size = 0;
177 }
178 }
179
180 /* unfortunate hack to test if the buffer has been Init()ialized */
181 int
182 MemBuf::isNull()
183 {
184 if (!buf && !max_capacity && !capacity && !size)
185 return 1; /* is null (not initialized) */
186
187 assert(buf && max_capacity && capacity); /* paranoid */
188
189 return 0;
190 }
191
192 mb_size_t MemBuf::spaceSize() const
193 {
194 const mb_size_t terminatedSize = size + 1;
195 return (terminatedSize < capacity) ? capacity - terminatedSize : 0;
196 }
197
198 mb_size_t MemBuf::potentialSpaceSize() const
199 {
200 const mb_size_t terminatedSize = size + 1;
201 return (terminatedSize < max_capacity) ? max_capacity - terminatedSize : 0;
202 }
203
204 // removes sz bytes and "packs" by moving content left
205 void MemBuf::consume(mb_size_t shiftSize)
206 {
207 const mb_size_t cSize = contentSize();
208 assert(0 <= shiftSize && shiftSize <= cSize);
209 assert(!stolen); /* not frozen */
210
211 PROF_start(MemBuf_consume);
212 if (shiftSize > 0) {
213 if (shiftSize < cSize)
214 xmemmove(buf, buf + shiftSize, cSize - shiftSize);
215
216 size -= shiftSize;
217
218 terminate();
219 }
220 PROF_stop(MemBuf_consume);
221 }
222
223 // calls memcpy, appends exactly size bytes, extends buffer if needed
224 void MemBuf::append(const char *newContent, mb_size_t sz)
225 {
226 assert(sz >= 0);
227 assert(buf);
228 assert(!stolen); /* not frozen */
229
230 PROF_start(MemBuf_append);
231 if (sz > 0) {
232 if (size + sz + 1 > capacity)
233 grow(size + sz + 1);
234
235 assert(size + sz <= capacity); /* paranoid */
236
237 xmemcpy(space(), newContent, sz);
238
239 appended(sz);
240 }
241 PROF_stop(MemBuf_append);
242 }
243
244 // updates content size after external append
245 void MemBuf::appended(mb_size_t sz)
246 {
247 assert(size + sz <= capacity);
248 size += sz;
249 terminate();
250 }
251
252 // 0-terminate in case we are used as a string.
253 // Extra octet is not counted in the content size (or space size)
254 // XXX: but the extra octet is counted when growth decisions are made!
255 // This will cause the buffer to grow when spaceSize() == 1 on append,
256 // which will assert() if the buffer cannot grow any more.
257 void MemBuf::terminate()
258 {
259 assert(size < capacity);
260 *space() = '\0';
261 }
262
263 /* calls memBufVPrintf */
264 #if STDC_HEADERS
265 void
266 MemBuf::Printf(const char *fmt,...)
267 {
268 va_list args;
269 va_start(args, fmt);
270 #else
271 void
272 MemBuf::Printf(va_alist)
273 va_dcl
274 {
275 va_list args;
276 mb_size_t sz = 0;
277 va_start(args);
278 const char *fmt = va_arg(args, char *);
279 #endif
280
281 vPrintf(fmt, args);
282 va_end(args);
283 }
284
285
286 /* vPrintf for other printf()'s to use; calls vsnprintf, extends buf if needed */
287 void
288 MemBuf::vPrintf(const char *fmt, va_list vargs) {
289 #ifdef VA_COPY
290 va_list ap;
291 #endif
292
293 int sz = 0;
294 assert(fmt);
295 assert(buf);
296 assert(!stolen); /* not frozen */
297 /* assert in Grow should quit first, but we do not want to have a scary infinite loop */
298
299 while (capacity <= max_capacity) {
300 mb_size_t free_space = capacity - size;
301 /* put as much as we can */
302
303 #ifdef VA_COPY
304 /* Fix of bug 753r. The value of vargs is undefined
305 * after vsnprintf() returns. Make a copy of vargs
306 * incase we loop around and call vsnprintf() again.
307 */
308 VA_COPY(ap,vargs);
309 sz = vsnprintf(buf + size, free_space, fmt, ap);
310 va_end(ap);
311 #else /* VA_COPY */
312
313 sz = vsnprintf(buf + size, free_space, fmt, vargs);
314 #endif /*VA_COPY*/
315 /* check for possible overflow */
316 /* snprintf on Linuz returns -1 on overflows */
317 /* snprintf on FreeBSD returns at least free_space on overflows */
318
319 if (sz < 0 || sz >= free_space)
320 grow(capacity + 1);
321 else
322 break;
323 }
324
325 size += sz;
326 /* on Linux and FreeBSD, '\0' is not counted in return value */
327 /* on XXX it might be counted */
328 /* check that '\0' is appended and not counted */
329
330 if (!size || buf[size - 1]) {
331 assert(!buf[size]);
332 } else {
333 size--;
334 }
335 }
336
337 /*
338 * returns free() function to be used.
339 * Important:
340 * calling this function "freezes" mb,
341 * do not _update_ mb after that in any way
342 * (you still can read-access .buf and .size)
343 */
344 FREE *
345 MemBuf::freeFunc() {
346 FREE *ff;
347 assert(buf);
348 assert(!stolen); /* not frozen */
349
350 ff = memFreeBufFunc((size_t) capacity);
351 stolen = 1; /* freeze */
352 return ff;
353 }
354
355 /* grows (doubles) internal buffer to satisfy required minimal capacity */
356 void
357 MemBuf::grow(mb_size_t min_cap) {
358 size_t new_cap;
359 size_t buf_cap;
360
361 assert(!stolen);
362 assert(capacity < min_cap);
363
364 PROF_start(MemBuf_grow);
365
366 /* determine next capacity */
367
368 if (min_cap > 64 * 1024) {
369 new_cap = 64 * 1024;
370
371 while (new_cap < (size_t) min_cap)
372 new_cap += 64 * 1024; /* increase in reasonable steps */
373 } else {
374 new_cap = (size_t) min_cap;
375 }
376
377 /* last chance to fit before we assert(!overflow) */
378 if (new_cap > (size_t) max_capacity)
379 new_cap = (size_t) max_capacity;
380
381 assert(new_cap <= (size_t) max_capacity); /* no overflow */
382
383 assert(new_cap > (size_t) capacity); /* progress */
384
385 buf_cap = (size_t) capacity;
386
387 buf = (char *)memReallocBuf(buf, new_cap, &buf_cap);
388
389 /* done */
390 capacity = (mb_size_t) buf_cap;
391 PROF_stop(MemBuf_grow);
392 }
393
394
395 /* Reports */
396
397 /* puts report on MemBuf _module_ usage into mb */
398 void
399 memBufReport(MemBuf * mb) {
400 assert(mb);
401 mb->Printf("memBufReport is not yet implemented @?@\n");
402 }
403
404 #ifndef _USE_INLINE_
405 #include "MemBuf.cci"
406 #endif