Whamcloud - gitweb
LU-482 test: sync MDS before first replay test
[fs/lustre-release.git] / snmp / lustre-snmp-util.c
1 /* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
2  * vim:expandtab:shiftwidth=8:tabstop=8:
3  *
4  * GPL HEADER START
5  *
6  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License version 2 only,
10  * as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License version 2 for more details (a copy is included
16  * in the LICENSE file that accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License
19  * version 2 along with this program; If not, see
20  * http://www.sun.com/software/products/lustre/docs/GPLv2.pdf
21  *
22  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
23  * CA 95054 USA or visit www.sun.com if you need additional information or
24  * have any questions.
25  *
26  * GPL HEADER END
27  */
28 /*
29  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
30  * Use is subject to license terms.
31  */
32 /*
33  * This file is part of Lustre, http://www.lustre.org/
34  * Lustre is a trademark of Sun Microsystems, Inc.
35  *
36  * snmp/lustre-snmp-util.c
37  *
38  * Author: PJ Kirner <pjkirner@clusterfs.com>
39  */
40
41 /*
42  *   include important headers
43  */
44
45 #include <net-snmp/net-snmp-config.h>
46 #include <net-snmp/net-snmp-includes.h>
47 #include <net-snmp/agent/net-snmp-agent-includes.h>
48
49 /*
50  *  include our .h file
51  */ 
52
53 #include <sys/types.h>
54 #if defined (__linux__)
55 #include <sys/vfs.h>
56 #endif
57 #include <dirent.h>
58 #include <sys/stat.h>
59 #include <unistd.h>
60 #include <stdio.h>
61 #include <stdarg.h>
62 #include <string.h>
63 #include "lustre-snmp-util.h"
64
65 /*********************************************************************
66  * Function:    get_file_list
67  *
68  * Description: For the given valid directory  path, returns the list
69  *              all directories or files in that path.
70  *
71  * Input:   'dirname' the directory path.
72  *          'file_type' if this takes the value DIR_TYPE then
73  *              returns the list of directories in that path.
74  *          If its of type FILE_TYPE then returns the list of files
75  *          in that path.
76  *          'count' pointer to number of elements returned in the
77  *          return string. 
78  *
79  * Output:  List of  directories/files in that path.
80  *
81  *********************************************************************/
82
83 char *get_file_list(const char *dirname, int file_type, uint32_t *count)
84 {
85
86     DIR           *pdir = NULL;
87     struct dirent *pdirent = NULL;
88     int           curr_offset = 0;
89     int           byte_count = 0;
90     int           file_count = 0;
91     char          *ret_str = NULL;
92     char          filename[MAX_PATH_SIZE];
93     int           cond1, cond2;
94
95     if ((dirname == NULL) || ((pdir = opendir(dirname)) == NULL )) {
96         if (dirname == NULL) {
97             report("%s %s:line %d %s", __FILE__, __FUNCTION__, __LINE__,
98                    "NULL directory is passed as parameter to funtion");
99         } else {
100             report("%s %s:line %d Error in opening the dir %s", __FILE__,
101                    __FUNCTION__, __LINE__, dirname);
102         }
103         if (count)
104             *count = 0;
105         return NULL;
106     }
107
108     while (1) {
109         if ((pdirent = readdir(pdir)) == NULL)
110             break;
111
112         /* Skip over '.' and '..' directores */
113         if ((pdirent->d_name[0] == '.') ||
114             !strcmp(pdirent->d_name, FILENAME_NUM_REF))
115             continue;
116         
117         sprintf(filename, "%s/%s", dirname, pdirent->d_name);
118         cond1 = (file_type == FILE_TYPE) && is_directory(filename);
119         cond2 = (file_type == DIR_TYPE) && (!is_directory(filename));
120
121         if (cond1 || cond2)
122             continue;
123
124         /* Calculate the number of bytes for this new entry.*/                    
125         byte_count += strlen(pdirent->d_name) + 1;
126         file_count++;
127     }
128     if (count)
129         *count = file_count;
130     
131     if (file_count != 0) {
132         
133         /* need one extra one for the finall NULL terminator*/
134         if ((ret_str = (char *) malloc(byte_count + 1)) == NULL) {
135             report("get_file_list() failed to malloc(%d)",byte_count+1);
136             closedir(pdir);
137             return NULL;
138         }    
139         
140         rewinddir(pdir);
141         
142         while (file_count != 0) {
143             if ((pdirent = readdir(pdir)) == NULL)
144                 break;
145
146             if ((pdirent->d_name[0] == '.') ||
147                 !strcmp(pdirent->d_name, FILENAME_NUM_REF))
148                 continue;
149             
150             sprintf(filename, "%s/%s", dirname, pdirent->d_name);
151             cond1 = (file_type == FILE_TYPE) && is_directory(filename);
152             cond2 = (file_type == DIR_TYPE) && (!is_directory(filename));
153
154             if (cond1 || cond2)
155                 continue;
156
157             strcpy(ret_str + curr_offset, pdirent->d_name);
158             curr_offset = curr_offset + strlen(pdirent->d_name) + 1;
159             file_count--;
160         }
161         /* Put in the finall null terminator*/
162         ret_str[byte_count] = '\0';
163     }
164     closedir(pdir);
165     return ret_str;
166 }
167
168
169 /*********************************************************************
170  * Function:    is_directory
171  *
172  * Description: Checks if given filename is a directory or not.
173  *              all directories or files in that path.
174  *
175  * Input:   'filename' the directory path to be checked.
176  *
177  * Output:  Returns 1 if its a directory else 0.
178  *
179  *********************************************************************/
180
181 int is_directory(const char *filename)
182 {
183
184     struct stat statf;
185     int result;
186
187     result = stat(filename, &statf);
188     return ((result == SUCCESS) && (statf.st_mode & S_IFDIR));
189 }
190
191 /*********************************************************************
192  * Function:    read_string
193  *
194  * Description: For the given valid file path, reads the data in
195  *              that file.
196  *
197  * Input:   'filepath' the file whose data is to be accessed.
198  *          'lustre_var' the data from the file is read into
199  *           this variable, returned to the requestor.
200  *          'var_max_size' the max size of the string
201  *          'report_error' boolean if error should be reported on 
202  *           missing filepath
203  *
204  * Output:  Returns SUCCESS if read successfully from file else
205  *          returns ERROR.
206  *********************************************************************/
207  
208 int  read_string(const char *filepath, char *lustre_var, size_t var_max_size)
209 {
210     FILE    *fptr = NULL;
211     int     len = 0;
212     int     ret_val = SUCCESS;
213     int     report_error = 1;
214
215     if ((filepath == NULL) || (lustre_var == NULL)) {
216         report("%s %s:line %d %s", __FILE__, __FUNCTION__, __LINE__,
217                "Input parameter is NULL");
218         ret_val = ERROR;
219     } else {
220         fptr = fopen(filepath, "r");
221
222         if (fptr == NULL) {
223             if(report_error)
224                 report("%s %s:line %d Unable to open the file %s", __FILE__,
225                        __FUNCTION__, __LINE__, filepath);
226             ret_val = ERROR;
227         } else {
228             if (fgets(lustre_var, var_max_size, fptr) == NULL) {
229                 report("%s %s:line %d read failed for file %s", __FILE__,
230                        __FUNCTION__, __LINE__, filepath);
231                  ret_val = ERROR;
232             } else {
233                 len = strlen(lustre_var);
234                 /*
235                     Last char is EOF, before string ends,
236                     so '\0' is moved to last but one.
237                 */
238                 lustre_var[len-1] = lustre_var[len];
239             }
240             fclose(fptr);
241         }
242     }
243     return ret_val;
244 }
245
246 /**************************************************************************
247  * Function:   lustrefs_ctrl
248  *
249  * Description: Execute /etc/init.d/lustre script for starting,
250  *              stopping and restarting Lustre services in child process.
251  *
252  * Input:  Start/Stop/Restart Command Number.
253  * Output: Returns  void
254  *
255  **************************************************************************/
256
257 void lustrefs_ctrl(int command)
258 {
259     char *cmd[3];
260
261     cmd[0] = LUSTRE_SERVICE;
262     switch (command) {
263     case ONLINE:
264         cmd[1] = "start";
265         break;
266     case OFFLINE:
267         cmd[1] = "stop";
268         break;
269     case RESTART:
270         cmd[1] = "restart";
271         break;
272     default:
273         return;
274     }
275
276     cmd[2] = (char *)0;
277
278     if (fork() == 0) {
279         execvp(cmd[0], cmd);
280         report("failed to execvp(\'%s %s\')",cmd[0],cmd[1]);
281     }
282     return;
283 }
284
285 /*****************************************************************************
286  * Function:     get_sysstatus
287  *
288  * Description:  Read /var/lustre/sysStatus file, and based on file contents
289  *               return the status of Lustre services.
290  *
291  * Input:   void
292  * Output:  Return ONLINE/OFFLINE/ONLINE PENDING/OFFLINE PENDING status
293  *          values.
294  *
295  ****************************************************************************/
296
297 int get_sysstatus(void)
298 {
299     int     ret_val = ERROR ;
300     char    sys_status[50] = {0};
301     
302     if(SUCCESS == read_string(FILENAME_SYS_STATUS,sys_status,sizeof(sys_status)))
303     {
304         if (memcmp(sys_status, STR_ONLINE_PENDING,strlen(STR_ONLINE_PENDING)) == 0)
305             ret_val = ONLINE_PENDING;
306         else if (memcmp(sys_status, STR_ONLINE, strlen(STR_ONLINE)) == 0)
307             ret_val = ONLINE;
308         else if (memcmp(sys_status, STR_OFFLINE_PENDING,strlen(STR_OFFLINE_PENDING)) == 0)
309             ret_val = OFFLINE_PENDING;
310         else if (memcmp(sys_status, STR_OFFLINE, strlen(STR_OFFLINE)) == 0)
311             ret_val = OFFLINE;
312         else
313             report("%s %s:line %d Bad Contents in file %s \'%s\'", __FILE__,
314                 __FUNCTION__, __LINE__, FILENAME_SYS_STATUS,sys_status);
315     }
316     return ret_val;
317 }
318
319
320 /*****************************************************************************
321  * Function:     read_ulong
322  *
323  * Description:  Read long values from lproc and copy to the location
324  *               pointed by input parameter.
325  *
326  * Input:   file path, and pointer for data to be copied
327  *
328  * Output:  Return ERROR or SUCCESS.
329  *
330  ****************************************************************************/
331
332 int read_ulong(const char *file_path, unsigned long *valuep)
333 {
334     char    file_data[MAX_LINE_SIZE];
335     int     ret_val;
336
337     if ((ret_val = read_string(file_path, file_data,sizeof(file_data))) == SUCCESS){
338         *valuep = strtoul(file_data,NULL,10);
339     }
340     return ret_val;
341 }
342
343 /*****************************************************************************
344  * Function:     read_counter64
345  *
346  * Description:  Read counter64 values from lproc and copy to the location
347  *               pointed by input parameter.
348  *
349  * Input:   file path, and pointer for data to be copied
350  *
351  * Output:  Return ERROR or SUCCESS.
352  *
353  ****************************************************************************/
354
355 int read_counter64(const char *file_path, counter64 *c64,int factor)
356 {
357     char    file_data[MAX_LINE_SIZE];
358     int     ret_val;
359     unsigned long long tmp = 0;
360
361     if ((ret_val = read_string(file_path, file_data,sizeof(file_data))) == SUCCESS) {
362         tmp = atoll(file_data) * factor;
363         c64->low = (unsigned long) (0x0FFFFFFFF & tmp);
364         tmp >>= 32; /* Shift right by 4 bytes */
365         c64->high = (unsigned long) (0x0FFFFFFFF & tmp);
366     }
367     return ret_val;
368 }
369
370 /*****************************************************************************
371  * Function:     get_nth_entry_from_list
372  *
373  * Description:  Find the n'th entry from a null terminated list of string
374  *
375  * Input:   dir_list - the list
376  *          num - the number of elements in the list
377  *          index - the index we are looking for
378  *
379  * Output:  Return NULL on failure, or the string name on success.
380  *
381  ****************************************************************************/
382
383 const char *get_nth_entry_from_list(const char* dir_list,int num,int index)
384 {
385     int i;
386     int cur_ptr = 0;
387     for(i=0;i<num;i++){
388         
389         /* 
390          * if we've reached the end of the list for some reason
391          * because num was wrong then stop processing
392          */
393         if( *(dir_list+cur_ptr) == 0)
394             break;
395             
396         /* If we've found the right one */    
397         if( i == index )
398             return dir_list+cur_ptr;
399             
400         /* Move to the next one*/            
401         cur_ptr += strlen(dir_list + cur_ptr)+1;
402     }
403     return NULL;
404 }
405
406 /*****************************************************************************
407  * Function:    report
408  *
409  * Description: This function used to report error msg to stderr and log into
410  *    log file(default file:/var/log/snmpd.log) when agent is started with
411  *    debug option -Dlsnmpd
412  * Input:   format string and variable arguments.
413  * Output:  void
414  ****************************************************************************/
415
416 void report(const char *fmt, ...)
417 {
418     char buf[1024];
419
420     va_list arg_list;
421     va_start(arg_list, fmt);
422     vsprintf(buf, fmt, arg_list);
423     va_end(arg_list);
424
425     DEBUGMSGTL(("lsnmpd", "%s\n", buf));
426     fprintf(stderr, "%s\n", buf);
427     return;
428 }
429
430
431
432 /**************************************************************************
433  * Function:   oid_table_ulong_handler
434  *
435  * Description: Fetch a unsigned long from the given location.
436  *              Setup var_len, and return a pointer to the data.
437  *
438  * Input:  file_path, and var_len pointer
439  *
440  * Output: NULL on failure, or pointer to data
441  *
442  **************************************************************************/
443
444 unsigned char* 
445     oid_table_ulong_handler(
446         const char* file_path,
447         size_t  *var_len)
448 {
449     static unsigned long ulong_ret;
450     if (SUCCESS != read_ulong(file_path,&ulong_ret))
451         return NULL;
452     *var_len = sizeof(ulong_ret);
453     return  (unsigned char *) &ulong_ret;
454 }
455
456 /**************************************************************************
457  * Function:   oid_table_c64_handler
458  *
459  * Description: Fetch a counter64 from the given location.
460  *              Setup var_len, and return a pointer to the data.
461  *
462  * Input:  file_path, and var_len pointer
463  *
464  * Output: NULL on failure, or pointer to data
465  *
466  **************************************************************************/
467
468 unsigned char* oid_table_c64_handler(const char* file_path,size_t  *var_len)
469 {
470     static counter64 c64;
471     if (SUCCESS != read_counter64(file_path,&c64,1))
472         return NULL;
473     *var_len = sizeof(c64);
474     return (unsigned char *) &c64;
475 }
476
477 /**************************************************************************
478  * Function:   oid_table_c64_kb_handler
479  *
480  * Description: Fetch a counter64 from the given location.
481  *              Setup var_len, and return a pointer to the data.
482  *              Different than oid_table_c64_handler in that
483  *              the original value is multiplied by 1024 before converting
484  *              to a counter64.  (e.g. turn KB into a Byte scaled value)
485  *
486  * Input:  file_path, and var_len pointer
487  *
488  * Output: NULL on failure, or pointer to data
489  *
490  **************************************************************************/
491
492 unsigned char* oid_table_c64_kb_handler(const char* file_path,size_t  *var_len)
493 {
494     static counter64 c64;
495     /* scale by factor of 1024*/
496     if (SUCCESS != read_counter64(file_path,&c64,1024))
497         return NULL;
498     *var_len = sizeof(c64);
499     return (unsigned char *) &c64;
500 }
501
502 /**************************************************************************
503  * Function:   oid_table_obj_name_handler
504  *
505  * Description: Just copy the file_path and return as the output value.
506  *
507  * Input:  file_path, and var_len pointer
508  *
509  * Output: NULL on failure, or pointer to data
510  *
511  **************************************************************************/
512
513 unsigned char* 
514     oid_table_obj_name_handler(
515         const char* file_path,
516         size_t  *var_len)
517 {
518     static unsigned char string[SPRINT_MAX_LEN];
519     *var_len = strlen(file_path);
520     *var_len = MIN_LEN(*var_len, sizeof(string));
521     memcpy(string, file_path, *var_len);
522     return (unsigned char *) string;
523 }
524
525 /**************************************************************************
526  * Function:   oid_table_string_handler
527  *
528  * Description: Fetch a string from the given location.
529  *              Setup var_len, and return a pointer to the data.
530  *
531  * Input:  file_path, and var_len pointer
532  *
533  * Output: NULL on failure, or pointer to data
534  *
535  **************************************************************************/
536
537 unsigned char* 
538     oid_table_string_handler(
539         const char* file_path,
540         size_t  *var_len)
541 {
542     static unsigned char string[SPRINT_MAX_LEN];
543     if( SUCCESS != read_string(file_path, (char *)string,sizeof(string)))
544         return NULL;
545     *var_len = strlen((char *)string);
546     return (unsigned char *) string;
547 }
548
549
550 /**************************************************************************
551  * Function:   oid_table_is_directory_handler
552  *
553  * Description: Determine if the file_path is a directory.  
554  *              Setup a boolean return value.
555  *              Setup var_len, and return a pointer to the data.
556  *
557  * Input:  file_path, and var_len pointer
558  *
559  * Output: NULL on failure, or pointer to data
560  *
561  **************************************************************************/
562
563 unsigned char* 
564     oid_table_is_directory_handler(
565         const char* file_path,
566         size_t *var_len)
567 {
568     static long long_ret;
569     long_ret =  is_directory(file_path);
570     *var_len = sizeof(long_ret);
571     return (unsigned char *) &long_ret;
572 }
573
574 /**************************************************************************
575  * Function:   var_genericTable
576  *
577  * Description: Handle Table driven OID processing
578  *
579  **************************************************************************/
580
581 unsigned char *
582 var_genericTable(struct variable *vp,
583             oid     *name,
584             size_t  *length,
585             int     exact,
586             size_t  *var_len,
587             WriteMethod **write_method,
588             const char *path,
589             struct oid_table *ptable)
590 {
591     char *dir_list;
592     uint32_t num;
593     int  deviceindex;
594     unsigned char *ret_val = NULL;
595     int i=0;
596     const char* obj_name;
597     
598     
599     /*
600      * Get the list of file.  If there are no elements
601      * return nothing
602      */
603     if( 0 == (dir_list = get_file_list(path, DIR_TYPE, &num)))
604         return NULL;
605
606     /*
607      * Setup the table
608      */
609     if (header_simple_table(vp,name,length,exact,var_len,write_method, num)
610                                                 == MATCH_FAILED )
611         goto cleanup_and_exit;
612
613     /*
614      * The number of the device we're looking at
615      */
616     deviceindex = name[*length - 1] - 1;
617
618     /*
619      * If we couldn't find this element
620      * something must have recently changed return
621      * nothing
622      */
623     if(deviceindex >= num){
624         report("deviceindex=%d exceeds number of elements=%d",deviceindex,num);
625         goto cleanup_and_exit;
626     }
627
628     /*
629      * Fetch the object name from the list
630      */
631     obj_name = get_nth_entry_from_list(dir_list,num,deviceindex);
632     if(obj_name == NULL){
633         /*
634          * Note this should never really happen because we check deviceindex >=num
635          * above.  And dir_list should be consitent with num
636          * but just in case...
637          */
638         report("object name not found in list",deviceindex,num);
639         goto cleanup_and_exit;
640     }
641
642     /*
643      * Find the matching magic - or the end of the list
644      */
645     while(ptable[i].magic != vp->magic && ptable[i].magic != 0)
646         i++;
647
648     /*
649      * If we didn't find a matching entry return
650      */
651     if(ptable[i].magic==0)
652         goto cleanup_and_exit;
653
654     /*
655      * If the name is NULL is a special case and 
656      * just just pass the obj_name as the file_path
657      * otherwise we create a file path from the given components
658      */
659     if(ptable[i].name != 0){
660         char file_path[MAX_PATH_SIZE];
661         sprintf(file_path, "%s%s/%s",path,obj_name,ptable[i].name);
662         ret_val =  ptable[i].fhandler(file_path,var_len);
663     }
664     else
665         ret_val =  ptable[i].fhandler(obj_name,var_len);
666
667 cleanup_and_exit:
668     free(dir_list);
669     return ret_val;
670 };
671
672 /**************************************************************************
673  * Function:   stats_values
674  *
675  * Description: Setup nb_sample, min, max, sum and sum_square stats values
676                 for name_value from filepath.
677  *
678  * Input:  filepath, name_value,
679  *         pointer to nb_sample, min, max, sum, sum_square
680  *
681  * Output: SUCCESS or ERROR on failure
682  *
683  **************************************************************************/
684 int stats_values(char * filepath,char * name_value, unsigned long long * nb_sample, unsigned long long * min, unsigned long long * max, unsigned long long * sum, unsigned long long * sum_square)
685 {
686   FILE * statfile;
687   char line[MAX_LINE_SIZE];
688   int nbReadValues = 0;
689
690   if( (statfile=fopen(filepath,"r")) == NULL) {
691     report("stats_value() failed to open %s",filepath);
692     return ERROR;
693   }
694 /*find the good line for name_value*/
695   do {
696     if( fgets(line,MAX_LINE_SIZE,statfile) == NULL ) {
697       report("stats_values() failed to find %s values in %s stat_file",name_value,statfile);
698       goto error_out;
699     }
700   } while ( strstr(line,name_value) == NULL );
701 /*get stats*/
702   if((nbReadValues=sscanf(line,"%*s %llu %*s %*s %llu %llu %llu %llu",nb_sample,min,max,sum,sum_square)) == 5) {
703     goto success_out;
704   } else if( nbReadValues == 1 && *nb_sample == 0) {
705     *min = *max = *sum = *sum_square = 0;
706     goto success_out;
707   } else {
708     report("stats_values() failed to read stats_values for %s value in %s stat_file",name_value,statfile);
709     goto error_out;
710   }
711
712 success_out :
713   fclose(statfile);
714   return SUCCESS;
715 error_out :
716   fclose(statfile);
717   return ERROR;
718 }
719
720 /**************************************************************************
721  * Function:   mds_stats_values
722  *
723  * Description: Setup nb_sample, min, max, sum and sum_square stats values
724                 for mds stats name_value .
725  *
726  * Input:  name_value,
727  *         pointer to nb_sample, min, max, sum, sum_square
728  *
729  * Output: SUCCESS or ERROR on failure
730  *
731  **************************************************************************/
732 extern int mds_stats_values(char * name_value, unsigned long long * nb_sample, unsigned long long * min, unsigned long long * max, unsigned long long * sum, unsigned long long * sum_square)
733 {
734   unsigned long long tmp_nb_sample=0,tmp_min=0,tmp_max=0,tmp_sum=0,tmp_sum_square=0;
735 /*we parse the three MDS stat files and sum values*/
736   if( stats_values(FILEPATH_MDS_SERVER_STATS,name_value,&tmp_nb_sample,&tmp_min,&tmp_max,&tmp_sum,&tmp_sum_square) == ERROR ) {
737     return ERROR;
738   } else {
739     *nb_sample=tmp_nb_sample;
740     *min=tmp_min;
741     *max=tmp_max;
742     *sum=tmp_sum;
743     *sum_square=tmp_sum_square;
744   }
745
746   if( stats_values(FILEPATH_MDS_SERVER_READPAGE_STATS,name_value,&tmp_nb_sample,&tmp_min,&tmp_max,&tmp_sum,&tmp_sum_square) == ERROR ) {
747     return ERROR;
748   } else {
749     *nb_sample += tmp_nb_sample;
750     *min += tmp_min;
751     *max += tmp_max;
752     *sum += tmp_sum;
753     *sum_square += tmp_sum_square;
754   }
755
756   if( stats_values(FILEPATH_MDS_SERVER_SETATTR_STATS,name_value,&tmp_nb_sample,&tmp_min,&tmp_max,&tmp_sum,&tmp_sum_square) == ERROR ) {
757     return ERROR;
758   } else {
759     *nb_sample += tmp_nb_sample;
760     *min += tmp_min;
761     *max += tmp_max;
762     *sum += tmp_sum;
763     *sum_square += tmp_sum_square;
764   }
765   
766   return SUCCESS;
767 }