xref: /haiku/src/apps/debuganalyzer/gui/main_window/SchedulingPage.cpp (revision c9ad965c81b08802fed0827fd1dd16f45297928a)
1 /*
2  * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "main_window/SchedulingPage.h"
8 
9 #include <stdio.h>
10 
11 #include <algorithm>
12 #include <new>
13 
14 #include <LayoutBuilder.h>
15 #include <LayoutUtils.h>
16 #include <ScrollView.h>
17 #include <SplitView.h>
18 
19 #include <DebugEventStream.h>
20 
21 #include <thread_defs.h>
22 
23 #include "Array.h"
24 
25 #include "chart/BigtimeChartAxisLegendSource.h"
26 #include "chart/LegendChartAxis.h"
27 #include "chart/StringChartLegend.h"
28 #include "HeaderView.h"
29 #include "Model.h"
30 
31 
32 static const float kThreadNameMargin = 3.0f;
33 static const float kViewSeparationMargin = 5.0f;
34 
35 
36 struct MainWindow::SchedulingPage::SchedulingEvent {
37 	bigtime_t						time;
38 	Model::ThreadWaitObjectGroup*	waitObject;
39 	ThreadState						state;
40 
41 	SchedulingEvent(bigtime_t time, ThreadState state,
42 		Model::ThreadWaitObjectGroup* waitObject)
43 		:
44 		time(time),
45 		waitObject(waitObject),
46 		state(state)
47 	{
48 	}
49 };
50 
51 
52 class MainWindow::SchedulingPage::SchedulingData {
53 public:
54 	SchedulingData()
55 		:
56 		fModel(NULL),
57 		fDataArrays(NULL),
58 		fRecordingEnabled(false)
59 	{
60 	}
61 
62 	status_t InitCheck() const
63 	{
64 		return fDataArrays != NULL ? B_OK : B_NO_MEMORY;
65 	}
66 
67 	void SetModel(Model* model)
68 	{
69 		delete[] fDataArrays;
70 
71 		fModel = model;
72 		fDataArrays = NULL;
73 
74 		if (fModel != NULL)
75 			fDataArrays = new(std::nothrow) DataArray[fModel->CountThreads()];
76 	}
77 
78 	void SetRecordingEnabled(bool enabled)
79 	{
80 		fRecordingEnabled = enabled;
81 	}
82 
83 	void Clear()
84 	{
85 		if (fDataArrays == NULL)
86 			return;
87 
88 		int32 count = fModel->CountThreads();
89 		for (int32 i = 0; i < count; i++)
90 			fDataArrays[i].Clear();
91 	}
92 
93 	const Array<SchedulingEvent>& EventsForThread(int32 index)
94 	{
95 		return fDataArrays[index];
96 	}
97 
98 	void AddState(Model::Thread* thread, bigtime_t time, ThreadState state,
99 		Model::ThreadWaitObjectGroup* waitObject)
100 	{
101 		DataArray& array = fDataArrays[thread->Index()];
102 		if (!array.IsEmpty()) {
103 			SchedulingEvent& lastEvent = array[array.Size() - 1];
104 			if (fRecordingEnabled) {
105 				if (lastEvent.state == state
106 					&& lastEvent.waitObject == waitObject) {
107 					return;
108 				}
109 			} else {
110 				// recording not enabled yet -- overwrite the last event
111 				lastEvent = SchedulingEvent(time, state, waitObject);
112 				return;
113 			}
114 		}
115 
116 		SchedulingEvent event(time, state, waitObject);
117 		array.Add(event);
118 	}
119 
120 	void AddRun(Model::Thread* thread, bigtime_t time)
121 	{
122 		AddState(thread, time, RUNNING, NULL);
123 	}
124 
125 	void AddLatency(Model::Thread* thread, bigtime_t time)
126 	{
127 		AddState(thread, time, READY, NULL);
128 	}
129 
130 	void AddPreemption(Model::Thread* thread, bigtime_t time)
131 	{
132 		AddState(thread, time, PREEMPTED, NULL);
133 	}
134 
135 	void AddWait(Model::Thread* thread, bigtime_t time,
136 		Model::ThreadWaitObjectGroup* waitObject)
137 	{
138 		AddState(thread, time, WAITING, waitObject);
139 	}
140 
141 	void AddUnspecifiedWait(Model::Thread* thread, bigtime_t time)
142 	{
143 		AddState(thread, time, WAITING, NULL);
144 	}
145 
146 private:
147 	typedef Array<SchedulingEvent> DataArray;
148 
149 private:
150 	Model*		fModel;
151 	DataArray*	fDataArrays;
152 	bool		fRecordingEnabled;
153 };
154 
155 
156 struct MainWindow::SchedulingPage::TimeRange : BReferenceable {
157 	bigtime_t	startTime;
158 	bigtime_t	endTime;
159 
160 	TimeRange(bigtime_t startTime, bigtime_t endTime)
161 		:
162 		startTime(startTime),
163 		endTime(endTime)
164 	{
165 	}
166 };
167 
168 
169 class MainWindow::SchedulingPage::TimelineHeaderRenderer
170 	: public HeaderRenderer {
171 public:
172 	TimelineHeaderRenderer()
173 		:
174 		fAxis(new BigtimeChartAxisLegendSource, new StringChartLegendRenderer)
175 	{
176 		fAxis.SetLocation(CHART_AXIS_TOP);
177 	}
178 
179 	virtual float HeaderHeight(BView* view, const Header* header)
180 	{
181 		_SetupAxis(view, header);
182 		return fAxis.PreferredSize(view, view->Frame().Size()).height;
183 	}
184 
185 	virtual float PreferredHeaderWidth(BView* view, const Header* header)
186 	{
187 		_SetupAxis(view, header);
188 		return fAxis.PreferredSize(view, view->Frame().Size()).width;
189 	}
190 
191 	virtual void DrawHeader(BView* view, BRect frame, BRect updateRect,
192 		const Header* header, uint32 flags)
193 	{
194 		_SetupAxis(view, header);
195 		fAxis.SetFrame(frame);
196 		DrawHeaderBackground(view, frame, updateRect, flags);
197 		fAxis.Render(view, updateRect);
198 	}
199 
200 private:
201 	void _SetupAxis(BView* view, const Header* header)
202 	{
203 		BVariant value;
204 		if (header->GetValue(value)) {
205 			TimeRange* timeRange = dynamic_cast<TimeRange*>(
206 				value.ToReferenceable());
207 			if (timeRange != NULL) {
208 				ChartDataRange range = ChartDataRange(timeRange->startTime,
209 					timeRange->endTime);
210 				if (range != fRange) {
211 					fAxis.SetRange(range);
212 					fRange = range;
213 				}
214 			}
215 		}
216 	}
217 
218 private:
219 	LegendChartAxis		fAxis;
220 	ChartDataRange		fRange;
221 };
222 
223 
224 class MainWindow::SchedulingPage::BaseView : public BView {
225 public:
226 	BaseView(const char* name, FontInfo& fontInfo)
227 		:
228 		BView(name, B_WILL_DRAW),
229 		fModel(NULL),
230 		fFontInfo(fontInfo)
231 	{
232 	}
233 
234 	virtual void SetModel(Model* model)
235 	{
236 		fModel = model;
237 
238 		InvalidateLayout();
239 	}
240 
241 protected:
242 	int32 _CountLines() const
243 	{
244 		return fModel != NULL ? fModel->CountThreads() : 0;
245 	}
246 
247 	float TotalHeight() const
248 	{
249 		return fFontInfo.lineHeight * _CountLines();
250 	}
251 
252 	void GetLineRange(BRect rect, int32& minLine, int32& maxLine) const
253 	{
254 		int32 lineHeight = (int32)fFontInfo.lineHeight;
255 		minLine = (int32)rect.top / lineHeight;
256 		maxLine = ((int32)ceilf(rect.bottom) + lineHeight - 1) / lineHeight;
257 		minLine = std::max(minLine, 0L);
258 		maxLine = std::min(maxLine, _CountLines() - 1);
259 	}
260 
261 	BRect LineRect(uint32 line) const
262 	{
263 		float y = (float)line * fFontInfo.lineHeight;
264 		return BRect(0, y, Bounds().right, y + fFontInfo.lineHeight - 1);
265 	}
266 
267 protected:
268 	Model*		fModel;
269 	FontInfo&	fFontInfo;
270 };
271 
272 
273 class MainWindow::SchedulingPage::ThreadsView : public BaseView {
274 public:
275 	ThreadsView(FontInfo& fontInfo)
276 		:
277 		BaseView("threads", fontInfo)
278 	{
279 	}
280 
281 	virtual void Draw(BRect updateRect)
282 	{
283 		if (fModel == NULL)
284 			return;
285 
286 		// get the lines intersecting with the update rect
287 		int32 minLine, maxLine;
288 		GetLineRange(updateRect, minLine, maxLine);
289 
290 		for (int32 i = minLine; i <= maxLine; i++) {
291 			float y = (float)(i + 1) * fFontInfo.lineHeight
292 				- fFontInfo.fontHeight.descent;
293 			DrawString(fModel->ThreadAt(i)->Name(),
294 				BPoint(kThreadNameMargin, y));
295 		}
296 	}
297 
298 	virtual BSize MinSize()
299 	{
300 		return BSize(100, TotalHeight());
301 	}
302 
303 	virtual BSize MaxSize()
304 	{
305 		return BSize(MinSize().width, B_SIZE_UNLIMITED);
306 	}
307 };
308 
309 
310 class MainWindow::SchedulingPage::SchedulingView : public BaseView {
311 public:
312 	struct Listener {
313 		virtual ~Listener()
314 		{
315 		}
316 
317 		virtual void DataWidthChanged() = 0;
318 		virtual void DataRangeChanged() = 0;
319 	};
320 
321 public:
322 	SchedulingView(FontInfo& fontInfo)
323 		:
324 		BaseView("scheduling", fontInfo),
325 		fStartTime(0),
326 		fEndTime(0),
327 		fUSecsPerPixel(1000),
328 		fLastMousePos(-1, -1),
329 		fListener(NULL)
330 	{
331 	}
332 
333 	virtual void SetModel(Model* model)
334 	{
335 		BaseView::SetModel(model);
336 		fSchedulingData.SetModel(model);
337 		fStartTime = 0;
338 		fEndTime = 0;
339 
340 		if (fListener != NULL) {
341 			fListener->DataWidthChanged();
342 			fListener->DataRangeChanged();
343 		}
344 
345 	}
346 
347 	void SetListener(Listener* listener)
348 	{
349 		fListener = listener;
350 	}
351 
352 	void UpdateScrollBar()
353 	{
354 		float width = Frame().Width();
355 		float dataWidth = std::max(width, MinSize().width);
356 
357 		if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL)) {
358 			float range = dataWidth - width;
359 			if (range > 0) {
360 				scrollBar->SetRange(0, range);
361 				scrollBar->SetProportion((width + 1) / (dataWidth + 1));
362 				scrollBar->SetSteps(fFontInfo.lineHeight, width + 1);
363 			} else {
364 				scrollBar->SetRange(0, 0);
365 				scrollBar->SetProportion(1);
366 			}
367 		}
368 	}
369 
370 	void GetDataRange(bigtime_t& _startTime, bigtime_t& _endTime)
371 	{
372 		_GetEventTimeRange(_startTime, _endTime);
373 	}
374 
375 	virtual BSize MinSize()
376 	{
377 		bigtime_t timeSpan = fModel != NULL ? fModel->LastEventTime() : 0;
378 		float width = std::max(float(timeSpan / fUSecsPerPixel), 100.0f);
379 		return BSize(width, TotalHeight());
380 	}
381 
382 	virtual BSize MaxSize()
383 	{
384 		return BSize(MinSize().width, B_SIZE_UNLIMITED);
385 	}
386 
387 	virtual void ScrollTo(BPoint where)
388 	{
389 		BaseView::ScrollTo(where);
390 		fStartTime = 0;
391 		fEndTime = 0;
392 
393 		if (fListener != NULL)
394 			fListener->DataRangeChanged();
395 	}
396 
397 	void MessageReceived(BMessage* message)
398 	{
399 		switch (message->what) {
400 			case B_MOUSE_WHEEL_CHANGED:
401 			{
402 				// We're only interested in Shift + vertical wheel.
403 				float deltaY;
404 				if ((modifiers() & B_SHIFT_KEY) == 0
405 					|| message->FindFloat("be:wheel_delta_y", &deltaY)
406 						!= B_OK) {
407 					break;
408 				}
409 
410 				_Zoom(fLastMousePos.x, deltaY);
411 
412 				return;
413 			}
414 		}
415 
416 		BView::MessageReceived(message);
417 	}
418 
419 	void MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
420 	{
421 		fLastMousePos = where - LeftTop();
422 
423 //		if (fDraggingStartPos.x < 0)
424 //			return;
425 //
426 //		ScrollBar(B_HORIZONTAL)->SetValue(fDraggingStartScrollValue
427 //			+ fDraggingStartPos.x - where.x);
428 	}
429 
430 	virtual void Draw(BRect updateRect)
431 	{
432 		if (fModel == NULL || fSchedulingData.InitCheck() != B_OK)
433 			return;
434 
435 		_UpdateData();
436 
437 		// draw the events
438 // TODO: Draw only the threads currently visible.
439 		int32 threadCount = fModel->CountThreads();
440 		for (int32 i = 0; i < threadCount; i++) {
441 			Model::Thread* thread = fModel->ThreadAt(i);
442 			const Array<SchedulingEvent>& events
443 				= fSchedulingData.EventsForThread(thread->Index());
444 
445 			int32 eventCount = events.Size();
446 //printf("drawing events for thread %ld: %ld events\n", thread->Index(), eventCount);
447 			for (int32 k = 0; k < eventCount; k++) {
448 				const SchedulingEvent& event = events[k];
449 				bigtime_t startTime = std::max(event.time, fStartTime);
450 				bigtime_t endTime = k + 1 < eventCount
451 					? std::min(events[k + 1].time, fEndTime) : fEndTime;
452 
453 				rgb_color color;
454 				switch (event.state) {
455 					case RUNNING:
456 					case STILL_RUNNING:
457 						color.set_to(0, 255, 0);
458 						break;
459 					case PREEMPTED:
460 						color.set_to(255, 127, 0);
461 						break;
462 					case READY:
463 						color.set_to(255, 0, 0);
464 						break;
465 					case WAITING:
466 					case UNKNOWN:
467 					default:
468 						continue;
469 				}
470 
471 				SetHighColor(color);
472 				BRect rect = LineRect(i);
473 				rect.left = startTime / fUSecsPerPixel;
474 				rect.right = endTime / fUSecsPerPixel - 1;
475 				FillRect(rect);
476 			}
477 		}
478 	}
479 
480 private:
481 	// shorthands for the longish structure names
482 	typedef system_profiler_thread_enqueued_in_run_queue
483 		thread_enqueued_in_run_queue;
484 	typedef system_profiler_thread_removed_from_run_queue
485 		thread_removed_from_run_queue;
486 
487 private:
488 	void _UpdateData()
489 	{
490 		// get the interesting event time range
491 		bigtime_t startTime;
492 		bigtime_t endTime;
493 		_GetEventTimeRange(startTime, endTime);
494 
495 		if (startTime == fStartTime && endTime == fEndTime)
496 			return;
497 		fStartTime = startTime;
498 		fEndTime = endTime;
499 
500 		fSchedulingData.Clear();
501 
502 //printf("MainWindow::SchedulingPage::SchedulingView::_UpdateData()\n");
503 //printf("  time range: %lld - %lld\n", startTime, endTime);
504 
505 		// get a scheduling state close to our start time
506 		const Model::CompactSchedulingState* compactState
507 			= fModel->ClosestSchedulingState(startTime);
508 //printf("  compactState: %p\n", compactState);
509 		fState.Clear();
510 		status_t error = fState.Init(compactState);
511 		if (error != B_OK)
512 			return;
513 
514 		// init the event stream
515 		BDebugEventInputStream input;
516 		error = input.SetTo((uint8*)fModel->EventData(),
517 			fModel->EventDataSize(), false);
518 		if (error == B_OK && compactState != NULL)
519 			error = input.Seek(compactState->EventOffset());
520 //printf("  event offset: %lld, input init error: %s\n", compactState != NULL ? compactState->EventOffset() : 0, strerror(error));
521 		if (error != B_OK)
522 			return;
523 
524 		fSchedulingData.SetRecordingEnabled(
525 			fState.LastEventTime() >= startTime);
526 
527 		// add the initial thread states to the scheduling data
528 		if (compactState != NULL) {
529 			int32 threadStateCount = compactState->CountThreadsStates();
530 			for (int32 i = 0; i < threadStateCount; i++) {
531 				const Model::CompactThreadSchedulingState* threadState
532 					= compactState->ThreadStateAt(i);
533 				switch (threadState->state) {
534 					case RUNNING:
535 					case STILL_RUNNING:
536 						fSchedulingData.AddRun(threadState->thread,
537 							threadState->lastTime);
538 						break;
539 					case PREEMPTED:
540 						fSchedulingData.AddPreemption(threadState->thread,
541 							threadState->lastTime);
542 						break;
543 					case READY:
544 						fSchedulingData.AddLatency(threadState->thread,
545 							threadState->lastTime);
546 						break;
547 					case WAITING:
548 					{
549 						Model::ThreadWaitObjectGroup* group = NULL;
550 						if (threadState->waitObject != NULL) {
551 							group = fModel->ThreadWaitObjectGroupFor(
552 								threadState->ID(),
553 								threadState->waitObject->Type(),
554 								threadState->waitObject->Object());
555 						}
556 						fSchedulingData.AddWait(threadState->thread,
557 							threadState->lastTime, group);
558 						break;
559 					}
560 					case UNKNOWN:
561 					default:
562 						break;
563 				}
564 			}
565 		}
566 
567 		// process the events
568 		while (true) {
569 			// get next event
570 			uint32 event;
571 			uint32 cpu;
572 			const void* buffer;
573 			off_t offset;
574 			ssize_t bufferSize = input.ReadNextEvent(&event, &cpu, &buffer,
575 				&offset);
576 			if (bufferSize < 0)
577 {
578 printf("failed to read event!\n");
579 				return;
580 }
581 			if (buffer == NULL)
582 				break;
583 
584 			// process the event
585 			error = _ProcessEvent(event, cpu, buffer, bufferSize);
586 			if (error != B_OK)
587 				return;
588 
589 			if (fState.LastEventTime() >= startTime)
590 				fSchedulingData.SetRecordingEnabled(true);
591 			if (fState.LastEventTime() >= endTime)
592 				break;
593 		}
594 	}
595 
596 	void _GetEventTimeRange(bigtime_t& _startTime, bigtime_t& _endTime)
597 	{
598 		if (fModel != NULL) {
599 			float scrollOffset = _ScrollOffset();
600 			_startTime = (bigtime_t)scrollOffset * fUSecsPerPixel;
601 			_endTime = (bigtime_t)(scrollOffset + Bounds().Width() + 1)
602 				* fUSecsPerPixel;
603 		} else {
604 			_startTime = 0;
605 			_endTime = 1;
606 		}
607 	}
608 
609 	float _ScrollOffset() const
610 	{
611 		if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL))
612 			return scrollBar->Value();
613 		return 0;
614 	}
615 
616 	void _Zoom(float x, float steps)
617 	{
618 		if (steps == 0 || fModel == NULL)
619 			return;
620 
621 		// compute the domain point where to zoom in
622 		float scrollOffset = _ScrollOffset();
623 		double timeForX = (scrollOffset + x) * fUSecsPerPixel;
624 
625 		uint32 factor = 4;
626 		if (steps < 0) {
627 			steps = -steps;
628 			factor = 1;
629 		}
630 
631 		uint32 oldUsecPerPixel = fUSecsPerPixel;
632 		for (; steps > 0; steps--)
633 			fUSecsPerPixel = fUSecsPerPixel * factor / 2;
634 
635 		if (fUSecsPerPixel < 1)
636 			fUSecsPerPixel = 1;
637 		else if (fUSecsPerPixel > 1000)
638 			fUSecsPerPixel = 1000;
639 
640 		if (fUSecsPerPixel == oldUsecPerPixel)
641 			return;
642 
643 		Invalidate();
644 
645 		UpdateScrollBar();
646 		if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL))
647 			scrollBar->SetValue(timeForX / fUSecsPerPixel - x);
648 
649 		if (fListener != NULL) {
650 			fListener->DataWidthChanged();
651 			fListener->DataRangeChanged();
652 		}
653 	}
654 
655 	inline void _UpdateLastEventTime(bigtime_t time)
656 	{
657 		fState.SetLastEventTime(time - fModel->BaseTime());
658 	}
659 
660 	status_t _ProcessEvent(uint32 event, uint32 cpu, const void* buffer,
661 		size_t size)
662 	{
663 		switch (event) {
664 			case B_SYSTEM_PROFILER_TEAM_ADDED:
665 			case B_SYSTEM_PROFILER_TEAM_REMOVED:
666 			case B_SYSTEM_PROFILER_TEAM_EXEC:
667 				break;
668 
669 			case B_SYSTEM_PROFILER_THREAD_ADDED:
670 				_HandleThreadAdded((system_profiler_thread_added*)buffer);
671 				break;
672 
673 			case B_SYSTEM_PROFILER_THREAD_REMOVED:
674 				_HandleThreadRemoved((system_profiler_thread_removed*)buffer);
675 				break;
676 
677 			case B_SYSTEM_PROFILER_THREAD_SCHEDULED:
678 				_HandleThreadScheduled(
679 					(system_profiler_thread_scheduled*)buffer);
680 				break;
681 
682 			case B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE:
683 				_HandleThreadEnqueuedInRunQueue(
684 					(thread_enqueued_in_run_queue*)buffer);
685 				break;
686 
687 			case B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE:
688 				_HandleThreadRemovedFromRunQueue(
689 					(thread_removed_from_run_queue*)buffer);
690 				break;
691 
692 			case B_SYSTEM_PROFILER_WAIT_OBJECT_INFO:
693 				break;
694 
695 			default:
696 printf("unsupported event type %lu, size: %lu\n", event, size);
697 return B_BAD_DATA;
698 				break;
699 		}
700 
701 		return B_OK;
702 	}
703 
704 	void _HandleThreadAdded(system_profiler_thread_added* event)
705 	{
706 //printf("  thread added: %ld\n", event->thread);
707 		// do we know the thread already?
708 		Model::ThreadSchedulingState* info = fState.LookupThread(event->thread);
709 		if (info != NULL) {
710 			// TODO: ?
711 			return;
712 		}
713 
714 		Model::Thread* thread = fModel->ThreadByID(event->thread);
715 		if (thread == NULL)
716 			return;
717 
718 		// create and add a ThreadSchedulingState
719 		info = new(std::nothrow) Model::ThreadSchedulingState(thread);
720 		if (info == NULL)
721 			return;
722 
723 		fState.InsertThread(info);
724 	}
725 
726 	void _HandleThreadRemoved(system_profiler_thread_removed* event)
727 	{
728 //printf("  thread removed: %ld\n", event->thread);
729 //		Model::ThreadSchedulingState* thread = fState.LookupThread(
730 //			event->thread);
731 //		if (thread != NULL) {
732 //			fState.RemoveThread(thread);
733 //			delete thread;
734 //// TODO: The thread will be unscheduled in a moment and cause a warning! So
735 //// maybe keep it around in a separate hash table a bit longer?
736 //		}
737 	}
738 
739 	void _HandleThreadScheduled(system_profiler_thread_scheduled* event)
740 	{
741 		_UpdateLastEventTime(event->time);
742 
743 		Model::ThreadSchedulingState* thread = fState.LookupThread(
744 			event->thread);
745 		if (thread == NULL) {
746 			printf("Schedule event for unknown thread: %ld\n", event->thread);
747 			return;
748 		}
749 
750 		thread->lastTime = fState.LastEventTime();
751 		thread->state = RUNNING;
752 		fSchedulingData.AddRun(thread->thread, fState.LastEventTime());
753 
754 		// unscheduled thread
755 
756 		if (event->thread == event->previous_thread)
757 			return;
758 
759 		thread = fState.LookupThread(event->previous_thread);
760 		if (thread == NULL) {
761 			printf("Schedule event for unknown previous thread: %ld\n",
762 				event->previous_thread);
763 			return;
764 		}
765 
766 		if (thread->state == STILL_RUNNING) {
767 			// thread preempted
768 			fSchedulingData.AddPreemption(thread->thread,
769 				fState.LastEventTime());
770 
771 			thread->lastTime = fState.LastEventTime();
772 			thread->state = PREEMPTED;
773 		} else if (thread->state == RUNNING) {
774 			// thread starts waiting (it hadn't been added to the run
775 			// queue before being unscheduled)
776 			if (event->previous_thread_state == B_THREAD_WAITING) {
777 				addr_t waitObject = event->previous_thread_wait_object;
778 				switch (event->previous_thread_wait_object_type) {
779 					case THREAD_BLOCK_TYPE_SNOOZE:
780 					case THREAD_BLOCK_TYPE_SIGNAL:
781 						waitObject = 0;
782 						break;
783 					case THREAD_BLOCK_TYPE_SEMAPHORE:
784 					case THREAD_BLOCK_TYPE_CONDITION_VARIABLE:
785 					case THREAD_BLOCK_TYPE_MUTEX:
786 					case THREAD_BLOCK_TYPE_RW_LOCK:
787 					case THREAD_BLOCK_TYPE_OTHER:
788 					default:
789 						break;
790 				}
791 
792 				fSchedulingData.AddWait(thread->thread, fState.LastEventTime(),
793 					fModel->ThreadWaitObjectGroupFor(thread->ID(),
794 						event->previous_thread_wait_object_type, waitObject));
795 			} else {
796 				fSchedulingData.AddUnspecifiedWait(thread->thread,
797 					fState.LastEventTime());
798 			}
799 
800 			thread->lastTime = fState.LastEventTime();
801 			thread->state = WAITING;
802 		} else if (thread->state == UNKNOWN) {
803 			uint32 threadState = event->previous_thread_state;
804 			if (threadState == B_THREAD_WAITING
805 				|| threadState == B_THREAD_SUSPENDED) {
806 				fSchedulingData.AddWait(thread->thread, fState.LastEventTime(),
807 					NULL);
808 				thread->lastTime = fState.LastEventTime();
809 				thread->state = WAITING;
810 			} else if (threadState == B_THREAD_READY) {
811 				thread->lastTime = fState.LastEventTime();
812 				thread->state = PREEMPTED;
813 				fSchedulingData.AddPreemption(thread->thread,
814 					fState.LastEventTime());
815 			}
816 		}
817 	}
818 
819 	void _HandleThreadEnqueuedInRunQueue(thread_enqueued_in_run_queue* event)
820 	{
821 		_UpdateLastEventTime(event->time);
822 
823 		Model::ThreadSchedulingState* thread = fState.LookupThread(
824 			event->thread);
825 		if (thread == NULL) {
826 			printf("Enqueued in run queue event for unknown thread: %ld\n",
827 				event->thread);
828 			return;
829 		}
830 
831 		if (thread->state == RUNNING || thread->state == STILL_RUNNING) {
832 			// Thread was running and is reentered into the run queue. This
833 			// is done by the scheduler, if the thread remains ready.
834 			thread->state = STILL_RUNNING;
835 		} else {
836 			// Thread was waiting and is ready now.
837 			bigtime_t diffTime = fState.LastEventTime() - thread->lastTime;
838 			if (thread->waitObject != NULL) {
839 				thread->waitObject->AddWait(diffTime);
840 				thread->waitObject = NULL;
841 			}
842 
843 			fSchedulingData.AddLatency(thread->thread, fState.LastEventTime());
844 			thread->lastTime = fState.LastEventTime();
845 			thread->state = READY;
846 		}
847 	}
848 
849 	void _HandleThreadRemovedFromRunQueue(thread_removed_from_run_queue* event)
850 	{
851 		_UpdateLastEventTime(event->time);
852 
853 		Model::ThreadSchedulingState* thread = fState.LookupThread(
854 			event->thread);
855 		if (thread == NULL) {
856 			printf("Removed from run queue event for unknown thread: %ld\n",
857 				event->thread);
858 			return;
859 		}
860 
861 		// This really only happens when the thread priority is changed
862 		// while the thread is ready.
863 		fSchedulingData.AddUnspecifiedWait(thread->thread,
864 			fState.LastEventTime());
865 
866 		thread->lastTime = fState.LastEventTime();
867 		thread->state = WAITING;
868 	}
869 
870 private:
871 	Model::SchedulingState	fState;
872 	SchedulingData			fSchedulingData;
873 	bigtime_t				fStartTime;
874 	bigtime_t				fEndTime;
875 	uint32					fUSecsPerPixel;
876 	BPoint					fLastMousePos;
877 	Listener*				fListener;
878 };
879 
880 
881 class MainWindow::SchedulingPage::ViewPort : public BaseView,
882 	private HeaderListener, private SchedulingView::Listener {
883 public:
884 	ViewPort(ThreadsView* threadsView, SchedulingView* schedulingView,
885 		FontInfo& fontInfo)
886 		:
887 		BaseView("viewport", fontInfo),
888 		fHeaderView(NULL),
889 		fThreadsView(threadsView),
890 		fSchedulingView(schedulingView)
891 	{
892 		AddChild(fHeaderView = new HeaderView);
893 		AddChild(threadsView);
894 		AddChild(schedulingView);
895 
896 		fSchedulingView->SetListener(this);
897 
898 		HeaderModel* headerModel = fHeaderView->Model();
899 
900 		Header* header = new Header(100, 100, 10000, 200, 0);
901 		header->SetValue("Thread");
902 		headerModel->AddHeader(header);
903 		header->AddListener(this);
904 
905 		header = new Header(100, 100, 10000, 200, 1);
906 			// TODO: Set header width correctly!
907 		header->SetValue("Activity");
908 		header->SetHeaderRenderer(new TimelineHeaderRenderer);
909 		headerModel->AddHeader(header);
910 //		header->AddListener(this);
911 	}
912 
913 	~ViewPort()
914 	{
915 		fHeaderView->Model()->HeaderAt(0)->RemoveListener(this);
916 //		fHeaderView->Model()->HeaderAt(1)->RemoveListener(this);
917 	}
918 
919 	void TargetedByScrollView(BScrollView* scrollView)
920 	{
921 		_UpdateScrollBars();
922 	}
923 
924 	virtual BSize MinSize()
925 	{
926 		return BSize(10, fHeaderView->MinSize().Height());
927 	}
928 
929 	virtual BSize MaxSize()
930 	{
931 		return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
932 	}
933 
934 	BSize PreferredSize()
935 	{
936 		BSize headerViewSize(fHeaderView->PreferredSize());
937 		BSize threadsViewSize(fThreadsView->PreferredSize());
938 		BSize schedulingViewSize(fSchedulingView->PreferredSize());
939 		BSize size(BLayoutUtils::AddDistances(
940 				threadsViewSize.width + kViewSeparationMargin,
941 				schedulingViewSize.width),
942 			std::max(threadsViewSize.height, schedulingViewSize.height));
943 
944 		return BSize(std::max(size.width, headerViewSize.width),
945 			BLayoutUtils::AddDistances(size.height, headerViewSize.height));
946 	}
947 
948 	void DoLayout()
949 	{
950 		float headerHeight = fHeaderView->MinSize().Height();
951 		float width = Bounds().Width();
952 		float height = fThreadsView->MinSize().Height();
953 		float threadsViewWidth = fThreadsView->MinSize().width;
954 		float schedulingViewLeft = threadsViewWidth + 1 + kViewSeparationMargin;
955 		float schedulingViewWidth = width - schedulingViewLeft;
956 
957 		fHeaderView->MoveTo(0, 0);
958 		fHeaderView->ResizeTo(width, headerHeight);
959 
960 		fThreadsView->MoveTo(0, headerHeight + 1);
961 		fThreadsView->ResizeTo(threadsViewWidth, height);
962 
963 		fSchedulingView->MoveTo(schedulingViewLeft, headerHeight + 1);
964 		fSchedulingView->ResizeTo(schedulingViewWidth, height);
965 
966 //		if (Header* header = fHeaderView->Model()->HeaderAt(0)) {
967 //		}
968 
969 		if (Header* header = fHeaderView->Model()->HeaderAt(1)) {
970 			float headerWidth = schedulingViewWidth + 1 + kViewSeparationMargin;
971 			header->SetMinWidth(headerWidth);
972 			header->SetMaxWidth(headerWidth);
973 			header->SetPreferredWidth(headerWidth);
974 			header->SetWidth(headerWidth);
975 		}
976 
977 		_UpdateScrollBars();
978 	}
979 
980 private:
981 	virtual void HeaderWidthChanged(Header* header)
982 	{
983 		if (header->ModelIndex() != 0)
984 			return;
985 
986 		// first column changed
987 	}
988 
989 	virtual void DataWidthChanged()
990 	{
991 	}
992 
993 	virtual void DataRangeChanged()
994 	{
995 		Header* header = fHeaderView->Model()->HeaderAt(1);
996 		if (header == NULL)
997 			return;
998 
999 		bigtime_t startTime;
1000 		bigtime_t endTime;
1001 		fSchedulingView->GetDataRange(startTime, endTime);
1002 		TimeRange* range = new(std::nothrow) TimeRange(startTime, endTime);
1003 		if (range != NULL) {
1004 			header->SetValue(BVariant(range, 'time'));
1005 			range->ReleaseReference();
1006 		}
1007 	}
1008 
1009 	void _UpdateScrollBars()
1010 	{
1011 		float height = Frame().Height();
1012 		float dataHeight = std::max(height, fSchedulingView->MinSize().height);
1013 
1014 		fSchedulingView->UpdateScrollBar();
1015 
1016 		if (BScrollBar* scrollBar = ScrollBar(B_VERTICAL)) {
1017 			float range = dataHeight - height;
1018 			if (range > 0) {
1019 				scrollBar->SetRange(0, range);
1020 				scrollBar->SetProportion(
1021 					(height + 1) / (dataHeight + 1));
1022 				scrollBar->SetSteps(fFontInfo.lineHeight, height + 1);
1023 			} else {
1024 				scrollBar->SetRange(0, 0);
1025 				scrollBar->SetProportion(1);
1026 			}
1027 		}
1028 	}
1029 
1030 private:
1031 	HeaderView*		fHeaderView;
1032 	ThreadsView*	fThreadsView;
1033 	SchedulingView*	fSchedulingView;
1034 };
1035 
1036 
1037 MainWindow::SchedulingPage::SchedulingPage(MainWindow* parent)
1038 	:
1039 	BGroupView(B_VERTICAL),
1040 	fParent(parent),
1041 	fModel(NULL),
1042 	fScrollView(NULL),
1043 	fViewPort(NULL),
1044 	fThreadsView(NULL),
1045 	fSchedulingView(NULL)
1046 {
1047 	SetName("Scheduling");
1048 
1049 	be_plain_font->GetHeight(&fFontInfo.fontHeight);
1050 	fFontInfo.lineHeight = ceilf(fFontInfo.fontHeight.ascent)
1051 		+ ceilf(fFontInfo.fontHeight.descent);
1052 
1053 	fViewPort = new ViewPort(fThreadsView = new ThreadsView(fFontInfo),
1054 		fSchedulingView = new SchedulingView(fFontInfo), fFontInfo);
1055 
1056 	AddChild(fScrollView = new BScrollView("scroll", fViewPort, 0, true, true));
1057 
1058 	fScrollView->ScrollBar(B_HORIZONTAL)->SetTarget(fSchedulingView);
1059 }
1060 
1061 
1062 MainWindow::SchedulingPage::~SchedulingPage()
1063 {
1064 }
1065 
1066 
1067 void
1068 MainWindow::SchedulingPage::SetModel(Model* model)
1069 {
1070 	if (model == fModel)
1071 		return;
1072 
1073 	if (fModel != NULL) {
1074 	}
1075 
1076 	fModel = model;
1077 
1078 	if (fModel != NULL) {
1079 	}
1080 
1081 	fViewPort->SetModel(fModel);
1082 	fThreadsView->SetModel(fModel);
1083 	fSchedulingView->SetModel(fModel);
1084 }
1085