void eval_inner_loop (struct rt_tmbench_context *ctx, long dt) { if (ctx->date <= ctx->start_time) ctx->curr.overruns++; if (dt > ctx->curr.max) ctx->curr.max = dt; if (dt < ctx->curr.min) ctx->curr.min = dt; ctx->curr.avg += dt; ctx->date += ctx->period; if (!ctx->warmup && ctx->histogram_size) add_histogram (ctx, ctx->histogram_avg, tsc2ns (dt)); /* Evaluate overruns and adjust next release date. * Beware of signedness! */ while (dt > 0 && (unsigned long) dt > ctx->period) { ctx->curr.overruns++; ctx->date += ctx->period; dt -= ctx->period; } }
static void eval_inner_loop(struct rt_tmbench_context *ctx, long dt) { if (dt > ctx->curr.max) ctx->curr.max = dt; if (dt < ctx->curr.min) ctx->curr.min = dt; ctx->curr.avg += dt; #ifdef CONFIG_IPIPE_TRACE if (ctx->freeze_max && (dt > ctx->result.overall.max) && !ctx->warmup) { ipipe_trace_frozen_reset(); ipipe_trace_freeze(dt); ctx->result.overall.max = dt; } #endif /* CONFIG_IPIPE_TRACE */ ctx->date += ctx->period; if (!ctx->warmup && ctx->histogram_size) add_histogram(ctx, ctx->histogram_avg, dt); /* Evaluate overruns and adjust next release date. Beware of signedness! */ while (dt > 0 && (unsigned long)dt > ctx->period) { ctx->curr.overruns++; ctx->date += ctx->period; dt -= ctx->period; } }
void eval_outer_loop (struct rt_tmbench_context *ctx) { long curr_max_ns = tsc2ns (ctx->curr.max); long curr_min_ns = tsc2ns (ctx->curr.min); long curr_avg_ns = tsc2ns (ctx->curr.avg); if (!ctx->warmup) { if (ctx->histogram_size) { add_histogram (ctx, ctx->histogram_max, curr_max_ns); add_histogram (ctx, ctx->histogram_min, curr_min_ns); } ctx->result.last.min = curr_min_ns; if (curr_min_ns < ctx->result.overall.min) ctx->result.overall.min = curr_min_ns; ctx->result.last.max = curr_max_ns; if (curr_max_ns > ctx->result.overall.max) ctx->result.overall.max = curr_max_ns; ctx->result.last.avg = slldiv (curr_avg_ns, ctx->samples_per_sec); ctx->result.overall.avg += ctx->result.last.avg; ctx->result.overall.overruns += ctx->curr.overruns; wake_up_interruptible (ctx->result_event); } if (ctx->warmup && (ctx->result.overall.test_loops == ctx->warmup_loops)) { ctx->result.overall.test_loops = 0; ctx->warmup = 0; } ctx->curr.min = 10000000; ctx->curr.max = -10000000; ctx->curr.avg = 0; ctx->curr.overruns = 0; ctx->result.overall.test_loops++; ctx->done = 1; }
void i2c_add(void *data1, const void *data2) { DECL_ASSIGN_I2C(i2c1,data1); DECL_ASSIGN_I2C(i2c2,data2); __u32 i; i2c1->maxouts = MAX(i2c1->maxouts, i2c2->maxouts); i2c1->oio_prev_time = MAX(i2c1->oio_prev_time, i2c2->oio_prev_time); if(i2c1->oio_size < i2c2->oio_size) { __u32 diff = i2c2->oio_size - i2c1->oio_size; i2c1->oio = realloc(i2c1->oio, i2c2->oio_size*sizeof(struct oio_data)); init_oio_data(i2c1->oio + diff, diff); i2c1->oio_size = i2c2->oio_size; } for (i = 0; i <= i2c2->maxouts; i++) { i2c1->oio[i].time += i2c2->oio[i].time; add_histogram(i2c1->oio[i].op[READ],i2c2->oio[i].op[READ]); add_histogram(i2c1->oio[i].op[WRITE],i2c2->oio[i].op[WRITE]); } }
static void eval_outer_loop(struct rt_tmbench_context *ctx) { if (!ctx->warmup) { if (ctx->histogram_size) { add_histogram(ctx, ctx->histogram_max, ctx->curr.max); add_histogram(ctx, ctx->histogram_min, ctx->curr.min); } ctx->result.last.min = ctx->curr.min; if (ctx->curr.min < ctx->result.overall.min) ctx->result.overall.min = ctx->curr.min; ctx->result.last.max = ctx->curr.max; if (ctx->curr.max > ctx->result.overall.max) ctx->result.overall.max = ctx->curr.max; ctx->result.last.avg = slldiv(ctx->curr.avg, ctx->samples_per_sec); ctx->result.overall.avg += ctx->result.last.avg; ctx->result.overall.overruns += ctx->curr.overruns; rtdm_event_pulse(&ctx->result_event); } if (ctx->warmup && (ctx->result.overall.test_loops == ctx->warmup_loops)) { ctx->result.overall.test_loops = 0; ctx->warmup = 0; } ctx->curr.min = 10000000; ctx->curr.max = -10000000; ctx->curr.avg = 0; ctx->curr.overruns = 0; ctx->result.overall.test_loops++; }
void latency (void *cookie) { int err, count, nsamples, warmup = 1; RTIME expected_tsc, period_tsc, start_ticks; RT_TIMER_INFO timer_info; RT_QUEUE q; rt_queue_create(&q, "queue", 0, 100, 0); if (!(hard_timer_running = rt_is_hard_timer_running())) { err = rt_timer_start(TM_ONESHOT); if (err) { fprintf(stderr,"latency: cannot start timer, code %d\n",err); return; } } err = rt_timer_inquire(&timer_info); if (err) { fprintf(stderr,"latency: rt_timer_inquire, code %d\n",err); return; } nsamples = ONE_BILLION / period_ns / 1; period_tsc = rt_timer_ns2tsc(period_ns); /* start time: one millisecond from now. */ start_ticks = timer_info.date + rt_timer_ns2ticks(1000000); expected_tsc = timer_info.tsc + rt_timer_ns2tsc(1000000); err = rt_task_set_periodic(NULL,start_ticks,period_ns); if (err) { fprintf(stderr,"latency: failed to set periodic, code %d\n",err); return; } for (;;) { long minj = TEN_MILLION, maxj = -TEN_MILLION, dt, sumj; long overrun = 0; test_loops++; for (count = sumj = 0; count < nsamples; count++) { expected_tsc += period_tsc; err = rt_task_wait_period(NULL); if (err) { if (err != -ETIMEDOUT) { rt_queue_delete(&q); rt_task_delete(NULL); /* Timer stopped. */ } overrun++; } dt = (long)(rt_timer_tsc() - expected_tsc); if (dt > maxj) maxj = dt; if (dt < minj) minj = dt; sumj += dt; if (!(finished || warmup) && (do_histogram || do_stats)) add_histogram(histogram_avg, dt); } if(!warmup) { if (!finished && (do_histogram || do_stats)) { add_histogram(histogram_max, maxj); add_histogram(histogram_min, minj); } minjitter = minj; if(minj < gminjitter) gminjitter = minj; maxjitter = maxj; if(maxj > gmaxjitter) gmaxjitter = maxj; avgjitter = sumj / nsamples; gavgjitter += avgjitter; goverrun += overrun; rt_sem_v(&display_sem); struct smpl_t { long minjitter, avgjitter, maxjitter, overrun; } *smpl; smpl = rt_queue_alloc(&q, sizeof(struct smpl_t)); #if 1 smpl->minjitter = rt_timer_tsc2ns(minj); smpl->maxjitter = rt_timer_tsc2ns(maxj); smpl->avgjitter = rt_timer_tsc2ns(sumj / nsamples); smpl->overrun = goverrun; rt_queue_send(&q, smpl, sizeof(struct smpl_t), TM_NONBLOCK); #endif } if(warmup && test_loops == WARMUP_TIME) { test_loops = 0; warmup = 0; } } }
void worker(void *cookie) { long long minj = 10000000, maxj = -10000000, dt, sumj = 0; unsigned long long count = 0; int err, n; err = rt_sem_create(&switch_sem, "dispsem", 0, S_FIFO); if (err) { fprintf(stderr,"switch: cannot create semaphore: %s\n", strerror(-err)); return; } for (n=0; n<nsamples; n++) { err = rt_sem_p(&switch_sem, TM_INFINITE); if (err) { if (err != -EIDRM) fprintf(stderr,"switch: failed to pend on semaphore, code %d\n", err); rt_task_delete(NULL); } if (++count != switch_count) { count = switch_count; lost++; continue; } // First few switches are slow. // Probably due to the Linux <-> RT context migration at task startup. if (count < ignore) continue; dt = (long) (rt_timer_tsc() - switch_tsc); if (dt > maxj) maxj = dt; if (dt < minj) minj = dt; sumj += dt; if (do_histogram) add_histogram(dt); } rt_sem_delete(&switch_sem); minjitter = minj; maxjitter = maxj; avgjitter = sumj / n; printf("RTH|%12s|%12s|%12s|%12s\n", "lat min", "lat avg", "lat max", "lost"); printf("RTD|%12.3f|%12.3f|%12.3f|%12lld\n", rt_timer_tsc2ns(minjitter) / 1000.0, rt_timer_tsc2ns(avgjitter) / 1000.0, rt_timer_tsc2ns(maxjitter) / 1000.0, lost); if (do_histogram) dump_histogram(); exit(0); }
static void worker(void *cookie) { long long minj = 10000000, maxj = -10000000, dt, sumj = 0; unsigned long long count = 0; int err, n; err = rt_sem_create(&switch_sem, "dispsem", 0, S_FIFO); if (err) { warning("failed to create semaphore (%s)\n", symerror(err)); return; } for (n = 0; n < nsamples; n++) { err = rt_sem_p(&switch_sem, TM_INFINITE); if (err) { if (err != -EIDRM && err != -EINVAL) warning("failed to pend on semaphore (%s)\n", symerror(err)); exit(EXIT_FAILURE); } dt = (long) (rt_timer_tsc() - switch_tsc); if (switch_count - count > 1) { lost += switch_count - count; count = switch_count; continue; } if (++count < warmup) continue; if (dt > maxj) maxj = dt; if (dt < minj) minj = dt; sumj += dt; if (do_histogram) add_histogram(dt); } rt_sem_delete(&switch_sem); minjitter = minj; maxjitter = maxj; avgjitter = sumj / n; printf("RTH|%12s|%12s|%12s|%12s\n", "lat min", "lat avg", "lat max", "lost"); printf("RTD|%12.3f|%12.3f|%12.3f|%12lld\n", rt_timer_tsc2ns(minjitter) / 1000.0, rt_timer_tsc2ns(avgjitter) / 1000.0, rt_timer_tsc2ns(maxjitter) / 1000.0, lost); if (late) printf("LATE: %d\n", late); if (do_histogram) dump_histogram(); exit(0); }
int read_histograms_x (HISTOGRAM **phisto, int nhisto, const long *xcld_ids, int nxcld, IO_BUFFER *iobuf) { int mhisto, ihisto, rc, ncounts; char title[256]; char type, cdummy; long ident; double rlower[2] = {0., 0.}, rupper[2] = {0., 0.}, rsum[2] = {0., 0.}, rtsum[2] = {0., 0.}; long ilower[2] = {0,0}, iupper[2] = {0,0}, isum[2] = {0,0}, itsum[2] = {0,0}; long entries=0, tentries=0, underflow[2] = {0,0}, overflow[2] = {0,0}; int nbins=0, nbins_2d=0, ibin, mbins[2] = {0,0}; HISTOGRAM *thisto=NULL, *ohisto=NULL; IO_ITEM_HEADER item_header; int adding = 0; if ( nhisto < 0 ) { adding = 1; nhisto = -nhisto; } if ( iobuf == (IO_BUFFER *) NULL ) return -1; item_header.type = 100; if ( (rc = get_item_begin(iobuf,&item_header)) != 0 ) return rc; if ( item_header.version < 1 || item_header.version > 2 ) { Warning("Wrong version no. of histogram data to be read"); return -1; } // fprintf(stderr,"Read histograms called, with %d histograms excluded\n",nxcld); mhisto = get_short(iobuf); for (ihisto=0; ihisto<mhisto; ihisto++) { int add_this = 0, exclude_this = 0; type = (char) get_byte(iobuf); if ( get_string(title,sizeof(title)-1,iobuf) % 2 == 0 ) cdummy = get_byte(iobuf); // Compiler may warn about it but this is OK. ident = get_long(iobuf); nbins = (int) get_short(iobuf); nbins_2d = (int) get_short(iobuf); entries = (uint32_t) get_long(iobuf); tentries = (uint32_t) get_long(iobuf); underflow[0] = (uint32_t) get_long(iobuf); overflow[0] = (uint32_t) get_long(iobuf); if ( type == 'R' || type == 'r' || type == 'F' || type == 'D' ) { rlower[0] = get_real(iobuf); rupper[0] = get_real(iobuf); rsum[0] = get_real(iobuf); rtsum[0] = get_real(iobuf); } else { ilower[0] = get_long(iobuf); iupper[0] = get_long(iobuf); isum[0] = get_long(iobuf); itsum[0] = get_long(iobuf); } if ( nbins_2d > 0 ) { underflow[1] = (uint32_t) get_long(iobuf); overflow[1] = (uint32_t) get_long(iobuf); if ( type == 'R' || type == 'r' || type == 'F' || type == 'D' ) { rlower[1] = get_real(iobuf); rupper[1] = get_real(iobuf); rsum[1] = get_real(iobuf); rtsum[1] = get_real(iobuf); } else { ilower[1] = get_long(iobuf); iupper[1] = get_long(iobuf); isum[1] = get_long(iobuf); itsum[1] = get_long(iobuf); } ncounts = nbins * nbins_2d; } else ncounts = nbins; /* Don't attempt to allocate histograms without data. */ if ( ncounts <= 0 ) continue; if ( xcld_ids != (const long *) NULL && nxcld > 0 ) { int ixcld; for ( ixcld=0; ixcld<nxcld && xcld_ids[ixcld] > 0; ixcld++ ) { if ( xcld_ids[ixcld] == ident ) { exclude_this = 1; break; } } } /* If the histogram has a numerical identifier delete a */ /* previously existing histogram with the same identifier. */ ohisto = NULL; if ( ident != 0 ) { if ( (ohisto=get_histogram_by_ident(ident)) != (HISTOGRAM *)NULL ) { if ( adding && ! exclude_this ) add_this = 1; else free_histogram(ohisto); } } /* (Re-) Allocate the new histogram according to its type. */ thisto = NULL; // if ( ! exclude_this ) /* Would really exclude all histograms of this ID but we just don't want to add it up */ { if ( nbins_2d > 0 ) { if ( type == 'R' || type == 'r' ) thisto = alloc_2d_real_histogram(rlower[0],rupper[0],nbins, rlower[1],rupper[1],nbins_2d); else if ( type == 'F' || type == 'D' ) { mbins[0] = nbins; mbins[1] = nbins_2d; thisto = allocate_histogram(&type,2,rlower,rupper,mbins); } else thisto = alloc_2d_int_histogram(ilower[0],iupper[0],nbins, ilower[1],iupper[1],nbins_2d); } else { if ( type == 'R' || type == 'r' ) thisto = alloc_real_histogram(rlower[0],rupper[0],nbins); else if ( type == 'F' || type == 'D' ) thisto = allocate_histogram(&type,1,rlower,rupper,&nbins); else thisto = alloc_int_histogram(ilower[0],iupper[0],nbins); } } /* If the allocation failed or the histogram should be excluded, skip the histogram contents. */ /* This should guarantee that reading the input doesn't get */ /* confused when there is not enough memory available to allocate */ /* a histogram. The drawback is that, so far, there is no failure */ /* indicator for the caller. */ if ( thisto == (HISTOGRAM *) NULL ) { if ( type == 'F' || type == 'D' ) for (ibin=0; ibin<10; ibin++ ) (void) get_real(iobuf); /* contents... in histogram extension */ if ( tentries > 0 ) for (ibin=0; ibin<ncounts; ibin++) (void) get_long(iobuf); /* long and real is the same length */ continue; } else thisto->type = type; /* Give the histogram its title and identifier. */ if ( *title ) describe_histogram(thisto,title,add_this?0:ident); #ifdef _REENTRANT histogram_lock(thisto); #endif /* Set the values for histogram statistics. */ thisto->entries = entries; thisto->tentries = tentries; thisto->underflow = underflow[0]; thisto->overflow = overflow[0]; if ( type == 'R' || type == 'r' || type == 'F' || type == 'D' ) { thisto->specific.real.sum = rsum[0]; thisto->specific.real.tsum = rtsum[0]; } else { thisto->specific.integer.sum = isum[0]; thisto->specific.integer.tsum = itsum[0]; } if ( nbins_2d > 0 ) { thisto->underflow_2d = underflow[1]; thisto->overflow_2d = overflow[1]; if ( type == 'R' || type == 'r' || type == 'F' || type == 'D' ) { thisto->specific_2d.real.sum = rsum[1]; thisto->specific_2d.real.tsum = rtsum[1]; } else { thisto->specific_2d.integer.sum = isum[1]; thisto->specific_2d.integer.tsum = itsum[1]; } } /* If wanted and possible, return the pointer to caller. */ if ( phisto != (HISTOGRAM **) NULL && ihisto < nhisto ) phisto[ihisto] = (add_this ? ohisto : thisto); /* Finally, read the histogram contents. */ if ( type == 'F' || type == 'D' ) { struct Histogram_Extension *he = thisto->extension; he->content_all = get_real(iobuf); he->content_inside = get_real(iobuf); get_vector_of_real(he->content_outside,8,iobuf); if ( type == 'F' ) { if ( thisto->tentries > 0 ) for ( ibin=0; ibin<ncounts; ibin++ ) he->fdata[ibin] = (float) get_real(iobuf); else for ( ibin=0; ibin<ncounts; ibin++ ) he->fdata[ibin] = (float) 0.; } else { if ( thisto->tentries > 0 ) get_vector_of_real(he->ddata,ncounts,iobuf); else for ( ibin=0; ibin<ncounts; ibin++ ) he->ddata[ibin] = 0.; } } else { if ( thisto->tentries > 0 ) get_vector_of_long((long *)thisto->counts,ncounts,iobuf); else for ( ibin=0; ibin<nbins; ibin++ ) thisto->counts[ibin] = 0; } #ifdef _REENTRANT histogram_unlock(thisto); #endif if ( add_this ) { // fprintf(stderr,"Adding histogram ID %ld\n", ident); add_histogram(ohisto,thisto); free_histogram(thisto); } } if ( (rc = get_item_end(iobuf,&item_header)) != 0 ) return rc; return(mhisto); }
void latency(void *cookie) { int err, count, nsamples, warmup = 1; RTIME expected_tsc, period_tsc, start_ticks, fault_threshold; RT_TIMER_INFO timer_info; unsigned old_relaxed = 0; err = rt_timer_inquire(&timer_info); if (err) { fprintf(stderr, "latency: rt_timer_inquire, code %d\n", err); return; } fault_threshold = rt_timer_ns2tsc(CONFIG_XENO_DEFAULT_PERIOD); nsamples = ONE_BILLION / period_ns / 1000; period_tsc = rt_timer_ns2tsc(period_ns); /* start time: one millisecond from now. */ start_ticks = timer_info.date + rt_timer_ns2ticks(1000000); expected_tsc = timer_info.tsc + rt_timer_ns2tsc(1000000); err = rt_task_set_periodic(NULL, start_ticks, rt_timer_ns2ticks(period_ns)); if (err) { fprintf(stderr, "latency: failed to set periodic, code %d\n", err); return; } for (;;) { long minj = TEN_MILLION, maxj = -TEN_MILLION, dt; long overrun = 0; long long sumj; test_loops++; for (count = sumj = 0; count < nsamples; count++) { unsigned new_relaxed; unsigned long ov; expected_tsc += period_tsc; err = rt_task_wait_period(&ov); dt = (long)(rt_timer_tsc() - expected_tsc); new_relaxed = sampling_relaxed; if (dt > maxj) { if (new_relaxed != old_relaxed && dt > fault_threshold) max_relaxed += new_relaxed - old_relaxed; maxj = dt; } old_relaxed = new_relaxed; if (dt < minj) minj = dt; sumj += dt; if (err) { if (err != -ETIMEDOUT) { fprintf(stderr, "latency: wait period failed, code %d\n", err); exit(EXIT_FAILURE); /* Timer stopped. */ } overrun += ov; expected_tsc += period_tsc * ov; } if (freeze_max && (dt > gmaxjitter) && !(finished || warmup)) { xntrace_user_freeze(rt_timer_tsc2ns(dt), 0); gmaxjitter = dt; } if (!(finished || warmup) && need_histo()) add_histogram(histogram_avg, dt); } if (!warmup) { if (!finished && need_histo()) { add_histogram(histogram_max, maxj); add_histogram(histogram_min, minj); } minjitter = minj; if (minj < gminjitter) gminjitter = minj; maxjitter = maxj; if (maxj > gmaxjitter) gmaxjitter = maxj; avgjitter = sumj / nsamples; gavgjitter += avgjitter; goverrun += overrun; rt_sem_v(&display_sem); } if (warmup && test_loops == WARMUP_TIME) { test_loops = 0; warmup = 0; } } }
void latency (void *cookie) { int err, count, nsamples; RTIME expected, period; err = rt_timer_start(TM_ONESHOT); if (err) { fprintf(stderr,"latency: cannot start timer, code %d\n",err); return; } nsamples = ONE_BILLION / sampling_period; period = rt_timer_ns2ticks(sampling_period); expected = rt_timer_tsc(); err = rt_task_set_periodic(NULL,TM_NOW,sampling_period); if (err) { fprintf(stderr,"latency: failed to set periodic, code %d\n",err); return; } for (;;) { long minj = TEN_MILLION, maxj = -TEN_MILLION, dt, sumj; overrun = 0; test_loops++; for (count = sumj = 0; count < nsamples; count++) { unsigned long ov; expected += period; err = rt_task_wait_period(&ov); if (err) { if (err != -ETIMEDOUT) rt_task_delete(NULL); /* Timer stopped. */ overrun += ov; } dt = (long)(rt_timer_tsc() - expected); if (dt > maxj) maxj = dt; if (dt < minj) minj = dt; sumj += dt; if (!finished && (do_histogram || do_stats)) add_histogram(histogram_avg, dt); } if (!finished && (do_histogram || do_stats)) { add_histogram(histogram_max, maxj); add_histogram(histogram_min, minj); } minjitter = rt_timer_ticks2ns(minj); maxjitter = rt_timer_ticks2ns(maxj); avgjitter = rt_timer_ticks2ns(sumj / nsamples); rt_sem_v(&display_sem); } }
void latency (void *cookie) { int err, count, nsamples, warmup = 1; RTIME expected_tsc, period_tsc, start_ticks; RT_TIMER_INFO timer_info; err = rt_timer_start(TM_ONESHOT); if (err) { fprintf(stderr,"latency: cannot start timer, code %d\n",err); return; } err = rt_timer_inquire(&timer_info); if (err) { fprintf(stderr,"latency: rt_timer_inquire, code %d\n",err); return; } nsamples = ONE_BILLION / period_ns; period_tsc = rt_timer_ns2tsc(period_ns); /* start time: one millisecond from now. */ start_ticks = timer_info.date + rt_timer_ns2ticks(1000000); expected_tsc = timer_info.tsc + rt_timer_ns2tsc(1000000); err = rt_task_set_periodic(NULL,start_ticks,rt_timer_ns2ticks(period_ns)); if (err) { fprintf(stderr,"latency: failed to set periodic, code %d\n",err); return; } for (;;) { long minj = TEN_MILLION, maxj = -TEN_MILLION, dt, sumj; long overrun = 0; test_loops++; for (count = sumj = 0; count < nsamples; count++) { expected_tsc += period_tsc; err = rt_task_wait_period(NULL); if (err) { if (err != -ETIMEDOUT) { fprintf(stderr,"latency: wait period failed, code %d\n",err); rt_task_delete(NULL); /* Timer stopped. */ } overrun++; } dt = (long)(rt_timer_tsc() - expected_tsc); if (dt > maxj) maxj = dt; if (dt < minj) minj = dt; sumj += dt; if (!(finished || warmup) && (do_histogram || do_stats)) add_histogram(histogram_avg, dt); } if(!warmup) { if (!finished && (do_histogram || do_stats)) { add_histogram(histogram_max, maxj); add_histogram(histogram_min, minj); } minjitter = minj; if(minj < gminjitter) gminjitter = minj; maxjitter = maxj; if(maxj > gmaxjitter) gmaxjitter = maxj; avgjitter = sumj / nsamples; gavgjitter += avgjitter; goverrun += overrun; rt_sem_v(&display_sem); } if(warmup && test_loops == WARMUP_TIME) { test_loops = 0; warmup = 0; } } }