1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2016 Couchbase, Inc
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17/** \file
18 * This file is internal to the inner workings of
19 * Phosphor and is not intended for public consumption.
20 */
21
22#pragma once
23
24#include <array>
25#include <iterator>
26#include <memory>
27#include <thread>
28#include <type_traits>
29#include <vector>
30
31#include <gsl_p/iterator.h>
32
33#include "phosphor/platform/core.h"
34#include "trace_event.h"
35
36namespace phosphor {
37
38#ifndef PHOSPHOR_CHUNK_PAGE_COUNT
39#define PHOSPHOR_CHUNK_PAGE_COUNT 1
40#endif
41
42    /**
43     * TraceChunk represents an array of TraceEvents
44     *
45     * The TraceChunk should be used from a single thread to
46     * store various events.
47     */
48    class PHOSPHOR_API TraceChunk {
49    public:
50        static constexpr auto chunk_page_count = PHOSPHOR_CHUNK_PAGE_COUNT;
51        static constexpr auto page_size = 4096;
52        static constexpr auto array_offset = 64;
53        static constexpr auto chunk_size =
54            (((page_size * chunk_page_count) - array_offset) /
55             sizeof(TraceEvent));
56        using event_array = std::array<TraceEvent, chunk_size>;
57
58        using const_iterator = event_array::const_iterator;
59
60        /**
61         * Constructor for a TraceChunk
62         */
63        TraceChunk() = default;
64
65        /**
66         * Reset the state of the TraceChunk
67         *
68         * This should be called before the TraceChunk is first used
69         * as TraceChunk is a trivial type and requires initialisation.
70         */
71        void reset();
72
73        /**
74         * Used for adding TraceEvents to the chunk
75         *
76         * @return A reference to a TraceEvent to be replaced
77         */
78        TraceEvent& addEvent();
79
80        /**
81         * Used for reviewing TraceEvents in the chunk
82         *
83         * Valid indexes are from 0 to `count()`. There is no
84         * bounds checking.
85         *
86         * @return A const reference to a TraceEvent in the chunk
87         *         that can be used to review the event data
88         */
89        const TraceEvent& operator[](const int index) const;
90
91        /**
92         * Determine if the chunk is full
93         *
94         * @return true if the chunk is full (and should be replaced)
95         *         or false otherwise.
96         */
97        bool isFull() const;
98
99        /**
100         * Determine up to which index of events is initialised
101         *
102         * @return The number of initialised events in the chunk
103         */
104        size_t count() const;
105
106        /**
107         * @return Const iterator to the start of the chunk
108         */
109        const_iterator begin() const;
110
111        /**
112         * @return Const iterator to the last initialised event in the chunk
113         */
114        const_iterator end() const;
115
116    private:
117        unsigned short next_free;
118        event_array chunk;
119    };
120
121    // Forward decl
122    class StatsCallback;
123
124    /**
125     * Abstract base-class for a buffer of TraceEvents
126     *
127     * The TraceBuffer loans out TraceChunks to individual
128     * threads to reduce lock-contention on event logging.
129     *
130     * This class is *not* thread-safe and should only be directly
131     * interacted either with the global lock owned by the TraceLog
132     * or after tracing has been finished.
133     *
134     * A trace buffer can be iterated over using C++11 style range
135     * iteration:
136     *
137     *     for(const auto& event : trace_buffer) {
138     *         std::cout << event << std::endl;
139     *     }
140     *
141     * Alternatively more complex iteration can be accomplished by
142     * using the iterators returned by TraceBuffer::begin() and
143     * TraceBuffer::end().
144     *
145     * Iteration should not be attempted while chunks are loaned out.
146     */
147    class TraceBuffer {
148    public:
149        /**
150         * Virtual destructor to allow subclasses to be cleaned up
151         * properly.
152         */
153        virtual ~TraceBuffer() = default;
154
155        /**
156         * Used for getting a TraceChunk to add events to
157         *
158         * @return A pointer to a TraceChunk to insert events into or
159         *         nullptr if the buffer is full.
160         */
161        virtual TraceChunk* getChunk() = 0;
162
163        /**
164         * Used for returning a TraceChunk once full
165         *
166         * For some buffer implementations this *may* be a no-op
167         * but for others which might reuse chunks this can be
168         * used to only reuse chunks that have been finished with.
169         *
170         * @param chunk The chunk to be returned
171         */
172        virtual void returnChunk(TraceChunk& chunk) = 0;
173
174        /**
175         * Determine if there are no remaining chunks left to be
176         * used
177         *
178         * @return true if there are no chunks left or false
179         *         otherwise
180         */
181        virtual bool isFull() const = 0;
182
183        /**
184         * Callback for retrieving stats from the Buffer implementation
185         *
186         * Implementations MUST supply the following stats as minimum:
187         *
188         * - buffer_name <cstring_span>: Textual representation of buffer type
189         * - buffer_is_full <bool>: True if the buffer is full
190         * - buffer_chunk_count <size_t>: Chunks that are returned or loaned
191         * - buffer_loaned_chunks <size_t>: Currently loaned chunks
192         * - buffer_total_loaned <size_t>: Count of all chunks ever loaned
193         * - buffer_size <size_t>: Max number of chunks that fit in the buffer
194         * - buffer_generation <size_t>: Generation number of the buffer
195         *
196         * On a non-rotating buffer, if buffer_chunk_count is equal to
197         * buffer-size then that must suggest the buffer is full and
198         * there are no more chunks to be loaned. On a rotating buffer
199         * it suggests that chunks are being reused.
200         *
201         * Buffer implementations may include other relevant stats but
202         * end-users SHOULD NOT assume the existence of those stats.
203         */
204        virtual void getStats(StatsCallback& addStats) const = 0;
205
206        /**
207         * Used for accessing TraceChunks in the buffer
208         *
209         * Valid indexes are from 0 to `count()`. There is no
210         * bounds checking.
211         *
212         * @return A const reference to a TraceEvent in the chunk
213         *         that can be used to review the event data
214         * @throw std::logic_error if chunks are currently loaned
215         *        out to chunk tenants.
216         */
217        virtual const TraceChunk& operator[](const int index) const = 0;
218
219        /**
220         * Used for determining the number of chunks in the buffer
221         *
222         * @return Number of chunks currently in the buffer
223         */
224        virtual size_t chunk_count() const = 0;
225
226        /**
227         * @return The generation number of the TraceBuffer
228         */
229        virtual size_t getGeneration() const = 0;
230
231        /**
232         * Const bi-directional iterator over the TraceChunks in a TraceBuffer
233         *
234         * Usage:
235         *
236         *    chunk_iterator it = buffer.chunk_start();
237         *     std::cout << *(it->start()) << std::endl;
238         *
239         *     for(const auto& chunk : buffer.chunks()) {
240         *         for(const auto& event : chunk) {
241         *             std::cout << event << std::endl;
242         *         }
243         *     }
244         */
245        class PHOSPHOR_API chunk_iterator
246            : public std::iterator<std::bidirectional_iterator_tag,
247                                   TraceChunk> {
248            using const_reference = const TraceChunk&;
249            using const_pointer = const TraceChunk*;
250            using _self = chunk_iterator;
251
252        public:
253            chunk_iterator() = default;
254            chunk_iterator(const TraceBuffer& buffer_);
255            chunk_iterator(const TraceBuffer& buffer_, size_t index_);
256            const_reference operator*() const;
257            const_pointer operator->() const;
258            chunk_iterator& operator++();
259            bool operator==(const chunk_iterator& other) const;
260            bool operator!=(const chunk_iterator& other) const;
261
262        protected:
263            const TraceBuffer& buffer;
264            size_t index;
265        };
266
267        /**
268         * @return A const iterator to the first chunk of the TraceBuffer
269         */
270        virtual chunk_iterator chunk_begin() const = 0;
271
272        /**
273         * @return A const iterator to after the last chunk of the TraceBuffer
274         */
275        virtual chunk_iterator chunk_end() const = 0;
276
277        /**
278         * Const iterator over a TraceBuffer, implements required methods of
279         * a bi-directional iterator.
280         *
281         * Usage:
282         *
283         *     event_iterator it = trace_buffer.start();
284         *     std::cout << *it << std::endl;
285         *     std::cout << *(++it) << std::endl;
286         *
287         *     for(const auto& event : trace_buffer) {
288         *         std::cout << event << std::endl;
289         *     }
290         *
291         * For resource efficiency event_iterator does not have
292         * post-increment
293         */
294        using event_iterator = gsl_p::multidimensional_iterator<chunk_iterator>;
295
296        /**
297         * @return A const iterator to the first event of the TraceBuffer
298         */
299        virtual event_iterator begin() const = 0;
300
301        /**
302         * @return A const iterator to after the last event of the TraceBuffer
303         */
304        virtual event_iterator end() const = 0;
305
306        /**
307         * Helper class that can be used to create for-range loops over
308         * the chunks of the TraceBuffer
309         *
310         *     for(const auto& chunk : TraceBuffer::chunk_iterable(buffer) {
311         *         // Do something with every chunk
312         *     }
313         */
314        class PHOSPHOR_API chunk_iterable {
315        public:
316            /**
317             * @param buffer_ The buffer to iterate over
318             */
319            chunk_iterable(const TraceBuffer& buffer_) : buffer(buffer_) {}
320
321            /**
322             * @return A const iterator to the first chunk of the buffer
323             */
324            chunk_iterator begin() {
325                return buffer.chunk_begin();
326            }
327
328            /**
329             * @return A const iterator to after the last chunk of the buffer
330             */
331            chunk_iterator end() {
332                return buffer.chunk_end();
333            }
334
335        private:
336            const TraceBuffer& buffer;
337        };
338
339        /**
340         * Helper function for creating a chunk_iterable over the buffer
341         *
342         *     for(const auto& chunk : buffer.chunks() {
343         *         // Do something with every chunk
344         *     }
345         *
346         * @return An iterable over the class
347         */
348        chunk_iterable chunks() const {
349            return chunk_iterable(*this);
350        }
351    };
352
353    using buffer_ptr = std::unique_ptr<TraceBuffer>;
354
355    /**
356     * Interface for a TraceBuffer factory
357     */
358    using trace_buffer_factory =
359        std::function<buffer_ptr(size_t generation, size_t buffer_size)>;
360
361    PHOSPHOR_API
362    buffer_ptr make_fixed_buffer(size_t generation, size_t buffer_size);
363
364    PHOSPHOR_API
365    buffer_ptr make_ring_buffer(size_t generation, size_t buffer_size);
366}
367