1 /* $Id$ */
2 /*
3 * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19 #include <pjsua-lib/pjsua.h>
20 #include <pjsua-lib/pjsua_internal.h>
21
good_number(char * buf,pj_int32_t val)22 const char *good_number(char *buf, pj_int32_t val)
23 {
24 if (val < 1000) {
25 pj_ansi_sprintf(buf, "%d", val);
26 } else if (val < 1000000) {
27 pj_ansi_sprintf(buf, "%d.%dK",
28 val / 1000,
29 (val % 1000) / 100);
30 } else {
31 pj_ansi_sprintf(buf, "%d.%02dM",
32 val / 1000000,
33 (val % 1000000) / 10000);
34 }
35
36 return buf;
37 }
38
dump_media_stat(const char * indent,char * buf,unsigned maxlen,const pjmedia_rtcp_stat * stat,const char * rx_info,const char * tx_info)39 static unsigned dump_media_stat(const char *indent,
40 char *buf, unsigned maxlen,
41 const pjmedia_rtcp_stat *stat,
42 const char *rx_info, const char *tx_info)
43 {
44 char last_update[64];
45 char packets[32], bytes[32], ipbytes[32], avg_bps[32], avg_ipbps[32];
46 pj_time_val media_duration, now;
47 char *p = buf, *end = buf+maxlen;
48 int len;
49
50 if (stat->rx.update_cnt == 0)
51 strcpy(last_update, "never");
52 else {
53 pj_gettimeofday(&now);
54 PJ_TIME_VAL_SUB(now, stat->rx.update);
55 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
56 now.sec / 3600,
57 (now.sec % 3600) / 60,
58 now.sec % 60,
59 now.msec);
60 }
61
62 pj_gettimeofday(&media_duration);
63 PJ_TIME_VAL_SUB(media_duration, stat->start);
64 if (PJ_TIME_VAL_MSEC(media_duration) == 0)
65 media_duration.msec = 1;
66
67 len = pj_ansi_snprintf(p, end-p,
68 "%s RX %s last update:%s\n"
69 "%s total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n"
70 "%s pkt loss=%d (%3.1f%%), discrd=%d (%3.1f%%), dup=%d (%2.1f%%), reord=%d (%3.1f%%)\n"
71 "%s (msec) min avg max last dev\n"
72 "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
73 "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
74 #if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
75 "%s raw jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
76 #endif
77 #if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
78 "%s IPDV : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
79 #endif
80 "%s",
81 indent,
82 rx_info? rx_info : "",
83 last_update,
84
85 indent,
86 good_number(packets, stat->rx.pkt),
87 good_number(bytes, stat->rx.bytes),
88 good_number(ipbytes, stat->rx.bytes + stat->rx.pkt * 40),
89 good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->rx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
90 good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->rx.bytes + stat->rx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
91 indent,
92 stat->rx.loss,
93 (stat->rx.loss? stat->rx.loss * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
94 stat->rx.discard,
95 (stat->rx.discard? stat->rx.discard * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
96 stat->rx.dup,
97 (stat->rx.dup? stat->rx.dup * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
98 stat->rx.reorder,
99 (stat->rx.reorder? stat->rx.reorder * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
100 indent, indent,
101 stat->rx.loss_period.min / 1000.0,
102 stat->rx.loss_period.mean / 1000.0,
103 stat->rx.loss_period.max / 1000.0,
104 stat->rx.loss_period.last / 1000.0,
105 pj_math_stat_get_stddev(&stat->rx.loss_period) / 1000.0,
106 indent,
107 stat->rx.jitter.min / 1000.0,
108 stat->rx.jitter.mean / 1000.0,
109 stat->rx.jitter.max / 1000.0,
110 stat->rx.jitter.last / 1000.0,
111 pj_math_stat_get_stddev(&stat->rx.jitter) / 1000.0,
112 #if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
113 indent,
114 stat->rx_raw_jitter.min / 1000.0,
115 stat->rx_raw_jitter.mean / 1000.0,
116 stat->rx_raw_jitter.max / 1000.0,
117 stat->rx_raw_jitter.last / 1000.0,
118 pj_math_stat_get_stddev(&stat->rx_raw_jitter) / 1000.0,
119 #endif
120 #if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
121 indent,
122 stat->rx_ipdv.min / 1000.0,
123 stat->rx_ipdv.mean / 1000.0,
124 stat->rx_ipdv.max / 1000.0,
125 stat->rx_ipdv.last / 1000.0,
126 pj_math_stat_get_stddev(&stat->rx_ipdv) / 1000.0,
127 #endif
128 ""
129 );
130
131 if (len < 1 || len >= end-p) {
132 *p = '\0';
133 return (unsigned)(p-buf);
134 }
135 p += len;
136
137 if (stat->tx.update_cnt == 0)
138 strcpy(last_update, "never");
139 else {
140 pj_gettimeofday(&now);
141 PJ_TIME_VAL_SUB(now, stat->tx.update);
142 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
143 now.sec / 3600,
144 (now.sec % 3600) / 60,
145 now.sec % 60,
146 now.msec);
147 }
148
149 len = pj_ansi_snprintf(p, end-p,
150 "%s TX %s last update:%s\n"
151 "%s total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n"
152 "%s pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n"
153 "%s (msec) min avg max last dev \n"
154 "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
155 "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
156 indent,
157 tx_info,
158 last_update,
159
160 indent,
161 good_number(packets, stat->tx.pkt),
162 good_number(bytes, stat->tx.bytes),
163 good_number(ipbytes, stat->tx.bytes + stat->tx.pkt * 40),
164 good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->tx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
165 good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->tx.bytes + stat->tx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
166
167 indent,
168 stat->tx.loss,
169 (stat->tx.loss? stat->tx.loss * 100.0 / stat->tx.pkt : 0),
170 stat->tx.dup,
171 (stat->tx.dup? stat->tx.dup * 100.0 / stat->tx.pkt : 0),
172 stat->tx.reorder,
173 (stat->tx.reorder? stat->tx.reorder * 100.0 / stat->tx.pkt : 0),
174
175 indent, indent,
176 stat->tx.loss_period.min / 1000.0,
177 stat->tx.loss_period.mean / 1000.0,
178 stat->tx.loss_period.max / 1000.0,
179 stat->tx.loss_period.last / 1000.0,
180 pj_math_stat_get_stddev(&stat->tx.loss_period) / 1000.0,
181 indent,
182 stat->tx.jitter.min / 1000.0,
183 stat->tx.jitter.mean / 1000.0,
184 stat->tx.jitter.max / 1000.0,
185 stat->tx.jitter.last / 1000.0,
186 pj_math_stat_get_stddev(&stat->tx.jitter) / 1000.0
187 );
188
189 if (len < 1 || len >= end-p) {
190 *p = '\0';
191 return (unsigned)(p-buf);
192 }
193 p += len;
194
195 len = pj_ansi_snprintf(p, end-p,
196 "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
197 indent,
198 stat->rtt.min / 1000.0,
199 stat->rtt.mean / 1000.0,
200 stat->rtt.max / 1000.0,
201 stat->rtt.last / 1000.0,
202 pj_math_stat_get_stddev(&stat->rtt) / 1000.0
203 );
204 if (len < 1 || len >= end-p) {
205 *p = '\0';
206 return (unsigned)(p-buf);
207 }
208 p += len;
209
210 return (unsigned)(p-buf);
211 }
212
213
214 /* Dump media session */
215 #if PJSUA_MEDIA_HAS_PJMEDIA || \
216 (PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO && \
217 PJSUA_THIRD_PARTY_STREAM_HAS_GET_STAT)
218
dump_media_session(const char * indent,char * buf,unsigned maxlen,pjsua_call * call)219 static void dump_media_session(const char *indent,
220 char *buf, unsigned maxlen,
221 pjsua_call *call)
222 {
223 unsigned i;
224 char *p = buf, *end = buf+maxlen;
225 int len;
226
227 for (i=0; i<call->med_cnt; ++i) {
228 pjsua_call_media *call_med = &call->media[i];
229 pjmedia_rtcp_stat stat;
230 pj_bool_t has_stat;
231 pjmedia_transport_info tp_info;
232 char rem_addr_buf[80];
233 char codec_info[32] = {'0'};
234 char rx_info[80] = {'\0'};
235 char tx_info[80] = {'\0'};
236 const char *rem_addr;
237 const char *dir_str;
238 const char *media_type_str;
239
240 switch (call_med->type) {
241 case PJMEDIA_TYPE_AUDIO:
242 media_type_str = "audio";
243 break;
244 case PJMEDIA_TYPE_VIDEO:
245 media_type_str = "video";
246 break;
247 case PJMEDIA_TYPE_APPLICATION:
248 media_type_str = "application";
249 break;
250 default:
251 media_type_str = "unknown";
252 break;
253 }
254
255 /* Check if the stream is deactivated */
256 if (call_med->tp == NULL ||
257 (!call_med->strm.a.stream && !call_med->strm.v.stream))
258 {
259 len = pj_ansi_snprintf(p, end-p,
260 "%s #%d %s deactivated\n",
261 indent, i, media_type_str);
262 if (len < 1 || len >= end-p) {
263 *p = '\0';
264 return;
265 }
266
267 p += len;
268 continue;
269 }
270
271 pjmedia_transport_info_init(&tp_info);
272 pjmedia_transport_get_info(call_med->tp, &tp_info);
273
274 // rem_addr will contain actual address of RTP originator, instead of
275 // remote RTP address specified by stream which is fetched from the SDP.
276 // Please note that we are assuming only one stream per call.
277 //rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr,
278 // rem_addr_buf, sizeof(rem_addr_buf), 3);
279 if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) {
280 rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf,
281 sizeof(rem_addr_buf), 3);
282 } else {
283 pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-");
284 rem_addr = rem_addr_buf;
285 }
286
287 if (call_med->dir == PJMEDIA_DIR_NONE) {
288 /* To handle when the stream that is currently being paused
289 * (http://trac.pjsip.org/repos/ticket/1079)
290 */
291 dir_str = "inactive";
292 } else if (call_med->dir == PJMEDIA_DIR_ENCODING)
293 dir_str = "sendonly";
294 else if (call_med->dir == PJMEDIA_DIR_DECODING)
295 dir_str = "recvonly";
296 else if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING)
297 dir_str = "sendrecv";
298 else
299 dir_str = "inactive";
300
301 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
302 pjmedia_stream *stream = call_med->strm.a.stream;
303 pjmedia_stream_info info;
304
305 pjmedia_stream_get_stat(stream, &stat);
306 has_stat = PJ_TRUE;
307
308 pjmedia_stream_get_info(stream, &info);
309 pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s @%dkHz",
310 (int)info.fmt.encoding_name.slen,
311 info.fmt.encoding_name.ptr,
312 info.fmt.clock_rate / 1000);
313 pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d,",
314 info.rx_pt);
315 pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, ptime=%d,",
316 info.tx_pt,
317 info.param->setting.frm_per_pkt*
318 info.param->info.frm_ptime);
319
320 #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
321 } else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
322 pjmedia_vid_stream *stream = call_med->strm.v.stream;
323 pjmedia_vid_stream_info info;
324
325 pjmedia_vid_stream_get_stat(stream, &stat);
326 has_stat = PJ_TRUE;
327
328 pjmedia_vid_stream_get_info(stream, &info);
329 pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s",
330 (int)info.codec_info.encoding_name.slen,
331 info.codec_info.encoding_name.ptr);
332 if (call_med->dir & PJMEDIA_DIR_DECODING) {
333 pjmedia_video_format_detail *vfd;
334 vfd = pjmedia_format_get_video_format_detail(
335 &info.codec_param->dec_fmt, PJ_TRUE);
336 pj_ansi_snprintf(rx_info, sizeof(rx_info),
337 "pt=%d, size=%dx%d, fps=%.2f,",
338 info.rx_pt,
339 vfd->size.w, vfd->size.h,
340 vfd->fps.num*1.0/vfd->fps.denum);
341 }
342 if (call_med->dir & PJMEDIA_DIR_ENCODING) {
343 pjmedia_video_format_detail *vfd;
344 vfd = pjmedia_format_get_video_format_detail(
345 &info.codec_param->enc_fmt, PJ_TRUE);
346 pj_ansi_snprintf(tx_info, sizeof(tx_info),
347 "pt=%d, size=%dx%d, fps=%.2f,",
348 info.tx_pt,
349 vfd->size.w, vfd->size.h,
350 vfd->fps.num*1.0/vfd->fps.denum);
351 }
352 #endif /* PJMEDIA_HAS_VIDEO */
353
354 } else {
355 has_stat = PJ_FALSE;
356 }
357
358 len = pj_ansi_snprintf(p, end-p,
359 "%s #%d %s%s, %s, peer=%s\n",
360 indent,
361 call_med->idx,
362 media_type_str,
363 codec_info,
364 dir_str,
365 rem_addr);
366 if (len < 1 || len >= end-p) {
367 *p = '\0';
368 return;
369 }
370 p += len;
371
372 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
373 if (pjsua_snd_is_active()) {
374 pjmedia_echo_stat ec_stat;
375 pj_status_t status;
376
377 status = pjsua_get_ec_stat(&ec_stat);
378 if (status == PJ_SUCCESS) {
379 len = pj_ansi_snprintf(p, end-p, " %s EC stat: %.*s\n",
380 indent,
381 (int)ec_stat.stat_info.slen,
382 ec_stat.stat_info.ptr);
383 if (len < 1 || len >= end-p) {
384 *p = '\0';
385 return;
386 }
387 p += len;
388 }
389 }
390 }
391
392 /* Get and ICE SRTP status */
393 if (call_med->tp) {
394 if (tp_info.specific_info_cnt > 0) {
395 unsigned j;
396 for (j = 0; j < tp_info.specific_info_cnt; ++j) {
397 if (tp_info.spc_info[j].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
398 {
399 pjmedia_srtp_info *srtp_info =
400 (pjmedia_srtp_info*) tp_info.spc_info[j].buffer;
401 const char *policy_name = srtp_info->tx_policy.name.ptr;
402
403 if (!policy_name)
404 policy_name = "";
405
406 len = pj_ansi_snprintf(p, end-p,
407 " %s SRTP status: %s Crypto-suite: %s",
408 indent,
409 (srtp_info->active?"Active":"Not active"),
410 policy_name);
411 if (len > 0 && len < end-p) {
412 p += len;
413 *p++ = '\n';
414 *p = '\0';
415 }
416 } else if (tp_info.spc_info[j].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
417 const pjmedia_ice_transport_info *ii;
418 unsigned jj;
419
420 ii = (const pjmedia_ice_transport_info*)
421 tp_info.spc_info[j].buffer;
422
423 len = pj_ansi_snprintf(p, end-p,
424 " %s ICE role: %s, state: %s, comp_cnt: %u",
425 indent,
426 pj_ice_sess_role_name(ii->role),
427 pj_ice_strans_state_name(ii->sess_state),
428 ii->comp_cnt);
429 if (len > 0 && len < end-p) {
430 p += len;
431 *p++ = '\n';
432 *p = '\0';
433 }
434
435 for (jj=0; ii->sess_state==PJ_ICE_STRANS_STATE_RUNNING && jj<2; ++jj) {
436 const char *type1 = pj_ice_get_cand_type_name(ii->comp[jj].lcand_type);
437 const char *type2 = pj_ice_get_cand_type_name(ii->comp[jj].rcand_type);
438 char addr1[PJ_INET6_ADDRSTRLEN+10];
439 char addr2[PJ_INET6_ADDRSTRLEN+10];
440
441 if (pj_sockaddr_has_addr(&ii->comp[jj].lcand_addr))
442 pj_sockaddr_print(&ii->comp[jj].lcand_addr, addr1, sizeof(addr1), 3);
443 else
444 strcpy(addr1, "0.0.0.0:0");
445 if (pj_sockaddr_has_addr(&ii->comp[jj].rcand_addr))
446 pj_sockaddr_print(&ii->comp[jj].rcand_addr, addr2, sizeof(addr2), 3);
447 else
448 strcpy(addr2, "0.0.0.0:0");
449 len = pj_ansi_snprintf(p, end-p,
450 " %s [%d]: L:%s (%c) --> R:%s (%c)\n",
451 indent, jj,
452 addr1, type1[0],
453 addr2, type2[0]);
454 if (len > 0 && len < end-p) {
455 p += len;
456 *p = '\0';
457 }
458 }
459 }
460 }
461 }
462 }
463
464
465 if (has_stat) {
466 len = dump_media_stat(indent, p, (unsigned)(end-p), &stat,
467 rx_info, tx_info);
468 p += len;
469 }
470
471 #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
472 # define SAMPLES_TO_USEC(usec, samples, clock_rate) \
473 do { \
474 if (samples <= 4294) \
475 usec = samples * 1000000 / clock_rate; \
476 else { \
477 usec = samples * 1000 / clock_rate; \
478 usec *= 1000; \
479 } \
480 } while(0)
481
482 # define PRINT_VOIP_MTC_VAL(s, v) \
483 if (v == 127) \
484 sprintf(s, "(na)"); \
485 else \
486 sprintf(s, "%d", v)
487
488 # define VALIDATE_PRINT_BUF() \
489 if (len < 1 || len >= end-p) { *p = '\0'; return; } \
490 p += len; *p++ = '\n'; *p = '\0'
491
492
493 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
494 pjmedia_stream_info info;
495 char last_update[64];
496 char loss[16], dup[16];
497 char jitter[80];
498 char toh[80];
499 char plc[16], jba[16], jbr[16];
500 char signal_lvl[16], noise_lvl[16], rerl[16];
501 char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
502 pjmedia_rtcp_xr_stat xr_stat;
503 unsigned clock_rate;
504 pj_time_val now;
505
506 if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream,
507 &xr_stat) != PJ_SUCCESS)
508 {
509 continue;
510 }
511
512 if (pjmedia_stream_get_info(call_med->strm.a.stream, &info)
513 != PJ_SUCCESS)
514 {
515 continue;
516 }
517
518 clock_rate = info.fmt.clock_rate;
519 pj_gettimeofday(&now);
520
521 len = pj_ansi_snprintf(p, end-p, "\n%s Extended reports:", indent);
522 VALIDATE_PRINT_BUF();
523
524 /* Statistics Summary */
525 len = pj_ansi_snprintf(p, end-p, "%s Statistics Summary", indent);
526 VALIDATE_PRINT_BUF();
527
528 if (xr_stat.rx.stat_sum.l)
529 sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
530 else
531 sprintf(loss, "(na)");
532
533 if (xr_stat.rx.stat_sum.d)
534 sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
535 else
536 sprintf(dup, "(na)");
537
538 if (xr_stat.rx.stat_sum.j) {
539 unsigned jmin, jmax, jmean, jdev;
540
541 SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
542 clock_rate);
543 SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
544 clock_rate);
545 SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
546 clock_rate);
547 SAMPLES_TO_USEC(jdev,
548 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
549 clock_rate);
550 sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
551 jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
552 } else
553 sprintf(jitter, "(report not available)");
554
555 if (xr_stat.rx.stat_sum.t) {
556 sprintf(toh, "%11d %11d %11d %11d",
557 xr_stat.rx.stat_sum.toh.min,
558 xr_stat.rx.stat_sum.toh.mean,
559 xr_stat.rx.stat_sum.toh.max,
560 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
561 } else
562 sprintf(toh, "(report not available)");
563
564 if (xr_stat.rx.stat_sum.update.sec == 0)
565 strcpy(last_update, "never");
566 else {
567 pj_gettimeofday(&now);
568 PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
569 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
570 now.sec / 3600,
571 (now.sec % 3600) / 60,
572 now.sec % 60,
573 now.msec);
574 }
575
576 len = pj_ansi_snprintf(p, end-p,
577 "%s RX last update: %s\n"
578 "%s begin seq=%d, end seq=%d\n"
579 "%s pkt loss=%s, dup=%s\n"
580 "%s (msec) min avg max dev\n"
581 "%s jitter : %s\n"
582 "%s toh : %s",
583 indent, last_update,
584 indent,
585 xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
586 indent, loss, dup,
587 indent,
588 indent, jitter,
589 indent, toh
590 );
591 VALIDATE_PRINT_BUF();
592
593 if (xr_stat.tx.stat_sum.l)
594 sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
595 else
596 sprintf(loss, "(na)");
597
598 if (xr_stat.tx.stat_sum.d)
599 sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
600 else
601 sprintf(dup, "(na)");
602
603 if (xr_stat.tx.stat_sum.j) {
604 unsigned jmin, jmax, jmean, jdev;
605
606 SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
607 clock_rate);
608 SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
609 clock_rate);
610 SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
611 clock_rate);
612 SAMPLES_TO_USEC(jdev,
613 pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
614 clock_rate);
615 sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
616 jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
617 } else
618 sprintf(jitter, "(report not available)");
619
620 if (xr_stat.tx.stat_sum.t) {
621 sprintf(toh, "%11d %11d %11d %11d",
622 xr_stat.tx.stat_sum.toh.min,
623 xr_stat.tx.stat_sum.toh.mean,
624 xr_stat.tx.stat_sum.toh.max,
625 pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
626 } else
627 sprintf(toh, "(report not available)");
628
629 if (xr_stat.tx.stat_sum.update.sec == 0)
630 strcpy(last_update, "never");
631 else {
632 pj_gettimeofday(&now);
633 PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
634 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
635 now.sec / 3600,
636 (now.sec % 3600) / 60,
637 now.sec % 60,
638 now.msec);
639 }
640
641 len = pj_ansi_snprintf(p, end-p,
642 "%s TX last update: %s\n"
643 "%s begin seq=%d, end seq=%d\n"
644 "%s pkt loss=%s, dup=%s\n"
645 "%s (msec) min avg max dev\n"
646 "%s jitter : %s\n"
647 "%s toh : %s",
648 indent, last_update,
649 indent,
650 xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
651 indent, loss, dup,
652 indent,
653 indent, jitter,
654 indent, toh
655 );
656 VALIDATE_PRINT_BUF();
657
658
659 /* VoIP Metrics */
660 len = pj_ansi_snprintf(p, end-p, "%s VoIP Metrics", indent);
661 VALIDATE_PRINT_BUF();
662
663 PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
664 PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
665 PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
666 PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
667 PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
668 PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
669 PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
670
671 switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
672 case PJMEDIA_RTCP_XR_PLC_DIS:
673 sprintf(plc, "DISABLED");
674 break;
675 case PJMEDIA_RTCP_XR_PLC_ENH:
676 sprintf(plc, "ENHANCED");
677 break;
678 case PJMEDIA_RTCP_XR_PLC_STD:
679 sprintf(plc, "STANDARD");
680 break;
681 case PJMEDIA_RTCP_XR_PLC_UNK:
682 default:
683 sprintf(plc, "UNKNOWN");
684 break;
685 }
686
687 switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
688 case PJMEDIA_RTCP_XR_JB_FIXED:
689 sprintf(jba, "FIXED");
690 break;
691 case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
692 sprintf(jba, "ADAPTIVE");
693 break;
694 default:
695 sprintf(jba, "UNKNOWN");
696 break;
697 }
698
699 sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
700
701 if (xr_stat.rx.voip_mtc.update.sec == 0)
702 strcpy(last_update, "never");
703 else {
704 pj_gettimeofday(&now);
705 PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
706 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
707 now.sec / 3600,
708 (now.sec % 3600) / 60,
709 now.sec % 60,
710 now.msec);
711 }
712
713 len = pj_ansi_snprintf(p, end-p,
714 "%s RX last update: %s\n"
715 "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
716 "%s burst : density=%d (%.2f%%), duration=%d%s\n"
717 "%s gap : density=%d (%.2f%%), duration=%d%s\n"
718 "%s delay : round trip=%d%s, end system=%d%s\n"
719 "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
720 "%s quality : R factor=%s, ext R factor=%s\n"
721 "%s MOS LQ=%s, MOS CQ=%s\n"
722 "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
723 "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s",
724 indent,
725 last_update,
726 /* packets */
727 indent,
728 xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
729 xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
730 /* burst */
731 indent,
732 xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
733 xr_stat.rx.voip_mtc.burst_dur, "ms",
734 /* gap */
735 indent,
736 xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
737 xr_stat.rx.voip_mtc.gap_dur, "ms",
738 /* delay */
739 indent,
740 xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
741 xr_stat.rx.voip_mtc.end_sys_delay, "ms",
742 /* level */
743 indent,
744 signal_lvl, "dB",
745 noise_lvl, "dB",
746 rerl, "",
747 /* quality */
748 indent,
749 r_factor, ext_r_factor,
750 indent,
751 mos_lq, mos_cq,
752 /* config */
753 indent,
754 plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
755 /* JB delay */
756 indent,
757 xr_stat.rx.voip_mtc.jb_nom, "ms",
758 xr_stat.rx.voip_mtc.jb_max, "ms",
759 xr_stat.rx.voip_mtc.jb_abs_max, "ms"
760 );
761 VALIDATE_PRINT_BUF();
762
763 PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
764 PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
765 PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
766 PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
767 PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
768 PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
769 PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
770
771 switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
772 case PJMEDIA_RTCP_XR_PLC_DIS:
773 sprintf(plc, "DISABLED");
774 break;
775 case PJMEDIA_RTCP_XR_PLC_ENH:
776 sprintf(plc, "ENHANCED");
777 break;
778 case PJMEDIA_RTCP_XR_PLC_STD:
779 sprintf(plc, "STANDARD");
780 break;
781 case PJMEDIA_RTCP_XR_PLC_UNK:
782 default:
783 sprintf(plc, "unknown");
784 break;
785 }
786
787 switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
788 case PJMEDIA_RTCP_XR_JB_FIXED:
789 sprintf(jba, "FIXED");
790 break;
791 case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
792 sprintf(jba, "ADAPTIVE");
793 break;
794 default:
795 sprintf(jba, "unknown");
796 break;
797 }
798
799 sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
800
801 if (xr_stat.tx.voip_mtc.update.sec == 0)
802 strcpy(last_update, "never");
803 else {
804 pj_gettimeofday(&now);
805 PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
806 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
807 now.sec / 3600,
808 (now.sec % 3600) / 60,
809 now.sec % 60,
810 now.msec);
811 }
812
813 len = pj_ansi_snprintf(p, end-p,
814 "%s TX last update: %s\n"
815 "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
816 "%s burst : density=%d (%.2f%%), duration=%d%s\n"
817 "%s gap : density=%d (%.2f%%), duration=%d%s\n"
818 "%s delay : round trip=%d%s, end system=%d%s\n"
819 "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
820 "%s quality : R factor=%s, ext R factor=%s\n"
821 "%s MOS LQ=%s, MOS CQ=%s\n"
822 "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
823 "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s",
824 indent,
825 last_update,
826 /* pakcets */
827 indent,
828 xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
829 xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
830 /* burst */
831 indent,
832 xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
833 xr_stat.tx.voip_mtc.burst_dur, "ms",
834 /* gap */
835 indent,
836 xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
837 xr_stat.tx.voip_mtc.gap_dur, "ms",
838 /* delay */
839 indent,
840 xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
841 xr_stat.tx.voip_mtc.end_sys_delay, "ms",
842 /* level */
843 indent,
844 signal_lvl, "dB",
845 noise_lvl, "dB",
846 rerl, "",
847 /* quality */
848 indent,
849 r_factor, ext_r_factor,
850 indent,
851 mos_lq, mos_cq,
852 /* config */
853 indent,
854 plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
855 /* JB delay */
856 indent,
857 xr_stat.tx.voip_mtc.jb_nom, "ms",
858 xr_stat.tx.voip_mtc.jb_max, "ms",
859 xr_stat.tx.voip_mtc.jb_abs_max, "ms"
860 );
861 VALIDATE_PRINT_BUF();
862
863
864 /* RTT delay (by receiver side) */
865 len = pj_ansi_snprintf(p, end-p,
866 "%s RTT (from recv) min avg max last dev",
867 indent);
868 VALIDATE_PRINT_BUF();
869 len = pj_ansi_snprintf(p, end-p,
870 "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f",
871 indent,
872 xr_stat.rtt.min / 1000.0,
873 xr_stat.rtt.mean / 1000.0,
874 xr_stat.rtt.max / 1000.0,
875 xr_stat.rtt.last / 1000.0,
876 pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0
877 );
878 VALIDATE_PRINT_BUF();
879 } /* if audio */;
880 #endif
881
882 }
883 }
884
885 #else /* PJSUA_MEDIA_HAS_PJMEDIA ||
886 (PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO &&
887 PJSUA_THIRD_PARTY_STREAM_HAS_GET_STAT) */
888
dump_media_session(const char * indent,char * buf,unsigned maxlen,pjsua_call * call)889 static void dump_media_session(const char *indent,
890 char *buf, unsigned maxlen,
891 pjsua_call *call)
892 {
893 PJ_UNUSED_ARG(indent);
894 PJ_UNUSED_ARG(buf);
895 PJ_UNUSED_ARG(maxlen);
896 PJ_UNUSED_ARG(call);
897 }
898
899 #endif /* PJSUA_MEDIA_HAS_PJMEDIA ||
900 (PJSUA_THIRD_PARTY_STREAM_HAS_GET_INFO &&
901 PJSUA_THIRD_PARTY_STREAM_HAS_GET_STAT) */
902
903
904 /* Print call info */
print_call(const char * title,int call_id,char * buf,pj_size_t size)905 void print_call(const char *title,
906 int call_id,
907 char *buf, pj_size_t size)
908 {
909 int len;
910 pjsua_call *call = &pjsua_var.calls[call_id];
911 pjsip_inv_session *inv = call->inv;
912 pjsip_dialog *dlg;
913 char userinfo[PJSIP_MAX_URL_SIZE];
914
915 /* Dump invite sesion info. */
916
917 dlg = (inv? inv->dlg: call->async_call.dlg);
918 len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
919 if (len < 0)
920 pj_ansi_strcpy(userinfo, "<--uri too long-->");
921 else
922 userinfo[len] = '\0';
923
924 len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
925 title,
926 pjsip_inv_state_name((call->hanging_up || !inv)?
927 PJSIP_INV_STATE_DISCONNECTED:
928 inv->state),
929 userinfo);
930 if (len < 1 || len >= (int)size) {
931 pj_ansi_strcpy(buf, "<--uri too long-->");
932 len = 18;
933 } else
934 buf[len] = '\0';
935 }
936
937
938 /*
939 * Dump call and media statistics to string.
940 */
pjsua_call_dump(pjsua_call_id call_id,pj_bool_t with_media,char * buffer,unsigned maxlen,const char * indent)941 PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id,
942 pj_bool_t with_media,
943 char *buffer,
944 unsigned maxlen,
945 const char *indent)
946 {
947 pjsua_call *call;
948 pjsip_dialog *dlg;
949 pj_time_val duration, res_delay, con_delay;
950 char tmp[128];
951 char *p, *end;
952 pj_status_t status;
953 int len;
954
955 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
956 PJ_EINVAL);
957
958 status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg);
959 if (status != PJ_SUCCESS)
960 return status;
961
962 *buffer = '\0';
963 p = buffer;
964 end = buffer + maxlen;
965 len = 0;
966
967 print_call(indent, call_id, tmp, sizeof(tmp));
968
969 len = (int)pj_ansi_strlen(tmp);
970 pj_ansi_strcpy(buffer, tmp);
971
972 p += len;
973 *p++ = '\r';
974 *p++ = '\n';
975
976 /* Calculate call duration */
977 if (call->conn_time.sec != 0) {
978 pj_gettimeofday(&duration);
979 PJ_TIME_VAL_SUB(duration, call->conn_time);
980 con_delay = call->conn_time;
981 PJ_TIME_VAL_SUB(con_delay, call->start_time);
982 } else {
983 duration.sec = duration.msec = 0;
984 con_delay.sec = con_delay.msec = 0;
985 }
986
987 /* Calculate first response delay */
988 if (call->res_time.sec != 0) {
989 res_delay = call->res_time;
990 PJ_TIME_VAL_SUB(res_delay, call->start_time);
991 } else {
992 res_delay.sec = res_delay.msec = 0;
993 }
994
995 /* Print duration */
996 len = pj_ansi_snprintf(p, end-p,
997 "%s Call time: %02dh:%02dm:%02ds, "
998 "1st res in %d ms, conn in %dms",
999 indent,
1000 (int)(duration.sec / 3600),
1001 (int)((duration.sec % 3600)/60),
1002 (int)(duration.sec % 60),
1003 (int)PJ_TIME_VAL_MSEC(res_delay),
1004 (int)PJ_TIME_VAL_MSEC(con_delay));
1005
1006 if (len > 0 && len < end-p) {
1007 p += len;
1008 *p++ = '\n';
1009 *p = '\0';
1010 }
1011
1012 /* Dump session statistics */
1013 if (with_media)
1014 dump_media_session(indent, p, (unsigned)(end-p), call);
1015
1016 pjsip_dlg_dec_lock(dlg);
1017
1018 return PJ_SUCCESS;
1019 }
1020
1021