summaryrefslogtreecommitdiffstats
path: root/tools/jprof/stub/libmalloc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/jprof/stub/libmalloc.cpp')
-rw-r--r--tools/jprof/stub/libmalloc.cpp790
1 files changed, 0 insertions, 790 deletions
diff --git a/tools/jprof/stub/libmalloc.cpp b/tools/jprof/stub/libmalloc.cpp
deleted file mode 100644
index 2da2a37b3..000000000
--- a/tools/jprof/stub/libmalloc.cpp
+++ /dev/null
@@ -1,790 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-// vim:cindent:sw=4:et:ts=8:
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// The linux glibc hides part of sigaction if _POSIX_SOURCE is defined
-#if defined(linux)
-#undef _POSIX_SOURCE
-#undef _SVID_SOURCE
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-#endif
-
-#include <errno.h>
-#if defined(linux)
-#include <linux/rtc.h>
-#include <pthread.h>
-#endif
-#include <unistd.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <signal.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/syscall.h>
-#include <ucontext.h>
-#include <execinfo.h>
-
-#include "libmalloc.h"
-#include "jprof.h"
-#include <string.h>
-#include <errno.h>
-#include <dlfcn.h>
-
-// Must define before including jprof.h
-void *moz_xmalloc(size_t size)
-{
- return malloc(size);
-}
-
-void moz_xfree(void *mem)
-{
- free(mem);
-}
-
-#ifdef NTO
-#include <sys/link.h>
-extern r_debug _r_debug;
-#else
-#include <link.h>
-#endif
-
-#define USE_GLIBC_BACKTRACE 1
-// To debug, use #define JPROF_STATIC
-#define JPROF_STATIC static
-
-static int gLogFD = -1;
-static pthread_t main_thread;
-
-static bool gIsSlave = false;
-static int gFilenamePID;
-
-static void startSignalCounter(unsigned long millisec);
-static int enableRTCSignals(bool enable);
-
-
-//----------------------------------------------------------------------
-// replace use of atexit()
-
-static void DumpAddressMap();
-
-struct JprofShutdown {
- JprofShutdown() {}
- ~JprofShutdown() {
- DumpAddressMap();
- }
-};
-
-static void RegisterJprofShutdown() {
- // This instanciates the dummy class above, and will trigger the class
- // destructor when libxul is unloaded. This is equivalent to atexit(),
- // but gracefully handles dlclose().
- static JprofShutdown t;
-}
-
-#if defined(i386) || defined(_i386) || defined(__x86_64__)
-JPROF_STATIC void CrawlStack(malloc_log_entry* me,
- void* stack_top, void* top_instr_ptr)
-{
-#if USE_GLIBC_BACKTRACE
- // This probably works on more than x86! But we need a way to get the
- // top instruction pointer, which is kindof arch-specific
- void *array[500];
- int cnt, i;
- u_long numpcs = 0;
-
- // This is from glibc. A more generic version might use
- // libunwind and/or CaptureStackBackTrace() on Windows
- cnt = backtrace(&array[0],sizeof(array)/sizeof(array[0]));
-
- // StackHook->JprofLog->CrawlStack
- // Then we have sigaction, which replaced top_instr_ptr
- array[3] = top_instr_ptr;
- for (i = 3; i < cnt; i++)
- {
- me->pcs[numpcs++] = (char *) array[i];
- }
- me->numpcs = numpcs;
-
-#else
- // original code - this breaks on many platforms
- void **bp;
-#if defined(__i386)
- __asm__( "movl %%ebp, %0" : "=g"(bp));
-#elif defined(__x86_64__)
- __asm__( "movq %%rbp, %0" : "=g"(bp));
-#else
- // It would be nice if this worked uniformly, but at least on i386 and
- // x86_64, it stopped working with gcc 4.1, because it points to the
- // end of the saved registers instead of the start.
- bp = __builtin_frame_address(0);
-#endif
- u_long numpcs = 0;
- bool tracing = false;
-
- me->pcs[numpcs++] = (char*) top_instr_ptr;
-
- while (numpcs < MAX_STACK_CRAWL) {
- void** nextbp = (void**) *bp++;
- void* pc = *bp;
- if (nextbp < bp) {
- break;
- }
- if (tracing) {
- // Skip the signal handling.
- me->pcs[numpcs++] = (char*) pc;
- }
- else if (pc == top_instr_ptr) {
- tracing = true;
- }
- bp = nextbp;
- }
- me->numpcs = numpcs;
-#endif
-}
-#endif
-
-//----------------------------------------------------------------------
-
-static int rtcHz;
-static int rtcFD = -1;
-static bool circular = false;
-
-#if defined(linux) || defined(NTO)
-static void DumpAddressMap()
-{
- // Turn off the timer so we don't get interrupts during shutdown
-#if defined(linux)
- if (rtcHz) {
- enableRTCSignals(false);
- } else
-#endif
- {
- startSignalCounter(0);
- }
-
- char filename[2048];
- if (gIsSlave)
- snprintf(filename, sizeof(filename), "%s-%d", M_MAPFILE, gFilenamePID);
- else
- snprintf(filename, sizeof(filename), "%s", M_MAPFILE);
-
- int mfd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
- if (mfd >= 0) {
- malloc_map_entry mme;
- link_map* map = _r_debug.r_map;
- while (nullptr != map) {
- if (map->l_name && *map->l_name) {
- mme.nameLen = strlen(map->l_name);
- mme.address = map->l_addr;
- write(mfd, &mme, sizeof(mme));
- write(mfd, map->l_name, mme.nameLen);
-#if 0
- write(1, map->l_name, mme.nameLen);
- write(1, "\n", 1);
-#endif
- }
- map = map->l_next;
- }
- close(mfd);
- }
-}
-#endif
-
-static bool was_paused = true;
-
-JPROF_STATIC void JprofBufferDump();
-JPROF_STATIC void JprofBufferClear();
-
-static void ClearProfilingHook(int signum)
-{
- if (circular) {
- JprofBufferClear();
- puts("Jprof: cleared circular buffer.");
- }
-}
-
-static void EndProfilingHook(int signum)
-{
- if (circular)
- JprofBufferDump();
-
- DumpAddressMap();
- was_paused = true;
- puts("Jprof: profiling paused.");
-}
-
-
-
-//----------------------------------------------------------------------
-// proper usage would be a template, including the function to find the
-// size of an entry, or include a size header explicitly to each entry.
-#if defined(linux)
-#define DUMB_LOCK() pthread_mutex_lock(&mutex);
-#define DUMB_UNLOCK() pthread_mutex_unlock(&mutex);
-#else
-#define DUMB_LOCK() FIXME()
-#define DUMB_UNLOCK() FIXME()
-#endif
-
-
-class DumbCircularBuffer
-{
-public:
- DumbCircularBuffer(size_t init_buffer_size) {
- used = 0;
- buffer_size = init_buffer_size;
- buffer = (unsigned char *) malloc(buffer_size);
- head = tail = buffer;
-
-#if defined(linux)
- pthread_mutexattr_t mAttr;
- pthread_mutexattr_settype(&mAttr, PTHREAD_MUTEX_RECURSIVE_NP);
- pthread_mutex_init(&mutex, &mAttr);
- pthread_mutexattr_destroy(&mAttr);
-#endif
- }
- ~DumbCircularBuffer() {
- free(buffer);
-#if defined(linux)
- pthread_mutex_destroy (&mutex);
-#endif
- }
-
- void clear() {
- DUMB_LOCK();
- head = tail;
- used = 0;
- DUMB_UNLOCK();
- }
-
- bool empty() {
- return head == tail;
- }
-
- size_t space_available() {
- size_t result;
- DUMB_LOCK();
- if (tail > head)
- result = buffer_size - (tail-head) - 1;
- else
- result = head-tail - 1;
- DUMB_UNLOCK();
- return result;
- }
-
- void drop(size_t size) {
- // assumes correctness!
- DUMB_LOCK();
- head += size;
- if (head >= &buffer[buffer_size])
- head -= buffer_size;
- used--;
- DUMB_UNLOCK();
- }
-
- bool insert(void *data, size_t size) {
- // can fail if not enough space in the entire buffer
- DUMB_LOCK();
- if (space_available() < size)
- return false;
-
- size_t max_without_wrap = &buffer[buffer_size] - tail;
- size_t initial = size > max_without_wrap ? max_without_wrap : size;
-#if DEBUG_CIRCULAR
- fprintf(stderr,"insert(%d): max_without_wrap %d, size %d, initial %d\n",used,max_without_wrap,size,initial);
-#endif
- memcpy(tail,data,initial);
- tail += initial;
- data = ((char *)data)+initial;
- size -= initial;
- if (size != 0) {
-#if DEBUG_CIRCULAR
- fprintf(stderr,"wrapping by %d bytes\n",size);
-#endif
- memcpy(buffer,data,size);
- tail = &(((unsigned char *)buffer)[size]);
- }
-
- used++;
- DUMB_UNLOCK();
-
- return true;
- }
-
- // for external access to the buffer (saving)
- void lock() {
- DUMB_LOCK();
- }
-
- void unlock() {
- DUMB_UNLOCK();
- }
-
- // XXX These really shouldn't be public...
- unsigned char *head;
- unsigned char *tail;
- unsigned int used;
- unsigned char *buffer;
- size_t buffer_size;
-
-private:
- pthread_mutex_t mutex;
-};
-
-class DumbCircularBuffer *JprofBuffer;
-
-JPROF_STATIC void
-JprofBufferInit(size_t size)
-{
- JprofBuffer = new DumbCircularBuffer(size);
-}
-
-JPROF_STATIC void
-JprofBufferClear()
-{
- fprintf(stderr,"Told to clear JPROF circular buffer\n");
- JprofBuffer->clear();
-}
-
-JPROF_STATIC size_t
-JprofEntrySizeof(malloc_log_entry *me)
-{
- return offsetof(malloc_log_entry, pcs) + me->numpcs*sizeof(char*);
-}
-
-JPROF_STATIC void
-JprofBufferAppend(malloc_log_entry *me)
-{
- size_t size = JprofEntrySizeof(me);
-
- do {
- while (JprofBuffer->space_available() < size &&
- JprofBuffer->used > 0) {
-#if DEBUG_CIRCULAR
- fprintf(stderr,"dropping entry: %d in use, %d free, need %d, size_to_free = %d\n",
- JprofBuffer->used,JprofBuffer->space_available(),size,JprofEntrySizeof((malloc_log_entry *) JprofBuffer->head));
-#endif
- JprofBuffer->drop(JprofEntrySizeof((malloc_log_entry *) JprofBuffer->head));
- }
- if (JprofBuffer->space_available() < size)
- return;
-
- } while (!JprofBuffer->insert(me,size));
-}
-
-JPROF_STATIC void
-JprofBufferDump()
-{
- JprofBuffer->lock();
-#if DEBUG_CIRCULAR
- fprintf(stderr,"dumping JP_CIRCULAR buffer, %d of %d bytes\n",
- JprofBuffer->tail > JprofBuffer->head ?
- JprofBuffer->tail - JprofBuffer->head :
- JprofBuffer->buffer_size + JprofBuffer->tail - JprofBuffer->head,
- JprofBuffer->buffer_size);
-#endif
- if (JprofBuffer->tail >= JprofBuffer->head) {
- write(gLogFD, JprofBuffer->head, JprofBuffer->tail - JprofBuffer->head);
- } else {
- write(gLogFD, JprofBuffer->head, &(JprofBuffer->buffer[JprofBuffer->buffer_size]) - JprofBuffer->head);
- write(gLogFD, JprofBuffer->buffer, JprofBuffer->tail - JprofBuffer->buffer);
- }
- JprofBuffer->clear();
- JprofBuffer->unlock();
-}
-
-//----------------------------------------------------------------------
-
-JPROF_STATIC void
-JprofLog(u_long aTime, void* stack_top, void* top_instr_ptr)
-{
- // Static is simply to make debugging tolerable
- static malloc_log_entry me;
-
- me.delTime = aTime;
- me.thread = syscall(SYS_gettid); //gettid();
- if (was_paused) {
- me.flags = JP_FIRST_AFTER_PAUSE;
- was_paused = 0;
- } else {
- me.flags = 0;
- }
-
- CrawlStack(&me, stack_top, top_instr_ptr);
-
-#ifndef NTO
- if (circular) {
- JprofBufferAppend(&me);
- } else {
- write(gLogFD, &me, JprofEntrySizeof(&me));
- }
-#else
- printf("Neutrino is missing the pcs member of malloc_log_entry!! \n");
-#endif
-}
-
-static int realTime;
-
-/* Lets interrupt at 10 Hz. This is so my log files don't get too large.
- * This can be changed to a faster value latter. This timer is not
- * programmed to reset, even though it is capable of doing so. This is
- * to keep from getting interrupts from inside of the handler.
-*/
-static void startSignalCounter(unsigned long millisec)
-{
- struct itimerval tvalue;
-
- tvalue.it_interval.tv_sec = 0;
- tvalue.it_interval.tv_usec = 0;
- tvalue.it_value.tv_sec = millisec/1000;
- tvalue.it_value.tv_usec = (millisec%1000)*1000;
-
- if (realTime) {
- setitimer(ITIMER_REAL, &tvalue, nullptr);
- } else {
- setitimer(ITIMER_PROF, &tvalue, nullptr);
- }
-}
-
-static long timerMilliSec = 50;
-
-#if defined(linux)
-static int setupRTCSignals(int hz, struct sigaction *sap)
-{
- /* global */ rtcFD = open("/dev/rtc", O_RDONLY);
- if (rtcFD < 0) {
- perror("JPROF_RTC setup: open(\"/dev/rtc\", O_RDONLY)");
- return 0;
- }
-
- if (sigaction(SIGIO, sap, nullptr) == -1) {
- perror("JPROF_RTC setup: sigaction(SIGIO)");
- return 0;
- }
-
- if (ioctl(rtcFD, RTC_IRQP_SET, hz) == -1) {
- perror("JPROF_RTC setup: ioctl(/dev/rtc, RTC_IRQP_SET, $JPROF_RTC_HZ)");
- return 0;
- }
-
- if (ioctl(rtcFD, RTC_PIE_ON, 0) == -1) {
- perror("JPROF_RTC setup: ioctl(/dev/rtc, RTC_PIE_ON)");
- return 0;
- }
-
- if (fcntl(rtcFD, F_SETSIG, 0) == -1) {
- perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETSIG, 0)");
- return 0;
- }
-
- if (fcntl(rtcFD, F_SETOWN, getpid()) == -1) {
- perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETOWN, getpid())");
- return 0;
- }
-
- return 1;
-}
-
-static int enableRTCSignals(bool enable)
-{
- static bool enabled = false;
- if (enabled == enable) {
- return 0;
- }
- enabled = enable;
-
- int flags = fcntl(rtcFD, F_GETFL);
- if (flags < 0) {
- perror("JPROF_RTC setup: fcntl(/dev/rtc, F_GETFL)");
- return 0;
- }
-
- if (enable) {
- flags |= FASYNC;
- } else {
- flags &= ~FASYNC;
- }
-
- if (fcntl(rtcFD, F_SETFL, flags) == -1) {
- if (enable) {
- perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETFL, flags | FASYNC)");
- } else {
- perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETFL, flags & ~FASYNC)");
- }
- return 0;
- }
-
- return 1;
-}
-#endif
-
-JPROF_STATIC void StackHook(
-int signum,
-siginfo_t *info,
-void *ucontext)
-{
- static struct timeval tFirst;
- static int first=1;
- size_t millisec = 0;
-
-#if defined(linux)
- if (rtcHz && pthread_self() != main_thread) {
- // Only collect stack data on the main thread, for now.
- return;
- }
-#endif
-
- if(first && !(first=0)) {
- puts("Jprof: received first signal");
-#if defined(linux)
- if (rtcHz) {
- enableRTCSignals(true);
- } else
-#endif
- {
- gettimeofday(&tFirst, 0);
- millisec = 0;
- }
- } else {
-#if defined(linux)
- if (rtcHz) {
- enableRTCSignals(true);
- } else
-#endif
- {
- struct timeval tNow;
- gettimeofday(&tNow, 0);
- double usec = 1e6*(tNow.tv_sec - tFirst.tv_sec);
- usec += (tNow.tv_usec - tFirst.tv_usec);
- millisec = static_cast<size_t>(usec*1e-3);
- }
- }
-
- gregset_t &gregs = ((ucontext_t*)ucontext)->uc_mcontext.gregs;
-#ifdef __x86_64__
- JprofLog(millisec, (void*)gregs[REG_RSP], (void*)gregs[REG_RIP]);
-#else
- JprofLog(millisec, (void*)gregs[REG_ESP], (void*)gregs[REG_EIP]);
-#endif
-
- if (!rtcHz)
- startSignalCounter(timerMilliSec);
-}
-
-NS_EXPORT_(void) setupProfilingStuff(void)
-{
- static int gFirstTime = 1;
- char filename[2048]; // XXX fix
-
- if(gFirstTime && !(gFirstTime=0)) {
- int startTimer = 1;
- int doNotStart = 1;
- int firstDelay = 0;
- int append = O_TRUNC;
- char *tst = getenv("JPROF_FLAGS");
-
- /* Options from JPROF_FLAGS environment variable:
- * JP_DEFER -> Wait for a SIGPROF (or SIGALRM, if JP_REALTIME
- * is set) from userland before starting
- * to generate them internally
- * JP_START -> Install the signal handler
- * JP_PERIOD -> Time between profiler ticks
- * JP_FIRST -> Extra delay before starting
- * JP_REALTIME -> Take stack traces in intervals of real time
- * rather than time used by the process (and the
- * system for the process). This is useful for
- * finding time spent by the X server.
- * JP_APPEND -> Append to jprof-log rather than overwriting it.
- * This is somewhat risky since it depends on the
- * address map staying constant across multiple runs.
- * JP_FILENAME -> base filename to use when saving logs. Note that
- * this does not affect the mapfile.
- * JP_CIRCULAR -> use a circular buffer of size N, write/clear on SIGUSR1
- *
- * JPROF_SLAVE is set if this is not the first process.
- */
-
- circular = false;
-
- if(tst) {
- if(strstr(tst, "JP_DEFER"))
- {
- doNotStart = 0;
- startTimer = 0;
- }
- if(strstr(tst, "JP_START")) doNotStart = 0;
- if(strstr(tst, "JP_REALTIME")) realTime = 1;
- if(strstr(tst, "JP_APPEND")) append = O_APPEND;
-
- char *delay = strstr(tst,"JP_PERIOD=");
- if(delay) {
- double tmp = strtod(delay+strlen("JP_PERIOD="), nullptr);
- if (tmp>=1e-3) {
- timerMilliSec = static_cast<unsigned long>(1000 * tmp);
- } else {
- fprintf(stderr,
- "JP_PERIOD of %g less than 0.001 (1ms), using 1ms\n",
- tmp);
- timerMilliSec = 1;
- }
- }
-
- char *circular_op = strstr(tst,"JP_CIRCULAR=");
- if(circular_op) {
- size_t size = atol(circular_op+strlen("JP_CIRCULAR="));
- if (size < 1000) {
- fprintf(stderr,
- "JP_CIRCULAR of %lu less than 1000, using 10000\n",
- (unsigned long) size);
- size = 10000;
- }
- JprofBufferInit(size);
- fprintf(stderr,"JP_CIRCULAR buffer of %lu bytes\n", (unsigned long) size);
- circular = true;
- }
-
- char *first = strstr(tst, "JP_FIRST=");
- if(first) {
- firstDelay = atol(first+strlen("JP_FIRST="));
- }
-
- char *rtc = strstr(tst, "JP_RTC_HZ=");
- if (rtc) {
-#if defined(linux)
- rtcHz = atol(rtc+strlen("JP_RTC_HZ="));
- timerMilliSec = 0; /* This makes JP_FIRST work right. */
- realTime = 1; /* It's the _R_TC and all. ;) */
-
-#define IS_POWER_OF_TWO(x) (((x) & ((x) - 1)) == 0)
-
- if (!IS_POWER_OF_TWO(rtcHz) || rtcHz < 2) {
- fprintf(stderr, "JP_RTC_HZ must be power of two and >= 2, "
- "but %d was provided; using default of 2048\n",
- rtcHz);
- rtcHz = 2048;
- }
-#else
- fputs("JP_RTC_HZ found, but RTC profiling only supported on "
- "Linux!\n", stderr);
-
-#endif
- }
- const char *f = strstr(tst,"JP_FILENAME=");
- if (f)
- f = f + strlen("JP_FILENAME=");
- else
- f = M_LOGFILE;
-
- char *is_slave = getenv("JPROF_SLAVE");
- if (!is_slave)
- setenv("JPROF_SLAVE","", 0);
- gIsSlave = !!is_slave;
-
- gFilenamePID = syscall(SYS_gettid); //gettid();
- if (is_slave)
- snprintf(filename,sizeof(filename),"%s-%d",f,gFilenamePID);
- else
- snprintf(filename,sizeof(filename),"%s",f);
-
- // XXX FIX! inherit current capture state!
- }
-
- if(!doNotStart) {
-
- if(gLogFD<0) {
- gLogFD = open(filename, O_CREAT | O_WRONLY | append, 0666);
- if(gLogFD<0) {
- fprintf(stderr, "Unable to create " M_LOGFILE);
- perror(":");
- } else {
- struct sigaction action;
- sigset_t mset;
-
- // Dump out the address map when we terminate
- RegisterJprofShutdown();
-
- main_thread = pthread_self();
- //fprintf(stderr,"jprof: main_thread = %u\n",
- // (unsigned int)main_thread);
-
- // FIX! probably should block these against each other
- // Very unlikely.
- sigemptyset(&mset);
- action.sa_handler = nullptr;
- action.sa_sigaction = StackHook;
- action.sa_mask = mset;
- action.sa_flags = SA_RESTART | SA_SIGINFO;
-#if defined(linux)
- if (rtcHz) {
- if (!setupRTCSignals(rtcHz, &action)) {
- fputs("jprof: Error initializing RTC, NOT "
- "profiling\n", stderr);
- return;
- }
- }
-
- if (!rtcHz || firstDelay != 0)
-#endif
- {
- if (realTime) {
- sigaction(SIGALRM, &action, nullptr);
- }
- }
- // enable PROF in all cases to simplify JP_DEFER/pause/restart
- sigaction(SIGPROF, &action, nullptr);
-
- // make it so a SIGUSR1 will stop the profiling
- // Note: It currently does not close the logfile.
- // This could be configurable (so that it could
- // later be reopened).
-
- struct sigaction stop_action;
- stop_action.sa_handler = EndProfilingHook;
- stop_action.sa_mask = mset;
- stop_action.sa_flags = SA_RESTART;
- sigaction(SIGUSR1, &stop_action, nullptr);
-
- // make it so a SIGUSR2 will clear the circular buffer
-
- stop_action.sa_handler = ClearProfilingHook;
- stop_action.sa_mask = mset;
- stop_action.sa_flags = SA_RESTART;
- sigaction(SIGUSR2, &stop_action, nullptr);
-
- printf("Jprof: Initialized signal handler and set "
- "timer for %lu %s, %d s "
- "initial delay\n",
- rtcHz ? rtcHz : timerMilliSec,
- rtcHz ? "Hz" : "ms",
- firstDelay);
-
- if(startTimer) {
-#if defined(linux)
- /* If we have an initial delay we can just use
- startSignalCounter to set up a timer to fire the
- first stackHook after that delay. When that happens
- we'll go and switch to RTC profiling. */
- if (rtcHz && firstDelay == 0) {
- puts("Jprof: enabled RTC signals");
- enableRTCSignals(true);
- } else
-#endif
- {
- puts("Jprof: started timer");
- startSignalCounter(firstDelay*1000 + timerMilliSec);
- }
- }
- }
- }
- }
- } else {
- printf("setupProfilingStuff() called multiple times\n");
- }
-}