• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  /*
2   * Copyright (C) 2009 The Android Open Source Project
3   * All rights reserved.
4   *
5   * Redistribution and use in source and binary forms, with or without
6   * modification, are permitted provided that the following conditions
7   * are met:
8   *  * Redistributions of source code must retain the above copyright
9   *    notice, this list of conditions and the following disclaimer.
10   *  * Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in
12   *    the documentation and/or other materials provided with the
13   *    distribution.
14   *
15   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18   * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19   * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21   * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22   * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23   * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26   * SUCH DAMAGE.
27   */
28  
29  #include <cstdio>
30  #include <cstdlib>
31  #include <ctime>
32  #include <errno.h>
33  #include <fcntl.h>
34  #include <getopt.h>
35  #include <limits.h>
36  #include <string.h>
37  #include <sys/stat.h>
38  #include <linux/fadvise.h>
39  #include <unistd.h>
40  #include <fts.h>
41  
42  #include "stopwatch.h"
43  #include "sysutil.h"
44  #include "testcase.h"
45  
46  // Stress test for the sdcard. Use this to generate some load on the
47  // sdcard and collect performance statistics. The output is either a
48  // human readable report or the raw timing samples that can be
49  // processed using another tool.
50  //
51  // Needs debugfs:
52  //   adb root;
53  //   adb shell mount -t debugfs none /sys/kernel/debug
54  //
55  // The following tests are defined (value of the --test flag):
56  //  write:       Open a file write some random data and close.
57  //  read:        Open a file read it and close.
58  //  read_write:  Combine readers and writers.
59  //  open_create: Open|create an non existing file.
60  //
61  // For each run you can control how many processes will run the test in
62  // parallel to simulate a real load (--procnb flag)
63  //
64  // For each process, the test selected will be executed many time to
65  // get a meaningful average/min/max (--iterations flag)
66  //
67  // Use --dump to also get the raw data.
68  //
69  // For read/write tests, size is the number of Kbytes to use.
70  //
71  // To build: mmm system/extras/tests/sdcard/Android.mk SDCARD_TESTS=1
72  //
73  // Examples:
74  // adb shell /system/bin/sdcard_perf_test --test=read --size=1000 --chunk-size=100 --procnb=1 --iterations=10 --dump > /tmp/data.txt
75  // adb shell /system/bin/sdcard_perf_test --test=write --size=1000 --chunk-size=100 --procnb=1 --iterations=100 --dump > /tmp/data.txt
76  //
77  // To watch the memory: cat /proc/meminfo
78  // If the phone crashes, look at /proc/last_kmsg on reboot.
79  //
80  // TODO: It would be cool if we could play with various fadvise()
81  // strategies in here to see how tweaking read-ahead changes things.
82  //
83  
84  extern char *optarg;
85  extern int optind, opterr, optopt;
86  
87  // TODO: No clue where fadvise is. Disabled for now.
88  #define FADVISE(fd, off, len, advice) (void)0
89  
90  #ifndef min
91  #define min(a,b) (((a)>(b))?(b):(a))
92  #endif
93  
94  namespace  {
95  using android::kernelVersion;
96  using android::kMinKernelVersionBufferSize;
97  using android::schedFeatures;
98  using android::kMinSchedFeaturesBufferSize;
99  using android_test::StopWatch;
100  using android::writePidAndWaitForReply;
101  using android::waitForChildrenAndSignal;
102  using android::waitForChildrenOrExit;
103  using android_test::TestCase;
104  
105  const char *kAppName = "sdcard_perf_test";
106  const char *kTestDir = "/sdcard/perf";
107  const bool kVerbose = false;
108  
109  // Used by getopt to parse the command line.
110  struct option long_options[] = {
111      {"size", required_argument, 0, 's'},
112      {"chunk-size", required_argument, 0, 'S'},
113      {"depth", required_argument, 0, 'D'},
114      {"iterations",  required_argument, 0, 'i'},
115      {"procnb",  required_argument, 0, 'p'},
116      {"test",  required_argument, 0, 't'},
117      {"dump",  no_argument, 0, 'd'},
118      {"cpu-scaling",  no_argument, 0, 'c'},
119      {"sync",  required_argument, 0, 'f'},
120      {"truncate", no_argument, 0, 'e'},
121      {"no-new-fair-sleepers", no_argument, 0, 'z'},
122      {"no-normalized-sleepers", no_argument, 0, 'Z'},
123      {"fadvise", required_argument, 0, 'a'},
124      {"help", no_argument, 0, 'h'},
125      {0, 0, 0, 0},
126  };
127  
usage()128  void usage()
129  {
130      printf("sdcard_perf_test --test=write|read|read_write|open_create|traverse [options]\n\n"
131             "  -t --test:        Select the test.\n"
132             "  -s --size:        Size in kbytes of the data.\n"
133             "  -S --chunk-size:  Size of a chunk. Default to size ie 1 chunk.\n"
134             "                    Data will be written/read using that chunk size.\n"
135             "  -D --depth:       Depth of directory tree to create for traversal.\n",
136             "  -i --iterations:  Number of time a process should carry its task.\n"
137             "  -p --procnb:      Number of processes to use.\n"
138             "  -d --dump:        Print the raw timing on stdout.\n"
139             "  -c --cpu-scaling: Enable cpu scaling.\n"
140             "  -s --sync: fsync|sync Use fsync or sync in write test. Default: no sync call.\n"
141             "  -e --truncate:    Truncate to size the file on creation.\n"
142             "  -z --no-new-fair-sleepers: Turn them off. You need to mount debugfs.\n"
143             "  -Z --no-normalized-sleepers: Turn them off. You need to mount debugfs.\n"
144             "  -a --fadvise:     Specify an fadvise policy (not supported).\n"
145             );
146  }
147  
148  // Print command line, pid, kernel version, OOM adj and scheduler.
printHeader(int argc,char ** argv,const TestCase & testCase)149  void printHeader(int argc, char **argv, const TestCase& testCase)
150  {
151      printf("# Command: ");
152      for (int i = 0; i < argc; ++i)
153      {
154          printf("%s ", argv[i]);
155      }
156      printf("\n");
157  
158      printf("# Pid: %d\n", getpid());
159  
160      {
161          char buffer[kMinKernelVersionBufferSize] = {0, };
162          if (kernelVersion(buffer, sizeof(buffer)) > 0)
163          {
164              printf("# Kernel: %s", buffer);
165          }
166      }
167  
168      // Earlier on, running this test was crashing the phone. It turned
169      // out that it was using too much memory but its oom_adj value was
170      // -17 which means disabled. -16 is the system_server and 0 is
171      // typically what applications run at. The issue is that adb runs
172      // at -17 and so is this test. We force oom_adj to 0 unless the
173      // oom_adj option has been used.
174      // TODO: We talked about adding an option to control oom_adj, not
175      // sure if we still need that.
176      int oomAdj = android::pidOutOfMemoryAdj();
177  
178      printf("# Oom_adj: %d ", oomAdj);
179      if (oomAdj < 0)
180      {
181          android::setPidOutOfMemoryAdj(0);
182          printf("adjuted to %d", android::pidOutOfMemoryAdj());
183      }
184      printf("\n");
185  
186      {
187          char buffer[kMinSchedFeaturesBufferSize] = {0, };
188          if (schedFeatures(buffer, sizeof(buffer)) > 0)
189          {
190              printf("# Sched features: %s", buffer);
191          }
192      }
193      printf("# Fadvise: %s\n", testCase.fadviseAsStr());
194  }
195  
196  // Remove all the files under kTestDir and clear the caches.
cleanup()197  void cleanup() {
198      android::resetDirectory(kTestDir);
199      android::syncAndDropCaches();  // don't pollute runs.
200  }
201  
202  // @param argc, argv have a wild guess.
203  // @param[out] testCase Structure built from the cmd line args.
parseCmdLine(int argc,char ** argv,TestCase * testCase)204  void parseCmdLine(int argc, char **argv, TestCase *testCase)\
205  {
206      int c;
207  
208      while(true)
209      {
210          // getopt_long stores the option index here.
211          int option_index = 0;
212  
213          c = getopt_long (argc, argv,
214                           "hS:s:D:i:p:t:dcf:ezZa:",
215                           long_options,
216                           &option_index);
217          // Detect the end of the options.
218          if (c == -1) break;
219  
220          switch (c)
221          {
222              case 's':
223                  testCase->setDataSize(atoi(optarg) * 1024);
224                  break;
225              case 'S':
226                  testCase->setChunkSize(atoi(optarg) * 1024);
227                  break;
228              case 'D': // tree depth
229                  testCase->setTreeDepth(atoi(optarg));
230                  break;
231              case 'i':
232                  testCase->setIter(atoi(optarg));
233                  printf("# Iterations: %d\n", testCase->iter());
234                  break;
235              case 'p':
236                  testCase->setNproc(atoi(optarg));
237                  printf("# Proc nb: %d\n", testCase->nproc());
238                  break;
239              case 't':
240                  testCase->setTypeFromName(optarg);
241                  printf("# Test name %s\n", testCase->name());
242                  break;
243              case 'd':
244                  testCase->setDump();
245                  break;
246              case 'c':
247                  printf("# Cpu scaling is on\n");
248                  testCase->setCpuScaling();
249                  break;
250              case 'f':
251                  if (strcmp("sync", optarg) == 0) {
252                      testCase->setSync(TestCase::SYNC);
253                  } else if (strcmp("fsync", optarg) == 0) {
254                      testCase->setSync(TestCase::FSYNC);
255                  }
256                  break;
257              case 'e':  // e for empty
258                  printf("# Will truncate to size\n");
259                  testCase->setTruncateToSize();
260                  break;
261              case 'z':  // no new fair sleepers
262                  testCase->setNewFairSleepers(false);
263                  break;
264              case 'Z':  // no normalized sleepers
265                  testCase->setNormalizedSleepers(false);
266                  break;
267              case 'a':  // fadvise
268                  testCase->setFadvise(optarg);
269                  break;
270              case 'h':
271                  usage();
272                  exit(0);
273              default:
274                  fprintf(stderr, "Unknown option %s\n", optarg);
275                  exit(EXIT_FAILURE);
276          }
277      }
278  }
279  
280  // ----------------------------------------------------------------------
281  // READ TEST
282  
283  // Read a file.  We use a new file each time to avoid any caching
284  // effect that would happen if we were reading the same file each
285  // time.
286  // @param chunk buffer large enough where the chunk read are written.
287  // @param idx iteration number.
288  // @param testCase has all the timers and paramters to run the test.
289  
readData(char * const chunk,const int idx,TestCase * testCase)290  bool readData(char *const chunk, const int idx, TestCase *testCase)
291  {
292      char filename[80] = {'\0',};
293  
294      sprintf(filename, "%s/file-%d-%d", kTestDir, idx, getpid());
295  
296      testCase->openTimer()->start();
297      int fd = open(filename, O_RDONLY);
298      testCase->openTimer()->stop();
299  
300      if (fd < 0)
301      {
302          fprintf(stderr, "Open read only failed.");
303          return false;
304      }
305      FADVISE(fd, 0, 0, testCase->fadvise());
306  
307      size_t left = testCase->dataSize();
308      pid_t *pid = (pid_t*)chunk;
309      while (left > 0)
310      {
311          char *dest = chunk;
312          size_t chunk_size = testCase->chunkSize();
313  
314          if (chunk_size > left)
315          {
316              chunk_size = left;
317              left = 0;
318          }
319          else
320          {
321              left -= chunk_size;
322          }
323  
324          testCase->readTimer()->start();
325          while (chunk_size > 0)
326          {
327              ssize_t s = read(fd, dest, chunk_size);
328              if (s < 0)
329              {
330                  fprintf(stderr, "Failed to read.\n");
331                  close(fd);
332                  return false;
333              }
334              chunk_size -= s;
335              dest += s;
336          }
337          testCase->readTimer()->stop();
338      }
339      close(fd);
340      if (testCase->pid() != *pid)
341      {
342          fprintf(stderr, "Wrong pid found @ read block %x != %x\n", testCase->pid(), *pid);
343          return false;
344      }
345      else
346      {
347          return true;
348      }
349  }
350  
351  
testRead(TestCase * testCase)352  bool testRead(TestCase *testCase) {
353      // Setup the testcase by writting some dummy files.
354      const size_t size = testCase->dataSize();
355      size_t chunk_size = testCase->chunkSize();
356      char *const chunk = new char[chunk_size];
357  
358      memset(chunk, 0xaa, chunk_size);
359      *((pid_t *)chunk) = testCase->pid(); // write our pid at the beginning of each chunk
360  
361      size_t iter = testCase->iter();
362  
363      // since readers are much faster we increase the number of
364      // iteration to last longer and have concurrent read/write
365      // thoughout the whole test.
366      if (testCase->type() == TestCase::READ_WRITE)
367      {
368          iter *= TestCase::kReadWriteFactor;
369      }
370  
371      for (size_t i = 0; i < iter; ++i)
372      {
373          char filename[80] = {'\0',};
374  
375          sprintf(filename, "%s/file-%d-%d", kTestDir, i, testCase->pid());
376          int fd = open(filename, O_RDWR | O_CREAT, S_IRWXU);
377  
378          size_t left = size;
379          while (left > 0)
380          {
381              if (chunk_size > left)
382              {
383                  chunk_size = left;
384              }
385              ssize_t written = write(fd, chunk, chunk_size);
386              if (written < 0)
387              {
388                  fprintf(stderr, "Write failed %d %s.", errno, strerror(errno));
389                  return false;
390              }
391              left -= written;
392          }
393          close(fd);
394      }
395      if (kVerbose) printf("Child %d all chunk written\n", testCase->pid());
396  
397      android::syncAndDropCaches();
398      testCase->signalParentAndWait();
399  
400      // Start the read test.
401      testCase->testTimer()->start();
402      for (size_t i = 0; i < iter; ++i)
403      {
404          if (!readData(chunk, i, testCase))
405          {
406              return false;
407          }
408      }
409      testCase->testTimer()->stop();
410  
411      delete [] chunk;
412      return true;
413  }
414  
415  // ----------------------------------------------------------------------
416  // WRITE TEST
417  
writeData(const char * const chunk,const int idx,TestCase * testCase)418  bool writeData(const char *const chunk, const int idx, TestCase *testCase) {
419      char filename[80] = {'\0',};
420  
421      sprintf(filename, "%s/file-%d-%d", kTestDir, idx, getpid());
422      testCase->openTimer()->start();
423      int fd = open(filename, O_RDWR | O_CREAT, S_IRWXU);  // no O_TRUNC, see header comment
424      testCase->openTimer()->stop();
425  
426      if (fd < 0)
427      {
428          fprintf(stderr, "Open write failed.");
429          return false;
430      }
431      FADVISE(fd, 0, 0, testCase->fadvise());
432  
433      if (testCase->truncateToSize())
434      {
435          testCase->truncateTimer()->start();
436          ftruncate(fd, testCase->dataSize());
437          testCase->truncateTimer()->stop();
438      }
439  
440      size_t left = testCase->dataSize();
441      while (left > 0)
442      {
443          const char *dest = chunk;
444          size_t chunk_size = testCase->chunkSize();
445  
446          if (chunk_size > left)
447          {
448              chunk_size = left;
449              left = 0;
450          }
451          else
452          {
453              left -= chunk_size;
454          }
455  
456  
457          testCase->writeTimer()->start();
458          while (chunk_size > 0)
459          {
460              ssize_t s = write(fd, dest, chunk_size);
461              if (s < 0)
462              {
463                  fprintf(stderr, "Failed to write.\n");
464                  close(fd);
465                  return false;
466              }
467              chunk_size -= s;
468              dest += s;
469          }
470          testCase->writeTimer()->stop();
471      }
472  
473      if (TestCase::FSYNC == testCase->sync())
474      {
475          testCase->syncTimer()->start();
476          fsync(fd);
477          testCase->syncTimer()->stop();
478      }
479      else if (TestCase::SYNC == testCase->sync())
480      {
481          testCase->syncTimer()->start();
482          sync();
483          testCase->syncTimer()->stop();
484      }
485      close(fd);
486      return true;
487  }
488  
testWrite(TestCase * testCase)489  bool testWrite(TestCase *testCase)
490  {
491      const size_t size = testCase->dataSize();
492      size_t chunk_size = testCase->chunkSize();
493      char *data = new char[chunk_size];
494  
495      memset(data, 0xaa, chunk_size);
496      // TODO: write the pid at the beginning like in the write
497      // test. Have a mode where we check the write was correct.
498      testCase->signalParentAndWait();
499  
500      testCase->testTimer()->start();
501      for (size_t i = 0; i < testCase->iter(); ++i)
502      {
503          if (!writeData(data, i, testCase))
504          {
505              return false;
506          }
507      }
508      testCase->testTimer()->stop();
509      delete [] data;
510      return true;
511  }
512  
513  
514  // ----------------------------------------------------------------------
515  // READ WRITE
516  
517  // Mix of read and write test. Even PID run the write test. Odd PID
518  // the read test. Not fool proof but work most of the time.
testReadWrite(TestCase * testCase)519  bool testReadWrite(TestCase *testCase)
520  {
521      if (getpid() & 0x1) {
522          return testRead(testCase);
523      } else {
524          return testWrite(testCase);
525      }
526  }
527  
528  // ----------------------------------------------------------------------
529  // OPEN CREATE TEST
530  
testOpenCreate(TestCase * testCase)531  bool testOpenCreate(TestCase *testCase)
532  {
533      char filename[80] = {'\0',};
534  
535      testCase->signalParentAndWait();
536      testCase->testTimer()->start();
537  
538      for (size_t i = 0; i < testCase->iter(); ++i)
539      {
540          sprintf(filename, "%s/file-%d-%d", kTestDir, i, testCase->pid());
541  
542          int fd = open(filename, O_RDWR | O_CREAT, S_IRWXU);
543          FADVISE(fd, 0, 0, testCase->fadvise());
544  
545          if (testCase->truncateToSize())
546          {
547              ftruncate(fd, testCase->dataSize());
548          }
549          if (fd < 0)
550          {
551              return false;
552          }
553          close(fd);
554      }
555      testCase->testTimer()->stop();
556      return true;
557  }
558  
writeTestFile(TestCase * testCase,const char * filename)559  bool writeTestFile(TestCase *testCase, const char* filename) {
560      int fd = open(filename, O_RDWR | O_CREAT, S_IRWXU);
561      if (fd < 0) {
562          fprintf(stderr, "open() failed: %s\n", strerror(errno));
563          return false;
564      }
565  
566      bool res = false;
567  
568      char * const chunk = new char[testCase->chunkSize()];
569      memset(chunk, 0xaa, testCase->chunkSize());
570  
571      size_t left = testCase->dataSize();
572      while (left > 0) {
573          char *dest = chunk;
574          size_t chunk_size = testCase->chunkSize();
575  
576          if (chunk_size > left) {
577              chunk_size = left;
578              left = 0;
579          } else {
580              left -= chunk_size;
581          }
582  
583          while (chunk_size > 0) {
584              ssize_t s = write(fd, dest, chunk_size);
585              if (s < 0) {
586                  fprintf(stderr, "write() failed: %s\n", strerror(errno));
587                  goto fail;
588              }
589              chunk_size -= s;
590              dest += s;
591          }
592      }
593  
594      res = true;
595  fail:
596      close(fd);
597      delete[] chunk;
598      return res;
599  }
600  
601  // ----------------------------------------------------------------------
602  // TRAVERSE
603  
604  #define MAX_PATH 512
605  
606  // Creates a directory tree that is both deep and wide, and times
607  // traversal using fts_open().
testTraverse(TestCase * testCase)608  bool testTraverse(TestCase *testCase) {
609      char path[MAX_PATH];
610      char filepath[MAX_PATH];
611      strcpy(path, kTestDir);
612  
613      // Generate a deep directory hierarchy
614      size_t depth = testCase->treeDepth();
615      for (size_t i = 0; i < depth; i++) {
616          // Go deeper by appending onto current path
617          snprintf(path + strlen(path), MAX_PATH - strlen(path), "/dir%d", i);
618          mkdir(path, S_IRWXU);
619  
620          // Create some files at this depth
621          strcpy(filepath, path);
622          int pathlen = strlen(path);
623          char* nameStart = filepath + pathlen;
624          for (size_t j = 0; j < depth; j++) {
625              snprintf(nameStart, MAX_PATH - pathlen, "/file%d", j);
626              writeTestFile(testCase, filepath);
627          }
628      }
629  
630      testCase->signalParentAndWait();
631      testCase->testTimer()->start();
632  
633      // Now traverse structure
634      size_t iter = testCase->iter();
635      for (size_t i = 0; i < iter; i++) {
636          testCase->traverseTimer()->start();
637  
638          FTS *ftsp;
639          if ((ftsp = fts_open((char **) &kTestDir, FTS_LOGICAL | FTS_XDEV, NULL)) == NULL) {
640              fprintf(stderr, "fts_open() failed: %s\n", strerror(errno));
641              return false;
642          }
643  
644          // Count discovered files
645          int dirs = 0, files = 0;
646  
647          FTSENT *curr;
648          while ((curr = fts_read(ftsp)) != NULL) {
649              switch (curr->fts_info) {
650              case FTS_D:
651                  dirs++;
652                  break;
653              case FTS_F:
654                  files++;
655                  break;
656              }
657          }
658  
659          fts_close(ftsp);
660  
661          testCase->traverseTimer()->stop();
662  
663          int expectedDirs = depth + 1;
664          if (expectedDirs != dirs) {
665              fprintf(stderr, "expected %d dirs, but found %d\n", expectedDirs, dirs);
666              return false;
667          }
668  
669          int expectedFiles = depth * depth;
670          if (expectedFiles != files) {
671              fprintf(stderr, "expected %d files, but found %d\n", expectedFiles, files);
672              return false;
673          }
674      }
675  
676      testCase->testTimer()->stop();
677      return true;
678  }
679  
680  }  // anonymous namespace
681  
main(int argc,char ** argv)682  int main(int argc, char **argv)
683  {
684      android_test::TestCase testCase(kAppName);
685  
686      cleanup();
687  
688      parseCmdLine(argc, argv, &testCase);
689      printHeader(argc, argv, testCase);
690  
691      printf("# File size %d kbytes\n", testCase.dataSize() / 1024);
692      printf("# Chunk size %d kbytes\n", testCase.chunkSize() / 1024);
693      printf("# Sync: %s\n", testCase.syncAsStr());
694  
695      if (!testCase.cpuScaling())
696      {
697          android::disableCpuScaling();
698      }
699  
700      switch(testCase.type()) {
701          case TestCase::WRITE:
702              testCase.mTestBody = testWrite;
703              break;
704          case TestCase::READ:
705              testCase.mTestBody = testRead;
706              break;
707          case TestCase::OPEN_CREATE:
708              testCase.mTestBody = testOpenCreate;
709              break;
710          case TestCase::READ_WRITE:
711              testCase.mTestBody = testReadWrite;
712              break;
713          case TestCase::TRAVERSE:
714              testCase.mTestBody = testTraverse;
715              break;
716          default:
717              fprintf(stderr, "Unknown test type %s", testCase.name());
718              exit(EXIT_FAILURE);
719      }
720  
721      testCase.createTimers();
722  
723      bool ok;
724  
725      ok = testCase.runTest();
726  
727      cleanup();
728      if (!ok)
729      {
730          printf("error %d %s", errno, strerror(errno));
731          exit(EXIT_FAILURE);
732      }
733      else
734      {
735          exit(EXIT_SUCCESS);
736      }
737  }
738