xref: /haiku/src/apps/debuganalyzer/gui/main_window/SchedulingPage.cpp (revision eea5774f46bba925156498abf9cb1a1165647bf7)
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 
36 static const float kThreadNameMargin = 3.0f;
37 static const float kViewSeparationMargin = 1.0f;
38 
39 
40 enum {
41 	MSG_SCHEDULING_FILTER_HIDE_SELECTED		= 'schs',
42 	MSG_SCHEDULING_FILTER_HIDE_UNSELECTED	= 'schu',
43 	MSG_SCHEDULING_FILTER_SHOW_ALL			= 'scsa'
44 };
45 
46 
47 enum {
48 	IO_SCHEDULING_STATE_IDLE,
49 	IO_SCHEDULING_STATE_PENDING_REQUEST,
50 	IO_SCHEDULING_STATE_PENDING_OPERATION
51 };
52 
53 
54 struct 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 
70 struct 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 
83 class MainWindow::SchedulingPage::SchedulingData {
84 public:
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 
200 private:
201 	typedef Array<SchedulingEvent> DataArray;
202 	typedef Array<IOSchedulingEvent> IODataArray;
203 
204 private:
205 	Model*			fModel;
206 	DataArray*		fDataArrays;
207 	IODataArray*	fIODataArrays;
208 	bool			fRecordingEnabled;
209 };
210 
211 
212 struct 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 
225 class MainWindow::SchedulingPage::TimelineHeaderRenderer
226 	: public HeaderRenderer {
227 public:
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 
256 private:
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 
274 private:
275 	LegendChartAxis		fAxis;
276 	ChartDataRange		fRange;
277 };
278 
279 
280 class MainWindow::SchedulingPage::BaseView : public BView {
281 public:
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 
299 protected:
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 
331 protected:
332 	Model*				fModel;
333 	ListSelectionModel*	fFilterModel;
334 	FontInfo&			fFontInfo;
335 };
336 
337 
338 class MainWindow::SchedulingPage::LineBaseView : public BaseView,
339 	protected ListSelectionModel::Listener {
340 public:
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 
535 protected:
536 	ListSelectionModel*		fSelectionModel;
537 	rgb_color				fTextColor;
538 	rgb_color				fSelectedTextColor;
539 	rgb_color				fBackgroundColor;
540 	rgb_color				fSelectedBackgroundColor;
541 };
542 
543 
544 class MainWindow::SchedulingPage::ThreadsView : public LineBaseView {
545 public:
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 
619 class MainWindow::SchedulingPage::SchedulingView : public LineBaseView {
620 public:
621 	struct Listener {
622 		virtual ~Listener()
623 		{
624 		}
625 
626 		virtual void DataWidthChanged() = 0;
627 		virtual void DataRangeChanged() = 0;
628 	};
629 
630 private:
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 
640 public:
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 
1015 private:
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 
1022 private:
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 {
1113 printf("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 
1578 private:
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 
1592 class MainWindow::SchedulingPage::ViewPort : public BaseView,
1593 	private HeaderListener, private SchedulingView::Listener {
1594 public:
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 
1710 private:
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 
1737 private:
1738 	HeaderView*		fHeaderView;
1739 	ThreadsView*	fThreadsView;
1740 	SchedulingView*	fSchedulingView;
1741 };
1742 
1743 
1744 MainWindow::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 	scrollChild->SetFlags(scrollChild->Flags() | B_SCROLL_VIEW_AWARE);
1773 
1774 	AddChild(fScrollView = new BScrollView("scroll", scrollChild, 0, true,
1775 		true));
1776 
1777 	fScrollView->ScrollBar(B_HORIZONTAL)->SetTarget(fSchedulingView);
1778 	fScrollView->ScrollBar(B_VERTICAL)->SetTarget(fViewPort);
1779 	fViewPort->UpdateScrollBars();
1780 }
1781 
1782 
1783 MainWindow::SchedulingPage::~SchedulingPage()
1784 {
1785 	fViewPort->RemoveListeners();
1786 	fThreadsView->RemoveListeners();
1787 	fSchedulingView->RemoveListeners();
1788 }
1789 
1790 
1791 void
1792 MainWindow::SchedulingPage::SetModel(Model* model)
1793 {
1794 	if (model == fModel)
1795 		return;
1796 
1797 	fSelectionModel.Clear();
1798 	fFilterModel.Clear();
1799 
1800 	if (fModel != NULL) {
1801 	}
1802 
1803 	fModel = model;
1804 
1805 	if (fModel != NULL) {
1806 		fFilterModel.SelectItems(0, fModel->CountThreads(), false);
1807 	}
1808 
1809 	fViewPort->SetModel(fModel);
1810 	fThreadsView->SetModel(fModel);
1811 	fSchedulingView->SetModel(fModel);
1812 }
1813