/* * This file is part of the Sofia-SIP package * * Copyright (C) 2005 Nokia Corporation. * * Contact: Pekka Pessi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /**@IFILE validator.c * * SIP parser tester. This uses output * * @author Pekka Pessi . * * @date Wed Mar 21 19:12:13 2001 ppessi */ #include "config.h" #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include char const *name = "validator"; typedef struct { unsigned o_verbose : 1; /**< Be verbose */ unsigned o_very_verbose : 1; /**< Be very verbose */ unsigned o_requests : 1; /**< Only requests */ unsigned o_responses : 1; /**< Only responses */ unsigned o_decode : 1; /**< Only try to decode, print error if unknown headers */ unsigned o_print : 1; /**< Print whole message */ unsigned o_times : 1; /**< Generate timing information */ unsigned o_memstats : 1; /**< Generate memory statistics */ unsigned o_histogram : 1; /**< Generate histograms */ unsigned o_sipstats : 1; /**< Generate header statistics */ unsigned o_vsipstats : 1; /**< Generate verbatim header statistics */ unsigned : 0; unsigned o_flags; /**< Message flags */ } options_t; typedef struct { size_t N; uint32_t bsize; double buckets[32768]; } histogram_t; static histogram_t *histogram_create(uint64_t max, uint32_t bsize) { size_t N = (max + bsize - 1) / bsize; histogram_t *h = calloc(1, offsetof(histogram_t, buckets[N + 1])); if (!h) { perror("calloc"); exit(1); } h->N = N, h->bsize = bsize; return h; } static double *histogram_update(histogram_t *h, uint32_t n) { if (h->bsize > 1) n /= h->bsize; if (n < h->N) return &h->buckets[n]; else return &h->buckets[h->N]; } static void histogram_div(histogram_t *h, histogram_t const *n) { size_t i; assert(h->N == n->N); assert(h->bsize == n->bsize); for (i = 0; i <= h->N; i++) { if (n->buckets[i]) { h->buckets[i] /= n->buckets[i]; } else { assert(h->buckets[i] == 0); } } } typedef struct { uint64_t number; uint64_t headers; uint64_t payloads; uint64_t pl_bytes; } sipstat_t; typedef struct { sipstat_t req, resp; histogram_t *hist_headers; } sipstats_t; typedef struct { char const *name; char const *sep; uint64_t messages; uint64_t bytes; uint64_t errors; uint32_t files; double time; options_t options[1]; /* Statistics */ histogram_t *hist_msgsize; histogram_t *hist_mallocs; histogram_t *hist_memsize; histogram_t *hist_nheaders; sipstats_t sipstats[1]; su_home_stat_t hs[1]; uint64_t est_fail, est_succ, est_slack; } context_t; void usage(void) { fprintf(stderr, "usage: %s [-vdp]\n", name); exit(2); } char *lastpart(char *path) { char *p = strrchr(path, '/'); if (p) return p + 1; else return path; } msg_mclass_t *mclass = NULL; int validate_file(int fd, char const *name, context_t *ctx); int validate_dump(char *, off_t, context_t *ctx); int report(context_t const *ctx); static void memstats(msg_t *, uint32_t msize, context_t *ctx); static void sipstats(msg_t *, uint32_t msize, sipstats_t *ss, context_t *ctx); int main(int argc, char *argv[]) { context_t ctx[1] = {{ 0 }}; options_t *o = ctx->options; name = lastpart(argv[0]); /* Set our name */ for (; argv[1]; argv++) { if (argv[1][0] == 0) usage(); else if (argv[1][0] != '-') break; else if (argv[1][1] == 0) { argv++; break; } else if (strcmp(argv[1], "-v") == 0) o->o_very_verbose = o->o_verbose, o->o_verbose = 1; else if (strcmp(argv[1], "-d") == 0) o->o_decode = 1; /* Decode only */ else if (strcmp(argv[1], "-p") == 0) o->o_print = 1; else if (strcmp(argv[1], "-q") == 0) o->o_requests = 1; else if (strcmp(argv[1], "-Q") == 0) o->o_responses = 1; else if (strcmp(argv[1], "-t") == 0) o->o_times = 1; else if (strcmp(argv[1], "-m") == 0) o->o_memstats = 1; else if (strcmp(argv[1], "-s") == 0) o->o_vsipstats = o->o_sipstats, o->o_sipstats = 1; else if (strcmp(argv[1], "-h") == 0) o->o_histogram = 1; else usage(); } if (o->o_requests && o->o_responses) usage(); if (!mclass) mclass = sip_default_mclass(); if (argv[1]) { for (; argv[1]; argv++) { int fd = open(argv[1], O_RDONLY, 000); if (fd == -1) perror(argv[1]), exit(1); if (validate_file(fd, argv[1], ctx)) exit(1); close(fd); } } else validate_file(0, "", ctx); report(ctx); exit(0); } int validate_file(int fd, char const *name, context_t *ctx) { void *p; off_t size; int retval; ctx->name = name; if (strlen(name)) ctx->sep = ": "; else ctx->sep = ""; ctx->files++; size = lseek(fd, 0, SEEK_END); if (size < 1) return 0; if (size > INT_MAX) { fprintf(stderr, "%s%stoo large file to map\n", ctx->name, ctx->sep); return -1; } #ifndef _WIN32 p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0L); if (p == NULL) { perror("mmap"); return -1; } retval = validate_dump(p, size, ctx); munmap(p, size); return retval; #else errno = EINVAL; perror("mmap not implemented"); return -1; #endif } static inline void nul_terminate(char *b, off_t size) { char *end; /* NUL-terminate */ for (end = b + size - 1; end != b; end--) if (*end == '\v') break; *end = '\0'; } static inline int search_msg(char **bb, char const *protocol) { int linelen, plen = strlen(protocol); char *b = *bb; for (;;) { if (!b[0]) return 0; if (strncmp(b, protocol, plen) == 0 && b[plen] == ' ') return 1; /* status */ linelen = strcspn(b, "\r\n"); if (linelen > plen + 1 && b[linelen - plen - 1] == ' ' && strncmp(b + linelen - plen, protocol, plen) == 0) return 1; /* request */ b += linelen + strspn(b + linelen, "\r\n"); *bb = b; } } int validate_dump(char *b, off_t size, context_t *ctx) { size_t n = 0, N = 0; struct message { char *b; int size; } *msgs = NULL; uint64_t time0, time1; options_t *o = ctx->options; int maxsize = 0; nul_terminate(b, size); /* Split dump file to messages */ while (search_msg(&b, SIP_VERSION_CURRENT)) { int msize = strcspn(b, "\v"); int linelen = strcspn(b, "\r\n"); if (o->o_responses && memcmp(b, SIP_VERSION_CURRENT, strlen(SIP_VERSION_CURRENT)) != 0) ; else if (o->o_requests && memcmp(b, SIP_VERSION_CURRENT, strlen(SIP_VERSION_CURRENT)) == 0) ; else { if (o->o_very_verbose) printf("message "MOD_ZU": %.*s\n", n, linelen, b); if (n == N) { N *= 2; if (n == N) N = 16; msgs = realloc(msgs, sizeof(*msgs) * N); if (msgs == NULL) { perror("realloc"); exit(1); } } msgs[n].b = b; msgs[n].size = msize; n++; ctx->bytes += msize; if (msize > maxsize) maxsize = msize; } b += msize; if (*b) *b++ = '\0'; } ctx->messages += N = n; if (o->o_histogram) { ctx->hist_msgsize = histogram_create(maxsize, 64); if (o->o_memstats) { ctx->hist_mallocs = histogram_create(maxsize, 64); ctx->hist_memsize = histogram_create(maxsize, 64); } if (o->o_sipstats) { ctx->sipstats->hist_headers = histogram_create(64, 1); ctx->hist_nheaders = histogram_create(maxsize, 64); } } time0 = su_nanocounter(); for (n = 0; n < N; n++) { msg_t *msg = msg_create(mclass, o->o_flags); int m; if (msg == NULL) { perror("msg_create"); exit(1); } if (o->o_memstats) su_home_init_stats(msg_home(msg)); msg_buf_set(msg, msgs[n].b, msgs[n].size + 1); msg_buf_commit(msg, msgs[n].size, 1); su_home_preload(msg_home(msg), 1, msgs[n].size + 384); m = msg_extract(msg); if (m < 0) { fprintf(stderr, "%s%sparsing error in message "MOD_ZU"\n", ctx->name, ctx->sep, n); ctx->errors++; } else { if (ctx->hist_msgsize) *histogram_update(ctx->hist_msgsize, msgs[n].size) += 1; if (o->o_sipstats) sipstats(msg, msgs[n].size, ctx->sipstats, ctx); if (o->o_memstats) memstats(msg, msgs[n].size, ctx); } msg_destroy(msg); } time1 = su_nanocounter(); if (o->o_times) { double dur = (time1 - time0) * 1E-9; ctx->time += dur; printf("%s%s"MOD_ZU" messages in %g seconds (%g msg/sec)\n" " parse speed %.1f Mb/s (on Ethernet wire %.1f Mb/s)\n", ctx->name, ctx->sep, N, dur, (double)N / dur, (double)ctx->bytes * 8 / ctx->time / 1e6, ((double)ctx->bytes + N * (16 + 20 + 8)) * 8 / ctx->time / 1e6); } free(msgs); return 0; } typedef unsigned longlong ull; static void report_memstats(char const *title, su_home_stat_t const hs[1]) { printf("%s%smemory statistics\n", title, strlen(title) ? " " : ""); if (hs->hs_allocs.hsa_number) printf("\t"LLU" allocs, "LLU" bytes, "LLU" rounded," " "LLU" max\n", (ull)hs->hs_allocs.hsa_number, (ull)hs->hs_allocs.hsa_bytes, (ull)hs->hs_allocs.hsa_rbytes, (ull)hs->hs_allocs.hsa_maxrbytes); if (hs->hs_frees.hsf_number) printf("\t"LLU" frees, "LLU" bytes, rounded to "LLU" bytes\n", (ull)hs->hs_frees.hsf_number, (ull)hs->hs_frees.hsf_bytes, (ull)hs->hs_frees.hsf_rbytes); if (hs->hs_rehash || hs->hs_clones) printf("\t"LLU" rehashes, "LLU" clones\n", (ull)hs->hs_rehash, (ull)hs->hs_clones); } void memstats(msg_t *msg, uint32_t msize, context_t *ctx) { options_t *o = ctx->options; su_home_stat_t hs[1]; su_home_get_stats(msg_home(msg), 1, hs, sizeof(hs)); su_home_stat_add(ctx->hs, hs); if (o->o_histogram) { *histogram_update(ctx->hist_mallocs, msize) += hs->hs_allocs.hsa_number; *histogram_update(ctx->hist_memsize, msize) += hs->hs_allocs.hsa_maxrbytes; } { int estimate = msize + 384; int slack = estimate - hs->hs_allocs.hsa_maxrbytes; if (slack < 0) ctx->est_fail++; else { ctx->est_succ++; ctx->est_slack += slack; } } if (o->o_very_verbose) report_memstats(ctx->name, hs); } void report_sipstat(char const *what, sipstat_t const *sss) { printf("%s: "LLU" with %.1f headers (total "LLU")\n", what, (ull)sss->number, (double)sss->headers / sss->number, (ull)sss->headers); if (sss->payloads) printf("\t"LLU" with body of %.1f bytes (total "LLU")\n", (ull)sss->payloads, (double)sss->pl_bytes / sss->payloads, (ull)sss->payloads); } void sipstats(msg_t *msg, uint32_t msize, sipstats_t *ss, context_t *ctx) { options_t *o = ctx->options; msg_pub_t *m = msg_object(msg); sip_t const *sip = sip_object(msg); msg_header_t *h; sipstat_t *sss; size_t n, bytes; if (!sip) return; if (m->msg_request) { sss = &ss->req; h = m->msg_request; } else if (m->msg_status) { sss = &ss->resp; h = m->msg_status; } else { return; } sss->number++; /* Count headers */ for (n = 0, h = h->sh_succ; h && !sip_is_separator((sip_header_t *)h); h = h->sh_succ) n++; sss->headers += n; bytes = sip->sip_payload ? (size_t)sip->sip_payload->pl_len : 0; if (bytes) { sss->payloads++; sss->pl_bytes += bytes; } if (ctx->hist_nheaders) { *histogram_update(ctx->hist_nheaders, msize) += n; *histogram_update(ss->hist_headers, n) += 1; } if (o->o_very_verbose) printf("%s%s"MOD_ZU" headers, "MOD_ZU" bytes in payload\n", ctx->name, ctx->sep, n, bytes); } void report_histogram(char const *title, histogram_t const *h) { size_t i, min_i, max_i; for (i = 0; i < h->N && h->buckets[i] == 0.0; i++) ; min_i = i; for (i = h->N - 1; i >= 0 && h->buckets[i] == 0.0; i--) ; max_i = i; if (min_i >= max_i) return; printf("%s histogram\n", title); for (i = min_i; i < max_i; i++) printf("\t"MOD_ZU".."MOD_ZU": %.1f\n", i * h->bsize, (i + 1) * h->bsize, h->buckets[i]); if (h->buckets[h->N]) printf("\t"MOD_ZU"..: %.1f\n", h->N * h->bsize, h->buckets[h->N]); } int report(context_t const *ctx) { const options_t *o = ctx->options; uint64_t n = ctx->messages; if (!n) return -1; printf("total "LLU" messages with "LLU" bytes (mean size "LLU")\n", (ull)n, (ull)ctx->bytes, (ull)(ctx->bytes / n)); if (ctx->hist_msgsize) report_histogram("Message size", ctx->hist_msgsize); if (o->o_times && ctx->files > 1) printf("total "LLU" messages in %g seconds (%g msg/sec)\n", (ull)n, ctx->time, (double)n / ctx->time); if (o->o_sipstats) { const sipstats_t *ss = ctx->sipstats; report_sipstat("requests", &ss->req); report_sipstat("responses", &ss->resp); if (ctx->hist_nheaders) { histogram_div(ctx->hist_nheaders, ctx->hist_msgsize); report_histogram("Number of headers", ctx->hist_nheaders); } } if (o->o_memstats) { su_home_stat_t hs[1]; *hs = *ctx->hs; report_memstats("total", hs); /* Calculate mean */ hs->hs_clones /= n; hs->hs_rehash /= n; hs->hs_allocs.hsa_number /= n; hs->hs_allocs.hsa_bytes /= n; hs->hs_allocs.hsa_rbytes /= n; hs->hs_allocs.hsa_maxrbytes /= n; hs->hs_frees.hsf_number /= n; hs->hs_frees.hsf_bytes /= n; hs->hs_frees.hsf_rbytes /= n; hs->hs_blocks.hsb_number /= n; hs->hs_blocks.hsb_bytes /= n; hs->hs_blocks.hsb_rbytes /= n; report_memstats("mean", hs); printf("\testimator fails %.1f%% times (mean slack %.0f bytes)\n", 100 * (double)ctx->est_fail / (ctx->est_fail + ctx->est_succ), (double)ctx->est_slack / ctx->est_succ); if (ctx->hist_memsize) { histogram_div(ctx->hist_memsize, ctx->hist_msgsize); report_histogram("Allocated memory", ctx->hist_memsize); } } return 0; }