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 <MenuItem.h>
17#include <PopUpMenu.h>
18#include <ScrollView.h>
19#include <SplitView.h>
20#include <ToolTip.h>
21
22#include <DebugEventStream.h>
23
24#include <thread_defs.h>
25
26#include "Array.h"
27
28#include "chart/NanotimeChartAxisLegendSource.h"
29#include "chart/LegendChartAxis.h"
30#include "chart/StringChartLegend.h"
31#include "HeaderView.h"
32#include "Model.h"
33#include "util/TimeUtils.h"
34
35
36static const float kThreadNameMargin = 3.0f;
37static const float kViewSeparationMargin = 1.0f;
38
39
40enum {
41	MSG_SCHEDULING_FILTER_HIDE_SELECTED		= 'schs',
42	MSG_SCHEDULING_FILTER_HIDE_UNSELECTED	= 'schu',
43	MSG_SCHEDULING_FILTER_SHOW_ALL			= 'scsa'
44};
45
46
47enum {
48	IO_SCHEDULING_STATE_IDLE,
49	IO_SCHEDULING_STATE_PENDING_REQUEST,
50	IO_SCHEDULING_STATE_PENDING_OPERATION
51};
52
53
54struct MainWindow::SchedulingPage::SchedulingEvent {
55	nanotime_t						time;
56	Model::ThreadWaitObjectGroup*	waitObject;
57	ThreadState						state;
58
59	SchedulingEvent(nanotime_t time, ThreadState state,
60		Model::ThreadWaitObjectGroup* waitObject)
61		:
62		time(time),
63		waitObject(waitObject),
64		state(state)
65	{
66	}
67};
68
69
70struct MainWindow::SchedulingPage::IOSchedulingEvent {
71	nanotime_t	time;
72	uint32		state;
73
74	IOSchedulingEvent(nanotime_t time, uint32 state)
75		:
76		time(time),
77		state(state)
78	{
79	}
80};
81
82
83class MainWindow::SchedulingPage::SchedulingData {
84public:
85	SchedulingData()
86		:
87		fModel(NULL),
88		fDataArrays(NULL),
89		fIODataArrays(NULL),
90		fRecordingEnabled(false)
91	{
92	}
93
94	status_t InitCheck() const
95	{
96		return fDataArrays != NULL && fIODataArrays != NULL
97			? B_OK : B_NO_MEMORY;
98	}
99
100	void SetModel(Model* model)
101	{
102		delete[] fDataArrays;
103		delete[] fIODataArrays;
104
105		fModel = model;
106		fDataArrays = NULL;
107		fIODataArrays = NULL;
108
109		if (fModel != NULL) {
110			int32 threadCount = fModel->CountThreads();
111			fDataArrays = new(std::nothrow) DataArray[threadCount];
112			fIODataArrays = new(std::nothrow) IODataArray[threadCount];
113		}
114	}
115
116	void SetRecordingEnabled(bool enabled)
117	{
118		fRecordingEnabled = enabled;
119	}
120
121	void Clear()
122	{
123		int32 count = fModel->CountThreads();
124
125		if (fDataArrays != NULL) {
126			for (int32 i = 0; i < count; i++)
127				fDataArrays[i].Clear();
128		}
129
130		if (fIODataArrays != NULL) {
131			for (int32 i = 0; i < count; i++)
132				fIODataArrays[i].Clear();
133		}
134	}
135
136	const Array<SchedulingEvent>& EventsForThread(int32 index)
137	{
138		return fDataArrays[index];
139	}
140
141	const Array<IOSchedulingEvent>& IOEventsForThread(int32 index)
142	{
143		return fIODataArrays[index];
144	}
145
146	void AddState(Model::Thread* thread, nanotime_t time, ThreadState state,
147		Model::ThreadWaitObjectGroup* waitObject)
148	{
149		DataArray& array = fDataArrays[thread->Index()];
150		if (!array.IsEmpty()) {
151			SchedulingEvent& lastEvent = array[array.Size() - 1];
152			if (fRecordingEnabled) {
153				if (lastEvent.state == state
154					&& lastEvent.waitObject == waitObject) {
155					return;
156				}
157			} else {
158				// recording not enabled yet -- overwrite the last event
159				lastEvent = SchedulingEvent(time, state, waitObject);
160				return;
161			}
162		}
163
164		SchedulingEvent event(time, state, waitObject);
165		array.Add(event);
166	}
167
168	void AddIOState(Model::Thread* thread, nanotime_t time, uint32 state)
169	{
170		IODataArray& array = fIODataArrays[thread->Index()];
171		array.Add(IOSchedulingEvent(time, state));
172	}
173
174	void AddRun(Model::Thread* thread, nanotime_t time)
175	{
176		AddState(thread, time, RUNNING, NULL);
177	}
178
179	void AddLatency(Model::Thread* thread, nanotime_t time)
180	{
181		AddState(thread, time, READY, NULL);
182	}
183
184	void AddPreemption(Model::Thread* thread, nanotime_t time)
185	{
186		AddState(thread, time, PREEMPTED, NULL);
187	}
188
189	void AddWait(Model::Thread* thread, nanotime_t time,
190		Model::ThreadWaitObjectGroup* waitObject)
191	{
192		AddState(thread, time, WAITING, waitObject);
193	}
194
195	void AddUnspecifiedWait(Model::Thread* thread, nanotime_t time)
196	{
197		AddState(thread, time, WAITING, NULL);
198	}
199
200private:
201	typedef Array<SchedulingEvent> DataArray;
202	typedef Array<IOSchedulingEvent> IODataArray;
203
204private:
205	Model*			fModel;
206	DataArray*		fDataArrays;
207	IODataArray*	fIODataArrays;
208	bool			fRecordingEnabled;
209};
210
211
212struct MainWindow::SchedulingPage::TimeRange : BReferenceable {
213	nanotime_t	startTime;
214	nanotime_t	endTime;
215
216	TimeRange(nanotime_t startTime, nanotime_t endTime)
217		:
218		startTime(startTime),
219		endTime(endTime)
220	{
221	}
222};
223
224
225class MainWindow::SchedulingPage::TimelineHeaderRenderer
226	: public HeaderRenderer {
227public:
228	TimelineHeaderRenderer()
229		:
230		fAxis(new NanotimeChartAxisLegendSource, new StringChartLegendRenderer)
231	{
232		fAxis.SetLocation(CHART_AXIS_TOP);
233	}
234
235	virtual float HeaderHeight(BView* view, const Header* header)
236	{
237		_SetupAxis(view, header);
238		return fAxis.PreferredSize(view, view->Frame().Size()).height;
239	}
240
241	virtual float PreferredHeaderWidth(BView* view, const Header* header)
242	{
243		_SetupAxis(view, header);
244		return fAxis.PreferredSize(view, view->Frame().Size()).width;
245	}
246
247	virtual void DrawHeader(BView* view, BRect frame, BRect updateRect,
248		const Header* header, uint32 flags)
249	{
250		_SetupAxis(view, header);
251		fAxis.SetFrame(frame);
252		DrawHeaderBackground(view, frame, updateRect, flags);
253		fAxis.Render(view, updateRect);
254	}
255
256private:
257	void _SetupAxis(BView* view, const Header* header)
258	{
259		BVariant value;
260		if (header->GetValue(value)) {
261			TimeRange* timeRange = dynamic_cast<TimeRange*>(
262				value.ToReferenceable());
263			if (timeRange != NULL) {
264				ChartDataRange range = ChartDataRange(timeRange->startTime,
265					timeRange->endTime);
266				if (range != fRange) {
267					fAxis.SetRange(range);
268					fRange = range;
269				}
270			}
271		}
272	}
273
274private:
275	LegendChartAxis		fAxis;
276	ChartDataRange		fRange;
277};
278
279
280class MainWindow::SchedulingPage::BaseView : public BView {
281public:
282	BaseView(const char* name, FontInfo& fontInfo,
283		ListSelectionModel* filterModel, uint32 flags = 0)
284		:
285		BView(name, flags),
286		fModel(NULL),
287		fFilterModel(filterModel),
288		fFontInfo(fontInfo)
289	{
290	}
291
292	virtual void SetModel(Model* model)
293	{
294		fModel = model;
295
296		InvalidateLayout();
297	}
298
299protected:
300	int32 CountLines() const
301	{
302		return fFilterModel->CountSelectedItems();
303	}
304
305	float TotalHeight() const
306	{
307		return fFontInfo.lineHeight * CountLines();
308	}
309
310	int32 LineAt(BPoint point) const
311	{
312		int32 line = (int32)point.y / (int32)fFontInfo.lineHeight;
313		return line < CountLines() ? line : -1;
314	}
315
316	void GetLineRange(BRect rect, int32& minLine, int32& maxLine) const
317	{
318		int32 lineHeight = (int32)fFontInfo.lineHeight;
319		minLine = (int32)rect.top / lineHeight;
320		maxLine = ((int32)ceilf(rect.bottom) + lineHeight - 1) / lineHeight;
321		minLine = std::max(minLine, (int32)0);
322		maxLine = std::min(maxLine, CountLines() - 1);
323	}
324
325	BRect LineRect(uint32 line) const
326	{
327		float y = (float)line * fFontInfo.lineHeight;
328		return BRect(0, y, Bounds().right, y + fFontInfo.lineHeight - 1);
329	}
330
331protected:
332	Model*				fModel;
333	ListSelectionModel*	fFilterModel;
334	FontInfo&			fFontInfo;
335};
336
337
338class MainWindow::SchedulingPage::LineBaseView : public BaseView,
339	protected ListSelectionModel::Listener {
340public:
341	LineBaseView(const char* name, FontInfo& fontInfo,
342		ListSelectionModel* filterModel, ListSelectionModel* selectionModel)
343		:
344		BaseView(name, fontInfo, filterModel, B_WILL_DRAW),
345		fSelectionModel(selectionModel),
346		fTextColor(ui_color(B_DOCUMENT_TEXT_COLOR)),
347		fSelectedTextColor(fTextColor),
348		fBackgroundColor(ui_color(B_DOCUMENT_BACKGROUND_COLOR)),
349		fSelectedBackgroundColor(tint_color(fBackgroundColor, B_DARKEN_2_TINT))
350	{
351		fFilterModel->AddListener(this);
352		fSelectionModel->AddListener(this);
353	}
354
355	void RemoveListeners()
356	{
357		fSelectionModel->RemoveListener(this);
358		fFilterModel->RemoveListener(this);
359	}
360
361	virtual void MessageReceived(BMessage* message)
362	{
363		switch (message->what) {
364			case MSG_SCHEDULING_FILTER_HIDE_SELECTED:
365			{
366				int32 filteredCount = fFilterModel->CountSelectedItems();
367				int32 selectedCount = fSelectionModel->CountSelectedItems();
368				if (selectedCount == 0 || selectedCount == filteredCount)
369					break;
370
371				// Set the filter model to the unselected items.
372				ListSelectionModel tempModel;
373				for (int32 i = 0; i < filteredCount; i++) {
374					if (!fSelectionModel->IsItemSelected(i)) {
375						tempModel.SelectItem(fFilterModel->SelectedItemAt(i),
376							true);
377					}
378				}
379
380				fSelectionModel->Clear();
381				*fFilterModel = tempModel;
382				Invalidate();
383				break;
384			}
385
386			case MSG_SCHEDULING_FILTER_HIDE_UNSELECTED:
387			{
388				int32 filteredCount = fFilterModel->CountSelectedItems();
389				int32 selectedCount = fSelectionModel->CountSelectedItems();
390				if (selectedCount == 0 || selectedCount == filteredCount)
391					break;
392
393				// Set the filter model to the selected items.
394				// There might already be a filter applied, so we have to
395				// build the new filter model manually.
396				ListSelectionModel tempModel;
397				for (int32 i = 0; i < selectedCount; i++) {
398					int32 index = fFilterModel->SelectedItemAt(
399						fSelectionModel->SelectedItemAt(i));
400					if (index >= 0)
401						tempModel.SelectItem(index, true);
402				}
403
404				fSelectionModel->Clear();
405				*fFilterModel = tempModel;
406				Invalidate();
407				break;
408			}
409
410			case MSG_SCHEDULING_FILTER_SHOW_ALL:
411			{
412				int32 threadCount = fModel->CountThreads();
413				if (fFilterModel->CountSelectedItems() == threadCount)
414					break;
415
416				// unset the filter
417				ListSelectionModel tempModel = *fFilterModel;
418				fSelectionModel->Clear();
419				fFilterModel->SelectItems(0, threadCount, false);
420				*fSelectionModel = tempModel;
421				Invalidate();
422				break;
423			}
424
425			default:
426				BaseView::MessageReceived(message);
427				break;
428		}
429	}
430
431	virtual void MouseDown(BPoint where)
432	{
433		// get buttons and modifiers
434		BMessage* message = Looper()->CurrentMessage();
435		if (message == NULL)
436			return;
437
438		int32 buttons;
439		if (message->FindInt32("buttons", &buttons) != B_OK)
440			return;
441
442		int32 modifiers;
443		if (message->FindInt32("modifiers", &modifiers) != B_OK)
444			modifiers = 0;
445
446		// update selection
447		int32 line = LineAt(where);
448		if (line >= 0) {
449			if ((modifiers & B_SHIFT_KEY) != 0) {
450				int32 selectedLines = fSelectionModel->CountSelectedItems();
451				if (selectedLines > 0) {
452					int32 firstLine = fSelectionModel->SelectedItemAt(0);
453					int32 lastLine = fSelectionModel->SelectedItemAt(
454						selectedLines - 1);
455					firstLine = std::min(firstLine, line);
456					lastLine = std::max(lastLine, line);
457					fSelectionModel->SelectItems(firstLine,
458						lastLine - firstLine + 1, false);
459				} else
460					fSelectionModel->SelectItem(line, true);
461			} else {
462				if (fSelectionModel->IsItemSelected(line)) {
463					if ((modifiers & B_COMMAND_KEY) != 0)
464						fSelectionModel->DeselectItem(line);
465				} else {
466					fSelectionModel->SelectItem(line,
467						(modifiers & B_COMMAND_KEY) != 0);
468				}
469			}
470		} else
471			fSelectionModel->Clear();
472
473		// on right mouse button open context menu
474		if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0 && fModel != NULL) {
475			BPopUpMenu* contextMenu = new BPopUpMenu("scheduling context menu",
476				false, false);
477
478			int32 filteredCount = fFilterModel->CountSelectedItems();
479			int32 selectedCount = fSelectionModel->CountSelectedItems();
480
481			BMenuItem* item = new BMenuItem("Hide selected threads",
482				new BMessage(MSG_SCHEDULING_FILTER_HIDE_SELECTED));
483			contextMenu->AddItem(item);
484			item->SetTarget(this);
485			if (selectedCount == 0 || selectedCount == filteredCount)
486				item->SetEnabled(false);
487
488			item = new BMenuItem("Hide unselected threads",
489				new BMessage(MSG_SCHEDULING_FILTER_HIDE_UNSELECTED));
490			contextMenu->AddItem(item);
491			item->SetTarget(this);
492			if (selectedCount == 0 || selectedCount == filteredCount)
493				item->SetEnabled(false);
494
495			item = new BMenuItem("Show all threads",
496				new BMessage(MSG_SCHEDULING_FILTER_SHOW_ALL));
497			contextMenu->AddItem(item);
498			item->SetTarget(this);
499			if (filteredCount == fModel->CountThreads())
500				item->SetEnabled(false);
501
502			BPoint screenWhere = ConvertToScreen(where);
503			BRect mouseRect(screenWhere, screenWhere);
504			mouseRect.InsetBy(-4.0, -4.0);
505			contextMenu->Go(screenWhere, true, false, mouseRect, true);
506		}
507	}
508
509	virtual void ItemsSelected(ListSelectionModel* model, int32 index,
510		int32 count)
511	{
512		if (model == fFilterModel)
513			Invalidate();
514		else
515			InvalidateLines(index, count);
516	}
517
518	virtual void ItemsDeselected(ListSelectionModel* model, int32 index,
519		int32 count)
520	{
521		if (model == fFilterModel)
522			Invalidate();
523		else
524			InvalidateLines(index, count);
525	}
526
527	void InvalidateLines(int32 index, int32 count)
528	{
529		float top = (float)index * fFontInfo.lineHeight;
530		float bottom = (float)(index + count) * fFontInfo.lineHeight - 1;
531		BRect bounds(Bounds());
532		Invalidate(BRect(bounds.left, top, bounds.right, bottom));
533	}
534
535protected:
536	ListSelectionModel*		fSelectionModel;
537	rgb_color				fTextColor;
538	rgb_color				fSelectedTextColor;
539	rgb_color				fBackgroundColor;
540	rgb_color				fSelectedBackgroundColor;
541};
542
543
544class MainWindow::SchedulingPage::ThreadsView : public LineBaseView {
545public:
546	ThreadsView(FontInfo& fontInfo, ListSelectionModel* filterModel,
547		ListSelectionModel* selectionModel)
548		:
549		LineBaseView("threads", fontInfo, filterModel, selectionModel)
550	{
551		SetViewColor(B_TRANSPARENT_32_BIT);
552	}
553
554	~ThreadsView()
555	{
556	}
557
558	virtual void Draw(BRect updateRect)
559	{
560		if (fModel == NULL)
561			return;
562
563		// get the lines intersecting with the update rect
564		int32 minLine, maxLine;
565		GetLineRange(updateRect, minLine, maxLine);
566
567		for (int32 i = minLine; i <= maxLine; i++) {
568			// draw the line background
569			BRect lineRect = LineRect(i);
570			if (fSelectionModel->IsItemSelected(i)) {
571				SetLowColor(fSelectedBackgroundColor);
572				SetHighColor(fTextColor);
573			} else {
574				SetLowColor(fBackgroundColor);
575				SetHighColor(fSelectedTextColor);
576			}
577			FillRect(lineRect, B_SOLID_LOW);
578
579			Model::Thread* thread = fModel->ThreadAt(
580				fFilterModel->SelectedItemAt(i));
581			if (thread == NULL)
582				continue;
583
584			// compose name string
585			BString name = thread->Name();
586			name << " (" << thread->ID() << ")";
587
588			// draw the string
589			float y = lineRect.bottom - fFontInfo.fontHeight.descent + 1;
590			DrawString(name, BPoint(kThreadNameMargin, y));
591		}
592
593		BRect bounds(Bounds());
594		BRect lowerRect(0, (float)CountLines() * fFontInfo.lineHeight,
595			bounds.Width(), bounds.Height());
596		if (lowerRect.Intersects(updateRect)) {
597			SetHighColor(fBackgroundColor);
598			FillRect(lowerRect);
599		}
600	}
601
602	virtual BSize MinSize()
603	{
604		return BSize(100, TotalHeight());
605	}
606
607	virtual BSize PreferredSize()
608	{
609		return BSize(250, TotalHeight());
610	}
611
612	virtual BSize MaxSize()
613	{
614		return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
615	}
616};
617
618
619class MainWindow::SchedulingPage::SchedulingView : public LineBaseView {
620public:
621	struct Listener {
622		virtual ~Listener()
623		{
624		}
625
626		virtual void DataWidthChanged() = 0;
627		virtual void DataRangeChanged() = 0;
628	};
629
630private:
631	enum {
632		COLOR_RUNNING = 0,
633		COLOR_PREEMPTED,
634		COLOR_READY,
635		COLOR_IO_REQUEST,
636		COLOR_IO_OPERATION,
637		ACTIVITY_COLOR_COUNT
638	};
639
640public:
641	SchedulingView(FontInfo& fontInfo, ListSelectionModel* filterModel,
642		ListSelectionModel* selectionModel)
643		:
644		LineBaseView("scheduling", fontInfo, filterModel, selectionModel),
645		fStartTime(0),
646		fEndTime(0),
647		fNSecsPerPixel(1000000),
648		fLastMousePos(-1, -1),
649		fListener(NULL)
650	{
651		fActivityColors[COLOR_RUNNING].set_to(0, 255, 0);
652		fActivityColors[COLOR_PREEMPTED].set_to(255, 127, 0);
653		fActivityColors[COLOR_READY].set_to(255, 0, 0);
654		fActivityColors[COLOR_IO_REQUEST].set_to(127, 127, 255);
655		fActivityColors[COLOR_IO_OPERATION].set_to(0, 0, 200);
656
657		fActivitySelectedColors[COLOR_RUNNING] = tint_color(
658			fActivityColors[COLOR_RUNNING], B_DARKEN_2_TINT);
659		fActivitySelectedColors[COLOR_PREEMPTED] = tint_color(
660			fActivityColors[COLOR_PREEMPTED], B_DARKEN_2_TINT);
661		fActivitySelectedColors[COLOR_READY] = tint_color(
662			fActivityColors[COLOR_READY], B_DARKEN_2_TINT);
663		fActivitySelectedColors[COLOR_IO_REQUEST] = tint_color(
664			fActivityColors[COLOR_IO_REQUEST], B_DARKEN_2_TINT);
665		fActivitySelectedColors[COLOR_IO_OPERATION] = tint_color(
666			fActivityColors[COLOR_IO_OPERATION], B_DARKEN_2_TINT);
667	}
668
669	~SchedulingView()
670	{
671	}
672
673	virtual void SetModel(Model* model)
674	{
675		LineBaseView::SetModel(model);
676		fSchedulingData.SetModel(model);
677		fStartTime = 0;
678		fEndTime = 0;
679
680		if (fListener != NULL) {
681			fListener->DataWidthChanged();
682			fListener->DataRangeChanged();
683		}
684
685	}
686
687	void SetListener(Listener* listener)
688	{
689		fListener = listener;
690	}
691
692	void UpdateScrollBar()
693	{
694		float width = Frame().Width();
695		float dataWidth = std::max(width, MinSize().width);
696
697		if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL)) {
698			float range = dataWidth - width;
699			if (range > 0) {
700				scrollBar->SetRange(0, range);
701				scrollBar->SetProportion((width + 1) / (dataWidth + 1));
702				scrollBar->SetSteps(fFontInfo.lineHeight, width + 1);
703			} else {
704				scrollBar->SetRange(0, 0);
705				scrollBar->SetProportion(1);
706			}
707		}
708	}
709
710	void GetDataRange(nanotime_t& _startTime, nanotime_t& _endTime)
711	{
712		_GetEventTimeRange(_startTime, _endTime);
713	}
714
715	virtual BSize MinSize()
716	{
717		nanotime_t timeSpan = fModel != NULL ? fModel->LastEventTime() : 0;
718		float width = std::max(float(timeSpan / fNSecsPerPixel), 100.0f);
719		return BSize(width, TotalHeight());
720	}
721
722	virtual BSize MaxSize()
723	{
724		return BSize(MinSize().width, B_SIZE_UNLIMITED);
725	}
726
727	virtual void ScrollTo(BPoint where)
728	{
729		LineBaseView::ScrollTo(where);
730		fStartTime = 0;
731		fEndTime = 0;
732
733		if (fListener != NULL)
734			fListener->DataRangeChanged();
735	}
736
737	virtual void MessageReceived(BMessage* message)
738	{
739		switch (message->what) {
740			case B_MOUSE_WHEEL_CHANGED:
741			{
742				// We're only interested in Shift + vertical wheel.
743				float deltaY;
744				if ((modifiers() & B_SHIFT_KEY) == 0
745					|| message->FindFloat("be:wheel_delta_y", &deltaY)
746						!= B_OK) {
747					break;
748				}
749
750				_Zoom(fLastMousePos.x, deltaY);
751
752				return;
753			}
754		}
755
756		LineBaseView::MessageReceived(message);
757	}
758
759	virtual void MouseMoved(BPoint where, uint32 code,
760		const BMessage* dragMessage)
761	{
762		fLastMousePos = where - LeftTop();
763
764//		if (fDraggingStartPos.x < 0)
765//			return;
766//
767//		ScrollBar(B_HORIZONTAL)->SetValue(fDraggingStartScrollValue
768//			+ fDraggingStartPos.x - where.x);
769	}
770
771	virtual bool GetToolTipAt(BPoint point, BToolTip** _tip)
772	{
773		Model::Thread* thread;
774		nanotime_t time;
775		_GetThreadAndTimeAt(point, thread, time);
776		if (thread == NULL)
777			return false;
778
779		// get the thread's state
780		ThreadState threadState = UNKNOWN;
781		nanotime_t threadStateTime = time;
782		nanotime_t threadStateEndTime = time;
783		Model::ThreadWaitObjectGroup* threadWaitObject = NULL;
784		{
785			const Array<SchedulingEvent>& events
786				= fSchedulingData.EventsForThread(thread->Index());
787			int32 eventCount = events.Size();
788
789			int32 lower = 0;
790			int32 upper = eventCount - 1;
791			while (lower < upper) {
792				int32 mid = (lower + upper + 1) / 2;
793				const SchedulingEvent& event = events[mid];
794				if (event.time > time)
795					upper = mid - 1;
796				else
797					lower = mid;
798			}
799
800			if (lower >= 0 && lower < eventCount) {
801				threadState = events[lower].state;
802				threadStateTime = events[lower].time;
803				threadWaitObject = events[lower].waitObject;
804				if (lower + 1 < eventCount)
805					threadStateEndTime = events[lower + 1].time;
806				else
807					threadStateEndTime = fModel->LastEventTime();
808			}
809		}
810
811		// get the thread's I/O state
812		BObjectList<Model::IORequest> ioRequests;
813		_GetIORequests(thread, time, ioRequests);
814
815		// find out which threads are running
816		system_profiler_event_header** events = fModel->Events();
817		size_t eventIndex = fModel->ClosestEventIndex(time + 1);
818			// find the first event after the one we're interested in; we'll
819			// skip it in the loop
820
821		int32 cpuCount = fModel->CountCPUs();
822		int32 missingThreads = cpuCount;
823		Model::Thread* runningThreads[cpuCount];
824		memset(runningThreads, 0, sizeof(Model::Thread*) * cpuCount);
825
826		while (missingThreads > 0 && eventIndex > 0) {
827			eventIndex--;
828			system_profiler_event_header* header = events[eventIndex];
829			if (header->event != B_SYSTEM_PROFILER_THREAD_SCHEDULED
830				|| runningThreads[header->cpu] != NULL) {
831				continue;
832			}
833
834			system_profiler_thread_scheduled* event
835				= (system_profiler_thread_scheduled*)(header + 1);
836			if (Model::Thread* thread = fModel->ThreadByID(event->thread)) {
837				runningThreads[header->cpu] = thread;
838				missingThreads--;
839			}
840		}
841
842		// create the tool tip
843		BString text;
844		text << "Time: " << format_nanotime(time) << "\n";
845		text << "Thread: " << thread->Name() << " (" << thread->ID() << ")\n";
846		text << "State: " << thread_state_name(threadState);
847		if (threadWaitObject != NULL) {
848			char objectName[32];
849			snprintf(objectName, sizeof(objectName), "%#lx",
850				threadWaitObject->Object());
851			text << " at " << wait_object_type_name(threadWaitObject->Type())
852				<< " \"" << threadWaitObject->Name() << "\" "
853				<< objectName << "\n";
854		} else
855			text << "\n";
856		text << "For: " << format_nanotime(time - threadStateTime) << " of "
857			<< format_nanotime(threadStateEndTime - threadStateTime);
858
859		if (!ioRequests.IsEmpty()) {
860			int32 scheduler = 0;
861			for (int32 i = 0; Model::IORequest* request = ioRequests.ItemAt(i);
862					i++) {
863				if (i == 0 || scheduler != request->Scheduler()) {
864					scheduler = request->Scheduler();
865					text << "\nI/O channel " << scheduler << ":";
866				}
867				text << "\n  " << (request->IsWrite() ? "write" : "read")
868					<< " request: " << request->BytesTransferred() << "/"
869					<< request->Length() << " ";
870				if (request->IsFinished()) {
871					text << (request->Status() == B_OK ? "ok" : "failed");
872					text << ", "
873						<< format_nanotime(
874							request->FinishedTime() - request->ScheduledTime());
875				} else
876					text << "unfinished";
877
878				for (size_t k = 0; k < request->operationCount; k++) {
879					Model::IOOperation& operation = request->operations[k];
880					if (operation.startedEvent->time > fModel->BaseTime() + time
881						|| (operation.IsFinished()
882							&& operation.FinishedTime()
883								<= fModel->BaseTime() + time)) {
884						continue;
885					}
886
887					text << "\n    " << (operation.IsWrite() ? "write" : "read")
888						<< ": " << operation.BytesTransferred() << "/"
889						<< operation.Length() << " ";
890					if (operation.IsFinished()) {
891						text << (request->Status() == B_OK ? "ok" : "failed");
892						text << ", "
893							<< format_nanotime(operation.FinishedTime()
894								- operation.StartedTime());
895					} else
896						text << "unfinished";
897				}
898			}
899		}
900
901		text << "\n\n";
902		text << "Running threads:";
903		for (int32 i = 0; i < cpuCount; i++) {
904			text << "\n  " << i << ": ";
905			if (Model::Thread* thread = runningThreads[i])
906				text << thread->Name() << " (" << thread->ID() << ")";
907			else
908				text << "?";
909		}
910
911		*_tip = new(std::nothrow) BTextToolTip(text);
912		return *_tip != NULL;
913	}
914
915	virtual void Draw(BRect updateRect)
916	{
917		if (fModel == NULL || fSchedulingData.InitCheck() != B_OK)
918			return;
919
920		_UpdateData();
921
922		// draw the events
923
924		// get the lines intersecting with the update rect
925		int32 minLine, maxLine;
926		GetLineRange(updateRect, minLine, maxLine);
927
928		for (int32 i = minLine; i <= maxLine; i++) {
929			// draw the background
930			const rgb_color* activityColors;
931			if (fSelectionModel->IsItemSelected(i)) {
932				activityColors = fActivitySelectedColors;
933				SetLowColor(fSelectedBackgroundColor);
934			} else {
935				activityColors = fActivityColors;
936				SetLowColor(fBackgroundColor);
937			}
938			BRect lineRect = LineRect(i);
939			FillRect(lineRect, B_SOLID_LOW);
940
941			Model::Thread* thread = fModel->ThreadAt(
942				fFilterModel->SelectedItemAt(i));
943			if (thread == NULL)
944				continue;
945
946			BRect schedulingLineRect = lineRect;
947			schedulingLineRect.bottom -= lineRect.IntegerHeight() / 2;
948			BRect ioLineRect = lineRect;
949			ioLineRect.top = schedulingLineRect.bottom + 1;
950
951			const Array<SchedulingEvent>& events
952				= fSchedulingData.EventsForThread(thread->Index());
953
954			int32 eventCount = events.Size();
955//printf("drawing events for thread %ld: %ld events\n", thread->Index(), eventCount);
956			for (int32 k = 0; k < eventCount; k++) {
957				const SchedulingEvent& event = events[k];
958				nanotime_t startTime = std::max(event.time, fStartTime);
959				nanotime_t endTime = k + 1 < eventCount
960					? std::min(events[k + 1].time, fEndTime) : fEndTime;
961
962				switch (event.state) {
963					case RUNNING:
964					case STILL_RUNNING:
965						SetHighColor(activityColors[COLOR_RUNNING]);
966						break;
967					case PREEMPTED:
968						SetHighColor(activityColors[COLOR_PREEMPTED]);
969						break;
970					case READY:
971						SetHighColor(activityColors[COLOR_READY]);
972						break;
973					case WAITING:
974					case UNKNOWN:
975					default:
976						continue;
977				}
978
979				BRect rect = schedulingLineRect;
980				rect.left = startTime / fNSecsPerPixel;
981				rect.right = endTime / fNSecsPerPixel - 1;
982				FillRect(rect);
983			}
984
985			const Array<IOSchedulingEvent>& ioEvents
986				= fSchedulingData.IOEventsForThread(thread->Index());
987			eventCount = ioEvents.Size();
988
989			for (int32 k = 0; k < eventCount; k++) {
990				const IOSchedulingEvent& event = ioEvents[k];
991				nanotime_t startTime = std::max(event.time, fStartTime);
992				nanotime_t endTime = k + 1 < eventCount
993					? std::min(ioEvents[k + 1].time, fEndTime) : fEndTime;
994
995				switch (event.state) {
996					case IO_SCHEDULING_STATE_PENDING_REQUEST:
997						SetHighColor(activityColors[COLOR_IO_REQUEST]);
998						break;
999					case IO_SCHEDULING_STATE_PENDING_OPERATION:
1000						SetHighColor(activityColors[COLOR_IO_OPERATION]);
1001						break;
1002					case IO_SCHEDULING_STATE_IDLE:
1003					default:
1004						continue;
1005				}
1006
1007				BRect rect = ioLineRect;
1008				rect.left = startTime / fNSecsPerPixel;
1009				rect.right = endTime / fNSecsPerPixel - 1;
1010				FillRect(rect);
1011			}
1012		}
1013	}
1014
1015private:
1016	// shorthands for the longish structure names
1017	typedef system_profiler_thread_enqueued_in_run_queue
1018		thread_enqueued_in_run_queue;
1019	typedef system_profiler_thread_removed_from_run_queue
1020		thread_removed_from_run_queue;
1021
1022private:
1023	void _UpdateData()
1024	{
1025		// get the interesting event time range
1026		nanotime_t startTime;
1027		nanotime_t endTime;
1028		_GetEventTimeRange(startTime, endTime);
1029
1030		if (startTime == fStartTime && endTime == fEndTime)
1031			return;
1032		fStartTime = startTime;
1033		fEndTime = endTime;
1034
1035		fSchedulingData.Clear();
1036
1037//printf("MainWindow::SchedulingPage::SchedulingView::_UpdateData()\n");
1038//printf("  time range: %lld - %lld\n", startTime, endTime);
1039
1040		// get a scheduling state close to our start time
1041		const Model::CompactSchedulingState* compactState
1042			= fModel->ClosestSchedulingState(startTime);
1043//printf("  compactState: %p\n", compactState);
1044		fState.Clear();
1045		status_t error = fState.Init(compactState);
1046		if (error != B_OK)
1047			return;
1048
1049		// init the event stream
1050		BDebugEventInputStream input;
1051		error = input.SetTo((uint8*)fModel->EventData(),
1052			fModel->EventDataSize(), false);
1053		if (error == B_OK && compactState != NULL)
1054			error = input.Seek(compactState->EventOffset());
1055//printf("  event offset: %lld, input init error: %s\n", compactState != NULL ? compactState->EventOffset() : 0, strerror(error));
1056		if (error != B_OK)
1057			return;
1058
1059		fSchedulingData.SetRecordingEnabled(
1060			fState.LastEventTime() >= startTime);
1061
1062		// add the initial thread states to the scheduling data
1063		if (compactState != NULL) {
1064			int32 threadStateCount = compactState->CountThreadsStates();
1065			for (int32 i = 0; i < threadStateCount; i++) {
1066				const Model::CompactThreadSchedulingState* threadState
1067					= compactState->ThreadStateAt(i);
1068				switch (threadState->state) {
1069					case RUNNING:
1070					case STILL_RUNNING:
1071						fSchedulingData.AddRun(threadState->thread,
1072							threadState->lastTime);
1073						break;
1074					case PREEMPTED:
1075						fSchedulingData.AddPreemption(threadState->thread,
1076							threadState->lastTime);
1077						break;
1078					case READY:
1079						fSchedulingData.AddLatency(threadState->thread,
1080							threadState->lastTime);
1081						break;
1082					case WAITING:
1083					{
1084						Model::ThreadWaitObjectGroup* group = NULL;
1085						if (threadState->waitObject != NULL) {
1086							group = fModel->ThreadWaitObjectGroupFor(
1087								threadState->ID(),
1088								threadState->waitObject->Type(),
1089								threadState->waitObject->Object());
1090						}
1091						fSchedulingData.AddWait(threadState->thread,
1092							threadState->lastTime, group);
1093						break;
1094					}
1095					case UNKNOWN:
1096					default:
1097						break;
1098				}
1099			}
1100		}
1101
1102		// process the events
1103		while (true) {
1104			// get next event
1105			uint32 event;
1106			uint32 cpu;
1107			const void* buffer;
1108			off_t offset;
1109			ssize_t bufferSize = input.ReadNextEvent(&event, &cpu, &buffer,
1110				&offset);
1111			if (bufferSize < 0)
1112{
1113printf("failed to read event!\n");
1114				return;
1115}
1116			if (buffer == NULL)
1117				break;
1118
1119			// process the event
1120			error = _ProcessEvent(event, cpu, buffer, bufferSize);
1121			if (error != B_OK)
1122				return;
1123
1124			if (fState.LastEventTime() >= startTime)
1125				fSchedulingData.SetRecordingEnabled(true);
1126			if (fState.LastEventTime() >= endTime)
1127				break;
1128		}
1129
1130		// process each thread's I/O events
1131		nanotime_t lastEventTime = fModel->BaseTime() - fModel->LastEventTime();
1132		int32 threadCount = fModel->CountThreads();
1133		for (int32 i = 0; i < threadCount; i++) {
1134			Model::Thread* thread = fModel->ThreadAt(i);
1135			Model::IORequest** requests = thread->IORequests();
1136			size_t requestCount = thread->CountIORequests();
1137			if (requestCount == 0)
1138				continue;
1139
1140			// first request
1141			nanotime_t clusterStart = requests[0]->scheduledEvent->time;
1142			nanotime_t clusterEnd = requests[0]->finishedEvent != NULL
1143				? requests[0]->finishedEvent->time : lastEventTime;
1144			BObjectList<Model::IOOperation> operations;
1145
1146			// add first request operations
1147			for (size_t l = 0; l < requests[0]->operationCount; l++)
1148				operations.AddItem(&requests[0]->operations[l]);
1149
1150			for (size_t k = 1; k < requestCount; k++) {
1151				nanotime_t requestStart = requests[k]->scheduledEvent->time;
1152				nanotime_t requestEnd = requests[k]->finishedEvent != NULL
1153					? requests[k]->finishedEvent->time : lastEventTime;
1154
1155				if (requestStart >= endTime)
1156					break;
1157
1158				if (requestStart > clusterEnd) {
1159					if (clusterEnd > startTime) {
1160						_AddThreadIOData(thread, clusterStart, clusterEnd,
1161							operations);
1162					}
1163					operations.MakeEmpty();
1164					clusterStart = requestStart;
1165					clusterEnd = requestEnd;
1166				} else
1167					clusterEnd = std::max(clusterEnd, requestEnd);
1168
1169				// add request operations
1170				for (size_t l = 0; l < requests[k]->operationCount; l++)
1171					operations.AddItem(&requests[k]->operations[l]);
1172			}
1173
1174			if (clusterEnd > startTime)
1175				_AddThreadIOData(thread, clusterStart, clusterEnd, operations);
1176		}
1177	}
1178
1179	void _AddThreadIOData(Model::Thread* thread, nanotime_t startTime,
1180		nanotime_t endTime, BObjectList<Model::IOOperation>& operations)
1181	{
1182//printf("  IORequest cluster: %lld - %lld\n", startTime, endTime);
1183		// start in pending request state
1184		fSchedulingData.AddIOState(thread, startTime - fModel->BaseTime(),
1185			IO_SCHEDULING_STATE_PENDING_REQUEST);
1186
1187		int32 operationCount = operations.CountItems();
1188		if (operationCount == 0)
1189			return;
1190
1191		// sort the operations
1192		if (operationCount > 1)
1193			operations.SortItems(Model::IOOperation::CompareByTime);
1194
1195		nanotime_t lastEventTime = fModel->BaseTime() + fModel->LastEventTime();
1196
1197		// process the operations
1198		Model::IOOperation* operation = operations.ItemAt(0);
1199		nanotime_t clusterStart = operation->startedEvent->time;
1200		nanotime_t clusterEnd = operation->finishedEvent != NULL
1201			? operation->finishedEvent->time : lastEventTime;
1202
1203		for (int32 i = 1; i < operationCount; i++) {
1204			operation = operations.ItemAt(i);
1205			nanotime_t operationStart = operation->startedEvent->time;
1206			nanotime_t operationEnd = operation->finishedEvent != NULL
1207				? operation->finishedEvent->time : lastEventTime;
1208
1209			if (operationStart > clusterEnd) {
1210				fSchedulingData.AddIOState(thread,
1211					clusterStart - fModel->BaseTime(),
1212					IO_SCHEDULING_STATE_PENDING_OPERATION);
1213				fSchedulingData.AddIOState(thread,
1214					clusterEnd - fModel->BaseTime(),
1215					IO_SCHEDULING_STATE_PENDING_REQUEST);
1216
1217				clusterStart = operationStart;
1218				clusterEnd = operationEnd;
1219			} else
1220				clusterEnd = std::max(clusterEnd, operationEnd);
1221		}
1222
1223		// add the last cluster
1224		fSchedulingData.AddIOState(thread, clusterStart - fModel->BaseTime(),
1225			IO_SCHEDULING_STATE_PENDING_OPERATION);
1226		fSchedulingData.AddIOState(thread, clusterEnd - fModel->BaseTime(),
1227			IO_SCHEDULING_STATE_PENDING_REQUEST);
1228
1229		// after the end of the request cluster, state is idle again
1230		fSchedulingData.AddIOState(thread, endTime - fModel->BaseTime(),
1231			IO_SCHEDULING_STATE_IDLE);
1232	}
1233
1234	void _GetEventTimeRange(nanotime_t& _startTime, nanotime_t& _endTime)
1235	{
1236		if (fModel != NULL) {
1237			float scrollOffset = _ScrollOffset();
1238			_startTime = (nanotime_t)scrollOffset * fNSecsPerPixel;
1239			_endTime = (nanotime_t)(scrollOffset + Bounds().Width() + 1)
1240				* fNSecsPerPixel;
1241		} else {
1242			_startTime = 0;
1243			_endTime = 1;
1244		}
1245	}
1246
1247	void _GetThreadAndTimeAt(BPoint point, Model::Thread*& _thread,
1248		nanotime_t& _time)
1249	{
1250		_thread = fModel->ThreadAt(
1251			fFilterModel->SelectedItemAt(LineAt(point)));
1252		_time = (nanotime_t)point.x * fNSecsPerPixel;
1253	}
1254
1255	void _GetIORequests(Model::Thread* thread, nanotime_t time,
1256		BObjectList<Model::IORequest>& ioRequests)
1257	{
1258		// find the time in the event data
1259		const Array<IOSchedulingEvent>& events
1260			= fSchedulingData.IOEventsForThread(thread->Index());
1261		int32 eventCount = events.Size();
1262
1263		int32 lower = 0;
1264		int32 upper = eventCount - 1;
1265		while (lower < upper) {
1266			int32 mid = (lower + upper + 1) / 2;
1267			const IOSchedulingEvent& event = events[mid];
1268			if (event.time > time)
1269				upper = mid - 1;
1270			else
1271				lower = mid;
1272		}
1273
1274		if (lower < 0 || lower >= eventCount)
1275			return;
1276
1277		if (events[lower].state == IO_SCHEDULING_STATE_IDLE)
1278			return;
1279
1280		// find the beginning and end of the I/O request cluster
1281		while (lower > 0) {
1282			if (events[lower - 1].state == IO_SCHEDULING_STATE_IDLE)
1283				break;
1284			lower--;
1285		}
1286
1287		while (upper + 1 < eventCount) {
1288			upper++;
1289			if (events[upper].state == IO_SCHEDULING_STATE_IDLE)
1290				break;
1291		}
1292
1293		nanotime_t startTime = events[lower].time;
1294		nanotime_t endTime = events[upper].state == IO_SCHEDULING_STATE_IDLE
1295			? events[upper].time : fEndTime;
1296
1297		// convert to absolute time -- as used by the I/O requests
1298		startTime += fModel->BaseTime();
1299		endTime += fModel->BaseTime();
1300		time += fModel->BaseTime();
1301
1302		// collect the requests in the range
1303		Model::IORequest** requests = thread->IORequests();
1304		size_t requestCount = thread->CountIORequests();
1305		size_t index = thread->ClosestRequestStartIndex(startTime);
1306		for (; index < requestCount; index++) {
1307			Model::IORequest* request = requests[index];
1308
1309			if (request->ScheduledTime() >= endTime)
1310				break;
1311
1312			if (request->ScheduledTime() > time
1313				|| (request->finishedEvent != NULL
1314					&& request->finishedEvent->time <= time)) {
1315				continue;
1316			}
1317
1318			ioRequests.AddItem(request);
1319		}
1320
1321		if (ioRequests.CountItems() > 1)
1322			ioRequests.SortItems(Model::IORequest::CompareSchedulerTime);
1323	}
1324
1325	float _ScrollOffset() const
1326	{
1327		if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL))
1328			return scrollBar->Value();
1329		return 0;
1330	}
1331
1332	void _Zoom(float x, float steps)
1333	{
1334		if (steps == 0 || fModel == NULL)
1335			return;
1336
1337		// compute the domain point where to zoom in
1338		float scrollOffset = _ScrollOffset();
1339		double timeForX = (scrollOffset + x) * fNSecsPerPixel;
1340
1341		uint32 factor = 4;
1342		if (steps < 0) {
1343			steps = -steps;
1344			factor = 1;
1345		}
1346
1347		uint32 oldNsecPerPixel = fNSecsPerPixel;
1348		for (; steps > 0; steps--)
1349			fNSecsPerPixel = fNSecsPerPixel * factor / 2;
1350
1351		if (fNSecsPerPixel < 1)
1352			fNSecsPerPixel = 1;
1353		else if (fNSecsPerPixel > 1000000)
1354			fNSecsPerPixel = 1000000;
1355
1356		if (fNSecsPerPixel == oldNsecPerPixel)
1357			return;
1358
1359		Invalidate();
1360
1361		UpdateScrollBar();
1362		if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL))
1363			scrollBar->SetValue(timeForX / fNSecsPerPixel - x);
1364
1365		if (fListener != NULL) {
1366			fListener->DataWidthChanged();
1367			fListener->DataRangeChanged();
1368		}
1369	}
1370
1371	inline void _UpdateLastEventTime(nanotime_t time)
1372	{
1373		fState.SetLastEventTime(time - fModel->BaseTime());
1374	}
1375
1376	status_t _ProcessEvent(uint32 event, uint32 cpu, const void* buffer,
1377		size_t size)
1378	{
1379		switch (event) {
1380			case B_SYSTEM_PROFILER_THREAD_ADDED:
1381				_HandleThreadAdded((system_profiler_thread_added*)buffer);
1382				break;
1383
1384			case B_SYSTEM_PROFILER_THREAD_REMOVED:
1385				_HandleThreadRemoved((system_profiler_thread_removed*)buffer);
1386				break;
1387
1388			case B_SYSTEM_PROFILER_THREAD_SCHEDULED:
1389				_HandleThreadScheduled(
1390					(system_profiler_thread_scheduled*)buffer);
1391				break;
1392
1393			case B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE:
1394				_HandleThreadEnqueuedInRunQueue(
1395					(thread_enqueued_in_run_queue*)buffer);
1396				break;
1397
1398			case B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE:
1399				_HandleThreadRemovedFromRunQueue(
1400					(thread_removed_from_run_queue*)buffer);
1401				break;
1402
1403			default:
1404				break;
1405		}
1406
1407		return B_OK;
1408	}
1409
1410	void _HandleThreadAdded(system_profiler_thread_added* event)
1411	{
1412//printf("  thread added: %ld\n", event->thread);
1413		// do we know the thread already?
1414		Model::ThreadSchedulingState* info = fState.LookupThread(event->thread);
1415		if (info != NULL) {
1416			// TODO: ?
1417			return;
1418		}
1419
1420		Model::Thread* thread = fModel->ThreadByID(event->thread);
1421		if (thread == NULL)
1422			return;
1423
1424		// create and add a ThreadSchedulingState
1425		info = new(std::nothrow) Model::ThreadSchedulingState(thread);
1426		if (info == NULL)
1427			return;
1428
1429		fState.InsertThread(info);
1430	}
1431
1432	void _HandleThreadRemoved(system_profiler_thread_removed* event)
1433	{
1434//printf("  thread removed: %ld\n", event->thread);
1435//		Model::ThreadSchedulingState* thread = fState.LookupThread(
1436//			event->thread);
1437//		if (thread != NULL) {
1438//			fState.RemoveThread(thread);
1439//			delete thread;
1440//// TODO: The thread will be unscheduled in a moment and cause a warning! So
1441//// maybe keep it around in a separate hash table a bit longer?
1442//		}
1443	}
1444
1445	void _HandleThreadScheduled(system_profiler_thread_scheduled* event)
1446	{
1447		_UpdateLastEventTime(event->time);
1448
1449		Model::ThreadSchedulingState* thread = fState.LookupThread(
1450			event->thread);
1451		if (thread == NULL) {
1452			printf("Schedule event for unknown thread: %" B_PRId32 "\n",
1453				event->thread);
1454			return;
1455		}
1456
1457		thread->lastTime = fState.LastEventTime();
1458		thread->state = RUNNING;
1459		fSchedulingData.AddRun(thread->thread, fState.LastEventTime());
1460
1461		// unscheduled thread
1462
1463		if (event->thread == event->previous_thread)
1464			return;
1465
1466		thread = fState.LookupThread(event->previous_thread);
1467		if (thread == NULL) {
1468			printf("Schedule event for unknown previous thread: %" B_PRId32
1469				"\n", event->previous_thread);
1470			return;
1471		}
1472
1473		if (thread->state == STILL_RUNNING) {
1474			// thread preempted
1475			fSchedulingData.AddPreemption(thread->thread,
1476				fState.LastEventTime());
1477
1478			thread->lastTime = fState.LastEventTime();
1479			thread->state = PREEMPTED;
1480		} else if (thread->state == RUNNING) {
1481			// thread starts waiting (it hadn't been added to the run
1482			// queue before being unscheduled)
1483			if (event->previous_thread_state == B_THREAD_WAITING) {
1484				addr_t waitObject = event->previous_thread_wait_object;
1485				switch (event->previous_thread_wait_object_type) {
1486					case THREAD_BLOCK_TYPE_SNOOZE:
1487					case THREAD_BLOCK_TYPE_SIGNAL:
1488						waitObject = 0;
1489						break;
1490					case THREAD_BLOCK_TYPE_SEMAPHORE:
1491					case THREAD_BLOCK_TYPE_CONDITION_VARIABLE:
1492					case THREAD_BLOCK_TYPE_MUTEX:
1493					case THREAD_BLOCK_TYPE_RW_LOCK:
1494					case THREAD_BLOCK_TYPE_OTHER:
1495					case THREAD_BLOCK_TYPE_OTHER_OBJECT:
1496					default:
1497						break;
1498				}
1499
1500				fSchedulingData.AddWait(thread->thread, fState.LastEventTime(),
1501					fModel->ThreadWaitObjectGroupFor(thread->ID(),
1502						event->previous_thread_wait_object_type, waitObject));
1503			} else {
1504				fSchedulingData.AddUnspecifiedWait(thread->thread,
1505					fState.LastEventTime());
1506			}
1507
1508			thread->lastTime = fState.LastEventTime();
1509			thread->state = WAITING;
1510		} else if (thread->state == UNKNOWN) {
1511			uint32 threadState = event->previous_thread_state;
1512			if (threadState == B_THREAD_WAITING
1513				|| threadState == B_THREAD_SUSPENDED) {
1514				fSchedulingData.AddWait(thread->thread, fState.LastEventTime(),
1515					NULL);
1516				thread->lastTime = fState.LastEventTime();
1517				thread->state = WAITING;
1518			} else if (threadState == B_THREAD_READY) {
1519				thread->lastTime = fState.LastEventTime();
1520				thread->state = PREEMPTED;
1521				fSchedulingData.AddPreemption(thread->thread,
1522					fState.LastEventTime());
1523			}
1524		}
1525	}
1526
1527	void _HandleThreadEnqueuedInRunQueue(thread_enqueued_in_run_queue* event)
1528	{
1529		_UpdateLastEventTime(event->time);
1530
1531		Model::ThreadSchedulingState* thread = fState.LookupThread(
1532			event->thread);
1533		if (thread == NULL) {
1534			printf("Enqueued in run queue event for unknown thread: %" B_PRId32
1535				"\n", event->thread);
1536			return;
1537		}
1538
1539		if (thread->state == RUNNING || thread->state == STILL_RUNNING) {
1540			// Thread was running and is reentered into the run queue. This
1541			// is done by the scheduler, if the thread remains ready.
1542			thread->state = STILL_RUNNING;
1543		} else {
1544			// Thread was waiting and is ready now.
1545			nanotime_t diffTime = fState.LastEventTime() - thread->lastTime;
1546			if (thread->waitObject != NULL) {
1547				thread->waitObject->AddWait(diffTime);
1548				thread->waitObject = NULL;
1549			}
1550
1551			fSchedulingData.AddLatency(thread->thread, fState.LastEventTime());
1552			thread->lastTime = fState.LastEventTime();
1553			thread->state = READY;
1554		}
1555	}
1556
1557	void _HandleThreadRemovedFromRunQueue(thread_removed_from_run_queue* event)
1558	{
1559		_UpdateLastEventTime(event->time);
1560
1561		Model::ThreadSchedulingState* thread = fState.LookupThread(
1562			event->thread);
1563		if (thread == NULL) {
1564			printf("Removed from run queue event for unknown thread: %" B_PRId32
1565				"\n", event->thread);
1566			return;
1567		}
1568
1569		// This really only happens when the thread priority is changed
1570		// while the thread is ready.
1571		fSchedulingData.AddUnspecifiedWait(thread->thread,
1572			fState.LastEventTime());
1573
1574		thread->lastTime = fState.LastEventTime();
1575		thread->state = WAITING;
1576	}
1577
1578private:
1579	Model::SchedulingState	fState;
1580	SchedulingData			fSchedulingData;
1581	nanotime_t				fStartTime;
1582	nanotime_t				fEndTime;
1583	uint32					fNSecsPerPixel;
1584	BPoint					fLastMousePos;
1585	Listener*				fListener;
1586
1587	rgb_color				fActivityColors[ACTIVITY_COLOR_COUNT];
1588	rgb_color				fActivitySelectedColors[ACTIVITY_COLOR_COUNT];
1589};
1590
1591
1592class MainWindow::SchedulingPage::ViewPort : public BaseView,
1593	private HeaderListener, private SchedulingView::Listener {
1594public:
1595	ViewPort(HeaderView* headerView, ThreadsView* threadsView,
1596		SchedulingView* schedulingView, FontInfo& fontInfo,
1597		ListSelectionModel* filterModel)
1598		:
1599		BaseView("viewport", fontInfo, filterModel),
1600		fHeaderView(headerView),
1601		fThreadsView(threadsView),
1602		fSchedulingView(schedulingView)
1603	{
1604		AddChild(threadsView);
1605		AddChild(schedulingView);
1606
1607		fSchedulingView->SetListener(this);
1608
1609		HeaderModel* headerModel = fHeaderView->Model();
1610
1611		Header* header = new Header((int32)fThreadsView->PreferredSize().width,
1612			(int32)fThreadsView->MinSize().width,
1613			(int32)fThreadsView->MaxSize().width,
1614			(int32)fThreadsView->PreferredSize().width, 0);
1615		header->SetValue("Thread");
1616		headerModel->AddHeader(header);
1617		header->AddListener(this);
1618
1619		header = new Header(100, 100, 10000, 200, 1);
1620			// TODO: Set header width correctly!
1621		header->SetValue("Activity");
1622		header->SetHeaderRenderer(new TimelineHeaderRenderer);
1623		headerModel->AddHeader(header);
1624//		header->AddListener(this);
1625	}
1626
1627	~ViewPort()
1628	{
1629	}
1630
1631	void RemoveListeners()
1632	{
1633		fHeaderView->Model()->HeaderAt(0)->RemoveListener(this);
1634	}
1635
1636	void UpdateScrollBars()
1637	{
1638		float height = Frame().Height();
1639		float dataHeight = std::max(height, fSchedulingView->MinSize().height);
1640
1641		fSchedulingView->UpdateScrollBar();
1642
1643		if (BScrollBar* scrollBar = ScrollBar(B_VERTICAL)) {
1644			float range = dataHeight - height;
1645			if (range > 0) {
1646				scrollBar->SetRange(0, range);
1647				scrollBar->SetProportion(
1648					(height + 1) / (dataHeight + 1));
1649				scrollBar->SetSteps(fFontInfo.lineHeight, height + 1);
1650			} else {
1651				scrollBar->SetRange(0, 0);
1652				scrollBar->SetProportion(1);
1653			}
1654		}
1655	}
1656
1657	virtual BSize MinSize()
1658	{
1659		return BSize(10, 10);
1660	}
1661
1662	virtual BSize MaxSize()
1663	{
1664		return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
1665	}
1666
1667	BSize PreferredSize()
1668	{
1669		BSize threadsViewSize(fThreadsView->PreferredSize());
1670		BSize schedulingViewSize(fSchedulingView->PreferredSize());
1671		return BSize(BLayoutUtils::AddDistances(
1672				threadsViewSize.width + kViewSeparationMargin,
1673				schedulingViewSize.width),
1674			std::max(threadsViewSize.height, schedulingViewSize.height));
1675	}
1676
1677	void DoLayout()
1678	{
1679		float width = Bounds().Width();
1680		float height = fThreadsView->MinSize().Height();
1681
1682		float threadsViewWidth = 0;
1683		if (fHeaderView->Model()->HeaderAt(0) != NULL)
1684			threadsViewWidth = fHeaderView->HeaderFrame(0).Width();
1685		threadsViewWidth = std::max(threadsViewWidth,
1686			fThreadsView->MinSize().width);
1687		threadsViewWidth = std::min(threadsViewWidth,
1688			fThreadsView->MaxSize().width);
1689
1690		float schedulingViewLeft = threadsViewWidth + 1 + kViewSeparationMargin;
1691		float schedulingViewWidth = width - schedulingViewLeft;
1692
1693		fThreadsView->MoveTo(0, 0);
1694		fThreadsView->ResizeTo(threadsViewWidth, height);
1695
1696		fSchedulingView->MoveTo(schedulingViewLeft, 0);
1697		fSchedulingView->ResizeTo(schedulingViewWidth, height);
1698
1699		if (Header* header = fHeaderView->Model()->HeaderAt(1)) {
1700			float headerWidth = schedulingViewWidth + 1 + kViewSeparationMargin;
1701			header->SetMinWidth(headerWidth);
1702			header->SetMaxWidth(headerWidth);
1703			header->SetPreferredWidth(headerWidth);
1704			header->SetWidth(headerWidth);
1705		}
1706
1707		UpdateScrollBars();
1708	}
1709
1710private:
1711	virtual void HeaderWidthChanged(Header* header)
1712	{
1713		if (header->ModelIndex() == 0)
1714			InvalidateLayout();
1715	}
1716
1717	virtual void DataWidthChanged()
1718	{
1719	}
1720
1721	virtual void DataRangeChanged()
1722	{
1723		Header* header = fHeaderView->Model()->HeaderAt(1);
1724		if (header == NULL)
1725			return;
1726
1727		nanotime_t startTime;
1728		nanotime_t endTime;
1729		fSchedulingView->GetDataRange(startTime, endTime);
1730		TimeRange* range = new(std::nothrow) TimeRange(startTime, endTime);
1731		if (range != NULL) {
1732			header->SetValue(BVariant(range, 'time'));
1733			range->ReleaseReference();
1734		}
1735	}
1736
1737private:
1738	HeaderView*		fHeaderView;
1739	ThreadsView*	fThreadsView;
1740	SchedulingView*	fSchedulingView;
1741};
1742
1743
1744MainWindow::SchedulingPage::SchedulingPage(MainWindow* parent)
1745	:
1746	BGroupView(B_VERTICAL),
1747	fParent(parent),
1748	fModel(NULL),
1749	fScrollView(NULL),
1750	fViewPort(NULL),
1751	fThreadsView(NULL),
1752	fSchedulingView(NULL),
1753	fFilterModel(),
1754	fSelectionModel()
1755{
1756	SetName("Scheduling");
1757
1758	be_plain_font->GetHeight(&fFontInfo.fontHeight);
1759	fFontInfo.lineHeight = ceilf(fFontInfo.fontHeight.ascent)
1760		+ ceilf(fFontInfo.fontHeight.descent);
1761
1762	HeaderView* headerView = new HeaderView;
1763	BView* scrollChild = BLayoutBuilder::Group<>(B_VERTICAL)
1764		.Add(headerView)
1765		.Add(fViewPort = new ViewPort(headerView,
1766			fThreadsView = new ThreadsView(fFontInfo, &fFilterModel,
1767				&fSelectionModel),
1768			fSchedulingView = new SchedulingView(fFontInfo, &fFilterModel,
1769				&fSelectionModel),
1770			fFontInfo, &fFilterModel)).View();
1771	;
1772
1773	AddChild(fScrollView = new BScrollView("scroll", scrollChild, 0, true,
1774		true));
1775
1776	fScrollView->ScrollBar(B_HORIZONTAL)->SetTarget(fSchedulingView);
1777	fScrollView->ScrollBar(B_VERTICAL)->SetTarget(fViewPort);
1778	fViewPort->UpdateScrollBars();
1779}
1780
1781
1782MainWindow::SchedulingPage::~SchedulingPage()
1783{
1784	fViewPort->RemoveListeners();
1785	fThreadsView->RemoveListeners();
1786	fSchedulingView->RemoveListeners();
1787}
1788
1789
1790void
1791MainWindow::SchedulingPage::SetModel(Model* model)
1792{
1793	if (model == fModel)
1794		return;
1795
1796	fSelectionModel.Clear();
1797	fFilterModel.Clear();
1798
1799	if (fModel != NULL) {
1800	}
1801
1802	fModel = model;
1803
1804	if (fModel != NULL) {
1805		fFilterModel.SelectItems(0, fModel->CountThreads(), false);
1806	}
1807
1808	fViewPort->SetModel(fModel);
1809	fThreadsView->SetModel(fModel);
1810	fSchedulingView->SetModel(fModel);
1811}
1812