1111#include < sstream>
1212#include < thread>
1313#include < vector>
14+ #include < algorithm>
1415
1516#if defined(_WIN32)
17+ # define WIN32_LEAN_AND_MEAN
18+ # ifndef NOMINMAX
19+ # define NOMINMAX
20+ # endif
1621# include < io.h>
1722# include < windows.h>
1823# define isatty _isatty
@@ -62,16 +67,15 @@ static const char* g_col[] = {
6267};
6368
6469struct common_log_entry {
65- enum ggml_log_level level;
66-
67- bool prefix;
68-
69- int64_t timestamp;
70+ enum ggml_log_level level {GGML_LOG_LEVEL_INFO };
7071
7172 std::vector<char > msg;
7273
73- // signals the worker thread to stop
74- bool is_end;
74+ int64_t timestamp { 0 };
75+ bool is_end { false }; // signals the worker thread to stop
76+ bool prefix { false };
77+
78+ common_log_entry (size_t size = 256 ) : msg(size) { }
7579
7680 void print (FILE * file = nullptr ) const {
7781 FILE * fcur = file;
@@ -122,22 +126,15 @@ struct common_log_entry {
122126};
123127
124128struct common_log {
125- // default capacity - will be expanded if needed
126- common_log () : common_log(256 ) {}
127-
128- common_log (size_t capacity) {
129- file = nullptr ;
130- prefix = false ;
129+ // default capacity
130+ common_log (size_t capacity = 512 ) {
131+ file = nullptr ;
132+ prefix = false ;
131133 timestamps = false ;
132- running = false ;
133- t_start = t_us ();
134-
135- // initial message size - will be expanded if longer messages arrive
136- entries.resize (capacity);
137- for (auto & entry : entries) {
138- entry.msg .resize (256 );
139- }
134+ running = false ;
135+ t_start = t_us ();
140136
137+ queue.resize (capacity, common_log_entry (256 ));
141138 head = 0 ;
142139 tail = 0 ;
143140
@@ -152,9 +149,10 @@ struct common_log {
152149 }
153150
154151private:
155- std::mutex mtx;
156- std::thread thrd;
157- std::condition_variable cv;
152+ std::mutex mtx;
153+ std::thread thrd;
154+ std::condition_variable cv_new; // new entry
155+ std::condition_variable cv_full; // wait on full
158156
159157 FILE * file;
160158
@@ -164,24 +162,53 @@ struct common_log {
164162
165163 int64_t t_start;
166164
167- // ring buffer of entries
168- std::vector<common_log_entry> entries ;
165+ // queue of entries
166+ std::vector<common_log_entry> queue ;
169167 size_t head;
170168 size_t tail;
171169
172- // worker thread copies into this
173- common_log_entry cur;
170+ bool print_entry (const common_log_entry & e) const {
171+ if (e.is_end ) return true ;
172+
173+ e.print ();
174+ if (file) {
175+ e.print (file);
176+ }
177+ return false ;
178+ }
179+
180+ bool flush_queue (size_t start_head, size_t end_tail, size_t & out_head) const {
181+ bool stop = false ;
182+ size_t h = start_head;
183+ while (h != end_tail && !stop) {
184+ stop = print_entry (queue[h]);
185+ h = (h + 1 ) % queue.size ();
186+ }
187+ out_head = h;
188+ return stop;
189+ }
174190
175191public:
192+ bool is_full () const {
193+ return ((tail + 1 ) % queue.size ()) == head;
194+ }
195+
196+ bool is_empty () const {
197+ return head == tail;
198+ }
199+
176200 void add (enum ggml_log_level level, const char * fmt, va_list args) {
177- std::lock_guard<std::mutex> lock (mtx);
201+ std::unique_lock<std::mutex> lock (mtx);
202+
203+ // block if the queue is full
204+ cv_full.wait (lock, [this ]() { return !running || !is_full (); });
178205
179206 if (!running) {
180207 // discard messages while the worker thread is paused
181208 return ;
182209 }
183210
184- auto & entry = entries [tail];
211+ auto & entry = queue [tail];
185212
186213 {
187214 // cannot use args twice, so make a copy in case we need to expand the buffer
@@ -216,38 +243,16 @@ struct common_log {
216243 va_end (args_copy);
217244 }
218245
219- entry.level = level;
220- entry.prefix = prefix;
246+ entry.is_end = false ;
247+ entry.level = level;
248+ entry.prefix = prefix;
221249 entry.timestamp = 0 ;
222250 if (timestamps) {
223251 entry.timestamp = t_us () - t_start;
224252 }
225- entry.is_end = false ;
226-
227- tail = (tail + 1 ) % entries.size ();
228- if (tail == head) {
229- // expand the buffer
230- std::vector<common_log_entry> new_entries (2 *entries.size ());
231-
232- size_t new_tail = 0 ;
233253
234- do {
235- new_entries[new_tail] = std::move (entries[head]);
236-
237- head = (head + 1 ) % entries.size ();
238- new_tail = (new_tail + 1 );
239- } while (head != tail);
240-
241- head = 0 ;
242- tail = new_tail;
243-
244- for (size_t i = tail; i < new_entries.size (); i++) {
245- new_entries[i].msg .resize (256 );
246- }
247-
248- entries = std::move (new_entries);
249- }
250- cv.notify_one ();
254+ tail = (tail + 1 ) % queue.size ();
255+ cv_new.notify_one ();
251256 }
252257
253258 void resume () {
@@ -261,22 +266,23 @@ struct common_log {
261266
262267 thrd = std::thread ([this ]() {
263268 while (true ) {
264- {
265- std::unique_lock<std::mutex> lock (mtx);
266- cv.wait (lock, [this ]() { return head != tail; });
267- cur = entries[head];
269+ std::unique_lock<std::mutex> lock (mtx);
270+ cv_new.wait (lock, [this ]() { return !is_empty (); });
268271
269- head = ( head + 1 ) % entries. size () ;
270- }
272+ size_t cached_head = head;
273+ size_t cached_tail = tail;
271274
272- if (cur.is_end ) {
273- break ;
274- }
275+ lock.unlock (); // drop the lock during flush
276+
277+ size_t next_head;
278+ bool stop = flush_queue (cached_head, cached_tail, next_head);
275279
276- cur.print (); // stdout and stderr
280+ lock.lock ();
281+ head = next_head;
282+ cv_full.notify_all ();
277283
278- if (file ) {
279- cur. print (file) ;
284+ if (stop ) {
285+ break ;
280286 }
281287 }
282288 });
@@ -293,13 +299,13 @@ struct common_log {
293299 running = false ;
294300
295301 // push an entry to signal the worker thread to stop
296- {
297- auto & entry = entries[tail] ;
298- entry. is_end = true ;
302+ auto & entry = queue[tail];
303+ entry. is_end = true ;
304+ tail = (tail + 1 ) % queue. size () ;
299305
300- tail = (tail + 1 ) % entries. size ();
301- }
302- cv. notify_one ();
306+ // wakeup everyone
307+ cv_new. notify_one ();
308+ cv_full. notify_all ();
303309 }
304310
305311 thrd.join ();
0 commit comments