| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "stream.h" |
| 4 | #include "stream-sender-internals.h" |
| 5 | |
| 6 | struct stream_circular_buffer { |
| 7 | struct circular_buffer *cb; |
| 8 | STREAM_CIRCULAR_BUFFER_STATS stats; |
| 9 | |
| 10 | usec_t last_recreate_ut; // recreates are only used to shrink the buffer, they are normal during operation |
| 11 | usec_t last_sent_ut; // the last time we removed or flushed data from the buffer |
| 12 | |
| 13 | struct { |
| 14 | // the current max size of the buffer |
| 15 | size_t max_size; |
| 16 | |
| 17 | // the current utilization of the buffer |
| 18 | size_t buffer_ratio; |
| 19 | |
| 20 | // the last time we flushed the buffer |
| 21 | // by monitoring this we can know if the system was reconnected |
| 22 | usec_t last_flush_ut; |
| 23 | } atomic; |
| 24 | }; |
| 25 | |
| 26 | static inline void stream_circular_buffer_stats_update_unsafe(STREAM_CIRCULAR_BUFFER *scb) { |
| 27 | scb->stats.bytes_size = scb->cb->size; |
| 28 | scb->stats.bytes_max_size = scb->cb->max_size; |
| 29 | scb->stats.bytes_outstanding = cbuffer_next_unsafe(scb->cb, NULL); |
| 30 | scb->stats.bytes_available = cbuffer_available_size_unsafe(scb->cb); |
| 31 | scb->stats.buffer_ratio = (double)(scb->cb->max_size - scb->stats.bytes_available) * 100.0 / (double)scb->cb->max_size; |
| 32 | |
| 33 | __atomic_store_n(&((scb)->atomic.buffer_ratio), (size_t)round(scb->stats.buffer_ratio), __ATOMIC_RELAXED); |
| 34 | } |
| 35 | |
| 36 | STREAM_CIRCULAR_BUFFER *stream_circular_buffer_create(void) { |
| 37 | STREAM_CIRCULAR_BUFFER *scb = callocz(1, sizeof(*scb)); |
| 38 | scb->cb = cbuffer_new(CBUFFER_INITIAL_SIZE, CBUFFER_INITIAL_MAX_SIZE, &netdata_buffers_statistics.cbuffers_streaming); |
| 39 | stream_circular_buffer_stats_update_unsafe(scb); |
| 40 | return scb; |
| 41 | } |
| 42 | |
| 43 | // returns true if it increased the buffer size |
| 44 | bool stream_circular_buffer_set_max_size_unsafe(STREAM_CIRCULAR_BUFFER *scb, size_t max_size, bool force) { |
| 45 | if(force || scb->cb->max_size < max_size) { |
| 46 | scb->cb->max_size = max_size; |
| 47 | scb->stats.bytes_max_size = scb->cb->max_size; |
| 48 | __atomic_store_n(&scb->atomic.max_size, scb->cb->max_size, __ATOMIC_RELAXED); |
| 49 | stream_circular_buffer_stats_update_unsafe(scb); |
| 50 | return true; |
| 51 | } |
| 52 | |
| 53 | return false; |
| 54 | } |
| 55 | |
| 56 | void stream_circular_buffer_flush_unsafe(STREAM_CIRCULAR_BUFFER *scb, size_t buffer_max_size) { |
| 57 | usec_t now_ut = now_monotonic_usec(); |
| 58 | __atomic_store_n(&scb->atomic.last_flush_ut, now_ut, __ATOMIC_RELAXED); |
| 59 | |
| 60 | // flush the output buffer from any data it may have |
| 61 | scb->last_sent_ut = now_ut; |
| 62 | cbuffer_flush(scb->cb); |
| 63 | memset(&scb->stats, 0, sizeof(scb->stats)); |
| 64 | stream_circular_buffer_set_max_size_unsafe(scb, buffer_max_size, true); |
| 65 | stream_circular_buffer_recreate_timed_unsafe(scb, now_monotonic_usec(), true); |
| 66 | } |
| 67 | |
| 68 | inline size_t stream_sender_get_buffer_used_percent(STREAM_CIRCULAR_BUFFER *scb) { |
| 69 | return __atomic_load_n(&scb->atomic.buffer_ratio, __ATOMIC_RELAXED); |
| 70 | } |
| 71 | |
| 72 | size_t stream_circular_buffer_get_max_size(STREAM_CIRCULAR_BUFFER *scb) { |
| 73 | return __atomic_load_n(&scb->atomic.max_size, __ATOMIC_RELAXED); |
| 74 | } |
| 75 | |
| 76 | void stream_circular_buffer_recreate_timed_unsafe(STREAM_CIRCULAR_BUFFER *scb, usec_t now_ut, bool force) { |
| 77 | if(!force && (scb->stats.bytes_outstanding || now_ut - scb->last_recreate_ut < 300 * USEC_PER_SEC)) |
| 78 | return; |
| 79 | |
| 80 | scb->last_recreate_ut = now_ut; |
| 81 | |
| 82 | scb->stats.recreates++; // we increase even if we don't do it, to have sender_start() recreate its buffers |
| 83 | |
| 84 | if(scb->cb && scb->cb->size > CBUFFER_INITIAL_SIZE) { |
| 85 | size_t max_size = scb->cb->max_size; |
| 86 | cbuffer_free(scb->cb); |
| 87 | scb->cb = cbuffer_new(CBUFFER_INITIAL_SIZE, max_size, &netdata_buffers_statistics.cbuffers_streaming); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | inline usec_t stream_circular_buffer_last_flush_ut(STREAM_CIRCULAR_BUFFER *scb) { |
| 92 | return __atomic_load_n(&((scb)->atomic.last_flush_ut), __ATOMIC_RELAXED); |
| 93 | } |
| 94 | |
| 95 | inline usec_t stream_circular_buffer_last_sent_ut(STREAM_CIRCULAR_BUFFER *scb) { |
| 96 | // this is ok without locks and atomics, since only the stream threads |
| 97 | // can actually remove data and call this |
| 98 | return scb->last_sent_ut; |
| 99 | } |
| 100 | |
| 101 | void stream_circular_buffer_destroy(STREAM_CIRCULAR_BUFFER *scb) { |
| 102 | if(!scb) return; |
| 103 | cbuffer_free(scb->cb); |
| 104 | freez(scb); |
| 105 | } |
| 106 | |
| 107 | // adds data to the circular buffer, returns false when it can't (buffer is full) |
| 108 | bool stream_circular_buffer_add_unsafe( |
| 109 | STREAM_CIRCULAR_BUFFER *scb, const char *data, |
| 110 | size_t bytes_actual, size_t bytes_uncompressed, STREAM_TRAFFIC_TYPE type, bool autoscale) { |
| 111 | scb->stats.adds++; |
| 112 | scb->stats.bytes_added += bytes_actual; |
| 113 | scb->stats.bytes_uncompressed += bytes_uncompressed; |
| 114 | scb->stats.bytes_sent_by_type[type] += bytes_actual; |
| 115 | |
| 116 | if(unlikely(autoscale && cbuffer_available_size_unsafe(scb->cb) < bytes_actual)) |
| 117 | stream_circular_buffer_set_max_size_unsafe(scb, scb->cb->max_size * 2, true); |
| 118 | |
| 119 | if(unlikely(cbuffer_add_unsafe(scb->cb, data, bytes_actual) != 0)) |
| 120 | return false; |
| 121 | |
| 122 | stream_circular_buffer_stats_update_unsafe(scb); |
| 123 | return true; |
| 124 | } |
| 125 | |
| 126 | // return the first available chunk at the beginning of the buffer |
| 127 | size_t stream_circular_buffer_get_unsafe(STREAM_CIRCULAR_BUFFER *scb, char **chunk) { |
| 128 | return cbuffer_next_unsafe(scb->cb, chunk); |
| 129 | } |
| 130 | |
| 131 | // removes data from the beginning of the circular buffer |
| 132 | void stream_circular_buffer_del_unsafe(STREAM_CIRCULAR_BUFFER *scb, size_t bytes, usec_t now_ut) { |
| 133 | scb->last_sent_ut = now_ut ? now_ut : now_monotonic_usec(); |
| 134 | scb->stats.sends++; |
| 135 | scb->stats.bytes_sent += bytes; |
| 136 | cbuffer_remove_unsafe(scb->cb, bytes); |
| 137 | stream_circular_buffer_stats_update_unsafe(scb); |
| 138 | } |
| 139 | |
| 140 | // returns a copy of the current circular buffer statistics |
| 141 | STREAM_CIRCULAR_BUFFER_STATS *stream_circular_buffer_stats_unsafe(STREAM_CIRCULAR_BUFFER *scb) { |
| 142 | return &scb->stats; |
| 143 | } |