/* Copyright (C) 2008-2009 Sun Microsystems, Inc This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** @file storage/perfschema/pfs_instr.cc Performance schema instruments (implementation). */ #include "my_global.h" #include "mysql_priv.h" #include "my_sys.h" #include "pfs_stat.h" #include "pfs_instr.h" #include "pfs_global.h" /** @addtogroup Performance_schema_buffers @{ */ /** Size of the mutex instances array. @sa mutex_array */ ulong mutex_max; /** Number of mutexes instance lost. @sa mutex_array */ ulong mutex_lost; /** Size of the rwlock instances array. @sa rwlock_array */ ulong rwlock_max; /** Number or rwlock instances lost. @sa rwlock_array */ ulong rwlock_lost; /** Size of the conditions instances array. @sa cond_array */ ulong cond_max; /** Number of conditions instances lost. @sa cond_array */ ulong cond_lost; /** Size of the thread instances array. @sa thread_array */ ulong thread_max; /** Number or thread instances lost. @sa thread_array */ ulong thread_lost; /** Size of the file instances array. @sa file_array */ ulong file_max; /** Number of file instances lost. @sa file_array */ ulong file_lost; /** Size of the file handle array. @sa file_handle_array. Signed value, for easier comparisons with a file descriptor number. */ long file_handle_max; /** Number of file handle lost. @sa file_handle_array */ ulong file_handle_lost; /** Size of the table instances array. @sa table_array */ ulong table_max; /** Number of table instances lost. @sa table_array */ ulong table_lost; /** Number of EVENTS_WAITS_HISTORY records per thread. */ ulong events_waits_history_per_thread; /** Number of instruments class per thread. */ ulong instr_class_per_thread; /** Number of locker lost. @sa LOCKER_STACK_SIZE. */ ulong locker_lost; /** Mutex instrumentation instances array. @sa mutex_max @sa mutex_lost */ PFS_mutex *mutex_array= NULL; /** RWLock instrumentation instances array. @sa rwlock_max @sa rwlock_lost */ PFS_rwlock *rwlock_array= NULL; /** Condition instrumentation instances array. @sa cond_max @sa cond_lost */ PFS_cond *cond_array= NULL; /** Thread instrumentation instances array. @sa thread_max @sa thread_lost */ PFS_thread *thread_array= NULL; /** File instrumentation instances array. @sa file_max @sa file_lost @sa filename_hash */ PFS_file *file_array= NULL; /** File instrumentation handle array. @sa file_handle_max @sa file_handle_lost */ PFS_file **file_handle_array= NULL; /** Table instrumentation instances array. @sa table_max @sa table_lost */ PFS_table *table_array= NULL; static volatile uint32 thread_internal_id_counter= 0; static uint per_thread_rwlock_class_start; static uint per_thread_cond_class_start; static uint per_thread_file_class_start; static uint thread_instr_class_waits_sizing; static PFS_single_stat_chain *thread_instr_class_waits_array= NULL; static PFS_events_waits *thread_history_array= NULL; /** Hash table for instrumented files. */ static LF_HASH filename_hash; /** True if filename_hash is initialized. */ static bool filename_hash_inited= false; /** Initialize all the instruments instance buffers. @param param sizing parameters @return 0 on success */ int init_instruments(const PFS_global_param *param) { uint thread_history_sizing; uint index; mutex_max= param->m_mutex_sizing; mutex_lost= 0; rwlock_max= param->m_rwlock_sizing; rwlock_lost= 0; cond_max= param->m_cond_sizing; cond_lost= 0; file_max= param->m_file_sizing; file_lost= 0; file_handle_max= param->m_file_handle_sizing; file_handle_lost= 0; table_max= param->m_table_sizing; table_lost= 0; thread_max= param->m_thread_sizing; thread_lost= 0; events_waits_history_per_thread= param->m_events_waits_history_sizing; thread_history_sizing= param->m_thread_sizing * events_waits_history_per_thread; per_thread_rwlock_class_start= param->m_mutex_class_sizing; per_thread_cond_class_start= per_thread_rwlock_class_start + param->m_rwlock_class_sizing; per_thread_file_class_start= per_thread_cond_class_start + param->m_cond_class_sizing; instr_class_per_thread= per_thread_file_class_start + param->m_file_class_sizing; thread_instr_class_waits_sizing= param->m_thread_sizing * instr_class_per_thread; mutex_array= NULL; rwlock_array= NULL; cond_array= NULL; file_array= NULL; file_handle_array= NULL; table_array= NULL; thread_array= NULL; thread_history_array= NULL; thread_instr_class_waits_array= NULL; thread_internal_id_counter= 0; if (mutex_max > 0) { mutex_array= PFS_MALLOC_ARRAY(mutex_max, PFS_mutex, MYF(MY_ZEROFILL)); if (unlikely(mutex_array == NULL)) return 1; } if (rwlock_max > 0) { rwlock_array= PFS_MALLOC_ARRAY(rwlock_max, PFS_rwlock, MYF(MY_ZEROFILL)); if (unlikely(rwlock_array == NULL)) return 1; } if (cond_max > 0) { cond_array= PFS_MALLOC_ARRAY(cond_max, PFS_cond, MYF(MY_ZEROFILL)); if (unlikely(cond_array == NULL)) return 1; } if (file_max > 0) { file_array= PFS_MALLOC_ARRAY(file_max, PFS_file, MYF(MY_ZEROFILL)); if (unlikely(file_array == NULL)) return 1; } if (file_handle_max > 0) { file_handle_array= PFS_MALLOC_ARRAY(file_handle_max, PFS_file*, MYF(MY_ZEROFILL)); if (unlikely(file_handle_array == NULL)) return 1; } if (table_max > 0) { table_array= PFS_MALLOC_ARRAY(table_max, PFS_table, MYF(MY_ZEROFILL)); if (unlikely(table_array == NULL)) return 1; } if (thread_max > 0) { thread_array= PFS_MALLOC_ARRAY(thread_max, PFS_thread, MYF(MY_ZEROFILL)); if (unlikely(thread_array == NULL)) return 1; } if (thread_history_sizing > 0) { thread_history_array= PFS_MALLOC_ARRAY(thread_history_sizing, PFS_events_waits, MYF(MY_ZEROFILL)); if (unlikely(thread_history_array == NULL)) return 1; } if (thread_instr_class_waits_sizing > 0) { thread_instr_class_waits_array= PFS_MALLOC_ARRAY(thread_instr_class_waits_sizing, PFS_single_stat_chain, MYF(MY_ZEROFILL)); if (unlikely(thread_instr_class_waits_array == NULL)) return 1; } for (index= 0; index < thread_instr_class_waits_sizing; index++) { /* Currently, this chain is of length 1, but it's still implemented as a stat chain, since more aggregations are planned to be implemented in m_parent. */ thread_instr_class_waits_array[index].m_control_flag= &flag_events_waits_summary_by_thread_by_event_name; thread_instr_class_waits_array[index].m_parent= NULL; } for (index= 0; index < thread_max; index++) { thread_array[index].m_waits_history= &thread_history_array[index * events_waits_history_per_thread]; thread_array[index].m_instr_class_wait_stats= &thread_instr_class_waits_array[index * instr_class_per_thread]; } return 0; } /** Find the per-thread wait statistics for a mutex class. @param thread input thread @param klass mutex class @return the per thread per mutex class wait stat */ PFS_single_stat_chain * find_per_thread_mutex_class_wait_stat(PFS_thread *thread, PFS_mutex_class *klass) { PFS_single_stat_chain *stat; uint index; DBUG_ASSERT(thread != NULL); DBUG_ASSERT(klass != NULL); index= klass->m_index; DBUG_ASSERT(index < mutex_class_max); stat= &(thread->m_instr_class_wait_stats[index]); return stat; } /** Find the per-thread wait statistics for a rwlock class. @param thread input thread @param klass rwlock class @return the per thread per rwlock class wait stat */ PFS_single_stat_chain * find_per_thread_rwlock_class_wait_stat(PFS_thread *thread, PFS_rwlock_class *klass) { PFS_single_stat_chain *stat; uint index; DBUG_ASSERT(thread != NULL); DBUG_ASSERT(klass != NULL); index= klass->m_index; DBUG_ASSERT(index < rwlock_class_max); stat= &(thread->m_instr_class_wait_stats [per_thread_rwlock_class_start + index]); return stat; } /** Find the per-thread wait statistics for a condition class. @param thread input thread @param klass condition class @return the per thread per condition class wait stat */ PFS_single_stat_chain * find_per_thread_cond_class_wait_stat(PFS_thread *thread, PFS_cond_class *klass) { PFS_single_stat_chain *stat; uint index; DBUG_ASSERT(thread != NULL); DBUG_ASSERT(klass != NULL); index= klass->m_index; DBUG_ASSERT(index < cond_class_max); stat= &(thread->m_instr_class_wait_stats [per_thread_cond_class_start + index]); return stat; } /** Find the per-thread wait statistics for a file class. @param thread input thread @param klass file class @return the per thread per file class wait stat */ PFS_single_stat_chain * find_per_thread_file_class_wait_stat(PFS_thread *thread, PFS_file_class *klass) { PFS_single_stat_chain *stat; uint index; DBUG_ASSERT(thread != NULL); DBUG_ASSERT(klass != NULL); index= klass->m_index; DBUG_ASSERT(index < file_class_max); stat= &(thread->m_instr_class_wait_stats [per_thread_file_class_start + index]); return stat; } /** Reset the wait statistics per thread. */ void reset_per_thread_wait_stat(void) { PFS_single_stat_chain *stat= thread_instr_class_waits_array; PFS_single_stat_chain *stat_last= stat + thread_instr_class_waits_sizing; for ( ; stat < stat_last; stat++) reset_single_stat_link(stat); } /** Cleanup all the instruments buffers. */ void cleanup_instruments(void) { pfs_free(mutex_array); mutex_array= NULL; mutex_max= 0; pfs_free(rwlock_array); rwlock_array= NULL; rwlock_max= 0; pfs_free(cond_array); cond_array= NULL; cond_max= 0; pfs_free(file_array); file_array= NULL; file_max= 0; pfs_free(file_handle_array); file_handle_array= NULL; file_handle_max= 0; pfs_free(table_array); table_array= NULL; table_max= 0; pfs_free(thread_array); thread_array= NULL; thread_max= 0; pfs_free(thread_history_array); thread_history_array= NULL; pfs_free(thread_instr_class_waits_array); thread_instr_class_waits_array= NULL; } static uchar *filename_hash_get_key(const uchar *entry, size_t *length, my_bool) { const PFS_file * const *typed_entry; const PFS_file *file; const void *result; typed_entry= reinterpret_cast<const PFS_file* const *> (entry); DBUG_ASSERT(typed_entry != NULL); file= *typed_entry; DBUG_ASSERT(file != NULL); *length= file->m_filename_length; result= file->m_filename; return const_cast<uchar*> (reinterpret_cast<const uchar*> (result)); } /** Initialize the file name hash. @return 0 on success */ int init_file_hash(void) { if (! filename_hash_inited) { lf_hash_init(&filename_hash, sizeof(PFS_file*), LF_HASH_UNIQUE, 0, 0, filename_hash_get_key, &my_charset_bin); filename_hash_inited= true; } return 0; } /** Cleanup the file name hash. */ void cleanup_file_hash(void) { if (filename_hash_inited) { lf_hash_destroy(&filename_hash); filename_hash_inited= false; } } /** Create instrumentation for a mutex instance. @param klass the mutex class @param identity the mutex address @return a mutex instance, or NULL */ PFS_mutex* create_mutex(PFS_mutex_class *klass, const void *identity) { int pass; uint i= randomized_index(identity, mutex_max); /* Pass 1: [random, mutex_max - 1] Pass 2: [0, mutex_max - 1] */ for (pass= 1; pass <= 2; i=0, pass++) { PFS_mutex *pfs= mutex_array + i; PFS_mutex *pfs_last= mutex_array + mutex_max; for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) { if (pfs->m_lock.free_to_dirty()) { pfs->m_identity= identity; pfs->m_class= klass; pfs->m_wait_stat.m_control_flag= &flag_events_waits_summary_by_instance; pfs->m_wait_stat.m_parent= &klass->m_wait_stat; reset_single_stat_link(&pfs->m_wait_stat); pfs->m_lock_stat.m_control_flag= &flag_events_locks_summary_by_instance; pfs->m_lock_stat.m_parent= &klass->m_lock_stat; reset_single_stat_link(&pfs->m_lock_stat); pfs->m_owner= NULL; pfs->m_last_locked= 0; pfs->m_lock.dirty_to_allocated(); return pfs; } } } } mutex_lost++; return NULL; } /** Destroy instrumentation for a mutex instance. @param pfs the mutex to destroy */ void destroy_mutex(PFS_mutex *pfs) { DBUG_ASSERT(pfs != NULL); pfs->m_lock.allocated_to_free(); } /** Create instrumentation for a rwlock instance. @param klass the rwlock class @param identity the rwlock address @return a rwlock instance, or NULL */ PFS_rwlock* create_rwlock(PFS_rwlock_class *klass, const void *identity) { int pass; uint i= randomized_index(identity, rwlock_max); /* Pass 1: [random, rwlock_max - 1] Pass 2: [0, rwlock_max - 1] */ for (pass= 1; pass <= 2; i=0, pass++) { PFS_rwlock *pfs= rwlock_array + i; PFS_rwlock *pfs_last= rwlock_array + rwlock_max; for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) { if (pfs->m_lock.free_to_dirty()) { pfs->m_identity= identity; pfs->m_class= klass; pfs->m_wait_stat.m_control_flag= &flag_events_waits_summary_by_instance; pfs->m_wait_stat.m_parent= &klass->m_wait_stat; reset_single_stat_link(&pfs->m_wait_stat); pfs->m_lock.dirty_to_allocated(); pfs->m_read_lock_stat.m_control_flag= &flag_events_locks_summary_by_instance; pfs->m_read_lock_stat.m_parent= &klass->m_read_lock_stat; reset_single_stat_link(&pfs->m_read_lock_stat); pfs->m_write_lock_stat.m_control_flag= &flag_events_locks_summary_by_instance; pfs->m_write_lock_stat.m_parent= &klass->m_write_lock_stat; reset_single_stat_link(&pfs->m_write_lock_stat); pfs->m_writer= NULL; pfs->m_readers= 0; pfs->m_last_written= 0; pfs->m_last_read= 0; return pfs; } } } } rwlock_lost++; return NULL; } /** Destroy instrumentation for a rwlock instance. @param pfs the rwlock to destroy */ void destroy_rwlock(PFS_rwlock *pfs) { DBUG_ASSERT(pfs != NULL); pfs->m_lock.allocated_to_free(); } /** Create instrumentation for a condition instance. @param klass the condition class @param identity the condition address @return a condition instance, or NULL */ PFS_cond* create_cond(PFS_cond_class *klass, const void *identity) { int pass; uint i= randomized_index(identity, cond_max); /* Pass 1: [random, cond_max - 1] Pass 2: [0, cond_max - 1] */ for (pass= 1; pass <= 2; i=0, pass++) { PFS_cond *pfs= cond_array + i; PFS_cond *pfs_last= cond_array + cond_max; for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) { if (pfs->m_lock.free_to_dirty()) { pfs->m_identity= identity; pfs->m_class= klass; pfs->m_cond_stat.m_signal_count= 0; pfs->m_cond_stat.m_broadcast_count= 0; pfs->m_wait_stat.m_control_flag= &flag_events_waits_summary_by_instance; pfs->m_wait_stat.m_parent= &klass->m_wait_stat; reset_single_stat_link(&pfs->m_wait_stat); pfs->m_lock.dirty_to_allocated(); return pfs; } } } } cond_lost++; return NULL; } /** Destroy instrumentation for a condition instance. @param pfs the condition to destroy */ void destroy_cond(PFS_cond *pfs) { DBUG_ASSERT(pfs != NULL); pfs->m_lock.allocated_to_free(); } /** Create instrumentation for a thread instance. @param klass the thread class @param identity the thread address, or a value characteristic of this thread @param thread_id the PROCESSLIST thread id, or 0 if unknown @return a thread instance, or NULL */ PFS_thread* create_thread(PFS_thread_class *klass, const void *identity, ulong thread_id) { int pass; uint i= randomized_index(identity, thread_max); /* Pass 1: [random, thread_max - 1] Pass 2: [0, thread_max - 1] */ for (pass= 1; pass <= 2; i=0, pass++) { PFS_thread *pfs= thread_array + i; PFS_thread *pfs_last= thread_array + thread_max; for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) { if (pfs->m_lock.free_to_dirty()) { pfs->m_thread_internal_id= PFS_atomic::add_u32(&thread_internal_id_counter, 1); pfs->m_thread_id= thread_id; pfs->m_event_id= 1; pfs->m_enabled= true; pfs->m_class= klass; pfs->m_wait_locker_count= 0; pfs->m_waits_history_full= false; pfs->m_waits_history_index= 0; PFS_single_stat_chain *stat= pfs->m_instr_class_wait_stats; PFS_single_stat_chain *stat_last= stat + instr_class_per_thread; for ( ; stat < stat_last; stat++) reset_single_stat_link(stat); pfs->m_filename_hash_pins= NULL; pfs->m_table_share_hash_pins= NULL; pfs->m_lock.dirty_to_allocated(); return pfs; } } } } thread_lost++; return NULL; } /** Sanitize a PFS_thread pointer. Validate that the PFS_thread is part of thread_array. Sanitizing data is required when the data can be damaged with expected race conditions, for example involving EVENTS_WAITS_HISTORY_LONG. @param unsafe the pointer to sanitize @return a valid pointer, or NULL */ PFS_thread *sanitize_thread(PFS_thread *unsafe) { if ((&thread_array[0] <= unsafe) && (unsafe < &thread_array[thread_max])) return unsafe; return NULL; } /** Destroy instrumentation for a thread instance. @param pfs the thread to destroy */ void destroy_thread(PFS_thread *pfs) { DBUG_ASSERT(pfs != NULL); if (pfs->m_filename_hash_pins) { lf_hash_put_pins(pfs->m_filename_hash_pins); pfs->m_filename_hash_pins= NULL; } if (pfs->m_table_share_hash_pins) { lf_hash_put_pins(pfs->m_table_share_hash_pins); pfs->m_table_share_hash_pins= NULL; } pfs->m_lock.allocated_to_free(); } /** Find or create instrumentation for a file instance by file name. @param thread the executing instrumented thread @param klass the file class @param filename the file name @param len the length in bytes of filename @return a file instance, or NULL */ PFS_file* find_or_create_file(PFS_thread *thread, PFS_file_class *klass, const char *filename, uint len) { PFS_file *pfs; int pass; if (! filename_hash_inited) { /* File instrumentation can be turned off. */ file_lost++; return NULL; } if (unlikely(thread->m_filename_hash_pins == NULL)) { thread->m_filename_hash_pins= lf_hash_get_pins(&filename_hash); if (unlikely(thread->m_filename_hash_pins == NULL)) { file_lost++; return NULL; } } if (len >= sizeof(pfs->m_filename)) len= sizeof(pfs->m_filename) - 1; PFS_file **entry; uint retry_count= 0; const uint retry_max= 3; search: entry= reinterpret_cast<PFS_file**> (lf_hash_search(&filename_hash, thread->m_filename_hash_pins, filename, len)); if (entry && (entry != MY_ERRPTR)) { pfs= *entry; pfs->m_file_stat.m_open_count++; lf_hash_search_unpin(thread->m_filename_hash_pins); return pfs; } /* filename is not constant, just using it for noise on create */ uint i= randomized_index(filename, file_max); /* Pass 1: [random, file_max - 1] Pass 2: [0, file_max - 1] */ for (pass= 1; pass <= 2; i=0, pass++) { pfs= file_array + i; PFS_file *pfs_last= file_array + file_max; for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) { if (pfs->m_lock.free_to_dirty()) { pfs->m_class= klass; strncpy(pfs->m_filename, filename, len); pfs->m_filename[len]= '\0'; pfs->m_filename_length= len; pfs->m_file_stat.m_open_count= 1; pfs->m_wait_stat.m_control_flag= &flag_events_waits_summary_by_instance; pfs->m_wait_stat.m_parent= &klass->m_wait_stat; reset_single_stat_link(&pfs->m_wait_stat); int res; res= lf_hash_insert(&filename_hash, thread->m_filename_hash_pins, &pfs); if (likely(res == 0)) { pfs->m_lock.dirty_to_allocated(); return pfs; } pfs->m_lock.dirty_to_free(); if (res > 0) { /* Duplicate insert by another thread */ if (++retry_count > retry_max) { /* Avoid infinite loops */ file_lost++; return NULL; } goto search; } /* OOM in lf_hash_insert */ file_lost++; return NULL; } } } } file_lost++; return NULL; } /** Release instrumentation for a file instance. @param pfs the file to release */ void release_file(PFS_file *pfs) { DBUG_ASSERT(pfs != NULL); pfs->m_file_stat.m_open_count--; } /** Destroy instrumentation for a file instance. @param thread the executing thread instrumentation @param pfs the file to destroy */ void destroy_file(PFS_thread *thread, PFS_file *pfs) { DBUG_ASSERT(thread != NULL); DBUG_ASSERT(thread->m_filename_hash_pins != NULL); DBUG_ASSERT(pfs != NULL); lf_hash_delete(&filename_hash, thread->m_filename_hash_pins, pfs->m_filename, pfs->m_filename_length); pfs->m_lock.allocated_to_free(); } /** Create instrumentation for a table instance. @param share the table share @param identity the table address @return a table instance, or NULL */ PFS_table* create_table(PFS_table_share *share, const void *identity) { int pass; uint i= randomized_index(identity, table_max); /* Pass 1: [random, table_max - 1] Pass 2: [0, table_max - 1] */ for (pass= 1; pass <= 2; i=0, pass++) { PFS_table *pfs= table_array + i; PFS_table *pfs_last= table_array + table_max; for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) { if (pfs->m_lock.free_to_dirty()) { pfs->m_identity= identity; pfs->m_share= share; pfs->m_wait_stat.m_control_flag= &flag_events_waits_summary_by_instance; pfs->m_wait_stat.m_parent= &share->m_wait_stat; reset_single_stat_link(&pfs->m_wait_stat); pfs->m_lock.dirty_to_allocated(); return pfs; } } } } table_lost++; return NULL; } /** Destroy instrumentation for a table instance. @param pfs the table to destroy */ void destroy_table(PFS_table *pfs) { DBUG_ASSERT(pfs != NULL); pfs->m_lock.allocated_to_free(); } static void reset_mutex_waits_by_instance(void) { PFS_mutex *pfs= mutex_array; PFS_mutex *pfs_last= mutex_array + mutex_max; for ( ; pfs < pfs_last; pfs++) reset_single_stat_link(&pfs->m_wait_stat); } static void reset_rwlock_waits_by_instance(void) { PFS_rwlock *pfs= rwlock_array; PFS_rwlock *pfs_last= rwlock_array + rwlock_max; for ( ; pfs < pfs_last; pfs++) reset_single_stat_link(&pfs->m_wait_stat); } static void reset_cond_waits_by_instance(void) { PFS_cond *pfs= cond_array; PFS_cond *pfs_last= cond_array + cond_max; for ( ; pfs < pfs_last; pfs++) reset_single_stat_link(&pfs->m_wait_stat); } static void reset_file_waits_by_instance(void) { PFS_file *pfs= file_array; PFS_file *pfs_last= file_array + file_max; for ( ; pfs < pfs_last; pfs++) reset_single_stat_link(&pfs->m_wait_stat); } /** Reset the wait statistics per object instance. */ void reset_events_waits_by_instance(void) { reset_mutex_waits_by_instance(); reset_rwlock_waits_by_instance(); reset_cond_waits_by_instance(); reset_file_waits_by_instance(); } /** Reset the io statistics per file instance. */ void reset_file_instance_io(void) { PFS_file *pfs= file_array; PFS_file *pfs_last= file_array + file_max; for ( ; pfs < pfs_last; pfs++) reset_file_stat(&pfs->m_file_stat); } /** @} */