static int check_storage_number_exists() { uint32_t flags = SN_EXISTS; for(flags = 0; flags < 7 ; flags++) { if(get_storage_number_flags(flags << 24) != flags << 24) { fprintf(stderr, "Flag 0x%08x is not checked correctly. It became 0x%08x\n", flags << 24, get_storage_number_flags(flags << 24)); return 1; } } flags = SN_EXISTS; calculated_number n = 0.0; storage_number s = pack_storage_number(n, flags); calculated_number d = unpack_storage_number(s); if(get_storage_number_flags(s) != flags) { fprintf(stderr, "Wrong flags. Given %08x, Got %08x!\n", flags, get_storage_number_flags(s)); return 1; } if(n != d) { fprintf(stderr, "Wrong number returned. Expected " CALCULATED_NUMBER_FORMAT ", returned " CALCULATED_NUMBER_FORMAT "!\n", n, d); return 1; } return 0; }
calculated_number unpack_storage_number(storage_number value) { if(!value) return 0; int sign = 0, exp = 0; value ^= get_storage_number_flags(value); if(value & (1 << 31)) { sign = 1; value ^= 1 << 31; } if(value & (1 << 30)) { exp = 1; value ^= 1 << 30; } int mul = value >> 27; value ^= mul << 27; calculated_number n = value; // fprintf(stderr, "UNPACK: %08X, sign = %d, exp = %d, mul = %d, n = " CALCULATED_NUMBER_FORMAT "\n", value, sign, exp, mul, n); while(mul > 0) { if(exp) n *= 10; else n /= 10; mul--; } if(sign) n = -n; return n; }
unsigned long long rrdset_done(RRDSET *st) { if(unlikely(netdata_exit)) return 0; debug(D_RRD_CALLS, "rrdset_done() for chart %s", st->name); RRDDIM *rd, *last; int oldstate, store_this_entry = 1, first_entry = 0; unsigned long long last_ut, now_ut, next_ut, stored_entries = 0; if(unlikely(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate) != 0)) error("Cannot set pthread cancel state to DISABLE."); // a read lock is OK here pthread_rwlock_rdlock(&st->rwlock); // enable the chart, if it was disabled if(unlikely(rrd_delete_unupdated_dimensions) && !st->enabled) st->enabled = 1; // check if the chart has a long time to be updated if(unlikely(st->usec_since_last_update > st->entries * st->update_every * 1000000ULL)) { info("%s: took too long to be updated (%0.3Lf secs). Reseting it.", st->name, (long double)(st->usec_since_last_update / 1000000.0)); rrdset_reset(st); st->usec_since_last_update = st->update_every * 1000000ULL; first_entry = 1; } if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: microseconds since last update: %llu", st->name, st->usec_since_last_update); // set last_collected_time if(unlikely(!st->last_collected_time.tv_sec)) { // it is the first entry // set the last_collected_time to now gettimeofday(&st->last_collected_time, NULL); // the first entry should not be stored store_this_entry = 0; first_entry = 1; if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: has not set last_collected_time. Setting it now. Will not store the next entry.", st->name); } else { // it is not the first entry // calculate the proper last_collected_time, using usec_since_last_update unsigned long long ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec + st->usec_since_last_update; st->last_collected_time.tv_sec = (time_t) (ut / 1000000ULL); st->last_collected_time.tv_usec = (suseconds_t) (ut % 1000000ULL); } // if this set has not been updated in the past // we fake the last_update time to be = now - usec_since_last_update if(unlikely(!st->last_updated.tv_sec)) { // it has never been updated before // set a fake last_updated, in the past using usec_since_last_update unsigned long long ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec - st->usec_since_last_update; st->last_updated.tv_sec = (time_t) (ut / 1000000ULL); st->last_updated.tv_usec = (suseconds_t) (ut % 1000000ULL); // the first entry should not be stored store_this_entry = 0; first_entry = 1; if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: initializing last_updated to now - %llu microseconds (%0.3Lf). Will not store the next entry.", st->name, st->usec_since_last_update, (long double)ut/1000000.0); } // check if we will re-write the entire data set if(unlikely(usecdiff(&st->last_collected_time, &st->last_updated) > st->update_every * st->entries * 1000000ULL)) { info("%s: too old data (last updated at %ld.%ld, last collected at %ld.%ld). Reseting it. Will not store the next entry.", st->name, st->last_updated.tv_sec, st->last_updated.tv_usec, st->last_collected_time.tv_sec, st->last_collected_time.tv_usec); rrdset_reset(st); st->usec_since_last_update = st->update_every * 1000000ULL; gettimeofday(&st->last_collected_time, NULL); unsigned long long ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec - st->usec_since_last_update; st->last_updated.tv_sec = (time_t) (ut / 1000000ULL); st->last_updated.tv_usec = (suseconds_t) (ut % 1000000ULL); // the first entry should not be stored store_this_entry = 0; first_entry = 1; } // these are the 3 variables that will help us in interpolation // last_ut = the last time we added a value to the storage // now_ut = the time the current value is taken at // next_ut = the time of the next interpolation point last_ut = st->last_updated.tv_sec * 1000000ULL + st->last_updated.tv_usec; now_ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec; next_ut = (st->last_updated.tv_sec + st->update_every) * 1000000ULL; if(unlikely(!first_entry && now_ut < next_ut)) { if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: THIS IS IN THE SAME INTERPOLATION POINT", st->name); } if(unlikely(st->debug)) { debug(D_RRD_STATS, "%s: last ut = %0.3Lf (last updated time)", st->name, (long double)last_ut/1000000.0); debug(D_RRD_STATS, "%s: now ut = %0.3Lf (current update time)", st->name, (long double)now_ut/1000000.0); debug(D_RRD_STATS, "%s: next ut = %0.3Lf (next interpolation point)", st->name, (long double)next_ut/1000000.0); } if(unlikely(!st->counter_done)) { store_this_entry = 0; if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: Will not store the next entry.", st->name); } st->counter_done++; // calculate totals and count the dimensions int dimensions; st->collected_total = 0; for( rd = st->dimensions, dimensions = 0 ; likely(rd) ; rd = rd->next, dimensions++ ) if(likely(rd->updated)) st->collected_total += rd->collected_value; uint32_t storage_flags = SN_EXISTS; // process all dimensions to calculate their values // based on the collected figures only // at this stage we do not interpolate anything for( rd = st->dimensions ; likely(rd) ; rd = rd->next ) { if(unlikely(!rd->updated)) { rd->calculated_value = 0; continue; } if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: START " " last_collected_value = " COLLECTED_NUMBER_FORMAT " collected_value = " COLLECTED_NUMBER_FORMAT " last_calculated_value = " CALCULATED_NUMBER_FORMAT " calculated_value = " CALCULATED_NUMBER_FORMAT , st->id, rd->name , rd->last_collected_value , rd->collected_value , rd->last_calculated_value , rd->calculated_value ); switch(rd->algorithm) { case RRDDIM_ABSOLUTE: rd->calculated_value = (calculated_number)rd->collected_value * (calculated_number)rd->multiplier / (calculated_number)rd->divisor; if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: CALC ABS/ABS-NO-IN " CALCULATED_NUMBER_FORMAT " = " COLLECTED_NUMBER_FORMAT " * " CALCULATED_NUMBER_FORMAT " / " CALCULATED_NUMBER_FORMAT , st->id, rd->name , rd->calculated_value , rd->collected_value , (calculated_number)rd->multiplier , (calculated_number)rd->divisor ); break; case RRDDIM_PCENT_OVER_ROW_TOTAL: if(unlikely(!st->collected_total)) rd->calculated_value = 0; else // the percentage of the current value // over the total of all dimensions rd->calculated_value = (calculated_number)100 * (calculated_number)rd->collected_value / (calculated_number)st->collected_total; if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: CALC PCENT-ROW " CALCULATED_NUMBER_FORMAT " = 100" " * " COLLECTED_NUMBER_FORMAT " / " COLLECTED_NUMBER_FORMAT , st->id, rd->name , rd->calculated_value , rd->collected_value , st->collected_total ); break; case RRDDIM_INCREMENTAL: if(unlikely(rd->counter <= 1)) { rd->calculated_value = 0; continue; } // if the new is smaller than the old (an overflow, or reset), set the old equal to the new // to reset the calculation (it will give zero as the calculation for this second) if(unlikely(rd->last_collected_value > rd->collected_value)) { debug(D_RRD_STATS, "%s.%s: RESET or OVERFLOW. Last collected value = " COLLECTED_NUMBER_FORMAT ", current = " COLLECTED_NUMBER_FORMAT , st->name, rd->name , rd->last_collected_value , rd->collected_value); if(!(rd->flags & RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS)) storage_flags = SN_EXISTS_RESET; rd->last_collected_value = rd->collected_value; } rd->calculated_value = (calculated_number)(rd->collected_value - rd->last_collected_value) * (calculated_number)rd->multiplier / (calculated_number)rd->divisor; if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: CALC INC PRE " CALCULATED_NUMBER_FORMAT " = (" COLLECTED_NUMBER_FORMAT " - " COLLECTED_NUMBER_FORMAT ")" " * " CALCULATED_NUMBER_FORMAT " / " CALCULATED_NUMBER_FORMAT , st->id, rd->name , rd->calculated_value , rd->collected_value, rd->last_collected_value , (calculated_number)rd->multiplier , (calculated_number)rd->divisor ); break; case RRDDIM_PCENT_OVER_DIFF_TOTAL: if(unlikely(rd->counter <= 1)) { rd->calculated_value = 0; continue; } // if the new is smaller than the old (an overflow, or reset), set the old equal to the new // to reset the calculation (it will give zero as the calculation for this second) if(unlikely(rd->last_collected_value > rd->collected_value)) { debug(D_RRD_STATS, "%s.%s: RESET or OVERFLOW. Last collected value = " COLLECTED_NUMBER_FORMAT ", current = " COLLECTED_NUMBER_FORMAT , st->name, rd->name , rd->last_collected_value , rd->collected_value); if(!(rd->flags & RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS)) storage_flags = SN_EXISTS_RESET; rd->last_collected_value = rd->collected_value; } // the percentage of the current increment // over the increment of all dimensions together if(unlikely(st->collected_total == st->last_collected_total)) rd->calculated_value = 0; else rd->calculated_value = (calculated_number)100 * (calculated_number)(rd->collected_value - rd->last_collected_value) / (calculated_number)(st->collected_total - st->last_collected_total); if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: CALC PCENT-DIFF " CALCULATED_NUMBER_FORMAT " = 100" " * (" COLLECTED_NUMBER_FORMAT " - " COLLECTED_NUMBER_FORMAT ")" " / (" COLLECTED_NUMBER_FORMAT " - " COLLECTED_NUMBER_FORMAT ")" , st->id, rd->name , rd->calculated_value , rd->collected_value, rd->last_collected_value , st->collected_total, st->last_collected_total ); break; default: // make the default zero, to make sure // it gets noticed when we add new types rd->calculated_value = 0; if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: CALC " CALCULATED_NUMBER_FORMAT " = 0" , st->id, rd->name , rd->calculated_value ); break; } if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: PHASE2 " " last_collected_value = " COLLECTED_NUMBER_FORMAT " collected_value = " COLLECTED_NUMBER_FORMAT " last_calculated_value = " CALCULATED_NUMBER_FORMAT " calculated_value = " CALCULATED_NUMBER_FORMAT , st->id, rd->name , rd->last_collected_value , rd->collected_value , rd->last_calculated_value , rd->calculated_value ); } // at this point we have all the calculated values ready // it is now time to interpolate values on a second boundary unsigned long long first_ut = last_ut; long long iterations = (now_ut - last_ut) / (st->update_every * 1000000ULL); if((now_ut % (st->update_every * 1000000ULL)) == 0) iterations++; for( ; likely(next_ut <= now_ut) ; next_ut += st->update_every * 1000000ULL, iterations-- ) { #ifdef NETDATA_INTERNAL_CHECKS if(iterations < 0) { error("%s: iterations calculation wrapped! first_ut = %llu, last_ut = %llu, next_ut = %llu, now_ut = %llu", st->name, first_ut, last_ut, next_ut, now_ut); } #endif if(unlikely(st->debug)) { debug(D_RRD_STATS, "%s: last ut = %0.3Lf (last updated time)", st->name, (long double)last_ut/1000000.0); debug(D_RRD_STATS, "%s: next ut = %0.3Lf (next interpolation point)", st->name, (long double)next_ut/1000000.0); } st->last_updated.tv_sec = (time_t) (next_ut / 1000000ULL); st->last_updated.tv_usec = 0; for( rd = st->dimensions ; likely(rd) ; rd = rd->next ) { calculated_number new_value; switch(rd->algorithm) { case RRDDIM_INCREMENTAL: new_value = (calculated_number) ( rd->calculated_value * (calculated_number)(next_ut - last_ut) / (calculated_number)(now_ut - last_ut) ); if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: CALC2 INC " CALCULATED_NUMBER_FORMAT " = " CALCULATED_NUMBER_FORMAT " * %llu" " / %llu" , st->id, rd->name , new_value , rd->calculated_value , (next_ut - last_ut) , (now_ut - last_ut) ); rd->calculated_value -= new_value; new_value += rd->last_calculated_value; rd->last_calculated_value = 0; new_value /= (calculated_number)st->update_every; break; case RRDDIM_ABSOLUTE: case RRDDIM_PCENT_OVER_ROW_TOTAL: case RRDDIM_PCENT_OVER_DIFF_TOTAL: default: if(iterations == 1) { // this is the last iteration // do not interpolate // just show the calculated value new_value = rd->calculated_value; } else { // we have missed an update // interpolate in the middle values new_value = (calculated_number) ( ( (rd->calculated_value - rd->last_calculated_value) * (calculated_number)(next_ut - first_ut) / (calculated_number)(now_ut - first_ut) ) + rd->last_calculated_value ); if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: CALC2 DEF " CALCULATED_NUMBER_FORMAT " = (((" "(" CALCULATED_NUMBER_FORMAT " - " CALCULATED_NUMBER_FORMAT ")" " * %llu" " / %llu) + " CALCULATED_NUMBER_FORMAT , st->id, rd->name , new_value , rd->calculated_value, rd->last_calculated_value , (next_ut - first_ut) , (now_ut - first_ut), rd->last_calculated_value ); // this is wrong // it fades the value towards the target // while we know the calculated value is different // if(likely(next_ut + st->update_every * 1000000ULL > now_ut)) rd->calculated_value = new_value; } break; } if(unlikely(!store_this_entry)) { store_this_entry = 1; continue; } if(likely(rd->updated && rd->counter > 1 && iterations < st->gap_when_lost_iterations_above)) { rd->values[st->current_entry] = pack_storage_number(new_value, storage_flags ); if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: STORE[%ld] " CALCULATED_NUMBER_FORMAT " = " CALCULATED_NUMBER_FORMAT , st->id, rd->name , st->current_entry , unpack_storage_number(rd->values[st->current_entry]), new_value ); } else { if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: STORE[%ld] = NON EXISTING " , st->id, rd->name , st->current_entry ); rd->values[st->current_entry] = pack_storage_number(0, SN_NOT_EXISTS); } stored_entries++; if(unlikely(st->debug)) { calculated_number t1 = new_value * (calculated_number)rd->multiplier / (calculated_number)rd->divisor; calculated_number t2 = unpack_storage_number(rd->values[st->current_entry]); calculated_number accuracy = accuracy_loss(t1, t2); debug(D_RRD_STATS, "%s/%s: UNPACK[%ld] = " CALCULATED_NUMBER_FORMAT " FLAGS=0x%08x (original = " CALCULATED_NUMBER_FORMAT ", accuracy loss = " CALCULATED_NUMBER_FORMAT "%%%s)" , st->id, rd->name , st->current_entry , t2 , get_storage_number_flags(rd->values[st->current_entry]) , t1 , accuracy , (accuracy > ACCURACY_LOSS) ? " **TOO BIG** " : "" ); rd->collected_volume += t1; rd->stored_volume += t2; accuracy = accuracy_loss(rd->collected_volume, rd->stored_volume); debug(D_RRD_STATS, "%s/%s: VOLUME[%ld] = " CALCULATED_NUMBER_FORMAT ", calculated = " CALCULATED_NUMBER_FORMAT ", accuracy loss = " CALCULATED_NUMBER_FORMAT "%%%s" , st->id, rd->name , st->current_entry , rd->stored_volume , rd->collected_volume , accuracy , (accuracy > ACCURACY_LOSS) ? " **TOO BIG** " : "" ); } } // reset the storage flags for the next point, if any; storage_flags = SN_EXISTS; st->counter++; st->current_entry = ((st->current_entry + 1) >= st->entries) ? 0 : st->current_entry + 1; last_ut = next_ut; } // align next interpolation to last collection point if(likely(stored_entries || !store_this_entry)) { st->last_updated.tv_sec = st->last_collected_time.tv_sec; st->last_updated.tv_usec = st->last_collected_time.tv_usec; st->last_collected_total = st->collected_total; } for( rd = st->dimensions; likely(rd) ; rd = rd->next ) { if(unlikely(!rd->updated)) continue; if(likely(stored_entries || !store_this_entry)) { if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: setting last_collected_value (old: " COLLECTED_NUMBER_FORMAT ") to last_collected_value (new: " COLLECTED_NUMBER_FORMAT ")", st->id, rd->name, rd->last_collected_value, rd->collected_value); rd->last_collected_value = rd->collected_value; if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: setting last_calculated_value (old: " CALCULATED_NUMBER_FORMAT ") to last_calculated_value (new: " CALCULATED_NUMBER_FORMAT ")", st->id, rd->name, rd->last_calculated_value, rd->calculated_value); rd->last_calculated_value = rd->calculated_value; } rd->calculated_value = 0; rd->collected_value = 0; rd->updated = 0; if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: END " " last_collected_value = " COLLECTED_NUMBER_FORMAT " collected_value = " COLLECTED_NUMBER_FORMAT " last_calculated_value = " CALCULATED_NUMBER_FORMAT " calculated_value = " CALCULATED_NUMBER_FORMAT , st->id, rd->name , rd->last_collected_value , rd->collected_value , rd->last_calculated_value , rd->calculated_value ); } // ALL DONE ABOUT THE DATA UPDATE // -------------------------------------------------------------------- // find if there are any obsolete dimensions (not updated recently) if(unlikely(rrd_delete_unupdated_dimensions)) { for( rd = st->dimensions; likely(rd) ; rd = rd->next ) if((rd->last_collected_time.tv_sec + (rrd_delete_unupdated_dimensions * st->update_every)) < st->last_collected_time.tv_sec) break; if(unlikely(rd)) { // there is dimension to free // upgrade our read lock to a write lock pthread_rwlock_unlock(&st->rwlock); pthread_rwlock_wrlock(&st->rwlock); for( rd = st->dimensions, last = NULL ; likely(rd) ; ) { // remove it only it is not updated in rrd_delete_unupdated_dimensions seconds if(unlikely((rd->last_collected_time.tv_sec + (rrd_delete_unupdated_dimensions * st->update_every)) < st->last_collected_time.tv_sec)) { info("Removing obsolete dimension '%s' (%s) of '%s' (%s).", rd->name, rd->id, st->name, st->id); if(unlikely(!last)) { st->dimensions = rd->next; rd->next = NULL; rrddim_free(st, rd); rd = st->dimensions; continue; } else { last->next = rd->next; rd->next = NULL; rrddim_free(st, rd); rd = last->next; continue; } } last = rd; rd = rd->next; } if(unlikely(!st->dimensions)) { info("Disabling chart %s (%s) since it does not have any dimensions", st->name, st->id); st->enabled = 0; } } } pthread_rwlock_unlock(&st->rwlock); if(unlikely(pthread_setcancelstate(oldstate, NULL) != 0)) error("Cannot set pthread cancel state to RESTORE (%d).", oldstate); return(st->usec_since_last_update); }
storage_number pack_storage_number(calculated_number value, uint32_t flags) { // bit 32 = sign 0:positive, 1:negative // bit 31 = 0:divide, 1:multiply // bit 30, 29, 28 = (multiplier or divider) 0-6 (7 total) // bit 27, 26, 25 flags // bit 24 to bit 1 = the value storage_number r = get_storage_number_flags(flags); if(!value) return r; int m = 0; calculated_number n = value; // if the value is negative // add the sign bit and make it positive if(n < 0) { r += (1 << 31); // the sign bit 32 n = -n; } // make its integer part fit in 0x00ffffff // by dividing it by 10 up to 7 times // and increasing the multiplier while(m < 7 && n > (calculated_number)0x00ffffff) { n /= 10; m++; } if(m) { // the value was too big and we divided it // so we add a multiplier to unpack it r += (1 << 30) + (m << 27); // the multiplier m if(n > (calculated_number)0x00ffffff) { error("Number " CALCULATED_NUMBER_FORMAT " is too big.", value); r += 0x00ffffff; return r; } } else { // 0x0019999e is the number that can be multiplied // by 10 to give 0x00ffffff // while the value is below 0x0019999e we can // multiply it by 10, up to 7 times, increasing // the multiplier while(m < 7 && n < (calculated_number)0x0019999e) { n *= 10; m++; } // the value was small enough and we multiplied it // so we add a divider to unpack it r += (0 << 30) + (m << 27); // the divider m } #ifdef STORAGE_WITH_MATH // without this there are rounding problems // example: 0.9 becomes 0.89 r += lrint((double) n); #else r += (storage_number)n; #endif return r; }